@kind-ui/charts 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +683 -1
  3. package/dist/activity-rings.d.ts +2 -1
  4. package/dist/activity-rings.js +6 -3
  5. package/dist/animation.d.ts +11 -3
  6. package/dist/animation.js +18 -10
  7. package/dist/area-chart.d.ts +3 -1
  8. package/dist/area-chart.js +15 -10
  9. package/dist/area-series.d.ts +9 -1
  10. package/dist/area-series.js +35 -8
  11. package/dist/bar-chart.d.ts +10 -3
  12. package/dist/bar-chart.js +17 -4
  13. package/dist/bar-material.js +2 -3
  14. package/dist/bar-series.d.ts +11 -1
  15. package/dist/bar-series.js +65 -13
  16. package/dist/box-plot.js +2 -2
  17. package/dist/category-cells.d.ts +7 -1
  18. package/dist/category-cells.js +32 -2
  19. package/dist/chart-background-pattern.d.ts +23 -0
  20. package/dist/chart-background-pattern.js +31 -0
  21. package/dist/chart-context.d.ts +6 -2
  22. package/dist/chart-interaction.d.ts +94 -0
  23. package/dist/chart-interaction.js +224 -0
  24. package/dist/combo-chart.d.ts +8 -5
  25. package/dist/combo-chart.js +14 -12
  26. package/dist/configured-line-chart.d.ts +5 -1
  27. package/dist/configured-line-chart.js +8 -5
  28. package/dist/emphasis.d.ts +5 -2
  29. package/dist/emphasis.js +18 -4
  30. package/dist/fill-pattern.d.ts +25 -0
  31. package/dist/fill-pattern.js +23 -0
  32. package/dist/heatmap.d.ts +4 -2
  33. package/dist/heatmap.js +34 -12
  34. package/dist/histogram-chart.js +10 -3
  35. package/dist/histogram-material.js +1 -2
  36. package/dist/index.d.ts +12 -3
  37. package/dist/index.js +8 -1
  38. package/dist/legend.d.ts +2 -0
  39. package/dist/legend.js +28 -7
  40. package/dist/line-chart.d.ts +13 -3
  41. package/dist/line-chart.js +54 -34
  42. package/dist/line-dash.d.ts +8 -0
  43. package/dist/line-dash.js +18 -0
  44. package/dist/line-material.d.ts +1 -1
  45. package/dist/line-material.js +1 -1
  46. package/dist/line-series.d.ts +9 -1
  47. package/dist/line-series.js +37 -11
  48. package/dist/loading-cartesian-designs.d.ts +6 -0
  49. package/dist/loading-cartesian-designs.js +123 -0
  50. package/dist/loading-motion.d.ts +37 -0
  51. package/dist/loading-motion.js +88 -0
  52. package/dist/loading-polar-designs.d.ts +6 -0
  53. package/dist/loading-polar-designs.js +95 -0
  54. package/dist/loading-skeleton.d.ts +29 -0
  55. package/dist/loading-skeleton.js +143 -0
  56. package/dist/loading-standalone-designs.d.ts +5 -0
  57. package/dist/loading-standalone-designs.js +126 -0
  58. package/dist/percent-stack.d.ts +18 -0
  59. package/dist/percent-stack.js +33 -0
  60. package/dist/pie-chart.d.ts +7 -3
  61. package/dist/pie-chart.js +37 -6
  62. package/dist/pie-material.js +1 -1
  63. package/dist/pie-pin-identity.d.ts +4 -0
  64. package/dist/pie-pin-identity.js +10 -0
  65. package/dist/pie-series.d.ts +4 -0
  66. package/dist/pie-series.js +138 -15
  67. package/dist/pie-tooltip-pin.d.ts +4 -0
  68. package/dist/pie-tooltip-pin.js +52 -0
  69. package/dist/point-marker.d.ts +19 -0
  70. package/dist/point-marker.js +19 -0
  71. package/dist/polar-chart.d.ts +14 -5
  72. package/dist/polar-chart.js +41 -10
  73. package/dist/polar-material.js +1 -1
  74. package/dist/polar-series.js +68 -22
  75. package/dist/radar-interaction.js +10 -4
  76. package/dist/radial-category.d.ts +2 -0
  77. package/dist/reveal-clip.d.ts +16 -0
  78. package/dist/reveal-clip.js +18 -0
  79. package/dist/root.d.ts +5 -9
  80. package/dist/root.js +46 -11
  81. package/dist/sankey-chart.d.ts +5 -1
  82. package/dist/sankey-chart.js +64 -13
  83. package/dist/sankey-colors.d.ts +3 -0
  84. package/dist/sankey-finish.d.ts +1 -1
  85. package/dist/sankey-finish.js +1 -1
  86. package/dist/sankey-interaction.d.ts +8 -0
  87. package/dist/sankey-interaction.js +32 -0
  88. package/dist/sankey-legend.d.ts +2 -1
  89. package/dist/sankey-legend.js +18 -3
  90. package/dist/sankey-node-label.d.ts +22 -0
  91. package/dist/sankey-node-label.js +34 -0
  92. package/dist/scatter-chart.d.ts +5 -3
  93. package/dist/scatter-chart.js +14 -4
  94. package/dist/scatter-material.js +1 -1
  95. package/dist/scatter-series.js +5 -3
  96. package/dist/series-color.d.ts +10 -0
  97. package/dist/series-color.js +48 -0
  98. package/dist/series-interaction.d.ts +12 -0
  99. package/dist/series-interaction.js +82 -0
  100. package/dist/series-label.d.ts +7 -0
  101. package/dist/series-label.js +18 -0
  102. package/dist/series-paint.d.ts +8 -0
  103. package/dist/series-paint.js +27 -0
  104. package/dist/styles.css +151 -6
  105. package/dist/stylesheet-warning.d.ts +2 -0
  106. package/dist/stylesheet-warning.js +86 -0
  107. package/dist/surface-material.js +1 -1
  108. package/dist/tooltip-content.d.ts +8 -1
  109. package/dist/tooltip-content.js +15 -4
  110. package/dist/tooltip.d.ts +3 -1
  111. package/dist/tooltip.js +8 -5
  112. package/dist/types.d.ts +11 -2
  113. package/dist/waterfall-chart.d.ts +4 -0
  114. package/dist/waterfall-chart.js +7 -0
  115. package/package.json +2 -2
  116. package/dist/bar-paper.d.ts +0 -2
  117. package/dist/bar-paper.js +0 -6
@@ -4,6 +4,8 @@ import { createContext, use, useCallback, useId, useLayoutEffect, useRef, useSta
4
4
  import { LineChart as EngineLineChart, getRelativeCoordinate, useChartHeight, useChartWidth, } from "recharts";
5
5
  import { useChart } from "./chart-context.js";
6
6
  import { useEmphasisActions } from "./emphasis.js";
7
+ import { ChartLoadingSkeleton, useLoadingSeed, } from "./loading-skeleton.js";
8
+ import { SeriesColorDefinitions, SeriesPaintBoundary } from "./series-paint.js";
7
9
  export const LineInteraction = createContext(null);
8
10
  export function useLineInteraction() {
9
11
  const value = use(LineInteraction);
@@ -53,7 +55,8 @@ function Lifecycle({ data, invalidate }) {
53
55
  }, [width, height, data, visibleSeries, invalidate]);
54
56
  return null;
55
57
  }
56
- export function LineChartFrame({ engine: EngineChart, interrupt = () => { }, clip, motionEnabled, categoryEmphasis = false, chartProps: props, children = props.children, }) {
58
+ export function LineChartFrame({ engine: EngineChart, interrupt = () => { }, clip, motionEnabled, categoryEmphasis = false, loading, loadingLabel, loadingSkeleton, loadingAnimation, loadingDesign, chartProps: props, children = props.children, }) {
59
+ const loadingSeed = useLoadingSeed(loading);
57
60
  const { onMouseMove, onMouseLeave } = props;
58
61
  const emphasis = useEmphasisActions();
59
62
  const frame = useRef(null);
@@ -105,12 +108,36 @@ export function LineChartFrame({ engine: EngineChart, interrupt = () => { }, cli
105
108
  return next;
106
109
  });
107
110
  }, []);
111
+ const previousLoading = useRef(loading);
112
+ const completing = previousLoading.current === true && loading !== true;
113
+ useLayoutEffect(() => {
114
+ previousLoading.current = loading;
115
+ });
108
116
  const invalidate = useCallback(() => {
109
117
  setPointer(null);
110
118
  setMotionReady(false);
111
- interrupt();
112
- }, [interrupt]);
113
- return (_jsx(LineInteraction, { value: {
119
+ if (!completing)
120
+ interrupt();
121
+ }, [interrupt, completing]);
122
+ const chart = (_jsx(SeriesPaintBoundary, { children: _jsxs(EngineChart, { ...props, className: [
123
+ "kind-ui-line-chart",
124
+ loading !== undefined && "kind-ui-loading-chart",
125
+ loading && "kind-ui-loading-chart-pending",
126
+ props.className,
127
+ ]
128
+ .filter(Boolean)
129
+ .join(" "), style: { ...props.style, "--kind-ui-line-clip": clip ?? "none" }, onMouseMove: (state, event) => {
130
+ const { relativeX, relativeY } = getRelativeCoordinate(event);
131
+ setKeyboard(false);
132
+ setMotionReady(true);
133
+ setPointer({ x: relativeX, y: relativeY });
134
+ interrupt();
135
+ onMouseMove?.(state, event);
136
+ }, onMouseLeave: (state, event) => {
137
+ setPointer(null);
138
+ onMouseLeave?.(state, event);
139
+ }, children: [_jsx(SeriesColorDefinitions, { viewport: true }), _jsx(Lifecycle, { data: props.data, invalidate: invalidate }), children, loading && loadingSkeleton && (_jsx(ChartLoadingSkeleton, { family: loadingSkeleton, seed: loadingSeed, animation: loadingAnimation, design: loadingDesign }))] }) }));
140
+ return (_jsxs(LineInteraction, { value: {
114
141
  pointer,
115
142
  keyboard,
116
143
  emphasisScope,
@@ -121,40 +148,33 @@ export function LineChartFrame({ engine: EngineChart, interrupt = () => { }, cli
121
148
  invalidate,
122
149
  seriesKeys,
123
150
  registerSeries,
124
- }, children: _jsx("div", { ref: frame, "data-kind-ui": "line-frame", "data-motion": motionEnabled === undefined ? undefined : motionEnabled ? "on" : "off", style: { display: "contents" }, onFocusCapture: (event) => {
125
- interrupt();
126
- if (event.target.matches(":focus-visible"))
127
- setKeyboard(true);
128
- }, onPointerDownCapture: () => {
129
- setKeyboard(false);
130
- interrupt();
131
- }, onPointerLeave: () => emphasis.clearScope(emphasisScope), onPointerCancel: () => emphasis.clearScope(emphasisScope), onPointerOverCapture: () => setKeyboard(false), onPointerMoveCapture: () => {
132
- setKeyboard(false);
133
- interrupt();
134
- }, onKeyDownCapture: (event) => {
135
- setKeyboard(event.key !== "Escape");
136
- interrupt();
137
- setMotionReady(true);
138
- setPointer(null);
139
- }, onBlurCapture: (event) => {
140
- if (!event.currentTarget.contains(event.relatedTarget)) {
141
- setPointer(null);
151
+ }, children: [_jsx("div", { ref: frame, "aria-busy": loading, "aria-hidden": loading || undefined, inert: loading || undefined, "data-kind-ui": "line-frame", "data-motion": motionEnabled === undefined ? undefined : motionEnabled ? "on" : "off", style: { display: "contents" }, onFocusCapture: (event) => {
152
+ interrupt();
153
+ if (event.target.matches(":focus-visible"))
154
+ setKeyboard(true);
155
+ }, onPointerDownCapture: () => {
142
156
  setKeyboard(false);
143
- emphasis.clear("keyboard");
144
- }
145
- }, children: _jsxs(EngineChart, { ...props, className: ["kind-ui-line-chart", props.className].filter(Boolean).join(" "), style: { ...props.style, "--kind-ui-line-clip": clip ?? "none" }, onMouseMove: (state, event) => {
146
- const { relativeX, relativeY } = getRelativeCoordinate(event);
157
+ interrupt();
158
+ }, onPointerLeave: () => emphasis.clearScope(emphasisScope), onPointerCancel: () => emphasis.clearScope(emphasisScope), onPointerOverCapture: () => setKeyboard(false), onPointerMoveCapture: () => {
147
159
  setKeyboard(false);
148
- setMotionReady(true);
149
- setPointer({ x: relativeX, y: relativeY });
150
160
  interrupt();
151
- onMouseMove?.(state, event);
152
- }, onMouseLeave: (state, event) => {
161
+ }, onKeyDownCapture: (event) => {
162
+ if ((event.key === "Enter" || event.key === " " || event.key === "Escape") &&
163
+ event.target.closest('[data-kind-ui="emphasis-mark"][role="button"], [data-kind-ui="series-interaction"][role="button"]'))
164
+ return;
165
+ setKeyboard(event.key !== "Escape");
166
+ interrupt();
167
+ setMotionReady(true);
153
168
  setPointer(null);
154
- onMouseLeave?.(state, event);
155
- }, children: [_jsx(Lifecycle, { data: props.data, invalidate: invalidate }), children] }) }) }));
169
+ }, onBlurCapture: (event) => {
170
+ if (!event.currentTarget.contains(event.relatedTarget)) {
171
+ setPointer(null);
172
+ setKeyboard(false);
173
+ emphasis.clear("keyboard");
174
+ }
175
+ }, children: chart }), loading !== undefined && (_jsx("span", { "data-kind-ui": "chart-loading-status", role: "status", "aria-atomic": "true", children: loading ? (loadingLabel ?? "Loading chart") : "" }))] }));
156
176
  }
157
177
  /** Recharts owns geometry and keyboard selection; Kind shares pointer/keyboard modality. */
158
- export function LineChart(props) {
159
- return _jsx(LineChartFrame, { chartProps: props, engine: EngineLineChart });
178
+ export function LineChart({ loading, loadingLabel, ...props }) {
179
+ return (_jsx(LineChartFrame, { chartProps: props, engine: EngineLineChart, loading: loading, loadingLabel: loadingLabel, loadingSkeleton: "line" }));
160
180
  }
@@ -0,0 +1,8 @@
1
+ /** Timing for one complete numeric dash-pattern cycle. */
2
+ export type LineDashAnimation = {
3
+ durationMs?: number;
4
+ direction?: "forward" | "reverse";
5
+ };
6
+ /** Unitless SVG lengths only: native CSS/percentage patterns remain consumer-owned. */
7
+ export declare function dashCycle(pattern: string | number | undefined): number | undefined;
8
+ export declare function dashDuration(options: LineDashAnimation): number | undefined;
@@ -0,0 +1,18 @@
1
+ /** Unitless SVG lengths only: native CSS/percentage patterns remain consumer-owned. */
2
+ export function dashCycle(pattern) {
3
+ if (pattern === undefined)
4
+ return undefined;
5
+ const tokens = String(pattern)
6
+ .trim()
7
+ .split(/[\s,]+/);
8
+ if (!tokens.length || tokens.some((token) => !/^(?:\d+\.?\d*|\.\d+)$/.test(token)))
9
+ return undefined;
10
+ const values = tokens.map(Number);
11
+ const sum = values.reduce((total, value) => total + value, 0);
12
+ const cycle = sum * (values.length % 2 ? 2 : 1);
13
+ return Number.isFinite(cycle) && cycle > 0 ? cycle : undefined;
14
+ }
15
+ export function dashDuration(options) {
16
+ const duration = options.durationMs ?? 1000;
17
+ return Number.isFinite(duration) && duration > 0 ? duration : undefined;
18
+ }
@@ -1,5 +1,5 @@
1
1
  import { type LineDrawShapeProps } from "recharts";
2
- export type LineMaterial = "plain" | "paper" | "clay" | "glow";
2
+ export type LineMaterial = "plain" | "clay" | "glow";
3
3
  /** Uses engine points only for filter bounds; Curve retains the original geometry and pathRef. */
4
4
  export declare function MaterialCurve({ material, filterId, materialWidth, animationElapsedTime: _time, isAnimating: _active, isEntrance: _entrance, visibleLength: _length, clipPath, ...props }: Omit<LineDrawShapeProps, "pathRef"> & {
5
5
  pathRef?: LineDrawShapeProps["pathRef"];
@@ -20,5 +20,5 @@ export function MaterialCurve({ material, filterId, materialWidth, animationElap
20
20
  const pad = (Number.isFinite(width) ? width : 12) / 2 + 6;
21
21
  const x = minX - pad;
22
22
  const y = minY - pad;
23
- return (_jsxs("g", { "data-kind-ui": "line-material", "data-material": material, clipPath: clipPath, children: [_jsx("defs", { pointerEvents: "none", children: _jsx("filter", { id: filterId, filterUnits: "userSpaceOnUse", x: x, y: y, width: maxX - x + pad, height: maxY - y + pad, colorInterpolationFilters: "sRGB", children: material === "paper" ? (_jsxs(_Fragment, { children: [_jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.7 0.25", numOctaves: 1, seed: 7, result: "fiber" }), _jsx("feFlood", { floodColor: "var(--kind-ui-line-paper-fiber, #fff)", floodOpacity: "var(--kind-ui-line-paper-grain, 0.38)" }), _jsx("feComposite", { in2: "fiber", operator: "in", result: "texture" }), _jsx("feComposite", { in: "texture", in2: "SourceAlpha", operator: "in", result: "grain" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "SourceGraphic" }), _jsx("feMergeNode", { in: "grain" })] })] })) : material === "glow" ? (_jsxs(_Fragment, { children: [_jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: 2, result: "halo" }), _jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: 0.65, result: "nearHalo" }), _jsx("feFlood", { floodOpacity: "var(--kind-ui-line-glow-opacity, 0.5)", result: "strength" }), _jsx("feComposite", { in: "halo", in2: "strength", operator: "in", result: "softHalo" }), _jsx("feComposite", { in: "nearHalo", in2: "strength", operator: "in", result: "closeHalo" }), _jsx("feMorphology", { in: "SourceAlpha", operator: "erode", radius: 0.55, result: "core" }), _jsx("feFlood", { floodColor: "var(--kind-ui-line-glow-light, #fff)", floodOpacity: 0.5 }), _jsx("feComposite", { in2: "core", operator: "in", result: "lightCore" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "softHalo" }), _jsx("feMergeNode", { in: "closeHalo" }), _jsx("feMergeNode", { in: "SourceGraphic" }), _jsx("feMergeNode", { in: "lightCore" })] })] })) : (_jsxs(_Fragment, { children: [_jsx("feGaussianBlur", { in: "SourceAlpha", stdDeviation: 0.8, result: "rounded" }), _jsx("feSpecularLighting", { in: "rounded", surfaceScale: 1.5, specularConstant: 0.4, specularExponent: 8, lightingColor: "var(--kind-ui-line-clay-light, #fff)", result: "light", children: _jsx("feDistantLight", { azimuth: 225, elevation: 45 }) }), _jsx("feComposite", { in: "light", in2: "SourceAlpha", operator: "in", result: "bevel" }), _jsx("feOffset", { in: "SourceAlpha", dx: -0.6, dy: -1, result: "offset" }), _jsx("feComposite", { in: "SourceAlpha", in2: "offset", operator: "out", result: "edge" }), _jsx("feFlood", { floodColor: "var(--kind-ui-line-clay-shade, #17212b)", floodOpacity: 0.16 }), _jsx("feComposite", { in2: "edge", operator: "in", result: "shade" }), _jsxs("feMerge", { result: "body", children: [_jsx("feMergeNode", { in: "SourceGraphic" }), _jsx("feMergeNode", { in: "shade" })] }), _jsx("feComposite", { in: "body", in2: "bevel", operator: "arithmetic", k2: 1, k3: 0.7, result: "lit" }), _jsx("feOffset", { in: "rounded", dx: 0, dy: 1.5, result: "dropped" }), _jsx("feFlood", { floodColor: "var(--kind-ui-line-clay-shade, #17212b)", floodOpacity: 0.16 }), _jsx("feComposite", { in2: "dropped", operator: "in", result: "shadow" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "shadow" }), _jsx("feMergeNode", { in: "lit" })] })] })) }) }), _jsx(Curve, { ...props, strokeWidth: materialWidth, filter: `url(#${filterId})` })] }));
23
+ return (_jsxs("g", { "data-kind-ui": "line-material", "data-material": material, clipPath: clipPath, children: [_jsx("defs", { pointerEvents: "none", children: _jsx("filter", { id: filterId, filterUnits: "userSpaceOnUse", x: x, y: y, width: maxX - x + pad, height: maxY - y + pad, colorInterpolationFilters: "sRGB", children: material === "glow" ? (_jsxs(_Fragment, { children: [_jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: 2, result: "halo" }), _jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: 0.65, result: "nearHalo" }), _jsx("feFlood", { floodOpacity: "var(--kind-ui-line-glow-opacity, 0.5)", result: "strength" }), _jsx("feComposite", { in: "halo", in2: "strength", operator: "in", result: "softHalo" }), _jsx("feComposite", { in: "nearHalo", in2: "strength", operator: "in", result: "closeHalo" }), _jsx("feMorphology", { in: "SourceAlpha", operator: "erode", radius: 0.55, result: "core" }), _jsx("feFlood", { floodColor: "var(--kind-ui-line-glow-light, #fff)", floodOpacity: 0.5 }), _jsx("feComposite", { in2: "core", operator: "in", result: "lightCore" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "softHalo" }), _jsx("feMergeNode", { in: "closeHalo" }), _jsx("feMergeNode", { in: "SourceGraphic" }), _jsx("feMergeNode", { in: "lightCore" })] })] })) : (_jsxs(_Fragment, { children: [_jsx("feGaussianBlur", { in: "SourceAlpha", stdDeviation: 0.8, result: "rounded" }), _jsx("feSpecularLighting", { in: "rounded", surfaceScale: 1.5, specularConstant: 0.4, specularExponent: 8, lightingColor: "var(--kind-ui-line-clay-light, #fff)", result: "light", children: _jsx("feDistantLight", { azimuth: 225, elevation: 45 }) }), _jsx("feComposite", { in: "light", in2: "SourceAlpha", operator: "in", result: "bevel" }), _jsx("feOffset", { in: "SourceAlpha", dx: -0.6, dy: -1, result: "offset" }), _jsx("feComposite", { in: "SourceAlpha", in2: "offset", operator: "out", result: "edge" }), _jsx("feFlood", { floodColor: "var(--kind-ui-line-clay-shade, #17212b)", floodOpacity: 0.16 }), _jsx("feComposite", { in2: "edge", operator: "in", result: "shade" }), _jsxs("feMerge", { result: "body", children: [_jsx("feMergeNode", { in: "SourceGraphic" }), _jsx("feMergeNode", { in: "shade" })] }), _jsx("feComposite", { in: "body", in2: "bevel", operator: "arithmetic", k2: 1, k3: 0.7, result: "lit" }), _jsx("feOffset", { in: "rounded", dx: 0, dy: 1.5, result: "dropped" }), _jsx("feFlood", { floodColor: "var(--kind-ui-line-clay-shade, #17212b)", floodOpacity: 0.16 }), _jsx("feComposite", { in2: "dropped", operator: "in", result: "shadow" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "shadow" }), _jsx("feMergeNode", { in: "lit" })] })] })) }) }), _jsx(Curve, { ...props, strokeWidth: materialWidth, filter: `url(#${filterId})` })] }));
24
24
  }
@@ -1,15 +1,23 @@
1
1
  import { type ComponentProps } from "react";
2
2
  import { Line } from "recharts";
3
+ import { type LineDashAnimation } from "./line-dash.js";
3
4
  import { type LineMaterial } from "./line-material.js";
5
+ import { type PointStyle } from "./point-marker.js";
4
6
  type DefaultLineDataKey = Extract<ComponentProps<typeof Line>["dataKey"], (row: never) => unknown>;
5
7
  export type LineSeriesProps<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>> = ComponentProps<typeof Line<DataPoint, Value>> & {
6
8
  /** Metadata/visibility key, required only for function or numeric data keys. */
7
9
  seriesKey?: string;
10
+ /** Optional point paint; explicit native dot takes precedence. */
11
+ pointStyle?: PointStyle;
12
+ /** Independent active point paint; explicit native activeDot takes precedence. */
13
+ activePointStyle?: PointStyle;
8
14
  /** Material on the default SVG curve; custom shape/filter retain consumer ownership. */
9
15
  material?: LineMaterial;
16
+ /** Continuous default-curve dashes; requires a numeric native strokeDasharray. */
17
+ dashAnimation?: false | LineDashAnimation;
10
18
  };
11
19
  /** A registered Recharts Line with Root colors and controlled visibility. */
12
- export declare function LineSeries<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>>({ seriesKey, hide, stroke, className, material, renderWhileHidden, ...props }: LineSeriesProps<DataPoint, Value> & {
20
+ export declare function LineSeries<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>>({ seriesKey, pointStyle, activePointStyle: _activePointStyle, hide, stroke, className, material, dashAnimation, renderWhileHidden, ...props }: LineSeriesProps<DataPoint, Value> & {
13
21
  renderWhileHidden?: boolean;
14
22
  }): import("react").JSX.Element;
15
23
  export {};
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useId, useLayoutEffect, useRef } from "react";
4
- import { Line } from "recharts";
4
+ import { DefaultZIndexes, Line, ZIndexLayer } from "recharts";
5
5
  import { useChart } from "./chart-context.js";
6
6
  import { useLineInteraction } from "./line-chart.js";
7
+ import { dashCycle, dashDuration } from "./line-dash.js";
7
8
  import { MaterialCurve } from "./line-material.js";
9
+ import { PointMarker } from "./point-marker.js";
10
+ import { SeriesInteractionLayer, useSeriesInteraction } from "./series-interaction.js";
8
11
  /** A registered Recharts Line with Root colors and controlled visibility. */
9
- export function LineSeries({ seriesKey, hide, stroke, className, material = "plain", renderWhileHidden = false, ...props }) {
10
- const { config, visibleSeries } = useChart();
12
+ export function LineSeries({ seriesKey, pointStyle = "default", activePointStyle: _activePointStyle, hide, stroke, className, material = "plain", dashAnimation = false, renderWhileHidden = false, ...props }) {
13
+ const { config, paints, visibleSeries } = useChart();
11
14
  const { registerSeries, invalidate } = useLineInteraction();
12
15
  const generatedId = useId();
13
16
  const id = props.id || generatedId;
@@ -29,12 +32,35 @@ export function LineSeries({ seriesKey, hide, stroke, className, material = "pla
29
32
  }, [id, key, registerSeries]);
30
33
  if (key === undefined && visibleSeries !== undefined)
31
34
  throw new Error("LineSeries requires seriesKey for controlled non-string dataKey");
32
- const color = stroke ?? (key && Object.hasOwn(config, key) ? `var(--color-${key})` : undefined);
33
- return (_jsx(Line, { isAnimationActive: false, ...props, ...(material !== "plain" && props.shape === undefined && props.filter === undefined
34
- ? {
35
- shape: (_jsx(MaterialCurve, { material: material, filterId: `${generatedId}-material`, materialWidth: props.strokeWidth ?? (material === "clay" ? 6 : material === "paper" ? 2.5 : 3) })),
36
- strokeLinecap: props.strokeLinecap ?? (props.strokeDasharray ? "butt" : "round"),
37
- strokeLinejoin: props.strokeLinejoin ?? "round",
38
- }
39
- : {}), id: id, hide: renderedHide, ...(color !== undefined ? { stroke: color } : {}), className: ["kind-ui-line-series", className].filter(Boolean).join(" ") }));
35
+ const interaction = useSeriesInteraction(key, effectiveHide, hide === true, props.data, props.onClick);
36
+ const color = stroke ?? (key && Object.hasOwn(config, key) ? paints[key] : undefined);
37
+ const cycle = dashCycle(props.style?.strokeDasharray ?? props.strokeDasharray);
38
+ const duration = dashAnimation && dashDuration(dashAnimation);
39
+ const dashed = dashAnimation !== false &&
40
+ duration !== undefined &&
41
+ cycle !== undefined &&
42
+ !effectiveHide &&
43
+ props.shape === undefined &&
44
+ props.isAnimationActive !== true;
45
+ const offset = props.style?.strokeDashoffset ?? props.strokeDashoffset ?? 0;
46
+ const baseline = Number.isFinite(Number(offset)) ? `${Number(offset)}px` : offset;
47
+ return (_jsx(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.line, children: _jsx(SeriesInteractionLayer, { seriesKey: key, hidden: effectiveHide, children: _jsx(Line, { isAnimationActive: false, ...props, ...(interaction.onClick !== undefined ? { onClick: interaction.onClick } : {}), zIndex: 0, ...(props.dot === undefined && pointStyle !== "default"
48
+ ? { dot: _jsx(PointMarker, { variant: pointStyle }) }
49
+ : {}), ...(material !== "plain" && props.shape === undefined && props.filter === undefined
50
+ ? {
51
+ shape: (_jsx(MaterialCurve, { material: material, filterId: `${generatedId}-material`, materialWidth: props.strokeWidth ?? (material === "clay" ? 6 : 3) })),
52
+ strokeLinecap: props.strokeLinecap ?? (props.strokeDasharray ? "butt" : "round"),
53
+ strokeLinejoin: props.strokeLinejoin ?? "round",
54
+ }
55
+ : {}), style: dashed
56
+ ? {
57
+ ...props.style,
58
+ "--kind-ui-dash-cycle": `${cycle}px`,
59
+ "--kind-ui-dash-offset": baseline,
60
+ "--kind-ui-dash-duration": `${duration}ms`,
61
+ "--kind-ui-dash-direction": dashAnimation && dashAnimation.direction === "reverse" ? "reverse" : "normal",
62
+ }
63
+ : props.style, id: id, hide: renderedHide, ...(color !== undefined ? { stroke: color } : {}), className: ["kind-ui-line-series", dashed && "kind-ui-line-dash", className]
64
+ .filter(Boolean)
65
+ .join(" ") }) }) }));
40
66
  }
@@ -0,0 +1,6 @@
1
+ export type CartesianLoadingFamily = "area" | "bar" | "combo" | "scatter" | "waterfall" | "histogram" | "box-plot";
2
+ /** Original bounded illustrations: seeded variation never reads consumer data or scales. */
3
+ export declare function CartesianLoadingDesign({ family, seed, }: {
4
+ family: CartesianLoadingFamily;
5
+ seed: number;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { LoadingPulseMark } from "./loading-motion.js";
4
+ const barProfiles = [
5
+ [35, 64, 92, 128, 164, 188, 216],
6
+ [211, 178, 154, 114, 82, 57, 30],
7
+ [185, 74, 209, 47, 149, 91, 195],
8
+ [38, 105, 173, 218, 154, 90, 32],
9
+ ];
10
+ const curveProfiles = [
11
+ [208, 181, 142, 115, 88, 51, 27],
12
+ [28, 52, 89, 116, 145, 180, 209],
13
+ [39, 88, 172, 210, 168, 87, 35],
14
+ [190, 51, 158, 33, 194, 74, 142],
15
+ ];
16
+ const waterfallProfiles = [
17
+ [192, 131, 160, 70, 117, 48],
18
+ [39, 109, 66, 161, 131, 204],
19
+ [172, 41, 182, 64, 194, 106],
20
+ [203, 162, 91, 145, 55, 83],
21
+ ];
22
+ const boxCenters = [
23
+ [174, 144, 114, 84, 54],
24
+ [55, 86, 117, 148, 179],
25
+ [131, 75, 159, 92, 143],
26
+ [86, 143, 61, 168, 111],
27
+ ];
28
+ const bound = (value, low, high) => Math.max(low, Math.min(high, value));
29
+ /** Original bounded illustrations: seeded variation never reads consumer data or scales. */
30
+ export function CartesianLoadingDesign({ family, seed, }) {
31
+ const variation = (slot) => {
32
+ const value = Math.sin(seed * 13.37 + slot * 7.91) * 43758.5453;
33
+ return (value - Math.floor(value)) * 2 - 1;
34
+ };
35
+ const profile = ((Math.trunc(seed) % 4) + 4) % 4;
36
+ const heights = (barProfiles[profile] ?? barProfiles[0]).map((height, index) => ({
37
+ x: 16 + index * 91,
38
+ height: bound(height + variation(index) * 12, 20, 220),
39
+ }));
40
+ const ys = (curveProfiles[profile] ?? curveProfiles[0]).map((y, index) => bound(y + variation(index + 20) * 10, 15, 220));
41
+ let areaCurve = `M 12 ${ys[0] ?? 120}`;
42
+ for (let index = 1; index < ys.length; index++) {
43
+ const x = 12 + index * (616 / 6);
44
+ const previousX = 12 + (index - 1) * (616 / 6);
45
+ areaCurve += ` C ${previousX + 51} ${ys[index - 1]} ${x - 51} ${ys[index]} ${x} ${ys[index]}`;
46
+ }
47
+ if (family === "area")
48
+ return (_jsx("path", { "data-loading-motion": "reveal", d: `${areaCurve} L 628 238 L 12 238 Z`, fill: "currentColor" }));
49
+ if (family === "bar")
50
+ return (_jsx("g", { "data-loading-motion": "grow", children: heights.map(({ x, height }) => (_jsx("rect", { x: x, y: 238 - height, width: 60, height: height, rx: 3, fill: "currentColor" }, x))) }));
51
+ if (family === "combo")
52
+ return (_jsxs(_Fragment, { children: [_jsx("g", { "data-loading-motion": "grow", children: heights.map(({ x, height }) => (_jsx("rect", { x: x, y: 238 - height * 0.67, width: 50, height: height * 0.67, rx: 3, fill: "currentColor", opacity: 0.5 }, x))) }), _jsxs("g", { children: [_jsx("path", { "data-loading-motion": "area-reveal", d: `${areaCurve} L 628 238 L 12 238 Z`, fill: "currentColor", opacity: 0.22 }), _jsx("path", { "data-loading-motion": "reveal", d: areaCurve, fill: "none", stroke: "currentColor", strokeWidth: 2.25, vectorEffect: "non-scaling-stroke", strokeLinecap: "round" })] })] }));
53
+ if (family === "scatter") {
54
+ const points = Array.from({ length: 24 }, (_, index) => {
55
+ const u = (variation(index + 50) + 1) / 2;
56
+ const v = (variation(index + 80) + 1) / 2;
57
+ let cx;
58
+ let cy;
59
+ if (profile === 0 || profile === 1) {
60
+ cx = 24 + index * 25;
61
+ cy = profile === 0 ? 211 - index * 7.6 : 28 + index * 7.6;
62
+ cy += variation(index + 110) * 24;
63
+ }
64
+ else if (profile === 2) {
65
+ const cluster = index % 3;
66
+ cx = [114, 328, 528][cluster] ?? 328;
67
+ cy = [166, 62, 159][cluster] ?? 110;
68
+ cx += (u - 0.5) * 126;
69
+ cy += (v - 0.5) * 83;
70
+ }
71
+ else {
72
+ cx = 22 + u * 596;
73
+ cy = 18 + v * 204;
74
+ }
75
+ return {
76
+ id: `point-${index}`,
77
+ cx: bound(cx, 12, 628),
78
+ cy: bound(cy, 12, 228),
79
+ r: 4 + (variation(index + 140) + 1) * 3,
80
+ };
81
+ });
82
+ return (_jsx(_Fragment, { children: points.map(({ id, cx, cy, r }) => (_jsx(LoadingPulseMark, { delay: (cx / 640) * 0.65, span: 0.35, children: _jsx("circle", { "data-loading-motion": "point", cx: cx, cy: cy, r: r, fill: "currentColor" }) }, id))) }));
83
+ }
84
+ if (family === "histogram") {
85
+ const gaussian = (x, peak, spread) => Math.exp(-((x - peak) ** 2) / (2 * spread ** 2));
86
+ const bins = Array.from({ length: 12 }, (_, index) => {
87
+ const density = profile === 0
88
+ ? gaussian(index, 2.5, 1.8)
89
+ : profile === 1
90
+ ? gaussian(index, 8.5, 1.8)
91
+ : profile === 2
92
+ ? Math.max(gaussian(index, 2, 1.15), gaussian(index, 8.5, 1.5))
93
+ : index === 0
94
+ ? 0.4
95
+ : Math.exp(-(index - 1) / 3.8);
96
+ return {
97
+ x: 8 + index * 52,
98
+ height: bound(18 + density * 194 + variation(index + 170) * 8, 10, 220),
99
+ };
100
+ });
101
+ return (_jsx("g", { "data-loading-motion": "grow", children: bins.map(({ x, height }) => (_jsx("rect", { x: x, y: 238 - height, width: 50, height: height, fill: "currentColor" }, x))) }));
102
+ }
103
+ if (family === "waterfall") {
104
+ const tops = (waterfallProfiles[profile] ?? waterfallProfiles[0]).map((top, index) => bound(top + variation(index + 200) * 9, 16, 220));
105
+ const levels = [238, ...tops, 238];
106
+ return (_jsx("g", { "data-loading-motion": "grow", children: levels.slice(1).map((top, index) => {
107
+ const previous = levels[index] ?? 238;
108
+ const x = 16 + index * 91;
109
+ return (_jsxs("g", { children: [_jsx("rect", { x: x, y: Math.min(top, previous), width: 48, height: Math.abs(top - previous), rx: 2, fill: "currentColor" }), index < 6 && (_jsx("path", { d: `M ${x + 48} ${top} H ${x + 91}`, fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeDasharray: "4 4", vectorEffect: "non-scaling-stroke" }))] }, x));
110
+ }) }));
111
+ }
112
+ const boxes = (boxCenters[profile] ?? boxCenters[0]).map((center, index) => {
113
+ const median = center + variation(index + 230) * 9;
114
+ const lowerSpan = 14 + (variation(index + 250) + 1) * 19;
115
+ const upperSpan = 12 + (variation(index + 270) + 1) * 20;
116
+ const q1 = bound(median - lowerSpan, 22, median - 6);
117
+ const q3 = bound(median + upperSpan, median + 6, 216);
118
+ const low = bound(q1 - 10 - (variation(index + 290) + 1) * 14, 10, q1 - 5);
119
+ const high = bound(q3 + 10 + (variation(index + 310) + 1) * 14, q3 + 5, 230);
120
+ return { x: 60 + index * 124, low, q1, median, q3, high };
121
+ });
122
+ return (_jsx("g", { "data-loading-motion": "grow", children: boxes.map(({ x, low, q1, median, q3, high }) => (_jsxs("g", { stroke: "currentColor", strokeWidth: 2, vectorEffect: "non-scaling-stroke", children: [_jsx("path", { d: `M ${x} ${low} V ${q1} M ${x} ${q3} V ${high} M ${x - 18} ${low} H ${x + 18} M ${x - 18} ${high} H ${x + 18}`, fill: "none" }), _jsx("rect", { x: x - 27, y: q1, width: 54, height: q3 - q1, fill: "currentColor", fillOpacity: 0.4, rx: 2 }), _jsx("path", { d: `M ${x - 27} ${median} H ${x + 27}`, fill: "none" })] }, x))) }));
123
+ }
@@ -0,0 +1,37 @@
1
+ import { type MotionValue, type Transition } from "motion/react";
2
+ import { type ReactNode } from "react";
3
+ export type LoadingAnimation = {
4
+ revealDurationMs?: number;
5
+ revealEasing?: Transition["ease"];
6
+ layout?: "horizontal" | "vertical";
7
+ direction?: "clockwise" | "anticlockwise";
8
+ lineReveal?: false | Pick<LoadingAnimation, "revealDurationMs" | "revealEasing">;
9
+ areaReveal?: false | Pick<LoadingAnimation, "revealDurationMs" | "revealEasing">;
10
+ barReveal?: false | Pick<LoadingAnimation, "revealDurationMs" | "revealEasing">;
11
+ };
12
+ export declare function useLoadingProgress(pulse: number, duration: number, easing: Transition["ease"], startDelay?: number, continuousTrail?: boolean): {
13
+ progress: MotionValue<number>;
14
+ reduced: boolean;
15
+ };
16
+ export declare const LoadingProgress: import("react").Context<{
17
+ progress: MotionValue<number>;
18
+ reduced: boolean;
19
+ } | null>;
20
+ /** The same screen/diagonal ordering as native scatter and heatmap entrances, with a soft trail. */
21
+ export declare function LoadingPulseMark({ delay, span, children, }: {
22
+ delay: number;
23
+ span: number;
24
+ children: ReactNode;
25
+ }): import("react").JSX.Element;
26
+ export declare function LoadingAngularBand({ progress, index, direction, }: {
27
+ progress: MotionValue<number>;
28
+ index: number;
29
+ direction: "clockwise" | "anticlockwise";
30
+ }): import("react").JSX.Element;
31
+ /** Native Sankey's source ordering and path-length reveal, with a following erase. */
32
+ export declare function LoadingPulseFlow({ d, width, delay, children, }: {
33
+ d: string;
34
+ width: number;
35
+ delay: number;
36
+ children: ReactNode;
37
+ }): import("react").JSX.Element;
@@ -0,0 +1,88 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { animate, motion, useMotionValue, useTransform, } from "motion/react";
4
+ import { createContext, use, useId, useLayoutEffect, useRef, useSyncExternalStore, } from "react";
5
+ const query = "(prefers-reduced-motion: reduce)";
6
+ function subscribe(change) {
7
+ const media = window.matchMedia(query);
8
+ media.addEventListener("change", change);
9
+ return () => media.removeEventListener("change", change);
10
+ }
11
+ export function useLoadingProgress(pulse, duration, easing, startDelay = 0, continuousTrail = false) {
12
+ const reduced = useSyncExternalStore(subscribe, () => window.matchMedia(query).matches, () => true);
13
+ const progress = useMotionValue(0);
14
+ const easingCache = useRef(easing);
15
+ if (Array.isArray(easing) && Array.isArray(easingCache.current)) {
16
+ if (JSON.stringify(easing) !== JSON.stringify(easingCache.current))
17
+ easingCache.current = easing;
18
+ }
19
+ else if (easing !== easingCache.current)
20
+ easingCache.current = easing;
21
+ const stableEasing = easingCache.current;
22
+ // biome-ignore lint/correctness/useExhaustiveDependencies: a new invisible pulse deliberately rearms the entrance.
23
+ useLayoutEffect(() => {
24
+ if (reduced || duration === 0) {
25
+ progress.set(1);
26
+ return;
27
+ }
28
+ progress.set(0);
29
+ // RadialBar keeps one angular velocity through the closing seam. Splitting
30
+ // an eased reveal and a linear trail nearly stops, then abruptly speeds up.
31
+ if (continuousTrail) {
32
+ const controls = animate(progress, 1.65, {
33
+ duration: (duration * 1.65) / 1000,
34
+ delay: startDelay / 1000,
35
+ ease: "linear",
36
+ });
37
+ return () => controls.stop();
38
+ }
39
+ let trail;
40
+ const controls = animate(progress, 1, {
41
+ duration: duration / 1000,
42
+ delay: startDelay / 1000,
43
+ ease: stableEasing ?? "easeOut",
44
+ onComplete: () => {
45
+ trail = animate(progress, 1.65, { duration: (duration * 0.65) / 1000, ease: "linear" });
46
+ },
47
+ });
48
+ return () => {
49
+ controls.stop();
50
+ trail?.stop();
51
+ };
52
+ }, [progress, pulse, duration, stableEasing, reduced, startDelay, continuousTrail]);
53
+ return { progress, reduced: reduced || duration === 0 };
54
+ }
55
+ export const LoadingProgress = createContext(null);
56
+ /** The same screen/diagonal ordering as native scatter and heatmap entrances, with a soft trail. */
57
+ export function LoadingPulseMark({ delay, span, children, }) {
58
+ const context = use(LoadingProgress);
59
+ const fallback = useMotionValue(1);
60
+ const opacity = useTransform(context?.progress ?? fallback, (value) => context?.reduced
61
+ ? 1
62
+ : Math.max(0, Math.min(1, (value - delay) / span, (delay + span + 0.4 - value) / 0.4)));
63
+ return (_jsx(motion.g, { "data-kind-ui": "loading-mark", style: { opacity }, children: children }));
64
+ }
65
+ export function LoadingAngularBand({ progress, index, direction, }) {
66
+ const sign = direction === "anticlockwise" ? -1 : 1;
67
+ const offset = useTransform(progress, (value) => -sign * value * 100);
68
+ const opacity = useTransform(progress, (value) => {
69
+ const angle = value * 360 - index * 18;
70
+ return (Math.max(0, Math.min(1, angle / 9, (360 - angle) / 9)) * Math.sin(((index + 1) / 9) * Math.PI));
71
+ });
72
+ return (_jsx(motion.circle, { "data-kind-ui": "loading-angular", cx: "320", cy: "120", r: "120", fill: "none", stroke: "white", strokeWidth: "240", pathLength: "100", strokeDasharray: "5 95", strokeDashoffset: offset, style: { opacity }, transform: `rotate(${-90 - sign * index * 18} 320 120)` }));
73
+ }
74
+ /** Native Sankey's source ordering and path-length reveal, with a following erase. */
75
+ export function LoadingPulseFlow({ d, width, delay, children, }) {
76
+ const context = use(LoadingProgress);
77
+ const fallback = useMotionValue(1);
78
+ const id = `loading-flow-${useId().replace(/:/g, "")}`;
79
+ const dash = useTransform(context?.progress ?? fallback, (value) => {
80
+ if (context?.reduced)
81
+ return "1 1";
82
+ const lead = Math.max(0, Math.min(1, (value - delay) / 0.45));
83
+ const trail = Math.max(0, Math.min(1, (value - delay - 0.45) / 0.4));
84
+ return `${Math.max(0, lead - trail)} 1`;
85
+ });
86
+ const offset = useTransform(context?.progress ?? fallback, (value) => context?.reduced ? 0 : -Math.max(0, Math.min(1, (value - delay - 0.45) / 0.4)));
87
+ return (_jsxs("g", { children: [_jsx("defs", { children: _jsxs("mask", { id: id, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: [_jsx("filter", { id: `${id}-soft`, filterUnits: "userSpaceOnUse", x: "-32", y: "-32", width: "704", height: "304", children: _jsx("feGaussianBlur", { stdDeviation: "2" }) }), _jsx(motion.path, { filter: `url(#${id}-soft)`, "data-kind-ui": "loading-flow-window", d: d, fill: "none", stroke: "white", strokeWidth: width + 2, pathLength: "1", strokeDasharray: dash, strokeDashoffset: offset })] }) }), _jsx("g", { mask: `url(#${id})`, children: children })] }));
88
+ }
@@ -0,0 +1,6 @@
1
+ export type PolarLoadingFamily = "pie" | "radar" | "radial-bar" | "activity-rings";
2
+ /** A bounded decorative profile, independent of the native chart data. */
3
+ export declare function PolarLoadingDesign({ family, seed, }: {
4
+ family: PolarLoadingFamily;
5
+ seed?: number;
6
+ }): import("react").JSX.Element;