@astralui/core 0.1.10 → 0.1.12

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 CHANGED
@@ -167,48 +167,139 @@ function AstralThemeProvider({ colors, children }) {
167
167
  children
168
168
  ] });
169
169
  }
170
- function useDismiss(opened, onClose) {
170
+ var dialogSeq = 0;
171
+ var dialogStack = [];
172
+ var FOCUSABLE = [
173
+ "a[href]",
174
+ "area[href]",
175
+ "button:not([disabled])",
176
+ 'input:not([disabled]):not([type="hidden"])',
177
+ "select:not([disabled])",
178
+ "textarea:not([disabled])",
179
+ "iframe",
180
+ "object",
181
+ "embed",
182
+ "[contenteditable]",
183
+ '[tabindex]:not([tabindex="-1"])'
184
+ ].join(",");
185
+ function useDialog(opened, onClose) {
186
+ const panelRef = useRef(null);
187
+ const idRef = useRef("");
188
+ if (!idRef.current) idRef.current = "au-dialog-title-" + String(++dialogSeq);
189
+ const onCloseRef = useRef(onClose);
190
+ onCloseRef.current = onClose;
171
191
  useEffect(() => {
172
192
  if (!opened) return;
193
+ const panel = panelRef.current;
194
+ if (!panel) return;
195
+ const restoreTo = document.activeElement;
196
+ dialogStack.push(panel);
197
+ const isTop = () => dialogStack[dialogStack.length - 1] === panel;
198
+ const focusables = () => Array.from(panel.querySelectorAll(FOCUSABLE)).filter(
199
+ (el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement
200
+ );
201
+ const raf = requestAnimationFrame(() => {
202
+ if (panel.contains(document.activeElement)) return;
203
+ const list = focusables();
204
+ const target = list.find((el) => !el.classList.contains("au-modal-x")) ?? list[0] ?? panel;
205
+ target.focus();
206
+ });
173
207
  const onKey = (e) => {
174
- if (e.key === "Escape") onClose();
208
+ if (!isTop()) return;
209
+ if (e.key === "Escape") {
210
+ onCloseRef.current();
211
+ return;
212
+ }
213
+ if (e.key !== "Tab") return;
214
+ const list = focusables();
215
+ if (!list.length) {
216
+ e.preventDefault();
217
+ panel.focus();
218
+ return;
219
+ }
220
+ const first = list[0];
221
+ const last = list[list.length - 1];
222
+ const active = document.activeElement;
223
+ if (!panel.contains(active)) {
224
+ e.preventDefault();
225
+ (e.shiftKey ? last : first).focus();
226
+ return;
227
+ }
228
+ if (e.shiftKey && active === first) {
229
+ e.preventDefault();
230
+ last.focus();
231
+ } else if (!e.shiftKey && active === last) {
232
+ e.preventDefault();
233
+ first.focus();
234
+ }
175
235
  };
176
- document.addEventListener("keydown", onKey);
177
- const prev = document.body.style.overflow;
236
+ document.addEventListener("keydown", onKey, true);
237
+ const prevOverflow = document.body.style.overflow;
178
238
  document.body.style.overflow = "hidden";
179
239
  return () => {
180
- document.removeEventListener("keydown", onKey);
181
- document.body.style.overflow = prev;
240
+ cancelAnimationFrame(raf);
241
+ document.removeEventListener("keydown", onKey, true);
242
+ const at = dialogStack.indexOf(panel);
243
+ if (at !== -1) dialogStack.splice(at, 1);
244
+ document.body.style.overflow = prevOverflow;
245
+ if (restoreTo && document.contains(restoreTo)) restoreTo.focus();
182
246
  };
183
- }, [opened, onClose]);
247
+ }, [opened]);
248
+ return { panelRef, titleId: idRef.current };
184
249
  }
185
250
  function AstralModal({ opened, onClose, title, brand, width = 460, zIndex, children }) {
186
- useDismiss(opened, onClose);
251
+ const { panelRef, titleId } = useDialog(opened, onClose);
187
252
  if (!opened) return null;
188
253
  const style = { maxWidth: width, ...brand ? { "--au-ic": brand } : {} };
189
254
  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
- ] }) }),
255
+ /* @__PURE__ */ jsx("div", { className: "au-modal-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxs(
256
+ "div",
257
+ {
258
+ ref: panelRef,
259
+ className: "au-modal-panel",
260
+ style,
261
+ onMouseDown: (e) => e.stopPropagation(),
262
+ role: "dialog",
263
+ "aria-modal": "true",
264
+ "aria-labelledby": title ? titleId : void 0,
265
+ tabIndex: -1,
266
+ children: [
267
+ /* @__PURE__ */ jsxs("div", { className: "au-modal-head", children: [
268
+ /* @__PURE__ */ jsx("span", { className: "au-modal-title", id: titleId, children: title }),
269
+ /* @__PURE__ */ jsx("button", { type: "button", className: "au-modal-x", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(IconX, {}) })
270
+ ] }),
271
+ /* @__PURE__ */ jsx("div", { className: "au-modal-body", children })
272
+ ]
273
+ }
274
+ ) }),
197
275
  document.body
198
276
  );
199
277
  }
200
278
  function AstralDrawer({ opened, onClose, title, brand, width = 440, zIndex, children }) {
201
- useDismiss(opened, onClose);
279
+ const { panelRef, titleId } = useDialog(opened, onClose);
202
280
  if (!opened) return null;
203
281
  const style = { width, ...brand ? { "--au-ic": brand } : {} };
204
282
  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
- ] }) }),
283
+ /* @__PURE__ */ jsx("div", { className: "au-drawer-overlay", onMouseDown: onClose, style: zIndex != null ? { zIndex } : void 0, children: /* @__PURE__ */ jsxs(
284
+ "div",
285
+ {
286
+ ref: panelRef,
287
+ className: "au-drawer-panel",
288
+ style,
289
+ onMouseDown: (e) => e.stopPropagation(),
290
+ role: "dialog",
291
+ "aria-modal": "true",
292
+ "aria-labelledby": title ? titleId : void 0,
293
+ tabIndex: -1,
294
+ children: [
295
+ /* @__PURE__ */ jsxs("div", { className: "au-drawer-head", children: [
296
+ /* @__PURE__ */ jsx("span", { className: "au-drawer-title", id: titleId, children: title }),
297
+ /* @__PURE__ */ jsx("button", { type: "button", className: "au-modal-x", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(IconX, {}) })
298
+ ] }),
299
+ /* @__PURE__ */ jsx("div", { className: "au-drawer-body", children })
300
+ ]
301
+ }
302
+ ) }),
212
303
  document.body
213
304
  );
214
305
  }
@@ -912,6 +1003,17 @@ var COLOR_MAP = {
912
1003
  replied: "#8b5cf6",
913
1004
  success: "#16a34a"
914
1005
  };
1006
+ var FG = {
1007
+ "#eab308": ["#854d0e", "#fde047"],
1008
+ "#06b6d4": ["#155e75", "#67e8f9"],
1009
+ "#EF8E2E": ["#9a4a06", "#fdba74"],
1010
+ "#9ca3af": ["#4b5563", "#d1d5db"],
1011
+ "#16a34a": ["#14532d", "#4ade80"],
1012
+ "#3b82f6": ["#1e40af", "#93c5fd"],
1013
+ "#ef4444": ["#991b1b", "#fca5a5"],
1014
+ "#8b5cf6": ["#5b21b6", "#c4b5fd"]
1015
+ };
1016
+ var SOLID = { "#16a34a": "#166534" };
915
1017
  var FILLED = /* @__PURE__ */ new Set(["running", "active", "online"]);
916
1018
  function StatusBadge({ status, label, color, filled }) {
917
1019
  const c = color ?? COLOR_MAP[status] ?? "#9ca3af";
@@ -921,7 +1023,11 @@ function StatusBadge({ status, label, color, filled }) {
921
1023
  "span",
922
1024
  {
923
1025
  className: `au-sbadge${isFilled ? " filled" : ""}`,
924
- style: { "--au-sb": c },
1026
+ style: {
1027
+ "--au-sb": c,
1028
+ ...FG[c] ? { "--au-sb-fg": `light-dark(${FG[c][0]}, ${FG[c][1]})` } : {},
1029
+ ...SOLID[c] ? { "--au-sb-solid": SOLID[c] } : {}
1030
+ },
925
1031
  title: typeof text === "string" ? text : void 0,
926
1032
  children: [
927
1033
  /* @__PURE__ */ jsx("span", { className: "au-sbadge-dot" }),