UNPKG

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