UNPKG

162 kBJavaScriptView Raw
1// UMD insanity
2// This code sets up support for (in order) AMD, ES6 modules, and globals.
3(function (root, factory) {
4 //@ts-ignore
5 if (typeof define === 'function' && define.amd) {
6 // AMD. Register as an anonymous module.
7 //@ts-ignore
8 define([], factory);
9 } else if (typeof module === 'object' && module.exports) {
10 // Node. Does not work with strict CommonJS, but
11 // only CommonJS-like environments that support module.exports,
12 // like Node.
13 module.exports = factory();
14 } else {
15 // Browser globals
16 root.htmx = root.htmx || factory();
17 }
18}(typeof self !== 'undefined' ? self : this, function () {
19return (function () {
20 'use strict';
21
22 // Public API
23 //** @type {import("./htmx").HtmxApi} */
24 // TODO: list all methods in public API
25 var htmx = {
26 onLoad: onLoadHelper,
27 process: processNode,
28 on: addEventListenerImpl,
29 off: removeEventListenerImpl,
30 trigger : triggerEvent,
31 ajax : ajaxHelper,
32 find : find,
33 findAll : findAll,
34 closest : closest,
35 values : function(elt, type){
36 var inputValues = getInputValues(elt, type || "post");
37 return inputValues.values;
38 },
39 remove : removeElement,
40 addClass : addClassToElement,
41 removeClass : removeClassFromElement,
42 toggleClass : toggleClassOnElement,
43 takeClass : takeClassForElement,
44 defineExtension : defineExtension,
45 removeExtension : removeExtension,
46 logAll : logAll,
47 logNone : logNone,
48 logger : null,
49 config : {
50 historyEnabled:true,
51 historyCacheSize:10,
52 refreshOnHistoryMiss:false,
53 defaultSwapStyle:'innerHTML',
54 defaultSwapDelay:0,
55 defaultSettleDelay:20,
56 includeIndicatorStyles:true,
57 indicatorClass:'htmx-indicator',
58 requestClass:'htmx-request',
59 addedClass:'htmx-added',
60 settlingClass:'htmx-settling',
61 swappingClass:'htmx-swapping',
62 allowEval:true,
63 allowScriptTags:true,
64 inlineScriptNonce:'',
65 attributesToSettle:["class", "style", "width", "height"],
66 withCredentials:false,
67 timeout:0,
68 wsReconnectDelay: 'full-jitter',
69 wsBinaryType: 'blob',
70 disableSelector: "[hx-disable], [data-hx-disable]",
71 useTemplateFragments: false,
72 scrollBehavior: 'smooth',
73 defaultFocusScroll: false,
74 getCacheBusterParam: false,
75 globalViewTransitions: false,
76 methodsThatUseUrlParams: ["get"],
77 selfRequestsOnly: false,
78 ignoreTitle: false,
79 scrollIntoViewOnBoost: true,
80 triggerSpecsCache: null,
81 },
82 parseInterval:parseInterval,
83 _:internalEval,
84 createEventSource: function(url){
85 return new EventSource(url, {withCredentials:true})
86 },
87 createWebSocket: function(url){
88 var sock = new WebSocket(url, []);
89 sock.binaryType = htmx.config.wsBinaryType;
90 return sock;
91 },
92 version: "1.9.11"
93 };
94
95 /** @type {import("./htmx").HtmxInternalApi} */
96 var internalAPI = {
97 addTriggerHandler: addTriggerHandler,
98 bodyContains: bodyContains,
99 canAccessLocalStorage: canAccessLocalStorage,
100 findThisElement: findThisElement,
101 filterValues: filterValues,
102 hasAttribute: hasAttribute,
103 getAttributeValue: getAttributeValue,
104 getClosestAttributeValue: getClosestAttributeValue,
105 getClosestMatch: getClosestMatch,
106 getExpressionVars: getExpressionVars,
107 getHeaders: getHeaders,
108 getInputValues: getInputValues,
109 getInternalData: getInternalData,
110 getSwapSpecification: getSwapSpecification,
111 getTriggerSpecs: getTriggerSpecs,
112 getTarget: getTarget,
113 makeFragment: makeFragment,
114 mergeObjects: mergeObjects,
115 makeSettleInfo: makeSettleInfo,
116 oobSwap: oobSwap,
117 querySelectorExt: querySelectorExt,
118 selectAndSwap: selectAndSwap,
119 settleImmediately: settleImmediately,
120 shouldCancel: shouldCancel,
121 triggerEvent: triggerEvent,
122 triggerErrorEvent: triggerErrorEvent,
123 withExtensions: withExtensions,
124 }
125
126 var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
127 var VERB_SELECTOR = VERBS.map(function(verb){
128 return "[hx-" + verb + "], [data-hx-" + verb + "]"
129 }).join(", ");
130
131 var HEAD_TAG_REGEX = makeTagRegEx('head'),
132 TITLE_TAG_REGEX = makeTagRegEx('title'),
133 SVG_TAGS_REGEX = makeTagRegEx('svg', true);
134
135 //====================================================================
136 // Utilities
137 //====================================================================
138
139 /**
140 * @param {string} tag
141 * @param {boolean} global
142 * @returns {RegExp}
143 */
144 function makeTagRegEx(tag, global = false) {
145 return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
146 global ? 'gim' : 'im');
147 }
148
149 function parseInterval(str) {
150 if (str == undefined) {
151 return undefined;
152 }
153
154 let interval = NaN;
155 if (str.slice(-2) == "ms") {
156 interval = parseFloat(str.slice(0, -2));
157 } else if (str.slice(-1) == "s") {
158 interval = parseFloat(str.slice(0, -1)) * 1000;
159 } else if (str.slice(-1) == "m") {
160 interval = parseFloat(str.slice(0, -1)) * 1000 * 60;
161 } else {
162 interval = parseFloat(str);
163 }
164 return isNaN(interval) ? undefined : interval;
165 }
166
167 /**
168 * @param {HTMLElement} elt
169 * @param {string} name
170 * @returns {(string | null)}
171 */
172 function getRawAttribute(elt, name) {
173 return elt.getAttribute && elt.getAttribute(name);
174 }
175
176 // resolve with both hx and data-hx prefixes
177 function hasAttribute(elt, qualifiedName) {
178 return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
179 elt.hasAttribute("data-" + qualifiedName));
180 }
181
182 /**
183 *
184 * @param {HTMLElement} elt
185 * @param {string} qualifiedName
186 * @returns {(string | null)}
187 */
188 function getAttributeValue(elt, qualifiedName) {
189 return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName);
190 }
191
192 /**
193 * @param {HTMLElement} elt
194 * @returns {HTMLElement | null}
195 */
196 function parentElt(elt) {
197 return elt.parentElement;
198 }
199
200 /**
201 * @returns {Document}
202 */
203 function getDocument() {
204 return document;
205 }
206
207 /**
208 * @param {HTMLElement} elt
209 * @param {(e:HTMLElement) => boolean} condition
210 * @returns {HTMLElement | null}
211 */
212 function getClosestMatch(elt, condition) {
213 while (elt && !condition(elt)) {
214 elt = parentElt(elt);
215 }
216
217 return elt ? elt : null;
218 }
219
220 function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName){
221 var attributeValue = getAttributeValue(ancestor, attributeName);
222 var disinherit = getAttributeValue(ancestor, "hx-disinherit");
223 if (initialElement !== ancestor && disinherit && (disinherit === "*" || disinherit.split(" ").indexOf(attributeName) >= 0)) {
224 return "unset";
225 } else {
226 return attributeValue
227 }
228 }
229
230 /**
231 * @param {HTMLElement} elt
232 * @param {string} attributeName
233 * @returns {string | null}
234 */
235 function getClosestAttributeValue(elt, attributeName) {
236 var closestAttr = null;
237 getClosestMatch(elt, function (e) {
238 return closestAttr = getAttributeValueWithDisinheritance(elt, e, attributeName);
239 });
240 if (closestAttr !== "unset") {
241 return closestAttr;
242 }
243 }
244
245 /**
246 * @param {HTMLElement} elt
247 * @param {string} selector
248 * @returns {boolean}
249 */
250 function matches(elt, selector) {
251 // @ts-ignore: non-standard properties for browser compatibility
252 // noinspection JSUnresolvedVariable
253 var matchesFunction = elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector;
254 return matchesFunction && matchesFunction.call(elt, selector);
255 }
256
257 /**
258 * @param {string} str
259 * @returns {string}
260 */
261 function getStartTag(str) {
262 var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
263 var match = tagMatcher.exec( str );
264 if (match) {
265 return match[1].toLowerCase();
266 } else {
267 return "";
268 }
269 }
270
271 /**
272 *
273 * @param {string} resp
274 * @param {number} depth
275 * @returns {Element}
276 */
277 function parseHTML(resp, depth) {
278 var parser = new DOMParser();
279 var responseDoc = parser.parseFromString(resp, "text/html");
280
281 /** @type {Element} */
282 var responseNode = responseDoc.body;
283 while (depth > 0) {
284 depth--;
285 // @ts-ignore
286 responseNode = responseNode.firstChild;
287 }
288 if (responseNode == null) {
289 // @ts-ignore
290 responseNode = getDocument().createDocumentFragment();
291 }
292 return responseNode;
293 }
294
295 function aFullPageResponse(resp) {
296 return /<body/.test(resp)
297 }
298
299 /**
300 *
301 * @param {string} response
302 * @returns {Element}
303 */
304 function makeFragment(response) {
305 var partialResponse = !aFullPageResponse(response);
306 var startTag = getStartTag(response);
307 var content = response;
308 if (startTag === 'head') {
309 content = content.replace(HEAD_TAG_REGEX, '');
310 }
311 if (htmx.config.useTemplateFragments && partialResponse) {
312 var fragment = parseHTML("<body><template>" + content + "</template></body>", 0);
313 // @ts-ignore type mismatch between DocumentFragment and Element.
314 // TODO: Are these close enough for htmx to use interchangeably?
315 var fragmentContent = fragment.querySelector('template').content;
316 if (htmx.config.allowScriptTags) {
317 // if there is a nonce set up, set it on the new script tags
318 forEach(fragmentContent.querySelectorAll("script"), function (script) {
319 if (htmx.config.inlineScriptNonce) {
320 script.nonce = htmx.config.inlineScriptNonce;
321 }
322 // mark as executed due to template insertion semantics on all browsers except firefox fml
323 script.htmxExecuted = navigator.userAgent.indexOf("Firefox") === -1;
324 })
325 } else {
326 forEach(fragmentContent.querySelectorAll("script"), function (script) {
327 // remove all script tags if scripts are disabled
328 removeElement(script);
329 })
330 }
331 return fragmentContent;
332 }
333 switch (startTag) {
334 case "thead":
335 case "tbody":
336 case "tfoot":
337 case "colgroup":
338 case "caption":
339 return parseHTML("<table>" + content + "</table>", 1);
340 case "col":
341 return parseHTML("<table><colgroup>" + content + "</colgroup></table>", 2);
342 case "tr":
343 return parseHTML("<table><tbody>" + content + "</tbody></table>", 2);
344 case "td":
345 case "th":
346 return parseHTML("<table><tbody><tr>" + content + "</tr></tbody></table>", 3);
347 case "script":
348 case "style":
349 return parseHTML("<div>" + content + "</div>", 1);
350 default:
351 return parseHTML(content, 0);
352 }
353 }
354
355 /**
356 * @param {Function} func
357 */
358 function maybeCall(func){
359 if(func) {
360 func();
361 }
362 }
363
364 /**
365 * @param {any} o
366 * @param {string} type
367 * @returns
368 */
369 function isType(o, type) {
370 return Object.prototype.toString.call(o) === "[object " + type + "]";
371 }
372
373 /**
374 * @param {*} o
375 * @returns {o is Function}
376 */
377 function isFunction(o) {
378 return isType(o, "Function");
379 }
380
381 /**
382 * @param {*} o
383 * @returns {o is Object}
384 */
385 function isRawObject(o) {
386 return isType(o, "Object");
387 }
388
389 /**
390 * getInternalData retrieves "private" data stored by htmx within an element
391 * @param {HTMLElement} elt
392 * @returns {*}
393 */
394 function getInternalData(elt) {
395 var dataProp = 'htmx-internal-data';
396 var data = elt[dataProp];
397 if (!data) {
398 data = elt[dataProp] = {};
399 }
400 return data;
401 }
402
403 /**
404 * toArray converts an ArrayLike object into a real array.
405 * @param {ArrayLike} arr
406 * @returns {any[]}
407 */
408 function toArray(arr) {
409 var returnArr = [];
410 if (arr) {
411 for (var i = 0; i < arr.length; i++) {
412 returnArr.push(arr[i]);
413 }
414 }
415 return returnArr
416 }
417
418 function forEach(arr, func) {
419 if (arr) {
420 for (var i = 0; i < arr.length; i++) {
421 func(arr[i]);
422 }
423 }
424 }
425
426 function isScrolledIntoView(el) {
427 var rect = el.getBoundingClientRect();
428 var elemTop = rect.top;
429 var elemBottom = rect.bottom;
430 return elemTop < window.innerHeight && elemBottom >= 0;
431 }
432
433 function bodyContains(elt) {
434 // IE Fix
435 if (elt.getRootNode && elt.getRootNode() instanceof window.ShadowRoot) {
436 return getDocument().body.contains(elt.getRootNode().host);
437 } else {
438 return getDocument().body.contains(elt);
439 }
440 }
441
442 function splitOnWhitespace(trigger) {
443 return trigger.trim().split(/\s+/);
444 }
445
446 /**
447 * mergeObjects takes all of the keys from
448 * obj2 and duplicates them into obj1
449 * @param {Object} obj1
450 * @param {Object} obj2
451 * @returns {Object}
452 */
453 function mergeObjects(obj1, obj2) {
454 for (var key in obj2) {
455 if (obj2.hasOwnProperty(key)) {
456 obj1[key] = obj2[key];
457 }
458 }
459 return obj1;
460 }
461
462 function parseJSON(jString) {
463 try {
464 return JSON.parse(jString);
465 } catch(error) {
466 logError(error);
467 return null;
468 }
469 }
470
471 function canAccessLocalStorage() {
472 var test = 'htmx:localStorageTest';
473 try {
474 localStorage.setItem(test, test);
475 localStorage.removeItem(test);
476 return true;
477 } catch(e) {
478 return false;
479 }
480 }
481
482 function normalizePath(path) {
483 try {
484 var url = new URL(path);
485 if (url) {
486 path = url.pathname + url.search;
487 }
488 // remove trailing slash, unless index page
489 if (!(/^\/$/.test(path))) {
490 path = path.replace(/\/+$/, '');
491 }
492 return path;
493 } catch (e) {
494 // be kind to IE11, which doesn't support URL()
495 return path;
496 }
497 }
498
499 //==========================================================================================
500 // public API
501 //==========================================================================================
502
503 function internalEval(str){
504 return maybeEval(getDocument().body, function () {
505 return eval(str);
506 });
507 }
508
509 function onLoadHelper(callback) {
510 var value = htmx.on("htmx:load", function(evt) {
511 callback(evt.detail.elt);
512 });
513 return value;
514 }
515
516 function logAll(){
517 htmx.logger = function(elt, event, data) {
518 if(console) {
519 console.log(event, elt, data);
520 }
521 }
522 }
523
524 function logNone() {
525 htmx.logger = null
526 }
527
528 function find(eltOrSelector, selector) {
529 if (selector) {
530 return eltOrSelector.querySelector(selector);
531 } else {
532 return find(getDocument(), eltOrSelector);
533 }
534 }
535
536 function findAll(eltOrSelector, selector) {
537 if (selector) {
538 return eltOrSelector.querySelectorAll(selector);
539 } else {
540 return findAll(getDocument(), eltOrSelector);
541 }
542 }
543
544 function removeElement(elt, delay) {
545 elt = resolveTarget(elt);
546 if (delay) {
547 setTimeout(function(){
548 removeElement(elt);
549 elt = null;
550 }, delay);
551 } else {
552 elt.parentElement.removeChild(elt);
553 }
554 }
555
556 function addClassToElement(elt, clazz, delay) {
557 elt = resolveTarget(elt);
558 if (delay) {
559 setTimeout(function(){
560 addClassToElement(elt, clazz);
561 elt = null;
562 }, delay);
563 } else {
564 elt.classList && elt.classList.add(clazz);
565 }
566 }
567
568 function removeClassFromElement(elt, clazz, delay) {
569 elt = resolveTarget(elt);
570 if (delay) {
571 setTimeout(function(){
572 removeClassFromElement(elt, clazz);
573 elt = null;
574 }, delay);
575 } else {
576 if (elt.classList) {
577 elt.classList.remove(clazz);
578 // if there are no classes left, remove the class attribute
579 if (elt.classList.length === 0) {
580 elt.removeAttribute("class");
581 }
582 }
583 }
584 }
585
586 function toggleClassOnElement(elt, clazz) {
587 elt = resolveTarget(elt);
588 elt.classList.toggle(clazz);
589 }
590
591 function takeClassForElement(elt, clazz) {
592 elt = resolveTarget(elt);
593 forEach(elt.parentElement.children, function(child){
594 removeClassFromElement(child, clazz);
595 })
596 addClassToElement(elt, clazz);
597 }
598
599 function closest(elt, selector) {
600 elt = resolveTarget(elt);
601 if (elt.closest) {
602 return elt.closest(selector);
603 } else {
604 // TODO remove when IE goes away
605 do{
606 if (elt == null || matches(elt, selector)){
607 return elt;
608 }
609 }
610 while (elt = elt && parentElt(elt));
611 return null;
612 }
613 }
614
615 function startsWith(str, prefix) {
616 return str.substring(0, prefix.length) === prefix
617 }
618
619 function endsWith(str, suffix) {
620 return str.substring(str.length - suffix.length) === suffix
621 }
622
623 function normalizeSelector(selector) {
624 var trimmedSelector = selector.trim();
625 if (startsWith(trimmedSelector, "<") && endsWith(trimmedSelector, "/>")) {
626 return trimmedSelector.substring(1, trimmedSelector.length - 2);
627 } else {
628 return trimmedSelector;
629 }
630 }
631
632 function querySelectorAllExt(elt, selector) {
633 if (selector.indexOf("closest ") === 0) {
634 return [closest(elt, normalizeSelector(selector.substr(8)))];
635 } else if (selector.indexOf("find ") === 0) {
636 return [find(elt, normalizeSelector(selector.substr(5)))];
637 } else if (selector === "next") {
638 return [elt.nextElementSibling]
639 } else if (selector.indexOf("next ") === 0) {
640 return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)))];
641 } else if (selector === "previous") {
642 return [elt.previousElementSibling]
643 } else if (selector.indexOf("previous ") === 0) {
644 return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)))];
645 } else if (selector === 'document') {
646 return [document];
647 } else if (selector === 'window') {
648 return [window];
649 } else if (selector === 'body') {
650 return [document.body];
651 } else {
652 return getDocument().querySelectorAll(normalizeSelector(selector));
653 }
654 }
655
656 var scanForwardQuery = function(start, match) {
657 var results = getDocument().querySelectorAll(match);
658 for (var i = 0; i < results.length; i++) {
659 var elt = results[i];
660 if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) {
661 return elt;
662 }
663 }
664 }
665
666 var scanBackwardsQuery = function(start, match) {
667 var results = getDocument().querySelectorAll(match);
668 for (var i = results.length - 1; i >= 0; i--) {
669 var elt = results[i];
670 if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) {
671 return elt;
672 }
673 }
674 }
675
676 function querySelectorExt(eltOrSelector, selector) {
677 if (selector) {
678 return querySelectorAllExt(eltOrSelector, selector)[0];
679 } else {
680 return querySelectorAllExt(getDocument().body, eltOrSelector)[0];
681 }
682 }
683
684 function resolveTarget(arg2) {
685 if (isType(arg2, 'String')) {
686 return find(arg2);
687 } else {
688 return arg2;
689 }
690 }
691
692 function processEventArgs(arg1, arg2, arg3) {
693 if (isFunction(arg2)) {
694 return {
695 target: getDocument().body,
696 event: arg1,
697 listener: arg2
698 }
699 } else {
700 return {
701 target: resolveTarget(arg1),
702 event: arg2,
703 listener: arg3
704 }
705 }
706
707 }
708
709 function addEventListenerImpl(arg1, arg2, arg3) {
710 ready(function(){
711 var eventArgs = processEventArgs(arg1, arg2, arg3);
712 eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener);
713 })
714 var b = isFunction(arg2);
715 return b ? arg2 : arg3;
716 }
717
718 function removeEventListenerImpl(arg1, arg2, arg3) {
719 ready(function(){
720 var eventArgs = processEventArgs(arg1, arg2, arg3);
721 eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener);
722 })
723 return isFunction(arg2) ? arg2 : arg3;
724 }
725
726 //====================================================================
727 // Node processing
728 //====================================================================
729
730 var DUMMY_ELT = getDocument().createElement("output"); // dummy element for bad selectors
731 function findAttributeTargets(elt, attrName) {
732 var attrTarget = getClosestAttributeValue(elt, attrName);
733 if (attrTarget) {
734 if (attrTarget === "this") {
735 return [findThisElement(elt, attrName)];
736 } else {
737 var result = querySelectorAllExt(elt, attrTarget);
738 if (result.length === 0) {
739 logError('The selector "' + attrTarget + '" on ' + attrName + " returned no matches!");
740 return [DUMMY_ELT]
741 } else {
742 return result;
743 }
744 }
745 }
746 }
747
748 function findThisElement(elt, attribute){
749 return getClosestMatch(elt, function (elt) {
750 return getAttributeValue(elt, attribute) != null;
751 })
752 }
753
754 function getTarget(elt) {
755 var targetStr = getClosestAttributeValue(elt, "hx-target");
756 if (targetStr) {
757 if (targetStr === "this") {
758 return findThisElement(elt,'hx-target');
759 } else {
760 return querySelectorExt(elt, targetStr)
761 }
762 } else {
763 var data = getInternalData(elt);
764 if (data.boosted) {
765 return getDocument().body;
766 } else {
767 return elt;
768 }
769 }
770 }
771
772 function shouldSettleAttribute(name) {
773 var attributesToSettle = htmx.config.attributesToSettle;
774 for (var i = 0; i < attributesToSettle.length; i++) {
775 if (name === attributesToSettle[i]) {
776 return true;
777 }
778 }
779 return false;
780 }
781
782 function cloneAttributes(mergeTo, mergeFrom) {
783 forEach(mergeTo.attributes, function (attr) {
784 if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) {
785 mergeTo.removeAttribute(attr.name)
786 }
787 });
788 forEach(mergeFrom.attributes, function (attr) {
789 if (shouldSettleAttribute(attr.name)) {
790 mergeTo.setAttribute(attr.name, attr.value);
791 }
792 });
793 }
794
795 function isInlineSwap(swapStyle, target) {
796 var extensions = getExtensions(target);
797 for (var i = 0; i < extensions.length; i++) {
798 var extension = extensions[i];
799 try {
800 if (extension.isInlineSwap(swapStyle)) {
801 return true;
802 }
803 } catch(e) {
804 logError(e);
805 }
806 }
807 return swapStyle === "outerHTML";
808 }
809
810 /**
811 *
812 * @param {string} oobValue
813 * @param {HTMLElement} oobElement
814 * @param {*} settleInfo
815 * @returns
816 */
817 function oobSwap(oobValue, oobElement, settleInfo) {
818 var selector = "#" + getRawAttribute(oobElement, "id");
819 var swapStyle = "outerHTML";
820 if (oobValue === "true") {
821 // do nothing
822 } else if (oobValue.indexOf(":") > 0) {
823 swapStyle = oobValue.substr(0, oobValue.indexOf(":"));
824 selector = oobValue.substr(oobValue.indexOf(":") + 1, oobValue.length);
825 } else {
826 swapStyle = oobValue;
827 }
828
829 var targets = getDocument().querySelectorAll(selector);
830 if (targets) {
831 forEach(
832 targets,
833 function (target) {
834 var fragment;
835 var oobElementClone = oobElement.cloneNode(true);
836 fragment = getDocument().createDocumentFragment();
837 fragment.appendChild(oobElementClone);
838 if (!isInlineSwap(swapStyle, target)) {
839 fragment = oobElementClone; // if this is not an inline swap, we use the content of the node, not the node itself
840 }
841
842 var beforeSwapDetails = {shouldSwap: true, target: target, fragment:fragment };
843 if (!triggerEvent(target, 'htmx:oobBeforeSwap', beforeSwapDetails)) return;
844
845 target = beforeSwapDetails.target; // allow re-targeting
846 if (beforeSwapDetails['shouldSwap']){
847 swap(swapStyle, target, target, fragment, settleInfo);
848 }
849 forEach(settleInfo.elts, function (elt) {
850 triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails);
851 });
852 }
853 );
854 oobElement.parentNode.removeChild(oobElement);
855 } else {
856 oobElement.parentNode.removeChild(oobElement);
857 triggerErrorEvent(getDocument().body, "htmx:oobErrorNoTarget", {content: oobElement});
858 }
859 return oobValue;
860 }
861
862 function handleOutOfBandSwaps(elt, fragment, settleInfo) {
863 var oobSelects = getClosestAttributeValue(elt, "hx-select-oob");
864 if (oobSelects) {
865 var oobSelectValues = oobSelects.split(",");
866 for (var i = 0; i < oobSelectValues.length; i++) {
867 var oobSelectValue = oobSelectValues[i].split(":", 2);
868 var id = oobSelectValue[0].trim();
869 if (id.indexOf("#") === 0) {
870 id = id.substring(1);
871 }
872 var oobValue = oobSelectValue[1] || "true";
873 var oobElement = fragment.querySelector("#" + id);
874 if (oobElement) {
875 oobSwap(oobValue, oobElement, settleInfo);
876 }
877 }
878 }
879 forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) {
880 var oobValue = getAttributeValue(oobElement, "hx-swap-oob");
881 if (oobValue != null) {
882 oobSwap(oobValue, oobElement, settleInfo);
883 }
884 });
885 }
886
887 function handlePreservedElements(fragment) {
888 forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) {
889 var id = getAttributeValue(preservedElt, "id");
890 var oldElt = getDocument().getElementById(id);
891 if (oldElt != null) {
892 preservedElt.parentNode.replaceChild(oldElt, preservedElt);
893 }
894 });
895 }
896
897 function handleAttributes(parentNode, fragment, settleInfo) {
898 forEach(fragment.querySelectorAll("[id]"), function (newNode) {
899 var id = getRawAttribute(newNode, "id")
900 if (id && id.length > 0) {
901 var normalizedId = id.replace("'", "\\'");
902 var normalizedTag = newNode.tagName.replace(':', '\\:');
903 var oldNode = parentNode.querySelector(normalizedTag + "[id='" + normalizedId + "']");
904 if (oldNode && oldNode !== parentNode) {
905 var newAttributes = newNode.cloneNode();
906 cloneAttributes(newNode, oldNode);
907 settleInfo.tasks.push(function () {
908 cloneAttributes(newNode, newAttributes);
909 });
910 }
911 }
912 });
913 }
914
915 function makeAjaxLoadTask(child) {
916 return function () {
917 removeClassFromElement(child, htmx.config.addedClass);
918 processNode(child);
919 processScripts(child);
920 processFocus(child)
921 triggerEvent(child, 'htmx:load');
922 };
923 }
924
925 function processFocus(child) {
926 var autofocus = "[autofocus]";
927 var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus)
928 if (autoFocusedElt != null) {
929 autoFocusedElt.focus();
930 }
931 }
932
933 function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) {
934 handleAttributes(parentNode, fragment, settleInfo);
935 while(fragment.childNodes.length > 0){
936 var child = fragment.firstChild;
937 addClassToElement(child, htmx.config.addedClass);
938 parentNode.insertBefore(child, insertBefore);
939 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
940 settleInfo.tasks.push(makeAjaxLoadTask(child));
941 }
942 }
943 }
944
945 // based on https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0,
946 // derived from Java's string hashcode implementation
947 function stringHash(string, hash) {
948 var char = 0;
949 while (char < string.length){
950 hash = (hash << 5) - hash + string.charCodeAt(char++) | 0; // bitwise or ensures we have a 32-bit int
951 }
952 return hash;
953 }
954
955 function attributeHash(elt) {
956 var hash = 0;
957 // IE fix
958 if (elt.attributes) {
959 for (var i = 0; i < elt.attributes.length; i++) {
960 var attribute = elt.attributes[i];
961 if(attribute.value){ // only include attributes w/ actual values (empty is same as non-existent)
962 hash = stringHash(attribute.name, hash);
963 hash = stringHash(attribute.value, hash);
964 }
965 }
966 }
967 return hash;
968 }
969
970 function deInitOnHandlers(elt) {
971 var internalData = getInternalData(elt);
972 if (internalData.onHandlers) {
973 for (var i = 0; i < internalData.onHandlers.length; i++) {
974 const handlerInfo = internalData.onHandlers[i];
975 elt.removeEventListener(handlerInfo.event, handlerInfo.listener);
976 }
977 delete internalData.onHandlers
978 }
979 }
980
981 function deInitNode(element) {
982 var internalData = getInternalData(element);
983 if (internalData.timeout) {
984 clearTimeout(internalData.timeout);
985 }
986 if (internalData.webSocket) {
987 internalData.webSocket.close();
988 }
989 if (internalData.sseEventSource) {
990 internalData.sseEventSource.close();
991 }
992 if (internalData.listenerInfos) {
993 forEach(internalData.listenerInfos, function (info) {
994 if (info.on) {
995 info.on.removeEventListener(info.trigger, info.listener);
996 }
997 });
998 }
999 deInitOnHandlers(element);
1000 forEach(Object.keys(internalData), function(key) { delete internalData[key] });
1001 }
1002
1003 function cleanUpElement(element) {
1004 triggerEvent(element, "htmx:beforeCleanupElement")
1005 deInitNode(element);
1006 if (element.children) { // IE
1007 forEach(element.children, function(child) { cleanUpElement(child) });
1008 }
1009 }
1010
1011 function swapOuterHTML(target, fragment, settleInfo) {
1012 if (target.tagName === "BODY") {
1013 return swapInnerHTML(target, fragment, settleInfo);
1014 } else {
1015 // @type {HTMLElement}
1016 var newElt
1017 var eltBeforeNewContent = target.previousSibling;
1018 insertNodesBefore(parentElt(target), target, fragment, settleInfo);
1019 if (eltBeforeNewContent == null) {
1020 newElt = parentElt(target).firstChild;
1021 } else {
1022 newElt = eltBeforeNewContent.nextSibling;
1023 }
1024 settleInfo.elts = settleInfo.elts.filter(function(e) { return e != target });
1025 while(newElt && newElt !== target) {
1026 if (newElt.nodeType === Node.ELEMENT_NODE) {
1027 settleInfo.elts.push(newElt);
1028 }
1029 newElt = newElt.nextElementSibling;
1030 }
1031 cleanUpElement(target);
1032 parentElt(target).removeChild(target);
1033 }
1034 }
1035
1036 function swapAfterBegin(target, fragment, settleInfo) {
1037 return insertNodesBefore(target, target.firstChild, fragment, settleInfo);
1038 }
1039
1040 function swapBeforeBegin(target, fragment, settleInfo) {
1041 return insertNodesBefore(parentElt(target), target, fragment, settleInfo);
1042 }
1043
1044 function swapBeforeEnd(target, fragment, settleInfo) {
1045 return insertNodesBefore(target, null, fragment, settleInfo);
1046 }
1047
1048 function swapAfterEnd(target, fragment, settleInfo) {
1049 return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo);
1050 }
1051 function swapDelete(target, fragment, settleInfo) {
1052 cleanUpElement(target);
1053 return parentElt(target).removeChild(target);
1054 }
1055
1056 function swapInnerHTML(target, fragment, settleInfo) {
1057 var firstChild = target.firstChild;
1058 insertNodesBefore(target, firstChild, fragment, settleInfo);
1059 if (firstChild) {
1060 while (firstChild.nextSibling) {
1061 cleanUpElement(firstChild.nextSibling)
1062 target.removeChild(firstChild.nextSibling);
1063 }
1064 cleanUpElement(firstChild)
1065 target.removeChild(firstChild);
1066 }
1067 }
1068
1069 function maybeSelectFromResponse(elt, fragment, selectOverride) {
1070 var selector = selectOverride || getClosestAttributeValue(elt, "hx-select");
1071 if (selector) {
1072 var newFragment = getDocument().createDocumentFragment();
1073 forEach(fragment.querySelectorAll(selector), function (node) {
1074 newFragment.appendChild(node);
1075 });
1076 fragment = newFragment;
1077 }
1078 return fragment;
1079 }
1080
1081 function swap(swapStyle, elt, target, fragment, settleInfo) {
1082 switch (swapStyle) {
1083 case "none":
1084 return;
1085 case "outerHTML":
1086 swapOuterHTML(target, fragment, settleInfo);
1087 return;
1088 case "afterbegin":
1089 swapAfterBegin(target, fragment, settleInfo);
1090 return;
1091 case "beforebegin":
1092 swapBeforeBegin(target, fragment, settleInfo);
1093 return;
1094 case "beforeend":
1095 swapBeforeEnd(target, fragment, settleInfo);
1096 return;
1097 case "afterend":
1098 swapAfterEnd(target, fragment, settleInfo);
1099 return;
1100 case "delete":
1101 swapDelete(target, fragment, settleInfo);
1102 return;
1103 default:
1104 var extensions = getExtensions(elt);
1105 for (var i = 0; i < extensions.length; i++) {
1106 var ext = extensions[i];
1107 try {
1108 var newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo);
1109 if (newElements) {
1110 if (typeof newElements.length !== 'undefined') {
1111 // if handleSwap returns an array (like) of elements, we handle them
1112 for (var j = 0; j < newElements.length; j++) {
1113 var child = newElements[j];
1114 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
1115 settleInfo.tasks.push(makeAjaxLoadTask(child));
1116 }
1117 }
1118 }
1119 return;
1120 }
1121 } catch (e) {
1122 logError(e);
1123 }
1124 }
1125 if (swapStyle === "innerHTML") {
1126 swapInnerHTML(target, fragment, settleInfo);
1127 } else {
1128 swap(htmx.config.defaultSwapStyle, elt, target, fragment, settleInfo);
1129 }
1130 }
1131 }
1132
1133 function findTitle(content) {
1134 if (content.indexOf('<title') > -1) {
1135 var contentWithSvgsRemoved = content.replace(SVG_TAGS_REGEX, '');
1136 var result = contentWithSvgsRemoved.match(TITLE_TAG_REGEX);
1137 if (result) {
1138 return result[2];
1139 }
1140 }
1141 }
1142
1143 function selectAndSwap(swapStyle, target, elt, responseText, settleInfo, selectOverride) {
1144 settleInfo.title = findTitle(responseText);
1145 var fragment = makeFragment(responseText);
1146 if (fragment) {
1147 handleOutOfBandSwaps(elt, fragment, settleInfo);
1148 fragment = maybeSelectFromResponse(elt, fragment, selectOverride);
1149 handlePreservedElements(fragment);
1150 return swap(swapStyle, elt, target, fragment, settleInfo);
1151 }
1152 }
1153
1154 function handleTrigger(xhr, header, elt) {
1155 var triggerBody = xhr.getResponseHeader(header);
1156 if (triggerBody.indexOf("{") === 0) {
1157 var triggers = parseJSON(triggerBody);
1158 for (var eventName in triggers) {
1159 if (triggers.hasOwnProperty(eventName)) {
1160 var detail = triggers[eventName];
1161 if (!isRawObject(detail)) {
1162 detail = {"value": detail}
1163 }
1164 triggerEvent(elt, eventName, detail);
1165 }
1166 }
1167 } else {
1168 var eventNames = triggerBody.split(",")
1169 for (var i = 0; i < eventNames.length; i++) {
1170 triggerEvent(elt, eventNames[i].trim(), []);
1171 }
1172 }
1173 }
1174
1175 var WHITESPACE = /\s/;
1176 var WHITESPACE_OR_COMMA = /[\s,]/;
1177 var SYMBOL_START = /[_$a-zA-Z]/;
1178 var SYMBOL_CONT = /[_$a-zA-Z0-9]/;
1179 var STRINGISH_START = ['"', "'", "/"];
1180 var NOT_WHITESPACE = /[^\s]/;
1181 var COMBINED_SELECTOR_START = /[{(]/;
1182 var COMBINED_SELECTOR_END = /[})]/;
1183 function tokenizeString(str) {
1184 var tokens = [];
1185 var position = 0;
1186 while (position < str.length) {
1187 if(SYMBOL_START.exec(str.charAt(position))) {
1188 var startPosition = position;
1189 while (SYMBOL_CONT.exec(str.charAt(position + 1))) {
1190 position++;
1191 }
1192 tokens.push(str.substr(startPosition, position - startPosition + 1));
1193 } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) {
1194 var startChar = str.charAt(position);
1195 var startPosition = position;
1196 position++;
1197 while (position < str.length && str.charAt(position) !== startChar ) {
1198 if (str.charAt(position) === "\\") {
1199 position++;
1200 }
1201 position++;
1202 }
1203 tokens.push(str.substr(startPosition, position - startPosition + 1));
1204 } else {
1205 var symbol = str.charAt(position);
1206 tokens.push(symbol);
1207 }
1208 position++;
1209 }
1210 return tokens;
1211 }
1212
1213 function isPossibleRelativeReference(token, last, paramName) {
1214 return SYMBOL_START.exec(token.charAt(0)) &&
1215 token !== "true" &&
1216 token !== "false" &&
1217 token !== "this" &&
1218 token !== paramName &&
1219 last !== ".";
1220 }
1221
1222 function maybeGenerateConditional(elt, tokens, paramName) {
1223 if (tokens[0] === '[') {
1224 tokens.shift();
1225 var bracketCount = 1;
1226 var conditionalSource = " return (function(" + paramName + "){ return (";
1227 var last = null;
1228 while (tokens.length > 0) {
1229 var token = tokens[0];
1230 if (token === "]") {
1231 bracketCount--;
1232 if (bracketCount === 0) {
1233 if (last === null) {
1234 conditionalSource = conditionalSource + "true";
1235 }
1236 tokens.shift();
1237 conditionalSource += ")})";
1238 try {
1239 var conditionFunction = maybeEval(elt,function () {
1240 return Function(conditionalSource)();
1241 },
1242 function(){return true})
1243 conditionFunction.source = conditionalSource;
1244 return conditionFunction;
1245 } catch (e) {
1246 triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource})
1247 return null;
1248 }
1249 }
1250 } else if (token === "[") {
1251 bracketCount++;
1252 }
1253 if (isPossibleRelativeReference(token, last, paramName)) {
1254 conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
1255 } else {
1256 conditionalSource = conditionalSource + token;
1257 }
1258 last = tokens.shift();
1259 }
1260 }
1261 }
1262
1263 function consumeUntil(tokens, match) {
1264 var result = "";
1265 while (tokens.length > 0 && !match.test(tokens[0])) {
1266 result += tokens.shift();
1267 }
1268 return result;
1269 }
1270
1271 function consumeCSSSelector(tokens) {
1272 var result;
1273 if (tokens.length > 0 && COMBINED_SELECTOR_START.test(tokens[0])) {
1274 tokens.shift();
1275 result = consumeUntil(tokens, COMBINED_SELECTOR_END).trim();
1276 tokens.shift();
1277 } else {
1278 result = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1279 }
1280 return result;
1281 }
1282
1283 var INPUT_SELECTOR = 'input, textarea, select';
1284
1285 /**
1286 * @param {HTMLElement} elt
1287 * @param {string} explicitTrigger
1288 * @param {cache} cache for trigger specs
1289 * @returns {import("./htmx").HtmxTriggerSpecification[]}
1290 */
1291 function parseAndCacheTrigger(elt, explicitTrigger, cache) {
1292 var triggerSpecs = [];
1293 var tokens = tokenizeString(explicitTrigger);
1294 do {
1295 consumeUntil(tokens, NOT_WHITESPACE);
1296 var initialLength = tokens.length;
1297 var trigger = consumeUntil(tokens, /[,\[\s]/);
1298 if (trigger !== "") {
1299 if (trigger === "every") {
1300 var every = {trigger: 'every'};
1301 consumeUntil(tokens, NOT_WHITESPACE);
1302 every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/));
1303 consumeUntil(tokens, NOT_WHITESPACE);
1304 var eventFilter = maybeGenerateConditional(elt, tokens, "event");
1305 if (eventFilter) {
1306 every.eventFilter = eventFilter;
1307 }
1308 triggerSpecs.push(every);
1309 } else if (trigger.indexOf("sse:") === 0) {
1310 triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
1311 } else {
1312 var triggerSpec = {trigger: trigger};
1313 var eventFilter = maybeGenerateConditional(elt, tokens, "event");
1314 if (eventFilter) {
1315 triggerSpec.eventFilter = eventFilter;
1316 }
1317 while (tokens.length > 0 && tokens[0] !== ",") {
1318 consumeUntil(tokens, NOT_WHITESPACE)
1319 var token = tokens.shift();
1320 if (token === "changed") {
1321 triggerSpec.changed = true;
1322 } else if (token === "once") {
1323 triggerSpec.once = true;
1324 } else if (token === "consume") {
1325 triggerSpec.consume = true;
1326 } else if (token === "delay" && tokens[0] === ":") {
1327 tokens.shift();
1328 triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
1329 } else if (token === "from" && tokens[0] === ":") {
1330 tokens.shift();
1331 if (COMBINED_SELECTOR_START.test(tokens[0])) {
1332 var from_arg = consumeCSSSelector(tokens);
1333 } else {
1334 var from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1335 if (from_arg === "closest" || from_arg === "find" || from_arg === "next" || from_arg === "previous") {
1336 tokens.shift();
1337 var selector = consumeCSSSelector(tokens);
1338 // `next` and `previous` allow a selector-less syntax
1339 if (selector.length > 0) {
1340 from_arg += " " + selector;
1341 }
1342 }
1343 }
1344 triggerSpec.from = from_arg;
1345 } else if (token === "target" && tokens[0] === ":") {
1346 tokens.shift();
1347 triggerSpec.target = consumeCSSSelector(tokens);
1348 } else if (token === "throttle" && tokens[0] === ":") {
1349 tokens.shift();
1350 triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
1351 } else if (token === "queue" && tokens[0] === ":") {
1352 tokens.shift();
1353 triggerSpec.queue = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1354 } else if (token === "root" && tokens[0] === ":") {
1355 tokens.shift();
1356 triggerSpec[token] = consumeCSSSelector(tokens);
1357 } else if (token === "threshold" && tokens[0] === ":") {
1358 tokens.shift();
1359 triggerSpec[token] = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1360 } else {
1361 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
1362 }
1363 }
1364 triggerSpecs.push(triggerSpec);
1365 }
1366 }
1367 if (tokens.length === initialLength) {
1368 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
1369 }
1370 consumeUntil(tokens, NOT_WHITESPACE);
1371 } while (tokens[0] === "," && tokens.shift())
1372 if (cache) {
1373 cache[explicitTrigger] = triggerSpecs
1374 }
1375 return triggerSpecs
1376 }
1377
1378 /**
1379 * @param {HTMLElement} elt
1380 * @returns {import("./htmx").HtmxTriggerSpecification[]}
1381 */
1382 function getTriggerSpecs(elt) {
1383 var explicitTrigger = getAttributeValue(elt, 'hx-trigger');
1384 var triggerSpecs = [];
1385 if (explicitTrigger) {
1386 var cache = htmx.config.triggerSpecsCache
1387 triggerSpecs = (cache && cache[explicitTrigger]) || parseAndCacheTrigger(elt, explicitTrigger, cache)
1388 }
1389
1390 if (triggerSpecs.length > 0) {
1391 return triggerSpecs;
1392 } else if (matches(elt, 'form')) {
1393 return [{trigger: 'submit'}];
1394 } else if (matches(elt, 'input[type="button"], input[type="submit"]')){
1395 return [{trigger: 'click'}];
1396 } else if (matches(elt, INPUT_SELECTOR)) {
1397 return [{trigger: 'change'}];
1398 } else {
1399 return [{trigger: 'click'}];
1400 }
1401 }
1402
1403 function cancelPolling(elt) {
1404 getInternalData(elt).cancelled = true;
1405 }
1406
1407 function processPolling(elt, handler, spec) {
1408 var nodeData = getInternalData(elt);
1409 nodeData.timeout = setTimeout(function () {
1410 if (bodyContains(elt) && nodeData.cancelled !== true) {
1411 if (!maybeFilterEvent(spec, elt, makeEvent('hx:poll:trigger', {
1412 triggerSpec: spec,
1413 target: elt
1414 }))) {
1415 handler(elt);
1416 }
1417 processPolling(elt, handler, spec);
1418 }
1419 }, spec.pollInterval);
1420 }
1421
1422 function isLocalLink(elt) {
1423 return location.hostname === elt.hostname &&
1424 getRawAttribute(elt,'href') &&
1425 getRawAttribute(elt,'href').indexOf("#") !== 0;
1426 }
1427
1428 function boostElement(elt, nodeData, triggerSpecs) {
1429 if ((elt.tagName === "A" && isLocalLink(elt) && (elt.target === "" || elt.target === "_self")) || elt.tagName === "FORM") {
1430 nodeData.boosted = true;
1431 var verb, path;
1432 if (elt.tagName === "A") {
1433 verb = "get";
1434 path = getRawAttribute(elt, 'href')
1435 } else {
1436 var rawAttribute = getRawAttribute(elt, "method");
1437 verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
1438 if (verb === "get") {
1439 }
1440 path = getRawAttribute(elt, 'action');
1441 }
1442 triggerSpecs.forEach(function(triggerSpec) {
1443 addEventListener(elt, function(elt, evt) {
1444 if (closest(elt, htmx.config.disableSelector)) {
1445 cleanUpElement(elt)
1446 return
1447 }
1448 issueAjaxRequest(verb, path, elt, evt)
1449 }, nodeData, triggerSpec, true);
1450 });
1451 }
1452 }
1453
1454 /**
1455 *
1456 * @param {Event} evt
1457 * @param {HTMLElement} elt
1458 * @returns
1459 */
1460 function shouldCancel(evt, elt) {
1461 if (evt.type === "submit" || evt.type === "click") {
1462 if (elt.tagName === "FORM") {
1463 return true;
1464 }
1465 if (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) {
1466 return true;
1467 }
1468 if (elt.tagName === "A" && elt.href &&
1469 (elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf("#") !== 0)) {
1470 return true;
1471 }
1472 }
1473 return false;
1474 }
1475
1476 function ignoreBoostedAnchorCtrlClick(elt, evt) {
1477 return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && (evt.ctrlKey || evt.metaKey);
1478 }
1479
1480 function maybeFilterEvent(triggerSpec, elt, evt) {
1481 var eventFilter = triggerSpec.eventFilter;
1482 if(eventFilter){
1483 try {
1484 return eventFilter.call(elt, evt) !== true;
1485 } catch(e) {
1486 triggerErrorEvent(getDocument().body, "htmx:eventFilter:error", {error: e, source:eventFilter.source});
1487 return true;
1488 }
1489 }
1490 return false;
1491 }
1492
1493 function addEventListener(elt, handler, nodeData, triggerSpec, explicitCancel) {
1494 var elementData = getInternalData(elt);
1495 var eltsToListenOn;
1496 if (triggerSpec.from) {
1497 eltsToListenOn = querySelectorAllExt(elt, triggerSpec.from);
1498 } else {
1499 eltsToListenOn = [elt];
1500 }
1501 // store the initial values of the elements, so we can tell if they change
1502 if (triggerSpec.changed) {
1503 eltsToListenOn.forEach(function (eltToListenOn) {
1504 var eltToListenOnData = getInternalData(eltToListenOn);
1505 eltToListenOnData.lastValue = eltToListenOn.value;
1506 })
1507 }
1508 forEach(eltsToListenOn, function (eltToListenOn) {
1509 var eventListener = function (evt) {
1510 if (!bodyContains(elt)) {
1511 eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener);
1512 return;
1513 }
1514 if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
1515 return;
1516 }
1517 if (explicitCancel || shouldCancel(evt, elt)) {
1518 evt.preventDefault();
1519 }
1520 if (maybeFilterEvent(triggerSpec, elt, evt)) {
1521 return;
1522 }
1523 var eventData = getInternalData(evt);
1524 eventData.triggerSpec = triggerSpec;
1525 if (eventData.handledFor == null) {
1526 eventData.handledFor = [];
1527 }
1528 if (eventData.handledFor.indexOf(elt) < 0) {
1529 eventData.handledFor.push(elt);
1530 if (triggerSpec.consume) {
1531 evt.stopPropagation();
1532 }
1533 if (triggerSpec.target && evt.target) {
1534 if (!matches(evt.target, triggerSpec.target)) {
1535 return;
1536 }
1537 }
1538 if (triggerSpec.once) {
1539 if (elementData.triggeredOnce) {
1540 return;
1541 } else {
1542 elementData.triggeredOnce = true;
1543 }
1544 }
1545 if (triggerSpec.changed) {
1546 var eltToListenOnData = getInternalData(eltToListenOn)
1547 if (eltToListenOnData.lastValue === eltToListenOn.value) {
1548 return;
1549 }
1550 eltToListenOnData.lastValue = eltToListenOn.value
1551 }
1552 if (elementData.delayed) {
1553 clearTimeout(elementData.delayed);
1554 }
1555 if (elementData.throttle) {
1556 return;
1557 }
1558
1559 if (triggerSpec.throttle > 0) {
1560 if (!elementData.throttle) {
1561 handler(elt, evt);
1562 elementData.throttle = setTimeout(function () {
1563 elementData.throttle = null;
1564 }, triggerSpec.throttle);
1565 }
1566 } else if (triggerSpec.delay > 0) {
1567 elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay);
1568 } else {
1569 triggerEvent(elt, 'htmx:trigger')
1570 handler(elt, evt);
1571 }
1572 }
1573 };
1574 if (nodeData.listenerInfos == null) {
1575 nodeData.listenerInfos = [];
1576 }
1577 nodeData.listenerInfos.push({
1578 trigger: triggerSpec.trigger,
1579 listener: eventListener,
1580 on: eltToListenOn
1581 })
1582 eltToListenOn.addEventListener(triggerSpec.trigger, eventListener);
1583 });
1584 }
1585
1586 var windowIsScrolling = false // used by initScrollHandler
1587 var scrollHandler = null;
1588 function initScrollHandler() {
1589 if (!scrollHandler) {
1590 scrollHandler = function() {
1591 windowIsScrolling = true
1592 };
1593 window.addEventListener("scroll", scrollHandler)
1594 setInterval(function() {
1595 if (windowIsScrolling) {
1596 windowIsScrolling = false;
1597 forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) {
1598 maybeReveal(elt);
1599 })
1600 }
1601 }, 200);
1602 }
1603 }
1604
1605 function maybeReveal(elt) {
1606 if (!hasAttribute(elt,'data-hx-revealed') && isScrolledIntoView(elt)) {
1607 elt.setAttribute('data-hx-revealed', 'true');
1608 var nodeData = getInternalData(elt);
1609 if (nodeData.initHash) {
1610 triggerEvent(elt, 'revealed');
1611 } else {
1612 // if the node isn't initialized, wait for it before triggering the request
1613 elt.addEventListener("htmx:afterProcessNode", function(evt) { triggerEvent(elt, 'revealed') }, {once: true});
1614 }
1615 }
1616 }
1617
1618 //====================================================================
1619 // Web Sockets
1620 //====================================================================
1621
1622 function processWebSocketInfo(elt, nodeData, info) {
1623 var values = splitOnWhitespace(info);
1624 for (var i = 0; i < values.length; i++) {
1625 var value = values[i].split(/:(.+)/);
1626 if (value[0] === "connect") {
1627 ensureWebSocket(elt, value[1], 0);
1628 }
1629 if (value[0] === "send") {
1630 processWebSocketSend(elt);
1631 }
1632 }
1633 }
1634
1635 function ensureWebSocket(elt, wssSource, retryCount) {
1636 if (!bodyContains(elt)) {
1637 return; // stop ensuring websocket connection when socket bearing element ceases to exist
1638 }
1639
1640 if (wssSource.indexOf("/") == 0) { // complete absolute paths only
1641 var base_part = location.hostname + (location.port ? ':'+location.port: '');
1642 if (location.protocol == 'https:') {
1643 wssSource = "wss://" + base_part + wssSource;
1644 } else if (location.protocol == 'http:') {
1645 wssSource = "ws://" + base_part + wssSource;
1646 }
1647 }
1648 var socket = htmx.createWebSocket(wssSource);
1649 socket.onerror = function (e) {
1650 triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket});
1651 maybeCloseWebSocketSource(elt);
1652 };
1653
1654 socket.onclose = function (e) {
1655 if ([1006, 1012, 1013].indexOf(e.code) >= 0) { // Abnormal Closure/Service Restart/Try Again Later
1656 var delay = getWebSocketReconnectDelay(retryCount);
1657 setTimeout(function() {
1658 ensureWebSocket(elt, wssSource, retryCount+1); // creates a websocket with a new timeout
1659 }, delay);
1660 }
1661 };
1662 socket.onopen = function (e) {
1663 retryCount = 0;
1664 }
1665
1666 getInternalData(elt).webSocket = socket;
1667 socket.addEventListener('message', function (event) {
1668 if (maybeCloseWebSocketSource(elt)) {
1669 return;
1670 }
1671
1672 var response = event.data;
1673 withExtensions(elt, function(extension){
1674 response = extension.transformResponse(response, null, elt);
1675 });
1676
1677 var settleInfo = makeSettleInfo(elt);
1678 var fragment = makeFragment(response);
1679 var children = toArray(fragment.children);
1680 for (var i = 0; i < children.length; i++) {
1681 var child = children[i];
1682 oobSwap(getAttributeValue(child, "hx-swap-oob") || "true", child, settleInfo);
1683 }
1684
1685 settleImmediately(settleInfo.tasks);
1686 });
1687 }
1688
1689 function maybeCloseWebSocketSource(elt) {
1690 if (!bodyContains(elt)) {
1691 getInternalData(elt).webSocket.close();
1692 return true;
1693 }
1694 }
1695
1696 function processWebSocketSend(elt) {
1697 var webSocketSourceElt = getClosestMatch(elt, function (parent) {
1698 return getInternalData(parent).webSocket != null;
1699 });
1700 if (webSocketSourceElt) {
1701 elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
1702 var webSocket = getInternalData(webSocketSourceElt).webSocket;
1703 var headers = getHeaders(elt, webSocketSourceElt);
1704 var results = getInputValues(elt, 'post');
1705 var errors = results.errors;
1706 var rawParameters = results.values;
1707 var expressionVars = getExpressionVars(elt);
1708 var allParameters = mergeObjects(rawParameters, expressionVars);
1709 var filteredParameters = filterValues(allParameters, elt);
1710 filteredParameters['HEADERS'] = headers;
1711 if (errors && errors.length > 0) {
1712 triggerEvent(elt, 'htmx:validation:halted', errors);
1713 return;
1714 }
1715 webSocket.send(JSON.stringify(filteredParameters));
1716 if(shouldCancel(evt, elt)){
1717 evt.preventDefault();
1718 }
1719 });
1720 } else {
1721 triggerErrorEvent(elt, "htmx:noWebSocketSourceError");
1722 }
1723 }
1724
1725 function getWebSocketReconnectDelay(retryCount) {
1726 var delay = htmx.config.wsReconnectDelay;
1727 if (typeof delay === 'function') {
1728 // @ts-ignore
1729 return delay(retryCount);
1730 }
1731 if (delay === 'full-jitter') {
1732 var exp = Math.min(retryCount, 6);
1733 var maxDelay = 1000 * Math.pow(2, exp);
1734 return maxDelay * Math.random();
1735 }
1736 logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
1737 }
1738
1739 //====================================================================
1740 // Server Sent Events
1741 //====================================================================
1742
1743 function processSSEInfo(elt, nodeData, info) {
1744 var values = splitOnWhitespace(info);
1745 for (var i = 0; i < values.length; i++) {
1746 var value = values[i].split(/:(.+)/);
1747 if (value[0] === "connect") {
1748 processSSESource(elt, value[1]);
1749 }
1750
1751 if ((value[0] === "swap")) {
1752 processSSESwap(elt, value[1])
1753 }
1754 }
1755 }
1756
1757 function processSSESource(elt, sseSrc) {
1758 var source = htmx.createEventSource(sseSrc);
1759 source.onerror = function (e) {
1760 triggerErrorEvent(elt, "htmx:sseError", {error:e, source:source});
1761 maybeCloseSSESource(elt);
1762 };
1763 getInternalData(elt).sseEventSource = source;
1764 }
1765
1766 function processSSESwap(elt, sseEventName) {
1767 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1768 if (sseSourceElt) {
1769 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1770 var sseListener = function (event) {
1771 if (maybeCloseSSESource(sseSourceElt)) {
1772 return;
1773 }
1774 if (!bodyContains(elt)) {
1775 sseEventSource.removeEventListener(sseEventName, sseListener);
1776 return;
1777 }
1778
1779 ///////////////////////////
1780 // TODO: merge this code with AJAX and WebSockets code in the future.
1781
1782 var response = event.data;
1783 withExtensions(elt, function(extension){
1784 response = extension.transformResponse(response, null, elt);
1785 });
1786
1787 var swapSpec = getSwapSpecification(elt)
1788 var target = getTarget(elt)
1789 var settleInfo = makeSettleInfo(elt);
1790
1791 selectAndSwap(swapSpec.swapStyle, target, elt, response, settleInfo)
1792 settleImmediately(settleInfo.tasks)
1793 triggerEvent(elt, "htmx:sseMessage", event)
1794 };
1795
1796 getInternalData(elt).sseListener = sseListener;
1797 sseEventSource.addEventListener(sseEventName, sseListener);
1798 } else {
1799 triggerErrorEvent(elt, "htmx:noSSESourceError");
1800 }
1801 }
1802
1803 function processSSETrigger(elt, handler, sseEventName) {
1804 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1805 if (sseSourceElt) {
1806 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1807 var sseListener = function () {
1808 if (!maybeCloseSSESource(sseSourceElt)) {
1809 if (bodyContains(elt)) {
1810 handler(elt);
1811 } else {
1812 sseEventSource.removeEventListener(sseEventName, sseListener);
1813 }
1814 }
1815 };
1816 getInternalData(elt).sseListener = sseListener;
1817 sseEventSource.addEventListener(sseEventName, sseListener);
1818 } else {
1819 triggerErrorEvent(elt, "htmx:noSSESourceError");
1820 }
1821 }
1822
1823 function maybeCloseSSESource(elt) {
1824 if (!bodyContains(elt)) {
1825 getInternalData(elt).sseEventSource.close();
1826 return true;
1827 }
1828 }
1829
1830 function hasEventSource(node) {
1831 return getInternalData(node).sseEventSource != null;
1832 }
1833
1834 //====================================================================
1835
1836 function loadImmediately(elt, handler, nodeData, delay) {
1837 var load = function(){
1838 if (!nodeData.loaded) {
1839 nodeData.loaded = true;
1840 handler(elt);
1841 }
1842 }
1843 if (delay > 0) {
1844 setTimeout(load, delay);
1845 } else {
1846 load();
1847 }
1848 }
1849
1850 function processVerbs(elt, nodeData, triggerSpecs) {
1851 var explicitAction = false;
1852 forEach(VERBS, function (verb) {
1853 if (hasAttribute(elt,'hx-' + verb)) {
1854 var path = getAttributeValue(elt, 'hx-' + verb);
1855 explicitAction = true;
1856 nodeData.path = path;
1857 nodeData.verb = verb;
1858 triggerSpecs.forEach(function(triggerSpec) {
1859 addTriggerHandler(elt, triggerSpec, nodeData, function (elt, evt) {
1860 if (closest(elt, htmx.config.disableSelector)) {
1861 cleanUpElement(elt)
1862 return
1863 }
1864 issueAjaxRequest(verb, path, elt, evt)
1865 })
1866 });
1867 }
1868 });
1869 return explicitAction;
1870 }
1871
1872 function addTriggerHandler(elt, triggerSpec, nodeData, handler) {
1873 if (triggerSpec.sseEvent) {
1874 processSSETrigger(elt, handler, triggerSpec.sseEvent);
1875 } else if (triggerSpec.trigger === "revealed") {
1876 initScrollHandler();
1877 addEventListener(elt, handler, nodeData, triggerSpec);
1878 maybeReveal(elt);
1879 } else if (triggerSpec.trigger === "intersect") {
1880 var observerOptions = {};
1881 if (triggerSpec.root) {
1882 observerOptions.root = querySelectorExt(elt, triggerSpec.root)
1883 }
1884 if (triggerSpec.threshold) {
1885 observerOptions.threshold = parseFloat(triggerSpec.threshold);
1886 }
1887 var observer = new IntersectionObserver(function (entries) {
1888 for (var i = 0; i < entries.length; i++) {
1889 var entry = entries[i];
1890 if (entry.isIntersecting) {
1891 triggerEvent(elt, "intersect");
1892 break;
1893 }
1894 }
1895 }, observerOptions);
1896 observer.observe(elt);
1897 addEventListener(elt, handler, nodeData, triggerSpec);
1898 } else if (triggerSpec.trigger === "load") {
1899 if (!maybeFilterEvent(triggerSpec, elt, makeEvent("load", {elt: elt}))) {
1900 loadImmediately(elt, handler, nodeData, triggerSpec.delay);
1901 }
1902 } else if (triggerSpec.pollInterval > 0) {
1903 nodeData.polling = true;
1904 processPolling(elt, handler, triggerSpec);
1905 } else {
1906 addEventListener(elt, handler, nodeData, triggerSpec);
1907 }
1908 }
1909
1910 function evalScript(script) {
1911 if (!script.htmxExecuted && htmx.config.allowScriptTags &&
1912 (script.type === "text/javascript" || script.type === "module" || script.type === "") ) {
1913 var newScript = getDocument().createElement("script");
1914 forEach(script.attributes, function (attr) {
1915 newScript.setAttribute(attr.name, attr.value);
1916 });
1917 newScript.textContent = script.textContent;
1918 newScript.async = false;
1919 if (htmx.config.inlineScriptNonce) {
1920 newScript.nonce = htmx.config.inlineScriptNonce;
1921 }
1922 var parent = script.parentElement;
1923
1924 try {
1925 parent.insertBefore(newScript, script);
1926 } catch (e) {
1927 logError(e);
1928 } finally {
1929 // remove old script element, but only if it is still in DOM
1930 if (script.parentElement) {
1931 script.parentElement.removeChild(script);
1932 }
1933 }
1934 }
1935 }
1936
1937 function processScripts(elt) {
1938 if (matches(elt, "script")) {
1939 evalScript(elt);
1940 }
1941 forEach(findAll(elt, "script"), function (script) {
1942 evalScript(script);
1943 });
1944 }
1945
1946 function shouldProcessHxOn(elt) {
1947 var attributes = elt.attributes
1948 for (var j = 0; j < attributes.length; j++) {
1949 var attrName = attributes[j].name
1950 if (startsWith(attrName, "hx-on:") || startsWith(attrName, "data-hx-on:") ||
1951 startsWith(attrName, "hx-on-") || startsWith(attrName, "data-hx-on-")) {
1952 return true
1953 }
1954 }
1955 return false
1956 }
1957
1958 function findHxOnWildcardElements(elt) {
1959 var node = null
1960 var elements = []
1961
1962 if (shouldProcessHxOn(elt)) {
1963 elements.push(elt)
1964 }
1965
1966 if (document.evaluate) {
1967 var iter = document.evaluate('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or' +
1968 ' starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]', elt)
1969 while (node = iter.iterateNext()) elements.push(node)
1970 } else {
1971 var allElements = elt.getElementsByTagName("*")
1972 for (var i = 0; i < allElements.length; i++) {
1973 if (shouldProcessHxOn(allElements[i])) {
1974 elements.push(allElements[i])
1975 }
1976 }
1977 }
1978
1979 return elements
1980 }
1981
1982 function findElementsToProcess(elt) {
1983 if (elt.querySelectorAll) {
1984 var boostedSelector = ", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]";
1985 var results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit'], [hx-sse], [data-hx-sse], [hx-ws]," +
1986 " [data-hx-ws], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger], [hx-on], [data-hx-on]");
1987 return results;
1988 } else {
1989 return [];
1990 }
1991 }
1992
1993 // Handle submit buttons/inputs that have the form attribute set
1994 // see https://developer.mozilla.org/docs/Web/HTML/Element/button
1995 function maybeSetLastButtonClicked(evt) {
1996 var elt = closest(evt.target, "button, input[type='submit']");
1997 var internalData = getRelatedFormData(evt)
1998 if (internalData) {
1999 internalData.lastButtonClicked = elt;
2000 }
2001 };
2002 function maybeUnsetLastButtonClicked(evt){
2003 var internalData = getRelatedFormData(evt)
2004 if (internalData) {
2005 internalData.lastButtonClicked = null;
2006 }
2007 }
2008 function getRelatedFormData(evt) {
2009 var elt = closest(evt.target, "button, input[type='submit']");
2010 if (!elt) {
2011 return;
2012 }
2013 var form = resolveTarget('#' + getRawAttribute(elt, 'form')) || closest(elt, 'form');
2014 if (!form) {
2015 return;
2016 }
2017 return getInternalData(form);
2018 }
2019 function initButtonTracking(elt) {
2020 // need to handle both click and focus in:
2021 // focusin - in case someone tabs in to a button and hits the space bar
2022 // click - on OSX buttons do not focus on click see https://bugs.webkit.org/show_bug.cgi?id=13724
2023 elt.addEventListener('click', maybeSetLastButtonClicked)
2024 elt.addEventListener('focusin', maybeSetLastButtonClicked)
2025 elt.addEventListener('focusout', maybeUnsetLastButtonClicked)
2026 }
2027
2028 function countCurlies(line) {
2029 var tokens = tokenizeString(line);
2030 var netCurlies = 0;
2031 for (var i = 0; i < tokens.length; i++) {
2032 const token = tokens[i];
2033 if (token === "{") {
2034 netCurlies++;
2035 } else if (token === "}") {
2036 netCurlies--;
2037 }
2038 }
2039 return netCurlies;
2040 }
2041
2042 function addHxOnEventHandler(elt, eventName, code) {
2043 var nodeData = getInternalData(elt);
2044 if (!Array.isArray(nodeData.onHandlers)) {
2045 nodeData.onHandlers = [];
2046 }
2047 var func;
2048 var listener = function (e) {
2049 return maybeEval(elt, function() {
2050 if (!func) {
2051 func = new Function("event", code);
2052 }
2053 func.call(elt, e);
2054 });
2055 };
2056 elt.addEventListener(eventName, listener);
2057 nodeData.onHandlers.push({event:eventName, listener:listener});
2058 }
2059
2060 function processHxOn(elt) {
2061 var hxOnValue = getAttributeValue(elt, 'hx-on');
2062 if (hxOnValue) {
2063 var handlers = {}
2064 var lines = hxOnValue.split("\n");
2065 var currentEvent = null;
2066 var curlyCount = 0;
2067 while (lines.length > 0) {
2068 var line = lines.shift();
2069 var match = line.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);
2070 if (curlyCount === 0 && match) {
2071 line.split(":")
2072 currentEvent = match[1].slice(0, -1); // strip last colon
2073 handlers[currentEvent] = match[2];
2074 } else {
2075 handlers[currentEvent] += line;
2076 }
2077 curlyCount += countCurlies(line);
2078 }
2079
2080 for (var eventName in handlers) {
2081 addHxOnEventHandler(elt, eventName, handlers[eventName]);
2082 }
2083 }
2084 }
2085
2086 function processHxOnWildcard(elt) {
2087 // wipe any previous on handlers so that this function takes precedence
2088 deInitOnHandlers(elt)
2089
2090 for (var i = 0; i < elt.attributes.length; i++) {
2091 var name = elt.attributes[i].name
2092 var value = elt.attributes[i].value
2093 if (startsWith(name, "hx-on") || startsWith(name, "data-hx-on")) {
2094 var afterOnPosition = name.indexOf("-on") + 3;
2095 var nextChar = name.slice(afterOnPosition, afterOnPosition + 1);
2096 if (nextChar === "-" || nextChar === ":") {
2097 var eventName = name.slice(afterOnPosition + 1);
2098 // if the eventName starts with a colon or dash, prepend "htmx" for shorthand support
2099 if (startsWith(eventName, ":")) {
2100 eventName = "htmx" + eventName
2101 } else if (startsWith(eventName, "-")) {
2102 eventName = "htmx:" + eventName.slice(1);
2103 } else if (startsWith(eventName, "htmx-")) {
2104 eventName = "htmx:" + eventName.slice(5);
2105 }
2106
2107 addHxOnEventHandler(elt, eventName, value)
2108 }
2109 }
2110 }
2111 }
2112
2113 function initNode(elt) {
2114 if (closest(elt, htmx.config.disableSelector)) {
2115 cleanUpElement(elt)
2116 return;
2117 }
2118 var nodeData = getInternalData(elt);
2119 if (nodeData.initHash !== attributeHash(elt)) {
2120 // clean up any previously processed info
2121 deInitNode(elt);
2122
2123 nodeData.initHash = attributeHash(elt);
2124
2125 processHxOn(elt);
2126
2127 triggerEvent(elt, "htmx:beforeProcessNode")
2128
2129 if (elt.value) {
2130 nodeData.lastValue = elt.value;
2131 }
2132
2133 var triggerSpecs = getTriggerSpecs(elt);
2134 var hasExplicitHttpAction = processVerbs(elt, nodeData, triggerSpecs);
2135
2136 if (!hasExplicitHttpAction) {
2137 if (getClosestAttributeValue(elt, "hx-boost") === "true") {
2138 boostElement(elt, nodeData, triggerSpecs);
2139 } else if (hasAttribute(elt, 'hx-trigger')) {
2140 triggerSpecs.forEach(function (triggerSpec) {
2141 // For "naked" triggers, don't do anything at all
2142 addTriggerHandler(elt, triggerSpec, nodeData, function () {
2143 })
2144 })
2145 }
2146 }
2147
2148 // Handle submit buttons/inputs that have the form attribute set
2149 // see https://developer.mozilla.org/docs/Web/HTML/Element/button
2150 if (elt.tagName === "FORM" || (getRawAttribute(elt, "type") === "submit" && hasAttribute(elt, "form"))) {
2151 initButtonTracking(elt)
2152 }
2153
2154 var sseInfo = getAttributeValue(elt, 'hx-sse');
2155 if (sseInfo) {
2156 processSSEInfo(elt, nodeData, sseInfo);
2157 }
2158
2159 var wsInfo = getAttributeValue(elt, 'hx-ws');
2160 if (wsInfo) {
2161 processWebSocketInfo(elt, nodeData, wsInfo);
2162 }
2163 triggerEvent(elt, "htmx:afterProcessNode");
2164 }
2165 }
2166
2167 function processNode(elt) {
2168 elt = resolveTarget(elt);
2169 if (closest(elt, htmx.config.disableSelector)) {
2170 cleanUpElement(elt)
2171 return;
2172 }
2173 initNode(elt);
2174 forEach(findElementsToProcess(elt), function(child) { initNode(child) });
2175 // Because it happens second, the new way of adding onHandlers superseeds the old one
2176 // i.e. if there are any hx-on:eventName attributes, the hx-on attribute will be ignored
2177 forEach(findHxOnWildcardElements(elt), processHxOnWildcard);
2178 }
2179
2180 //====================================================================
2181 // Event/Log Support
2182 //====================================================================
2183
2184 function kebabEventName(str) {
2185 return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
2186 }
2187
2188 function makeEvent(eventName, detail) {
2189 var evt;
2190 if (window.CustomEvent && typeof window.CustomEvent === 'function') {
2191 evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
2192 } else {
2193 evt = getDocument().createEvent('CustomEvent');
2194 evt.initCustomEvent(eventName, true, true, detail);
2195 }
2196 return evt;
2197 }
2198
2199 function triggerErrorEvent(elt, eventName, detail) {
2200 triggerEvent(elt, eventName, mergeObjects({error:eventName}, detail));
2201 }
2202
2203 function ignoreEventForLogging(eventName) {
2204 return eventName === "htmx:afterProcessNode"
2205 }
2206
2207 /**
2208 * `withExtensions` locates all active extensions for a provided element, then
2209 * executes the provided function using each of the active extensions. It should
2210 * be called internally at every extendable execution point in htmx.
2211 *
2212 * @param {HTMLElement} elt
2213 * @param {(extension:import("./htmx").HtmxExtension) => void} toDo
2214 * @returns void
2215 */
2216 function withExtensions(elt, toDo) {
2217 forEach(getExtensions(elt), function(extension){
2218 try {
2219 toDo(extension);
2220 } catch (e) {
2221 logError(e);
2222 }
2223 });
2224 }
2225
2226 function logError(msg) {
2227 if(console.error) {
2228 console.error(msg);
2229 } else if (console.log) {
2230 console.log("ERROR: ", msg);
2231 }
2232 }
2233
2234 function triggerEvent(elt, eventName, detail) {
2235 elt = resolveTarget(elt);
2236 if (detail == null) {
2237 detail = {};
2238 }
2239 detail["elt"] = elt;
2240 var event = makeEvent(eventName, detail);
2241 if (htmx.logger && !ignoreEventForLogging(eventName)) {
2242 htmx.logger(elt, eventName, detail);
2243 }
2244 if (detail.error) {
2245 logError(detail.error);
2246 triggerEvent(elt, "htmx:error", {errorInfo:detail})
2247 }
2248 var eventResult = elt.dispatchEvent(event);
2249 var kebabName = kebabEventName(eventName);
2250 if (eventResult && kebabName !== eventName) {
2251 var kebabedEvent = makeEvent(kebabName, event.detail);
2252 eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
2253 }
2254 withExtensions(elt, function (extension) {
2255 eventResult = eventResult && (extension.onEvent(eventName, event) !== false && !event.defaultPrevented)
2256 });
2257 return eventResult;
2258 }
2259
2260 //====================================================================
2261 // History Support
2262 //====================================================================
2263 var currentPathForHistory = location.pathname+location.search;
2264
2265 function getHistoryElement() {
2266 var historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]');
2267 return historyElt || getDocument().body;
2268 }
2269
2270 function saveToHistoryCache(url, content, title, scroll) {
2271 if (!canAccessLocalStorage()) {
2272 return;
2273 }
2274
2275 if (htmx.config.historyCacheSize <= 0) {
2276 // make sure that an eventually already existing cache is purged
2277 localStorage.removeItem("htmx-history-cache");
2278 return;
2279 }
2280
2281 url = normalizePath(url);
2282
2283 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
2284 for (var i = 0; i < historyCache.length; i++) {
2285 if (historyCache[i].url === url) {
2286 historyCache.splice(i, 1);
2287 break;
2288 }
2289 }
2290 var newHistoryItem = {url:url, content: content, title:title, scroll:scroll};
2291 triggerEvent(getDocument().body, "htmx:historyItemCreated", {item:newHistoryItem, cache: historyCache})
2292 historyCache.push(newHistoryItem)
2293 while (historyCache.length > htmx.config.historyCacheSize) {
2294 historyCache.shift();
2295 }
2296 while(historyCache.length > 0){
2297 try {
2298 localStorage.setItem("htmx-history-cache", JSON.stringify(historyCache));
2299 break;
2300 } catch (e) {
2301 triggerErrorEvent(getDocument().body, "htmx:historyCacheError", {cause:e, cache: historyCache})
2302 historyCache.shift(); // shrink the cache and retry
2303 }
2304 }
2305 }
2306
2307 function getCachedHistory(url) {
2308 if (!canAccessLocalStorage()) {
2309 return null;
2310 }
2311
2312 url = normalizePath(url);
2313
2314 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
2315 for (var i = 0; i < historyCache.length; i++) {
2316 if (historyCache[i].url === url) {
2317 return historyCache[i];
2318 }
2319 }
2320 return null;
2321 }
2322
2323 function cleanInnerHtmlForHistory(elt) {
2324 var className = htmx.config.requestClass;
2325 var clone = elt.cloneNode(true);
2326 forEach(findAll(clone, "." + className), function(child){
2327 removeClassFromElement(child, className);
2328 });
2329 return clone.innerHTML;
2330 }
2331
2332 function saveCurrentPageToHistory() {
2333 var elt = getHistoryElement();
2334 var path = currentPathForHistory || location.pathname+location.search;
2335
2336 // Allow history snapshot feature to be disabled where hx-history="false"
2337 // is present *anywhere* in the current document we're about to save,
2338 // so we can prevent privileged data entering the cache.
2339 // The page will still be reachable as a history entry, but htmx will fetch it
2340 // live from the server onpopstate rather than look in the localStorage cache
2341 var disableHistoryCache
2342 try {
2343 disableHistoryCache = getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')
2344 } catch (e) {
2345 // IE11: insensitive modifier not supported so fallback to case sensitive selector
2346 disableHistoryCache = getDocument().querySelector('[hx-history="false"],[data-hx-history="false"]')
2347 }
2348 if (!disableHistoryCache) {
2349 triggerEvent(getDocument().body, "htmx:beforeHistorySave", {path: path, historyElt: elt});
2350 saveToHistoryCache(path, cleanInnerHtmlForHistory(elt), getDocument().title, window.scrollY);
2351 }
2352
2353 if (htmx.config.historyEnabled) history.replaceState({htmx: true}, getDocument().title, window.location.href);
2354 }
2355
2356 function pushUrlIntoHistory(path) {
2357 // remove the cache buster parameter, if any
2358 if (htmx.config.getCacheBusterParam) {
2359 path = path.replace(/org\.htmx\.cache-buster=[^&]*&?/, '')
2360 if (endsWith(path, '&') || endsWith(path, "?")) {
2361 path = path.slice(0, -1);
2362 }
2363 }
2364 if(htmx.config.historyEnabled) {
2365 history.pushState({htmx:true}, "", path);
2366 }
2367 currentPathForHistory = path;
2368 }
2369
2370 function replaceUrlInHistory(path) {
2371 if(htmx.config.historyEnabled) history.replaceState({htmx:true}, "", path);
2372 currentPathForHistory = path;
2373 }
2374
2375 function settleImmediately(tasks) {
2376 forEach(tasks, function (task) {
2377 task.call();
2378 });
2379 }
2380
2381 function loadHistoryFromServer(path) {
2382 var request = new XMLHttpRequest();
2383 var details = {path: path, xhr:request};
2384 triggerEvent(getDocument().body, "htmx:historyCacheMiss", details);
2385 request.open('GET', path, true);
2386 request.setRequestHeader("HX-Request", "true");
2387 request.setRequestHeader("HX-History-Restore-Request", "true");
2388 request.setRequestHeader("HX-Current-URL", getDocument().location.href);
2389 request.onload = function () {
2390 if (this.status >= 200 && this.status < 400) {
2391 triggerEvent(getDocument().body, "htmx:historyCacheMissLoad", details);
2392 var fragment = makeFragment(this.response);
2393 // @ts-ignore
2394 fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment;
2395 var historyElement = getHistoryElement();
2396 var settleInfo = makeSettleInfo(historyElement);
2397 var title = findTitle(this.response);
2398 if (title) {
2399 var titleElt = find("title");
2400 if (titleElt) {
2401 titleElt.innerHTML = title;
2402 } else {
2403 window.document.title = title;
2404 }
2405 }
2406 // @ts-ignore
2407 swapInnerHTML(historyElement, fragment, settleInfo)
2408 settleImmediately(settleInfo.tasks);
2409 currentPathForHistory = path;
2410 triggerEvent(getDocument().body, "htmx:historyRestore", {path: path, cacheMiss:true, serverResponse:this.response});
2411 } else {
2412 triggerErrorEvent(getDocument().body, "htmx:historyCacheMissLoadError", details);
2413 }
2414 };
2415 request.send();
2416 }
2417
2418 function restoreHistory(path) {
2419 saveCurrentPageToHistory();
2420 path = path || location.pathname+location.search;
2421 var cached = getCachedHistory(path);
2422 if (cached) {
2423 var fragment = makeFragment(cached.content);
2424 var historyElement = getHistoryElement();
2425 var settleInfo = makeSettleInfo(historyElement);
2426 swapInnerHTML(historyElement, fragment, settleInfo)
2427 settleImmediately(settleInfo.tasks);
2428 document.title = cached.title;
2429 setTimeout(function () {
2430 window.scrollTo(0, cached.scroll);
2431 }, 0); // next 'tick', so browser has time to render layout
2432 currentPathForHistory = path;
2433 triggerEvent(getDocument().body, "htmx:historyRestore", {path:path, item:cached});
2434 } else {
2435 if (htmx.config.refreshOnHistoryMiss) {
2436
2437 // @ts-ignore: optional parameter in reload() function throws error
2438 window.location.reload(true);
2439 } else {
2440 loadHistoryFromServer(path);
2441 }
2442 }
2443 }
2444
2445 function addRequestIndicatorClasses(elt) {
2446 var indicators = findAttributeTargets(elt, 'hx-indicator');
2447 if (indicators == null) {
2448 indicators = [elt];
2449 }
2450 forEach(indicators, function (ic) {
2451 var internalData = getInternalData(ic);
2452 internalData.requestCount = (internalData.requestCount || 0) + 1;
2453 ic.classList["add"].call(ic.classList, htmx.config.requestClass);
2454 });
2455 return indicators;
2456 }
2457
2458 function disableElements(elt) {
2459 var disabledElts = findAttributeTargets(elt, 'hx-disabled-elt');
2460 if (disabledElts == null) {
2461 disabledElts = [];
2462 }
2463 forEach(disabledElts, function (disabledElement) {
2464 var internalData = getInternalData(disabledElement);
2465 internalData.requestCount = (internalData.requestCount || 0) + 1;
2466 disabledElement.setAttribute("disabled", "");
2467 });
2468 return disabledElts;
2469 }
2470
2471 function removeRequestIndicators(indicators, disabled) {
2472 forEach(indicators, function (ic) {
2473 var internalData = getInternalData(ic);
2474 internalData.requestCount = (internalData.requestCount || 0) - 1;
2475 if (internalData.requestCount === 0) {
2476 ic.classList["remove"].call(ic.classList, htmx.config.requestClass);
2477 }
2478 });
2479 forEach(disabled, function (disabledElement) {
2480 var internalData = getInternalData(disabledElement);
2481 internalData.requestCount = (internalData.requestCount || 0) - 1;
2482 if (internalData.requestCount === 0) {
2483 disabledElement.removeAttribute('disabled');
2484 }
2485 });
2486 }
2487
2488 //====================================================================
2489 // Input Value Processing
2490 //====================================================================
2491
2492 function haveSeenNode(processed, elt) {
2493 for (var i = 0; i < processed.length; i++) {
2494 var node = processed[i];
2495 if (node.isSameNode(elt)) {
2496 return true;
2497 }
2498 }
2499 return false;
2500 }
2501
2502 function shouldInclude(elt) {
2503 if(elt.name === "" || elt.name == null || elt.disabled || closest(elt, "fieldset[disabled]")) {
2504 return false;
2505 }
2506 // ignore "submitter" types (see jQuery src/serialize.js)
2507 if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
2508 return false;
2509 }
2510 if (elt.type === "checkbox" || elt.type === "radio" ) {
2511 return elt.checked;
2512 }
2513 return true;
2514 }
2515
2516 function addValueToValues(name, value, values) {
2517 // This is a little ugly because both the current value of the named value in the form
2518 // and the new value could be arrays, so we have to handle all four cases :/
2519 if (name != null && value != null) {
2520 var current = values[name];
2521 if (current === undefined) {
2522 values[name] = value;
2523 } else if (Array.isArray(current)) {
2524 if (Array.isArray(value)) {
2525 values[name] = current.concat(value);
2526 } else {
2527 current.push(value);
2528 }
2529 } else {
2530 if (Array.isArray(value)) {
2531 values[name] = [current].concat(value);
2532 } else {
2533 values[name] = [current, value];
2534 }
2535 }
2536 }
2537 }
2538
2539 function processInputValue(processed, values, errors, elt, validate) {
2540 if (elt == null || haveSeenNode(processed, elt)) {
2541 return;
2542 } else {
2543 processed.push(elt);
2544 }
2545 if (shouldInclude(elt)) {
2546 var name = getRawAttribute(elt,"name");
2547 var value = elt.value;
2548 if (elt.multiple && elt.tagName === "SELECT") {
2549 value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value });
2550 }
2551 // include file inputs
2552 if (elt.files) {
2553 value = toArray(elt.files);
2554 }
2555 addValueToValues(name, value, values);
2556 if (validate) {
2557 validateElement(elt, errors);
2558 }
2559 }
2560 if (matches(elt, 'form')) {
2561 var inputs = elt.elements;
2562 forEach(inputs, function(input) {
2563 processInputValue(processed, values, errors, input, validate);
2564 });
2565 }
2566 }
2567
2568 function validateElement(element, errors) {
2569 if (element.willValidate) {
2570 triggerEvent(element, "htmx:validation:validate")
2571 if (!element.checkValidity()) {
2572 errors.push({elt: element, message:element.validationMessage, validity:element.validity});
2573 triggerEvent(element, "htmx:validation:failed", {message:element.validationMessage, validity:element.validity})
2574 }
2575 }
2576 }
2577
2578 /**
2579 * @param {HTMLElement} elt
2580 * @param {string} verb
2581 */
2582 function getInputValues(elt, verb) {
2583 var processed = [];
2584 var values = {};
2585 var formValues = {};
2586 var errors = [];
2587 var internalData = getInternalData(elt);
2588 if (internalData.lastButtonClicked && !bodyContains(internalData.lastButtonClicked)) {
2589 internalData.lastButtonClicked = null
2590 }
2591
2592 // only validate when form is directly submitted and novalidate or formnovalidate are not set
2593 // or if the element has an explicit hx-validate="true" on it
2594 var validate = (matches(elt, 'form') && elt.noValidate !== true) || getAttributeValue(elt, "hx-validate") === "true";
2595 if (internalData.lastButtonClicked) {
2596 validate = validate && internalData.lastButtonClicked.formNoValidate !== true;
2597 }
2598
2599 // for a non-GET include the closest form
2600 if (verb !== 'get') {
2601 processInputValue(processed, formValues, errors, closest(elt, 'form'), validate);
2602 }
2603
2604 // include the element itself
2605 processInputValue(processed, values, errors, elt, validate);
2606
2607 // if a button or submit was clicked last, include its value
2608 if (internalData.lastButtonClicked || elt.tagName === "BUTTON" ||
2609 (elt.tagName === "INPUT" && getRawAttribute(elt, "type") === "submit")) {
2610 var button = internalData.lastButtonClicked || elt
2611 var name = getRawAttribute(button, "name")
2612 addValueToValues(name, button.value, formValues)
2613 }
2614
2615 // include any explicit includes
2616 var includes = findAttributeTargets(elt, "hx-include");
2617 forEach(includes, function(node) {
2618 processInputValue(processed, values, errors, node, validate);
2619 // if a non-form is included, include any input values within it
2620 if (!matches(node, 'form')) {
2621 forEach(node.querySelectorAll(INPUT_SELECTOR), function (descendant) {
2622 processInputValue(processed, values, errors, descendant, validate);
2623 })
2624 }
2625 });
2626
2627 // form values take precedence, overriding the regular values
2628 values = mergeObjects(values, formValues);
2629
2630 return {errors:errors, values:values};
2631 }
2632
2633 function appendParam(returnStr, name, realValue) {
2634 if (returnStr !== "") {
2635 returnStr += "&";
2636 }
2637 if (String(realValue) === "[object Object]") {
2638 realValue = JSON.stringify(realValue);
2639 }
2640 var s = encodeURIComponent(realValue);
2641 returnStr += encodeURIComponent(name) + "=" + s;
2642 return returnStr;
2643 }
2644
2645 function urlEncode(values) {
2646 var returnStr = "";
2647 for (var name in values) {
2648 if (values.hasOwnProperty(name)) {
2649 var value = values[name];
2650 if (Array.isArray(value)) {
2651 forEach(value, function(v) {
2652 returnStr = appendParam(returnStr, name, v);
2653 });
2654 } else {
2655 returnStr = appendParam(returnStr, name, value);
2656 }
2657 }
2658 }
2659 return returnStr;
2660 }
2661
2662 function makeFormData(values) {
2663 var formData = new FormData();
2664 for (var name in values) {
2665 if (values.hasOwnProperty(name)) {
2666 var value = values[name];
2667 if (Array.isArray(value)) {
2668 forEach(value, function(v) {
2669 formData.append(name, v);
2670 });
2671 } else {
2672 formData.append(name, value);
2673 }
2674 }
2675 }
2676 return formData;
2677 }
2678
2679 //====================================================================
2680 // Ajax
2681 //====================================================================
2682
2683 /**
2684 * @param {HTMLElement} elt
2685 * @param {HTMLElement} target
2686 * @param {string} prompt
2687 * @returns {Object} // TODO: Define/Improve HtmxHeaderSpecification
2688 */
2689 function getHeaders(elt, target, prompt) {
2690 var headers = {
2691 "HX-Request" : "true",
2692 "HX-Trigger" : getRawAttribute(elt, "id"),
2693 "HX-Trigger-Name" : getRawAttribute(elt, "name"),
2694 "HX-Target" : getAttributeValue(target, "id"),
2695 "HX-Current-URL" : getDocument().location.href,
2696 }
2697 getValuesForElement(elt, "hx-headers", false, headers)
2698 if (prompt !== undefined) {
2699 headers["HX-Prompt"] = prompt;
2700 }
2701 if (getInternalData(elt).boosted) {
2702 headers["HX-Boosted"] = "true";
2703 }
2704 return headers;
2705 }
2706
2707 /**
2708 * filterValues takes an object containing form input values
2709 * and returns a new object that only contains keys that are
2710 * specified by the closest "hx-params" attribute
2711 * @param {Object} inputValues
2712 * @param {HTMLElement} elt
2713 * @returns {Object}
2714 */
2715 function filterValues(inputValues, elt) {
2716 var paramsValue = getClosestAttributeValue(elt, "hx-params");
2717 if (paramsValue) {
2718 if (paramsValue === "none") {
2719 return {};
2720 } else if (paramsValue === "*") {
2721 return inputValues;
2722 } else if(paramsValue.indexOf("not ") === 0) {
2723 forEach(paramsValue.substr(4).split(","), function (name) {
2724 name = name.trim();
2725 delete inputValues[name];
2726 });
2727 return inputValues;
2728 } else {
2729 var newValues = {}
2730 forEach(paramsValue.split(","), function (name) {
2731 name = name.trim();
2732 newValues[name] = inputValues[name];
2733 });
2734 return newValues;
2735 }
2736 } else {
2737 return inputValues;
2738 }
2739 }
2740
2741 function isAnchorLink(elt) {
2742 return getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf("#") >=0
2743 }
2744
2745 /**
2746 *
2747 * @param {HTMLElement} elt
2748 * @param {string} swapInfoOverride
2749 * @returns {import("./htmx").HtmxSwapSpecification}
2750 */
2751 function getSwapSpecification(elt, swapInfoOverride) {
2752 var swapInfo = swapInfoOverride ? swapInfoOverride : getClosestAttributeValue(elt, "hx-swap");
2753 var swapSpec = {
2754 "swapStyle" : getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle,
2755 "swapDelay" : htmx.config.defaultSwapDelay,
2756 "settleDelay" : htmx.config.defaultSettleDelay
2757 }
2758 if (htmx.config.scrollIntoViewOnBoost && getInternalData(elt).boosted && !isAnchorLink(elt)) {
2759 swapSpec["show"] = "top"
2760 }
2761 if (swapInfo) {
2762 var split = splitOnWhitespace(swapInfo);
2763 if (split.length > 0) {
2764 for (var i = 0; i < split.length; i++) {
2765 var value = split[i];
2766 if (value.indexOf("swap:") === 0) {
2767 swapSpec["swapDelay"] = parseInterval(value.substr(5));
2768 } else if (value.indexOf("settle:") === 0) {
2769 swapSpec["settleDelay"] = parseInterval(value.substr(7));
2770 } else if (value.indexOf("transition:") === 0) {
2771 swapSpec["transition"] = value.substr(11) === "true";
2772 } else if (value.indexOf("ignoreTitle:") === 0) {
2773 swapSpec["ignoreTitle"] = value.substr(12) === "true";
2774 } else if (value.indexOf("scroll:") === 0) {
2775 var scrollSpec = value.substr(7);
2776 var splitSpec = scrollSpec.split(":");
2777 var scrollVal = splitSpec.pop();
2778 var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
2779 swapSpec["scroll"] = scrollVal;
2780 swapSpec["scrollTarget"] = selectorVal;
2781 } else if (value.indexOf("show:") === 0) {
2782 var showSpec = value.substr(5);
2783 var splitSpec = showSpec.split(":");
2784 var showVal = splitSpec.pop();
2785 var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
2786 swapSpec["show"] = showVal;
2787 swapSpec["showTarget"] = selectorVal;
2788 } else if (value.indexOf("focus-scroll:") === 0) {
2789 var focusScrollVal = value.substr("focus-scroll:".length);
2790 swapSpec["focusScroll"] = focusScrollVal == "true";
2791 } else if (i == 0) {
2792 swapSpec["swapStyle"] = value;
2793 } else {
2794 logError('Unknown modifier in hx-swap: ' + value);
2795 }
2796 }
2797 }
2798 }
2799 return swapSpec;
2800 }
2801
2802 function usesFormData(elt) {
2803 return getClosestAttributeValue(elt, "hx-encoding") === "multipart/form-data" ||
2804 (matches(elt, "form") && getRawAttribute(elt, 'enctype') === "multipart/form-data");
2805 }
2806
2807 function encodeParamsForBody(xhr, elt, filteredParameters) {
2808 var encodedParameters = null;
2809 withExtensions(elt, function (extension) {
2810 if (encodedParameters == null) {
2811 encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt);
2812 }
2813 });
2814 if (encodedParameters != null) {
2815 return encodedParameters;
2816 } else {
2817 if (usesFormData(elt)) {
2818 return makeFormData(filteredParameters);
2819 } else {
2820 return urlEncode(filteredParameters);
2821 }
2822 }
2823 }
2824
2825 /**
2826 *
2827 * @param {Element} target
2828 * @returns {import("./htmx").HtmxSettleInfo}
2829 */
2830 function makeSettleInfo(target) {
2831 return {tasks: [], elts: [target]};
2832 }
2833
2834 function updateScrollState(content, swapSpec) {
2835 var first = content[0];
2836 var last = content[content.length - 1];
2837 if (swapSpec.scroll) {
2838 var target = null;
2839 if (swapSpec.scrollTarget) {
2840 target = querySelectorExt(first, swapSpec.scrollTarget);
2841 }
2842 if (swapSpec.scroll === "top" && (first || target)) {
2843 target = target || first;
2844 target.scrollTop = 0;
2845 }
2846 if (swapSpec.scroll === "bottom" && (last || target)) {
2847 target = target || last;
2848 target.scrollTop = target.scrollHeight;
2849 }
2850 }
2851 if (swapSpec.show) {
2852 var target = null;
2853 if (swapSpec.showTarget) {
2854 var targetStr = swapSpec.showTarget;
2855 if (swapSpec.showTarget === "window") {
2856 targetStr = "body";
2857 }
2858 target = querySelectorExt(first, targetStr);
2859 }
2860 if (swapSpec.show === "top" && (first || target)) {
2861 target = target || first;
2862 target.scrollIntoView({block:'start', behavior: htmx.config.scrollBehavior});
2863 }
2864 if (swapSpec.show === "bottom" && (last || target)) {
2865 target = target || last;
2866 target.scrollIntoView({block:'end', behavior: htmx.config.scrollBehavior});
2867 }
2868 }
2869 }
2870
2871 /**
2872 * @param {HTMLElement} elt
2873 * @param {string} attr
2874 * @param {boolean=} evalAsDefault
2875 * @param {Object=} values
2876 * @returns {Object}
2877 */
2878 function getValuesForElement(elt, attr, evalAsDefault, values) {
2879 if (values == null) {
2880 values = {};
2881 }
2882 if (elt == null) {
2883 return values;
2884 }
2885 var attributeValue = getAttributeValue(elt, attr);
2886 if (attributeValue) {
2887 var str = attributeValue.trim();
2888 var evaluateValue = evalAsDefault;
2889 if (str === "unset") {
2890 return null;
2891 }
2892 if (str.indexOf("javascript:") === 0) {
2893 str = str.substr(11);
2894 evaluateValue = true;
2895 } else if (str.indexOf("js:") === 0) {
2896 str = str.substr(3);
2897 evaluateValue = true;
2898 }
2899 if (str.indexOf('{') !== 0) {
2900 str = "{" + str + "}";
2901 }
2902 var varsValues;
2903 if (evaluateValue) {
2904 varsValues = maybeEval(elt,function () {return Function("return (" + str + ")")();}, {});
2905 } else {
2906 varsValues = parseJSON(str);
2907 }
2908 for (var key in varsValues) {
2909 if (varsValues.hasOwnProperty(key)) {
2910 if (values[key] == null) {
2911 values[key] = varsValues[key];
2912 }
2913 }
2914 }
2915 }
2916 return getValuesForElement(parentElt(elt), attr, evalAsDefault, values);
2917 }
2918
2919 function maybeEval(elt, toEval, defaultVal) {
2920 if (htmx.config.allowEval) {
2921 return toEval();
2922 } else {
2923 triggerErrorEvent(elt, 'htmx:evalDisallowedError');
2924 return defaultVal;
2925 }
2926 }
2927
2928 /**
2929 * @param {HTMLElement} elt
2930 * @param {*} expressionVars
2931 * @returns
2932 */
2933 function getHXVarsForElement(elt, expressionVars) {
2934 return getValuesForElement(elt, "hx-vars", true, expressionVars);
2935 }
2936
2937 /**
2938 * @param {HTMLElement} elt
2939 * @param {*} expressionVars
2940 * @returns
2941 */
2942 function getHXValsForElement(elt, expressionVars) {
2943 return getValuesForElement(elt, "hx-vals", false, expressionVars);
2944 }
2945
2946 /**
2947 * @param {HTMLElement} elt
2948 * @returns {Object}
2949 */
2950 function getExpressionVars(elt) {
2951 return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt));
2952 }
2953
2954 function safelySetHeaderValue(xhr, header, headerValue) {
2955 if (headerValue !== null) {
2956 try {
2957 xhr.setRequestHeader(header, headerValue);
2958 } catch (e) {
2959 // On an exception, try to set the header URI encoded instead
2960 xhr.setRequestHeader(header, encodeURIComponent(headerValue));
2961 xhr.setRequestHeader(header + "-URI-AutoEncoded", "true");
2962 }
2963 }
2964 }
2965
2966 function getPathFromResponse(xhr) {
2967 // NB: IE11 does not support this stuff
2968 if (xhr.responseURL && typeof(URL) !== "undefined") {
2969 try {
2970 var url = new URL(xhr.responseURL);
2971 return url.pathname + url.search;
2972 } catch (e) {
2973 triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL});
2974 }
2975 }
2976 }
2977
2978 function hasHeader(xhr, regexp) {
2979 return regexp.test(xhr.getAllResponseHeaders())
2980 }
2981
2982 function ajaxHelper(verb, path, context) {
2983 verb = verb.toLowerCase();
2984 if (context) {
2985 if (context instanceof Element || isType(context, 'String')) {
2986 return issueAjaxRequest(verb, path, null, null, {
2987 targetOverride: resolveTarget(context),
2988 returnPromise: true
2989 });
2990 } else {
2991 return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
2992 {
2993 handler : context.handler,
2994 headers : context.headers,
2995 values : context.values,
2996 targetOverride: resolveTarget(context.target),
2997 swapOverride: context.swap,
2998 select: context.select,
2999 returnPromise: true
3000 });
3001 }
3002 } else {
3003 return issueAjaxRequest(verb, path, null, null, {
3004 returnPromise: true
3005 });
3006 }
3007 }
3008
3009 function hierarchyForElt(elt) {
3010 var arr = [];
3011 while (elt) {
3012 arr.push(elt);
3013 elt = elt.parentElement;
3014 }
3015 return arr;
3016 }
3017
3018 function verifyPath(elt, path, requestConfig) {
3019 var sameHost
3020 var url
3021 if (typeof URL === "function") {
3022 url = new URL(path, document.location.href);
3023 var origin = document.location.origin;
3024 sameHost = origin === url.origin;
3025 } else {
3026 // IE11 doesn't support URL
3027 url = path
3028 sameHost = startsWith(path, document.location.origin)
3029 }
3030
3031 if (htmx.config.selfRequestsOnly) {
3032 if (!sameHost) {
3033 return false;
3034 }
3035 }
3036 return triggerEvent(elt, "htmx:validateUrl", mergeObjects({url: url, sameHost: sameHost}, requestConfig));
3037 }
3038
3039 function issueAjaxRequest(verb, path, elt, event, etc, confirmed) {
3040 var resolve = null;
3041 var reject = null;
3042 etc = etc != null ? etc : {};
3043 if(etc.returnPromise && typeof Promise !== "undefined"){
3044 var promise = new Promise(function (_resolve, _reject) {
3045 resolve = _resolve;
3046 reject = _reject;
3047 });
3048 }
3049 if(elt == null) {
3050 elt = getDocument().body;
3051 }
3052 var responseHandler = etc.handler || handleAjaxResponse;
3053 var select = etc.select || null;
3054
3055 if (!bodyContains(elt)) {
3056 // do not issue requests for elements removed from the DOM
3057 maybeCall(resolve);
3058 return promise;
3059 }
3060 var target = etc.targetOverride || getTarget(elt);
3061 if (target == null || target == DUMMY_ELT) {
3062 triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
3063 maybeCall(reject);
3064 return promise;
3065 }
3066
3067 var eltData = getInternalData(elt);
3068 var submitter = eltData.lastButtonClicked;
3069
3070 if (submitter) {
3071 var buttonPath = getRawAttribute(submitter, "formaction");
3072 if (buttonPath != null) {
3073 path = buttonPath;
3074 }
3075
3076 var buttonVerb = getRawAttribute(submitter, "formmethod")
3077 if (buttonVerb != null) {
3078 // ignore buttons with formmethod="dialog"
3079 if (buttonVerb.toLowerCase() !== "dialog") {
3080 verb = buttonVerb;
3081 }
3082 }
3083 }
3084
3085 var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm");
3086 // allow event-based confirmation w/ a callback
3087 if (confirmed === undefined) {
3088 var issueRequest = function(skipConfirmation) {
3089 return issueAjaxRequest(verb, path, elt, event, etc, !!skipConfirmation);
3090 }
3091 var confirmDetails = {target: target, elt: elt, path: path, verb: verb, triggeringEvent: event, etc: etc, issueRequest: issueRequest, question: confirmQuestion};
3092 if (triggerEvent(elt, 'htmx:confirm', confirmDetails) === false) {
3093 maybeCall(resolve);
3094 return promise;
3095 }
3096 }
3097
3098 var syncElt = elt;
3099 var syncStrategy = getClosestAttributeValue(elt, "hx-sync");
3100 var queueStrategy = null;
3101 var abortable = false;
3102 if (syncStrategy) {
3103 var syncStrings = syncStrategy.split(":");
3104 var selector = syncStrings[0].trim();
3105 if (selector === "this") {
3106 syncElt = findThisElement(elt, 'hx-sync');
3107 } else {
3108 syncElt = querySelectorExt(elt, selector);
3109 }
3110 // default to the drop strategy
3111 syncStrategy = (syncStrings[1] || 'drop').trim();
3112 eltData = getInternalData(syncElt);
3113 if (syncStrategy === "drop" && eltData.xhr && eltData.abortable !== true) {
3114 maybeCall(resolve);
3115 return promise;
3116 } else if (syncStrategy === "abort") {
3117 if (eltData.xhr) {
3118 maybeCall(resolve);
3119 return promise;
3120 } else {
3121 abortable = true;
3122 }
3123 } else if (syncStrategy === "replace") {
3124 triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
3125 } else if (syncStrategy.indexOf("queue") === 0) {
3126 var queueStrArray = syncStrategy.split(" ");
3127 queueStrategy = (queueStrArray[1] || "last").trim();
3128 }
3129 }
3130
3131 if (eltData.xhr) {
3132 if (eltData.abortable) {
3133 triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
3134 } else {
3135 if(queueStrategy == null){
3136 if (event) {
3137 var eventData = getInternalData(event);
3138 if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
3139 queueStrategy = eventData.triggerSpec.queue;
3140 }
3141 }
3142 if (queueStrategy == null) {
3143 queueStrategy = "last";
3144 }
3145 }
3146 if (eltData.queuedRequests == null) {
3147 eltData.queuedRequests = [];
3148 }
3149 if (queueStrategy === "first" && eltData.queuedRequests.length === 0) {
3150 eltData.queuedRequests.push(function () {
3151 issueAjaxRequest(verb, path, elt, event, etc)
3152 });
3153 } else if (queueStrategy === "all") {
3154 eltData.queuedRequests.push(function () {
3155 issueAjaxRequest(verb, path, elt, event, etc)
3156 });
3157 } else if (queueStrategy === "last") {
3158 eltData.queuedRequests = []; // dump existing queue
3159 eltData.queuedRequests.push(function () {
3160 issueAjaxRequest(verb, path, elt, event, etc)
3161 });
3162 }
3163 maybeCall(resolve);
3164 return promise;
3165 }
3166 }
3167
3168 var xhr = new XMLHttpRequest();
3169 eltData.xhr = xhr;
3170 eltData.abortable = abortable;
3171 var endRequestLock = function(){
3172 eltData.xhr = null;
3173 eltData.abortable = false;
3174 if (eltData.queuedRequests != null &&
3175 eltData.queuedRequests.length > 0) {
3176 var queuedRequest = eltData.queuedRequests.shift();
3177 queuedRequest();
3178 }
3179 }
3180 var promptQuestion = getClosestAttributeValue(elt, "hx-prompt");
3181 if (promptQuestion) {
3182 var promptResponse = prompt(promptQuestion);
3183 // prompt returns null if cancelled and empty string if accepted with no entry
3184 if (promptResponse === null ||
3185 !triggerEvent(elt, 'htmx:prompt', {prompt: promptResponse, target:target})) {
3186 maybeCall(resolve);
3187 endRequestLock();
3188 return promise;
3189 }
3190 }
3191
3192 if (confirmQuestion && !confirmed) {
3193 if(!confirm(confirmQuestion)) {
3194 maybeCall(resolve);
3195 endRequestLock()
3196 return promise;
3197 }
3198 }
3199
3200
3201 var headers = getHeaders(elt, target, promptResponse);
3202
3203 if (verb !== 'get' && !usesFormData(elt)) {
3204 headers['Content-Type'] = 'application/x-www-form-urlencoded';
3205 }
3206
3207 if (etc.headers) {
3208 headers = mergeObjects(headers, etc.headers);
3209 }
3210 var results = getInputValues(elt, verb);
3211 var errors = results.errors;
3212 var rawParameters = results.values;
3213 if (etc.values) {
3214 rawParameters = mergeObjects(rawParameters, etc.values);
3215 }
3216 var expressionVars = getExpressionVars(elt);
3217 var allParameters = mergeObjects(rawParameters, expressionVars);
3218 var filteredParameters = filterValues(allParameters, elt);
3219
3220 if (htmx.config.getCacheBusterParam && verb === 'get') {
3221 filteredParameters['org.htmx.cache-buster'] = getRawAttribute(target, "id") || "true";
3222 }
3223
3224 // behavior of anchors w/ empty href is to use the current URL
3225 if (path == null || path === "") {
3226 path = getDocument().location.href;
3227 }
3228
3229
3230 var requestAttrValues = getValuesForElement(elt, 'hx-request');
3231
3232 var eltIsBoosted = getInternalData(elt).boosted;
3233
3234 var useUrlParams = htmx.config.methodsThatUseUrlParams.indexOf(verb) >= 0
3235
3236 var requestConfig = {
3237 boosted: eltIsBoosted,
3238 useUrlParams: useUrlParams,
3239 parameters: filteredParameters,
3240 unfilteredParameters: allParameters,
3241 headers:headers,
3242 target:target,
3243 verb:verb,
3244 errors:errors,
3245 withCredentials: etc.credentials || requestAttrValues.credentials || htmx.config.withCredentials,
3246 timeout: etc.timeout || requestAttrValues.timeout || htmx.config.timeout,
3247 path:path,
3248 triggeringEvent:event
3249 };
3250
3251 if(!triggerEvent(elt, 'htmx:configRequest', requestConfig)){
3252 maybeCall(resolve);
3253 endRequestLock();
3254 return promise;
3255 }
3256
3257 // copy out in case the object was overwritten
3258 path = requestConfig.path;
3259 verb = requestConfig.verb;
3260 headers = requestConfig.headers;
3261 filteredParameters = requestConfig.parameters;
3262 errors = requestConfig.errors;
3263 useUrlParams = requestConfig.useUrlParams;
3264
3265 if(errors && errors.length > 0){
3266 triggerEvent(elt, 'htmx:validation:halted', requestConfig)
3267 maybeCall(resolve);
3268 endRequestLock();
3269 return promise;
3270 }
3271
3272 var splitPath = path.split("#");
3273 var pathNoAnchor = splitPath[0];
3274 var anchor = splitPath[1];
3275
3276 var finalPath = path
3277 if (useUrlParams) {
3278 finalPath = pathNoAnchor;
3279 var values = Object.keys(filteredParameters).length !== 0;
3280 if (values) {
3281 if (finalPath.indexOf("?") < 0) {
3282 finalPath += "?";
3283 } else {
3284 finalPath += "&";
3285 }
3286 finalPath += urlEncode(filteredParameters);
3287 if (anchor) {
3288 finalPath += "#" + anchor;
3289 }
3290 }
3291 }
3292
3293 if (!verifyPath(elt, finalPath, requestConfig)) {
3294 triggerErrorEvent(elt, 'htmx:invalidPath', requestConfig)
3295 maybeCall(reject);
3296 return promise;
3297 };
3298
3299 xhr.open(verb.toUpperCase(), finalPath, true);
3300 xhr.overrideMimeType("text/html");
3301 xhr.withCredentials = requestConfig.withCredentials;
3302 xhr.timeout = requestConfig.timeout;
3303
3304 // request headers
3305 if (requestAttrValues.noHeaders) {
3306 // ignore all headers
3307 } else {
3308 for (var header in headers) {
3309 if (headers.hasOwnProperty(header)) {
3310 var headerValue = headers[header];
3311 safelySetHeaderValue(xhr, header, headerValue);
3312 }
3313 }
3314 }
3315
3316 var responseInfo = {
3317 xhr: xhr, target: target, requestConfig: requestConfig, etc: etc, boosted: eltIsBoosted, select: select,
3318 pathInfo: {
3319 requestPath: path,
3320 finalRequestPath: finalPath,
3321 anchor: anchor
3322 }
3323 };
3324
3325 xhr.onload = function () {
3326 try {
3327 var hierarchy = hierarchyForElt(elt);
3328 responseInfo.pathInfo.responsePath = getPathFromResponse(xhr);
3329 responseHandler(elt, responseInfo);
3330 removeRequestIndicators(indicators, disableElts);
3331 triggerEvent(elt, 'htmx:afterRequest', responseInfo);
3332 triggerEvent(elt, 'htmx:afterOnLoad', responseInfo);
3333 // if the body no longer contains the element, trigger the event on the closest parent
3334 // remaining in the DOM
3335 if (!bodyContains(elt)) {
3336 var secondaryTriggerElt = null;
3337 while (hierarchy.length > 0 && secondaryTriggerElt == null) {
3338 var parentEltInHierarchy = hierarchy.shift();
3339 if (bodyContains(parentEltInHierarchy)) {
3340 secondaryTriggerElt = parentEltInHierarchy;
3341 }
3342 }
3343 if (secondaryTriggerElt) {
3344 triggerEvent(secondaryTriggerElt, 'htmx:afterRequest', responseInfo);
3345 triggerEvent(secondaryTriggerElt, 'htmx:afterOnLoad', responseInfo);
3346 }
3347 }
3348 maybeCall(resolve);
3349 endRequestLock();
3350 } catch (e) {
3351 triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo));
3352 throw e;
3353 }
3354 }
3355 xhr.onerror = function () {
3356 removeRequestIndicators(indicators, disableElts);
3357 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
3358 triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
3359 maybeCall(reject);
3360 endRequestLock();
3361 }
3362 xhr.onabort = function() {
3363 removeRequestIndicators(indicators, disableElts);
3364 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
3365 triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo);
3366 maybeCall(reject);
3367 endRequestLock();
3368 }
3369 xhr.ontimeout = function() {
3370 removeRequestIndicators(indicators, disableElts);
3371 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
3372 triggerErrorEvent(elt, 'htmx:timeout', responseInfo);
3373 maybeCall(reject);
3374 endRequestLock();
3375 }
3376 if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)){
3377 maybeCall(resolve);
3378 endRequestLock()
3379 return promise
3380 }
3381 var indicators = addRequestIndicatorClasses(elt);
3382 var disableElts = disableElements(elt);
3383
3384 forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
3385 forEach([xhr, xhr.upload], function (target) {
3386 target.addEventListener(eventName, function(event){
3387 triggerEvent(elt, "htmx:xhr:" + eventName, {
3388 lengthComputable:event.lengthComputable,
3389 loaded:event.loaded,
3390 total:event.total
3391 });
3392 })
3393 });
3394 });
3395 triggerEvent(elt, 'htmx:beforeSend', responseInfo);
3396 var params = useUrlParams ? null : encodeParamsForBody(xhr, elt, filteredParameters)
3397 xhr.send(params);
3398 return promise;
3399 }
3400
3401 function determineHistoryUpdates(elt, responseInfo) {
3402
3403 var xhr = responseInfo.xhr;
3404
3405 //===========================================
3406 // First consult response headers
3407 //===========================================
3408 var pathFromHeaders = null;
3409 var typeFromHeaders = null;
3410 if (hasHeader(xhr,/HX-Push:/i)) {
3411 pathFromHeaders = xhr.getResponseHeader("HX-Push");
3412 typeFromHeaders = "push";
3413 } else if (hasHeader(xhr,/HX-Push-Url:/i)) {
3414 pathFromHeaders = xhr.getResponseHeader("HX-Push-Url");
3415 typeFromHeaders = "push";
3416 } else if (hasHeader(xhr,/HX-Replace-Url:/i)) {
3417 pathFromHeaders = xhr.getResponseHeader("HX-Replace-Url");
3418 typeFromHeaders = "replace";
3419 }
3420
3421 // if there was a response header, that has priority
3422 if (pathFromHeaders) {
3423 if (pathFromHeaders === "false") {
3424 return {}
3425 } else {
3426 return {
3427 type: typeFromHeaders,
3428 path : pathFromHeaders
3429 }
3430 }
3431 }
3432
3433 //===========================================
3434 // Next resolve via DOM values
3435 //===========================================
3436 var requestPath = responseInfo.pathInfo.finalRequestPath;
3437 var responsePath = responseInfo.pathInfo.responsePath;
3438
3439 var pushUrl = getClosestAttributeValue(elt, "hx-push-url");
3440 var replaceUrl = getClosestAttributeValue(elt, "hx-replace-url");
3441 var elementIsBoosted = getInternalData(elt).boosted;
3442
3443 var saveType = null;
3444 var path = null;
3445
3446 if (pushUrl) {
3447 saveType = "push";
3448 path = pushUrl;
3449 } else if (replaceUrl) {
3450 saveType = "replace";
3451 path = replaceUrl;
3452 } else if (elementIsBoosted) {
3453 saveType = "push";
3454 path = responsePath || requestPath; // if there is no response path, go with the original request path
3455 }
3456
3457 if (path) {
3458 // false indicates no push, return empty object
3459 if (path === "false") {
3460 return {};
3461 }
3462
3463 // true indicates we want to follow wherever the server ended up sending us
3464 if (path === "true") {
3465 path = responsePath || requestPath; // if there is no response path, go with the original request path
3466 }
3467
3468 // restore any anchor associated with the request
3469 if (responseInfo.pathInfo.anchor &&
3470 path.indexOf("#") === -1) {
3471 path = path + "#" + responseInfo.pathInfo.anchor;
3472 }
3473
3474 return {
3475 type:saveType,
3476 path: path
3477 }
3478 } else {
3479 return {};
3480 }
3481 }
3482
3483 function handleAjaxResponse(elt, responseInfo) {
3484 var xhr = responseInfo.xhr;
3485 var target = responseInfo.target;
3486 var etc = responseInfo.etc;
3487 var requestConfig = responseInfo.requestConfig;
3488 var select = responseInfo.select;
3489
3490 if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return;
3491
3492 if (hasHeader(xhr, /HX-Trigger:/i)) {
3493 handleTrigger(xhr, "HX-Trigger", elt);
3494 }
3495
3496 if (hasHeader(xhr, /HX-Location:/i)) {
3497 saveCurrentPageToHistory();
3498 var redirectPath = xhr.getResponseHeader("HX-Location");
3499 var swapSpec;
3500 if (redirectPath.indexOf("{") === 0) {
3501 swapSpec = parseJSON(redirectPath);
3502 // what's the best way to throw an error if the user didn't include this
3503 redirectPath = swapSpec['path'];
3504 delete swapSpec['path'];
3505 }
3506 ajaxHelper('GET', redirectPath, swapSpec).then(function(){
3507 pushUrlIntoHistory(redirectPath);
3508 });
3509 return;
3510 }
3511
3512 var shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && "true" === xhr.getResponseHeader("HX-Refresh");
3513
3514 if (hasHeader(xhr, /HX-Redirect:/i)) {
3515 location.href = xhr.getResponseHeader("HX-Redirect");
3516 shouldRefresh && location.reload();
3517 return;
3518 }
3519
3520 if (shouldRefresh) {
3521 location.reload();
3522 return;
3523 }
3524
3525 if (hasHeader(xhr,/HX-Retarget:/i)) {
3526 if (xhr.getResponseHeader("HX-Retarget") === "this") {
3527 responseInfo.target = elt;
3528 } else {
3529 responseInfo.target = querySelectorExt(elt, xhr.getResponseHeader("HX-Retarget"));
3530 }
3531 }
3532
3533 var historyUpdate = determineHistoryUpdates(elt, responseInfo);
3534
3535 // by default htmx only swaps on 200 return codes and does not swap
3536 // on 204 'No Content'
3537 // this can be ovverriden by responding to the htmx:beforeSwap event and
3538 // overriding the detail.shouldSwap property
3539 var shouldSwap = xhr.status >= 200 && xhr.status < 400 && xhr.status !== 204;
3540 var serverResponse = xhr.response;
3541 var isError = xhr.status >= 400;
3542 var ignoreTitle = htmx.config.ignoreTitle
3543 var beforeSwapDetails = mergeObjects({shouldSwap: shouldSwap, serverResponse:serverResponse, isError:isError, ignoreTitle:ignoreTitle }, responseInfo);
3544 if (!triggerEvent(target, 'htmx:beforeSwap', beforeSwapDetails)) return;
3545
3546 target = beforeSwapDetails.target; // allow re-targeting
3547 serverResponse = beforeSwapDetails.serverResponse; // allow updating content
3548 isError = beforeSwapDetails.isError; // allow updating error
3549 ignoreTitle = beforeSwapDetails.ignoreTitle; // allow updating ignoring title
3550
3551 responseInfo.target = target; // Make updated target available to response events
3552 responseInfo.failed = isError; // Make failed property available to response events
3553 responseInfo.successful = !isError; // Make successful property available to response events
3554
3555 if (beforeSwapDetails.shouldSwap) {
3556 if (xhr.status === 286) {
3557 cancelPolling(elt);
3558 }
3559
3560 withExtensions(elt, function (extension) {
3561 serverResponse = extension.transformResponse(serverResponse, xhr, elt);
3562 });
3563
3564 // Save current page if there will be a history update
3565 if (historyUpdate.type) {
3566 saveCurrentPageToHistory();
3567 }
3568
3569 var swapOverride = etc.swapOverride;
3570 if (hasHeader(xhr,/HX-Reswap:/i)) {
3571 swapOverride = xhr.getResponseHeader("HX-Reswap");
3572 }
3573 var swapSpec = getSwapSpecification(elt, swapOverride);
3574
3575 if (swapSpec.hasOwnProperty('ignoreTitle')) {
3576 ignoreTitle = swapSpec.ignoreTitle;
3577 }
3578
3579 target.classList.add(htmx.config.swappingClass);
3580
3581 // optional transition API promise callbacks
3582 var settleResolve = null;
3583 var settleReject = null;
3584
3585 var doSwap = function () {
3586 try {
3587 var activeElt = document.activeElement;
3588 var selectionInfo = {};
3589 try {
3590 selectionInfo = {
3591 elt: activeElt,
3592 // @ts-ignore
3593 start: activeElt ? activeElt.selectionStart : null,
3594 // @ts-ignore
3595 end: activeElt ? activeElt.selectionEnd : null
3596 };
3597 } catch (e) {
3598 // safari issue - see https://github.com/microsoft/playwright/issues/5894
3599 }
3600
3601 var selectOverride;
3602 if (select) {
3603 selectOverride = select;
3604 }
3605
3606 if (hasHeader(xhr, /HX-Reselect:/i)) {
3607 selectOverride = xhr.getResponseHeader("HX-Reselect");
3608 }
3609
3610 // if we need to save history, do so, before swapping so that relative resources have the correct base URL
3611 if (historyUpdate.type) {
3612 triggerEvent(getDocument().body, 'htmx:beforeHistoryUpdate', mergeObjects({ history: historyUpdate }, responseInfo));
3613 if (historyUpdate.type === "push") {
3614 pushUrlIntoHistory(historyUpdate.path);
3615 triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path: historyUpdate.path});
3616 } else {
3617 replaceUrlInHistory(historyUpdate.path);
3618 triggerEvent(getDocument().body, 'htmx:replacedInHistory', {path: historyUpdate.path});
3619 }
3620 }
3621
3622 var settleInfo = makeSettleInfo(target);
3623 selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo, selectOverride);
3624
3625 if (selectionInfo.elt &&
3626 !bodyContains(selectionInfo.elt) &&
3627 getRawAttribute(selectionInfo.elt, "id")) {
3628 var newActiveElt = document.getElementById(getRawAttribute(selectionInfo.elt, "id"));
3629 var focusOptions = { preventScroll: swapSpec.focusScroll !== undefined ? !swapSpec.focusScroll : !htmx.config.defaultFocusScroll };
3630 if (newActiveElt) {
3631 // @ts-ignore
3632 if (selectionInfo.start && newActiveElt.setSelectionRange) {
3633 // @ts-ignore
3634 try {
3635 newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
3636 } catch (e) {
3637 // the setSelectionRange method is present on fields that don't support it, so just let this fail
3638 }
3639 }
3640 newActiveElt.focus(focusOptions);
3641 }
3642 }
3643
3644 target.classList.remove(htmx.config.swappingClass);
3645 forEach(settleInfo.elts, function (elt) {
3646 if (elt.classList) {
3647 elt.classList.add(htmx.config.settlingClass);
3648 }
3649 triggerEvent(elt, 'htmx:afterSwap', responseInfo);
3650 });
3651
3652 if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
3653 var finalElt = elt;
3654 if (!bodyContains(elt)) {
3655 finalElt = getDocument().body;
3656 }
3657 handleTrigger(xhr, "HX-Trigger-After-Swap", finalElt);
3658 }
3659
3660 var doSettle = function () {
3661 forEach(settleInfo.tasks, function (task) {
3662 task.call();
3663 });
3664 forEach(settleInfo.elts, function (elt) {
3665 if (elt.classList) {
3666 elt.classList.remove(htmx.config.settlingClass);
3667 }
3668 triggerEvent(elt, 'htmx:afterSettle', responseInfo);
3669 });
3670
3671 if (responseInfo.pathInfo.anchor) {
3672 var anchorTarget = getDocument().getElementById(responseInfo.pathInfo.anchor);
3673 if(anchorTarget) {
3674 anchorTarget.scrollIntoView({block:'start', behavior: "auto"});
3675 }
3676 }
3677
3678 if(settleInfo.title && !ignoreTitle) {
3679 var titleElt = find("title");
3680 if(titleElt) {
3681 titleElt.innerHTML = settleInfo.title;
3682 } else {
3683 window.document.title = settleInfo.title;
3684 }
3685 }
3686
3687 updateScrollState(settleInfo.elts, swapSpec);
3688
3689 if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
3690 var finalElt = elt;
3691 if (!bodyContains(elt)) {
3692 finalElt = getDocument().body;
3693 }
3694 handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt);
3695 }
3696 maybeCall(settleResolve);
3697 }
3698
3699 if (swapSpec.settleDelay > 0) {
3700 setTimeout(doSettle, swapSpec.settleDelay)
3701 } else {
3702 doSettle();
3703 }
3704 } catch (e) {
3705 triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
3706 maybeCall(settleReject);
3707 throw e;
3708 }
3709 };
3710
3711 var shouldTransition = htmx.config.globalViewTransitions
3712 if(swapSpec.hasOwnProperty('transition')){
3713 shouldTransition = swapSpec.transition;
3714 }
3715
3716 if(shouldTransition &&
3717 triggerEvent(elt, 'htmx:beforeTransition', responseInfo) &&
3718 typeof Promise !== "undefined" && document.startViewTransition){
3719 var settlePromise = new Promise(function (_resolve, _reject) {
3720 settleResolve = _resolve;
3721 settleReject = _reject;
3722 });
3723 // wrap the original doSwap() in a call to startViewTransition()
3724 var innerDoSwap = doSwap;
3725 doSwap = function() {
3726 document.startViewTransition(function () {
3727 innerDoSwap();
3728 return settlePromise;
3729 });
3730 }
3731 }
3732
3733
3734 if (swapSpec.swapDelay > 0) {
3735 setTimeout(doSwap, swapSpec.swapDelay)
3736 } else {
3737 doSwap();
3738 }
3739 }
3740 if (isError) {
3741 triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + xhr.status + " from " + responseInfo.pathInfo.requestPath}, responseInfo));
3742 }
3743 }
3744
3745 //====================================================================
3746 // Extensions API
3747 //====================================================================
3748
3749 /** @type {Object<string, import("./htmx").HtmxExtension>} */
3750 var extensions = {};
3751
3752 /**
3753 * extensionBase defines the default functions for all extensions.
3754 * @returns {import("./htmx").HtmxExtension}
3755 */
3756 function extensionBase() {
3757 return {
3758 init: function(api) {return null;},
3759 onEvent : function(name, evt) {return true;},
3760 transformResponse : function(text, xhr, elt) {return text;},
3761 isInlineSwap : function(swapStyle) {return false;},
3762 handleSwap : function(swapStyle, target, fragment, settleInfo) {return false;},
3763 encodeParameters : function(xhr, parameters, elt) {return null;}
3764 }
3765 }
3766
3767 /**
3768 * defineExtension initializes the extension and adds it to the htmx registry
3769 *
3770 * @param {string} name
3771 * @param {import("./htmx").HtmxExtension} extension
3772 */
3773 function defineExtension(name, extension) {
3774 if(extension.init) {
3775 extension.init(internalAPI)
3776 }
3777 extensions[name] = mergeObjects(extensionBase(), extension);
3778 }
3779
3780 /**
3781 * removeExtension removes an extension from the htmx registry
3782 *
3783 * @param {string} name
3784 */
3785 function removeExtension(name) {
3786 delete extensions[name];
3787 }
3788
3789 /**
3790 * getExtensions searches up the DOM tree to return all extensions that can be applied to a given element
3791 *
3792 * @param {HTMLElement} elt
3793 * @param {import("./htmx").HtmxExtension[]=} extensionsToReturn
3794 * @param {import("./htmx").HtmxExtension[]=} extensionsToIgnore
3795 */
3796 function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
3797
3798 if (elt == undefined) {
3799 return extensionsToReturn;
3800 }
3801 if (extensionsToReturn == undefined) {
3802 extensionsToReturn = [];
3803 }
3804 if (extensionsToIgnore == undefined) {
3805 extensionsToIgnore = [];
3806 }
3807 var extensionsForElement = getAttributeValue(elt, "hx-ext");
3808 if (extensionsForElement) {
3809 forEach(extensionsForElement.split(","), function(extensionName){
3810 extensionName = extensionName.replace(/ /g, '');
3811 if (extensionName.slice(0, 7) == "ignore:") {
3812 extensionsToIgnore.push(extensionName.slice(7));
3813 return;
3814 }
3815 if (extensionsToIgnore.indexOf(extensionName) < 0) {
3816 var extension = extensions[extensionName];
3817 if (extension && extensionsToReturn.indexOf(extension) < 0) {
3818 extensionsToReturn.push(extension);
3819 }
3820 }
3821 });
3822 }
3823 return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore);
3824 }
3825
3826 //====================================================================
3827 // Initialization
3828 //====================================================================
3829 var isReady = false
3830 getDocument().addEventListener('DOMContentLoaded', function() {
3831 isReady = true
3832 })
3833
3834 /**
3835 * Execute a function now if DOMContentLoaded has fired, otherwise listen for it.
3836 *
3837 * This function uses isReady because there is no realiable way to ask the browswer whether
3838 * the DOMContentLoaded event has already been fired; there's a gap between DOMContentLoaded
3839 * firing and readystate=complete.
3840 */
3841 function ready(fn) {
3842 // Checking readyState here is a failsafe in case the htmx script tag entered the DOM by
3843 // some means other than the initial page load.
3844 if (isReady || getDocument().readyState === 'complete') {
3845 fn();
3846 } else {
3847 getDocument().addEventListener('DOMContentLoaded', fn);
3848 }
3849 }
3850
3851 function insertIndicatorStyles() {
3852 if (htmx.config.includeIndicatorStyles !== false) {
3853 getDocument().head.insertAdjacentHTML("beforeend",
3854 "<style>\
3855 ." + htmx.config.indicatorClass + "{opacity:0}\
3856 ." + htmx.config.requestClass + " ." + htmx.config.indicatorClass + "{opacity:1; transition: opacity 200ms ease-in;}\
3857 ." + htmx.config.requestClass + "." + htmx.config.indicatorClass + "{opacity:1; transition: opacity 200ms ease-in;}\
3858 </style>");
3859 }
3860 }
3861
3862 function getMetaConfig() {
3863 var element = getDocument().querySelector('meta[name="htmx-config"]');
3864 if (element) {
3865 // @ts-ignore
3866 return parseJSON(element.content);
3867 } else {
3868 return null;
3869 }
3870 }
3871
3872 function mergeMetaConfig() {
3873 var metaConfig = getMetaConfig();
3874 if (metaConfig) {
3875 htmx.config = mergeObjects(htmx.config , metaConfig)
3876 }
3877 }
3878
3879 // initialize the document
3880 ready(function () {
3881 mergeMetaConfig();
3882 insertIndicatorStyles();
3883 var body = getDocument().body;
3884 processNode(body);
3885 var restoredElts = getDocument().querySelectorAll(
3886 "[hx-trigger='restored'],[data-hx-trigger='restored']"
3887 );
3888 body.addEventListener("htmx:abort", function (evt) {
3889 var target = evt.target;
3890 var internalData = getInternalData(target);
3891 if (internalData && internalData.xhr) {
3892 internalData.xhr.abort();
3893 }
3894 });
3895 /** @type {(ev: PopStateEvent) => any} */
3896 const originalPopstate = window.onpopstate ? window.onpopstate.bind(window) : null;
3897 /** @type {(ev: PopStateEvent) => any} */
3898 window.onpopstate = function (event) {
3899 if (event.state && event.state.htmx) {
3900 restoreHistory();
3901 forEach(restoredElts, function(elt){
3902 triggerEvent(elt, 'htmx:restored', {
3903 'document': getDocument(),
3904 'triggerEvent': triggerEvent
3905 });
3906 });
3907 } else {
3908 if (originalPopstate) {
3909 originalPopstate(event);
3910 }
3911 }
3912 };
3913 setTimeout(function () {
3914 triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event
3915 body = null; // kill reference for gc
3916 }, 0);
3917 })
3918
3919 return htmx;
3920 }
3921)()
3922}));