kithkin/resources/js/app.js

138 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 isnt 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);
});