@tensor_1/react 2.16.0 → 2.18.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 (75) hide show
  1. package/README.md +29 -0
  2. package/dist/chart-preset.cjs +15 -3
  3. package/dist/chart.cjs +15 -3
  4. package/dist/components/Alert.js +15 -7
  5. package/dist/components/AppShell.d.ts +3 -1
  6. package/dist/components/AppShell.js +3 -1
  7. package/dist/components/Avatar.js +2 -1
  8. package/dist/components/Button.js +32 -16
  9. package/dist/components/Card.d.ts +3 -1
  10. package/dist/components/Card.js +2 -2
  11. package/dist/components/ChartCore.js +15 -3
  12. package/dist/components/CommandPalette.d.ts +4 -2
  13. package/dist/components/CommandPalette.js +66 -13
  14. package/dist/components/Comments.d.ts +5 -1
  15. package/dist/components/Comments.js +1 -1
  16. package/dist/components/Composer.d.ts +8 -1
  17. package/dist/components/Composer.js +7 -2
  18. package/dist/components/DetailLayout.js +1 -1
  19. package/dist/components/Drawer.d.ts +3 -4
  20. package/dist/components/Drawer.js +6 -4
  21. package/dist/components/Field.js +6 -3
  22. package/dist/components/Inbox.d.ts +13 -3
  23. package/dist/components/Inbox.js +62 -3
  24. package/dist/components/Input.d.ts +13 -2
  25. package/dist/components/Input.js +20 -5
  26. package/dist/components/Kanban.d.ts +15 -3
  27. package/dist/components/Kanban.js +42 -10
  28. package/dist/components/Meter.d.ts +3 -1
  29. package/dist/components/Meter.js +9 -1
  30. package/dist/components/Modal.d.ts +4 -5
  31. package/dist/components/Modal.js +4 -4
  32. package/dist/components/NavFavourites.js +11 -0
  33. package/dist/components/PageHeader.d.ts +3 -1
  34. package/dist/components/PageHeader.js +2 -2
  35. package/dist/components/Peek.d.ts +3 -4
  36. package/dist/components/Peek.js +9 -6
  37. package/dist/components/Progress.d.ts +3 -1
  38. package/dist/components/Progress.js +5 -3
  39. package/dist/components/Radio.d.ts +3 -1
  40. package/dist/components/Radio.js +6 -2
  41. package/dist/components/RelationshipGraph.d.ts +81 -3
  42. package/dist/components/RelationshipGraph.js +81 -57
  43. package/dist/components/RelationshipGraphLayered.d.ts +8 -0
  44. package/dist/components/RelationshipGraphLayered.js +420 -0
  45. package/dist/components/RelationshipGraphList.d.ts +16 -0
  46. package/dist/components/RelationshipGraphList.js +33 -0
  47. package/dist/components/Spinner.d.ts +3 -1
  48. package/dist/components/Spinner.js +3 -3
  49. package/dist/components/Stat.js +5 -1
  50. package/dist/components/States.d.ts +1 -0
  51. package/dist/components/States.js +7 -1
  52. package/dist/components/Table.d.ts +8 -1
  53. package/dist/components/Table.js +10 -6
  54. package/dist/components/Tabs.d.ts +13 -1
  55. package/dist/components/Tabs.js +79 -4
  56. package/dist/components/Timeline.d.ts +3 -3
  57. package/dist/components/Timeline.js +3 -3
  58. package/dist/components/Toast.js +42 -5
  59. package/dist/components/Tooltip.d.ts +3 -1
  60. package/dist/components/Tooltip.js +44 -5
  61. package/dist/components/Tree.d.ts +2 -0
  62. package/dist/components/Tree.js +26 -4
  63. package/dist/hooks/useDialog.d.ts +1 -1
  64. package/dist/hooks/useDialog.js +17 -2
  65. package/dist/hooks/useTable.d.ts +5 -0
  66. package/dist/hooks/useTable.js +20 -1
  67. package/dist/index.cjs +1791 -446
  68. package/dist/index.d.cts +311 -36
  69. package/dist/index.d.ts +4 -4
  70. package/dist/index.js +10 -3
  71. package/dist/relationship-graph-model.d.ts +112 -1
  72. package/dist/relationship-graph-model.js +420 -1
  73. package/dist/tree-model.d.ts +4 -0
  74. package/dist/tree-model.js +8 -0
  75. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -50,6 +50,7 @@ __export(src_exports, {
50
50
  ConfirmDialog: () => ConfirmDialog,
51
51
  ContentList: () => ContentList,
52
52
  ContentListItem: () => ContentListItem,
53
+ DEFAULT_RELATIONSHIP_GRAPH_LABELS: () => DEFAULT_RELATIONSHIP_GRAPH_LABELS,
53
54
  DateRangePicker: () => DateRangePicker,
54
55
  DescriptionItem: () => DescriptionItem,
55
56
  DescriptionList: () => DescriptionList,
@@ -134,6 +135,7 @@ __export(src_exports, {
134
135
  SubNavItem: () => SubNavItem,
135
136
  Switch: () => Switch,
136
137
  Tab: () => Tab,
138
+ TabPanel: () => TabPanel,
137
139
  Table: () => Table,
138
140
  TableWrap: () => TableWrap,
139
141
  Tabs: () => Tabs,
@@ -160,6 +162,8 @@ __export(src_exports, {
160
162
  buildThreeWayDiff: () => buildThreeWayDiff,
161
163
  clampMetricValue: () => clampMetricValue,
162
164
  clampWidth: () => clampWidth,
165
+ classifyLayeredEdges: () => classifyLayeredEdges,
166
+ clusterIdFor: () => clusterIdFor,
163
167
  computePosition: () => computePosition,
164
168
  contrastRatio: () => contrastRatio,
165
169
  cx: () => cx,
@@ -174,6 +178,8 @@ __export(src_exports, {
174
178
  isDateInRange: () => isDateInRange,
175
179
  isDateUnavailable: () => isDateUnavailable,
176
180
  isRuleGroup: () => isRuleGroup,
181
+ layeredNeighbor: () => layeredNeighbor,
182
+ layoutLayeredGraph: () => layoutLayeredGraph,
177
183
  layoutRelationshipGraph: () => layoutRelationshipGraph,
178
184
  mapRuleGroup: () => mapRuleGroup,
179
185
  matchFilter: () => matchFilter,
@@ -215,6 +221,7 @@ __export(src_exports, {
215
221
  useVirtualRows: () => useVirtualRows,
216
222
  validateRuleDefinition: () => validateRuleDefinition,
217
223
  virtualWindow: () => virtualWindow,
224
+ wrapEdgeLabel: () => wrapEdgeLabel,
218
225
  zonedDateKey: () => zonedDateKey,
219
226
  zonedDateSpan: () => zonedDateSpan
220
227
  });
@@ -257,8 +264,10 @@ function unlockBodyScroll() {
257
264
  saved = null;
258
265
  }
259
266
  }
260
- function useDialog(open) {
267
+ function useDialog(open, onClose) {
261
268
  const ref = (0, import_react.useRef)(null);
269
+ const onCloseRef = (0, import_react.useRef)(onClose);
270
+ onCloseRef.current = onClose;
262
271
  (0, import_react.useEffect)(() => {
263
272
  const el = ref.current;
264
273
  if (!el || !open) {
@@ -267,8 +276,21 @@ function useDialog(open) {
267
276
  }
268
277
  if (!el.open) el.showModal();
269
278
  lockBodyScroll();
279
+ let locked = true;
280
+ const handleClose = () => {
281
+ if (locked) {
282
+ locked = false;
283
+ unlockBodyScroll();
284
+ }
285
+ onCloseRef.current?.();
286
+ };
287
+ el.addEventListener("close", handleClose);
270
288
  return () => {
271
- unlockBodyScroll();
289
+ el.removeEventListener("close", handleClose);
290
+ if (locked) {
291
+ locked = false;
292
+ unlockBodyScroll();
293
+ }
272
294
  if (el.open) el.close();
273
295
  };
274
296
  }, [open]);
@@ -541,6 +563,24 @@ function useTable(data, options = {}) {
541
563
  const toggleAllRows = (0, import_react2.useCallback)(() => setSelected((s) => togglePage(s, pageIds)), [pageIds]);
542
564
  const clearSelection = (0, import_react2.useCallback)(() => setSelected(/* @__PURE__ */ new Set()), []);
543
565
  const togglePin = (0, import_react2.useCallback)((id) => setPinned((s) => toggleId(s, id)), []);
566
+ const [announcement, setAnnouncement] = (0, import_react2.useState)("");
567
+ const mounted = (0, import_react2.useRef)(false);
568
+ (0, import_react2.useEffect)(() => {
569
+ if (!mounted.current) return;
570
+ const key = sort[0];
571
+ setAnnouncement(key ? `Sorted by ${key.field}, ${key.dir}` : "Sort cleared");
572
+ }, [sort]);
573
+ (0, import_react2.useEffect)(() => {
574
+ if (!mounted.current) return;
575
+ setAnnouncement(`${rows.length} result${rows.length === 1 ? "" : "s"}`);
576
+ }, [filters, query]);
577
+ (0, import_react2.useEffect)(() => {
578
+ if (!mounted.current) return;
579
+ setAnnouncement(`${selected.size} selected`);
580
+ }, [selected]);
581
+ (0, import_react2.useEffect)(() => {
582
+ mounted.current = true;
583
+ }, []);
544
584
  const view = (0, import_react2.useMemo)(() => ({ sort, filters, q: query }), [sort, filters, query]);
545
585
  const viewQueryString = (0, import_react2.useMemo)(() => serializeView(view), [view]);
546
586
  const applyView = (0, import_react2.useCallback)((queryString) => {
@@ -557,6 +597,7 @@ function useTable(data, options = {}) {
557
597
  selected,
558
598
  pinned,
559
599
  selectAll,
600
+ announcement,
560
601
  toggleSort: doToggleSort,
561
602
  setSort,
562
603
  setFilters,
@@ -726,24 +767,40 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
726
767
  // src/components/Button.tsx
727
768
  var import_react5 = require("react");
728
769
  var import_jsx_runtime = require("react/jsx-runtime");
770
+ var warnedIconButton = false;
729
771
  var Button = (0, import_react5.forwardRef)(
730
- ({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
731
- "button",
732
- {
733
- ref,
734
- className: cx(
735
- "uix-btn",
736
- `uix-btn--${variant}`,
737
- size !== "md" && `uix-btn--${size}`,
738
- icon && "uix-btn--icon",
739
- className
740
- ),
741
- disabled: disabled || loading,
742
- "data-loading": loading || void 0,
743
- ...props,
744
- children
772
+ ({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, onClick, ...props }, ref) => {
773
+ if (!warnedIconButton && icon && children == null && !props["aria-label"] && !props["aria-labelledby"] && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
774
+ warnedIconButton = true;
775
+ console.warn("uix: icon-only <Button> has no accessible name \u2014 pass aria-label or aria-labelledby.");
745
776
  }
746
- )
777
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
778
+ "button",
779
+ {
780
+ ref,
781
+ className: cx(
782
+ "uix-btn",
783
+ `uix-btn--${variant}`,
784
+ size !== "md" && `uix-btn--${size}`,
785
+ icon && "uix-btn--icon",
786
+ className
787
+ ),
788
+ disabled,
789
+ "aria-disabled": loading || void 0,
790
+ "aria-busy": loading || void 0,
791
+ "data-loading": loading || void 0,
792
+ onClick: loading || onClick ? (e) => {
793
+ if (loading) {
794
+ e.preventDefault();
795
+ return;
796
+ }
797
+ onClick?.(e);
798
+ } : void 0,
799
+ ...props,
800
+ children
801
+ }
802
+ );
803
+ }
747
804
  );
748
805
  Button.displayName = "Button";
749
806
  function ButtonGroup({ children, className }) {
@@ -766,7 +823,22 @@ var Input = (0, import_react6.forwardRef)(
766
823
  )
767
824
  );
768
825
  Input.displayName = "Input";
769
- function InputGroup({ children, leadingIcon, trailingIcon, className }) {
826
+ function InputGroup({
827
+ children,
828
+ leadingIcon,
829
+ trailingIcon,
830
+ className,
831
+ id,
832
+ "aria-describedby": describedBy,
833
+ "aria-invalid": invalid,
834
+ "aria-required": required
835
+ }) {
836
+ const control = (0, import_react6.isValidElement)(children) && (id ?? describedBy ?? invalid ?? required) != null ? (0, import_react6.cloneElement)(children, {
837
+ id: children.props.id ?? id,
838
+ "aria-describedby": children.props["aria-describedby"] ?? describedBy,
839
+ "aria-invalid": children.props["aria-invalid"] ?? invalid,
840
+ "aria-required": children.props["aria-required"] ?? required
841
+ }) : children;
770
842
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
771
843
  "div",
772
844
  {
@@ -777,9 +849,9 @@ function InputGroup({ children, leadingIcon, trailingIcon, className }) {
777
849
  className
778
850
  ),
779
851
  children: [
780
- leadingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon", children: leadingIcon }),
781
- children,
782
- trailingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", children: trailingIcon })
852
+ leadingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon", "aria-hidden": "true", children: leadingIcon }),
853
+ control,
854
+ trailingIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", "aria-hidden": "true", children: trailingIcon })
783
855
  ]
784
856
  }
785
857
  );
@@ -986,8 +1058,12 @@ var Radio = (0, import_react11.forwardRef)(
986
1058
  ] })
987
1059
  );
988
1060
  Radio.displayName = "Radio";
989
- function RadioGroup({ children, className }) {
990
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cx("uix-radio-group", className), children });
1061
+ function RadioGroup({ children, className, label }) {
1062
+ if (label == null) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cx("uix-radio-group", className), children });
1063
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("fieldset", { className: cx("uix-radio-group", className), children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("legend", { className: "uix-radio-group__legend", children: label }),
1065
+ children
1066
+ ] });
991
1067
  }
992
1068
 
993
1069
  // src/components/Switch.tsx
@@ -1009,14 +1085,17 @@ function Field({ label, hint, error, success, required, children, className, htm
1009
1085
  const uid = (0, import_react13.useId)();
1010
1086
  const msgId = `${uid}-msg`;
1011
1087
  const describedBy = error || success || hint ? msgId : void 0;
1088
+ const controlId = (0, import_react13.isValidElement)(children) ? children.props.id ?? `${uid}-ctl` : void 0;
1012
1089
  const control = (0, import_react13.isValidElement)(children) ? (0, import_react13.cloneElement)(children, {
1090
+ id: controlId,
1013
1091
  "aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0,
1014
- "aria-invalid": error ? true : children.props["aria-invalid"]
1092
+ "aria-invalid": error ? true : children.props["aria-invalid"],
1093
+ "aria-required": children.props["aria-required"] ?? (required || void 0)
1015
1094
  }) : children;
1016
1095
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: cx("uix-field", className), children: [
1017
- label && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "uix-field__label", htmlFor, "data-required": required || void 0, children: label }),
1096
+ label && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "uix-field__label", htmlFor: htmlFor ?? controlId, "data-required": required || void 0, children: label }),
1018
1097
  control,
1019
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "uix-field__msg", id: msgId, children: error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "uix-field__error", role: "alert", children: error }) : success ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "uix-field__success", children: success }) : hint ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "uix-field__hint", children: hint }) : null })
1098
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "uix-field__msg", id: msgId, children: error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "uix-field__error", role: "alert", children: error }) : success ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "uix-field__success", role: "status", children: success }) : hint ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "uix-field__hint", children: hint }) : null })
1020
1099
  ] });
1021
1100
  }
1022
1101
 
@@ -1024,12 +1103,12 @@ function Field({ label, hint, error, success, required, children, className, htm
1024
1103
  var import_react14 = require("react");
1025
1104
  var import_jsx_runtime10 = require("react/jsx-runtime");
1026
1105
  var CloseIcon = () => /* @__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" }) });
1027
- function Modal({ open, onClose, title, children, footer, className, style, closeLabel = "Close dialog", role = "dialog" }) {
1028
- const ref = useDialog(open);
1106
+ function Modal({ open, onClose, title, children, footer, className, closeLabel = "Close dialog", role = "dialog", ...rest }) {
1107
+ const ref = useDialog(open, onClose);
1029
1108
  const titleId = (0, import_react14.useId)();
1030
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("dialog", { ref, className: cx("uix-dialog", className), style, onClose, role, "aria-labelledby": title != null ? titleId : void 0, children: [
1109
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("dialog", { ref, className: cx("uix-dialog", className), role, "aria-labelledby": title != null ? titleId : void 0, ...rest, children: [
1031
1110
  (title != null || onClose) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "uix-dialog__header", children: [
1032
- title && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { id: titleId, className: "uix-dialog__title", children: title }),
1111
+ title && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { className: "uix-dialog__title", id: titleId, children: title }),
1033
1112
  onClose && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "uix-dialog__close", onClick: onClose, "aria-label": closeLabel, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CloseIcon, {}) })
1034
1113
  ] }),
1035
1114
  children != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "uix-dialog__body", children }),
@@ -1087,13 +1166,15 @@ function PromptDialog({ open, title, description, inputLabel, defaultValue = "",
1087
1166
  }
1088
1167
 
1089
1168
  // src/components/Drawer.tsx
1169
+ var import_react16 = require("react");
1090
1170
  var import_jsx_runtime12 = require("react/jsx-runtime");
1091
1171
  var CloseIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime12.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_runtime12.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" }) });
1092
- function Drawer({ open, onClose, title, children, footer, className }) {
1093
- const ref = useDialog(open);
1094
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("dialog", { ref, className: cx("uix-drawer", className), onClose, children: [
1172
+ function Drawer({ open, onClose, title, children, footer, className, ...rest }) {
1173
+ const ref = useDialog(open, onClose);
1174
+ const titleId = (0, import_react16.useId)();
1175
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("dialog", { ref, className: cx("uix-drawer", className), "aria-labelledby": title ? titleId : void 0, ...rest, children: [
1095
1176
  (title != null || onClose) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "uix-drawer__header", children: [
1096
- title && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { fontWeight: 600, fontSize: "var(--uix-text-h3)" }, children: title }),
1177
+ title && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { id: titleId, style: { fontWeight: 600, fontSize: "var(--uix-text-h3)", fontFamily: "inherit", letterSpacing: "inherit", lineHeight: "inherit" }, children: title }),
1097
1178
  onClose && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1098
1179
  "button",
1099
1180
  {
@@ -1110,27 +1191,30 @@ function Drawer({ open, onClose, title, children, footer, className }) {
1110
1191
  }
1111
1192
 
1112
1193
  // src/components/Peek.tsx
1194
+ var import_react17 = require("react");
1113
1195
  var import_jsx_runtime13 = require("react/jsx-runtime");
1114
1196
  var CloseIcon3 = () => /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" }) });
1115
1197
  var ChevronUp = () => /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)("path", { d: "M2 8l4-4 4 4" }) });
1116
1198
  var ChevronDown = () => /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)("path", { d: "M2 4l4 4 4-4" }) });
1117
- function Peek({ open, onClose, title, children, footer, onNavPrev, onNavNext, hint, className }) {
1118
- const ref = useDialog(open);
1199
+ function Peek({ open, onClose, title, children, footer, onNavPrev, onNavNext, hint, className, onClick, ...rest }) {
1200
+ const ref = useDialog(open, onClose);
1119
1201
  const hasNav = onNavPrev != null || onNavNext != null;
1202
+ const titleId = (0, import_react17.useId)();
1120
1203
  const onBackdropClick = (e) => {
1204
+ onClick?.(e);
1121
1205
  if (!onClose || e.target !== e.currentTarget) return;
1122
1206
  const r = e.currentTarget.getBoundingClientRect();
1123
1207
  if (r.width === 0) return;
1124
1208
  const outside = e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
1125
1209
  if (outside) onClose();
1126
1210
  };
1127
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("dialog", { ref, className: cx("uix-peek", className), onClose, onClick: onBackdropClick, children: [
1211
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("dialog", { ref, className: cx("uix-peek", className), "aria-labelledby": title ? titleId : void 0, ...rest, onClick: onBackdropClick, children: [
1128
1212
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "uix-peek__header", children: [
1129
1213
  hasNav && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "uix-peek__nav", children: [
1130
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { className: "uix-peek__navbtn", onClick: onNavPrev, disabled: !onNavPrev, "aria-label": "Previous record", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronUp, {}) }),
1131
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { className: "uix-peek__navbtn", onClick: onNavNext, disabled: !onNavNext, "aria-label": "Next record", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronDown, {}) })
1214
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { className: "uix-peek__navbtn", onClick: () => onNavPrev?.(), "aria-disabled": onNavPrev ? void 0 : true, "aria-label": "Previous record", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronUp, {}) }),
1215
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { className: "uix-peek__navbtn", onClick: () => onNavNext?.(), "aria-disabled": onNavNext ? void 0 : true, "aria-label": "Next record", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronDown, {}) })
1132
1216
  ] }),
1133
- title && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "uix-peek__title", children: title }),
1217
+ title && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "uix-peek__title", id: titleId, children: title }),
1134
1218
  hint && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "uix-peek__hint", children: hint }),
1135
1219
  onClose && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { className: "uix-peek__close", onClick: onClose, "aria-label": "Close preview", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CloseIcon3, {}) })
1136
1220
  ] }),
@@ -1141,12 +1225,12 @@ function Peek({ open, onClose, title, children, footer, onNavPrev, onNavNext, hi
1141
1225
 
1142
1226
  // src/components/Card.tsx
1143
1227
  var import_jsx_runtime14 = require("react/jsx-runtime");
1144
- function Card({ title, subtitle, headerAction, footer, children, className, ...props }) {
1228
+ function Card({ title, titleAs: TitleTag = "div", subtitle, headerAction, footer, children, className, ...props }) {
1145
1229
  const hasHeader = title != null || subtitle != null || headerAction != null;
1146
1230
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: cx("uix-card", className), ...props, children: [
1147
1231
  hasHeader && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "uix-card__header", children: [
1148
1232
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { flex: 1 }, children: [
1149
- title && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "uix-card__title", children: title }),
1233
+ title && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TitleTag, { className: "uix-card__title", children: title }),
1150
1234
  subtitle && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "uix-card__subtitle", children: subtitle })
1151
1235
  ] }),
1152
1236
  headerAction
@@ -1171,12 +1255,12 @@ function CardLink({ title, subtitle, headerAction, footer, children, className,
1171
1255
  }
1172
1256
 
1173
1257
  // src/components/Breadcrumbs.tsx
1174
- var import_react16 = require("react");
1258
+ var import_react18 = require("react");
1175
1259
  var import_jsx_runtime15 = require("react/jsx-runtime");
1176
1260
  function Breadcrumbs({ items, label, separator = "/", className, ...props }) {
1177
1261
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("nav", { "aria-label": label, className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ol", { className: "uix-breadcrumbs", children: items.map((item, index) => {
1178
1262
  const current = item.current ?? index === items.length - 1;
1179
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react16.Fragment, { children: [
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react18.Fragment, { children: [
1180
1264
  index > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { className: "uix-breadcrumbs__sep", "aria-hidden": "true", children: separator }),
1181
1265
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: item.href && !current ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { href: item.href, children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-current": current ? "page" : void 0, children: item.label }) })
1182
1266
  ] }, item.id ?? `${item.href ?? "current"}-${index}`);
@@ -1185,11 +1269,11 @@ function Breadcrumbs({ items, label, separator = "/", className, ...props }) {
1185
1269
 
1186
1270
  // src/components/PageHeader.tsx
1187
1271
  var import_jsx_runtime16 = require("react/jsx-runtime");
1188
- function PageHeader({ eyebrow, title, subtitle, actions, className, ...props }) {
1272
+ function PageHeader({ eyebrow, title, subtitle, actions, as: TitleTag = "h1", className, ...props }) {
1189
1273
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: cx("uix-page-header", className), ...props, children: [
1190
1274
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "uix-page-header__titles", children: [
1191
1275
  eyebrow != null && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "uix-page-header__eyebrow", children: eyebrow }),
1192
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h1", { className: "uix-page-header__title", children: title }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TitleTag, { className: "uix-page-header__title", children: title }),
1193
1277
  subtitle != null && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "uix-page-header__subtitle", children: subtitle })
1194
1278
  ] }),
1195
1279
  actions != null && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "uix-page-header__actions", children: actions })
@@ -1201,7 +1285,7 @@ var import_jsx_runtime17 = require("react/jsx-runtime");
1201
1285
  function DetailLayout({ side, children, className, ...props }) {
1202
1286
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cx("uix-detail", className), ...props, children: [
1203
1287
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "uix-detail__main", children }),
1204
- side != null && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "uix-detail__side", children: side })
1288
+ side != null && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("aside", { className: "uix-detail__side", children: side })
1205
1289
  ] });
1206
1290
  }
1207
1291
 
@@ -1278,11 +1362,11 @@ function ListItem({ title, meta, trail, leading, children, className, ...props }
1278
1362
  }
1279
1363
 
1280
1364
  // src/components/DescriptionList.tsx
1281
- var import_react17 = require("react");
1365
+ var import_react19 = require("react");
1282
1366
  var import_jsx_runtime23 = require("react/jsx-runtime");
1283
1367
  function DescriptionList({ items, children, className, ...props }) {
1284
1368
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("dl", { className: cx("uix-dl", className), ...props, children: [
1285
- items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react17.Fragment, { children: [
1369
+ items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react19.Fragment, { children: [
1286
1370
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("dt", { children: item.term }),
1287
1371
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("dd", { children: item.description })
1288
1372
  ] }, item.id ?? i)),
@@ -1297,22 +1381,81 @@ function DescriptionItem({ term, children }) {
1297
1381
  }
1298
1382
 
1299
1383
  // src/components/Tabs.tsx
1300
- var import_react18 = require("react");
1384
+ var import_react20 = require("react");
1301
1385
  var import_jsx_runtime24 = require("react/jsx-runtime");
1302
- var TabsCtx = (0, import_react18.createContext)({});
1386
+ var TabsCtx = (0, import_react20.createContext)({});
1303
1387
  function Tabs({ variant = "line", value, onChange, children, className }) {
1304
- const contextValue = (0, import_react18.useMemo)(() => ({ value, onChange }), [value, onChange]);
1305
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TabsCtx.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
1388
+ const baseId = (0, import_react20.useId)();
1389
+ const listRef = (0, import_react20.useRef)(null);
1390
+ const contextValue = (0, import_react20.useMemo)(() => ({ value, onChange, baseId }), [value, onChange, baseId]);
1391
+ const kids = import_react20.Children.toArray(children);
1392
+ const panels = kids.filter((k) => (0, import_react20.isValidElement)(k) && k.type === TabPanel);
1393
+ const tabs = kids.filter((k) => !((0, import_react20.isValidElement)(k) && k.type === TabPanel));
1394
+ (0, import_react20.useEffect)(() => {
1395
+ const list = listRef.current;
1396
+ if (!list) return;
1397
+ const all = Array.from(list.querySelectorAll('[role="tab"]'));
1398
+ const enabled = all.filter((t) => !t.matches(":disabled"));
1399
+ const active = enabled.find((t) => t.getAttribute("aria-selected") === "true") ?? enabled[0];
1400
+ for (const t of all) t.tabIndex = t === active ? 0 : -1;
1401
+ });
1402
+ const onKeyDown = (e) => {
1403
+ const items = Array.from(
1404
+ listRef.current?.querySelectorAll('[role="tab"]:not(:disabled)') ?? []
1405
+ );
1406
+ const cur = e.target.closest('[role="tab"]');
1407
+ const idx = cur ? items.indexOf(cur) : -1;
1408
+ if (idx < 0 || items.length === 0) return;
1409
+ let next;
1410
+ switch (e.key) {
1411
+ case "ArrowRight":
1412
+ next = (idx + 1) % items.length;
1413
+ break;
1414
+ case "ArrowLeft":
1415
+ next = (idx - 1 + items.length) % items.length;
1416
+ break;
1417
+ case "Home":
1418
+ next = 0;
1419
+ break;
1420
+ case "End":
1421
+ next = items.length - 1;
1422
+ break;
1423
+ default:
1424
+ return;
1425
+ }
1426
+ e.preventDefault();
1427
+ const target = items[next];
1428
+ if (target) {
1429
+ target.focus();
1430
+ target.click();
1431
+ }
1432
+ };
1433
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(TabsCtx.Provider, { value: contextValue, children: [
1434
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1435
+ "div",
1436
+ {
1437
+ ref: listRef,
1438
+ role: "tablist",
1439
+ className: cx("uix-tabs", `uix-tabs--${variant}`, className),
1440
+ onKeyDown,
1441
+ children: tabs
1442
+ }
1443
+ ),
1444
+ panels
1445
+ ] });
1306
1446
  }
1307
1447
  function Tab({ value, children, disabled, className, onClick, ...props }) {
1308
- const ctx = (0, import_react18.useContext)(TabsCtx);
1448
+ const ctx = (0, import_react20.useContext)(TabsCtx);
1309
1449
  const selected = ctx.value === value;
1310
1450
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1311
1451
  "button",
1312
1452
  {
1313
1453
  role: "tab",
1454
+ id: ctx.baseId ? `${ctx.baseId}-tab-${value}` : void 0,
1455
+ "aria-controls": ctx.baseId ? `${ctx.baseId}-panel-${value}` : void 0,
1314
1456
  className: cx("uix-tab", className),
1315
1457
  "aria-selected": selected,
1458
+ tabIndex: selected ? 0 : -1,
1316
1459
  disabled,
1317
1460
  onClick: (e) => {
1318
1461
  ctx.onChange?.(value);
@@ -1323,9 +1466,24 @@ function Tab({ value, children, disabled, className, onClick, ...props }) {
1323
1466
  }
1324
1467
  );
1325
1468
  }
1469
+ function TabPanel({ value, children, ...props }) {
1470
+ const ctx = (0, import_react20.useContext)(TabsCtx);
1471
+ if (ctx.value !== value) return null;
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1473
+ "div",
1474
+ {
1475
+ role: "tabpanel",
1476
+ id: ctx.baseId ? `${ctx.baseId}-panel-${value}` : void 0,
1477
+ "aria-labelledby": ctx.baseId ? `${ctx.baseId}-tab-${value}` : void 0,
1478
+ tabIndex: 0,
1479
+ ...props,
1480
+ children
1481
+ }
1482
+ );
1483
+ }
1326
1484
 
1327
1485
  // src/components/AppShell.tsx
1328
- var import_react19 = require("react");
1486
+ var import_react21 = require("react");
1329
1487
  var import_jsx_runtime25 = require("react/jsx-runtime");
1330
1488
  function AppShell({
1331
1489
  nav,
@@ -1333,6 +1491,7 @@ function AppShell({
1333
1491
  focus,
1334
1492
  onExitFocus,
1335
1493
  mainBleed,
1494
+ mainId = "uix-main",
1336
1495
  sidebar,
1337
1496
  topbar,
1338
1497
  children,
@@ -1340,7 +1499,7 @@ function AppShell({
1340
1499
  ...props
1341
1500
  }) {
1342
1501
  const navState = nav ?? (collapsed ? "rail" : "full");
1343
- (0, import_react19.useEffect)(() => {
1502
+ (0, import_react21.useEffect)(() => {
1344
1503
  if (!focus || !onExitFocus) return;
1345
1504
  const onKey = (e) => {
1346
1505
  if (e.key === "Escape") onExitFocus();
@@ -1357,9 +1516,10 @@ function AppShell({
1357
1516
  "data-focus": focus || void 0,
1358
1517
  ...props,
1359
1518
  children: [
1519
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("a", { className: "uix-skiplink", href: `#${mainId}`, children: "Skip to content" }),
1360
1520
  sidebar && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "uix-shell__sidebar", children: sidebar }),
1361
1521
  topbar && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "uix-shell__topbar", children: topbar }),
1362
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("main", { className: cx("uix-shell__main", mainBleed && "uix-shell__main--bleed"), children }),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("main", { id: mainId, tabIndex: -1, className: cx("uix-shell__main", mainBleed && "uix-shell__main--bleed"), children }),
1363
1523
  focus && onExitFocus && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "uix-shell__focus-exit", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: onExitFocus, children: "Esc \xB7 Exit focus" }) })
1364
1524
  ]
1365
1525
  }
@@ -1367,7 +1527,7 @@ function AppShell({
1367
1527
  }
1368
1528
 
1369
1529
  // src/components/Sidebar.tsx
1370
- var import_react20 = require("react");
1530
+ var import_react22 = require("react");
1371
1531
  var import_jsx_runtime26 = require("react/jsx-runtime");
1372
1532
  var ChevronIcon = () => /* @__PURE__ */ (0, import_jsx_runtime26.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_runtime26.jsx)("path", { d: "M2 4l4 4 4-4" }) });
1373
1533
  function Sidebar({ collapsed, nav, onToggle, brand, toggleIcon, children, expandLabel = "Expand sidebar", collapseLabel = "Collapse sidebar", className, ...props }) {
@@ -1423,12 +1583,12 @@ function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...p
1423
1583
  );
1424
1584
  }
1425
1585
  function NavGroup({ icon, label, children, defaultExpanded = true, expanded: controlledExpanded, onExpandedChange }) {
1426
- const [internalExpanded, setInternalExpanded] = (0, import_react20.useState)(defaultExpanded);
1586
+ const [internalExpanded, setInternalExpanded] = (0, import_react22.useState)(defaultExpanded);
1427
1587
  const expanded = controlledExpanded ?? internalExpanded;
1428
- const panelId = (0, import_react20.useId)();
1429
- const triggerRef = (0, import_react20.useRef)(null);
1430
- const panelRef = (0, import_react20.useRef)(null);
1431
- (0, import_react20.useEffect)(() => {
1588
+ const panelId = (0, import_react22.useId)();
1589
+ const triggerRef = (0, import_react22.useRef)(null);
1590
+ const panelRef = (0, import_react22.useRef)(null);
1591
+ (0, import_react22.useEffect)(() => {
1432
1592
  panelRef.current?.toggleAttribute("inert", !expanded);
1433
1593
  if (!expanded && panelRef.current?.contains(document.activeElement)) triggerRef.current?.focus();
1434
1594
  }, [expanded]);
@@ -1507,7 +1667,7 @@ function StarButton({
1507
1667
  }
1508
1668
 
1509
1669
  // src/components/NavFavourites.tsx
1510
- var import_react21 = require("react");
1670
+ var import_react23 = require("react");
1511
1671
  var import_jsx_runtime28 = require("react/jsx-runtime");
1512
1672
  var ChevronDownIcon = () => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1513
1673
  "svg",
@@ -1570,13 +1730,14 @@ function NavFavourites({
1570
1730
  emptyReason,
1571
1731
  className
1572
1732
  }) {
1573
- const regionId = (0, import_react21.useId)();
1574
- const headerRef = (0, import_react21.useRef)(null);
1575
- const regionRef = (0, import_react21.useRef)(null);
1576
- const focusAfterRef = (0, import_react21.useRef)(null);
1577
- const triggerRefs = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
1578
- const order = (0, import_react21.useMemo)(() => items.map((item) => item.id), [items]);
1579
- (0, import_react21.useEffect)(() => {
1733
+ const regionId = (0, import_react23.useId)();
1734
+ const headerRef = (0, import_react23.useRef)(null);
1735
+ const regionRef = (0, import_react23.useRef)(null);
1736
+ const [announcement, setAnnouncement] = (0, import_react23.useState)("");
1737
+ const focusAfterRef = (0, import_react23.useRef)(null);
1738
+ const triggerRefs = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
1739
+ const order = (0, import_react23.useMemo)(() => items.map((item) => item.id), [items]);
1740
+ (0, import_react23.useEffect)(() => {
1580
1741
  const id = focusAfterRef.current;
1581
1742
  if (!id) return;
1582
1743
  focusAfterRef.current = null;
@@ -1593,7 +1754,7 @@ function NavFavourites({
1593
1754
  }
1594
1755
  onToggleCollapsed?.();
1595
1756
  };
1596
- const move = (0, import_react21.useCallback)((id, dir) => {
1757
+ const move = (0, import_react23.useCallback)((id, dir) => {
1597
1758
  const idx = order.indexOf(id);
1598
1759
  const next = idx + dir;
1599
1760
  if (idx < 0 || next < 0 || next >= order.length) return;
@@ -1602,19 +1763,22 @@ function NavFavourites({
1602
1763
  if (it === void 0) return;
1603
1764
  reordered.splice(next, 0, it);
1604
1765
  focusAfterRef.current = id;
1766
+ const moved = items.find((i) => i.id === id);
1767
+ if (moved) setAnnouncement(`${moved.label} moved to position ${next + 1} of ${order.length}`);
1605
1768
  onReorder(reordered);
1606
1769
  }, [onReorder, order]);
1607
- const remove = (0, import_react21.useCallback)((id) => {
1770
+ const remove = (0, import_react23.useCallback)((id) => {
1608
1771
  const idx = order.indexOf(id);
1609
1772
  const neighbour = order[idx + 1] ?? order[idx - 1] ?? null;
1610
1773
  focusAfterRef.current = neighbour;
1611
1774
  onRemove(id);
1612
1775
  }, [onRemove, order]);
1613
- const registerTrigger = (0, import_react21.useCallback)((id, element) => {
1776
+ const registerTrigger = (0, import_react23.useCallback)((id, element) => {
1614
1777
  if (element) triggerRefs.current.set(id, element);
1615
1778
  else triggerRefs.current.delete(id);
1616
1779
  }, []);
1617
1780
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: cx("uix-favourites", className), children: [
1781
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { role: "status", className: "uix-visually-hidden", children: announcement }),
1618
1782
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1619
1783
  "button",
1620
1784
  {
@@ -1661,7 +1825,7 @@ function NavFavourites({
1661
1825
  )
1662
1826
  ] });
1663
1827
  }
1664
- var FavouriteRow = (0, import_react21.memo)(function FavouriteRow2({
1828
+ var FavouriteRow = (0, import_react23.memo)(function FavouriteRow2({
1665
1829
  item,
1666
1830
  isFirst,
1667
1831
  isLast,
@@ -1670,17 +1834,17 @@ var FavouriteRow = (0, import_react21.memo)(function FavouriteRow2({
1670
1834
  onRemove,
1671
1835
  registerTrigger
1672
1836
  }) {
1673
- const [open, setOpen] = (0, import_react21.useState)(false);
1674
- const menuId = (0, import_react21.useId)();
1675
- const triggerRef = (0, import_react21.useRef)(null);
1676
- const itemRefs = (0, import_react21.useRef)([]);
1837
+ const [open, setOpen] = (0, import_react23.useState)(false);
1838
+ const menuId = (0, import_react23.useId)();
1839
+ const triggerRef = (0, import_react23.useRef)(null);
1840
+ const itemRefs = (0, import_react23.useRef)([]);
1677
1841
  const optionsTemplate = labels.options ?? "{label} favourite options";
1678
1842
  const actions = [
1679
1843
  ...isFirst ? [] : [{ label: labels.moveUp, onSelect: () => onMove(item.id, -1) }],
1680
1844
  ...isLast ? [] : [{ label: labels.moveDown, onSelect: () => onMove(item.id, 1) }],
1681
1845
  { label: labels.remove.replace("{label}", item.label), onSelect: () => onRemove(item.id) }
1682
1846
  ];
1683
- (0, import_react21.useEffect)(() => {
1847
+ (0, import_react23.useEffect)(() => {
1684
1848
  if (open) itemRefs.current[0]?.focus();
1685
1849
  }, [open]);
1686
1850
  const closeAndReturnFocus = () => {
@@ -1696,6 +1860,12 @@ var FavouriteRow = (0, import_react21.memo)(function FavouriteRow2({
1696
1860
  } else if (e.key === "ArrowUp") {
1697
1861
  e.preventDefault();
1698
1862
  menuItems[(currentIdx - 1 + menuItems.length) % menuItems.length]?.focus();
1863
+ } else if (e.key === "Home") {
1864
+ e.preventDefault();
1865
+ menuItems[0]?.focus();
1866
+ } else if (e.key === "End") {
1867
+ e.preventDefault();
1868
+ menuItems[menuItems.length - 1]?.focus();
1699
1869
  } else if (e.key === "Escape") {
1700
1870
  e.preventDefault();
1701
1871
  closeAndReturnFocus();
@@ -1754,6 +1924,7 @@ var FavouriteRow = (0, import_react21.memo)(function FavouriteRow2({
1754
1924
  {
1755
1925
  id: menuId,
1756
1926
  role: "menu",
1927
+ "aria-label": optionsTemplate.replace("{label}", item.label),
1757
1928
  onKeyDown: onMenuKeyDown,
1758
1929
  className: "uix-favourites__menu",
1759
1930
  children: actions.map((action, i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -1785,61 +1956,108 @@ var FavouriteRow = (0, import_react21.memo)(function FavouriteRow2({
1785
1956
  // src/components/Alert.tsx
1786
1957
  var import_jsx_runtime29 = require("react/jsx-runtime");
1787
1958
  function Alert({ tone, title, icon, children, className, ...props }) {
1788
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: cx("uix-alert", tone && tone !== "neutral" && `uix-alert--${tone}`, className), ...props, children: [
1789
- icon && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uix-alert__icon", children: icon }),
1790
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { children: [
1791
- title && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uix-alert__title", children: title }),
1792
- children && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uix-alert__body", children })
1793
- ] })
1794
- ] });
1959
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1960
+ "div",
1961
+ {
1962
+ className: cx("uix-alert", tone && tone !== "neutral" && `uix-alert--${tone}`, className),
1963
+ role: tone === "danger" || tone === "warning" ? "alert" : "status",
1964
+ ...props,
1965
+ children: [
1966
+ icon && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uix-alert__icon", children: icon }),
1967
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { children: [
1968
+ title && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uix-alert__title", children: title }),
1969
+ children && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uix-alert__body", children })
1970
+ ] })
1971
+ ]
1972
+ }
1973
+ );
1795
1974
  }
1796
1975
 
1797
1976
  // src/components/Spinner.tsx
1798
1977
  var import_jsx_runtime30 = require("react/jsx-runtime");
1799
- function Spinner({ size = "md", accent, className, ...props }) {
1978
+ function Spinner({ size = "md", accent, label, className, ...props }) {
1800
1979
  return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1801
1980
  "span",
1802
1981
  {
1803
1982
  className: cx("uix-spinner", accent && "uix-spinner--accent", size === "lg" && "uix-spinner--lg", className),
1804
1983
  role: "status",
1805
- "aria-label": "Loading",
1806
- ...props
1984
+ ...props,
1985
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "uix-visually-hidden", children: label ?? "Loading\u2026" })
1807
1986
  }
1808
1987
  );
1809
1988
  }
1810
1989
 
1811
1990
  // src/components/Toast.tsx
1991
+ var import_react24 = require("react");
1812
1992
  var import_jsx_runtime31 = require("react/jsx-runtime");
1993
+ var ToasterContext = (0, import_react24.createContext)(null);
1813
1994
  function Toast({ title, message, tone, icon, onClose, leaving, className, ...props }) {
1814
1995
  const assertive = tone === "danger";
1996
+ const toaster = (0, import_react24.useContext)(ToasterContext);
1997
+ const bodyRef = (0, import_react24.useRef)(null);
1998
+ (0, import_react24.useEffect)(() => {
1999
+ if (assertive || !toaster) return;
2000
+ const text = bodyRef.current?.textContent?.trim();
2001
+ if (text) toaster.announce(text);
2002
+ }, []);
2003
+ const handleClose = (e) => {
2004
+ const btn = e.currentTarget;
2005
+ if (document.activeElement === btn) {
2006
+ const toasterEl = btn.closest(".uix-toaster");
2007
+ if (toasterEl) {
2008
+ const closes = Array.from(toasterEl.querySelectorAll(".uix-toast__close"));
2009
+ const i = closes.indexOf(btn);
2010
+ (closes[i + 1] ?? closes[i - 1])?.focus();
2011
+ }
2012
+ }
2013
+ onClose?.();
2014
+ };
2015
+ const ownLiveRegion = assertive || !toaster;
1815
2016
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1816
2017
  "div",
1817
2018
  {
1818
2019
  className: cx("uix-toast", tone && `uix-toast--${tone}`, className),
1819
2020
  "data-leaving": leaving || void 0,
1820
- role: assertive ? "alert" : "status",
1821
- "aria-live": assertive ? "assertive" : "polite",
2021
+ role: ownLiveRegion ? assertive ? "alert" : "status" : void 0,
2022
+ "aria-live": ownLiveRegion ? assertive ? "assertive" : "polite" : void 0,
1822
2023
  ...props,
1823
2024
  children: [
1824
2025
  icon && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "uix-toast__icon", "aria-hidden": "true", children: icon }),
1825
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "uix-toast__body", children: [
2026
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "uix-toast__body", ref: bodyRef, children: [
1826
2027
  title && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "uix-toast__title", children: title }),
1827
2028
  message && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "uix-toast__msg", children: message })
1828
2029
  ] }),
1829
- onClose && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("button", { className: "uix-toast__close", onClick: onClose, "aria-label": "Dismiss", children: "\xD7" })
2030
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("button", { className: "uix-toast__close", onClick: handleClose, "aria-label": "Dismiss", children: "\xD7" })
1830
2031
  ]
1831
2032
  }
1832
2033
  );
1833
2034
  }
1834
2035
  function Toaster({ children, className, ...props }) {
1835
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: cx("uix-toaster", className), ...props, children });
2036
+ const [announced, setAnnounced] = (0, import_react24.useState)("");
2037
+ const clearTimer = (0, import_react24.useRef)(null);
2038
+ const announce = (0, import_react24.useCallback)((text) => {
2039
+ setAnnounced((cur) => cur ? `${cur} ${text}` : text);
2040
+ if (clearTimer.current != null) clearTimeout(clearTimer.current);
2041
+ clearTimer.current = window.setTimeout(() => setAnnounced(""), 3e3);
2042
+ }, []);
2043
+ (0, import_react24.useEffect)(() => () => {
2044
+ if (clearTimer.current != null) clearTimeout(clearTimer.current);
2045
+ }, []);
2046
+ const ctx = (0, import_react24.useMemo)(() => ({ announce }), [announce]);
2047
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: cx("uix-toaster", className), role: "region", "aria-label": "Notifications", ...props, children: [
2048
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
2049
+ children
2050
+ ] }) });
1836
2051
  }
1837
2052
 
1838
2053
  // src/components/Table.tsx
1839
- var import_react22 = require("react");
2054
+ var import_react25 = require("react");
1840
2055
  var import_jsx_runtime32 = require("react/jsx-runtime");
1841
- var TableWrap = (0, import_react22.forwardRef)(function TableWrap2({ children, className, ...props }, ref) {
1842
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref, className: cx("uix-table-wrap", className), ...props, children });
2056
+ var TableWrap = (0, import_react25.forwardRef)(function TableWrap2({ announcement, children, className, ...props }, ref) {
2057
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { ref, className: cx("uix-table-wrap", className), ...props, children: [
2058
+ announcement != null && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { role: "status", className: "uix-visually-hidden", children: announcement }),
2059
+ children
2060
+ ] });
1843
2061
  });
1844
2062
  function Table({
1845
2063
  density = "standard",
@@ -1878,9 +2096,9 @@ function Th({ sortable, sortDirection, sortOrder, onSort, className, children, o
1878
2096
  "data-sort": sortable || void 0,
1879
2097
  "data-sort-order": sortOrder != null && sortOrder > 0 ? sortOrder : void 0,
1880
2098
  "aria-sort": sortDirection && sortDirection !== "none" ? sortDirection : void 0,
1881
- onClick: sortable ? onSort ?? onClick : onClick,
2099
+ onClick: sortable && onSort ? void 0 : onClick,
1882
2100
  ...props,
1883
- children
2101
+ children: sortable ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { type: "button", className: "uix-th__sortbtn", onClick: onSort, children }) : children
1884
2102
  }
1885
2103
  );
1886
2104
  }
@@ -1892,7 +2110,7 @@ function Tr({ selected, pinned, className, children, ...props }) {
1892
2110
  "tr",
1893
2111
  {
1894
2112
  className,
1895
- "aria-selected": selected || void 0,
2113
+ "data-selected": selected || void 0,
1896
2114
  "data-pinned": pinned || void 0,
1897
2115
  ...props,
1898
2116
  children
@@ -1915,13 +2133,14 @@ function RowActions({ children, className, ...props }) {
1915
2133
  function RowAction({ children, className, type = "button", ...props }) {
1916
2134
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { type, className: cx("uix-rowact", className), ...props, children });
1917
2135
  }
1918
- function ExpandToggle({ expanded, className, type = "button", ...props }) {
2136
+ function ExpandToggle({ expanded, controls, className, type = "button", ...props }) {
1919
2137
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1920
2138
  "button",
1921
2139
  {
1922
2140
  type,
1923
2141
  className: cx("uix-table__expand", className),
1924
2142
  "aria-expanded": expanded,
2143
+ "aria-controls": controls,
1925
2144
  "aria-label": expanded ? "Collapse row" : "Expand row",
1926
2145
  ...props,
1927
2146
  children: /* @__PURE__ */ (0, import_jsx_runtime32.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_runtime32.jsx)("path", { d: "M6 4l4 4-4 4" }) })
@@ -2012,15 +2231,17 @@ function StatusPill({ tone = "neutral", treatment = "filled", dot, children, cla
2012
2231
 
2013
2232
  // src/components/Progress.tsx
2014
2233
  var import_jsx_runtime35 = require("react/jsx-runtime");
2015
- function Progress({ value = 0, max = 100, indeterminate, className, ...props }) {
2016
- const pct = Math.max(0, Math.min(100, value / (max || 100) * 100));
2234
+ function Progress({ value = 0, max = 100, indeterminate, label, className, ...props }) {
2235
+ const clamped = Math.max(0, Math.min(max || 100, value));
2236
+ const pct = clamped / (max || 100) * 100;
2017
2237
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2018
2238
  "div",
2019
2239
  {
2020
2240
  role: "progressbar",
2021
2241
  "aria-valuemin": 0,
2022
2242
  "aria-valuemax": max,
2023
- "aria-valuenow": indeterminate ? void 0 : value,
2243
+ "aria-valuenow": indeterminate ? void 0 : clamped,
2244
+ "aria-label": label,
2024
2245
  className: cx("uix-progress", indeterminate && "uix-progress--indeterminate", className),
2025
2246
  ...props,
2026
2247
  children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "uix-progress__bar", style: indeterminate ? void 0 : { width: `${pct}%` } })
@@ -2030,7 +2251,13 @@ function Progress({ value = 0, max = 100, indeterminate, className, ...props })
2030
2251
 
2031
2252
  // src/components/Meter.tsx
2032
2253
  var import_jsx_runtime36 = require("react/jsx-runtime");
2033
- function Meter({ value = 0, tone, className, ...props }) {
2254
+ var toneText = {
2255
+ warning: "warning",
2256
+ danger: "critical",
2257
+ attention: "needs attention",
2258
+ overdue: "overdue"
2259
+ };
2260
+ function Meter({ value = 0, tone, label, className, ...props }) {
2034
2261
  const pct = Math.max(0, Math.min(100, value));
2035
2262
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2036
2263
  "div",
@@ -2039,6 +2266,8 @@ function Meter({ value = 0, tone, className, ...props }) {
2039
2266
  "aria-valuenow": pct,
2040
2267
  "aria-valuemin": 0,
2041
2268
  "aria-valuemax": 100,
2269
+ "aria-valuetext": tone && tone !== "success" ? `${pct}%, ${toneText[tone]}` : void 0,
2270
+ "aria-label": label,
2042
2271
  className: cx("uix-meter", className),
2043
2272
  ...props,
2044
2273
  children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -2063,22 +2292,34 @@ function Stat({ label, value, icon, meta, trend, className, ...props }) {
2063
2292
  ] }),
2064
2293
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "uix-stat__value", children: value }),
2065
2294
  (meta != null || trend != null) && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "uix-stat__meta", children: [
2066
- trend != null && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: cx("uix-stat__trend", `uix-stat__trend--${trend.direction}`), children: trend.label }),
2295
+ trend != null && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: cx("uix-stat__trend", `uix-stat__trend--${trend.direction}`), children: [
2296
+ /* @__PURE__ */ (0, import_jsx_runtime37.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_runtime37.jsx)("path", { d: "M12 19V5m-6 6 6-6 6 6" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("path", { d: "M12 5v14m6-6-6 6-6-6" }) }),
2297
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "uix-visually-hidden", children: trend.direction === "up" ? "up" : "down" }),
2298
+ trend.label
2299
+ ] }),
2067
2300
  meta
2068
2301
  ] })
2069
2302
  ] });
2070
2303
  }
2071
2304
 
2072
2305
  // src/components/Tooltip.tsx
2073
- var import_react23 = require("react");
2306
+ var import_react26 = require("react");
2074
2307
  var import_jsx_runtime38 = require("react/jsx-runtime");
2075
2308
  function Tooltip({ label, placement = "top", className, children, ...props }) {
2076
- const triggerRef = (0, import_react23.useRef)(null);
2077
- const bubbleRef = (0, import_react23.useRef)(null);
2078
- const [open, setOpen] = (0, import_react23.useState)(false);
2079
- const id = (0, import_react23.useId)();
2309
+ const triggerRef = (0, import_react26.useRef)(null);
2310
+ const bubbleRef = (0, import_react26.useRef)(null);
2311
+ const closeTimer = (0, import_react26.useRef)(null);
2312
+ const [open, setOpen] = (0, import_react26.useState)(false);
2313
+ const id = (0, import_react26.useId)();
2080
2314
  useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
2315
+ const cancelClose = () => {
2316
+ if (closeTimer.current != null) {
2317
+ clearTimeout(closeTimer.current);
2318
+ closeTimer.current = null;
2319
+ }
2320
+ };
2081
2321
  const show = () => {
2322
+ cancelClose();
2082
2323
  const b = bubbleRef.current;
2083
2324
  if (b && !b.matches(":popover-open")) {
2084
2325
  try {
@@ -2089,6 +2330,7 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
2089
2330
  setOpen(true);
2090
2331
  };
2091
2332
  const hide = () => {
2333
+ cancelClose();
2092
2334
  const b = bubbleRef.current;
2093
2335
  if (b && b.matches(":popover-open")) {
2094
2336
  try {
@@ -2098,7 +2340,20 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
2098
2340
  }
2099
2341
  setOpen(false);
2100
2342
  };
2101
- (0, import_react23.useEffect)(() => () => {
2343
+ const scheduleHide = () => {
2344
+ cancelClose();
2345
+ closeTimer.current = window.setTimeout(hide, 100);
2346
+ };
2347
+ (0, import_react26.useEffect)(() => {
2348
+ if (!open) return;
2349
+ const onKey = (e) => {
2350
+ if (e.key === "Escape") hide();
2351
+ };
2352
+ document.addEventListener("keydown", onKey);
2353
+ return () => document.removeEventListener("keydown", onKey);
2354
+ }, [open]);
2355
+ (0, import_react26.useEffect)(() => () => {
2356
+ if (closeTimer.current != null) clearTimeout(closeTimer.current);
2102
2357
  const b = bubbleRef.current;
2103
2358
  if (b && b.matches(":popover-open")) {
2104
2359
  try {
@@ -2108,14 +2363,19 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
2108
2363
  }
2109
2364
  }, []);
2110
2365
  const bubbleAttr = { popover: "manual" };
2366
+ const single = (0, import_react26.isValidElement)(children);
2367
+ const child = single ? (0, import_react26.cloneElement)(children, {
2368
+ "aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" ")
2369
+ }) : children;
2111
2370
  return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
2112
2371
  "span",
2113
2372
  {
2114
2373
  ref: triggerRef,
2115
2374
  className: cx("uix-tooltip", className),
2116
- "aria-describedby": id,
2375
+ "aria-describedby": single ? void 0 : id,
2376
+ tabIndex: single ? void 0 : 0,
2117
2377
  onMouseEnter: show,
2118
- onMouseLeave: hide,
2378
+ onMouseLeave: scheduleHide,
2119
2379
  onFocus: show,
2120
2380
  onBlur: hide,
2121
2381
  onKeyDown: (e) => {
@@ -2123,8 +2383,20 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
2123
2383
  },
2124
2384
  ...props,
2125
2385
  children: [
2126
- children,
2127
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { ref: bubbleRef, id, role: "tooltip", className: "uix-tooltip__bubble", ...bubbleAttr, children: label })
2386
+ child,
2387
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2388
+ "span",
2389
+ {
2390
+ ref: bubbleRef,
2391
+ id,
2392
+ role: "tooltip",
2393
+ className: "uix-tooltip__bubble",
2394
+ onMouseEnter: cancelClose,
2395
+ onMouseLeave: scheduleHide,
2396
+ ...bubbleAttr,
2397
+ children: label
2398
+ }
2399
+ )
2128
2400
  ]
2129
2401
  }
2130
2402
  );
@@ -2184,11 +2456,17 @@ function Skeleton({ variant = "block", width, height, className, style, ...props
2184
2456
  );
2185
2457
  }
2186
2458
  function LoadingState({ rows = 3, density = "comfortable", label, className, ...props }) {
2187
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: cx("uix-stack", density === "compact" && "uix-stack--compact", className), role: "status", "aria-busy": "true", "aria-label": label, ...props, children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Skeleton, { variant: "text", width: `${92 - i % 3 * 14}%` }, i)) });
2459
+ return (
2460
+ // no aria-busy: busy=true tells AT the region isn't ready and suppresses the announcement (UIX-A11Y-4)
2461
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: cx("uix-stack", density === "compact" && "uix-stack--compact", className), role: "status", ...props, children: [
2462
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "uix-visually-hidden", children: label ?? "Loading" }),
2463
+ Array.from({ length: rows }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Skeleton, { variant: "text", width: `${92 - i % 3 * 14}%` }, i))
2464
+ ] })
2465
+ );
2188
2466
  }
2189
2467
 
2190
2468
  // src/components/RelativeTime.tsx
2191
- var import_react24 = require("react");
2469
+ var import_react27 = require("react");
2192
2470
 
2193
2471
  // src/relative-time-model.ts
2194
2472
  function parseDateValue(value) {
@@ -2251,12 +2529,12 @@ function RelativeTime({
2251
2529
  }) {
2252
2530
  const interval = refreshInterval === false ? 0 : Math.max(0, refreshInterval);
2253
2531
  const fixedNow = now == null ? void 0 : new Date(now).getTime();
2254
- const subscribe = (0, import_react24.useCallback)((listener) => fixedNow == null ? subscribeRelativeClock(interval, listener) : () => {
2532
+ const subscribe = (0, import_react27.useCallback)((listener) => fixedNow == null ? subscribeRelativeClock(interval, listener) : () => {
2255
2533
  }, [fixedNow, interval]);
2256
- const getSnapshot = (0, import_react24.useCallback)(() => fixedNow ?? readRelativeClock(interval), [fixedNow, interval]);
2257
- const getServerSnapshot = (0, import_react24.useCallback)(() => fixedNow ?? Date.now(), [fixedNow]);
2258
- const clock = (0, import_react24.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
2259
- const date = (0, import_react24.useMemo)(() => parseDateValue(value), [value]);
2534
+ const getSnapshot = (0, import_react27.useCallback)(() => fixedNow ?? readRelativeClock(interval), [fixedNow, interval]);
2535
+ const getServerSnapshot = (0, import_react27.useCallback)(() => fixedNow ?? Date.now(), [fixedNow]);
2536
+ const clock = (0, import_react27.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
2537
+ const date = (0, import_react27.useMemo)(() => parseDateValue(value), [value]);
2260
2538
  if (!date) return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("time", { "data-invalid": "true", suppressHydrationWarning: true, ...props, children: invalidLabel });
2261
2539
  const current = new Date(clock);
2262
2540
  const content = formatter ? formatter({ date, now: current, locale, timeZone }) : (() => {
@@ -2305,7 +2583,8 @@ var import_jsx_runtime43 = require("react/jsx-runtime");
2305
2583
  function Avatar({ size = "md", src, alt, children, status, className, ...props }) {
2306
2584
  return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: cx("uix-avatar", size !== "md" && `uix-avatar--${size}`, className), ...props, children: [
2307
2585
  src ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("img", { src, alt: alt ?? "" }) : children,
2308
- status && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "uix-avatar__status" })
2586
+ status && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "uix-avatar__status", "aria-hidden": "true" }),
2587
+ status && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "uix-visually-hidden", children: "online" })
2309
2588
  ] });
2310
2589
  }
2311
2590
  function AvatarGroup({ children, className, ...props }) {
@@ -2325,7 +2604,7 @@ function UserChip({ avatar, name, sub, className, children, ...props }) {
2325
2604
  // src/components/Comments.tsx
2326
2605
  var import_jsx_runtime44 = require("react/jsx-runtime");
2327
2606
  function Comments({ children, className, ...props }) {
2328
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: cx("uix-comments", className), ...props, children });
2607
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { role: "log", className: cx("uix-comments", className), ...props, children });
2329
2608
  }
2330
2609
  function Comment({ avatar, author, meta, children, className, ...props }) {
2331
2610
  return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: cx("uix-comment", className), ...props, children: [
@@ -2342,20 +2621,25 @@ function Comment({ avatar, author, meta, children, className, ...props }) {
2342
2621
  }
2343
2622
 
2344
2623
  // src/components/Composer.tsx
2624
+ var import_react28 = require("react");
2345
2625
  var import_jsx_runtime45 = require("react/jsx-runtime");
2346
- function Composer({ children, className, ...props }) {
2347
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: cx("uix-composer", className), ...props, children });
2626
+ function Composer({ children, className, label = "Add a comment", ...props }) {
2627
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: cx("uix-composer", className), ...props, children: import_react28.Children.map(children, (child) => {
2628
+ if (!(0, import_react28.isValidElement)(child) || child.type !== "textarea") return child;
2629
+ const p = child.props;
2630
+ return p["aria-label"] || p["aria-labelledby"] || p.id ? child : (0, import_react28.cloneElement)(child, { "aria-label": label });
2631
+ }) });
2348
2632
  }
2349
2633
  function ComposerBar({ children, className, ...props }) {
2350
2634
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: cx("uix-composer__bar", className), ...props, children });
2351
2635
  }
2352
2636
 
2353
2637
  // src/components/Segmented.tsx
2354
- var import_react25 = require("react");
2638
+ var import_react29 = require("react");
2355
2639
  var import_jsx_runtime46 = require("react/jsx-runtime");
2356
- var SegmentedCtx = (0, import_react25.createContext)({});
2640
+ var SegmentedCtx = (0, import_react29.createContext)({});
2357
2641
  function Segmented({ value, onChange, children, className, ...props }) {
2358
- const contextValue = (0, import_react25.useMemo)(() => ({ value, onChange }), [value, onChange]);
2642
+ const contextValue = (0, import_react29.useMemo)(() => ({ value, onChange }), [value, onChange]);
2359
2643
  return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
2360
2644
  }
2361
2645
  function SegmentedOption({
@@ -2365,7 +2649,7 @@ function SegmentedOption({
2365
2649
  onClick,
2366
2650
  ...props
2367
2651
  }) {
2368
- const ctx = (0, import_react25.useContext)(SegmentedCtx);
2652
+ const ctx = (0, import_react29.useContext)(SegmentedCtx);
2369
2653
  const selected = ctx.value === value;
2370
2654
  return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2371
2655
  "button",
@@ -2384,12 +2668,12 @@ function SegmentedOption({
2384
2668
  }
2385
2669
 
2386
2670
  // src/components/TableControls.tsx
2387
- var import_react26 = require("react");
2671
+ var import_react30 = require("react");
2388
2672
  var import_jsx_runtime47 = require("react/jsx-runtime");
2389
2673
  function ViewMenu({ density, densityLabel, densityOptions, onDensityChange, zebra, freeze, columns, columnsLabel, onColumnVisibilityChange }) {
2390
- const densityLabelId = (0, import_react26.useId)();
2391
- const zebraLabelId = (0, import_react26.useId)();
2392
- const freezeLabelId = (0, import_react26.useId)();
2674
+ const densityLabelId = (0, import_react30.useId)();
2675
+ const zebraLabelId = (0, import_react30.useId)();
2676
+ const freezeLabelId = (0, import_react30.useId)();
2393
2677
  return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "uix-view-menu", children: [
2394
2678
  /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "uix-view-menu__group", children: [
2395
2679
  /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { id: densityLabelId, className: "uix-view-menu__label", children: densityLabel }),
@@ -2438,11 +2722,11 @@ function SavedViewMenu({ items, onSelect, onPinChange, pinLabel, unpinLabel, emp
2438
2722
  // src/components/Timeline.tsx
2439
2723
  var import_jsx_runtime48 = require("react/jsx-runtime");
2440
2724
  function Timeline({ children, className, ...props }) {
2441
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: cx("uix-timeline", className), ...props, children });
2725
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("ol", { className: cx("uix-timeline", className), ...props, children });
2442
2726
  }
2443
2727
  function TimelineItem({ node, meta, children, className, ...props }) {
2444
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: cx("uix-timeline__item", className), ...props, children: [
2445
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "uix-timeline__rail", children: [
2728
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("li", { className: cx("uix-timeline__item", className), ...props, children: [
2729
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "uix-timeline__rail", "aria-hidden": "true", children: [
2446
2730
  node ?? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "uix-timeline__node" }),
2447
2731
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "uix-timeline__line" })
2448
2732
  ] }),
@@ -2454,10 +2738,10 @@ function TimelineItem({ node, meta, children, className, ...props }) {
2454
2738
  }
2455
2739
 
2456
2740
  // src/components/Pipeline.tsx
2457
- var import_react27 = require("react");
2741
+ var import_react31 = require("react");
2458
2742
  var import_jsx_runtime49 = require("react/jsx-runtime");
2459
2743
  function Pipeline({ detailed = false, children, className, tabIndex, ...props }) {
2460
- const stages = detailed ? import_react27.Children.map(children, (child) => (0, import_react27.isValidElement)(child) && child.type === PipelineStage ? (0, import_react27.cloneElement)(child, { detailed: child.props.detailed ?? true }) : child) : children;
2744
+ const stages = detailed ? import_react31.Children.map(children, (child) => (0, import_react31.isValidElement)(child) && child.type === PipelineStage ? (0, import_react31.cloneElement)(child, { detailed: child.props.detailed ?? true }) : child) : children;
2461
2745
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
2462
2746
  "ol",
2463
2747
  {
@@ -2573,7 +2857,7 @@ function Note({ tone, icon, children, className, ...props }) {
2573
2857
  }
2574
2858
 
2575
2859
  // src/components/Popover.tsx
2576
- var import_react28 = require("react");
2860
+ var import_react32 = require("react");
2577
2861
  var import_jsx_runtime52 = require("react/jsx-runtime");
2578
2862
  function Popover({
2579
2863
  popover = "auto",
@@ -2584,15 +2868,15 @@ function Popover({
2584
2868
  children,
2585
2869
  ...props
2586
2870
  }) {
2587
- const ref = (0, import_react28.useRef)(null);
2588
- const [open, setOpen] = (0, import_react28.useState)(false);
2871
+ const ref = (0, import_react32.useRef)(null);
2872
+ const [open, setOpen] = (0, import_react32.useState)(false);
2589
2873
  const popoverAttr = { popover };
2590
2874
  const reposition = useAnchoredPosition(anchor ?? null, ref, {
2591
2875
  open: open && !!anchor,
2592
2876
  placement,
2593
2877
  offset
2594
2878
  });
2595
- (0, import_react28.useEffect)(() => {
2879
+ (0, import_react32.useEffect)(() => {
2596
2880
  const el = ref.current;
2597
2881
  if (!el || !anchor) return;
2598
2882
  const sync = () => {
@@ -2607,34 +2891,138 @@ function Popover({
2607
2891
  }
2608
2892
 
2609
2893
  // src/components/CommandPalette.tsx
2894
+ var import_react33 = require("react");
2610
2895
  var import_jsx_runtime53 = require("react/jsx-runtime");
2896
+ var CmdkContext = (0, import_react33.createContext)(null);
2611
2897
  function CommandPalette({ inputProps, children, className, ...props }) {
2612
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: cx("uix-cmdk", className), ...props, children: [
2613
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("input", { className: "uix-cmdk__input", ...inputProps }),
2614
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { className: "uix-cmdk__list", children })
2615
- ] });
2898
+ const listId = (0, import_react33.useId)();
2899
+ const [activeId, setActiveId] = (0, import_react33.useState)(null);
2900
+ const [count, setCount] = (0, import_react33.useState)(0);
2901
+ const [announced, setAnnounced] = (0, import_react33.useState)("");
2902
+ const setActive = (0, import_react33.useCallback)((id, active) => {
2903
+ setActiveId((cur) => active ? id : cur === id ? null : cur);
2904
+ }, []);
2905
+ const registerItem = (0, import_react33.useCallback)(() => {
2906
+ setCount((c) => c + 1);
2907
+ return () => setCount((c) => c - 1);
2908
+ }, []);
2909
+ (0, import_react33.useEffect)(() => {
2910
+ const t = window.setTimeout(() => setAnnounced(`${count} result${count === 1 ? "" : "s"}`), 150);
2911
+ return () => clearTimeout(t);
2912
+ }, [count]);
2913
+ const ctx = (0, import_react33.useMemo)(() => ({ listId, setActive, registerItem }), [listId, setActive, registerItem]);
2914
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(CmdkContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: cx("uix-cmdk", className), ...props, children: [
2915
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
2916
+ "input",
2917
+ {
2918
+ className: "uix-cmdk__input",
2919
+ role: "combobox",
2920
+ "aria-expanded": "true",
2921
+ "aria-autocomplete": "list",
2922
+ "aria-controls": listId,
2923
+ "aria-activedescendant": activeId ?? void 0,
2924
+ "aria-label": "Command palette",
2925
+ ...inputProps
2926
+ }
2927
+ ),
2928
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { className: "uix-cmdk__list", role: "listbox", id: listId, children }),
2929
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "uix-visually-hidden", role: "status", children: announced })
2930
+ ] }) });
2616
2931
  }
2617
2932
  function CommandGroup({ label, children }) {
2618
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_jsx_runtime53.Fragment, { children: [
2619
- label != null && /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { className: "uix-cmdk__group", children: label }),
2933
+ const labelId = (0, import_react33.useId)();
2934
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { role: "group", "aria-labelledby": label != null ? labelId : void 0, children: [
2935
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { className: "uix-cmdk__group", id: labelId, children: label }),
2620
2936
  children
2621
2937
  ] });
2622
2938
  }
2623
- function CommandItem({ icon, shortcut, active, children, className, ...props }) {
2624
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: cx("uix-cmdk__item", className), "data-active": active || void 0, ...props, children: [
2625
- icon,
2626
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { children }),
2627
- shortcut
2628
- ] });
2939
+ function CommandItem({ icon, shortcut, active, children, className, id, ...props }) {
2940
+ const ctx = (0, import_react33.useContext)(CmdkContext);
2941
+ const autoId = (0, import_react33.useId)();
2942
+ const itemId = id ?? autoId;
2943
+ (0, import_react33.useEffect)(() => ctx?.registerItem(), [ctx]);
2944
+ (0, import_react33.useEffect)(() => {
2945
+ if (!ctx || !active) return;
2946
+ ctx.setActive(itemId, true);
2947
+ return () => ctx.setActive(itemId, false);
2948
+ }, [ctx, itemId, active]);
2949
+ return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
2950
+ "div",
2951
+ {
2952
+ className: cx("uix-cmdk__item", className),
2953
+ "data-active": active || void 0,
2954
+ id: itemId,
2955
+ role: "option",
2956
+ "aria-selected": !!active,
2957
+ ...props,
2958
+ children: [
2959
+ icon,
2960
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { children }),
2961
+ shortcut
2962
+ ]
2963
+ }
2964
+ );
2629
2965
  }
2630
2966
 
2631
2967
  // src/components/Inbox.tsx
2968
+ var import_react34 = require("react");
2632
2969
  var import_jsx_runtime54 = require("react/jsx-runtime");
2633
- function Inbox({ children, className, ...props }) {
2634
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { className: cx("uix-inbox", className), ...props, children });
2970
+ function Inbox({ view, children, className, ...props }) {
2971
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { className: cx("uix-inbox", className), "data-view": view, ...props, children });
2635
2972
  }
2973
+ var InboxListCtx = (0, import_react34.createContext)({});
2636
2974
  function InboxList({ children, className, ...props }) {
2637
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { className: cx("uix-inbox__list", className), role: "listbox", ...props, children });
2975
+ const listRef = (0, import_react34.useRef)(null);
2976
+ const [activeId, setActiveId] = (0, import_react34.useState)(void 0);
2977
+ const ctx = (0, import_react34.useMemo)(() => ({ activeId }), [activeId]);
2978
+ const onKeyDown = (e) => {
2979
+ const opts = Array.from(listRef.current?.querySelectorAll('[role="option"]') ?? []);
2980
+ if (opts.length === 0) return;
2981
+ const idx = opts.findIndex((el2) => el2.id === activeId);
2982
+ let next;
2983
+ switch (e.key) {
2984
+ case "ArrowDown":
2985
+ next = Math.min(idx + 1, opts.length - 1);
2986
+ break;
2987
+ case "ArrowUp":
2988
+ next = idx < 0 ? 0 : Math.max(idx - 1, 0);
2989
+ break;
2990
+ case "Home":
2991
+ next = 0;
2992
+ break;
2993
+ case "End":
2994
+ next = opts.length - 1;
2995
+ break;
2996
+ case "Enter":
2997
+ case " ":
2998
+ if (idx >= 0) {
2999
+ e.preventDefault();
3000
+ opts[idx]?.click();
3001
+ }
3002
+ return;
3003
+ default:
3004
+ return;
3005
+ }
3006
+ e.preventDefault();
3007
+ const el = opts[next];
3008
+ if (!el || !el.id) return;
3009
+ setActiveId(el.id);
3010
+ el.scrollIntoView({ block: "nearest" });
3011
+ };
3012
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(InboxListCtx.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3013
+ "div",
3014
+ {
3015
+ ref: listRef,
3016
+ className: cx("uix-inbox__list", className),
3017
+ role: "listbox",
3018
+ tabIndex: 0,
3019
+ "aria-label": "Inbox",
3020
+ "aria-activedescendant": activeId,
3021
+ onKeyDown,
3022
+ ...props,
3023
+ children
3024
+ }
3025
+ ) });
2638
3026
  }
2639
3027
  function InboxItem({
2640
3028
  subject,
@@ -2644,13 +3032,19 @@ function InboxItem({
2644
3032
  selected,
2645
3033
  children,
2646
3034
  className,
3035
+ id,
2647
3036
  ...props
2648
3037
  }) {
3038
+ const autoId = (0, import_react34.useId)();
3039
+ const itemId = id ?? autoId;
3040
+ const { activeId } = (0, import_react34.useContext)(InboxListCtx);
2649
3041
  return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
2650
3042
  "div",
2651
3043
  {
2652
3044
  role: "option",
3045
+ id: itemId,
2653
3046
  "aria-selected": selected ?? false,
3047
+ "data-active": activeId === itemId ? "" : void 0,
2654
3048
  className: cx("uix-inbox__item", className),
2655
3049
  ...unread ? { "data-unread": "" } : {},
2656
3050
  ...props,
@@ -2668,29 +3062,60 @@ function InboxDetail({ children, className, ...props }) {
2668
3062
  }
2669
3063
 
2670
3064
  // src/components/Kanban.tsx
3065
+ var import_react35 = require("react");
2671
3066
  var import_jsx_runtime55 = require("react/jsx-runtime");
2672
- function Kanban({ children, className, ...props }) {
2673
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: cx("uix-kanban", className), ...props, children });
3067
+ function Kanban({ announcement, children, className, ...props }) {
3068
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: cx("uix-kanban", className), ...props, children: [
3069
+ announcement != null && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { role: "status", className: "uix-visually-hidden", children: announcement }),
3070
+ children
3071
+ ] });
2674
3072
  }
2675
3073
  function KanbanColumn({ title, count, children, className, ...props }) {
3074
+ const headId = (0, import_react35.useId)();
3075
+ const hasHead = title != null || count != null;
2676
3076
  return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: cx("uix-kanban__col", className), ...props, children: [
2677
- (title != null || count != null) && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "uix-kanban__head", children: [
3077
+ hasHead && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "uix-kanban__head", id: headId, children: [
2678
3078
  title,
2679
3079
  count != null && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "uix-kanban__count", children: count })
2680
3080
  ] }),
2681
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "uix-kanban__body", children })
3081
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "uix-kanban__body", role: "list", "aria-labelledby": hasHead ? headId : void 0, children })
2682
3082
  ] });
2683
3083
  }
2684
- function KanbanCard({ title, meta, children, className, ...props }) {
2685
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: cx("uix-kanban__card", className), ...props, children: [
2686
- title && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "uix-kanban__card-title", children: title }),
2687
- meta && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "uix-kanban__card-meta", children: meta }),
2688
- children
2689
- ] });
3084
+ var MOVE_KEYS = {
3085
+ ArrowUp: "up",
3086
+ ArrowDown: "down",
3087
+ ArrowLeft: "left",
3088
+ ArrowRight: "right"
3089
+ };
3090
+ function KanbanCard({ title, meta, children, className, onMove, onKeyDown, ...props }) {
3091
+ const handleKeyDown = (e) => {
3092
+ onKeyDown?.(e);
3093
+ if (!onMove || !e.altKey || e.defaultPrevented) return;
3094
+ const dir = MOVE_KEYS[e.key];
3095
+ if (dir) {
3096
+ e.preventDefault();
3097
+ onMove(dir);
3098
+ }
3099
+ };
3100
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
3101
+ "div",
3102
+ {
3103
+ role: "listitem",
3104
+ tabIndex: 0,
3105
+ className: cx("uix-kanban__card", className),
3106
+ onKeyDown: handleKeyDown,
3107
+ ...props,
3108
+ children: [
3109
+ title && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "uix-kanban__card-title", children: title }),
3110
+ meta && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "uix-kanban__card-meta", children: meta }),
3111
+ children
3112
+ ]
3113
+ }
3114
+ );
2690
3115
  }
2691
3116
 
2692
3117
  // src/components/Tree.tsx
2693
- var import_react29 = require("react");
3118
+ var import_react36 = require("react");
2694
3119
 
2695
3120
  // src/tree-model.ts
2696
3121
  function flattenTree(nodes, expanded, level = 1) {
@@ -2735,6 +3160,14 @@ function treeNav(flat, currentId, key) {
2735
3160
  case " ":
2736
3161
  return cur.hasChildren ? { toggleId: cur.node.id, selectId: cur.node.id } : { selectId: cur.node.id };
2737
3162
  default:
3163
+ if (key.length === 1) {
3164
+ const q = key.toLowerCase();
3165
+ for (let step = 1; step <= flat.length; step++) {
3166
+ const f = flat[(idx + step) % flat.length];
3167
+ const label = f.node.typeaheadLabel ?? (typeof f.node.label === "string" ? f.node.label : void 0);
3168
+ if (label != null && label.toLowerCase().startsWith(q)) return { focusId: f.node.id };
3169
+ }
3170
+ }
2738
3171
  return {};
2739
3172
  }
2740
3173
  }
@@ -2802,12 +3235,12 @@ function Tree({
2802
3235
  className,
2803
3236
  ...props
2804
3237
  }) {
2805
- const [internalExpanded, setInternalExpanded] = (0, import_react29.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
3238
+ const [internalExpanded, setInternalExpanded] = (0, import_react36.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
2806
3239
  const expanded = controlledExpanded ?? internalExpanded;
2807
- const rootRef = (0, import_react29.useRef)(null);
2808
- const [focusedId, setFocusedId] = (0, import_react29.useState)(void 0);
3240
+ const rootRef = (0, import_react36.useRef)(null);
3241
+ const [focusedId, setFocusedId] = (0, import_react36.useState)(void 0);
2809
3242
  const selectable = selected !== void 0 || onSelect != null;
2810
- const handleToggle = (0, import_react29.useCallback)((id) => {
3243
+ const handleToggle = (0, import_react36.useCallback)((id) => {
2811
3244
  if (!controlledExpanded) {
2812
3245
  setInternalExpanded((prev) => {
2813
3246
  const next = new Set(prev);
@@ -2818,7 +3251,7 @@ function Tree({
2818
3251
  }
2819
3252
  onToggle?.(id);
2820
3253
  }, [controlledExpanded, onToggle]);
2821
- const flat = (0, import_react29.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
3254
+ const flat = (0, import_react36.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
2822
3255
  const useVirtual = virtualize ?? shouldVirtualize(flat.length);
2823
3256
  const tabbableId = focusedId ?? selected ?? nodes[0]?.id;
2824
3257
  if (useVirtual) {
@@ -2889,6 +3322,13 @@ function Tree({
2889
3322
  setFocusedId(id);
2890
3323
  break;
2891
3324
  default:
3325
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
3326
+ const act = treeNav(flat, id, e.key);
3327
+ if (act.focusId !== void 0) {
3328
+ e.preventDefault();
3329
+ focusItem(rootRef.current?.querySelector(`[role="treeitem"][data-id="${CSS.escape(act.focusId)}"]`));
3330
+ }
3331
+ }
2892
3332
  break;
2893
3333
  }
2894
3334
  };
@@ -2922,12 +3362,14 @@ function VirtualTreeView({
2922
3362
  className,
2923
3363
  ...props
2924
3364
  }) {
2925
- const scrollRef = (0, import_react29.useRef)(null);
2926
- const [scrollTop, setScrollTop] = (0, import_react29.useState)(0);
2927
- const pendingFocus = (0, import_react29.useRef)(false);
3365
+ const scrollRef = (0, import_react36.useRef)(null);
3366
+ const [scrollTop, setScrollTop] = (0, import_react36.useState)(0);
3367
+ const pendingFocus = (0, import_react36.useRef)(false);
3368
+ const hadFocus = (0, import_react36.useRef)(false);
2928
3369
  const win = virtualWindow(scrollTop, maxHeight, rowHeight, flat.length);
2929
3370
  const rows = flat.slice(win.start, win.end);
2930
- (0, import_react29.useEffect)(() => {
3371
+ const windowHasTabbable = tabbableId != null && rows.some((f) => f.node.id === tabbableId);
3372
+ (0, import_react36.useEffect)(() => {
2931
3373
  if (!pendingFocus.current || focusedId == null) return;
2932
3374
  const el = scrollRef.current?.querySelector(`[role="treeitem"][data-id="${CSS.escape(focusedId)}"]`);
2933
3375
  if (el) {
@@ -2935,6 +3377,11 @@ function VirtualTreeView({
2935
3377
  pendingFocus.current = false;
2936
3378
  }
2937
3379
  }, [focusedId, win.start, win.end]);
3380
+ (0, import_react36.useEffect)(() => {
3381
+ if (!hadFocus.current || pendingFocus.current) return;
3382
+ const active = document.activeElement;
3383
+ if (active === document.body || active == null) scrollRef.current?.focus({ preventScroll: true });
3384
+ });
2938
3385
  const moveTo = (id) => {
2939
3386
  const i = flat.findIndex((f) => f.node.id === id);
2940
3387
  if (i < 0) return;
@@ -2950,6 +3397,7 @@ function VirtualTreeView({
2950
3397
  setFocusedId(id);
2951
3398
  };
2952
3399
  const onKeyDown = (e) => {
3400
+ if (e.key.length === 1 && (e.ctrlKey || e.metaKey || e.altKey)) return;
2953
3401
  const curId = e.target.closest('[role="treeitem"]')?.dataset.id ?? focusedId;
2954
3402
  const act = treeNav(flat, curId, e.key);
2955
3403
  if (act.focusId === void 0 && act.toggleId === void 0 && act.selectId === void 0) return;
@@ -2970,13 +3418,20 @@ function VirtualTreeView({
2970
3418
  ref: scrollRef,
2971
3419
  className: cx("uix-tree", "uix-tree--virtual", className),
2972
3420
  role: "tree",
3421
+ tabIndex: -1,
2973
3422
  style: { maxHeight, overflow: "auto" },
2974
3423
  onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
2975
3424
  onKeyDown,
3425
+ onFocus: () => {
3426
+ hadFocus.current = true;
3427
+ },
3428
+ onBlur: (e) => {
3429
+ if (!e.currentTarget.contains(e.relatedTarget)) hadFocus.current = false;
3430
+ },
2976
3431
  ...props,
2977
3432
  children: [
2978
- win.padTop > 0 && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("li", { "aria-hidden": "true", style: { height: win.padTop } }),
2979
- rows.map((f) => {
3433
+ win.padTop > 0 && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("li", { role: "presentation", "aria-hidden": "true", style: { height: win.padTop } }),
3434
+ rows.map((f, i) => {
2980
3435
  const rowStyle = { height: rowHeight, paddingLeft: `calc(${f.level - 1} * var(--uix-space-5))` };
2981
3436
  return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
2982
3437
  "li",
@@ -2988,7 +3443,7 @@ function VirtualTreeView({
2988
3443
  "aria-expanded": f.hasChildren ? f.isExpanded : void 0,
2989
3444
  "aria-selected": selectable ? selected === f.node.id : void 0,
2990
3445
  "data-id": f.node.id,
2991
- tabIndex: tabbableId === f.node.id ? 0 : -1,
3446
+ tabIndex: tabbableId === f.node.id || !windowHasTabbable && i === 0 ? 0 : -1,
2992
3447
  className: "uix-tree__item",
2993
3448
  onFocus: (e) => {
2994
3449
  if (e.target === e.currentTarget) setFocusedId(f.node.id);
@@ -3008,7 +3463,7 @@ function VirtualTreeView({
3008
3463
  f.node.id
3009
3464
  );
3010
3465
  }),
3011
- win.padBottom > 0 && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("li", { "aria-hidden": "true", style: { height: win.padBottom } })
3466
+ win.padBottom > 0 && /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("li", { role: "presentation", "aria-hidden": "true", style: { height: win.padBottom } })
3012
3467
  ]
3013
3468
  }
3014
3469
  );
@@ -3427,7 +3882,7 @@ function RuleBuilder({
3427
3882
  }
3428
3883
 
3429
3884
  // src/components/BuilderCanvas.tsx
3430
- var import_react30 = require("react");
3885
+ var import_react37 = require("react");
3431
3886
  var import_jsx_runtime59 = require("react/jsx-runtime");
3432
3887
  function BuilderCanvas({
3433
3888
  items,
@@ -3443,13 +3898,13 @@ function BuilderCanvas({
3443
3898
  emptyMessage = "Add an item from the palette to begin.",
3444
3899
  className
3445
3900
  }) {
3446
- const [internalSelectedId, setInternalSelectedId] = (0, import_react30.useState)();
3447
- const [draggedId, setDraggedId] = (0, import_react30.useState)();
3448
- const propertyHeadingRef = (0, import_react30.useRef)(null);
3449
- const shouldFocusProperties = (0, import_react30.useRef)(false);
3901
+ const [internalSelectedId, setInternalSelectedId] = (0, import_react37.useState)();
3902
+ const [draggedId, setDraggedId] = (0, import_react37.useState)();
3903
+ const propertyHeadingRef = (0, import_react37.useRef)(null);
3904
+ const shouldFocusProperties = (0, import_react37.useRef)(false);
3450
3905
  const selectedId = controlledSelectedId ?? internalSelectedId;
3451
3906
  const selected = items.find((item) => item.id === selectedId);
3452
- (0, import_react30.useEffect)(() => {
3907
+ (0, import_react37.useEffect)(() => {
3453
3908
  if (shouldFocusProperties.current && selected) {
3454
3909
  propertyHeadingRef.current?.focus();
3455
3910
  shouldFocusProperties.current = false;
@@ -3540,7 +3995,7 @@ function BuilderCanvas({
3540
3995
  }
3541
3996
 
3542
3997
  // src/components/SchedulingCalendar.tsx
3543
- var import_react31 = require("react");
3998
+ var import_react38 = require("react");
3544
3999
 
3545
4000
  // src/calendar-model.ts
3546
4001
  function fromDateKey(value) {
@@ -3643,19 +4098,19 @@ function SchedulingCalendar({
3643
4098
  onRetry,
3644
4099
  className
3645
4100
  }) {
3646
- const [internalView, setInternalView] = (0, import_react31.useState)("month");
4101
+ const [internalView, setInternalView] = (0, import_react38.useState)("month");
3647
4102
  const view = controlledView ?? internalView;
3648
- const [activeDate, setActiveDate] = (0, import_react31.useState)(anchorDate);
3649
- const gridRef = (0, import_react31.useRef)(null);
3650
- const visibleEntries = (0, import_react31.useMemo)(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
3651
- const entrySpans = (0, import_react31.useMemo)(() => new Map(visibleEntries.map((entry) => [entry.id, zonedDateSpan(entry.start, entry.end, timeZone)])), [visibleEntries, timeZone]);
3652
- const overlaySpans = (0, import_react31.useMemo)(() => new Map(overlays.map((overlay) => [overlay.id, { start: overlay.start.slice(0, 10), end: overlay.end.slice(0, 10) }])), [overlays]);
3653
- const monthDays = (0, import_react31.useMemo)(() => buildMonthGrid(startOfMonth(anchorDate)), [anchorDate]);
4103
+ const [activeDate, setActiveDate] = (0, import_react38.useState)(anchorDate);
4104
+ const gridRef = (0, import_react38.useRef)(null);
4105
+ const visibleEntries = (0, import_react38.useMemo)(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
4106
+ const entrySpans = (0, import_react38.useMemo)(() => new Map(visibleEntries.map((entry) => [entry.id, zonedDateSpan(entry.start, entry.end, timeZone)])), [visibleEntries, timeZone]);
4107
+ const overlaySpans = (0, import_react38.useMemo)(() => new Map(overlays.map((overlay) => [overlay.id, { start: overlay.start.slice(0, 10), end: overlay.end.slice(0, 10) }])), [overlays]);
4108
+ const monthDays = (0, import_react38.useMemo)(() => buildMonthGrid(startOfMonth(anchorDate)), [anchorDate]);
3654
4109
  const weekday = ((/* @__PURE__ */ new Date(`${anchorDate}T00:00:00Z`)).getUTCDay() + 6) % 7;
3655
4110
  const weekStart = addCalendarDays(anchorDate, -weekday);
3656
- const weekDays = (0, import_react31.useMemo)(() => Array.from({ length: 7 }, (_, index) => addCalendarDays(weekStart, index)), [weekStart]);
3657
- const days = (0, import_react31.useMemo)(() => view === "month" ? monthDays.map((day) => day.date) : weekDays, [view, monthDays, weekDays]);
3658
- (0, import_react31.useEffect)(() => {
4111
+ const weekDays = (0, import_react38.useMemo)(() => Array.from({ length: 7 }, (_, index) => addCalendarDays(weekStart, index)), [weekStart]);
4112
+ const days = (0, import_react38.useMemo)(() => view === "month" ? monthDays.map((day) => day.date) : weekDays, [view, monthDays, weekDays]);
4113
+ (0, import_react38.useEffect)(() => {
3659
4114
  if (view !== "agenda" && !days.includes(activeDate)) setActiveDate(days[0]);
3660
4115
  }, [activeDate, days, view]);
3661
4116
  const setView = (next) => {
@@ -3739,7 +4194,7 @@ function SchedulingCalendar({
3739
4194
  }
3740
4195
 
3741
4196
  // src/components/DateRangePicker.tsx
3742
- var import_react32 = require("react");
4197
+ var import_react39 = require("react");
3743
4198
  var import_jsx_runtime61 = require("react/jsx-runtime");
3744
4199
  var monthLabel = (date, locale) => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
3745
4200
  var dateLabel2 = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
@@ -3759,25 +4214,25 @@ function DateRangePicker({
3759
4214
  disabled,
3760
4215
  className
3761
4216
  }) {
3762
- const id = (0, import_react32.useId)();
3763
- const [internalMonth, setInternalMonth] = (0, import_react32.useState)(() => startOfMonth(controlledMonth ?? value.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)));
4217
+ const id = (0, import_react39.useId)();
4218
+ const [internalMonth, setInternalMonth] = (0, import_react39.useState)(() => startOfMonth(controlledMonth ?? value.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)));
3764
4219
  const visibleMonth = startOfMonth(controlledMonth ?? internalMonth);
3765
- const [activeDate, setActiveDate] = (0, import_react32.useState)(value.end ?? value.start ?? visibleMonth);
3766
- const rootRef = (0, import_react32.useRef)(null);
3767
- const pendingFocus = (0, import_react32.useRef)(false);
4220
+ const [activeDate, setActiveDate] = (0, import_react39.useState)(value.end ?? value.start ?? visibleMonth);
4221
+ const rootRef = (0, import_react39.useRef)(null);
4222
+ const pendingFocus = (0, import_react39.useRef)(false);
3768
4223
  const count = Math.max(1, Math.min(3, months));
3769
- const calendars = (0, import_react32.useMemo)(() => Array.from({ length: count }, (_, index) => {
4224
+ const calendars = (0, import_react39.useMemo)(() => Array.from({ length: count }, (_, index) => {
3770
4225
  const month = startOfMonth(addCalendarMonths(visibleMonth, index));
3771
4226
  return { month, days: buildMonthGrid(month).filter((day) => day.inMonth) };
3772
4227
  }), [visibleMonth, count]);
3773
4228
  const rangeInvalid = invalid || !!value.start && !!value.end && value.start > value.end;
3774
- (0, import_react32.useEffect)(() => {
4229
+ (0, import_react39.useEffect)(() => {
3775
4230
  const first = calendars[0]?.days[0]?.date;
3776
4231
  const finalDays = calendars[calendars.length - 1]?.days;
3777
4232
  const last = finalDays?.[finalDays.length - 1]?.date;
3778
4233
  if (first && last && (activeDate < first || activeDate > last)) setActiveDate(first);
3779
4234
  }, [activeDate, calendars]);
3780
- (0, import_react32.useEffect)(() => {
4235
+ (0, import_react39.useEffect)(() => {
3781
4236
  if (!pendingFocus.current) return;
3782
4237
  rootRef.current?.querySelector(`[data-date="${activeDate}"]`)?.focus();
3783
4238
  pendingFocus.current = false;
@@ -3858,7 +4313,7 @@ function DateRangePicker({
3858
4313
  }
3859
4314
 
3860
4315
  // src/components/RelationshipGraph.tsx
3861
- var import_react33 = require("react");
4316
+ var import_react41 = require("react");
3862
4317
 
3863
4318
  // src/relationship-graph-model.ts
3864
4319
  function boundRelationshipGraph(nodes, edges, maxNodes = 40) {
@@ -3923,37 +4378,955 @@ function traverseRelationshipNode(nodes, currentId, direction) {
3923
4378
  const current = Math.max(0, nodes.findIndex((node) => node.id === currentId));
3924
4379
  return nodes[(current + direction + nodes.length) % nodes.length]?.id;
3925
4380
  }
4381
+ var LAYERED_GEOMETRY = {
4382
+ standard: { nodeWidth: 176, nodeHeight: 76, columnGap: 96, rowGap: 12 },
4383
+ compact: { nodeWidth: 160, nodeHeight: 60, columnGap: 80, rowGap: 8 }
4384
+ };
4385
+ var LAYERED_PADDING = 16;
4386
+ var LAYERED_HEADER = 28;
4387
+ var GROUP_HEADER = 28;
4388
+ var AUTO_LABEL_MAX_FANOUT = 4;
4389
+ var LABEL_CHAR_WIDTH = 5.6;
4390
+ var LABEL_MAX_LINES = 2;
4391
+ function wrapEdgeLabel(label, width) {
4392
+ const max = Math.max(4, Math.floor(width / LABEL_CHAR_WIDTH));
4393
+ const clip = (word) => word.length > max ? `${word.slice(0, max - 1)}\u2026` : word;
4394
+ const lines = [];
4395
+ let current = "";
4396
+ for (const word of label.split(/\s+/).filter(Boolean)) {
4397
+ const next = current ? `${current} ${word}` : word;
4398
+ if (next.length <= max) {
4399
+ current = next;
4400
+ continue;
4401
+ }
4402
+ if (current) lines.push(current);
4403
+ current = clip(word);
4404
+ if (lines.length === LABEL_MAX_LINES) break;
4405
+ }
4406
+ if (current && lines.length < LABEL_MAX_LINES) lines.push(current);
4407
+ if (lines.length === LABEL_MAX_LINES) {
4408
+ const consumed = lines.join(" ").replace(/…$/, "").length;
4409
+ if (consumed < label.replace(/\s+/g, " ").trim().length) {
4410
+ const last = lines[LABEL_MAX_LINES - 1];
4411
+ lines[LABEL_MAX_LINES - 1] = last.endsWith("\u2026") ? last : `${last.slice(0, Math.max(1, max - 1))}\u2026`;
4412
+ }
4413
+ }
4414
+ return lines;
4415
+ }
4416
+ var drawableEdge = (edge, ids) => ids.has(edge.source) && ids.has(edge.target) && edge.source !== edge.target;
4417
+ function classifyLayeredEdges(nodes, edges, rootId = nodes[0]?.id) {
4418
+ const ids = new Set(nodes.map((node) => node.id));
4419
+ const root = rootId !== void 0 && ids.has(rootId) ? rootId : nodes[0]?.id;
4420
+ const outgoing = /* @__PURE__ */ new Map();
4421
+ for (const edge of edges) {
4422
+ if (!drawableEdge(edge, ids)) continue;
4423
+ const list = outgoing.get(edge.source);
4424
+ if (list) list.push(edge.target);
4425
+ else outgoing.set(edge.source, [edge.target]);
4426
+ }
4427
+ const bfs = /* @__PURE__ */ new Map();
4428
+ if (root !== void 0) {
4429
+ bfs.set(root, 0);
4430
+ const queue = [root];
4431
+ for (let head = 0; head < queue.length; head += 1) {
4432
+ const current = queue[head];
4433
+ for (const next of outgoing.get(current) ?? []) {
4434
+ if (!bfs.has(next)) {
4435
+ bfs.set(next, bfs.get(current) + 1);
4436
+ queue.push(next);
4437
+ }
4438
+ }
4439
+ }
4440
+ }
4441
+ const depthById = /* @__PURE__ */ new Map();
4442
+ let deepest = 0;
4443
+ for (const node of nodes) {
4444
+ const supplied = node.depth !== void 0 && Number.isFinite(node.depth) ? Math.max(0, Math.floor(node.depth)) : void 0;
4445
+ const depth = supplied ?? bfs.get(node.id);
4446
+ if (depth === void 0) continue;
4447
+ depthById.set(node.id, depth);
4448
+ deepest = Math.max(deepest, depth);
4449
+ }
4450
+ for (const node of nodes) if (!depthById.has(node.id)) depthById.set(node.id, deepest + 1);
4451
+ const forwardOut = /* @__PURE__ */ new Map();
4452
+ const kindById = /* @__PURE__ */ new Map();
4453
+ const backEdges = [];
4454
+ for (const edge of edges) {
4455
+ if (!drawableEdge(edge, ids)) continue;
4456
+ const from = depthById.get(edge.source);
4457
+ const to = depthById.get(edge.target);
4458
+ if (to === from + 1) {
4459
+ kindById.set(edge.id, "forward");
4460
+ const list = forwardOut.get(edge.source);
4461
+ if (list) list.push(edge.target);
4462
+ else forwardOut.set(edge.source, [edge.target]);
4463
+ } else if (to < from) {
4464
+ backEdges.push(edge);
4465
+ } else {
4466
+ kindById.set(edge.id, "lateral");
4467
+ }
4468
+ }
4469
+ for (const edge of backEdges) {
4470
+ const limit = depthById.get(edge.source);
4471
+ const seen = /* @__PURE__ */ new Set([edge.target]);
4472
+ const stack = [edge.target];
4473
+ let closes = false;
4474
+ while (stack.length > 0 && !closes) {
4475
+ const current = stack.pop();
4476
+ for (const next of forwardOut.get(current) ?? []) {
4477
+ if (next === edge.source) {
4478
+ closes = true;
4479
+ break;
4480
+ }
4481
+ if (!seen.has(next) && depthById.get(next) < limit) {
4482
+ seen.add(next);
4483
+ stack.push(next);
4484
+ }
4485
+ }
4486
+ }
4487
+ kindById.set(edge.id, closes ? "cycle" : "lateral");
4488
+ }
4489
+ return { depthById, kindById };
4490
+ }
4491
+ function clusterIdFor(parentId, type, edgeType) {
4492
+ return `cluster:${parentId}:${type ?? ""}:${edgeType ?? ""}`;
4493
+ }
4494
+ var mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
4495
+ var round1 = (value) => Math.round(value * 10) / 10;
4496
+ function layoutLayeredGraph(nodes, edges, options = {}) {
4497
+ const { nodeWidth, nodeHeight, columnGap, rowGap } = LAYERED_GEOMETRY[options.density ?? "standard"];
4498
+ const threshold = options.clusterThreshold ?? 6;
4499
+ const expanded = options.expandedClusterIds ?? /* @__PURE__ */ new Set();
4500
+ const rootId = options.rootId !== void 0 && nodes.some((node) => node.id === options.rootId) ? options.rootId : nodes[0]?.id;
4501
+ if (nodes.length === 0) {
4502
+ return {
4503
+ rootId,
4504
+ items: [],
4505
+ edges: [],
4506
+ clusters: [],
4507
+ columns: [],
4508
+ itemOf: {},
4509
+ width: 0,
4510
+ height: 0,
4511
+ nodeWidth,
4512
+ nodeHeight,
4513
+ headerHeight: LAYERED_HEADER
4514
+ };
4515
+ }
4516
+ const { depthById, kindById } = classifyLayeredEdges(nodes, edges, rootId);
4517
+ const drawable = edges.filter((edge) => kindById.has(edge.id));
4518
+ const forwardParents = /* @__PURE__ */ new Map();
4519
+ const forwardChildCount = /* @__PURE__ */ new Map();
4520
+ const inCycle = /* @__PURE__ */ new Set();
4521
+ const nodeDegree = /* @__PURE__ */ new Map();
4522
+ for (const edge of drawable) {
4523
+ nodeDegree.set(edge.source, (nodeDegree.get(edge.source) ?? 0) + 1);
4524
+ nodeDegree.set(edge.target, (nodeDegree.get(edge.target) ?? 0) + 1);
4525
+ if (kindById.get(edge.id) === "forward") {
4526
+ const list = forwardParents.get(edge.target);
4527
+ if (list) list.push(edge);
4528
+ else forwardParents.set(edge.target, [edge]);
4529
+ forwardChildCount.set(edge.source, (forwardChildCount.get(edge.source) ?? 0) + 1);
4530
+ } else {
4531
+ inCycle.add(edge.source);
4532
+ inCycle.add(edge.target);
4533
+ }
4534
+ }
4535
+ const groups = /* @__PURE__ */ new Map();
4536
+ if (threshold > 0) {
4537
+ for (const node of nodes) {
4538
+ if (node.id === rootId || inCycle.has(node.id) || (forwardChildCount.get(node.id) ?? 0) > 0) continue;
4539
+ const parents = forwardParents.get(node.id) ?? [];
4540
+ if (parents.length !== 1) continue;
4541
+ const via = parents[0];
4542
+ const id = clusterIdFor(via.source, node.type, via.type);
4543
+ const group = groups.get(id);
4544
+ if (group) {
4545
+ group.members.push(node);
4546
+ continue;
4547
+ }
4548
+ const created = { id, parentId: via.source, depth: depthById.get(node.id), members: [node] };
4549
+ if (node.type !== void 0) created.type = node.type;
4550
+ if (via.type !== void 0) created.edgeType = via.type;
4551
+ const edgeLabel = via.label ?? via.type;
4552
+ if (edgeLabel !== void 0) created.edgeLabel = edgeLabel;
4553
+ groups.set(id, created);
4554
+ }
4555
+ }
4556
+ const clusterById = /* @__PURE__ */ new Map();
4557
+ const itemOf = {};
4558
+ const expandedGroupOf = /* @__PURE__ */ new Map();
4559
+ for (const group of groups.values()) {
4560
+ if (group.members.length < threshold) continue;
4561
+ if (expanded.has(group.id)) {
4562
+ for (const member of group.members) expandedGroupOf.set(member.id, group);
4563
+ continue;
4564
+ }
4565
+ clusterById.set(group.id, group);
4566
+ for (const member of group.members) itemOf[member.id] = group.id;
4567
+ }
4568
+ for (const node of nodes) if (itemOf[node.id] === void 0) itemOf[node.id] = node.id;
4569
+ const columnsByDepth = /* @__PURE__ */ new Map();
4570
+ const itemById = /* @__PURE__ */ new Map();
4571
+ for (const node of nodes) {
4572
+ const id = itemOf[node.id];
4573
+ if (itemById.has(id)) continue;
4574
+ const cluster = clusterById.get(id);
4575
+ const depth = depthById.get(node.id);
4576
+ const item = {
4577
+ id,
4578
+ kind: cluster ? "cluster" : "node",
4579
+ depth,
4580
+ index: 0,
4581
+ x: 0,
4582
+ y: 0,
4583
+ width: nodeWidth,
4584
+ height: nodeHeight,
4585
+ parents: [],
4586
+ children: [],
4587
+ inCycle: cluster ? false : inCycle.has(node.id),
4588
+ degree: cluster ? cluster.members.length : nodeDegree.get(node.id) ?? 0
4589
+ };
4590
+ if (cluster) item.cluster = cluster;
4591
+ else item.node = node;
4592
+ itemById.set(id, item);
4593
+ const column = columnsByDepth.get(depth);
4594
+ if (column) column.push(item);
4595
+ else columnsByDepth.set(depth, [item]);
4596
+ }
4597
+ for (const edge of drawable) {
4598
+ if (kindById.get(edge.id) !== "forward") continue;
4599
+ const from = itemById.get(itemOf[edge.source]);
4600
+ const to = itemById.get(itemOf[edge.target]);
4601
+ if (!from.children.includes(to.id)) from.children.push(to.id);
4602
+ if (!to.parents.includes(from.id)) to.parents.push(from.id);
4603
+ }
4604
+ const depths = [...columnsByDepth.keys()].sort((a, b) => a - b);
4605
+ const position = /* @__PURE__ */ new Map();
4606
+ const reindex = (column) => {
4607
+ column.forEach((item, index) => {
4608
+ item.index = index;
4609
+ position.set(item.id, index);
4610
+ });
4611
+ };
4612
+ for (const depth of depths) reindex(columnsByDepth.get(depth));
4613
+ const groupOfItem = (item) => item.node ? expandedGroupOf.get(item.node.id) : void 0;
4614
+ const sweep = (depth, neighbours) => {
4615
+ const keyed = columnsByDepth.get(depth).map((item) => {
4616
+ const around = neighbours(item).map((id) => position.get(id)).filter((value) => value !== void 0);
4617
+ return { item, key: around.length > 0 ? mean(around) : item.index, tie: item.index };
4618
+ });
4619
+ keyed.sort((a, b) => a.key - b.key || a.tie - b.tie);
4620
+ const ordered = [];
4621
+ const placed = /* @__PURE__ */ new Set();
4622
+ for (const { item } of keyed) {
4623
+ if (placed.has(item.id)) continue;
4624
+ const group = groupOfItem(item);
4625
+ for (const candidate of group ? keyed.map((entry) => entry.item).filter((other) => groupOfItem(other) === group) : [item]) {
4626
+ if (placed.has(candidate.id)) continue;
4627
+ ordered.push(candidate);
4628
+ placed.add(candidate.id);
4629
+ }
4630
+ }
4631
+ columnsByDepth.set(depth, ordered);
4632
+ reindex(ordered);
4633
+ };
4634
+ const inner = depths.slice(1);
4635
+ for (const depth of inner) sweep(depth, (item) => item.parents);
4636
+ for (const depth of [...inner].reverse()) sweep(depth, (item) => item.children);
4637
+ for (const depth of inner) sweep(depth, (item) => item.parents);
4638
+ const top = LAYERED_PADDING + LAYERED_HEADER;
4639
+ const columns = [];
4640
+ let width = 0;
4641
+ let height = 0;
4642
+ for (const depth of depths) {
4643
+ const column = columnsByDepth.get(depth);
4644
+ const x = LAYERED_PADDING + depth * (nodeWidth + columnGap);
4645
+ let cursor = top;
4646
+ let count = 0;
4647
+ const ledGroups = /* @__PURE__ */ new Set();
4648
+ for (const item of column) {
4649
+ const parentCentres = item.parents.map((id) => itemById.get(id)).filter((parent) => parent.depth < depth).map((parent) => parent.y + parent.height / 2);
4650
+ const group = groupOfItem(item);
4651
+ const leadsGroup = group !== void 0 && !ledGroups.has(group);
4652
+ if (group) ledGroups.add(group);
4653
+ const floor = cursor + (leadsGroup ? GROUP_HEADER : 0);
4654
+ const target = parentCentres.length > 0 ? mean(parentCentres) - item.height / 2 : floor;
4655
+ item.x = x;
4656
+ item.y = round1(Math.max(target, floor));
4657
+ if (leadsGroup) item.expandedGroup = group;
4658
+ cursor = item.y + item.height + rowGap;
4659
+ count += item.cluster ? item.cluster.members.length : 1;
4660
+ width = Math.max(width, x + item.width);
4661
+ height = Math.max(height, item.y + item.height);
4662
+ }
4663
+ columns.push({ depth, x, count });
4664
+ }
4665
+ const byPosition = (a, b) => (position.get(a) ?? 0) - (position.get(b) ?? 0);
4666
+ for (const item of itemById.values()) {
4667
+ item.children.sort(byPosition);
4668
+ item.parents.sort(byPosition);
4669
+ }
4670
+ const routed = /* @__PURE__ */ new Map();
4671
+ for (const edge of drawable) {
4672
+ const kind = kindById.get(edge.id);
4673
+ const from = itemById.get(itemOf[edge.source]);
4674
+ const to = itemById.get(itemOf[edge.target]);
4675
+ const key = from.kind === "cluster" || to.kind === "cluster" ? `${from.id}->${to.id}` : edge.id;
4676
+ const existing = routed.get(key);
4677
+ if (existing) {
4678
+ existing.edgeIds.push(edge.id);
4679
+ continue;
4680
+ }
4681
+ let startX;
4682
+ let startY;
4683
+ let endX;
4684
+ let endY;
4685
+ let path;
4686
+ let labelX;
4687
+ let labelY;
4688
+ let startAngle;
4689
+ let endAngle;
4690
+ if (kind === "forward") {
4691
+ startX = from.x + from.width;
4692
+ startY = from.y + from.height / 2;
4693
+ endX = to.x;
4694
+ endY = to.y + to.height / 2;
4695
+ const bend = Math.max(columnGap / 2, 24);
4696
+ path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bend)} ${round1(startY)} ${round1(endX - bend)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
4697
+ labelX = (startX + endX) / 2;
4698
+ labelY = (startY + endY) / 2;
4699
+ startAngle = Math.PI;
4700
+ endAngle = 0;
4701
+ } else if (from.depth === to.depth) {
4702
+ startX = from.x + from.width;
4703
+ startY = from.y + from.height / 2;
4704
+ endX = to.x + to.width;
4705
+ endY = to.y + to.height / 2;
4706
+ const bulge = Math.min(columnGap - 8, 20 + Math.abs(endY - startY) * 0.1);
4707
+ path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bulge)} ${round1(startY)} ${round1(endX + bulge)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
4708
+ labelX = startX + bulge * 0.75;
4709
+ labelY = (startY + endY) / 2;
4710
+ startAngle = Math.PI;
4711
+ endAngle = Math.PI;
4712
+ } else {
4713
+ startX = from.x + from.width / 2;
4714
+ startY = from.y + from.height;
4715
+ endX = to.x + to.width / 2;
4716
+ endY = to.y + to.height;
4717
+ const dip = 28 + Math.abs(endX - startX) * 0.12;
4718
+ const low = Math.max(startY, endY) + dip;
4719
+ path = `M${round1(startX)} ${round1(startY)} C${round1(startX)} ${round1(low)} ${round1(endX)} ${round1(low)} ${round1(endX)} ${round1(endY)}`;
4720
+ labelX = (startX + endX) / 2;
4721
+ labelY = low - dip * 0.25;
4722
+ startAngle = -Math.PI / 2;
4723
+ endAngle = -Math.PI / 2;
4724
+ height = Math.max(height, low);
4725
+ }
4726
+ const arrow = edge.arrow ?? "forward";
4727
+ const atStart = arrow === "backward";
4728
+ const entry = {
4729
+ id: key,
4730
+ source: from.id,
4731
+ target: to.id,
4732
+ kind,
4733
+ edgeIds: [edge.id],
4734
+ arrow,
4735
+ emphasis: edge.emphasis === true,
4736
+ path,
4737
+ labelX: round1(labelX),
4738
+ labelY: round1(labelY),
4739
+ arrowX: round1(atStart ? startX : endX),
4740
+ arrowY: round1(atStart ? startY : endY),
4741
+ arrowAngle: atStart ? startAngle : endAngle,
4742
+ labelLines: [],
4743
+ autoLabel: to.kind !== "cluster" && (edge.emphasis === true || kind === "forward" && from.children.length <= AUTO_LABEL_MAX_FANOUT)
4744
+ };
4745
+ if (edge.type !== void 0) entry.type = edge.type;
4746
+ const label = edge.label ?? edge.type;
4747
+ if (label !== void 0) entry.label = label;
4748
+ routed.set(key, entry);
4749
+ }
4750
+ const seenLabels = /* @__PURE__ */ new Set();
4751
+ for (const entry of routed.values()) {
4752
+ if (entry.label === void 0) continue;
4753
+ entry.labelLines = wrapEdgeLabel(entry.label, columnGap - 8);
4754
+ if (!entry.autoLabel || entry.emphasis || entry.kind !== "forward") continue;
4755
+ const key = `${entry.source}|${entry.label}`;
4756
+ if (seenLabels.has(key)) entry.autoLabel = false;
4757
+ else seenLabels.add(key);
4758
+ }
4759
+ return {
4760
+ rootId,
4761
+ items: depths.flatMap((depth) => columnsByDepth.get(depth)),
4762
+ edges: [...routed.values()],
4763
+ clusters: [...clusterById.values()],
4764
+ columns,
4765
+ itemOf,
4766
+ width: round1(width + LAYERED_PADDING),
4767
+ height: round1(height + LAYERED_PADDING + 8),
4768
+ nodeWidth,
4769
+ nodeHeight,
4770
+ headerHeight: LAYERED_HEADER
4771
+ };
4772
+ }
4773
+ function layeredNeighbor(layout, currentId, key) {
4774
+ const root = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
4775
+ const current = layout.items.find((item) => item.id === currentId);
4776
+ if (!current) return root ?? layout.items[0]?.id;
4777
+ const column = layout.items.filter((item) => item.depth === current.depth);
4778
+ switch (key) {
4779
+ case "ArrowUp":
4780
+ return column[current.index - 1]?.id;
4781
+ case "ArrowDown":
4782
+ return column[current.index + 1]?.id;
4783
+ case "ArrowRight":
4784
+ return current.children[0];
4785
+ case "ArrowLeft":
4786
+ return current.parents[0];
4787
+ case "Home":
4788
+ return root;
4789
+ case "End":
4790
+ return column[column.length - 1]?.id;
4791
+ default:
4792
+ return void 0;
4793
+ }
4794
+ }
3926
4795
 
3927
- // src/components/RelationshipGraph.tsx
4796
+ // src/components/RelationshipGraphLayered.tsx
4797
+ var import_react40 = require("react");
4798
+
4799
+ // src/components/RelationshipGraphList.tsx
3928
4800
  var import_jsx_runtime62 = require("react/jsx-runtime");
4801
+ function EquivalentRelationshipList({
4802
+ nodes,
4803
+ edges,
4804
+ selectedId,
4805
+ labels,
4806
+ onSelect,
4807
+ onExpandNeighbors,
4808
+ onOpenDetails
4809
+ }) {
4810
+ const labelById = new Map(nodes.map((node) => [node.id, node.label]));
4811
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "uix-relationship-graph__list", role: "region", "aria-label": labels.list, children: [
4812
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("h3", { children: labels.listHeading }),
4813
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("ul", { children: nodes.map((node) => {
4814
+ const neighbors = relationshipNeighbors(node.id, edges).map((id) => labelById.get(id) ?? id);
4815
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("li", { "data-selected": selectedId === node.id || void 0, children: [
4816
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("button", { type: "button", onClick: () => onSelect(node.id), "aria-pressed": selectedId === node.id, children: [
4817
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("strong", { children: node.label }),
4818
+ node.type && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: node.type }),
4819
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: neighbors.length ? labels.connectedTo(neighbors.join(", ")) : labels.noConnections })
4820
+ ] }),
4821
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "uix-relationship-graph__list-actions", children: [
4822
+ onExpandNeighbors && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onExpandNeighbors(node), children: labels.expandNeighbors }),
4823
+ onOpenDetails && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onOpenDetails(node), children: labels.openDetails })
4824
+ ] })
4825
+ ] }, node.id);
4826
+ }) })
4827
+ ] });
4828
+ }
4829
+
4830
+ // src/components/RelationshipGraphLayered.tsx
4831
+ var import_jsx_runtime63 = require("react/jsx-runtime");
3929
4832
  var EMPTY_IDS = /* @__PURE__ */ new Set();
3930
- var EMPTY_LEGEND = [];
3931
- function RelationshipGraph({
4833
+ var MIN_ZOOM = 0.4;
4834
+ var MAX_ZOOM = 1.5;
4835
+ var READABLE_ZOOM = 0.75;
4836
+ var ZOOM_STEP = 1.2;
4837
+ var PAN_STEP = 64;
4838
+ var FOCUS_MARGIN = 24;
4839
+ var NAV_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"];
4840
+ var clampZoom = (zoom) => Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
4841
+ function initialView(layout, size) {
4842
+ if (size.width === 0 || size.height === 0 || layout.width === 0) return { x: 0, y: 0, zoom: 1 };
4843
+ const fit = Math.min(size.width / layout.width, size.height / layout.height);
4844
+ if (fit < READABLE_ZOOM) return { x: 0, y: 0, zoom: READABLE_ZOOM };
4845
+ const zoom = Math.min(fit, 1);
4846
+ return { x: (size.width - layout.width * zoom) / 2, y: Math.max(0, (size.height - layout.height * zoom) / 2), zoom };
4847
+ }
4848
+ function fitView(layout, size) {
4849
+ if (size.width === 0 || layout.width === 0) return { x: 0, y: 0, zoom: 1 };
4850
+ const zoom = clampZoom(Math.min(size.width / layout.width, size.height / layout.height, 1));
4851
+ return { x: (size.width - layout.width * zoom) / 2, y: Math.max(0, (size.height - layout.height * zoom) / 2), zoom };
4852
+ }
4853
+ var arrowPoints = "0,0 -8,-4.5 -8,4.5";
4854
+ function LayeredRelationshipGraph({
3932
4855
  nodes,
3933
4856
  edges,
3934
4857
  selectedId,
3935
4858
  onSelect,
3936
4859
  onExpandNeighbors,
3937
4860
  onOpenDetails,
3938
- maxNodes = 40,
4861
+ maxNodes = 400,
3939
4862
  density = "standard",
3940
4863
  highlightedNodeIds = EMPTY_IDS,
3941
4864
  conflictedNodeIds = EMPTY_IDS,
3942
4865
  highlightedEdgeIds = EMPTY_IDS,
3943
4866
  conflictedEdgeIds = EMPTY_IDS,
4867
+ legend = [],
4868
+ className,
4869
+ labels,
4870
+ rootId,
4871
+ renderNode,
4872
+ nodeAriaLabel,
4873
+ renderCluster,
4874
+ clusterThreshold,
4875
+ expandedClusterIds,
4876
+ onToggleCluster,
4877
+ dimmedNodeIds = EMPTY_IDS,
4878
+ edgeLabels = "auto",
4879
+ showList = true,
4880
+ height,
4881
+ focusId,
4882
+ onFocusChange
4883
+ }) {
4884
+ const hintId = (0, import_react40.useId)();
4885
+ const bounded = (0, import_react40.useMemo)(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
4886
+ const [localExpanded, setLocalExpanded] = (0, import_react40.useState)(EMPTY_IDS);
4887
+ const expanded = expandedClusterIds ?? localExpanded;
4888
+ const layout = (0, import_react40.useMemo)(
4889
+ () => layoutLayeredGraph(bounded.nodes, bounded.edges, {
4890
+ ...rootId !== void 0 ? { rootId } : {},
4891
+ ...clusterThreshold !== void 0 ? { clusterThreshold } : {},
4892
+ expandedClusterIds: expanded,
4893
+ density
4894
+ }),
4895
+ [bounded, rootId, clusterThreshold, expanded, density]
4896
+ );
4897
+ const itemById = (0, import_react40.useMemo)(() => new Map(layout.items.map((item) => [item.id, item])), [layout]);
4898
+ const viewportRef = (0, import_react40.useRef)(null);
4899
+ const itemRefs = (0, import_react40.useRef)(/* @__PURE__ */ new Map());
4900
+ const [size, setSize] = (0, import_react40.useState)({ width: 0, height: 0 });
4901
+ const [view, setView] = (0, import_react40.useState)({ x: 0, y: 0, zoom: 1 });
4902
+ const [animate, setAnimate] = (0, import_react40.useState)(false);
4903
+ const [localFocus, setLocalFocus] = (0, import_react40.useState)(void 0);
4904
+ const [hoverId, setHoverId] = (0, import_react40.useState)(void 0);
4905
+ const viewRef = (0, import_react40.useRef)(view);
4906
+ viewRef.current = view;
4907
+ const layoutRef = (0, import_react40.useRef)(layout);
4908
+ layoutRef.current = layout;
4909
+ const pendingFocus = (0, import_react40.useRef)(void 0);
4910
+ const initialisedFor = (0, import_react40.useRef)(void 0);
4911
+ const rootItemId = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
4912
+ const requestedFocus = focusId !== void 0 ? layout.itemOf[focusId] ?? focusId : localFocus;
4913
+ const focusedId = requestedFocus !== void 0 && itemById.has(requestedFocus) ? requestedFocus : rootItemId;
4914
+ const selectedItemId = selectedId !== void 0 ? layout.itemOf[selectedId] : void 0;
4915
+ const activeIds = (0, import_react40.useMemo)(
4916
+ () => new Set([hoverId, selectedItemId].filter((id) => id !== void 0)),
4917
+ [hoverId, selectedItemId]
4918
+ );
4919
+ (0, import_react40.useEffect)(() => {
4920
+ const element = viewportRef.current;
4921
+ if (!element) return;
4922
+ const measure = () => setSize({ width: element.clientWidth, height: element.clientHeight });
4923
+ measure();
4924
+ if (typeof ResizeObserver === "undefined") return;
4925
+ const observer = new ResizeObserver(measure);
4926
+ observer.observe(element);
4927
+ return () => observer.disconnect();
4928
+ }, []);
4929
+ (0, import_react40.useEffect)(() => {
4930
+ if (size.width === 0) return;
4931
+ const key = `${layout.rootId ?? ""}`;
4932
+ if (initialisedFor.current === key) return;
4933
+ initialisedFor.current = key;
4934
+ setView(initialView(layout, size));
4935
+ }, [layout, size]);
4936
+ const moveView = (0, import_react40.useCallback)((next, smooth) => {
4937
+ setAnimate(smooth);
4938
+ setView(next);
4939
+ }, []);
4940
+ const ensureVisible = (0, import_react40.useCallback)((id, smooth) => {
4941
+ const element = viewportRef.current;
4942
+ const item = layoutRef.current.items.find((candidate) => candidate.id === id);
4943
+ if (!element || !item) return;
4944
+ const { x, y, zoom } = viewRef.current;
4945
+ const width = element.clientWidth;
4946
+ const heightPx = element.clientHeight;
4947
+ const left = item.x * zoom + x;
4948
+ const top = item.y * zoom + y;
4949
+ const right = left + item.width * zoom;
4950
+ const bottom = top + item.height * zoom;
4951
+ let nextX = x;
4952
+ let nextY = y;
4953
+ if (left < FOCUS_MARGIN) nextX += FOCUS_MARGIN - left;
4954
+ else if (right > width - FOCUS_MARGIN) nextX -= right - (width - FOCUS_MARGIN);
4955
+ if (top < FOCUS_MARGIN) nextY += FOCUS_MARGIN - top;
4956
+ else if (bottom > heightPx - FOCUS_MARGIN) nextY -= bottom - (heightPx - FOCUS_MARGIN);
4957
+ if (nextX !== x || nextY !== y) moveView({ x: nextX, y: nextY, zoom }, smooth);
4958
+ }, [moveView]);
4959
+ const focusItem = (0, import_react40.useCallback)((id) => {
4960
+ setLocalFocus(id);
4961
+ onFocusChange?.(id);
4962
+ requestAnimationFrame(() => {
4963
+ itemRefs.current.get(id)?.focus({ preventScroll: true });
4964
+ ensureVisible(id, true);
4965
+ });
4966
+ }, [ensureVisible, onFocusChange]);
4967
+ (0, import_react40.useEffect)(() => {
4968
+ const wanted = pendingFocus.current;
4969
+ if (wanted !== void 0 && itemById.has(wanted)) {
4970
+ pendingFocus.current = void 0;
4971
+ focusItem(wanted);
4972
+ }
4973
+ }, [itemById, focusItem]);
4974
+ const controlledTarget = focusId !== void 0 ? layout.itemOf[focusId] ?? focusId : void 0;
4975
+ const controlledPresent = controlledTarget !== void 0 && itemById.has(controlledTarget);
4976
+ const controlledKey = `${controlledTarget ?? ""}|${controlledPresent}`;
4977
+ const lastControlledKey = (0, import_react40.useRef)(controlledKey);
4978
+ (0, import_react40.useEffect)(() => {
4979
+ if (lastControlledKey.current === controlledKey) return;
4980
+ lastControlledKey.current = controlledKey;
4981
+ if (!controlledPresent || controlledTarget === void 0) return;
4982
+ requestAnimationFrame(() => {
4983
+ itemRefs.current.get(controlledTarget)?.focus({ preventScroll: true });
4984
+ ensureVisible(controlledTarget, true);
4985
+ });
4986
+ }, [controlledKey, controlledTarget, controlledPresent, ensureVisible]);
4987
+ (0, import_react40.useEffect)(() => {
4988
+ const element = viewportRef.current;
4989
+ if (!element) return;
4990
+ const onWheel = (event) => {
4991
+ if (!event.ctrlKey && !event.metaKey) return;
4992
+ event.preventDefault();
4993
+ const rect = element.getBoundingClientRect();
4994
+ const px = event.clientX - rect.left;
4995
+ const py = event.clientY - rect.top;
4996
+ const current = viewRef.current;
4997
+ const zoom = clampZoom(current.zoom * Math.exp(-event.deltaY * 2e-3));
4998
+ const ratio = zoom / current.zoom;
4999
+ setAnimate(false);
5000
+ setView({ zoom, x: px - (px - current.x) * ratio, y: py - (py - current.y) * ratio });
5001
+ };
5002
+ element.addEventListener("wheel", onWheel, { passive: false });
5003
+ return () => element.removeEventListener("wheel", onWheel);
5004
+ }, []);
5005
+ const pointers = (0, import_react40.useRef)(/* @__PURE__ */ new Map());
5006
+ const pinchDistance = (0, import_react40.useRef)(void 0);
5007
+ const onPointerDown = (event) => {
5008
+ if (event.target.closest("button")) return;
5009
+ event.currentTarget.setPointerCapture?.(event.pointerId);
5010
+ pointers.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
5011
+ pinchDistance.current = void 0;
5012
+ setAnimate(false);
5013
+ };
5014
+ const onPointerMove = (event) => {
5015
+ const previous = pointers.current.get(event.pointerId);
5016
+ if (!previous) return;
5017
+ const current = { x: event.clientX, y: event.clientY };
5018
+ pointers.current.set(event.pointerId, current);
5019
+ const all = [...pointers.current.values()];
5020
+ if (all.length >= 2) {
5021
+ const [a, b] = all;
5022
+ const distance = Math.hypot(a.x - b.x, a.y - b.y);
5023
+ const rect = event.currentTarget.getBoundingClientRect();
5024
+ const mx = (a.x + b.x) / 2 - rect.left;
5025
+ const my = (a.y + b.y) / 2 - rect.top;
5026
+ if (pinchDistance.current !== void 0 && pinchDistance.current > 0) {
5027
+ const base = viewRef.current;
5028
+ const zoom = clampZoom(base.zoom * (distance / pinchDistance.current));
5029
+ const ratio = zoom / base.zoom;
5030
+ setView({ zoom, x: mx - (mx - base.x) * ratio, y: my - (my - base.y) * ratio });
5031
+ }
5032
+ pinchDistance.current = distance;
5033
+ return;
5034
+ }
5035
+ const dx = current.x - previous.x;
5036
+ const dy = current.y - previous.y;
5037
+ setView((value) => ({ ...value, x: value.x + dx, y: value.y + dy }));
5038
+ };
5039
+ const onPointerEnd = (event) => {
5040
+ pointers.current.delete(event.pointerId);
5041
+ pinchDistance.current = void 0;
5042
+ };
5043
+ const zoomBy = (factor) => {
5044
+ const current = viewRef.current;
5045
+ const zoom = clampZoom(current.zoom * factor);
5046
+ const ratio = zoom / current.zoom;
5047
+ const cx0 = size.width / 2;
5048
+ const cy0 = size.height / 2;
5049
+ moveView({ zoom, x: cx0 - (cx0 - current.x) * ratio, y: cy0 - (cy0 - current.y) * ratio }, true);
5050
+ };
5051
+ const panBy = (dx, dy) => moveView({ ...viewRef.current, x: viewRef.current.x + dx, y: viewRef.current.y + dy }, true);
5052
+ const toggleCluster = (clusterId, focusAfter) => {
5053
+ const isExpanded = expanded.has(clusterId);
5054
+ pendingFocus.current = focusAfter;
5055
+ if (expandedClusterIds === void 0) {
5056
+ setLocalExpanded((previous) => {
5057
+ const next = new Set(previous);
5058
+ if (isExpanded) next.delete(clusterId);
5059
+ else next.add(clusterId);
5060
+ return next;
5061
+ });
5062
+ }
5063
+ onToggleCluster?.(clusterId, !isExpanded);
5064
+ };
5065
+ const activate = (item) => {
5066
+ if (item.cluster) {
5067
+ toggleCluster(item.cluster.id, item.cluster.members[0]?.id ?? item.id);
5068
+ return;
5069
+ }
5070
+ if (item.node) {
5071
+ setLocalFocus(item.id);
5072
+ onFocusChange?.(item.id);
5073
+ onSelect?.(item.node);
5074
+ }
5075
+ };
5076
+ const onItemKeyDown = (event, item) => {
5077
+ if (NAV_KEYS.includes(event.key)) {
5078
+ event.preventDefault();
5079
+ const next = layeredNeighbor(layout, item.id, event.key);
5080
+ if (next !== void 0) focusItem(next);
5081
+ }
5082
+ };
5083
+ const labelFor = (item) => {
5084
+ if (item.cluster) {
5085
+ const cluster = item.cluster;
5086
+ return `${labels.cluster(cluster.members.length, cluster.type, cluster.edgeLabel)}, ${labels.expandCluster}`;
5087
+ }
5088
+ const node = item.node;
5089
+ const conflict = conflictedNodeIds.has(node.id) ? labels.conflict : void 0;
5090
+ if (nodeAriaLabel) return [nodeAriaLabel(node), item.inCycle ? labels.cycle : void 0, conflict].filter(Boolean).join(", ");
5091
+ return [node.label, node.type, node.description, labels.relationships(item.degree), item.inCycle ? labels.cycle : void 0, conflict].filter(Boolean).join(", ");
5092
+ };
5093
+ const showLabel = (edge) => {
5094
+ if (edgeLabels === "never" || edge.label === void 0) return false;
5095
+ if (edgeLabels === "always") return true;
5096
+ return edge.autoLabel || activeIds.has(edge.source) || activeIds.has(edge.target) || focusedId === edge.source || focusedId === edge.target;
5097
+ };
5098
+ const viewportStyle = height !== void 0 ? { "--uix-relationship-graph-height": height } : {};
5099
+ const stageStyle = {
5100
+ width: layout.width,
5101
+ height: layout.height,
5102
+ transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom})`
5103
+ };
5104
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("section", { className: cx("uix-relationship-graph", "uix-relationship-graph--layered", density === "compact" && "uix-relationship-graph--compact", className), "aria-label": labels.graph, children: [
5105
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-relationship-graph__toolbar", role: "group", "aria-label": labels.controls, children: [
5106
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => zoomBy(ZOOM_STEP), disabled: view.zoom >= MAX_ZOOM, children: labels.zoomIn }),
5107
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => zoomBy(1 / ZOOM_STEP), disabled: view.zoom <= MIN_ZOOM, children: labels.zoomOut }),
5108
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => moveView(fitView(layout, size), true), children: labels.fit }),
5109
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => moveView(initialView(layout, size), true), children: labels.reset }),
5110
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(PAN_STEP, 0), "aria-label": labels.panLeft, children: labels.left }),
5111
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(0, PAN_STEP), "aria-label": labels.panUp, children: labels.up }),
5112
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(0, -PAN_STEP), "aria-label": labels.panDown, children: labels.down }),
5113
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", onClick: () => panBy(-PAN_STEP, 0), "aria-label": labels.panRight, children: labels.right }),
5114
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { "aria-live": "polite", children: labels.zoomLevel(Math.round(view.zoom * 100)) })
5115
+ ] }),
5116
+ bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { className: "uix-relationship-graph__bounded", role: "status", children: labels.bounded(bounded.nodes.length, bounded.omittedNodeCount) }),
5117
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { id: hintId, className: "uix-visually-hidden", children: labels.keyboardHint }),
5118
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5119
+ "div",
5120
+ {
5121
+ ref: viewportRef,
5122
+ className: "uix-relationship-graph__viewport",
5123
+ style: viewportStyle,
5124
+ onPointerDown,
5125
+ onPointerMove,
5126
+ onPointerUp: onPointerEnd,
5127
+ onPointerCancel: onPointerEnd,
5128
+ children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-relationship-graph__stage", style: stageStyle, "data-animate": animate || void 0, onTransitionEnd: () => setAnimate(false), children: [
5129
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("svg", { className: "uix-relationship-graph__edges", width: layout.width, height: layout.height, "aria-hidden": "true", focusable: "false", children: layout.edges.map((edge) => {
5130
+ const active = activeIds.has(edge.source) || activeIds.has(edge.target) || edge.edgeIds.some((id) => highlightedEdgeIds.has(id));
5131
+ const angle = edge.arrowAngle * 180 / Math.PI;
5132
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(
5133
+ "g",
5134
+ {
5135
+ className: "uix-relationship-graph__link",
5136
+ "data-kind": edge.kind,
5137
+ "data-active": active || void 0,
5138
+ "data-emphasis": edge.emphasis || void 0,
5139
+ "data-conflicted": edge.edgeIds.some((id) => conflictedEdgeIds.has(id)) || void 0,
5140
+ "data-dimmed": dimmedNodeIds.has(edge.source) && dimmedNodeIds.has(edge.target) || void 0,
5141
+ children: [
5142
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("path", { d: edge.path }),
5143
+ edge.arrow !== "none" && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("polygon", { points: arrowPoints, transform: `translate(${edge.arrowX} ${edge.arrowY}) rotate(${angle})` }),
5144
+ showLabel(edge) && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("text", { x: edge.labelX, y: edge.labelY, textAnchor: "middle", dominantBaseline: "middle", children: (edge.kind === "cycle" ? [...edge.labelLines, labels.cycle] : edge.labelLines).map((line, index, lines) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("tspan", { x: edge.labelX, dy: index === 0 ? `${-(lines.length - 1) * 0.55}em` : "1.1em", children: line }, index)) })
5145
+ ]
5146
+ },
5147
+ edge.id
5148
+ );
5149
+ }) }),
5150
+ layout.columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5151
+ "div",
5152
+ {
5153
+ className: "uix-relationship-graph__column",
5154
+ "aria-hidden": "true",
5155
+ style: { left: column.x, width: layout.nodeWidth },
5156
+ children: labels.column(column.depth, column.count)
5157
+ },
5158
+ column.depth
5159
+ )),
5160
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { role: "group", "aria-roledescription": labels.roleDescription, "aria-label": labels.graphSummary(bounded.nodes.length, bounded.edges.length), "aria-describedby": hintId, children: layout.items.map((item) => {
5161
+ const selected = item.id === selectedItemId;
5162
+ const dimmed = item.node ? dimmedNodeIds.has(item.node.id) : item.cluster.members.every((member) => dimmedNodeIds.has(member.id));
5163
+ const body = item.cluster ? renderCluster?.(item.cluster) ?? /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(import_jsx_runtime63.Fragment, { children: [
5164
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-label", children: labels.cluster(item.cluster.members.length, item.cluster.type, item.cluster.edgeLabel) }),
5165
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-meta", children: labels.expandCluster })
5166
+ ] }) : renderNode?.(item.node, { selected, dimmed }) ?? /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(import_jsx_runtime63.Fragment, { children: [
5167
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-label", children: item.node.label }),
5168
+ (item.node.type || item.node.description) && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-meta", children: [item.node.type, item.node.description].filter(Boolean).join(" \xB7 ") })
5169
+ ] });
5170
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(
5171
+ "button",
5172
+ {
5173
+ ref: (element) => {
5174
+ if (element) itemRefs.current.set(item.id, element);
5175
+ else itemRefs.current.delete(item.id);
5176
+ },
5177
+ type: "button",
5178
+ className: "uix-relationship-graph__item",
5179
+ "data-kind": item.kind,
5180
+ "data-node-id": item.id,
5181
+ "data-selected": selected || void 0,
5182
+ "data-dimmed": dimmed || void 0,
5183
+ "data-highlighted": item.node && highlightedNodeIds.has(item.node.id) || void 0,
5184
+ "data-conflicted": item.node && conflictedNodeIds.has(item.node.id) || void 0,
5185
+ "data-root": item.id === rootItemId || void 0,
5186
+ style: { left: item.x, top: item.y, width: item.width, height: item.height },
5187
+ tabIndex: item.id === focusedId ? 0 : -1,
5188
+ "aria-label": labelFor(item),
5189
+ ...item.cluster ? { "aria-expanded": false } : { "aria-pressed": selected },
5190
+ onClick: () => activate(item),
5191
+ onKeyDown: (event) => onItemKeyDown(event, item),
5192
+ onFocus: () => {
5193
+ if (item.id === focusedId) return;
5194
+ setLocalFocus(item.id);
5195
+ onFocusChange?.(item.id);
5196
+ },
5197
+ onPointerEnter: () => setHoverId(item.id),
5198
+ onPointerLeave: () => setHoverId((current) => current === item.id ? void 0 : current),
5199
+ children: [
5200
+ body,
5201
+ item.inCycle && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-relationship-graph__item-flag", "aria-hidden": "true", children: labels.cycle })
5202
+ ]
5203
+ },
5204
+ item.id
5205
+ );
5206
+ }) }),
5207
+ layout.items.filter((item) => item.expandedGroup).map((item) => {
5208
+ const group = item.expandedGroup;
5209
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5210
+ "button",
5211
+ {
5212
+ type: "button",
5213
+ className: "uix-relationship-graph__collapse",
5214
+ "aria-expanded": "true",
5215
+ style: { left: item.x, top: item.y - 26, width: item.width },
5216
+ onClick: () => toggleCluster(group.id, group.id),
5217
+ children: labels.collapseCluster(group.members.length, group.type)
5218
+ },
5219
+ `collapse:${group.id}`
5220
+ );
5221
+ })
5222
+ ] })
5223
+ }
5224
+ ),
5225
+ legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": labels.legend, children: legend.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("li", { "data-type": entry.id, children: entry.label }, entry.id)) }),
5226
+ showList && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5227
+ EquivalentRelationshipList,
5228
+ {
5229
+ nodes: bounded.nodes,
5230
+ edges: bounded.edges,
5231
+ selectedId,
5232
+ labels,
5233
+ onSelect: (id) => {
5234
+ const node = bounded.nodes.find((candidate) => candidate.id === id);
5235
+ if (node) onSelect?.(node);
5236
+ },
5237
+ onExpandNeighbors,
5238
+ onOpenDetails
5239
+ }
5240
+ )
5241
+ ] });
5242
+ }
5243
+
5244
+ // src/components/RelationshipGraph.tsx
5245
+ var import_jsx_runtime64 = require("react/jsx-runtime");
5246
+ var DEFAULT_RELATIONSHIP_GRAPH_LABELS = {
5247
+ graph: "Relationship graph",
5248
+ controls: "Graph view controls",
5249
+ list: "Accessible relationship list",
5250
+ listHeading: "Relationship list",
5251
+ legend: "Relationship types",
5252
+ panLeft: "Pan left",
5253
+ panRight: "Pan right",
5254
+ panUp: "Pan up",
5255
+ panDown: "Pan down",
5256
+ left: "Left",
5257
+ right: "Right",
5258
+ up: "Up",
5259
+ down: "Down",
5260
+ zoomIn: "Zoom in",
5261
+ zoomOut: "Zoom out",
5262
+ fit: "Fit",
5263
+ reset: "Reset view",
5264
+ zoomLevel: (percent) => `Zoom ${percent}%`,
5265
+ bounded: (shown, omitted) => `Showing ${shown} nodes; ${omitted} outside this bounded neighborhood.`,
5266
+ graphSummary: (nodes, edges) => `Graph with ${nodes} nodes and ${edges} relationships`,
5267
+ relationship: "Relationship",
5268
+ conflict: "conflict",
5269
+ connectedTo: (names) => `Connected to ${names}`,
5270
+ noConnections: "No visible connections",
5271
+ expandNeighbors: "Expand neighbors",
5272
+ openDetails: "Open details",
5273
+ column: (depth, count) => `${depth === 0 ? "Start" : `${depth} ${depth === 1 ? "hop" : "hops"}`} \xB7 ${count}`,
5274
+ cluster: (count, type, edgeLabel) => `${count} \xD7 ${type ?? "items"}${edgeLabel ? ` \xB7 ${edgeLabel}` : ""}`,
5275
+ expandCluster: "Show all",
5276
+ collapseCluster: (count, type) => `Collapse ${count} ${type ?? "items"}`,
5277
+ cycle: "cycle",
5278
+ relationships: (count) => `${count} ${count === 1 ? "relationship" : "relationships"}`,
5279
+ roleDescription: "relationship map",
5280
+ keyboardHint: "Arrow keys move between connected items, Enter selects, Home returns to the start.",
5281
+ loading: "Loading relationships\u2026",
5282
+ retry: "Try again",
5283
+ emptyTitle: "No relationships to display.",
5284
+ emptyHint: "Adjust the neighborhood or filters to include a node."
5285
+ };
5286
+ var EMPTY_IDS2 = /* @__PURE__ */ new Set();
5287
+ var EMPTY_LEGEND = [];
5288
+ function RelationshipGraph(props) {
5289
+ const labels = (0, import_react41.useMemo)(() => ({ ...DEFAULT_RELATIONSHIP_GRAPH_LABELS, ...props.labels }), [props.labels]);
5290
+ const stateClass = cx("uix-relationship-graph uix-relationship-graph--state", props.className);
5291
+ if (props.loading) return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: stateClass, role: "status", children: labels.loading });
5292
+ if (props.error) {
5293
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: stateClass, role: "alert", children: [
5294
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { children: props.error }),
5295
+ props.onRetry && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: props.onRetry, children: labels.retry })
5296
+ ] });
5297
+ }
5298
+ if (props.nodes.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: stateClass, children: [
5299
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { children: labels.emptyTitle }),
5300
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { children: labels.emptyHint })
5301
+ ] });
5302
+ if (props.layout === "layered") return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(LayeredRelationshipGraph, { ...props, labels });
5303
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(RadialRelationshipGraph, { ...props, labels });
5304
+ }
5305
+ function RadialRelationshipGraph({
5306
+ nodes,
5307
+ edges,
5308
+ selectedId,
5309
+ onSelect,
5310
+ onExpandNeighbors,
5311
+ onOpenDetails,
5312
+ maxNodes = 40,
5313
+ density = "standard",
5314
+ highlightedNodeIds = EMPTY_IDS2,
5315
+ conflictedNodeIds = EMPTY_IDS2,
5316
+ highlightedEdgeIds = EMPTY_IDS2,
5317
+ conflictedEdgeIds = EMPTY_IDS2,
3944
5318
  legend = EMPTY_LEGEND,
3945
- loading,
3946
- error,
3947
- onRetry,
3948
- className
5319
+ className,
5320
+ labels,
5321
+ showList = true
3949
5322
  }) {
3950
- const bounded = (0, import_react33.useMemo)(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
3951
- const positioned = (0, import_react33.useMemo)(() => layoutRelationshipGraph(bounded.nodes, bounded.edges, selectedId), [bounded, selectedId]);
3952
- const positionedById = (0, import_react33.useMemo)(() => new Map(positioned.map((node) => [node.id, node])), [positioned]);
3953
- const [zoom, setZoom] = (0, import_react33.useState)(1);
3954
- const [pan, setPan] = (0, import_react33.useState)({ x: 0, y: 0 });
3955
- const [focusedId, setFocusedId] = (0, import_react33.useState)(selectedId ?? positioned[0]?.id);
3956
- const svgRef = (0, import_react33.useRef)(null);
5323
+ const bounded = (0, import_react41.useMemo)(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
5324
+ const positioned = (0, import_react41.useMemo)(() => layoutRelationshipGraph(bounded.nodes, bounded.edges, selectedId), [bounded, selectedId]);
5325
+ const positionedById = (0, import_react41.useMemo)(() => new Map(positioned.map((node) => [node.id, node])), [positioned]);
5326
+ const [zoom, setZoom] = (0, import_react41.useState)(1);
5327
+ const [pan, setPan] = (0, import_react41.useState)({ x: 0, y: 0 });
5328
+ const [focusedId, setFocusedId] = (0, import_react41.useState)(selectedId ?? positioned[0]?.id);
5329
+ const svgRef = (0, import_react41.useRef)(null);
3957
5330
  const select = (id) => {
3958
5331
  const node = bounded.nodes.find((item) => item.id === id);
3959
5332
  if (node) onSelect?.(node);
@@ -3976,49 +5349,30 @@ function RelationshipGraph({
3976
5349
  setZoom(1);
3977
5350
  setPan({ x: 0, y: 0 });
3978
5351
  };
3979
- if (loading) return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "status", children: "Loading relationships\u2026" });
3980
- if (error) return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), role: "alert", children: [
3981
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { children: error }),
3982
- onRetry && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
3983
- ] });
3984
- if (bounded.nodes.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: cx("uix-relationship-graph uix-relationship-graph--state", className), children: [
3985
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { children: "No relationships to display." }),
3986
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { children: "Adjust the neighborhood or filters to include a node." })
3987
- ] });
3988
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("section", { className: cx("uix-relationship-graph", `uix-relationship-graph--${density}`, className), "aria-label": "Relationship graph", children: [
3989
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "uix-relationship-graph__toolbar", "aria-label": "Graph view controls", children: [
3990
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x - 24 })), "aria-label": "Pan left", children: "Left" }),
3991
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y - 24 })), "aria-label": "Pan up", children: "Up" }),
3992
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y + 24 })), "aria-label": "Pan down", children: "Down" }),
3993
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x + 24 })), "aria-label": "Pan right", children: "Right" }),
3994
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.min(2, Number((value + 0.2).toFixed(1)))), "aria-label": "Zoom in", children: "Zoom in" }),
3995
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.max(0.5, Number((value - 0.2).toFixed(1)))), "aria-label": "Zoom out", children: "Zoom out" }),
3996
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: reset, children: "Reset view" }),
3997
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { "aria-live": "polite", children: [
3998
- "Zoom ",
3999
- Math.round(zoom * 100),
4000
- "%"
4001
- ] })
4002
- ] }),
4003
- bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("p", { className: "uix-relationship-graph__bounded", role: "status", children: [
4004
- "Showing ",
4005
- bounded.nodes.length,
4006
- " nodes; ",
4007
- bounded.omittedNodeCount,
4008
- " outside this bounded neighborhood."
5352
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("section", { className: cx("uix-relationship-graph", `uix-relationship-graph--${density}`, className), "aria-label": labels.graph, children: [
5353
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: "uix-relationship-graph__toolbar", "aria-label": labels.controls, children: [
5354
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x - 24 })), "aria-label": labels.panLeft, children: labels.left }),
5355
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y - 24 })), "aria-label": labels.panUp, children: labels.up }),
5356
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y + 24 })), "aria-label": labels.panDown, children: labels.down }),
5357
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x + 24 })), "aria-label": labels.panRight, children: labels.right }),
5358
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.min(2, Number((value + 0.2).toFixed(1)))), "aria-label": labels.zoomIn, children: labels.zoomIn }),
5359
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: () => setZoom((value) => Math.max(0.5, Number((value - 0.2).toFixed(1)))), "aria-label": labels.zoomOut, children: labels.zoomOut }),
5360
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", onClick: reset, children: labels.reset }),
5361
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { "aria-live": "polite", children: labels.zoomLevel(Math.round(zoom * 100)) })
4009
5362
  ] }),
4010
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "uix-relationship-graph__visual", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("svg", { ref: svgRef, viewBox: "0 0 600 400", role: "group", "aria-label": `Graph with ${bounded.nodes.length} nodes and ${bounded.edges.length} relationships`, children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("g", { transform: `translate(${pan.x} ${pan.y}) scale(${zoom})`, children: [
5363
+ bounded.omittedNodeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("p", { className: "uix-relationship-graph__bounded", role: "status", children: labels.bounded(bounded.nodes.length, bounded.omittedNodeCount) }),
5364
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: "uix-relationship-graph__visual", children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("svg", { ref: svgRef, viewBox: "0 0 600 400", role: "group", "aria-label": labels.graphSummary(bounded.nodes.length, bounded.edges.length), children: /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("g", { transform: `translate(${pan.x} ${pan.y}) scale(${zoom})`, children: [
4011
5365
  bounded.edges.map((edge) => {
4012
5366
  const source = positionedById.get(edge.source);
4013
5367
  const target = positionedById.get(edge.target);
4014
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("line", { x1: source.x * 6, y1: source.y * 4, x2: target.x * 6, y2: target.y * 4, className: "uix-relationship-graph__edge", "data-highlighted": highlightedEdgeIds.has(edge.id) || void 0, "data-conflicted": conflictedEdgeIds.has(edge.id) || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("title", { children: edge.label ?? edge.type ?? "Relationship" }) }, edge.id);
5368
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("line", { x1: source.x * 6, y1: source.y * 4, x2: target.x * 6, y2: target.y * 4, className: "uix-relationship-graph__edge", "data-highlighted": highlightedEdgeIds.has(edge.id) || void 0, "data-conflicted": conflictedEdgeIds.has(edge.id) || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("title", { children: edge.label ?? edge.type ?? labels.relationship }) }, edge.id);
4015
5369
  }),
4016
- positioned.map((node) => /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(
5370
+ positioned.map((node) => /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(
4017
5371
  "g",
4018
5372
  {
4019
5373
  transform: `translate(${node.x * 6} ${node.y * 4})`,
4020
5374
  role: "button",
4021
- "aria-label": `${node.label}${node.type ? `, ${node.type}` : ""}${conflictedNodeIds.has(node.id) ? ", conflict" : ""}`,
5375
+ "aria-label": `${node.label}${node.type ? `, ${node.type}` : ""}${conflictedNodeIds.has(node.id) ? `, ${labels.conflict}` : ""}`,
4022
5376
  tabIndex: focusedId === node.id ? 0 : -1,
4023
5377
  "data-node-id": node.id,
4024
5378
  className: "uix-relationship-graph__node",
@@ -4029,37 +5383,21 @@ function RelationshipGraph({
4029
5383
  onKeyDown: (event) => onNodeKeyDown(event, node.id),
4030
5384
  onClick: () => select(node.id),
4031
5385
  children: [
4032
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("rect", { x: "-55", y: "-18", width: "110", height: "36", rx: "8" }),
4033
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("text", { textAnchor: "middle", dominantBaseline: "middle", children: node.label.length > 16 ? `${node.label.slice(0, 15)}\u2026` : node.label })
5386
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("rect", { x: "-55", y: "-18", width: "110", height: "36", rx: "8" }),
5387
+ /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("text", { textAnchor: "middle", dominantBaseline: "middle", children: node.label.length > 16 ? `${node.label.slice(0, 15)}\u2026` : node.label })
4034
5388
  ]
4035
5389
  },
4036
5390
  node.id
4037
5391
  ))
4038
5392
  ] }) }) }),
4039
- legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": "Relationship types", children: legend.map((item) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("li", { "data-type": item.id, children: item.label }, item.id)) }),
4040
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "uix-relationship-graph__list", role: "region", "aria-label": "Accessible relationship list", children: [
4041
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("h3", { children: "Relationship list" }),
4042
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("ul", { children: bounded.nodes.map((node) => {
4043
- const neighbors = relationshipNeighbors(node.id, bounded.edges).map((id) => bounded.nodes.find((item) => item.id === id)?.label ?? id);
4044
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("li", { "data-selected": selectedId === node.id || void 0, children: [
4045
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("button", { type: "button", onClick: () => select(node.id), "aria-pressed": selectedId === node.id, children: [
4046
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("strong", { children: node.label }),
4047
- node.type && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: node.type }),
4048
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: neighbors.length ? `Connected to ${neighbors.join(", ")}` : "No visible connections" })
4049
- ] }),
4050
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "uix-relationship-graph__list-actions", children: [
4051
- onExpandNeighbors && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onExpandNeighbors(node), children: "Expand neighbors" }),
4052
- onOpenDetails && /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("button", { type: "button", onClick: () => onOpenDetails(node), children: "Open details" })
4053
- ] })
4054
- ] }, node.id);
4055
- }) })
4056
- ] })
5393
+ legend.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("ul", { className: "uix-relationship-graph__legend", "aria-label": labels.legend, children: legend.map((item) => /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("li", { "data-type": item.id, children: item.label }, item.id)) }),
5394
+ showList && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(EquivalentRelationshipList, { nodes: bounded.nodes, edges: bounded.edges, selectedId, labels, onSelect: select, onExpandNeighbors, onOpenDetails })
4057
5395
  ] });
4058
5396
  }
4059
5397
 
4060
5398
  // src/components/MatchReview.tsx
4061
- var import_react34 = require("react");
4062
- var import_jsx_runtime63 = require("react/jsx-runtime");
5399
+ var import_react42 = require("react");
5400
+ var import_jsx_runtime65 = require("react/jsx-runtime");
4063
5401
  var valueText = (value) => value == null ? "\u2014" : typeof value === "string" ? value : JSON.stringify(value);
4064
5402
  function MatchReview({
4065
5403
  incoming,
@@ -4075,11 +5413,11 @@ function MatchReview({
4075
5413
  onRetry,
4076
5414
  className
4077
5415
  }) {
4078
- const [internalSelection, setInternalSelection] = (0, import_react34.useState)(/* @__PURE__ */ new Set());
4079
- const [announcement, setAnnouncement] = (0, import_react34.useState)("");
4080
- const [bulkPending, setBulkPending] = (0, import_react34.useState)(false);
5416
+ const [internalSelection, setInternalSelection] = (0, import_react42.useState)(/* @__PURE__ */ new Set());
5417
+ const [announcement, setAnnouncement] = (0, import_react42.useState)("");
5418
+ const [bulkPending, setBulkPending] = (0, import_react42.useState)(false);
4081
5419
  const selection = selectedIds ?? internalSelection;
4082
- const pending = (0, import_react34.useMemo)(() => candidates.filter((candidate) => candidate.status !== "resolved"), [candidates]);
5420
+ const pending = (0, import_react42.useMemo)(() => candidates.filter((candidate) => candidate.status !== "resolved"), [candidates]);
4083
5421
  const setSelection = (next) => {
4084
5422
  if (selectedIds === void 0) setInternalSelection(next);
4085
5423
  onSelectionChange?.(next);
@@ -4107,55 +5445,55 @@ function MatchReview({
4107
5445
  setBulkPending(false);
4108
5446
  }
4109
5447
  };
4110
- if (loading) return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
4111
- if (error) return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
4112
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { children: error }),
4113
- onRetry && /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
5448
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
5449
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
5450
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { children: error }),
5451
+ onRetry && /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
4114
5452
  ] });
4115
- if (candidates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
4116
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { children: "No candidate matches found." }),
4117
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { children: "Choose another record source or continue without a match." })
5453
+ if (candidates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
5454
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { children: "No candidate matches found." }),
5455
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { children: "Choose another record source or continue without a match." })
4118
5456
  ] });
4119
- return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
4120
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-match-review__summary", children: [
4121
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { children: [
4122
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("strong", { children: incomingLabel }),
4123
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("span", { children: [
5457
+ return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
5458
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-match-review__summary", children: [
5459
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { children: [
5460
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("strong", { children: incomingLabel }),
5461
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { children: [
4124
5462
  pending.length,
4125
5463
  " pending of ",
4126
5464
  candidates.length
4127
5465
  ] })
4128
5466
  ] }),
4129
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("span", { children: [
4130
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("b", { children: field.label }),
5467
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { children: [
5468
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("b", { children: field.label }),
4131
5469
  field.render?.(incoming[field.id], incoming) ?? valueText(incoming[field.id])
4132
5470
  ] }, field.id)) })
4133
5471
  ] }),
4134
- onBulkDecision && /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
4135
- /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("span", { children: [
5472
+ onBulkDecision && /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
5473
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { children: [
4136
5474
  selection.size,
4137
5475
  " selected"
4138
5476
  ] }),
4139
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? "Applying\u2026" : "Accept selected" }),
4140
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? "Applying\u2026" : "Dismiss selected" })
5477
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? "Applying\u2026" : "Accept selected" }),
5478
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? "Applying\u2026" : "Dismiss selected" })
4141
5479
  ] }),
4142
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("table", { className: "uix-table uix-match-review__table", children: [
4143
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
4144
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("tr", { children: [
4145
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-visually-hidden", children: "Select" }) }),
4146
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: "Confidence" }),
4147
- fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: field.label }, field.id)),
4148
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: "Status" }),
4149
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("th", { scope: "col", children: "Actions" })
5480
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("table", { className: "uix-table uix-match-review__table", children: [
5481
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
5482
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("tr", { children: [
5483
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "uix-visually-hidden", children: "Select" }) }),
5484
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: "Confidence" }),
5485
+ fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: field.label }, field.id)),
5486
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: "Status" }),
5487
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("th", { scope: "col", children: "Actions" })
4150
5488
  ] }) }),
4151
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("tbody", { children: candidates.map((candidate) => {
5489
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("tbody", { children: candidates.map((candidate) => {
4152
5490
  const resolved = candidate.status === "resolved";
4153
- return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("tr", { "data-status": candidate.status ?? "pending", children: [
4154
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
4155
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
4156
- fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
4157
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
4158
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5491
+ return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("tr", { "data-status": candidate.status ?? "pending", children: [
5492
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
5493
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
5494
+ fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
5495
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
5496
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
4159
5497
  "button",
4160
5498
  {
4161
5499
  type: "button",
@@ -4169,12 +5507,12 @@ function MatchReview({
4169
5507
  ] }, candidate.id);
4170
5508
  }) })
4171
5509
  ] }) }),
4172
- /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
5510
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
4173
5511
  ] });
4174
5512
  }
4175
5513
 
4176
5514
  // src/components/MetricInput.tsx
4177
- var import_react35 = require("react");
5515
+ var import_react43 = require("react");
4178
5516
 
4179
5517
  // src/metric-model.ts
4180
5518
  function decimals(value) {
@@ -4199,7 +5537,7 @@ function parseMetricValue(value) {
4199
5537
  }
4200
5538
 
4201
5539
  // src/components/MetricInput.tsx
4202
- var import_jsx_runtime64 = require("react/jsx-runtime");
5540
+ var import_jsx_runtime66 = require("react/jsx-runtime");
4203
5541
  var defaultMetricFormat = (input) => input == null ? "" : String(input);
4204
5542
  function MetricInput({
4205
5543
  value,
@@ -4218,11 +5556,11 @@ function MetricInput({
4218
5556
  className,
4219
5557
  id: providedId
4220
5558
  }) {
4221
- const generatedId = (0, import_react35.useId)();
5559
+ const generatedId = (0, import_react43.useId)();
4222
5560
  const id = providedId ?? generatedId;
4223
5561
  const errorId = `${id}-error`;
4224
- const [text, setText] = (0, import_react35.useState)(() => format(value));
4225
- (0, import_react35.useEffect)(() => setText(format(value)), [value, format]);
5562
+ const [text, setText] = (0, import_react43.useState)(() => format(value));
5563
+ (0, import_react43.useEffect)(() => setText(format(value)), [value, format]);
4226
5564
  const commit = (input) => {
4227
5565
  const parsed = parse(input);
4228
5566
  if (input.trim() === "" || parsed !== null) {
@@ -4248,10 +5586,10 @@ function MetricInput({
4248
5586
  const parsed = parse(event.currentTarget.value);
4249
5587
  setText(parsed === null && event.currentTarget.value.trim() !== "" ? format(value) : format(parsed));
4250
5588
  };
4251
- return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: cx("uix-metric-input", invalid && "uix-metric-input--invalid", className), children: [
4252
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("label", { className: "uix-field__label", htmlFor: id, children: label }),
4253
- /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: "uix-metric-input__control", children: [
4254
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
5589
+ return /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: cx("uix-metric-input", invalid && "uix-metric-input--invalid", className), children: [
5590
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("label", { className: "uix-field__label", htmlFor: id, children: label }),
5591
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "uix-metric-input__control", children: [
5592
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
4255
5593
  Input,
4256
5594
  {
4257
5595
  id,
@@ -4270,22 +5608,22 @@ function MetricInput({
4270
5608
  readOnly
4271
5609
  }
4272
5610
  ),
4273
- unit && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: "uix-metric-input__unit", "aria-hidden": "true", children: unit }),
4274
- /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("span", { className: "uix-metric-input__steps", children: [
4275
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children: "+" }),
4276
- /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children: "\u2212" })
5611
+ unit && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "uix-metric-input__unit", "aria-hidden": "true", children: unit }),
5612
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "uix-metric-input__steps", children: [
5613
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children: "+" }),
5614
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children: "\u2212" })
4277
5615
  ] })
4278
5616
  ] }),
4279
- unit && /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("span", { className: "uix-visually-hidden", children: [
5617
+ unit && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "uix-visually-hidden", children: [
4280
5618
  "Unit: ",
4281
5619
  unit
4282
5620
  ] }),
4283
- invalid && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { id: errorId, className: "uix-field__error", children: errorMessage })
5621
+ invalid && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { id: errorId, className: "uix-field__error", children: errorMessage })
4284
5622
  ] });
4285
5623
  }
4286
5624
 
4287
5625
  // src/components/LicensePositionBar.tsx
4288
- var import_jsx_runtime65 = require("react/jsx-runtime");
5626
+ var import_jsx_runtime67 = require("react/jsx-runtime");
4289
5627
  var defaultCapacityFormat = (value) => new Intl.NumberFormat().format(value);
4290
5628
  function LicensePositionBar({
4291
5629
  consumed,
@@ -4306,18 +5644,18 @@ function LicensePositionBar({
4306
5644
  const percent = safeEntitled === 0 ? safeConsumed > 0 ? 100 : 0 : safeConsumed / safeEntitled * 100;
4307
5645
  const format = (value) => `${formatValue(value)}${unit ? ` ${unit}` : ""}`;
4308
5646
  const summary = `${consumedLabel}: ${format(safeConsumed)}. ${entitledLabel}: ${format(safeEntitled)}. ${over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}`}.`;
4309
- return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: cx("uix-license-position", over > 0 && "uix-license-position--over", className), ...props, children: [
4310
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-license-position__header", children: [
4311
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("span", { className: "uix-license-position__value", children: [
5647
+ return /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: cx("uix-license-position", over > 0 && "uix-license-position--over", className), ...props, children: [
5648
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-license-position__header", children: [
5649
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("span", { className: "uix-license-position__value", children: [
4312
5650
  format(safeConsumed),
4313
5651
  " / ",
4314
5652
  format(safeEntitled)
4315
5653
  ] }),
4316
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(StatusPill, { tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", children: over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}` })
5654
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(StatusPill, { tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", children: over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}` })
4317
5655
  ] }),
4318
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: "uix-license-position__track", children: [
4319
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(Meter, { value: Math.min(100, percent), tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", "aria-label": summary, "aria-valuetext": summary }),
4320
- over > 0 && /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
5656
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-license-position__track", children: [
5657
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(Meter, { value: Math.min(100, percent), tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", "aria-label": summary, "aria-valuetext": summary }),
5658
+ over > 0 && /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(
4321
5659
  "span",
4322
5660
  {
4323
5661
  className: "uix-license-position__overflow",
@@ -4326,12 +5664,12 @@ function LicensePositionBar({
4326
5664
  }
4327
5665
  )
4328
5666
  ] }),
4329
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("span", { className: "uix-visually-hidden", children: summary })
5667
+ /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "uix-visually-hidden", children: summary })
4330
5668
  ] });
4331
5669
  }
4332
5670
 
4333
5671
  // src/components/BrandProfiles.tsx
4334
- var import_react37 = require("react");
5672
+ var import_react45 = require("react");
4335
5673
 
4336
5674
  // src/color-model.ts
4337
5675
  var clamp2 = (value, min, max) => Math.min(max, Math.max(min, value));
@@ -4400,8 +5738,8 @@ function meetsContrast(background, foreground, minimum = 4.5) {
4400
5738
  }
4401
5739
 
4402
5740
  // src/components/ColorPicker.tsx
4403
- var import_react36 = require("react");
4404
- var import_jsx_runtime66 = require("react/jsx-runtime");
5741
+ var import_react44 = require("react");
5742
+ var import_jsx_runtime68 = require("react/jsx-runtime");
4405
5743
  var EMPTY_COLORS = [];
4406
5744
  function ColorPicker({
4407
5745
  value,
@@ -4414,17 +5752,17 @@ function ColorPicker({
4414
5752
  disabled,
4415
5753
  className
4416
5754
  }) {
4417
- const id = (0, import_react36.useId)();
4418
- const triggerRef = (0, import_react36.useRef)(null);
4419
- const dialogRef = (0, import_react36.useRef)(null);
4420
- const [open, setOpen] = (0, import_react36.useState)(false);
5755
+ const id = (0, import_react44.useId)();
5756
+ const triggerRef = (0, import_react44.useRef)(null);
5757
+ const dialogRef = (0, import_react44.useRef)(null);
5758
+ const [open, setOpen] = (0, import_react44.useState)(false);
4421
5759
  const normalized = normalizeHex(value) ?? "#000000";
4422
- const [hexText, setHexText] = (0, import_react36.useState)(normalized);
4423
- const hsv = (0, import_react36.useMemo)(() => rgbToHsv(hexToRgb(normalized)), [normalized]);
5760
+ const [hexText, setHexText] = (0, import_react44.useState)(normalized);
5761
+ const hsv = (0, import_react44.useMemo)(() => rgbToHsv(hexToRgb(normalized)), [normalized]);
4424
5762
  const ratio = contrastRatio(normalized, foreground) ?? 0;
4425
5763
  const passes = ratio >= minimumContrast;
4426
- (0, import_react36.useEffect)(() => setHexText(normalized), [normalized]);
4427
- (0, import_react36.useEffect)(() => {
5764
+ (0, import_react44.useEffect)(() => setHexText(normalized), [normalized]);
5765
+ (0, import_react44.useEffect)(() => {
4428
5766
  if (!open) return;
4429
5767
  const dialog = dialogRef.current;
4430
5768
  dialog?.querySelector("input,button")?.focus();
@@ -4467,8 +5805,8 @@ function ColorPicker({
4467
5805
  if (next) onChange(next);
4468
5806
  };
4469
5807
  const sampleStyle = { backgroundColor: normalized };
4470
- return /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: cx("uix-color-picker", className), children: [
4471
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
5808
+ return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: cx("uix-color-picker", className), children: [
5809
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
4472
5810
  "button",
4473
5811
  {
4474
5812
  ref: triggerRef,
@@ -4480,12 +5818,12 @@ function ColorPicker({
4480
5818
  onClick: () => setOpen((current) => !current),
4481
5819
  disabled,
4482
5820
  children: [
4483
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "uix-color-picker__swatch", style: sampleStyle, "aria-hidden": "true" }),
4484
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { children: normalized })
5821
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { className: "uix-color-picker__swatch", style: sampleStyle, "aria-hidden": "true" }),
5822
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { children: normalized })
4485
5823
  ]
4486
5824
  }
4487
5825
  ),
4488
- open && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
5826
+ open && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
4489
5827
  "div",
4490
5828
  {
4491
5829
  ref: dialogRef,
@@ -4496,57 +5834,57 @@ function ColorPicker({
4496
5834
  className: "uix-color-picker__popover",
4497
5835
  onKeyDown: onDialogKeyDown,
4498
5836
  children: [
4499
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { className: "uix-color-picker__preview", style: sampleStyle, children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { style: { color: foreground }, children: "Aa" }) }),
4500
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__range", children: [
5837
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { className: "uix-color-picker__preview", style: sampleStyle, children: /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { style: { color: foreground }, children: "Aa" }) }),
5838
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
4501
5839
  "Hue ",
4502
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { children: [
5840
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4503
5841
  Math.round(hsv.h),
4504
5842
  "\xB0"
4505
5843
  ] }),
4506
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { type: "range", min: "0", max: "359", value: Math.round(hsv.h), onChange: (event) => updateHsv({ h: Number(event.currentTarget.value) }) })
5844
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { type: "range", min: "0", max: "359", value: Math.round(hsv.h), onChange: (event) => updateHsv({ h: Number(event.currentTarget.value) }) })
4507
5845
  ] }),
4508
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__range", children: [
5846
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
4509
5847
  "Saturation ",
4510
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { children: [
5848
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4511
5849
  Math.round(hsv.s * 100),
4512
5850
  "%"
4513
5851
  ] }),
4514
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.s * 100), onChange: (event) => updateHsv({ s: Number(event.currentTarget.value) / 100 }) })
5852
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.s * 100), onChange: (event) => updateHsv({ s: Number(event.currentTarget.value) / 100 }) })
4515
5853
  ] }),
4516
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__range", children: [
5854
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__range", children: [
4517
5855
  "Brightness ",
4518
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { children: [
5856
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4519
5857
  Math.round(hsv.v * 100),
4520
5858
  "%"
4521
5859
  ] }),
4522
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.v * 100), onChange: (event) => updateHsv({ v: Number(event.currentTarget.value) / 100 }) })
5860
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.v * 100), onChange: (event) => updateHsv({ v: Number(event.currentTarget.value) / 100 }) })
4523
5861
  ] }),
4524
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("label", { className: "uix-color-picker__hex", children: [
5862
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("label", { className: "uix-color-picker__hex", children: [
4525
5863
  "Hex",
4526
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "uix-color-picker__hex-row", children: [
4527
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("input", { className: "uix-input", value: hexText, onChange: (event) => setHexText(event.currentTarget.value), onBlur: commitHex, onKeyDown: (event) => {
5864
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { className: "uix-color-picker__hex-row", children: [
5865
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("input", { className: "uix-input", value: hexText, onChange: (event) => setHexText(event.currentTarget.value), onBlur: commitHex, onKeyDown: (event) => {
4528
5866
  if (event.key === "Enter") commitHex();
4529
5867
  }, "aria-invalid": !normalizeHex(hexText) }),
4530
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: commitHex, disabled: !normalizeHex(hexText), children: "Set" })
5868
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: commitHex, disabled: !normalizeHex(hexText), children: "Set" })
4531
5869
  ] })
4532
5870
  ] }),
4533
- (presets.length > 0 || recent.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "uix-color-picker__palettes", children: [
4534
- presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("fieldset", { children: [
4535
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("legend", { children: "Presets" }),
4536
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { children: presets.map((color) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use preset ${color}` }, color)) })
5871
+ (presets.length > 0 || recent.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "uix-color-picker__palettes", children: [
5872
+ presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("fieldset", { children: [
5873
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("legend", { children: "Presets" }),
5874
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { children: presets.map((color) => /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use preset ${color}` }, color)) })
4537
5875
  ] }),
4538
- recent.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("fieldset", { children: [
4539
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("legend", { children: "Recent" }),
4540
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { children: recent.map((color) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use recent color ${color}` }, color)) })
5876
+ recent.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("fieldset", { children: [
5877
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("legend", { children: "Recent" }),
5878
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { children: recent.map((color) => /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use recent color ${color}` }, color)) })
4541
5879
  ] })
4542
5880
  ] }),
4543
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("p", { className: cx("uix-color-picker__contrast", passes ? "uix-color-picker__contrast--pass" : "uix-color-picker__contrast--fail"), role: "status", "aria-live": "polite", children: [
5881
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("p", { className: cx("uix-color-picker__contrast", passes ? "uix-color-picker__contrast--pass" : "uix-color-picker__contrast--fail"), role: "status", "aria-live": "polite", children: [
4544
5882
  "Contrast ",
4545
5883
  ratio.toFixed(2),
4546
5884
  ":1 \u2014 ",
4547
5885
  passes ? `passes WCAG AA (${minimumContrast}:1)` : `does not meet WCAG AA (${minimumContrast}:1)`
4548
5886
  ] }),
4549
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", onClick: close, children: "Done" })
5887
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", onClick: close, children: "Done" })
4550
5888
  ]
4551
5889
  }
4552
5890
  )
@@ -4554,7 +5892,7 @@ function ColorPicker({
4554
5892
  }
4555
5893
 
4556
5894
  // src/components/BrandProfiles.tsx
4557
- var import_jsx_runtime67 = require("react/jsx-runtime");
5895
+ var import_jsx_runtime69 = require("react/jsx-runtime");
4558
5896
  var PROFILE_PROPERTIES = {
4559
5897
  brand: "--uix-brand",
4560
5898
  brandForeground: "--uix-brand-fg",
@@ -4623,7 +5961,7 @@ function BrandProfileEditor({
4623
5961
  colorPresets = EMPTY_COLORS2,
4624
5962
  className
4625
5963
  }) {
4626
- const [announcement, setAnnouncement] = (0, import_react37.useState)("");
5964
+ const [announcement, setAnnouncement] = (0, import_react45.useState)("");
4627
5965
  const update = (patch) => onChange({ ...value, ...patch });
4628
5966
  const apply = () => {
4629
5967
  if (applyTarget !== null) applyBrandProfile(value, applyTarget);
@@ -4640,54 +5978,54 @@ function BrandProfileEditor({
4640
5978
  ...value.typography?.sans ? { "--uix-font-sans": value.typography.sans } : {},
4641
5979
  ...value.typography?.heading ? { "--uix-font-heading": value.typography.heading } : {}
4642
5980
  };
4643
- return /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("section", { className: cx("uix-brand-profiles", className), "aria-label": "Brand profile editor", children: [
4644
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__form", children: [
4645
- profiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
5981
+ return /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("section", { className: cx("uix-brand-profiles", className), "aria-label": "Brand profile editor", children: [
5982
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__form", children: [
5983
+ profiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
4646
5984
  "Profile",
4647
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("select", { className: "uix-select", value: value.id, onChange: (event) => {
5985
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("select", { className: "uix-select", value: value.id, onChange: (event) => {
4648
5986
  const selected = profiles.find((profile) => profile.id === event.currentTarget.value);
4649
5987
  if (selected) onSelectProfile?.(selected);
4650
- }, children: profiles.map((profile) => /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("option", { value: profile.id, children: profile.name }, profile.id)) })
5988
+ }, children: profiles.map((profile) => /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("option", { value: profile.id, children: profile.name }, profile.id)) })
4651
5989
  ] }),
4652
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
5990
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
4653
5991
  "Name",
4654
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { className: "uix-input", value: value.name, onChange: (event) => update({ name: event.currentTarget.value }) })
5992
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { className: "uix-input", value: value.name, onChange: (event) => update({ name: event.currentTarget.value }) })
4655
5993
  ] }),
4656
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__colors", children: [
4657
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { children: [
5994
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__colors", children: [
5995
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { children: [
4658
5996
  "Brand",
4659
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(ColorPicker, { label: "Brand color", value: value.brand, foreground: value.brandForeground, presets: colorPresets, onChange: (brand) => update({ brand }) })
5997
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(ColorPicker, { label: "Brand color", value: value.brand, foreground: value.brandForeground, presets: colorPresets, onChange: (brand) => update({ brand }) })
4660
5998
  ] }),
4661
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { children: [
5999
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { children: [
4662
6000
  "On brand",
4663
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(ColorPicker, { label: "Brand foreground color", value: value.brandForeground, foreground: value.brand, presets: colorPresets, onChange: (brandForeground) => update({ brandForeground }) })
6001
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(ColorPicker, { label: "Brand foreground color", value: value.brandForeground, foreground: value.brand, presets: colorPresets, onChange: (brandForeground) => update({ brandForeground }) })
4664
6002
  ] })
4665
6003
  ] }),
4666
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
6004
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
4667
6005
  "Body font",
4668
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { className: "uix-input", value: value.typography?.sans ?? "", onChange: (event) => update({ typography: { ...value.typography, sans: event.currentTarget.value || void 0 } }) })
6006
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { className: "uix-input", value: value.typography?.sans ?? "", onChange: (event) => update({ typography: { ...value.typography, sans: event.currentTarget.value || void 0 } }) })
4669
6007
  ] }),
4670
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-field__label", children: [
6008
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-field__label", children: [
4671
6009
  "Heading font",
4672
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { className: "uix-input", value: value.typography?.heading ?? "", onChange: (event) => update({ typography: { ...value.typography, heading: event.currentTarget.value || void 0 } }) })
6010
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { className: "uix-input", value: value.typography?.heading ?? "", onChange: (event) => update({ typography: { ...value.typography, heading: event.currentTarget.value || void 0 } }) })
4673
6011
  ] }),
4674
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("label", { className: "uix-file-upload uix-brand-profiles__upload", children: [
6012
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("label", { className: "uix-file-upload uix-brand-profiles__upload", children: [
4675
6013
  "Logo file",
4676
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("input", { type: "file", accept: "image/*", onChange: onFile })
6014
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("input", { type: "file", accept: "image/*", onChange: onFile })
4677
6015
  ] }),
4678
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: apply, children: "Apply profile" }),
4679
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
6016
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", onClick: apply, children: "Apply profile" }),
6017
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
4680
6018
  ] }),
4681
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__preview", style: previewStyle, "aria-label": `Live preview of ${value.name}`, children: [
4682
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__preview-header", children: [
4683
- value.logo ? /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("img", { src: value.logo.src, alt: value.logo.alt }) : /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("span", { className: "uix-brand-profiles__logo-placeholder", children: "Logo" }),
4684
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("strong", { children: value.name || "Brand preview" })
6019
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__preview", style: previewStyle, "aria-label": `Live preview of ${value.name}`, children: [
6020
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__preview-header", children: [
6021
+ value.logo ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("img", { src: value.logo.src, alt: value.logo.alt }) : /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "uix-brand-profiles__logo-placeholder", children: "Logo" }),
6022
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("strong", { children: value.name || "Brand preview" })
4685
6023
  ] }),
4686
- /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)("div", { className: "uix-brand-profiles__preview-body", children: [
4687
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("h3", { children: "Readable, serializable branding" }),
4688
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("p", { children: "Accent, links, rings, and muted states continue through the existing UIx token chain." }),
4689
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", children: "Primary action" }),
4690
- /* @__PURE__ */ (0, import_jsx_runtime67.jsx)("a", { href: "#brand-profile-preview", children: "Preview link" })
6024
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uix-brand-profiles__preview-body", children: [
6025
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("h3", { children: "Readable, serializable branding" }),
6026
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("p", { children: "Accent, links, rings, and muted states continue through the existing UIx token chain." }),
6027
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary", children: "Primary action" }),
6028
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("a", { href: "#brand-profile-preview", children: "Preview link" })
4691
6029
  ] })
4692
6030
  ] })
4693
6031
  ] });
@@ -4695,7 +6033,7 @@ function BrandProfileEditor({
4695
6033
  var BrandProfiles = BrandProfileEditor;
4696
6034
 
4697
6035
  // src/components/DiffViewer.tsx
4698
- var import_react38 = require("react");
6036
+ var import_react46 = require("react");
4699
6037
 
4700
6038
  // src/diff-model.ts
4701
6039
  function isJsonObject(value) {
@@ -4746,7 +6084,7 @@ function summarizeDiff(entries, resolutions = {}) {
4746
6084
  }
4747
6085
 
4748
6086
  // src/components/DiffViewer.tsx
4749
- var import_jsx_runtime68 = require("react/jsx-runtime");
6087
+ var import_jsx_runtime70 = require("react/jsx-runtime");
4750
6088
  var GROUPS = [
4751
6089
  { kind: "conflicted", label: "Conflicted" },
4752
6090
  { kind: "changed", label: "Changed" },
@@ -4765,65 +6103,65 @@ function DiffViewer({
4765
6103
  labels = DEFAULT_LABELS,
4766
6104
  className
4767
6105
  }) {
4768
- const id = (0, import_react38.useId)();
4769
- const entries = (0, import_react38.useMemo)(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
4770
- const [internalResolutions, setInternalResolutions] = (0, import_react38.useState)({});
6106
+ const id = (0, import_react46.useId)();
6107
+ const entries = (0, import_react46.useMemo)(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
6108
+ const [internalResolutions, setInternalResolutions] = (0, import_react46.useState)({});
4771
6109
  const resolutions = controlledResolutions ?? internalResolutions;
4772
6110
  const summary = summarizeDiff(entries, resolutions);
4773
6111
  const resolve2 = (path, resolution) => {
4774
6112
  if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
4775
6113
  onResolutionChange?.(path, resolution);
4776
6114
  };
4777
- if (entries.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
4778
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("h3", { children: "No differences" }),
4779
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { children: "Base, current, and incoming values are aligned." })
6115
+ if (entries.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
6116
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("h3", { children: "No differences" }),
6117
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("p", { children: "Base, current, and incoming values are aligned." })
4780
6118
  ] });
4781
- return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
4782
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
4783
- GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4784
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("b", { children: summary[group.kind] }),
6119
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
6120
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
6121
+ GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("span", { children: [
6122
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("b", { children: summary[group.kind] }),
4785
6123
  " ",
4786
6124
  group.label.toLowerCase()
4787
6125
  ] }, group.kind)),
4788
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4789
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("b", { children: summary.resolved }),
6126
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("span", { children: [
6127
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("b", { children: summary.resolved }),
4790
6128
  " resolved"
4791
6129
  ] }),
4792
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("span", { children: [
4793
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("b", { children: summary.pending }),
6130
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("span", { children: [
6131
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("b", { children: summary.pending }),
4794
6132
  " pending"
4795
6133
  ] })
4796
6134
  ] }),
4797
6135
  GROUPS.map((group) => {
4798
6136
  const grouped = entries.filter((entry) => entry.kind === group.kind);
4799
6137
  if (!grouped.length) return null;
4800
- return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
4801
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("h3", { id: `${id}-${group.kind}`, children: [
6138
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
6139
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("h3", { id: `${id}-${group.kind}`, children: [
4802
6140
  group.label,
4803
6141
  " ",
4804
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("span", { children: grouped.length })
6142
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("span", { children: grouped.length })
4805
6143
  ] }),
4806
6144
  grouped.map((entry) => {
4807
6145
  const resolution = resolutions[entry.path] ?? "pending";
4808
- return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
4809
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("summary", { children: [
4810
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("code", { children: entry.path }),
4811
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
6146
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
6147
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("summary", { children: [
6148
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("code", { children: entry.path }),
6149
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
4812
6150
  ] }),
4813
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
4814
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
4815
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("pre", { children: renderValue(entry[version]) })
6151
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
6152
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
6153
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("pre", { children: renderValue(entry[version]) })
4816
6154
  ] }, version)) }),
4817
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
4818
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve2(entry.path, "accept"), children: "Accept incoming" }),
4819
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve2(entry.path, "skip"), children: "Keep current" }),
4820
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve2(entry.path, "pending"), children: "Mark pending" })
6155
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
6156
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve2(entry.path, "accept"), children: "Accept incoming" }),
6157
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve2(entry.path, "skip"), children: "Keep current" }),
6158
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve2(entry.path, "pending"), children: "Mark pending" })
4821
6159
  ] })
4822
6160
  ] }, entry.path);
4823
6161
  })
4824
6162
  ] }, group.kind);
4825
6163
  }),
4826
- /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
6164
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
4827
6165
  summary.resolved,
4828
6166
  " of ",
4829
6167
  entries.length,
@@ -4863,6 +6201,7 @@ function DiffViewer({
4863
6201
  ConfirmDialog,
4864
6202
  ContentList,
4865
6203
  ContentListItem,
6204
+ DEFAULT_RELATIONSHIP_GRAPH_LABELS,
4866
6205
  DateRangePicker,
4867
6206
  DescriptionItem,
4868
6207
  DescriptionList,
@@ -4947,6 +6286,7 @@ function DiffViewer({
4947
6286
  SubNavItem,
4948
6287
  Switch,
4949
6288
  Tab,
6289
+ TabPanel,
4950
6290
  Table,
4951
6291
  TableWrap,
4952
6292
  Tabs,
@@ -4973,6 +6313,8 @@ function DiffViewer({
4973
6313
  buildThreeWayDiff,
4974
6314
  clampMetricValue,
4975
6315
  clampWidth,
6316
+ classifyLayeredEdges,
6317
+ clusterIdFor,
4976
6318
  computePosition,
4977
6319
  contrastRatio,
4978
6320
  cx,
@@ -4987,6 +6329,8 @@ function DiffViewer({
4987
6329
  isDateInRange,
4988
6330
  isDateUnavailable,
4989
6331
  isRuleGroup,
6332
+ layeredNeighbor,
6333
+ layoutLayeredGraph,
4990
6334
  layoutRelationshipGraph,
4991
6335
  mapRuleGroup,
4992
6336
  matchFilter,
@@ -5028,6 +6372,7 @@ function DiffViewer({
5028
6372
  useVirtualRows,
5029
6373
  validateRuleDefinition,
5030
6374
  virtualWindow,
6375
+ wrapEdgeLabel,
5031
6376
  zonedDateKey,
5032
6377
  zonedDateSpan
5033
6378
  });