declare const htmx: any; /** * Runs initializeElement once for each element matching selector, on initial * page load and inside every htmx-swapped fragment (a port of FastHTML's * proc_htmx). htmx fires htmx:load for the initial document and for each * swapped-in element, so a single registration covers both; the WeakSet * guarantees once-per-element initialization, replacing the old * DOMContentLoaded + htmx:afterSwap + per-element guard-flag pattern. */ function onSwap(selector: string, initializeElement: (element: Element) => void) { const initialized = new WeakSet(); htmx.onLoad((swappedElement: Element) => { const elements: Element[] = Array.from(htmx.findAll(swappedElement, selector)); if (swappedElement.matches && swappedElement.matches(selector)) { elements.unshift(swappedElement); } for (const element of elements) { if (initialized.has(element)) continue; initialized.add(element); initializeElement(element); } }); } /** Formats Date to a UTC string accepted by the datetime-local input field. */ function toISOUTCString(date: Date): string { function stringAndPad(number: number) { return number.toString().padStart(2, "0"); } const year = date.getFullYear(); const month = stringAndPad(date.getMonth() + 1); const day = stringAndPad(date.getDate()); const hours = stringAndPad(date.getHours()); const minutes = stringAndPad(date.getMinutes()); return `${year}-${month}-${day}T${hours}:${minutes}`; } /** * Mirrors each source element's value onto its target until the target is * focused (manual edit wins). Each syncData entry maps a source selector and * property onto a target selector and property. */ function syncSelectInputUntilChanged(syncData: Array<{ source: string; target: string; source_value: string; target_value: string }>, parentSelector: string | Document = document) { const parentElement = parentSelector === document ? document : document.querySelector(parentSelector as string); if (!parentElement) { console.error(`The parent selector "${parentSelector}" is not valid.`); return; } // Set up a single change event listener on the document for handling all source changes parentElement.addEventListener("change", function (event) { // Loop through each sync configuration item syncData.forEach((syncItem: { source: string; target: string; source_value: string; target_value: string }) => { // Check if the change event target matches the source selector if ((event.target as HTMLElement).matches(syncItem.source)) { if (!event.target) return; const sourceElement = event.target; const valueToSync = getValueFromProperty( sourceElement, syncItem.source_value ); const targetElement = document.querySelector(syncItem.target); if (targetElement && valueToSync !== null) { console.log(`Changing value of ${syncItem.target} to ${valueToSync}`); (targetElement as unknown as Record)[syncItem.target_value] = valueToSync; } } }); }); // Set up a single focus event listener on the document for handling all target focuses const syncListener = (event: Event) => { // Loop through each sync configuration item syncData.forEach((syncItem: { source: string; target: string; source_value: string; target_value: string }) => { // Check if the focus event target matches the target selector if ((event.target as HTMLElement).matches(syncItem.target)) { // Remove the change event listener to stop syncing // This assumes you want to stop syncing once any target receives focus // You may need a more sophisticated way to remove listeners if you want to stop // syncing selectively based on other conditions document.removeEventListener("change", syncListener); } }); } parentElement.addEventListener( "focus", syncListener, true ); // Use capture phase to ensure the event is captured during focus, not bubble } /** * Reads a property off the source element. For a