@kind-ui/charts 0.1.1 → 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 +26 -0
  2. package/README.md +674 -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-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
@@ -6,6 +6,7 @@ import { type RadialBarChartProps } from "./polar-chart.js";
6
6
  import { type RadialBarSeriesProps } from "./polar-series.js";
7
7
  import { type RootProps } from "./root.js";
8
8
  import type { SeriesConfig } from "./types.js";
9
+ type ActivityRootProps = RootProps extends infer Props ? Props extends object ? Omit<Props, "config" | "children"> : never : never;
9
10
  export type ActivityRing = {
10
11
  /** Stable metadata identity in config. Array order controls ring order. */
11
12
  key: string;
@@ -40,7 +41,7 @@ export type ActivityRingsProps = Omit<RadialBarChartProps<ActivityRingDatum>, "d
40
41
  legend?: false | LegendProps;
41
42
  /** Custom content receives native payload with original value and normalized progress. */
42
43
  tooltip?: false | TooltipProps;
43
- rootProps?: Omit<RootProps, "config" | "children" | "visibleSeries" | "onVisibleSeriesChange">;
44
+ rootProps?: ActivityRootProps;
44
45
  };
45
46
  /** A narrow progress recipe; native RadialBarChart remains the full composition escape hatch. */
46
47
  export declare function ActivityRings({ rings, config, domain, series, labels, legend, tooltip, rootProps, width, height, style, className, responsive, accessibilityLayer, ...chartProps }: ActivityRingsProps): import("react").JSX.Element;
@@ -5,10 +5,11 @@ import { Cell, LabelList, PolarAngleAxis, PolarRadiusAxis } from "recharts";
5
5
  import { Tooltip } from "./animation.js";
6
6
  import { ChartContext } from "./chart-context.js";
7
7
  import { Legend } from "./legend.js";
8
- import { RadialBarChart } from "./polar-chart.js";
8
+ import { RadialBarChartFrame } from "./polar-chart.js";
9
9
  import { RadialBarSeries } from "./polar-series.js";
10
10
  import { RadialBarLabel } from "./radial-bar-label.js";
11
11
  import { Root } from "./root.js";
12
+ import { resolveSeriesLabel } from "./series-label.js";
12
13
  import { TooltipContent } from "./tooltip-content.js";
13
14
  /** A narrow progress recipe; native RadialBarChart remains the full composition escape hatch. */
14
15
  export function ActivityRings({ rings, config, domain = [0, 100], series, labels = false, legend, tooltip, rootProps, width, height, style, className, responsive = true, accessibilityLayer = true, ...chartProps }) {
@@ -33,7 +34,7 @@ export function ActivityRings({ rings, config, domain = [0, 100], series, labels
33
34
  return {
34
35
  ...ring,
35
36
  // biome-ignore lint/style/noNonNullAssertion: Object.hasOwn above rejects ring keys absent from config.
36
- label: config[ring.key].label,
37
+ label: resolveSeriesLabel(ring.key, config[ring.key].label),
37
38
  rawValue: ring.value,
38
39
  progress: Math.max(0, Math.min(100, ((ring.value - min) / (max - min)) * 100)),
39
40
  };
@@ -41,7 +42,9 @@ export function ActivityRings({ rings, config, domain = [0, 100], series, labels
41
42
  // biome-ignore lint/style/noNonNullAssertion: Every key was validated against config while constructing data.
42
43
  const activeConfig = Object.fromEntries(keys.map((key) => [key, config[key]]));
43
44
  const describedBy = [chartProps["aria-describedby"], descriptionId].filter(Boolean).join(" ");
44
- return (_jsxs(Root, { ...rootProps, config: activeConfig, children: [_jsx("dl", { id: descriptionId, "data-kind-ui": "chart-instructions", children: data.map((ring) => (_jsxs("div", { children: [_jsx("dt", { children: ring.label }), _jsx("dd", { children: config[ring.key].formatValue?.(ring.rawValue) ?? ring.rawValue })] }, ring.key))) }), _jsxs(RadialBarChart, { innerRadius: "30%", outerRadius: "90%", startAngle: 90, endAngle: -270, barCategoryGap: "15%", ...chartProps, "aria-describedby": describedBy, data: data, categoryKey: "key", layout: "radial", accessibilityLayer: accessibilityLayer, responsive: responsive, ...(width !== undefined ? { width } : {}), ...(height !== undefined ? { height } : {}), className: ["kind-ui-activity-rings", className].filter(Boolean).join(" "), style: { width: width ?? "100%", height: height ?? 300, ...style }, children: [_jsx(PolarAngleAxis, { type: "number", domain: [0, 100], tick: false, axisLine: false }), _jsx(PolarRadiusAxis, { type: "category", dataKey: "key", tick: false, axisLine: false }), _jsxs(RadialBarSeries, { background: true, cornerRadius: "50%", ...series, dataKey: "progress", children: [data.some((ring) => ring.cellProps !== undefined) &&
45
+ return (_jsxs(Root, { ...rootProps, config: activeConfig, children: [_jsx("dl", { id: descriptionId, "data-kind-ui": "chart-instructions", "aria-hidden": chartProps.loading || undefined, children: data.map((ring) => (_jsxs("div", { children: [_jsx("dt", { children: ring.label }), _jsx("dd", { children: config[ring.key].formatValue?.(ring.rawValue) ?? ring.rawValue })] }, ring.key))) }), _jsxs(RadialBarChartFrame, { skeletonFamily: "activity-rings", innerRadius: "30%", outerRadius: "90%", startAngle: 90, endAngle: -270, barCategoryGap: "15%", ...chartProps, "aria-describedby": describedBy, data: data, categoryKey: "key", ...(rootProps?.interaction?.kind === "category"
46
+ ? { interactionBinding: "root" }
47
+ : {}), layout: "radial", accessibilityLayer: accessibilityLayer, responsive: responsive, ...(width !== undefined ? { width } : {}), ...(height !== undefined ? { height } : {}), className: ["kind-ui-activity-rings", className].filter(Boolean).join(" "), style: { width: width ?? "100%", height: height ?? 300, ...style }, children: [_jsx(PolarAngleAxis, { type: "number", domain: [0, 100], tick: false, axisLine: false }), _jsx(PolarRadiusAxis, { type: "category", dataKey: "key", tick: false, axisLine: false }), _jsxs(RadialBarSeries, { background: true, cornerRadius: "50%", ...series, dataKey: "progress", children: [data.some((ring) => ring.cellProps !== undefined) &&
45
48
  data.map((ring) => _jsx(Cell, { ...ring.cellProps }, ring.key)), labels !== false && (_jsx(LabelList, { content: _jsx(RadialBarLabel, {}), ...labels, dataKey: "label" }))] }), tooltip !== false && (_jsx(Tooltip, { content: (native) => (_jsx(TooltipContent, { tooltip: {
46
49
  ...native,
47
50
  payload: native.payload.map((entry) => {
@@ -3,12 +3,18 @@ import { type ReactElement } from "react";
3
3
  import { type DotProps } from "recharts";
4
4
  import { type LineChartProps as StaticLineChartProps } from "./line-chart.js";
5
5
  import { type LineSeriesProps as StaticLineSeriesProps } from "./line-series.js";
6
+ import { type PointStyle } from "./point-marker.js";
7
+ import { type RevealDirection } from "./reveal-clip.js";
6
8
  import { type TooltipProps } from "./tooltip.js";
7
- export type LineAnimation = {
9
+ export type BaseAnimation = {
8
10
  revealDurationMs?: number;
9
11
  revealEasing?: Transition["ease"];
10
12
  hoverTransition?: Transition;
11
13
  };
14
+ export type LineAnimation = BaseAnimation & {
15
+ /** Physical horizontal entrance direction, independent of native chart layout. */
16
+ revealDirection?: RevealDirection;
17
+ };
12
18
  type DefaultLineDataKey = Extract<StaticLineSeriesProps["dataKey"], (row: never) => unknown>;
13
19
  export type LineSeriesProps<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>> = Omit<StaticLineSeriesProps<DataPoint, Value>, "isAnimationActive">;
14
20
  export type LineChartProps = StaticLineChartProps & {
@@ -19,8 +25,10 @@ export declare const MotionContext: import("react").Context<{
19
25
  transition: Transition;
20
26
  }>;
21
27
  /** Motion owns the shared entrance clip and default active marks. */
22
- export declare function LineChart({ animate, children, ...props }: LineChartProps): import("react").JSX.Element;
23
- export declare function ActiveMarker({ cx, cy, fill, stroke }: DotProps): import("react").JSX.Element | null;
28
+ export declare function LineChart({ animate, loading, loadingLabel, children, ...props }: LineChartProps): import("react").JSX.Element;
29
+ export declare function ActiveMarker({ cx, cy, fill, stroke, variant, }: DotProps & {
30
+ variant?: PointStyle | undefined;
31
+ }): import("react").JSX.Element | null;
24
32
  export declare function LineSeries<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>>(props: LineSeriesProps<DataPoint, Value>): ReactElement;
25
33
  export declare function LineSeries(props: LineSeriesProps): ReactElement;
26
34
  export declare function Tooltip(props: TooltipProps): import("react").JSX.Element;
package/dist/animation.js CHANGED
@@ -6,6 +6,9 @@ import { DefaultZIndexes, LineChart as EngineLineChart, ZIndexLayer, } from "rec
6
6
  import { useChart } from "./chart-context.js";
7
7
  import { LineChartFrame, useLineInteraction, } from "./line-chart.js";
8
8
  import { LineSeries as StaticLineSeries, } from "./line-series.js";
9
+ import { registerPiePinComponent } from "./pie-pin-identity.js";
10
+ import { markerPaint } from "./point-marker.js";
11
+ import { RevealClip } from "./reveal-clip.js";
9
12
  import { TooltipBase } from "./tooltip.js";
10
13
  const defaultHover = { type: "spring", stiffness: 210, damping: 28, mass: 0.8 };
11
14
  export const MotionContext = createContext({ enabled: false, transition: defaultHover });
@@ -18,19 +21,23 @@ function subscribe(change) {
18
21
  const snapshot = () => window.matchMedia(query).matches;
19
22
  const serverSnapshot = () => true;
20
23
  /** Motion owns the shared entrance clip and default active marks. */
21
- export function LineChart({ animate = false, children, ...props }) {
24
+ export function LineChart({ animate = false, loading, loadingLabel, children, ...props }) {
22
25
  const id = useId();
23
26
  const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
24
27
  const [interacted, setInteracted] = useState(false);
25
28
  const options = typeof animate === "object" ? animate : {};
26
- const enabled = animate !== false && !reduced;
27
- const interrupt = useCallback(() => setInteracted(true), []);
29
+ const enabled = animate !== false && !reduced && !loading;
30
+ const interrupt = useCallback(() => {
31
+ if (!loading)
32
+ setInteracted(true);
33
+ }, [loading]);
34
+ useLayoutEffect(() => {
35
+ if (loading)
36
+ setInteracted(false);
37
+ }, [loading]);
28
38
  const reveal = enabled && !interacted;
29
39
  const transition = options.hoverTransition ?? defaultHover;
30
- return (_jsx(MotionContext, { value: { enabled, transition }, children: _jsxs(LineChartFrame, { chartProps: props, engine: EngineLineChart, motionEnabled: enabled, interrupt: interrupt, ...(reveal ? { clip: `url(#${id}-reveal)` } : {}), children: [reveal && (_jsx("defs", { children: _jsx("clipPath", { id: `${id}-reveal`, clipPathUnits: "userSpaceOnUse", children: _jsx(motion.rect, { x: 0, y: 0, height: "100%", initial: { width: "0%" }, animate: { width: "100%" }, transition: {
31
- duration: Math.max(0, options.revealDurationMs ?? 1000) / 1000,
32
- ease: options.revealEasing ?? [0.25, 0.1, 0.25, 1],
33
- } }) }) })), children] }) }));
40
+ return (_jsx(MotionContext, { value: { enabled, transition }, children: _jsxs(LineChartFrame, { chartProps: props, loading: loading, loadingLabel: loadingLabel, loadingSkeleton: "line", loadingAnimation: options, engine: EngineLineChart, motionEnabled: enabled, interrupt: interrupt, ...(reveal ? { clip: `url(#${id}-reveal)` } : {}), children: [reveal && (_jsx(RevealClip, { id: `${id}-reveal`, options: options, ...(options.revealDirection === undefined ? {} : { finish: interrupt }) })), children] }) }));
34
41
  }
35
42
  // Stop the previous target before retargeting or snapping, without remounting consumer DOM.
36
43
  function useAnimatedCoordinate(target, enabled, transition) {
@@ -45,7 +52,7 @@ function useAnimatedCoordinate(target, enabled, transition) {
45
52
  }, [value, target, enabled, transition]);
46
53
  return value;
47
54
  }
48
- export function ActiveMarker({ cx, cy, fill, stroke }) {
55
+ export function ActiveMarker({ cx, cy, fill, stroke, variant = "default", }) {
49
56
  const { enabled, transition } = use(MotionContext);
50
57
  const { motionReady } = useLineInteraction();
51
58
  const animate = enabled && motionReady;
@@ -53,7 +60,7 @@ export function ActiveMarker({ cx, cy, fill, stroke }) {
53
60
  const y = useAnimatedCoordinate(cy ?? 0, animate, transition);
54
61
  if (cx == null || cy == null)
55
62
  return null;
56
- return (_jsx(motion.circle, { "data-kind-ui": "active-marker", initial: false, cx: x, cy: y, r: 5, fill: fill ?? stroke, stroke: "var(--card, white)", strokeWidth: 2, strokeDasharray: "none", pointerEvents: "none" }));
63
+ return (_jsx(motion.circle, { "data-kind-ui": "active-marker", initial: false, cx: x, cy: y, r: 5, fill: fill ?? stroke, stroke: "var(--card, white)", strokeWidth: 2, strokeDasharray: "none", ...markerPaint(variant, fill ?? stroke), "data-point-style": variant, pointerEvents: "none" }));
57
64
  }
58
65
  export function LineSeries(props) {
59
66
  const { enabled } = use(MotionContext);
@@ -87,7 +94,7 @@ export function LineSeries(props) {
87
94
  });
88
95
  return () => controls.stop();
89
96
  }, [visible, enabled, opacity]);
90
- return (_jsx(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.line, children: _jsx(motion.g, { initial: false, style: { opacity }, pointerEvents: visible ? undefined : "none", "aria-hidden": visible ? undefined : true, children: _jsx(StaticLineSeries, { ...props, activeDot: visible ? (props.activeDot ?? _jsx(ActiveMarker, {})) : false, zIndex: 0, renderWhileHidden: enabled && drawn, isAnimationActive: false }) }) }));
97
+ return (_jsx(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.line, children: _jsx(motion.g, { initial: false, style: { opacity }, pointerEvents: visible ? undefined : "none", "aria-hidden": visible ? undefined : true, children: _jsx(StaticLineSeries, { ...props, activeDot: visible ? (props.activeDot ?? _jsx(ActiveMarker, { variant: props.activePointStyle })) : false, zIndex: 0, renderWhileHidden: enabled && drawn, isAnimationActive: false }) }) }));
91
98
  }
92
99
  function MovingFrame({ x, y, maxX, maxY, ref, style, frameProps, children }) {
93
100
  const { enabled, transition } = use(MotionContext);
@@ -100,3 +107,4 @@ function MovingFrame({ x, y, maxX, maxY, ref, style, frameProps, children }) {
100
107
  export function Tooltip(props) {
101
108
  return _jsx(TooltipBase, { ...props, Frame: MovingFrame });
102
109
  }
110
+ registerPiePinComponent(Tooltip, "tooltip");
@@ -4,6 +4,8 @@ import { type LineAnimation } from "./animation.js";
4
4
  export type AreaAnimation = LineAnimation;
5
5
  export type AreaChartProps = ComponentProps<typeof EngineAreaChart> & {
6
6
  animate?: boolean | AreaAnimation | undefined;
7
+ loading?: boolean | undefined;
8
+ loadingLabel?: string | undefined;
7
9
  };
8
10
  /** Native Recharts geometry and composition with a shared Motion entrance and tooltip. */
9
- export declare function AreaChart({ animate, children, ...props }: AreaChartProps): import("react").JSX.Element;
11
+ export declare function AreaChart({ animate, loading, loadingLabel, children, ...props }: AreaChartProps): import("react").JSX.Element;
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { motion } from "motion/react";
4
- import { useCallback, useId, useState, useSyncExternalStore } from "react";
3
+ import { useCallback, useId, useLayoutEffect, useState, useSyncExternalStore, } from "react";
5
4
  import { AreaChart as EngineAreaChart } from "recharts";
6
5
  import { MotionContext } from "./animation.js";
7
6
  import { LineChartFrame } from "./line-chart.js";
7
+ import { CartesianLoadingDesign } from "./loading-cartesian-designs.js";
8
+ import { RevealClip } from "./reveal-clip.js";
8
9
  const defaultHover = { type: "spring", stiffness: 210, damping: 28, mass: 0.8 };
9
10
  const query = "(prefers-reduced-motion: reduce)";
10
11
  function subscribe(change) {
@@ -15,19 +16,23 @@ function subscribe(change) {
15
16
  const snapshot = () => window.matchMedia(query).matches;
16
17
  const serverSnapshot = () => true;
17
18
  /** Native Recharts geometry and composition with a shared Motion entrance and tooltip. */
18
- export function AreaChart({ animate = false, children, ...props }) {
19
+ export function AreaChart({ animate = false, loading, loadingLabel, children, ...props }) {
19
20
  const id = useId();
20
21
  const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
21
22
  const [interacted, setInteracted] = useState(false);
22
23
  const options = typeof animate === "object" ? animate : {};
23
- const enabled = animate !== false && reduced === false;
24
- const interrupt = useCallback(() => setInteracted(true), []);
24
+ const enabled = animate !== false && reduced === false && !loading;
25
+ const interrupt = useCallback(() => {
26
+ if (!loading)
27
+ setInteracted(true);
28
+ }, [loading]);
29
+ useLayoutEffect(() => {
30
+ if (loading)
31
+ setInteracted(false);
32
+ }, [loading]);
25
33
  const reveal = enabled && !interacted;
26
- return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsxs(LineChartFrame, { chartProps: {
34
+ return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsxs(LineChartFrame, { loading: loading, loadingLabel: loadingLabel, loadingSkeleton: "area", loadingDesign: (seed) => _jsx(CartesianLoadingDesign, { family: "area", seed: seed }), loadingAnimation: options, chartProps: {
27
35
  ...props,
28
36
  className: ["kind-ui-area-chart", props.className].filter(Boolean).join(" "),
29
- }, engine: EngineAreaChart, motionEnabled: enabled, interrupt: interrupt, ...(reveal ? { clip: `url(#${id}-area-reveal)` } : {}), children: [reveal && (_jsx("defs", { children: _jsx("clipPath", { id: `${id}-area-reveal`, clipPathUnits: "userSpaceOnUse", children: _jsx(motion.rect, { "data-area-reveal": "", x: 0, y: 0, height: "100%", initial: { width: "0%" }, animate: { width: "100%" }, onAnimationComplete: interrupt, transition: {
30
- duration: Math.max(0, options.revealDurationMs ?? 1000) / 1000,
31
- ease: options.revealEasing ?? [0.25, 0.1, 0.25, 1],
32
- } }) }) })), children] }) }));
37
+ }, engine: EngineAreaChart, motionEnabled: enabled, interrupt: interrupt, ...(reveal ? { clip: `url(#${id}-area-reveal)` } : {}), children: [reveal && (_jsx(RevealClip, { id: `${id}-area-reveal`, family: "area", options: options, finish: interrupt })), children] }) }));
33
38
  }
@@ -1,11 +1,19 @@
1
1
  import { type ComponentProps } from "react";
2
2
  import { Area } from "recharts";
3
3
  import { type AreaMaterial } from "./area-material.js";
4
+ import { type FillPattern } from "./fill-pattern.js";
5
+ import { type PointStyle } from "./point-marker.js";
4
6
  export type AreaSeriesProps<DataPoint = unknown, Value = unknown> = Omit<ComponentProps<typeof Area<DataPoint, Value>>, "isAnimationActive"> & {
5
7
  /** Metadata/visibility key, required only for function or numeric data keys. */
6
8
  seriesKey?: string;
9
+ /** Optional point paint; explicit native dot takes precedence. */
10
+ pointStyle?: PointStyle;
11
+ /** Independent active point paint; explicit native activeDot takes precedence. */
12
+ activePointStyle?: PointStyle;
7
13
  /** Finish on the native area; explicit shape/filter retain consumer ownership. */
8
14
  material?: AreaMaterial;
15
+ /** Static fill encoding; false opts out of configured patterns. Native paint/shape wins. */
16
+ pattern?: FillPattern | false | undefined;
9
17
  };
10
18
  /** A registered Recharts Area with Root colors and controlled visibility. */
11
- export declare function AreaSeries<DataPoint = unknown, Value = unknown>({ seriesKey, material, hide, stroke, fill, className, ...props }: AreaSeriesProps<DataPoint, Value>): import("react").JSX.Element;
19
+ export declare function AreaSeries<DataPoint = unknown, Value = unknown>({ seriesKey, pointStyle, activePointStyle, material, pattern, hide, stroke, fill, className, ...props }: AreaSeriesProps<DataPoint, Value>): import("react").JSX.Element;
@@ -1,14 +1,23 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useId, useLayoutEffect, useRef } from "react";
4
- import { Area } from "recharts";
4
+ import { Area, DefaultZIndexes, ZIndexLayer } from "recharts";
5
5
  import { ActiveMarker } from "./animation.js";
6
6
  import { MaterialArea } from "./area-material.js";
7
7
  import { useChart } from "./chart-context.js";
8
+ import { FillPatternDefinition, patternResourceId } from "./fill-pattern.js";
8
9
  import { useLineInteraction } from "./line-chart.js";
10
+ import { PointMarker } from "./point-marker.js";
11
+ import { SeriesInteractionLayer, useSeriesInteraction } from "./series-interaction.js";
9
12
  /** A registered Recharts Area with Root colors and controlled visibility. */
10
- export function AreaSeries({ seriesKey, material = "plain", hide, stroke, fill, className, ...props }) {
11
- const { config, visibleSeries } = useChart();
13
+ export function AreaSeries({ seriesKey, pointStyle = "default", activePointStyle = "default", material = "plain", pattern, hide, stroke, fill, className, ...props }) {
14
+ // Diagnose unsupported JavaScript/spread input without adding it to the public Area API.
15
+ const { dashAnimation, ...nativeProps } = props;
16
+ useLayoutEffect(() => {
17
+ if (dashAnimation !== undefined && process.env.NODE_ENV === "development")
18
+ console.warn("AreaSeries does not support dashAnimation. Remove it or use LineSeries with strokeDasharray for animated dashes.");
19
+ }, [dashAnimation]);
20
+ const { config, paints, visibleSeries } = useChart();
12
21
  const { registerSeries, invalidate } = useLineInteraction();
13
22
  const generatedId = useId();
14
23
  const id = props.id || generatedId;
@@ -27,8 +36,26 @@ export function AreaSeries({ seriesKey, material = "plain", hide, stroke, fill,
27
36
  }, [id, key, registerSeries]);
28
37
  if (key === undefined && visibleSeries !== undefined)
29
38
  throw new Error("AreaSeries requires seriesKey for controlled non-string dataKey");
30
- const color = stroke ?? (key && Object.hasOwn(config, key) ? `var(--color-${key})` : undefined);
31
- return (_jsx(Area, { activeDot: _jsx(ActiveMarker, {}), ...props, ...(material !== "plain" && props.shape === undefined && props.filter === undefined
32
- ? { shape: _jsx(MaterialArea, { material: material, filterId: `${generatedId}-area-material` }) }
33
- : {}), isAnimationActive: false, id: id, hide: effectiveHide, ...(color !== undefined ? { stroke: color } : {}), ...(fill !== undefined ? { fill } : color !== undefined ? { fill: color } : {}), className: ["kind-ui-area-series", className].filter(Boolean).join(" ") }));
39
+ const configuredPattern = key && Object.hasOwn(config, key) ? config[key]?.pattern : undefined;
40
+ const resolvedPattern = pattern === false ? undefined : (pattern ?? configuredPattern);
41
+ const patternId = patternResourceId(generatedId);
42
+ const patterned = resolvedPattern !== undefined &&
43
+ fill === undefined &&
44
+ props.style?.fill === undefined &&
45
+ props.shape === undefined;
46
+ const interaction = useSeriesInteraction(key, effectiveHide, hide === true, props.data, props.onClick);
47
+ const color = stroke ?? (key && Object.hasOwn(config, key) ? paints[key] : undefined);
48
+ return (_jsxs(_Fragment, { children: [patterned && (_jsx("defs", { pointerEvents: "none", children: _jsx(FillPatternDefinition, { id: patternId, pattern: resolvedPattern, baseColor: stroke ?? (key && Object.hasOwn(config, key) ? `var(--color-${key})` : "currentColor") }) })), _jsx(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.area, children: _jsx(SeriesInteractionLayer, { seriesKey: key, hidden: effectiveHide, children: _jsx(Area, { activeDot: _jsx(ActiveMarker, { variant: activePointStyle }), ...nativeProps, ...(props.dot === undefined && pointStyle !== "default"
49
+ ? { dot: _jsx(PointMarker, { variant: pointStyle }) }
50
+ : {}), ...(interaction.onClick !== undefined ? { onClick: interaction.onClick } : {}), ...(material !== "plain" && props.shape === undefined && props.filter === undefined
51
+ ? {
52
+ shape: (_jsx(MaterialArea, { material: material, filterId: `${generatedId}-area-material` })),
53
+ }
54
+ : {}), isAnimationActive: false, id: id, zIndex: 0, hide: effectiveHide, ...(color !== undefined ? { stroke: color } : {}), ...(fill !== undefined
55
+ ? { fill }
56
+ : patterned
57
+ ? { fill: `url(#${patternId})` }
58
+ : color !== undefined
59
+ ? { fill: color }
60
+ : {}), className: ["kind-ui-area-series", className].filter(Boolean).join(" ") }) }) })] }));
34
61
  }
@@ -1,9 +1,11 @@
1
1
  import { type ComponentProps } from "react";
2
2
  import { BarChart as EngineBarChart } from "recharts";
3
- import { type LineAnimation } from "./animation.js";
4
- export type BarAnimation = LineAnimation;
3
+ import { type BaseAnimation } from "./animation.js";
4
+ export type BarAnimation = BaseAnimation;
5
5
  export type BarChartProps = ComponentProps<typeof EngineBarChart> & {
6
6
  animate?: boolean | BarAnimation | undefined;
7
+ loading?: boolean | undefined;
8
+ loadingLabel?: string | undefined;
7
9
  /** Opt in only for complete native category comparisons; any unsafe visible peer falls back. */
8
10
  emphasis?: "none" | "category" | undefined;
9
11
  };
@@ -14,4 +16,9 @@ export declare const BarMotion: import("react").Context<{
14
16
  }>;
15
17
  export declare function BarLifecycle({ layout, barGap, barCategoryGap, barSize, stackOffset, reverseStackOrder, }: BarChartProps): null;
16
18
  /** Native Recharts composition with Kind interaction and optional Motion. */
17
- export declare function BarChart({ animate, emphasis, children, ...props }: BarChartProps): import("react").JSX.Element;
19
+ export declare function BarChartImplementation({ chartProps, family, }: {
20
+ chartProps: BarChartProps;
21
+ family: "bar" | "waterfall" | "histogram" | "box-plot";
22
+ }): import("react").JSX.Element;
23
+ /** Native bar composition with a deterministic loading silhouette. */
24
+ export declare function BarChart(props: BarChartProps): import("react").JSX.Element;
package/dist/bar-chart.js CHANGED
@@ -5,6 +5,7 @@ import { BarChart as EngineBarChart } from "recharts";
5
5
  import { MotionContext } from "./animation.js";
6
6
  import { BarCategoryBoundary } from "./bar-category.js";
7
7
  import { LineChartFrame, useLineInteraction } from "./line-chart.js";
8
+ import { CartesianLoadingDesign } from "./loading-cartesian-designs.js";
8
9
  export const BarMotion = createContext({
9
10
  reveal: false,
10
11
  options: {},
@@ -32,11 +33,23 @@ export function BarLifecycle({ layout, barGap, barCategoryGap, barSize, stackOff
32
33
  return null;
33
34
  }
34
35
  /** Native Recharts composition with Kind interaction and optional Motion. */
35
- export function BarChart({ animate = false, emphasis = "none", children, ...props }) {
36
+ export function BarChartImplementation({ chartProps, family, }) {
37
+ const { animate = false, loading, loadingLabel, emphasis = "none", children, ...props } = chartProps;
36
38
  const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
37
39
  const [interacted, setInteracted] = useState(false);
38
- const finish = useCallback(() => setInteracted(true), []);
39
- const enabled = animate !== false && !reduced;
40
+ const finish = useCallback(() => {
41
+ if (!loading)
42
+ setInteracted(true);
43
+ }, [loading]);
44
+ useLayoutEffect(() => {
45
+ if (loading)
46
+ setInteracted(false);
47
+ }, [loading]);
48
+ const enabled = animate !== false && !reduced && !loading;
40
49
  const options = typeof animate === "object" ? animate : {};
41
- return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(BarMotion, { value: { reveal: enabled && !interacted, options, finish }, children: _jsx(BarCategoryBoundary, { children: _jsxs(LineChartFrame, { chartProps: props, categoryEmphasis: emphasis === "category", engine: EngineBarChart, motionEnabled: enabled, interrupt: finish, children: [_jsx(BarLifecycle, { ...props }), children] }) }) }) }));
50
+ return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(BarMotion, { value: { reveal: enabled && !interacted, options, finish }, children: _jsx(BarCategoryBoundary, { children: _jsxs(LineChartFrame, { chartProps: props, loadingSkeleton: family, loadingDesign: (seed) => _jsx(CartesianLoadingDesign, { family: family, seed: seed }), loadingAnimation: { ...options, ...(props.layout ? { layout: props.layout } : {}) }, loading: loading, loadingLabel: loadingLabel, categoryEmphasis: emphasis === "category", engine: EngineBarChart, motionEnabled: enabled, interrupt: finish, children: [_jsx(BarLifecycle, { ...props }), children] }) }) }) }));
51
+ }
52
+ /** Native bar composition with a deterministic loading silhouette. */
53
+ export function BarChart(props) {
54
+ return _jsx(BarChartImplementation, { chartProps: props, family: "bar" });
42
55
  }
@@ -1,6 +1,12 @@
1
1
  import { type ComponentProps } from "react";
2
2
  import { Bar } from "recharts";
3
3
  import { type BarMaterial } from "./bar-material.js";
4
+ import { type FillPattern } from "./fill-pattern.js";
5
+ /** Caller-owned identity selection; this component never computes forecast values. */
6
+ export type BarProjection<DataPoint> = {
7
+ isProjected: (datum: DataPoint) => boolean;
8
+ pattern: FillPattern;
9
+ };
4
10
  export type BarSeriesProps<DataPoint = unknown, Value = unknown> = Omit<ComponentProps<typeof Bar<DataPoint, Value>>, "isAnimationActive"> & {
5
11
  /** Metadata/visibility key, required for controlled function or numeric data keys. */
6
12
  seriesKey?: string;
@@ -8,6 +14,10 @@ export type BarSeriesProps<DataPoint = unknown, Value = unknown> = Omit<Componen
8
14
  emphasisKey?: ((payload: unknown) => string | number | undefined) | undefined;
9
15
  /** Finish on native rectangles; custom shapes and filters retain ownership. */
10
16
  material?: BarMaterial | undefined;
17
+ /** Static encoding for implicit fills; explicit fills/Cells/custom shapes retain ownership. */
18
+ pattern?: FillPattern | false | undefined;
19
+ /** Pattern selected rows; selection remains caller-owned. */
20
+ projection?: BarProjection<DataPoint> | undefined;
11
21
  };
12
22
  /** A registered native Bar; axes, shape, cells, labels and handlers stay consumer-owned. */
13
- export declare function BarSeries<DataPoint = unknown, Value = unknown>({ seriesKey, emphasisKey, hide, fill, className, style, material, ...props }: BarSeriesProps<DataPoint, Value>): import("react").JSX.Element;
23
+ export declare function BarSeries<DataPoint = unknown, Value = unknown>({ seriesKey, emphasisKey, hide, fill, className, style, material, pattern, projection, children, ...props }: BarSeriesProps<DataPoint, Value>): import("react").JSX.Element;
@@ -1,17 +1,19 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { motion } from "motion/react";
4
- import { use, useCallback, useId, useLayoutEffect, useRef } from "react";
5
- import { Bar, Rectangle, useActiveTooltipDataPoints, useActiveTooltipLabel, useChartLayout, useIsTooltipActive, usePlotArea, useXAxisDomain, useXAxisScale, useYAxisDomain, useYAxisScale, } from "recharts";
4
+ import { Children, isValidElement, use, useCallback, useId, useLayoutEffect, useRef, } from "react";
5
+ import { Bar, Cell, DefaultZIndexes, Rectangle, useActiveTooltipDataPoints, useActiveTooltipLabel, useChartLayout, useIsTooltipActive, usePlotArea, useXAxisDomain, useXAxisScale, useYAxisDomain, useYAxisScale, ZIndexLayer, } from "recharts";
6
6
  import { useBarCategoryHover } from "./bar-category.js";
7
7
  import { BarMotion } from "./bar-chart.js";
8
8
  import { BarMaterialFilter } from "./bar-material.js";
9
9
  import { useChart } from "./chart-context.js";
10
10
  import { useEmphasis } from "./emphasis.js";
11
+ import { FillPatternDefinition, patternResourceId } from "./fill-pattern.js";
11
12
  import { useChartKeyboard, useLineInteraction } from "./line-chart.js";
13
+ import { SeriesInteractionLayer, useSeriesInteraction } from "./series-interaction.js";
12
14
  /** A registered native Bar; axes, shape, cells, labels and handlers stay consumer-owned. */
13
- export function BarSeries({ seriesKey, emphasisKey, hide, fill, className, style, material = "plain", ...props }) {
14
- const { config, visibleSeries } = useChart();
15
+ export function BarSeries({ seriesKey, emphasisKey, hide, fill, className, style, material = "plain", pattern, projection, children, ...props }) {
16
+ const { config, paints, visibleSeries } = useChart();
15
17
  const { registerSeries, invalidate, data, categoryEmphasis, registerCategoryEligibility } = useLineInteraction();
16
18
  const { reveal, options, finish } = use(BarMotion);
17
19
  const generatedId = useId();
@@ -102,7 +104,28 @@ export function BarSeries({ seriesKey, emphasisKey, hide, fill, className, style
102
104
  }, [id, key, registerSeries]);
103
105
  if (key === undefined && visibleSeries !== undefined)
104
106
  throw new Error("BarSeries requires seriesKey for controlled non-string dataKey");
105
- const color = fill ?? (key && Object.hasOwn(config, key) ? `var(--color-${key})` : undefined);
107
+ const configuredPattern = key && Object.hasOwn(config, key) ? config[key]?.pattern : undefined;
108
+ const resolvedPattern = pattern === false ? undefined : (pattern ?? configuredPattern);
109
+ const patternId = patternResourceId(generatedId);
110
+ const patterned = resolvedPattern !== undefined &&
111
+ fill === undefined &&
112
+ style?.fill === undefined &&
113
+ props.shape === undefined &&
114
+ (props.activeBar === undefined || typeof props.activeBar === "boolean");
115
+ const interaction = useSeriesInteraction(key, effectiveHide, hide === true, undefined, props.onClick);
116
+ const projectionId = `${patternId}-projection`;
117
+ // Explicit Cell composition owns per-datum paint, including nested native Cells.
118
+ // Do not override consumer Cells or flatten their markup.
119
+ const hasCells = (nodes) => Children.toArray(nodes).some((node) => isValidElement(node) &&
120
+ (node.type === Cell || hasCells(node.props.children)));
121
+ const projectionPaint = projection !== undefined &&
122
+ pattern !== false &&
123
+ fill === undefined &&
124
+ style?.fill === undefined &&
125
+ props.shape === undefined &&
126
+ (props.activeBar === undefined || typeof props.activeBar === "boolean") &&
127
+ !hasCells(children);
128
+ const color = fill ?? (key && Object.hasOwn(config, key) ? paints[key] : undefined);
106
129
  const nativeRows = (!("data" in props) &&
107
130
  data?.every((row) => {
108
131
  if (typeof props.dataKey !== "string" ||
@@ -130,18 +153,47 @@ export function BarSeries({ seriesKey, emphasisKey, hide, fill, className, style
130
153
  props.shape === undefined &&
131
154
  (props.activeBar === undefined || typeof props.activeBar === "boolean");
132
155
  useLayoutEffect(() => registerCategoryEligibility(id, eligible, effectiveHide), [id, eligible, effectiveHide, registerCategoryEligibility]);
133
- const categoryShape = useCallback((shapeProps) => (_jsx(CategoryBar, { ...shapeProps, seriesKey: key, emphasisKey: emphasisKey, axisId: horizontal ? props.yAxisId : props.xAxisId })), [key, emphasisKey, horizontal, props.xAxisId, props.yAxisId]);
156
+ const categoryShape = useCallback((shapeProps) => {
157
+ // Select the actual displayed payload, not the Cell index: native Brush
158
+ // slices and missing-value filtering must not shift projection identity.
159
+ const datum = shapeProps.payload;
160
+ const datumPaint = typeof datum === "object" && datum !== null
161
+ ? datum
162
+ : undefined;
163
+ const projected = projectionPaint &&
164
+ datum != null &&
165
+ projection.isProjected(datum) &&
166
+ datumPaint?.fill === undefined &&
167
+ datumPaint?.style?.fill === undefined;
168
+ const nativeProps = projected ? { ...shapeProps, fill: `url(#${projectionId})` } : shapeProps;
169
+ return categoryEmphasis && eligible ? (_jsx(CategoryBar, { ...nativeProps, seriesKey: key, emphasisKey: emphasisKey, axisId: horizontal ? props.yAxisId : props.xAxisId })) : (_jsx(Rectangle, { ...nativeProps }));
170
+ }, [
171
+ key,
172
+ emphasisKey,
173
+ horizontal,
174
+ props.xAxisId,
175
+ props.yAxisId,
176
+ projectionPaint,
177
+ projection,
178
+ projectionId,
179
+ categoryEmphasis,
180
+ eligible,
181
+ ]);
134
182
  const clipped = reveal && !effectiveHide && usablePlot;
135
183
  return (_jsxs(_Fragment, { children: [_jsx("style", { children: `.${selector} { --kind-ui-bar-clip: ${clipped ? `url(#${clipId})` : "none"}; }` }), clipped && (_jsx("defs", { children: _jsx("clipPath", { id: clipId, clipPathUnits: "userSpaceOnUse", children: _jsx(motion.rect, { "data-kind-ui": "bar-reveal", initial: horizontal
136
184
  ? { x: zero, y: area.y, width: 0, height: area.height }
137
185
  : { x: area.x, y: zero, height: 0, width: area.width }, animate: { x: area.x, y: area.y, width: area.width, height: area.height }, transition: {
138
186
  duration: Math.max(0, options.revealDurationMs ?? 1000) / 1000,
139
187
  ease: options.revealEasing ?? [0.25, 0.1, 0.25, 1],
140
- }, onAnimationComplete: finish }, geometry) }) })), materialized && (_jsx("defs", { "data-kind-ui": "bar-material", "data-material": material, pointerEvents: "none", children: _jsx(BarMaterialFilter, { material: material, id: filterId, horizontal: horizontal }) })), _jsx(Bar, { ...props, ...(categoryEmphasis && eligible
141
- ? {
142
- shape: categoryShape,
143
- }
144
- : {}), ...(materialized ? { filter: `url(#${filterId})` } : {}), id: id, hide: effectiveHide, ...(color !== undefined ? { fill: color } : {}), className: ["kind-ui-bar-series", selector, className].filter(Boolean).join(" "), style: style, isAnimationActive: false })] }));
188
+ }, onAnimationComplete: finish }, geometry) }) })), patterned && (_jsx("defs", { pointerEvents: "none", children: _jsx(FillPatternDefinition, { id: patternId, pattern: resolvedPattern, baseColor: key && Object.hasOwn(config, key) ? `var(--color-${key})` : (color ?? "currentColor") }) })), projectionPaint && (_jsx("defs", { pointerEvents: "none", children: _jsx(FillPatternDefinition, { id: projectionId, pattern: projection.pattern, baseColor: key && Object.hasOwn(config, key) ? `var(--color-${key})` : (color ?? "currentColor") }) })), materialized && (_jsx("defs", { "data-kind-ui": "bar-material", "data-material": material, pointerEvents: "none", children: _jsx(BarMaterialFilter, { material: material, id: filterId, horizontal: horizontal }) })), _jsx(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.bar, children: _jsx(SeriesInteractionLayer, { seriesKey: key, hidden: effectiveHide, children: _jsx(Bar, { ...props, ...(interaction.onClick !== undefined ? { onClick: interaction.onClick } : {}), ...(projectionPaint || (categoryEmphasis && eligible)
189
+ ? {
190
+ shape: categoryShape,
191
+ }
192
+ : {}), ...(materialized ? { filter: `url(#${filterId})` } : {}), id: id, zIndex: 0, hide: effectiveHide, ...(patterned
193
+ ? { fill: `url(#${patternId})` }
194
+ : color !== undefined
195
+ ? { fill: color }
196
+ : {}), className: ["kind-ui-bar-series", selector, className].filter(Boolean).join(" "), style: style, isAnimationActive: false, children: children }) }) })] }));
145
197
  }
146
198
  function CategoryBar({ seriesKey, emphasisKey, axisId, ...props }) {
147
199
  const { emphasisScope, pointer } = useLineInteraction();
@@ -163,7 +215,7 @@ function CategoryBar({ seriesKey, emphasisKey, axisId, ...props }) {
163
215
  key: String(semantic),
164
216
  scope: `${emphasisScope}/${String(axisId ?? 0)}`,
165
217
  seriesKey,
166
- }, eligible);
218
+ }, eligible, false);
167
219
  // Native axis inspection owns the category, including whitespace above and between bars.
168
220
  // A painted mark's leave must not clear a category while the cursor remains in its band.
169
221
  useLayoutEffect(() => {
@@ -174,5 +226,5 @@ function CategoryBar({ seriesKey, emphasisKey, axisId, ...props }) {
174
226
  else if (!hover || (pointer && !active))
175
227
  emphasis.leave("pointer");
176
228
  }, [active, keyboard, pointer, hover, emphasis.enter, emphasis.leave]);
177
- return (_jsx("g", { "data-kind-ui": "emphasis-mark", "data-emphasis": emphasis.dimmed ? "dimmed" : "baseline", children: _jsx("g", { "data-kind-ui": "emphasis-paint", style: { opacity: emphasis.factor }, children: _jsx(Rectangle, { ...props }) }) }));
229
+ return (_jsx("g", { "data-kind-ui": "emphasis-mark", "data-emphasis": emphasis.dimmed ? "dimmed" : "baseline", children: _jsx("g", { "data-kind-ui": "emphasis-paint", style: { opacity: emphasis.active?.kind === "series" ? 1 : emphasis.factor }, children: _jsx(Rectangle, { ...props }) }) }));
178
230
  }
package/dist/box-plot.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from "react";
4
4
  import { useChartLayout, useXAxisScale, useYAxisScale } from "recharts";
5
- import { BarChart } from "./bar-chart.js";
5
+ import { BarChartImplementation } from "./bar-chart.js";
6
6
  import { BarSeries } from "./bar-series.js";
7
7
  import { BoxMaterialFilter } from "./box-material.js";
8
8
  const fields = ["lowerWhisker", "q1", "median", "q3", "upperWhisker"];
@@ -48,7 +48,7 @@ export function boxPlotExtent(summary) {
48
48
  }
49
49
  /** Public Bar composition; Recharts owns axes, layout, domains and keyboard selection. */
50
50
  export function BoxPlotChart(props) {
51
- return _jsx(BarChart, { ...props });
51
+ return _jsx(BarChartImplementation, { chartProps: props, family: "box-plot" });
52
52
  }
53
53
  /** A reusable SVG mark. Zero IQR remains a line; no minimum numeric extent is invented. */
54
54
  export function BoxPlotMark({ coordinates: c, center, size, orientation = "vertical", outlierRadius = 3, strokeWidth = 1.5, material = "plain", ...props }) {
@@ -3,4 +3,10 @@ import type { SeriesConfig } from "./types.js";
3
3
  /** Category identity is an own field or an accessor, never an array position. */
4
4
  export type CategoryKey<Row> = (keyof Row & string) | ((row: Row) => string);
5
5
  /** Only Pie and Radial category mode use this native Cell defaulting seam. */
6
- export declare function categoryCells<Row>(data: readonly Row[] | undefined, categoryKey: CategoryKey<Row>, config: SeriesConfig, children: ReactNode, fill: string | undefined): ReactNode;
6
+ export declare function categoryCells<Row>(data: readonly Row[] | undefined, categoryKey: CategoryKey<Row>, config: SeriesConfig, paints: Readonly<Record<string, string>>, children: ReactNode, fill: string | undefined): ReactNode;
7
+ /** Filter original rows and positional Cells together before native geometry is computed. */
8
+ export declare function filterCategoryRows<Row>(data: readonly Row[], categoryKey: CategoryKey<Row>, visible: readonly string[] | undefined, children: ReactNode): {
9
+ data: Row[];
10
+ children: ReactNode;
11
+ keyOf: (row: Row) => string;
12
+ };
@@ -3,7 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { Children, cloneElement, Fragment, isValidElement } from "react";
4
4
  import { Cell } from "recharts";
5
5
  /** Only Pie and Radial category mode use this native Cell defaulting seam. */
6
- export function categoryCells(data, categoryKey, config, children, fill) {
6
+ export function categoryCells(data, categoryKey, config, paints, children, fill) {
7
7
  if (!data)
8
8
  throw new Error("Category color defaults require explicit data");
9
9
  const colors = data.map((row) => {
@@ -15,7 +15,7 @@ export function categoryCells(data, categoryKey, config, children, fill) {
15
15
  if (typeof key !== "string" || !Object.hasOwn(config, key))
16
16
  throw new Error("categoryKey must resolve to a string key in Root.config for every row");
17
17
  const datumFill = row !== null && typeof row === "object" && "fill" in row ? row.fill : undefined;
18
- return fill === undefined && datumFill === undefined ? `var(--color-${key})` : undefined;
18
+ return fill === undefined && datumFill === undefined ? paints[key] : undefined;
19
19
  });
20
20
  if (fill !== undefined)
21
21
  return children;
@@ -39,3 +39,33 @@ export function categoryCells(data, categoryKey, config, children, fill) {
39
39
  // biome-ignore lint/suspicious/noArrayIndexKey: Native Cell alignment is positional; Cells hold no consumer state.
40
40
  _jsx(Cell, { ...(color ? { fill: color } : {}) }, `kind-category-${index + offset}`)))] }));
41
41
  }
42
+ /** Filter original rows and positional Cells together before native geometry is computed. */
43
+ export function filterCategoryRows(data, categoryKey, visible, children) {
44
+ const keyOf = (row) => {
45
+ const key = typeof categoryKey === "function"
46
+ ? categoryKey(row)
47
+ : row !== null && typeof row === "object" && Object.hasOwn(row, categoryKey)
48
+ ? row[categoryKey]
49
+ : undefined;
50
+ if (typeof key !== "string" || !key)
51
+ throw new Error("Root interaction binding requires stable string category keys");
52
+ return key;
53
+ };
54
+ const keys = data.map(keyOf);
55
+ if (new Set(keys).size !== keys.length)
56
+ throw new Error("Root interaction binding requires unique category keys");
57
+ const keep = keys.map((key) => visible === undefined || visible.includes(key));
58
+ let index = 0;
59
+ function filter(parts) {
60
+ return Children.map(parts, (child) => {
61
+ if (!isValidElement(child))
62
+ return child;
63
+ if (child.type === Fragment)
64
+ return cloneElement(child, {}, filter(child.props.children));
65
+ if (child.type !== Cell)
66
+ return child;
67
+ return keep[index++] ? child : null;
68
+ });
69
+ }
70
+ return { data: data.filter((_row, index) => keep[index]), children: filter(children), keyOf };
71
+ }