UNPKG

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