@kind-ui/charts 0.1.1 → 0.3.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/CHANGELOG.md +32 -0
- package/README.md +683 -1
- package/dist/activity-rings.d.ts +2 -1
- package/dist/activity-rings.js +6 -3
- package/dist/animation.d.ts +11 -3
- package/dist/animation.js +18 -10
- package/dist/area-chart.d.ts +3 -1
- package/dist/area-chart.js +15 -10
- package/dist/area-series.d.ts +9 -1
- package/dist/area-series.js +35 -8
- package/dist/bar-chart.d.ts +10 -3
- package/dist/bar-chart.js +17 -4
- package/dist/bar-material.js +2 -3
- package/dist/bar-series.d.ts +11 -1
- package/dist/bar-series.js +65 -13
- package/dist/box-plot.js +2 -2
- package/dist/category-cells.d.ts +7 -1
- package/dist/category-cells.js +32 -2
- package/dist/chart-background-pattern.d.ts +23 -0
- package/dist/chart-background-pattern.js +31 -0
- package/dist/chart-context.d.ts +6 -2
- package/dist/chart-interaction.d.ts +94 -0
- package/dist/chart-interaction.js +224 -0
- package/dist/combo-chart.d.ts +8 -5
- package/dist/combo-chart.js +14 -12
- package/dist/configured-line-chart.d.ts +5 -1
- package/dist/configured-line-chart.js +8 -5
- package/dist/emphasis.d.ts +5 -2
- package/dist/emphasis.js +18 -4
- package/dist/fill-pattern.d.ts +25 -0
- package/dist/fill-pattern.js +23 -0
- package/dist/heatmap.d.ts +4 -2
- package/dist/heatmap.js +34 -12
- package/dist/histogram-chart.js +10 -3
- package/dist/histogram-material.js +1 -2
- package/dist/index.d.ts +12 -3
- package/dist/index.js +8 -1
- package/dist/legend.d.ts +2 -0
- package/dist/legend.js +28 -7
- package/dist/line-chart.d.ts +13 -3
- package/dist/line-chart.js +54 -34
- package/dist/line-dash.d.ts +8 -0
- package/dist/line-dash.js +18 -0
- package/dist/line-material.d.ts +1 -1
- package/dist/line-material.js +1 -1
- package/dist/line-series.d.ts +9 -1
- package/dist/line-series.js +37 -11
- package/dist/loading-cartesian-designs.d.ts +6 -0
- package/dist/loading-cartesian-designs.js +123 -0
- package/dist/loading-motion.d.ts +37 -0
- package/dist/loading-motion.js +88 -0
- package/dist/loading-polar-designs.d.ts +6 -0
- package/dist/loading-polar-designs.js +95 -0
- package/dist/loading-skeleton.d.ts +29 -0
- package/dist/loading-skeleton.js +143 -0
- package/dist/loading-standalone-designs.d.ts +5 -0
- package/dist/loading-standalone-designs.js +126 -0
- package/dist/percent-stack.d.ts +18 -0
- package/dist/percent-stack.js +33 -0
- package/dist/pie-chart.d.ts +7 -3
- package/dist/pie-chart.js +37 -6
- package/dist/pie-material.js +1 -1
- package/dist/pie-pin-identity.d.ts +4 -0
- package/dist/pie-pin-identity.js +10 -0
- package/dist/pie-series.d.ts +4 -0
- package/dist/pie-series.js +138 -15
- package/dist/pie-tooltip-pin.d.ts +4 -0
- package/dist/pie-tooltip-pin.js +52 -0
- package/dist/point-marker.d.ts +19 -0
- package/dist/point-marker.js +19 -0
- package/dist/polar-chart.d.ts +14 -5
- package/dist/polar-chart.js +41 -10
- package/dist/polar-material.js +1 -1
- package/dist/polar-series.js +68 -22
- package/dist/radar-interaction.js +10 -4
- package/dist/radial-category.d.ts +2 -0
- package/dist/reveal-clip.d.ts +16 -0
- package/dist/reveal-clip.js +18 -0
- package/dist/root.d.ts +5 -9
- package/dist/root.js +46 -11
- package/dist/sankey-chart.d.ts +5 -1
- package/dist/sankey-chart.js +64 -13
- package/dist/sankey-colors.d.ts +3 -0
- package/dist/sankey-finish.d.ts +1 -1
- package/dist/sankey-finish.js +1 -1
- package/dist/sankey-interaction.d.ts +8 -0
- package/dist/sankey-interaction.js +32 -0
- package/dist/sankey-legend.d.ts +2 -1
- package/dist/sankey-legend.js +18 -3
- package/dist/sankey-node-label.d.ts +22 -0
- package/dist/sankey-node-label.js +34 -0
- package/dist/scatter-chart.d.ts +5 -3
- package/dist/scatter-chart.js +14 -4
- package/dist/scatter-material.js +1 -1
- package/dist/scatter-series.js +5 -3
- package/dist/series-color.d.ts +10 -0
- package/dist/series-color.js +48 -0
- package/dist/series-interaction.d.ts +12 -0
- package/dist/series-interaction.js +82 -0
- package/dist/series-label.d.ts +7 -0
- package/dist/series-label.js +18 -0
- package/dist/series-paint.d.ts +8 -0
- package/dist/series-paint.js +27 -0
- package/dist/styles.css +151 -6
- package/dist/stylesheet-warning.d.ts +2 -0
- package/dist/stylesheet-warning.js +86 -0
- package/dist/surface-material.js +1 -1
- package/dist/tooltip-content.d.ts +8 -1
- package/dist/tooltip-content.js +15 -4
- package/dist/tooltip.d.ts +3 -1
- package/dist/tooltip.js +8 -5
- package/dist/types.d.ts +11 -2
- package/dist/waterfall-chart.d.ts +4 -0
- package/dist/waterfall-chart.js +7 -0
- package/package.json +2 -2
- package/dist/bar-paper.d.ts +0 -2
- package/dist/bar-paper.js +0 -6
package/dist/polar-series.js
CHANGED
|
@@ -5,25 +5,32 @@ import { animate, motion, useMotionValue } from "motion/react";
|
|
|
5
5
|
import { memo, use, useId, useLayoutEffect, useRef, useState } from "react";
|
|
6
6
|
import { DefaultZIndexes, Polygon, Radar, RadialBar, Sector, ZIndexLayer } from "recharts";
|
|
7
7
|
import { ActiveMarker } from "./animation.js";
|
|
8
|
-
import { categoryCells } from "./category-cells.js";
|
|
8
|
+
import { categoryCells, filterCategoryRows } from "./category-cells.js";
|
|
9
9
|
import { useChart } from "./chart-context.js";
|
|
10
|
+
import { useChartInteraction, useInteractionFocus } from "./chart-interaction.js";
|
|
11
|
+
import { EmphasisMark } from "./emphasis.js";
|
|
10
12
|
import { useLineInteraction } from "./line-chart.js";
|
|
11
13
|
import { PolarMotion, RadarMotion, RadialMotion } from "./polar-chart.js";
|
|
12
14
|
import { PolarMaterialFilter } from "./polar-material.js";
|
|
13
15
|
import { RadarSelectionLayer, useRadarSelectionDot } from "./radar-interaction.js";
|
|
14
16
|
import { RadialCategory } from "./radial-category.js";
|
|
17
|
+
import { SeriesInteractionLayer, useSeriesInteraction } from "./series-interaction.js";
|
|
15
18
|
// Native Radar uses a props-identity animation key even with animation disabled.
|
|
16
19
|
// Avoid replacing its polygon for unrelated frame state during a pointer press.
|
|
17
20
|
const StableRadar = memo(Radar);
|
|
18
21
|
const radarActiveDot = _jsx(ActiveMarker, {});
|
|
19
22
|
function usePolarSeries(kind, props, geometry) {
|
|
20
|
-
const { config, visibleSeries } = useChart();
|
|
23
|
+
const { config, paints, visibleSeries } = useChart();
|
|
24
|
+
const interactionOwner = useChartInteraction();
|
|
21
25
|
const { registerSeries, invalidate } = useLineInteraction();
|
|
22
26
|
const { reveal, options } = use(PolarMotion);
|
|
23
27
|
const generatedId = useId();
|
|
24
28
|
const id = props.id || generatedId;
|
|
25
29
|
const key = props.seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined);
|
|
26
|
-
const hide = props.hide === true ||
|
|
30
|
+
const hide = props.hide === true ||
|
|
31
|
+
(interactionOwner.kind === "series" &&
|
|
32
|
+
visibleSeries !== undefined &&
|
|
33
|
+
!visibleSeries.includes(key ?? ""));
|
|
27
34
|
const previous = useRef([...geometry, hide]);
|
|
28
35
|
useLayoutEffect(() => {
|
|
29
36
|
const inputs = [...geometry, hide];
|
|
@@ -56,7 +63,7 @@ function usePolarSeries(kind, props, geometry) {
|
|
|
56
63
|
}, [reveal, hide, opacity, duration, easing]);
|
|
57
64
|
if (key === undefined && visibleSeries !== undefined)
|
|
58
65
|
throw new Error(`${kind} requires seriesKey for controlled non-string dataKey`);
|
|
59
|
-
const color = key !== undefined && Object.hasOwn(config, key) ?
|
|
66
|
+
const color = key !== undefined && Object.hasOwn(config, key) ? paints[key] : undefined;
|
|
60
67
|
return { id, hide, color, opacity };
|
|
61
68
|
}
|
|
62
69
|
// Recharts injects its original computed points and callback props into this renderer.
|
|
@@ -90,24 +97,50 @@ export function RadarSeries({ seriesKey, hide, stroke, fill, className, material
|
|
|
90
97
|
props.baseLinePoints,
|
|
91
98
|
props.isRange,
|
|
92
99
|
]);
|
|
100
|
+
const interaction = useSeriesInteraction(seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined), series.hide, hide === true, undefined, props.onClick);
|
|
93
101
|
const selectionDot = useRadarSelectionDot(props.dot, seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined));
|
|
94
102
|
const filterId = `kind-ui-polar-${useId().replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
95
103
|
const materialized = material !== "plain" &&
|
|
96
104
|
props.shape === undefined &&
|
|
97
105
|
props.filter === undefined &&
|
|
98
106
|
props.style?.filter === undefined;
|
|
99
|
-
return (_jsxs(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.area, children: [materialized && (_jsxs(_Fragment, { children: [_jsx(PolarMaterialFilter, { material: material, id: filterId }), _jsx("style", { children: `.${filterId} .recharts-radar-polygon > .recharts-polygon { filter: url(#${filterId}); }` })] })), _jsx(RadarSelectionLayer, { seriesKey: seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined), hidden: series.hide, children: _jsx(motion.g, { "data-kind-ui": "radar-reveal", initial: false, children: _jsx(StableRadar, { activeDot: radarActiveDot, ...props, ...(props.shape === undefined ? { shape: renderRadarEntrance } : {}), ...(selectionDot !== undefined ? { dot: selectionDot } : {}), id: series.id, hide: series.hide, isAnimationActive: false, zIndex: 0, ...(stroke !== undefined
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
return (_jsxs(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.area, children: [materialized && (_jsxs(_Fragment, { children: [_jsx(PolarMaterialFilter, { material: material, id: filterId }), _jsx("style", { children: `.${filterId} .recharts-radar-polygon > .recharts-polygon { filter: url(#${filterId}); }` })] })), _jsx(SeriesInteractionLayer, { seriesKey: seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined), hidden: series.hide, children: _jsx(RadarSelectionLayer, { seriesKey: seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined), hidden: series.hide, children: _jsx(motion.g, { "data-kind-ui": "radar-reveal", initial: false, children: _jsx(StableRadar, { activeDot: radarActiveDot, ...props, ...(interaction.onClick !== undefined ? { onClick: interaction.onClick } : {}), ...(props.shape === undefined ? { shape: renderRadarEntrance } : {}), ...(selectionDot !== undefined ? { dot: selectionDot } : {}), id: series.id, hide: series.hide, isAnimationActive: false, zIndex: 0, ...(stroke !== undefined
|
|
108
|
+
? { stroke }
|
|
109
|
+
: series.color !== undefined
|
|
110
|
+
? { stroke: series.color }
|
|
111
|
+
: {}), ...(fill !== undefined
|
|
112
|
+
? { fill }
|
|
113
|
+
: series.color !== undefined
|
|
114
|
+
? { fill: series.color }
|
|
115
|
+
: {}), className: ["kind-ui-radar-series", filterId, className].filter(Boolean).join(" ") }) }) }) })] }));
|
|
108
116
|
}
|
|
109
117
|
// Recharts computes every arc; the entrance masks its paint without changing its path.
|
|
110
118
|
function RadialEntranceSector(props) {
|
|
119
|
+
const categories = use(RadialCategory);
|
|
120
|
+
const interaction = useChartInteraction();
|
|
121
|
+
const key = categories?.bound ? categories.key(props.payload) : undefined;
|
|
122
|
+
const content = useRadialSector(props);
|
|
123
|
+
const interactive = key !== undefined &&
|
|
124
|
+
interaction.interactive &&
|
|
125
|
+
interaction.markActivation &&
|
|
126
|
+
interaction.eligible.includes(key);
|
|
127
|
+
const focusRef = useInteractionFocus(key, interactive);
|
|
128
|
+
return key === undefined ? (content) : (_jsx(EmphasisMark, { ref: focusRef, "data-interaction-focus-key": interactive ? key : undefined, target: { kind: "category", key, scope: "radial", seriesKey: key }, role: interactive ? "button" : undefined, tabIndex: interactive ? 0 : undefined, "aria-label": interactive ? `${interaction.mode === "focus" ? "Highlight" : "Toggle"} ${key}` : undefined, "aria-pressed": interactive
|
|
129
|
+
? interaction.mode === "focus"
|
|
130
|
+
? interaction.selected === key
|
|
131
|
+
: true
|
|
132
|
+
: undefined, onKeyDown: (event) => {
|
|
133
|
+
if (interactive &&
|
|
134
|
+
event.target === event.currentTarget &&
|
|
135
|
+
!event.defaultPrevented &&
|
|
136
|
+
(event.key === "Enter" || event.key === " ")) {
|
|
137
|
+
if (!event.repeat)
|
|
138
|
+
interaction.activate({ kind: "category", key }, "mark", event);
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
}
|
|
141
|
+
}, children: content }));
|
|
142
|
+
}
|
|
143
|
+
function useRadialSector(props) {
|
|
111
144
|
const { reveal, progress, direction } = use(RadialMotion);
|
|
112
145
|
const id = `kind-ui-radial-entrance-${useId().replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
113
146
|
const group = useRef(null);
|
|
@@ -139,9 +172,13 @@ function RadialEntranceSector(props) {
|
|
|
139
172
|
/** Series identity is independent from native category payload/Cell identity. */
|
|
140
173
|
export function RadialBarSeries({ seriesKey, hide, fill, className, material = "plain", ...props }) {
|
|
141
174
|
const categories = use(RadialCategory);
|
|
142
|
-
const { config } = useChart();
|
|
175
|
+
const { config, paints } = useChart();
|
|
176
|
+
const owner = useChartInteraction();
|
|
177
|
+
const filtered = categories?.bound
|
|
178
|
+
? filterCategoryRows(categories.originalData, categories.key, owner.visible, props.children)
|
|
179
|
+
: undefined;
|
|
143
180
|
const children = categories
|
|
144
|
-
? categoryCells(categories.data, categories.key, config, props.children, fill)
|
|
181
|
+
? categoryCells(categories.data, categories.key, config, paints, filtered?.children ?? props.children, fill)
|
|
145
182
|
: props.children;
|
|
146
183
|
const series = usePolarSeries("RadialBarSeries", { ...props, seriesKey, hide }, [
|
|
147
184
|
props.dataKey,
|
|
@@ -153,17 +190,26 @@ export function RadialBarSeries({ seriesKey, hide, fill, className, material = "
|
|
|
153
190
|
props.maxBarSize,
|
|
154
191
|
props.minPointSize,
|
|
155
192
|
]);
|
|
193
|
+
const interaction = useSeriesInteraction(seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined), series.hide, hide === true, undefined, props.onClick);
|
|
156
194
|
const filterId = `kind-ui-polar-${useId().replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
157
195
|
const materialized = material !== "plain" &&
|
|
158
196
|
(props.shape === undefined || typeof props.shape === "boolean") &&
|
|
159
197
|
(props.activeShape === undefined || typeof props.activeShape === "boolean") &&
|
|
160
198
|
props.filter === undefined &&
|
|
161
199
|
props.style?.filter === undefined;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
200
|
+
const onClick = categories?.bound
|
|
201
|
+
? (...args) => {
|
|
202
|
+
props.onClick?.(...args);
|
|
203
|
+
owner.activate({ kind: "category", key: categories.key(args[0].payload) }, "mark", args[2]);
|
|
204
|
+
}
|
|
205
|
+
: interaction.onClick;
|
|
206
|
+
return (_jsxs(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.bar, children: [materialized && _jsx(PolarMaterialFilter, { material: material, id: filterId }), _jsx(SeriesInteractionLayer, { seriesKey: seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined), hidden: series.hide, children: _jsx(motion.g, { "data-kind-ui": "radial-bar-reveal", initial: false, children: _jsx(RadialBar, { ...props, ...(onClick !== undefined ? { onClick } : {}), ...(props.shape === undefined ? { shape: RadialEntranceSector } : {}), ...(categories?.bound && props.shape === undefined && props.activeShape === undefined
|
|
207
|
+
? { activeShape: RadialEntranceSector }
|
|
208
|
+
: {}), ...(props.background === true
|
|
209
|
+
? { background: { fill: "var(--kind-ui-radial-track, #f1f1f1)" } }
|
|
210
|
+
: {}), ...(materialized ? { filter: `url(#${filterId})` } : {}), id: series.id, hide: series.hide, isAnimationActive: false, zIndex: 0, ...(fill !== undefined
|
|
211
|
+
? { fill }
|
|
212
|
+
: series.color !== undefined
|
|
213
|
+
? { fill: series.color }
|
|
214
|
+
: {}), className: ["kind-ui-radial-bar-series", className].filter(Boolean).join(" "), children: children }) }) })] }));
|
|
169
215
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { cloneElement, createContext, isValidElement, use, useCallback, useEffect, useId, useLayoutEffect, useMemo, useState, } from "react";
|
|
4
4
|
import { Dot } from "recharts";
|
|
5
5
|
import { useChart } from "./chart-context.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useChartInteraction } from "./chart-interaction.js";
|
|
7
7
|
import { useLineInteraction } from "./line-chart.js";
|
|
8
8
|
const Context = createContext(null);
|
|
9
9
|
const Mode = createContext(false);
|
|
@@ -11,8 +11,11 @@ const Mode = createContext(false);
|
|
|
11
11
|
export function RadarSelectionProvider({ selection = "none", selectedSeries, onSelectedSeriesChange, children, }) {
|
|
12
12
|
if (selectedSeries !== undefined && onSelectedSeriesChange === undefined)
|
|
13
13
|
throw new Error("RadarChart requires onSelectedSeriesChange for controlled selectedSeries");
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const interaction = useChartInteraction();
|
|
15
|
+
if (interaction.mode === "focus" &&
|
|
16
|
+
(selection !== "none" || selectedSeries !== undefined || onSelectedSeriesChange !== undefined))
|
|
17
|
+
throw new Error("RadarChart selection controls conflict with Root focus; choose one selection owner");
|
|
18
|
+
const enabled = selection === "series";
|
|
16
19
|
const [internal, setInternal] = useState(null);
|
|
17
20
|
const [series, setSeries] = useState(() => new Map());
|
|
18
21
|
const register = useCallback((owner, key, visible) => {
|
|
@@ -51,7 +54,10 @@ export function RadarSelectionProvider({ selection = "none", selectedSeries, onS
|
|
|
51
54
|
const value = useMemo(() => ({ enabled, selected, register, toggle }), [enabled, selected, register, toggle]);
|
|
52
55
|
return (_jsx(Mode, { value: enabled, children: _jsx(Context, { value: value, children: _jsx("div", { style: { display: "contents" }, onKeyDown: (event) => {
|
|
53
56
|
if (event.key === "Escape" && !event.defaultPrevented && enabled && requested !== null)
|
|
54
|
-
|
|
57
|
+
queueMicrotask(() => {
|
|
58
|
+
if (!event.defaultPrevented)
|
|
59
|
+
change(null);
|
|
60
|
+
});
|
|
55
61
|
}, children: children }) }) }));
|
|
56
62
|
}
|
|
57
63
|
/** An extra paint/control layer preserves native shape, data, refs and event handlers. */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Transition } from "motion/react";
|
|
2
|
+
export type RevealDirection = "left-to-right" | "right-to-left" | "center-out" | "edges-in";
|
|
3
|
+
type Options = {
|
|
4
|
+
revealDirection?: RevealDirection;
|
|
5
|
+
revealDurationMs?: number;
|
|
6
|
+
revealEasing?: Transition["ease"];
|
|
7
|
+
};
|
|
8
|
+
/** Entrance paint only: percentage rectangles never transform native series geometry. */
|
|
9
|
+
export declare function RevealClip({ id, family, combo, options, finish, }: {
|
|
10
|
+
id: string;
|
|
11
|
+
family?: "line" | "area";
|
|
12
|
+
combo?: boolean;
|
|
13
|
+
options: Options;
|
|
14
|
+
finish?: () => void;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { motion } from "motion/react";
|
|
4
|
+
/** Entrance paint only: percentage rectangles never transform native series geometry. */
|
|
5
|
+
export function RevealClip({ id, family, combo = false, options, finish, }) {
|
|
6
|
+
const direction = options.revealDirection ?? "left-to-right";
|
|
7
|
+
const split = direction === "edges-in";
|
|
8
|
+
const initialX = direction === "right-to-left" ? "100%" : direction === "center-out" ? "50%" : "0%";
|
|
9
|
+
const transition = {
|
|
10
|
+
duration: Math.max(0, options.revealDurationMs ?? 1000) / 1000,
|
|
11
|
+
ease: options.revealEasing ?? [0.25, 0.1, 0.25, 1],
|
|
12
|
+
};
|
|
13
|
+
return (_jsx("defs", { children: _jsxs("clipPath", { id: id, clipPathUnits: "userSpaceOnUse", "data-reveal-direction": direction, children: [_jsx(motion.rect, { ...(combo
|
|
14
|
+
? { "data-combo-reveal": family }
|
|
15
|
+
: family === "area"
|
|
16
|
+
? { "data-area-reveal": "" }
|
|
17
|
+
: {}), y: 0, height: "100%", initial: { attrX: initialX, width: "0%" }, animate: { attrX: "0%", width: split ? "50%" : "100%" }, ...(finish ? { onAnimationComplete: finish } : {}), transition: transition }), split && (_jsx(motion.rect, { y: 0, height: "100%", initial: { attrX: "100%", width: "0%" }, animate: { attrX: "50%", width: "50%" }, transition: transition }))] }) }));
|
|
18
|
+
}
|
package/dist/root.d.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentPropsWithRef } from "react";
|
|
2
|
+
import { type ChartInteractionConfig, type ChartVisibilityProps } from "./chart-interaction.js";
|
|
2
3
|
import type { SeriesConfig } from "./types.js";
|
|
3
4
|
export type RootProps = ComponentPropsWithRef<"div"> & {
|
|
4
5
|
config: SeriesConfig;
|
|
5
6
|
emphasis?: "auto" | "none";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
onVisibleSeriesChange?: never;
|
|
9
|
-
} | {
|
|
10
|
-
visibleSeries: readonly string[];
|
|
11
|
-
onVisibleSeriesChange?: (next: string[]) => void;
|
|
12
|
-
});
|
|
7
|
+
interaction?: ChartInteractionConfig;
|
|
8
|
+
} & ChartVisibilityProps;
|
|
13
9
|
/** Scopes presentation metadata and CSS colors; the consumer owns chart geometry and state. */
|
|
14
|
-
export declare function Root({ config, emphasis, visibleSeries, onVisibleSeriesChange, style, children, ...props }: RootProps): import("react").JSX.Element;
|
|
10
|
+
export declare function Root({ config, emphasis, interaction, defaultVisibleSeries, visibleSeries, onVisibleSeriesChange, style, children, ...props }: RootProps): import("react").JSX.Element;
|
package/dist/root.js
CHANGED
|
@@ -1,29 +1,64 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
3
4
|
import { ChartContext } from "./chart-context.js";
|
|
5
|
+
import { ChartInteractionProvider, ChartInteractionStatus, useChartInteraction, } from "./chart-interaction.js";
|
|
4
6
|
import { EmphasisProvider, useEmphasisActions } from "./emphasis.js";
|
|
7
|
+
import { colorResourceId, colorStopToken, resolveSeriesColor, } from "./series-color.js";
|
|
8
|
+
import { resolveSeriesConfig } from "./series-label.js";
|
|
9
|
+
import { SeriesColorDefinitions } from "./series-paint.js";
|
|
10
|
+
import { StylesheetWarning } from "./stylesheet-warning.js";
|
|
5
11
|
/** Scopes presentation metadata and CSS colors; the consumer owns chart geometry and state. */
|
|
6
|
-
export function Root({ config, emphasis = "auto", visibleSeries, onVisibleSeriesChange, style, children, ...props }) {
|
|
7
|
-
if (onVisibleSeriesChange &&
|
|
8
|
-
throw new Error("Root requires visibleSeries when onVisibleSeriesChange is provided");
|
|
12
|
+
export function Root({ config, emphasis = "auto", interaction, defaultVisibleSeries, visibleSeries, onVisibleSeriesChange, style, children, ...props }) {
|
|
13
|
+
if (onVisibleSeriesChange && visibleSeries === undefined && defaultVisibleSeries === undefined)
|
|
14
|
+
throw new Error("Root requires visibleSeries or defaultVisibleSeries when onVisibleSeriesChange is provided");
|
|
15
|
+
if (visibleSeries !== undefined && defaultVisibleSeries !== undefined)
|
|
16
|
+
throw new Error("Root visibility cannot be both controlled and defaulted");
|
|
17
|
+
const id = useId();
|
|
9
18
|
const colors = {};
|
|
19
|
+
const paints = {};
|
|
20
|
+
const colorStops = {};
|
|
10
21
|
for (const [key, item] of Object.entries(config)) {
|
|
11
22
|
if (!/^[a-zA-Z][\w-]*$/.test(key))
|
|
12
23
|
throw new Error(`Chart series key "${key}" must start with a letter and contain only letters, numbers, underscores or hyphens`);
|
|
13
|
-
|
|
24
|
+
const resolved = resolveSeriesColor(item.color, key);
|
|
25
|
+
colors[`--color-${key}`] = resolved.colors[0];
|
|
26
|
+
for (const [index, color] of resolved.colors.entries())
|
|
27
|
+
colors[colorStopToken(key, index)] = color;
|
|
28
|
+
colorStops[key] = resolved;
|
|
29
|
+
const gradientId = colorResourceId(id, key);
|
|
30
|
+
const gradient = resolved.colors.length > 1;
|
|
31
|
+
paints[key] = gradient ? `url(#${gradientId})` : `var(--color-${key})`;
|
|
32
|
+
colors[colorStopToken(key, "gradient")] = gradient
|
|
33
|
+
? `linear-gradient(to right in srgb, ${resolved.offsets.map((offset, index) => `var(${colorStopToken(key, index)}) ${offset * 100}%`).join(", ")})`
|
|
34
|
+
: `var(--color-${key})`;
|
|
14
35
|
}
|
|
15
36
|
const value = {
|
|
16
|
-
config,
|
|
37
|
+
config: resolveSeriesConfig(config),
|
|
38
|
+
paints,
|
|
39
|
+
colorId: id,
|
|
40
|
+
colorStops,
|
|
17
41
|
...(visibleSeries ? { visibleSeries } : {}),
|
|
18
42
|
...(onVisibleSeriesChange ? { onVisibleSeriesChange } : {}),
|
|
19
43
|
};
|
|
20
|
-
return (_jsx(
|
|
44
|
+
return (_jsx(ChartInteractionProvider, { config: value.config, ...(interaction ? { interaction } : {}), ...(visibleSeries !== undefined
|
|
45
|
+
? { visibleSeries, ...(onVisibleSeriesChange ? { onVisibleSeriesChange } : {}) }
|
|
46
|
+
: defaultVisibleSeries !== undefined
|
|
47
|
+
? { defaultVisibleSeries, ...(onVisibleSeriesChange ? { onVisibleSeriesChange } : {}) }
|
|
48
|
+
: {}), children: (visible, change) => (_jsx(ChartContext, { value: {
|
|
49
|
+
...value,
|
|
50
|
+
...(visible !== undefined ? { visibleSeries: visible } : {}),
|
|
51
|
+
...(change ? { onVisibleSeriesChange: change } : {}),
|
|
52
|
+
}, children: _jsx(EmphasisProvider, { enabled: emphasis === "auto", children: _jsxs(RootFrame, { ...props, style: { ...colors, ...style }, children: [process.env.NODE_ENV === "development" && _jsx(StylesheetWarning, {}), Object.values(colorStops).some((stops) => stops.colors.length > 1) && (_jsx("svg", { "aria-hidden": "true", focusable: "false", width: 0, height: 0, style: { position: "absolute" }, "data-kind-ui": "color-resources", children: _jsx(SeriesColorDefinitions, {}) })), children] }) }) })) }));
|
|
21
53
|
}
|
|
22
54
|
function RootFrame({ children, ...props }) {
|
|
23
55
|
const { reset } = useEmphasisActions();
|
|
24
|
-
|
|
25
|
-
|
|
56
|
+
const interaction = useChartInteraction();
|
|
57
|
+
return (
|
|
58
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: Delegates Escape from descendants after consumer handlers; this frame is not a control.
|
|
59
|
+
_jsxs("div", { ...props, "data-kind-ui": "chart", onKeyDown: (event) => {
|
|
60
|
+
props.onKeyDown?.(event);
|
|
61
|
+
if (event.key === "Escape" && interaction.reset(event))
|
|
26
62
|
reset();
|
|
27
|
-
|
|
28
|
-
}, children: children }));
|
|
63
|
+
}, children: [children, _jsx(ChartInteractionStatus, {})] }));
|
|
29
64
|
}
|
package/dist/sankey-chart.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type SankeyChartProps = Omit<NativeProps, "data" | "node" | "link" | "onC
|
|
|
14
14
|
data: SankeyFlowData;
|
|
15
15
|
/** Explicit node-ID metadata; enables matching default Kind node/source-link paint. */
|
|
16
16
|
nodeConfig?: SankeyNodeConfig | undefined;
|
|
17
|
+
/** Explicit node-focus binding; Root must use kind node and mode focus. */
|
|
18
|
+
interactionBinding?: "root";
|
|
17
19
|
node?: Exclude<NativeProps["node"], (props: NativeNodeProps) => ReactNode> | ((props: SankeyNodeProps) => ReactNode);
|
|
18
20
|
link?: Exclude<NativeProps["link"], (props: NativeLinkProps) => ReactNode> | ((props: SankeyLinkProps) => ReturnType<NonNullable<Extract<NativeProps["link"], (props: NativeLinkProps) => ReactNode>>>);
|
|
19
21
|
onClick?: IdentityEvent;
|
|
@@ -21,6 +23,8 @@ export type SankeyChartProps = Omit<NativeProps, "data" | "node" | "link" | "onC
|
|
|
21
23
|
onMouseLeave?: IdentityEvent;
|
|
22
24
|
/** Kind SankeyLink paint flows along unchanged paths; custom renderers retain ownership. */
|
|
23
25
|
animate?: boolean | SankeyAnimation;
|
|
26
|
+
loading?: boolean | undefined;
|
|
27
|
+
loadingLabel?: string | undefined;
|
|
24
28
|
empty?: ReactNode;
|
|
25
29
|
};
|
|
26
30
|
export declare const SankeyMotion: import("react").Context<{
|
|
@@ -29,5 +33,5 @@ export declare const SankeyMotion: import("react").Context<{
|
|
|
29
33
|
width: number;
|
|
30
34
|
}>;
|
|
31
35
|
/** Recharts owns layout, renderers, labels and events. Zero flows remain in the data alternative. */
|
|
32
|
-
export declare function SankeyChart({ data, nodeConfig, animate, empty, ...props }: SankeyChartProps): import("react").JSX.Element;
|
|
36
|
+
export declare function SankeyChart({ data, nodeConfig, interactionBinding, animate, loading, loadingLabel, empty, ...props }: SankeyChartProps): import("react").JSX.Element;
|
|
33
37
|
export {};
|
package/dist/sankey-chart.js
CHANGED
|
@@ -3,8 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { animate as animateValue, useMotionValue } from "motion/react";
|
|
4
4
|
import { createContext, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
|
|
5
5
|
import { Sankey as EngineSankey, useChartHeight, useChartWidth, } from "recharts";
|
|
6
|
+
import { useOptionalChartInteraction } from "./chart-interaction.js";
|
|
7
|
+
import { LoadingSkeletonSurface, LoadingStatus, useLoadingSeed } from "./loading-skeleton.js";
|
|
8
|
+
import { StandaloneLoadingDesign } from "./loading-standalone-designs.js";
|
|
6
9
|
import { SankeyColors } from "./sankey-colors.js";
|
|
7
10
|
import { prepareSankeyData } from "./sankey-data.js";
|
|
11
|
+
import { SankeyFocusMark } from "./sankey-interaction.js";
|
|
8
12
|
import { SankeyLink, SankeyNode, } from "./sankey-marks.js";
|
|
9
13
|
const configuredNode = (shape) => _jsx(SankeyNode, { ...shape });
|
|
10
14
|
const configuredLink = (shape) => _jsx(SankeyLink, { ...shape });
|
|
@@ -27,7 +31,25 @@ const snapshot = () => window.matchMedia(query).matches;
|
|
|
27
31
|
const serverSnapshot = () => true;
|
|
28
32
|
export const SankeyMotion = createContext({ reveal: false, progress: null, width: 1 });
|
|
29
33
|
/** Recharts owns layout, renderers, labels and events. Zero flows remain in the data alternative. */
|
|
30
|
-
export function SankeyChart({ data, nodeConfig, animate = false, empty = "No positive flows", ...props }) {
|
|
34
|
+
export function SankeyChart({ data, nodeConfig, interactionBinding, animate = false, loading, loadingLabel, empty = "No positive flows", ...props }) {
|
|
35
|
+
const interaction = useOptionalChartInteraction();
|
|
36
|
+
if (interactionBinding &&
|
|
37
|
+
(!interaction || interaction.kind !== "node" || interaction.mode !== "focus"))
|
|
38
|
+
throw new Error("SankeyChart Root interaction binding requires node focus");
|
|
39
|
+
const adjacent = new Map();
|
|
40
|
+
for (const link of data.links) {
|
|
41
|
+
const source = typeof link.source === "string" ? link.source : data.nodes[link.source]?.id;
|
|
42
|
+
const target = typeof link.target === "string" ? link.target : data.nodes[link.target]?.id;
|
|
43
|
+
if (source && target) {
|
|
44
|
+
const from = adjacent.get(source) ?? new Set();
|
|
45
|
+
from.add(target);
|
|
46
|
+
adjacent.set(source, from);
|
|
47
|
+
const to = adjacent.get(target) ?? new Set();
|
|
48
|
+
to.add(source);
|
|
49
|
+
adjacent.set(target, to);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const loadingSeed = useLoadingSeed(loading);
|
|
31
53
|
const duration = typeof animate === "object" ? (animate.revealDurationMs ?? 450) : 450;
|
|
32
54
|
if (!Number.isFinite(duration) || duration < 0)
|
|
33
55
|
throw new Error("Sankey revealDurationMs must be finite and nonnegative");
|
|
@@ -63,15 +85,21 @@ export function SankeyChart({ data, nodeConfig, animate = false, empty = "No pos
|
|
|
63
85
|
const [interrupted, setInterrupted] = useState(false);
|
|
64
86
|
const [size, setSize] = useState(null);
|
|
65
87
|
const measured = useRef(null);
|
|
88
|
+
const previousLoading = useRef(loading);
|
|
89
|
+
const completing = previousLoading.current === true && loading !== true;
|
|
66
90
|
const onSize = useCallback((width, height) => {
|
|
67
91
|
const prior = measured.current;
|
|
68
92
|
if (prior?.width === width && prior?.height === height)
|
|
69
93
|
return;
|
|
70
|
-
if (prior)
|
|
94
|
+
if (prior && !loading && !completing)
|
|
71
95
|
setInterrupted(true);
|
|
72
96
|
measured.current = { width, height };
|
|
73
97
|
setSize(measured.current);
|
|
74
|
-
}, []);
|
|
98
|
+
}, [loading, completing]);
|
|
99
|
+
useLayoutEffect(() => {
|
|
100
|
+
if (loading)
|
|
101
|
+
setInterrupted(false);
|
|
102
|
+
}, [loading]);
|
|
75
103
|
const inputs = [
|
|
76
104
|
duration,
|
|
77
105
|
data,
|
|
@@ -91,17 +119,20 @@ export function SankeyChart({ data, nodeConfig, animate = false, empty = "No pos
|
|
|
91
119
|
const started = useRef(false);
|
|
92
120
|
const previous = useRef(inputs);
|
|
93
121
|
useLayoutEffect(() => {
|
|
94
|
-
if (
|
|
122
|
+
if (!loading &&
|
|
123
|
+
!completing &&
|
|
124
|
+
inputs.some((value, index) => (index !== 0 || started.current) && value !== previous.current[index]))
|
|
95
125
|
setInterrupted(true);
|
|
96
126
|
previous.current = inputs;
|
|
127
|
+
previousLoading.current = loading;
|
|
97
128
|
});
|
|
98
|
-
const enabled = Boolean(animate) && !reduced;
|
|
129
|
+
const enabled = Boolean(animate) && !reduced && !loading;
|
|
99
130
|
const previousEnabled = useRef(enabled);
|
|
100
131
|
useLayoutEffect(() => {
|
|
101
|
-
if (previousEnabled.current && !enabled)
|
|
132
|
+
if (previousEnabled.current && !enabled && !loading)
|
|
102
133
|
setInterrupted(true);
|
|
103
134
|
previousEnabled.current = enabled;
|
|
104
|
-
}, [enabled]);
|
|
135
|
+
}, [enabled, loading]);
|
|
105
136
|
const reveal = enabled && !interrupted && size !== null;
|
|
106
137
|
const progress = useMotionValue(1);
|
|
107
138
|
useLayoutEffect(() => {
|
|
@@ -133,10 +164,30 @@ export function SankeyChart({ data, nodeConfig, animate = false, empty = "No pos
|
|
|
133
164
|
links.some((link) => link.value * minimumScale === 0))
|
|
134
165
|
throw new Error("Sankey flows exceed native renderer numeric limits at this size; use explicitly rescaled input units.");
|
|
135
166
|
}
|
|
136
|
-
return (_jsx(SankeyColors, { value: nodeConfig, children: _jsx(SankeyMotion, { value: { reveal, progress, width: size?.width ?? 1 }, children: _jsxs("div", { "data-kind-ui": "sankey", onPointerDownCapture: () =>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
return (_jsx(SankeyColors, { value: nodeConfig, children: _jsx(SankeyMotion, { value: { reveal, progress, width: size?.width ?? 1 }, children: _jsxs("div", { "data-kind-ui": "sankey", onPointerDownCapture: () => {
|
|
168
|
+
if (!loading)
|
|
169
|
+
setInterrupted(true);
|
|
170
|
+
}, onFocusCapture: () => {
|
|
171
|
+
if (!loading)
|
|
172
|
+
setInterrupted(true);
|
|
173
|
+
}, style: { position: "relative", width: "fit-content", height: "fit-content" }, children: [_jsx("div", { "data-kind-ui": "sankey-content", "data-loading": loading || undefined, "aria-busy": loading, "aria-hidden": loading || undefined, inert: loading || undefined, style: { display: "contents" }, children: _jsxs(EngineSankey, { ...props, ...(nodeConfig
|
|
174
|
+
? {
|
|
175
|
+
node: props.node ?? configuredNode,
|
|
176
|
+
link: props.link ?? configuredLink,
|
|
177
|
+
}
|
|
178
|
+
: {}), onClick: (item, type, event) => {
|
|
179
|
+
props.onClick?.(item, type, event);
|
|
180
|
+
if (interactionBinding && type === "node")
|
|
181
|
+
interaction?.activate({ kind: "node", key: item.payload.id }, "mark", event);
|
|
182
|
+
}, ...(interactionBinding
|
|
183
|
+
? {
|
|
184
|
+
node: props.node ??
|
|
185
|
+
((shape) => (_jsx(SankeyFocusMark, { nodeKey: shape.payload.id, adjacent: adjacent, children: _jsx(SankeyNode, { ...shape }) }))),
|
|
186
|
+
link: props.link ??
|
|
187
|
+
((shape) => (_jsx(SankeyFocusMark, { endpoints: [
|
|
188
|
+
shape.payload.source.id,
|
|
189
|
+
shape.payload.target.id,
|
|
190
|
+
], adjacent: adjacent, children: _jsx(SankeyLink, { ...shape }) }))),
|
|
191
|
+
}
|
|
192
|
+
: {}), data: drawable && links.length ? { nodes, links } : { nodes: [], links: [] }, children: [_jsx(NativeSize, { onSize: onSize }), props.children] }) }), loading && (_jsx(LoadingSkeletonSurface, { family: "sankey", design: (seed) => _jsx(StandaloneLoadingDesign, { family: "sankey", seed: seed }), width: "100%", height: "100%", seed: loadingSeed, animation: { revealDurationMs: duration } })), _jsx(LoadingStatus, { loading: loading, label: loadingLabel }), !loading && !links.length ? (_jsx("div", { role: "status", style: { position: "absolute", inset: 0 }, children: empty })) : !loading && !drawable ? (_jsx("div", { role: "status", style: { position: "absolute", inset: 0 }, children: "Insufficient space for flows; use the data table" })) : null] }) }) }));
|
|
142
193
|
}
|
package/dist/sankey-colors.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
/** Sankey IDs are arbitrary nonempty strings, independent from Root CSS variable keys. */
|
|
2
3
|
export type SankeyNodeConfig = Readonly<Record<string, {
|
|
3
4
|
label: string;
|
|
4
5
|
color: string;
|
|
6
|
+
icon?: ReactNode;
|
|
5
7
|
}>>;
|
|
6
8
|
export declare const SankeyColors: import("react").Context<Readonly<Record<string, {
|
|
7
9
|
label: string;
|
|
8
10
|
color: string;
|
|
11
|
+
icon?: ReactNode;
|
|
9
12
|
}>> | undefined>;
|
|
10
13
|
export declare function sankeyColor(config: SankeyNodeConfig | undefined, id: string): string | undefined;
|
package/dist/sankey-finish.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SankeyFinish = "plain" | "
|
|
1
|
+
export type SankeyFinish = "plain" | "clay" | "glow";
|
|
2
2
|
/** Sankey-local surface treatment. Never displace the quantitative silhouette. */
|
|
3
3
|
export declare function SankeyFinishFilter({ id, finish, thickness, bounds, }: {
|
|
4
4
|
id: string;
|
package/dist/sankey-finish.js
CHANGED
|
@@ -6,5 +6,5 @@ export function SankeyFinishFilter({ id, finish, thickness, bounds, }) {
|
|
|
6
6
|
const relief = Math.min(4, thickness / 4);
|
|
7
7
|
// A 0.6px blur disappeared in Chromium probes; retain a measured thin-flow halo.
|
|
8
8
|
const halo = Math.min(1, Math.max(0.85, thickness / 4));
|
|
9
|
-
return (_jsxs("filter", { id: id, filterUnits: "userSpaceOnUse", primitiveUnits: "userSpaceOnUse", ...bounds, colorInterpolationFilters: "sRGB", children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), finish === "clay" ? (_jsxs(_Fragment, { children: [_jsx("feOffset", { in: "footprint", dx: relief, dy: relief, result: "lower" }), _jsx("feComposite", { in: "footprint", in2: "lower", operator: "arithmetic", k2: 1, k3: -1, result: "top" }), _jsx("feGaussianBlur", { in: "top", stdDeviation: relief * 0.8, result: "softTop" }), _jsx("feFlood", { floodColor: "#fff", floodOpacity: 0.48 }), _jsx("feComposite", { in2: "softTop", operator: "in", result: "light" }), _jsx("feOffset", { in: "footprint", dx: -relief, dy: -relief, result: "upper" }), _jsx("feComposite", { in: "footprint", in2: "upper", operator: "arithmetic", k2: 1, k3: -1, result: "bottom" }), _jsx("feGaussianBlur", { in: "bottom", stdDeviation: relief * 0.8, result: "softBottom" }), _jsx("feFlood", { floodColor: "#17212b", floodOpacity: 0.26 }), _jsx("feComposite", { in2: "softBottom", operator: "in", result: "shade" }), _jsx("feComposite", { in: "shade", in2: "SourceGraphic", operator: "atop", result: "shaded" }), _jsx("feComposite", { in: "light", in2: "shaded", operator: "atop", result: "body" })] })) : (_jsxs(_Fragment, { children: [_jsx("feMorphology", { in: "footprint", operator: "erode", radius: edge, result: "inside" }), _jsx("feComposite", { in: "footprint", in2: "inside", operator: "out", result: "edge" }),
|
|
9
|
+
return (_jsxs("filter", { id: id, filterUnits: "userSpaceOnUse", primitiveUnits: "userSpaceOnUse", ...bounds, colorInterpolationFilters: "sRGB", children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), finish === "clay" ? (_jsxs(_Fragment, { children: [_jsx("feOffset", { in: "footprint", dx: relief, dy: relief, result: "lower" }), _jsx("feComposite", { in: "footprint", in2: "lower", operator: "arithmetic", k2: 1, k3: -1, result: "top" }), _jsx("feGaussianBlur", { in: "top", stdDeviation: relief * 0.8, result: "softTop" }), _jsx("feFlood", { floodColor: "#fff", floodOpacity: 0.48 }), _jsx("feComposite", { in2: "softTop", operator: "in", result: "light" }), _jsx("feOffset", { in: "footprint", dx: -relief, dy: -relief, result: "upper" }), _jsx("feComposite", { in: "footprint", in2: "upper", operator: "arithmetic", k2: 1, k3: -1, result: "bottom" }), _jsx("feGaussianBlur", { in: "bottom", stdDeviation: relief * 0.8, result: "softBottom" }), _jsx("feFlood", { floodColor: "#17212b", floodOpacity: 0.26 }), _jsx("feComposite", { in2: "softBottom", operator: "in", result: "shade" }), _jsx("feComposite", { in: "shade", in2: "SourceGraphic", operator: "atop", result: "shaded" }), _jsx("feComposite", { in: "light", in2: "shaded", operator: "atop", result: "body" })] })) : (_jsxs(_Fragment, { children: [_jsx("feMorphology", { in: "footprint", operator: "erode", radius: edge, result: "inside" }), _jsx("feComposite", { in: "footprint", in2: "inside", operator: "out", result: "edge" }), _jsx("feGaussianBlur", { in: "edge", stdDeviation: edge, result: "softEdge" }), _jsx("feFlood", { floodColor: "#fff", floodOpacity: 0.8 }), _jsx("feComposite", { in2: "softEdge", operator: "in", result: "light" }), _jsx("feComposite", { in: "light", in2: "SourceGraphic", operator: "atop", result: "body" })] })), finish !== "glow" ? (_jsxs(_Fragment, { children: [_jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.65 0.3", numOctaves: 1, seed: 7, result: "fiber" }), _jsx("feFlood", { floodColor: "#fff", floodOpacity: 0.025 }), _jsx("feComposite", { in2: "fiber", operator: "in", result: "grain" }), _jsx("feComposite", { in: "grain", in2: "body", operator: "atop" })] })) : (_jsxs(_Fragment, { children: [_jsx("feGaussianBlur", { in: "SourceAlpha", stdDeviation: halo, result: "blur" }), _jsx("feComposite", { in: "blur", in2: "footprint", operator: "out", result: "exterior" }), _jsx("feFlood", { floodColor: "#fff", floodOpacity: 0.12 }), _jsx("feComposite", { in2: "exterior", operator: "in", result: "halo" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "halo" }), _jsx("feMergeNode", { in: "body" })] })] }))] }));
|
|
10
10
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/** Node-only focus includes incident links and endpoint nodes; no flow filtering. */
|
|
3
|
+
export declare function SankeyFocusMark({ nodeKey, endpoints, adjacent, children, }: {
|
|
4
|
+
nodeKey?: string;
|
|
5
|
+
endpoints?: readonly string[];
|
|
6
|
+
adjacent: ReadonlyMap<string, ReadonlySet<string>>;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useChartInteraction } from "./chart-interaction.js";
|
|
4
|
+
import { useEmphasis } from "./emphasis.js";
|
|
5
|
+
/** Node-only focus includes incident links and endpoint nodes; no flow filtering. */
|
|
6
|
+
export function SankeyFocusMark({ nodeKey, endpoints, adjacent, children, }) {
|
|
7
|
+
const interaction = useChartInteraction();
|
|
8
|
+
const key = nodeKey ?? endpoints?.[0] ?? "";
|
|
9
|
+
const emphasis = useEmphasis({ kind: "series", key, scope: "sankey", seriesKey: key });
|
|
10
|
+
const inspected = emphasis.active?.kind === "series" ? emphasis.active.key : null;
|
|
11
|
+
const focus = inspected ?? interaction.selected;
|
|
12
|
+
const related = focus === null ||
|
|
13
|
+
(nodeKey !== undefined
|
|
14
|
+
? nodeKey === focus || adjacent.get(focus)?.has(nodeKey)
|
|
15
|
+
: endpoints?.includes(focus));
|
|
16
|
+
const interactive = nodeKey !== undefined && interaction.markActivation && interaction.eligible.includes(nodeKey);
|
|
17
|
+
return (
|
|
18
|
+
// biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useAriaPropsSupportedByRole: Role, pressed state and handlers are enabled together for opted-in SVG controls.
|
|
19
|
+
_jsx("g", { "data-kind-ui": "sankey-focus-mark", "data-node": nodeKey, role: interactive ? "button" : undefined, tabIndex: interactive ? 0 : undefined, "aria-label": interactive ? `Highlight ${nodeKey}` : undefined, "aria-pressed": interactive ? interaction.selected === nodeKey : undefined, onPointerEnter: (event) => {
|
|
20
|
+
if (event.pointerType !== "touch")
|
|
21
|
+
emphasis.enter("pointer");
|
|
22
|
+
}, onPointerLeave: () => emphasis.leave("pointer"), onPointerCancel: () => emphasis.leave("pointer"), onFocus: () => emphasis.enter("keyboard"), onBlur: () => emphasis.leave("keyboard"), onKeyDown: (event) => {
|
|
23
|
+
if (interactive &&
|
|
24
|
+
event.target === event.currentTarget &&
|
|
25
|
+
!event.defaultPrevented &&
|
|
26
|
+
(event.key === "Enter" || event.key === " ")) {
|
|
27
|
+
if (!event.repeat)
|
|
28
|
+
interaction.activate({ kind: "node", key: nodeKey }, "mark", event);
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
}
|
|
31
|
+
}, children: _jsx("g", { style: { opacity: related ? 1 : 0.28 }, children: children }) }));
|
|
32
|
+
}
|
package/dist/sankey-legend.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { SankeyNodeConfig } from "./sankey-colors.js";
|
|
|
3
3
|
export type SankeyLegendProps = Omit<ComponentPropsWithRef<"ul">, "children"> & {
|
|
4
4
|
/** Share this explicit reference with SankeyChart.nodeConfig. Config order owns legend order. */
|
|
5
5
|
config: SankeyNodeConfig;
|
|
6
|
+
interactionBinding?: "root";
|
|
6
7
|
children?: (item: {
|
|
7
8
|
key: string;
|
|
8
9
|
label: string;
|
|
@@ -11,4 +12,4 @@ export type SankeyLegendProps = Omit<ComponentPropsWithRef<"ul">, "children"> &
|
|
|
11
12
|
}) => ReactNode;
|
|
12
13
|
};
|
|
13
14
|
/** Node identity legend; solid links inherit source color, gradient links use both endpoints. */
|
|
14
|
-
export declare function SankeyLegend({ config, children, ...props }: SankeyLegendProps): import("react").JSX.Element;
|
|
15
|
+
export declare function SankeyLegend({ config, interactionBinding, children, ...props }: SankeyLegendProps): import("react").JSX.Element;
|