@@ -279,7 +279,14 @@ var htmx = (function() {
279279 * @type boolean
280280 * @default true
281281 */
282- historyRestoreAsHxRequest : true
282+ historyRestoreAsHxRequest : true ,
283+ /**
284+ * Weather to report input validation errors to the end user and update focus to the first input that fails validation.
285+ * This should always be enabled as this matches default browser form submit behaviour
286+ * @type boolean
287+ * @default false
288+ */
289+ reportValidityOfForms : false
283290 } ,
284291 /** @type {typeof parseInterval } */
285292 parseInterval : null ,
@@ -290,7 +297,7 @@ var htmx = (function() {
290297 location,
291298 /** @type {typeof internalEval } */
292299 _ : null ,
293- version : '2.0.6 '
300+ version : '2.0.7 '
294301 }
295302 // Tsc madness part 2
296303 htmx . onLoad = onLoadHelper
@@ -1414,7 +1421,7 @@ var htmx = (function() {
14141421 * @param {Element } mergeFrom
14151422 */
14161423 function cloneAttributes ( mergeTo , mergeFrom ) {
1417- forEach ( mergeTo . attributes , function ( attr ) {
1424+ forEach ( Array . from ( mergeTo . attributes ) , function ( attr ) {
14181425 if ( ! mergeFrom . hasAttribute ( attr . name ) && shouldSettleAttribute ( attr . name ) ) {
14191426 mergeTo . removeAttribute ( attr . name )
14201427 }
@@ -2425,21 +2432,22 @@ var htmx = (function() {
24252432 * @returns {boolean }
24262433 */
24272434 function shouldCancel ( evt , elt ) {
2428- if ( evt . type === 'submit' || evt . type === 'click' ) {
2429- // use elt from event that was submitted/clicked where possible to determining if default form/link behavior should be canceled
2430- elt = asElement ( evt . target ) || elt
2431- if ( elt . tagName === 'FORM' ) {
2432- return true
2433- }
2434- // @ts -ignore Do not cancel on buttons that 1) don't have a related form or 2) have a type attribute of 'reset'/'button'.
2435- // The properties will resolve to undefined for elements that don't define 'type' or 'form', which is fine
2436- if ( elt . form && elt . type === 'submit' ) {
2435+ if ( evt . type === 'submit' && elt . tagName === 'FORM' ) {
2436+ return true
2437+ } else if ( evt . type === 'click' ) {
2438+ // find button wrapping the trigger element
2439+ const btn = /** @type {HTMLButtonElement|HTMLInputElement|null } */ ( elt . closest ( 'input[type="submit"], button' ) )
2440+ // Do not cancel on buttons that 1) don't have a related form or 2) have a type attribute of 'reset'/'button'.
2441+ if ( btn && btn . form && btn . type === 'submit' ) {
24372442 return true
24382443 }
2439- elt = elt . closest ( 'a' )
2440- // @ts -ignore check for a link wrapping the event elt or if elt is a link. elt will be link so href check is fine
2441- if ( elt && elt . href &&
2442- ( elt . getAttribute ( 'href' ) === '#' || elt . getAttribute ( 'href' ) . indexOf ( '#' ) !== 0 ) ) {
2444+
2445+ // find link wrapping the trigger element
2446+ const link = elt . closest ( 'a' )
2447+ // Allow links with href="#fragment" (anchors with content after #) to perform normal fragment navigation.
2448+ // Cancel default action for links with href="#" (bare hash) to prevent scrolling to top and unwanted URL changes.
2449+ const samePageAnchor = / ^ # .+ /
2450+ if ( link && link . href && ! samePageAnchor . test ( link . getAttribute ( 'href' ) ) ) {
24432451 return true
24442452 }
24452453 }
@@ -2516,7 +2524,7 @@ var htmx = (function() {
25162524 if ( ignoreBoostedAnchorCtrlClick ( elt , evt ) ) {
25172525 return
25182526 }
2519- if ( explicitCancel || shouldCancel ( evt , elt ) ) {
2527+ if ( explicitCancel || shouldCancel ( evt , eltToListenOn ) ) {
25202528 evt . preventDefault ( )
25212529 }
25222530 if ( maybeFilterEvent ( triggerSpec , elt , evt ) ) {
@@ -2856,6 +2864,9 @@ var htmx = (function() {
28562864 return
28572865 }
28582866 const form = getRelatedForm ( elt )
2867+ if ( ! form ) {
2868+ return
2869+ }
28592870 return getInternalData ( form )
28602871 }
28612872
@@ -3547,8 +3558,17 @@ var htmx = (function() {
35473558 if ( element . willValidate ) {
35483559 triggerEvent ( element , 'htmx:validation:validate' )
35493560 if ( ! element . checkValidity ( ) ) {
3561+ if (
3562+ triggerEvent ( element , 'htmx:validation:failed' , {
3563+ message : element . validationMessage ,
3564+ validity : element . validity
3565+ } ) &&
3566+ ! errors . length &&
3567+ htmx . config . reportValidityOfForms
3568+ ) {
3569+ element . reportValidity ( )
3570+ }
35503571 errors . push ( { elt : element , message : element . validationMessage , validity : element . validity } )
3551- triggerEvent ( element , 'htmx:validation:failed' , { message : element . validationMessage , validity : element . validity } )
35523572 }
35533573 }
35543574 }
@@ -5061,12 +5081,14 @@ var htmx = (function() {
50615081 function insertIndicatorStyles ( ) {
50625082 if ( htmx . config . includeIndicatorStyles !== false ) {
50635083 const nonceAttribute = htmx . config . inlineStyleNonce ? ` nonce="${ htmx . config . inlineStyleNonce } "` : ''
5084+ const indicator = htmx . config . indicatorClass
5085+ const request = htmx . config . requestClass
50645086 getDocument ( ) . head . insertAdjacentHTML ( 'beforeend' ,
5065- ' <style' + nonceAttribute + '>\
5066- .' + htmx . config . indicatorClass + '{ opacity:0}\
5067- .' + htmx . config . requestClass + ' .' + htmx . config . indicatorClass + '{ opacity:1; transition: opacity 200ms ease-in;}\
5068- .' + htmx . config . requestClass + '.' + htmx . config . indicatorClass + '{opacity:1; transition: opacity 200ms ease-in;}\
5069- </style>' )
5087+ ` <style${ nonceAttribute } >` +
5088+ `. ${ indicator } { opacity:0;visibility: hidden} ` +
5089+ `. ${ request } . ${ indicator } , . ${ request } . ${ indicator } { opacity:1;visibility: visible; transition: opacity 200ms ease-in}` +
5090+ '</style>'
5091+ )
50705092 }
50715093 }
50725094
0 commit comments