@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
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export { ToggleRow, ToggleRowProps } from './components/ToggleRow.js';
|
|
|
28
28
|
export { CollapsibleSection, CollapsibleSectionProps } from './components/CollapsibleSection.js';
|
|
29
29
|
export { List, ListItem, ListItemProps, ListProps } from './components/List.js';
|
|
30
30
|
export { DescriptionItem, DescriptionItemProps, DescriptionList, DescriptionListItem, DescriptionListProps } from './components/DescriptionList.js';
|
|
31
|
-
export { Tab, TabProps, Tabs, TabsProps } from './components/Tabs.js';
|
|
31
|
+
export { Tab, TabPanel, TabPanelProps, TabProps, Tabs, TabsProps } from './components/Tabs.js';
|
|
32
32
|
export { AppShell, AppShellProps, ShellNav } from './components/AppShell.js';
|
|
33
33
|
export { NavGroup, NavGroupProps, NavItem, NavItemProps, Sidebar, SidebarProps, SidebarSection, SidebarSectionProps, SubNavItem, SubNavItemProps } from './components/Sidebar.js';
|
|
34
34
|
export { StarButton, StarButtonProps } from './components/StarButton.js';
|
|
@@ -61,7 +61,7 @@ export { Note, NoteProps, NoteTone, Prose, ProseProps } from './components/Prose
|
|
|
61
61
|
export { Popover, PopoverProps } from './components/Popover.js';
|
|
62
62
|
export { CommandGroup, CommandGroupProps, CommandItem, CommandItemProps, CommandPalette, CommandPaletteProps } from './components/CommandPalette.js';
|
|
63
63
|
export { Inbox, InboxDetail, InboxDetailProps, InboxItem, InboxItemProps, InboxList, InboxListProps, InboxProps } from './components/Inbox.js';
|
|
64
|
-
export { Kanban, KanbanCard, KanbanCardProps, KanbanColumn, KanbanColumnProps, KanbanProps } from './components/Kanban.js';
|
|
64
|
+
export { Kanban, KanbanCard, KanbanCardProps, KanbanColumn, KanbanColumnProps, KanbanMoveDirection, KanbanProps } from './components/Kanban.js';
|
|
65
65
|
export { Tree, TreeNodeData, TreeNodeProps, TreeProps } from './components/Tree.js';
|
|
66
66
|
export { FlatNode, TreeLike, TreeNavAction, flattenTree, treeNav } from './tree-model.js';
|
|
67
67
|
export { ContentList, ContentListItem, ContentListItemProps, ContentListProps, EventRow, EventRowProps, FeaturedRundown, FeaturedRundownItem, FeaturedRundownItemProps, FeaturedRundownProps, FeaturedStage, FeaturedStageProps, NewsLead, NewsLeadProps, NoticeQueue, NoticeQueueProps, PageIntro, PageIntroProps, ResourceGrid, ResourceGridProps, SectionHead, SectionHeadProps, StatLine, StatLineItem, StatLineProps, StatusRow, StatusRowProps } from './components/EditorialHome.js';
|
|
@@ -72,8 +72,8 @@ export { BuilderCanvas, BuilderCanvasItem, BuilderCanvasProps, BuilderPaletteIte
|
|
|
72
72
|
export { SchedulingCalendar, SchedulingCalendarEntry, SchedulingCalendarOverlay, SchedulingCalendarProps, SchedulingCalendarView, SchedulingEntryState, SchedulingOverlayKind } from './components/SchedulingCalendar.js';
|
|
73
73
|
export { DateRangePicker, DateRangePickerProps } from './components/DateRangePicker.js';
|
|
74
74
|
export { CalendarDay, DateRangeValue, ZonedDateSpan, addCalendarDays, addCalendarMonths, buildMonthGrid, enumerateDateSpan, isDateInRange, isDateUnavailable, selectRangeDate, startOfMonth, toDateKey, zonedDateKey, zonedDateSpan } from './calendar-model.js';
|
|
75
|
-
export { RelationshipGraph, RelationshipGraphLegendItem, RelationshipGraphProps } from './components/RelationshipGraph.js';
|
|
76
|
-
export { BoundedRelationshipGraph, PositionedRelationshipNode, RelationshipGraphEdge, RelationshipGraphNode, boundRelationshipGraph, layoutRelationshipGraph, relationshipNeighbors, traverseRelationshipNode } from './relationship-graph-model.js';
|
|
75
|
+
export { DEFAULT_RELATIONSHIP_GRAPH_LABELS, RelationshipGraph, RelationshipGraphLabels, RelationshipGraphLegendItem, RelationshipGraphProps } from './components/RelationshipGraph.js';
|
|
76
|
+
export { BoundedRelationshipGraph, LayeredColumn, LayeredEdgeClassification, LayeredEdgeKind, LayeredItem, LayeredLayout, LayeredLayoutOptions, LayeredNavigationKey, PositionedRelationshipNode, RelationshipGraphArrow, RelationshipGraphCluster, RelationshipGraphEdge, RelationshipGraphNode, RoutedEdge, boundRelationshipGraph, classifyLayeredEdges, clusterIdFor, layeredNeighbor, layoutLayeredGraph, layoutRelationshipGraph, relationshipNeighbors, traverseRelationshipNode, wrapEdgeLabel } from './relationship-graph-model.js';
|
|
77
77
|
export { MatchBulkResult, MatchReview, MatchReviewAction, MatchReviewCandidate, MatchReviewField, MatchReviewProps } from './components/MatchReview.js';
|
|
78
78
|
export { MetricInput, MetricInputProps } from './components/MetricInput.js';
|
|
79
79
|
export { clampMetricValue, parseMetricValue, stepMetricValue } from './metric-model.js';
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ import { ToggleRow } from "./components/ToggleRow.js";
|
|
|
28
28
|
import { CollapsibleSection } from "./components/CollapsibleSection.js";
|
|
29
29
|
import { List, ListItem } from "./components/List.js";
|
|
30
30
|
import { DescriptionList, DescriptionItem } from "./components/DescriptionList.js";
|
|
31
|
-
import { Tabs, Tab } from "./components/Tabs.js";
|
|
31
|
+
import { Tabs, Tab, TabPanel } from "./components/Tabs.js";
|
|
32
32
|
import { AppShell } from "./components/AppShell.js";
|
|
33
33
|
import { Sidebar, SidebarSection, NavItem, NavGroup, SubNavItem } from "./components/Sidebar.js";
|
|
34
34
|
import { StarButton } from "./components/StarButton.js";
|
|
@@ -121,8 +121,8 @@ import {
|
|
|
121
121
|
zonedDateKey,
|
|
122
122
|
zonedDateSpan
|
|
123
123
|
} from "./calendar-model.js";
|
|
124
|
-
import { RelationshipGraph } from "./components/RelationshipGraph.js";
|
|
125
|
-
import { boundRelationshipGraph, layoutRelationshipGraph, relationshipNeighbors, traverseRelationshipNode } from "./relationship-graph-model.js";
|
|
124
|
+
import { RelationshipGraph, DEFAULT_RELATIONSHIP_GRAPH_LABELS } from "./components/RelationshipGraph.js";
|
|
125
|
+
import { boundRelationshipGraph, layoutRelationshipGraph, relationshipNeighbors, traverseRelationshipNode, classifyLayeredEdges, clusterIdFor, layeredNeighbor, layoutLayeredGraph, wrapEdgeLabel } from "./relationship-graph-model.js";
|
|
126
126
|
import { MatchReview } from "./components/MatchReview.js";
|
|
127
127
|
import { MetricInput } from "./components/MetricInput.js";
|
|
128
128
|
import { clampMetricValue, parseMetricValue, stepMetricValue } from "./metric-model.js";
|
|
@@ -163,6 +163,7 @@ export {
|
|
|
163
163
|
ConfirmDialog,
|
|
164
164
|
ContentList,
|
|
165
165
|
ContentListItem,
|
|
166
|
+
DEFAULT_RELATIONSHIP_GRAPH_LABELS,
|
|
166
167
|
DateRangePicker,
|
|
167
168
|
DescriptionItem,
|
|
168
169
|
DescriptionList,
|
|
@@ -247,6 +248,7 @@ export {
|
|
|
247
248
|
SubNavItem,
|
|
248
249
|
Switch,
|
|
249
250
|
Tab,
|
|
251
|
+
TabPanel,
|
|
250
252
|
Table,
|
|
251
253
|
TableWrap,
|
|
252
254
|
Tabs,
|
|
@@ -271,6 +273,8 @@ export {
|
|
|
271
273
|
buildMonthGrid,
|
|
272
274
|
buildThreeWayDiff,
|
|
273
275
|
clampMetricValue,
|
|
276
|
+
classifyLayeredEdges,
|
|
277
|
+
clusterIdFor,
|
|
274
278
|
computePosition,
|
|
275
279
|
contrastRatio,
|
|
276
280
|
cx,
|
|
@@ -284,6 +288,8 @@ export {
|
|
|
284
288
|
isDateInRange,
|
|
285
289
|
isDateUnavailable,
|
|
286
290
|
isRuleGroup,
|
|
291
|
+
layeredNeighbor,
|
|
292
|
+
layoutLayeredGraph,
|
|
287
293
|
layoutRelationshipGraph,
|
|
288
294
|
mapRuleGroup,
|
|
289
295
|
meetsContrast,
|
|
@@ -311,6 +317,7 @@ export {
|
|
|
311
317
|
useTable,
|
|
312
318
|
useVirtualRows,
|
|
313
319
|
validateRuleDefinition,
|
|
320
|
+
wrapEdgeLabel,
|
|
314
321
|
zonedDateKey,
|
|
315
322
|
zonedDateSpan
|
|
316
323
|
};
|
|
@@ -5,13 +5,21 @@ interface RelationshipGraphNode {
|
|
|
5
5
|
description?: string;
|
|
6
6
|
x?: number;
|
|
7
7
|
y?: number;
|
|
8
|
+
/** Layered layout: the node's column. Omitted → BFS distance from the root along source→target. */
|
|
9
|
+
depth?: number;
|
|
8
10
|
}
|
|
11
|
+
/** Where an edge's arrowhead sits: at the target (default), at the source, or nowhere. */
|
|
12
|
+
type RelationshipGraphArrow = 'forward' | 'backward' | 'none';
|
|
9
13
|
interface RelationshipGraphEdge {
|
|
10
14
|
id: string;
|
|
11
15
|
source: string;
|
|
12
16
|
target: string;
|
|
13
17
|
type?: string;
|
|
14
18
|
label?: string;
|
|
19
|
+
/** Layered layout: arrowhead placement (default `forward`). */
|
|
20
|
+
arrow?: RelationshipGraphArrow;
|
|
21
|
+
/** Layered layout: always draw this edge's label, whatever the label policy. */
|
|
22
|
+
emphasis?: boolean;
|
|
15
23
|
}
|
|
16
24
|
interface PositionedRelationshipNode extends RelationshipGraphNode {
|
|
17
25
|
x: number;
|
|
@@ -27,5 +35,108 @@ declare function boundRelationshipGraph(nodes: RelationshipGraphNode[], edges: R
|
|
|
27
35
|
declare function relationshipNeighbors(nodeId: string, edges: RelationshipGraphEdge[]): string[];
|
|
28
36
|
declare function layoutRelationshipGraph(nodes: RelationshipGraphNode[], edges: RelationshipGraphEdge[], rootId?: string): PositionedRelationshipNode[];
|
|
29
37
|
declare function traverseRelationshipNode(nodes: RelationshipGraphNode[], currentId: string | undefined, direction: -1 | 1): string | undefined;
|
|
38
|
+
type LayeredEdgeKind = 'forward' | 'lateral' | 'cycle';
|
|
39
|
+
interface LayeredLayoutOptions {
|
|
40
|
+
rootId?: string;
|
|
41
|
+
/** Minimum sibling group size that collapses into a cluster. Default 6; 0 disables. */
|
|
42
|
+
clusterThreshold?: number;
|
|
43
|
+
expandedClusterIds?: ReadonlySet<string>;
|
|
44
|
+
density?: 'compact' | 'standard';
|
|
45
|
+
}
|
|
46
|
+
interface RelationshipGraphCluster {
|
|
47
|
+
id: string;
|
|
48
|
+
parentId: string;
|
|
49
|
+
depth: number;
|
|
50
|
+
type?: string;
|
|
51
|
+
edgeType?: string;
|
|
52
|
+
edgeLabel?: string;
|
|
53
|
+
members: RelationshipGraphNode[];
|
|
54
|
+
}
|
|
55
|
+
interface LayeredItem {
|
|
56
|
+
/** Node id, or cluster id for a collapsed cluster. */
|
|
57
|
+
id: string;
|
|
58
|
+
kind: 'node' | 'cluster';
|
|
59
|
+
node?: RelationshipGraphNode;
|
|
60
|
+
cluster?: RelationshipGraphCluster;
|
|
61
|
+
depth: number;
|
|
62
|
+
/** Position within its column (0-based, top to bottom). */
|
|
63
|
+
index: number;
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
/** Forward parents / children as item ids, in column order. */
|
|
69
|
+
parents: string[];
|
|
70
|
+
children: string[];
|
|
71
|
+
/** Set on the first member of an expanded cluster: the group its collapse control belongs to. */
|
|
72
|
+
expandedGroup?: RelationshipGraphCluster;
|
|
73
|
+
/** True when a lateral or cycle edge touches this node. */
|
|
74
|
+
inCycle: boolean;
|
|
75
|
+
/** Edges (any kind) touching this item. */
|
|
76
|
+
degree: number;
|
|
77
|
+
}
|
|
78
|
+
interface RoutedEdge {
|
|
79
|
+
id: string;
|
|
80
|
+
/** Item ids (a cluster id when the edge enters a collapsed cluster). */
|
|
81
|
+
source: string;
|
|
82
|
+
target: string;
|
|
83
|
+
kind: LayeredEdgeKind;
|
|
84
|
+
/** Original edge ids drawn by this path (several for a cluster). */
|
|
85
|
+
edgeIds: string[];
|
|
86
|
+
type?: string;
|
|
87
|
+
label?: string;
|
|
88
|
+
arrow: RelationshipGraphArrow;
|
|
89
|
+
emphasis: boolean;
|
|
90
|
+
path: string;
|
|
91
|
+
labelX: number;
|
|
92
|
+
labelY: number;
|
|
93
|
+
/** Arrowhead tip and the direction it points in (radians, 0 = pointing right). */
|
|
94
|
+
arrowX: number;
|
|
95
|
+
arrowY: number;
|
|
96
|
+
arrowAngle: number;
|
|
97
|
+
/** Label policy `auto` shows this label without interaction. */
|
|
98
|
+
autoLabel: boolean;
|
|
99
|
+
/** The label wrapped to fit the column gap (at most two lines). */
|
|
100
|
+
labelLines: string[];
|
|
101
|
+
}
|
|
102
|
+
interface LayeredColumn {
|
|
103
|
+
depth: number;
|
|
104
|
+
x: number;
|
|
105
|
+
/** Underlying nodes in the column, cluster members included. */
|
|
106
|
+
count: number;
|
|
107
|
+
}
|
|
108
|
+
interface LayeredLayout {
|
|
109
|
+
rootId: string | undefined;
|
|
110
|
+
items: LayeredItem[];
|
|
111
|
+
edges: RoutedEdge[];
|
|
112
|
+
clusters: RelationshipGraphCluster[];
|
|
113
|
+
columns: LayeredColumn[];
|
|
114
|
+
/** Node id → id of the item that currently draws it (itself, or its cluster). */
|
|
115
|
+
itemOf: Record<string, string>;
|
|
116
|
+
width: number;
|
|
117
|
+
height: number;
|
|
118
|
+
nodeWidth: number;
|
|
119
|
+
nodeHeight: number;
|
|
120
|
+
headerHeight: number;
|
|
121
|
+
}
|
|
122
|
+
/** Word-wrap a label into at most two lines that fit `width`; overlong words end in an ellipsis. */
|
|
123
|
+
declare function wrapEdgeLabel(label: string, width: number): string[];
|
|
124
|
+
interface LayeredEdgeClassification {
|
|
125
|
+
depthById: Map<string, number>;
|
|
126
|
+
kindById: Map<string, LayeredEdgeKind>;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Column (depth) for every node and a kind for every drawable edge.
|
|
130
|
+
* Supplied `node.depth` wins; otherwise BFS from the root along source→target.
|
|
131
|
+
* Nodes the BFS cannot reach go to a trailing column. Self-loops and edges with
|
|
132
|
+
* a missing endpoint get no kind and are not drawn.
|
|
133
|
+
*/
|
|
134
|
+
declare function classifyLayeredEdges(nodes: RelationshipGraphNode[], edges: RelationshipGraphEdge[], rootId?: string | undefined): LayeredEdgeClassification;
|
|
135
|
+
declare function clusterIdFor(parentId: string, type: string | undefined, edgeType: string | undefined): string;
|
|
136
|
+
/** Layered, clustered, crossing-reduced layout for `RelationshipGraph layout="layered"`. */
|
|
137
|
+
declare function layoutLayeredGraph(nodes: RelationshipGraphNode[], edges: RelationshipGraphEdge[], options?: LayeredLayoutOptions): LayeredLayout;
|
|
138
|
+
type LayeredNavigationKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight' | 'Home' | 'End';
|
|
139
|
+
/** Keyboard traversal over a layered layout: ←/→ along forward edges, ↑/↓ within a column. */
|
|
140
|
+
declare function layeredNeighbor(layout: LayeredLayout, currentId: string | undefined, key: LayeredNavigationKey): string | undefined;
|
|
30
141
|
|
|
31
|
-
export { type BoundedRelationshipGraph, type PositionedRelationshipNode, type RelationshipGraphEdge, type RelationshipGraphNode, boundRelationshipGraph, layoutRelationshipGraph, relationshipNeighbors, traverseRelationshipNode };
|
|
142
|
+
export { type BoundedRelationshipGraph, type LayeredColumn, type LayeredEdgeClassification, type LayeredEdgeKind, type LayeredItem, type LayeredLayout, type LayeredLayoutOptions, type LayeredNavigationKey, type PositionedRelationshipNode, type RelationshipGraphArrow, type RelationshipGraphCluster, type RelationshipGraphEdge, type RelationshipGraphNode, type RoutedEdge, boundRelationshipGraph, classifyLayeredEdges, clusterIdFor, layeredNeighbor, layoutLayeredGraph, layoutRelationshipGraph, relationshipNeighbors, traverseRelationshipNode, wrapEdgeLabel };
|
|
@@ -60,9 +60,428 @@ function traverseRelationshipNode(nodes, currentId, direction) {
|
|
|
60
60
|
const current = Math.max(0, nodes.findIndex((node) => node.id === currentId));
|
|
61
61
|
return nodes[(current + direction + nodes.length) % nodes.length]?.id;
|
|
62
62
|
}
|
|
63
|
+
const LAYERED_GEOMETRY = {
|
|
64
|
+
standard: { nodeWidth: 176, nodeHeight: 76, columnGap: 96, rowGap: 12 },
|
|
65
|
+
compact: { nodeWidth: 160, nodeHeight: 60, columnGap: 80, rowGap: 8 }
|
|
66
|
+
};
|
|
67
|
+
const LAYERED_PADDING = 16;
|
|
68
|
+
const LAYERED_HEADER = 28;
|
|
69
|
+
const GROUP_HEADER = 28;
|
|
70
|
+
const AUTO_LABEL_MAX_FANOUT = 4;
|
|
71
|
+
const LABEL_CHAR_WIDTH = 5.6;
|
|
72
|
+
const LABEL_MAX_LINES = 2;
|
|
73
|
+
function wrapEdgeLabel(label, width) {
|
|
74
|
+
const max = Math.max(4, Math.floor(width / LABEL_CHAR_WIDTH));
|
|
75
|
+
const clip = (word) => word.length > max ? `${word.slice(0, max - 1)}\u2026` : word;
|
|
76
|
+
const lines = [];
|
|
77
|
+
let current = "";
|
|
78
|
+
for (const word of label.split(/\s+/).filter(Boolean)) {
|
|
79
|
+
const next = current ? `${current} ${word}` : word;
|
|
80
|
+
if (next.length <= max) {
|
|
81
|
+
current = next;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (current) lines.push(current);
|
|
85
|
+
current = clip(word);
|
|
86
|
+
if (lines.length === LABEL_MAX_LINES) break;
|
|
87
|
+
}
|
|
88
|
+
if (current && lines.length < LABEL_MAX_LINES) lines.push(current);
|
|
89
|
+
if (lines.length === LABEL_MAX_LINES) {
|
|
90
|
+
const consumed = lines.join(" ").replace(/…$/, "").length;
|
|
91
|
+
if (consumed < label.replace(/\s+/g, " ").trim().length) {
|
|
92
|
+
const last = lines[LABEL_MAX_LINES - 1];
|
|
93
|
+
lines[LABEL_MAX_LINES - 1] = last.endsWith("\u2026") ? last : `${last.slice(0, Math.max(1, max - 1))}\u2026`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
const drawableEdge = (edge, ids) => ids.has(edge.source) && ids.has(edge.target) && edge.source !== edge.target;
|
|
99
|
+
function classifyLayeredEdges(nodes, edges, rootId = nodes[0]?.id) {
|
|
100
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
101
|
+
const root = rootId !== void 0 && ids.has(rootId) ? rootId : nodes[0]?.id;
|
|
102
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const edge of edges) {
|
|
104
|
+
if (!drawableEdge(edge, ids)) continue;
|
|
105
|
+
const list = outgoing.get(edge.source);
|
|
106
|
+
if (list) list.push(edge.target);
|
|
107
|
+
else outgoing.set(edge.source, [edge.target]);
|
|
108
|
+
}
|
|
109
|
+
const bfs = /* @__PURE__ */ new Map();
|
|
110
|
+
if (root !== void 0) {
|
|
111
|
+
bfs.set(root, 0);
|
|
112
|
+
const queue = [root];
|
|
113
|
+
for (let head = 0; head < queue.length; head += 1) {
|
|
114
|
+
const current = queue[head];
|
|
115
|
+
for (const next of outgoing.get(current) ?? []) {
|
|
116
|
+
if (!bfs.has(next)) {
|
|
117
|
+
bfs.set(next, bfs.get(current) + 1);
|
|
118
|
+
queue.push(next);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const depthById = /* @__PURE__ */ new Map();
|
|
124
|
+
let deepest = 0;
|
|
125
|
+
for (const node of nodes) {
|
|
126
|
+
const supplied = node.depth !== void 0 && Number.isFinite(node.depth) ? Math.max(0, Math.floor(node.depth)) : void 0;
|
|
127
|
+
const depth = supplied ?? bfs.get(node.id);
|
|
128
|
+
if (depth === void 0) continue;
|
|
129
|
+
depthById.set(node.id, depth);
|
|
130
|
+
deepest = Math.max(deepest, depth);
|
|
131
|
+
}
|
|
132
|
+
for (const node of nodes) if (!depthById.has(node.id)) depthById.set(node.id, deepest + 1);
|
|
133
|
+
const forwardOut = /* @__PURE__ */ new Map();
|
|
134
|
+
const kindById = /* @__PURE__ */ new Map();
|
|
135
|
+
const backEdges = [];
|
|
136
|
+
for (const edge of edges) {
|
|
137
|
+
if (!drawableEdge(edge, ids)) continue;
|
|
138
|
+
const from = depthById.get(edge.source);
|
|
139
|
+
const to = depthById.get(edge.target);
|
|
140
|
+
if (to === from + 1) {
|
|
141
|
+
kindById.set(edge.id, "forward");
|
|
142
|
+
const list = forwardOut.get(edge.source);
|
|
143
|
+
if (list) list.push(edge.target);
|
|
144
|
+
else forwardOut.set(edge.source, [edge.target]);
|
|
145
|
+
} else if (to < from) {
|
|
146
|
+
backEdges.push(edge);
|
|
147
|
+
} else {
|
|
148
|
+
kindById.set(edge.id, "lateral");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const edge of backEdges) {
|
|
152
|
+
const limit = depthById.get(edge.source);
|
|
153
|
+
const seen = /* @__PURE__ */ new Set([edge.target]);
|
|
154
|
+
const stack = [edge.target];
|
|
155
|
+
let closes = false;
|
|
156
|
+
while (stack.length > 0 && !closes) {
|
|
157
|
+
const current = stack.pop();
|
|
158
|
+
for (const next of forwardOut.get(current) ?? []) {
|
|
159
|
+
if (next === edge.source) {
|
|
160
|
+
closes = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (!seen.has(next) && depthById.get(next) < limit) {
|
|
164
|
+
seen.add(next);
|
|
165
|
+
stack.push(next);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
kindById.set(edge.id, closes ? "cycle" : "lateral");
|
|
170
|
+
}
|
|
171
|
+
return { depthById, kindById };
|
|
172
|
+
}
|
|
173
|
+
function clusterIdFor(parentId, type, edgeType) {
|
|
174
|
+
return `cluster:${parentId}:${type ?? ""}:${edgeType ?? ""}`;
|
|
175
|
+
}
|
|
176
|
+
const mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
177
|
+
const round1 = (value) => Math.round(value * 10) / 10;
|
|
178
|
+
function layoutLayeredGraph(nodes, edges, options = {}) {
|
|
179
|
+
const { nodeWidth, nodeHeight, columnGap, rowGap } = LAYERED_GEOMETRY[options.density ?? "standard"];
|
|
180
|
+
const threshold = options.clusterThreshold ?? 6;
|
|
181
|
+
const expanded = options.expandedClusterIds ?? /* @__PURE__ */ new Set();
|
|
182
|
+
const rootId = options.rootId !== void 0 && nodes.some((node) => node.id === options.rootId) ? options.rootId : nodes[0]?.id;
|
|
183
|
+
if (nodes.length === 0) {
|
|
184
|
+
return {
|
|
185
|
+
rootId,
|
|
186
|
+
items: [],
|
|
187
|
+
edges: [],
|
|
188
|
+
clusters: [],
|
|
189
|
+
columns: [],
|
|
190
|
+
itemOf: {},
|
|
191
|
+
width: 0,
|
|
192
|
+
height: 0,
|
|
193
|
+
nodeWidth,
|
|
194
|
+
nodeHeight,
|
|
195
|
+
headerHeight: LAYERED_HEADER
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const { depthById, kindById } = classifyLayeredEdges(nodes, edges, rootId);
|
|
199
|
+
const drawable = edges.filter((edge) => kindById.has(edge.id));
|
|
200
|
+
const forwardParents = /* @__PURE__ */ new Map();
|
|
201
|
+
const forwardChildCount = /* @__PURE__ */ new Map();
|
|
202
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
203
|
+
const nodeDegree = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const edge of drawable) {
|
|
205
|
+
nodeDegree.set(edge.source, (nodeDegree.get(edge.source) ?? 0) + 1);
|
|
206
|
+
nodeDegree.set(edge.target, (nodeDegree.get(edge.target) ?? 0) + 1);
|
|
207
|
+
if (kindById.get(edge.id) === "forward") {
|
|
208
|
+
const list = forwardParents.get(edge.target);
|
|
209
|
+
if (list) list.push(edge);
|
|
210
|
+
else forwardParents.set(edge.target, [edge]);
|
|
211
|
+
forwardChildCount.set(edge.source, (forwardChildCount.get(edge.source) ?? 0) + 1);
|
|
212
|
+
} else {
|
|
213
|
+
inCycle.add(edge.source);
|
|
214
|
+
inCycle.add(edge.target);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const groups = /* @__PURE__ */ new Map();
|
|
218
|
+
if (threshold > 0) {
|
|
219
|
+
for (const node of nodes) {
|
|
220
|
+
if (node.id === rootId || inCycle.has(node.id) || (forwardChildCount.get(node.id) ?? 0) > 0) continue;
|
|
221
|
+
const parents = forwardParents.get(node.id) ?? [];
|
|
222
|
+
if (parents.length !== 1) continue;
|
|
223
|
+
const via = parents[0];
|
|
224
|
+
const id = clusterIdFor(via.source, node.type, via.type);
|
|
225
|
+
const group = groups.get(id);
|
|
226
|
+
if (group) {
|
|
227
|
+
group.members.push(node);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const created = { id, parentId: via.source, depth: depthById.get(node.id), members: [node] };
|
|
231
|
+
if (node.type !== void 0) created.type = node.type;
|
|
232
|
+
if (via.type !== void 0) created.edgeType = via.type;
|
|
233
|
+
const edgeLabel = via.label ?? via.type;
|
|
234
|
+
if (edgeLabel !== void 0) created.edgeLabel = edgeLabel;
|
|
235
|
+
groups.set(id, created);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const clusterById = /* @__PURE__ */ new Map();
|
|
239
|
+
const itemOf = {};
|
|
240
|
+
const expandedGroupOf = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const group of groups.values()) {
|
|
242
|
+
if (group.members.length < threshold) continue;
|
|
243
|
+
if (expanded.has(group.id)) {
|
|
244
|
+
for (const member of group.members) expandedGroupOf.set(member.id, group);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
clusterById.set(group.id, group);
|
|
248
|
+
for (const member of group.members) itemOf[member.id] = group.id;
|
|
249
|
+
}
|
|
250
|
+
for (const node of nodes) if (itemOf[node.id] === void 0) itemOf[node.id] = node.id;
|
|
251
|
+
const columnsByDepth = /* @__PURE__ */ new Map();
|
|
252
|
+
const itemById = /* @__PURE__ */ new Map();
|
|
253
|
+
for (const node of nodes) {
|
|
254
|
+
const id = itemOf[node.id];
|
|
255
|
+
if (itemById.has(id)) continue;
|
|
256
|
+
const cluster = clusterById.get(id);
|
|
257
|
+
const depth = depthById.get(node.id);
|
|
258
|
+
const item = {
|
|
259
|
+
id,
|
|
260
|
+
kind: cluster ? "cluster" : "node",
|
|
261
|
+
depth,
|
|
262
|
+
index: 0,
|
|
263
|
+
x: 0,
|
|
264
|
+
y: 0,
|
|
265
|
+
width: nodeWidth,
|
|
266
|
+
height: nodeHeight,
|
|
267
|
+
parents: [],
|
|
268
|
+
children: [],
|
|
269
|
+
inCycle: cluster ? false : inCycle.has(node.id),
|
|
270
|
+
degree: cluster ? cluster.members.length : nodeDegree.get(node.id) ?? 0
|
|
271
|
+
};
|
|
272
|
+
if (cluster) item.cluster = cluster;
|
|
273
|
+
else item.node = node;
|
|
274
|
+
itemById.set(id, item);
|
|
275
|
+
const column = columnsByDepth.get(depth);
|
|
276
|
+
if (column) column.push(item);
|
|
277
|
+
else columnsByDepth.set(depth, [item]);
|
|
278
|
+
}
|
|
279
|
+
for (const edge of drawable) {
|
|
280
|
+
if (kindById.get(edge.id) !== "forward") continue;
|
|
281
|
+
const from = itemById.get(itemOf[edge.source]);
|
|
282
|
+
const to = itemById.get(itemOf[edge.target]);
|
|
283
|
+
if (!from.children.includes(to.id)) from.children.push(to.id);
|
|
284
|
+
if (!to.parents.includes(from.id)) to.parents.push(from.id);
|
|
285
|
+
}
|
|
286
|
+
const depths = [...columnsByDepth.keys()].sort((a, b) => a - b);
|
|
287
|
+
const position = /* @__PURE__ */ new Map();
|
|
288
|
+
const reindex = (column) => {
|
|
289
|
+
column.forEach((item, index) => {
|
|
290
|
+
item.index = index;
|
|
291
|
+
position.set(item.id, index);
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
for (const depth of depths) reindex(columnsByDepth.get(depth));
|
|
295
|
+
const groupOfItem = (item) => item.node ? expandedGroupOf.get(item.node.id) : void 0;
|
|
296
|
+
const sweep = (depth, neighbours) => {
|
|
297
|
+
const keyed = columnsByDepth.get(depth).map((item) => {
|
|
298
|
+
const around = neighbours(item).map((id) => position.get(id)).filter((value) => value !== void 0);
|
|
299
|
+
return { item, key: around.length > 0 ? mean(around) : item.index, tie: item.index };
|
|
300
|
+
});
|
|
301
|
+
keyed.sort((a, b) => a.key - b.key || a.tie - b.tie);
|
|
302
|
+
const ordered = [];
|
|
303
|
+
const placed = /* @__PURE__ */ new Set();
|
|
304
|
+
for (const { item } of keyed) {
|
|
305
|
+
if (placed.has(item.id)) continue;
|
|
306
|
+
const group = groupOfItem(item);
|
|
307
|
+
for (const candidate of group ? keyed.map((entry) => entry.item).filter((other) => groupOfItem(other) === group) : [item]) {
|
|
308
|
+
if (placed.has(candidate.id)) continue;
|
|
309
|
+
ordered.push(candidate);
|
|
310
|
+
placed.add(candidate.id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
columnsByDepth.set(depth, ordered);
|
|
314
|
+
reindex(ordered);
|
|
315
|
+
};
|
|
316
|
+
const inner = depths.slice(1);
|
|
317
|
+
for (const depth of inner) sweep(depth, (item) => item.parents);
|
|
318
|
+
for (const depth of [...inner].reverse()) sweep(depth, (item) => item.children);
|
|
319
|
+
for (const depth of inner) sweep(depth, (item) => item.parents);
|
|
320
|
+
const top = LAYERED_PADDING + LAYERED_HEADER;
|
|
321
|
+
const columns = [];
|
|
322
|
+
let width = 0;
|
|
323
|
+
let height = 0;
|
|
324
|
+
for (const depth of depths) {
|
|
325
|
+
const column = columnsByDepth.get(depth);
|
|
326
|
+
const x = LAYERED_PADDING + depth * (nodeWidth + columnGap);
|
|
327
|
+
let cursor = top;
|
|
328
|
+
let count = 0;
|
|
329
|
+
const ledGroups = /* @__PURE__ */ new Set();
|
|
330
|
+
for (const item of column) {
|
|
331
|
+
const parentCentres = item.parents.map((id) => itemById.get(id)).filter((parent) => parent.depth < depth).map((parent) => parent.y + parent.height / 2);
|
|
332
|
+
const group = groupOfItem(item);
|
|
333
|
+
const leadsGroup = group !== void 0 && !ledGroups.has(group);
|
|
334
|
+
if (group) ledGroups.add(group);
|
|
335
|
+
const floor = cursor + (leadsGroup ? GROUP_HEADER : 0);
|
|
336
|
+
const target = parentCentres.length > 0 ? mean(parentCentres) - item.height / 2 : floor;
|
|
337
|
+
item.x = x;
|
|
338
|
+
item.y = round1(Math.max(target, floor));
|
|
339
|
+
if (leadsGroup) item.expandedGroup = group;
|
|
340
|
+
cursor = item.y + item.height + rowGap;
|
|
341
|
+
count += item.cluster ? item.cluster.members.length : 1;
|
|
342
|
+
width = Math.max(width, x + item.width);
|
|
343
|
+
height = Math.max(height, item.y + item.height);
|
|
344
|
+
}
|
|
345
|
+
columns.push({ depth, x, count });
|
|
346
|
+
}
|
|
347
|
+
const byPosition = (a, b) => (position.get(a) ?? 0) - (position.get(b) ?? 0);
|
|
348
|
+
for (const item of itemById.values()) {
|
|
349
|
+
item.children.sort(byPosition);
|
|
350
|
+
item.parents.sort(byPosition);
|
|
351
|
+
}
|
|
352
|
+
const routed = /* @__PURE__ */ new Map();
|
|
353
|
+
for (const edge of drawable) {
|
|
354
|
+
const kind = kindById.get(edge.id);
|
|
355
|
+
const from = itemById.get(itemOf[edge.source]);
|
|
356
|
+
const to = itemById.get(itemOf[edge.target]);
|
|
357
|
+
const key = from.kind === "cluster" || to.kind === "cluster" ? `${from.id}->${to.id}` : edge.id;
|
|
358
|
+
const existing = routed.get(key);
|
|
359
|
+
if (existing) {
|
|
360
|
+
existing.edgeIds.push(edge.id);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
let startX;
|
|
364
|
+
let startY;
|
|
365
|
+
let endX;
|
|
366
|
+
let endY;
|
|
367
|
+
let path;
|
|
368
|
+
let labelX;
|
|
369
|
+
let labelY;
|
|
370
|
+
let startAngle;
|
|
371
|
+
let endAngle;
|
|
372
|
+
if (kind === "forward") {
|
|
373
|
+
startX = from.x + from.width;
|
|
374
|
+
startY = from.y + from.height / 2;
|
|
375
|
+
endX = to.x;
|
|
376
|
+
endY = to.y + to.height / 2;
|
|
377
|
+
const bend = Math.max(columnGap / 2, 24);
|
|
378
|
+
path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bend)} ${round1(startY)} ${round1(endX - bend)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
|
|
379
|
+
labelX = (startX + endX) / 2;
|
|
380
|
+
labelY = (startY + endY) / 2;
|
|
381
|
+
startAngle = Math.PI;
|
|
382
|
+
endAngle = 0;
|
|
383
|
+
} else if (from.depth === to.depth) {
|
|
384
|
+
startX = from.x + from.width;
|
|
385
|
+
startY = from.y + from.height / 2;
|
|
386
|
+
endX = to.x + to.width;
|
|
387
|
+
endY = to.y + to.height / 2;
|
|
388
|
+
const bulge = Math.min(columnGap - 8, 20 + Math.abs(endY - startY) * 0.1);
|
|
389
|
+
path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bulge)} ${round1(startY)} ${round1(endX + bulge)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
|
|
390
|
+
labelX = startX + bulge * 0.75;
|
|
391
|
+
labelY = (startY + endY) / 2;
|
|
392
|
+
startAngle = Math.PI;
|
|
393
|
+
endAngle = Math.PI;
|
|
394
|
+
} else {
|
|
395
|
+
startX = from.x + from.width / 2;
|
|
396
|
+
startY = from.y + from.height;
|
|
397
|
+
endX = to.x + to.width / 2;
|
|
398
|
+
endY = to.y + to.height;
|
|
399
|
+
const dip = 28 + Math.abs(endX - startX) * 0.12;
|
|
400
|
+
const low = Math.max(startY, endY) + dip;
|
|
401
|
+
path = `M${round1(startX)} ${round1(startY)} C${round1(startX)} ${round1(low)} ${round1(endX)} ${round1(low)} ${round1(endX)} ${round1(endY)}`;
|
|
402
|
+
labelX = (startX + endX) / 2;
|
|
403
|
+
labelY = low - dip * 0.25;
|
|
404
|
+
startAngle = -Math.PI / 2;
|
|
405
|
+
endAngle = -Math.PI / 2;
|
|
406
|
+
height = Math.max(height, low);
|
|
407
|
+
}
|
|
408
|
+
const arrow = edge.arrow ?? "forward";
|
|
409
|
+
const atStart = arrow === "backward";
|
|
410
|
+
const entry = {
|
|
411
|
+
id: key,
|
|
412
|
+
source: from.id,
|
|
413
|
+
target: to.id,
|
|
414
|
+
kind,
|
|
415
|
+
edgeIds: [edge.id],
|
|
416
|
+
arrow,
|
|
417
|
+
emphasis: edge.emphasis === true,
|
|
418
|
+
path,
|
|
419
|
+
labelX: round1(labelX),
|
|
420
|
+
labelY: round1(labelY),
|
|
421
|
+
arrowX: round1(atStart ? startX : endX),
|
|
422
|
+
arrowY: round1(atStart ? startY : endY),
|
|
423
|
+
arrowAngle: atStart ? startAngle : endAngle,
|
|
424
|
+
labelLines: [],
|
|
425
|
+
autoLabel: to.kind !== "cluster" && (edge.emphasis === true || kind === "forward" && from.children.length <= AUTO_LABEL_MAX_FANOUT)
|
|
426
|
+
};
|
|
427
|
+
if (edge.type !== void 0) entry.type = edge.type;
|
|
428
|
+
const label = edge.label ?? edge.type;
|
|
429
|
+
if (label !== void 0) entry.label = label;
|
|
430
|
+
routed.set(key, entry);
|
|
431
|
+
}
|
|
432
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
433
|
+
for (const entry of routed.values()) {
|
|
434
|
+
if (entry.label === void 0) continue;
|
|
435
|
+
entry.labelLines = wrapEdgeLabel(entry.label, columnGap - 8);
|
|
436
|
+
if (!entry.autoLabel || entry.emphasis || entry.kind !== "forward") continue;
|
|
437
|
+
const key = `${entry.source}|${entry.label}`;
|
|
438
|
+
if (seenLabels.has(key)) entry.autoLabel = false;
|
|
439
|
+
else seenLabels.add(key);
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
rootId,
|
|
443
|
+
items: depths.flatMap((depth) => columnsByDepth.get(depth)),
|
|
444
|
+
edges: [...routed.values()],
|
|
445
|
+
clusters: [...clusterById.values()],
|
|
446
|
+
columns,
|
|
447
|
+
itemOf,
|
|
448
|
+
width: round1(width + LAYERED_PADDING),
|
|
449
|
+
height: round1(height + LAYERED_PADDING + 8),
|
|
450
|
+
nodeWidth,
|
|
451
|
+
nodeHeight,
|
|
452
|
+
headerHeight: LAYERED_HEADER
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function layeredNeighbor(layout, currentId, key) {
|
|
456
|
+
const root = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
|
|
457
|
+
const current = layout.items.find((item) => item.id === currentId);
|
|
458
|
+
if (!current) return root ?? layout.items[0]?.id;
|
|
459
|
+
const column = layout.items.filter((item) => item.depth === current.depth);
|
|
460
|
+
switch (key) {
|
|
461
|
+
case "ArrowUp":
|
|
462
|
+
return column[current.index - 1]?.id;
|
|
463
|
+
case "ArrowDown":
|
|
464
|
+
return column[current.index + 1]?.id;
|
|
465
|
+
case "ArrowRight":
|
|
466
|
+
return current.children[0];
|
|
467
|
+
case "ArrowLeft":
|
|
468
|
+
return current.parents[0];
|
|
469
|
+
case "Home":
|
|
470
|
+
return root;
|
|
471
|
+
case "End":
|
|
472
|
+
return column[column.length - 1]?.id;
|
|
473
|
+
default:
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
63
477
|
export {
|
|
64
478
|
boundRelationshipGraph,
|
|
479
|
+
classifyLayeredEdges,
|
|
480
|
+
clusterIdFor,
|
|
481
|
+
layeredNeighbor,
|
|
482
|
+
layoutLayeredGraph,
|
|
65
483
|
layoutRelationshipGraph,
|
|
66
484
|
relationshipNeighbors,
|
|
67
|
-
traverseRelationshipNode
|
|
485
|
+
traverseRelationshipNode,
|
|
486
|
+
wrapEdgeLabel
|
|
68
487
|
};
|
package/dist/tree-model.d.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
interface TreeLike<T> {
|
|
12
12
|
id: string;
|
|
13
13
|
children?: T[];
|
|
14
|
+
/** The display label — typeahead matches it only when it is a plain string (UIX-A11Y-2). */
|
|
15
|
+
label?: unknown;
|
|
16
|
+
/** Explicit typeahead text for nodes whose `label` is not a string (rich ReactNode labels). */
|
|
17
|
+
typeaheadLabel?: string;
|
|
14
18
|
}
|
|
15
19
|
interface FlatNode<T> {
|
|
16
20
|
node: T;
|
package/dist/tree-model.js
CHANGED
|
@@ -40,6 +40,14 @@ function treeNav(flat, currentId, key) {
|
|
|
40
40
|
case " ":
|
|
41
41
|
return cur.hasChildren ? { toggleId: cur.node.id, selectId: cur.node.id } : { selectId: cur.node.id };
|
|
42
42
|
default:
|
|
43
|
+
if (key.length === 1) {
|
|
44
|
+
const q = key.toLowerCase();
|
|
45
|
+
for (let step = 1; step <= flat.length; step++) {
|
|
46
|
+
const f = flat[(idx + step) % flat.length];
|
|
47
|
+
const label = f.node.typeaheadLabel ?? (typeof f.node.label === "string" ? f.node.label : void 0);
|
|
48
|
+
if (label != null && label.toLowerCase().startsWith(q)) return { focusId: f.node.id };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
43
51
|
return {};
|
|
44
52
|
}
|
|
45
53
|
}
|