@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.
- package/README.md +29 -0
- package/dist/chart-preset.cjs +15 -3
- package/dist/chart.cjs +15 -3
- package/dist/components/Alert.js +15 -7
- package/dist/components/AppShell.d.ts +3 -1
- package/dist/components/AppShell.js +3 -1
- package/dist/components/Avatar.js +2 -1
- package/dist/components/Button.js +32 -16
- package/dist/components/Card.d.ts +3 -1
- package/dist/components/Card.js +2 -2
- package/dist/components/ChartCore.js +15 -3
- package/dist/components/CommandPalette.d.ts +4 -2
- package/dist/components/CommandPalette.js +66 -13
- package/dist/components/Comments.d.ts +5 -1
- package/dist/components/Comments.js +1 -1
- package/dist/components/Composer.d.ts +8 -1
- package/dist/components/Composer.js +7 -2
- package/dist/components/DetailLayout.js +1 -1
- package/dist/components/Drawer.d.ts +3 -4
- package/dist/components/Drawer.js +6 -4
- package/dist/components/Field.js +6 -3
- package/dist/components/Inbox.d.ts +13 -3
- package/dist/components/Inbox.js +62 -3
- package/dist/components/Input.d.ts +13 -2
- package/dist/components/Input.js +20 -5
- package/dist/components/Kanban.d.ts +15 -3
- package/dist/components/Kanban.js +42 -10
- package/dist/components/Meter.d.ts +3 -1
- package/dist/components/Meter.js +9 -1
- package/dist/components/Modal.d.ts +4 -5
- package/dist/components/Modal.js +4 -4
- package/dist/components/NavFavourites.js +11 -0
- package/dist/components/PageHeader.d.ts +3 -1
- package/dist/components/PageHeader.js +2 -2
- package/dist/components/Peek.d.ts +3 -4
- package/dist/components/Peek.js +9 -6
- package/dist/components/Progress.d.ts +3 -1
- package/dist/components/Progress.js +5 -3
- package/dist/components/Radio.d.ts +3 -1
- package/dist/components/Radio.js +6 -2
- package/dist/components/RelationshipGraph.d.ts +81 -3
- package/dist/components/RelationshipGraph.js +81 -57
- package/dist/components/RelationshipGraphLayered.d.ts +8 -0
- package/dist/components/RelationshipGraphLayered.js +420 -0
- package/dist/components/RelationshipGraphList.d.ts +16 -0
- package/dist/components/RelationshipGraphList.js +33 -0
- package/dist/components/Spinner.d.ts +3 -1
- package/dist/components/Spinner.js +3 -3
- package/dist/components/Stat.js +5 -1
- package/dist/components/States.d.ts +1 -0
- package/dist/components/States.js +7 -1
- package/dist/components/Table.d.ts +8 -1
- package/dist/components/Table.js +10 -6
- package/dist/components/Tabs.d.ts +13 -1
- package/dist/components/Tabs.js +79 -4
- package/dist/components/Timeline.d.ts +3 -3
- package/dist/components/Timeline.js +3 -3
- package/dist/components/Toast.js +42 -5
- package/dist/components/Tooltip.d.ts +3 -1
- package/dist/components/Tooltip.js +44 -5
- package/dist/components/Tree.d.ts +2 -0
- package/dist/components/Tree.js +26 -4
- package/dist/hooks/useDialog.d.ts +1 -1
- package/dist/hooks/useDialog.js +17 -2
- package/dist/hooks/useTable.d.ts +5 -0
- package/dist/hooks/useTable.js +20 -1
- package/dist/index.cjs +1791 -446
- package/dist/index.d.cts +311 -36
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -3
- package/dist/relationship-graph-model.d.ts +112 -1
- package/dist/relationship-graph-model.js +420 -1
- package/dist/tree-model.d.ts +4 -0
- package/dist/tree-model.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { boundRelationshipGraph, layeredNeighbor, layoutLayeredGraph } from "../relationship-graph-model.js";
|
|
5
|
+
import { cx } from "../cx.js";
|
|
6
|
+
import { EquivalentRelationshipList } from "./RelationshipGraphList.js";
|
|
7
|
+
const EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
8
|
+
const MIN_ZOOM = 0.4;
|
|
9
|
+
const MAX_ZOOM = 1.5;
|
|
10
|
+
const READABLE_ZOOM = 0.75;
|
|
11
|
+
const ZOOM_STEP = 1.2;
|
|
12
|
+
const PAN_STEP = 64;
|
|
13
|
+
const FOCUS_MARGIN = 24;
|
|
14
|
+
const NAV_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End"];
|
|
15
|
+
const clampZoom = (zoom) => Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
|
|
16
|
+
function initialView(layout, size) {
|
|
17
|
+
if (size.width === 0 || size.height === 0 || layout.width === 0) return { x: 0, y: 0, zoom: 1 };
|
|
18
|
+
const fit = Math.min(size.width / layout.width, size.height / layout.height);
|
|
19
|
+
if (fit < READABLE_ZOOM) return { x: 0, y: 0, zoom: READABLE_ZOOM };
|
|
20
|
+
const zoom = Math.min(fit, 1);
|
|
21
|
+
return { x: (size.width - layout.width * zoom) / 2, y: Math.max(0, (size.height - layout.height * zoom) / 2), zoom };
|
|
22
|
+
}
|
|
23
|
+
function fitView(layout, size) {
|
|
24
|
+
if (size.width === 0 || layout.width === 0) return { x: 0, y: 0, zoom: 1 };
|
|
25
|
+
const zoom = clampZoom(Math.min(size.width / layout.width, size.height / layout.height, 1));
|
|
26
|
+
return { x: (size.width - layout.width * zoom) / 2, y: Math.max(0, (size.height - layout.height * zoom) / 2), zoom };
|
|
27
|
+
}
|
|
28
|
+
const arrowPoints = "0,0 -8,-4.5 -8,4.5";
|
|
29
|
+
function LayeredRelationshipGraph({
|
|
30
|
+
nodes,
|
|
31
|
+
edges,
|
|
32
|
+
selectedId,
|
|
33
|
+
onSelect,
|
|
34
|
+
onExpandNeighbors,
|
|
35
|
+
onOpenDetails,
|
|
36
|
+
maxNodes = 400,
|
|
37
|
+
density = "standard",
|
|
38
|
+
highlightedNodeIds = EMPTY_IDS,
|
|
39
|
+
conflictedNodeIds = EMPTY_IDS,
|
|
40
|
+
highlightedEdgeIds = EMPTY_IDS,
|
|
41
|
+
conflictedEdgeIds = EMPTY_IDS,
|
|
42
|
+
legend = [],
|
|
43
|
+
className,
|
|
44
|
+
labels,
|
|
45
|
+
rootId,
|
|
46
|
+
renderNode,
|
|
47
|
+
nodeAriaLabel,
|
|
48
|
+
renderCluster,
|
|
49
|
+
clusterThreshold,
|
|
50
|
+
expandedClusterIds,
|
|
51
|
+
onToggleCluster,
|
|
52
|
+
dimmedNodeIds = EMPTY_IDS,
|
|
53
|
+
edgeLabels = "auto",
|
|
54
|
+
showList = true,
|
|
55
|
+
height,
|
|
56
|
+
focusId,
|
|
57
|
+
onFocusChange
|
|
58
|
+
}) {
|
|
59
|
+
const hintId = useId();
|
|
60
|
+
const bounded = useMemo(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
|
|
61
|
+
const [localExpanded, setLocalExpanded] = useState(EMPTY_IDS);
|
|
62
|
+
const expanded = expandedClusterIds ?? localExpanded;
|
|
63
|
+
const layout = useMemo(
|
|
64
|
+
() => layoutLayeredGraph(bounded.nodes, bounded.edges, {
|
|
65
|
+
...rootId !== void 0 ? { rootId } : {},
|
|
66
|
+
...clusterThreshold !== void 0 ? { clusterThreshold } : {},
|
|
67
|
+
expandedClusterIds: expanded,
|
|
68
|
+
density
|
|
69
|
+
}),
|
|
70
|
+
[bounded, rootId, clusterThreshold, expanded, density]
|
|
71
|
+
);
|
|
72
|
+
const itemById = useMemo(() => new Map(layout.items.map((item) => [item.id, item])), [layout]);
|
|
73
|
+
const viewportRef = useRef(null);
|
|
74
|
+
const itemRefs = useRef(/* @__PURE__ */ new Map());
|
|
75
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
76
|
+
const [view, setView] = useState({ x: 0, y: 0, zoom: 1 });
|
|
77
|
+
const [animate, setAnimate] = useState(false);
|
|
78
|
+
const [localFocus, setLocalFocus] = useState(void 0);
|
|
79
|
+
const [hoverId, setHoverId] = useState(void 0);
|
|
80
|
+
const viewRef = useRef(view);
|
|
81
|
+
viewRef.current = view;
|
|
82
|
+
const layoutRef = useRef(layout);
|
|
83
|
+
layoutRef.current = layout;
|
|
84
|
+
const pendingFocus = useRef(void 0);
|
|
85
|
+
const initialisedFor = useRef(void 0);
|
|
86
|
+
const rootItemId = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
|
|
87
|
+
const requestedFocus = focusId !== void 0 ? layout.itemOf[focusId] ?? focusId : localFocus;
|
|
88
|
+
const focusedId = requestedFocus !== void 0 && itemById.has(requestedFocus) ? requestedFocus : rootItemId;
|
|
89
|
+
const selectedItemId = selectedId !== void 0 ? layout.itemOf[selectedId] : void 0;
|
|
90
|
+
const activeIds = useMemo(
|
|
91
|
+
() => new Set([hoverId, selectedItemId].filter((id) => id !== void 0)),
|
|
92
|
+
[hoverId, selectedItemId]
|
|
93
|
+
);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const element = viewportRef.current;
|
|
96
|
+
if (!element) return;
|
|
97
|
+
const measure = () => setSize({ width: element.clientWidth, height: element.clientHeight });
|
|
98
|
+
measure();
|
|
99
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
100
|
+
const observer = new ResizeObserver(measure);
|
|
101
|
+
observer.observe(element);
|
|
102
|
+
return () => observer.disconnect();
|
|
103
|
+
}, []);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (size.width === 0) return;
|
|
106
|
+
const key = `${layout.rootId ?? ""}`;
|
|
107
|
+
if (initialisedFor.current === key) return;
|
|
108
|
+
initialisedFor.current = key;
|
|
109
|
+
setView(initialView(layout, size));
|
|
110
|
+
}, [layout, size]);
|
|
111
|
+
const moveView = useCallback((next, smooth) => {
|
|
112
|
+
setAnimate(smooth);
|
|
113
|
+
setView(next);
|
|
114
|
+
}, []);
|
|
115
|
+
const ensureVisible = useCallback((id, smooth) => {
|
|
116
|
+
const element = viewportRef.current;
|
|
117
|
+
const item = layoutRef.current.items.find((candidate) => candidate.id === id);
|
|
118
|
+
if (!element || !item) return;
|
|
119
|
+
const { x, y, zoom } = viewRef.current;
|
|
120
|
+
const width = element.clientWidth;
|
|
121
|
+
const heightPx = element.clientHeight;
|
|
122
|
+
const left = item.x * zoom + x;
|
|
123
|
+
const top = item.y * zoom + y;
|
|
124
|
+
const right = left + item.width * zoom;
|
|
125
|
+
const bottom = top + item.height * zoom;
|
|
126
|
+
let nextX = x;
|
|
127
|
+
let nextY = y;
|
|
128
|
+
if (left < FOCUS_MARGIN) nextX += FOCUS_MARGIN - left;
|
|
129
|
+
else if (right > width - FOCUS_MARGIN) nextX -= right - (width - FOCUS_MARGIN);
|
|
130
|
+
if (top < FOCUS_MARGIN) nextY += FOCUS_MARGIN - top;
|
|
131
|
+
else if (bottom > heightPx - FOCUS_MARGIN) nextY -= bottom - (heightPx - FOCUS_MARGIN);
|
|
132
|
+
if (nextX !== x || nextY !== y) moveView({ x: nextX, y: nextY, zoom }, smooth);
|
|
133
|
+
}, [moveView]);
|
|
134
|
+
const focusItem = useCallback((id) => {
|
|
135
|
+
setLocalFocus(id);
|
|
136
|
+
onFocusChange?.(id);
|
|
137
|
+
requestAnimationFrame(() => {
|
|
138
|
+
itemRefs.current.get(id)?.focus({ preventScroll: true });
|
|
139
|
+
ensureVisible(id, true);
|
|
140
|
+
});
|
|
141
|
+
}, [ensureVisible, onFocusChange]);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const wanted = pendingFocus.current;
|
|
144
|
+
if (wanted !== void 0 && itemById.has(wanted)) {
|
|
145
|
+
pendingFocus.current = void 0;
|
|
146
|
+
focusItem(wanted);
|
|
147
|
+
}
|
|
148
|
+
}, [itemById, focusItem]);
|
|
149
|
+
const controlledTarget = focusId !== void 0 ? layout.itemOf[focusId] ?? focusId : void 0;
|
|
150
|
+
const controlledPresent = controlledTarget !== void 0 && itemById.has(controlledTarget);
|
|
151
|
+
const controlledKey = `${controlledTarget ?? ""}|${controlledPresent}`;
|
|
152
|
+
const lastControlledKey = useRef(controlledKey);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (lastControlledKey.current === controlledKey) return;
|
|
155
|
+
lastControlledKey.current = controlledKey;
|
|
156
|
+
if (!controlledPresent || controlledTarget === void 0) return;
|
|
157
|
+
requestAnimationFrame(() => {
|
|
158
|
+
itemRefs.current.get(controlledTarget)?.focus({ preventScroll: true });
|
|
159
|
+
ensureVisible(controlledTarget, true);
|
|
160
|
+
});
|
|
161
|
+
}, [controlledKey, controlledTarget, controlledPresent, ensureVisible]);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const element = viewportRef.current;
|
|
164
|
+
if (!element) return;
|
|
165
|
+
const onWheel = (event) => {
|
|
166
|
+
if (!event.ctrlKey && !event.metaKey) return;
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
const rect = element.getBoundingClientRect();
|
|
169
|
+
const px = event.clientX - rect.left;
|
|
170
|
+
const py = event.clientY - rect.top;
|
|
171
|
+
const current = viewRef.current;
|
|
172
|
+
const zoom = clampZoom(current.zoom * Math.exp(-event.deltaY * 2e-3));
|
|
173
|
+
const ratio = zoom / current.zoom;
|
|
174
|
+
setAnimate(false);
|
|
175
|
+
setView({ zoom, x: px - (px - current.x) * ratio, y: py - (py - current.y) * ratio });
|
|
176
|
+
};
|
|
177
|
+
element.addEventListener("wheel", onWheel, { passive: false });
|
|
178
|
+
return () => element.removeEventListener("wheel", onWheel);
|
|
179
|
+
}, []);
|
|
180
|
+
const pointers = useRef(/* @__PURE__ */ new Map());
|
|
181
|
+
const pinchDistance = useRef(void 0);
|
|
182
|
+
const onPointerDown = (event) => {
|
|
183
|
+
if (event.target.closest("button")) return;
|
|
184
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
185
|
+
pointers.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
186
|
+
pinchDistance.current = void 0;
|
|
187
|
+
setAnimate(false);
|
|
188
|
+
};
|
|
189
|
+
const onPointerMove = (event) => {
|
|
190
|
+
const previous = pointers.current.get(event.pointerId);
|
|
191
|
+
if (!previous) return;
|
|
192
|
+
const current = { x: event.clientX, y: event.clientY };
|
|
193
|
+
pointers.current.set(event.pointerId, current);
|
|
194
|
+
const all = [...pointers.current.values()];
|
|
195
|
+
if (all.length >= 2) {
|
|
196
|
+
const [a, b] = all;
|
|
197
|
+
const distance = Math.hypot(a.x - b.x, a.y - b.y);
|
|
198
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
199
|
+
const mx = (a.x + b.x) / 2 - rect.left;
|
|
200
|
+
const my = (a.y + b.y) / 2 - rect.top;
|
|
201
|
+
if (pinchDistance.current !== void 0 && pinchDistance.current > 0) {
|
|
202
|
+
const base = viewRef.current;
|
|
203
|
+
const zoom = clampZoom(base.zoom * (distance / pinchDistance.current));
|
|
204
|
+
const ratio = zoom / base.zoom;
|
|
205
|
+
setView({ zoom, x: mx - (mx - base.x) * ratio, y: my - (my - base.y) * ratio });
|
|
206
|
+
}
|
|
207
|
+
pinchDistance.current = distance;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const dx = current.x - previous.x;
|
|
211
|
+
const dy = current.y - previous.y;
|
|
212
|
+
setView((value) => ({ ...value, x: value.x + dx, y: value.y + dy }));
|
|
213
|
+
};
|
|
214
|
+
const onPointerEnd = (event) => {
|
|
215
|
+
pointers.current.delete(event.pointerId);
|
|
216
|
+
pinchDistance.current = void 0;
|
|
217
|
+
};
|
|
218
|
+
const zoomBy = (factor) => {
|
|
219
|
+
const current = viewRef.current;
|
|
220
|
+
const zoom = clampZoom(current.zoom * factor);
|
|
221
|
+
const ratio = zoom / current.zoom;
|
|
222
|
+
const cx0 = size.width / 2;
|
|
223
|
+
const cy0 = size.height / 2;
|
|
224
|
+
moveView({ zoom, x: cx0 - (cx0 - current.x) * ratio, y: cy0 - (cy0 - current.y) * ratio }, true);
|
|
225
|
+
};
|
|
226
|
+
const panBy = (dx, dy) => moveView({ ...viewRef.current, x: viewRef.current.x + dx, y: viewRef.current.y + dy }, true);
|
|
227
|
+
const toggleCluster = (clusterId, focusAfter) => {
|
|
228
|
+
const isExpanded = expanded.has(clusterId);
|
|
229
|
+
pendingFocus.current = focusAfter;
|
|
230
|
+
if (expandedClusterIds === void 0) {
|
|
231
|
+
setLocalExpanded((previous) => {
|
|
232
|
+
const next = new Set(previous);
|
|
233
|
+
if (isExpanded) next.delete(clusterId);
|
|
234
|
+
else next.add(clusterId);
|
|
235
|
+
return next;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
onToggleCluster?.(clusterId, !isExpanded);
|
|
239
|
+
};
|
|
240
|
+
const activate = (item) => {
|
|
241
|
+
if (item.cluster) {
|
|
242
|
+
toggleCluster(item.cluster.id, item.cluster.members[0]?.id ?? item.id);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (item.node) {
|
|
246
|
+
setLocalFocus(item.id);
|
|
247
|
+
onFocusChange?.(item.id);
|
|
248
|
+
onSelect?.(item.node);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const onItemKeyDown = (event, item) => {
|
|
252
|
+
if (NAV_KEYS.includes(event.key)) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
const next = layeredNeighbor(layout, item.id, event.key);
|
|
255
|
+
if (next !== void 0) focusItem(next);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const labelFor = (item) => {
|
|
259
|
+
if (item.cluster) {
|
|
260
|
+
const cluster = item.cluster;
|
|
261
|
+
return `${labels.cluster(cluster.members.length, cluster.type, cluster.edgeLabel)}, ${labels.expandCluster}`;
|
|
262
|
+
}
|
|
263
|
+
const node = item.node;
|
|
264
|
+
const conflict = conflictedNodeIds.has(node.id) ? labels.conflict : void 0;
|
|
265
|
+
if (nodeAriaLabel) return [nodeAriaLabel(node), item.inCycle ? labels.cycle : void 0, conflict].filter(Boolean).join(", ");
|
|
266
|
+
return [node.label, node.type, node.description, labels.relationships(item.degree), item.inCycle ? labels.cycle : void 0, conflict].filter(Boolean).join(", ");
|
|
267
|
+
};
|
|
268
|
+
const showLabel = (edge) => {
|
|
269
|
+
if (edgeLabels === "never" || edge.label === void 0) return false;
|
|
270
|
+
if (edgeLabels === "always") return true;
|
|
271
|
+
return edge.autoLabel || activeIds.has(edge.source) || activeIds.has(edge.target) || focusedId === edge.source || focusedId === edge.target;
|
|
272
|
+
};
|
|
273
|
+
const viewportStyle = height !== void 0 ? { "--uix-relationship-graph-height": height } : {};
|
|
274
|
+
const stageStyle = {
|
|
275
|
+
width: layout.width,
|
|
276
|
+
height: layout.height,
|
|
277
|
+
transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom})`
|
|
278
|
+
};
|
|
279
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-relationship-graph", "uix-relationship-graph--layered", density === "compact" && "uix-relationship-graph--compact", className), "aria-label": labels.graph, children: [
|
|
280
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-relationship-graph__toolbar", role: "group", "aria-label": labels.controls, children: [
|
|
281
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => zoomBy(ZOOM_STEP), disabled: view.zoom >= MAX_ZOOM, children: labels.zoomIn }),
|
|
282
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => zoomBy(1 / ZOOM_STEP), disabled: view.zoom <= MIN_ZOOM, children: labels.zoomOut }),
|
|
283
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => moveView(fitView(layout, size), true), children: labels.fit }),
|
|
284
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => moveView(initialView(layout, size), true), children: labels.reset }),
|
|
285
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => panBy(PAN_STEP, 0), "aria-label": labels.panLeft, children: labels.left }),
|
|
286
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => panBy(0, PAN_STEP), "aria-label": labels.panUp, children: labels.up }),
|
|
287
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => panBy(0, -PAN_STEP), "aria-label": labels.panDown, children: labels.down }),
|
|
288
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => panBy(-PAN_STEP, 0), "aria-label": labels.panRight, children: labels.right }),
|
|
289
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", children: labels.zoomLevel(Math.round(view.zoom * 100)) })
|
|
290
|
+
] }),
|
|
291
|
+
bounded.omittedNodeCount > 0 && /* @__PURE__ */ jsx("p", { className: "uix-relationship-graph__bounded", role: "status", children: labels.bounded(bounded.nodes.length, bounded.omittedNodeCount) }),
|
|
292
|
+
/* @__PURE__ */ jsx("p", { id: hintId, className: "uix-visually-hidden", children: labels.keyboardHint }),
|
|
293
|
+
/* @__PURE__ */ jsx(
|
|
294
|
+
"div",
|
|
295
|
+
{
|
|
296
|
+
ref: viewportRef,
|
|
297
|
+
className: "uix-relationship-graph__viewport",
|
|
298
|
+
style: viewportStyle,
|
|
299
|
+
onPointerDown,
|
|
300
|
+
onPointerMove,
|
|
301
|
+
onPointerUp: onPointerEnd,
|
|
302
|
+
onPointerCancel: onPointerEnd,
|
|
303
|
+
children: /* @__PURE__ */ jsxs("div", { className: "uix-relationship-graph__stage", style: stageStyle, "data-animate": animate || void 0, onTransitionEnd: () => setAnimate(false), children: [
|
|
304
|
+
/* @__PURE__ */ jsx("svg", { className: "uix-relationship-graph__edges", width: layout.width, height: layout.height, "aria-hidden": "true", focusable: "false", children: layout.edges.map((edge) => {
|
|
305
|
+
const active = activeIds.has(edge.source) || activeIds.has(edge.target) || edge.edgeIds.some((id) => highlightedEdgeIds.has(id));
|
|
306
|
+
const angle = edge.arrowAngle * 180 / Math.PI;
|
|
307
|
+
return /* @__PURE__ */ jsxs(
|
|
308
|
+
"g",
|
|
309
|
+
{
|
|
310
|
+
className: "uix-relationship-graph__link",
|
|
311
|
+
"data-kind": edge.kind,
|
|
312
|
+
"data-active": active || void 0,
|
|
313
|
+
"data-emphasis": edge.emphasis || void 0,
|
|
314
|
+
"data-conflicted": edge.edgeIds.some((id) => conflictedEdgeIds.has(id)) || void 0,
|
|
315
|
+
"data-dimmed": dimmedNodeIds.has(edge.source) && dimmedNodeIds.has(edge.target) || void 0,
|
|
316
|
+
children: [
|
|
317
|
+
/* @__PURE__ */ jsx("path", { d: edge.path }),
|
|
318
|
+
edge.arrow !== "none" && /* @__PURE__ */ jsx("polygon", { points: arrowPoints, transform: `translate(${edge.arrowX} ${edge.arrowY}) rotate(${angle})` }),
|
|
319
|
+
showLabel(edge) && /* @__PURE__ */ 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__ */ jsx("tspan", { x: edge.labelX, dy: index === 0 ? `${-(lines.length - 1) * 0.55}em` : "1.1em", children: line }, index)) })
|
|
320
|
+
]
|
|
321
|
+
},
|
|
322
|
+
edge.id
|
|
323
|
+
);
|
|
324
|
+
}) }),
|
|
325
|
+
layout.columns.map((column) => /* @__PURE__ */ jsx(
|
|
326
|
+
"div",
|
|
327
|
+
{
|
|
328
|
+
className: "uix-relationship-graph__column",
|
|
329
|
+
"aria-hidden": "true",
|
|
330
|
+
style: { left: column.x, width: layout.nodeWidth },
|
|
331
|
+
children: labels.column(column.depth, column.count)
|
|
332
|
+
},
|
|
333
|
+
column.depth
|
|
334
|
+
)),
|
|
335
|
+
/* @__PURE__ */ 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) => {
|
|
336
|
+
const selected = item.id === selectedItemId;
|
|
337
|
+
const dimmed = item.node ? dimmedNodeIds.has(item.node.id) : item.cluster.members.every((member) => dimmedNodeIds.has(member.id));
|
|
338
|
+
const body = item.cluster ? renderCluster?.(item.cluster) ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
339
|
+
/* @__PURE__ */ jsx("span", { className: "uix-relationship-graph__item-label", children: labels.cluster(item.cluster.members.length, item.cluster.type, item.cluster.edgeLabel) }),
|
|
340
|
+
/* @__PURE__ */ jsx("span", { className: "uix-relationship-graph__item-meta", children: labels.expandCluster })
|
|
341
|
+
] }) : renderNode?.(item.node, { selected, dimmed }) ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
342
|
+
/* @__PURE__ */ jsx("span", { className: "uix-relationship-graph__item-label", children: item.node.label }),
|
|
343
|
+
(item.node.type || item.node.description) && /* @__PURE__ */ jsx("span", { className: "uix-relationship-graph__item-meta", children: [item.node.type, item.node.description].filter(Boolean).join(" \xB7 ") })
|
|
344
|
+
] });
|
|
345
|
+
return /* @__PURE__ */ jsxs(
|
|
346
|
+
"button",
|
|
347
|
+
{
|
|
348
|
+
ref: (element) => {
|
|
349
|
+
if (element) itemRefs.current.set(item.id, element);
|
|
350
|
+
else itemRefs.current.delete(item.id);
|
|
351
|
+
},
|
|
352
|
+
type: "button",
|
|
353
|
+
className: "uix-relationship-graph__item",
|
|
354
|
+
"data-kind": item.kind,
|
|
355
|
+
"data-node-id": item.id,
|
|
356
|
+
"data-selected": selected || void 0,
|
|
357
|
+
"data-dimmed": dimmed || void 0,
|
|
358
|
+
"data-highlighted": item.node && highlightedNodeIds.has(item.node.id) || void 0,
|
|
359
|
+
"data-conflicted": item.node && conflictedNodeIds.has(item.node.id) || void 0,
|
|
360
|
+
"data-root": item.id === rootItemId || void 0,
|
|
361
|
+
style: { left: item.x, top: item.y, width: item.width, height: item.height },
|
|
362
|
+
tabIndex: item.id === focusedId ? 0 : -1,
|
|
363
|
+
"aria-label": labelFor(item),
|
|
364
|
+
...item.cluster ? { "aria-expanded": false } : { "aria-pressed": selected },
|
|
365
|
+
onClick: () => activate(item),
|
|
366
|
+
onKeyDown: (event) => onItemKeyDown(event, item),
|
|
367
|
+
onFocus: () => {
|
|
368
|
+
if (item.id === focusedId) return;
|
|
369
|
+
setLocalFocus(item.id);
|
|
370
|
+
onFocusChange?.(item.id);
|
|
371
|
+
},
|
|
372
|
+
onPointerEnter: () => setHoverId(item.id),
|
|
373
|
+
onPointerLeave: () => setHoverId((current) => current === item.id ? void 0 : current),
|
|
374
|
+
children: [
|
|
375
|
+
body,
|
|
376
|
+
item.inCycle && /* @__PURE__ */ jsx("span", { className: "uix-relationship-graph__item-flag", "aria-hidden": "true", children: labels.cycle })
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
item.id
|
|
380
|
+
);
|
|
381
|
+
}) }),
|
|
382
|
+
layout.items.filter((item) => item.expandedGroup).map((item) => {
|
|
383
|
+
const group = item.expandedGroup;
|
|
384
|
+
return /* @__PURE__ */ jsx(
|
|
385
|
+
"button",
|
|
386
|
+
{
|
|
387
|
+
type: "button",
|
|
388
|
+
className: "uix-relationship-graph__collapse",
|
|
389
|
+
"aria-expanded": "true",
|
|
390
|
+
style: { left: item.x, top: item.y - 26, width: item.width },
|
|
391
|
+
onClick: () => toggleCluster(group.id, group.id),
|
|
392
|
+
children: labels.collapseCluster(group.members.length, group.type)
|
|
393
|
+
},
|
|
394
|
+
`collapse:${group.id}`
|
|
395
|
+
);
|
|
396
|
+
})
|
|
397
|
+
] })
|
|
398
|
+
}
|
|
399
|
+
),
|
|
400
|
+
legend.length > 0 && /* @__PURE__ */ jsx("ul", { className: "uix-relationship-graph__legend", "aria-label": labels.legend, children: legend.map((entry) => /* @__PURE__ */ jsx("li", { "data-type": entry.id, children: entry.label }, entry.id)) }),
|
|
401
|
+
showList && /* @__PURE__ */ jsx(
|
|
402
|
+
EquivalentRelationshipList,
|
|
403
|
+
{
|
|
404
|
+
nodes: bounded.nodes,
|
|
405
|
+
edges: bounded.edges,
|
|
406
|
+
selectedId,
|
|
407
|
+
labels,
|
|
408
|
+
onSelect: (id) => {
|
|
409
|
+
const node = bounded.nodes.find((candidate) => candidate.id === id);
|
|
410
|
+
if (node) onSelect?.(node);
|
|
411
|
+
},
|
|
412
|
+
onExpandNeighbors,
|
|
413
|
+
onOpenDetails
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
] });
|
|
417
|
+
}
|
|
418
|
+
export {
|
|
419
|
+
LayeredRelationshipGraph
|
|
420
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { RelationshipGraphNode, RelationshipGraphEdge } from '../relationship-graph-model.js';
|
|
3
|
+
import { RelationshipGraphLabels } from './RelationshipGraph.js';
|
|
4
|
+
|
|
5
|
+
/** The list every RelationshipGraph mode pairs with its picture (internal). */
|
|
6
|
+
declare function EquivalentRelationshipList({ nodes, edges, selectedId, labels, onSelect, onExpandNeighbors, onOpenDetails, }: {
|
|
7
|
+
nodes: RelationshipGraphNode[];
|
|
8
|
+
edges: RelationshipGraphEdge[];
|
|
9
|
+
selectedId: string | undefined;
|
|
10
|
+
labels: RelationshipGraphLabels;
|
|
11
|
+
onSelect: (id: string) => void;
|
|
12
|
+
onExpandNeighbors: ((node: RelationshipGraphNode) => void) | undefined;
|
|
13
|
+
onOpenDetails: ((node: RelationshipGraphNode) => void) | undefined;
|
|
14
|
+
}): react.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { EquivalentRelationshipList };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { relationshipNeighbors } from "../relationship-graph-model.js";
|
|
3
|
+
function EquivalentRelationshipList({
|
|
4
|
+
nodes,
|
|
5
|
+
edges,
|
|
6
|
+
selectedId,
|
|
7
|
+
labels,
|
|
8
|
+
onSelect,
|
|
9
|
+
onExpandNeighbors,
|
|
10
|
+
onOpenDetails
|
|
11
|
+
}) {
|
|
12
|
+
const labelById = new Map(nodes.map((node) => [node.id, node.label]));
|
|
13
|
+
return /* @__PURE__ */ jsxs("div", { className: "uix-relationship-graph__list", role: "region", "aria-label": labels.list, children: [
|
|
14
|
+
/* @__PURE__ */ jsx("h3", { children: labels.listHeading }),
|
|
15
|
+
/* @__PURE__ */ jsx("ul", { children: nodes.map((node) => {
|
|
16
|
+
const neighbors = relationshipNeighbors(node.id, edges).map((id) => labelById.get(id) ?? id);
|
|
17
|
+
return /* @__PURE__ */ jsxs("li", { "data-selected": selectedId === node.id || void 0, children: [
|
|
18
|
+
/* @__PURE__ */ jsxs("button", { type: "button", onClick: () => onSelect(node.id), "aria-pressed": selectedId === node.id, children: [
|
|
19
|
+
/* @__PURE__ */ jsx("strong", { children: node.label }),
|
|
20
|
+
node.type && /* @__PURE__ */ jsx("span", { children: node.type }),
|
|
21
|
+
/* @__PURE__ */ jsx("span", { children: neighbors.length ? labels.connectedTo(neighbors.join(", ")) : labels.noConnections })
|
|
22
|
+
] }),
|
|
23
|
+
/* @__PURE__ */ jsxs("span", { className: "uix-relationship-graph__list-actions", children: [
|
|
24
|
+
onExpandNeighbors && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onExpandNeighbors(node), children: labels.expandNeighbors }),
|
|
25
|
+
onOpenDetails && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onOpenDetails(node), children: labels.openDetails })
|
|
26
|
+
] })
|
|
27
|
+
] }, node.id);
|
|
28
|
+
}) })
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
EquivalentRelationshipList
|
|
33
|
+
};
|
|
@@ -4,7 +4,9 @@ import { HTMLAttributes } from 'react';
|
|
|
4
4
|
interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
5
|
size?: 'md' | 'lg';
|
|
6
6
|
accent?: boolean;
|
|
7
|
+
/** Announced loading text (default "Loading…"). */
|
|
8
|
+
label?: string;
|
|
7
9
|
}
|
|
8
|
-
declare function Spinner({ size, accent, className, ...props }: SpinnerProps): react.JSX.Element;
|
|
10
|
+
declare function Spinner({ size, accent, label, className, ...props }: SpinnerProps): react.JSX.Element;
|
|
9
11
|
|
|
10
12
|
export { Spinner, type SpinnerProps };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from "../cx.js";
|
|
3
|
-
function Spinner({ size = "md", accent, className, ...props }) {
|
|
3
|
+
function Spinner({ size = "md", accent, label, className, ...props }) {
|
|
4
4
|
return /* @__PURE__ */ jsx(
|
|
5
5
|
"span",
|
|
6
6
|
{
|
|
7
7
|
className: cx("uix-spinner", accent && "uix-spinner--accent", size === "lg" && "uix-spinner--lg", className),
|
|
8
8
|
role: "status",
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
...props,
|
|
10
|
+
children: /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: label ?? "Loading\u2026" })
|
|
11
11
|
}
|
|
12
12
|
);
|
|
13
13
|
}
|
package/dist/components/Stat.js
CHANGED
|
@@ -8,7 +8,11 @@ function Stat({ label, value, icon, meta, trend, className, ...props }) {
|
|
|
8
8
|
] }),
|
|
9
9
|
/* @__PURE__ */ jsx("div", { className: "uix-stat__value", children: value }),
|
|
10
10
|
(meta != null || trend != null) && /* @__PURE__ */ jsxs("div", { className: "uix-stat__meta", children: [
|
|
11
|
-
trend != null && /* @__PURE__ */
|
|
11
|
+
trend != null && /* @__PURE__ */ jsxs("span", { className: cx("uix-stat__trend", `uix-stat__trend--${trend.direction}`), children: [
|
|
12
|
+
/* @__PURE__ */ 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__ */ jsx("path", { d: "M12 19V5m-6 6 6-6 6 6" }) : /* @__PURE__ */ jsx("path", { d: "M12 5v14m6-6-6 6-6-6" }) }),
|
|
13
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: trend.direction === "up" ? "up" : "down" }),
|
|
14
|
+
trend.label
|
|
15
|
+
] }),
|
|
12
16
|
meta
|
|
13
17
|
] })
|
|
14
18
|
] });
|
|
@@ -49,6 +49,7 @@ interface LoadingStateProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
49
49
|
/** Number of skeleton rows to render. */
|
|
50
50
|
rows?: number;
|
|
51
51
|
density?: 'compact' | 'comfortable';
|
|
52
|
+
/** Announced loading text (default "Loading"). */
|
|
52
53
|
label?: string;
|
|
53
54
|
}
|
|
54
55
|
/** A stack of skeleton rows for list/section loading. `role="status"`. */
|
|
@@ -52,7 +52,13 @@ function Skeleton({ variant = "block", width, height, className, style, ...props
|
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
54
|
function LoadingState({ rows = 3, density = "comfortable", label, className, ...props }) {
|
|
55
|
-
return
|
|
55
|
+
return (
|
|
56
|
+
// no aria-busy: busy=true tells AT the region isn't ready and suppresses the announcement (UIX-A11Y-4)
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: cx("uix-stack", density === "compact" && "uix-stack--compact", className), role: "status", ...props, children: [
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: label ?? "Loading" }),
|
|
59
|
+
Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: `${92 - i % 3 * 14}%` }, i))
|
|
60
|
+
] })
|
|
61
|
+
);
|
|
56
62
|
}
|
|
57
63
|
export {
|
|
58
64
|
EmptyState,
|
|
@@ -2,6 +2,11 @@ import * as react from 'react';
|
|
|
2
2
|
import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, TdHTMLAttributes, TableHTMLAttributes, ThHTMLAttributes } from 'react';
|
|
3
3
|
|
|
4
4
|
interface TableWrapProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/**
|
|
6
|
+
* Polite live-region text for table state changes — "Sorted by …", "N results",
|
|
7
|
+
* "N selected". Pass `useTable().announcement` straight through (UIX-A11Y-2).
|
|
8
|
+
*/
|
|
9
|
+
announcement?: string;
|
|
5
10
|
children?: ReactNode;
|
|
6
11
|
}
|
|
7
12
|
declare const TableWrap: react.ForwardRefExoticComponent<TableWrapProps & react.RefAttributes<HTMLDivElement>>;
|
|
@@ -53,9 +58,11 @@ interface RowActionProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
53
58
|
declare function RowAction({ children, className, type, ...props }: RowActionProps): react.JSX.Element;
|
|
54
59
|
interface ExpandToggleProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
55
60
|
expanded?: boolean;
|
|
61
|
+
/** id of the detail row this toggle controls → `aria-controls` (UIX-A11Y-2). */
|
|
62
|
+
controls?: string;
|
|
56
63
|
}
|
|
57
64
|
/** Chevron button that expands/collapses an inline detail row. */
|
|
58
|
-
declare function ExpandToggle({ expanded, className, type, ...props }: ExpandToggleProps): react.JSX.Element;
|
|
65
|
+
declare function ExpandToggle({ expanded, controls, className, type, ...props }: ExpandToggleProps): react.JSX.Element;
|
|
59
66
|
/** Emphasised primary cell text. */
|
|
60
67
|
declare function CellStrong({ children, className, ...props }: HTMLAttributes<HTMLSpanElement>): react.JSX.Element;
|
|
61
68
|
/** Secondary line under a primary cell value (muted, meta size). */
|
package/dist/components/Table.js
CHANGED
|
@@ -2,8 +2,11 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import { cx } from "../cx.js";
|
|
4
4
|
import { highlightSegments } from "../table-engine.js";
|
|
5
|
-
const TableWrap = forwardRef(function TableWrap2({ children, className, ...props }, ref) {
|
|
6
|
-
return /* @__PURE__ */
|
|
5
|
+
const TableWrap = forwardRef(function TableWrap2({ announcement, children, className, ...props }, ref) {
|
|
6
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: cx("uix-table-wrap", className), ...props, children: [
|
|
7
|
+
announcement != null && /* @__PURE__ */ jsx("div", { role: "status", className: "uix-visually-hidden", children: announcement }),
|
|
8
|
+
children
|
|
9
|
+
] });
|
|
7
10
|
});
|
|
8
11
|
function Table({
|
|
9
12
|
density = "standard",
|
|
@@ -42,9 +45,9 @@ function Th({ sortable, sortDirection, sortOrder, onSort, className, children, o
|
|
|
42
45
|
"data-sort": sortable || void 0,
|
|
43
46
|
"data-sort-order": sortOrder != null && sortOrder > 0 ? sortOrder : void 0,
|
|
44
47
|
"aria-sort": sortDirection && sortDirection !== "none" ? sortDirection : void 0,
|
|
45
|
-
onClick: sortable
|
|
48
|
+
onClick: sortable && onSort ? void 0 : onClick,
|
|
46
49
|
...props,
|
|
47
|
-
children
|
|
50
|
+
children: sortable ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-th__sortbtn", onClick: onSort, children }) : children
|
|
48
51
|
}
|
|
49
52
|
);
|
|
50
53
|
}
|
|
@@ -56,7 +59,7 @@ function Tr({ selected, pinned, className, children, ...props }) {
|
|
|
56
59
|
"tr",
|
|
57
60
|
{
|
|
58
61
|
className,
|
|
59
|
-
"
|
|
62
|
+
"data-selected": selected || void 0,
|
|
60
63
|
"data-pinned": pinned || void 0,
|
|
61
64
|
...props,
|
|
62
65
|
children
|
|
@@ -79,13 +82,14 @@ function RowActions({ children, className, ...props }) {
|
|
|
79
82
|
function RowAction({ children, className, type = "button", ...props }) {
|
|
80
83
|
return /* @__PURE__ */ jsx("button", { type, className: cx("uix-rowact", className), ...props, children });
|
|
81
84
|
}
|
|
82
|
-
function ExpandToggle({ expanded, className, type = "button", ...props }) {
|
|
85
|
+
function ExpandToggle({ expanded, controls, className, type = "button", ...props }) {
|
|
83
86
|
return /* @__PURE__ */ jsx(
|
|
84
87
|
"button",
|
|
85
88
|
{
|
|
86
89
|
type,
|
|
87
90
|
className: cx("uix-table__expand", className),
|
|
88
91
|
"aria-expanded": expanded,
|
|
92
|
+
"aria-controls": controls,
|
|
89
93
|
"aria-label": expanded ? "Collapse row" : "Expand row",
|
|
90
94
|
...props,
|
|
91
95
|
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 4l4 4-4 4" }) })
|
|
@@ -15,5 +15,17 @@ interface TabProps extends Omit<HTMLAttributes<HTMLButtonElement>, 'onChange'> {
|
|
|
15
15
|
disabled?: boolean;
|
|
16
16
|
}
|
|
17
17
|
declare function Tab({ value, children, disabled, className, onClick, ...props }: TabProps): react.JSX.Element;
|
|
18
|
+
interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
19
|
+
/** The owning `<Tab>`'s value — the panel renders only while that tab is selected. */
|
|
20
|
+
value: string;
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The tabpanel paired with a `<Tab>` by `value` (UIX-A11Y-2): labelled by its tab,
|
|
25
|
+
* `tabIndex 0` so Tab from the tablist lands in the content, rendered only while
|
|
26
|
+
* selected. Place it as a direct child of `<Tabs>` — it is hoisted out of the tablist —
|
|
27
|
+
* or anywhere the Tabs context reaches.
|
|
28
|
+
*/
|
|
29
|
+
declare function TabPanel({ value, children, ...props }: TabPanelProps): react.JSX.Element | null;
|
|
18
30
|
|
|
19
|
-
export { Tab, type TabProps, Tabs, type TabsProps };
|
|
31
|
+
export { Tab, TabPanel, type TabPanelProps, type TabProps, Tabs, type TabsProps };
|