@tensor_1/react 2.9.0 → 2.10.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.
Files changed (66) hide show
  1. package/dist/chart.cjs +12 -3
  2. package/dist/components/Alert.js +15 -7
  3. package/dist/components/AppShell.d.ts +3 -1
  4. package/dist/components/AppShell.js +3 -1
  5. package/dist/components/Avatar.js +2 -1
  6. package/dist/components/Button.js +32 -16
  7. package/dist/components/Card.d.ts +3 -1
  8. package/dist/components/Card.js +2 -2
  9. package/dist/components/Chart.js +12 -3
  10. package/dist/components/CommandPalette.d.ts +4 -2
  11. package/dist/components/CommandPalette.js +66 -13
  12. package/dist/components/Comments.d.ts +5 -1
  13. package/dist/components/Comments.js +1 -1
  14. package/dist/components/Composer.d.ts +8 -1
  15. package/dist/components/Composer.js +7 -2
  16. package/dist/components/DetailLayout.js +1 -1
  17. package/dist/components/Drawer.d.ts +3 -4
  18. package/dist/components/Drawer.js +6 -4
  19. package/dist/components/Field.js +6 -3
  20. package/dist/components/Inbox.d.ts +13 -3
  21. package/dist/components/Inbox.js +61 -3
  22. package/dist/components/Input.d.ts +13 -2
  23. package/dist/components/Input.js +20 -5
  24. package/dist/components/Kanban.d.ts +15 -3
  25. package/dist/components/Kanban.js +42 -10
  26. package/dist/components/Meter.d.ts +3 -1
  27. package/dist/components/Meter.js +9 -1
  28. package/dist/components/Modal.d.ts +3 -5
  29. package/dist/components/Modal.js +6 -4
  30. package/dist/components/NavFavourites.js +11 -0
  31. package/dist/components/PageHeader.d.ts +3 -1
  32. package/dist/components/PageHeader.js +2 -2
  33. package/dist/components/Peek.d.ts +3 -4
  34. package/dist/components/Peek.js +9 -6
  35. package/dist/components/Progress.d.ts +3 -1
  36. package/dist/components/Progress.js +5 -3
  37. package/dist/components/Radio.d.ts +3 -1
  38. package/dist/components/Radio.js +6 -2
  39. package/dist/components/Sidebar.js +4 -2
  40. package/dist/components/Spinner.d.ts +3 -1
  41. package/dist/components/Spinner.js +3 -3
  42. package/dist/components/Stat.js +5 -1
  43. package/dist/components/States.d.ts +3 -1
  44. package/dist/components/States.js +8 -2
  45. package/dist/components/Table.d.ts +9 -2
  46. package/dist/components/Table.js +10 -6
  47. package/dist/components/Tabs.d.ts +13 -1
  48. package/dist/components/Tabs.js +78 -3
  49. package/dist/components/Timeline.d.ts +3 -3
  50. package/dist/components/Timeline.js +3 -3
  51. package/dist/components/Toast.js +42 -5
  52. package/dist/components/Tooltip.d.ts +3 -1
  53. package/dist/components/Tooltip.js +44 -5
  54. package/dist/components/Tree.d.ts +2 -0
  55. package/dist/components/Tree.js +26 -4
  56. package/dist/hooks/useDialog.d.ts +1 -1
  57. package/dist/hooks/useDialog.js +17 -2
  58. package/dist/hooks/useTable.d.ts +5 -0
  59. package/dist/hooks/useTable.js +20 -1
  60. package/dist/index.cjs +616 -162
  61. package/dist/index.d.cts +127 -37
  62. package/dist/index.d.ts +2 -2
  63. package/dist/index.js +2 -1
  64. package/dist/tree-model.d.ts +4 -0
  65. package/dist/tree-model.js +8 -0
  66. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -103,6 +103,7 @@ __export(src_exports, {
103
103
  SubNavItem: () => SubNavItem,
104
104
  Switch: () => Switch,
105
105
  Tab: () => Tab,
106
+ TabPanel: () => TabPanel,
106
107
  Table: () => Table,
107
108
  TableWrap: () => TableWrap,
108
109
  Tabs: () => Tabs,
@@ -181,8 +182,10 @@ function unlockBodyScroll() {
181
182
  saved = null;
182
183
  }
183
184
  }
184
- function useDialog(open) {
185
+ function useDialog(open, onClose) {
185
186
  const ref = (0, import_react.useRef)(null);
187
+ const onCloseRef = (0, import_react.useRef)(onClose);
188
+ onCloseRef.current = onClose;
186
189
  (0, import_react.useEffect)(() => {
187
190
  const el = ref.current;
188
191
  if (!el || !open) {
@@ -191,8 +194,21 @@ function useDialog(open) {
191
194
  }
192
195
  if (!el.open) el.showModal();
193
196
  lockBodyScroll();
197
+ let locked = true;
198
+ const handleClose = () => {
199
+ if (locked) {
200
+ locked = false;
201
+ unlockBodyScroll();
202
+ }
203
+ onCloseRef.current?.();
204
+ };
205
+ el.addEventListener("close", handleClose);
194
206
  return () => {
195
- unlockBodyScroll();
207
+ el.removeEventListener("close", handleClose);
208
+ if (locked) {
209
+ locked = false;
210
+ unlockBodyScroll();
211
+ }
196
212
  if (el.open) el.close();
197
213
  };
198
214
  }, [open]);
@@ -459,6 +475,24 @@ function useTable(data, options = {}) {
459
475
  const toggleAllRows = (0, import_react2.useCallback)(() => setSelected((s) => togglePage(s, pageIds)), [pageIds]);
460
476
  const clearSelection = (0, import_react2.useCallback)(() => setSelected(/* @__PURE__ */ new Set()), []);
461
477
  const togglePin = (0, import_react2.useCallback)((id) => setPinned((s) => toggleId(s, id)), []);
478
+ const [announcement, setAnnouncement] = (0, import_react2.useState)("");
479
+ const mounted = (0, import_react2.useRef)(false);
480
+ (0, import_react2.useEffect)(() => {
481
+ if (!mounted.current) return;
482
+ const key = sort[0];
483
+ setAnnouncement(key ? `Sorted by ${key.field}, ${key.dir}` : "Sort cleared");
484
+ }, [sort]);
485
+ (0, import_react2.useEffect)(() => {
486
+ if (!mounted.current) return;
487
+ setAnnouncement(`${rows.length} result${rows.length === 1 ? "" : "s"}`);
488
+ }, [filters, query]);
489
+ (0, import_react2.useEffect)(() => {
490
+ if (!mounted.current) return;
491
+ setAnnouncement(`${selected.size} selected`);
492
+ }, [selected]);
493
+ (0, import_react2.useEffect)(() => {
494
+ mounted.current = true;
495
+ }, []);
462
496
  const view = (0, import_react2.useMemo)(() => ({ sort, filters, q: query }), [sort, filters, query]);
463
497
  const viewQueryString = (0, import_react2.useMemo)(() => serializeView(view), [view]);
464
498
  const applyView = (0, import_react2.useCallback)((queryString) => {
@@ -475,6 +509,7 @@ function useTable(data, options = {}) {
475
509
  selected,
476
510
  pinned,
477
511
  selectAll,
512
+ announcement,
478
513
  toggleSort: doToggleSort,
479
514
  setSort,
480
515
  setFilters,
@@ -597,24 +632,40 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
597
632
  // src/components/Button.tsx
598
633
  var import_react4 = require("react");
599
634
  var import_jsx_runtime = require("react/jsx-runtime");
635
+ var warnedIconButton = false;
600
636
  var Button = (0, import_react4.forwardRef)(
601
- ({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
602
- "button",
603
- {
604
- ref,
605
- className: cx(
606
- "uix-btn",
607
- `uix-btn--${variant}`,
608
- size !== "md" && `uix-btn--${size}`,
609
- icon && "uix-btn--icon",
610
- className
611
- ),
612
- disabled: disabled || loading,
613
- "data-loading": loading || void 0,
614
- ...props,
615
- children
637
+ ({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, onClick, ...props }, ref) => {
638
+ if (!warnedIconButton && icon && children == null && !props["aria-label"] && !props["aria-labelledby"] && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
639
+ warnedIconButton = true;
640
+ console.warn("uix: icon-only <Button> has no accessible name \u2014 pass aria-label or aria-labelledby.");
616
641
  }
617
- )
642
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
643
+ "button",
644
+ {
645
+ ref,
646
+ className: cx(
647
+ "uix-btn",
648
+ `uix-btn--${variant}`,
649
+ size !== "md" && `uix-btn--${size}`,
650
+ icon && "uix-btn--icon",
651
+ className
652
+ ),
653
+ disabled,
654
+ "aria-disabled": loading || void 0,
655
+ "aria-busy": loading || void 0,
656
+ "data-loading": loading || void 0,
657
+ onClick: loading || onClick ? (e) => {
658
+ if (loading) {
659
+ e.preventDefault();
660
+ return;
661
+ }
662
+ onClick?.(e);
663
+ } : void 0,
664
+ ...props,
665
+ children
666
+ }
667
+ );
668
+ }
618
669
  );
619
670
  Button.displayName = "Button";
620
671
  function ButtonGroup({ children, className }) {
@@ -637,7 +688,22 @@ var Input = (0, import_react5.forwardRef)(
637
688
  )
638
689
  );
639
690
  Input.displayName = "Input";
640
- function InputGroup({ children, leadingIcon, trailingIcon, className }) {
691
+ function InputGroup({
692
+ children,
693
+ leadingIcon,
694
+ trailingIcon,
695
+ className,
696
+ id,
697
+ "aria-describedby": describedBy,
698
+ "aria-invalid": invalid,
699
+ "aria-required": required
700
+ }) {
701
+ const control = (0, import_react5.isValidElement)(children) && (id ?? describedBy ?? invalid ?? required) != null ? (0, import_react5.cloneElement)(children, {
702
+ id: children.props.id ?? id,
703
+ "aria-describedby": children.props["aria-describedby"] ?? describedBy,
704
+ "aria-invalid": children.props["aria-invalid"] ?? invalid,
705
+ "aria-required": children.props["aria-required"] ?? required
706
+ }) : children;
641
707
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
642
708
  "div",
643
709
  {
@@ -648,9 +714,9 @@ function InputGroup({ children, leadingIcon, trailingIcon, className }) {
648
714
  className
649
715
  ),
650
716
  children: [
651
- leadingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon", children: leadingIcon }),
652
- children,
653
- trailingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", children: trailingIcon })
717
+ leadingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon", "aria-hidden": "true", children: leadingIcon }),
718
+ control,
719
+ trailingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", "aria-hidden": "true", children: trailingIcon })
654
720
  ]
655
721
  }
656
722
  );
@@ -730,8 +796,12 @@ var Radio = (0, import_react9.forwardRef)(
730
796
  ] })
731
797
  );
732
798
  Radio.displayName = "Radio";
733
- function RadioGroup({ children, className }) {
734
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cx("uix-radio-group", className), children });
799
+ function RadioGroup({ children, className, label }) {
800
+ if (label == null) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cx("uix-radio-group", className), children });
801
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("fieldset", { className: cx("uix-radio-group", className), children: [
802
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { className: "uix-radio-group__legend", children: label }),
803
+ children
804
+ ] });
735
805
  }
736
806
 
737
807
  // src/components/Switch.tsx
@@ -753,25 +823,30 @@ function Field({ label, hint, error, success, required, children, className, htm
753
823
  const uid = (0, import_react11.useId)();
754
824
  const msgId = `${uid}-msg`;
755
825
  const describedBy = error || success || hint ? msgId : void 0;
826
+ const controlId = (0, import_react11.isValidElement)(children) ? children.props.id ?? `${uid}-ctl` : void 0;
756
827
  const control = (0, import_react11.isValidElement)(children) ? (0, import_react11.cloneElement)(children, {
828
+ id: controlId,
757
829
  "aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0,
758
- "aria-invalid": error ? true : children.props["aria-invalid"]
830
+ "aria-invalid": error ? true : children.props["aria-invalid"],
831
+ "aria-required": children.props["aria-required"] ?? (required || void 0)
759
832
  }) : children;
760
833
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cx("uix-field", className), children: [
761
- label && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { className: "uix-field__label", htmlFor, "data-required": required || void 0, children: label }),
834
+ label && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { className: "uix-field__label", htmlFor: htmlFor ?? controlId, "data-required": required || void 0, children: label }),
762
835
  control,
763
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "uix-field__msg", id: msgId, children: error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__error", role: "alert", children: error }) : success ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__success", children: success }) : hint ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__hint", children: hint }) : null })
836
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "uix-field__msg", id: msgId, children: error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__error", role: "alert", children: error }) : success ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__success", role: "status", children: success }) : hint ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__hint", children: hint }) : null })
764
837
  ] });
765
838
  }
766
839
 
767
840
  // src/components/Modal.tsx
841
+ var import_react12 = require("react");
768
842
  var import_jsx_runtime9 = require("react/jsx-runtime");
769
843
  var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" }) });
770
- function Modal({ open, onClose, title, children, footer, className, style }) {
771
- const ref = useDialog(open);
772
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("dialog", { ref, className: cx("uix-dialog", className), style, onClose, children: [
844
+ function Modal({ open, onClose, title, children, footer, className, ...rest }) {
845
+ const ref = useDialog(open, onClose);
846
+ const titleId = (0, import_react12.useId)();
847
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("dialog", { ref, className: cx("uix-dialog", className), "aria-labelledby": title ? titleId : void 0, ...rest, children: [
773
848
  (title != null || onClose) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "uix-dialog__header", children: [
774
- title && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "uix-dialog__title", children: title }),
849
+ title && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "uix-dialog__title", id: titleId, children: title }),
775
850
  onClose && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { className: "uix-dialog__close", onClick: onClose, "aria-label": "Close dialog", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CloseIcon, {}) })
776
851
  ] }),
777
852
  children != null && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "uix-dialog__body", children }),
@@ -780,13 +855,15 @@ function Modal({ open, onClose, title, children, footer, className, style }) {
780
855
  }
781
856
 
782
857
  // src/components/Drawer.tsx
858
+ var import_react13 = require("react");
783
859
  var import_jsx_runtime10 = require("react/jsx-runtime");
784
860
  var CloseIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" }) });
785
- function Drawer({ open, onClose, title, children, footer, className }) {
786
- const ref = useDialog(open);
787
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("dialog", { ref, className: cx("uix-drawer", className), onClose, children: [
861
+ function Drawer({ open, onClose, title, children, footer, className, ...rest }) {
862
+ const ref = useDialog(open, onClose);
863
+ const titleId = (0, import_react13.useId)();
864
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("dialog", { ref, className: cx("uix-drawer", className), "aria-labelledby": title ? titleId : void 0, ...rest, children: [
788
865
  (title != null || onClose) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "uix-drawer__header", children: [
789
- title && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { fontWeight: 600, fontSize: "var(--uix-text-h3)" }, children: title }),
866
+ title && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { id: titleId, style: { fontWeight: 600, fontSize: "var(--uix-text-h3)", fontFamily: "inherit", letterSpacing: "inherit", lineHeight: "inherit" }, children: title }),
790
867
  onClose && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
791
868
  "button",
792
869
  {
@@ -803,27 +880,30 @@ function Drawer({ open, onClose, title, children, footer, className }) {
803
880
  }
804
881
 
805
882
  // src/components/Peek.tsx
883
+ var import_react14 = require("react");
806
884
  var import_jsx_runtime11 = require("react/jsx-runtime");
807
885
  var CloseIcon3 = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" }) });
808
886
  var ChevronUp = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M2 8l4-4 4 4" }) });
809
887
  var ChevronDown = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M2 4l4 4 4-4" }) });
810
- function Peek({ open, onClose, title, children, footer, onNavPrev, onNavNext, hint, className }) {
811
- const ref = useDialog(open);
888
+ function Peek({ open, onClose, title, children, footer, onNavPrev, onNavNext, hint, className, onClick, ...rest }) {
889
+ const ref = useDialog(open, onClose);
812
890
  const hasNav = onNavPrev != null || onNavNext != null;
891
+ const titleId = (0, import_react14.useId)();
813
892
  const onBackdropClick = (e) => {
893
+ onClick?.(e);
814
894
  if (!onClose || e.target !== e.currentTarget) return;
815
895
  const r = e.currentTarget.getBoundingClientRect();
816
896
  if (r.width === 0) return;
817
897
  const outside = e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
818
898
  if (outside) onClose();
819
899
  };
820
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("dialog", { ref, className: cx("uix-peek", className), onClose, onClick: onBackdropClick, children: [
900
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("dialog", { ref, className: cx("uix-peek", className), "aria-labelledby": title ? titleId : void 0, ...rest, onClick: onBackdropClick, children: [
821
901
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "uix-peek__header", children: [
822
902
  hasNav && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "uix-peek__nav", children: [
823
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { className: "uix-peek__navbtn", onClick: onNavPrev, disabled: !onNavPrev, "aria-label": "Previous record", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChevronUp, {}) }),
824
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { className: "uix-peek__navbtn", onClick: onNavNext, disabled: !onNavNext, "aria-label": "Next record", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChevronDown, {}) })
903
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { className: "uix-peek__navbtn", onClick: () => onNavPrev?.(), "aria-disabled": onNavPrev ? void 0 : true, "aria-label": "Previous record", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChevronUp, {}) }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { className: "uix-peek__navbtn", onClick: () => onNavNext?.(), "aria-disabled": onNavNext ? void 0 : true, "aria-label": "Next record", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChevronDown, {}) })
825
905
  ] }),
826
- title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "uix-peek__title", children: title }),
906
+ title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "uix-peek__title", id: titleId, children: title }),
827
907
  hint && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "uix-peek__hint", children: hint }),
828
908
  onClose && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { className: "uix-peek__close", onClick: onClose, "aria-label": "Close preview", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CloseIcon3, {}) })
829
909
  ] }),
@@ -834,12 +914,12 @@ function Peek({ open, onClose, title, children, footer, onNavPrev, onNavNext, hi
834
914
 
835
915
  // src/components/Card.tsx
836
916
  var import_jsx_runtime12 = require("react/jsx-runtime");
837
- function Card({ title, subtitle, headerAction, footer, children, className, ...props }) {
917
+ function Card({ title, titleAs: TitleTag = "div", subtitle, headerAction, footer, children, className, ...props }) {
838
918
  const hasHeader = title != null || subtitle != null || headerAction != null;
839
919
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: cx("uix-card", className), ...props, children: [
840
920
  hasHeader && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "uix-card__header", children: [
841
921
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { flex: 1 }, children: [
842
- title && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "uix-card__title", children: title }),
922
+ title && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TitleTag, { className: "uix-card__title", children: title }),
843
923
  subtitle && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "uix-card__subtitle", children: subtitle })
844
924
  ] }),
845
925
  headerAction
@@ -851,11 +931,11 @@ function Card({ title, subtitle, headerAction, footer, children, className, ...p
851
931
 
852
932
  // src/components/PageHeader.tsx
853
933
  var import_jsx_runtime13 = require("react/jsx-runtime");
854
- function PageHeader({ eyebrow, title, subtitle, actions, className, ...props }) {
934
+ function PageHeader({ eyebrow, title, subtitle, actions, as: TitleTag = "h1", className, ...props }) {
855
935
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("header", { className: cx("uix-page-header", className), ...props, children: [
856
936
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "uix-page-header__titles", children: [
857
937
  eyebrow != null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "uix-page-header__eyebrow", children: eyebrow }),
858
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h1", { className: "uix-page-header__title", children: title }),
938
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TitleTag, { className: "uix-page-header__title", children: title }),
859
939
  subtitle != null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "uix-page-header__subtitle", children: subtitle })
860
940
  ] }),
861
941
  actions != null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "uix-page-header__actions", children: actions })
@@ -867,7 +947,7 @@ var import_jsx_runtime14 = require("react/jsx-runtime");
867
947
  function DetailLayout({ side, children, className, ...props }) {
868
948
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: cx("uix-detail", className), ...props, children: [
869
949
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "uix-detail__main", children }),
870
- side != null && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "uix-detail__side", children: side })
950
+ side != null && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("aside", { className: "uix-detail__side", children: side })
871
951
  ] });
872
952
  }
873
953
 
@@ -890,11 +970,11 @@ function ListItem({ title, meta, trail, leading, children, className, ...props }
890
970
  }
891
971
 
892
972
  // src/components/DescriptionList.tsx
893
- var import_react12 = require("react");
973
+ var import_react15 = require("react");
894
974
  var import_jsx_runtime16 = require("react/jsx-runtime");
895
975
  function DescriptionList({ items, children, className, ...props }) {
896
976
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("dl", { className: cx("uix-dl", className), ...props, children: [
897
- items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react12.Fragment, { children: [
977
+ items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react15.Fragment, { children: [
898
978
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dt", { children: item.term }),
899
979
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dd", { children: item.description })
900
980
  ] }, item.id ?? i)),
@@ -909,21 +989,80 @@ function DescriptionItem({ term, children }) {
909
989
  }
910
990
 
911
991
  // src/components/Tabs.tsx
912
- var import_react13 = require("react");
992
+ var import_react16 = require("react");
913
993
  var import_jsx_runtime17 = require("react/jsx-runtime");
914
- var TabsCtx = (0, import_react13.createContext)({});
994
+ var TabsCtx = (0, import_react16.createContext)({});
915
995
  function Tabs({ variant = "line", value, onChange, children, className }) {
916
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TabsCtx.Provider, { value: { value, onChange }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
996
+ const baseId = (0, import_react16.useId)();
997
+ const listRef = (0, import_react16.useRef)(null);
998
+ const kids = import_react16.Children.toArray(children);
999
+ const panels = kids.filter((k) => (0, import_react16.isValidElement)(k) && k.type === TabPanel);
1000
+ const tabs = kids.filter((k) => !((0, import_react16.isValidElement)(k) && k.type === TabPanel));
1001
+ (0, import_react16.useEffect)(() => {
1002
+ const list = listRef.current;
1003
+ if (!list) return;
1004
+ const all = Array.from(list.querySelectorAll('[role="tab"]'));
1005
+ const enabled = all.filter((t) => !t.matches(":disabled"));
1006
+ const active = enabled.find((t) => t.getAttribute("aria-selected") === "true") ?? enabled[0];
1007
+ for (const t of all) t.tabIndex = t === active ? 0 : -1;
1008
+ });
1009
+ const onKeyDown = (e) => {
1010
+ const items = Array.from(
1011
+ listRef.current?.querySelectorAll('[role="tab"]:not(:disabled)') ?? []
1012
+ );
1013
+ const cur = e.target.closest('[role="tab"]');
1014
+ const idx = cur ? items.indexOf(cur) : -1;
1015
+ if (idx < 0 || items.length === 0) return;
1016
+ let next;
1017
+ switch (e.key) {
1018
+ case "ArrowRight":
1019
+ next = (idx + 1) % items.length;
1020
+ break;
1021
+ case "ArrowLeft":
1022
+ next = (idx - 1 + items.length) % items.length;
1023
+ break;
1024
+ case "Home":
1025
+ next = 0;
1026
+ break;
1027
+ case "End":
1028
+ next = items.length - 1;
1029
+ break;
1030
+ default:
1031
+ return;
1032
+ }
1033
+ e.preventDefault();
1034
+ const target = items[next];
1035
+ if (target) {
1036
+ target.focus();
1037
+ target.click();
1038
+ }
1039
+ };
1040
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(TabsCtx.Provider, { value: { value, onChange, baseId }, children: [
1041
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1042
+ "div",
1043
+ {
1044
+ ref: listRef,
1045
+ role: "tablist",
1046
+ className: cx("uix-tabs", `uix-tabs--${variant}`, className),
1047
+ onKeyDown,
1048
+ children: tabs
1049
+ }
1050
+ ),
1051
+ panels
1052
+ ] });
917
1053
  }
918
1054
  function Tab({ value, children, disabled, className, onClick, ...props }) {
919
- const ctx = (0, import_react13.useContext)(TabsCtx);
1055
+ const ctx = (0, import_react16.useContext)(TabsCtx);
920
1056
  const selected = ctx.value === value;
921
1057
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
922
1058
  "button",
923
1059
  {
924
1060
  role: "tab",
1061
+ id: ctx.baseId ? `${ctx.baseId}-tab-${value}` : void 0,
1062
+ "aria-controls": ctx.baseId ? `${ctx.baseId}-panel-${value}` : void 0,
925
1063
  className: cx("uix-tab", className),
926
1064
  "aria-selected": selected,
1065
+ tabIndex: selected ? 0 : -1,
927
1066
  disabled,
928
1067
  onClick: (e) => {
929
1068
  ctx.onChange?.(value);
@@ -934,9 +1073,24 @@ function Tab({ value, children, disabled, className, onClick, ...props }) {
934
1073
  }
935
1074
  );
936
1075
  }
1076
+ function TabPanel({ value, children, ...props }) {
1077
+ const ctx = (0, import_react16.useContext)(TabsCtx);
1078
+ if (ctx.value !== value) return null;
1079
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1080
+ "div",
1081
+ {
1082
+ role: "tabpanel",
1083
+ id: ctx.baseId ? `${ctx.baseId}-panel-${value}` : void 0,
1084
+ "aria-labelledby": ctx.baseId ? `${ctx.baseId}-tab-${value}` : void 0,
1085
+ tabIndex: 0,
1086
+ ...props,
1087
+ children
1088
+ }
1089
+ );
1090
+ }
937
1091
 
938
1092
  // src/components/AppShell.tsx
939
- var import_react14 = require("react");
1093
+ var import_react17 = require("react");
940
1094
  var import_jsx_runtime18 = require("react/jsx-runtime");
941
1095
  function AppShell({
942
1096
  nav,
@@ -944,6 +1098,7 @@ function AppShell({
944
1098
  focus,
945
1099
  onExitFocus,
946
1100
  mainBleed,
1101
+ mainId = "uix-main",
947
1102
  sidebar,
948
1103
  topbar,
949
1104
  children,
@@ -951,7 +1106,7 @@ function AppShell({
951
1106
  ...props
952
1107
  }) {
953
1108
  const navState = nav ?? (collapsed ? "rail" : "full");
954
- (0, import_react14.useEffect)(() => {
1109
+ (0, import_react17.useEffect)(() => {
955
1110
  if (!focus || !onExitFocus) return;
956
1111
  const onKey = (e) => {
957
1112
  if (e.key === "Escape") onExitFocus();
@@ -968,9 +1123,10 @@ function AppShell({
968
1123
  "data-focus": focus || void 0,
969
1124
  ...props,
970
1125
  children: [
1126
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("a", { className: "uix-skiplink", href: `#${mainId}`, children: "Skip to content" }),
971
1127
  sidebar && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uix-shell__sidebar", children: sidebar }),
972
1128
  topbar && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uix-shell__topbar", children: topbar }),
973
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("main", { className: cx("uix-shell__main", mainBleed && "uix-shell__main--bleed"), children }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("main", { id: mainId, tabIndex: -1, className: cx("uix-shell__main", mainBleed && "uix-shell__main--bleed"), children }),
974
1130
  focus && onExitFocus && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uix-shell__focus-exit", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: onExitFocus, children: "Esc \xB7 Exit focus" }) })
975
1131
  ]
976
1132
  }
@@ -978,7 +1134,7 @@ function AppShell({
978
1134
  }
979
1135
 
980
1136
  // src/components/Sidebar.tsx
981
- var import_react15 = require("react");
1137
+ var import_react18 = require("react");
982
1138
  var import_jsx_runtime19 = require("react/jsx-runtime");
983
1139
  var ChevronIcon = () => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { className: "uix-navgroup__chevron", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M2 4l4 4 4-4" }) });
984
1140
  function Sidebar({ collapsed, onToggle, brand, toggleIcon, children, className, ...props }) {
@@ -1032,13 +1188,15 @@ function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...p
1032
1188
  );
1033
1189
  }
1034
1190
  function NavGroup({ icon, label, children, defaultExpanded = true }) {
1035
- const [expanded, setExpanded] = (0, import_react15.useState)(defaultExpanded);
1191
+ const [expanded, setExpanded] = (0, import_react18.useState)(defaultExpanded);
1192
+ const panelId = (0, import_react18.useId)();
1036
1193
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "uix-navgroup", children: [
1037
1194
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1038
1195
  "button",
1039
1196
  {
1040
1197
  className: "uix-navitem uix-navgroup__trigger",
1041
1198
  "aria-expanded": expanded,
1199
+ "aria-controls": panelId,
1042
1200
  onClick: () => setExpanded((v) => !v),
1043
1201
  children: [
1044
1202
  icon && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "uix-navitem__icon", "aria-hidden": "true", children: icon }),
@@ -1047,7 +1205,7 @@ function NavGroup({ icon, label, children, defaultExpanded = true }) {
1047
1205
  ]
1048
1206
  }
1049
1207
  ),
1050
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "uix-navgroup__panel", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { children }) })
1208
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "uix-navgroup__panel", id: panelId, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { children }) })
1051
1209
  ] });
1052
1210
  }
1053
1211
  function SubNavItem({ active, as: Tag = "a", children, className, ...props }) {
@@ -1100,7 +1258,7 @@ function StarButton({
1100
1258
  }
1101
1259
 
1102
1260
  // src/components/NavFavourites.tsx
1103
- var import_react16 = require("react");
1261
+ var import_react19 = require("react");
1104
1262
  var import_jsx_runtime21 = require("react/jsx-runtime");
1105
1263
  var ChevronDownIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1106
1264
  "svg",
@@ -1164,13 +1322,14 @@ function NavFavourites({
1164
1322
  emptyReason,
1165
1323
  className
1166
1324
  }) {
1167
- const regionId = (0, import_react16.useId)();
1168
- const headerRef = (0, import_react16.useRef)(null);
1169
- const regionRef = (0, import_react16.useRef)(null);
1170
- const focusAfterRef = (0, import_react16.useRef)(null);
1171
- const triggerRefs = (0, import_react16.useRef)(/* @__PURE__ */ new Map());
1325
+ const regionId = (0, import_react19.useId)();
1326
+ const headerRef = (0, import_react19.useRef)(null);
1327
+ const regionRef = (0, import_react19.useRef)(null);
1328
+ const [announcement, setAnnouncement] = (0, import_react19.useState)("");
1329
+ const focusAfterRef = (0, import_react19.useRef)(null);
1330
+ const triggerRefs = (0, import_react19.useRef)(/* @__PURE__ */ new Map());
1172
1331
  const order = items.map((i) => i.id);
1173
- (0, import_react16.useEffect)(() => {
1332
+ (0, import_react19.useEffect)(() => {
1174
1333
  const id = focusAfterRef.current;
1175
1334
  if (!id) return;
1176
1335
  focusAfterRef.current = null;
@@ -1196,6 +1355,8 @@ function NavFavourites({
1196
1355
  if (it === void 0) return;
1197
1356
  reordered.splice(next, 0, it);
1198
1357
  focusAfterRef.current = id;
1358
+ const moved = items.find((i) => i.id === id);
1359
+ if (moved) setAnnouncement(`${moved.label} moved to position ${next + 1} of ${order.length}`);
1199
1360
  onReorder(reordered);
1200
1361
  };
1201
1362
  const remove = (id) => {
@@ -1205,6 +1366,7 @@ function NavFavourites({
1205
1366
  onRemove(id);
1206
1367
  };
1207
1368
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cx("uix-favourites", className), style: { marginBottom: "var(--uix-space-1)" }, children: [
1369
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "status", className: "uix-visually-hidden", children: announcement }),
1208
1370
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1209
1371
  "button",
1210
1372
  {
@@ -1281,18 +1443,18 @@ function FavouriteRow({
1281
1443
  onRemove,
1282
1444
  registerTrigger
1283
1445
  }) {
1284
- const [open, setOpen] = (0, import_react16.useState)(false);
1285
- const [revealed, setRevealed] = (0, import_react16.useState)(false);
1286
- const menuId = (0, import_react16.useId)();
1287
- const triggerRef = (0, import_react16.useRef)(null);
1288
- const itemRefs = (0, import_react16.useRef)([]);
1446
+ const [open, setOpen] = (0, import_react19.useState)(false);
1447
+ const [revealed, setRevealed] = (0, import_react19.useState)(false);
1448
+ const menuId = (0, import_react19.useId)();
1449
+ const triggerRef = (0, import_react19.useRef)(null);
1450
+ const itemRefs = (0, import_react19.useRef)([]);
1289
1451
  const optionsTemplate = labels.options ?? "{label} favourite options";
1290
1452
  const actions = [
1291
1453
  ...isFirst ? [] : [{ label: labels.moveUp, onSelect: onMoveUp }],
1292
1454
  ...isLast ? [] : [{ label: labels.moveDown, onSelect: onMoveDown }],
1293
1455
  { label: labels.remove.replace("{label}", item.label), onSelect: onRemove }
1294
1456
  ];
1295
- (0, import_react16.useEffect)(() => {
1457
+ (0, import_react19.useEffect)(() => {
1296
1458
  if (open) itemRefs.current[0]?.focus();
1297
1459
  }, [open]);
1298
1460
  const closeAndReturnFocus = () => {
@@ -1308,6 +1470,12 @@ function FavouriteRow({
1308
1470
  } else if (e.key === "ArrowUp") {
1309
1471
  e.preventDefault();
1310
1472
  menuItems[(currentIdx - 1 + menuItems.length) % menuItems.length]?.focus();
1473
+ } else if (e.key === "Home") {
1474
+ e.preventDefault();
1475
+ menuItems[0]?.focus();
1476
+ } else if (e.key === "End") {
1477
+ e.preventDefault();
1478
+ menuItems[menuItems.length - 1]?.focus();
1311
1479
  } else if (e.key === "Escape") {
1312
1480
  e.preventDefault();
1313
1481
  closeAndReturnFocus();
@@ -1388,6 +1556,7 @@ function FavouriteRow({
1388
1556
  {
1389
1557
  id: menuId,
1390
1558
  role: "menu",
1559
+ "aria-label": optionsTemplate.replace("{label}", item.label),
1391
1560
  onKeyDown: onMenuKeyDown,
1392
1561
  style: {
1393
1562
  position: "absolute",
@@ -1451,60 +1620,107 @@ function FavouriteRow({
1451
1620
  // src/components/Alert.tsx
1452
1621
  var import_jsx_runtime22 = require("react/jsx-runtime");
1453
1622
  function Alert({ tone, title, icon, children, className, ...props }) {
1454
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: cx("uix-alert", tone && tone !== "neutral" && `uix-alert--${tone}`, className), ...props, children: [
1455
- icon && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "uix-alert__icon", children: icon }),
1456
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
1457
- title && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "uix-alert__title", children: title }),
1458
- children && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "uix-alert__body", children })
1459
- ] })
1460
- ] });
1623
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1624
+ "div",
1625
+ {
1626
+ className: cx("uix-alert", tone && tone !== "neutral" && `uix-alert--${tone}`, className),
1627
+ role: tone === "danger" || tone === "warning" ? "alert" : "status",
1628
+ ...props,
1629
+ children: [
1630
+ icon && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "uix-alert__icon", children: icon }),
1631
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { children: [
1632
+ title && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "uix-alert__title", children: title }),
1633
+ children && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "uix-alert__body", children })
1634
+ ] })
1635
+ ]
1636
+ }
1637
+ );
1461
1638
  }
1462
1639
 
1463
1640
  // src/components/Spinner.tsx
1464
1641
  var import_jsx_runtime23 = require("react/jsx-runtime");
1465
- function Spinner({ size = "md", accent, className, ...props }) {
1642
+ function Spinner({ size = "md", accent, label, className, ...props }) {
1466
1643
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1467
1644
  "span",
1468
1645
  {
1469
1646
  className: cx("uix-spinner", accent && "uix-spinner--accent", size === "lg" && "uix-spinner--lg", className),
1470
1647
  role: "status",
1471
- "aria-label": "Loading",
1472
- ...props
1648
+ ...props,
1649
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "uix-visually-hidden", children: label ?? "Loading\u2026" })
1473
1650
  }
1474
1651
  );
1475
1652
  }
1476
1653
 
1477
1654
  // src/components/Toast.tsx
1655
+ var import_react20 = require("react");
1478
1656
  var import_jsx_runtime24 = require("react/jsx-runtime");
1657
+ var ToasterContext = (0, import_react20.createContext)(null);
1479
1658
  function Toast({ title, message, tone, icon, onClose, leaving, className, ...props }) {
1480
1659
  const assertive = tone === "danger";
1660
+ const toaster = (0, import_react20.useContext)(ToasterContext);
1661
+ const bodyRef = (0, import_react20.useRef)(null);
1662
+ (0, import_react20.useEffect)(() => {
1663
+ if (assertive || !toaster) return;
1664
+ const text = bodyRef.current?.textContent?.trim();
1665
+ if (text) toaster.announce(text);
1666
+ }, []);
1667
+ const handleClose = (e) => {
1668
+ const btn = e.currentTarget;
1669
+ if (document.activeElement === btn) {
1670
+ const toasterEl = btn.closest(".uix-toaster");
1671
+ if (toasterEl) {
1672
+ const closes = Array.from(toasterEl.querySelectorAll(".uix-toast__close"));
1673
+ const i = closes.indexOf(btn);
1674
+ (closes[i + 1] ?? closes[i - 1])?.focus();
1675
+ }
1676
+ }
1677
+ onClose?.();
1678
+ };
1679
+ const ownLiveRegion = assertive || !toaster;
1481
1680
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1482
1681
  "div",
1483
1682
  {
1484
1683
  className: cx("uix-toast", tone && `uix-toast--${tone}`, className),
1485
1684
  "data-leaving": leaving || void 0,
1486
- role: assertive ? "alert" : "status",
1487
- "aria-live": assertive ? "assertive" : "polite",
1685
+ role: ownLiveRegion ? assertive ? "alert" : "status" : void 0,
1686
+ "aria-live": ownLiveRegion ? assertive ? "assertive" : "polite" : void 0,
1488
1687
  ...props,
1489
1688
  children: [
1490
1689
  icon && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "uix-toast__icon", "aria-hidden": "true", children: icon }),
1491
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "uix-toast__body", children: [
1690
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "uix-toast__body", ref: bodyRef, children: [
1492
1691
  title && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "uix-toast__title", children: title }),
1493
1692
  message && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "uix-toast__msg", children: message })
1494
1693
  ] }),
1495
- onClose && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { className: "uix-toast__close", onClick: onClose, "aria-label": "Dismiss", children: "\xD7" })
1694
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { className: "uix-toast__close", onClick: handleClose, "aria-label": "Dismiss", children: "\xD7" })
1496
1695
  ]
1497
1696
  }
1498
1697
  );
1499
1698
  }
1500
1699
  function Toaster({ children, className, ...props }) {
1501
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cx("uix-toaster", className), ...props, children });
1700
+ const [announced, setAnnounced] = (0, import_react20.useState)("");
1701
+ const clearTimer = (0, import_react20.useRef)(null);
1702
+ const announce = (0, import_react20.useCallback)((text) => {
1703
+ setAnnounced((cur) => cur ? `${cur} ${text}` : text);
1704
+ if (clearTimer.current != null) clearTimeout(clearTimer.current);
1705
+ clearTimer.current = window.setTimeout(() => setAnnounced(""), 3e3);
1706
+ }, []);
1707
+ (0, import_react20.useEffect)(() => () => {
1708
+ if (clearTimer.current != null) clearTimeout(clearTimer.current);
1709
+ }, []);
1710
+ const ctx = (0, import_react20.useMemo)(() => ({ announce }), [announce]);
1711
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: cx("uix-toaster", className), role: "region", "aria-label": "Notifications", ...props, children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
1713
+ children
1714
+ ] }) });
1502
1715
  }
1503
1716
 
1504
1717
  // src/components/Table.tsx
1505
1718
  var import_jsx_runtime25 = require("react/jsx-runtime");
1506
- function TableWrap({ children, className, ...props }) {
1507
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: cx("uix-table-wrap", className), ...props, children });
1719
+ function TableWrap({ announcement, children, className, ...props }) {
1720
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: cx("uix-table-wrap", className), ...props, children: [
1721
+ announcement != null && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { role: "status", className: "uix-visually-hidden", children: announcement }),
1722
+ children
1723
+ ] });
1508
1724
  }
1509
1725
  function Table({
1510
1726
  density = "standard",
@@ -1543,9 +1759,9 @@ function Th({ sortable, sortDirection, sortOrder, onSort, className, children, o
1543
1759
  "data-sort": sortable || void 0,
1544
1760
  "data-sort-order": sortOrder != null && sortOrder > 0 ? sortOrder : void 0,
1545
1761
  "aria-sort": sortDirection && sortDirection !== "none" ? sortDirection : void 0,
1546
- onClick: sortable ? onSort ?? onClick : onClick,
1762
+ onClick: sortable && onSort ? void 0 : onClick,
1547
1763
  ...props,
1548
- children
1764
+ children: sortable ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("button", { type: "button", className: "uix-th__sortbtn", onClick: onSort, children }) : children
1549
1765
  }
1550
1766
  );
1551
1767
  }
@@ -1557,7 +1773,7 @@ function Tr({ selected, pinned, className, children, ...props }) {
1557
1773
  "tr",
1558
1774
  {
1559
1775
  className,
1560
- "aria-selected": selected || void 0,
1776
+ "data-selected": selected || void 0,
1561
1777
  "data-pinned": pinned || void 0,
1562
1778
  ...props,
1563
1779
  children
@@ -1580,13 +1796,14 @@ function RowActions({ children, className, ...props }) {
1580
1796
  function RowAction({ children, className, type = "button", ...props }) {
1581
1797
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("button", { type, className: cx("uix-rowact", className), ...props, children });
1582
1798
  }
1583
- function ExpandToggle({ expanded, className, type = "button", ...props }) {
1799
+ function ExpandToggle({ expanded, controls, className, type = "button", ...props }) {
1584
1800
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1585
1801
  "button",
1586
1802
  {
1587
1803
  type,
1588
1804
  className: cx("uix-table__expand", className),
1589
1805
  "aria-expanded": expanded,
1806
+ "aria-controls": controls,
1590
1807
  "aria-label": expanded ? "Collapse row" : "Expand row",
1591
1808
  ...props,
1592
1809
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("path", { d: "M6 4l4 4-4 4" }) })
@@ -1677,15 +1894,17 @@ function StatusPill({ tone = "neutral", treatment = "filled", dot, children, cla
1677
1894
 
1678
1895
  // src/components/Progress.tsx
1679
1896
  var import_jsx_runtime28 = require("react/jsx-runtime");
1680
- function Progress({ value = 0, max = 100, indeterminate, className, ...props }) {
1681
- const pct = Math.max(0, Math.min(100, value / (max || 100) * 100));
1897
+ function Progress({ value = 0, max = 100, indeterminate, label, className, ...props }) {
1898
+ const clamped = Math.max(0, Math.min(max || 100, value));
1899
+ const pct = clamped / (max || 100) * 100;
1682
1900
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1683
1901
  "div",
1684
1902
  {
1685
1903
  role: "progressbar",
1686
1904
  "aria-valuemin": 0,
1687
1905
  "aria-valuemax": max,
1688
- "aria-valuenow": indeterminate ? void 0 : value,
1906
+ "aria-valuenow": indeterminate ? void 0 : clamped,
1907
+ "aria-label": label,
1689
1908
  className: cx("uix-progress", indeterminate && "uix-progress--indeterminate", className),
1690
1909
  ...props,
1691
1910
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "uix-progress__bar", style: indeterminate ? void 0 : { width: `${pct}%` } })
@@ -1695,7 +1914,13 @@ function Progress({ value = 0, max = 100, indeterminate, className, ...props })
1695
1914
 
1696
1915
  // src/components/Meter.tsx
1697
1916
  var import_jsx_runtime29 = require("react/jsx-runtime");
1698
- function Meter({ value = 0, tone, className, ...props }) {
1917
+ var toneText = {
1918
+ warning: "warning",
1919
+ danger: "critical",
1920
+ attention: "needs attention",
1921
+ overdue: "overdue"
1922
+ };
1923
+ function Meter({ value = 0, tone, label, className, ...props }) {
1699
1924
  const pct = Math.max(0, Math.min(100, value));
1700
1925
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1701
1926
  "div",
@@ -1704,6 +1929,8 @@ function Meter({ value = 0, tone, className, ...props }) {
1704
1929
  "aria-valuenow": pct,
1705
1930
  "aria-valuemin": 0,
1706
1931
  "aria-valuemax": 100,
1932
+ "aria-valuetext": tone && tone !== "success" ? `${pct}%, ${toneText[tone]}` : void 0,
1933
+ "aria-label": label,
1707
1934
  className: cx("uix-meter", className),
1708
1935
  ...props,
1709
1936
  children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
@@ -1728,22 +1955,34 @@ function Stat({ label, value, icon, meta, trend, className, ...props }) {
1728
1955
  ] }),
1729
1956
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "uix-stat__value", children: value }),
1730
1957
  (meta != null || trend != null) && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "uix-stat__meta", children: [
1731
- trend != null && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: cx("uix-stat__trend", `uix-stat__trend--${trend.direction}`), children: trend.label }),
1958
+ trend != null && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: cx("uix-stat__trend", `uix-stat__trend--${trend.direction}`), children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: trend.direction === "up" ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M12 19V5m-6 6 6-6 6 6" }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { d: "M12 5v14m6-6-6 6-6-6" }) }),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "uix-visually-hidden", children: trend.direction === "up" ? "up" : "down" }),
1961
+ trend.label
1962
+ ] }),
1732
1963
  meta
1733
1964
  ] })
1734
1965
  ] });
1735
1966
  }
1736
1967
 
1737
1968
  // src/components/Tooltip.tsx
1738
- var import_react17 = require("react");
1969
+ var import_react21 = require("react");
1739
1970
  var import_jsx_runtime31 = require("react/jsx-runtime");
1740
1971
  function Tooltip({ label, placement = "top", className, children, ...props }) {
1741
- const triggerRef = (0, import_react17.useRef)(null);
1742
- const bubbleRef = (0, import_react17.useRef)(null);
1743
- const [open, setOpen] = (0, import_react17.useState)(false);
1744
- const id = (0, import_react17.useId)();
1972
+ const triggerRef = (0, import_react21.useRef)(null);
1973
+ const bubbleRef = (0, import_react21.useRef)(null);
1974
+ const closeTimer = (0, import_react21.useRef)(null);
1975
+ const [open, setOpen] = (0, import_react21.useState)(false);
1976
+ const id = (0, import_react21.useId)();
1745
1977
  useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
1978
+ const cancelClose = () => {
1979
+ if (closeTimer.current != null) {
1980
+ clearTimeout(closeTimer.current);
1981
+ closeTimer.current = null;
1982
+ }
1983
+ };
1746
1984
  const show = () => {
1985
+ cancelClose();
1747
1986
  const b = bubbleRef.current;
1748
1987
  if (b && !b.matches(":popover-open")) {
1749
1988
  try {
@@ -1754,6 +1993,7 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
1754
1993
  setOpen(true);
1755
1994
  };
1756
1995
  const hide = () => {
1996
+ cancelClose();
1757
1997
  const b = bubbleRef.current;
1758
1998
  if (b && b.matches(":popover-open")) {
1759
1999
  try {
@@ -1763,7 +2003,20 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
1763
2003
  }
1764
2004
  setOpen(false);
1765
2005
  };
1766
- (0, import_react17.useEffect)(() => () => {
2006
+ const scheduleHide = () => {
2007
+ cancelClose();
2008
+ closeTimer.current = window.setTimeout(hide, 100);
2009
+ };
2010
+ (0, import_react21.useEffect)(() => {
2011
+ if (!open) return;
2012
+ const onKey = (e) => {
2013
+ if (e.key === "Escape") hide();
2014
+ };
2015
+ document.addEventListener("keydown", onKey);
2016
+ return () => document.removeEventListener("keydown", onKey);
2017
+ }, [open]);
2018
+ (0, import_react21.useEffect)(() => () => {
2019
+ if (closeTimer.current != null) clearTimeout(closeTimer.current);
1767
2020
  const b = bubbleRef.current;
1768
2021
  if (b && b.matches(":popover-open")) {
1769
2022
  try {
@@ -1773,14 +2026,19 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
1773
2026
  }
1774
2027
  }, []);
1775
2028
  const bubbleAttr = { popover: "manual" };
2029
+ const single = (0, import_react21.isValidElement)(children);
2030
+ const child = single ? (0, import_react21.cloneElement)(children, {
2031
+ "aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" ")
2032
+ }) : children;
1776
2033
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1777
2034
  "span",
1778
2035
  {
1779
2036
  ref: triggerRef,
1780
2037
  className: cx("uix-tooltip", className),
1781
- "aria-describedby": id,
2038
+ "aria-describedby": single ? void 0 : id,
2039
+ tabIndex: single ? void 0 : 0,
1782
2040
  onMouseEnter: show,
1783
- onMouseLeave: hide,
2041
+ onMouseLeave: scheduleHide,
1784
2042
  onFocus: show,
1785
2043
  onBlur: hide,
1786
2044
  onKeyDown: (e) => {
@@ -1788,8 +2046,20 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
1788
2046
  },
1789
2047
  ...props,
1790
2048
  children: [
1791
- children,
1792
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { ref: bubbleRef, id, role: "tooltip", className: "uix-tooltip__bubble", ...bubbleAttr, children: label })
2049
+ child,
2050
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2051
+ "span",
2052
+ {
2053
+ ref: bubbleRef,
2054
+ id,
2055
+ role: "tooltip",
2056
+ className: "uix-tooltip__bubble",
2057
+ onMouseEnter: cancelClose,
2058
+ onMouseLeave: scheduleHide,
2059
+ ...bubbleAttr,
2060
+ children: label
2061
+ }
2062
+ )
1793
2063
  ]
1794
2064
  }
1795
2065
  );
@@ -1829,8 +2099,14 @@ function Skeleton({ variant = "block", width, height, className, style, ...props
1829
2099
  }
1830
2100
  );
1831
2101
  }
1832
- function LoadingState({ rows = 3, className, ...props }) {
1833
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cx("uix-stack", className), role: "status", "aria-busy": "true", ...props, children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Skeleton, { variant: "text", width: `${92 - i % 3 * 14}%` }, i)) });
2102
+ function LoadingState({ rows = 3, label, className, ...props }) {
2103
+ return (
2104
+ // no aria-busy: busy=true tells AT the region isn't ready and suppresses the announcement (UIX-A11Y-4)
2105
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: cx("uix-stack", className), role: "status", ...props, children: [
2106
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "uix-visually-hidden", children: label ?? "Loading" }),
2107
+ Array.from({ length: rows }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Skeleton, { variant: "text", width: `${92 - i % 3 * 14}%` }, i))
2108
+ ] })
2109
+ );
1834
2110
  }
1835
2111
 
1836
2112
  // src/components/Label.tsx
@@ -1848,7 +2124,8 @@ var import_jsx_runtime34 = require("react/jsx-runtime");
1848
2124
  function Avatar({ size = "md", src, alt, children, status, className, ...props }) {
1849
2125
  return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: cx("uix-avatar", size !== "md" && `uix-avatar--${size}`, className), ...props, children: [
1850
2126
  src ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("img", { src, alt: alt ?? "" }) : children,
1851
- status && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "uix-avatar__status" })
2127
+ status && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "uix-avatar__status", "aria-hidden": "true" }),
2128
+ status && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "uix-visually-hidden", children: "online" })
1852
2129
  ] });
1853
2130
  }
1854
2131
  function AvatarGroup({ children, className, ...props }) {
@@ -1868,7 +2145,7 @@ function UserChip({ avatar, name, sub, className, children, ...props }) {
1868
2145
  // src/components/Comments.tsx
1869
2146
  var import_jsx_runtime35 = require("react/jsx-runtime");
1870
2147
  function Comments({ children, className, ...props }) {
1871
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: cx("uix-comments", className), ...props, children });
2148
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "log", className: cx("uix-comments", className), ...props, children });
1872
2149
  }
1873
2150
  function Comment({ avatar, author, meta, children, className, ...props }) {
1874
2151
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: cx("uix-comment", className), ...props, children: [
@@ -1885,18 +2162,23 @@ function Comment({ avatar, author, meta, children, className, ...props }) {
1885
2162
  }
1886
2163
 
1887
2164
  // src/components/Composer.tsx
2165
+ var import_react22 = require("react");
1888
2166
  var import_jsx_runtime36 = require("react/jsx-runtime");
1889
- function Composer({ children, className, ...props }) {
1890
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: cx("uix-composer", className), ...props, children });
2167
+ function Composer({ children, className, label = "Add a comment", ...props }) {
2168
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: cx("uix-composer", className), ...props, children: import_react22.Children.map(children, (child) => {
2169
+ if (!(0, import_react22.isValidElement)(child) || child.type !== "textarea") return child;
2170
+ const p = child.props;
2171
+ return p["aria-label"] || p["aria-labelledby"] || p.id ? child : (0, import_react22.cloneElement)(child, { "aria-label": label });
2172
+ }) });
1891
2173
  }
1892
2174
  function ComposerBar({ children, className, ...props }) {
1893
2175
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: cx("uix-composer__bar", className), ...props, children });
1894
2176
  }
1895
2177
 
1896
2178
  // src/components/Segmented.tsx
1897
- var import_react18 = require("react");
2179
+ var import_react23 = require("react");
1898
2180
  var import_jsx_runtime37 = require("react/jsx-runtime");
1899
- var SegmentedCtx = (0, import_react18.createContext)({});
2181
+ var SegmentedCtx = (0, import_react23.createContext)({});
1900
2182
  function Segmented({ value, onChange, children, className, ...props }) {
1901
2183
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SegmentedCtx.Provider, { value: { value, onChange }, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
1902
2184
  }
@@ -1907,7 +2189,7 @@ function SegmentedOption({
1907
2189
  onClick,
1908
2190
  ...props
1909
2191
  }) {
1910
- const ctx = (0, import_react18.useContext)(SegmentedCtx);
2192
+ const ctx = (0, import_react23.useContext)(SegmentedCtx);
1911
2193
  const selected = ctx.value === value;
1912
2194
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1913
2195
  "button",
@@ -1928,11 +2210,11 @@ function SegmentedOption({
1928
2210
  // src/components/Timeline.tsx
1929
2211
  var import_jsx_runtime38 = require("react/jsx-runtime");
1930
2212
  function Timeline({ children, className, ...props }) {
1931
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: cx("uix-timeline", className), ...props, children });
2213
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ol", { className: cx("uix-timeline", className), ...props, children });
1932
2214
  }
1933
2215
  function TimelineItem({ node, meta, children, className, ...props }) {
1934
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: cx("uix-timeline__item", className), ...props, children: [
1935
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "uix-timeline__rail", children: [
2216
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("li", { className: cx("uix-timeline__item", className), ...props, children: [
2217
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "uix-timeline__rail", "aria-hidden": "true", children: [
1936
2218
  node ?? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "uix-timeline__node" }),
1937
2219
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "uix-timeline__line" })
1938
2220
  ] }),
@@ -1956,7 +2238,7 @@ function Note({ tone, icon, children, className, ...props }) {
1956
2238
  }
1957
2239
 
1958
2240
  // src/components/Popover.tsx
1959
- var import_react19 = require("react");
2241
+ var import_react24 = require("react");
1960
2242
  var import_jsx_runtime40 = require("react/jsx-runtime");
1961
2243
  function Popover({
1962
2244
  popover = "auto",
@@ -1967,15 +2249,15 @@ function Popover({
1967
2249
  children,
1968
2250
  ...props
1969
2251
  }) {
1970
- const ref = (0, import_react19.useRef)(null);
1971
- const [open, setOpen] = (0, import_react19.useState)(false);
2252
+ const ref = (0, import_react24.useRef)(null);
2253
+ const [open, setOpen] = (0, import_react24.useState)(false);
1972
2254
  const popoverAttr = { popover };
1973
2255
  const reposition = useAnchoredPosition(anchor ?? null, ref, {
1974
2256
  open: open && !!anchor,
1975
2257
  placement,
1976
2258
  offset
1977
2259
  });
1978
- (0, import_react19.useEffect)(() => {
2260
+ (0, import_react24.useEffect)(() => {
1979
2261
  const el = ref.current;
1980
2262
  if (!el || !anchor) return;
1981
2263
  const sync = () => {
@@ -1990,34 +2272,138 @@ function Popover({
1990
2272
  }
1991
2273
 
1992
2274
  // src/components/CommandPalette.tsx
2275
+ var import_react25 = require("react");
1993
2276
  var import_jsx_runtime41 = require("react/jsx-runtime");
2277
+ var CmdkContext = (0, import_react25.createContext)(null);
1994
2278
  function CommandPalette({ inputProps, children, className, ...props }) {
1995
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: cx("uix-cmdk", className), ...props, children: [
1996
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("input", { className: "uix-cmdk__input", ...inputProps }),
1997
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "uix-cmdk__list", children })
1998
- ] });
2279
+ const listId = (0, import_react25.useId)();
2280
+ const [activeId, setActiveId] = (0, import_react25.useState)(null);
2281
+ const [count, setCount] = (0, import_react25.useState)(0);
2282
+ const [announced, setAnnounced] = (0, import_react25.useState)("");
2283
+ const setActive = (0, import_react25.useCallback)((id, active) => {
2284
+ setActiveId((cur) => active ? id : cur === id ? null : cur);
2285
+ }, []);
2286
+ const registerItem = (0, import_react25.useCallback)(() => {
2287
+ setCount((c) => c + 1);
2288
+ return () => setCount((c) => c - 1);
2289
+ }, []);
2290
+ (0, import_react25.useEffect)(() => {
2291
+ const t = window.setTimeout(() => setAnnounced(`${count} result${count === 1 ? "" : "s"}`), 150);
2292
+ return () => clearTimeout(t);
2293
+ }, [count]);
2294
+ const ctx = (0, import_react25.useMemo)(() => ({ listId, setActive, registerItem }), [listId, setActive, registerItem]);
2295
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CmdkContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: cx("uix-cmdk", className), ...props, children: [
2296
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2297
+ "input",
2298
+ {
2299
+ className: "uix-cmdk__input",
2300
+ role: "combobox",
2301
+ "aria-expanded": "true",
2302
+ "aria-autocomplete": "list",
2303
+ "aria-controls": listId,
2304
+ "aria-activedescendant": activeId ?? void 0,
2305
+ "aria-label": "Command palette",
2306
+ ...inputProps
2307
+ }
2308
+ ),
2309
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "uix-cmdk__list", role: "listbox", id: listId, children }),
2310
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "uix-visually-hidden", role: "status", children: announced })
2311
+ ] }) });
1999
2312
  }
2000
2313
  function CommandGroup({ label, children }) {
2001
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
2002
- label != null && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "uix-cmdk__group", children: label }),
2314
+ const labelId = (0, import_react25.useId)();
2315
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { role: "group", "aria-labelledby": label != null ? labelId : void 0, children: [
2316
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "uix-cmdk__group", id: labelId, children: label }),
2003
2317
  children
2004
2318
  ] });
2005
2319
  }
2006
- function CommandItem({ icon, shortcut, active, children, className, ...props }) {
2007
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: cx("uix-cmdk__item", className), "data-active": active || void 0, ...props, children: [
2008
- icon,
2009
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children }),
2010
- shortcut
2011
- ] });
2320
+ function CommandItem({ icon, shortcut, active, children, className, id, ...props }) {
2321
+ const ctx = (0, import_react25.useContext)(CmdkContext);
2322
+ const autoId = (0, import_react25.useId)();
2323
+ const itemId = id ?? autoId;
2324
+ (0, import_react25.useEffect)(() => ctx?.registerItem(), [ctx]);
2325
+ (0, import_react25.useEffect)(() => {
2326
+ if (!ctx || !active) return;
2327
+ ctx.setActive(itemId, true);
2328
+ return () => ctx.setActive(itemId, false);
2329
+ }, [ctx, itemId, active]);
2330
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2331
+ "div",
2332
+ {
2333
+ className: cx("uix-cmdk__item", className),
2334
+ "data-active": active || void 0,
2335
+ id: itemId,
2336
+ role: "option",
2337
+ "aria-selected": !!active,
2338
+ ...props,
2339
+ children: [
2340
+ icon,
2341
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children }),
2342
+ shortcut
2343
+ ]
2344
+ }
2345
+ );
2012
2346
  }
2013
2347
 
2014
2348
  // src/components/Inbox.tsx
2349
+ var import_react26 = require("react");
2015
2350
  var import_jsx_runtime42 = require("react/jsx-runtime");
2016
- function Inbox({ children, className, ...props }) {
2017
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: cx("uix-inbox", className), ...props, children });
2351
+ function Inbox({ view, children, className, ...props }) {
2352
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: cx("uix-inbox", className), "data-view": view, ...props, children });
2018
2353
  }
2354
+ var InboxListCtx = (0, import_react26.createContext)({});
2019
2355
  function InboxList({ children, className, ...props }) {
2020
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: cx("uix-inbox__list", className), role: "listbox", ...props, children });
2356
+ const listRef = (0, import_react26.useRef)(null);
2357
+ const [activeId, setActiveId] = (0, import_react26.useState)(void 0);
2358
+ const ctx = (0, import_react26.useMemo)(() => ({ activeId }), [activeId]);
2359
+ const onKeyDown = (e) => {
2360
+ const opts = Array.from(listRef.current?.querySelectorAll('[role="option"]') ?? []);
2361
+ if (opts.length === 0) return;
2362
+ const idx = opts.findIndex((el2) => el2.id === activeId);
2363
+ let next;
2364
+ switch (e.key) {
2365
+ case "ArrowDown":
2366
+ next = Math.min(idx + 1, opts.length - 1);
2367
+ break;
2368
+ case "ArrowUp":
2369
+ next = idx < 0 ? 0 : Math.max(idx - 1, 0);
2370
+ break;
2371
+ case "Home":
2372
+ next = 0;
2373
+ break;
2374
+ case "End":
2375
+ next = opts.length - 1;
2376
+ break;
2377
+ case "Enter":
2378
+ case " ":
2379
+ if (idx >= 0) {
2380
+ e.preventDefault();
2381
+ opts[idx]?.click();
2382
+ }
2383
+ return;
2384
+ default:
2385
+ return;
2386
+ }
2387
+ e.preventDefault();
2388
+ const el = opts[next];
2389
+ if (!el || !el.id) return;
2390
+ setActiveId(el.id);
2391
+ el.scrollIntoView({ block: "nearest" });
2392
+ };
2393
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(InboxListCtx.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2394
+ "div",
2395
+ {
2396
+ ref: listRef,
2397
+ className: cx("uix-inbox__list", className),
2398
+ role: "listbox",
2399
+ tabIndex: 0,
2400
+ "aria-label": "Inbox",
2401
+ "aria-activedescendant": activeId,
2402
+ onKeyDown,
2403
+ ...props,
2404
+ children
2405
+ }
2406
+ ) });
2021
2407
  }
2022
2408
  function InboxItem({
2023
2409
  subject,
@@ -2027,13 +2413,19 @@ function InboxItem({
2027
2413
  selected,
2028
2414
  children,
2029
2415
  className,
2416
+ id,
2030
2417
  ...props
2031
2418
  }) {
2419
+ const autoId = (0, import_react26.useId)();
2420
+ const itemId = id ?? autoId;
2421
+ const { activeId } = (0, import_react26.useContext)(InboxListCtx);
2032
2422
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
2033
2423
  "div",
2034
2424
  {
2035
2425
  role: "option",
2426
+ id: itemId,
2036
2427
  "aria-selected": selected ?? false,
2428
+ "data-active": activeId === itemId ? "" : void 0,
2037
2429
  className: cx("uix-inbox__item", className),
2038
2430
  ...unread ? { "data-unread": "" } : {},
2039
2431
  ...props,
@@ -2051,29 +2443,60 @@ function InboxDetail({ children, className, ...props }) {
2051
2443
  }
2052
2444
 
2053
2445
  // src/components/Kanban.tsx
2446
+ var import_react27 = require("react");
2054
2447
  var import_jsx_runtime43 = require("react/jsx-runtime");
2055
- function Kanban({ children, className, ...props }) {
2056
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: cx("uix-kanban", className), ...props, children });
2448
+ function Kanban({ announcement, children, className, ...props }) {
2449
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: cx("uix-kanban", className), ...props, children: [
2450
+ announcement != null && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { role: "status", className: "uix-visually-hidden", children: announcement }),
2451
+ children
2452
+ ] });
2057
2453
  }
2058
2454
  function KanbanColumn({ title, count, children, className, ...props }) {
2455
+ const headId = (0, import_react27.useId)();
2456
+ const hasHead = title != null || count != null;
2059
2457
  return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: cx("uix-kanban__col", className), ...props, children: [
2060
- (title != null || count != null) && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "uix-kanban__head", children: [
2458
+ hasHead && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "uix-kanban__head", id: headId, children: [
2061
2459
  title,
2062
2460
  count != null && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "uix-kanban__count", children: count })
2063
2461
  ] }),
2064
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "uix-kanban__body", children })
2462
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "uix-kanban__body", role: "list", "aria-labelledby": hasHead ? headId : void 0, children })
2065
2463
  ] });
2066
2464
  }
2067
- function KanbanCard({ title, meta, children, className, ...props }) {
2068
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: cx("uix-kanban__card", className), ...props, children: [
2069
- title && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "uix-kanban__card-title", children: title }),
2070
- meta && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "uix-kanban__card-meta", children: meta }),
2071
- children
2072
- ] });
2465
+ var MOVE_KEYS = {
2466
+ ArrowUp: "up",
2467
+ ArrowDown: "down",
2468
+ ArrowLeft: "left",
2469
+ ArrowRight: "right"
2470
+ };
2471
+ function KanbanCard({ title, meta, children, className, onMove, onKeyDown, ...props }) {
2472
+ const handleKeyDown = (e) => {
2473
+ onKeyDown?.(e);
2474
+ if (!onMove || !e.altKey || e.defaultPrevented) return;
2475
+ const dir = MOVE_KEYS[e.key];
2476
+ if (dir) {
2477
+ e.preventDefault();
2478
+ onMove(dir);
2479
+ }
2480
+ };
2481
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
2482
+ "div",
2483
+ {
2484
+ role: "listitem",
2485
+ tabIndex: 0,
2486
+ className: cx("uix-kanban__card", className),
2487
+ onKeyDown: handleKeyDown,
2488
+ ...props,
2489
+ children: [
2490
+ title && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "uix-kanban__card-title", children: title }),
2491
+ meta && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "uix-kanban__card-meta", children: meta }),
2492
+ children
2493
+ ]
2494
+ }
2495
+ );
2073
2496
  }
2074
2497
 
2075
2498
  // src/components/Tree.tsx
2076
- var import_react20 = require("react");
2499
+ var import_react28 = require("react");
2077
2500
 
2078
2501
  // src/tree-model.ts
2079
2502
  function flattenTree(nodes, expanded, level = 1) {
@@ -2118,6 +2541,14 @@ function treeNav(flat, currentId, key) {
2118
2541
  case " ":
2119
2542
  return cur.hasChildren ? { toggleId: cur.node.id, selectId: cur.node.id } : { selectId: cur.node.id };
2120
2543
  default:
2544
+ if (key.length === 1) {
2545
+ const q = key.toLowerCase();
2546
+ for (let step = 1; step <= flat.length; step++) {
2547
+ const f = flat[(idx + step) % flat.length];
2548
+ const label = f.node.typeaheadLabel ?? (typeof f.node.label === "string" ? f.node.label : void 0);
2549
+ if (label != null && label.toLowerCase().startsWith(q)) return { focusId: f.node.id };
2550
+ }
2551
+ }
2121
2552
  return {};
2122
2553
  }
2123
2554
  }
@@ -2185,12 +2616,12 @@ function Tree({
2185
2616
  className,
2186
2617
  ...props
2187
2618
  }) {
2188
- const [internalExpanded, setInternalExpanded] = (0, import_react20.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
2619
+ const [internalExpanded, setInternalExpanded] = (0, import_react28.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
2189
2620
  const expanded = controlledExpanded ?? internalExpanded;
2190
- const rootRef = (0, import_react20.useRef)(null);
2191
- const [focusedId, setFocusedId] = (0, import_react20.useState)(void 0);
2621
+ const rootRef = (0, import_react28.useRef)(null);
2622
+ const [focusedId, setFocusedId] = (0, import_react28.useState)(void 0);
2192
2623
  const selectable = selected !== void 0 || onSelect != null;
2193
- const handleToggle = (0, import_react20.useCallback)((id) => {
2624
+ const handleToggle = (0, import_react28.useCallback)((id) => {
2194
2625
  if (!controlledExpanded) {
2195
2626
  setInternalExpanded((prev) => {
2196
2627
  const next = new Set(prev);
@@ -2201,7 +2632,7 @@ function Tree({
2201
2632
  }
2202
2633
  onToggle?.(id);
2203
2634
  }, [controlledExpanded, onToggle]);
2204
- const flat = (0, import_react20.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
2635
+ const flat = (0, import_react28.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
2205
2636
  const useVirtual = virtualize ?? shouldVirtualize(flat.length);
2206
2637
  const tabbableId = focusedId ?? selected ?? nodes[0]?.id;
2207
2638
  if (useVirtual) {
@@ -2272,6 +2703,13 @@ function Tree({
2272
2703
  setFocusedId(id);
2273
2704
  break;
2274
2705
  default:
2706
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
2707
+ const act = treeNav(flat, id, e.key);
2708
+ if (act.focusId !== void 0) {
2709
+ e.preventDefault();
2710
+ focusItem(rootRef.current?.querySelector(`[role="treeitem"][data-id="${CSS.escape(act.focusId)}"]`));
2711
+ }
2712
+ }
2275
2713
  break;
2276
2714
  }
2277
2715
  };
@@ -2305,12 +2743,14 @@ function VirtualTreeView({
2305
2743
  className,
2306
2744
  ...props
2307
2745
  }) {
2308
- const scrollRef = (0, import_react20.useRef)(null);
2309
- const [scrollTop, setScrollTop] = (0, import_react20.useState)(0);
2310
- const pendingFocus = (0, import_react20.useRef)(false);
2746
+ const scrollRef = (0, import_react28.useRef)(null);
2747
+ const [scrollTop, setScrollTop] = (0, import_react28.useState)(0);
2748
+ const pendingFocus = (0, import_react28.useRef)(false);
2749
+ const hadFocus = (0, import_react28.useRef)(false);
2311
2750
  const win = virtualWindow(scrollTop, maxHeight, rowHeight, flat.length);
2312
2751
  const rows = flat.slice(win.start, win.end);
2313
- (0, import_react20.useEffect)(() => {
2752
+ const windowHasTabbable = tabbableId != null && rows.some((f) => f.node.id === tabbableId);
2753
+ (0, import_react28.useEffect)(() => {
2314
2754
  if (!pendingFocus.current || focusedId == null) return;
2315
2755
  const el = scrollRef.current?.querySelector(`[role="treeitem"][data-id="${CSS.escape(focusedId)}"]`);
2316
2756
  if (el) {
@@ -2318,6 +2758,11 @@ function VirtualTreeView({
2318
2758
  pendingFocus.current = false;
2319
2759
  }
2320
2760
  }, [focusedId, win.start, win.end]);
2761
+ (0, import_react28.useEffect)(() => {
2762
+ if (!hadFocus.current || pendingFocus.current) return;
2763
+ const active = document.activeElement;
2764
+ if (active === document.body || active == null) scrollRef.current?.focus({ preventScroll: true });
2765
+ });
2321
2766
  const moveTo = (id) => {
2322
2767
  const i = flat.findIndex((f) => f.node.id === id);
2323
2768
  if (i < 0) return;
@@ -2333,6 +2778,7 @@ function VirtualTreeView({
2333
2778
  setFocusedId(id);
2334
2779
  };
2335
2780
  const onKeyDown = (e) => {
2781
+ if (e.key.length === 1 && (e.ctrlKey || e.metaKey || e.altKey)) return;
2336
2782
  const curId = e.target.closest('[role="treeitem"]')?.dataset.id ?? focusedId;
2337
2783
  const act = treeNav(flat, curId, e.key);
2338
2784
  if (act.focusId === void 0 && act.toggleId === void 0 && act.selectId === void 0) return;
@@ -2353,13 +2799,20 @@ function VirtualTreeView({
2353
2799
  ref: scrollRef,
2354
2800
  className: cx("uix-tree", "uix-tree--virtual", className),
2355
2801
  role: "tree",
2802
+ tabIndex: -1,
2356
2803
  style: { maxHeight, overflow: "auto" },
2357
2804
  onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
2358
2805
  onKeyDown,
2806
+ onFocus: () => {
2807
+ hadFocus.current = true;
2808
+ },
2809
+ onBlur: (e) => {
2810
+ if (!e.currentTarget.contains(e.relatedTarget)) hadFocus.current = false;
2811
+ },
2359
2812
  ...props,
2360
2813
  children: [
2361
- win.padTop > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { "aria-hidden": "true", style: { height: win.padTop } }),
2362
- rows.map((f) => {
2814
+ win.padTop > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { role: "presentation", "aria-hidden": "true", style: { height: win.padTop } }),
2815
+ rows.map((f, i) => {
2363
2816
  const rowStyle = { height: rowHeight, paddingLeft: `calc(${f.level - 1} * var(--uix-space-5))` };
2364
2817
  return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2365
2818
  "li",
@@ -2371,7 +2824,7 @@ function VirtualTreeView({
2371
2824
  "aria-expanded": f.hasChildren ? f.isExpanded : void 0,
2372
2825
  "aria-selected": selectable ? selected === f.node.id : void 0,
2373
2826
  "data-id": f.node.id,
2374
- tabIndex: tabbableId === f.node.id ? 0 : -1,
2827
+ tabIndex: tabbableId === f.node.id || !windowHasTabbable && i === 0 ? 0 : -1,
2375
2828
  className: "uix-tree__item",
2376
2829
  onFocus: (e) => {
2377
2830
  if (e.target === e.currentTarget) setFocusedId(f.node.id);
@@ -2391,7 +2844,7 @@ function VirtualTreeView({
2391
2844
  f.node.id
2392
2845
  );
2393
2846
  }),
2394
- win.padBottom > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { "aria-hidden": "true", style: { height: win.padBottom } })
2847
+ win.padBottom > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { role: "presentation", "aria-hidden": "true", style: { height: win.padBottom } })
2395
2848
  ]
2396
2849
  }
2397
2850
  );
@@ -2650,6 +3103,7 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2650
3103
  SubNavItem,
2651
3104
  Switch,
2652
3105
  Tab,
3106
+ TabPanel,
2653
3107
  Table,
2654
3108
  TableWrap,
2655
3109
  Tabs,