update web

This commit is contained in:
Carson Gross 2025-12-06 11:37:16 -07:00
parent 7972c4ea5a
commit 0df6c98e4e
30 changed files with 527 additions and 235 deletions

View File

@ -10,7 +10,7 @@
}
}
htmx.defineExtension('compat', {
htmx.registerExtension('compat', {
init: (internalAPI) => {
api = internalAPI;
@ -24,14 +24,68 @@
htmx.config.noSwap.push("4xx", "5xx");
}
},
htmx_after_implicitInheritance : function(elt, detail) {
// TODO - how should we alert users? collect a report? just log?
// TODO - needs a config option to enable
// Re-delegate new events to old event names for backwards compatibility
htmx_after_implicitInheritance: function (elt, detail) {
if (!htmx.config.compat?.suppressInheritanceLogs) {
console.log("IMPLICIT INHERITANCE DETECTED, attribute: " + detail.name + ", elt: ", elt, ", inherited from: ", detail.parent)
let evt = new CustomEvent("htmxImplicitInheritace", {
detail,
cancelable: true,
bubbles : true,
composed: true,
});
elt.dispatchEvent(evt)
}
},
// TODO - catch all new events and redelegate to old event names as best we can
// this can probably be done as a map... See www/content/migration-guide-htmx-4.md:94
htmx_after_init: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:afterOnLoad", detail);
maybeRetriggerEvent(elt, "htmx:afterProcessNode", detail);
maybeRetriggerEvent(elt, "htmx:load", detail);
},
htmx_after_request: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:afterRequest", detail);
},
htmx_after_swap: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:afterSettle", detail);
maybeRetriggerEvent(elt, "htmx:afterSwap", detail);
},
htmx_before_cleanup: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:beforeCleanupElement", detail);
},
htmx_before_history_update: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:beforeHistoryUpdate", detail);
maybeRetriggerEvent(elt, "htmx:beforeHistorySave", detail);
},
htmx_before_init: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:beforeOnLoad", detail);
},
htmx_before_process: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:beforeProcessNode", detail);
},
htmx_before_request: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:beforeRequest", detail);
maybeRetriggerEvent(elt, "htmx:beforeSend", detail);
},
htmx_before_swap: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:beforeSwap", detail);
},
htmx_before_viewTransition: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:beforeTransition", detail);
},
htmx_config_request: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:configRequest", detail);
},
htmx_before_restore_history: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:historyRestore", detail);
},
htmx_after_push_into_history: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:pushedIntoHistory", detail);
},
htmx_after_replace_into_history: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:replacedInHistory", detail);
},
htmx_error: function (elt, detail) {
maybeRetriggerEvent(elt, "htmx:targetError", detail);
},
});
})()

Binary file not shown.

View File

@ -1 +1 @@
(()=>{let t;htmx.defineExtension("compat",{init:i=>{t=i,htmx.config.compat?.useExplicitInheritace||(htmx.config.implicitInheritance=!0),htmx.config.compat?.swapErrorResponseCodes||htmx.config.noSwap.push("4xx","5xx")},htmx_after_implicitInheritance:function(t,i){},htmx_after_init:function(t,i){!function(t,i,n){htmx.config.compat?.doNotTriggerOldEvents||htmx.trigger(t,i,n)}(t,"htmx:afterOnLoad",i)}})})();
(()=>{let t;function e(t,e,o){htmx.config.compat?.doNotTriggerOldEvents||htmx.trigger(t,e,o)}htmx.registerExtension("compat",{init:e=>{t=e,htmx.config.compat?.useExplicitInheritace||(htmx.config.implicitInheritance=!0),htmx.config.compat?.swapErrorResponseCodes||htmx.config.noSwap.push("4xx","5xx")},htmx_after_implicitInheritance:function(t,e){if(!htmx.config.compat?.suppressInheritanceLogs){console.log("IMPLICIT INHERITANCE DETECTED, attribute: "+e.name+", elt: ",t,", inherited from: ",e.parent);let o=new CustomEvent("htmxImplicitInheritace",{detail:e,cancelable:!0,bubbles:!0,composed:!0});t.dispatchEvent(o)}},htmx_after_init:function(t,o){e(t,"htmx:afterOnLoad",o),e(t,"htmx:afterProcessNode",o),e(t,"htmx:load",o)},htmx_after_request:function(t,o){e(t,"htmx:afterRequest",o)},htmx_after_swap:function(t,o){e(t,"htmx:afterSettle",o),e(t,"htmx:afterSwap",o)},htmx_before_cleanup:function(t,o){e(t,"htmx:beforeCleanupElement",o)},htmx_before_history_update:function(t,o){e(t,"htmx:beforeHistoryUpdate",o),e(t,"htmx:beforeHistorySave",o)},htmx_before_init:function(t,o){e(t,"htmx:beforeOnLoad",o)},htmx_before_process:function(t,o){e(t,"htmx:beforeProcessNode",o)},htmx_before_request:function(t,o){e(t,"htmx:beforeRequest",o),e(t,"htmx:beforeSend",o)},htmx_before_swap:function(t,o){e(t,"htmx:beforeSwap",o)},htmx_before_viewTransition:function(t,o){e(t,"htmx:beforeTransition",o)},htmx_config_request:function(t,o){e(t,"htmx:configRequest",o)},htmx_before_restore_history:function(t,o){e(t,"htmx:historyRestore",o)},htmx_after_push_into_history:function(t,o){e(t,"htmx:pushedIntoHistory",o)},htmx_after_replace_into_history:function(t,o){e(t,"htmx:replacedInHistory",o)},htmx_error:function(t,o){e(t,"htmx:targetError",o)}})})();

View File

@ -1 +1 @@
{"version":3,"names":["api","htmx","defineExtension","init","internalAPI","config","compat","useExplicitInheritace","implicitInheritance","swapErrorResponseCodes","noSwap","push","htmx_after_implicitInheritance","elt","detail","htmx_after_init","evtName","doNotTriggerOldEvents","trigger","maybeRetriggerEvent"],"sources":["dist/ext/hx-compat.js"],"mappings":"AAAA,MAII,IAAIA,EAQJC,KAAKC,gBAAgB,SAAU,CAC3BC,KAAOC,IACHJ,EAAMI,EAGDH,KAAKI,OAAOC,QAAQC,wBACrBN,KAAKI,OAAOG,qBAAsB,GAIjCP,KAAKI,OAAOC,QAAQG,wBACrBR,KAAKI,OAAOK,OAAOC,KAAK,MAAO,QAGvCC,+BAAiC,SAASC,EAAKC,GAG/C,EAGAC,gBAAiB,SAAUF,EAAKC,IA1BpC,SAA6BD,EAAKG,EAASF,GAClCb,KAAKI,OAAOC,QAAQW,uBACrBhB,KAAKiB,QAAQL,EAAKG,EAASF,EAEnC,CAuBQK,CAAoBN,EAAK,mBAAoBC,EACjD,GAEP,EApCD","ignoreList":[]}
{"version":3,"names":["api","maybeRetriggerEvent","elt","evtName","detail","htmx","config","compat","doNotTriggerOldEvents","trigger","registerExtension","init","internalAPI","useExplicitInheritace","implicitInheritance","swapErrorResponseCodes","noSwap","push","htmx_after_implicitInheritance","suppressInheritanceLogs","console","log","name","parent","evt","CustomEvent","cancelable","bubbles","composed","dispatchEvent","htmx_after_init","htmx_after_request","htmx_after_swap","htmx_before_cleanup","htmx_before_history_update","htmx_before_init","htmx_before_process","htmx_before_request","htmx_before_swap","htmx_before_viewTransition","htmx_config_request","htmx_before_restore_history","htmx_after_push_into_history","htmx_after_replace_into_history","htmx_error"],"sources":["dist/ext/hx-compat.js"],"mappings":"AAAA,MAII,IAAIA,EAEJ,SAASC,EAAoBC,EAAKC,EAASC,GAClCC,KAAKC,OAAOC,QAAQC,uBACrBH,KAAKI,QAAQP,EAAKC,EAASC,EAEnC,CAEAC,KAAKK,kBAAkB,SAAU,CAC7BC,KAAOC,IACHZ,EAAMY,EAGDP,KAAKC,OAAOC,QAAQM,wBACrBR,KAAKC,OAAOQ,qBAAsB,GAIjCT,KAAKC,OAAOC,QAAQQ,wBACrBV,KAAKC,OAAOU,OAAOC,KAAK,MAAO,QAIvCC,+BAAgC,SAAUhB,EAAKE,GAC3C,IAAKC,KAAKC,OAAOC,QAAQY,wBAAyB,CAC9CC,QAAQC,IAAI,6CAA+CjB,EAAOkB,KAAO,UAAWpB,EAAK,qBAAsBE,EAAOmB,QACtH,IAAIC,EAAM,IAAIC,YAAY,yBAA0B,CAChDrB,SACAsB,YAAY,EACZC,SAAU,EACVC,UAAU,IAEd1B,EAAI2B,cAAcL,EACtB,CACJ,EACAM,gBAAiB,SAAU5B,EAAKE,GAC5BH,EAAoBC,EAAK,mBAAoBE,GAC7CH,EAAoBC,EAAK,wBAAyBE,GAClDH,EAAoBC,EAAK,YAAaE,EAC1C,EACA2B,mBAAoB,SAAU7B,EAAKE,GAC/BH,EAAoBC,EAAK,oBAAqBE,EAClD,EACA4B,gBAAiB,SAAU9B,EAAKE,GAC5BH,EAAoBC,EAAK,mBAAoBE,GAC7CH,EAAoBC,EAAK,iBAAkBE,EAC/C,EACA6B,oBAAqB,SAAU/B,EAAKE,GAChCH,EAAoBC,EAAK,4BAA6BE,EAC1D,EACA8B,2BAA4B,SAAUhC,EAAKE,GACvCH,EAAoBC,EAAK,2BAA4BE,GACrDH,EAAoBC,EAAK,yBAA0BE,EACvD,EACA+B,iBAAkB,SAAUjC,EAAKE,GAC7BH,EAAoBC,EAAK,oBAAqBE,EAClD,EACAgC,oBAAqB,SAAUlC,EAAKE,GAChCH,EAAoBC,EAAK,yBAA0BE,EACvD,EACAiC,oBAAqB,SAAUnC,EAAKE,GAChCH,EAAoBC,EAAK,qBAAsBE,GAC/CH,EAAoBC,EAAK,kBAAmBE,EAChD,EACAkC,iBAAkB,SAAUpC,EAAKE,GAC7BH,EAAoBC,EAAK,kBAAmBE,EAChD,EACAmC,2BAA4B,SAAUrC,EAAKE,GACvCH,EAAoBC,EAAK,wBAAyBE,EACtD,EACAoC,oBAAqB,SAAUtC,EAAKE,GAChCH,EAAoBC,EAAK,qBAAsBE,EACnD,EACAqC,4BAA6B,SAAUvC,EAAKE,GACxCH,EAAoBC,EAAK,sBAAuBE,EACpD,EACAsC,6BAA8B,SAAUxC,EAAKE,GACzCH,EAAoBC,EAAK,yBAA0BE,EACvD,EACAuC,gCAAiC,SAAUzC,EAAKE,GAC5CH,EAAoBC,EAAK,yBAA0BE,EACvD,EACAwC,WAAY,SAAU1C,EAAKE,GACvBH,EAAoBC,EAAK,mBAAoBE,EACjD,GAEP,EA1FD","ignoreList":[]}

View File

@ -0,0 +1,129 @@
//==========================================================
// head-support.js
//
// An extension to add head tag merging.
//==========================================================
(function () {
let api
function log() {
//console.log(arguments)
}
function mergeHead(newContent, defaultMergeStrategy) {
if (newContent && newContent.indexOf('<head') > -1) {
const htmlDoc = document.createElement("html")
// remove svgs to avoid conflicts
let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '')
// extract head tag
let headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im)
// if the head tag exists...
if (headTag) {
let added = []
let removed = []
let preserved = []
let nodesToAppend = []
htmlDoc.innerHTML = headTag
let newHeadTag = htmlDoc.querySelector("head")
let currentHead = document.head
if (newHeadTag == null) {
return
}
// put all new head elements into a Map, by their outerHTML
let srcToNewHeadNodes = new Map()
for (const newHeadChild of newHeadTag.children) {
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild)
}
// determine merge strategy
let mergeStrategy = api.attributeValue(newHeadTag, "hx-head") || defaultMergeStrategy
// get the current head
for (const currentHeadElt of currentHead.children) {
// If the current head element is in the map
let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML)
let isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval"
let isPreserved = api.attributeValue(currentHeadElt, "hx-preserve") === "true"
if (inNewContent || isPreserved) {
if (isReAppended) {
// remove the current version and let the new version replace it and re-execute
removed.push(currentHeadElt)
} else {
// this element already exists and should not be re-appended, so remove it from
// the new content map, preserving it in the DOM
srcToNewHeadNodes.delete(currentHeadElt.outerHTML)
preserved.push(currentHeadElt)
}
} else {
if (mergeStrategy === "append") {
// we are appending and this existing element is not new content
// so if and only if it is marked for re-append do we do anything
if (isReAppended) {
removed.push(currentHeadElt)
nodesToAppend.push(currentHeadElt)
}
} else {
// if this is a merge, we remove this content since it is not in the new head
if (htmx.trigger(document.body, "htmx:before:head:remove", {headElement: currentHeadElt}) !== false) {
removed.push(currentHeadElt)
}
}
}
}
// Push the remaining new head elements in the Map into the
// nodes to append to the head tag
nodesToAppend.push(...srcToNewHeadNodes.values())
log("to append: ", nodesToAppend)
for (const newNode of nodesToAppend) {
log("adding: ", newNode)
let newElt = document.createRange().createContextualFragment(newNode.outerHTML)
log(newElt)
if (htmx.trigger(document.body, "htmx:before:head:add", {headElement: newElt}) !== false) {
currentHead.appendChild(newElt)
added.push(newElt)
}
}
// remove all removed elements, after we have appended the new elements to avoid
// additional network requests for things like style sheets
for (const removedElement of removed) {
if (htmx.trigger(document.body, "htmx:before:head:remove", {headElement: removedElement}) !== false) {
currentHead.removeChild(removedElement)
}
}
htmx.trigger(document.body, "htmx:after:head:merge", {
added: added,
kept: preserved,
removed: removed
})
}
}
}
htmx.registerExtension("hx-head", {
init: (internalAPI) => {
api = internalAPI;
},
htmx_after_swap: (elt, detail) => {
let ctx = detail.ctx
let target = ctx.target
// TODO - is there a better way to handle this? it used to be based on if the element was boosted
let defaultMergeStrategy = target === document.body ? "merge" : "append";
if (htmx.trigger(document.body, "htmx:before:head:merge", detail)) {
mergeHead(ctx.text, defaultMergeStrategy)
}
}
})
})()

Binary file not shown.

1
www/static/js/ext/hx-head.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(){let e;function t(){}htmx.registerExtension("hx-head",{init:t=>{e=t},htmx_after_swap:(r,d)=>{let h=d.ctx,o=h.target===document.body?"merge":"append";htmx.trigger(document.body,"htmx:before:head:merge",d)&&function(r,d){if(r&&r.indexOf("<head")>-1){const h=document.createElement("html");let o=r.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"").match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);if(o){let r=[],a=[],n=[],m=[];h.innerHTML=o;let u=h.querySelector("head"),l=document.head;if(null==u)return;let c=new Map;for(const e of u.children)c.set(e.outerHTML,e);let i=e.attributeValue(u,"hx-head")||d;for(const t of l.children){let r=c.has(t.outerHTML),d="re-eval"===t.getAttribute("hx-head"),h="true"===e.attributeValue(t,"hx-preserve");r||h?d?a.push(t):(c.delete(t.outerHTML),n.push(t)):"append"===i?d&&(a.push(t),m.push(t)):!1!==htmx.trigger(document.body,"htmx:before:head:remove",{headElement:t})&&a.push(t)}m.push(...c.values());for(const e of m){t();let d=document.createRange().createContextualFragment(e.outerHTML);t(),!1!==htmx.trigger(document.body,"htmx:before:head:add",{headElement:d})&&(l.appendChild(d),r.push(d))}for(const e of a)!1!==htmx.trigger(document.body,"htmx:before:head:remove",{headElement:e})&&l.removeChild(e);htmx.trigger(document.body,"htmx:after:head:merge",{added:r,kept:n,removed:a})}}}(h.text,o)}})}();

Binary file not shown.

View File

@ -0,0 +1 @@
{"version":3,"names":["api","log","htmx","registerExtension","init","internalAPI","htmx_after_swap","elt","detail","ctx","defaultMergeStrategy","target","document","body","trigger","newContent","indexOf","htmlDoc","createElement","headTag","replace","match","added","removed","preserved","nodesToAppend","innerHTML","newHeadTag","querySelector","currentHead","head","srcToNewHeadNodes","Map","newHeadChild","children","set","outerHTML","mergeStrategy","attributeValue","currentHeadElt","inNewContent","has","isReAppended","getAttribute","isPreserved","push","delete","headElement","values","newNode","newElt","createRange","createContextualFragment","appendChild","removedElement","removeChild","kept","mergeHead","text"],"sources":["dist/ext/hx-head.js"],"mappings":"CAKA,WAEI,IAAIA,EAEJ,SAASC,IAET,CAsGAC,KAAKC,kBAAkB,UAAW,CAC9BC,KAAOC,IACHL,EAAMK,GAEVC,gBAAiB,CAACC,EAAKC,KACnB,IAAIC,EAAMD,EAAOC,IAGbC,EAFSD,EAAIE,SAEqBC,SAASC,KAAO,QAAU,SAC5DX,KAAKY,QAAQF,SAASC,KAAM,yBAA0BL,IA7GlE,SAAmBO,EAAYL,GAE3B,GAAIK,GAAcA,EAAWC,QAAQ,UAAY,EAAG,CAChD,MAAMC,EAAUL,SAASM,cAAc,QAEvC,IAEIC,EAFyBJ,EAAWK,QAAQ,uCAAwC,IAEnDC,MAAM,2CAG3C,GAAIF,EAAS,CAET,IAAIG,EAAQ,GACRC,EAAU,GACVC,EAAY,GACZC,EAAgB,GAEpBR,EAAQS,UAAYP,EACpB,IAAIQ,EAAaV,EAAQW,cAAc,QACnCC,EAAcjB,SAASkB,KAE3B,GAAkB,MAAdH,EACA,OAIJ,IAAII,EAAoB,IAAIC,IAC5B,IAAK,MAAMC,KAAgBN,EAAWO,SAClCH,EAAkBI,IAAIF,EAAaG,UAAWH,GAIlD,IAAII,EAAgBrC,EAAIsC,eAAeX,EAAY,YAAcjB,EAGjE,IAAK,MAAM6B,KAAkBV,EAAYK,SAAU,CAG/C,IAAIM,EAAeT,EAAkBU,IAAIF,EAAeH,WACpDM,EAA0D,YAA3CH,EAAeI,aAAa,WAC3CC,EAAoE,SAAtD5C,EAAIsC,eAAeC,EAAgB,eACjDC,GAAgBI,EACZF,EAEAnB,EAAQsB,KAAKN,IAIbR,EAAkBe,OAAOP,EAAeH,WACxCZ,EAAUqB,KAAKN,IAGG,WAAlBF,EAGIK,IACAnB,EAAQsB,KAAKN,GACbd,EAAcoB,KAAKN,KAIuE,IAA1FrC,KAAKY,QAAQF,SAASC,KAAM,0BAA2B,CAACkC,YAAaR,KACrEhB,EAAQsB,KAAKN,EAI7B,CAIAd,EAAcoB,QAAQd,EAAkBiB,UAGxC,IAAK,MAAMC,KAAWxB,EAAe,CACjCxB,IACA,IAAIiD,EAAStC,SAASuC,cAAcC,yBAAyBH,EAAQb,WACrEnC,KACmF,IAA/EC,KAAKY,QAAQF,SAASC,KAAM,uBAAwB,CAACkC,YAAaG,MAClErB,EAAYwB,YAAYH,GACxB5B,EAAMuB,KAAKK,GAEnB,CAIA,IAAK,MAAMI,KAAkB/B,GACqE,IAA1FrB,KAAKY,QAAQF,SAASC,KAAM,0BAA2B,CAACkC,YAAaO,KACrEzB,EAAY0B,YAAYD,GAIhCpD,KAAKY,QAAQF,SAASC,KAAM,wBAAyB,CACjDS,MAAOA,EACPkC,KAAMhC,EACND,QAASA,GAEjB,CACJ,CACJ,CAYYkC,CAAUhD,EAAIiD,KAAMhD,KAKnC,CA3HD","ignoreList":[]}

View File

@ -5,17 +5,26 @@
let preloadSpec = api.attributeValue(elt, "hx-preload");
if (!preloadSpec && !elt._htmx?.boosted) return;
let eventName;
let timeout;
let preloadEvents = []
let timeout = 5000;
if (preloadSpec) {
let specs = api.parseTriggerSpecs(preloadSpec);
if (specs.length === 0) return;
let spec = specs[0];
eventName = spec.name;
timeout = spec.timeout ? htmx.parseInterval(spec.timeout) : 5000;
for (const spec of specs) {
preloadEvents.push(spec.name)
if (spec.timeout) {
timeout = htmx.parseInterval(spec.timeout)
}
}
} else {
eventName = htmx.config?.preload?.boostEvent || "mousedown"
timeout = htmx.config?.preload?.boostTimeout ? htmx.parseInterval(htmx.config?.preload?.boostTimeout) : 5000;
//only boosted links are supported
if (elt.tagName === "A") {
if(htmx.config?.preload?.boostTimeout) {
timeout = htmx.parseInterval(htmx.config.preload.boostTimeout)
}
preloadEvents.push(htmx.config?.preload?.boostEvent || "mousedown");
preloadEvents.push("touchstart");
}
}
let preloadListener = async (evt) => {
@ -47,9 +56,11 @@
delete elt._htmx.preload;
}
};
elt.addEventListener(eventName, preloadListener);
for (let eventName of preloadEvents) {
elt.addEventListener(eventName, preloadListener);
}
elt._htmx.preloadListener = preloadListener;
elt._htmx.preloadEvent = eventName;
elt._htmx.preloadEvents = preloadEvents;
}
htmx.registerExtension('preload', {
@ -76,7 +87,9 @@
htmx_before_cleanup: (elt) => {
if (elt._htmx?.preloadListener) {
elt.removeEventListener(elt._htmx.preloadEvent, elt._htmx.preloadListener);
for (let eventName of elt._htmx.preloadEvents) {
elt.removeEventListener(eventName, elt._htmx.preloadListener);
}
}
}
});

Binary file not shown.

View File

@ -1 +1 @@
(()=>{let e;htmx.registerExtension("preload",{init:t=>{e=t},htmx_after_init:t=>{!function(t){let r,o,a=e.attributeValue(t,"hx-preload");if(!a&&!t._htmx?.boosted)return;if(a){let t=e.parseTriggerSpecs(a);if(0===t.length)return;let l=t[0];r=l.name,o=l.timeout?htmx.parseInterval(l.timeout):5e3}else r=htmx.config?.preload?.boostEvent||"mousedown",o=htmx.config?.preload?.boostTimeout?htmx.parseInterval(htmx.config?.preload?.boostTimeout):5e3;let l=async r=>{let{method:a}=e.determineMethodAndAction(t,r);if("GET"!==a)return;if(t._htmx?.preload)return;let l=e.createRequestContext(t,r),n=t.form||t.closest("form"),m=e.collectFormData(t,n,r.submitter);e.handleHxVals(t,m);let h=l.request.action.replace?.(/#.*$/,""),i=new URLSearchParams(m);i.size&&(h+=(/\?/.test(h)?"&":"?")+i),t._htmx.preload={prefetch:fetch(h,l.request),action:h,expiresAt:Date.now()+o};try{await t._htmx.preload.prefetch}catch(e){delete t._htmx.preload}};t.addEventListener(r,l),t._htmx.preloadListener=l,t._htmx.preloadEvent=r}(t)},htmx_before_request:(e,t)=>{let{ctx:r}=t;if(e._htmx?.preload&&e._htmx.preload.action===r.request.action&&Date.now()<e._htmx.preload.expiresAt){let t=e._htmx.preload.prefetch;r.fetch=()=>t,delete e._htmx.preload}else e._htmx&&delete e._htmx.preload},htmx_before_cleanup:e=>{e._htmx?.preloadListener&&e.removeEventListener(e._htmx.preloadEvent,e._htmx.preloadListener)}})})();
(()=>{let e;htmx.registerExtension("preload",{init:t=>{e=t},htmx_after_init:t=>{!function(t){let r=e.attributeValue(t,"hx-preload");if(!r&&!t._htmx?.boosted)return;let o=[],a=5e3;if(r){let t=e.parseTriggerSpecs(r);if(0===t.length)return;for(const e of t)o.push(e.name),e.timeout&&(a=htmx.parseInterval(e.timeout))}else"A"===t.tagName&&(htmx.config?.preload?.boostTimeout&&(a=htmx.parseInterval(htmx.config.preload.boostTimeout)),o.push(htmx.config?.preload?.boostEvent||"mousedown"),o.push("touchstart"));let l=async r=>{let{method:o}=e.determineMethodAndAction(t,r);if("GET"!==o)return;if(t._htmx?.preload)return;let l=e.createRequestContext(t,r),n=t.form||t.closest("form"),h=e.collectFormData(t,n,r.submitter);e.handleHxVals(t,h);let m=l.request.action.replace?.(/#.*$/,""),s=new URLSearchParams(h);s.size&&(m+=(/\?/.test(m)?"&":"?")+s),t._htmx.preload={prefetch:fetch(m,l.request),action:m,expiresAt:Date.now()+a};try{await t._htmx.preload.prefetch}catch(e){delete t._htmx.preload}};for(let e of o)t.addEventListener(e,l);t._htmx.preloadListener=l,t._htmx.preloadEvents=o}(t)},htmx_before_request:(e,t)=>{let{ctx:r}=t;if(e._htmx?.preload&&e._htmx.preload.action===r.request.action&&Date.now()<e._htmx.preload.expiresAt){let t=e._htmx.preload.prefetch;r.fetch=()=>t,delete e._htmx.preload}else e._htmx&&delete e._htmx.preload},htmx_before_cleanup:e=>{if(e._htmx?.preloadListener)for(let t of e._htmx.preloadEvents)e.removeEventListener(t,e._htmx.preloadListener)}})})();

View File

@ -1 +1 @@
{"version":3,"names":["api","htmx","registerExtension","init","internalAPI","htmx_after_init","elt","eventName","timeout","preloadSpec","attributeValue","_htmx","boosted","specs","parseTriggerSpecs","length","spec","name","parseInterval","config","preload","boostEvent","boostTimeout","preloadListener","async","evt","method","determineMethodAndAction","ctx","createRequestContext","form","closest","body","collectFormData","submitter","handleHxVals","action","request","replace","params","URLSearchParams","size","test","prefetch","fetch","expiresAt","Date","now","error","addEventListener","preloadEvent","initializePreload","htmx_before_request","detail","htmx_before_cleanup","removeEventListener"],"sources":["dist/ext/hx-preload.js"],"mappings":"AAAA,MACI,IAAIA,EAqDJC,KAAKC,kBAAkB,UAAW,CAC9BC,KAAOC,IACHJ,EAAMI,GAGVC,gBAAkBC,KAxDtB,SAA2BA,GACvB,IAGIC,EACAC,EAJAC,EAAcT,EAAIU,eAAeJ,EAAK,cAC1C,IAAKG,IAAgBH,EAAIK,OAAOC,QAAS,OAIzC,GAAIH,EAAa,CACb,IAAII,EAAQb,EAAIc,kBAAkBL,GAClC,GAAqB,IAAjBI,EAAME,OAAc,OACxB,IAAIC,EAAOH,EAAM,GACjBN,EAAYS,EAAKC,KACjBT,EAAUQ,EAAKR,QAAUP,KAAKiB,cAAcF,EAAKR,SAAW,GAChE,MACID,EAAYN,KAAKkB,QAAQC,SAASC,YAAc,YAChDb,EAAUP,KAAKkB,QAAQC,SAASE,aAAerB,KAAKiB,cAAcjB,KAAKkB,QAAQC,SAASE,cAAgB,IAG5G,IAAIC,EAAkBC,MAAOC,IACzB,IAAIC,OAACA,GAAU1B,EAAI2B,yBAAyBrB,EAAKmB,GACjD,GAAe,QAAXC,EAAkB,OAEtB,GAAIpB,EAAIK,OAAOS,QAAS,OAExB,IAAIQ,EAAM5B,EAAI6B,qBAAqBvB,EAAKmB,GACpCK,EAAOxB,EAAIwB,MAAQxB,EAAIyB,QAAQ,QAC/BC,EAAOhC,EAAIiC,gBAAgB3B,EAAKwB,EAAML,EAAIS,WAC9ClC,EAAImC,aAAa7B,EAAK0B,GAEtB,IAAII,EAASR,EAAIS,QAAQD,OAAOE,UAAU,OAAQ,IAG9CC,EAAS,IAAIC,gBAAgBR,GAC7BO,EAAOE,OAAML,IAAW,KAAKM,KAAKN,GAAU,IAAM,KAAOG,GAE7DjC,EAAIK,MAAMS,QAAU,CAChBuB,SAAUC,MAAMR,EAAQR,EAAIS,SAC5BD,OAAQA,EACRS,UAAWC,KAAKC,MAAQvC,GAG5B,UACUF,EAAIK,MAAMS,QAAQuB,QAC5B,CAAE,MAAOK,UACE1C,EAAIK,MAAMS,OACrB,GAEJd,EAAI2C,iBAAiB1C,EAAWgB,GAChCjB,EAAIK,MAAMY,gBAAkBA,EAC5BjB,EAAIK,MAAMuC,aAAe3C,CAC7B,CAQQ4C,CAAkB7C,IAGtB8C,oBAAqB,CAAC9C,EAAK+C,KACvB,IAAIzB,IAACA,GAAOyB,EACZ,GAAI/C,EAAIK,OAAOS,SACXd,EAAIK,MAAMS,QAAQgB,SAAWR,EAAIS,QAAQD,QACzCU,KAAKC,MAAQzC,EAAIK,MAAMS,QAAQyB,UAAW,CAC1C,IAAIF,EAAWrC,EAAIK,MAAMS,QAAQuB,SACjCf,EAAIgB,MAAQ,IAAMD,SACXrC,EAAIK,MAAMS,OACrB,MACQd,EAAIK,cAAcL,EAAIK,MAAMS,SAIxCkC,oBAAsBhD,IACdA,EAAIK,OAAOY,iBACXjB,EAAIiD,oBAAoBjD,EAAIK,MAAMuC,aAAc5C,EAAIK,MAAMY,mBAIzE,EAlFD","ignoreList":[]}
{"version":3,"names":["api","htmx","registerExtension","init","internalAPI","htmx_after_init","elt","preloadSpec","attributeValue","_htmx","boosted","preloadEvents","timeout","specs","parseTriggerSpecs","length","spec","push","name","parseInterval","tagName","config","preload","boostTimeout","boostEvent","preloadListener","async","evt","method","determineMethodAndAction","ctx","createRequestContext","form","closest","body","collectFormData","submitter","handleHxVals","action","request","replace","params","URLSearchParams","size","test","prefetch","fetch","expiresAt","Date","now","error","eventName","addEventListener","initializePreload","htmx_before_request","detail","htmx_before_cleanup","removeEventListener"],"sources":["dist/ext/hx-preload.js"],"mappings":"AAAA,MACI,IAAIA,EAgEJC,KAAKC,kBAAkB,UAAW,CAC9BC,KAAOC,IACHJ,EAAMI,GAGVC,gBAAkBC,KAnEtB,SAA2BA,GACvB,IAAIC,EAAcP,EAAIQ,eAAeF,EAAK,cAC1C,IAAKC,IAAgBD,EAAIG,OAAOC,QAAS,OAEzC,IAAIC,EAAgB,GAChBC,EAAU,IACd,GAAIL,EAAa,CACb,IAAIM,EAAQb,EAAIc,kBAAkBP,GAClC,GAAqB,IAAjBM,EAAME,OAAc,OACxB,IAAK,MAAMC,KAAQH,EACfF,EAAcM,KAAKD,EAAKE,MACpBF,EAAKJ,UACLA,EAAUX,KAAKkB,cAAcH,EAAKJ,SAG9C,KAEwB,MAAhBN,EAAIc,UACDnB,KAAKoB,QAAQC,SAASC,eACrBX,EAAUX,KAAKkB,cAAclB,KAAKoB,OAAOC,QAAQC,eAErDZ,EAAcM,KAAKhB,KAAKoB,QAAQC,SAASE,YAAc,aACvDb,EAAcM,KAAK,eAI3B,IAAIQ,EAAkBC,MAAOC,IACzB,IAAIC,OAACA,GAAU5B,EAAI6B,yBAAyBvB,EAAKqB,GACjD,GAAe,QAAXC,EAAkB,OAEtB,GAAItB,EAAIG,OAAOa,QAAS,OAExB,IAAIQ,EAAM9B,EAAI+B,qBAAqBzB,EAAKqB,GACpCK,EAAO1B,EAAI0B,MAAQ1B,EAAI2B,QAAQ,QAC/BC,EAAOlC,EAAImC,gBAAgB7B,EAAK0B,EAAML,EAAIS,WAC9CpC,EAAIqC,aAAa/B,EAAK4B,GAEtB,IAAII,EAASR,EAAIS,QAAQD,OAAOE,UAAU,OAAQ,IAG9CC,EAAS,IAAIC,gBAAgBR,GAC7BO,EAAOE,OAAML,IAAW,KAAKM,KAAKN,GAAU,IAAM,KAAOG,GAE7DnC,EAAIG,MAAMa,QAAU,CAChBuB,SAAUC,MAAMR,EAAQR,EAAIS,SAC5BD,OAAQA,EACRS,UAAWC,KAAKC,MAAQrC,GAG5B,UACUN,EAAIG,MAAMa,QAAQuB,QAC5B,CAAE,MAAOK,UACE5C,EAAIG,MAAMa,OACrB,GAEJ,IAAK,IAAI6B,KAAaxC,EAClBL,EAAI8C,iBAAiBD,EAAW1B,GAEpCnB,EAAIG,MAAMgB,gBAAkBA,EAC5BnB,EAAIG,MAAME,cAAgBA,CAC9B,CAQQ0C,CAAkB/C,IAGtBgD,oBAAqB,CAAChD,EAAKiD,KACvB,IAAIzB,IAACA,GAAOyB,EACZ,GAAIjD,EAAIG,OAAOa,SACXhB,EAAIG,MAAMa,QAAQgB,SAAWR,EAAIS,QAAQD,QACzCU,KAAKC,MAAQ3C,EAAIG,MAAMa,QAAQyB,UAAW,CAC1C,IAAIF,EAAWvC,EAAIG,MAAMa,QAAQuB,SACjCf,EAAIgB,MAAQ,IAAMD,SACXvC,EAAIG,MAAMa,OACrB,MACQhB,EAAIG,cAAcH,EAAIG,MAAMa,SAIxCkC,oBAAsBlD,IAClB,GAAIA,EAAIG,OAAOgB,gBACX,IAAK,IAAI0B,KAAa7C,EAAIG,MAAME,cAC5BL,EAAImD,oBAAoBN,EAAW7C,EAAIG,MAAMgB,mBAKhE,EA/FD","ignoreList":[]}

View File

@ -78,6 +78,23 @@
}
}
// Close and remove listeners from old socket
if (entry.socket) {
let oldSocket = entry.socket;
entry.socket = null;
oldSocket.onopen = null;
oldSocket.onmessage = null;
oldSocket.onclose = null;
oldSocket.onerror = null;
try {
if (oldSocket.readyState === WebSocket.OPEN || oldSocket.readyState === WebSocket.CONNECTING) {
oldSocket.close();
}
} catch (e) {}
}
try {
entry.socket = new WebSocket(url);
@ -93,7 +110,10 @@
handleMessage(entry, event);
});
entry.socket.addEventListener('close', () => {
entry.socket.addEventListener('close', (event) => {
// Check if this socket is still the active one
if (event.target !== entry.socket) return;
if (firstElement) {
triggerEvent(firstElement, 'htmx:ws:close', { url });
}
@ -305,9 +325,9 @@
if (partials.length === 0) {
// No partials, treat entire payload as content for element's target
let target = resolveTarget(element);
let target = resolveTarget(element, envelope.target);
if (target) {
swapContent(target, envelope.payload, element);
swapContent(target, envelope.payload, element, envelope.swap);
}
return;
}
@ -323,7 +343,13 @@
});
}
function resolveTarget(element) {
function resolveTarget(element, envelopeTarget) {
if (envelopeTarget) {
if (envelopeTarget === 'this') {
return element;
}
return document.querySelector(envelopeTarget);
}
let targetSelector = api.attributeValue(element, 'hx-target');
if (targetSelector) {
if (targetSelector === 'this') {
@ -334,8 +360,8 @@
return element;
}
function swapContent(target, content, sourceElement) {
let swapStyle = api.attributeValue(sourceElement, 'hx-swap') || htmx.config.defaultSwap;
function swapContent(target, content, sourceElement, envelopeSwap) {
let swapStyle = envelopeSwap || api.attributeValue(sourceElement, 'hx-swap') || htmx.config.defaultSwap;
// Parse swap style (just get the main style, ignore modifiers for now)
let style = swapStyle.split(' ')[0];
@ -549,7 +575,7 @@
processNode(element);
// Process descendants
element.querySelectorAll('[hx-ws\\:connect], [hx-ws-connect], [hx-ws\\:send], [hx-ws-send], [hx-ws], [ws-connect], [ws-send]').forEach(processNode);
element.querySelectorAll('[hx-ws\\:connect], [hx-ws-connect], [hx-ws\\:send], [hx-ws-send], [hx-ws], [ws-connect], [ws-send]').forEach(processNode);
},
htmx_before_cleanup: (element) => {

Binary file not shown.

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

View File

@ -80,7 +80,8 @@ var htmx = (() => {
determineMethodAndAction: this.#determineMethodAndAction.bind(this),
createRequestContext: this.#createRequestContext.bind(this),
collectFormData: this.#collectFormData.bind(this),
handleHxVals: this.#handleHxVals.bind(this)
handleHxVals: this.#handleHxVals.bind(this),
insertContent: this.#insertContent.bind(this)
};
document.addEventListener("DOMContentLoaded", () => {
this.#initHistoryHandling();
@ -90,10 +91,10 @@ var htmx = (() => {
#initHtmxConfig() {
this.config = {
version: '4.0.0-alpha3',
version: '4.0.0-alpha5',
logAll: false,
prefix: "",
transitions: true,
transitions: false,
history: true,
historyReload: false,
mode: 'same-origin',
@ -115,19 +116,9 @@ var htmx = (() => {
noSwap: [204, 304],
implicitInheritance: false
}
let metaConfig = document.querySelector('meta[name="htmx:config"]');
let metaConfig = document.querySelector('meta[name="htmx-config"]');
if (metaConfig) {
let content = metaConfig.content;
let overrides = this.#parseConfig(content);
// Deep merge nested config objects
for (let key in overrides) {
let val = overrides[key];
if (val && typeof val === 'object' && !Array.isArray(val) && this.config[key]) {
Object.assign(this.config[key], val);
} else {
this.config[key] = val;
}
}
this.#mergeConfig(metaConfig.content, this.config);
}
this.#approvedExt = this.config.extensions;
}
@ -210,7 +201,7 @@ var htmx = (() => {
if (parent) {
let val = this.#attributeValue(parent, name, undefined, returnElt);
if (!returnElt && val && this.config.implicitInheritance) {
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, parent})
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, name, parent})
}
return val;
}
@ -231,6 +222,19 @@ var htmx = (() => {
}, {});
}
#mergeConfig(configString, target) {
let parsed = this.#parseConfig(configString);
for (let key in parsed) {
let val = parsed[key];
if (val && typeof val === 'object' && !Array.isArray(val) && target[key]) {
Object.assign(target[key], val);
} else {
target[key] = val;
}
}
return target;
}
#parseTriggerSpecs(spec) {
return spec.split(',').map(s => {
let m = s.match(/^\s*(\S+\[[^\]]*\]|\S+)\s*(.*?)\s*$/);
@ -305,14 +309,14 @@ var htmx = (() => {
status: "created",
select: this.#attributeValue(sourceElement, "hx-select"),
selectOOB: this.#attributeValue(sourceElement, "hx-select-oob"),
target: this.#resolveTarget(sourceElement, this.#attributeValue(sourceElement, "hx-target")),
swap: this.#attributeValue(sourceElement, "hx-swap", this.config.defaultSwap),
target: this.#attributeValue(sourceElement, "hx-target"),
swap: this.#attributeValue(sourceElement, "hx-swap") ?? this.config.defaultSwap,
push: this.#attributeValue(sourceElement, "hx-push-url"),
replace: this.#attributeValue(sourceElement, "hx-replace-url"),
transition: this.config.transitions,
confirm: this.#attributeValue(sourceElement, "hx-confirm"),
request: {
validate: "true" === this.#attributeValue(sourceElement, "hx-validate", sourceElement.matches('form') ? "true" : "false"),
validate: "true" === this.#attributeValue(sourceElement, "hx-validate", sourceElement.matches('form') && !sourceElement.noValidate && !sourceEvent.submitter?.formNoValidate ? "true" : "false"),
action: fullAction,
anchor,
method,
@ -323,26 +327,18 @@ var htmx = (() => {
mode: this.config.mode
}
};
// Apply boost config overrides
if (sourceElement._htmx?.boosted) {
this.#mergeConfig(sourceElement._htmx.boosted, ctx);
}
ctx.target = this.#resolveTarget(sourceElement, ctx.target);
// Apply hx-config overrides
let configAttr = this.#attributeValue(sourceElement, "hx-config");
if (configAttr) {
let configOverrides = this.#parseConfig(configAttr);
let req = ctx.request;
for (let key in configOverrides) {
if (key.startsWith('+')) {
let actualKey = key.substring(1);
if (req[actualKey] && typeof req[actualKey] === 'object') {
Object.assign(req[actualKey], configOverrides[key]);
} else {
req[actualKey] = configOverrides[key];
}
} else {
req[key] = configOverrides[key];
}
}
if (req.etag) {
(sourceElement._htmx ||= {}).etag ||= req.etag
this.#mergeConfig(configAttr, ctx.request);
if (ctx.request.etag) {
(sourceElement._htmx ||= {}).etag ||= ctx.request.etag
}
}
if (sourceElement._htmx?.etag) {
@ -351,10 +347,14 @@ var htmx = (() => {
return ctx;
}
#buildIdentifier(elt) {
return `${elt.tagName.toLowerCase()}${elt.id ? '#' + elt.id : ''}`;
}
#determineHeaders(elt) {
let headers = {
"HX-Request": "true",
"HX-Source": elt.id || elt.name,
"HX-Source": this.#buildIdentifier(elt),
"HX-Current-URL": location.href,
"Accept": "text/html, text/event-stream"
};
@ -363,7 +363,7 @@ var htmx = (() => {
}
let headersAttribute = this.#attributeValue(elt, "hx-headers");
if (headersAttribute) {
Object.assign(headers, this.#parseConfig(headersAttribute));
this.#mergeConfig(headersAttribute, headers);
}
return headers;
}
@ -371,10 +371,9 @@ var htmx = (() => {
#resolveTarget(elt, selector) {
if (selector instanceof Element) {
return selector;
} else if (selector === 'this') {
return this.#attributeValue(elt, "hx-target", undefined, true);
} else if (selector != null) {
return this.find(elt, selector);
let thisElt = this.#attributeValue(elt, "hx-target", undefined, true);
return this.#findAllExt(elt, selector, false, thisElt)[0];
} else if (this.#isBoosted(elt)) {
return document.body
} else {
@ -397,7 +396,8 @@ var htmx = (() => {
// Build request body
let form = elt.form || elt.closest("form")
let body = this.#collectFormData(elt, form, evt.submitter)
let body = this.#collectFormData(elt, form, evt.submitter, ctx.request.validate)
if (!body) return // Validation failed
let valsResult = this.#handleHxVals(elt, body)
if (valsResult) await valsResult // Only await if it returned a promise
if (ctx.values) {
@ -407,6 +407,12 @@ var htmx = (() => {
}
}
// 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) {
ctx.request.headers["HX-Target"] = this.#buildIdentifier(ctx.target);
}
// Setup event-dependent request details
Object.assign(ctx.request, {
form,
@ -416,7 +422,6 @@ var htmx = (() => {
if (!this.#trigger(elt, "htmx:config:request", {ctx: ctx})) return
if (!this.#verbs.includes(ctx.request.method.toLowerCase())) return
if (ctx.request.validate && ctx.request.form && !ctx.request.form.reportValidity()) return
let javascriptContent = this.#extractJavascriptContent(ctx.request.action);
if (javascriptContent != null) {
@ -452,10 +457,8 @@ var htmx = (() => {
ctx.status = "issuing"
this.#initTimeout(ctx);
let indicatorsSelector = this.#attributeValue(elt, "hx-indicator");
let indicators = this.#showIndicators(elt, indicatorsSelector);
let disableSelector = this.#attributeValue(elt, "hx-disable");
let disableElements = this.#disableElements(elt, disableSelector);
let indicators = this.#showIndicators(elt);
let disableElements = this.#disableElements(elt);
try {
// Handle confirmation
@ -1001,8 +1004,9 @@ var htmx = (() => {
}
#maybeBoost(elt) {
if (this.#attributeValue(elt, "hx-boost") === "true" && this.#shouldBoost(elt)) {
elt._htmx = {eventHandler: this.#createHtmxEventHandler(elt), requests: [], boosted: true}
let boostValue = this.#attributeValue(elt, "hx-boost");
if (boostValue && boostValue !== "false" && this.#shouldBoost(elt)) {
elt._htmx = {eventHandler: this.#createHtmxEventHandler(elt), requests: [], boosted: boostValue}
elt.setAttribute('data-htmx-powered', 'true');
if (elt.matches('a') && !elt.hasAttribute("target")) {
elt.addEventListener('click', (click) => {
@ -1187,13 +1191,11 @@ var htmx = (() => {
let type = templateElt.getAttribute('type');
if (type === 'partial') {
let swapSpec = this.#parseSwapSpec(templateElt.getAttribute(this.#prefix('hx-swap')) || this.config.defaultSwap);
tasks.push({
type: 'partial',
fragment: templateElt.content.cloneNode(true),
target: templateElt.getAttribute(this.#prefix('hx-target')),
swapSpec,
swapSpec: this.#parseSwapSpec(templateElt.getAttribute(this.#prefix('hx-swap')) || this.config.defaultSwap),
sourceElement: ctx.sourceElement
});
} else {
@ -1210,18 +1212,18 @@ var htmx = (() => {
autofocus?.focus?.()
}
#handleScroll(task) {
if (task.swapSpec.scroll) {
let target = task.swapSpec.scrollTarget ? this.#findExt(task.swapSpec.scrollTarget) : task.target;
if (task.swapSpec.scroll === 'top') {
target.scrollTop = 0;
} else if (task.swapSpec.scroll === 'bottom'){
target.scrollTop = target.scrollHeight;
#handleScroll(swapSpec, target) {
if (swapSpec.scroll) {
let scrollTarget = swapSpec.scrollTarget ? this.#findExt(swapSpec.scrollTarget) : target;
if (swapSpec.scroll === 'top') {
scrollTarget.scrollTop = 0;
} else if (swapSpec.scroll === 'bottom'){
scrollTarget.scrollTop = scrollTarget.scrollHeight;
}
}
if (task.swapSpec.show) {
let target = task.swapSpec.showTarget ? this.#findExt(task.swapSpec.showTarget) : task.target;
target.scrollIntoView(task.swapSpec.show === 'top')
if (swapSpec.show) {
let showTarget = swapSpec.showTarget ? this.#findExt(swapSpec.showTarget) : target;
showTarget.scrollIntoView(swapSpec.show === 'top')
}
}
@ -1270,25 +1272,35 @@ var htmx = (() => {
// TODO - can we remove this and just let the function complete?
if (tasks.length === 0) return;
// Separate transition/nonTransition tasks
let transitionTasks = tasks.filter(t => t.transition);
let nonTransitionTasks = tasks.filter(t => !t.transition);
if(!this.#trigger(document, "htmx:before:swap", {ctx, tasks})){
return
}
// insert non-transition tasks immediately or with delay
for (let task of nonTransitionTasks) {
if (task.swapSpec?.swap) {
setTimeout(() => this.#insertContent(task), this.parseInterval(task.swapSpec.swap));
} else {
// insert non-transition tasks immediately or with delay, collect transition tasks
let transitionTasks = [];
for (let task of tasks) {
// OOB/partial tasks with swap delays should be non-transition (non-blocking)
let swapDelay = task.swapSpec?.swap;
if (!(task.swapSpec?.transition ?? mainSwap?.transition) || (swapDelay && task !== mainSwap)) {
if (swapDelay) {
if (task === mainSwap) {
await this.timeout(swapDelay);
} else {
setTimeout(() => this.#insertContent(task), this.parseInterval(swapDelay));
continue;
}
}
this.#insertContent(task)
} else {
transitionTasks.push(task);
}
}
// insert transition tasks in the transition queue
if (transitionTasks.length > 0) {
if (mainSwap?.transition && mainSwap?.swapSpec?.swap) {
await this.timeout(mainSwap.swapSpec.swap);
}
let tasksWrapper = ()=> {
for (let task of transitionTasks) {
this.#insertContent(task)
@ -1316,7 +1328,7 @@ var htmx = (() => {
// Create main task if needed
let swapSpec = this.#parseSwapSpec(ctx.swap || this.config.defaultSwap);
// skip creating main swap if extracting partials resulted in empty response except for delete style
if (swapSpec.style === 'delete' || /\S/.test(fragment.innerHTML || '') || !partialTasks.length) {
if (swapSpec.style === 'delete' || fragment.childElementCount > 0 || /\S/.test(fragment.textContent) || !partialTasks.length) {
if (ctx.select) {
let selected = fragment.querySelectorAll(ctx.select);
fragment = document.createDocumentFragment();
@ -1343,8 +1355,10 @@ var htmx = (() => {
target = document.querySelector(target);
}
if (!target) return;
if (typeof swapSpec === 'string') {
swapSpec = this.#parseSwapSpec(swapSpec);
}
if (swapSpec.strip && fragment.firstElementChild) {
task.unstripped = fragment;
fragment = document.createDocumentFragment();
fragment.append(...(task.fragment.firstElementChild.content || task.fragment.firstElementChild).childNodes);
}
@ -1390,17 +1404,24 @@ var htmx = (() => {
} else if (swapSpec.style === 'none') {
return;
} else {
task.target = target;
task.fragment = fragment;
if (!this.#triggerExtensions(target, 'htmx:handle:swap', task)) return;
throw new Error(`Unknown swap style: ${swapSpec.style}`);
let methods = this.#extMethods.get('handle_swap')
let handled = false;
for (const method of methods) {
if (method(swapSpec.style, target, fragment)) {
handled = true;
break;
}
}
if (!handled) {
throw new Error(`Unknown swap style: ${swapSpec.style}`);
}
}
this.#restorePreservedElements(pantry);
for (const elt of newContent) {
this.process(elt);
this.#handleAutoFocus(elt);
}
this.#handleScroll(task);
this.#handleScroll(swapSpec, target);
}
#trigger(on, eventName, detail = {}, bubbles = true) {
@ -1631,15 +1652,19 @@ var htmx = (() => {
}
}
#showIndicators(elt, indicatorsSelector) {
let indicatorElements = []
if (indicatorsSelector) {
indicatorElements = [elt, ...this.#queryEltAndDescendants(elt, indicatorsSelector)];
for (const indicator of indicatorElements) {
indicator._htmxReqCount ||= 0
indicator._htmxReqCount++
indicator.classList.add(this.config.requestClass)
}
#showIndicators(elt) {
let indicatorsSelector = this.#attributeValue(elt, "hx-indicator");
let indicatorElements;
if (!indicatorsSelector) {
indicatorElements = [elt]
} else {
let thisElt = this.#attributeValue(elt, "hx-indicator", undefined, true);
indicatorElements = this.#findAllExt(elt, indicatorsSelector, false, thisElt);
}
for (const indicator of indicatorElements) {
indicator._htmxReqCount ||= 0
indicator._htmxReqCount++
indicator.classList.add(this.config.requestClass)
}
return indicatorElements
}
@ -1656,7 +1681,8 @@ var htmx = (() => {
}
}
#disableElements(elt, disabledSelector) {
#disableElements(elt) {
let disabledSelector = this.#attributeValue(elt, "hx-disable");
let disabledElements = []
if (disabledSelector) {
disabledElements = this.#queryEltAndDescendants(elt, disabledSelector);
@ -1681,10 +1707,13 @@ var htmx = (() => {
}
}
#collectFormData(elt, form, submitter) {
#collectFormData(elt, form, submitter, validate) {
if (validate && form && !form.reportValidity()) return
let formData = form ? new FormData(form) : new FormData()
let included = form ? new Set(form.elements) : new Set()
if (!form && elt.name) {
if (validate && elt.reportValidity && !elt.reportValidity()) return
formData.append(elt.name, elt.value)
included.add(elt);
}
@ -1694,8 +1723,8 @@ var htmx = (() => {
}
let includeSelector = this.#attributeValue(elt, "hx-include");
if (includeSelector) {
let includeNodes = this.#findAllExt(elt, includeSelector);
for (let node of includeNodes) {
for (let node of this.#findAllExt(elt, includeSelector)) {
if (validate && node.reportValidity && !node.reportValidity()) return
this.#addInputValues(node, included, formData);
}
}
@ -1758,11 +1787,11 @@ var htmx = (() => {
return s.startsWith('<') && s.endsWith('/>') ? s.slice(1, -2) : s;
}
#findAllExt(eltOrSelector, maybeSelector, global) {
#findAllExt(eltOrSelector, maybeSelector, global, thisElt) {
let selector = maybeSelector ?? eltOrSelector;
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
if (selector.startsWith('global ')) {
return this.#findAllExt(elt, selector.slice(7), true);
return this.#findAllExt(elt, selector.slice(7), true, thisElt);
}
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
@ -1774,7 +1803,9 @@ var htmx = (() => {
if (selector.startsWith('closest ')) {
item = elt.closest(selector.slice(8))
} else if (selector.startsWith('find ')) {
item = document.querySelector(elt, selector.slice(5))
item = elt.querySelector(selector.slice(5))
} else if (selector.startsWith('findAll ')) {
result.push(...elt.querySelectorAll(selector.slice(8)))
} else if (selector === 'next' || selector === 'nextElementSibling') {
item = elt.nextElementSibling
} else if (selector.startsWith('next ')) {
@ -1789,10 +1820,10 @@ var htmx = (() => {
item = window
} else if (selector === 'body') {
item = document.body
} else if (selector === 'root') {
item = this.#getRootNode(elt, !!global)
} else if (selector === 'host') {
item = (elt.getRootNode()).host
} else if (selector === 'this') {
item = thisElt || elt
} else {
unprocessedParts.push(selector)
}
@ -1836,7 +1867,7 @@ var htmx = (() => {
}
}
#findExt(eltOrSelector, selector) {
#findExt(eltOrSelector, selector, thisElt) {
return this.#findAllExt(eltOrSelector, selector)[0]
}
@ -1992,8 +2023,8 @@ var htmx = (() => {
let type = newNode.nodeType;
if (type === 1) {
let noMorph = this.config.morphIgnore || [];
this.#copyAttributes(oldNode, newNode, noMorph);
if (this.config.morphSkip && oldNode.matches?.(this.config.morphSkip)) return;
this.#copyAttributes(oldNode, newNode);
if (oldNode instanceof HTMLTextAreaElement && oldNode.defaultValue != newNode.defaultValue) {
oldNode.value = newNode.value;
}
@ -2002,10 +2033,13 @@ var htmx = (() => {
if ((type === 8 || type === 3) && oldNode.nodeValue !== newNode.nodeValue) {
oldNode.nodeValue = newNode.nodeValue;
}
if (!oldNode.isEqualNode(newNode)) this.#morphChildren(ctx, oldNode, newNode);
let skipChildren = this.config.morphSkipChildren && oldNode.matches?.(this.config.morphSkipChildren);
if (!skipChildren && !oldNode.isEqualNode(newNode)) this.#morphChildren(ctx, oldNode, newNode);
}
#copyAttributes(destination, source, attributesToIgnore = []) {
#copyAttributes(destination, source) {
let attributesToIgnore = this.config.morphIgnore || [];
for (const attr of source.attributes) {
if (!attributesToIgnore.includes(attr.name) && destination.getAttribute(attr.name) !== attr.value) {
destination.setAttribute(attr.name, attr.value);
@ -2075,7 +2109,7 @@ var htmx = (() => {
}
let statusValue = this.#attributeValue(ctx.sourceElement, "hx-status:" + pattern);
if (statusValue) {
Object.assign(ctx, this.#parseConfig(statusValue));
this.#mergeConfig(statusValue, ctx);
return;
}
}

Binary file not shown.

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

View File

@ -80,7 +80,8 @@ var htmx = (() => {
determineMethodAndAction: this.#determineMethodAndAction.bind(this),
createRequestContext: this.#createRequestContext.bind(this),
collectFormData: this.#collectFormData.bind(this),
handleHxVals: this.#handleHxVals.bind(this)
handleHxVals: this.#handleHxVals.bind(this),
insertContent: this.#insertContent.bind(this)
};
document.addEventListener("DOMContentLoaded", () => {
this.#initHistoryHandling();
@ -90,10 +91,10 @@ var htmx = (() => {
#initHtmxConfig() {
this.config = {
version: '4.0.0-alpha3',
version: '4.0.0-alpha5',
logAll: false,
prefix: "",
transitions: true,
transitions: false,
history: true,
historyReload: false,
mode: 'same-origin',
@ -115,19 +116,9 @@ var htmx = (() => {
noSwap: [204, 304],
implicitInheritance: false
}
let metaConfig = document.querySelector('meta[name="htmx:config"]');
let metaConfig = document.querySelector('meta[name="htmx-config"]');
if (metaConfig) {
let content = metaConfig.content;
let overrides = this.#parseConfig(content);
// Deep merge nested config objects
for (let key in overrides) {
let val = overrides[key];
if (val && typeof val === 'object' && !Array.isArray(val) && this.config[key]) {
Object.assign(this.config[key], val);
} else {
this.config[key] = val;
}
}
this.#mergeConfig(metaConfig.content, this.config);
}
this.#approvedExt = this.config.extensions;
}
@ -210,7 +201,7 @@ var htmx = (() => {
if (parent) {
let val = this.#attributeValue(parent, name, undefined, returnElt);
if (!returnElt && val && this.config.implicitInheritance) {
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, parent})
this.#triggerExtensions(elt, "htmx:after:implicitInheritance", {elt, name, parent})
}
return val;
}
@ -231,6 +222,19 @@ var htmx = (() => {
}, {});
}
#mergeConfig(configString, target) {
let parsed = this.#parseConfig(configString);
for (let key in parsed) {
let val = parsed[key];
if (val && typeof val === 'object' && !Array.isArray(val) && target[key]) {
Object.assign(target[key], val);
} else {
target[key] = val;
}
}
return target;
}
#parseTriggerSpecs(spec) {
return spec.split(',').map(s => {
let m = s.match(/^\s*(\S+\[[^\]]*\]|\S+)\s*(.*?)\s*$/);
@ -305,14 +309,14 @@ var htmx = (() => {
status: "created",
select: this.#attributeValue(sourceElement, "hx-select"),
selectOOB: this.#attributeValue(sourceElement, "hx-select-oob"),
target: this.#resolveTarget(sourceElement, this.#attributeValue(sourceElement, "hx-target")),
swap: this.#attributeValue(sourceElement, "hx-swap", this.config.defaultSwap),
target: this.#attributeValue(sourceElement, "hx-target"),
swap: this.#attributeValue(sourceElement, "hx-swap") ?? this.config.defaultSwap,
push: this.#attributeValue(sourceElement, "hx-push-url"),
replace: this.#attributeValue(sourceElement, "hx-replace-url"),
transition: this.config.transitions,
confirm: this.#attributeValue(sourceElement, "hx-confirm"),
request: {
validate: "true" === this.#attributeValue(sourceElement, "hx-validate", sourceElement.matches('form') ? "true" : "false"),
validate: "true" === this.#attributeValue(sourceElement, "hx-validate", sourceElement.matches('form') && !sourceElement.noValidate && !sourceEvent.submitter?.formNoValidate ? "true" : "false"),
action: fullAction,
anchor,
method,
@ -323,26 +327,18 @@ var htmx = (() => {
mode: this.config.mode
}
};
// Apply boost config overrides
if (sourceElement._htmx?.boosted) {
this.#mergeConfig(sourceElement._htmx.boosted, ctx);
}
ctx.target = this.#resolveTarget(sourceElement, ctx.target);
// Apply hx-config overrides
let configAttr = this.#attributeValue(sourceElement, "hx-config");
if (configAttr) {
let configOverrides = this.#parseConfig(configAttr);
let req = ctx.request;
for (let key in configOverrides) {
if (key.startsWith('+')) {
let actualKey = key.substring(1);
if (req[actualKey] && typeof req[actualKey] === 'object') {
Object.assign(req[actualKey], configOverrides[key]);
} else {
req[actualKey] = configOverrides[key];
}
} else {
req[key] = configOverrides[key];
}
}
if (req.etag) {
(sourceElement._htmx ||= {}).etag ||= req.etag
this.#mergeConfig(configAttr, ctx.request);
if (ctx.request.etag) {
(sourceElement._htmx ||= {}).etag ||= ctx.request.etag
}
}
if (sourceElement._htmx?.etag) {
@ -351,10 +347,14 @@ var htmx = (() => {
return ctx;
}
#buildIdentifier(elt) {
return `${elt.tagName.toLowerCase()}${elt.id ? '#' + elt.id : ''}`;
}
#determineHeaders(elt) {
let headers = {
"HX-Request": "true",
"HX-Source": elt.id || elt.name,
"HX-Source": this.#buildIdentifier(elt),
"HX-Current-URL": location.href,
"Accept": "text/html, text/event-stream"
};
@ -363,7 +363,7 @@ var htmx = (() => {
}
let headersAttribute = this.#attributeValue(elt, "hx-headers");
if (headersAttribute) {
Object.assign(headers, this.#parseConfig(headersAttribute));
this.#mergeConfig(headersAttribute, headers);
}
return headers;
}
@ -371,10 +371,9 @@ var htmx = (() => {
#resolveTarget(elt, selector) {
if (selector instanceof Element) {
return selector;
} else if (selector === 'this') {
return this.#attributeValue(elt, "hx-target", undefined, true);
} else if (selector != null) {
return this.find(elt, selector);
let thisElt = this.#attributeValue(elt, "hx-target", undefined, true);
return this.#findAllExt(elt, selector, false, thisElt)[0];
} else if (this.#isBoosted(elt)) {
return document.body
} else {
@ -397,7 +396,8 @@ var htmx = (() => {
// Build request body
let form = elt.form || elt.closest("form")
let body = this.#collectFormData(elt, form, evt.submitter)
let body = this.#collectFormData(elt, form, evt.submitter, ctx.request.validate)
if (!body) return // Validation failed
let valsResult = this.#handleHxVals(elt, body)
if (valsResult) await valsResult // Only await if it returned a promise
if (ctx.values) {
@ -407,6 +407,12 @@ var htmx = (() => {
}
}
// 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) {
ctx.request.headers["HX-Target"] = this.#buildIdentifier(ctx.target);
}
// Setup event-dependent request details
Object.assign(ctx.request, {
form,
@ -416,7 +422,6 @@ var htmx = (() => {
if (!this.#trigger(elt, "htmx:config:request", {ctx: ctx})) return
if (!this.#verbs.includes(ctx.request.method.toLowerCase())) return
if (ctx.request.validate && ctx.request.form && !ctx.request.form.reportValidity()) return
let javascriptContent = this.#extractJavascriptContent(ctx.request.action);
if (javascriptContent != null) {
@ -452,10 +457,8 @@ var htmx = (() => {
ctx.status = "issuing"
this.#initTimeout(ctx);
let indicatorsSelector = this.#attributeValue(elt, "hx-indicator");
let indicators = this.#showIndicators(elt, indicatorsSelector);
let disableSelector = this.#attributeValue(elt, "hx-disable");
let disableElements = this.#disableElements(elt, disableSelector);
let indicators = this.#showIndicators(elt);
let disableElements = this.#disableElements(elt);
try {
// Handle confirmation
@ -1001,8 +1004,9 @@ var htmx = (() => {
}
#maybeBoost(elt) {
if (this.#attributeValue(elt, "hx-boost") === "true" && this.#shouldBoost(elt)) {
elt._htmx = {eventHandler: this.#createHtmxEventHandler(elt), requests: [], boosted: true}
let boostValue = this.#attributeValue(elt, "hx-boost");
if (boostValue && boostValue !== "false" && this.#shouldBoost(elt)) {
elt._htmx = {eventHandler: this.#createHtmxEventHandler(elt), requests: [], boosted: boostValue}
elt.setAttribute('data-htmx-powered', 'true');
if (elt.matches('a') && !elt.hasAttribute("target")) {
elt.addEventListener('click', (click) => {
@ -1187,13 +1191,11 @@ var htmx = (() => {
let type = templateElt.getAttribute('type');
if (type === 'partial') {
let swapSpec = this.#parseSwapSpec(templateElt.getAttribute(this.#prefix('hx-swap')) || this.config.defaultSwap);
tasks.push({
type: 'partial',
fragment: templateElt.content.cloneNode(true),
target: templateElt.getAttribute(this.#prefix('hx-target')),
swapSpec,
swapSpec: this.#parseSwapSpec(templateElt.getAttribute(this.#prefix('hx-swap')) || this.config.defaultSwap),
sourceElement: ctx.sourceElement
});
} else {
@ -1210,18 +1212,18 @@ var htmx = (() => {
autofocus?.focus?.()
}
#handleScroll(task) {
if (task.swapSpec.scroll) {
let target = task.swapSpec.scrollTarget ? this.#findExt(task.swapSpec.scrollTarget) : task.target;
if (task.swapSpec.scroll === 'top') {
target.scrollTop = 0;
} else if (task.swapSpec.scroll === 'bottom'){
target.scrollTop = target.scrollHeight;
#handleScroll(swapSpec, target) {
if (swapSpec.scroll) {
let scrollTarget = swapSpec.scrollTarget ? this.#findExt(swapSpec.scrollTarget) : target;
if (swapSpec.scroll === 'top') {
scrollTarget.scrollTop = 0;
} else if (swapSpec.scroll === 'bottom'){
scrollTarget.scrollTop = scrollTarget.scrollHeight;
}
}
if (task.swapSpec.show) {
let target = task.swapSpec.showTarget ? this.#findExt(task.swapSpec.showTarget) : task.target;
target.scrollIntoView(task.swapSpec.show === 'top')
if (swapSpec.show) {
let showTarget = swapSpec.showTarget ? this.#findExt(swapSpec.showTarget) : target;
showTarget.scrollIntoView(swapSpec.show === 'top')
}
}
@ -1270,25 +1272,35 @@ var htmx = (() => {
// TODO - can we remove this and just let the function complete?
if (tasks.length === 0) return;
// Separate transition/nonTransition tasks
let transitionTasks = tasks.filter(t => t.transition);
let nonTransitionTasks = tasks.filter(t => !t.transition);
if(!this.#trigger(document, "htmx:before:swap", {ctx, tasks})){
return
}
// insert non-transition tasks immediately or with delay
for (let task of nonTransitionTasks) {
if (task.swapSpec?.swap) {
setTimeout(() => this.#insertContent(task), this.parseInterval(task.swapSpec.swap));
} else {
// insert non-transition tasks immediately or with delay, collect transition tasks
let transitionTasks = [];
for (let task of tasks) {
// OOB/partial tasks with swap delays should be non-transition (non-blocking)
let swapDelay = task.swapSpec?.swap;
if (!(task.swapSpec?.transition ?? mainSwap?.transition) || (swapDelay && task !== mainSwap)) {
if (swapDelay) {
if (task === mainSwap) {
await this.timeout(swapDelay);
} else {
setTimeout(() => this.#insertContent(task), this.parseInterval(swapDelay));
continue;
}
}
this.#insertContent(task)
} else {
transitionTasks.push(task);
}
}
// insert transition tasks in the transition queue
if (transitionTasks.length > 0) {
if (mainSwap?.transition && mainSwap?.swapSpec?.swap) {
await this.timeout(mainSwap.swapSpec.swap);
}
let tasksWrapper = ()=> {
for (let task of transitionTasks) {
this.#insertContent(task)
@ -1316,7 +1328,7 @@ var htmx = (() => {
// Create main task if needed
let swapSpec = this.#parseSwapSpec(ctx.swap || this.config.defaultSwap);
// skip creating main swap if extracting partials resulted in empty response except for delete style
if (swapSpec.style === 'delete' || /\S/.test(fragment.innerHTML || '') || !partialTasks.length) {
if (swapSpec.style === 'delete' || fragment.childElementCount > 0 || /\S/.test(fragment.textContent) || !partialTasks.length) {
if (ctx.select) {
let selected = fragment.querySelectorAll(ctx.select);
fragment = document.createDocumentFragment();
@ -1343,8 +1355,10 @@ var htmx = (() => {
target = document.querySelector(target);
}
if (!target) return;
if (typeof swapSpec === 'string') {
swapSpec = this.#parseSwapSpec(swapSpec);
}
if (swapSpec.strip && fragment.firstElementChild) {
task.unstripped = fragment;
fragment = document.createDocumentFragment();
fragment.append(...(task.fragment.firstElementChild.content || task.fragment.firstElementChild).childNodes);
}
@ -1390,17 +1404,24 @@ var htmx = (() => {
} else if (swapSpec.style === 'none') {
return;
} else {
task.target = target;
task.fragment = fragment;
if (!this.#triggerExtensions(target, 'htmx:handle:swap', task)) return;
throw new Error(`Unknown swap style: ${swapSpec.style}`);
let methods = this.#extMethods.get('handle_swap')
let handled = false;
for (const method of methods) {
if (method(swapSpec.style, target, fragment)) {
handled = true;
break;
}
}
if (!handled) {
throw new Error(`Unknown swap style: ${swapSpec.style}`);
}
}
this.#restorePreservedElements(pantry);
for (const elt of newContent) {
this.process(elt);
this.#handleAutoFocus(elt);
}
this.#handleScroll(task);
this.#handleScroll(swapSpec, target);
}
#trigger(on, eventName, detail = {}, bubbles = true) {
@ -1631,15 +1652,19 @@ var htmx = (() => {
}
}
#showIndicators(elt, indicatorsSelector) {
let indicatorElements = []
if (indicatorsSelector) {
indicatorElements = [elt, ...this.#queryEltAndDescendants(elt, indicatorsSelector)];
for (const indicator of indicatorElements) {
indicator._htmxReqCount ||= 0
indicator._htmxReqCount++
indicator.classList.add(this.config.requestClass)
}
#showIndicators(elt) {
let indicatorsSelector = this.#attributeValue(elt, "hx-indicator");
let indicatorElements;
if (!indicatorsSelector) {
indicatorElements = [elt]
} else {
let thisElt = this.#attributeValue(elt, "hx-indicator", undefined, true);
indicatorElements = this.#findAllExt(elt, indicatorsSelector, false, thisElt);
}
for (const indicator of indicatorElements) {
indicator._htmxReqCount ||= 0
indicator._htmxReqCount++
indicator.classList.add(this.config.requestClass)
}
return indicatorElements
}
@ -1656,7 +1681,8 @@ var htmx = (() => {
}
}
#disableElements(elt, disabledSelector) {
#disableElements(elt) {
let disabledSelector = this.#attributeValue(elt, "hx-disable");
let disabledElements = []
if (disabledSelector) {
disabledElements = this.#queryEltAndDescendants(elt, disabledSelector);
@ -1681,10 +1707,13 @@ var htmx = (() => {
}
}
#collectFormData(elt, form, submitter) {
#collectFormData(elt, form, submitter, validate) {
if (validate && form && !form.reportValidity()) return
let formData = form ? new FormData(form) : new FormData()
let included = form ? new Set(form.elements) : new Set()
if (!form && elt.name) {
if (validate && elt.reportValidity && !elt.reportValidity()) return
formData.append(elt.name, elt.value)
included.add(elt);
}
@ -1694,8 +1723,8 @@ var htmx = (() => {
}
let includeSelector = this.#attributeValue(elt, "hx-include");
if (includeSelector) {
let includeNodes = this.#findAllExt(elt, includeSelector);
for (let node of includeNodes) {
for (let node of this.#findAllExt(elt, includeSelector)) {
if (validate && node.reportValidity && !node.reportValidity()) return
this.#addInputValues(node, included, formData);
}
}
@ -1758,11 +1787,11 @@ var htmx = (() => {
return s.startsWith('<') && s.endsWith('/>') ? s.slice(1, -2) : s;
}
#findAllExt(eltOrSelector, maybeSelector, global) {
#findAllExt(eltOrSelector, maybeSelector, global, thisElt) {
let selector = maybeSelector ?? eltOrSelector;
let elt = maybeSelector ? this.#normalizeElement(eltOrSelector) : document;
if (selector.startsWith('global ')) {
return this.#findAllExt(elt, selector.slice(7), true);
return this.#findAllExt(elt, selector.slice(7), true, thisElt);
}
let parts = selector ? selector.replace(/<[^>]+\/>/g, m => m.replace(/,/g, '%2C'))
.split(',').map(p => p.replace(/%2C/g, ',')) : [];
@ -1774,7 +1803,9 @@ var htmx = (() => {
if (selector.startsWith('closest ')) {
item = elt.closest(selector.slice(8))
} else if (selector.startsWith('find ')) {
item = document.querySelector(elt, selector.slice(5))
item = elt.querySelector(selector.slice(5))
} else if (selector.startsWith('findAll ')) {
result.push(...elt.querySelectorAll(selector.slice(8)))
} else if (selector === 'next' || selector === 'nextElementSibling') {
item = elt.nextElementSibling
} else if (selector.startsWith('next ')) {
@ -1789,10 +1820,10 @@ var htmx = (() => {
item = window
} else if (selector === 'body') {
item = document.body
} else if (selector === 'root') {
item = this.#getRootNode(elt, !!global)
} else if (selector === 'host') {
item = (elt.getRootNode()).host
} else if (selector === 'this') {
item = thisElt || elt
} else {
unprocessedParts.push(selector)
}
@ -1836,7 +1867,7 @@ var htmx = (() => {
}
}
#findExt(eltOrSelector, selector) {
#findExt(eltOrSelector, selector, thisElt) {
return this.#findAllExt(eltOrSelector, selector)[0]
}
@ -1992,8 +2023,8 @@ var htmx = (() => {
let type = newNode.nodeType;
if (type === 1) {
let noMorph = this.config.morphIgnore || [];
this.#copyAttributes(oldNode, newNode, noMorph);
if (this.config.morphSkip && oldNode.matches?.(this.config.morphSkip)) return;
this.#copyAttributes(oldNode, newNode);
if (oldNode instanceof HTMLTextAreaElement && oldNode.defaultValue != newNode.defaultValue) {
oldNode.value = newNode.value;
}
@ -2002,10 +2033,13 @@ var htmx = (() => {
if ((type === 8 || type === 3) && oldNode.nodeValue !== newNode.nodeValue) {
oldNode.nodeValue = newNode.nodeValue;
}
if (!oldNode.isEqualNode(newNode)) this.#morphChildren(ctx, oldNode, newNode);
let skipChildren = this.config.morphSkipChildren && oldNode.matches?.(this.config.morphSkipChildren);
if (!skipChildren && !oldNode.isEqualNode(newNode)) this.#morphChildren(ctx, oldNode, newNode);
}
#copyAttributes(destination, source, attributesToIgnore = []) {
#copyAttributes(destination, source) {
let attributesToIgnore = this.config.morphIgnore || [];
for (const attr of source.attributes) {
if (!attributesToIgnore.includes(attr.name) && destination.getAttribute(attr.name) !== attr.value) {
destination.setAttribute(attr.name, attr.value);
@ -2075,7 +2109,7 @@ var htmx = (() => {
}
let statusValue = this.#attributeValue(ctx.sourceElement, "hx-status:" + pattern);
if (statusValue) {
Object.assign(ctx, this.#parseConfig(statusValue));
this.#mergeConfig(statusValue, ctx);
return;
}
}

Binary file not shown.

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