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