fix: resolve HTMX toggle swap error and restore smooth animations
PROBLEM: - htmx:swapError with "Cannot read properties of null (reading 'insertBefore')" on double-click - Toggle animations were "digital" (instant snap) instead of "analogical" (smooth slide) - Conflict between server templates with hx-swap-oob and client-side hyperscript ROOT CAUSE: - Server templates returned HTML with hx-swap="outerHTML" + hx-swap-oob="true" - This destroyed and recreated DOM elements during swap - Second click tried to insert into null parent (element was destroyed) - CSS transitions broke because element was destroyed mid-animation SOLUTION: - Remove all HTML from toggle templates (length-toggle.html, logo-toggle.html, theme-toggle.html) - Templates now return empty comment: "<!-- Template not used - toggles use hx-swap="none" with inline hyperscript -->" - Toggles use hx-swap="none" to prevent any DOM replacement - All visual updates handled client-side via inline hyperscript - Server only saves cookies in background (no HTML returned) BENEFITS: - ✅ No more null reference errors (no DOM destruction) - ✅ Smooth CSS transitions work perfectly (element preserved) - ✅ Desktop/mobile toggles sync via direct ID manipulation - ✅ Zero HTMX swap conflicts - ✅ Clean separation: client handles visuals, server persists state DOCUMENTATION: - Updated MODERN-WEB-TECHNIQUES.md with Phase 8 - Documented the complete debug journey and solution - Added architecture pattern for client-first toggles
This commit is contained in:
@@ -1,56 +1 @@
|
||||
<!-- Primary response: Desktop length toggle -->
|
||||
<div class="selector-group" id="desktop-length-toggle">
|
||||
<label class="selector-label">{{if eq .Lang "es"}}Longitud{{else}}Length{{end}}:</label>
|
||||
<label class="icon-toggle">
|
||||
<input type="checkbox"
|
||||
id="lengthToggle"
|
||||
{{if eq .CVLengthClass "cv-long"}}checked{{end}}
|
||||
hx-post="/toggle/length?lang={{.Lang}}"
|
||||
hx-target="#desktop-length-toggle"
|
||||
hx-swap="outerHTML"
|
||||
_="on htmx:afterRequest
|
||||
if my.checked
|
||||
remove .cv-short from .cv-paper
|
||||
add .cv-long to .cv-paper
|
||||
set localStorage['cv-length'] to 'long'
|
||||
else
|
||||
remove .cv-long from .cv-paper
|
||||
add .cv-short to .cv-paper
|
||||
set localStorage['cv-length'] to 'short'
|
||||
end">
|
||||
<span class="icon-toggle-slider">
|
||||
<iconify-icon icon="mdi:file-document-outline" width="16" height="16" class="icon-left"></iconify-icon>
|
||||
<iconify-icon icon="mdi:file-document-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Out-of-band swap: Mobile length toggle -->
|
||||
<div class="menu-control-item" id="mobile-length-toggle" hx-swap-oob="true">
|
||||
<label class="menu-control-label">
|
||||
<iconify-icon icon="mdi:file-document-outline" width="20" height="20"></iconify-icon>
|
||||
<span>{{if eq .Lang "es"}}Longitud{{else}}Length{{end}}</span>
|
||||
</label>
|
||||
<label class="icon-toggle">
|
||||
<input type="checkbox"
|
||||
id="lengthToggleMenu"
|
||||
{{if eq .CVLengthClass "cv-long"}}checked{{end}}
|
||||
hx-post="/toggle/length?lang={{.Lang}}"
|
||||
hx-target="#mobile-length-toggle"
|
||||
hx-swap="outerHTML"
|
||||
_="on htmx:afterRequest
|
||||
if my.checked
|
||||
remove .cv-short from .cv-paper
|
||||
add .cv-long to .cv-paper
|
||||
set localStorage['cv-length'] to 'long'
|
||||
else
|
||||
remove .cv-long from .cv-paper
|
||||
add .cv-short to .cv-paper
|
||||
set localStorage['cv-length'] to 'short'
|
||||
end">
|
||||
<span class="icon-toggle-slider">
|
||||
<iconify-icon icon="mdi:file-document-outline" width="16" height="16" class="icon-left"></iconify-icon>
|
||||
<iconify-icon icon="mdi:file-document-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<!-- Template not used - toggles use hx-swap="none" with inline hyperscript -->
|
||||
|
||||
Reference in New Issue
Block a user