138 lines
4.1 KiB
JavaScript
138 lines
4.1 KiB
JavaScript
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 <input type="color"> + 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);
|
||
});
|