@kind-ui/charts 0.1.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 (121) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +966 -0
  4. package/dist/activity-rings.d.ts +47 -0
  5. package/dist/activity-rings.js +55 -0
  6. package/dist/animation.d.ts +27 -0
  7. package/dist/animation.js +102 -0
  8. package/dist/area-chart.d.ts +9 -0
  9. package/dist/area-chart.js +33 -0
  10. package/dist/area-clay.d.ts +2 -0
  11. package/dist/area-clay.js +6 -0
  12. package/dist/area-material.d.ts +8 -0
  13. package/dist/area-material.js +66 -0
  14. package/dist/area-series.d.ts +11 -0
  15. package/dist/area-series.js +34 -0
  16. package/dist/bar-category.d.ts +5 -0
  17. package/dist/bar-category.js +35 -0
  18. package/dist/bar-chart.d.ts +17 -0
  19. package/dist/bar-chart.js +42 -0
  20. package/dist/bar-clay.d.ts +4 -0
  21. package/dist/bar-clay.js +6 -0
  22. package/dist/bar-material.d.ts +8 -0
  23. package/dist/bar-material.js +19 -0
  24. package/dist/bar-paper.d.ts +2 -0
  25. package/dist/bar-paper.js +6 -0
  26. package/dist/bar-series.d.ts +13 -0
  27. package/dist/bar-series.js +178 -0
  28. package/dist/box-material.d.ts +14 -0
  29. package/dist/box-material.js +30 -0
  30. package/dist/box-plot.d.ts +50 -0
  31. package/dist/box-plot.js +218 -0
  32. package/dist/category-cells.d.ts +6 -0
  33. package/dist/category-cells.js +41 -0
  34. package/dist/chart-context.d.ts +11 -0
  35. package/dist/chart-context.js +9 -0
  36. package/dist/combo-chart.d.ts +16 -0
  37. package/dist/combo-chart.js +64 -0
  38. package/dist/configured-line-chart.d.ts +79 -0
  39. package/dist/configured-line-chart.js +77 -0
  40. package/dist/emphasis.d.ts +44 -0
  41. package/dist/emphasis.js +128 -0
  42. package/dist/heatmap-model.d.ts +38 -0
  43. package/dist/heatmap-model.js +73 -0
  44. package/dist/heatmap.d.ts +56 -0
  45. package/dist/heatmap.js +366 -0
  46. package/dist/histogram-chart.d.ts +14 -0
  47. package/dist/histogram-chart.js +27 -0
  48. package/dist/histogram-data.d.ts +17 -0
  49. package/dist/histogram-data.js +66 -0
  50. package/dist/histogram-material.d.ts +13 -0
  51. package/dist/histogram-material.js +8 -0
  52. package/dist/histogram-series.d.ts +16 -0
  53. package/dist/histogram-series.js +67 -0
  54. package/dist/index.d.ts +45 -0
  55. package/dist/index.js +36 -0
  56. package/dist/legend.d.ts +16 -0
  57. package/dist/legend.js +30 -0
  58. package/dist/line-chart.d.ts +41 -0
  59. package/dist/line-chart.js +160 -0
  60. package/dist/line-material.d.ts +9 -0
  61. package/dist/line-material.js +24 -0
  62. package/dist/line-series.d.ts +15 -0
  63. package/dist/line-series.js +40 -0
  64. package/dist/pie-chart.d.ts +17 -0
  65. package/dist/pie-chart.js +68 -0
  66. package/dist/pie-material.d.ts +19 -0
  67. package/dist/pie-material.js +16 -0
  68. package/dist/pie-series.d.ts +14 -0
  69. package/dist/pie-series.js +208 -0
  70. package/dist/polar-chart.d.ts +33 -0
  71. package/dist/polar-chart.js +145 -0
  72. package/dist/polar-material.d.ts +7 -0
  73. package/dist/polar-material.js +9 -0
  74. package/dist/polar-series.d.ts +17 -0
  75. package/dist/polar-series.js +169 -0
  76. package/dist/radar-interaction.d.ts +29 -0
  77. package/dist/radar-interaction.js +138 -0
  78. package/dist/radial-bar-label.d.ts +15 -0
  79. package/dist/radial-bar-label.js +136 -0
  80. package/dist/radial-category.d.ts +4 -0
  81. package/dist/radial-category.js +4 -0
  82. package/dist/root.d.ts +14 -0
  83. package/dist/root.js +29 -0
  84. package/dist/sankey-chart.d.ts +33 -0
  85. package/dist/sankey-chart.js +142 -0
  86. package/dist/sankey-colors.d.ts +10 -0
  87. package/dist/sankey-colors.js +6 -0
  88. package/dist/sankey-data.d.ts +30 -0
  89. package/dist/sankey-data.js +65 -0
  90. package/dist/sankey-finish.d.ts +13 -0
  91. package/dist/sankey-finish.js +10 -0
  92. package/dist/sankey-legend.d.ts +14 -0
  93. package/dist/sankey-legend.js +9 -0
  94. package/dist/sankey-marks.d.ts +31 -0
  95. package/dist/sankey-marks.js +67 -0
  96. package/dist/sankey-table.d.ts +11 -0
  97. package/dist/sankey-table.js +8 -0
  98. package/dist/scatter-chart.d.ts +16 -0
  99. package/dist/scatter-chart.js +64 -0
  100. package/dist/scatter-material.d.ts +8 -0
  101. package/dist/scatter-material.js +39 -0
  102. package/dist/scatter-series.d.ts +11 -0
  103. package/dist/scatter-series.js +74 -0
  104. package/dist/scatter-tooltip.d.ts +24 -0
  105. package/dist/scatter-tooltip.js +96 -0
  106. package/dist/styles.css +443 -0
  107. package/dist/surface-material.d.ts +13 -0
  108. package/dist/surface-material.js +7 -0
  109. package/dist/tooltip-content.d.ts +19 -0
  110. package/dist/tooltip-content.js +60 -0
  111. package/dist/tooltip-number.d.ts +4 -0
  112. package/dist/tooltip-number.js +68 -0
  113. package/dist/tooltip.d.ts +29 -0
  114. package/dist/tooltip.js +73 -0
  115. package/dist/types.d.ts +11 -0
  116. package/dist/types.js +1 -0
  117. package/dist/waterfall-data.d.ts +24 -0
  118. package/dist/waterfall-data.js +45 -0
  119. package/dist/waterfall-series.d.ts +15 -0
  120. package/dist/waterfall-series.js +35 -0
  121. package/package.json +49 -0
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /** Sankey-local surface treatment. Never displace the quantitative silhouette. */
4
+ export function SankeyFinishFilter({ id, finish, thickness, bounds, }) {
5
+ const edge = Math.min(1.2, thickness / 6);
6
+ const relief = Math.min(4, thickness / 4);
7
+ // A 0.6px blur disappeared in Chromium probes; retain a measured thin-flow halo.
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" }), finish === "paper" ? (_jsxs(_Fragment, { children: [_jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.08 0.2", numOctaves: 1, seed: 11, result: "pencil" }), _jsx("feColorMatrix", { in: "pencil", type: "luminanceToAlpha", result: "pencilMask" }), _jsx("feComponentTransfer", { in: "pencilMask", result: "sketch", children: _jsx("feFuncA", { type: "linear", slope: 1.6, intercept: -0.15 }) }), _jsx("feComposite", { in: "edge", in2: "sketch", operator: "in", result: "sketchEdge" }), _jsx("feFlood", { floodColor: "#17212b", floodOpacity: 0.5 }), _jsx("feComposite", { in2: "sketchEdge", operator: "in", result: "contour" }), _jsx("feComposite", { in: "contour", in2: "SourceGraphic", operator: "atop", result: "body" })] })) : (_jsxs(_Fragment, { children: [_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: finish === "paper" ? 0.14 : 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
+ }
@@ -0,0 +1,14 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from "react";
2
+ import type { SankeyNodeConfig } from "./sankey-colors.js";
3
+ export type SankeyLegendProps = Omit<ComponentPropsWithRef<"ul">, "children"> & {
4
+ /** Share this explicit reference with SankeyChart.nodeConfig. Config order owns legend order. */
5
+ config: SankeyNodeConfig;
6
+ children?: (item: {
7
+ key: string;
8
+ label: string;
9
+ color: string;
10
+ marker: ReactNode;
11
+ }) => ReactNode;
12
+ };
13
+ /** 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;
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /** Node identity legend; solid links inherit source color, gradient links use both endpoints. */
4
+ export function SankeyLegend({ config, children, ...props }) {
5
+ return (_jsx("ul", { "aria-label": "Chart legend", ...props, "data-kind-ui": "chart-legend", children: Object.entries(config).map(([key, item]) => {
6
+ const marker = (_jsx("span", { "aria-hidden": "true", "data-kind-ui": "chart-indicator", style: { "--kind-ui-chart-indicator-color": item.color } }));
7
+ return (_jsx("li", { "data-kind-ui": "chart-legend-item", "data-node": key, children: _jsx("span", { children: children ? (children({ key, label: item.label, color: item.color, marker })) : (_jsxs(_Fragment, { children: [marker, item.label] })) }) }, key));
8
+ }) }));
9
+ }
@@ -0,0 +1,31 @@
1
+ import { type SVGProps } from "react";
2
+ import type { SankeyLinkProps as NativeLinkProps, SankeyNodeProps as NativeNodeProps } from "recharts";
3
+ import { type SankeyFinish } from "./sankey-finish.js";
4
+ export type SankeyMaterial = "solid" | "gradient";
5
+ export type SankeyLinkProps = Omit<NativeLinkProps, "payload"> & {
6
+ payload: NativeLinkProps["payload"] & {
7
+ id: string;
8
+ source: NativeNodeProps["payload"] & {
9
+ id: string;
10
+ };
11
+ target: NativeNodeProps["payload"] & {
12
+ id: string;
13
+ };
14
+ };
15
+ material?: SankeyMaterial;
16
+ finish?: SankeyFinish;
17
+ color?: string | undefined;
18
+ targetColor?: string | undefined;
19
+ /** Presentation and handlers; computed path and flow width always win. */
20
+ pathProps?: SVGProps<SVGPathElement>;
21
+ };
22
+ export declare function SankeyLink({ sourceX, sourceY, targetX, targetY, sourceControlX, targetControlX, linkWidth, sourceRelativeY: _sourceRelativeY, targetRelativeY: _targetRelativeY, index: _index, payload, material, finish, color, targetColor, pathProps, ...presentation }: SankeyLinkProps): import("react").JSX.Element;
23
+ export type SankeyNodeProps = Omit<NativeNodeProps, "payload"> & {
24
+ payload: NativeNodeProps["payload"] & {
25
+ id: string;
26
+ };
27
+ color?: string | undefined;
28
+ rectProps?: SVGProps<SVGRectElement>;
29
+ finish?: SankeyFinish;
30
+ };
31
+ export declare function SankeyNode({ x, y, width, height, color, rectProps, finish, index: _index, payload, ...presentation }: SankeyNodeProps): import("react").JSX.Element;
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { motion, useMotionValue, useTransform } from "motion/react";
4
+ import { use, useId, useLayoutEffect, useRef, useState } from "react";
5
+ import { SankeyMotion } from "./sankey-chart.js";
6
+ import { SankeyColors, sankeyColor } from "./sankey-colors.js";
7
+ import { SankeyFinishFilter } from "./sankey-finish.js";
8
+ export function SankeyLink({ sourceX, sourceY, targetX, targetY, sourceControlX, targetControlX, linkWidth, sourceRelativeY: _sourceRelativeY, targetRelativeY: _targetRelativeY, index: _index, payload, material = "solid", finish = "plain", color, targetColor, pathProps, ...presentation }) {
9
+ const config = use(SankeyColors);
10
+ color ??= (config ? sankeyColor(config, payload.source.id) : undefined) ?? "#4f46e5";
11
+ targetColor ??= (config ? sankeyColor(config, payload.target.id) : undefined) ?? "#06b6d4";
12
+ const id = `sankey-${useId().replace(/:/g, "")}`;
13
+ const { reveal, progress, width } = use(SankeyMotion);
14
+ const settled = useMotionValue(1);
15
+ const delay = Math.max(0, Math.min(1, sourceX / Math.max(1, width))) * 0.55;
16
+ const dash = useTransform(progress ?? settled, (value) => `${Math.max(0, Math.min(1, (value - delay) / 0.45))} 1`);
17
+ const native = useRef(null);
18
+ const [transformed, setTransformed] = useState();
19
+ // biome-ignore lint/correctness/useExhaustiveDependencies: consumer styles/classes can own transforms.
20
+ useLayoutEffect(() => {
21
+ const path = native.current?.querySelector("path:not([data-kind-ui])");
22
+ if (!path)
23
+ return;
24
+ const css = getComputedStyle(path);
25
+ setTransformed(css.transform !== "none" ||
26
+ css.translate !== "none" ||
27
+ css.rotate !== "none" ||
28
+ css.scale !== "none");
29
+ }, [pathProps, presentation.className, presentation.style, presentation.transform]);
30
+ const mask = reveal &&
31
+ transformed === false &&
32
+ presentation.transform === undefined &&
33
+ pathProps?.transform === undefined;
34
+ const d = `M${sourceX},${sourceY}C${sourceControlX},${sourceY} ${targetControlX},${targetY} ${targetX},${targetY}`;
35
+ const finished = finish !== "plain" &&
36
+ presentation.filter == null &&
37
+ pathProps?.filter == null &&
38
+ presentation.style?.filter == null &&
39
+ pathProps?.style?.filter == null;
40
+ const pad = linkWidth / 2 + 3;
41
+ const bounds = {
42
+ x: Math.min(sourceX, targetX, sourceControlX, targetControlX) - pad,
43
+ y: Math.min(sourceY, targetY) - pad,
44
+ width: Math.max(sourceX, targetX, sourceControlX, targetControlX) -
45
+ Math.min(sourceX, targetX, sourceControlX, targetControlX) +
46
+ pad * 2,
47
+ height: Math.abs(targetY - sourceY) + pad * 2,
48
+ };
49
+ return (_jsxs("g", { ref: native, mask: mask ? `url(#${id}-entrance)` : undefined, children: [mask && (_jsx("defs", { children: _jsx("mask", { id: `${id}-entrance`, maskUnits: "userSpaceOnUse", ...bounds, style: { maskType: "alpha" }, children: _jsx(motion.path, { "data-kind-ui": "sankey-link-entrance", d: d, fill: "none", stroke: "white", strokeWidth: linkWidth + 6, pathLength: 1, strokeDasharray: dash }) }) })), finished && (_jsx("defs", { children: _jsx(SankeyFinishFilter, { id: `${id}-finish`, finish: finish, thickness: linkWidth, bounds: bounds }) })), material === "gradient" && (_jsx("defs", { children: _jsxs("linearGradient", { id: id, gradientUnits: "userSpaceOnUse", x1: sourceX, y1: sourceY, x2: targetX, y2: targetY, children: [_jsx("stop", { stopColor: color }), _jsx("stop", { offset: "1", stopColor: targetColor })] }) })), _jsx("path", { ...presentation, ...pathProps, filter: pathProps?.filter ?? presentation.filter ?? (finished ? `url(#${id}-finish)` : undefined), d: d, fill: "none", stroke: material === "gradient" ? `url(#${id})` : color, strokeWidth: linkWidth, style: { ...presentation.style, ...pathProps?.style, strokeWidth: linkWidth } })] }));
50
+ }
51
+ export function SankeyNode({ x, y, width, height, color, rectProps, finish = "plain", index: _index, payload, ...presentation }) {
52
+ const config = use(SankeyColors);
53
+ color ??= (config ? sankeyColor(config, payload.id) : undefined) ?? "#4f46e5";
54
+ const id = `sankey-node-${useId().replace(/:/g, "")}`;
55
+ const finished = finish !== "plain" &&
56
+ presentation.filter == null &&
57
+ rectProps?.filter == null &&
58
+ presentation.style?.filter == null &&
59
+ rectProps?.style?.filter == null;
60
+ const rect = (_jsx("rect", { ...presentation, ...rectProps, filter: rectProps?.filter ?? presentation.filter ?? (finished ? `url(#${id})` : undefined), x: x, y: y, width: width, height: height, fill: color, style: { ...presentation.style, ...rectProps?.style, width, height } }));
61
+ if (!finished)
62
+ return rect;
63
+ return (_jsxs("g", { children: [_jsx("defs", { children: _jsx(SankeyFinishFilter, { id: id, finish: finish, thickness: Math.min(width, height),
64
+ // Viewport-relative bounds preserve inherited/class-based and em/percent strokes.
65
+ // The native SVG viewport still clips output; no DOM measurements or style parsing.
66
+ bounds: { x: "-100%", y: "-100%", width: "300%", height: "300%" } }) }), rect] }));
67
+ }
@@ -0,0 +1,11 @@
1
+ import type { ComponentPropsWithRef } from "react";
2
+ import { type SankeyFlowData, type SankeyFlowLink } from "./sankey-data.js";
3
+ export type SankeyTableProps = Omit<ComponentPropsWithRef<"table">, "children"> & {
4
+ data: SankeyFlowData;
5
+ caption: string;
6
+ formatValue?: (value: number) => string;
7
+ onInspect?: (link: SankeyFlowLink) => void;
8
+ activeLinkId?: string | null;
9
+ };
10
+ /** Native table and buttons provide an independent, complete keyboard flow inspector. */
11
+ export declare function SankeyTable({ data, caption, formatValue, onInspect, activeLinkId, ...props }: SankeyTableProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { prepareSankeyData } from "./sankey-data.js";
4
+ /** Native table and buttons provide an independent, complete keyboard flow inspector. */
5
+ export function SankeyTable({ data, caption, formatValue = String, onInspect, activeLinkId, ...props }) {
6
+ const validated = prepareSankeyData(data);
7
+ return (_jsxs("table", { ...props, children: [_jsx("caption", { children: caption }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Flow" }), _jsx("th", { scope: "col", children: "Source" }), _jsx("th", { scope: "col", children: "Target" }), _jsx("th", { scope: "col", children: "Value" })] }) }), _jsx("tbody", { children: validated.links.map((link, index) => (_jsxs("tr", { "data-active": activeLinkId === link.id || undefined, children: [_jsx("th", { scope: "row", children: onInspect ? (_jsx("button", { type: "button", "aria-pressed": activeLinkId === link.id, onClick: () => onInspect(data.links[index]), children: link.id })) : (link.id) }), _jsx("td", { children: validated.nodes[link.source]?.name }), _jsx("td", { children: validated.nodes[link.target]?.name }), _jsx("td", { children: formatValue(link.value) })] }, link.id))) })] }));
8
+ }
@@ -0,0 +1,16 @@
1
+ import { type MotionValue } from "motion/react";
2
+ import { type ComponentProps } from "react";
3
+ import { ScatterChart as EngineScatterChart } from "recharts";
4
+ import { type LineAnimation } from "./animation.js";
5
+ export type ScatterAnimation = LineAnimation;
6
+ export type ScatterChartProps = ComponentProps<typeof EngineScatterChart> & {
7
+ animate?: boolean | ScatterAnimation | undefined;
8
+ };
9
+ export declare const ScatterMotion: import("react").Context<{
10
+ reveal: boolean;
11
+ options: ScatterAnimation;
12
+ finish: () => void;
13
+ progress: MotionValue<number> | null;
14
+ }>;
15
+ /** Native numeric geometry and item selection, with optional entrance fade and shared tooltip Motion. */
16
+ export declare function ScatterChart({ animate, children, ...props }: ScatterChartProps): import("react").JSX.Element;
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { animate as animateValue, useMotionValue } from "motion/react";
4
+ import { createContext, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
5
+ import { ScatterChart as EngineScatterChart } from "recharts";
6
+ import { MotionContext } from "./animation.js";
7
+ import { LineChartFrame } from "./line-chart.js";
8
+ export const ScatterMotion = createContext({
9
+ reveal: false,
10
+ options: {},
11
+ finish: () => { },
12
+ progress: null,
13
+ });
14
+ const query = "(prefers-reduced-motion: reduce)";
15
+ function subscribe(change) {
16
+ const media = window.matchMedia(query);
17
+ media.addEventListener("change", change);
18
+ return () => media.removeEventListener("change", change);
19
+ }
20
+ const snapshot = () => window.matchMedia(query).matches;
21
+ const serverSnapshot = () => true;
22
+ const defaultHover = { type: "spring", stiffness: 210, damping: 28, mass: 0.8 };
23
+ /** Native numeric geometry and item selection, with optional entrance fade and shared tooltip Motion. */
24
+ export function ScatterChart({ animate = false, children, ...props }) {
25
+ const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
26
+ const [interacted, setInteracted] = useState(false);
27
+ const enabled = animate !== false && !reduced;
28
+ const interact = useCallback(() => setInteracted(true), []);
29
+ const finish = useCallback(() => {
30
+ if (enabled)
31
+ setInteracted(true);
32
+ }, [enabled]);
33
+ const options = typeof animate === "object" ? animate : {};
34
+ const reveal = enabled && !interacted;
35
+ const progress = useMotionValue(1);
36
+ const duration = options.revealDurationMs ?? 700;
37
+ const easing = options.revealEasing ?? "easeOut";
38
+ const started = useRef(false);
39
+ const previous = useRef([duration, easing]);
40
+ const previousEnabled = useRef(enabled);
41
+ useLayoutEffect(() => {
42
+ const inputs = [duration, easing];
43
+ if ((started.current && inputs.some((value, index) => value !== previous.current[index])) ||
44
+ (previousEnabled.current && !enabled))
45
+ interact();
46
+ previous.current = inputs;
47
+ previousEnabled.current = enabled;
48
+ });
49
+ useLayoutEffect(() => {
50
+ if (!reveal) {
51
+ progress.set(1);
52
+ return;
53
+ }
54
+ started.current = true;
55
+ progress.set(0);
56
+ const controls = animateValue(progress, 1, {
57
+ duration: Math.max(0, duration) / 1000,
58
+ ease: easing,
59
+ onComplete: interact,
60
+ });
61
+ return () => controls.stop();
62
+ }, [reveal, duration, easing, progress, interact]);
63
+ return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(ScatterMotion, { value: { reveal, options, finish, progress }, children: _jsx("div", { style: { display: "contents" }, onFocusCapture: interact, onPointerDownCapture: interact, onPointerMoveCapture: interact, onKeyDownCapture: interact, children: _jsx(LineChartFrame, { chartProps: props, engine: EngineScatterChart, motionEnabled: enabled, interrupt: finish, children: children }) }) }) }));
64
+ }
@@ -0,0 +1,8 @@
1
+ import { type ComponentProps } from "react";
2
+ import { Symbols } from "recharts";
3
+ import type { LineMaterial } from "./line-material.js";
4
+ export type ScatterMaterial = LineMaterial;
5
+ /** Finish only a native symbol. Quantitative path, paint alpha and point props survive. */
6
+ export declare function ScatterMaterialSymbol({ material, ...props }: ComponentProps<typeof Symbols> & {
7
+ material: Exclude<ScatterMaterial, "plain">;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useId } from "react";
4
+ import { Symbols } from "recharts";
5
+ /** Finish only a native symbol. Quantitative path, paint alpha and point props survive. */
6
+ export function ScatterMaterialSymbol({ material, ...props }) {
7
+ const generatedId = useId();
8
+ const id = `kind-ui-scatter-${generatedId.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
9
+ // Do not compete with per-Cell paint pipelines or unresolved native geometry.
10
+ if (props.filter !== undefined ||
11
+ props.style?.filter !== undefined ||
12
+ props.clipPath !== undefined ||
13
+ props.style?.clipPath !== undefined ||
14
+ props.cx == null ||
15
+ props.cy == null ||
16
+ !Number.isFinite(props.size) ||
17
+ (props.size ?? 0) <= 0)
18
+ return _jsx(Symbols, { ...props });
19
+ // No minimum pixel radius: effects shrink with small marks rather than enlarging them.
20
+ const span = props.sizeType === "diameter" ? (props.size ?? 64) : Math.sqrt(props.size ?? 64);
21
+ const declaredStroke = props.style?.stroke ?? props.stroke;
22
+ const stroke = declaredStroke === undefined || declaredStroke === "none"
23
+ ? 0
24
+ : Number(props.style?.strokeWidth ?? props.strokeWidth ?? 1);
25
+ const round = (props.type ?? "circle") === "circle" ||
26
+ (props.style?.strokeLinejoin ?? props.strokeLinejoin) === "round";
27
+ const miter = Number(props.style?.strokeMiterlimit ?? props.strokeMiterlimit ?? 4);
28
+ const strokeExtent = Math.abs(stroke) * (round ? 0.5 : Math.max(1, Number.isFinite(miter) ? miter : 4) / 2);
29
+ // A consumer stroke wider than the bounded filter region keeps native rendering.
30
+ if (!Number.isFinite(stroke) || strokeExtent > span)
31
+ return _jsx(Symbols, { ...props });
32
+ const guard = 1 + strokeExtent;
33
+ const rim = Math.min(0.9, span * 0.055);
34
+ const relief = Math.min(3, span * 0.17);
35
+ const soft = Math.min(2, span * 0.1);
36
+ const halo = Math.min(1.8, span * 0.065);
37
+ const extent = span * 3 + strokeExtent;
38
+ return (_jsxs(_Fragment, { children: [_jsxs("defs", { "data-kind-ui": "scatter-material", "data-material": material, pointerEvents: "none", children: [material === "glow" && (_jsxs(_Fragment, { children: [_jsx("clipPath", { id: `${id}-geometry`, clipPathUnits: "userSpaceOnUse", children: _jsx(Symbols, { cx: props.cx, cy: props.cy, size: props.size ?? 64, ...(props.type !== undefined ? { type: props.type } : {}), ...(props.sizeType !== undefined ? { sizeType: props.sizeType } : {}), fill: "#000", stroke: "#000", strokeWidth: props.strokeWidth ?? 0 }) }), _jsx("filter", { id: `${id}-guard`, x: -1, y: -1, width: 3, height: 3, filterUnits: "objectBoundingBox", primitiveUnits: "userSpaceOnUse", children: _jsx("feMorphology", { in: "SourceGraphic", operator: "dilate", radius: guard }) }), _jsxs("mask", { id: `${id}-outside`, maskUnits: "userSpaceOnUse", x: (props.cx ?? 0) - extent, y: (props.cy ?? 0) - extent, width: extent * 2, height: extent * 2, children: [_jsx("rect", { x: (props.cx ?? 0) - extent, y: (props.cy ?? 0) - extent, width: extent * 2, height: extent * 2, fill: "#fff" }), _jsx("g", { filter: `url(#${id}-guard)`, children: _jsx("rect", { x: (props.cx ?? 0) - extent, y: (props.cy ?? 0) - extent, width: extent * 2, height: extent * 2, fill: "#000", clipPath: `url(#${id}-geometry)` }) })] }), _jsxs("filter", { id: `${id}-halo`, x: -1, y: -1, width: 3, height: 3, filterUnits: "objectBoundingBox", primitiveUnits: "userSpaceOnUse", colorInterpolationFilters: "sRGB", children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "painted", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), _jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: halo, result: "halo" }), _jsx("feFlood", { floodOpacity: "var(--kind-ui-scatter-glow-opacity, 0.65)", result: "strength" }), _jsx("feComposite", { in: "halo", in2: "strength", operator: "in", result: "softHalo" }), _jsx("feComposite", { in: "softHalo", in2: "painted", operator: "out" })] })] })), _jsxs("filter", { id: id, x: -1, y: -1, width: 3, height: 3, filterUnits: "objectBoundingBox", primitiveUnits: "userSpaceOnUse", colorInterpolationFilters: "sRGB", children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), material === "paper" ? (_jsxs(_Fragment, { children: [_jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.65 0.3", numOctaves: 1, seed: 7, result: "fiber" }), _jsx("feFlood", { floodColor: "var(--kind-ui-scatter-paper-fiber, #fff)", floodOpacity: "var(--kind-ui-scatter-paper-grain, 0.13)" }), _jsx("feComposite", { in2: "fiber", operator: "in", result: "texture" }), _jsx("feComposite", { in: "texture", in2: "SourceGraphic", operator: "atop", result: "paper" }), _jsx("feMorphology", { in: "footprint", operator: "erode", radius: rim, result: "inside" }), _jsx("feComposite", { in: "footprint", in2: "inside", operator: "out", result: "edge" }), _jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.13 0.22", numOctaves: 1, seed: 11, result: "pencil" }), _jsx("feColorMatrix", { in: "pencil", type: "luminanceToAlpha", result: "pencilMask" }), _jsx("feComponentTransfer", { in: "pencilMask", result: "sketch", children: _jsx("feFuncA", { type: "linear", slope: 2, intercept: -0.2 }) }), _jsx("feComposite", { in: "edge", in2: "sketch", operator: "in", result: "sketchEdge" }), _jsx("feColorMatrix", { in: "SourceGraphic", type: "matrix", values: "0.25 0 0 0 0 0 0.25 0 0 0 0 0 0.25 0 0 0 0 0 1 0", result: "ink" }), _jsx("feComposite", { in: "ink", in2: "sketchEdge", operator: "in", result: "contour" }), _jsx("feComposite", { in: "contour", in2: "paper", operator: "atop" })] })) : material === "clay" ? (_jsxs(_Fragment, { children: [_jsx("feOffset", { in: "footprint", dx: relief, dy: relief, result: "lower" }), _jsx("feComposite", { in: "footprint", in2: "lower", operator: "out", result: "top" }), _jsx("feGaussianBlur", { in: "top", stdDeviation: soft, result: "softTop" }), _jsx("feFlood", { floodColor: "var(--kind-ui-scatter-clay-light, #fff)", floodOpacity: 0.62 }), _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: "out", result: "bottom" }), _jsx("feGaussianBlur", { in: "bottom", stdDeviation: soft, result: "softBottom" }), _jsx("feFlood", { floodColor: "var(--kind-ui-scatter-clay-shade, #17212b)", floodOpacity: 0.38 }), _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" }), _jsx("feTurbulence", { type: "fractalNoise", baseFrequency: 0.7, numOctaves: 1, seed: 17, result: "grain" }), _jsx("feFlood", { floodColor: "#fff", floodOpacity: 0.035 }), _jsx("feComposite", { in2: "grain", operator: "in", result: "texture" }), _jsx("feComposite", { in: "texture", in2: "body", operator: "atop" })] })) : (_jsxs(_Fragment, { children: [_jsx("feMorphology", { in: "footprint", operator: "erode", radius: rim, result: "inside" }), _jsx("feComposite", { in: "footprint", in2: "inside", operator: "out", result: "edge" }), _jsx("feFlood", { floodColor: "var(--kind-ui-scatter-glow-light, #fff)", floodOpacity: 0.55 }), _jsx("feComposite", { in2: "edge", operator: "in", result: "light" }), _jsx("feComposite", { in: "light", in2: "SourceGraphic", operator: "atop", result: "body" }), _jsx("feFlood", { floodColor: "var(--kind-ui-scatter-glow-light, #fff)", floodOpacity: 0.12, result: "bloom" }), _jsx("feComposite", { in: "bloom", in2: "body", operator: "atop", result: "luminousBody" })] }))] })] }), material === "glow" ? (_jsxs(_Fragment, { children: [_jsx("use", { "data-kind-ui": "scatter-light", href: `#${id}-body`, filter: `url(#${id}-halo)`, mask: `url(#${id}-outside)`, pointerEvents: "none", "aria-hidden": "true", focusable: "false", tabIndex: -1 }), _jsx("g", { id: `${id}-body`, children: _jsx(Symbols, { ...props, filter: `url(#${id})` }) })] })) : (_jsx(Symbols, { ...props, filter: `url(#${id})` }))] }));
39
+ }
@@ -0,0 +1,11 @@
1
+ import { type ComponentProps } from "react";
2
+ import { Scatter } from "recharts";
3
+ import { type ScatterMaterial } from "./scatter-material.js";
4
+ export type ScatterSeriesProps = Omit<ComponentProps<typeof Scatter>, "isAnimationActive"> & {
5
+ /** Series identity is separate from numeric axis data keys. Required for controlled visibility. */
6
+ seriesKey?: string;
7
+ /** Finish built-in symbols; custom shapes and consumer filters retain ownership. */
8
+ material?: ScatterMaterial | undefined;
9
+ };
10
+ /** Native Scatter owns marks, Cells, labels, handlers, axis mapping and point payloads. */
11
+ export declare function ScatterSeries({ seriesKey, hide, fill, material, ...props }: ScatterSeriesProps): import("react").JSX.Element;
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { motion, useMotionValue, useTransform } from "motion/react";
4
+ import { use, useId, useLayoutEffect, useMemo, useRef } from "react";
5
+ import { DefaultZIndexes, Scatter, Symbols, usePlotArea, useXAxisDomain, useXAxisScale, useYAxisDomain, useYAxisScale, ZIndexLayer, } from "recharts";
6
+ import { useChart } from "./chart-context.js";
7
+ import { useLineInteraction } from "./line-chart.js";
8
+ import { ScatterMotion } from "./scatter-chart.js";
9
+ import { ScatterMaterialSymbol } from "./scatter-material.js";
10
+ // Delay by native screen X, rather than sorting or changing quantitative data.
11
+ function EntranceSymbol({ material, type, ...props }) {
12
+ const { progress } = use(ScatterMotion);
13
+ const settled = useMotionValue(1);
14
+ const area = usePlotArea();
15
+ const x = area && area.width > 0
16
+ ? Math.max(0, Math.min(1, ((props.cx ?? area.x) - area.x) / area.width))
17
+ : 0;
18
+ const opacity = useTransform(progress ?? settled, (value) => Math.max(0, Math.min(1, (value - x * 0.65) / 0.35)));
19
+ return (_jsx(motion.g, { "data-kind-ui": "scatter-point-entrance", "data-entrance-x": props.cx, initial: false, style: { opacity }, children: material === "plain" ? (_jsx(Symbols, { ...props, ...(type !== undefined ? { type } : {}) })) : (_jsx(ScatterMaterialSymbol, { ...props, material: material, ...(type !== undefined ? { type } : {}) })) }));
20
+ }
21
+ /** Native Scatter owns marks, Cells, labels, handlers, axis mapping and point payloads. */
22
+ export function ScatterSeries({ seriesKey, hide, fill, material = "plain", ...props }) {
23
+ const { config, visibleSeries } = useChart();
24
+ const { registerSeries, invalidate } = useLineInteraction();
25
+ const shapes = useMemo(() => {
26
+ function finish(option) {
27
+ if (option !== undefined && typeof option !== "string" && typeof option !== "boolean")
28
+ return option;
29
+ return (point) => (_jsx(EntranceSymbol, { material: material, type: typeof option === "string" ? option : "circle", ...point }));
30
+ }
31
+ return {
32
+ shape: finish(props.shape),
33
+ // Undefined activeShape must stay undefined: native activation/portal semantics.
34
+ activeShape: props.activeShape === undefined || props.activeShape === false
35
+ ? props.activeShape
36
+ : finish(props.activeShape),
37
+ };
38
+ }, [material, props.shape, props.activeShape]);
39
+ const generatedId = useId();
40
+ const id = props.id ?? generatedId;
41
+ const key = seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined);
42
+ const hidden = hide === true || (visibleSeries !== undefined && !visibleSeries.includes(key ?? ""));
43
+ const area = usePlotArea();
44
+ const xDomain = useXAxisDomain(props.xAxisId);
45
+ const yDomain = useYAxisDomain(props.yAxisId);
46
+ const xScale = useXAxisScale(props.xAxisId);
47
+ const yScale = useYAxisScale(props.yAxisId);
48
+ const geometry = area && area.width > 0 && area.height > 0 && xDomain && yDomain && xScale && yScale
49
+ ? JSON.stringify([area, xDomain.map((v) => xScale?.(v)), yDomain.map((v) => yScale?.(v))])
50
+ : undefined;
51
+ const inputs = [
52
+ props.data,
53
+ props.dataKey,
54
+ props.xAxisId,
55
+ props.yAxisId,
56
+ props.zAxisId,
57
+ hidden,
58
+ geometry,
59
+ ];
60
+ const previous = useRef(inputs);
61
+ useLayoutEffect(() => {
62
+ if (inputs.some((v, i) => v !== previous.current[i] && (i !== 6 || previous.current[i] !== undefined)))
63
+ invalidate();
64
+ previous.current = inputs;
65
+ });
66
+ useLayoutEffect(() => {
67
+ if (key !== undefined)
68
+ return registerSeries(id, key);
69
+ }, [id, key, registerSeries]);
70
+ if (key === undefined && visibleSeries !== undefined)
71
+ throw new Error("ScatterSeries requires seriesKey for controlled non-string dataKey");
72
+ const color = fill ?? (key && Object.hasOwn(config, key) ? `var(--color-${key})` : undefined);
73
+ return (_jsx(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.scatter, children: _jsx("g", { "data-kind-ui": "scatter-fade", children: _jsx(Scatter, { ...props, ...(shapes.shape !== undefined ? { shape: shapes.shape } : {}), ...(shapes.activeShape !== undefined ? { activeShape: shapes.activeShape } : {}), id: id, hide: hidden, ...(color !== undefined ? { fill: color } : {}), zIndex: 0, isAnimationActive: false }) }) }));
74
+ }
@@ -0,0 +1,24 @@
1
+ import { type ComponentPropsWithRef, type ReactNode } from "react";
2
+ import type { TooltipContentProps as NativeContentProps } from "recharts";
3
+ import { type TooltipProps } from "./animation.js";
4
+ import type { TooltipContentProps } from "./tooltip-content.js";
5
+ export type ScatterSizeDimension<Row = unknown> = {
6
+ /** Own top-level property key, or the same typed record accessor used by ZAxis. */
7
+ dataKey: string | ((record: Row) => number | null | undefined);
8
+ name?: string;
9
+ unit?: string;
10
+ };
11
+ export type ScatterTooltipContentProps<Row = unknown> = Omit<ComponentPropsWithRef<"div">, "children"> & {
12
+ tooltip: NativeContentProps;
13
+ /** Receives the selected native record, never a categorical label or chart-wide index. */
14
+ pointLabel?: (record: Row) => ReactNode;
15
+ /** Recovers zero/missing Z entries omitted by native Recharts, without changing marker geometry. */
16
+ zDimension?: ScatterSizeDimension<Row>;
17
+ missingValue?: ReactNode;
18
+ valueAnimation?: TooltipContentProps["valueAnimation"];
19
+ };
20
+ /** Dimension metadata uses each entry's dataKey; series identity only controls visibility/title. */
21
+ export declare function ScatterTooltipContent<Row = unknown>({ tooltip, pointLabel, zDimension, missingValue, valueAnimation, ...props }: ScatterTooltipContentProps<Row>): import("react").JSX.Element | null;
22
+ export type ScatterTooltipProps<Row = unknown> = TooltipProps & Pick<ScatterTooltipContentProps<Row>, "pointLabel" | "zDimension" | "missingValue">;
23
+ /** Shared bounded positioning/Motion with Scatter-aware default dimension content. */
24
+ export declare function ScatterTooltip<Row = unknown>({ pointLabel, zDimension, missingValue, valueAnimation, content, ...props }: ScatterTooltipProps<Row>): import("react").JSX.Element;
@@ -0,0 +1,96 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { use } from "react";
4
+ import { Tooltip } from "./animation.js";
5
+ import { useChart } from "./chart-context.js";
6
+ import { LineInteraction } from "./line-chart.js";
7
+ import { TooltipNumber } from "./tooltip-number.js";
8
+ /** Dimension metadata uses each entry's dataKey; series identity only controls visibility/title. */
9
+ export function ScatterTooltipContent({ tooltip, pointLabel, zDimension, missingValue = "No data", valueAnimation, ...props }) {
10
+ const { config, visibleSeries } = useChart();
11
+ const interaction = use(LineInteraction);
12
+ const seriesIdentity = (id) => id ? (interaction?.seriesKeys.get(id) ?? id) : undefined;
13
+ let entries = tooltip.active
14
+ ? tooltip.payload.filter((entry) => {
15
+ const series = seriesIdentity(entry.graphicalItemId);
16
+ return (!entry.hide &&
17
+ entry.type !== "none" &&
18
+ (visibleSeries === undefined || (series !== undefined && visibleSeries.includes(series))));
19
+ })
20
+ : [];
21
+ const first = entries[0];
22
+ if (zDimension &&
23
+ first &&
24
+ entries.length <= 2 &&
25
+ !entries.some((entry) => entry.dataKey === zDimension.dataKey)) {
26
+ const record = first.payload;
27
+ const raw = typeof zDimension.dataKey === "function"
28
+ ? zDimension.dataKey(first.payload)
29
+ : record !== null && typeof record === "object" && Object.hasOwn(record, zDimension.dataKey)
30
+ ? Reflect.get(record, zDimension.dataKey)
31
+ : undefined;
32
+ // Native Scatter omits only zero/missing Z. Nonzero entries survive filterNull,
33
+ // and Recharts wraps function keys, so reference equality cannot identify them.
34
+ if (raw === 0 || raw == null) {
35
+ const { value: _nativeValue, ...identity } = first;
36
+ entries = [
37
+ ...entries,
38
+ {
39
+ ...identity,
40
+ dataKey: zDimension.dataKey,
41
+ name: zDimension.name ?? "Size",
42
+ unit: zDimension.unit ?? "",
43
+ ...(typeof raw === "number" && Number.isFinite(raw) ? { value: raw } : {}),
44
+ },
45
+ ];
46
+ }
47
+ }
48
+ if (!entries.some((entry) => entry.value != null))
49
+ return null;
50
+ const series = seriesIdentity(first?.graphicalItemId);
51
+ const seriesLabel = series !== undefined ? config[series]?.label : undefined;
52
+ const title = pointLabel?.(first?.payload) ?? seriesLabel;
53
+ let hasValue = false;
54
+ const items = entries.map((entry, index) => {
55
+ // Native Scatter payload slots are X/Y/Z identities, including equal dataKey/name pairs.
56
+ const dimension = ["x", "y", "z"][index] ?? String(index);
57
+ const key = String(entry.dataKey ?? entry.name ?? dimension);
58
+ const meta = Object.hasOwn(config, key) ? config[key] : undefined;
59
+ let name = meta?.label ?? entry.name ?? key;
60
+ let value = missingValue;
61
+ if (entry.value != null) {
62
+ const format = entry.formatter ?? tooltip.formatter;
63
+ if (format) {
64
+ const result = format(entry.value, entry.name, entry, index, entries);
65
+ if (result == null)
66
+ return null;
67
+ if (Array.isArray(result))
68
+ [value, name] = result;
69
+ else
70
+ value = result;
71
+ }
72
+ else
73
+ value = meta?.formatValue
74
+ ? meta.formatValue(entry.value)
75
+ : Array.isArray(entry.value)
76
+ ? entry.value.join(" – ")
77
+ : `${entry.value}${entry.unit ?? ""}`;
78
+ hasValue = true;
79
+ }
80
+ return (_jsxs("li", { "data-kind-ui": "chart-tooltip-item", "data-dimension": key, children: [_jsx("span", { children: name }), _jsx("strong", { "data-kind-ui": "chart-tooltip-value", children: valueAnimation === "shuffle" &&
81
+ typeof entry.value === "number" &&
82
+ Number.isFinite(entry.value) &&
83
+ (typeof value === "string" || (typeof value === "number" && Number.isFinite(value))) ? (_jsx(TooltipNumber, { value: value })) : (value) })] }, `${entry.graphicalItemId ?? "point"}-${dimension}`));
84
+ });
85
+ if (!hasValue)
86
+ return null;
87
+ return (_jsxs("div", { role: tooltip.accessibilityLayer ? "status" : undefined, "aria-live": tooltip.accessibilityLayer ? "assertive" : undefined, "aria-atomic": "true", ...props, "data-kind-ui": "chart-tooltip", children: [title != null && (_jsx("strong", { "data-kind-ui": "chart-tooltip-label", children: tooltip.labelFormatter ? tooltip.labelFormatter(title, entries) : title })), _jsx("ul", { "data-kind-ui": "chart-tooltip-list", children: items })] }));
88
+ }
89
+ // A stable native-content adapter retains digit/width state across consumer rerenders.
90
+ function DefaultScatterContent({ pointLabel, zDimension, missingValue, valueAnimation, ...tooltip }) {
91
+ return (_jsx(ScatterTooltipContent, { tooltip: tooltip, ...(pointLabel ? { pointLabel } : {}), ...(zDimension ? { zDimension } : {}), ...(missingValue !== undefined ? { missingValue } : {}), ...(valueAnimation ? { valueAnimation } : {}) }));
92
+ }
93
+ /** Shared bounded positioning/Motion with Scatter-aware default dimension content. */
94
+ export function ScatterTooltip({ pointLabel, zDimension, missingValue, valueAnimation, content, ...props }) {
95
+ return (_jsx(Tooltip, { ...props, content: content ?? (_jsx(DefaultScatterContent, { active: false, payload: [], coordinate: undefined, activeIndex: undefined, accessibilityLayer: false, ...(pointLabel ? { pointLabel } : {}), ...(zDimension ? { zDimension } : {}), ...(missingValue !== undefined ? { missingValue } : {}), ...(valueAnimation ? { valueAnimation } : {}) })) }));
96
+ }