@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.
Files changed (117) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +683 -1
  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-material.js +2 -3
  14. package/dist/bar-series.d.ts +11 -1
  15. package/dist/bar-series.js +65 -13
  16. package/dist/box-plot.js +2 -2
  17. package/dist/category-cells.d.ts +7 -1
  18. package/dist/category-cells.js +32 -2
  19. package/dist/chart-background-pattern.d.ts +23 -0
  20. package/dist/chart-background-pattern.js +31 -0
  21. package/dist/chart-context.d.ts +6 -2
  22. package/dist/chart-interaction.d.ts +94 -0
  23. package/dist/chart-interaction.js +224 -0
  24. package/dist/combo-chart.d.ts +8 -5
  25. package/dist/combo-chart.js +14 -12
  26. package/dist/configured-line-chart.d.ts +5 -1
  27. package/dist/configured-line-chart.js +8 -5
  28. package/dist/emphasis.d.ts +5 -2
  29. package/dist/emphasis.js +18 -4
  30. package/dist/fill-pattern.d.ts +25 -0
  31. package/dist/fill-pattern.js +23 -0
  32. package/dist/heatmap.d.ts +4 -2
  33. package/dist/heatmap.js +34 -12
  34. package/dist/histogram-chart.js +10 -3
  35. package/dist/histogram-material.js +1 -2
  36. package/dist/index.d.ts +12 -3
  37. package/dist/index.js +8 -1
  38. package/dist/legend.d.ts +2 -0
  39. package/dist/legend.js +28 -7
  40. package/dist/line-chart.d.ts +13 -3
  41. package/dist/line-chart.js +54 -34
  42. package/dist/line-dash.d.ts +8 -0
  43. package/dist/line-dash.js +18 -0
  44. package/dist/line-material.d.ts +1 -1
  45. package/dist/line-material.js +1 -1
  46. package/dist/line-series.d.ts +9 -1
  47. package/dist/line-series.js +37 -11
  48. package/dist/loading-cartesian-designs.d.ts +6 -0
  49. package/dist/loading-cartesian-designs.js +123 -0
  50. package/dist/loading-motion.d.ts +37 -0
  51. package/dist/loading-motion.js +88 -0
  52. package/dist/loading-polar-designs.d.ts +6 -0
  53. package/dist/loading-polar-designs.js +95 -0
  54. package/dist/loading-skeleton.d.ts +29 -0
  55. package/dist/loading-skeleton.js +143 -0
  56. package/dist/loading-standalone-designs.d.ts +5 -0
  57. package/dist/loading-standalone-designs.js +126 -0
  58. package/dist/percent-stack.d.ts +18 -0
  59. package/dist/percent-stack.js +33 -0
  60. package/dist/pie-chart.d.ts +7 -3
  61. package/dist/pie-chart.js +37 -6
  62. package/dist/pie-material.js +1 -1
  63. package/dist/pie-pin-identity.d.ts +4 -0
  64. package/dist/pie-pin-identity.js +10 -0
  65. package/dist/pie-series.d.ts +4 -0
  66. package/dist/pie-series.js +138 -15
  67. package/dist/pie-tooltip-pin.d.ts +4 -0
  68. package/dist/pie-tooltip-pin.js +52 -0
  69. package/dist/point-marker.d.ts +19 -0
  70. package/dist/point-marker.js +19 -0
  71. package/dist/polar-chart.d.ts +14 -5
  72. package/dist/polar-chart.js +41 -10
  73. package/dist/polar-material.js +1 -1
  74. package/dist/polar-series.js +68 -22
  75. package/dist/radar-interaction.js +10 -4
  76. package/dist/radial-category.d.ts +2 -0
  77. package/dist/reveal-clip.d.ts +16 -0
  78. package/dist/reveal-clip.js +18 -0
  79. package/dist/root.d.ts +5 -9
  80. package/dist/root.js +46 -11
  81. package/dist/sankey-chart.d.ts +5 -1
  82. package/dist/sankey-chart.js +64 -13
  83. package/dist/sankey-colors.d.ts +3 -0
  84. package/dist/sankey-finish.d.ts +1 -1
  85. package/dist/sankey-finish.js +1 -1
  86. package/dist/sankey-interaction.d.ts +8 -0
  87. package/dist/sankey-interaction.js +32 -0
  88. package/dist/sankey-legend.d.ts +2 -1
  89. package/dist/sankey-legend.js +18 -3
  90. package/dist/sankey-node-label.d.ts +22 -0
  91. package/dist/sankey-node-label.js +34 -0
  92. package/dist/scatter-chart.d.ts +5 -3
  93. package/dist/scatter-chart.js +14 -4
  94. package/dist/scatter-material.js +1 -1
  95. package/dist/scatter-series.js +5 -3
  96. package/dist/series-color.d.ts +10 -0
  97. package/dist/series-color.js +48 -0
  98. package/dist/series-interaction.d.ts +12 -0
  99. package/dist/series-interaction.js +82 -0
  100. package/dist/series-label.d.ts +7 -0
  101. package/dist/series-label.js +18 -0
  102. package/dist/series-paint.d.ts +8 -0
  103. package/dist/series-paint.js +27 -0
  104. package/dist/styles.css +151 -6
  105. package/dist/stylesheet-warning.d.ts +2 -0
  106. package/dist/stylesheet-warning.js +86 -0
  107. package/dist/surface-material.js +1 -1
  108. package/dist/tooltip-content.d.ts +8 -1
  109. package/dist/tooltip-content.js +15 -4
  110. package/dist/tooltip.d.ts +3 -1
  111. package/dist/tooltip.js +8 -5
  112. package/dist/types.d.ts +11 -2
  113. package/dist/waterfall-chart.d.ts +4 -0
  114. package/dist/waterfall-chart.js +7 -0
  115. package/package.json +2 -2
  116. package/dist/bar-paper.d.ts +0 -2
  117. package/dist/bar-paper.js +0 -6
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { animate, motion, useMotionValue } from "motion/react";
4
+ import { memo, use, useLayoutEffect, useMemo } from "react";
5
+ import { LoadingProgress } from "./loading-motion.js";
6
+ function point(radius, angle) {
7
+ const radians = (angle * Math.PI) / 180;
8
+ return [320 + Math.cos(radians) * radius, 120 + Math.sin(radians) * radius];
9
+ }
10
+ function arc(radius, start, end) {
11
+ const [sx, sy] = point(radius, start);
12
+ const [ex, ey] = point(radius, end);
13
+ return `M ${sx} ${sy} A ${radius} ${radius} 0 ${end - start > 180 ? 1 : 0} 1 ${ex} ${ey}`;
14
+ }
15
+ function polygon(radius) {
16
+ return [-90, -30, 30, 90, 150, 210].map((angle) => point(radius, angle).join(",")).join(" ");
17
+ }
18
+ /** A bounded decorative profile, independent of the native chart data. */
19
+ export function PolarLoadingDesign({ family, seed = 0, }) {
20
+ const cycle = Math.abs(Math.trunc(seed));
21
+ const variant = cycle % 4;
22
+ // Four authored profiles guarantee visibly distinct adjacent pulses. Small
23
+ // deterministic deviations keep later cycles fresh without random render work.
24
+ const deviation = (index, amount) => {
25
+ let hash = (cycle ^ Math.imul(index + 1, 0x45d9f3b)) >>> 0;
26
+ hash = Math.imul(hash ^ (hash >>> 16), 0x45d9f3b) >>> 0;
27
+ return ((hash % 101) / 50 - 1) * amount;
28
+ };
29
+ const sectorProfiles = [
30
+ [70, 20, 10],
31
+ [9, 15, 7, 30, 12, 27],
32
+ [10, 45, 30, 15],
33
+ [35, 8, 12, 10, 35],
34
+ ];
35
+ if (family === "pie") {
36
+ const weights = (sectorProfiles[variant] ?? sectorProfiles[0] ?? []).map((weight, index) => Math.max(5, weight + deviation(index, 2)));
37
+ const total = weights.reduce((sum, weight) => sum + weight, 0);
38
+ let angle = -90;
39
+ return (_jsx("g", { fill: "currentColor", children: weights.map((weight) => {
40
+ const start = angle + 2;
41
+ angle += (weight / total) * 360;
42
+ return _jsx("path", { d: `${arc(100, start, angle - 2)} L 320 120 Z` }, start);
43
+ }) }));
44
+ }
45
+ if (family === "radar") {
46
+ const reduced = use(LoadingProgress)?.reduced ?? true;
47
+ return (_jsxs("g", { "data-kind-ui": "loading-radar-motion", stroke: "currentColor", fill: "none", strokeWidth: "2", vectorEffect: "non-scaling-stroke", children: [[34, 67, 100].map((radius) => (_jsx("polygon", { points: polygon(radius), opacity: "0.4" }, radius))), [-90, -30, 30, 90, 150, 210].map((angle) => {
48
+ const [x, y] = point(100, angle);
49
+ return _jsx("line", { x1: "320", y1: "120", x2: x, y2: y, opacity: "0.35" }, angle);
50
+ }), _jsx(RadarLoadingPolygon, { seed: seed, layer: 0, reduced: reduced }), _jsx(RadarLoadingPolygon, { seed: seed, layer: 1, reduced: reduced })] }));
51
+ }
52
+ const rings = family === "activity-rings" ? [91, 66, 41] : [97, 73, 49, 25];
53
+ const sweeps = family === "activity-rings"
54
+ ? [
55
+ [325, 80, 190],
56
+ [85, 315, 55],
57
+ [200, 50, 325],
58
+ [50, 205, 100],
59
+ ][variant]
60
+ : [
61
+ [325, 70, 190, 45],
62
+ [75, 310, 45, 220],
63
+ [195, 45, 325, 90],
64
+ [45, 200, 90, 315],
65
+ ][variant];
66
+ return (_jsx("g", { fill: "none", stroke: "currentColor", strokeWidth: family === "activity-rings" ? 17 : 13, children: rings.map((radius, index) => (_jsxs("g", { children: [_jsx("circle", { cx: "320", cy: "120", r: radius, opacity: "0.2" }), _jsx("path", { d: arc(radius, -90, -90 + Math.max(35, Math.min(338, (sweeps?.[index] ?? 180) + deviation(index, 9)))), strokeLinecap: "round" })] }, radius))) }));
67
+ }
68
+ const radarProfiles = [
69
+ [96, 28, 42, 90, 25, 35],
70
+ [30, 94, 32, 45, 94, 28],
71
+ [40, 35, 96, 28, 45, 94],
72
+ [86, 72, 28, 34, 82, 96],
73
+ ];
74
+ /** Identical vertex order makes every interpolated shape valid and continuous. */
75
+ const RadarLoadingPolygon = memo(function RadarLoadingPolygon({ seed, layer, reduced, }) {
76
+ const frames = useMemo(() => {
77
+ const first = (Math.abs(Math.trunc(seed)) + layer * 2) % radarProfiles.length;
78
+ const points = Array.from({ length: radarProfiles.length }, (_, step) => (radarProfiles[(first + step) % radarProfiles.length] ?? radarProfiles[0])
79
+ .map((radius, index) => point(radius * (layer === 0 ? 1 : 0.82), -90 + index * 60).join(","))
80
+ .join(" "));
81
+ return [...points, points[0] ?? ""];
82
+ }, [seed, layer]);
83
+ const points = useMotionValue(frames[0] ?? "");
84
+ useLayoutEffect(() => {
85
+ if (reduced) {
86
+ points.set(frames[0] ?? "");
87
+ return;
88
+ }
89
+ // A persistent MotionValue keeps host rerenders from resetting an in-flight
90
+ // SVG attribute back to its first keyframe.
91
+ const controls = animate(points, frames, { duration: 8, ease: "easeInOut", repeat: Infinity });
92
+ return () => controls.stop();
93
+ }, [points, frames, reduced]);
94
+ return (_jsx(motion.polygon, { "data-kind-ui": "loading-radar-polygon", points: points, fill: "currentColor", fillOpacity: layer === 0 ? 0.2 : 0.12, strokeWidth: layer === 0 ? 3 : 2, strokeLinejoin: "round" }));
95
+ });
@@ -0,0 +1,29 @@
1
+ import { type CSSProperties, type ReactNode } from "react";
2
+ import { type LoadingAnimation } from "./loading-motion.js";
3
+ export type LoadingFamily = "line" | "area" | "bar" | "combo" | "scatter" | "waterfall" | "histogram" | "box-plot" | "pie" | "radar" | "radial-bar" | "activity-rings" | "heatmap" | "sankey";
4
+ /** Internal composition keeps unused family illustrations out of single-chart bundles. */
5
+ export type LoadingDesign = (seed: number) => ReactNode;
6
+ /** Fixed decorative design; native plot bounds supply only its available surface. */
7
+ export declare function LoadingSkeletonSurface({ family, seed: suppliedSeed, width, height, x, y, style, animation, design, }: {
8
+ family: LoadingFamily;
9
+ seed?: number;
10
+ width?: number | string;
11
+ height?: number | string;
12
+ x?: number;
13
+ y?: number;
14
+ style?: CSSProperties;
15
+ animation?: LoadingAnimation | undefined;
16
+ design?: LoadingDesign | undefined;
17
+ }): import("react").JSX.Element;
18
+ export declare function ChartLoadingSkeleton({ family, seed, animation, design, }: {
19
+ family: LoadingFamily;
20
+ seed: number;
21
+ animation?: LoadingAnimation | undefined;
22
+ design?: LoadingDesign | undefined;
23
+ }): import("react").JSX.Element | null;
24
+ export declare function LoadingStatus({ loading, label, }: {
25
+ loading?: boolean | undefined;
26
+ label?: string | undefined;
27
+ }): import("react").JSX.Element | null;
28
+ /** Cycle state belongs to the persistent host, so resize/render cannot change a design. */
29
+ export declare function useLoadingSeed(loading: boolean | undefined): number;
@@ -0,0 +1,143 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { motion, useTransform } from "motion/react";
4
+ import { useId, useState } from "react";
5
+ import { usePlotArea } from "recharts";
6
+ import { LoadingAngularBand, LoadingProgress, useLoadingProgress, } from "./loading-motion.js";
7
+ const cartesianEase = [0.25, 0.1, 0.25, 1];
8
+ const lineDesign = (seed) => (_jsx("path", { d: linePath(seed), fill: "none", stroke: "currentColor", strokeWidth: "2.25", vectorEffect: "non-scaling-stroke", strokeLinecap: "round" }));
9
+ const lineProfiles = [
10
+ [212, 188, 155, 174, 105, 81, 45, 24],
11
+ [28, 62, 42, 110, 132, 178, 158, 215],
12
+ [38, 75, 151, 220, 203, 139, 63, 28],
13
+ [195, 43, 174, 26, 210, 73, 186, 42],
14
+ ];
15
+ function linePath(seed) {
16
+ const profile = lineProfiles[seed % 4] ?? lineProfiles[0] ?? [];
17
+ const points = profile.map((y, index) => [
18
+ 12 + index * 88,
19
+ Math.max(12, Math.min(225, y + ((Math.imul(seed + index * 11, 2654435761) >>> 0) % 23) - 11)),
20
+ ]);
21
+ let d = `M ${points[0]?.join(" ")}`;
22
+ for (let i = 1; i < points.length; i++) {
23
+ const previous = points[i - 1];
24
+ const point = points[i];
25
+ if (!previous || !point)
26
+ continue;
27
+ const middle = ((previous[0] ?? 0) + (point[0] ?? 0)) / 2;
28
+ d += ` C ${middle} ${previous[1]} ${middle} ${point[1]} ${point.join(" ")}`;
29
+ }
30
+ return d;
31
+ }
32
+ /** Fixed decorative design; native plot bounds supply only its available surface. */
33
+ export function LoadingSkeletonSurface({ family, seed: suppliedSeed, width = "100%", height = "100%", x, y, style, animation, design = lineDesign, }) {
34
+ const id = useId().replace(/:/g, "");
35
+ const seed = suppliedSeed ??
36
+ Array.from(id).reduce((value, char) => (value * 31 + char.charCodeAt(0)) >>> 0, 7);
37
+ const [pulse, setPulse] = useState(0);
38
+ const designSeed = seed + pulse;
39
+ const isCartesian = [
40
+ "line",
41
+ "area",
42
+ "bar",
43
+ "waterfall",
44
+ "histogram",
45
+ "box-plot",
46
+ "combo",
47
+ ].includes(family);
48
+ const duration = Math.max(0, animation?.revealDurationMs ??
49
+ (family === "scatter" ? 700 : family === "heatmap" ? 800 : family === "sankey" ? 450 : 1000));
50
+ const easing = animation?.revealEasing ?? (isCartesian ? cartesianEase : "easeOut");
51
+ const part = (kind) => {
52
+ const option = animation?.[kind];
53
+ return {
54
+ duration: family !== "combo" || option === false
55
+ ? 0
56
+ : Math.max(0, option?.revealDurationMs ?? duration),
57
+ easing: option === false ? easing : (option?.revealEasing ?? easing),
58
+ };
59
+ };
60
+ const line = part("lineReveal");
61
+ const area = part("areaReveal");
62
+ const bar = part("barReveal");
63
+ const cycleDuration = family === "combo" ? Math.max(line.duration, area.duration, bar.duration) : duration;
64
+ const cycleMs = cycleDuration * 2 + 200;
65
+ const startDelay = cycleMs * 0.12;
66
+ const { progress, reduced } = useLoadingProgress(pulse, duration, easing, startDelay, family === "radial-bar");
67
+ const lineMotion = useLoadingProgress(pulse, line.duration, line.easing, startDelay);
68
+ const areaMotion = useLoadingProgress(pulse, area.duration, area.easing, startDelay);
69
+ const barMotion = useLoadingProgress(pulse, bar.duration, bar.easing, startDelay);
70
+ const horizontalProgress = family === "combo" ? lineMotion.progress : progress;
71
+ const verticalProgress = family === "combo" ? barMotion.progress : progress;
72
+ const horizontalReduced = family === "combo" ? lineMotion.reduced : reduced;
73
+ const verticalReduced = family === "combo" ? barMotion.reduced : reduced;
74
+ const horizontal = animation?.layout === "vertical";
75
+ const position = useTransform(horizontalProgress, (value) => (value - 0.5) * 640);
76
+ const verticalPosition = useTransform(verticalProgress, (value) => 240 - value * 240);
77
+ const areaPosition = useTransform(areaMotion.progress, (value) => (value - 0.5) * 640);
78
+ const barPosition = useTransform(barMotion.progress, (value) => (value - 0.5) * 640);
79
+ const pulseStyle = {
80
+ ...style,
81
+ // Radar's two polygon MotionValues continuously morph; disabling the CSS
82
+ // opacity pulse keeps them visible rather than disabling their animation.
83
+ ...(cycleDuration === 0 || family === "radar" ? { animation: "none" } : {}),
84
+ "--kind-ui-loading-cycle": `${cycleMs}ms`,
85
+ "--kind-ui-loading-horizontal": `url(#${id}-horizontal)`,
86
+ "--kind-ui-loading-vertical": `url(#${id}-vertical)`,
87
+ "--kind-ui-loading-area": `url(#${id}-area)`,
88
+ };
89
+ const polar = ["pie", "radar", "radial-bar", "activity-rings"].includes(family);
90
+ return (_jsxs("svg", { "data-kind-ui": "chart-loading-skeleton", "data-family": family, "data-loading-layout": animation?.layout, "data-loading-motion": family === "line" ||
91
+ family === "area" ||
92
+ ["bar", "waterfall", "histogram", "box-plot"].includes(family)
93
+ ? "sweep"
94
+ : family === "radar"
95
+ ? "morph"
96
+ : polar
97
+ ? "angular"
98
+ : family === "heatmap"
99
+ ? "wave"
100
+ : family === "sankey"
101
+ ? "flow"
102
+ : family === "scatter"
103
+ ? "emerge"
104
+ : family === "combo"
105
+ ? "combined"
106
+ : "grow", onAnimationIteration: (event) => {
107
+ if (event.target === event.currentTarget && event.animationName === "kind-ui-loading-pulse")
108
+ setPulse((value) => value + 1);
109
+ }, "aria-hidden": "true", focusable: "false", x: x, y: y, width: width, height: height, style: pulseStyle, "data-reveal-duration": duration, viewBox: polar ? "200 0 240 240" : "0 0 640 240", preserveAspectRatio: polar || family === "scatter" ? "xMidYMid meet" : "none", children: [_jsxs("defs", { children: [_jsxs("linearGradient", { id: `${id}-soft-x`, children: [_jsx("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }), _jsx("stop", { offset: "0.35", stopColor: "white" }), _jsx("stop", { offset: "0.75", stopColor: "white" }), _jsx("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })] }), _jsxs("linearGradient", { id: `${id}-soft-y`, x1: "0", y1: "1", x2: "0", y2: "0", children: [_jsx("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }), _jsx("stop", { offset: "0.35", stopColor: "white" }), _jsx("stop", { offset: "0.75", stopColor: "white" }), _jsx("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })] }), _jsx("mask", { id: `${id}-horizontal`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: horizontalReduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { "data-kind-ui": "loading-leading-window", x: position, y: "0", width: "320", height: "240", fill: `url(#${id}-soft-x)` })) }), _jsx("mask", { id: `${id}-vertical`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: verticalReduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { "data-kind-ui": "loading-leading-window", x: "0", y: verticalPosition, width: "640", height: "120", fill: `url(#${id}-soft-y)` })) }), _jsx("mask", { id: `${id}-area`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: areaMotion.reduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { x: areaPosition, y: "0", width: "320", height: "240", fill: `url(#${id}-soft-x)` })) }), _jsx("mask", { id: `${id}-bar-horizontal`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: barMotion.reduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { x: barPosition, y: "0", width: "320", height: "240", fill: `url(#${id}-soft-x)` })) }), _jsx("mask", { id: `${id}-angular`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: reduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : ([0, 18, 36, 54, 72, 90, 108, 126].map((angle) => (_jsx(LoadingAngularBand, { progress: progress, index: angle / 18, direction: animation?.direction ?? "clockwise" }, angle)))) })] }), _jsx("g", { "data-kind-ui": "loading-design", opacity: "0.48", mask: family === "radar"
110
+ ? undefined
111
+ : polar
112
+ ? `url(#${id}-angular)`
113
+ : family === "line" || family === "area"
114
+ ? `url(#${id}-horizontal)`
115
+ : family === "bar" ||
116
+ family === "waterfall" ||
117
+ family === "histogram" ||
118
+ family === "box-plot"
119
+ ? `url(#${id}-horizontal)`
120
+ : undefined, children: _jsx(LoadingProgress, { value: { progress, reduced }, children: _jsx("g", { transform: horizontal && ["bar", "waterfall", "histogram", "box-plot"].includes(family)
121
+ ? "matrix(0 .375 -2.6666667 0 640 0)"
122
+ : undefined, children: design(family === "radar" ? seed : designSeed) }) }) })] }));
123
+ }
124
+ export function ChartLoadingSkeleton({ family, seed, animation, design, }) {
125
+ const plot = usePlotArea();
126
+ if (!plot || plot.width <= 0 || plot.height <= 0)
127
+ return null;
128
+ return (_jsx(LoadingSkeletonSurface, { family: family, seed: seed, animation: animation, design: design, x: plot.x, y: plot.y, width: plot.width, height: plot.height }));
129
+ }
130
+ export function LoadingStatus({ loading, label, }) {
131
+ return loading === undefined ? null : (_jsx("span", { role: "status", "data-kind-ui": "chart-loading-status", children: loading ? (label ?? "Loading chart") : "" }));
132
+ }
133
+ /** Cycle state belongs to the persistent host, so resize/render cannot change a design. */
134
+ export function useLoadingSeed(loading) {
135
+ const id = useId();
136
+ const [state, setState] = useState({ pending: !!loading, cycle: 0 });
137
+ let cycle = state.cycle;
138
+ if (state.pending !== !!loading) {
139
+ cycle += loading ? 1 : 0;
140
+ setState({ pending: !!loading, cycle });
141
+ }
142
+ return Array.from(id).reduce((value, char) => (value * 31 + char.charCodeAt(0)) >>> 0, 7) + cycle;
143
+ }
@@ -0,0 +1,5 @@
1
+ /** Fixed illustrations; no user cells, flows, values, or native marks are sampled. */
2
+ export declare function StandaloneLoadingDesign({ family, seed, }: {
3
+ family: "heatmap" | "sankey";
4
+ seed: number;
5
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,126 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { LoadingPulseFlow, LoadingPulseMark } from "./loading-motion.js";
3
+ // Authored clustered, diagonal, paired-hotspot and striped illustrations.
4
+ const heatmapPatterns = [
5
+ [
6
+ 9, 9, 7, 2, 1, 1, 1, 2, 9, 8, 6, 2, 1, 1, 2, 1, 7, 6, 4, 2, 1, 2, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1,
7
+ 1, 1, 1, 2, 1, 1, 2, 1,
8
+ ],
9
+ [
10
+ 9, 7, 2, 1, 1, 1, 1, 1, 2, 8, 9, 6, 1, 1, 1, 1, 1, 1, 3, 9, 8, 3, 1, 1, 1, 1, 1, 1, 5, 9, 7, 2,
11
+ 1, 1, 1, 1, 1, 2, 7, 9,
12
+ ],
13
+ [
14
+ 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 5, 3, 1, 1, 4, 7, 5, 6, 9, 8, 2, 2, 8, 9, 8,
15
+ 4, 8, 6, 1, 1, 6, 8, 5,
16
+ ],
17
+ [
18
+ 1, 8, 9, 1, 2, 1, 8, 9, 2, 7, 9, 1, 1, 1, 9, 7, 1, 9, 8, 2, 1, 2, 7, 9, 1, 8, 9, 1, 1, 1, 9, 8,
19
+ 2, 9, 7, 1, 2, 1, 8, 9,
20
+ ],
21
+ ];
22
+ // Each flow width is conserved at its node junctions. These are decorative authored layouts.
23
+ const sankeyProfiles = [
24
+ {
25
+ nodes: [
26
+ [16, 20, 16, 60],
27
+ [16, 100, 16, 50],
28
+ [16, 170, 16, 50],
29
+ [298, 35, 24, 90],
30
+ [298, 145, 24, 70],
31
+ [608, 20, 16, 50],
32
+ [608, 95, 16, 40],
33
+ [608, 155, 16, 70],
34
+ ],
35
+ flows: [
36
+ [32, 20, 298, 35, 60],
37
+ [32, 100, 298, 95, 30],
38
+ [32, 130, 298, 145, 20],
39
+ [32, 170, 298, 165, 50],
40
+ [322, 35, 608, 20, 50],
41
+ [322, 85, 608, 95, 40],
42
+ [322, 145, 608, 155, 70],
43
+ ],
44
+ },
45
+ {
46
+ nodes: [
47
+ [16, 20, 16, 100],
48
+ [16, 140, 16, 80],
49
+ [298, 20, 24, 50],
50
+ [298, 90, 24, 60],
51
+ [298, 170, 24, 70],
52
+ [608, 25, 16, 100],
53
+ [608, 145, 16, 80],
54
+ ],
55
+ flows: [
56
+ [32, 20, 298, 20, 50],
57
+ [32, 70, 298, 90, 50],
58
+ [32, 140, 298, 140, 10],
59
+ [32, 150, 298, 170, 70],
60
+ [322, 20, 608, 25, 50],
61
+ [322, 90, 608, 75, 50],
62
+ [322, 140, 608, 145, 10],
63
+ [322, 170, 608, 155, 70],
64
+ ],
65
+ },
66
+ {
67
+ nodes: [
68
+ [16, 32, 16, 176],
69
+ [298, 12, 24, 64],
70
+ [298, 96, 24, 46],
71
+ [298, 162, 24, 66],
72
+ [608, 20, 16, 110],
73
+ [608, 154, 16, 66],
74
+ ],
75
+ flows: [
76
+ [32, 32, 298, 12, 64],
77
+ [32, 96, 298, 96, 46],
78
+ [32, 142, 298, 162, 66],
79
+ [322, 12, 608, 20, 64],
80
+ [322, 96, 608, 84, 46],
81
+ [322, 162, 608, 154, 66],
82
+ ],
83
+ },
84
+ {
85
+ nodes: [
86
+ [16, 12, 16, 30],
87
+ [16, 58, 16, 40],
88
+ [16, 114, 16, 50],
89
+ [16, 174, 16, 60],
90
+ [608, 20, 16, 60],
91
+ [608, 102, 16, 55],
92
+ [608, 174, 16, 65],
93
+ ],
94
+ flows: [
95
+ [32, 12, 608, 20, 30],
96
+ [32, 58, 608, 50, 30],
97
+ [32, 88, 608, 102, 10],
98
+ [32, 114, 608, 112, 45],
99
+ [32, 159, 608, 174, 5],
100
+ [32, 174, 608, 179, 60],
101
+ ],
102
+ },
103
+ ];
104
+ /** Fixed illustrations; no user cells, flows, values, or native marks are sampled. */
105
+ export function StandaloneLoadingDesign({ family, seed, }) {
106
+ const profile = seed % 4;
107
+ if (family === "heatmap") {
108
+ return (_jsx("g", { fill: "currentColor", "data-profile": profile, children: heatmapPatterns[profile]?.map((intensity, index) => {
109
+ const row = Math.floor(index / 8);
110
+ const column = index % 8;
111
+ return (_jsx(LoadingPulseMark, { delay: ((row + column) / 11) * (0.55 / 0.8), span: 0.25 / 0.8, children: _jsx("rect", { "data-motion": "heatmap-cell", x: 16 + column * 77, y: 10 + row * 45, width: "69", height: "37", rx: "3", opacity: 0.2 + (intensity / 9) * 0.8 }) }, `${row}-${column}`));
112
+ }) }));
113
+ }
114
+ const design = sankeyProfiles[profile];
115
+ if (!design)
116
+ return null;
117
+ const band = ([x0, y0, x1, y1, width]) => {
118
+ const middle = (x0 + x1) / 2;
119
+ return `M ${x0} ${y0} C ${middle} ${y0} ${middle} ${y1} ${x1} ${y1} L ${x1} ${y1 + width} C ${middle} ${y1 + width} ${middle} ${y0 + width} ${x0} ${y0 + width} Z`;
120
+ };
121
+ const flow = ([x0, y0, x1, y1, width]) => {
122
+ const middle = (x0 + x1) / 2;
123
+ return `M ${x0} ${y0 + width / 2} C ${middle} ${y0 + width / 2} ${middle} ${y1 + width / 2} ${x1} ${y1 + width / 2}`;
124
+ };
125
+ return (_jsxs("g", { fill: "currentColor", "data-profile": profile, children: [design.flows.map((item) => (_jsxs(LoadingPulseFlow, { d: flow(item), width: item[4], delay: (item[0] / 640) * 0.55, children: [_jsx("path", { d: band(item), opacity: "0.5" }), _jsx("path", { "data-motion": "sankey-flow", d: flow(item), fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", opacity: "0.55" })] }, item.join("-")))), design.nodes.map(([x, y, width, height]) => (_jsx(LoadingPulseMark, { delay: (x / 640) * 0.55, span: 0.45, children: _jsx("rect", { x: x, y: y, width: width, height: height, rx: "2" }) }, `${x}-${y}`)))] }));
126
+ }
@@ -0,0 +1,18 @@
1
+ import type { TooltipContentProps } from "recharts";
2
+ type Entry = TooltipContentProps["payload"][number];
3
+ /** Return a fraction for selected entries; undefined retains ordinary formatting. */
4
+ export type NormalizedValue = (entry: Entry) => number | undefined;
5
+ export type PercentStackOptions = {
6
+ /** Raw members of this entry's native expand stack, including its own value.
7
+ * Return undefined for other series/axes. Match native hidden-series membership.
8
+ */
9
+ values: (entry: Entry) => readonly (number | null | undefined)[] | undefined;
10
+ };
11
+ /** Formats an existing fraction; never normalizes chart data or changes domains. */
12
+ export declare function formatPercent(value: number): string;
13
+ /** Formatting only for native stackOffset="expand" scalar stacks. */
14
+ export declare function createPercentStack({ values }: PercentStackOptions): {
15
+ tickFormatter: typeof formatPercent;
16
+ normalizedValue: NormalizedValue;
17
+ };
18
+ export {};
@@ -0,0 +1,33 @@
1
+ /** Formats an existing fraction; never normalizes chart data or changes domains. */
2
+ export function formatPercent(value) {
3
+ return `${Number((value * 100).toFixed(1))}%`;
4
+ }
5
+ /** Formatting only for native stackOffset="expand" scalar stacks. */
6
+ export function createPercentStack({ values }) {
7
+ const normalizedValue = (entry) => {
8
+ if (typeof entry.value !== "number" || !Number.isFinite(entry.value))
9
+ return undefined;
10
+ const members = values(entry);
11
+ if (!members || members.length === 0)
12
+ return undefined;
13
+ let total = 0;
14
+ let allZero = true;
15
+ for (const member of members) {
16
+ if (member == null)
17
+ continue;
18
+ if (!Number.isFinite(member))
19
+ return undefined;
20
+ total += member;
21
+ if (member !== 0)
22
+ allZero = false;
23
+ }
24
+ if (!Number.isFinite(total))
25
+ return undefined;
26
+ // Expand uses a signed sum. A cancelling sum has no normalized share.
27
+ if (total === 0)
28
+ return allZero && entry.value === 0 ? 0 : undefined;
29
+ const fraction = entry.value / total;
30
+ return Number.isFinite(fraction * 100) ? fraction : undefined;
31
+ };
32
+ return { tickFormatter: formatPercent, normalizedValue };
33
+ }
@@ -1,9 +1,13 @@
1
1
  import { type ComponentProps } from "react";
2
2
  import { PieChart as EnginePieChart } from "recharts";
3
- import { type LineAnimation } from "./animation.js";
4
- export type PieAnimation = LineAnimation;
3
+ import { type BaseAnimation } from "./animation.js";
4
+ export type PieAnimation = BaseAnimation;
5
5
  export type PieChartProps = ComponentProps<typeof EnginePieChart> & {
6
+ /** Initial tooltip category; one direct categoryKey PieSeries with explicit data only. */
7
+ defaultPinnedCategory?: string | undefined;
6
8
  animate?: boolean | PieAnimation | undefined;
9
+ loading?: boolean | undefined;
10
+ loadingLabel?: string | undefined;
7
11
  /** Entrance sweep only; native start/end angles and data order are unchanged. */
8
12
  animationDirection?: "clockwise" | "anticlockwise" | undefined;
9
13
  };
@@ -14,4 +18,4 @@ export declare const PieMotion: import("react").Context<{
14
18
  direction: "clockwise" | "anticlockwise";
15
19
  }>;
16
20
  /** Native polar composition, shared interaction, and optional Motion-owned sector entrance. */
17
- export declare function PieChart({ animate, animationDirection, children, ...props }: PieChartProps): import("react").JSX.Element;
21
+ export declare function PieChart({ animate, loading, loadingLabel, animationDirection, children, defaultPinnedCategory, ...props }: PieChartProps): import("react").JSX.Element;
package/dist/pie-chart.js CHANGED
@@ -1,10 +1,13 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { animate as animateValue } from "motion/react";
4
- import { createContext, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
4
+ import { createContext, memo, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
5
5
  import { PieChart as EnginePieChart } from "recharts";
6
6
  import { MotionContext } from "./animation.js";
7
+ import { useChart } from "./chart-context.js";
7
8
  import { LineChartFrame } from "./line-chart.js";
9
+ import { PolarLoadingDesign } from "./loading-polar-designs.js";
10
+ import { PieTooltipPin, pinnedPieIndex } from "./pie-tooltip-pin.js";
8
11
  export const PieMotion = createContext({
9
12
  reveal: false,
10
13
  options: {},
@@ -21,11 +24,33 @@ const snapshot = () => window.matchMedia(query).matches;
21
24
  const serverSnapshot = () => true;
22
25
  const defaultHover = { type: "spring", stiffness: 210, damping: 28, mass: 0.8 };
23
26
  /** Native polar composition, shared interaction, and optional Motion-owned sector entrance. */
24
- export function PieChart({ animate = false, animationDirection = "clockwise", children, ...props }) {
27
+ export function PieChart({ animate = false, loading, loadingLabel, animationDirection = "clockwise", children, defaultPinnedCategory, ...props }) {
28
+ const { visibleSeries } = useChart();
29
+ const [initialCategory] = useState(defaultPinnedCategory);
30
+ const [pinCleared, setPinCleared] = useState(false);
31
+ const clearPin = useCallback(() => setPinCleared(true), []);
32
+ const pinIndex = initialCategory === undefined || pinCleared
33
+ ? undefined
34
+ : visibleSeries !== undefined && !visibleSeries.includes(initialCategory)
35
+ ? undefined
36
+ : pinnedPieIndex(children, initialCategory, visibleSeries);
37
+ useLayoutEffect(() => {
38
+ if (initialCategory !== undefined && pinIndex === undefined)
39
+ clearPin();
40
+ }, [initialCategory, pinIndex, clearPin]);
41
+ const chart = (_jsx(PieChartPlot, { ...props, animate: animate, loading: loading, loadingLabel: loadingLabel, animationDirection: animationDirection, children: children }));
42
+ return (_jsx(PieTooltipPin, { value: pinIndex, children: initialCategory === undefined ? (chart) : (_jsx("div", { style: { display: "contents" }, onPointerMoveCapture: clearPin, onPointerDownCapture: clearPin, onFocusCapture: clearPin, onKeyDownCapture: clearPin, children: chart })) }));
43
+ }
44
+ // Pin dismissal changes Tooltip context only. Keep the native plot's providers and
45
+ // geometry inputs intact while a pressed sector is awaiting its native click.
46
+ const PieChartPlot = memo(function PieChartPlot({ animate = false, loading, loadingLabel, animationDirection = "clockwise", children, ...props }) {
25
47
  const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
26
48
  const [interacted, setInteracted] = useState(false);
27
- const finish = useCallback(() => setInteracted(true), []);
28
- const enabled = animate !== false && !reduced;
49
+ const finish = useCallback(() => {
50
+ if (!loading)
51
+ setInteracted(true);
52
+ }, [loading]);
53
+ const enabled = animate !== false && !reduced && !loading;
29
54
  const interrupt = useCallback(() => {
30
55
  if (enabled)
31
56
  finish();
@@ -42,6 +67,12 @@ export function PieChart({ animate = false, animationDirection = "clockwise", ch
42
67
  const duration = options.revealDurationMs ?? 1000;
43
68
  const easing = options.revealEasing ?? "easeOut";
44
69
  const started = useRef(false);
70
+ useLayoutEffect(() => {
71
+ if (loading) {
72
+ setInteracted(false);
73
+ started.current = false;
74
+ }
75
+ }, [loading]);
45
76
  const previous = useRef([duration, easing, animationDirection]);
46
77
  useLayoutEffect(() => {
47
78
  const inputs = [duration, easing, animationDirection];
@@ -64,5 +95,5 @@ export function PieChart({ animate = false, animationDirection = "clockwise", ch
64
95
  });
65
96
  return () => controls.stop();
66
97
  }, [reveal, duration, easing, finish]);
67
- return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(PieMotion, { value: { reveal, options, progress, direction: animationDirection }, children: _jsx(LineChartFrame, { chartProps: props, engine: EnginePieChart, motionEnabled: enabled, interrupt: interrupt, children: children }) }) }));
68
- }
98
+ return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(PieMotion, { value: { reveal, options, progress, direction: animationDirection }, children: _jsx(LineChartFrame, { chartProps: props, engine: EnginePieChart, loading: loading, loadingLabel: loadingLabel, loadingSkeleton: "pie", loadingDesign: (seed) => _jsx(PolarLoadingDesign, { family: "pie", seed: seed }), loadingAnimation: { ...options, direction: animationDirection }, motionEnabled: enabled, interrupt: interrupt, children: children }) }) }));
99
+ });
@@ -8,7 +8,7 @@ export function PieMaterialFilter({ material, id, radius, thickness, cx, cy, str
8
8
  const y = Math.floor(cy - radius - margin);
9
9
  const width = Math.ceil(cx + radius + margin) - x;
10
10
  const height = Math.ceil(cy + radius + margin) - y;
11
- return (_jsx("filter", { id: id, filterUnits: "userSpaceOnUse", x: bounds?.x ?? x, y: bounds?.y ?? y, width: bounds?.width ?? width, height: bounds?.height ?? height, colorInterpolationFilters: "sRGB", children: material === "clay" ? (_jsxs(_Fragment, { children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), _jsx("feGaussianBlur", { in: "footprint", stdDeviation: relief, result: "rounded" }), _jsx("feOffset", { in: "rounded", dx: relief * 0.7, dy: relief * 0.9, result: "lower" }), _jsx("feComposite", { in: "footprint", in2: "lower", operator: "out", result: "top" }), _jsx("feGaussianBlur", { in: "top", stdDeviation: relief * 0.45, result: "softTop" }), _jsx("feFlood", { floodColor: "var(--kind-ui-pie-clay-light, #fff)", floodOpacity: "var(--kind-ui-pie-clay-highlight, 0.55)" }), _jsx("feComposite", { in2: "softTop", operator: "in", result: "light" }), _jsx("feOffset", { in: "rounded", dx: -relief * 0.8, dy: -relief, result: "upper" }), _jsx("feComposite", { in: "footprint", in2: "upper", operator: "out", result: "bottom" }), _jsx("feGaussianBlur", { in: "bottom", stdDeviation: relief * 0.45, result: "softBottom" }), _jsx("feFlood", { floodColor: "var(--kind-ui-pie-clay-shade, #17212b)", floodOpacity: "var(--kind-ui-pie-clay-shadow, 0.32)" }), _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: "finished" }), _jsx(NativePaintColor, {})] })) : material === "paper" ? (_jsxs(_Fragment, { children: [_jsx("feTurbulence", { type: "fractalNoise", x: bounds?.x ?? x, y: bounds?.y ?? y, width: 32, height: 32, baseFrequency: "0.2 0.3", numOctaves: 1, seed: 7, result: "fiberTile" }), _jsx("feTile", { in: "fiberTile", result: "fiber" }), _jsx("feFlood", { floodColor: "var(--kind-ui-pie-paper-fiber, #fff)", floodOpacity: "var(--kind-ui-pie-paper-grain, 0.16)" }), _jsx("feComposite", { in2: "fiber", operator: "in", result: "texture" }), _jsx("feComposite", { in: "texture", in2: "SourceGraphic", operator: "atop", result: "paper" }), _jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), _jsx("feGaussianBlur", { in: "footprint", stdDeviation: 1.2, result: "inside" }), _jsx("feComposite", { in: "footprint", in2: "inside", operator: "out", result: "edge" }), _jsx("feComponentTransfer", { in: "edge", result: "pencilEdge", children: _jsx("feFuncA", { type: "linear", slope: 6 }) }), _jsx("feColorMatrix", { in: "fiber", type: "luminanceToAlpha", result: "sketch" }), _jsx("feComposite", { in: "pencilEdge", in2: "sketch", operator: "in", result: "contour" }), _jsx("feFlood", { floodColor: "var(--kind-ui-pie-paper-ink, #17212b)", floodOpacity: 0.72 }), _jsx("feComposite", { in2: "contour", operator: "in", result: "ink" }), _jsx("feComposite", { in: "ink", in2: "paper", operator: "atop", result: "outlined" }), _jsx("feOffset", { in: "pencilEdge", dx: 1.2, dy: 1.2, result: "inset" }), _jsx("feComposite", { in: "inset", in2: "footprint", operator: "in", result: "pencilLine" }), _jsx("feComposite", { in: "pencilLine", in2: "sketch", operator: "in", result: "brokenLine" }), _jsx("feFlood", { floodColor: "var(--kind-ui-pie-paper-ink, #17212b)", floodOpacity: 0.45 }), _jsx("feComposite", { in2: "brokenLine", operator: "in", result: "pencilPaint" }), _jsx("feComposite", { in: "pencilPaint", in2: "outlined", operator: "atop", result: "finished" }), _jsx(NativePaintColor, {})] })) : (_jsxs(_Fragment, { children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), _jsx("feMorphology", { in: "footprint", operator: "dilate", radius: 1.5, result: "haloGuard" }), _jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: 4.5, result: "soft" }), _jsx("feFlood", { floodOpacity: "var(--kind-ui-pie-glow-opacity, 0.8)", result: "strength" }), _jsx("feComposite", { in: "soft", in2: "strength", operator: "in", result: "haloPaint" }), _jsx("feComposite", { in: "haloPaint", in2: "haloGuard", operator: "out", result: "halo" }), _jsx("feMerge", { children: _jsx("feMergeNode", { in: "halo" }) })] })) }));
11
+ return (_jsx("filter", { id: id, filterUnits: "userSpaceOnUse", x: bounds?.x ?? x, y: bounds?.y ?? y, width: bounds?.width ?? width, height: bounds?.height ?? height, colorInterpolationFilters: "sRGB", children: material === "clay" ? (_jsxs(_Fragment, { children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), _jsx("feGaussianBlur", { in: "footprint", stdDeviation: relief, result: "rounded" }), _jsx("feOffset", { in: "rounded", dx: relief * 0.7, dy: relief * 0.9, result: "lower" }), _jsx("feComposite", { in: "footprint", in2: "lower", operator: "out", result: "top" }), _jsx("feGaussianBlur", { in: "top", stdDeviation: relief * 0.45, result: "softTop" }), _jsx("feFlood", { floodColor: "var(--kind-ui-pie-clay-light, #fff)", floodOpacity: "var(--kind-ui-pie-clay-highlight, 0.55)" }), _jsx("feComposite", { in2: "softTop", operator: "in", result: "light" }), _jsx("feOffset", { in: "rounded", dx: -relief * 0.8, dy: -relief, result: "upper" }), _jsx("feComposite", { in: "footprint", in2: "upper", operator: "out", result: "bottom" }), _jsx("feGaussianBlur", { in: "bottom", stdDeviation: relief * 0.45, result: "softBottom" }), _jsx("feFlood", { floodColor: "var(--kind-ui-pie-clay-shade, #17212b)", floodOpacity: "var(--kind-ui-pie-clay-shadow, 0.32)" }), _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: "finished" }), _jsx(NativePaintColor, {})] })) : (_jsxs(_Fragment, { children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), _jsx("feMorphology", { in: "footprint", operator: "dilate", radius: 1.5, result: "haloGuard" }), _jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: 4.5, result: "soft" }), _jsx("feFlood", { floodOpacity: "var(--kind-ui-pie-glow-opacity, 0.8)", result: "strength" }), _jsx("feComposite", { in: "soft", in2: "strength", operator: "in", result: "haloPaint" }), _jsx("feComposite", { in: "haloPaint", in2: "haloGuard", operator: "out", result: "halo" }), _jsx("feMerge", { children: _jsx("feMergeNode", { in: "halo" }) })] })) }));
12
12
  }
13
13
  /** The enclosing native-paint mask owns alpha; this surface contributes color only. */
14
14
  function NativePaintColor() {
@@ -0,0 +1,4 @@
1
+ type PiePinComponentKind = "series" | "tooltip";
2
+ export declare function registerPiePinComponent(component: object, kind: PiePinComponentKind): void;
3
+ export declare function piePinComponentKind(component: unknown): PiePinComponentKind | undefined;
4
+ export {};
@@ -0,0 +1,10 @@
1
+ // Canonical exports register themselves; resolving children never imports their renderers.
2
+ const components = new WeakMap();
3
+ export function registerPiePinComponent(component, kind) {
4
+ components.set(component, kind);
5
+ }
6
+ export function piePinComponentKind(component) {
7
+ return typeof component === "function" || (typeof component === "object" && component !== null)
8
+ ? components.get(component)
9
+ : undefined;
10
+ }
@@ -5,8 +5,12 @@ import { type PieMaterial } from "./pie-material.js";
5
5
  export type PieSeriesProps<DataPoint = unknown, Value = unknown> = Omit<ComponentProps<typeof Pie<DataPoint, Value>>, "isAnimationActive"> & {
6
6
  /** Opt-in category colors from Root.config; requires explicit series data. */
7
7
  categoryKey?: CategoryKey<DataPoint> | undefined;
8
+ /** Explicitly share this Pie's category identities and actions with Root/Legend. */
9
+ interactionBinding?: "root";
8
10
  /** Finish on default native sectors; custom shapes, filters and CSS transforms keep ownership. */
9
11
  material?: PieMaterial | undefined;
12
+ /** Categories receiving glow instead of material; requires categoryKey and explicit data. Unknown IDs are ignored. */
13
+ glowCategories?: readonly string[] | undefined;
10
14
  /** Stable sector identity; defaults to the native nameKey value. */
11
15
  emphasisKey?: ((payload: unknown) => string | number | undefined) | undefined;
12
16
  };