UNPKG

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