UNPKG

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