UNPKG

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