mirror of
https://github.com/bigskysoftware/htmx.git
synced 2026-01-07 17:22:14 +00:00
prep release
This commit is contained in:
parent
f0ff590fb4
commit
c917b4e880
131
dist/htmx.esm.js
vendored
131
dist/htmx.esm.js
vendored
@ -171,48 +171,60 @@ var htmx = (() => {
|
||||
style === 'append' ? 'beforeend' : style;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, returnElt) {
|
||||
#findThisElements(elt, attrName) {
|
||||
let result = [];
|
||||
this.#attributeValue(elt, attrName, undefined, (val, elt) => {
|
||||
if (val?.split(/\s*,\s*/).includes('this')) result.push(elt);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, eltCollector) {
|
||||
name = this.#prefix(name);
|
||||
let appendName = name + this.#maybeAdjustMetaCharacter(":append");
|
||||
let inheritName = name + (this.config.implicitInheritance ? "" : this.#maybeAdjustMetaCharacter(":inherited"));
|
||||
let inheritAppendName = name + this.#maybeAdjustMetaCharacter(":inherited:append");
|
||||
|
||||
if (elt.hasAttribute(name)) {
|
||||
return returnElt ? elt : elt.getAttribute(name);
|
||||
let val = elt.getAttribute(name);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(inheritName)) {
|
||||
return returnElt ? elt : elt.getAttribute(inheritName);
|
||||
let val = elt.getAttribute(inheritName);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(appendName) || elt.hasAttribute(inheritAppendName)) {
|
||||
let appendValue = elt.getAttribute(appendName) || elt.getAttribute(inheritAppendName);
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
return returnElt ? inherited : (inherited ? inherited + "," + appendValue : appendValue);
|
||||
} else {
|
||||
return returnElt ? elt : appendValue;
|
||||
if (eltCollector) {
|
||||
eltCollector(appendValue, elt);
|
||||
}
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
return inherited ? (inherited + "," + appendValue).replace(/[{}]/g, '') : appendValue;
|
||||
}
|
||||
return appendValue;
|
||||
}
|
||||
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let val = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
if (!returnElt && val && this.config.implicitInheritance) {
|
||||
let val = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
if (!eltCollector && val && this.config.implicitInheritance) {
|
||||
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, name, parent})
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return returnElt ? elt : defaultVal;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
#parseConfig(configString) {
|
||||
if (configString[0] === '{') return JSON.parse(configString);
|
||||
let configPattern = /([^\s,]+?)(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
let configPattern = /(?:"([^"]+)"|([^\s,:]+))(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
return [...configString.matchAll(configPattern)].reduce((result, match) => {
|
||||
let keyPath = match[1].split('.');
|
||||
let value = (match[2] ?? match[3] ?? match[4] ?? match[5] ?? 'true').trim();
|
||||
let keyPath = (match[1] ?? match[2]).split('.');
|
||||
let value = (match[3] ?? match[4] ?? match[5] ?? match[6] ?? 'true').trim();
|
||||
if (value === 'true') value = true;
|
||||
else if (value === 'false') value = false;
|
||||
else if (/^\d+$/.test(value)) value = parseInt(value);
|
||||
@ -319,7 +331,7 @@ var htmx = (() => {
|
||||
action: fullAction,
|
||||
anchor,
|
||||
method,
|
||||
headers: this.#determineHeaders(sourceElement),
|
||||
headers: this.#createCoreHeaders(sourceElement),
|
||||
abort: ac.abort.bind(ac),
|
||||
credentials: "same-origin",
|
||||
signal: ac.signal,
|
||||
@ -350,7 +362,7 @@ var htmx = (() => {
|
||||
return `${elt.tagName.toLowerCase()}${elt.id ? '#' + elt.id : ''}`;
|
||||
}
|
||||
|
||||
#determineHeaders(elt) {
|
||||
#createCoreHeaders(elt) {
|
||||
let headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Source": this.#buildIdentifier(elt),
|
||||
@ -360,19 +372,31 @@ var htmx = (() => {
|
||||
if (this.#isBoosted(elt)) {
|
||||
headers["HX-Boosted"] = "true"
|
||||
}
|
||||
let headersAttribute = this.#attributeValue(elt, "hx-headers");
|
||||
if (headersAttribute) {
|
||||
this.#mergeConfig(headersAttribute, headers);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
#handleHxHeaders(elt, headers) {
|
||||
let result = this.#getAttributeObject(elt, "hx-headers");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
headers[key] = String(obj[key]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let key in result) {
|
||||
headers[key] = String(result[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#resolveTarget(elt, selector) {
|
||||
if (selector instanceof Element) {
|
||||
return selector;
|
||||
} else if (selector != null) {
|
||||
let thisElt = this.#attributeValue(elt, "hx-target", undefined, true);
|
||||
return this.#findAllExt(elt, selector, false, thisElt)[0];
|
||||
return this.#findExt(elt, selector, "hx-target");
|
||||
} else if (this.#isBoosted(elt)) {
|
||||
return document.body
|
||||
} else {
|
||||
@ -406,6 +430,10 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dynamic headers
|
||||
let headersResult = this.#handleHxHeaders(elt, ctx.request.headers)
|
||||
if (headersResult) await headersResult // Only await if it returned a promise
|
||||
|
||||
// Add HX-Request-Type and HX-Target headers
|
||||
ctx.request.headers["HX-Request-Type"] = (ctx.target === document.body || ctx.select) ? "full" : "partial";
|
||||
if (ctx.target) {
|
||||
@ -1476,7 +1504,7 @@ var htmx = (() => {
|
||||
}
|
||||
|
||||
takeClass(element, className, container = element.parentElement) {
|
||||
for (let elt of this.findAll(this.#normalizeElement(container), "." + className)) {
|
||||
for (let elt of this.#findAllExt(this.#normalizeElement(container), "." + className)) {
|
||||
elt.classList.remove(className);
|
||||
}
|
||||
element.classList.add(className);
|
||||
@ -1657,8 +1685,7 @@ var htmx = (() => {
|
||||
if (!indicatorsSelector) {
|
||||
indicatorElements = [elt]
|
||||
} else {
|
||||
let thisElt = this.#attributeValue(elt, "hx-indicator", undefined, true);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, false, thisElt);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, "hx-indicator");
|
||||
}
|
||||
for (const indicator of indicatorElements) {
|
||||
indicator._htmxReqCount ||= 0
|
||||
@ -1684,7 +1711,7 @@ var htmx = (() => {
|
||||
let disabledSelector = this.#attributeValue(elt, "hx-disable");
|
||||
let disabledElements = []
|
||||
if (disabledSelector) {
|
||||
disabledElements = this.#queryEltAndDescendants(elt, disabledSelector);
|
||||
disabledElements = this.#findAllExt(elt, disabledSelector, "hx-disable");
|
||||
for (let indicator of disabledElements) {
|
||||
indicator._htmxDisableCount ||= 0
|
||||
indicator._htmxDisableCount++
|
||||
@ -1760,22 +1787,36 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#getAttributeObject(elt, attrName) {
|
||||
let attrValue = this.#attributeValue(elt, attrName);
|
||||
if (!attrValue) return null;
|
||||
|
||||
let javascriptContent = this.#extractJavascriptContent(attrValue);
|
||||
if (javascriptContent) {
|
||||
// Wrap in braces if not already wrapped (for htmx 2.x compatibility)
|
||||
if (javascriptContent.indexOf('{') !== 0) {
|
||||
javascriptContent = '{' + javascriptContent + '}';
|
||||
}
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true);
|
||||
} else {
|
||||
// Synchronous path - return the parsed object directly
|
||||
return this.#parseConfig(attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
#handleHxVals(elt, body) {
|
||||
let hxValsValue = this.#attributeValue(elt, "hx-vals");
|
||||
if (hxValsValue) {
|
||||
let javascriptContent = this.#extractJavascriptContent(hxValsValue);
|
||||
if (javascriptContent) {
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true).then(obj => {
|
||||
let result = this.#getAttributeObject(elt, "hx-vals");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
body.set(key, obj[key])
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Synchronous path
|
||||
let obj = this.#parseConfig(hxValsValue);
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
for (let key in result) {
|
||||
body.set(key, result[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1786,11 +1827,11 @@ var htmx = (() => {
|
||||
return s.startsWith('<') && s.endsWith('/>') ? s.slice(1, -2) : s;
|
||||
}
|
||||
|
||||
#findAllExt(eltOrSelector, maybeSelector, global, thisElt) {
|
||||
#findAllExt(eltOrSelector, maybeSelector, thisAttr, global) {
|
||||
let selector = maybeSelector ?? eltOrSelector;
|
||||
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
|
||||
if (selector.startsWith('global ')) {
|
||||
return this.#findAllExt(elt, selector.slice(7), true, thisElt);
|
||||
return this.#findAllExt(elt, selector.slice(7), thisAttr, true);
|
||||
}
|
||||
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
|
||||
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
|
||||
@ -1822,7 +1863,11 @@ var htmx = (() => {
|
||||
} else if (selector === 'host') {
|
||||
item = (elt.getRootNode()).host
|
||||
} else if (selector === 'this') {
|
||||
item = thisElt || elt
|
||||
if (thisAttr) {
|
||||
result.push(...this.#findThisElements(elt, thisAttr));
|
||||
continue;
|
||||
}
|
||||
item = elt
|
||||
} else {
|
||||
unprocessedParts.push(selector)
|
||||
}
|
||||
@ -1838,7 +1883,7 @@ var htmx = (() => {
|
||||
result.push(...rootNode.querySelectorAll(standardSelector))
|
||||
}
|
||||
|
||||
return result
|
||||
return [...new Set(result)]
|
||||
}
|
||||
|
||||
#scanForwardQuery(start, match, global) {
|
||||
@ -1866,8 +1911,8 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#findExt(eltOrSelector, selector, thisElt) {
|
||||
return this.#findAllExt(eltOrSelector, selector)[0]
|
||||
#findExt(eltOrSelector, selector, thisAttr) {
|
||||
return this.#findAllExt(eltOrSelector, selector, thisAttr)[0]
|
||||
}
|
||||
|
||||
#extractJavascriptContent(string) {
|
||||
|
||||
BIN
dist/htmx.esm.js.br
vendored
BIN
dist/htmx.esm.js.br
vendored
Binary file not shown.
2
dist/htmx.esm.min.js
vendored
2
dist/htmx.esm.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/htmx.esm.min.js.br
vendored
BIN
dist/htmx.esm.min.js.br
vendored
Binary file not shown.
2
dist/htmx.esm.min.js.map
vendored
2
dist/htmx.esm.min.js.map
vendored
File diff suppressed because one or more lines are too long
131
dist/htmx.js
vendored
131
dist/htmx.js
vendored
@ -171,48 +171,60 @@ var htmx = (() => {
|
||||
style === 'append' ? 'beforeend' : style;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, returnElt) {
|
||||
#findThisElements(elt, attrName) {
|
||||
let result = [];
|
||||
this.#attributeValue(elt, attrName, undefined, (val, elt) => {
|
||||
if (val?.split(/\s*,\s*/).includes('this')) result.push(elt);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, eltCollector) {
|
||||
name = this.#prefix(name);
|
||||
let appendName = name + this.#maybeAdjustMetaCharacter(":append");
|
||||
let inheritName = name + (this.config.implicitInheritance ? "" : this.#maybeAdjustMetaCharacter(":inherited"));
|
||||
let inheritAppendName = name + this.#maybeAdjustMetaCharacter(":inherited:append");
|
||||
|
||||
if (elt.hasAttribute(name)) {
|
||||
return returnElt ? elt : elt.getAttribute(name);
|
||||
let val = elt.getAttribute(name);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(inheritName)) {
|
||||
return returnElt ? elt : elt.getAttribute(inheritName);
|
||||
let val = elt.getAttribute(inheritName);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(appendName) || elt.hasAttribute(inheritAppendName)) {
|
||||
let appendValue = elt.getAttribute(appendName) || elt.getAttribute(inheritAppendName);
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
return returnElt ? inherited : (inherited ? inherited + "," + appendValue : appendValue);
|
||||
} else {
|
||||
return returnElt ? elt : appendValue;
|
||||
if (eltCollector) {
|
||||
eltCollector(appendValue, elt);
|
||||
}
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
return inherited ? (inherited + "," + appendValue).replace(/[{}]/g, '') : appendValue;
|
||||
}
|
||||
return appendValue;
|
||||
}
|
||||
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let val = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
if (!returnElt && val && this.config.implicitInheritance) {
|
||||
let val = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
if (!eltCollector && val && this.config.implicitInheritance) {
|
||||
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, name, parent})
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return returnElt ? elt : defaultVal;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
#parseConfig(configString) {
|
||||
if (configString[0] === '{') return JSON.parse(configString);
|
||||
let configPattern = /([^\s,]+?)(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
let configPattern = /(?:"([^"]+)"|([^\s,:]+))(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
return [...configString.matchAll(configPattern)].reduce((result, match) => {
|
||||
let keyPath = match[1].split('.');
|
||||
let value = (match[2] ?? match[3] ?? match[4] ?? match[5] ?? 'true').trim();
|
||||
let keyPath = (match[1] ?? match[2]).split('.');
|
||||
let value = (match[3] ?? match[4] ?? match[5] ?? match[6] ?? 'true').trim();
|
||||
if (value === 'true') value = true;
|
||||
else if (value === 'false') value = false;
|
||||
else if (/^\d+$/.test(value)) value = parseInt(value);
|
||||
@ -319,7 +331,7 @@ var htmx = (() => {
|
||||
action: fullAction,
|
||||
anchor,
|
||||
method,
|
||||
headers: this.#determineHeaders(sourceElement),
|
||||
headers: this.#createCoreHeaders(sourceElement),
|
||||
abort: ac.abort.bind(ac),
|
||||
credentials: "same-origin",
|
||||
signal: ac.signal,
|
||||
@ -350,7 +362,7 @@ var htmx = (() => {
|
||||
return `${elt.tagName.toLowerCase()}${elt.id ? '#' + elt.id : ''}`;
|
||||
}
|
||||
|
||||
#determineHeaders(elt) {
|
||||
#createCoreHeaders(elt) {
|
||||
let headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Source": this.#buildIdentifier(elt),
|
||||
@ -360,19 +372,31 @@ var htmx = (() => {
|
||||
if (this.#isBoosted(elt)) {
|
||||
headers["HX-Boosted"] = "true"
|
||||
}
|
||||
let headersAttribute = this.#attributeValue(elt, "hx-headers");
|
||||
if (headersAttribute) {
|
||||
this.#mergeConfig(headersAttribute, headers);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
#handleHxHeaders(elt, headers) {
|
||||
let result = this.#getAttributeObject(elt, "hx-headers");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
headers[key] = String(obj[key]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let key in result) {
|
||||
headers[key] = String(result[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#resolveTarget(elt, selector) {
|
||||
if (selector instanceof Element) {
|
||||
return selector;
|
||||
} else if (selector != null) {
|
||||
let thisElt = this.#attributeValue(elt, "hx-target", undefined, true);
|
||||
return this.#findAllExt(elt, selector, false, thisElt)[0];
|
||||
return this.#findExt(elt, selector, "hx-target");
|
||||
} else if (this.#isBoosted(elt)) {
|
||||
return document.body
|
||||
} else {
|
||||
@ -406,6 +430,10 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dynamic headers
|
||||
let headersResult = this.#handleHxHeaders(elt, ctx.request.headers)
|
||||
if (headersResult) await headersResult // Only await if it returned a promise
|
||||
|
||||
// Add HX-Request-Type and HX-Target headers
|
||||
ctx.request.headers["HX-Request-Type"] = (ctx.target === document.body || ctx.select) ? "full" : "partial";
|
||||
if (ctx.target) {
|
||||
@ -1476,7 +1504,7 @@ var htmx = (() => {
|
||||
}
|
||||
|
||||
takeClass(element, className, container = element.parentElement) {
|
||||
for (let elt of this.findAll(this.#normalizeElement(container), "." + className)) {
|
||||
for (let elt of this.#findAllExt(this.#normalizeElement(container), "." + className)) {
|
||||
elt.classList.remove(className);
|
||||
}
|
||||
element.classList.add(className);
|
||||
@ -1657,8 +1685,7 @@ var htmx = (() => {
|
||||
if (!indicatorsSelector) {
|
||||
indicatorElements = [elt]
|
||||
} else {
|
||||
let thisElt = this.#attributeValue(elt, "hx-indicator", undefined, true);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, false, thisElt);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, "hx-indicator");
|
||||
}
|
||||
for (const indicator of indicatorElements) {
|
||||
indicator._htmxReqCount ||= 0
|
||||
@ -1684,7 +1711,7 @@ var htmx = (() => {
|
||||
let disabledSelector = this.#attributeValue(elt, "hx-disable");
|
||||
let disabledElements = []
|
||||
if (disabledSelector) {
|
||||
disabledElements = this.#queryEltAndDescendants(elt, disabledSelector);
|
||||
disabledElements = this.#findAllExt(elt, disabledSelector, "hx-disable");
|
||||
for (let indicator of disabledElements) {
|
||||
indicator._htmxDisableCount ||= 0
|
||||
indicator._htmxDisableCount++
|
||||
@ -1760,22 +1787,36 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#getAttributeObject(elt, attrName) {
|
||||
let attrValue = this.#attributeValue(elt, attrName);
|
||||
if (!attrValue) return null;
|
||||
|
||||
let javascriptContent = this.#extractJavascriptContent(attrValue);
|
||||
if (javascriptContent) {
|
||||
// Wrap in braces if not already wrapped (for htmx 2.x compatibility)
|
||||
if (javascriptContent.indexOf('{') !== 0) {
|
||||
javascriptContent = '{' + javascriptContent + '}';
|
||||
}
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true);
|
||||
} else {
|
||||
// Synchronous path - return the parsed object directly
|
||||
return this.#parseConfig(attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
#handleHxVals(elt, body) {
|
||||
let hxValsValue = this.#attributeValue(elt, "hx-vals");
|
||||
if (hxValsValue) {
|
||||
let javascriptContent = this.#extractJavascriptContent(hxValsValue);
|
||||
if (javascriptContent) {
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true).then(obj => {
|
||||
let result = this.#getAttributeObject(elt, "hx-vals");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
body.set(key, obj[key])
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Synchronous path
|
||||
let obj = this.#parseConfig(hxValsValue);
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
for (let key in result) {
|
||||
body.set(key, result[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1786,11 +1827,11 @@ var htmx = (() => {
|
||||
return s.startsWith('<') && s.endsWith('/>') ? s.slice(1, -2) : s;
|
||||
}
|
||||
|
||||
#findAllExt(eltOrSelector, maybeSelector, global, thisElt) {
|
||||
#findAllExt(eltOrSelector, maybeSelector, thisAttr, global) {
|
||||
let selector = maybeSelector ?? eltOrSelector;
|
||||
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
|
||||
if (selector.startsWith('global ')) {
|
||||
return this.#findAllExt(elt, selector.slice(7), true, thisElt);
|
||||
return this.#findAllExt(elt, selector.slice(7), thisAttr, true);
|
||||
}
|
||||
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
|
||||
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
|
||||
@ -1822,7 +1863,11 @@ var htmx = (() => {
|
||||
} else if (selector === 'host') {
|
||||
item = (elt.getRootNode()).host
|
||||
} else if (selector === 'this') {
|
||||
item = thisElt || elt
|
||||
if (thisAttr) {
|
||||
result.push(...this.#findThisElements(elt, thisAttr));
|
||||
continue;
|
||||
}
|
||||
item = elt
|
||||
} else {
|
||||
unprocessedParts.push(selector)
|
||||
}
|
||||
@ -1838,7 +1883,7 @@ var htmx = (() => {
|
||||
result.push(...rootNode.querySelectorAll(standardSelector))
|
||||
}
|
||||
|
||||
return result
|
||||
return [...new Set(result)]
|
||||
}
|
||||
|
||||
#scanForwardQuery(start, match, global) {
|
||||
@ -1866,8 +1911,8 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#findExt(eltOrSelector, selector, thisElt) {
|
||||
return this.#findAllExt(eltOrSelector, selector)[0]
|
||||
#findExt(eltOrSelector, selector, thisAttr) {
|
||||
return this.#findAllExt(eltOrSelector, selector, thisAttr)[0]
|
||||
}
|
||||
|
||||
#extractJavascriptContent(string) {
|
||||
|
||||
BIN
dist/htmx.js.br
vendored
BIN
dist/htmx.js.br
vendored
Binary file not shown.
2
dist/htmx.min.js
vendored
2
dist/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/htmx.min.js.br
vendored
BIN
dist/htmx.min.js.br
vendored
Binary file not shown.
2
dist/htmx.min.js.map
vendored
2
dist/htmx.min.js.map
vendored
File diff suppressed because one or more lines are too long
@ -171,48 +171,60 @@ var htmx = (() => {
|
||||
style === 'append' ? 'beforeend' : style;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, returnElt) {
|
||||
#findThisElements(elt, attrName) {
|
||||
let result = [];
|
||||
this.#attributeValue(elt, attrName, undefined, (val, elt) => {
|
||||
if (val?.split(/\s*,\s*/).includes('this')) result.push(elt);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, eltCollector) {
|
||||
name = this.#prefix(name);
|
||||
let appendName = name + this.#maybeAdjustMetaCharacter(":append");
|
||||
let inheritName = name + (this.config.implicitInheritance ? "" : this.#maybeAdjustMetaCharacter(":inherited"));
|
||||
let inheritAppendName = name + this.#maybeAdjustMetaCharacter(":inherited:append");
|
||||
|
||||
if (elt.hasAttribute(name)) {
|
||||
return returnElt ? elt : elt.getAttribute(name);
|
||||
let val = elt.getAttribute(name);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(inheritName)) {
|
||||
return returnElt ? elt : elt.getAttribute(inheritName);
|
||||
let val = elt.getAttribute(inheritName);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(appendName) || elt.hasAttribute(inheritAppendName)) {
|
||||
let appendValue = elt.getAttribute(appendName) || elt.getAttribute(inheritAppendName);
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
return returnElt ? inherited : (inherited ? inherited + "," + appendValue : appendValue);
|
||||
} else {
|
||||
return returnElt ? elt : appendValue;
|
||||
if (eltCollector) {
|
||||
eltCollector(appendValue, elt);
|
||||
}
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
return inherited ? (inherited + "," + appendValue).replace(/[{}]/g, '') : appendValue;
|
||||
}
|
||||
return appendValue;
|
||||
}
|
||||
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let val = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
if (!returnElt && val && this.config.implicitInheritance) {
|
||||
let val = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
if (!eltCollector && val && this.config.implicitInheritance) {
|
||||
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, name, parent})
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return returnElt ? elt : defaultVal;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
#parseConfig(configString) {
|
||||
if (configString[0] === '{') return JSON.parse(configString);
|
||||
let configPattern = /([^\s,]+?)(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
let configPattern = /(?:"([^"]+)"|([^\s,:]+))(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
return [...configString.matchAll(configPattern)].reduce((result, match) => {
|
||||
let keyPath = match[1].split('.');
|
||||
let value = (match[2] ?? match[3] ?? match[4] ?? match[5] ?? 'true').trim();
|
||||
let keyPath = (match[1] ?? match[2]).split('.');
|
||||
let value = (match[3] ?? match[4] ?? match[5] ?? match[6] ?? 'true').trim();
|
||||
if (value === 'true') value = true;
|
||||
else if (value === 'false') value = false;
|
||||
else if (/^\d+$/.test(value)) value = parseInt(value);
|
||||
@ -319,7 +331,7 @@ var htmx = (() => {
|
||||
action: fullAction,
|
||||
anchor,
|
||||
method,
|
||||
headers: this.#determineHeaders(sourceElement),
|
||||
headers: this.#createCoreHeaders(sourceElement),
|
||||
abort: ac.abort.bind(ac),
|
||||
credentials: "same-origin",
|
||||
signal: ac.signal,
|
||||
@ -350,7 +362,7 @@ var htmx = (() => {
|
||||
return `${elt.tagName.toLowerCase()}${elt.id ? '#' + elt.id : ''}`;
|
||||
}
|
||||
|
||||
#determineHeaders(elt) {
|
||||
#createCoreHeaders(elt) {
|
||||
let headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Source": this.#buildIdentifier(elt),
|
||||
@ -360,19 +372,31 @@ var htmx = (() => {
|
||||
if (this.#isBoosted(elt)) {
|
||||
headers["HX-Boosted"] = "true"
|
||||
}
|
||||
let headersAttribute = this.#attributeValue(elt, "hx-headers");
|
||||
if (headersAttribute) {
|
||||
this.#mergeConfig(headersAttribute, headers);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
#handleHxHeaders(elt, headers) {
|
||||
let result = this.#getAttributeObject(elt, "hx-headers");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
headers[key] = String(obj[key]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let key in result) {
|
||||
headers[key] = String(result[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#resolveTarget(elt, selector) {
|
||||
if (selector instanceof Element) {
|
||||
return selector;
|
||||
} else if (selector != null) {
|
||||
let thisElt = this.#attributeValue(elt, "hx-target", undefined, true);
|
||||
return this.#findAllExt(elt, selector, false, thisElt)[0];
|
||||
return this.#findExt(elt, selector, "hx-target");
|
||||
} else if (this.#isBoosted(elt)) {
|
||||
return document.body
|
||||
} else {
|
||||
@ -406,6 +430,10 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dynamic headers
|
||||
let headersResult = this.#handleHxHeaders(elt, ctx.request.headers)
|
||||
if (headersResult) await headersResult // Only await if it returned a promise
|
||||
|
||||
// Add HX-Request-Type and HX-Target headers
|
||||
ctx.request.headers["HX-Request-Type"] = (ctx.target === document.body || ctx.select) ? "full" : "partial";
|
||||
if (ctx.target) {
|
||||
@ -1476,7 +1504,7 @@ var htmx = (() => {
|
||||
}
|
||||
|
||||
takeClass(element, className, container = element.parentElement) {
|
||||
for (let elt of this.findAll(this.#normalizeElement(container), "." + className)) {
|
||||
for (let elt of this.#findAllExt(this.#normalizeElement(container), "." + className)) {
|
||||
elt.classList.remove(className);
|
||||
}
|
||||
element.classList.add(className);
|
||||
@ -1657,8 +1685,7 @@ var htmx = (() => {
|
||||
if (!indicatorsSelector) {
|
||||
indicatorElements = [elt]
|
||||
} else {
|
||||
let thisElt = this.#attributeValue(elt, "hx-indicator", undefined, true);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, false, thisElt);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, "hx-indicator");
|
||||
}
|
||||
for (const indicator of indicatorElements) {
|
||||
indicator._htmxReqCount ||= 0
|
||||
@ -1684,7 +1711,7 @@ var htmx = (() => {
|
||||
let disabledSelector = this.#attributeValue(elt, "hx-disable");
|
||||
let disabledElements = []
|
||||
if (disabledSelector) {
|
||||
disabledElements = this.#queryEltAndDescendants(elt, disabledSelector);
|
||||
disabledElements = this.#findAllExt(elt, disabledSelector, "hx-disable");
|
||||
for (let indicator of disabledElements) {
|
||||
indicator._htmxDisableCount ||= 0
|
||||
indicator._htmxDisableCount++
|
||||
@ -1760,22 +1787,36 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#getAttributeObject(elt, attrName) {
|
||||
let attrValue = this.#attributeValue(elt, attrName);
|
||||
if (!attrValue) return null;
|
||||
|
||||
let javascriptContent = this.#extractJavascriptContent(attrValue);
|
||||
if (javascriptContent) {
|
||||
// Wrap in braces if not already wrapped (for htmx 2.x compatibility)
|
||||
if (javascriptContent.indexOf('{') !== 0) {
|
||||
javascriptContent = '{' + javascriptContent + '}';
|
||||
}
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true);
|
||||
} else {
|
||||
// Synchronous path - return the parsed object directly
|
||||
return this.#parseConfig(attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
#handleHxVals(elt, body) {
|
||||
let hxValsValue = this.#attributeValue(elt, "hx-vals");
|
||||
if (hxValsValue) {
|
||||
let javascriptContent = this.#extractJavascriptContent(hxValsValue);
|
||||
if (javascriptContent) {
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true).then(obj => {
|
||||
let result = this.#getAttributeObject(elt, "hx-vals");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
body.set(key, obj[key])
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Synchronous path
|
||||
let obj = this.#parseConfig(hxValsValue);
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
for (let key in result) {
|
||||
body.set(key, result[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1786,11 +1827,11 @@ var htmx = (() => {
|
||||
return s.startsWith('<') && s.endsWith('/>') ? s.slice(1, -2) : s;
|
||||
}
|
||||
|
||||
#findAllExt(eltOrSelector, maybeSelector, global, thisElt) {
|
||||
#findAllExt(eltOrSelector, maybeSelector, thisAttr, global) {
|
||||
let selector = maybeSelector ?? eltOrSelector;
|
||||
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
|
||||
if (selector.startsWith('global ')) {
|
||||
return this.#findAllExt(elt, selector.slice(7), true, thisElt);
|
||||
return this.#findAllExt(elt, selector.slice(7), thisAttr, true);
|
||||
}
|
||||
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
|
||||
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
|
||||
@ -1822,7 +1863,11 @@ var htmx = (() => {
|
||||
} else if (selector === 'host') {
|
||||
item = (elt.getRootNode()).host
|
||||
} else if (selector === 'this') {
|
||||
item = thisElt || elt
|
||||
if (thisAttr) {
|
||||
result.push(...this.#findThisElements(elt, thisAttr));
|
||||
continue;
|
||||
}
|
||||
item = elt
|
||||
} else {
|
||||
unprocessedParts.push(selector)
|
||||
}
|
||||
@ -1838,7 +1883,7 @@ var htmx = (() => {
|
||||
result.push(...rootNode.querySelectorAll(standardSelector))
|
||||
}
|
||||
|
||||
return result
|
||||
return [...new Set(result)]
|
||||
}
|
||||
|
||||
#scanForwardQuery(start, match, global) {
|
||||
@ -1866,8 +1911,8 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#findExt(eltOrSelector, selector, thisElt) {
|
||||
return this.#findAllExt(eltOrSelector, selector)[0]
|
||||
#findExt(eltOrSelector, selector, thisAttr) {
|
||||
return this.#findAllExt(eltOrSelector, selector, thisAttr)[0]
|
||||
}
|
||||
|
||||
#extractJavascriptContent(string) {
|
||||
|
||||
Binary file not shown.
2
www/static/js/htmx.esm.min.js
vendored
2
www/static/js/htmx.esm.min.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -171,48 +171,60 @@ var htmx = (() => {
|
||||
style === 'append' ? 'beforeend' : style;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, returnElt) {
|
||||
#findThisElements(elt, attrName) {
|
||||
let result = [];
|
||||
this.#attributeValue(elt, attrName, undefined, (val, elt) => {
|
||||
if (val?.split(/\s*,\s*/).includes('this')) result.push(elt);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
#attributeValue(elt, name, defaultVal, eltCollector) {
|
||||
name = this.#prefix(name);
|
||||
let appendName = name + this.#maybeAdjustMetaCharacter(":append");
|
||||
let inheritName = name + (this.config.implicitInheritance ? "" : this.#maybeAdjustMetaCharacter(":inherited"));
|
||||
let inheritAppendName = name + this.#maybeAdjustMetaCharacter(":inherited:append");
|
||||
|
||||
if (elt.hasAttribute(name)) {
|
||||
return returnElt ? elt : elt.getAttribute(name);
|
||||
let val = elt.getAttribute(name);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(inheritName)) {
|
||||
return returnElt ? elt : elt.getAttribute(inheritName);
|
||||
let val = elt.getAttribute(inheritName);
|
||||
return eltCollector ? eltCollector(val, elt) : val;
|
||||
}
|
||||
|
||||
if (elt.hasAttribute(appendName) || elt.hasAttribute(inheritAppendName)) {
|
||||
let appendValue = elt.getAttribute(appendName) || elt.getAttribute(inheritAppendName);
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
return returnElt ? inherited : (inherited ? inherited + "," + appendValue : appendValue);
|
||||
} else {
|
||||
return returnElt ? elt : appendValue;
|
||||
if (eltCollector) {
|
||||
eltCollector(appendValue, elt);
|
||||
}
|
||||
if (parent) {
|
||||
let inherited = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
return inherited ? (inherited + "," + appendValue).replace(/[{}]/g, '') : appendValue;
|
||||
}
|
||||
return appendValue;
|
||||
}
|
||||
|
||||
let parent = elt.parentNode?.closest?.(`[${CSS.escape(inheritName)}],[${CSS.escape(inheritAppendName)}]`);
|
||||
if (parent) {
|
||||
let val = this.#attributeValue(parent, name, undefined, returnElt);
|
||||
if (!returnElt && val && this.config.implicitInheritance) {
|
||||
let val = this.#attributeValue(parent, name, undefined, eltCollector);
|
||||
if (!eltCollector && val && this.config.implicitInheritance) {
|
||||
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, name, parent})
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return returnElt ? elt : defaultVal;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
#parseConfig(configString) {
|
||||
if (configString[0] === '{') return JSON.parse(configString);
|
||||
let configPattern = /([^\s,]+?)(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
let configPattern = /(?:"([^"]+)"|([^\s,:]+))(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<([^>]+)\/>|([^\s,]+)))?(?=\s|,|$)/g;
|
||||
return [...configString.matchAll(configPattern)].reduce((result, match) => {
|
||||
let keyPath = match[1].split('.');
|
||||
let value = (match[2] ?? match[3] ?? match[4] ?? match[5] ?? 'true').trim();
|
||||
let keyPath = (match[1] ?? match[2]).split('.');
|
||||
let value = (match[3] ?? match[4] ?? match[5] ?? match[6] ?? 'true').trim();
|
||||
if (value === 'true') value = true;
|
||||
else if (value === 'false') value = false;
|
||||
else if (/^\d+$/.test(value)) value = parseInt(value);
|
||||
@ -319,7 +331,7 @@ var htmx = (() => {
|
||||
action: fullAction,
|
||||
anchor,
|
||||
method,
|
||||
headers: this.#determineHeaders(sourceElement),
|
||||
headers: this.#createCoreHeaders(sourceElement),
|
||||
abort: ac.abort.bind(ac),
|
||||
credentials: "same-origin",
|
||||
signal: ac.signal,
|
||||
@ -350,7 +362,7 @@ var htmx = (() => {
|
||||
return `${elt.tagName.toLowerCase()}${elt.id ? '#' + elt.id : ''}`;
|
||||
}
|
||||
|
||||
#determineHeaders(elt) {
|
||||
#createCoreHeaders(elt) {
|
||||
let headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Source": this.#buildIdentifier(elt),
|
||||
@ -360,19 +372,31 @@ var htmx = (() => {
|
||||
if (this.#isBoosted(elt)) {
|
||||
headers["HX-Boosted"] = "true"
|
||||
}
|
||||
let headersAttribute = this.#attributeValue(elt, "hx-headers");
|
||||
if (headersAttribute) {
|
||||
this.#mergeConfig(headersAttribute, headers);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
#handleHxHeaders(elt, headers) {
|
||||
let result = this.#getAttributeObject(elt, "hx-headers");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
headers[key] = String(obj[key]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let key in result) {
|
||||
headers[key] = String(result[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#resolveTarget(elt, selector) {
|
||||
if (selector instanceof Element) {
|
||||
return selector;
|
||||
} else if (selector != null) {
|
||||
let thisElt = this.#attributeValue(elt, "hx-target", undefined, true);
|
||||
return this.#findAllExt(elt, selector, false, thisElt)[0];
|
||||
return this.#findExt(elt, selector, "hx-target");
|
||||
} else if (this.#isBoosted(elt)) {
|
||||
return document.body
|
||||
} else {
|
||||
@ -406,6 +430,10 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dynamic headers
|
||||
let headersResult = this.#handleHxHeaders(elt, ctx.request.headers)
|
||||
if (headersResult) await headersResult // Only await if it returned a promise
|
||||
|
||||
// Add HX-Request-Type and HX-Target headers
|
||||
ctx.request.headers["HX-Request-Type"] = (ctx.target === document.body || ctx.select) ? "full" : "partial";
|
||||
if (ctx.target) {
|
||||
@ -1476,7 +1504,7 @@ var htmx = (() => {
|
||||
}
|
||||
|
||||
takeClass(element, className, container = element.parentElement) {
|
||||
for (let elt of this.findAll(this.#normalizeElement(container), "." + className)) {
|
||||
for (let elt of this.#findAllExt(this.#normalizeElement(container), "." + className)) {
|
||||
elt.classList.remove(className);
|
||||
}
|
||||
element.classList.add(className);
|
||||
@ -1657,8 +1685,7 @@ var htmx = (() => {
|
||||
if (!indicatorsSelector) {
|
||||
indicatorElements = [elt]
|
||||
} else {
|
||||
let thisElt = this.#attributeValue(elt, "hx-indicator", undefined, true);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, false, thisElt);
|
||||
indicatorElements = this.#findAllExt(elt, indicatorsSelector, "hx-indicator");
|
||||
}
|
||||
for (const indicator of indicatorElements) {
|
||||
indicator._htmxReqCount ||= 0
|
||||
@ -1684,7 +1711,7 @@ var htmx = (() => {
|
||||
let disabledSelector = this.#attributeValue(elt, "hx-disable");
|
||||
let disabledElements = []
|
||||
if (disabledSelector) {
|
||||
disabledElements = this.#queryEltAndDescendants(elt, disabledSelector);
|
||||
disabledElements = this.#findAllExt(elt, disabledSelector, "hx-disable");
|
||||
for (let indicator of disabledElements) {
|
||||
indicator._htmxDisableCount ||= 0
|
||||
indicator._htmxDisableCount++
|
||||
@ -1760,22 +1787,36 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#getAttributeObject(elt, attrName) {
|
||||
let attrValue = this.#attributeValue(elt, attrName);
|
||||
if (!attrValue) return null;
|
||||
|
||||
let javascriptContent = this.#extractJavascriptContent(attrValue);
|
||||
if (javascriptContent) {
|
||||
// Wrap in braces if not already wrapped (for htmx 2.x compatibility)
|
||||
if (javascriptContent.indexOf('{') !== 0) {
|
||||
javascriptContent = '{' + javascriptContent + '}';
|
||||
}
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true);
|
||||
} else {
|
||||
// Synchronous path - return the parsed object directly
|
||||
return this.#parseConfig(attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
#handleHxVals(elt, body) {
|
||||
let hxValsValue = this.#attributeValue(elt, "hx-vals");
|
||||
if (hxValsValue) {
|
||||
let javascriptContent = this.#extractJavascriptContent(hxValsValue);
|
||||
if (javascriptContent) {
|
||||
// Return promise for async evaluation
|
||||
return this.#executeJavaScriptAsync(elt, {}, javascriptContent, true).then(obj => {
|
||||
let result = this.#getAttributeObject(elt, "hx-vals");
|
||||
if (result) {
|
||||
if (result instanceof Promise) {
|
||||
return result.then(obj => {
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
body.set(key, obj[key])
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Synchronous path
|
||||
let obj = this.#parseConfig(hxValsValue);
|
||||
for (let key in obj) {
|
||||
body.append(key, obj[key])
|
||||
for (let key in result) {
|
||||
body.set(key, result[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1786,11 +1827,11 @@ var htmx = (() => {
|
||||
return s.startsWith('<') && s.endsWith('/>') ? s.slice(1, -2) : s;
|
||||
}
|
||||
|
||||
#findAllExt(eltOrSelector, maybeSelector, global, thisElt) {
|
||||
#findAllExt(eltOrSelector, maybeSelector, thisAttr, global) {
|
||||
let selector = maybeSelector ?? eltOrSelector;
|
||||
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
|
||||
if (selector.startsWith('global ')) {
|
||||
return this.#findAllExt(elt, selector.slice(7), true, thisElt);
|
||||
return this.#findAllExt(elt, selector.slice(7), thisAttr, true);
|
||||
}
|
||||
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
|
||||
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
|
||||
@ -1822,7 +1863,11 @@ var htmx = (() => {
|
||||
} else if (selector === 'host') {
|
||||
item = (elt.getRootNode()).host
|
||||
} else if (selector === 'this') {
|
||||
item = thisElt || elt
|
||||
if (thisAttr) {
|
||||
result.push(...this.#findThisElements(elt, thisAttr));
|
||||
continue;
|
||||
}
|
||||
item = elt
|
||||
} else {
|
||||
unprocessedParts.push(selector)
|
||||
}
|
||||
@ -1838,7 +1883,7 @@ var htmx = (() => {
|
||||
result.push(...rootNode.querySelectorAll(standardSelector))
|
||||
}
|
||||
|
||||
return result
|
||||
return [...new Set(result)]
|
||||
}
|
||||
|
||||
#scanForwardQuery(start, match, global) {
|
||||
@ -1866,8 +1911,8 @@ var htmx = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
#findExt(eltOrSelector, selector, thisElt) {
|
||||
return this.#findAllExt(eltOrSelector, selector)[0]
|
||||
#findExt(eltOrSelector, selector, thisAttr) {
|
||||
return this.#findAllExt(eltOrSelector, selector, thisAttr)[0]
|
||||
}
|
||||
|
||||
#extractJavascriptContent(string) {
|
||||
|
||||
Binary file not shown.
2
www/static/js/htmx.min.js
vendored
2
www/static/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -130,11 +130,17 @@
|
||||
<!-- ============================================ -->
|
||||
<script src="./tests/attributes/hx-boost.js"></script>
|
||||
<script src="./tests/attributes/hx-config.js"></script>
|
||||
<script src="./tests/attributes/hx-delete.js"></script>
|
||||
<script src="./tests/attributes/hx-disable.js"></script>
|
||||
<script src="./tests/attributes/hx-get.js"></script>
|
||||
<script src="./tests/attributes/hx-headers.js"></script>
|
||||
<script src="./tests/attributes/hx-include.js"></script>
|
||||
<script src="./tests/attributes/hx-indicator.js"></script>
|
||||
<script src="./tests/attributes/hx-on.js"></script>
|
||||
<script src="./tests/attributes/hx-patch.js"></script>
|
||||
<script src="./tests/attributes/hx-post.js"></script>
|
||||
<script src="./tests/attributes/hx-preserve.js"></script>
|
||||
<script src="./tests/attributes/hx-put.js"></script>
|
||||
<script src="./tests/attributes/hx-select.js"></script>
|
||||
<script src="./tests/attributes/hx-select-oob.js"></script>
|
||||
<script src="./tests/attributes/hx-status.js"></script>
|
||||
|
||||
20
www/static/test/tests/attributes/hx-delete.js
Normal file
20
www/static/test/tests/attributes/hx-delete.js
Normal file
@ -0,0 +1,20 @@
|
||||
describe('hx-delete attribute', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTest(this.currentTest)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
it('issues a DELETE request', async function() {
|
||||
mockResponse('DELETE', '/test', 'Deleted!')
|
||||
let btn = createProcessedHTML('<button hx-delete="/test">Click Me!</button>')
|
||||
btn.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.method.should.equal('DELETE');
|
||||
btn.innerHTML.should.equal('Deleted!')
|
||||
})
|
||||
|
||||
})
|
||||
149
www/static/test/tests/attributes/hx-disable.js
Normal file
149
www/static/test/tests/attributes/hx-disable.js
Normal file
@ -0,0 +1,149 @@
|
||||
describe('hx-disable attribute', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTest(this.currentTest)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
it('single element can be disabled w/ hx-disable', async function() {
|
||||
mockResponse('GET', '/test', 'Clicked!')
|
||||
let btn = createProcessedHTML('<button hx-get="/test" hx-disable="this">Click Me!</button>')
|
||||
btn.hasAttribute('disabled').should.equal(false)
|
||||
btn.click()
|
||||
btn.hasAttribute('disabled').should.equal(true)
|
||||
await forRequest()
|
||||
btn.hasAttribute('disabled').should.equal(false)
|
||||
})
|
||||
|
||||
it('single element can be disabled w/ closest syntax', async function() {
|
||||
mockResponse('GET', '/test', 'Clicked!')
|
||||
let fieldset = createProcessedHTML('<fieldset><button id="b1" hx-get="/test" hx-disable="closest fieldset">Click Me!</button></fieldset>')
|
||||
let btn = find('#b1')
|
||||
fieldset.hasAttribute('disabled').should.equal(false)
|
||||
btn.click()
|
||||
fieldset.hasAttribute('disabled').should.equal(true)
|
||||
await forRequest()
|
||||
fieldset.hasAttribute('disabled').should.equal(false)
|
||||
})
|
||||
|
||||
it('multiple requests with same disabled elt are handled properly', async function() {
|
||||
mockResponse('GET', '/test', 'Clicked!')
|
||||
createProcessedHTML('<button id="b1" hx-get="/test" hx-disable="#b3">Click Me!</button>' +
|
||||
'<button id="b2" hx-get="/test" hx-disable="#b3">Click Me!</button>' +
|
||||
'<button id="b3">Demo</button>')
|
||||
|
||||
let b1 = find('#b1')
|
||||
let b2 = find('#b2')
|
||||
let b3 = find('#b3')
|
||||
|
||||
b3.hasAttribute('disabled').should.equal(false)
|
||||
|
||||
b1.click()
|
||||
b3.hasAttribute('disabled').should.equal(true)
|
||||
|
||||
b2.click()
|
||||
b3.hasAttribute('disabled').should.equal(true)
|
||||
|
||||
// Wait for first request to complete
|
||||
await forRequest()
|
||||
|
||||
b3.hasAttribute('disabled').should.equal(true)
|
||||
|
||||
// Wait for second request to complete
|
||||
await forRequest()
|
||||
|
||||
b3.hasAttribute('disabled').should.equal(false)
|
||||
})
|
||||
|
||||
it('multiple elts can be disabled', async function() {
|
||||
mockResponse('GET', '/test', 'Clicked!')
|
||||
createProcessedHTML('<button id="b1" hx-get="/test" hx-disable="#b2, #b3">Click Me!</button>' +
|
||||
'<button id="b2">Click Me!</button>' +
|
||||
'<button id="b3">Demo</button>')
|
||||
|
||||
let b1 = find('#b1')
|
||||
let b2 = find('#b2')
|
||||
let b3 = find('#b3')
|
||||
|
||||
b2.hasAttribute('disabled').should.equal(false)
|
||||
b3.hasAttribute('disabled').should.equal(false)
|
||||
|
||||
b1.click()
|
||||
b2.hasAttribute('disabled').should.equal(true)
|
||||
b3.hasAttribute('disabled').should.equal(true)
|
||||
|
||||
await forRequest()
|
||||
|
||||
b2.hasAttribute('disabled').should.equal(false)
|
||||
b3.hasAttribute('disabled').should.equal(false)
|
||||
})
|
||||
|
||||
it('load trigger does not prevent disabled element working', async function() {
|
||||
mockResponse('GET', '/test', 'Loaded!')
|
||||
createProcessedHTML('<div id="d1" hx-get="/test" hx-disable="#b1" hx-trigger="load">Load Me!</div><button id="b1">Demo</button>')
|
||||
|
||||
let div = find('#d1')
|
||||
let btn = find('#b1')
|
||||
|
||||
div.innerHTML.should.equal('Load Me!')
|
||||
btn.hasAttribute('disabled').should.equal(true)
|
||||
|
||||
await forRequest()
|
||||
|
||||
div.innerHTML.should.equal('Loaded!')
|
||||
btn.hasAttribute('disabled').should.equal(false)
|
||||
})
|
||||
|
||||
it('hx-disable supports multiple extended selectors', async function() {
|
||||
mockResponse('GET', '/test', 'Clicked!')
|
||||
let form = createProcessedHTML('<form hx-get="/test" hx-disable="find input[type=\'text\'], find button" hx-swap="none"><input id="i1" type="text" placeholder="Type here..."><button id="b2" type="submit">Send</button></form>')
|
||||
|
||||
let i1 = find('#i1')
|
||||
let b2 = find('#b2')
|
||||
|
||||
i1.hasAttribute('disabled').should.equal(false)
|
||||
b2.hasAttribute('disabled').should.equal(false)
|
||||
|
||||
b2.click()
|
||||
i1.hasAttribute('disabled').should.equal(true)
|
||||
b2.hasAttribute('disabled').should.equal(true)
|
||||
|
||||
await forRequest()
|
||||
|
||||
i1.hasAttribute('disabled').should.equal(false)
|
||||
b2.hasAttribute('disabled').should.equal(false)
|
||||
})
|
||||
|
||||
it('closest/find/next/previous handle nothing to find without exception', async function() {
|
||||
mockResponse('GET', '/test', 'Clicked!')
|
||||
createProcessedHTML('<button id="btn1" hx-get="/test" hx-disable="closest input">Click Me!</button>' +
|
||||
'<button id="btn2" hx-get="/test" hx-disable="find input">Click Me!</button>' +
|
||||
'<button id="btn3" hx-get="/test" hx-disable="next input">Click Me!</button>' +
|
||||
'<button id="btn4" hx-get="/test" hx-disable="previous input">Click Me!</button>')
|
||||
|
||||
let btn1 = find('#btn1')
|
||||
let btn2 = find('#btn2')
|
||||
let btn3 = find('#btn3')
|
||||
let btn4 = find('#btn4')
|
||||
|
||||
btn1.click()
|
||||
btn1.hasAttribute('disabled').should.equal(false)
|
||||
await forRequest()
|
||||
|
||||
btn2.click()
|
||||
btn2.hasAttribute('disabled').should.equal(false)
|
||||
await forRequest()
|
||||
|
||||
btn3.click()
|
||||
btn3.hasAttribute('disabled').should.equal(false)
|
||||
await forRequest()
|
||||
|
||||
btn4.click()
|
||||
btn4.hasAttribute('disabled').should.equal(false)
|
||||
await forRequest()
|
||||
})
|
||||
|
||||
})
|
||||
107
www/static/test/tests/attributes/hx-headers.js
Normal file
107
www/static/test/tests/attributes/hx-headers.js
Normal file
@ -0,0 +1,107 @@
|
||||
describe('hx-headers attribute', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTest(this.currentTest)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
it('basic hx-headers works', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML("<div hx-post='/vars' hx-headers='\"i1\":\"test\"'></div>")
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('basic hx-headers works with braces', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div hx-post="/vars" hx-headers=\'{"i1":"test"}\'></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('multiple hx-headers works', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div hx-post="/vars" hx-headers=\'{"v1":"test", "v2":"42"}\'></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.v1.should.equal('test');
|
||||
fetchMock.calls[0].request.headers.v2.should.equal('42');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('hx-headers can be inherited from parents', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
createProcessedHTML("<div hx-headers:inherited='\"i1\":\"test\"'><div id='d1' hx-post='/vars'></div></div>")
|
||||
let div = find('#d1')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('child hx-headers can override parent', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
createProcessedHTML("<div hx-headers:inherited='\"i1\":\"test\"'><div id='d1' hx-headers='\"i1\":\"best\"' hx-post='/vars'></div></div>")
|
||||
let div = find('#d1')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('best');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('basic hx-headers javascript: works', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div hx-post="/vars" hx-headers="javascript:i1:\'test\'"></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('hx-headers works with braces and javascript:', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div hx-post="/vars" hx-headers="javascript:{i1:\'test\'}"></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('multiple hx-headers works with javascript', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div hx-post="/vars" hx-headers="javascript:v1:\'test\', v2:42"></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.v1.should.equal('test');
|
||||
fetchMock.calls[0].request.headers.v2.should.equal('42');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('hx-headers can be inherited from parents with javascript', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
createProcessedHTML('<div hx-headers:inherited="javascript:i1:\'test\'"><div id="d1" hx-post="/vars"></div></div>')
|
||||
let div = find('#d1')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('child hx-headers can override parent with javascript', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
createProcessedHTML('<div hx-headers:inherited="javascript:i1:\'test\'"><div id="d1" hx-headers="javascript:i1:\'best\'" hx-post="/vars"></div></div>')
|
||||
let div = find('#d1')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.headers.i1.should.equal('best');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
})
|
||||
20
www/static/test/tests/attributes/hx-patch.js
Normal file
20
www/static/test/tests/attributes/hx-patch.js
Normal file
@ -0,0 +1,20 @@
|
||||
describe('hx-patch attribute', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTest(this.currentTest)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
it('issues a PATCH request', async function() {
|
||||
mockResponse('PATCH', '/test', 'Patched!')
|
||||
let btn = createProcessedHTML('<button hx-patch="/test">Click Me!</button>')
|
||||
btn.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.method.should.equal('PATCH');
|
||||
btn.innerHTML.should.equal('Patched!')
|
||||
})
|
||||
|
||||
})
|
||||
21
www/static/test/tests/attributes/hx-post.js
Normal file
21
www/static/test/tests/attributes/hx-post.js
Normal file
@ -0,0 +1,21 @@
|
||||
describe('hx-post attribute', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTest(this.currentTest)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
it('issues a POST request with proper headers', async function() {
|
||||
mockResponse('POST', '/test', 'Posted!')
|
||||
let btn = createProcessedHTML('<button hx-post="/test">Click Me!</button>')
|
||||
btn.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.method.should.equal('POST');
|
||||
should.equal(fetchMock.calls[0].request.headers['X-HTTP-Method-Override'], undefined);
|
||||
btn.innerHTML.should.equal('Posted!')
|
||||
})
|
||||
|
||||
})
|
||||
20
www/static/test/tests/attributes/hx-put.js
Normal file
20
www/static/test/tests/attributes/hx-put.js
Normal file
@ -0,0 +1,20 @@
|
||||
describe('hx-put attribute', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTest(this.currentTest)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
it('issues a PUT request', async function() {
|
||||
mockResponse('PUT', '/test', 'Put!')
|
||||
let btn = createProcessedHTML('<button hx-put="/test">Click Me!</button>')
|
||||
btn.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.method.should.equal('PUT');
|
||||
btn.innerHTML.should.equal('Put!')
|
||||
})
|
||||
|
||||
})
|
||||
202
www/static/test/tests/attributes/hx-sync.js
Normal file
202
www/static/test/tests/attributes/hx-sync.js
Normal file
@ -0,0 +1,202 @@
|
||||
describe('hx-sync attribute', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTest(this.currentTest)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
|
||||
it('defaults to queue first strategy', async function() {
|
||||
createProcessedHTML('<div hx-sync:inherited="this">' +
|
||||
'<button id="b1" hx-get="/test1">Initial</button>' +
|
||||
'<button id="b2" hx-get="/test2">Initial</button>' +
|
||||
'<button id="b3" hx-get="/test3">Initial</button></div>')
|
||||
|
||||
let b1 = find('#b1')
|
||||
let b2 = find('#b2')
|
||||
let b3 = find('#b3')
|
||||
b1.click()
|
||||
b2.click()
|
||||
b3.click()
|
||||
await forRequest()
|
||||
b1.innerHTML.should.equal('Click 1')
|
||||
b2.innerHTML.should.equal('Initial')
|
||||
b3.innerHTML.should.equal('Initial')
|
||||
await forRequest()
|
||||
b1.innerHTML.should.equal('Click 1')
|
||||
b2.innerHTML.should.equal('Click 2')
|
||||
b3.innerHTML.should.equal('Initial')
|
||||
})
|
||||
|
||||
// it('can use replace strategy', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div id="sync-container">' +
|
||||
// '<button id="b1" hx-get="/test" hx-sync="#sync-container:replace">Initial</button>' +
|
||||
// '<button id="b2" hx-get="/test" hx-sync="#sync-container:replace">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// b1.click()
|
||||
// b2.click()
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Initial')
|
||||
// b2.innerHTML.should.equal('Click 0')
|
||||
// })
|
||||
//
|
||||
// it('can use queue all strategy', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div id="sync-container">' +
|
||||
// '<button id="b1" hx-get="/test" hx-sync="#sync-container:queue all">Initial</button>' +
|
||||
// '<button id="b2" hx-get="/test" hx-sync="#sync-container:queue all">Initial</button>' +
|
||||
// '<button id="b3" hx-get="/test" hx-sync="#sync-container:queue all">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// let b3 = find('#b3')
|
||||
//
|
||||
// b1.click()
|
||||
// b2.click()
|
||||
// b3.click()
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Initial')
|
||||
// b3.innerHTML.should.equal('Initial')
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Click 1')
|
||||
// b3.innerHTML.should.equal('Initial')
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Click 1')
|
||||
// b3.innerHTML.should.equal('Click 2')
|
||||
// })
|
||||
//
|
||||
// it('can use queue last strategy', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div id="sync-container">' +
|
||||
// '<button id="b1" hx-get="/test" hx-sync="#sync-container:queue last">Initial</button>' +
|
||||
// '<button id="b2" hx-get="/test" hx-sync="#sync-container:queue last">Initial</button>' +
|
||||
// '<button id="b3" hx-get="/test" hx-sync="#sync-container:queue last">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// let b3 = find('#b3')
|
||||
//
|
||||
// b1.click()
|
||||
// b2.click()
|
||||
// b3.click()
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Initial')
|
||||
// b3.innerHTML.should.equal('Initial')
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Initial')
|
||||
// b3.innerHTML.should.equal('Click 1')
|
||||
// })
|
||||
//
|
||||
// it('can use queue first strategy', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div id="sync-container">' +
|
||||
// '<button id="b1" hx-get="/test" hx-sync="#sync-container:queue first">Initial</button>' +
|
||||
// '<button id="b2" hx-get="/test" hx-sync="#sync-container:queue first">Initial</button>' +
|
||||
// '<button id="b3" hx-get="/test" hx-sync="#sync-container:queue first">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// let b3 = find('#b3')
|
||||
//
|
||||
// b1.click()
|
||||
// b2.click()
|
||||
// b3.click()
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Initial')
|
||||
// b3.innerHTML.should.equal('Initial')
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Click 1')
|
||||
// b3.innerHTML.should.equal('Initial')
|
||||
// })
|
||||
//
|
||||
// it('can use abort strategy to end existing abortable request', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div id="sync-container">' +
|
||||
// '<button id="b1" hx-sync="#sync-container:abort" hx-get="/test">Initial</button>' +
|
||||
// '<button id="b2" hx-sync="#sync-container:drop" hx-get="/test">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// b1.click()
|
||||
// b2.click()
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Initial')
|
||||
// b2.innerHTML.should.equal('Click 0')
|
||||
// })
|
||||
//
|
||||
// it('can use abort strategy to drop abortable request when one is in flight', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div id="sync-container">' +
|
||||
// '<button id="b1" hx-sync="#sync-container:abort" hx-get="/test">Initial</button>' +
|
||||
// '<button id="b2" hx-sync="#sync-container:drop" hx-get="/test">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// b2.click()
|
||||
// b1.click()
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Initial')
|
||||
// b2.innerHTML.should.equal('Click 0')
|
||||
// })
|
||||
//
|
||||
// it('can abort a request programmatically', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div><button id="b1" hx-get="/test">Initial</button>' +
|
||||
// '<button id="b2" hx-get="/test">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// b1.click()
|
||||
// b2.click()
|
||||
//
|
||||
// htmx.trigger(b1, 'htmx:abort')
|
||||
//
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Initial')
|
||||
// b2.innerHTML.should.equal('Click 0')
|
||||
// })
|
||||
//
|
||||
// it('can use drop strategy', async function() {
|
||||
// let count = 0
|
||||
// mockResponse('GET', '/test', () => 'Click ' + count++)
|
||||
// createProcessedHTML('<div id="sync-container">' +
|
||||
// '<button id="b1" hx-get="/test" hx-sync="#sync-container:drop">Initial</button>' +
|
||||
// '<button id="b2" hx-get="/test" hx-sync="#sync-container:drop">Initial</button></div>')
|
||||
//
|
||||
// let b1 = find('#b1')
|
||||
// let b2 = find('#b2')
|
||||
// b1.click()
|
||||
// b2.click()
|
||||
// await forRequest()
|
||||
// b1.innerHTML.should.equal('Click 0')
|
||||
// b2.innerHTML.should.equal('Initial')
|
||||
// })
|
||||
|
||||
})
|
||||
@ -8,6 +8,110 @@ describe('hx-vals attribute', function() {
|
||||
cleanupTest(this.currentTest)
|
||||
})
|
||||
|
||||
// TODO - convert to a direct test
|
||||
it('basic hx-vals works with HCON', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML("<div hx-post='/vars' hx-vals='i1:\"test\"'></div>")
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("i1").should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('basic hx-vals works without braces', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML("<div hx-post='/vars' hx-vals='\"i1\":\"test\"'></div>")
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("i1").should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('basic hx-vals works with braces', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div hx-post="/vars" hx-vals=\'{"i1":"test"}\'></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("i1").should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('multiple hx-vals works', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div hx-post="/vars" hx-vals=\'{"v1":"test", "v2":42}\'></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("v1").should.equal('test');
|
||||
fetchMock.calls[0].request.body.get("v2").should.equal('42');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('Dynamic hx-vals using spread operator works', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
window.foo = function() {
|
||||
return { v1: 'test', v2: 42 }
|
||||
}
|
||||
let div = createProcessedHTML("<div hx-post='/vars' hx-vals='js:{...foo()}'></div>")
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("v1").should.equal('test');
|
||||
fetchMock.calls[0].request.body.get("v2").should.equal('42');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
delete window.foo
|
||||
})
|
||||
|
||||
it('hx-vals can be inherited from parents', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
createProcessedHTML("<div hx-vals:inherited='\"i1\":\"test\"'><div id='d1' hx-post='/vars'></div></div>")
|
||||
let div = find('#d1')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("i1").should.equal('test');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('child hx-vals can override parent', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
createProcessedHTML("<div hx-vals:inherited='\"i1\":\"test\"'><div id='d1' hx-post='/vars' hx-vals='\"i1\":\"override\"'></div></div>")
|
||||
let div = find('#d1')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("i1").should.equal('override');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('hx-vals overrides input values', async function() {
|
||||
mockResponse('POST', '/vals', 'Submitted')
|
||||
let form = createProcessedHTML('<form hx-post="/vals" hx-vals=\'{"i1":"test"}\'>' +
|
||||
'<input name="i1" value="original"/>' +
|
||||
'<button>Submit</button>' +
|
||||
'</form>')
|
||||
form.querySelector('button').click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("i1").should.equal('test');
|
||||
form.innerHTML.should.equal('Submitted')
|
||||
})
|
||||
|
||||
it('computed values using event in js: prefix', async function() {
|
||||
mockResponse('POST', '/vars', 'Clicked!')
|
||||
let div = createProcessedHTML('<div id="myDiv" hx-post="/vars" hx-vals=\'js:{i1: event.target.id}\'></div>')
|
||||
div.click()
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get("i1").should.equal('myDiv');
|
||||
div.innerHTML.should.equal('Clicked!')
|
||||
})
|
||||
|
||||
it('hx-vals:append merges JSON objects correctly', async function() {
|
||||
mockResponse('POST', '/test', 'Success')
|
||||
const div = createProcessedHTML(
|
||||
'<div hx-vals:inherited=\'{"a":1}\'>' +
|
||||
' <button hx-post="/test" hx-vals:append=\'{"b":"hi"}\'>Click</button>' +
|
||||
'</div>'
|
||||
);
|
||||
const button = div.querySelector('button');
|
||||
button.click();
|
||||
await forRequest()
|
||||
fetchMock.calls[0].request.body.get('a').should.equal('1');
|
||||
fetchMock.calls[0].request.body.get('b').should.equal('hi');
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
@ -27,7 +27,7 @@ describe('__atributeValue() unit tests', function() {
|
||||
);
|
||||
const button = container.querySelector('button');
|
||||
const result = htmx.__attributeValue(button, 'hx-vals');
|
||||
assert.equal(result, '{"a":1},{"b":2}');
|
||||
assert.equal(result, '"a":1,"b":2');
|
||||
});
|
||||
|
||||
it(':inherited still works normally', function () {
|
||||
|
||||
@ -130,4 +130,55 @@ describe('__disableElements / __enableElements unit tests', function() {
|
||||
assert.equal(input._htmxDisableCount, 1)
|
||||
})
|
||||
|
||||
it('resolves this selector for disable', function () {
|
||||
let container = createProcessedHTML('<button hx-disable="this" hx-get="/test"></button>');
|
||||
|
||||
let elements = htmx.__disableElements(container);
|
||||
|
||||
assert.isTrue(container.disabled);
|
||||
assert.equal(elements.length, 1);
|
||||
assert.equal(elements[0], container);
|
||||
})
|
||||
|
||||
it('resolves this selector with inherited disable', function () {
|
||||
let container = createProcessedHTML('<button hx-disable:inherited="this"><span hx-get="/test"></span></button>');
|
||||
let span = container.querySelector('span');
|
||||
|
||||
let elements = htmx.__disableElements(span);
|
||||
|
||||
assert.isTrue(container.disabled);
|
||||
})
|
||||
|
||||
it('resolves this selector respecting disable override', function () {
|
||||
let html = '<button hx-disable="this"><span hx-disable=".other"><input hx-get="/test"></span></button>';
|
||||
let outer = createProcessedHTML(html);
|
||||
let input = outer.querySelector('input');
|
||||
|
||||
let elements = htmx.__disableElements(input);
|
||||
|
||||
assert.isFalse(outer.disabled);
|
||||
assert.equal(elements.length, 0);
|
||||
})
|
||||
|
||||
it('resolves this selector with append for disable', function () {
|
||||
let html = '<button hx-disable:inherited="this"><input hx-disable:append="this" hx-get="/test"></button>';
|
||||
let outer = createProcessedHTML(html);
|
||||
let inner = outer.querySelector('input');
|
||||
|
||||
let elements = htmx.__disableElements(inner);
|
||||
|
||||
assert.equal(elements.length, 2);
|
||||
assert.isTrue(inner.disabled);
|
||||
assert.isTrue(outer.disabled);
|
||||
})
|
||||
|
||||
it('resolves this selector with comma-separated disable values', function () {
|
||||
let html = '<button hx-disable="this, .other" hx-get="/test"></button>';
|
||||
let button = createProcessedHTML(html);
|
||||
|
||||
let elements = htmx.__disableElements(button);
|
||||
|
||||
assert.isTrue(button.disabled);
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@ -130,4 +130,61 @@ describe('__showIndicators / __hideIndicators unit tests', function() {
|
||||
assert.equal(div._htmxReqCount, 1)
|
||||
})
|
||||
|
||||
it('resolves this selector for indicators', function () {
|
||||
let container = createProcessedHTML('<div hx-indicator="this"><button hx-get="/test" hx-indicator="this"></button></div>');
|
||||
let button = container.querySelector('button');
|
||||
|
||||
let indicators = htmx.__showIndicators(button);
|
||||
|
||||
assert.isTrue(button.classList.contains('htmx-request'));
|
||||
assert.equal(indicators.length, 1);
|
||||
assert.equal(indicators[0], button);
|
||||
})
|
||||
|
||||
it('resolves this selector with inherited indicator', function () {
|
||||
let outer = createProcessedHTML('<div hx-indicator:inherited="this"><button hx-get="/test"></button></div>');
|
||||
let button = outer.querySelector('button');
|
||||
|
||||
let indicators = htmx.__showIndicators(button);
|
||||
|
||||
assert.isTrue(outer.classList.contains('htmx-request'));
|
||||
assert.equal(indicators.length, 1);
|
||||
assert.equal(indicators[0], outer);
|
||||
})
|
||||
|
||||
it('resolves this selector respecting indicator override', function () {
|
||||
let html = '<div hx-indicator="this"><button hx-get="/test" hx-indicator=".other" class="other"></button></div>';
|
||||
let outer = createProcessedHTML(html);
|
||||
let button = outer.querySelector('button');
|
||||
|
||||
let indicators = htmx.__showIndicators(button);
|
||||
|
||||
assert.isFalse(outer.classList.contains('htmx-request'));
|
||||
assert.isTrue(button.classList.contains('htmx-request'));
|
||||
})
|
||||
|
||||
it('resolves this selector with append for indicators', function () {
|
||||
let html = '<div hx-indicator:inherited="this"><button hx-get="/test" hx-indicator:append="this"></button></div>';
|
||||
let outer = createProcessedHTML(html);
|
||||
let button = outer.querySelector('button');
|
||||
|
||||
let indicators = htmx.__showIndicators(button);
|
||||
|
||||
assert.isTrue(outer.classList.contains('htmx-request'));
|
||||
assert.isTrue(button.classList.contains('htmx-request'));
|
||||
assert.equal(indicators.length, 2);
|
||||
})
|
||||
|
||||
it('resolves this selector with comma-separated indicator values', function () {
|
||||
let html = '<div class="other"><button hx-get="/test" hx-indicator="this, .other"></button></div>';
|
||||
let container = createProcessedHTML(html);
|
||||
let button = container.querySelector('button');
|
||||
|
||||
let indicators = htmx.__showIndicators(button);
|
||||
|
||||
assert.isTrue(button.classList.contains('htmx-request'));
|
||||
assert.isTrue(container.classList.contains('htmx-request'));
|
||||
assert.equal(indicators.length, 2);
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@ -53,7 +53,7 @@ describe('htmx.config.implicitInheritance test', function() {
|
||||
);
|
||||
const button = container.querySelector('button');
|
||||
const result = htmx.__attributeValue(button, 'hx-vals');
|
||||
assert.equal(result, '{"a":1},{"b":2}');
|
||||
assert.equal(result, '"a":1,"b":2');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user