import './bootstrap'; import htmx from 'htmx.org'; /** * htmx/global */ // make html globally visible to use the devtools and extensions window.htmx = htmx; // global htmx config htmx.config.historyEnabled = true; // HX-Boost back/forward support htmx.logger = console.log; // verbose logging during dev // csrf on htmx requests document.addEventListener('htmx:configRequest', (evt) => { const token = document.querySelector('meta[name="csrf-token"]')?.content if (token) evt.detail.headers['X-CSRF-TOKEN'] = token }) /** * calendar toggle * progressive enhancement on html form with no js */ document.addEventListener('change', event => { const checkbox = event.target; // ignore anything that isn’t one of our checkboxes if (!checkbox.matches('.calendar-toggle')) return; const slug = checkbox.value; const show = checkbox.checked; // toggle .hidden on every matching event element document .querySelectorAll(`[data-calendar="${slug}"]`) .forEach(el => el.classList.toggle('hidden', !show)); }); /** * color picker component * native + hex + random palette) */ function initColorPickers(root = document) { const isHex = (v) => /^#?[0-9a-fA-F]{6}$/.test((v || '').trim()); const normalize = (v) => { let s = (v || '').trim(); if (!s) return null; if (!s.startsWith('#')) s = '#' + s; if (!isHex(s)) return null; return s.toUpperCase(); }; const pickRandom = (arr) => arr[Math.floor(Math.random() * arr.length)]; const wire = (el) => { // avoid double-binding when htmx swaps if (el.__colorpickerWired) return; el.__colorpickerWired = true; const color = el.querySelector('[data-colorpicker-color]'); const hex = el.querySelector('[data-colorpicker-hex]'); const btn = el.querySelector('[data-colorpicker-random]'); if (!color || !hex) return; let palette = []; try { palette = JSON.parse(el.getAttribute('data-palette') || '[]'); } catch { palette = []; } const setValue = (val) => { const n = normalize(val); if (!n) return false; color.value = n; hex.value = n; // bubble input/change for any listeners (htmx, previews, etc.) color.dispatchEvent(new Event('input', { bubbles: true })); color.dispatchEvent(new Event('change', { bubbles: true })); return true; }; // init sync from native input hex.value = normalize(color.value) || '#000000'; // native picker -> hex field color.addEventListener('input', () => { const n = normalize(color.value); if (n) hex.value = n; }); // hex typing -> native picker (on blur + Enter) const commitHex = () => { const ok = setValue(hex.value); if (!ok) hex.value = normalize(color.value) || hex.value; }; hex.addEventListener('blur', commitHex); hex.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); commitHex(); } }); // random button if (btn && palette.length) { btn.addEventListener('click', (e) => { e.preventDefault(); // defensive: never submit, never navigate e.stopPropagation(); let next = pickRandom(palette); if (palette.length > 1) { const current = normalize(color.value); // avoid re-rolling the same number if possible while (normalize(next) === current) next = pickRandom(palette); } setValue(next); }); } }; root.querySelectorAll('[data-colorpicker]').forEach(wire); } // initial bind document.addEventListener('DOMContentLoaded', () => initColorPickers()); // rebind in htmx for swapped content document.addEventListener('htmx:afterSwap', (e) => { initColorPickers(e.target); });