@kind-ui/charts 0.1.0 → 0.2.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +657 -865
  3. package/dist/activity-rings.d.ts +2 -1
  4. package/dist/activity-rings.js +6 -3
  5. package/dist/animation.d.ts +11 -3
  6. package/dist/animation.js +18 -10
  7. package/dist/area-chart.d.ts +3 -1
  8. package/dist/area-chart.js +15 -10
  9. package/dist/area-series.d.ts +9 -1
  10. package/dist/area-series.js +35 -8
  11. package/dist/bar-chart.d.ts +10 -3
  12. package/dist/bar-chart.js +17 -4
  13. package/dist/bar-series.d.ts +11 -1
  14. package/dist/bar-series.js +65 -13
  15. package/dist/box-plot.js +2 -2
  16. package/dist/category-cells.d.ts +7 -1
  17. package/dist/category-cells.js +32 -2
  18. package/dist/chart-background-pattern.d.ts +23 -0
  19. package/dist/chart-background-pattern.js +31 -0
  20. package/dist/chart-context.d.ts +6 -2
  21. package/dist/chart-interaction.d.ts +94 -0
  22. package/dist/chart-interaction.js +224 -0
  23. package/dist/combo-chart.d.ts +8 -5
  24. package/dist/combo-chart.js +14 -12
  25. package/dist/configured-line-chart.d.ts +5 -1
  26. package/dist/configured-line-chart.js +8 -5
  27. package/dist/emphasis.d.ts +5 -2
  28. package/dist/emphasis.js +18 -4
  29. package/dist/fill-pattern.d.ts +25 -0
  30. package/dist/fill-pattern.js +23 -0
  31. package/dist/heatmap.d.ts +3 -1
  32. package/dist/heatmap.js +34 -12
  33. package/dist/histogram-chart.js +10 -3
  34. package/dist/index.d.ts +12 -3
  35. package/dist/index.js +8 -1
  36. package/dist/legend.d.ts +2 -0
  37. package/dist/legend.js +28 -7
  38. package/dist/line-chart.d.ts +13 -3
  39. package/dist/line-chart.js +54 -34
  40. package/dist/line-dash.d.ts +8 -0
  41. package/dist/line-dash.js +18 -0
  42. package/dist/line-series.d.ts +9 -1
  43. package/dist/line-series.js +38 -11
  44. package/dist/loading-cartesian-designs.d.ts +6 -0
  45. package/dist/loading-cartesian-designs.js +123 -0
  46. package/dist/loading-motion.d.ts +37 -0
  47. package/dist/loading-motion.js +88 -0
  48. package/dist/loading-polar-designs.d.ts +6 -0
  49. package/dist/loading-polar-designs.js +95 -0
  50. package/dist/loading-skeleton.d.ts +29 -0
  51. package/dist/loading-skeleton.js +143 -0
  52. package/dist/loading-standalone-designs.d.ts +5 -0
  53. package/dist/loading-standalone-designs.js +126 -0
  54. package/dist/percent-stack.d.ts +18 -0
  55. package/dist/percent-stack.js +33 -0
  56. package/dist/pie-chart.d.ts +7 -3
  57. package/dist/pie-chart.js +37 -6
  58. package/dist/pie-pin-identity.d.ts +4 -0
  59. package/dist/pie-pin-identity.js +10 -0
  60. package/dist/pie-series.d.ts +4 -0
  61. package/dist/pie-series.js +137 -14
  62. package/dist/pie-tooltip-pin.d.ts +4 -0
  63. package/dist/pie-tooltip-pin.js +52 -0
  64. package/dist/point-marker.d.ts +19 -0
  65. package/dist/point-marker.js +19 -0
  66. package/dist/polar-chart.d.ts +14 -5
  67. package/dist/polar-chart.js +41 -10
  68. package/dist/polar-series.js +68 -22
  69. package/dist/radar-interaction.js +10 -4
  70. package/dist/radial-category.d.ts +2 -0
  71. package/dist/reveal-clip.d.ts +16 -0
  72. package/dist/reveal-clip.js +18 -0
  73. package/dist/root.d.ts +5 -9
  74. package/dist/root.js +46 -11
  75. package/dist/sankey-chart.d.ts +5 -1
  76. package/dist/sankey-chart.js +64 -13
  77. package/dist/sankey-colors.d.ts +3 -0
  78. package/dist/sankey-interaction.d.ts +8 -0
  79. package/dist/sankey-interaction.js +32 -0
  80. package/dist/sankey-legend.d.ts +2 -1
  81. package/dist/sankey-legend.js +18 -3
  82. package/dist/sankey-node-label.d.ts +22 -0
  83. package/dist/sankey-node-label.js +34 -0
  84. package/dist/scatter-chart.d.ts +5 -3
  85. package/dist/scatter-chart.js +14 -4
  86. package/dist/scatter-series.js +5 -3
  87. package/dist/series-color.d.ts +10 -0
  88. package/dist/series-color.js +48 -0
  89. package/dist/series-interaction.d.ts +12 -0
  90. package/dist/series-interaction.js +82 -0
  91. package/dist/series-label.d.ts +7 -0
  92. package/dist/series-label.js +18 -0
  93. package/dist/series-paint.d.ts +8 -0
  94. package/dist/series-paint.js +27 -0
  95. package/dist/styles.css +151 -1
  96. package/dist/stylesheet-warning.d.ts +2 -0
  97. package/dist/stylesheet-warning.js +86 -0
  98. package/dist/tooltip-content.d.ts +8 -1
  99. package/dist/tooltip-content.js +15 -4
  100. package/dist/tooltip.d.ts +3 -1
  101. package/dist/tooltip.js +8 -5
  102. package/dist/types.d.ts +11 -2
  103. package/dist/waterfall-chart.d.ts +4 -0
  104. package/dist/waterfall-chart.js +7 -0
  105. package/package.json +2 -2
@@ -1,17 +1,22 @@
1
1
  import { type ComponentProps } from "react";
2
2
  import { RadarChart as EngineRadarChart, RadialBarChart as EngineRadialBarChart } from "recharts";
3
- import { type LineAnimation } from "./animation.js";
3
+ import { type BaseAnimation } from "./animation.js";
4
4
  import type { CategoryKey } from "./category-cells.js";
5
5
  import { type RadarSelectionProps } from "./radar-interaction.js";
6
- export type RadarAnimation = LineAnimation;
7
- export type RadialBarAnimation = LineAnimation;
6
+ export type RadarAnimation = BaseAnimation;
7
+ export type RadialBarAnimation = BaseAnimation;
8
8
  export type RadarChartProps<DataPoint = unknown> = ComponentProps<typeof EngineRadarChart<DataPoint>> & RadarSelectionProps & {
9
9
  animate?: boolean | RadarAnimation | undefined;
10
+ loading?: boolean | undefined;
11
+ loadingLabel?: string | undefined;
10
12
  };
11
13
  export type RadialBarChartProps<DataPoint = unknown> = ComponentProps<typeof EngineRadialBarChart<DataPoint>> & {
12
14
  /** Opt-in category colors from Root.config, resolved from original chart rows. */
13
15
  categoryKey?: CategoryKey<DataPoint> | undefined;
16
+ interactionBinding?: "root";
14
17
  animate?: boolean | RadialBarAnimation | undefined;
18
+ loading?: boolean | undefined;
19
+ loadingLabel?: string | undefined;
15
20
  /** Entrance direction only; native chart and axis angles stay consumer-owned. */
16
21
  animationDirection?: "clockwise" | "anticlockwise" | undefined;
17
22
  };
@@ -29,5 +34,9 @@ export declare const RadialMotion: import("react").Context<{
29
34
  direction: "clockwise" | "anticlockwise";
30
35
  }>;
31
36
  /** Native polar composition with shared metadata, interaction and optional Motion. */
32
- export declare function RadarChart<DataPoint = unknown>({ animate, selection, selectedSeries, onSelectedSeriesChange, children, ...props }: RadarChartProps<DataPoint>): import("react").JSX.Element;
33
- export declare function RadialBarChart<DataPoint = unknown>({ animate, animationDirection, categoryKey, children, ...props }: RadialBarChartProps<DataPoint>): import("react").JSX.Element;
37
+ export declare function RadarChart<DataPoint = unknown>({ animate, loading, loadingLabel, selection, selectedSeries, onSelectedSeriesChange, children, ...props }: RadarChartProps<DataPoint>): import("react").JSX.Element;
38
+ export declare function RadialBarChart<DataPoint = unknown>(props: RadialBarChartProps<DataPoint>): import("react").JSX.Element;
39
+ /** Private recipe integration; the public chart keeps its native prop contract. */
40
+ export declare function RadialBarChartFrame<DataPoint = unknown>({ skeletonFamily, animate, loading, loadingLabel, animationDirection, categoryKey, interactionBinding, children, ...props }: RadialBarChartProps<DataPoint> & {
41
+ skeletonFamily?: "radial-bar" | "activity-rings";
42
+ }): import("react").JSX.Element;
@@ -4,7 +4,10 @@ import { animate as animateValue } from "motion/react";
4
4
  import { createContext, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
5
5
  import { RadarChart as EngineRadarChart, RadialBarChart as EngineRadialBarChart } from "recharts";
6
6
  import { MotionContext } from "./animation.js";
7
+ import { filterCategoryRows } from "./category-cells.js";
8
+ import { useChartInteraction } from "./chart-interaction.js";
7
9
  import { LineChartFrame, useLineInteraction } from "./line-chart.js";
10
+ import { PolarLoadingDesign } from "./loading-polar-designs.js";
8
11
  import { RadarSelectionProvider } from "./radar-interaction.js";
9
12
  import { RadialCategory } from "./radial-category.js";
10
13
  export const PolarMotion = createContext({
@@ -26,11 +29,18 @@ function subscribe(change) {
26
29
  const snapshot = () => window.matchMedia(query).matches;
27
30
  const serverSnapshot = () => true;
28
31
  const defaultHover = { type: "spring", stiffness: 210, damping: 28, mass: 0.8 };
29
- function usePolarMotion(animate) {
32
+ function usePolarMotion(animate, loading) {
30
33
  const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
31
34
  const [finished, setFinished] = useState(false);
32
- const interrupt = useCallback(() => setFinished(true), []);
33
- const enabled = animate !== false && !reduced;
35
+ const interrupt = useCallback(() => {
36
+ if (!loading)
37
+ setFinished(true);
38
+ }, [loading]);
39
+ useLayoutEffect(() => {
40
+ if (loading)
41
+ setFinished(false);
42
+ }, [loading]);
43
+ const enabled = animate !== false && !reduced && !loading;
34
44
  const options = typeof animate === "object" ? animate : {};
35
45
  return { enabled, options, reveal: enabled && !finished, interrupt };
36
46
  }
@@ -62,12 +72,16 @@ function PolarLifecycle({ layout, cx, cy, innerRadius, outerRadius, startAngle,
62
72
  return null;
63
73
  }
64
74
  /** Native polar composition with shared metadata, interaction and optional Motion. */
65
- export function RadarChart({ animate = false, selection, selectedSeries, onSelectedSeriesChange, children, ...props }) {
66
- const { enabled, options, reveal, interrupt } = usePolarMotion(animate);
75
+ export function RadarChart({ animate = false, loading, loadingLabel, selection, selectedSeries, onSelectedSeriesChange, children, ...props }) {
76
+ const { enabled, options, reveal, interrupt } = usePolarMotion(animate, loading);
67
77
  const [progress, setProgress] = useState(1);
68
78
  const duration = options.revealDurationMs ?? 1000;
69
79
  const easing = options.revealEasing ?? "easeOut";
70
80
  const started = useRef(false);
81
+ useLayoutEffect(() => {
82
+ if (loading)
83
+ started.current = false;
84
+ }, [loading]);
71
85
  const previous = useRef([duration, easing]);
72
86
  const previousEnabled = useRef(enabled);
73
87
  useLayoutEffect(() => {
@@ -93,15 +107,28 @@ export function RadarChart({ animate = false, selection, selectedSeries, onSelec
93
107
  });
94
108
  return () => controls.stop();
95
109
  }, [reveal, duration, easing, interrupt]);
96
- return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(RadarMotion, { value: { reveal, progress }, children: _jsx(PolarMotion, { value: { reveal, options }, children: _jsx(RadarSelectionProvider, { selection: selection, selectedSeries: selectedSeries, onSelectedSeriesChange: onSelectedSeriesChange, children: _jsxs(LineChartFrame, { engine: (EngineRadarChart), chartProps: props, motionEnabled: enabled, interrupt: interrupt, children: [_jsx(PolarLifecycle, { ...props, children: children }), children] }) }) }) }) }));
110
+ return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(RadarMotion, { value: { reveal, progress }, children: _jsx(PolarMotion, { value: { reveal, options }, children: _jsx(RadarSelectionProvider, { selection: selection, selectedSeries: selectedSeries, onSelectedSeriesChange: onSelectedSeriesChange, children: _jsxs(LineChartFrame, { engine: (EngineRadarChart), loading: loading, loadingLabel: loadingLabel, loadingSkeleton: "radar", loadingDesign: (seed) => _jsx(PolarLoadingDesign, { family: "radar", seed: seed }), loadingAnimation: options, chartProps: props, motionEnabled: enabled, interrupt: interrupt, children: [_jsx(PolarLifecycle, { ...props, children: children }), children] }) }) }) }) }));
97
111
  }
98
- export function RadialBarChart({ animate = false, animationDirection = "clockwise", categoryKey, children, ...props }) {
112
+ export function RadialBarChart(props) {
113
+ return _jsx(RadialBarChartFrame, { ...props });
114
+ }
115
+ /** Private recipe integration; the public chart keeps its native prop contract. */
116
+ export function RadialBarChartFrame({ skeletonFamily = "radial-bar", animate = false, loading, loadingLabel, animationDirection = "clockwise", categoryKey, interactionBinding, children, ...props }) {
117
+ const interaction = useChartInteraction();
118
+ if (interactionBinding &&
119
+ (interaction.kind !== "category" || categoryKey === undefined || props.data === undefined))
120
+ throw new Error("RadialBarChart Root interaction binding requires category-kind Root, categoryKey and explicit data");
121
+ const filtered = interactionBinding && categoryKey !== undefined && props.data
122
+ ? filterCategoryRows(props.data, categoryKey, interaction.visible, null)
123
+ : undefined;
99
124
  if (categoryKey !== undefined && props.data === undefined)
100
125
  throw new Error("RadialBarChart categoryKey requires explicit chart data");
101
126
  const categories = categoryKey === undefined
102
127
  ? null
103
128
  : {
104
- data: props.data ?? [],
129
+ data: filtered?.data ?? props.data ?? [],
130
+ originalData: props.data ?? [],
131
+ bound: interactionBinding === "root",
105
132
  key: (row) => {
106
133
  if (typeof categoryKey === "function")
107
134
  return categoryKey(row);
@@ -110,11 +137,15 @@ export function RadialBarChart({ animate = false, animationDirection = "clockwis
110
137
  : "";
111
138
  },
112
139
  };
113
- const { enabled, options, reveal, interrupt } = usePolarMotion(animate);
140
+ const { enabled, options, reveal, interrupt } = usePolarMotion(animate, loading);
114
141
  const [progress, setProgress] = useState(1);
115
142
  const duration = options.revealDurationMs ?? 1000;
116
143
  const easing = options.revealEasing ?? "easeOut";
117
144
  const started = useRef(false);
145
+ useLayoutEffect(() => {
146
+ if (loading)
147
+ started.current = false;
148
+ }, [loading]);
118
149
  const previous = useRef([duration, easing, animationDirection]);
119
150
  const previousEnabled = useRef(enabled);
120
151
  useLayoutEffect(() => {
@@ -141,5 +172,5 @@ export function RadialBarChart({ animate = false, animationDirection = "clockwis
141
172
  });
142
173
  return () => controls.stop();
143
174
  }, [reveal, duration, easing, interrupt]);
144
- return (_jsx(RadialCategory, { value: categories, children: _jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(RadialMotion, { value: { reveal, progress, direction: animationDirection }, children: _jsx(PolarMotion, { value: { reveal, options }, children: _jsxs(LineChartFrame, { engine: (EngineRadialBarChart), chartProps: props, motionEnabled: enabled, interrupt: interrupt, children: [_jsx(PolarLifecycle, { ...props, children: children }), children] }) }) }) }) }));
175
+ return (_jsx(RadialCategory, { value: categories, children: _jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(RadialMotion, { value: { reveal, progress, direction: animationDirection }, children: _jsx(PolarMotion, { value: { reveal, options }, children: _jsxs(LineChartFrame, { engine: (EngineRadialBarChart), loading: loading, loadingLabel: loadingLabel, loadingSkeleton: skeletonFamily, loadingDesign: (seed) => _jsx(PolarLoadingDesign, { family: skeletonFamily, seed: seed }), loadingAnimation: { ...options, direction: animationDirection }, chartProps: { ...props, ...(filtered ? { data: filtered.data } : {}) }, motionEnabled: enabled, interrupt: interrupt, children: [_jsx(PolarLifecycle, { ...props, children: children }), children] }) }) }) }) }));
145
176
  }
@@ -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 || (visibleSeries !== undefined && !visibleSeries.includes(key ?? ""));
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) ? `var(--color-${key})` : undefined;
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
- ? { stroke }
101
- : series.color !== undefined
102
- ? { stroke: series.color }
103
- : {}), ...(fill !== undefined
104
- ? { fill }
105
- : series.color !== undefined
106
- ? { fill: series.color }
107
- : {}), className: ["kind-ui-radar-series", filterId, className].filter(Boolean).join(" ") }) }) })] }));
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
- return (_jsxs(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.bar, children: [materialized && _jsx(PolarMaterialFilter, { material: material, id: filterId }), _jsx(motion.g, { "data-kind-ui": "radial-bar-reveal", initial: false, children: _jsx(RadialBar, { ...props, ...(props.shape === undefined ? { shape: RadialEntranceSector } : {}), ...(props.background === true
163
- ? { background: { fill: "var(--kind-ui-radial-track, #f1f1f1)" } }
164
- : {}), ...(materialized ? { filter: `url(#${filterId})` } : {}), id: series.id, hide: series.hide, isAnimationActive: false, zIndex: 0, ...(fill !== undefined
165
- ? { fill }
166
- : series.color !== undefined
167
- ? { fill: series.color }
168
- : {}), className: ["kind-ui-radial-bar-series", className].filter(Boolean).join(" "), children: children }) })] }));
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 { useEmphasisActions } from "./emphasis.js";
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 { enabled: emphasisEnabled } = useEmphasisActions();
15
- const enabled = selection === "series" && emphasisEnabled;
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
- change(null);
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. */
@@ -1,4 +1,6 @@
1
1
  export declare const RadialCategory: import("react").Context<{
2
2
  data: readonly unknown[];
3
+ originalData: readonly unknown[];
4
+ bound: boolean;
3
5
  key: (row: unknown) => string;
4
6
  } | null>;
@@ -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 { ComponentPropsWithRef } from "react";
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
- visibleSeries?: undefined;
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 && !visibleSeries)
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
- colors[`--color-${key}`] = item.color;
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(ChartContext, { value: value, children: _jsx(EmphasisProvider, { enabled: emphasis === "auto", children: _jsx(RootFrame, { ...props, style: { ...colors, ...style }, children: children }) }) }));
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
- return (_jsx("div", { ...props, "data-kind-ui": "chart", onKeyDownCapture: (event) => {
25
- if (event.key === "Escape")
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
- props.onKeyDownCapture?.(event);
28
- }, children: children }));
63
+ }, children: [children, _jsx(ChartInteractionStatus, {})] }));
29
64
  }
@@ -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 {};