Add toast notification system
Add more toast types
This commit is contained in:
+258
-8
@@ -30,6 +30,15 @@
|
||||
--color-orange-800: oklch(47% 0.157 37.304);
|
||||
--color-orange-900: oklch(40.8% 0.123 38.172);
|
||||
--color-orange-950: oklch(26.6% 0.079 36.259);
|
||||
--color-amber-50: oklch(98.7% 0.022 95.277);
|
||||
--color-amber-200: oklch(92.4% 0.12 95.746);
|
||||
--color-amber-300: oklch(87.9% 0.169 91.605);
|
||||
--color-amber-400: oklch(82.8% 0.189 84.429);
|
||||
--color-amber-500: oklch(76.9% 0.188 70.08);
|
||||
--color-amber-600: oklch(66.6% 0.179 58.318);
|
||||
--color-amber-700: oklch(55.5% 0.163 48.998);
|
||||
--color-amber-800: oklch(47.3% 0.137 46.201);
|
||||
--color-amber-900: oklch(41.4% 0.112 45.904);
|
||||
--color-yellow-50: oklch(98.7% 0.026 102.212);
|
||||
--color-yellow-100: oklch(97.3% 0.071 103.193);
|
||||
--color-yellow-200: oklch(94.5% 0.129 101.54);
|
||||
@@ -458,6 +467,9 @@
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
.pointer-events-auto {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -916,6 +928,9 @@
|
||||
.mt-0 {
|
||||
margin-top: calc(var(--spacing) * 0);
|
||||
}
|
||||
.mt-0\.5 {
|
||||
margin-top: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
.mt-1 {
|
||||
margin-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
@@ -1547,6 +1562,9 @@
|
||||
.w-64 {
|
||||
width: calc(var(--spacing) * 64);
|
||||
}
|
||||
.w-72 {
|
||||
width: calc(var(--spacing) * 72);
|
||||
}
|
||||
.w-80 {
|
||||
width: calc(var(--spacing) * 80);
|
||||
}
|
||||
@@ -1631,6 +1649,9 @@
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.-translate-x-full {
|
||||
--tw-translate-x: -100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@@ -1639,6 +1660,10 @@
|
||||
--tw-translate-x: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-x-8 {
|
||||
--tw-translate-x: calc(var(--spacing) * 8);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-x-full {
|
||||
--tw-translate-x: 100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@@ -1941,6 +1966,12 @@
|
||||
.border-accent {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
.border-amber-200 {
|
||||
border-color: var(--color-amber-200);
|
||||
}
|
||||
.border-blue-200 {
|
||||
border-color: var(--color-blue-200);
|
||||
}
|
||||
.border-brand {
|
||||
border-color: var(--color-brand);
|
||||
}
|
||||
@@ -1959,9 +1990,15 @@
|
||||
.border-gray-300 {
|
||||
border-color: var(--color-gray-300);
|
||||
}
|
||||
.border-green-200 {
|
||||
border-color: var(--color-green-200);
|
||||
}
|
||||
.border-purple-200 {
|
||||
border-color: var(--color-purple-200);
|
||||
}
|
||||
.border-red-200 {
|
||||
border-color: var(--color-red-200);
|
||||
}
|
||||
.border-transparent {
|
||||
border-color: transparent;
|
||||
}
|
||||
@@ -1996,12 +2033,18 @@
|
||||
background-color: var(--color-neutral-secondary-medium);
|
||||
}
|
||||
}
|
||||
.bg-amber-50 {
|
||||
background-color: var(--color-amber-50);
|
||||
}
|
||||
.bg-black\/70 {
|
||||
background-color: color-mix(in srgb, #000 70%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-black) 70%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-blue-50 {
|
||||
background-color: var(--color-blue-50);
|
||||
}
|
||||
.bg-blue-100 {
|
||||
background-color: var(--color-blue-100);
|
||||
}
|
||||
@@ -2041,6 +2084,9 @@
|
||||
background-color: color-mix(in oklab, var(--color-gray-900) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-green-50 {
|
||||
background-color: var(--color-green-50);
|
||||
}
|
||||
.bg-green-500 {
|
||||
background-color: var(--color-green-500);
|
||||
}
|
||||
@@ -2071,6 +2117,9 @@
|
||||
.bg-purple-500 {
|
||||
background-color: var(--color-purple-500);
|
||||
}
|
||||
.bg-red-50 {
|
||||
background-color: var(--color-red-50);
|
||||
}
|
||||
.bg-red-500 {
|
||||
background-color: var(--color-red-500);
|
||||
}
|
||||
@@ -2454,9 +2503,24 @@
|
||||
color: var(--color-heading);
|
||||
}
|
||||
}
|
||||
.text-amber-400 {
|
||||
color: var(--color-amber-400);
|
||||
}
|
||||
.text-amber-500 {
|
||||
color: var(--color-amber-500);
|
||||
}
|
||||
.text-amber-800 {
|
||||
color: var(--color-amber-800);
|
||||
}
|
||||
.text-black {
|
||||
color: var(--color-black);
|
||||
}
|
||||
.text-blue-400 {
|
||||
color: var(--color-blue-400);
|
||||
}
|
||||
.text-blue-500 {
|
||||
color: var(--color-blue-500);
|
||||
}
|
||||
.text-blue-600 {
|
||||
color: var(--color-blue-600);
|
||||
}
|
||||
@@ -2487,17 +2551,32 @@
|
||||
.text-gray-700 {
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
.text-gray-800 {
|
||||
color: var(--color-gray-800);
|
||||
}
|
||||
.text-gray-900 {
|
||||
color: var(--color-gray-900);
|
||||
}
|
||||
.text-green-600 {
|
||||
color: var(--color-green-600);
|
||||
.text-green-400 {
|
||||
color: var(--color-green-400);
|
||||
}
|
||||
.text-green-500 {
|
||||
color: var(--color-green-500);
|
||||
}
|
||||
.text-green-800 {
|
||||
color: var(--color-green-800);
|
||||
}
|
||||
.text-heading {
|
||||
color: var(--color-heading);
|
||||
}
|
||||
.text-red-600 {
|
||||
color: var(--color-red-600);
|
||||
.text-red-400 {
|
||||
color: var(--color-red-400);
|
||||
}
|
||||
.text-red-500 {
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
.text-red-800 {
|
||||
color: var(--color-red-800);
|
||||
}
|
||||
.text-slate-300 {
|
||||
color: var(--color-slate-300);
|
||||
@@ -2548,6 +2627,10 @@
|
||||
--tw-shadow: 0 1px var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-lg {
|
||||
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
@@ -2712,6 +2795,11 @@
|
||||
color: var(--color-body);
|
||||
}
|
||||
}
|
||||
.last\:mb-0 {
|
||||
&:last-child {
|
||||
margin-bottom: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
.odd\:bg-white {
|
||||
&:nth-child(odd) {
|
||||
background-color: var(--color-white);
|
||||
@@ -2834,6 +2922,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-amber-600 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-amber-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-blue-600 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-blue-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-blue-700 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2848,6 +2950,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-gray-600 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-gray-700 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2862,6 +2971,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-green-600 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-green-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-heading {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2869,6 +2985,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-red-600 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-red-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-white {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -3220,6 +3343,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:border-amber-700 {
|
||||
&:is(.dark *) {
|
||||
border-color: var(--color-amber-700);
|
||||
}
|
||||
}
|
||||
.dark\:border-blue-700 {
|
||||
&:is(.dark *) {
|
||||
border-color: var(--color-blue-700);
|
||||
}
|
||||
}
|
||||
.dark\:border-gray-500 {
|
||||
&:is(.dark *) {
|
||||
border-color: var(--color-gray-500);
|
||||
@@ -3235,11 +3368,26 @@
|
||||
border-color: var(--color-gray-700);
|
||||
}
|
||||
}
|
||||
.dark\:border-green-700 {
|
||||
&:is(.dark *) {
|
||||
border-color: var(--color-green-700);
|
||||
}
|
||||
}
|
||||
.dark\:border-purple-600 {
|
||||
&:is(.dark *) {
|
||||
border-color: var(--color-purple-600);
|
||||
}
|
||||
}
|
||||
.dark\:border-red-700 {
|
||||
&:is(.dark *) {
|
||||
border-color: var(--color-red-700);
|
||||
}
|
||||
}
|
||||
.dark\:bg-amber-900 {
|
||||
&:is(.dark *) {
|
||||
background-color: var(--color-amber-900);
|
||||
}
|
||||
}
|
||||
.dark\:bg-blue-200 {
|
||||
&:is(.dark *) {
|
||||
background-color: var(--color-blue-200);
|
||||
@@ -3250,6 +3398,11 @@
|
||||
background-color: var(--color-blue-600);
|
||||
}
|
||||
}
|
||||
.dark\:bg-blue-900 {
|
||||
&:is(.dark *) {
|
||||
background-color: var(--color-blue-900);
|
||||
}
|
||||
}
|
||||
.dark\:bg-gray-600 {
|
||||
&:is(.dark *) {
|
||||
background-color: var(--color-gray-600);
|
||||
@@ -3299,6 +3452,11 @@
|
||||
background-color: var(--color-green-600);
|
||||
}
|
||||
}
|
||||
.dark\:bg-green-900 {
|
||||
&:is(.dark *) {
|
||||
background-color: var(--color-green-900);
|
||||
}
|
||||
}
|
||||
.dark\:bg-purple-800 {
|
||||
&:is(.dark *) {
|
||||
background-color: var(--color-purple-800);
|
||||
@@ -3309,6 +3467,31 @@
|
||||
background-color: var(--color-red-600);
|
||||
}
|
||||
}
|
||||
.dark\:bg-red-900 {
|
||||
&:is(.dark *) {
|
||||
background-color: var(--color-red-900);
|
||||
}
|
||||
}
|
||||
.dark\:text-amber-200 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-amber-200);
|
||||
}
|
||||
}
|
||||
.dark\:text-amber-500 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-amber-500);
|
||||
}
|
||||
}
|
||||
.dark\:text-blue-200 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-blue-200);
|
||||
}
|
||||
}
|
||||
.dark\:text-blue-500 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-blue-500);
|
||||
}
|
||||
}
|
||||
.dark\:text-blue-800 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-blue-800);
|
||||
@@ -3339,14 +3522,24 @@
|
||||
color: var(--color-gray-600);
|
||||
}
|
||||
}
|
||||
.dark\:text-green-400 {
|
||||
.dark\:text-green-200 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-green-400);
|
||||
color: var(--color-green-200);
|
||||
}
|
||||
}
|
||||
.dark\:text-red-400 {
|
||||
.dark\:text-green-500 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-red-400);
|
||||
color: var(--color-green-500);
|
||||
}
|
||||
}
|
||||
.dark\:text-red-200 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-red-200);
|
||||
}
|
||||
}
|
||||
.dark\:text-red-500 {
|
||||
&:is(.dark *) {
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
}
|
||||
.dark\:text-slate-300 {
|
||||
@@ -3471,6 +3664,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:text-amber-300 {
|
||||
&:is(.dark *) {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-amber-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:text-blue-300 {
|
||||
&:is(.dark *) {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-blue-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:text-gray-300 {
|
||||
&:is(.dark *) {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-gray-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:text-green-300 {
|
||||
&:is(.dark *) {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-green-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:text-red-300 {
|
||||
&:is(.dark *) {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-red-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:hover\:text-white {
|
||||
&:is(.dark *) {
|
||||
&:hover {
|
||||
@@ -4042,6 +4280,18 @@ form input:disabled, select:disabled, textarea:disabled {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
right: calc(var(--spacing) * 0);
|
||||
bottom: calc(var(--spacing) * 0);
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
input:where([type='text']),input:where(:not([type])),input:where([type='email']),input:where([type='url']),input:where([type='password']),input:where([type='number']),input:where([type='date']),input:where([type='datetime-local']),input:where([type='month']),input:where([type='search']),input:where([type='tel']),input:where([type='time']),input:where([type='week']),select:where([multiple]),textarea,select {
|
||||
appearance: none;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
(function() {
|
||||
htmx.defineExtension("hx-redirect-toast", {
|
||||
isInlineSwap: function(swapStyle) {
|
||||
return swapStyle === "hx-redirect-toast";
|
||||
},
|
||||
handleSwap: function(swapStyle, target, fragment, settleInfo, htmxConfig) {
|
||||
var xhr = htmxConfig.xhr;
|
||||
var hxRedirect = xhr.getResponseHeader("HX-Redirect");
|
||||
var 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) {
|
||||
var triggers = JSON.parse(hxTrigger);
|
||||
var events = Array.isArray(triggers) ? triggers : [triggers];
|
||||
events.forEach(function(triggerObj) {
|
||||
Object.entries(triggerObj).forEach(function(entry) {
|
||||
var name = entry[0];
|
||||
var detail = entry[1];
|
||||
try { detail = JSON.parse(detail); } catch(e) {}
|
||||
target.dispatchEvent(new CustomEvent(name, {
|
||||
detail: detail,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
// Return null to prevent any DOM swap
|
||||
return null;
|
||||
}
|
||||
});
|
||||
})();
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,173 @@
|
||||
document.addEventListener("alpine:init", () => {
|
||||
let idCounter = 0;
|
||||
|
||||
console.log("[toast] Alpine available:", typeof Alpine !== "undefined");
|
||||
|
||||
Alpine.store("toasts", {
|
||||
toasts: [],
|
||||
|
||||
addToast(message, type) {
|
||||
console.log("[toast] addToast called:", { message, type });
|
||||
if (!type) type = "info";
|
||||
const validTypes = ["success", "error", "info", "warning", "debug"];
|
||||
if (!validTypes.includes(type)) type = "info";
|
||||
|
||||
if (this.toasts.length >= 3) {
|
||||
console.log("[toast] max 3 toasts reached, removing oldest");
|
||||
this.toasts.shift();
|
||||
}
|
||||
|
||||
const id = ++idCounter;
|
||||
console.log("[toast] toast added, count:", this.toasts.length);
|
||||
this.toasts.push({ id, message, type, visible: true, timer: null, pausedAt: null });
|
||||
|
||||
if (type !== "error") {
|
||||
const toast = this.toasts[this.toasts.length - 1];
|
||||
const autoDismissDelay = type === "debug" ? 3000 : 5000;
|
||||
toast.timer = setTimeout(() => {
|
||||
console.log("[toast] auto-dismiss after " + (autoDismissDelay / 1000) + "s");
|
||||
this.dismissToast(id);
|
||||
}, autoDismissDelay);
|
||||
}
|
||||
},
|
||||
|
||||
dismissToast(id) {
|
||||
console.log("[toast] dismissToast for id:", id);
|
||||
const idx = this.toasts.findIndex((t) => t.id === id);
|
||||
if (idx === -1) { console.log("[toast] toast not found"); return; }
|
||||
|
||||
const toast = this.toasts[idx];
|
||||
if (toast.timer) clearTimeout(toast.timer);
|
||||
toast.visible = false;
|
||||
|
||||
setTimeout(() => {
|
||||
this.toasts = this.toasts.filter((t) => t.id !== id);
|
||||
console.log("[toast] after dismiss, count:", this.toasts.length);
|
||||
}, 300);
|
||||
},
|
||||
|
||||
clearToastTimer(id) {
|
||||
const toast = this.toasts.find((t) => t.id === id);
|
||||
if (toast?.timer) {
|
||||
console.log("[toast] pause timer for toast id:", id);
|
||||
clearTimeout(toast.timer);
|
||||
toast.timer = null;
|
||||
toast.pausedAt = Date.now();
|
||||
}
|
||||
},
|
||||
|
||||
resumeToastTimer(id, duration) {
|
||||
const toast = this.toasts.find((t) => t.id === id);
|
||||
if (toast?.pausedAt && toast.timer === null) {
|
||||
console.log("[toast] resume timer for toast id:", id);
|
||||
toast.timer = setTimeout(() => {
|
||||
this.dismissToast(id);
|
||||
}, duration);
|
||||
toast.pausedAt = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Alpine.data("toastStore", () => ({
|
||||
init() {
|
||||
console.log("[toast] toastStore.init running");
|
||||
console.log("[toast] Alpine store toasts:", Alpine.store("toasts").toasts);
|
||||
|
||||
window.addEventListener("show-toast", (e) => {
|
||||
console.log("[toast] show-toast event received:", e.detail);
|
||||
if (Array.isArray(e.detail)) {
|
||||
e.detail.forEach((msg) => {
|
||||
Alpine.store("toasts").addToast(msg.message, msg.type);
|
||||
});
|
||||
} else {
|
||||
Alpine.store("toasts").addToast(e.detail.message, e.detail.type);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const script = document.getElementById("django-messages");
|
||||
if (script) {
|
||||
const msgs = JSON.parse(
|
||||
script.textContent || script.innerText || "[]"
|
||||
);
|
||||
console.log("[toast] django-messages script found:", msgs);
|
||||
if (Array.isArray(msgs)) {
|
||||
msgs.forEach((msg) => {
|
||||
console.log("[toast] loading django-message:", msg);
|
||||
Alpine.store("toasts").addToast(msg.message, msg.type || "info");
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[toast] localStorage restore failed:", e);
|
||||
// ignore parse errors
|
||||
}
|
||||
},
|
||||
|
||||
addToast(message, type) {
|
||||
console.log("[toast] toastStore.addToast delegating:", { message, type });
|
||||
Alpine.store("toasts").addToast(message, type);
|
||||
},
|
||||
|
||||
dismissToast(id) {
|
||||
console.log("[toast] toastStore.dismissToast delegating:", id);
|
||||
Alpine.store("toasts").dismissToast(id);
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
function toast(message, type) {
|
||||
console.log("[toast] toast() called:", { message, type });
|
||||
const evt = new CustomEvent("show-toast", {
|
||||
detail: { message, type },
|
||||
bubbles: true,
|
||||
});
|
||||
document.dispatchEvent(evt);
|
||||
console.log("[toast] CustomEvent dispatched, type:", evt.type);
|
||||
}
|
||||
window.toast = toast;
|
||||
|
||||
/**
|
||||
* Wrapper around fetch() that dispatches HTMX HX-Trigger events.
|
||||
* Use this for any fetch() call that expects HX-Trigger headers
|
||||
* (e.g., to show toasts via the HTMX middleware).
|
||||
*
|
||||
* @todo Migrate these call sites to hx-post + hx-on::after-request
|
||||
* for HTMX-native toast handling.
|
||||
*/
|
||||
window.fetchWithHtmxTriggers = function fetchWithHtmxTriggers(url, options = {}) {
|
||||
console.log("[fetchWithHtmxTriggers] fetching:", url);
|
||||
return fetch(url, options).then(async (response) => {
|
||||
console.log("[fetchWithHtmxTriggers] response status:", response.status);
|
||||
const htmxTrigger = response.headers.get("HX-Trigger");
|
||||
console.log("[fetchWithHtmxTriggers] HX-Trigger header:", htmxTrigger);
|
||||
if (htmxTrigger) {
|
||||
let triggers;
|
||||
try {
|
||||
triggers = JSON.parse(htmxTrigger);
|
||||
console.log("[fetchWithHtmxTriggers] parsed triggers:", triggers);
|
||||
} catch {
|
||||
console.warn("[fetchWithHtmxTriggers] failed to parse HX-Trigger JSON");
|
||||
return response;
|
||||
}
|
||||
// Handle both single object and array of events
|
||||
const events = Array.isArray(triggers) ? triggers : [triggers];
|
||||
events.forEach((triggerObj) => {
|
||||
Object.entries(triggerObj).forEach(([name, detail]) => {
|
||||
console.log("[fetchWithHtmxTriggers] dispatching event:", name, detail);
|
||||
let parsedDetail = detail;
|
||||
try {
|
||||
parsedDetail = JSON.parse(detail);
|
||||
} catch {
|
||||
// keep as string
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent(name, {
|
||||
detail: parsedDetail,
|
||||
bubbles: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
return response;
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user