@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.
- package/README.md +26 -1
- package/dist/ChartCore-Bt1C84ty.d.cts +22 -0
- package/dist/calendar-model.d.ts +31 -0
- package/dist/calendar-model.js +89 -0
- package/dist/chart-preset.cjs +168 -0
- package/dist/chart-preset.d.cts +9 -0
- package/dist/chart-preset.d.ts +9 -0
- package/dist/chart-preset.js +35 -0
- package/dist/chart.cjs +58 -32
- package/dist/chart.d.cts +6 -21
- package/dist/chart.d.ts +2 -1
- package/dist/color-model.d.ts +20 -0
- package/dist/color-model.js +74 -0
- package/dist/components/BrandProfiles.d.ts +45 -0
- package/dist/components/BrandProfiles.js +150 -0
- package/dist/components/BuilderCanvas.d.ts +34 -0
- package/dist/components/BuilderCanvas.js +117 -0
- package/dist/components/Chart.d.ts +6 -21
- package/dist/components/Chart.js +5 -76
- package/dist/components/ChartCore.d.ts +33 -0
- package/dist/components/ChartCore.js +100 -0
- package/dist/components/ColorPicker.d.ts +17 -0
- package/dist/components/ColorPicker.js +158 -0
- package/dist/components/DateRangePicker.d.ts +23 -0
- package/dist/components/DateRangePicker.js +123 -0
- package/dist/components/DiffViewer.d.ts +18 -0
- package/dist/components/DiffViewer.js +93 -0
- package/dist/components/Inbox.js +0 -1
- package/dist/components/LicensePositionBar.d.ts +17 -0
- package/dist/components/LicensePositionBar.js +50 -0
- package/dist/components/MatchReview.d.ts +39 -0
- package/dist/components/MatchReview.js +120 -0
- package/dist/components/Meter.js +0 -1
- package/dist/components/MetricInput.d.ts +23 -0
- package/dist/components/MetricInput.js +92 -0
- package/dist/components/NavFavourites.js +35 -108
- package/dist/components/RelationshipGraph.d.ts +30 -0
- package/dist/components/RelationshipGraph.js +138 -0
- package/dist/components/RuleBuilder.d.ts +45 -0
- package/dist/components/RuleBuilder.js +163 -0
- package/dist/components/SchedulingCalendar.d.ts +42 -0
- package/dist/components/SchedulingCalendar.js +124 -0
- package/dist/components/Segmented.js +3 -2
- package/dist/components/Table.d.ts +1 -1
- package/dist/components/Table.js +4 -3
- package/dist/components/Tabs.js +3 -2
- package/dist/diff-model.d.ts +23 -0
- package/dist/diff-model.js +50 -0
- package/dist/hooks/useTable.js +11 -7
- package/dist/hooks/useVirtualRows.d.ts +27 -0
- package/dist/hooks/useVirtualRows.js +50 -0
- package/dist/index.cjs +1930 -194
- package/dist/index.d.cts +464 -2
- package/dist/index.d.ts +19 -0
- package/dist/index.js +93 -1
- package/dist/json-value.d.ts +10 -0
- package/dist/json-value.js +19 -0
- package/dist/metric-model.d.ts +9 -0
- package/dist/metric-model.js +25 -0
- package/dist/relationship-graph-model.d.ts +31 -0
- package/dist/relationship-graph-model.js +68 -0
- package/dist/rule-builder-model.d.ts +41 -0
- package/dist/rule-builder-model.js +101 -0
- package/dist/table-engine.js +8 -6
- 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
|
+
};
|
package/dist/components/Meter.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
157
|
+
const FavouriteRow = memo(function FavouriteRow2({
|
|
175
158
|
item,
|
|
176
159
|
isFirst,
|
|
177
160
|
isLast,
|
|
178
161
|
labels,
|
|
179
|
-
|
|
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:
|
|
192
|
-
...isLast ? [] : [{ label: labels.moveDown, onSelect:
|
|
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
|
-
|
|
203
|
+
onMove(item.id, -1);
|
|
223
204
|
} else if (e.key === "ArrowDown" && !isLast) {
|
|
224
205
|
e.preventDefault();
|
|
225
|
-
|
|
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
|
-
|
|
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", {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|