detached 274b490

Cleanup unused js libs

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

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close