var htmx = (function() { 'use strict' // Public API const htmx = { // Tsc madness here, assigning the functions directly results in an invalid TypeScript output, but reassigning is fine /* Event processing */ /** @type {typeof onLoadHelper} */ onLoad: null, /** @type {typeof processNode} */ process: null, /** @type {typeof addEventListenerImpl} */ on: null, /** @type {typeof removeEventListenerImpl} */ off: null, /** @type {typeof triggerEvent} */ trigger: null, /** @type {typeof ajaxHelper} */ ajax: null, /* DOM querying helpers */ /** @type {typeof find} */ find: null, /** @type {typeof findAll} */ findAll: null, /** @type {typeof closest} */ closest: null, /** * Returns the input values that would resolve for a given element via the htmx value resolution mechanism * * @see https://htmx.org/api/#values * * @param {Element} elt the element to resolve values on * @param {HttpVerb} type the request type (e.g. **get** or **post**) non-GET's will include the enclosing form of the element. Defaults to **post** * @returns {Object} */ values: function(elt, type) { const inputValues = getInputValues(elt, type || 'post') return inputValues.values }, /* DOM manipulation helpers */ /** @type {typeof removeElement} */ remove: null, /** @type {typeof addClassToElement} */ addClass: null, /** @type {typeof removeClassFromElement} */ removeClass: null, /** @type {typeof toggleClassOnElement} */ toggleClass: null, /** @type {typeof takeClassForElement} */ takeClass: null, /** @type {typeof swap} */ swap: null, /* Extension entrypoints */ /** @type {typeof defineExtension} */ defineExtension: null, /** @type {typeof removeExtension} */ removeExtension: null, /* Debugging */ /** @type {typeof logAll} */ logAll: null, /** @type {typeof logNone} */ logNone: null, /* Debugging */ /** * The logger htmx uses to log with * * @see https://htmx.org/api/#logger */ logger: null, /** * A property holding the configuration htmx uses at runtime. * * Note that using a [meta tag](https://htmx.org/docs/#config) is the preferred mechanism for setting these properties. * * @see https://htmx.org/api/#config */ config: { /** * Whether to use history. * @type boolean * @default true */ historyEnabled: true, /** * The number of pages to keep in **localStorage** for history support. * @type number * @default 10 */ historyCacheSize: 10, /** * @type boolean * @default false */ refreshOnHistoryMiss: false, /** * The default swap style to use if **[hx-swap](https://htmx.org/attributes/hx-swap)** is omitted. * @type HtmxSwapStyle * @default 'innerHTML' */ defaultSwapStyle: 'innerHTML', /** * The default delay between receiving a response from the server and doing the swap. * @type number * @default 0 */ defaultSwapDelay: 0, /** * The default delay between completing the content swap and settling attributes. * @type number * @default 20 */ defaultSettleDelay: 20, /** * If true, htmx will inject a small amount of CSS into the page to make indicators invisible unless the **htmx-indicator** class is present. * @type boolean * @default true */ includeIndicatorStyles: true, /** * The class to place on indicators when a request is in flight. * @type string * @default 'htmx-indicator' */ indicatorClass: 'htmx-indicator', /** * The class to place on triggering elements when a request is in flight. * @type string * @default 'htmx-request' */ requestClass: 'htmx-request', /** * The class to temporarily place on elements that htmx has added to the DOM. * @type string * @default 'htmx-added' */ addedClass: 'htmx-added', /** * The class to place on target elements when htmx is in the settling phase. * @type string * @default 'htmx-settling' */ settlingClass: 'htmx-settling', /** * The class to place on target elements when htmx is in the swapping phase. * @type string * @default 'htmx-swapping' */ swappingClass: 'htmx-swapping', /** * Allows the use of eval-like functionality in htmx, to enable **hx-vars**, trigger conditions & script tag evaluation. Can be set to **false** for CSP compatibility. * @type boolean * @default true */ allowEval: true, /** * If set to false, disables the interpretation of script tags. * @type boolean * @default true */ allowScriptTags: true, /** * If set, the nonce will be added to inline scripts. * @type string * @default '' */ inlineScriptNonce: '', /** * If set, the nonce will be added to inline styles. * @type string * @default '' */ inlineStyleNonce: '', /** * The attributes to settle during the settling phase. * @type string[] * @default ['class', 'style', 'width', 'height'] */ attributesToSettle: ['class', 'style', 'width', 'height'], /** * Allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates. * @type boolean * @default false */ withCredentials: false, /** * @type number * @default 0 */ timeout: 0, /** * The default implementation of **getWebSocketReconnectDelay** for reconnecting after unexpected connection loss by the event code **Abnormal Closure**, **Service Restart** or **Try Again Later**. * @type {'full-jitter' | ((retryCount:number) => number)} * @default "full-jitter" */ wsReconnectDelay: 'full-jitter', /** * The type of binary data being received over the WebSocket connection * @type BinaryType * @default 'blob' */ wsBinaryType: 'blob', /** * @type string * @default '[hx-disable], [data-hx-disable]' */ disableSelector: '[hx-disable], [data-hx-disable]', /** * @type {'auto' | 'instant' | 'smooth'} * @default 'instant' */ scrollBehavior: 'instant', /** * If the focused element should be scrolled into view. * @type boolean * @default false */ defaultFocusScroll: false, /** * If set to true htmx will include a cache-busting parameter in GET requests to avoid caching partial responses by the browser * @type boolean * @default false */ getCacheBusterParam: false, /** * If set to true, htmx will use the View Transition API when swapping in new content. * @type boolean * @default false */ globalViewTransitions: false, /** * htmx will format requests with these methods by encoding their parameters in the URL, not the request body * @type {(HttpVerb)[]} * @default ['get', 'delete'] */ methodsThatUseUrlParams: ['get', 'delete'], /** * If set to true, disables htmx-based requests to non-origin hosts. * @type boolean * @default false */ selfRequestsOnly: true, /** * If set to true htmx will not update the title of the document when a title tag is found in new content * @type boolean * @default false */ ignoreTitle: false, /** * Whether the target of a boosted element is scrolled into the viewport. * @type boolean * @default true */ scrollIntoViewOnBoost: true, /** * The cache to store evaluated trigger specifications into. * You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) * @type {Object|null} * @default null */ triggerSpecsCache: null, /** @type boolean */ disableInheritance: false, /** @type HtmxResponseHandlingConfig[] */ responseHandling: [ { code: '204', swap: false }, { code: '[23]..', swap: true }, { code: '[45]..', swap: false, error: true } ], /** * Whether to process OOB swaps on elements that are nested within the main response element. * @type boolean * @default true */ allowNestedOobSwaps: true }, /** @type {typeof parseInterval} */ parseInterval: null, /** @type {typeof internalEval} */ _: null, version: '2.0.2' } // Tsc madness part 2 htmx.onLoad = onLoadHelper htmx.process = processNode htmx.on = addEventListenerImpl htmx.off = removeEventListenerImpl htmx.trigger = triggerEvent htmx.ajax = ajaxHelper htmx.find = find htmx.findAll = findAll htmx.closest = closest htmx.remove = removeElement htmx.addClass = addClassToElement htmx.removeClass = removeClassFromElement htmx.toggleClass = toggleClassOnElement htmx.takeClass = takeClassForElement htmx.swap = swap htmx.defineExtension = defineExtension htmx.removeExtension = removeExtension htmx.logAll = logAll htmx.logNone = logNone htmx.parseInterval = parseInterval htmx._ = internalEval const internalAPI = { addTriggerHandler, bodyContains, canAccessLocalStorage, findThisElement, filterValues, swap, hasAttribute, getAttributeValue, getClosestAttributeValue, getClosestMatch, getExpressionVars, getHeaders, getInputValues, getInternalData, getSwapSpecification, getTriggerSpecs, getTarget, makeFragment, mergeObjects, makeSettleInfo, oobSwap, querySelectorExt, settleImmediately, shouldCancel, triggerEvent, triggerErrorEvent, withExtensions } const VERBS = ['get', 'post', 'put', 'delete', 'patch'] const VERB_SELECTOR = VERBS.map(function(verb) { return '[hx-' + verb + '], [data-hx-' + verb + ']' }).join(', ') const HEAD_TAG_REGEX = makeTagRegEx('head') //= =================================================================== // Utilities //= =================================================================== /** * @param {string} tag * @param {boolean} global * @returns {RegExp} */ function makeTagRegEx(tag, global = false) { return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`, global ? 'gim' : 'im') } /** * Parses an interval string consistent with the way htmx does. Useful for plugins that have timing-related attributes. * * Caution: Accepts an int followed by either **s** or **ms**. All other values use **parseFloat** * * @see https://htmx.org/api/#parseInterval * * @param {string} str timing string * @returns {number|undefined} */ function parseInterval(str) { if (str == undefined) { return undefined } let interval = NaN if (str.slice(-2) == 'ms') { interval = parseFloat(str.slice(0, -2)) } else if (str.slice(-1) == 's') { interval = parseFloat(str.slice(0, -1)) * 1000 } else if (str.slice(-1) == 'm') { interval = parseFloat(str.slice(0, -1)) * 1000 * 60 } else { interval = parseFloat(str) } return isNaN(interval) ? undefined : interval } /** * @param {Node} elt * @param {string} name * @returns {(string | null)} */ function getRawAttribute(elt, name) { return elt instanceof Element && elt.getAttribute(name) } /** * @param {Element} elt * @param {string} qualifiedName * @returns {boolean} */ // resolve with both hx and data-hx prefixes function hasAttribute(elt, qualifiedName) { return !!elt.hasAttribute && (elt.hasAttribute(qualifiedName) || elt.hasAttribute('data-' + qualifiedName)) } /** * * @param {Node} elt * @param {string} qualifiedName * @returns {(string | null)} */ function getAttributeValue(elt, qualifiedName) { return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, 'data-' + qualifiedName) } /** * @param {Node} elt * @returns {Node | null} */ function parentElt(elt) { const parent = elt.parentElement if (!parent && elt.parentNode instanceof ShadowRoot) return elt.parentNode return parent } /** * @returns {Document} */ function getDocument() { return document } /** * @param {Node} elt * @param {boolean} global * @returns {Node|Document} */ function getRootNode(elt, global) { return elt.getRootNode ? elt.getRootNode({ composed: global }) : getDocument() } /** * @param {Node} elt * @param {(e:Node) => boolean} condition * @returns {Node | null} */ function getClosestMatch(elt, condition) { while (elt && !condition(elt)) { elt = parentElt(elt) } return elt || null } /** * @param {Element} initialElement * @param {Element} ancestor * @param {string} attributeName * @returns {string|null} */ function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName) { const attributeValue = getAttributeValue(ancestor, attributeName) const disinherit = getAttributeValue(ancestor, 'hx-disinherit') var inherit = getAttributeValue(ancestor, 'hx-inherit') if (initialElement !== ancestor) { if (htmx.config.disableInheritance) { if (inherit && (inherit === '*' || inherit.split(' ').indexOf(attributeName) >= 0)) { return attributeValue } else { return null } } if (disinherit && (disinherit === '*' || disinherit.split(' ').indexOf(attributeName) >= 0)) { return 'unset' } } return attributeValue } /** * @param {Element} elt * @param {string} attributeName * @returns {string | null} */ function getClosestAttributeValue(elt, attributeName) { let closestAttr = null getClosestMatch(elt, function(e) { return !!(closestAttr = getAttributeValueWithDisinheritance(elt, asElement(e), attributeName)) }) if (closestAttr !== 'unset') { return closestAttr } } /** * @param {Node} elt * @param {string} selector * @returns {boolean} */ function matches(elt, selector) { // @ts-ignore: non-standard properties for browser compatibility // noinspection JSUnresolvedVariable const matchesFunction = elt instanceof Element && (elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector) return !!matchesFunction && matchesFunction.call(elt, selector) } /** * @param {string} str * @returns {string} */ function getStartTag(str) { const tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i const match = tagMatcher.exec(str) if (match) { return match[1].toLowerCase() } else { return '' } } /** * @param {string} resp * @returns {Document} */ function parseHTML(resp) { const parser = new DOMParser() return parser.parseFromString(resp, 'text/html') } /** * @param {DocumentFragment} fragment * @param {Node} elt */ function takeChildrenFor(fragment, elt) { while (elt.childNodes.length > 0) { fragment.append(elt.childNodes[0]) } } /** * @param {HTMLScriptElement} script * @returns {HTMLScriptElement} */ function duplicateScript(script) { const newScript = getDocument().createElement('script') forEach(script.attributes, function(attr) { newScript.setAttribute(attr.name, attr.value) }) newScript.textContent = script.textContent newScript.async = false if (htmx.config.inlineScriptNonce) { newScript.nonce = htmx.config.inlineScriptNonce } return newScript } /** * @param {HTMLScriptElement} script * @returns {boolean} */ function isJavaScriptScriptNode(script) { return script.matches('script') && (script.type === 'text/javascript' || script.type === 'module' || script.type === '') } /** * we have to make new copies of script tags that we are going to insert because * SOME browsers (not saying who, but it involves an element and an animal) don't * execute scripts created in