Files
timetracker/ts/htmx-redirect-toast.ts
T
lukas 893cb22bf8 Convert htmx-redirect-toast.js to TS; remove dead legacy utils.js (issue #17)
- Add ts/htmx-redirect-toast.ts: typed port of the hx-redirect-toast htmx
  extension. Stays a classic (non-module) script — only touches the global
  htmx and registers an extension; layout.py now serves dist/htmx-redirect-toast.js
- Delete games/static/js/utils.js: the legacy hand-written copy is dead — every
  compiled module imports dist/utils.js (from ts/utils.ts); nothing references
  the old path

With this, the only first-party JS served is compiled from ts/; the sole
remaining hand-written .js in static is the vendored datepicker.umd.js bundle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 17:45:09 +02:00

61 lines
1.9 KiB
TypeScript

/**
* htmx "hx-redirect-toast" extension.
*
* A custom swap style that performs no DOM swap. On an HX-Redirect response it
* navigates immediately (the toast shows on the destination page); otherwise it
* turns the HX-Trigger header into CustomEvents so toasts fire in place.
*
* Classic (non-module) script: it only touches the global htmx and registers an
* extension, so it stays a plain <script> like the other vendored-adjacent glue.
*/
declare const htmx: any;
(() => {
htmx.defineExtension("hx-redirect-toast", {
isInlineSwap(swapStyle: string): boolean {
return swapStyle === "hx-redirect-toast";
},
handleSwap(
swapStyle: string,
target: HTMLElement,
fragment: Node,
settleInfo: unknown,
htmxConfig: { xhr: XMLHttpRequest }
): null {
const xhr = htmxConfig.xhr;
const hxRedirect = xhr.getResponseHeader("HX-Redirect");
const hxTrigger = xhr.getResponseHeader("HX-Trigger");
// Redirect immediately (toast will be shown on the new page)
if (hxRedirect) {
window.location.href = hxRedirect;
}
// Only dispatch HX-Trigger events for toasts when not redirecting
if (!hxRedirect && hxTrigger) {
const triggers = JSON.parse(hxTrigger);
const events = Array.isArray(triggers) ? triggers : [triggers];
events.forEach((triggerObject: Record<string, unknown>) => {
Object.entries(triggerObject).forEach(([name, rawDetail]) => {
let detail: unknown = rawDetail;
try {
detail = JSON.parse(rawDetail as string);
} catch {
// keep as-is
}
target.dispatchEvent(
new CustomEvent(name, {
detail,
bubbles: true,
cancelable: true,
})
);
});
});
}
// Return null to prevent any DOM swap
return null;
},
});
})();