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