@astralui/core 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,621 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var chroma = require('chroma-js');
6
+ var reactDom = require('react-dom');
7
+ var iconsReact = require('@tabler/icons-react');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var chroma__default = /*#__PURE__*/_interopDefault(chroma);
12
+
13
+ // src/theme/ColorSchemeProvider.tsx
14
+ var ColorSchemeContext = react.createContext({
15
+ colorScheme: "dark",
16
+ setColorScheme: () => {
17
+ },
18
+ toggle: () => {
19
+ }
20
+ });
21
+ function ColorSchemeProvider({ children, defaultScheme = "dark", storageKey = "astral-color-scheme" }) {
22
+ const [colorScheme, setScheme] = react.useState(() => {
23
+ try {
24
+ const v = localStorage.getItem(storageKey);
25
+ if (v === "light" || v === "dark") return v;
26
+ } catch {
27
+ }
28
+ return defaultScheme;
29
+ });
30
+ react.useEffect(() => {
31
+ const root = document.documentElement;
32
+ root.setAttribute("data-astral-scheme", colorScheme);
33
+ root.style.colorScheme = colorScheme;
34
+ }, [colorScheme]);
35
+ const setColorScheme = react.useCallback((scheme) => {
36
+ try {
37
+ localStorage.setItem(storageKey, scheme);
38
+ } catch {
39
+ }
40
+ setScheme(scheme);
41
+ }, [storageKey]);
42
+ const toggle = react.useCallback(() => {
43
+ setScheme((prev) => {
44
+ const next = prev === "dark" ? "light" : "dark";
45
+ try {
46
+ localStorage.setItem(storageKey, next);
47
+ } catch {
48
+ }
49
+ return next;
50
+ });
51
+ }, [storageKey]);
52
+ return /* @__PURE__ */ jsxRuntime.jsx(ColorSchemeContext.Provider, { value: { colorScheme, setColorScheme, toggle }, children });
53
+ }
54
+ function useColorScheme() {
55
+ return react.useContext(ColorSchemeContext);
56
+ }
57
+ var LIGHTNESS_MAP = [0.96, 0.907, 0.805, 0.697, 0.605, 0.547, 0.518, 0.445, 0.395, 0.34];
58
+ var SATURATION_MAP = [0.32, 0.16, 0.08, 0.04, 0, 0, 0.04, 0.08, 0.16, 0.32];
59
+ function getClosestLightness(colorObject) {
60
+ const lightnessGoal = colorObject.get("hsl.l");
61
+ return LIGHTNESS_MAP.reduce(
62
+ (prev, curr) => Math.abs(curr - lightnessGoal) < Math.abs(prev - lightnessGoal) ? curr : prev
63
+ );
64
+ }
65
+ function generateColors(color) {
66
+ const colorObject = chroma__default.default(color);
67
+ const closestLightness = getClosestLightness(colorObject);
68
+ const baseColorIndex = LIGHTNESS_MAP.findIndex((l) => l === closestLightness);
69
+ const colors = LIGHTNESS_MAP.map((l) => colorObject.set("hsl.l", l)).map((c) => chroma__default.default(c)).map((c, i) => {
70
+ const saturationDelta = SATURATION_MAP[i] - SATURATION_MAP[baseColorIndex];
71
+ return saturationDelta >= 0 ? c.saturate(saturationDelta) : c.desaturate(saturationDelta * -1);
72
+ });
73
+ colors[baseColorIndex] = chroma__default.default(color);
74
+ return colors.map((c) => c.hex());
75
+ }
76
+ var ThemePreviewContext = react.createContext({
77
+ preview: null,
78
+ setPreview: () => {
79
+ }
80
+ });
81
+ function useThemePreview() {
82
+ return react.useContext(ThemePreviewContext);
83
+ }
84
+ var HEX_RE = /^#[0-9a-f]{6}$/i;
85
+ var isValidHex = (v) => !!v && HEX_RE.test(v);
86
+ function adjustHex(hex, amount) {
87
+ const c = hex.replace("#", "");
88
+ const r = Math.max(0, Math.min(255, parseInt(c.substring(0, 2), 16) + amount));
89
+ const g = Math.max(0, Math.min(255, parseInt(c.substring(2, 4), 16) + amount));
90
+ const b = Math.max(0, Math.min(255, parseInt(c.substring(4, 6), 16) + amount));
91
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
92
+ }
93
+ function hexToRgba(hex, alpha) {
94
+ const c = hex.replace("#", "");
95
+ const r = parseInt(c.substring(0, 2), 16);
96
+ const g = parseInt(c.substring(2, 4), 16);
97
+ const b = parseInt(c.substring(4, 6), 16);
98
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
99
+ }
100
+ function primaryVars(palette, scheme) {
101
+ if (scheme === "light") {
102
+ return [
103
+ `--astral-primary-color-filled:${palette[6]}`,
104
+ `--astral-primary-color-filled-hover:${palette[7]}`,
105
+ `--astral-primary-color-light:${hexToRgba(palette[6], 0.1)}`,
106
+ `--astral-primary-color-light-hover:${hexToRgba(palette[6], 0.12)}`,
107
+ `--astral-primary-color-light-color:${palette[6]}`
108
+ ];
109
+ }
110
+ return [
111
+ `--astral-primary-color-filled:${palette[8]}`,
112
+ `--astral-primary-color-filled-hover:${palette[9]}`,
113
+ `--astral-primary-color-light:${hexToRgba(palette[6], 0.15)}`,
114
+ `--astral-primary-color-light-hover:${hexToRgba(palette[6], 0.2)}`,
115
+ `--astral-primary-color-light-color:${palette[3]}`
116
+ ];
117
+ }
118
+ function safeGenerate(hex) {
119
+ try {
120
+ return generateColors(hex);
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+ function buildOrgCss(src) {
126
+ const brand = isValidHex(src.primary_color) ? safeGenerate(src.primary_color) : null;
127
+ const lightBrand = isValidHex(src.light_primary_color) ? safeGenerate(src.light_primary_color) : null;
128
+ const lightPalette = lightBrand ?? brand;
129
+ const noDarkBrand = !brand;
130
+ const vBg = isValidHex(src.background_color) ? src.background_color : "";
131
+ const vCard = isValidHex(src.card_color) ? src.card_color : "";
132
+ const vNav = isValidHex(src.navbar_color) ? src.navbar_color : "";
133
+ const vFont = isValidHex(src.font_color) ? src.font_color : "";
134
+ const vLBg = isValidHex(src.light_background_color) ? src.light_background_color : "";
135
+ const vLCard = isValidHex(src.light_card_color) ? src.light_card_color : "";
136
+ const vLNav = isValidHex(src.light_navbar_color) ? src.light_navbar_color : "";
137
+ const vLFont = isValidHex(src.light_font_color) ? src.light_font_color : "";
138
+ const dark = [];
139
+ const light = [];
140
+ if (brand) dark.push(...primaryVars(brand, "dark"));
141
+ if (vFont) {
142
+ dark.push(`--astral-color-dark-0:${vFont}`, `--astral-color-dark-1:${adjustHex(vFont, -30)}`, `--astral-color-dark-2:${adjustHex(vFont, -60)}`, `--astral-color-text:${vFont}`);
143
+ }
144
+ if (vCard) dark.push(`--astral-color-dark-6:${vCard}`);
145
+ if (vBg) dark.push(`--astral-color-dark-7:${vBg}`, `--astral-color-dark-9:${adjustHex(vBg, -10)}`, `--astral-color-body:${vBg}`, `--astral-app-bg:${vBg}`);
146
+ if (vNav) dark.push(`--astral-color-dark-8:${vNav}`, `--astral-app-nav:${vNav}`);
147
+ if (vCard) dark.push(`--astral-app-card:${vCard}`);
148
+ if (lightBrand && noDarkBrand) {
149
+ for (let i = 0; i < 10; i++) light.push(`--astral-color-violet-${i}:${lightBrand[i]}`);
150
+ }
151
+ if (lightPalette) light.push(...primaryVars(lightPalette, "light"));
152
+ if (vLFont) light.push(`--astral-color-text:${vLFont}`);
153
+ if (vLBg) light.push(`--astral-app-bg:${vLBg}`);
154
+ if (vLNav) light.push(`--astral-app-nav:${vLNav}`);
155
+ if (vLCard) light.push(`--astral-app-card:${vLCard}`);
156
+ let css = "";
157
+ if (dark.length) css += `html:root[data-astral-scheme="dark"]{${dark.join(";")}}`;
158
+ if (light.length) css += `html:root[data-astral-scheme="light"]{${light.join(";")}}`;
159
+ return css;
160
+ }
161
+ function AstralThemeProvider({ colors, children }) {
162
+ const [preview, setPreview] = react.useState(null);
163
+ const stableSetPreview = react.useCallback((c) => {
164
+ setPreview(c);
165
+ }, []);
166
+ const effective = preview ? { ...colors ?? {}, ...preview } : colors ?? null;
167
+ const orgCss = react.useMemo(
168
+ () => effective ? buildOrgCss(effective) : "",
169
+ [effective ? JSON.stringify(effective) : ""]
170
+ );
171
+ return /* @__PURE__ */ jsxRuntime.jsxs(ThemePreviewContext.Provider, { value: { preview, setPreview: stableSetPreview }, children: [
172
+ /* @__PURE__ */ jsxRuntime.jsx("style", { "data-astral-theme": true, children: orgCss }),
173
+ children
174
+ ] });
175
+ }
176
+ function useDismiss(opened, onClose) {
177
+ react.useEffect(() => {
178
+ if (!opened) return;
179
+ const onKey = (e) => {
180
+ if (e.key === "Escape") onClose();
181
+ };
182
+ document.addEventListener("keydown", onKey);
183
+ const prev = document.body.style.overflow;
184
+ document.body.style.overflow = "hidden";
185
+ return () => {
186
+ document.removeEventListener("keydown", onKey);
187
+ document.body.style.overflow = prev;
188
+ };
189
+ }, [opened, onClose]);
190
+ }
191
+ function AstralModal({ opened, onClose, title, brand, width = 460, zIndex, children }) {
192
+ useDismiss(opened, onClose);
193
+ if (!opened) return null;
194
+ const style = { maxWidth: width, ...brand ? { "--au-ic": brand } : {} };
195
+ return reactDom.createPortal(
196
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-modal-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-modal-panel", style, onMouseDown: (e) => e.stopPropagation(), children: [
197
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-modal-head", children: [
198
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-modal-title", children: title }),
199
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "au-modal-x", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, {}) })
200
+ ] }),
201
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-modal-body", children })
202
+ ] }) }),
203
+ document.body
204
+ );
205
+ }
206
+ function AstralDrawer({ opened, onClose, title, brand, width = 440, zIndex, children }) {
207
+ useDismiss(opened, onClose);
208
+ if (!opened) return null;
209
+ const style = { width, ...brand ? { "--au-ic": brand } : {} };
210
+ return reactDom.createPortal(
211
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-drawer-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-drawer-panel", style, onMouseDown: (e) => e.stopPropagation(), children: [
212
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-drawer-head", children: [
213
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-drawer-title", children: title }),
214
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "au-modal-x", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, {}) })
215
+ ] }),
216
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-drawer-body", children })
217
+ ] }) }),
218
+ document.body
219
+ );
220
+ }
221
+ function AstralPinInput({ length = 6, value, onChange, autoFocus }) {
222
+ const refs = react.useRef([]);
223
+ const digits = Array.from({ length }, (_, i) => value[i] ?? "");
224
+ const focusBox = (i) => {
225
+ refs.current[Math.max(0, Math.min(length - 1, i))]?.focus();
226
+ };
227
+ const handleChange = (i, raw) => {
228
+ const clean2 = raw.replace(/\D/g, "");
229
+ if (!clean2) return;
230
+ const arr = value.split("");
231
+ let pos = i;
232
+ for (const ch of clean2) {
233
+ if (pos >= length) break;
234
+ arr[pos] = ch;
235
+ pos++;
236
+ }
237
+ onChange(arr.join("").slice(0, length));
238
+ focusBox(pos);
239
+ };
240
+ const handleKeyDown = (i, e) => {
241
+ if (e.key === "Backspace") {
242
+ e.preventDefault();
243
+ const arr = value.split("");
244
+ if (arr[i]) {
245
+ arr[i] = "";
246
+ onChange(arr.join(""));
247
+ } else if (i > 0) {
248
+ arr[i - 1] = "";
249
+ onChange(arr.join(""));
250
+ focusBox(i - 1);
251
+ }
252
+ } else if (e.key === "ArrowLeft") {
253
+ e.preventDefault();
254
+ focusBox(i - 1);
255
+ } else if (e.key === "ArrowRight") {
256
+ e.preventDefault();
257
+ focusBox(i + 1);
258
+ }
259
+ };
260
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-pin", children: digits.map((d, i) => /* @__PURE__ */ jsxRuntime.jsx(
261
+ "input",
262
+ {
263
+ ref: (el) => {
264
+ refs.current[i] = el;
265
+ },
266
+ className: "au-pin-box",
267
+ inputMode: "numeric",
268
+ autoComplete: "one-time-code",
269
+ "aria-label": `Digit ${i + 1}`,
270
+ value: d,
271
+ autoFocus: autoFocus && i === 0,
272
+ onChange: (e) => handleChange(i, e.currentTarget.value),
273
+ onKeyDown: (e) => handleKeyDown(i, e),
274
+ onFocus: (e) => e.currentTarget.select()
275
+ },
276
+ i
277
+ )) });
278
+ }
279
+ function AstralSelect({ value, onChange, options, placeholder, disabled, searchable, searchPlaceholder, noResults, clearable, creatable }) {
280
+ const [open, setOpen] = react.useState(false);
281
+ const [q, setQ] = react.useState("");
282
+ const triggerRef = react.useRef(null);
283
+ const menuRef = react.useRef(null);
284
+ const [pos, setPos] = react.useState(null);
285
+ const reposition = react.useCallback(() => {
286
+ const el = triggerRef.current;
287
+ if (!el) return;
288
+ const r = el.getBoundingClientRect();
289
+ const spaceBelow = window.innerHeight - r.bottom - 12;
290
+ const spaceAbove = r.top - 12;
291
+ const flipUp = spaceBelow < 200 && spaceAbove > spaceBelow;
292
+ setPos({
293
+ left: r.left,
294
+ width: r.width,
295
+ ...flipUp ? { bottom: window.innerHeight - r.top + 5, maxHeight: Math.max(140, Math.min(300, spaceAbove)) } : { top: r.bottom + 5, maxHeight: Math.max(140, Math.min(300, spaceBelow)) }
296
+ });
297
+ }, []);
298
+ react.useEffect(() => {
299
+ if (!open) return;
300
+ reposition();
301
+ const onScroll = () => reposition();
302
+ const onResize = () => setOpen(false);
303
+ const onDoc = (e) => {
304
+ if (triggerRef.current?.contains(e.target)) return;
305
+ if (menuRef.current?.contains(e.target)) return;
306
+ setOpen(false);
307
+ };
308
+ window.addEventListener("scroll", onScroll, true);
309
+ window.addEventListener("resize", onResize);
310
+ document.addEventListener("mousedown", onDoc, true);
311
+ return () => {
312
+ window.removeEventListener("scroll", onScroll, true);
313
+ window.removeEventListener("resize", onResize);
314
+ document.removeEventListener("mousedown", onDoc, true);
315
+ };
316
+ }, [open, reposition]);
317
+ const selected = options.find((o) => o.value === value);
318
+ const hasValue = value != null && value !== "";
319
+ const display = selected ? selected.label : value ?? "";
320
+ const filtered = searchable && q.trim() ? options.filter((o) => o.label.toLowerCase().includes(q.trim().toLowerCase())) : options;
321
+ const showCreate = !!creatable && !!q.trim() && !options.some((o) => o.label.toLowerCase() === q.trim().toLowerCase());
322
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-select", children: [
323
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { ref: triggerRef, type: "button", className: "au-input au-select-trigger", disabled, onClick: () => setOpen((o) => !o), children: [
324
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: hasValue ? "" : "ph", children: hasValue ? display : placeholder }),
325
+ clearable && hasValue ? /* @__PURE__ */ jsxRuntime.jsx(
326
+ "span",
327
+ {
328
+ className: "au-select-caret au-select-clear",
329
+ role: "button",
330
+ tabIndex: -1,
331
+ onClick: (e) => {
332
+ e.stopPropagation();
333
+ onChange("");
334
+ },
335
+ children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, { size: 14 })
336
+ }
337
+ ) : /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconChevronDown, { size: 15, className: "au-select-caret" })
338
+ ] }),
339
+ open && !disabled && pos && reactDom.createPortal(
340
+ /* @__PURE__ */ jsxRuntime.jsxs(
341
+ "div",
342
+ {
343
+ ref: menuRef,
344
+ className: "au-select-menu",
345
+ style: { left: pos.left, top: pos.top, bottom: pos.bottom, width: pos.width, maxHeight: pos.maxHeight },
346
+ children: [
347
+ searchable && /* @__PURE__ */ jsxRuntime.jsx(
348
+ "input",
349
+ {
350
+ autoFocus: true,
351
+ className: "au-select-search",
352
+ placeholder: searchPlaceholder,
353
+ value: q,
354
+ onChange: (e) => setQ(e.currentTarget.value)
355
+ }
356
+ ),
357
+ filtered.length === 0 && !showCreate && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-select-empty", children: noResults }),
358
+ filtered.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
359
+ "button",
360
+ {
361
+ type: "button",
362
+ className: `au-select-opt${o.value === value ? " on" : ""}`,
363
+ onClick: () => {
364
+ onChange(o.value);
365
+ setOpen(false);
366
+ setQ("");
367
+ },
368
+ children: o.label
369
+ },
370
+ o.value
371
+ )),
372
+ showCreate && /* @__PURE__ */ jsxRuntime.jsxs(
373
+ "button",
374
+ {
375
+ type: "button",
376
+ className: "au-select-opt au-select-create",
377
+ onClick: () => {
378
+ onChange(q.trim());
379
+ setOpen(false);
380
+ setQ("");
381
+ },
382
+ children: [
383
+ "+ ",
384
+ q.trim()
385
+ ]
386
+ }
387
+ )
388
+ ]
389
+ }
390
+ ),
391
+ document.body
392
+ )
393
+ ] });
394
+ }
395
+ function AstralMenu({ trigger, items, align = "end", width = 220 }) {
396
+ const [open, setOpen] = react.useState(false);
397
+ const anchorRef = react.useRef(null);
398
+ const menuRef = react.useRef(null);
399
+ const [pos, setPos] = react.useState(null);
400
+ const reposition = react.useCallback(() => {
401
+ const el = anchorRef.current;
402
+ if (!el) return;
403
+ const r = el.getBoundingClientRect();
404
+ const spaceBelow = window.innerHeight - r.bottom - 12;
405
+ const spaceAbove = r.top - 12;
406
+ const flipUp = spaceBelow < 220 && spaceAbove > spaceBelow;
407
+ const left = align === "end" ? Math.max(8, Math.min(r.right - width, window.innerWidth - width - 8)) : Math.min(r.left, window.innerWidth - width - 8);
408
+ setPos({
409
+ left,
410
+ minWidth: width,
411
+ ...flipUp ? { bottom: window.innerHeight - r.top + 5, maxHeight: Math.max(160, Math.min(380, spaceAbove)) } : { top: r.bottom + 5, maxHeight: Math.max(160, Math.min(380, spaceBelow)) }
412
+ });
413
+ }, [align, width]);
414
+ react.useEffect(() => {
415
+ if (!open) return;
416
+ reposition();
417
+ const onScroll = () => reposition();
418
+ const onResize = () => setOpen(false);
419
+ const onDoc = (e) => {
420
+ if (anchorRef.current?.contains(e.target)) return;
421
+ if (menuRef.current?.contains(e.target)) return;
422
+ setOpen(false);
423
+ };
424
+ window.addEventListener("scroll", onScroll, true);
425
+ window.addEventListener("resize", onResize);
426
+ document.addEventListener("mousedown", onDoc, true);
427
+ return () => {
428
+ window.removeEventListener("scroll", onScroll, true);
429
+ window.removeEventListener("resize", onResize);
430
+ document.removeEventListener("mousedown", onDoc, true);
431
+ };
432
+ }, [open, reposition]);
433
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
434
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-menu-anchor", ref: anchorRef, onClick: () => setOpen((o) => !o), children: trigger }),
435
+ open && pos && reactDom.createPortal(
436
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: menuRef, className: "au-menu", style: { left: pos.left, top: pos.top, bottom: pos.bottom, minWidth: pos.minWidth, maxHeight: pos.maxHeight }, children: items.map((it, i) => it.divider ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-menu-divider" }, it.key ?? `d${i}`) : /* @__PURE__ */ jsxRuntime.jsxs(
437
+ "button",
438
+ {
439
+ type: "button",
440
+ className: `au-menu-item${it.danger ? " danger" : ""}`,
441
+ disabled: it.disabled,
442
+ onClick: () => {
443
+ setOpen(false);
444
+ it.onClick?.();
445
+ },
446
+ children: [
447
+ it.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-menu-ic", children: it.icon }),
448
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-menu-label", children: it.label })
449
+ ]
450
+ },
451
+ it.key ?? i
452
+ )) }),
453
+ document.body
454
+ )
455
+ ] });
456
+ }
457
+ var toasts = [];
458
+ var listeners = /* @__PURE__ */ new Set();
459
+ var timers = /* @__PURE__ */ new Map();
460
+ var counter = 0;
461
+ var emit = () => listeners.forEach((l) => l());
462
+ function clearTimer(id) {
463
+ const x = timers.get(id);
464
+ if (x) {
465
+ clearTimeout(x);
466
+ timers.delete(id);
467
+ }
468
+ }
469
+ function scheduleAutoClose(t) {
470
+ clearTimer(t.id);
471
+ if (t.autoClose === false || t.loading) return;
472
+ const ms = typeof t.autoClose === "number" ? t.autoClose : 4e3;
473
+ timers.set(t.id, setTimeout(() => hide(t.id), ms));
474
+ }
475
+ function show(opts) {
476
+ const id = opts.id ?? `toast-${++counter}`;
477
+ const existing = toasts.find((t) => t.id === id);
478
+ const toast = { withCloseButton: true, ...existing, ...opts, id };
479
+ toasts = existing ? toasts.map((t) => t.id === id ? toast : t) : [...toasts, toast];
480
+ emit();
481
+ scheduleAutoClose(toast);
482
+ return id;
483
+ }
484
+ function update(opts) {
485
+ if (!opts.id) return show(opts);
486
+ const existing = toasts.find((t) => t.id === opts.id);
487
+ if (!existing) return show(opts);
488
+ const toast = { ...existing, ...opts, id: opts.id };
489
+ toasts = toasts.map((t) => t.id === opts.id ? toast : t);
490
+ emit();
491
+ scheduleAutoClose(toast);
492
+ return opts.id;
493
+ }
494
+ function hide(id) {
495
+ const t = toasts.find((x) => x.id === id);
496
+ clearTimer(id);
497
+ toasts = toasts.filter((x) => x.id !== id);
498
+ emit();
499
+ t?.onClose?.();
500
+ }
501
+ function clean() {
502
+ toasts.forEach((t) => clearTimer(t.id));
503
+ toasts = [];
504
+ emit();
505
+ }
506
+ var notifications = { show, update, hide, clean };
507
+ var subscribe = (cb) => {
508
+ listeners.add(cb);
509
+ return () => {
510
+ listeners.delete(cb);
511
+ };
512
+ };
513
+ var getSnapshot = () => toasts;
514
+ var DEFAULT_ICON = {
515
+ green: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconCheck, { size: 16 }),
516
+ teal: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconCheck, { size: 16 }),
517
+ red: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconExclamationCircle, { size: 16 }),
518
+ orange: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconAlertTriangle, { size: 16 }),
519
+ yellow: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconAlertTriangle, { size: 16 })
520
+ };
521
+ function Spinner() {
522
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-toast-spin" });
523
+ }
524
+ function ToastCard({ t }) {
525
+ const accent = t.color ? `var(--astral-color-${t.color}-6)` : "var(--astral-color-default-border)";
526
+ const lead = t.loading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) : t.icon ? t.icon : t.color && DEFAULT_ICON[t.color] || /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconInfoCircle, { size: 16 });
527
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-toast", style: { ["--au-toast-accent"]: accent }, role: "status", children: [
528
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-toast-lead", children: lead }),
529
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-toast-body", children: [
530
+ t.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-toast-title", children: t.title }),
531
+ t.message != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-toast-msg", children: t.message })
532
+ ] }),
533
+ t.withCloseButton !== false && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "au-toast-x", onClick: () => hide(t.id), "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, { size: 14 }) })
534
+ ] });
535
+ }
536
+ function AstralToaster() {
537
+ const items = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
538
+ if (typeof document === "undefined") return null;
539
+ return reactDom.createPortal(
540
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-toast-host", role: "region", "aria-live": "polite", "aria-label": "Notifications", children: items.map((t) => /* @__PURE__ */ jsxRuntime.jsx(ToastCard, { t }, t.id)) }),
541
+ document.body
542
+ );
543
+ }
544
+ var pad2 = (n) => String(n).padStart(2, "0");
545
+ var pad4 = (n) => String(n).padStart(4, "0");
546
+ function dateToInputStr(d) {
547
+ if (!d || isNaN(d.getTime())) return "";
548
+ return `${pad4(d.getFullYear())}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
549
+ }
550
+ function dateTimeToInputStr(d) {
551
+ if (!d || isNaN(d.getTime())) return "";
552
+ return `${dateToInputStr(d)}T${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
553
+ }
554
+ function DateInput({ type = "date", value, onChange, min, max, className, style, disabled, "aria-label": ariaLabel }) {
555
+ const isTime = type === "datetime-local";
556
+ const fmt = isTime ? dateTimeToInputStr : dateToInputStr;
557
+ const [s, setS] = react.useState(() => fmt(value));
558
+ const t = value?.getTime();
559
+ react.useEffect(() => {
560
+ setS(fmt(value));
561
+ }, [t]);
562
+ const handle = (str) => {
563
+ setS(str);
564
+ if (str === "") {
565
+ onChange(null);
566
+ return;
567
+ }
568
+ const m = (isTime ? /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/ : /^(\d{4})-(\d{2})-(\d{2})$/).exec(str);
569
+ if (!m) return;
570
+ const y = Number(m[1]);
571
+ if (y < 1970 || y > 2100) return;
572
+ const d = isTime ? new Date(y, Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5])) : new Date(y, Number(m[2]) - 1, Number(m[3]));
573
+ if (isNaN(d.getTime())) return;
574
+ onChange(d);
575
+ };
576
+ return /* @__PURE__ */ jsxRuntime.jsx(
577
+ "input",
578
+ {
579
+ type,
580
+ className,
581
+ style,
582
+ disabled,
583
+ "aria-label": ariaLabel,
584
+ value: s,
585
+ min: min ? fmt(min) : void 0,
586
+ max: max ? fmt(max) : void 0,
587
+ onChange: (e) => handle(e.currentTarget.value)
588
+ }
589
+ );
590
+ }
591
+ function Spinner2({ size = 24, className, style }) {
592
+ return /* @__PURE__ */ jsxRuntime.jsx(
593
+ "span",
594
+ {
595
+ className: className ? `au-spinner ${className}` : "au-spinner",
596
+ style: { width: size, height: size, ...style },
597
+ role: "status",
598
+ "aria-label": "Loading"
599
+ }
600
+ );
601
+ }
602
+
603
+ exports.AstralDrawer = AstralDrawer;
604
+ exports.AstralMenu = AstralMenu;
605
+ exports.AstralModal = AstralModal;
606
+ exports.AstralPinInput = AstralPinInput;
607
+ exports.AstralSelect = AstralSelect;
608
+ exports.AstralThemeProvider = AstralThemeProvider;
609
+ exports.AstralToaster = AstralToaster;
610
+ exports.ColorSchemeProvider = ColorSchemeProvider;
611
+ exports.DateInput = DateInput;
612
+ exports.Spinner = Spinner2;
613
+ exports.buildOrgCss = buildOrgCss;
614
+ exports.dateTimeToInputStr = dateTimeToInputStr;
615
+ exports.dateToInputStr = dateToInputStr;
616
+ exports.generateColors = generateColors;
617
+ exports.notifications = notifications;
618
+ exports.useColorScheme = useColorScheme;
619
+ exports.useThemePreview = useThemePreview;
620
+ //# sourceMappingURL=index.cjs.map
621
+ //# sourceMappingURL=index.cjs.map