efficient-shell 0.0.0 → 4.126.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +758 -3
- package/dist/Browser-LM4RRZIL.js +478 -0
- package/dist/Browser-LM4RRZIL.js.map +1 -0
- package/dist/Calculator-HCKFTSJR.js +188 -0
- package/dist/Calculator-HCKFTSJR.js.map +1 -0
- package/dist/ConfirmDialog-LJXMB45A.js +3 -0
- package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
- package/dist/CurrencyConverter-BKKORN6K.js +232 -0
- package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
- package/dist/Documents-DD3QI5ER.js +437 -0
- package/dist/Documents-DD3QI5ER.js.map +1 -0
- package/dist/Files-BOPCPMJE.js +801 -0
- package/dist/Files-BOPCPMJE.js.map +1 -0
- package/dist/Notepad-KJOLF63O.js +406 -0
- package/dist/Notepad-KJOLF63O.js.map +1 -0
- package/dist/PdfViewer-LSP3YNFC.js +4 -0
- package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
- package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
- package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
- package/dist/Preview-5UOC7ZRU.js +2955 -0
- package/dist/Preview-5UOC7ZRU.js.map +1 -0
- package/dist/Sidebar-J76PV4QF.js +290 -0
- package/dist/Sidebar-J76PV4QF.js.map +1 -0
- package/dist/Spreadsheet-J7BH7UHC.js +515 -0
- package/dist/Spreadsheet-J7BH7UHC.js.map +1 -0
- package/dist/Stock-CDUS4VCZ.js +154 -0
- package/dist/Stock-CDUS4VCZ.js.map +1 -0
- package/dist/Weather-EEDBWBPQ.js +427 -0
- package/dist/Weather-EEDBWBPQ.js.map +1 -0
- package/dist/WorldClock-FFFULVTJ.js +198 -0
- package/dist/WorldClock-FFFULVTJ.js.map +1 -0
- package/dist/apps/index.d.ts +267 -0
- package/dist/apps/index.js +147 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/brand.css +2 -0
- package/dist/chunk-2VM2FRZJ.js +215 -0
- package/dist/chunk-2VM2FRZJ.js.map +1 -0
- package/dist/chunk-32W6VY2J.js +549 -0
- package/dist/chunk-32W6VY2J.js.map +1 -0
- package/dist/chunk-3CUSBZWG.js +59 -0
- package/dist/chunk-3CUSBZWG.js.map +1 -0
- package/dist/chunk-3L463T5H.js +26 -0
- package/dist/chunk-3L463T5H.js.map +1 -0
- package/dist/chunk-4TVV2BIU.js +13334 -0
- package/dist/chunk-4TVV2BIU.js.map +1 -0
- package/dist/chunk-6XQHH7D2.js +105 -0
- package/dist/chunk-6XQHH7D2.js.map +1 -0
- package/dist/chunk-7CCHEEYC.js +94 -0
- package/dist/chunk-7CCHEEYC.js.map +1 -0
- package/dist/chunk-7KIOEKHS.js +617 -0
- package/dist/chunk-7KIOEKHS.js.map +1 -0
- package/dist/chunk-AXWC3332.js +1103 -0
- package/dist/chunk-AXWC3332.js.map +1 -0
- package/dist/chunk-BFBWLCO6.js +16 -0
- package/dist/chunk-BFBWLCO6.js.map +1 -0
- package/dist/chunk-CT3HVZRZ.js +26 -0
- package/dist/chunk-CT3HVZRZ.js.map +1 -0
- package/dist/chunk-D7PYW2QS.js +265 -0
- package/dist/chunk-D7PYW2QS.js.map +1 -0
- package/dist/chunk-DMCFOWOJ.js +153 -0
- package/dist/chunk-DMCFOWOJ.js.map +1 -0
- package/dist/chunk-FQB24B42.js +416 -0
- package/dist/chunk-FQB24B42.js.map +1 -0
- package/dist/chunk-GA35NEND.js +56 -0
- package/dist/chunk-GA35NEND.js.map +1 -0
- package/dist/chunk-HNH7FOYB.js +69 -0
- package/dist/chunk-HNH7FOYB.js.map +1 -0
- package/dist/chunk-HYZ5QMVO.js +462 -0
- package/dist/chunk-HYZ5QMVO.js.map +1 -0
- package/dist/chunk-I464GYJJ.js +17 -0
- package/dist/chunk-I464GYJJ.js.map +1 -0
- package/dist/chunk-JFQGPY7W.js +819 -0
- package/dist/chunk-JFQGPY7W.js.map +1 -0
- package/dist/chunk-MGGY74RY.js +14 -0
- package/dist/chunk-MGGY74RY.js.map +1 -0
- package/dist/chunk-N3CG5HG5.js +144 -0
- package/dist/chunk-N3CG5HG5.js.map +1 -0
- package/dist/chunk-QRGFTK2X.js +79 -0
- package/dist/chunk-QRGFTK2X.js.map +1 -0
- package/dist/chunk-SZ4WMHUX.js +7 -0
- package/dist/chunk-SZ4WMHUX.js.map +1 -0
- package/dist/chunk-W6XTG23K.js +3708 -0
- package/dist/chunk-W6XTG23K.js.map +1 -0
- package/dist/chunk-YG45DAUT.js +96 -0
- package/dist/chunk-YG45DAUT.js.map +1 -0
- package/dist/chunk-YNGPTOL6.js +46 -0
- package/dist/chunk-YNGPTOL6.js.map +1 -0
- package/dist/chunk-YNHLQ24V.js +400 -0
- package/dist/chunk-YNHLQ24V.js.map +1 -0
- package/dist/chunk-YNKK7NE3.js +171 -0
- package/dist/chunk-YNKK7NE3.js.map +1 -0
- package/dist/chunk-YPO6RDMX.js +20 -0
- package/dist/chunk-YPO6RDMX.js.map +1 -0
- package/dist/file-intake/index.d.ts +87 -0
- package/dist/file-intake/index.js +3 -0
- package/dist/file-intake/index.js.map +1 -0
- package/dist/index-dQia6NpB.d.ts +5904 -0
- package/dist/index.d.ts +2495 -0
- package/dist/index.js +7460 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/index.d.ts +118 -0
- package/dist/markdown/index.js +184 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markup/index.d.ts +235 -0
- package/dist/markup/index.js +135 -0
- package/dist/markup/index.js.map +1 -0
- package/dist/page-tabs/index.d.ts +154 -0
- package/dist/page-tabs/index.js +706 -0
- package/dist/page-tabs/index.js.map +1 -0
- package/dist/shell.css +105 -0
- package/dist/sounds-NT4DEZGD.js +3 -0
- package/dist/sounds-NT4DEZGD.js.map +1 -0
- package/dist/styles.css +22 -0
- package/dist/themes.css +249 -0
- package/dist/types-BOinJXUf.d.ts +118 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui.css +1518 -0
- package/package.json +172 -3
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// src/shell/toast.ts
|
|
2
|
+
var TOAST_CONTAINER_ID = "toast-container";
|
|
3
|
+
var TOAST_BOTTOM_CONTAINER_ID = "toast-container-bottom";
|
|
4
|
+
var NOTIF_CONTAINER_ID = "notif-container";
|
|
5
|
+
var FADE_MS = 300;
|
|
6
|
+
var defaults = {};
|
|
7
|
+
var live = /* @__PURE__ */ new Map();
|
|
8
|
+
function getOrCreate(id, className) {
|
|
9
|
+
let el = document.getElementById(id);
|
|
10
|
+
if (!el) {
|
|
11
|
+
el = document.createElement("div");
|
|
12
|
+
el.id = id;
|
|
13
|
+
el.className = className;
|
|
14
|
+
document.body.appendChild(el);
|
|
15
|
+
}
|
|
16
|
+
return el;
|
|
17
|
+
}
|
|
18
|
+
function getMenuOpacity() {
|
|
19
|
+
try {
|
|
20
|
+
const val = getComputedStyle(document.documentElement).getPropertyValue("--menu-opacity")?.trim();
|
|
21
|
+
if (val) return parseFloat(val);
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
return 0.95;
|
|
25
|
+
}
|
|
26
|
+
function glassBackground(o) {
|
|
27
|
+
return `linear-gradient(135deg, rgba(255,255,255,${o * 0.85}) 0%, rgba(255,255,255,${o * 0.65}) 50%, rgba(255,255,255,${o * 0.75}) 100%)`;
|
|
28
|
+
}
|
|
29
|
+
var GLASS_COMMON = `
|
|
30
|
+
backdrop-filter: blur(40px) saturate(1.8); -webkit-backdrop-filter: blur(40px) saturate(1.8);
|
|
31
|
+
border: 1px solid rgba(255,255,255,0.35);
|
|
32
|
+
box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), 0 8px 32px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08);
|
|
33
|
+
`;
|
|
34
|
+
function showToast(variant, message, opts) {
|
|
35
|
+
const opts_ = { ...defaults, ...opts };
|
|
36
|
+
const placement = opts_.placement ?? "top";
|
|
37
|
+
const sticky = opts_.sticky ?? false;
|
|
38
|
+
const key = `${placement}|${variant}|${message}`;
|
|
39
|
+
if (opts_.dedupe) {
|
|
40
|
+
const existing = live.get(key);
|
|
41
|
+
if (existing) {
|
|
42
|
+
existing.restart();
|
|
43
|
+
return { dismiss: existing.dismiss };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (variant !== "loading") {
|
|
47
|
+
import('./sounds-NT4DEZGD.js').then((s) => {
|
|
48
|
+
if (variant === "success") s.playSuccess();
|
|
49
|
+
else if (variant === "error") s.playError();
|
|
50
|
+
else s.playNotification();
|
|
51
|
+
}).catch(() => {
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const container = placement === "bottom" ? getOrCreate(TOAST_BOTTOM_CONTAINER_ID, "fixed bottom-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col-reverse gap-2 items-center pointer-events-none") : getOrCreate(TOAST_CONTAINER_ID, "fixed top-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col gap-2 items-center pointer-events-none");
|
|
55
|
+
const o = getMenuOpacity();
|
|
56
|
+
const color = variant === "success" ? "#22c55e" : variant === "error" ? "#ef4444" : (
|
|
57
|
+
// Amber, and deliberately not the error red: a warning is something the
|
|
58
|
+
// user should read before continuing, not something that failed. Painting
|
|
59
|
+
// both red teaches people to ignore the colour.
|
|
60
|
+
variant === "warning" ? "#f59e0b" : "#3b82f6"
|
|
61
|
+
);
|
|
62
|
+
const icons = {
|
|
63
|
+
success: '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="' + color + '" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>',
|
|
64
|
+
error: '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="' + color + '" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>',
|
|
65
|
+
warning: '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="' + color + '" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>',
|
|
66
|
+
info: '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="' + color + '" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>',
|
|
67
|
+
// An open arc that spins — same vocabulary as LoadingSpinner.
|
|
68
|
+
loading: '<svg class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24" stroke="' + color + '" stroke-width="2"><path stroke-linecap="round" d="M12 3a9 9 0 019 9"/></svg>'
|
|
69
|
+
};
|
|
70
|
+
const icon = icons[variant];
|
|
71
|
+
const offscreen = placement === "bottom" ? "translateY(10px)" : "translateY(-10px)";
|
|
72
|
+
const el = document.createElement("div");
|
|
73
|
+
el.className = "pointer-events-auto cursor-pointer";
|
|
74
|
+
el.style.cssText = `
|
|
75
|
+
padding: 8px 20px; border-radius: 12px;
|
|
76
|
+
background: ${glassBackground(o)}; ${GLASS_COMMON}
|
|
77
|
+
opacity: 0; transform: ${offscreen} scale(0.95);
|
|
78
|
+
transition: opacity ${FADE_MS}ms ease, transform ${FADE_MS}ms ease;
|
|
79
|
+
display: flex; align-items: center; gap: 8px;
|
|
80
|
+
font-size: 13px; font-weight: 500; color: rgb(55,65,81);
|
|
81
|
+
max-width: min(90vw, 460px);
|
|
82
|
+
`;
|
|
83
|
+
el.setAttribute("role", variant === "error" ? "alert" : "status");
|
|
84
|
+
el.setAttribute("aria-atomic", "true");
|
|
85
|
+
el.innerHTML = icon;
|
|
86
|
+
const span = document.createElement("span");
|
|
87
|
+
span.textContent = message;
|
|
88
|
+
el.appendChild(span);
|
|
89
|
+
container.appendChild(el);
|
|
90
|
+
requestAnimationFrame(() => {
|
|
91
|
+
el.style.opacity = "1";
|
|
92
|
+
el.style.transform = "translateY(0) scale(1)";
|
|
93
|
+
});
|
|
94
|
+
let timer;
|
|
95
|
+
let gone = false;
|
|
96
|
+
const dismiss = () => {
|
|
97
|
+
if (gone) return;
|
|
98
|
+
gone = true;
|
|
99
|
+
if (timer) clearTimeout(timer);
|
|
100
|
+
live.delete(key);
|
|
101
|
+
el.style.opacity = "0";
|
|
102
|
+
el.style.transform = `${offscreen} scale(0.95)`;
|
|
103
|
+
setTimeout(() => el.remove(), FADE_MS);
|
|
104
|
+
};
|
|
105
|
+
const duration = opts_.duration ?? (variant === "info" ? 4500 : 3e3);
|
|
106
|
+
const restart = () => {
|
|
107
|
+
if (timer) clearTimeout(timer);
|
|
108
|
+
if (!sticky) timer = setTimeout(dismiss, duration);
|
|
109
|
+
};
|
|
110
|
+
restart();
|
|
111
|
+
el.addEventListener("mouseenter", () => {
|
|
112
|
+
if (timer) clearTimeout(timer);
|
|
113
|
+
});
|
|
114
|
+
el.addEventListener("mouseleave", () => {
|
|
115
|
+
if (!gone) restart();
|
|
116
|
+
});
|
|
117
|
+
el.addEventListener("click", dismiss);
|
|
118
|
+
live.set(key, { el, restart, dismiss });
|
|
119
|
+
return { dismiss };
|
|
120
|
+
}
|
|
121
|
+
function showNotification(message, opts) {
|
|
122
|
+
import('./sounds-NT4DEZGD.js').then((s) => s.playNotification()).catch(() => {
|
|
123
|
+
});
|
|
124
|
+
const container = getOrCreate(NOTIF_CONTAINER_ID, "fixed top-4 right-4 z-[9999] flex flex-col gap-3 items-end pointer-events-none");
|
|
125
|
+
const o = getMenuOpacity();
|
|
126
|
+
const el = document.createElement("div");
|
|
127
|
+
el.className = "pointer-events-auto cursor-pointer";
|
|
128
|
+
el.style.cssText = `
|
|
129
|
+
min-width: 280px; max-width: 380px; padding: 12px 16px; border-radius: 16px;
|
|
130
|
+
background: ${glassBackground(o)}; ${GLASS_COMMON}
|
|
131
|
+
opacity: 0; transform: translateX(30px) scale(0.95);
|
|
132
|
+
transition: opacity ${FADE_MS}ms cubic-bezier(0.4,0,0.2,1), transform ${FADE_MS}ms cubic-bezier(0.4,0,0.2,1);
|
|
133
|
+
display: flex; align-items: flex-start; gap: 12px;
|
|
134
|
+
`;
|
|
135
|
+
el.innerHTML = `
|
|
136
|
+
<div style="width: 36px; height: 36px; border-radius: 10px; background: rgba(59,130,246,0.15); display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
|
|
137
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="#3b82f6" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"/></svg>
|
|
138
|
+
</div>
|
|
139
|
+
<div style="flex: 1; min-width: 0;">
|
|
140
|
+
<div style="font-size: 11px; font-weight: 600; color: #3b82f6; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 2px;">Notification</div>
|
|
141
|
+
<div class="notif-msg" style="font-size: 13px; font-weight: 500; color: rgb(55,65,81); line-height: 1.4;"></div>
|
|
142
|
+
</div>
|
|
143
|
+
<button style="flex-shrink: 0; padding: 4px; color: rgb(156,163,175); font-size: 18px; line-height: 1; transition: color 0.2s;" onmouseenter="this.style.color='rgb(75,85,99)'" onmouseleave="this.style.color='rgb(156,163,175)'">×</button>
|
|
144
|
+
`;
|
|
145
|
+
const msgEl = el.querySelector(".notif-msg");
|
|
146
|
+
if (msgEl) msgEl.textContent = message;
|
|
147
|
+
const dismiss = () => {
|
|
148
|
+
el.style.opacity = "0";
|
|
149
|
+
el.style.transform = "translateX(30px) scale(0.95)";
|
|
150
|
+
setTimeout(() => el.remove(), FADE_MS);
|
|
151
|
+
};
|
|
152
|
+
el.querySelector("button")?.addEventListener("click", (e) => {
|
|
153
|
+
e.stopPropagation();
|
|
154
|
+
dismiss();
|
|
155
|
+
});
|
|
156
|
+
el.addEventListener("click", dismiss);
|
|
157
|
+
container.appendChild(el);
|
|
158
|
+
requestAnimationFrame(() => {
|
|
159
|
+
el.style.opacity = "1";
|
|
160
|
+
el.style.transform = "translateX(0) scale(1)";
|
|
161
|
+
});
|
|
162
|
+
setTimeout(dismiss, opts?.duration ?? 1e4);
|
|
163
|
+
}
|
|
164
|
+
var toast = {
|
|
165
|
+
success: (message, opts) => showToast("success", message, opts),
|
|
166
|
+
error: (message, opts) => showToast("error", message, opts),
|
|
167
|
+
/**
|
|
168
|
+
* Something the user should read before continuing — a partial success, a
|
|
169
|
+
* setting that will not apply until they do something else. Distinct from
|
|
170
|
+
* `error`, which is "this did not happen".
|
|
171
|
+
*/
|
|
172
|
+
warning: (message, opts) => showToast("warning", message, opts),
|
|
173
|
+
info: (message, opts) => showToast("info", message, opts),
|
|
174
|
+
// Persistent top-right notification card (the old toast.info presentation).
|
|
175
|
+
notify: (message, opts) => showNotification(message, opts),
|
|
176
|
+
/**
|
|
177
|
+
* One toast for one async operation: a spinning "loading" toast while `p`
|
|
178
|
+
* is pending, swapped for a success or error toast when it settles. The
|
|
179
|
+
* promise is returned untouched, so `await toast.promise(save(), …)` still
|
|
180
|
+
* throws to the caller — this narrates the operation, it does not handle it.
|
|
181
|
+
*
|
|
182
|
+
* `error` is REQUIRED, and there is deliberately no fallback that prints
|
|
183
|
+
* the exception: a raw `e.message` in a toast is how internals leak to the
|
|
184
|
+
* screen (the same reasoning that keeps ErrorBoundary's stack behind
|
|
185
|
+
* `showDetails`). The success/error messages may be functions of the
|
|
186
|
+
* resolved value / the rejection, for the "Saved 3 rows" case.
|
|
187
|
+
*/
|
|
188
|
+
promise: (p, msgs, opts) => {
|
|
189
|
+
const pending = showToast("loading", msgs.loading, { ...opts, sticky: true });
|
|
190
|
+
p.then(
|
|
191
|
+
(value) => {
|
|
192
|
+
pending.dismiss();
|
|
193
|
+
showToast("success", typeof msgs.success === "function" ? msgs.success(value) : msgs.success, opts);
|
|
194
|
+
},
|
|
195
|
+
(error) => {
|
|
196
|
+
pending.dismiss();
|
|
197
|
+
showToast("error", typeof msgs.error === "function" ? msgs.error(error) : msgs.error, opts);
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
return p;
|
|
201
|
+
},
|
|
202
|
+
/**
|
|
203
|
+
* Set defaults for every subsequent toast. Call once at app startup; later
|
|
204
|
+
* calls replace the whole object rather than merging, so a consumer sets its
|
|
205
|
+
* policy in one place. Per-call options still take precedence.
|
|
206
|
+
*/
|
|
207
|
+
configure: (next) => {
|
|
208
|
+
defaults = { ...next };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var toast_default = toast;
|
|
212
|
+
|
|
213
|
+
export { toast_default };
|
|
214
|
+
//# sourceMappingURL=chunk-2VM2FRZJ.js.map
|
|
215
|
+
//# sourceMappingURL=chunk-2VM2FRZJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shell/toast.ts"],"names":[],"mappings":"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","file":"chunk-2VM2FRZJ.js","sourcesContent":["/**\n * Two presentations:\n *\n * 1. toast.success / error / info — brief operation feedback, top-center,\n * auto-dismiss (success/error ~3s, info ~4.5s). The everyday \"what just\n * happened\" feedback — most messages want this.\n * 2. toast.notify — system notification, top-right card, stays 10s, dismissible.\n * For an alert worth lingering on; reach for it deliberately, not by default.\n *\n * (Historically `toast.info` rendered the top-right notification card; it now\n * renders a brief toast — that persistent card moved to `toast.notify`.)\n */\n\nconst TOAST_CONTAINER_ID = 'toast-container';\nconst TOAST_BOTTOM_CONTAINER_ID = 'toast-container-bottom';\nconst NOTIF_CONTAINER_ID = 'notif-container';\nconst FADE_MS = 300;\n\nexport type ToastPlacement = 'top' | 'bottom';\n\nexport interface ToastOptions {\n /** Milliseconds before auto-dismiss. Ignored when `sticky`. */\n duration?: number;\n /**\n * Stay until dismissed. For a message the user must actually act on — the\n * kind that must not scroll past while they are looking at a customer.\n * Implies tap-to-dismiss is the only way out, which it already is.\n */\n sticky?: boolean;\n /**\n * `bottom` centres toasts along the bottom edge. On a till the top of the\n * screen is the cart total and the bottom is nearest the hand.\n */\n placement?: ToastPlacement;\n /**\n * Drop a message identical to one already showing, and RESTART its timer\n * rather than stacking a second copy. A failing poll otherwise writes the\n * same sentence a dozen times. The restart matters: the user asked twice, so\n * the message should persist, not expire on the first one's schedule.\n */\n dedupe?: boolean;\n}\n\n/**\n * Defaults for every subsequent toast, set once at app startup — the same\n * \"consumer wires this once\" shape as `setShellApiClient`/`setShellNavIcons`.\n * A till calls `toast.configure({ placement: 'bottom', duration: 6000,\n * dedupe: true })` instead of repeating those options at 40 call sites.\n * Per-call options still win.\n */\nlet defaults: ToastOptions = {};\n\n/** Live toasts by dedupe key, so a repeat can find and refresh its own. */\nconst live = new Map<string, { el: HTMLElement; restart: () => void; dismiss: () => void }>();\n\nfunction getOrCreate(id: string, className: string): HTMLElement {\n let el = document.getElementById(id);\n if (!el) {\n el = document.createElement('div');\n el.id = id;\n el.className = className;\n document.body.appendChild(el);\n }\n return el;\n}\n\nfunction getMenuOpacity(): number {\n try {\n const val = getComputedStyle(document.documentElement).getPropertyValue('--menu-opacity')?.trim();\n if (val) return parseFloat(val);\n } catch {}\n return 0.95;\n}\n\nfunction glassBackground(o: number): string {\n return `linear-gradient(135deg, rgba(255,255,255,${o * 0.85}) 0%, rgba(255,255,255,${o * 0.65}) 50%, rgba(255,255,255,${o * 0.75}) 100%)`;\n}\n\nconst GLASS_COMMON = `\n backdrop-filter: blur(40px) saturate(1.8); -webkit-backdrop-filter: blur(40px) saturate(1.8);\n border: 1px solid rgba(255,255,255,0.35);\n box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), 0 8px 32px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08);\n`;\n\n// ── Toast (operation feedback) — top-center, brief ──\n\nfunction showToast(variant: 'success' | 'error' | 'warning' | 'info' | 'loading', message: string,\n opts?: ToastOptions): { dismiss: () => void } {\n const opts_ = { ...defaults, ...opts };\n const placement: ToastPlacement = opts_.placement ?? 'top';\n const sticky = opts_.sticky ?? false;\n\n // A repeat of a message already on screen refreshes it instead of stacking.\n const key = `${placement}|${variant}|${message}`;\n if (opts_.dedupe) {\n const existing = live.get(key);\n if (existing) { existing.restart(); return { dismiss: existing.dismiss }; }\n }\n\n // A loading toast is the START of an operation — the sound belongs to the\n // outcome, which follows in a moment.\n if (variant !== 'loading') {\n import('../utils/sounds').then(s => {\n if (variant === 'success') s.playSuccess();\n else if (variant === 'error') s.playError();\n else s.playNotification();\n }).catch(() => {});\n }\n\n const container = placement === 'bottom'\n ? getOrCreate(TOAST_BOTTOM_CONTAINER_ID, 'fixed bottom-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col-reverse gap-2 items-center pointer-events-none')\n : getOrCreate(TOAST_CONTAINER_ID, 'fixed top-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col gap-2 items-center pointer-events-none');\n const o = getMenuOpacity();\n const color =\n variant === 'success' ? '#22c55e' :\n variant === 'error' ? '#ef4444' :\n // Amber, and deliberately not the error red: a warning is something the\n // user should read before continuing, not something that failed. Painting\n // both red teaches people to ignore the colour.\n variant === 'warning' ? '#f59e0b' :\n '#3b82f6'; // info and loading share the working blue\n const icons = {\n success: '<svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\"/></svg>',\n error: '<svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"/></svg>',\n warning: '<svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"/></svg>',\n info: '<svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>',\n // An open arc that spins — same vocabulary as LoadingSpinner.\n loading: '<svg class=\"h-4 w-4 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" d=\"M12 3a9 9 0 019 9\"/></svg>',\n };\n const icon = icons[variant];\n\n // Enter from the edge the toast is anchored to, so it reads as arriving from\n // off-screen rather than drifting in from the wrong direction.\n const offscreen = placement === 'bottom' ? 'translateY(10px)' : 'translateY(-10px)';\n\n const el = document.createElement('div');\n el.className = 'pointer-events-auto cursor-pointer';\n el.style.cssText = `\n padding: 8px 20px; border-radius: 12px;\n background: ${glassBackground(o)}; ${GLASS_COMMON}\n opacity: 0; transform: ${offscreen} scale(0.95);\n transition: opacity ${FADE_MS}ms ease, transform ${FADE_MS}ms ease;\n display: flex; align-items: center; gap: 8px;\n font-size: 13px; font-weight: 500; color: rgb(55,65,81);\n max-width: min(90vw, 460px);\n `;\n // A toast IS a status message, and it appears without the user moving focus\n // — so without a live region it is announced to nobody. `alert` is\n // assertive and interrupts whatever is being read, which is right for a\n // failure and wrong for a confirmation the user already expected.\n el.setAttribute('role', variant === 'error' ? 'alert' : 'status');\n // Read the whole toast, not the word that changed. Without it a repeat that\n // only alters a number announces the number alone.\n el.setAttribute('aria-atomic', 'true');\n\n el.innerHTML = icon;\n const span = document.createElement('span');\n span.textContent = message;\n el.appendChild(span);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateY(0) scale(1)'; });\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let gone = false;\n const dismiss = () => {\n if (gone) return;\n gone = true;\n if (timer) clearTimeout(timer);\n live.delete(key);\n el.style.opacity = '0';\n el.style.transform = `${offscreen} scale(0.95)`;\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n // Info messages are usually a sentence (\"no matches — check X\"); give them a\n // beat longer to read than the terse success/error confirmations.\n const duration = opts_.duration ?? (variant === 'info' ? 4500 : 3000);\n const restart = () => {\n if (timer) clearTimeout(timer);\n if (!sticky) timer = setTimeout(dismiss, duration);\n };\n restart();\n\n // Hold the timer while the pointer rests on it. Three seconds is enough to\n // read a confirmation and not enough to read an address someone leaned in\n // for — and a toast that vanishes as you reach for it cannot be re-read at\n // all, since there is no history to open.\n el.addEventListener('mouseenter', () => { if (timer) clearTimeout(timer); });\n el.addEventListener('mouseleave', () => { if (!gone) restart(); });\n\n // Tap anywhere on the toast to dismiss it. A toast has no other click\n // affordance, and a sticky one has no other exit at all — `notify` has\n // behaved this way since it existed.\n el.addEventListener('click', dismiss);\n\n live.set(key, { el, restart, dismiss });\n // Internal handle — `toast.promise` swaps its loading toast for the outcome.\n return { dismiss };\n}\n\n// ── Notification (system alert) — top-right, stays longer ──\n\nfunction showNotification(message: string, opts?: { duration?: number }) {\n import('../utils/sounds').then(s => s.playNotification()).catch(() => {});\n\n const container = getOrCreate(NOTIF_CONTAINER_ID, 'fixed top-4 right-4 z-[9999] flex flex-col gap-3 items-end pointer-events-none');\n const o = getMenuOpacity();\n\n const el = document.createElement('div');\n el.className = 'pointer-events-auto cursor-pointer';\n el.style.cssText = `\n min-width: 280px; max-width: 380px; padding: 12px 16px; border-radius: 16px;\n background: ${glassBackground(o)}; ${GLASS_COMMON}\n opacity: 0; transform: translateX(30px) scale(0.95);\n transition: opacity ${FADE_MS}ms cubic-bezier(0.4,0,0.2,1), transform ${FADE_MS}ms cubic-bezier(0.4,0,0.2,1);\n display: flex; align-items: flex-start; gap: 12px;\n `;\n\n el.innerHTML = `\n <div style=\"width: 36px; height: 36px; border-radius: 10px; background: rgba(59,130,246,0.15); display: flex; align-items: center; justify-content: center; flex-shrink: 0;\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"#3b82f6\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0\"/></svg>\n </div>\n <div style=\"flex: 1; min-width: 0;\">\n <div style=\"font-size: 11px; font-weight: 600; color: #3b82f6; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 2px;\">Notification</div>\n <div class=\"notif-msg\" style=\"font-size: 13px; font-weight: 500; color: rgb(55,65,81); line-height: 1.4;\"></div>\n </div>\n <button style=\"flex-shrink: 0; padding: 4px; color: rgb(156,163,175); font-size: 18px; line-height: 1; transition: color 0.2s;\" onmouseenter=\"this.style.color='rgb(75,85,99)'\" onmouseleave=\"this.style.color='rgb(156,163,175)'\">×</button>\n `;\n\n const msgEl = el.querySelector('.notif-msg');\n if (msgEl) msgEl.textContent = message;\n\n const dismiss = () => {\n el.style.opacity = '0';\n el.style.transform = 'translateX(30px) scale(0.95)';\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n el.querySelector('button')?.addEventListener('click', (e) => { e.stopPropagation(); dismiss(); });\n el.addEventListener('click', dismiss);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(0) scale(1)'; });\n\n setTimeout(dismiss, opts?.duration ?? 10000);\n}\n\nconst toast = {\n success: (message: string, opts?: ToastOptions) => showToast('success', message, opts),\n error: (message: string, opts?: ToastOptions) => showToast('error', message, opts),\n /**\n * Something the user should read before continuing — a partial success, a\n * setting that will not apply until they do something else. Distinct from\n * `error`, which is \"this did not happen\".\n */\n warning: (message: string, opts?: ToastOptions) => showToast('warning', message, opts),\n info: (message: string, opts?: ToastOptions) => showToast('info', message, opts),\n // Persistent top-right notification card (the old toast.info presentation).\n notify: (message: string, opts?: { duration?: number }) => showNotification(message, opts),\n /**\n * One toast for one async operation: a spinning \"loading\" toast while `p`\n * is pending, swapped for a success or error toast when it settles. The\n * promise is returned untouched, so `await toast.promise(save(), …)` still\n * throws to the caller — this narrates the operation, it does not handle it.\n *\n * `error` is REQUIRED, and there is deliberately no fallback that prints\n * the exception: a raw `e.message` in a toast is how internals leak to the\n * screen (the same reasoning that keeps ErrorBoundary's stack behind\n * `showDetails`). The success/error messages may be functions of the\n * resolved value / the rejection, for the \"Saved 3 rows\" case.\n */\n promise: <T,>(\n p: Promise<T>,\n msgs: {\n loading: string;\n success: string | ((value: T) => string);\n error: string | ((error: unknown) => string);\n },\n opts?: ToastOptions,\n ): Promise<T> => {\n const pending = showToast('loading', msgs.loading, { ...opts, sticky: true });\n p.then(\n value => {\n pending.dismiss();\n showToast('success', typeof msgs.success === 'function' ? msgs.success(value) : msgs.success, opts);\n },\n error => {\n pending.dismiss();\n showToast('error', typeof msgs.error === 'function' ? msgs.error(error) : msgs.error, opts);\n },\n );\n return p;\n },\n /**\n * Set defaults for every subsequent toast. Call once at app startup; later\n * calls replace the whole object rather than merging, so a consumer sets its\n * policy in one place. Per-call options still take precedence.\n */\n configure: (next: ToastOptions) => { defaults = { ...next }; },\n};\n\nexport default toast;\n"]}
|