@tensor_1/react 2.11.0 → 2.13.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 (65) hide show
  1. package/README.md +26 -1
  2. package/dist/ChartCore-Bt1C84ty.d.cts +22 -0
  3. package/dist/calendar-model.d.ts +31 -0
  4. package/dist/calendar-model.js +89 -0
  5. package/dist/chart-preset.cjs +168 -0
  6. package/dist/chart-preset.d.cts +9 -0
  7. package/dist/chart-preset.d.ts +9 -0
  8. package/dist/chart-preset.js +35 -0
  9. package/dist/chart.cjs +58 -32
  10. package/dist/chart.d.cts +6 -21
  11. package/dist/chart.d.ts +2 -1
  12. package/dist/color-model.d.ts +20 -0
  13. package/dist/color-model.js +74 -0
  14. package/dist/components/BrandProfiles.d.ts +45 -0
  15. package/dist/components/BrandProfiles.js +150 -0
  16. package/dist/components/BuilderCanvas.d.ts +34 -0
  17. package/dist/components/BuilderCanvas.js +117 -0
  18. package/dist/components/Chart.d.ts +6 -21
  19. package/dist/components/Chart.js +5 -76
  20. package/dist/components/ChartCore.d.ts +33 -0
  21. package/dist/components/ChartCore.js +100 -0
  22. package/dist/components/ColorPicker.d.ts +17 -0
  23. package/dist/components/ColorPicker.js +158 -0
  24. package/dist/components/DateRangePicker.d.ts +23 -0
  25. package/dist/components/DateRangePicker.js +123 -0
  26. package/dist/components/DiffViewer.d.ts +18 -0
  27. package/dist/components/DiffViewer.js +93 -0
  28. package/dist/components/Inbox.js +0 -1
  29. package/dist/components/LicensePositionBar.d.ts +17 -0
  30. package/dist/components/LicensePositionBar.js +50 -0
  31. package/dist/components/MatchReview.d.ts +39 -0
  32. package/dist/components/MatchReview.js +120 -0
  33. package/dist/components/Meter.js +0 -1
  34. package/dist/components/MetricInput.d.ts +23 -0
  35. package/dist/components/MetricInput.js +92 -0
  36. package/dist/components/NavFavourites.js +35 -108
  37. package/dist/components/RelationshipGraph.d.ts +30 -0
  38. package/dist/components/RelationshipGraph.js +138 -0
  39. package/dist/components/RuleBuilder.d.ts +45 -0
  40. package/dist/components/RuleBuilder.js +163 -0
  41. package/dist/components/SchedulingCalendar.d.ts +42 -0
  42. package/dist/components/SchedulingCalendar.js +124 -0
  43. package/dist/components/Segmented.js +3 -2
  44. package/dist/components/Table.d.ts +1 -1
  45. package/dist/components/Table.js +4 -3
  46. package/dist/components/Tabs.js +3 -2
  47. package/dist/diff-model.d.ts +23 -0
  48. package/dist/diff-model.js +50 -0
  49. package/dist/hooks/useTable.js +11 -7
  50. package/dist/hooks/useVirtualRows.d.ts +27 -0
  51. package/dist/hooks/useVirtualRows.js +50 -0
  52. package/dist/index.cjs +1930 -194
  53. package/dist/index.d.cts +464 -2
  54. package/dist/index.d.ts +19 -0
  55. package/dist/index.js +93 -1
  56. package/dist/json-value.d.ts +10 -0
  57. package/dist/json-value.js +19 -0
  58. package/dist/metric-model.d.ts +9 -0
  59. package/dist/metric-model.js +25 -0
  60. package/dist/relationship-graph-model.d.ts +31 -0
  61. package/dist/relationship-graph-model.js +68 -0
  62. package/dist/rule-builder-model.d.ts +41 -0
  63. package/dist/rule-builder-model.js +101 -0
  64. package/dist/table-engine.js +8 -6
  65. package/package.json +16 -2
@@ -0,0 +1,120 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useMemo, useState } from "react";
4
+ import { cx } from "../cx.js";
5
+ import { StatusPill } from "./StatusPill.js";
6
+ const valueText = (value) => value == null ? "\u2014" : typeof value === "string" ? value : JSON.stringify(value);
7
+ function MatchReview({
8
+ incoming,
9
+ incomingLabel = "Incoming record",
10
+ fields,
11
+ candidates,
12
+ selectedIds,
13
+ onSelectionChange,
14
+ onDecision,
15
+ onBulkDecision,
16
+ loading,
17
+ error,
18
+ onRetry,
19
+ className
20
+ }) {
21
+ const [internalSelection, setInternalSelection] = useState(/* @__PURE__ */ new Set());
22
+ const [announcement, setAnnouncement] = useState("");
23
+ const [bulkPending, setBulkPending] = useState(false);
24
+ const selection = selectedIds ?? internalSelection;
25
+ const pending = useMemo(() => candidates.filter((candidate) => candidate.status !== "resolved"), [candidates]);
26
+ const setSelection = (next) => {
27
+ if (selectedIds === void 0) setInternalSelection(next);
28
+ onSelectionChange?.(next);
29
+ };
30
+ const toggle = (id) => {
31
+ const next = new Set(selection);
32
+ if (next.has(id)) next.delete(id);
33
+ else next.add(id);
34
+ setSelection(next);
35
+ };
36
+ const runBulk = async (action) => {
37
+ if (!onBulkDecision || selection.size === 0 || bulkPending) return;
38
+ const ids = [...selection];
39
+ setBulkPending(true);
40
+ setAnnouncement(`${action === "accept" ? "Accepting" : "Dismissing"} ${ids.length} candidate${ids.length === 1 ? "" : "s"}\u2026`);
41
+ try {
42
+ const result = await onBulkDecision(ids, action);
43
+ const succeeded = result?.succeeded ?? ids.length;
44
+ const failed = result?.failed ?? 0;
45
+ setAnnouncement(`${succeeded} candidate${succeeded === 1 ? "" : "s"} ${action === "accept" ? "accepted" : "dismissed"}${failed ? `; ${failed} failed` : ""}.`);
46
+ if (!failed) setSelection(/* @__PURE__ */ new Set());
47
+ } catch {
48
+ setAnnouncement(`Bulk ${action} failed. Try again.`);
49
+ } finally {
50
+ setBulkPending(false);
51
+ }
52
+ };
53
+ if (loading) return /* @__PURE__ */ jsx("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
54
+ if (error) return /* @__PURE__ */ jsxs("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
55
+ /* @__PURE__ */ jsx("p", { children: error }),
56
+ onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
57
+ ] });
58
+ if (candidates.length === 0) return /* @__PURE__ */ jsxs("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
59
+ /* @__PURE__ */ jsx("p", { children: "No candidate matches found." }),
60
+ /* @__PURE__ */ jsx("p", { children: "Choose another record source or continue without a match." })
61
+ ] });
62
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
63
+ /* @__PURE__ */ jsxs("div", { className: "uix-match-review__summary", children: [
64
+ /* @__PURE__ */ jsxs("div", { children: [
65
+ /* @__PURE__ */ jsx("strong", { children: incomingLabel }),
66
+ /* @__PURE__ */ jsxs("span", { children: [
67
+ pending.length,
68
+ " pending of ",
69
+ candidates.length
70
+ ] })
71
+ ] }),
72
+ /* @__PURE__ */ jsx("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ jsxs("span", { children: [
73
+ /* @__PURE__ */ jsx("b", { children: field.label }),
74
+ field.render?.(incoming[field.id], incoming) ?? valueText(incoming[field.id])
75
+ ] }, field.id)) })
76
+ ] }),
77
+ onBulkDecision && /* @__PURE__ */ jsxs("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
78
+ /* @__PURE__ */ jsxs("span", { children: [
79
+ selection.size,
80
+ " selected"
81
+ ] }),
82
+ /* @__PURE__ */ 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" }),
83
+ /* @__PURE__ */ 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" })
84
+ ] }),
85
+ /* @__PURE__ */ jsx("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ jsxs("table", { className: "uix-table uix-match-review__table", children: [
86
+ /* @__PURE__ */ jsx("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
87
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
88
+ /* @__PURE__ */ jsx("th", { scope: "col", children: /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: "Select" }) }),
89
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Confidence" }),
90
+ fields.map((field) => /* @__PURE__ */ jsx("th", { scope: "col", children: field.label }, field.id)),
91
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Status" }),
92
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Actions" })
93
+ ] }) }),
94
+ /* @__PURE__ */ jsx("tbody", { children: candidates.map((candidate) => {
95
+ const resolved = candidate.status === "resolved";
96
+ return /* @__PURE__ */ jsxs("tr", { "data-status": candidate.status ?? "pending", children: [
97
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
98
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
99
+ fields.map((field) => /* @__PURE__ */ jsx("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
100
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
101
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ jsx(
102
+ "button",
103
+ {
104
+ type: "button",
105
+ className: "uix-btn uix-btn--ghost uix-btn--sm",
106
+ disabled: resolved,
107
+ onClick: () => onDecision(candidate.id, action),
108
+ children: action === "pick-other" ? "Pick other" : `${action[0].toUpperCase()}${action.slice(1)}`
109
+ },
110
+ action
111
+ )) }) })
112
+ ] }, candidate.id);
113
+ }) })
114
+ ] }) }),
115
+ /* @__PURE__ */ jsx("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
116
+ ] });
117
+ }
118
+ export {
119
+ MatchReview
120
+ };
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  import { jsx } from "react/jsx-runtime";
3
2
  import { cx } from "../cx.js";
4
3
  function Meter({ value = 0, tone, className, ...props }) {
@@ -0,0 +1,23 @@
1
+ import * as react from 'react';
2
+
3
+ interface MetricInputProps {
4
+ value: number | null;
5
+ onValueChange: (value: number | null) => void;
6
+ min?: number;
7
+ max?: number;
8
+ step?: number;
9
+ unit?: string;
10
+ label: string;
11
+ invalid?: boolean;
12
+ errorMessage?: string;
13
+ disabled?: boolean;
14
+ readOnly?: boolean;
15
+ parse?: (input: string) => number | null;
16
+ format?: (value: number | null) => string;
17
+ className?: string;
18
+ id?: string;
19
+ }
20
+ /** Accessible numeric recipe composed from Input and the UIx stepper controls. */
21
+ declare function MetricInput({ value, onValueChange, min, max, step, unit, label, invalid, errorMessage, disabled, readOnly, parse, format, className, id: providedId, }: MetricInputProps): react.JSX.Element;
22
+
23
+ export { MetricInput, type MetricInputProps };
@@ -0,0 +1,92 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useState } from "react";
4
+ import { cx } from "../cx.js";
5
+ import { clampMetricValue, parseMetricValue, stepMetricValue } from "../metric-model.js";
6
+ import { Input } from "./Input.js";
7
+ const defaultMetricFormat = (input) => input == null ? "" : String(input);
8
+ function MetricInput({
9
+ value,
10
+ onValueChange,
11
+ min,
12
+ max,
13
+ step = 1,
14
+ unit,
15
+ label,
16
+ invalid,
17
+ errorMessage,
18
+ disabled,
19
+ readOnly,
20
+ parse = parseMetricValue,
21
+ format = defaultMetricFormat,
22
+ className,
23
+ id: providedId
24
+ }) {
25
+ const generatedId = useId();
26
+ const id = providedId ?? generatedId;
27
+ const errorId = `${id}-error`;
28
+ const [text, setText] = useState(() => format(value));
29
+ useEffect(() => setText(format(value)), [value, format]);
30
+ const commit = (input) => {
31
+ const parsed = parse(input);
32
+ if (input.trim() === "" || parsed !== null) {
33
+ onValueChange(parsed === null ? null : clampMetricValue(parsed, min, max));
34
+ }
35
+ };
36
+ const stepBy = (direction) => {
37
+ const next = stepMetricValue(value, direction, { min, max, step });
38
+ onValueChange(next);
39
+ setText(format(next));
40
+ };
41
+ const onKeyDown = (event) => {
42
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
43
+ event.preventDefault();
44
+ stepBy(event.key === "ArrowUp" ? 1 : -1);
45
+ }
46
+ };
47
+ const onChange = (event) => {
48
+ setText(event.currentTarget.value);
49
+ commit(event.currentTarget.value);
50
+ };
51
+ const onBlur = (event) => {
52
+ const parsed = parse(event.currentTarget.value);
53
+ setText(parsed === null && event.currentTarget.value.trim() !== "" ? format(value) : format(parsed));
54
+ };
55
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-metric-input", invalid && "uix-metric-input--invalid", className), children: [
56
+ /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: id, children: label }),
57
+ /* @__PURE__ */ jsxs("div", { className: "uix-metric-input__control", children: [
58
+ /* @__PURE__ */ jsx(
59
+ Input,
60
+ {
61
+ id,
62
+ value: text,
63
+ onChange,
64
+ onBlur,
65
+ onKeyDown,
66
+ inputMode: "decimal",
67
+ role: "spinbutton",
68
+ "aria-valuenow": value ?? void 0,
69
+ "aria-valuemin": min,
70
+ "aria-valuemax": max,
71
+ "aria-describedby": invalid && errorMessage ? errorId : void 0,
72
+ invalid,
73
+ disabled,
74
+ readOnly
75
+ }
76
+ ),
77
+ unit && /* @__PURE__ */ jsx("span", { className: "uix-metric-input__unit", "aria-hidden": "true", children: unit }),
78
+ /* @__PURE__ */ jsxs("span", { className: "uix-metric-input__steps", children: [
79
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children: "+" }),
80
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children: "\u2212" })
81
+ ] })
82
+ ] }),
83
+ unit && /* @__PURE__ */ jsxs("span", { className: "uix-visually-hidden", children: [
84
+ "Unit: ",
85
+ unit
86
+ ] }),
87
+ invalid && errorMessage && /* @__PURE__ */ jsx("span", { id: errorId, className: "uix-field__error", children: errorMessage })
88
+ ] });
89
+ }
90
+ export {
91
+ MetricInput
92
+ };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useId, useRef, useState } from "react";
3
+ import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  const ChevronDownIcon = () => /* @__PURE__ */ jsx(
6
6
  "svg",
@@ -14,7 +14,7 @@ const ChevronDownIcon = () => /* @__PURE__ */ jsx(
14
14
  strokeLinecap: "round",
15
15
  strokeLinejoin: "round",
16
16
  "aria-hidden": "true",
17
- style: { flexShrink: 0 },
17
+ className: "uix-favourites__chevron",
18
18
  children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
19
19
  }
20
20
  );
@@ -30,7 +30,7 @@ const ChevronRightIcon = () => /* @__PURE__ */ jsx(
30
30
  strokeLinecap: "round",
31
31
  strokeLinejoin: "round",
32
32
  "aria-hidden": "true",
33
- style: { flexShrink: 0 },
33
+ className: "uix-favourites__chevron",
34
34
  children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
35
35
  }
36
36
  );
@@ -53,7 +53,6 @@ const MoreIcon = () => /* @__PURE__ */ jsxs(
53
53
  ]
54
54
  }
55
55
  );
56
- const MUTED = "color-mix(in srgb, var(--uix-text) 50%, transparent)";
57
56
  function NavFavourites({
58
57
  items,
59
58
  collapsed = false,
@@ -69,7 +68,7 @@ function NavFavourites({
69
68
  const regionRef = useRef(null);
70
69
  const focusAfterRef = useRef(null);
71
70
  const triggerRefs = useRef(/* @__PURE__ */ new Map());
72
- const order = items.map((i) => i.id);
71
+ const order = useMemo(() => items.map((item) => item.id), [items]);
73
72
  useEffect(() => {
74
73
  const id = focusAfterRef.current;
75
74
  if (!id) return;
@@ -77,7 +76,7 @@ function NavFavourites({
77
76
  const el = triggerRefs.current.get(id);
78
77
  if (el) el.focus();
79
78
  else headerRef.current?.focus();
80
- }, [order.join("|")]);
79
+ }, [order]);
81
80
  const handleToggle = () => {
82
81
  if (!collapsed) {
83
82
  const active = document.activeElement;
@@ -87,7 +86,7 @@ function NavFavourites({
87
86
  }
88
87
  onToggleCollapsed?.();
89
88
  };
90
- const move = (id, dir) => {
89
+ const move = useCallback((id, dir) => {
91
90
  const idx = order.indexOf(id);
92
91
  const next = idx + dir;
93
92
  if (idx < 0 || next < 0 || next >= order.length) return;
@@ -97,14 +96,18 @@ function NavFavourites({
97
96
  reordered.splice(next, 0, it);
98
97
  focusAfterRef.current = id;
99
98
  onReorder(reordered);
100
- };
101
- const remove = (id) => {
99
+ }, [onReorder, order]);
100
+ const remove = useCallback((id) => {
102
101
  const idx = order.indexOf(id);
103
102
  const neighbour = order[idx + 1] ?? order[idx - 1] ?? null;
104
103
  focusAfterRef.current = neighbour;
105
104
  onRemove(id);
106
- };
107
- return /* @__PURE__ */ jsxs("div", { className: cx("uix-favourites", className), style: { marginBottom: "var(--uix-space-1)" }, children: [
105
+ }, [onRemove, order]);
106
+ const registerTrigger = useCallback((id, element) => {
107
+ if (element) triggerRefs.current.set(id, element);
108
+ else triggerRefs.current.delete(id);
109
+ }, []);
110
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-favourites", className), children: [
108
111
  /* @__PURE__ */ jsxs(
109
112
  "button",
110
113
  {
@@ -113,18 +116,7 @@ function NavFavourites({
113
116
  "aria-expanded": !collapsed,
114
117
  "aria-controls": regionId,
115
118
  onClick: handleToggle,
116
- className: "uix-text-eyebrow",
117
- style: {
118
- display: "flex",
119
- width: "100%",
120
- alignItems: "center",
121
- gap: "6px",
122
- padding: "8px 4px 4px",
123
- border: 0,
124
- background: "transparent",
125
- cursor: "pointer",
126
- color: "inherit"
127
- },
119
+ className: "uix-favourites__heading uix-text-eyebrow",
128
120
  children: [
129
121
  collapsed ? /* @__PURE__ */ jsx(ChevronRightIcon, {}) : /* @__PURE__ */ jsx(ChevronDownIcon, {}),
130
122
  labels.heading
@@ -137,20 +129,12 @@ function NavFavourites({
137
129
  ref: regionRef,
138
130
  id: regionId,
139
131
  hidden: collapsed,
140
- style: {
141
- display: "flex",
142
- flexDirection: "column",
143
- gap: "1px",
144
- listStyle: "none",
145
- margin: 0,
146
- padding: 0
147
- },
132
+ className: "uix-favourites__list",
148
133
  children: items.length === 0 ? /* @__PURE__ */ jsx(
149
134
  "li",
150
135
  {
151
- className: "uix-text-meta",
136
+ className: "uix-favourites__empty uix-text-meta",
152
137
  "data-empty-reason": emptyReason,
153
- style: { padding: "6px 8px", color: MUTED },
154
138
  children: labels.empty
155
139
  }
156
140
  ) : items.map((item, idx) => /* @__PURE__ */ jsx(
@@ -160,10 +144,9 @@ function NavFavourites({
160
144
  isFirst: idx === 0,
161
145
  isLast: idx === items.length - 1,
162
146
  labels,
163
- onMoveUp: () => move(item.id, -1),
164
- onMoveDown: () => move(item.id, 1),
165
- onRemove: () => remove(item.id),
166
- registerTrigger: (el) => triggerRefs.current.set(item.id, el)
147
+ onMove: move,
148
+ onRemove: remove,
149
+ registerTrigger
167
150
  },
168
151
  item.id
169
152
  ))
@@ -171,26 +154,24 @@ function NavFavourites({
171
154
  )
172
155
  ] });
173
156
  }
174
- function FavouriteRow({
157
+ const FavouriteRow = memo(function FavouriteRow2({
175
158
  item,
176
159
  isFirst,
177
160
  isLast,
178
161
  labels,
179
- onMoveUp,
180
- onMoveDown,
162
+ onMove,
181
163
  onRemove,
182
164
  registerTrigger
183
165
  }) {
184
166
  const [open, setOpen] = useState(false);
185
- const [revealed, setRevealed] = useState(false);
186
167
  const menuId = useId();
187
168
  const triggerRef = useRef(null);
188
169
  const itemRefs = useRef([]);
189
170
  const optionsTemplate = labels.options ?? "{label} favourite options";
190
171
  const actions = [
191
- ...isFirst ? [] : [{ label: labels.moveUp, onSelect: onMoveUp }],
192
- ...isLast ? [] : [{ label: labels.moveDown, onSelect: onMoveDown }],
193
- { label: labels.remove.replace("{label}", item.label), onSelect: onRemove }
172
+ ...isFirst ? [] : [{ label: labels.moveUp, onSelect: () => onMove(item.id, -1) }],
173
+ ...isLast ? [] : [{ label: labels.moveDown, onSelect: () => onMove(item.id, 1) }],
174
+ { label: labels.remove.replace("{label}", item.label), onSelect: () => onRemove(item.id) }
194
175
  ];
195
176
  useEffect(() => {
196
177
  if (open) itemRefs.current[0]?.focus();
@@ -219,34 +200,27 @@ function FavouriteRow({
219
200
  if (!e.altKey) return;
220
201
  if (e.key === "ArrowUp" && !isFirst) {
221
202
  e.preventDefault();
222
- onMoveUp();
203
+ onMove(item.id, -1);
223
204
  } else if (e.key === "ArrowDown" && !isLast) {
224
205
  e.preventDefault();
225
- onMoveDown();
206
+ onMove(item.id, 1);
226
207
  }
227
208
  };
228
- const triggerVisible = revealed || open;
229
209
  return (
230
210
  // biome-ignore lint/a11y/noStaticElementInteractions: keyboard accelerators on the row; the focusable controls inside carry their own semantics.
231
211
  /* @__PURE__ */ jsxs(
232
212
  "li",
233
213
  {
234
- style: { position: "relative", display: "flex", alignItems: "center" },
214
+ className: "uix-favourites__row",
235
215
  onKeyDown: onRowKeyDown,
236
- onMouseEnter: () => setRevealed(true),
237
- onMouseLeave: () => setRevealed(false),
238
- onFocus: () => setRevealed(true),
239
- onBlur: (e) => {
240
- if (!e.currentTarget.contains(e.relatedTarget)) setRevealed(false);
241
- },
242
216
  children: [
243
- /* @__PURE__ */ jsx("div", { style: { minWidth: 0, flex: 1 }, children: item.link }),
217
+ /* @__PURE__ */ jsx("div", { className: "uix-favourites__link", children: item.link }),
244
218
  /* @__PURE__ */ jsx(
245
219
  "button",
246
220
  {
247
221
  ref: (el) => {
248
222
  triggerRef.current = el;
249
- registerTrigger(el);
223
+ registerTrigger(item.id, el);
250
224
  },
251
225
  type: "button",
252
226
  "aria-haspopup": "menu",
@@ -254,22 +228,7 @@ function FavouriteRow({
254
228
  "aria-controls": open ? menuId : void 0,
255
229
  "aria-label": optionsTemplate.replace("{label}", item.label),
256
230
  onClick: () => setOpen((v) => !v),
257
- style: {
258
- marginLeft: "auto",
259
- display: "inline-flex",
260
- height: "20px",
261
- width: "20px",
262
- flexShrink: 0,
263
- alignItems: "center",
264
- justifyContent: "center",
265
- border: 0,
266
- borderRadius: "2px",
267
- background: "transparent",
268
- cursor: "pointer",
269
- color: MUTED,
270
- opacity: triggerVisible ? 1 : 0,
271
- transition: "opacity var(--uix-dur-fast) var(--uix-ease-out)"
272
- },
231
+ className: "uix-favourites__menu-trigger",
273
232
  children: /* @__PURE__ */ jsx(MoreIcon, {})
274
233
  }
275
234
  ),
@@ -280,7 +239,7 @@ function FavouriteRow({
280
239
  "aria-hidden": "true",
281
240
  role: "presentation",
282
241
  onClick: () => setOpen(false),
283
- style: { position: "fixed", inset: 0, cursor: "default", background: "transparent", zIndex: 40 }
242
+ className: "uix-favourites__backdrop"
284
243
  }
285
244
  ),
286
245
  /* @__PURE__ */ jsx(
@@ -289,21 +248,7 @@ function FavouriteRow({
289
248
  id: menuId,
290
249
  role: "menu",
291
250
  onKeyDown: onMenuKeyDown,
292
- style: {
293
- position: "absolute",
294
- right: 0,
295
- top: "calc(100% + 2px)",
296
- zIndex: 50,
297
- minWidth: "160px",
298
- overflow: "hidden",
299
- margin: 0,
300
- padding: "4px 0",
301
- listStyle: "none",
302
- borderRadius: "6px",
303
- border: "1px solid var(--uix-border)",
304
- background: "var(--uix-surface)",
305
- boxShadow: "var(--uix-shadow-popover)"
306
- },
251
+ className: "uix-favourites__menu",
307
252
  children: actions.map((action, i) => /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
308
253
  "button",
309
254
  {
@@ -317,25 +262,7 @@ function FavouriteRow({
317
262
  setOpen(false);
318
263
  action.onSelect();
319
264
  },
320
- style: {
321
- display: "flex",
322
- width: "100%",
323
- alignItems: "center",
324
- padding: "6px 12px",
325
- border: 0,
326
- background: "transparent",
327
- cursor: "pointer",
328
- textAlign: "left",
329
- fontSize: "13px",
330
- color: "color-mix(in srgb, var(--uix-text) 85%, transparent)",
331
- transition: "background-color var(--uix-dur-fast) var(--uix-ease-out)"
332
- },
333
- onMouseEnter: (e) => {
334
- e.currentTarget.style.background = "var(--uix-bg-hover)";
335
- },
336
- onMouseLeave: (e) => {
337
- e.currentTarget.style.background = "transparent";
338
- },
265
+ className: "uix-favourites__menu-item",
339
266
  children: action.label
340
267
  }
341
268
  ) }, action.label))
@@ -346,7 +273,7 @@ function FavouriteRow({
346
273
  }
347
274
  )
348
275
  );
349
- }
276
+ });
350
277
  export {
351
278
  NavFavourites
352
279
  };
@@ -0,0 +1,30 @@
1
+ import * as react from 'react';
2
+ import { RelationshipGraphNode, RelationshipGraphEdge } from '../relationship-graph-model.js';
3
+
4
+ interface RelationshipGraphLegendItem {
5
+ id: string;
6
+ label: string;
7
+ }
8
+ interface RelationshipGraphProps {
9
+ nodes: RelationshipGraphNode[];
10
+ edges: RelationshipGraphEdge[];
11
+ selectedId?: string;
12
+ onSelect?: (node: RelationshipGraphNode) => void;
13
+ onExpandNeighbors?: (node: RelationshipGraphNode) => void;
14
+ onOpenDetails?: (node: RelationshipGraphNode) => void;
15
+ maxNodes?: number;
16
+ density?: 'compact' | 'standard';
17
+ highlightedNodeIds?: ReadonlySet<string>;
18
+ conflictedNodeIds?: ReadonlySet<string>;
19
+ highlightedEdgeIds?: ReadonlySet<string>;
20
+ conflictedEdgeIds?: ReadonlySet<string>;
21
+ legend?: RelationshipGraphLegendItem[];
22
+ loading?: boolean;
23
+ error?: string;
24
+ onRetry?: () => void;
25
+ className?: string;
26
+ }
27
+ /** Bounded, deterministic SVG relationship view with an equivalent accessible list. */
28
+ declare function RelationshipGraph({ nodes, edges, selectedId, onSelect, onExpandNeighbors, onOpenDetails, maxNodes, density, highlightedNodeIds, conflictedNodeIds, highlightedEdgeIds, conflictedEdgeIds, legend, loading, error, onRetry, className, }: RelationshipGraphProps): react.JSX.Element;
29
+
30
+ export { RelationshipGraph, type RelationshipGraphLegendItem, type RelationshipGraphProps };