From d94cb71a47da5e40b325c614a3a44e74bf08312a Mon Sep 17 00:00:00 2001 From: rameel Date: Tue, 13 Oct 2026 19:00:04 +0500 Subject: [PATCH] script: return the target instead of live childNodes from outerSync The outerSync fallback for HTMX 1.9.x and 2.x returned source.childNodes from handleSwap. That NodeList is live, so it was already empty once replaceChildren had moved the nodes into the target. HTMX 1.9.x therefore found nothing to process, and HTMX 2.x ignores any value that is not an array. As a result, hx-* attributes on the new children were not activated and htmx:load did not fire. Copying the nodes into an array would fix only the children: the target's own hx-* attributes are synchronized too, and HTMX would not re-initialize it. Return [target], as HTMX 4.x does. HTMX then processes the target together with its new children and re-initializes it when its attributes have changed. --- src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js | 7 ++++--- src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js index 20c7f58..32591ce 100644 --- a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js +++ b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js @@ -53,10 +53,11 @@ document._r_htmx ||= ((document, htmx) => { target.setAttribute(attr.name, attr.value); } - let nodes = source.childNodes; - target.replaceChildren(...nodes); + target.replaceChildren(...source.childNodes); - return nodes; + // Like HTMX 4.x, report the target itself: HTMX processes it with + // its new children and re-initializes it if its attributes changed. + return [target]; } } diff --git a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js index cc7a1b5..c436e52 100644 --- a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js +++ b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js @@ -1 +1 @@ -document._r_htmx||=((e,t)=>{const r=e=>console.warn(`ramstack.htmxtoolkit: ${e}`),a=parseInt(t.version);let n;t.defineExtension&&t.defineExtension("ramstack-swap",{init(e){n=e},isInlineSwap:e=>"outerMorph"===e||"outerSync"===e,handleSwap(e,t,a){if("textContent"===e)return t.textContent=a.textContent,[t];let n="innerMorph"===e?"innerHTML":"outerMorph"===e?"outerHTML":null;if(n){let o=globalThis.Idiomorph;if(o?.morph)return o.morph(t,a.children,{morphStyle:n});r(`Idiomorph is unavailable; falling back from ${e} to ${n}`);let l=[...a.childNodes];return"innerHTML"===n?t.replaceChildren(...l):t.replaceWith(...l),l}if("outerSync"===e){r("outerSync requires HTMX 4.x; falling back to attribute sync and innerHTML");let e=a.firstElementChild;if(e){for(let r of[...t.attributes])e.hasAttribute(r.name)||t.removeAttribute(r.name);for(let r of e.attributes)t.setAttribute(r.name,r.value);let r=e.childNodes;return t.replaceChildren(...r),r}}}});const o=(t,r)=>{e.addEventListener(t,r)},l=e=>{let t=e.querySelector("meta[name='htmx-config']")?.dataset||{};return{headerName:t.antiforgeryHeaderName,formFieldName:t.antiforgeryFormFieldName,requestToken:t.antiforgeryRequestToken}};let i=l(e);const s=(e,t,r)=>{if(!/^get$/i.test(e)){const{headerName:e,formFieldName:a,requestToken:n}=i;n&&(r.has?.(a)||r[a]||(e?t[e]=n:r.set?r.set(a,n):r[a]=n))}},u=e=>{let t=(new DOMParser).parseFromString(e||"","text/html"),r=l(t);r.requestToken&&(i=r)};return o("htmx:afterOnLoad",e=>{let t=e.detail;t.boosted&&u(t.xhr.responseText)}),o("htmx:after:request",e=>{let t=e.detail.ctx;t.boosted&&u(t.text)}),o("htmx:configRequest",e=>{let t=e.detail;s(t.verb,t.headers,t.parameters)}),o("htmx:config:request",e=>{let t=e.detail.ctx.request;s(t.method,t.headers,t.body)}),o("htmx:beforeRequest",t=>{if(4>a){let r=t.target,{xhr:a,target:o,select:l,requestConfig:i}=t.detail;null!=i.headers["HX-Request-Type"]||(e=>{let t=!1;return n.getClosestMatch(e,e=>{let r=n.getAttributeValue(e,"hx-request")?.trim();if(r&&!/^(js|javascript):/.test(r))try{let{noHeaders:e}=JSON.parse("{"===r[0]?r:`{${r}}`);if(null!=e)return t=!!e,!0}catch{}}),t})(r)||(l||=n.getClosestAttributeValue(r,"hx-select"),a.setRequestHeader("HX-Request-Type",o===e.body||l&&"unset"!==l?"full":"partial"))}}),o("rs:event",e=>{let r=e.target;for(let{key:n,value:o}of e.detail.value||e.detail){let e=r;4>a?"[object Object]"===Object.prototype.toString.call(o)?2===a&&void 0!==o.target&&(r=e=o.target):o={value:o}:(o?.target&&(e=t.find(o.target)),"object"!=typeof o&&(o={value:o})),t.trigger(e,n,o)}}),!0})(document,htmx); \ No newline at end of file +document._r_htmx||=((e,t)=>{const r=e=>console.warn(`ramstack.htmxtoolkit: ${e}`),a=parseInt(t.version);let n;t.defineExtension&&t.defineExtension("ramstack-swap",{init(e){n=e},isInlineSwap:e=>"outerMorph"===e||"outerSync"===e,handleSwap(e,t,a){if("textContent"===e)return t.textContent=a.textContent,[t];let n="innerMorph"===e?"innerHTML":"outerMorph"===e?"outerHTML":null;if(n){let o=globalThis.Idiomorph;if(o?.morph)return o.morph(t,a.children,{morphStyle:n});r(`Idiomorph is unavailable; falling back from ${e} to ${n}`);let l=[...a.childNodes];return"innerHTML"===n?t.replaceChildren(...l):t.replaceWith(...l),l}if("outerSync"===e){r("outerSync requires HTMX 4.x; falling back to attribute sync and innerHTML");let e=a.firstElementChild;if(e){for(let r of[...t.attributes])e.hasAttribute(r.name)||t.removeAttribute(r.name);for(let r of e.attributes)t.setAttribute(r.name,r.value);return t.replaceChildren(...e.childNodes),[t]}}}});const o=(t,r)=>{e.addEventListener(t,r)},l=e=>{let t=e.querySelector("meta[name='htmx-config']")?.dataset||{};return{headerName:t.antiforgeryHeaderName,formFieldName:t.antiforgeryFormFieldName,requestToken:t.antiforgeryRequestToken}};let i=l(e);const s=(e,t,r)=>{if(!/^get$/i.test(e)){const{headerName:e,formFieldName:a,requestToken:n}=i;n&&(r.has?.(a)||r[a]||(e?t[e]=n:r.set?r.set(a,n):r[a]=n))}},u=e=>{let t=(new DOMParser).parseFromString(e||"","text/html"),r=l(t);r.requestToken&&(i=r)};return o("htmx:afterOnLoad",e=>{let t=e.detail;t.boosted&&u(t.xhr.responseText)}),o("htmx:after:request",e=>{let t=e.detail.ctx;t.boosted&&u(t.text)}),o("htmx:configRequest",e=>{let t=e.detail;s(t.verb,t.headers,t.parameters)}),o("htmx:config:request",e=>{let t=e.detail.ctx.request;s(t.method,t.headers,t.body)}),o("htmx:beforeRequest",t=>{if(4>a){let r=t.target,{xhr:a,target:o,select:l,requestConfig:i}=t.detail;null!=i.headers["HX-Request-Type"]||(e=>{let t=!1;return n.getClosestMatch(e,e=>{let r=n.getAttributeValue(e,"hx-request")?.trim();if(r&&!/^(js|javascript):/.test(r))try{let{noHeaders:e}=JSON.parse("{"===r[0]?r:`{${r}}`);if(null!=e)return t=!!e,!0}catch{}}),t})(r)||(l||=n.getClosestAttributeValue(r,"hx-select"),a.setRequestHeader("HX-Request-Type",o===e.body||l&&"unset"!==l?"full":"partial"))}}),o("rs:event",e=>{let r=e.target;for(let{key:n,value:o}of e.detail.value||e.detail){let e=r;4>a?"[object Object]"===Object.prototype.toString.call(o)?2===a&&void 0!==o.target&&(r=e=o.target):o={value:o}:(o?.target&&(e=t.find(o.target)),"object"!=typeof o&&(o={value:o})),t.trigger(e,n,o)}}),!0})(document,htmx); \ No newline at end of file