@astralui/core 0.1.9 → 0.1.11
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 +145 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +145 -37
- package/dist/index.js.map +1 -1
- package/dist/styles.css +15 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -173,48 +173,139 @@ function AstralThemeProvider({ colors, children }) {
|
|
|
173
173
|
children
|
|
174
174
|
] });
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
var dialogSeq = 0;
|
|
177
|
+
var dialogStack = [];
|
|
178
|
+
var FOCUSABLE = [
|
|
179
|
+
"a[href]",
|
|
180
|
+
"area[href]",
|
|
181
|
+
"button:not([disabled])",
|
|
182
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
183
|
+
"select:not([disabled])",
|
|
184
|
+
"textarea:not([disabled])",
|
|
185
|
+
"iframe",
|
|
186
|
+
"object",
|
|
187
|
+
"embed",
|
|
188
|
+
"[contenteditable]",
|
|
189
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
190
|
+
].join(",");
|
|
191
|
+
function useDialog(opened, onClose) {
|
|
192
|
+
const panelRef = react.useRef(null);
|
|
193
|
+
const idRef = react.useRef("");
|
|
194
|
+
if (!idRef.current) idRef.current = "au-dialog-title-" + String(++dialogSeq);
|
|
195
|
+
const onCloseRef = react.useRef(onClose);
|
|
196
|
+
onCloseRef.current = onClose;
|
|
177
197
|
react.useEffect(() => {
|
|
178
198
|
if (!opened) return;
|
|
199
|
+
const panel = panelRef.current;
|
|
200
|
+
if (!panel) return;
|
|
201
|
+
const restoreTo = document.activeElement;
|
|
202
|
+
dialogStack.push(panel);
|
|
203
|
+
const isTop = () => dialogStack[dialogStack.length - 1] === panel;
|
|
204
|
+
const focusables = () => Array.from(panel.querySelectorAll(FOCUSABLE)).filter(
|
|
205
|
+
(el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement
|
|
206
|
+
);
|
|
207
|
+
const raf = requestAnimationFrame(() => {
|
|
208
|
+
if (panel.contains(document.activeElement)) return;
|
|
209
|
+
const list = focusables();
|
|
210
|
+
const target = list.find((el) => !el.classList.contains("au-modal-x")) ?? list[0] ?? panel;
|
|
211
|
+
target.focus();
|
|
212
|
+
});
|
|
179
213
|
const onKey = (e) => {
|
|
180
|
-
if (
|
|
214
|
+
if (!isTop()) return;
|
|
215
|
+
if (e.key === "Escape") {
|
|
216
|
+
onCloseRef.current();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (e.key !== "Tab") return;
|
|
220
|
+
const list = focusables();
|
|
221
|
+
if (!list.length) {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
panel.focus();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const first = list[0];
|
|
227
|
+
const last = list[list.length - 1];
|
|
228
|
+
const active = document.activeElement;
|
|
229
|
+
if (!panel.contains(active)) {
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
(e.shiftKey ? last : first).focus();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (e.shiftKey && active === first) {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
last.focus();
|
|
237
|
+
} else if (!e.shiftKey && active === last) {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
first.focus();
|
|
240
|
+
}
|
|
181
241
|
};
|
|
182
|
-
document.addEventListener("keydown", onKey);
|
|
183
|
-
const
|
|
242
|
+
document.addEventListener("keydown", onKey, true);
|
|
243
|
+
const prevOverflow = document.body.style.overflow;
|
|
184
244
|
document.body.style.overflow = "hidden";
|
|
185
245
|
return () => {
|
|
186
|
-
|
|
187
|
-
document.
|
|
246
|
+
cancelAnimationFrame(raf);
|
|
247
|
+
document.removeEventListener("keydown", onKey, true);
|
|
248
|
+
const at = dialogStack.indexOf(panel);
|
|
249
|
+
if (at !== -1) dialogStack.splice(at, 1);
|
|
250
|
+
document.body.style.overflow = prevOverflow;
|
|
251
|
+
if (restoreTo && document.contains(restoreTo)) restoreTo.focus();
|
|
188
252
|
};
|
|
189
|
-
}, [opened
|
|
253
|
+
}, [opened]);
|
|
254
|
+
return { panelRef, titleId: idRef.current };
|
|
190
255
|
}
|
|
191
256
|
function AstralModal({ opened, onClose, title, brand, width = 460, zIndex, children }) {
|
|
192
|
-
|
|
257
|
+
const { panelRef, titleId } = useDialog(opened, onClose);
|
|
193
258
|
if (!opened) return null;
|
|
194
259
|
const style = { maxWidth: width, ...brand ? { "--au-ic": brand } : {} };
|
|
195
260
|
return reactDom.createPortal(
|
|
196
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-modal-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-modal-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
262
|
+
"div",
|
|
263
|
+
{
|
|
264
|
+
ref: panelRef,
|
|
265
|
+
className: "au-modal-panel",
|
|
266
|
+
style,
|
|
267
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
268
|
+
role: "dialog",
|
|
269
|
+
"aria-modal": "true",
|
|
270
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
271
|
+
tabIndex: -1,
|
|
272
|
+
children: [
|
|
273
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-modal-head", children: [
|
|
274
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-modal-title", id: titleId, children: title }),
|
|
275
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "au-modal-x", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, {}) })
|
|
276
|
+
] }),
|
|
277
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-modal-body", children })
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
) }),
|
|
203
281
|
document.body
|
|
204
282
|
);
|
|
205
283
|
}
|
|
206
284
|
function AstralDrawer({ opened, onClose, title, brand, width = 440, zIndex, children }) {
|
|
207
|
-
|
|
285
|
+
const { panelRef, titleId } = useDialog(opened, onClose);
|
|
208
286
|
if (!opened) return null;
|
|
209
287
|
const style = { width, ...brand ? { "--au-ic": brand } : {} };
|
|
210
288
|
return reactDom.createPortal(
|
|
211
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-drawer-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
289
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-drawer-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
290
|
+
"div",
|
|
291
|
+
{
|
|
292
|
+
ref: panelRef,
|
|
293
|
+
className: "au-drawer-panel",
|
|
294
|
+
style,
|
|
295
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
296
|
+
role: "dialog",
|
|
297
|
+
"aria-modal": "true",
|
|
298
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
299
|
+
tabIndex: -1,
|
|
300
|
+
children: [
|
|
301
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-drawer-head", children: [
|
|
302
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-drawer-title", id: titleId, children: title }),
|
|
303
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "au-modal-x", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, {}) })
|
|
304
|
+
] }),
|
|
305
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-drawer-body", children })
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
) }),
|
|
218
309
|
document.body
|
|
219
310
|
);
|
|
220
311
|
}
|
|
@@ -454,6 +545,8 @@ function AstralMenu({ trigger, items, align = "end", width = 220 }) {
|
|
|
454
545
|
)
|
|
455
546
|
] });
|
|
456
547
|
}
|
|
548
|
+
var DEFAULT_LIMIT = 4;
|
|
549
|
+
var EXIT_MS = 220;
|
|
457
550
|
var toasts = [];
|
|
458
551
|
var listeners = /* @__PURE__ */ new Set();
|
|
459
552
|
var timers = /* @__PURE__ */ new Map();
|
|
@@ -475,7 +568,7 @@ function scheduleAutoClose(t) {
|
|
|
475
568
|
function show(opts) {
|
|
476
569
|
const id = opts.id ?? `toast-${++counter}`;
|
|
477
570
|
const existing = toasts.find((t) => t.id === id);
|
|
478
|
-
const toast = { withCloseButton: true, ...existing, ...opts, id };
|
|
571
|
+
const toast = { withCloseButton: true, ...existing, ...opts, id, exiting: false };
|
|
479
572
|
toasts = existing ? toasts.map((t) => t.id === id ? toast : t) : [...toasts, toast];
|
|
480
573
|
emit();
|
|
481
574
|
scheduleAutoClose(toast);
|
|
@@ -485,7 +578,7 @@ function update(opts) {
|
|
|
485
578
|
if (!opts.id) return show(opts);
|
|
486
579
|
const existing = toasts.find((t) => t.id === opts.id);
|
|
487
580
|
if (!existing) return show(opts);
|
|
488
|
-
const toast = { ...existing, ...opts, id: opts.id };
|
|
581
|
+
const toast = { ...existing, ...opts, id: opts.id, exiting: false };
|
|
489
582
|
toasts = toasts.map((t) => t.id === opts.id ? toast : t);
|
|
490
583
|
emit();
|
|
491
584
|
scheduleAutoClose(toast);
|
|
@@ -493,10 +586,16 @@ function update(opts) {
|
|
|
493
586
|
}
|
|
494
587
|
function hide(id) {
|
|
495
588
|
const t = toasts.find((x) => x.id === id);
|
|
589
|
+
if (!t || t.exiting) return;
|
|
496
590
|
clearTimer(id);
|
|
497
|
-
toasts = toasts.
|
|
591
|
+
toasts = toasts.map((x) => x.id === id ? { ...x, exiting: true } : x);
|
|
498
592
|
emit();
|
|
499
|
-
|
|
593
|
+
timers.set(id, setTimeout(() => {
|
|
594
|
+
toasts = toasts.filter((x) => x.id !== id);
|
|
595
|
+
timers.delete(id);
|
|
596
|
+
emit();
|
|
597
|
+
t.onClose?.();
|
|
598
|
+
}, EXIT_MS));
|
|
500
599
|
}
|
|
501
600
|
function clean() {
|
|
502
601
|
toasts.forEach((t) => clearTimer(t.id));
|
|
@@ -524,20 +623,29 @@ function Spinner() {
|
|
|
524
623
|
function ToastCard({ t }) {
|
|
525
624
|
const accent = t.color ? `var(--astral-color-${t.color}-6)` : "var(--astral-color-default-border)";
|
|
526
625
|
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(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
t.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
626
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
627
|
+
"div",
|
|
628
|
+
{
|
|
629
|
+
className: t.exiting ? "au-toast au-toast-out" : "au-toast",
|
|
630
|
+
style: { ["--au-toast-accent"]: accent },
|
|
631
|
+
role: "status",
|
|
632
|
+
children: [
|
|
633
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-toast-lead", children: lead }),
|
|
634
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-toast-body", children: [
|
|
635
|
+
t.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-toast-title", children: t.title }),
|
|
636
|
+
t.message != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-toast-msg", children: t.message })
|
|
637
|
+
] }),
|
|
638
|
+
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 }) })
|
|
639
|
+
]
|
|
640
|
+
}
|
|
641
|
+
);
|
|
535
642
|
}
|
|
536
|
-
function AstralToaster() {
|
|
643
|
+
function AstralToaster({ limit = DEFAULT_LIMIT } = {}) {
|
|
537
644
|
const items = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
538
645
|
if (typeof document === "undefined") return null;
|
|
646
|
+
const visible = limit > 0 ? items.filter((t, i) => t.exiting || i >= items.length - limit) : items;
|
|
539
647
|
return reactDom.createPortal(
|
|
540
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-toast-host", role: "region", "aria-live": "polite", "aria-label": "Notifications", children:
|
|
648
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-toast-host", role: "region", "aria-live": "polite", "aria-label": "Notifications", children: visible.map((t) => /* @__PURE__ */ jsxRuntime.jsx(ToastCard, { t }, t.id)) }),
|
|
541
649
|
document.body
|
|
542
650
|
);
|
|
543
651
|
}
|