@langchain/macaw-components 1.0.0-beta.1 → 1.0.0-beta.7
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/dist/_chunks/{LoadingIndicator-DgtU9aqD.js → LoadingIndicator-CDcArsiM.js} +13 -12
- package/dist/_chunks/LoadingIndicator-CDcArsiM.js.map +1 -0
- package/dist/_chunks/render-attachment-icon-BWBEMxXZ.js +270 -0
- package/dist/_chunks/render-attachment-icon-BWBEMxXZ.js.map +1 -0
- package/dist/catalog.json +527 -408
- package/dist/components/Attachment/Attachment.d.ts +21 -0
- package/dist/components/Attachment/Attachment.js +165 -0
- package/dist/components/Attachment/Attachment.js.map +1 -0
- package/dist/components/Attachment/AttachmentList.d.ts +5 -0
- package/dist/components/Attachment/AttachmentList.js +32 -0
- package/dist/components/Attachment/AttachmentList.js.map +1 -0
- package/dist/components/Attachment/index.d.ts +4 -0
- package/dist/components/Attachment/index.js +3 -0
- package/dist/components/Attachment/render-attachment-icon.d.ts +3 -0
- package/dist/components/Attachment/render-attachment-icon.js +2 -0
- package/dist/components/Avatar/Avatar.js +2 -2
- package/dist/components/Badge/Badge.d.ts +2 -0
- package/dist/components/Badge/Badge.js +12 -1
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Banner/Banner.js +1 -1
- package/dist/components/Button/Button.js +1 -1
- package/dist/components/Button/constants.js +2 -2
- package/dist/components/Button/constants.js.map +1 -1
- package/dist/components/ChartCard/ChartCard.d.ts +1 -4
- package/dist/components/ChartCard/ChartCard.js +2 -2
- package/dist/components/ChartCard/ChartCard.js.map +1 -1
- package/dist/components/ChartCard/ChartCardSkeleton.d.ts +1 -1
- package/dist/components/ChartCard/ChartCardSkeleton.js +37 -0
- package/dist/components/ChartCard/ChartCardSkeleton.js.map +1 -1
- package/dist/components/ChartLegend/ChartLegend.js +2 -2
- package/dist/components/ChartLegend/ChartLegendItemRenderer.js +2 -2
- package/dist/components/ChartLegend/ChartLegendItemRenderer.js.map +1 -1
- package/dist/components/Code/Code.utils.d.ts +3 -2
- package/dist/components/Code/Code.utils.js +7 -3
- package/dist/components/Code/Code.utils.js.map +1 -1
- package/dist/components/Code/CodeLanguageSelect.d.ts +7 -0
- package/dist/components/Code/CodeLanguageSelect.js +29 -0
- package/dist/components/Code/CodeLanguageSelect.js.map +1 -0
- package/dist/components/Code/CodeLite.js +3 -0
- package/dist/components/Code/CodeLite.js.map +1 -1
- package/dist/components/Code/CodeToolbar.d.ts +7 -0
- package/dist/components/Code/CodeToolbar.js +19 -0
- package/dist/components/Code/CodeToolbar.js.map +1 -0
- package/dist/components/Code/codeLanguages.d.ts +7 -0
- package/dist/components/Code/codeLanguages.js +59 -0
- package/dist/components/Code/codeLanguages.js.map +1 -0
- package/dist/components/Code/index.d.ts +15 -2
- package/dist/components/Code/index.js +70 -50
- package/dist/components/Code/index.js.map +1 -1
- package/dist/components/Code/types.d.ts +1 -1
- package/dist/components/Command/Command.d.ts +10 -13
- package/dist/components/Command/Command.js +7 -2
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/Command/CommandMenu.d.ts +7 -1
- package/dist/components/Command/CommandMenu.js +17 -9
- package/dist/components/Command/CommandMenu.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +6 -0
- package/dist/components/ContextMenu/ContextMenu.js +3 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/CopyButton/CopyButton.js +2 -2
- package/dist/components/Dialog/index.js +2 -2
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +11 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +5 -4
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/ErrorMessage/ErrorMessage.js +1 -1
- package/dist/components/GroupedTabs/GroupedTabs.d.ts +5 -2
- package/dist/components/GroupedTabs/GroupedTabs.js +33 -26
- package/dist/components/GroupedTabs/GroupedTabs.js.map +1 -1
- package/dist/components/IconButton/IconButton.js +2 -2
- package/dist/components/Input/Input.js +1 -1
- package/dist/components/LineChart/LineChart.d.ts +2 -2
- package/dist/components/LineChart/LineChart.js +3 -1
- package/dist/components/LineChart/LineChart.js.map +1 -1
- package/dist/components/LineChart/LineChart.types.d.ts +16 -0
- package/dist/components/LineChart/LineChartPlot.d.ts +3 -2
- package/dist/components/LineChart/LineChartPlot.js +8 -1
- package/dist/components/LineChart/LineChartPlot.js.map +1 -1
- package/dist/components/LineChart/LineChartVerticalMarkers.d.ts +8 -0
- package/dist/components/LineChart/LineChartVerticalMarkers.js +47 -0
- package/dist/components/LineChart/LineChartVerticalMarkers.js.map +1 -0
- package/dist/components/LineChart/index.d.ts +1 -1
- package/dist/components/Logo/index.js +11 -6
- package/dist/components/Logo/index.js.map +1 -1
- package/dist/components/MetricChart/MetricChart.d.ts +2 -0
- package/dist/components/MetricChart/MetricChart.js +15 -4
- package/dist/components/MetricChart/MetricChart.js.map +1 -1
- package/dist/components/MetricChart/index.d.ts +1 -1
- package/dist/components/Pane/Pane.js +2 -2
- package/dist/components/SankeyChart/AnimatedSankeyLink.d.ts +17 -0
- package/dist/components/SankeyChart/AnimatedSankeyLink.js +44 -0
- package/dist/components/SankeyChart/AnimatedSankeyLink.js.map +1 -0
- package/dist/components/SankeyChart/AnimatedSankeyNode.d.ts +16 -0
- package/dist/components/SankeyChart/AnimatedSankeyNode.js +42 -0
- package/dist/components/SankeyChart/AnimatedSankeyNode.js.map +1 -0
- package/dist/components/SankeyChart/SankeyChart.d.ts +42 -0
- package/dist/components/SankeyChart/SankeyChart.js +185 -0
- package/dist/components/SankeyChart/SankeyChart.js.map +1 -0
- package/dist/components/SankeyChart/SankeyChart.types.d.ts +90 -0
- package/dist/components/SankeyChart/SankeyChart.types.js +0 -0
- package/dist/components/SankeyChart/SankeyChart.utils.d.ts +21 -0
- package/dist/components/SankeyChart/SankeyChart.utils.js +299 -0
- package/dist/components/SankeyChart/SankeyChart.utils.js.map +1 -0
- package/dist/components/SankeyChart/SankeyNodeLabel.d.ts +10 -0
- package/dist/components/SankeyChart/SankeyNodeLabel.js +30 -0
- package/dist/components/SankeyChart/SankeyNodeLabel.js.map +1 -0
- package/dist/components/SankeyChart/SankeyNodeLabel.utils.d.ts +9 -0
- package/dist/components/SankeyChart/SankeyNodeLabel.utils.js +29 -0
- package/dist/components/SankeyChart/SankeyNodeLabel.utils.js.map +1 -0
- package/dist/components/SankeyChart/constants.d.ts +22 -0
- package/dist/components/SankeyChart/constants.js +27 -0
- package/dist/components/SankeyChart/constants.js.map +1 -0
- package/dist/components/SankeyChart/index.d.ts +5 -0
- package/dist/components/SankeyChart/index.js +4 -0
- package/dist/components/Select/Select.js +5 -5
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/SelectPrimitive.d.ts +6 -0
- package/dist/components/Select/SelectPrimitive.js +3 -2
- package/dist/components/Select/SelectPrimitive.js.map +1 -1
- package/dist/components/Slider/Slider.js +10 -3
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/SplitViewPane/SplitViewPane.d.ts +2 -1
- package/dist/components/SplitViewPane/SplitViewPane.js +113 -219
- package/dist/components/SplitViewPane/SplitViewPane.js.map +1 -1
- package/dist/components/SplitViewPane/SplitViewPaneHeader.js +2 -2
- package/dist/components/SplitViewPane/useShowChildren.d.ts +5 -0
- package/dist/components/SplitViewPane/useShowChildren.js +14 -0
- package/dist/components/SplitViewPane/useShowChildren.js.map +1 -0
- package/dist/components/SplitViewPane/useSplitViewPaneResize.d.ts +21 -0
- package/dist/components/SplitViewPane/useSplitViewPaneResize.js +145 -0
- package/dist/components/SplitViewPane/useSplitViewPaneResize.js.map +1 -0
- package/dist/components/Textarea/Textarea.d.ts +4 -3
- package/dist/components/Textarea/Textarea.js +7 -21
- package/dist/components/Textarea/Textarea.js.map +1 -1
- package/dist/components/ThinkingState/LoadingIndicator.d.ts +1 -0
- package/dist/components/ThinkingState/LoadingIndicator.js +1 -1
- package/dist/components/ThinkingState/ThinkingState.d.ts +1 -0
- package/dist/components/ThinkingState/ThinkingState.js +5 -4
- package/dist/components/ThinkingState/ThinkingState.js.map +1 -1
- package/dist/components/ThinkingState/index.js +1 -1
- package/dist/components/Toast/index.js +33 -28
- package/dist/components/Toast/index.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +2 -2
- package/dist/components/Typeahead/Typeahead.js +18 -8
- package/dist/components/Typeahead/Typeahead.js.map +1 -1
- package/dist/components/Typeahead/Typeahead.types.d.ts +2 -0
- package/dist/components/Typeahead/TypeaheadParts.d.ts +3 -1
- package/dist/components/Typeahead/TypeaheadParts.js +7 -6
- package/dist/components/Typeahead/TypeaheadParts.js.map +1 -1
- package/dist/components/Typeahead/TypeaheadView.js +19 -13
- package/dist/components/Typeahead/TypeaheadView.js.map +1 -1
- package/dist/components/Typeahead/useTypeaheadDomSync.d.ts +1 -1
- package/dist/components/Typeahead/useTypeaheadDomSync.js +14 -8
- package/dist/components/Typeahead/useTypeaheadDomSync.js.map +1 -1
- package/dist/components/Typeahead/useTypeaheadUncontrolledInput.d.ts +2 -1
- package/dist/components/Typeahead/useTypeaheadUncontrolledInput.js +10 -9
- package/dist/components/Typeahead/useTypeaheadUncontrolledInput.js.map +1 -1
- package/dist/components/UnsavedChangesDialog.js +2 -2
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.js +7 -5
- package/dist/components.css +22 -18
- package/dist/hooks/useDebouncedCommit.d.ts +14 -0
- package/dist/hooks/useDebouncedCommit.js +35 -0
- package/dist/hooks/useDebouncedCommit.js.map +1 -0
- package/dist/icons/LongTailPhosphorIcons.js +1 -1
- package/dist/icons/PaddedPhosphorIcons.d.ts +1 -0
- package/dist/icons/PaddedPhosphorIcons.js +4 -2
- package/dist/icons/PaddedPhosphorIcons.js.map +1 -1
- package/dist/icons/WeightedPhosphorIcons.d.ts +1 -0
- package/dist/icons/WeightedPhosphorIcons.js +3 -1
- package/dist/icons/WeightedPhosphorIcons.js.map +1 -1
- package/dist/icons/index.js +3 -3
- package/dist/index.js +7 -5
- package/dist/utilities.css +1 -1
- package/dist/utils/chartColors.d.ts +2 -1
- package/dist/utils/chartColors.js +2 -1
- package/dist/utils/chartColors.js.map +1 -1
- package/dist/utils/componentSizes.d.ts +0 -1
- package/dist/utils/componentSizes.js.map +1 -1
- package/dist/utils/lazy-codemirror-loader.d.ts +0 -2
- package/dist/utils/lazy-codemirror-loader.js +1 -3
- package/dist/utils/lazy-codemirror-loader.js.map +1 -1
- package/dist/utils/spacing.d.ts +1 -1
- package/dist/utils/spacing.js +1 -1
- package/dist/utils/spacing.js.map +1 -1
- package/docs/DESIGN.md +17 -0
- package/docs/STYLES.md +4 -1
- package/package.json +21 -8
- package/dist/_chunks/LoadingIndicator-DgtU9aqD.js.map +0 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { clamp } from "../../utils/clamp.js";
|
|
2
|
+
import { SANKEY_LINK_CURVATURE } from "./constants.js";
|
|
3
|
+
//#region packages/components/src/components/SankeyChart/SankeyChart.utils.ts
|
|
4
|
+
/** Ignores links naming unknown nodes or with non-positive / non-finite values. */
|
|
5
|
+
var isValidLink = (link, nodeIds) => nodeIds.has(link.sourceId) && nodeIds.has(link.targetId) && Number.isFinite(link.value) && link.value > 0;
|
|
6
|
+
/** Longest-path depths from sources and heights from sinks, rejecting cycles. */
|
|
7
|
+
var computeDepthsAndHeights = (nodes) => {
|
|
8
|
+
let frontier = nodes.filter((node) => node.targetLinks.length === 0);
|
|
9
|
+
let depth = 0;
|
|
10
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11
|
+
while (frontier.length > 0) {
|
|
12
|
+
const next = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const node of frontier) {
|
|
14
|
+
visited.add(node);
|
|
15
|
+
if (depth > node.depth || node.depth === 0) node.depth = depth;
|
|
16
|
+
for (const link of node.sourceLinks) if (link.target.depth <= depth) next.add(link.target);
|
|
17
|
+
}
|
|
18
|
+
frontier = [...next];
|
|
19
|
+
depth += 1;
|
|
20
|
+
if (frontier.length > 0 && depth > nodes.length) throw new Error("circular link");
|
|
21
|
+
}
|
|
22
|
+
if (visited.size < nodes.length) throw new Error("circular link");
|
|
23
|
+
let sinkFrontier = nodes.filter((node) => node.sourceLinks.length === 0);
|
|
24
|
+
let sinkDepth = 0;
|
|
25
|
+
while (sinkFrontier.length > 0) {
|
|
26
|
+
const next = /* @__PURE__ */ new Set();
|
|
27
|
+
for (const node of sinkFrontier) {
|
|
28
|
+
if (sinkDepth > node.height || node.height === 0) node.height = sinkDepth;
|
|
29
|
+
for (const link of node.targetLinks) if (link.source.height <= sinkDepth) next.add(link.source);
|
|
30
|
+
}
|
|
31
|
+
sinkFrontier = [...next];
|
|
32
|
+
sinkDepth += 1;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var getAlignmentLayer = (node, columnCount, alignment) => {
|
|
36
|
+
const lastColumn = columnCount - 1;
|
|
37
|
+
switch (alignment) {
|
|
38
|
+
case "left": return node.depth;
|
|
39
|
+
case "right": return lastColumn - node.height;
|
|
40
|
+
case "center": return node.targetLinks.length > 0 ? node.depth : Math.min(node.sourceLinks.length > 0 ? Math.min(...node.sourceLinks.map((link) => link.target.depth)) - 1 : 0, lastColumn);
|
|
41
|
+
default: return node.sourceLinks.length > 0 ? node.depth : lastColumn;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** Mean incoming link position, the quantity both relaxation passes minimize. */
|
|
45
|
+
var relaxLeftToRight = (columns, innerHeight, nodePadding, alpha, beta) => {
|
|
46
|
+
for (let index = 1; index < columns.length; index += 1) {
|
|
47
|
+
for (const node of columns[index] ?? []) {
|
|
48
|
+
let total = 0;
|
|
49
|
+
let weight = 0;
|
|
50
|
+
for (const link of node.targetLinks) {
|
|
51
|
+
const layerDistance = node.layer - link.source.layer;
|
|
52
|
+
total += link.source.y0 * link.weight * layerDistance;
|
|
53
|
+
weight += link.weight * layerDistance;
|
|
54
|
+
}
|
|
55
|
+
if (weight <= 0) continue;
|
|
56
|
+
const shift = (total / weight - node.y0) * alpha * beta;
|
|
57
|
+
node.y0 += shift;
|
|
58
|
+
node.y1 += shift;
|
|
59
|
+
}
|
|
60
|
+
resolveCollisions(columns[index], innerHeight, nodePadding);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var relaxRightToLeft = (columns, innerHeight, nodePadding, alpha, beta) => {
|
|
64
|
+
for (let index = columns.length - 2; index >= 0; index -= 1) {
|
|
65
|
+
for (const node of columns[index] ?? []) {
|
|
66
|
+
let total = 0;
|
|
67
|
+
let weight = 0;
|
|
68
|
+
for (const link of node.sourceLinks) {
|
|
69
|
+
const layerDistance = link.target.layer - node.layer;
|
|
70
|
+
total += link.target.y0 * link.weight * layerDistance;
|
|
71
|
+
weight += link.weight * layerDistance;
|
|
72
|
+
}
|
|
73
|
+
if (weight <= 0) continue;
|
|
74
|
+
const shift = (total / weight - node.y0) * alpha * beta;
|
|
75
|
+
node.y0 += shift;
|
|
76
|
+
node.y1 += shift;
|
|
77
|
+
}
|
|
78
|
+
resolveCollisions(columns[index], innerHeight, nodePadding);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** Shifts a column back inside the plot without changing node spacing. */
|
|
82
|
+
var clampColumnToPlot = (sorted, innerHeight) => {
|
|
83
|
+
const top = sorted[0]?.y0 ?? 0;
|
|
84
|
+
const bottom = sorted[sorted.length - 1]?.y1 ?? 0;
|
|
85
|
+
const overBottom = Math.max(0, bottom - innerHeight);
|
|
86
|
+
const drift = top < 0 ? -top : overBottom > 0 ? -Math.min(overBottom, top) : 0;
|
|
87
|
+
if (Math.abs(drift) > 1e-6) for (const node of sorted) {
|
|
88
|
+
node.y0 += drift;
|
|
89
|
+
node.y1 += drift;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/** Nudges the column's nodes back inside the plot and pushes overlaps apart. */
|
|
93
|
+
var resolveCollisions = (column, innerHeight, nodePadding) => {
|
|
94
|
+
if (column == null || column.length === 0) return;
|
|
95
|
+
column.sort((left, right) => left.y0 - right.y0);
|
|
96
|
+
clampColumnToPlot(column, innerHeight);
|
|
97
|
+
for (const [index, node] of column.entries()) {
|
|
98
|
+
const previous = column[index - 1];
|
|
99
|
+
if (previous == null) continue;
|
|
100
|
+
const overlap = previous.y1 + nodePadding - node.y0;
|
|
101
|
+
if (overlap > 1e-6) {
|
|
102
|
+
node.y0 += overlap;
|
|
103
|
+
node.y1 += overlap;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
clampColumnToPlot(column, innerHeight);
|
|
107
|
+
};
|
|
108
|
+
/** Packs links along node edges in the order of their opposite endpoints. */
|
|
109
|
+
var computeLinkBreadths = (nodes) => {
|
|
110
|
+
for (const node of nodes) {
|
|
111
|
+
node.sourceLinks.sort((left, right) => left.target.y0 - right.target.y0);
|
|
112
|
+
node.targetLinks.sort((left, right) => left.source.y0 - right.source.y0);
|
|
113
|
+
let y = node.y0;
|
|
114
|
+
for (const link of node.sourceLinks) {
|
|
115
|
+
link.y0 = y + link.width / 2;
|
|
116
|
+
y += link.width;
|
|
117
|
+
}
|
|
118
|
+
y = node.y0;
|
|
119
|
+
for (const link of node.targetLinks) {
|
|
120
|
+
link.y1 = y + link.width / 2;
|
|
121
|
+
y += link.width;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Assigns columns, scales flow, and relaxes positions toward connected nodes.
|
|
127
|
+
* Nodes use the larger of inflow and outflow; crowded plots expand to fit.
|
|
128
|
+
*/
|
|
129
|
+
var getSankeyChartLayout = ({ nodes, links, innerWidth: requestedInnerWidth, innerHeight: requestedInnerHeight, alignment = "justified", nodePadding = 12 }) => {
|
|
130
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
131
|
+
const workingNodes = nodes.map((node) => ({
|
|
132
|
+
...node,
|
|
133
|
+
depth: 0,
|
|
134
|
+
height: 0,
|
|
135
|
+
layer: 0,
|
|
136
|
+
weight: 0,
|
|
137
|
+
y0: 0,
|
|
138
|
+
y1: 0,
|
|
139
|
+
sourceLinks: [],
|
|
140
|
+
targetLinks: []
|
|
141
|
+
}));
|
|
142
|
+
const nodeById = new Map(workingNodes.map((node) => [node.id, node]));
|
|
143
|
+
const validLinks = links.filter((link) => isValidLink(link, nodeIds));
|
|
144
|
+
const explicitLinkIds = new Set(validLinks.flatMap((link) => link.id ?? []));
|
|
145
|
+
const seenLinkIds = /* @__PURE__ */ new Set();
|
|
146
|
+
const workingLinks = [];
|
|
147
|
+
for (const link of validLinks) {
|
|
148
|
+
const source = nodeById.get(link.sourceId);
|
|
149
|
+
const target = nodeById.get(link.targetId);
|
|
150
|
+
if (source == null || target == null) continue;
|
|
151
|
+
const baseId = `${link.sourceId}->${link.targetId}`;
|
|
152
|
+
let id = link.id ?? baseId;
|
|
153
|
+
if (link.id == null) {
|
|
154
|
+
let suffix = 1;
|
|
155
|
+
while (seenLinkIds.has(id) || explicitLinkIds.has(id)) id = `${baseId}#${suffix++}`;
|
|
156
|
+
}
|
|
157
|
+
if (seenLinkIds.has(id)) continue;
|
|
158
|
+
seenLinkIds.add(id);
|
|
159
|
+
const working = {
|
|
160
|
+
id,
|
|
161
|
+
sourceId: link.sourceId,
|
|
162
|
+
targetId: link.targetId,
|
|
163
|
+
value: link.value,
|
|
164
|
+
weight: 0,
|
|
165
|
+
color: link.color,
|
|
166
|
+
width: 0,
|
|
167
|
+
y0: 0,
|
|
168
|
+
y1: 0,
|
|
169
|
+
source,
|
|
170
|
+
target
|
|
171
|
+
};
|
|
172
|
+
source.sourceLinks.push(working);
|
|
173
|
+
target.targetLinks.push(working);
|
|
174
|
+
workingLinks.push(working);
|
|
175
|
+
}
|
|
176
|
+
if (workingNodes.length === 0) return {
|
|
177
|
+
nodes: [],
|
|
178
|
+
links: [],
|
|
179
|
+
innerWidth: Math.max(0, requestedInnerWidth),
|
|
180
|
+
innerHeight: Math.max(0, requestedInnerHeight)
|
|
181
|
+
};
|
|
182
|
+
const maximumValue = workingLinks.reduce((maximum, link) => Math.max(maximum, link.value), 0);
|
|
183
|
+
for (const link of workingLinks) link.weight = link.value / maximumValue;
|
|
184
|
+
computeDepthsAndHeights(workingNodes);
|
|
185
|
+
const columnCount = Math.max(...workingNodes.map((node) => node.depth)) + 1;
|
|
186
|
+
const innerWidth = Math.max(requestedInnerWidth, columnCount * 16 + (columnCount - 1) * 32);
|
|
187
|
+
for (const node of workingNodes) {
|
|
188
|
+
node.layer = clamp(getAlignmentLayer(node, columnCount, alignment), 0, columnCount - 1);
|
|
189
|
+
node.weight = Math.max(node.sourceLinks.reduce((sum, link) => sum + link.weight, 0), node.targetLinks.reduce((sum, link) => sum + link.weight, 0));
|
|
190
|
+
}
|
|
191
|
+
const columns = Array.from({ length: columnCount }, () => []);
|
|
192
|
+
for (const node of workingNodes) columns[node.layer]?.push(node);
|
|
193
|
+
const maximumColumnSize = Math.max(...columns.map((column) => column.length));
|
|
194
|
+
const innerHeight = Math.max(requestedInnerHeight, maximumColumnSize * 2);
|
|
195
|
+
nodePadding = clamp(nodePadding, 0, (innerHeight - maximumColumnSize * 2) / Math.max(1, maximumColumnSize - 1));
|
|
196
|
+
const columnScales = columns.map((column) => {
|
|
197
|
+
let availableHeight = innerHeight - (column.length - 1) * nodePadding;
|
|
198
|
+
let remainingValue = column.reduce((sum, node) => sum + node.weight, 0);
|
|
199
|
+
for (const node of [...column].sort((left, right) => left.weight - right.weight).slice(0, -1)) {
|
|
200
|
+
if (node.weight * availableHeight >= 2 * remainingValue) break;
|
|
201
|
+
availableHeight -= 2;
|
|
202
|
+
remainingValue -= node.weight;
|
|
203
|
+
}
|
|
204
|
+
return remainingValue > 0 ? Math.max(0, availableHeight / remainingValue) : Infinity;
|
|
205
|
+
});
|
|
206
|
+
const minimumScale = Math.min(...columnScales);
|
|
207
|
+
const scale = Number.isFinite(minimumScale) ? minimumScale : 0;
|
|
208
|
+
for (const link of workingLinks) {
|
|
209
|
+
const scaledValue = link.value * scale;
|
|
210
|
+
link.width = link.weight < Number.MIN_VALUE / Number.EPSILON && scale > 1 && Number.isFinite(scaledValue) ? scaledValue / maximumValue : link.weight * scale;
|
|
211
|
+
}
|
|
212
|
+
for (const column of columns) {
|
|
213
|
+
let y = 0;
|
|
214
|
+
for (const node of column) {
|
|
215
|
+
node.y0 = y;
|
|
216
|
+
node.y1 = y + Math.max(node.weight * scale, 2);
|
|
217
|
+
y = node.y1 + nodePadding;
|
|
218
|
+
}
|
|
219
|
+
const freeSpace = innerHeight - (y - nodePadding);
|
|
220
|
+
const lead = Math.max(0, freeSpace / 2);
|
|
221
|
+
for (const node of column) {
|
|
222
|
+
node.y0 += lead;
|
|
223
|
+
node.y1 += lead;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const iterations = 6;
|
|
227
|
+
for (let index = 0; index < iterations; index += 1) {
|
|
228
|
+
const alpha = Math.pow(.99, index);
|
|
229
|
+
const beta = Math.max(1 - alpha, (index + 1) / iterations);
|
|
230
|
+
relaxRightToLeft(columns, innerHeight, nodePadding, alpha, beta);
|
|
231
|
+
relaxLeftToRight(columns, innerHeight, nodePadding, alpha, beta);
|
|
232
|
+
}
|
|
233
|
+
for (const column of columns) {
|
|
234
|
+
column.sort((left, right) => left.y0 - right.y0);
|
|
235
|
+
const occupied = column.reduce((sum, node) => sum + (node.y1 - node.y0), 0);
|
|
236
|
+
let y = Math.max(0, (innerHeight - occupied - (column.length - 1) * nodePadding) / 2);
|
|
237
|
+
for (const node of column) {
|
|
238
|
+
const height = node.y1 - node.y0;
|
|
239
|
+
node.y0 = y;
|
|
240
|
+
node.y1 = y + height;
|
|
241
|
+
y = node.y1 + nodePadding;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
computeLinkBreadths(workingNodes);
|
|
245
|
+
const layoutNodes = /* @__PURE__ */ new Map();
|
|
246
|
+
const columnGap = Math.max(0, innerWidth - 16) / Math.max(columnCount - 1, 1);
|
|
247
|
+
for (const node of workingNodes) layoutNodes.set(node.id, {
|
|
248
|
+
id: node.id,
|
|
249
|
+
label: node.label,
|
|
250
|
+
ariaLabel: node.ariaLabel,
|
|
251
|
+
color: node.color ?? "var(--chart-single-fill)",
|
|
252
|
+
depth: node.layer,
|
|
253
|
+
value: Math.min(Number.MAX_VALUE, Math.max(node.sourceLinks.reduce((sum, link) => sum + link.value, 0), node.targetLinks.reduce((sum, link) => sum + link.value, 0))),
|
|
254
|
+
x: node.layer * columnGap,
|
|
255
|
+
y0: node.y0,
|
|
256
|
+
y1: node.y1
|
|
257
|
+
});
|
|
258
|
+
const layoutLinks = workingLinks.map((link) => ({
|
|
259
|
+
id: link.id,
|
|
260
|
+
sourceId: link.sourceId,
|
|
261
|
+
targetId: link.targetId,
|
|
262
|
+
value: link.value,
|
|
263
|
+
color: link.color ?? layoutNodes.get(link.sourceId)?.color ?? "var(--chart-single-fill)",
|
|
264
|
+
width: link.width,
|
|
265
|
+
y0: link.y0,
|
|
266
|
+
y1: link.y1,
|
|
267
|
+
source: layoutNodes.get(link.sourceId),
|
|
268
|
+
target: layoutNodes.get(link.targetId)
|
|
269
|
+
}));
|
|
270
|
+
return {
|
|
271
|
+
nodes: [...layoutNodes.values()],
|
|
272
|
+
links: layoutLinks,
|
|
273
|
+
innerWidth,
|
|
274
|
+
innerHeight
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
/** Cubic bezier flow ribbon between a link's node edges. */
|
|
278
|
+
var getSankeyLinkPath = (link) => {
|
|
279
|
+
const sourceX = link.sourceX;
|
|
280
|
+
const targetX = link.targetX;
|
|
281
|
+
const controlSource = sourceX + (targetX - sourceX) * SANKEY_LINK_CURVATURE;
|
|
282
|
+
const controlTarget = sourceX + (targetX - sourceX) * (1 - SANKEY_LINK_CURVATURE);
|
|
283
|
+
const halfWidth = link.width / 2;
|
|
284
|
+
return [
|
|
285
|
+
`M ${sourceX} ${link.y0 - halfWidth}`,
|
|
286
|
+
`C ${controlSource} ${link.y0 - halfWidth}`,
|
|
287
|
+
`${controlTarget} ${link.y1 - halfWidth}`,
|
|
288
|
+
`${targetX} ${link.y1 - halfWidth}`,
|
|
289
|
+
`L ${targetX} ${link.y1 + halfWidth}`,
|
|
290
|
+
`C ${controlTarget} ${link.y1 + halfWidth}`,
|
|
291
|
+
`${controlSource} ${link.y0 + halfWidth}`,
|
|
292
|
+
`${sourceX} ${link.y0 + halfWidth}`,
|
|
293
|
+
"Z"
|
|
294
|
+
].join(" ");
|
|
295
|
+
};
|
|
296
|
+
//#endregion
|
|
297
|
+
export { getSankeyChartLayout, getSankeyLinkPath };
|
|
298
|
+
|
|
299
|
+
//# sourceMappingURL=SankeyChart.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SankeyChart.utils.js","names":[],"sources":["../../../src/components/SankeyChart/SankeyChart.utils.ts"],"sourcesContent":["import { clamp } from '../../utils/clamp';\nimport {\n SANKEY_LINK_CURVATURE,\n SANKEY_MINIMUM_COLUMN_GAP,\n SANKEY_MINIMUM_NODE_HEIGHT,\n SANKEY_NODE_PADDING,\n SANKEY_NODE_WIDTH,\n} from './constants';\nimport type {\n SankeyChartAlignment,\n SankeyChartLayout,\n SankeyChartLayoutLink,\n SankeyChartLayoutNode,\n SankeyChartLink,\n SankeyChartNode,\n} from './SankeyChart.types';\n\ntype WorkingNode = SankeyChartNode & {\n depth: number;\n height: number;\n layer: number;\n weight: number;\n y0: number;\n y1: number;\n sourceLinks: WorkingLink[];\n targetLinks: WorkingLink[];\n};\n\ntype WorkingLink = {\n id: string;\n sourceId: string;\n targetId: string;\n value: number;\n weight: number;\n color?: string;\n width: number;\n y0: number;\n y1: number;\n source: WorkingNode;\n target: WorkingNode;\n};\n\n/** Ignores links naming unknown nodes or with non-positive / non-finite values. */\nconst isValidLink = (link: SankeyChartLink, nodeIds: ReadonlySet<string>) =>\n nodeIds.has(link.sourceId) &&\n nodeIds.has(link.targetId) &&\n Number.isFinite(link.value) &&\n link.value > 0;\n\n/** Longest-path depths from sources and heights from sinks, rejecting cycles. */\nconst computeDepthsAndHeights = (nodes: WorkingNode[]) => {\n let frontier = nodes.filter((node) => node.targetLinks.length === 0);\n let depth = 0;\n const visited = new Set<WorkingNode>();\n while (frontier.length > 0) {\n const next = new Set<WorkingNode>();\n for (const node of frontier) {\n visited.add(node);\n // Reconverging paths can revisit a node at a greater depth.\n if (depth > node.depth || node.depth === 0) {\n node.depth = depth;\n }\n for (const link of node.sourceLinks) {\n if (link.target.depth <= depth) next.add(link.target);\n }\n }\n frontier = [...next];\n depth += 1;\n if (frontier.length > 0 && depth > nodes.length) {\n throw new Error('circular link');\n }\n }\n if (visited.size < nodes.length) throw new Error('circular link');\n let sinkFrontier = nodes.filter((node) => node.sourceLinks.length === 0);\n let sinkDepth = 0;\n while (sinkFrontier.length > 0) {\n const next = new Set<WorkingNode>();\n for (const node of sinkFrontier) {\n if (sinkDepth > node.height || node.height === 0) {\n node.height = sinkDepth;\n }\n for (const link of node.targetLinks) {\n if (link.source.height <= sinkDepth) next.add(link.source);\n }\n }\n sinkFrontier = [...next];\n sinkDepth += 1;\n }\n};\n\nconst getAlignmentLayer = (\n node: WorkingNode,\n columnCount: number,\n alignment: SankeyChartAlignment\n): number => {\n const lastColumn = columnCount - 1;\n switch (alignment) {\n case 'left':\n return node.depth;\n case 'right':\n return lastColumn - node.height;\n case 'center':\n return node.targetLinks.length > 0\n ? node.depth\n : Math.min(\n node.sourceLinks.length > 0\n ? Math.min(...node.sourceLinks.map((link) => link.target.depth)) -\n 1\n : 0,\n lastColumn\n );\n default:\n return node.sourceLinks.length > 0 ? node.depth : lastColumn;\n }\n};\n\n/** Mean incoming link position, the quantity both relaxation passes minimize. */\nconst relaxLeftToRight = (\n columns: readonly WorkingNode[][],\n innerHeight: number,\n nodePadding: number,\n alpha: number,\n beta: number\n) => {\n for (let index = 1; index < columns.length; index += 1) {\n for (const node of columns[index] ?? []) {\n let total = 0;\n let weight = 0;\n for (const link of node.targetLinks) {\n const layerDistance = node.layer - link.source.layer;\n total += link.source.y0 * link.weight * layerDistance;\n weight += link.weight * layerDistance;\n }\n if (weight <= 0) continue;\n const ideal = total / weight;\n const shift = (ideal - node.y0) * alpha * beta;\n node.y0 += shift;\n node.y1 += shift;\n }\n resolveCollisions(columns[index], innerHeight, nodePadding);\n }\n};\n\nconst relaxRightToLeft = (\n columns: readonly WorkingNode[][],\n innerHeight: number,\n nodePadding: number,\n alpha: number,\n beta: number\n) => {\n for (let index = columns.length - 2; index >= 0; index -= 1) {\n for (const node of columns[index] ?? []) {\n let total = 0;\n let weight = 0;\n for (const link of node.sourceLinks) {\n const layerDistance = link.target.layer - node.layer;\n total += link.target.y0 * link.weight * layerDistance;\n weight += link.weight * layerDistance;\n }\n if (weight <= 0) continue;\n const ideal = total / weight;\n const shift = (ideal - node.y0) * alpha * beta;\n node.y0 += shift;\n node.y1 += shift;\n }\n resolveCollisions(columns[index], innerHeight, nodePadding);\n }\n};\n\n/** Shifts a column back inside the plot without changing node spacing. */\nconst clampColumnToPlot = (sorted: WorkingNode[], innerHeight: number) => {\n const top = sorted[0]?.y0 ?? 0;\n const bottom = sorted[sorted.length - 1]?.y1 ?? 0;\n const overBottom = Math.max(0, bottom - innerHeight);\n const drift =\n top < 0 ? -top : overBottom > 0 ? -Math.min(overBottom, top) : 0;\n if (Math.abs(drift) > 1e-6) {\n for (const node of sorted) {\n node.y0 += drift;\n node.y1 += drift;\n }\n }\n};\n\n/** Nudges the column's nodes back inside the plot and pushes overlaps apart. */\nconst resolveCollisions = (\n column: WorkingNode[] | undefined,\n innerHeight: number,\n nodePadding: number\n) => {\n if (column == null || column.length === 0) return;\n column.sort((left, right) => left.y0 - right.y0);\n clampColumnToPlot(column, innerHeight);\n for (const [index, node] of column.entries()) {\n const previous = column[index - 1];\n if (previous == null) continue;\n const overlap = previous.y1 + nodePadding - node.y0;\n if (overlap > 1e-6) {\n node.y0 += overlap;\n node.y1 += overlap;\n }\n }\n // Separating overlaps can push the last node below the plot.\n clampColumnToPlot(column, innerHeight);\n};\n\n/** Packs links along node edges in the order of their opposite endpoints. */\nconst computeLinkBreadths = (nodes: readonly WorkingNode[]) => {\n for (const node of nodes) {\n node.sourceLinks.sort((left, right) => left.target.y0 - right.target.y0);\n node.targetLinks.sort((left, right) => left.source.y0 - right.source.y0);\n let y = node.y0;\n for (const link of node.sourceLinks) {\n link.y0 = y + link.width / 2;\n y += link.width;\n }\n y = node.y0;\n for (const link of node.targetLinks) {\n link.y1 = y + link.width / 2;\n y += link.width;\n }\n }\n};\n\n/**\n * Assigns columns, scales flow, and relaxes positions toward connected nodes.\n * Nodes use the larger of inflow and outflow; crowded plots expand to fit.\n */\nexport const getSankeyChartLayout = ({\n nodes,\n links,\n innerWidth: requestedInnerWidth,\n innerHeight: requestedInnerHeight,\n alignment = 'justified',\n nodePadding = SANKEY_NODE_PADDING,\n}: {\n nodes: readonly SankeyChartNode[];\n links: readonly SankeyChartLink[];\n innerWidth: number;\n innerHeight: number;\n alignment?: SankeyChartAlignment;\n nodePadding?: number;\n}): SankeyChartLayout => {\n const nodeIds = new Set(nodes.map((node) => node.id));\n const workingNodes: WorkingNode[] = nodes.map((node) => ({\n ...node,\n depth: 0,\n height: 0,\n layer: 0,\n weight: 0,\n y0: 0,\n y1: 0,\n sourceLinks: [],\n targetLinks: [],\n }));\n const nodeById = new Map(\n workingNodes.map((node) => [node.id, node] as const)\n );\n const validLinks = links.filter((link) => isValidLink(link, nodeIds));\n const explicitLinkIds = new Set(validLinks.flatMap((link) => link.id ?? []));\n const seenLinkIds = new Set<string>();\n const workingLinks: WorkingLink[] = [];\n for (const link of validLinks) {\n const source = nodeById.get(link.sourceId);\n const target = nodeById.get(link.targetId);\n if (source == null || target == null) continue;\n const baseId = `${link.sourceId}->${link.targetId}`;\n let id = link.id ?? baseId;\n if (link.id == null) {\n let suffix = 1;\n while (seenLinkIds.has(id) || explicitLinkIds.has(id)) {\n id = `${baseId}#${suffix++}`;\n }\n }\n if (seenLinkIds.has(id)) continue;\n seenLinkIds.add(id);\n const working: WorkingLink = {\n id,\n sourceId: link.sourceId,\n targetId: link.targetId,\n value: link.value,\n weight: 0,\n color: link.color,\n width: 0,\n y0: 0,\n y1: 0,\n source,\n target,\n };\n source.sourceLinks.push(working);\n target.targetLinks.push(working);\n workingLinks.push(working);\n }\n if (workingNodes.length === 0) {\n return {\n nodes: [],\n links: [],\n innerWidth: Math.max(0, requestedInnerWidth),\n innerHeight: Math.max(0, requestedInnerHeight),\n };\n }\n\n // Relative weights keep totals, pixel scaling, and relaxation finite.\n const maximumValue = workingLinks.reduce(\n (maximum, link) => Math.max(maximum, link.value),\n 0\n );\n for (const link of workingLinks) link.weight = link.value / maximumValue;\n\n computeDepthsAndHeights(workingNodes);\n const columnCount = Math.max(...workingNodes.map((node) => node.depth)) + 1;\n const innerWidth = Math.max(\n requestedInnerWidth,\n columnCount * SANKEY_NODE_WIDTH +\n (columnCount - 1) * SANKEY_MINIMUM_COLUMN_GAP\n );\n for (const node of workingNodes) {\n node.layer = clamp(\n getAlignmentLayer(node, columnCount, alignment),\n 0,\n columnCount - 1\n );\n node.weight = Math.max(\n node.sourceLinks.reduce((sum, link) => sum + link.weight, 0),\n node.targetLinks.reduce((sum, link) => sum + link.weight, 0)\n );\n }\n\n const columns: WorkingNode[][] = Array.from(\n { length: columnCount },\n () => []\n );\n for (const node of workingNodes) columns[node.layer]?.push(node);\n\n const maximumColumnSize = Math.max(...columns.map((column) => column.length));\n const innerHeight = Math.max(\n requestedInnerHeight,\n maximumColumnSize * SANKEY_MINIMUM_NODE_HEIGHT\n );\n nodePadding = clamp(\n nodePadding,\n 0,\n (innerHeight - maximumColumnSize * SANKEY_MINIMUM_NODE_HEIGHT) /\n Math.max(1, maximumColumnSize - 1)\n );\n // Reserve minimum heights for small nodes before scaling the remaining flow.\n const columnScales = columns.map((column) => {\n let availableHeight = innerHeight - (column.length - 1) * nodePadding;\n let remainingValue = column.reduce((sum, node) => sum + node.weight, 0);\n // Keep the largest node in the scale calculation even when rounding makes\n // the available height slightly smaller than its minimum height.\n for (const node of [...column]\n .sort((left, right) => left.weight - right.weight)\n .slice(0, -1)) {\n if (\n node.weight * availableHeight >=\n SANKEY_MINIMUM_NODE_HEIGHT * remainingValue\n )\n break;\n availableHeight -= SANKEY_MINIMUM_NODE_HEIGHT;\n remainingValue -= node.weight;\n }\n return remainingValue > 0\n ? Math.max(0, availableHeight / remainingValue)\n : Infinity;\n });\n const minimumScale = Math.min(...columnScales);\n const scale = Number.isFinite(minimumScale) ? minimumScale : 0;\n for (const link of workingLinks) {\n // Only magnify before normalizing when the relative weight loses precision.\n // Ordinary weights must share the node scale, even for subnormal inputs.\n const scaledValue = link.value * scale;\n link.width =\n link.weight < Number.MIN_VALUE / Number.EPSILON &&\n scale > 1 &&\n Number.isFinite(scaledValue)\n ? scaledValue / maximumValue\n : link.weight * scale;\n }\n\n for (const column of columns) {\n let y = 0;\n for (const node of column) {\n node.y0 = y;\n node.y1 = y + Math.max(node.weight * scale, SANKEY_MINIMUM_NODE_HEIGHT);\n y = node.y1 + nodePadding;\n }\n // Center the column's occupied band in the plot, like d3's remainder split.\n const freeSpace = innerHeight - (y - nodePadding);\n const lead = Math.max(0, freeSpace / 2);\n for (const node of column) {\n node.y0 += lead;\n node.y1 += lead;\n }\n }\n\n const iterations = 6;\n for (let index = 0; index < iterations; index += 1) {\n const alpha = Math.pow(0.99, index);\n const beta = Math.max(1 - alpha, (index + 1) / iterations);\n relaxRightToLeft(columns, innerHeight, nodePadding, alpha, beta);\n relaxLeftToRight(columns, innerHeight, nodePadding, alpha, beta);\n }\n // Pack the settled node order to eliminate gaps left by collision resolution.\n for (const column of columns) {\n column.sort((left, right) => left.y0 - right.y0);\n const occupied = column.reduce((sum, node) => sum + (node.y1 - node.y0), 0);\n const lead = Math.max(\n 0,\n (innerHeight - occupied - (column.length - 1) * nodePadding) / 2\n );\n let y = lead;\n for (const node of column) {\n const height = node.y1 - node.y0;\n node.y0 = y;\n node.y1 = y + height;\n y = node.y1 + nodePadding;\n }\n }\n computeLinkBreadths(workingNodes);\n\n const layoutNodes = new Map<string, SankeyChartLayoutNode>();\n const columnGap =\n Math.max(0, innerWidth - SANKEY_NODE_WIDTH) / Math.max(columnCount - 1, 1);\n for (const node of workingNodes) {\n layoutNodes.set(node.id, {\n id: node.id,\n label: node.label,\n ariaLabel: node.ariaLabel,\n color: node.color ?? 'var(--chart-single-fill)',\n depth: node.layer,\n value: Math.min(\n Number.MAX_VALUE,\n Math.max(\n node.sourceLinks.reduce((sum, link) => sum + link.value, 0),\n node.targetLinks.reduce((sum, link) => sum + link.value, 0)\n )\n ),\n x: node.layer * columnGap,\n y0: node.y0,\n y1: node.y1,\n });\n }\n const layoutLinks: SankeyChartLayoutLink[] = workingLinks.map((link) => ({\n id: link.id,\n sourceId: link.sourceId,\n targetId: link.targetId,\n value: link.value,\n color:\n link.color ??\n layoutNodes.get(link.sourceId)?.color ??\n 'var(--chart-single-fill)',\n width: link.width,\n y0: link.y0,\n y1: link.y1,\n source: layoutNodes.get(link.sourceId)!,\n target: layoutNodes.get(link.targetId)!,\n }));\n\n return {\n nodes: [...layoutNodes.values()],\n links: layoutLinks,\n innerWidth,\n innerHeight,\n };\n};\n\n/** Cubic bezier flow ribbon between a link's node edges. */\nexport const getSankeyLinkPath = (link: {\n sourceX: number;\n targetX: number;\n y0: number;\n y1: number;\n width: number;\n}): string => {\n const sourceX = link.sourceX;\n const targetX = link.targetX;\n const controlSource = sourceX + (targetX - sourceX) * SANKEY_LINK_CURVATURE;\n const controlTarget =\n sourceX + (targetX - sourceX) * (1 - SANKEY_LINK_CURVATURE);\n const halfWidth = link.width / 2;\n return [\n `M ${sourceX} ${link.y0 - halfWidth}`,\n `C ${controlSource} ${link.y0 - halfWidth}`,\n `${controlTarget} ${link.y1 - halfWidth}`,\n `${targetX} ${link.y1 - halfWidth}`,\n `L ${targetX} ${link.y1 + halfWidth}`,\n `C ${controlTarget} ${link.y1 + halfWidth}`,\n `${controlSource} ${link.y0 + halfWidth}`,\n `${sourceX} ${link.y0 + halfWidth}`,\n 'Z',\n ].join(' ');\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SankeyChartLayoutNode, SankeyChartLabelSide } from './SankeyChart.types.js';
|
|
2
|
+
/** Label rendered just past the node rectangle, on the given side. */
|
|
3
|
+
export declare const SankeyNodeLabel: ({ node, isDimmed, isActive, side, x, width, }: {
|
|
4
|
+
node: SankeyChartLayoutNode;
|
|
5
|
+
isDimmed: boolean;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
side: SankeyChartLabelSide;
|
|
8
|
+
x: number;
|
|
9
|
+
width: number;
|
|
10
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cn } from "../../utils/cn.js";
|
|
2
|
+
import { Text } from "../Text/Text.js";
|
|
3
|
+
import { CHART_DIMMED_OPACITY } from "../../utils/chartConstants.js";
|
|
4
|
+
import "./constants.js";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region packages/components/src/components/SankeyChart/SankeyNodeLabel.tsx
|
|
7
|
+
/** Label rendered just past the node rectangle, on the given side. */
|
|
8
|
+
var SankeyNodeLabel = ({ node, isDimmed, isActive, side, x, width }) => /* @__PURE__ */ jsx("foreignObject", {
|
|
9
|
+
"aria-hidden": "true",
|
|
10
|
+
x,
|
|
11
|
+
y: (node.y0 + node.y1 - 16) / 2,
|
|
12
|
+
width,
|
|
13
|
+
height: 16,
|
|
14
|
+
opacity: isDimmed ? CHART_DIMMED_OPACITY : 1,
|
|
15
|
+
className: "pointer-events-none overflow-hidden transition-opacity duration-fast motion-reduce:transition-none",
|
|
16
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
17
|
+
className: cn("flex size-full items-center", side === "left" && "justify-end"),
|
|
18
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
19
|
+
variant: "xs",
|
|
20
|
+
color: "primary",
|
|
21
|
+
weight: isActive ? "medium" : "normal",
|
|
22
|
+
className: "block truncate",
|
|
23
|
+
children: node.label == null || typeof node.label === "boolean" ? node.ariaLabel ?? node.id : node.label
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
//#endregion
|
|
28
|
+
export { SankeyNodeLabel };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=SankeyNodeLabel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SankeyNodeLabel.js","names":[],"sources":["../../../src/components/SankeyChart/SankeyNodeLabel.tsx"],"sourcesContent":["import { CHART_DIMMED_OPACITY } from '../../utils/chartConstants';\nimport { cn } from '../../utils/cn';\nimport { Text } from '../Text';\nimport { SANKEY_NODE_LABEL_HEIGHT } from './constants';\nimport type {\n SankeyChartLayoutNode,\n SankeyChartLabelSide,\n} from './SankeyChart.types';\n\n/** Label rendered just past the node rectangle, on the given side. */\nexport const SankeyNodeLabel = ({\n node,\n isDimmed,\n isActive,\n side,\n x,\n width,\n}: {\n node: SankeyChartLayoutNode;\n isDimmed: boolean;\n isActive: boolean;\n side: SankeyChartLabelSide;\n x: number;\n width: number;\n}) => (\n <foreignObject\n aria-hidden=\"true\"\n x={x}\n y={(node.y0 + node.y1 - SANKEY_NODE_LABEL_HEIGHT) / 2}\n width={width}\n height={SANKEY_NODE_LABEL_HEIGHT}\n opacity={isDimmed ? CHART_DIMMED_OPACITY : 1}\n className=\"pointer-events-none overflow-hidden transition-opacity duration-fast motion-reduce:transition-none\"\n >\n <div\n className={cn(\n 'flex size-full items-center',\n side === 'left' && 'justify-end'\n )}\n >\n <Text\n variant=\"xs\"\n color=\"primary\"\n weight={isActive ? 'medium' : 'normal'}\n className=\"block truncate\"\n >\n {node.label == null || typeof node.label === 'boolean'\n ? (node.ariaLabel ?? node.id)\n : node.label}\n </Text>\n </div>\n </foreignObject>\n);\n"],"mappings":";;;;;;;AAUA,IAAa,mBAAmB,EAC9B,MACA,UACA,UACA,MACA,GACA,YASA,oBAAC,iBAAD;CACE,eAAY;CACT;CACH,IAAI,KAAK,KAAK,KAAK,KAAA,MAAiC;CAC7C;CACP,QAAA;CACA,SAAS,WAAW,uBAAuB;CAC3C,WAAU;CAEV,UAAA,oBAAC,OAAD;EACE,WAAW,GACT,+BACA,SAAS,UAAU,aACrB;EAEA,UAAA,oBAAC,MAAD;GACE,SAAQ;GACR,OAAM;GACN,QAAQ,WAAW,WAAW;GAC9B,WAAU;GAET,UAAA,KAAK,SAAS,QAAQ,OAAO,KAAK,UAAU,YACxC,KAAK,aAAa,KAAK,KACxB,KAAK;EACL,CAAA;CACH,CAAA;AACQ,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SankeyChartAlignment, SankeyChartLayoutNode, SankeyChartLabelSide } from './SankeyChart.types.js';
|
|
2
|
+
/** Bounds labels to outer gutters or the space between occupied columns. */
|
|
3
|
+
export declare const getSankeyNodeLabels: (nodes: readonly SankeyChartLayoutNode[], alignment: SankeyChartAlignment) => Array<{
|
|
4
|
+
node: SankeyChartLayoutNode;
|
|
5
|
+
side: SankeyChartLabelSide;
|
|
6
|
+
x: number;
|
|
7
|
+
width: number;
|
|
8
|
+
isVisible: boolean;
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "./constants.js";
|
|
2
|
+
//#region packages/components/src/components/SankeyChart/SankeyNodeLabel.utils.ts
|
|
3
|
+
/** Bounds labels to outer gutters or the space between occupied columns. */
|
|
4
|
+
var getSankeyNodeLabels = (nodes, alignment) => {
|
|
5
|
+
const columns = [...new Set(nodes.map((node) => node.x))].sort((a, b) => a - b);
|
|
6
|
+
const lastLabelCenter = /* @__PURE__ */ new Map();
|
|
7
|
+
return [...nodes].sort((a, b) => a.y0 + a.y1 - b.y0 - b.y1).map((node) => {
|
|
8
|
+
const center = (node.y0 + node.y1) / 2;
|
|
9
|
+
const isVisible = center - (lastLabelCenter.get(node.x) ?? -Infinity) >= 16;
|
|
10
|
+
if (isVisible) lastLabelCenter.set(node.x, center);
|
|
11
|
+
const column = columns.indexOf(node.x);
|
|
12
|
+
const side = alignment === "left" || column === columns.length - 1 ? "left" : "right";
|
|
13
|
+
const neighbor = columns[column + (side === "left" ? -1 : 1)];
|
|
14
|
+
let available = neighbor == null ? 128 : Math.abs(neighbor - node.x) - 16 - 12;
|
|
15
|
+
if (alignment !== "left" && neighbor != null && column >= columns.length - 2) available = (available - 6) / 2;
|
|
16
|
+
const width = Math.max(0, Math.min(128, available));
|
|
17
|
+
return {
|
|
18
|
+
node,
|
|
19
|
+
side,
|
|
20
|
+
width,
|
|
21
|
+
isVisible,
|
|
22
|
+
x: side === "left" ? node.x - 6 - width : node.x + 16 + 6
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { getSankeyNodeLabels };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=SankeyNodeLabel.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SankeyNodeLabel.utils.js","names":[],"sources":["../../../src/components/SankeyChart/SankeyNodeLabel.utils.ts"],"sourcesContent":["import {\n SANKEY_NODE_WIDTH,\n SANKEY_NODE_LABEL_GAP,\n SANKEY_NODE_LABEL_HEIGHT,\n SANKEY_NODE_LABEL_WIDTH,\n} from './constants';\nimport type {\n SankeyChartAlignment,\n SankeyChartLayoutNode,\n SankeyChartLabelSide,\n} from './SankeyChart.types';\n\n/** Bounds labels to outer gutters or the space between occupied columns. */\nexport const getSankeyNodeLabels = (\n nodes: readonly SankeyChartLayoutNode[],\n alignment: SankeyChartAlignment\n): Array<{\n node: SankeyChartLayoutNode;\n side: SankeyChartLabelSide;\n x: number;\n width: number;\n isVisible: boolean;\n}> => {\n const columns = [...new Set(nodes.map((node) => node.x))].sort(\n (a, b) => a - b\n );\n const lastLabelCenter = new Map<number, number>();\n return [...nodes]\n .sort((a, b) => a.y0 + a.y1 - b.y0 - b.y1)\n .map((node) => {\n const center = (node.y0 + node.y1) / 2;\n const isVisible =\n center - (lastLabelCenter.get(node.x) ?? -Infinity) >=\n SANKEY_NODE_LABEL_HEIGHT;\n if (isVisible) lastLabelCenter.set(node.x, center);\n const column = columns.indexOf(node.x);\n const side =\n alignment === 'left' || column === columns.length - 1\n ? 'left'\n : 'right';\n const neighbor = columns[column + (side === 'left' ? -1 : 1)];\n let available =\n neighbor == null\n ? SANKEY_NODE_LABEL_WIDTH\n : Math.abs(neighbor - node.x) -\n SANKEY_NODE_WIDTH -\n 2 * SANKEY_NODE_LABEL_GAP;\n // The final pair face each other and share the same gap.\n if (\n alignment !== 'left' &&\n neighbor != null &&\n column >= columns.length - 2\n ) {\n available = (available - SANKEY_NODE_LABEL_GAP) / 2;\n }\n const width = Math.max(0, Math.min(SANKEY_NODE_LABEL_WIDTH, available));\n return {\n node,\n side,\n width,\n isVisible,\n x:\n side === 'left'\n ? node.x - SANKEY_NODE_LABEL_GAP - width\n : node.x + SANKEY_NODE_WIDTH + SANKEY_NODE_LABEL_GAP,\n };\n });\n};\n"],"mappings":";;;AAaA,IAAa,uBACX,OACA,cAOI;CACJ,MAAM,UAAU,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MACvD,GAAG,MAAM,IAAI,CAChB;CACA,MAAM,kCAAkB,IAAI,IAAoB;CAChD,OAAO,CAAC,GAAG,KAAK,CAAC,CACd,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CACzC,KAAK,SAAS;EACb,MAAM,UAAU,KAAK,KAAK,KAAK,MAAM;EACrC,MAAM,YACJ,UAAU,gBAAgB,IAAI,KAAK,CAAC,KAAK,cAAA;EAE3C,IAAI,WAAW,gBAAgB,IAAI,KAAK,GAAG,MAAM;EACjD,MAAM,SAAS,QAAQ,QAAQ,KAAK,CAAC;EACrC,MAAM,OACJ,cAAc,UAAU,WAAW,QAAQ,SAAS,IAChD,SACA;EACN,MAAM,WAAW,QAAQ,UAAU,SAAS,SAAS,KAAK;EAC1D,IAAI,YACF,YAAY,OAAA,MAER,KAAK,IAAI,WAAW,KAAK,CAAC,IAAA,KAE1B;EAEN,IACE,cAAc,UACd,YAAY,QACZ,UAAU,QAAQ,SAAS,GAE3B,aAAa,YAAA,KAAqC;EAEpD,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAA,KAA6B,SAAS,CAAC;EACtE,OAAO;GACL;GACA;GACA;GACA;GACA,GACE,SAAS,SACL,KAAK,IAAA,IAA4B,QACjC,KAAK,IAAA,KAAA;EACb;CACF,CAAC;AACL"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Layout tuning shared by the Sankey renderer and its tests. */
|
|
2
|
+
/** Width of every node rectangle, in SVG pixels. */
|
|
3
|
+
export declare const SANKEY_NODE_WIDTH = 16;
|
|
4
|
+
/** Minimum horizontal space between node rectangles in adjacent columns. */
|
|
5
|
+
export declare const SANKEY_MINIMUM_COLUMN_GAP = 32;
|
|
6
|
+
/** Narrowest comfortable node rectangle. */
|
|
7
|
+
export declare const SANKEY_MINIMUM_NODE_HEIGHT = 2;
|
|
8
|
+
/** Vertical breathing room between nodes in one column, in SVG pixels. */
|
|
9
|
+
export declare const SANKEY_NODE_PADDING = 12;
|
|
10
|
+
/** Maximum width of a built-in node label, in SVG pixels. */
|
|
11
|
+
export declare const SANKEY_NODE_LABEL_WIDTH = 128;
|
|
12
|
+
export declare const SANKEY_NODE_LABEL_HEIGHT = 16;
|
|
13
|
+
/** Gap between a node rectangle and the label rendered beside it. */
|
|
14
|
+
export declare const SANKEY_NODE_LABEL_GAP = 6;
|
|
15
|
+
/** Narrowest the plot area gets before the diagram scrolls horizontally. */
|
|
16
|
+
export declare const SANKEY_MINIMUM_PLOT_WIDTH = 160;
|
|
17
|
+
/** Bezier bend shared by every link ribbon. */
|
|
18
|
+
export declare const SANKEY_LINK_CURVATURE = 0.5;
|
|
19
|
+
/** Resting opacity of a link that is not emphasized or dimmed. */
|
|
20
|
+
export declare const SANKEY_LINK_REST_OPACITY = 0.5;
|
|
21
|
+
/** Opacity of a link that does not match the active node or link. */
|
|
22
|
+
export declare const SANKEY_LINK_DIMMED_OPACITY = 0.08;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region packages/components/src/components/SankeyChart/constants.ts
|
|
2
|
+
/** Layout tuning shared by the Sankey renderer and its tests. */
|
|
3
|
+
/** Width of every node rectangle, in SVG pixels. */
|
|
4
|
+
var SANKEY_NODE_WIDTH = 16;
|
|
5
|
+
/** Minimum horizontal space between node rectangles in adjacent columns. */
|
|
6
|
+
var SANKEY_MINIMUM_COLUMN_GAP = 32;
|
|
7
|
+
/** Narrowest comfortable node rectangle. */
|
|
8
|
+
var SANKEY_MINIMUM_NODE_HEIGHT = 2;
|
|
9
|
+
/** Vertical breathing room between nodes in one column, in SVG pixels. */
|
|
10
|
+
var SANKEY_NODE_PADDING = 12;
|
|
11
|
+
/** Maximum width of a built-in node label, in SVG pixels. */
|
|
12
|
+
var SANKEY_NODE_LABEL_WIDTH = 128;
|
|
13
|
+
var SANKEY_NODE_LABEL_HEIGHT = 16;
|
|
14
|
+
/** Gap between a node rectangle and the label rendered beside it. */
|
|
15
|
+
var SANKEY_NODE_LABEL_GAP = 6;
|
|
16
|
+
/** Narrowest the plot area gets before the diagram scrolls horizontally. */
|
|
17
|
+
var SANKEY_MINIMUM_PLOT_WIDTH = 160;
|
|
18
|
+
/** Bezier bend shared by every link ribbon. */
|
|
19
|
+
var SANKEY_LINK_CURVATURE = .5;
|
|
20
|
+
/** Resting opacity of a link that is not emphasized or dimmed. */
|
|
21
|
+
var SANKEY_LINK_REST_OPACITY = .5;
|
|
22
|
+
/** Opacity of a link that does not match the active node or link. */
|
|
23
|
+
var SANKEY_LINK_DIMMED_OPACITY = .08;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { SANKEY_LINK_CURVATURE, SANKEY_LINK_DIMMED_OPACITY, SANKEY_LINK_REST_OPACITY, SANKEY_MINIMUM_COLUMN_GAP, SANKEY_MINIMUM_NODE_HEIGHT, SANKEY_MINIMUM_PLOT_WIDTH, SANKEY_NODE_LABEL_GAP, SANKEY_NODE_LABEL_HEIGHT, SANKEY_NODE_LABEL_WIDTH, SANKEY_NODE_PADDING, SANKEY_NODE_WIDTH };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../../../src/components/SankeyChart/constants.ts"],"sourcesContent":["/** Layout tuning shared by the Sankey renderer and its tests. */\n/** Width of every node rectangle, in SVG pixels. */\nexport const SANKEY_NODE_WIDTH = 16;\n\n/** Minimum horizontal space between node rectangles in adjacent columns. */\nexport const SANKEY_MINIMUM_COLUMN_GAP = 32;\n\n/** Narrowest comfortable node rectangle. */\nexport const SANKEY_MINIMUM_NODE_HEIGHT = 2;\n\n/** Vertical breathing room between nodes in one column, in SVG pixels. */\nexport const SANKEY_NODE_PADDING = 12;\n\n/** Maximum width of a built-in node label, in SVG pixels. */\nexport const SANKEY_NODE_LABEL_WIDTH = 128;\n\nexport const SANKEY_NODE_LABEL_HEIGHT = 16;\n\n/** Gap between a node rectangle and the label rendered beside it. */\nexport const SANKEY_NODE_LABEL_GAP = 6;\n\n/** Narrowest the plot area gets before the diagram scrolls horizontally. */\nexport const SANKEY_MINIMUM_PLOT_WIDTH = 160;\n\n/** Bezier bend shared by every link ribbon. */\nexport const SANKEY_LINK_CURVATURE = 0.5;\n\n/** Resting opacity of a link that is not emphasized or dimmed. */\nexport const SANKEY_LINK_REST_OPACITY = 0.5;\n\n/** Opacity of a link that does not match the active node or link. */\nexport const SANKEY_LINK_DIMMED_OPACITY = 0.08;\n"],"mappings":";;;AAEA,IAAa,oBAAoB;;AAGjC,IAAa,4BAA4B;;AAGzC,IAAa,6BAA6B;;AAG1C,IAAa,sBAAsB;;AAGnC,IAAa,0BAA0B;AAEvC,IAAa,2BAA2B;;AAGxC,IAAa,wBAAwB;;AAGrC,IAAa,4BAA4B;;AAGzC,IAAa,wBAAwB;;AAGrC,IAAa,2BAA2B;;AAGxC,IAAa,6BAA6B"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SankeyChart } from './SankeyChart.js';
|
|
2
|
+
export type { SankeyChartProps } from './SankeyChart.js';
|
|
3
|
+
export type { SankeyChartAlignment, SankeyChartLabelSide, SankeyChartLayout, SankeyChartLayoutLink, SankeyChartLayoutNode, SankeyChartLink, SankeyChartNode, SankeyChartRenderProps, SankeyChartSlots, } from './SankeyChart.types.js';
|
|
4
|
+
export { SANKEY_MINIMUM_PLOT_WIDTH, SANKEY_NODE_WIDTH } from './constants.js';
|
|
5
|
+
export { getSankeyChartLayout, getSankeyLinkPath } from './SankeyChart.utils.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SANKEY_MINIMUM_PLOT_WIDTH, SANKEY_NODE_WIDTH } from "./constants.js";
|
|
2
|
+
import { getSankeyChartLayout, getSankeyLinkPath } from "./SankeyChart.utils.js";
|
|
3
|
+
import { SankeyChart } from "./SankeyChart.js";
|
|
4
|
+
export { SANKEY_MINIMUM_PLOT_WIDTH, SANKEY_NODE_WIDTH, SankeyChart, getSankeyChartLayout, getSankeyLinkPath };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { cn } from "../../utils/cn.js";
|
|
2
2
|
import { CONTROL_SIZES } from "../../utils/componentSizes.js";
|
|
3
3
|
import { Text } from "../Text/Text.js";
|
|
4
|
-
import { CaretDownIcon } from "../../icons/PaddedPhosphorIcons.js";
|
|
5
|
-
import { Icon } from "../Icon/Icon.js";
|
|
6
4
|
import { Button } from "../Button/Button.js";
|
|
5
|
+
import { Icon } from "../Icon/Icon.js";
|
|
6
|
+
import { CaretDownIcon } from "../../icons/PaddedPhosphorIcons.js";
|
|
7
7
|
import { Popover, PopoverContent, PopoverTrigger } from "../Popover/Popover.js";
|
|
8
|
-
import { CommandMenu } from "../Command/CommandMenu.js";
|
|
9
8
|
import { CONTROL_ICON_SIZES } from "../../utils/controlIconSizes.js";
|
|
9
|
+
import { CommandMenu } from "../Command/CommandMenu.js";
|
|
10
10
|
import { useCallback, useMemo, useState } from "react";
|
|
11
11
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
//#region packages/components/src/components/Select/Select.tsx
|
|
@@ -64,7 +64,8 @@ function OptionSelect({ value, onChange, options, groups, placeholder = "Select.
|
|
|
64
64
|
onEndReached,
|
|
65
65
|
endReachedThresholdPx,
|
|
66
66
|
renderItem,
|
|
67
|
-
inputSize: searchInputSize
|
|
67
|
+
inputSize: searchInputSize,
|
|
68
|
+
itemRowSize: size === "xs" || size === "sm" ? "sm" : "md"
|
|
68
69
|
};
|
|
69
70
|
const triggerContent = selectedOption && renderValue ? renderValue(selectedOption) : /* @__PURE__ */ jsx(Text, {
|
|
70
71
|
as: "span",
|
|
@@ -100,7 +101,6 @@ function OptionSelect({ value, onChange, options, groups, placeholder = "Select.
|
|
|
100
101
|
className: cn("w-max min-w-[var(--radix-popover-trigger-width)] max-w-[min(24rem,calc(100vw-1rem))] p-0 text-primary", contentClassName),
|
|
101
102
|
side,
|
|
102
103
|
align,
|
|
103
|
-
onCloseAutoFocus: (e) => e.preventDefault(),
|
|
104
104
|
children: commandGroups ? /* @__PURE__ */ jsx(CommandMenu, {
|
|
105
105
|
groups: commandGroups,
|
|
106
106
|
...commandMenuSharedProps
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { useCallback, useMemo, useState } from 'react';\n\nimport { CaretDownIcon } from '../../icons/PaddedPhosphorIcons';\nimport { cn } from '../../utils/cn';\nimport { CONTROL_SIZES, type ControlSize } from '../../utils/componentSizes';\nimport { CONTROL_ICON_SIZES } from '../../utils/controlIconSizes';\nimport { Button } from '../Button';\nimport { CommandMenu } from '../Command/CommandMenu';\nimport type { CommandMenuGroup, CommandMenuItem } from '../Command/CommandMenu';\nimport { Icon } from '../Icon';\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover';\nimport { Text } from '../Text';\n\nexport interface SelectOption<T extends string = string> {\n /** The value used for selection and filtering */\n value: T;\n /** Display label (defaults to value if not provided) */\n label?: string;\n /** Secondary description text displayed below the label */\n description?: string;\n /** Element displayed on the right side of the item (e.g. badge, shortcut) */\n rightDecorator?: ReactNode;\n /** Whether this option is disabled */\n disabled?: boolean;\n}\n\nexport interface SelectGroup<T extends string = string> {\n heading?: string;\n options: SelectOption<T>[];\n}\n\nexport type SelectSize = ControlSize;\n\ninterface SelectBaseProps<T extends string = string> {\n /** The currently selected value */\n value?: T;\n /** Callback when selection changes. Receives undefined when deselected. */\n onChange: (value: T | undefined) => void;\n /** Placeholder text when no value is selected */\n placeholder?: string;\n /** Placeholder text for the search input */\n searchPlaceholder?: string;\n /** Whether the select is disabled */\n disabled?: boolean;\n /** Exact trigger height: xs=20px, sm=24px, md=32px, lg=40px. */\n size?: SelectSize;\n /** Text to show when no options match the search */\n emptyText?: string;\n /** Allow deselecting the current value by clicking it again (default: false) */\n allowDeselect?: boolean;\n /** Hide the search input (default: true, search is hidden) */\n hideSearch?: boolean;\n /** Disable client-side filtering by cmdk (default: false). Set true for server-side filtering. */\n disableFilter?: boolean;\n /** Callback when the search input value changes. Useful for server-side filtering. */\n onSearchChange?: (query: string) => void;\n /** Whether more items are currently being loaded */\n loading?: boolean;\n /** Number of skeleton rows to show while loading (default: 5) */\n loadingRows?: number;\n /** Callback when the user scrolls near the end of the list. Use for infinite loading. */\n onEndReached?: () => void;\n /** Distance in pixels from the bottom to trigger onEndReached (default: 50) */\n endReachedThresholdPx?: number;\n /** Additional class name for the trigger button */\n triggerClassName?: string;\n /** Additional class name for the popover content */\n contentClassName?: string;\n /** Custom render function for the selected value in the trigger */\n renderValue?: (option: SelectOption<T>) => ReactNode;\n /** Custom render function for each option in the list */\n renderOption?: (option: SelectOption<T>, isSelected: boolean) => ReactNode;\n /** Side of the trigger to align the popover */\n side?: 'top' | 'bottom' | 'left' | 'right';\n /** Alignment of the popover relative to the trigger */\n align?: 'start' | 'center' | 'end';\n /** aria-label for the trigger button */\n 'aria-label'?: string;\n /** Test id for the trigger button */\n 'data-testid'?: string;\n}\n\nexport type SelectProps<T extends string = string> = SelectBaseProps<T> &\n (\n | {\n options: SelectOption<T>[];\n groups?: never;\n }\n | {\n groups: SelectGroup<T>[];\n options?: never;\n }\n ) &\n (\n | {\n /** Controlled open state */\n open: boolean;\n /** Callback when open state changes (required when `open` is provided) */\n onOpenChange: (open: boolean) => void;\n }\n | {\n open?: never;\n onOpenChange?: never;\n }\n );\n\nfunction optionToItem(opt: SelectOption): CommandMenuItem {\n return {\n value: opt.value,\n label: opt.label,\n description: opt.description,\n rightDecorator: opt.rightDecorator,\n disabled: opt.disabled,\n keywords: opt.label ? [opt.label] : undefined,\n };\n}\n\nfunction OptionSelect<T extends string = string>({\n value,\n onChange,\n options,\n groups,\n placeholder = 'Select...',\n searchPlaceholder = 'Search...',\n disabled = false,\n size = 'sm',\n emptyText = 'No results found.',\n open: controlledOpen,\n onOpenChange: controlledOnOpenChange,\n allowDeselect,\n hideSearch = true,\n disableFilter,\n onSearchChange,\n loading,\n loadingRows,\n onEndReached,\n endReachedThresholdPx,\n triggerClassName,\n contentClassName,\n renderValue,\n renderOption,\n side,\n align = 'start',\n 'aria-label': ariaLabel,\n 'data-testid': dataTestId,\n}: SelectProps<T>) {\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : uncontrolledOpen;\n const setOpen = isControlled ? controlledOnOpenChange : setUncontrolledOpen;\n\n const allOptions = useMemo<SelectOption<T>[]>(\n () => (groups ? groups.flatMap((g) => g.options) : (options ?? [])),\n [groups, options]\n );\n\n const selectedOption = useMemo(\n () => allOptions.find((opt) => opt.value === value),\n [allOptions, value]\n );\n\n const handleSelect = useCallback(\n (optionValue: string) => {\n const match = allOptions.find((o) => o.value === optionValue);\n if (!match) return;\n if (allowDeselect && match.value === value) {\n onChange(undefined);\n } else {\n onChange(match.value);\n }\n setOpen(false);\n },\n [allowDeselect, value, onChange, setOpen, allOptions]\n );\n\n const items: CommandMenuItem[] = useMemo(\n () => (options ?? []).map(optionToItem),\n [options]\n );\n\n const commandGroups: CommandMenuGroup[] | undefined = useMemo(\n () =>\n groups?.map((g) => ({\n heading: g.heading,\n items: g.options.map(optionToItem),\n })),\n [groups]\n );\n\n const renderItem = renderOption\n ? (item: CommandMenuItem, isSelected: boolean) => {\n const option = allOptions.find((o) => o.value === item.value);\n return option ? renderOption(option, isSelected) : null;\n }\n : undefined;\n const searchInputSize = size === 'xs' ? 'sm' : size === 'lg' ? 'md' : size;\n const triggerButtonSize = size === 'lg' ? 'md' : size;\n\n const commandMenuSharedProps = {\n value,\n onSelect: handleSelect,\n placeholder: searchPlaceholder,\n emptyText,\n hideSearch,\n disableFilter,\n onSearchChange,\n loading,\n loadingRows,\n onEndReached,\n endReachedThresholdPx,\n renderItem,\n inputSize: searchInputSize,\n };\n\n const triggerContent =\n selectedOption && renderValue ? (\n renderValue(selectedOption)\n ) : (\n <Text\n as=\"span\"\n variant={triggerButtonSize}\n weight=\"normal\"\n className=\"min-w-0 truncate text-left\"\n >\n {selectedOption\n ? (selectedOption.label ?? selectedOption.value)\n : placeholder}\n </Text>\n );\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n role=\"combobox\"\n aria-expanded={open}\n aria-label={ariaLabel ?? placeholder}\n variant=\"outlined\"\n color=\"secondary\"\n size={triggerButtonSize}\n className={cn(\n 'box-border w-full justify-between py-0',\n CONTROL_SIZES[size].heightClassName,\n triggerClassName\n )}\n disabled={disabled}\n data-testid={dataTestId}\n >\n {triggerContent}\n <Icon\n aria-hidden\n icon={CaretDownIcon}\n iconClassName={CONTROL_ICON_SIZES[size].iconClassName}\n className=\"text-inherit\"\n weight=\"regular\"\n />\n </Button>\n </PopoverTrigger>\n <PopoverContent\n className={cn(\n 'w-max min-w-[var(--radix-popover-trigger-width)] max-w-[min(24rem,calc(100vw-1rem))] p-0 text-primary',\n contentClassName\n )}\n side={side}\n align={align}\n onCloseAutoFocus={(e) => e.preventDefault()}\n >\n {commandGroups ? (\n <CommandMenu groups={commandGroups} {...commandMenuSharedProps} />\n ) : (\n <CommandMenu items={items} {...commandMenuSharedProps} />\n )}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport function Select<T extends string = string>(\n props: SelectProps<T>\n): React.ReactElement<unknown> {\n return <OptionSelect {...props} />;\n}\n"],"mappings":";;;;;;;;;;;;AA2GA,SAAS,aAAa,KAAoC;CACxD,OAAO;EACL,OAAO,IAAI;EACX,OAAO,IAAI;EACX,aAAa,IAAI;EACjB,gBAAgB,IAAI;EACpB,UAAU,IAAI;EACd,UAAU,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,KAAA;CACtC;AACF;AAEA,SAAS,aAAwC,EAC/C,OACA,UACA,SACA,QACA,cAAc,aACd,oBAAoB,aACpB,WAAW,OACX,OAAO,MACP,YAAY,qBACZ,MAAM,gBACN,cAAc,wBACd,eACA,aAAa,MACb,eACA,gBACA,SACA,aACA,cACA,uBACA,kBACA,kBACA,aACA,cACA,MACA,QAAQ,SACR,cAAc,WACd,eAAe,cACE;CACjB,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,KAAK;CAC9D,MAAM,eAAe,mBAAmB,KAAA;CACxC,MAAM,OAAO,eAAe,iBAAiB;CAC7C,MAAM,UAAU,eAAe,yBAAyB;CAExD,MAAM,aAAa,cACV,SAAS,OAAO,SAAS,MAAM,EAAE,OAAO,IAAK,WAAW,CAAC,GAChE,CAAC,QAAQ,OAAO,CAClB;CAEA,MAAM,iBAAiB,cACf,WAAW,MAAM,QAAQ,IAAI,UAAU,KAAK,GAClD,CAAC,YAAY,KAAK,CACpB;CAEA,MAAM,eAAe,aAClB,gBAAwB;EACvB,MAAM,QAAQ,WAAW,MAAM,MAAM,EAAE,UAAU,WAAW;EAC5D,IAAI,CAAC,OAAO;EACZ,IAAI,iBAAiB,MAAM,UAAU,OACnC,SAAS,KAAA,CAAS;OAElB,SAAS,MAAM,KAAK;EAEtB,QAAQ,KAAK;CACf,GACA;EAAC;EAAe;EAAO;EAAU;EAAS;CAAU,CACtD;CAEA,MAAM,QAA2B,eACxB,WAAW,CAAC,EAAA,CAAG,IAAI,YAAY,GACtC,CAAC,OAAO,CACV;CAEA,MAAM,gBAAgD,cAElD,QAAQ,KAAK,OAAO;EAClB,SAAS,EAAE;EACX,OAAO,EAAE,QAAQ,IAAI,YAAY;CACnC,EAAE,GACJ,CAAC,MAAM,CACT;CAEA,MAAM,aAAa,gBACd,MAAuB,eAAwB;EAC9C,MAAM,SAAS,WAAW,MAAM,MAAM,EAAE,UAAU,KAAK,KAAK;EAC5D,OAAO,SAAS,aAAa,QAAQ,UAAU,IAAI;CACrD,IACA,KAAA;CACJ,MAAM,kBAAkB,SAAS,OAAO,OAAO,SAAS,OAAO,OAAO;CACtE,MAAM,oBAAoB,SAAS,OAAO,OAAO;CAEjD,MAAM,yBAAyB;EAC7B;EACA,UAAU;EACV,aAAa;EACb;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAAW;CACb;CAEA,MAAM,iBACJ,kBAAkB,cAChB,YAAY,cAAc,IAE1B,oBAAC,MAAD;EACE,IAAG;EACH,SAAS;EACT,QAAO;EACP,WAAU;EAET,UAAA,iBACI,eAAe,SAAS,eAAe,QACxC;CACA,CAAA;CAGV,OACE,qBAAC,SAAD;EAAe;EAAM,cAAc;EAAnC,UAAA,CACE,oBAAC,gBAAD;GAAgB,SAAA;GACd,UAAA,qBAAC,QAAD;IACE,MAAK;IACL,iBAAe;IACf,cAAY,aAAa;IACzB,SAAQ;IACR,OAAM;IACN,MAAM;IACN,WAAW,GACT,0CACA,cAAc,KAAK,CAAC,iBACpB,gBACF;IACU;IACV,eAAa;IAbf,UAAA,CAeG,gBACD,oBAAC,MAAD;KACE,eAAA;KACA,MAAM;KACN,eAAe,mBAAmB,KAAK,CAAC;KACxC,WAAU;KACV,QAAO;IACR,CAAA,CACK;;EACM,CAAA,GAChB,oBAAC,gBAAD;GACE,WAAW,GACT,yGACA,gBACF;GACM;GACC;GACP,mBAAmB,MAAM,EAAE,eAAe;GAEzC,UAAA,gBACC,oBAAC,aAAD;IAAa,QAAQ;IAAe,GAAI;GAAyB,CAAA,IAEjE,oBAAC,aAAD;IAAoB;IAAO,GAAI;GAAyB,CAAA;EAE5C,CAAA,CACT;;AAEb;AAEA,SAAgB,OACd,OAC6B;CAC7B,OAAO,oBAAC,cAAD,EAAc,GAAI,MAAQ,CAAA;AACnC"}
|
|
1
|
+
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { useCallback, useMemo, useState } from 'react';\n\nimport { CaretDownIcon } from '../../icons/PaddedPhosphorIcons';\nimport { cn } from '../../utils/cn';\nimport {\n CONTROL_SIZES,\n type ControlSize,\n type OptionRowSize,\n} from '../../utils/componentSizes';\nimport { CONTROL_ICON_SIZES } from '../../utils/controlIconSizes';\nimport { Button } from '../Button';\nimport { CommandMenu } from '../Command/CommandMenu';\nimport type { CommandMenuGroup, CommandMenuItem } from '../Command/CommandMenu';\nimport { Icon } from '../Icon';\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover';\nimport { Text } from '../Text';\n\nexport interface SelectOption<T extends string = string> {\n /** The value used for selection and filtering */\n value: T;\n /** Display label (defaults to value if not provided) */\n label?: string;\n /** Secondary description text displayed below the label */\n description?: string;\n /** Element displayed on the right side of the item (e.g. badge, shortcut) */\n rightDecorator?: ReactNode;\n /** Whether this option is disabled */\n disabled?: boolean;\n}\n\nexport interface SelectGroup<T extends string = string> {\n heading?: string;\n options: SelectOption<T>[];\n}\n\nexport type SelectSize = ControlSize;\n\ninterface SelectBaseProps<T extends string = string> {\n /** The currently selected value */\n value?: T;\n /** Callback when selection changes. Receives undefined when deselected. */\n onChange: (value: T | undefined) => void;\n /** Placeholder text when no value is selected */\n placeholder?: string;\n /** Placeholder text for the search input */\n searchPlaceholder?: string;\n /** Whether the select is disabled */\n disabled?: boolean;\n /** Exact trigger height: xs=20px, sm=24px, md=32px, lg=40px. */\n size?: SelectSize;\n /** Text to show when no options match the search */\n emptyText?: string;\n /** Allow deselecting the current value by clicking it again (default: false) */\n allowDeselect?: boolean;\n /** Hide the search input (default: true, search is hidden) */\n hideSearch?: boolean;\n /** Disable client-side filtering by cmdk (default: false). Set true for server-side filtering. */\n disableFilter?: boolean;\n /** Callback when the search input value changes. Useful for server-side filtering. */\n onSearchChange?: (query: string) => void;\n /** Whether more items are currently being loaded */\n loading?: boolean;\n /** Number of skeleton rows to show while loading (default: 5) */\n loadingRows?: number;\n /** Callback when the user scrolls near the end of the list. Use for infinite loading. */\n onEndReached?: () => void;\n /** Distance in pixels from the bottom to trigger onEndReached (default: 50) */\n endReachedThresholdPx?: number;\n /** Additional class name for the trigger button */\n triggerClassName?: string;\n /** Additional class name for the popover content */\n contentClassName?: string;\n /** Custom render function for the selected value in the trigger */\n renderValue?: (option: SelectOption<T>) => ReactNode;\n /** Custom render function for each option in the list */\n renderOption?: (option: SelectOption<T>, isSelected: boolean) => ReactNode;\n /** Side of the trigger to align the popover */\n side?: 'top' | 'bottom' | 'left' | 'right';\n /** Alignment of the popover relative to the trigger */\n align?: 'start' | 'center' | 'end';\n /** aria-label for the trigger button */\n 'aria-label'?: string;\n /** Test id for the trigger button */\n 'data-testid'?: string;\n}\n\nexport type SelectProps<T extends string = string> = SelectBaseProps<T> &\n (\n | {\n options: SelectOption<T>[];\n groups?: never;\n }\n | {\n groups: SelectGroup<T>[];\n options?: never;\n }\n ) &\n (\n | {\n /** Controlled open state */\n open: boolean;\n /** Callback when open state changes (required when `open` is provided) */\n onOpenChange: (open: boolean) => void;\n }\n | {\n open?: never;\n onOpenChange?: never;\n }\n );\n\nfunction optionToItem(opt: SelectOption): CommandMenuItem {\n return {\n value: opt.value,\n label: opt.label,\n description: opt.description,\n rightDecorator: opt.rightDecorator,\n disabled: opt.disabled,\n keywords: opt.label ? [opt.label] : undefined,\n };\n}\n\nfunction OptionSelect<T extends string = string>({\n value,\n onChange,\n options,\n groups,\n placeholder = 'Select...',\n searchPlaceholder = 'Search...',\n disabled = false,\n size = 'sm',\n emptyText = 'No results found.',\n open: controlledOpen,\n onOpenChange: controlledOnOpenChange,\n allowDeselect,\n hideSearch = true,\n disableFilter,\n onSearchChange,\n loading,\n loadingRows,\n onEndReached,\n endReachedThresholdPx,\n triggerClassName,\n contentClassName,\n renderValue,\n renderOption,\n side,\n align = 'start',\n 'aria-label': ariaLabel,\n 'data-testid': dataTestId,\n}: SelectProps<T>) {\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : uncontrolledOpen;\n const setOpen = isControlled ? controlledOnOpenChange : setUncontrolledOpen;\n\n const allOptions = useMemo<SelectOption<T>[]>(\n () => (groups ? groups.flatMap((g) => g.options) : (options ?? [])),\n [groups, options]\n );\n\n const selectedOption = useMemo(\n () => allOptions.find((opt) => opt.value === value),\n [allOptions, value]\n );\n\n const handleSelect = useCallback(\n (optionValue: string) => {\n const match = allOptions.find((o) => o.value === optionValue);\n if (!match) return;\n if (allowDeselect && match.value === value) {\n onChange(undefined);\n } else {\n onChange(match.value);\n }\n setOpen(false);\n },\n [allowDeselect, value, onChange, setOpen, allOptions]\n );\n\n const items: CommandMenuItem[] = useMemo(\n () => (options ?? []).map(optionToItem),\n [options]\n );\n\n const commandGroups: CommandMenuGroup[] | undefined = useMemo(\n () =>\n groups?.map((g) => ({\n heading: g.heading,\n items: g.options.map(optionToItem),\n })),\n [groups]\n );\n\n const renderItem = renderOption\n ? (item: CommandMenuItem, isSelected: boolean) => {\n const option = allOptions.find((o) => o.value === item.value);\n return option ? renderOption(option, isSelected) : null;\n }\n : undefined;\n const searchInputSize = size === 'xs' ? 'sm' : size === 'lg' ? 'md' : size;\n const triggerButtonSize = size === 'lg' ? 'md' : size;\n const itemRowSize: OptionRowSize =\n size === 'xs' || size === 'sm' ? 'sm' : 'md';\n\n const commandMenuSharedProps = {\n value,\n onSelect: handleSelect,\n placeholder: searchPlaceholder,\n emptyText,\n hideSearch,\n disableFilter,\n onSearchChange,\n loading,\n loadingRows,\n onEndReached,\n endReachedThresholdPx,\n renderItem,\n inputSize: searchInputSize,\n itemRowSize,\n };\n\n const triggerContent =\n selectedOption && renderValue ? (\n renderValue(selectedOption)\n ) : (\n <Text\n as=\"span\"\n variant={triggerButtonSize}\n weight=\"normal\"\n className=\"min-w-0 truncate text-left\"\n >\n {selectedOption\n ? (selectedOption.label ?? selectedOption.value)\n : placeholder}\n </Text>\n );\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n role=\"combobox\"\n aria-expanded={open}\n aria-label={ariaLabel ?? placeholder}\n variant=\"outlined\"\n color=\"secondary\"\n size={triggerButtonSize}\n className={cn(\n 'box-border w-full justify-between py-0',\n CONTROL_SIZES[size].heightClassName,\n triggerClassName\n )}\n disabled={disabled}\n data-testid={dataTestId}\n >\n {triggerContent}\n <Icon\n aria-hidden\n icon={CaretDownIcon}\n iconClassName={CONTROL_ICON_SIZES[size].iconClassName}\n className=\"text-inherit\"\n weight=\"regular\"\n />\n </Button>\n </PopoverTrigger>\n <PopoverContent\n className={cn(\n 'w-max min-w-[var(--radix-popover-trigger-width)] max-w-[min(24rem,calc(100vw-1rem))] p-0 text-primary',\n contentClassName\n )}\n side={side}\n align={align}\n >\n {commandGroups ? (\n <CommandMenu groups={commandGroups} {...commandMenuSharedProps} />\n ) : (\n <CommandMenu items={items} {...commandMenuSharedProps} />\n )}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport function Select<T extends string = string>(\n props: SelectProps<T>\n): React.ReactElement<unknown> {\n return <OptionSelect {...props} />;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as RadixSelect from '@radix-ui/react-select';
|
|
3
|
+
import { type OptionRowSize } from '../../utils/componentSizes.js';
|
|
3
4
|
export declare const SelectRoot: typeof RadixSelect.Root;
|
|
4
5
|
export type SelectTriggerProps = React.ComponentPropsWithoutRef<typeof RadixSelect.Trigger> & {
|
|
5
6
|
iconClassName?: string;
|
|
@@ -12,6 +13,11 @@ export declare const SelectLabel: React.ForwardRefExoticComponent<Omit<RadixSele
|
|
|
12
13
|
export declare const SelectItem: React.ForwardRefExoticComponent<Omit<RadixSelect.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
|
|
13
14
|
checkmarkPosition?: "left" | "right";
|
|
14
15
|
selectedIndicator?: "checkmark" | "background";
|
|
16
|
+
/**
|
|
17
|
+
* Controls minimum row height and default label typography.
|
|
18
|
+
* Rich or multiline content grows intrinsically.
|
|
19
|
+
*/
|
|
20
|
+
size?: OptionRowSize;
|
|
15
21
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
16
22
|
export declare const SelectSeparator: React.ForwardRefExoticComponent<Omit<RadixSelect.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
17
23
|
export declare const SelectValue: React.ForwardRefExoticComponent<RadixSelect.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
|