@kind-ui/charts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +966 -0
  4. package/dist/activity-rings.d.ts +47 -0
  5. package/dist/activity-rings.js +55 -0
  6. package/dist/animation.d.ts +27 -0
  7. package/dist/animation.js +102 -0
  8. package/dist/area-chart.d.ts +9 -0
  9. package/dist/area-chart.js +33 -0
  10. package/dist/area-clay.d.ts +2 -0
  11. package/dist/area-clay.js +6 -0
  12. package/dist/area-material.d.ts +8 -0
  13. package/dist/area-material.js +66 -0
  14. package/dist/area-series.d.ts +11 -0
  15. package/dist/area-series.js +34 -0
  16. package/dist/bar-category.d.ts +5 -0
  17. package/dist/bar-category.js +35 -0
  18. package/dist/bar-chart.d.ts +17 -0
  19. package/dist/bar-chart.js +42 -0
  20. package/dist/bar-clay.d.ts +4 -0
  21. package/dist/bar-clay.js +6 -0
  22. package/dist/bar-material.d.ts +8 -0
  23. package/dist/bar-material.js +19 -0
  24. package/dist/bar-paper.d.ts +2 -0
  25. package/dist/bar-paper.js +6 -0
  26. package/dist/bar-series.d.ts +13 -0
  27. package/dist/bar-series.js +178 -0
  28. package/dist/box-material.d.ts +14 -0
  29. package/dist/box-material.js +30 -0
  30. package/dist/box-plot.d.ts +50 -0
  31. package/dist/box-plot.js +218 -0
  32. package/dist/category-cells.d.ts +6 -0
  33. package/dist/category-cells.js +41 -0
  34. package/dist/chart-context.d.ts +11 -0
  35. package/dist/chart-context.js +9 -0
  36. package/dist/combo-chart.d.ts +16 -0
  37. package/dist/combo-chart.js +64 -0
  38. package/dist/configured-line-chart.d.ts +79 -0
  39. package/dist/configured-line-chart.js +77 -0
  40. package/dist/emphasis.d.ts +44 -0
  41. package/dist/emphasis.js +128 -0
  42. package/dist/heatmap-model.d.ts +38 -0
  43. package/dist/heatmap-model.js +73 -0
  44. package/dist/heatmap.d.ts +56 -0
  45. package/dist/heatmap.js +366 -0
  46. package/dist/histogram-chart.d.ts +14 -0
  47. package/dist/histogram-chart.js +27 -0
  48. package/dist/histogram-data.d.ts +17 -0
  49. package/dist/histogram-data.js +66 -0
  50. package/dist/histogram-material.d.ts +13 -0
  51. package/dist/histogram-material.js +8 -0
  52. package/dist/histogram-series.d.ts +16 -0
  53. package/dist/histogram-series.js +67 -0
  54. package/dist/index.d.ts +45 -0
  55. package/dist/index.js +36 -0
  56. package/dist/legend.d.ts +16 -0
  57. package/dist/legend.js +30 -0
  58. package/dist/line-chart.d.ts +41 -0
  59. package/dist/line-chart.js +160 -0
  60. package/dist/line-material.d.ts +9 -0
  61. package/dist/line-material.js +24 -0
  62. package/dist/line-series.d.ts +15 -0
  63. package/dist/line-series.js +40 -0
  64. package/dist/pie-chart.d.ts +17 -0
  65. package/dist/pie-chart.js +68 -0
  66. package/dist/pie-material.d.ts +19 -0
  67. package/dist/pie-material.js +16 -0
  68. package/dist/pie-series.d.ts +14 -0
  69. package/dist/pie-series.js +208 -0
  70. package/dist/polar-chart.d.ts +33 -0
  71. package/dist/polar-chart.js +145 -0
  72. package/dist/polar-material.d.ts +7 -0
  73. package/dist/polar-material.js +9 -0
  74. package/dist/polar-series.d.ts +17 -0
  75. package/dist/polar-series.js +169 -0
  76. package/dist/radar-interaction.d.ts +29 -0
  77. package/dist/radar-interaction.js +138 -0
  78. package/dist/radial-bar-label.d.ts +15 -0
  79. package/dist/radial-bar-label.js +136 -0
  80. package/dist/radial-category.d.ts +4 -0
  81. package/dist/radial-category.js +4 -0
  82. package/dist/root.d.ts +14 -0
  83. package/dist/root.js +29 -0
  84. package/dist/sankey-chart.d.ts +33 -0
  85. package/dist/sankey-chart.js +142 -0
  86. package/dist/sankey-colors.d.ts +10 -0
  87. package/dist/sankey-colors.js +6 -0
  88. package/dist/sankey-data.d.ts +30 -0
  89. package/dist/sankey-data.js +65 -0
  90. package/dist/sankey-finish.d.ts +13 -0
  91. package/dist/sankey-finish.js +10 -0
  92. package/dist/sankey-legend.d.ts +14 -0
  93. package/dist/sankey-legend.js +9 -0
  94. package/dist/sankey-marks.d.ts +31 -0
  95. package/dist/sankey-marks.js +67 -0
  96. package/dist/sankey-table.d.ts +11 -0
  97. package/dist/sankey-table.js +8 -0
  98. package/dist/scatter-chart.d.ts +16 -0
  99. package/dist/scatter-chart.js +64 -0
  100. package/dist/scatter-material.d.ts +8 -0
  101. package/dist/scatter-material.js +39 -0
  102. package/dist/scatter-series.d.ts +11 -0
  103. package/dist/scatter-series.js +74 -0
  104. package/dist/scatter-tooltip.d.ts +24 -0
  105. package/dist/scatter-tooltip.js +96 -0
  106. package/dist/styles.css +443 -0
  107. package/dist/surface-material.d.ts +13 -0
  108. package/dist/surface-material.js +7 -0
  109. package/dist/tooltip-content.d.ts +19 -0
  110. package/dist/tooltip-content.js +60 -0
  111. package/dist/tooltip-number.d.ts +4 -0
  112. package/dist/tooltip-number.js +68 -0
  113. package/dist/tooltip.d.ts +29 -0
  114. package/dist/tooltip.js +73 -0
  115. package/dist/types.d.ts +11 -0
  116. package/dist/types.js +1 -0
  117. package/dist/waterfall-data.d.ts +24 -0
  118. package/dist/waterfall-data.js +45 -0
  119. package/dist/waterfall-series.d.ts +15 -0
  120. package/dist/waterfall-series.js +35 -0
  121. package/package.json +49 -0
@@ -0,0 +1,79 @@
1
+ import { type ReactNode } from "react";
2
+ import { type CartesianGridProps, type DataKey, type XAxisProps, type YAxisProps } from "recharts";
3
+ import { type LineChartProps as ComposedLineChartProps, type LineSeriesProps, type TooltipProps } from "./animation.js";
4
+ import { type LegendProps } from "./legend.js";
5
+ import { type RootProps } from "./root.js";
6
+ import type { SeriesConfig } from "./types.js";
7
+ type DefaultDataPoint = Parameters<Extract<LineSeriesProps["dataKey"], (row: never) => unknown>>[0];
8
+ export type ConfiguredLineSeries<DataPoint = DefaultDataPoint> = LineSeriesProps<DataPoint> & {
9
+ /** Explicit metadata identity; required for numeric and function data keys too. */
10
+ seriesKey: string;
11
+ dataKey: NonNullable<LineSeriesProps<DataPoint>["dataKey"]>;
12
+ };
13
+ type Visibility = {
14
+ visibleSeries: readonly string[];
15
+ defaultVisibleSeries?: never;
16
+ onVisibleSeriesChange?: (next: string[]) => void;
17
+ } | {
18
+ visibleSeries?: undefined;
19
+ defaultVisibleSeries?: readonly string[];
20
+ onVisibleSeriesChange?: (next: string[]) => void;
21
+ };
22
+ type Name = {
23
+ "aria-label": string;
24
+ "aria-labelledby"?: string;
25
+ } | {
26
+ "aria-labelledby": string;
27
+ "aria-label"?: string;
28
+ };
29
+ type GeneratedParts<DataPoint> = {
30
+ children?: undefined;
31
+ layout?: "horizontal";
32
+ xDataKey: DataKey<DataPoint>;
33
+ curve?: LineSeriesProps["type"];
34
+ material?: LineSeriesProps["material"];
35
+ xAxis?: false | XAxisProps<DataPoint>;
36
+ yAxis?: false | YAxisProps<DataPoint>;
37
+ grid?: false | CartesianGridProps;
38
+ tooltip?: false | TooltipProps;
39
+ series?: readonly ConfiguredLineSeries<DataPoint>[];
40
+ };
41
+ type ExplicitParts = {
42
+ layout?: NonNullable<ComposedLineChartProps["layout"]>;
43
+ children: Exclude<ReactNode, undefined>;
44
+ curve?: never;
45
+ material?: never;
46
+ xDataKey?: never;
47
+ xAxis?: never;
48
+ yAxis?: never;
49
+ grid?: never;
50
+ tooltip?: never;
51
+ series?: never;
52
+ };
53
+ export type ConfiguredLineChartProps<DataPoint = DefaultDataPoint> = Omit<ComposedLineChartProps, "data" | "children" | "layout"> & {
54
+ config: SeriesConfig;
55
+ data: readonly DataPoint[];
56
+ "aria-describedby"?: string;
57
+ legend?: false | LegendProps;
58
+ rootProps?: Omit<RootProps, "config" | "children" | "visibleSeries" | "onVisibleSeriesChange">;
59
+ } & Name & Visibility & (GeneratedParts<DataPoint> | ExplicitParts);
60
+ export type LineChartProps = ComposedLineChartProps;
61
+ type CompositionLineChartProps = LineChartProps & {
62
+ config?: never;
63
+ xDataKey?: never;
64
+ xAxis?: never;
65
+ yAxis?: never;
66
+ grid?: never;
67
+ tooltip?: never;
68
+ curve?: never;
69
+ material?: never;
70
+ series?: never;
71
+ legend?: never;
72
+ rootProps?: never;
73
+ visibleSeries?: never;
74
+ defaultVisibleSeries?: never;
75
+ onVisibleSeriesChange?: never;
76
+ };
77
+ /** Existing composition unchanged; config explicitly opts into package-owned composition. */
78
+ export declare function LineChart<DataPoint = DefaultDataPoint>(props: CompositionLineChartProps | ConfiguredLineChartProps<DataPoint>): import("react").JSX.Element;
79
+ export {};
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useContext, useId, useMemo, useRef, useState } from "react";
4
+ import { CartesianGrid, XAxis, YAxis, } from "recharts";
5
+ import { LineChart as ComposedLineChart, LineSeries, Tooltip, } from "./animation.js";
6
+ import { ChartContext } from "./chart-context.js";
7
+ import { Legend } from "./legend.js";
8
+ import { Root } from "./root.js";
9
+ /** Existing composition unchanged; config explicitly opts into package-owned composition. */
10
+ export function LineChart(props) {
11
+ if ("config" in props && props.config !== undefined)
12
+ return _jsx(ConfiguredLineChart, { ...props });
13
+ return _jsx(ComposedLineChart, { ...props });
14
+ }
15
+ function ConfiguredLineChart(props) {
16
+ const parent = useContext(ChartContext);
17
+ const { config, data, children, xDataKey, curve, material, xAxis, yAxis, grid, tooltip, series, legend, rootProps, visibleSeries, defaultVisibleSeries, onVisibleSeriesChange, style, className, width, height, responsive, accessibilityLayer = true, animate = true, ...chartProps } = props;
18
+ const controlled = visibleSeries !== undefined;
19
+ const initialMode = useRef(controlled);
20
+ // Undefined means all current configured keys, including keys added later.
21
+ // A user/default selection is a whitelist; new keys then start hidden.
22
+ const [selection, setSelection] = useState(defaultVisibleSeries);
23
+ const instructionId = useId();
24
+ if (parent)
25
+ throw new Error("Configured LineChart owns Root; remove the surrounding Root or omit config and compose explicitly");
26
+ if (controlled !== initialMode.current)
27
+ throw new Error("LineChart cannot switch controlled visibility mode; keep visibleSeries defined or remount with a new key");
28
+ if (visibleSeries !== undefined && defaultVisibleSeries !== undefined)
29
+ throw new Error("LineChart accepts visibleSeries or defaultVisibleSeries, not both");
30
+ if (typeof chartProps["aria-label"] !== "string" &&
31
+ typeof chartProps["aria-labelledby"] !== "string")
32
+ throw new Error("Configured LineChart requires aria-label or aria-labelledby");
33
+ const generated = children === undefined;
34
+ if (!generated &&
35
+ [xDataKey, curve, material, xAxis, yAxis, grid, tooltip, series].some((part) => part !== undefined))
36
+ throw new Error("Explicit LineChart children replace generated parts; remove xDataKey and generated-part options");
37
+ if (generated && xDataKey === undefined)
38
+ throw new Error("Configured LineChart requires xDataKey when children are omitted");
39
+ if (generated && chartProps.layout !== undefined && chartProps.layout !== "horizontal")
40
+ throw new Error("Configured LineChart defaults use horizontal layout; supply explicit children for vertical layout");
41
+ const keys = Object.keys(config);
42
+ const identities = generated && series !== undefined ? series.map((item) => item.seriesKey) : keys;
43
+ if (new Set(identities).size !== identities.length)
44
+ throw new Error("Configured LineChart seriesKey values must be unique");
45
+ for (const key of identities)
46
+ if (!Object.hasOwn(config, key))
47
+ throw new Error(`LineChart seriesKey "${key}" must exist in config`);
48
+ const activeConfig = generated && series !== undefined
49
+ ? // biome-ignore lint/style/noNonNullAssertion: The preceding loop rejects each identity absent from config.
50
+ Object.fromEntries(identities.map((key) => [key, config[key]]))
51
+ : config;
52
+ const candidate = (visibleSeries ?? selection ?? identities).filter((key) => identities.includes(key));
53
+ const visibilityKey = JSON.stringify(candidate);
54
+ // The shared lifecycle uses reference identity to invalidate geometry. Keep
55
+ // equal selections stable through unrelated parent/style/motion updates.
56
+ // biome-ignore lint/correctness/useExhaustiveDependencies: visibilityKey captures the exact string-array contents.
57
+ const visible = useMemo(() => candidate, [visibilityKey]);
58
+ const change = controlled
59
+ ? onVisibleSeriesChange
60
+ : (next) => {
61
+ setSelection(next);
62
+ onVisibleSeriesChange?.(next);
63
+ };
64
+ const describedBy = [
65
+ chartProps["aria-describedby"],
66
+ accessibilityLayer ? instructionId : undefined,
67
+ ]
68
+ .filter(Boolean)
69
+ .join(" ");
70
+ return (_jsxs(Root, { ...rootProps, className: ["kind-ui-configured-line-root", rootProps?.className].filter(Boolean).join(" "), config: activeConfig, visibleSeries: visible, ...(change ? { onVisibleSeriesChange: change } : {}), children: [accessibilityLayer && (_jsx("p", { id: instructionId, "data-kind-ui": "chart-instructions", children: "Focus the chart and use the arrow keys to inspect values. Press Escape to dismiss the tooltip." })), _jsx(ComposedLineChart, { ...chartProps, data: data, accessibilityLayer: accessibilityLayer, animate: animate, ...(describedBy ? { "aria-describedby": describedBy } : {}), responsive: responsive ?? true, ...(width !== undefined ? { width } : {}), ...(height !== undefined ? { height } : {}), className: ["kind-ui-configured-line-chart", className].filter(Boolean).join(" "), style: {
71
+ ...(width !== undefined ? { width } : {}),
72
+ ...(height !== undefined ? { height } : {}),
73
+ ...style,
74
+ }, children: generated ? (_jsxs(_Fragment, { children: [grid !== false && (_jsx(CartesianGrid, { vertical: false, stroke: "var(--kind-ui-chart-grid, #d1d5db)", strokeDasharray: "3 3", ...grid })), xAxis !== false && (_jsx(XAxis, { ...(xDataKey !== undefined ? { dataKey: xDataKey } : {}), axisLine: false, tickLine: false, tick: { fontSize: 12, fill: "var(--kind-ui-chart-muted-foreground, #6b7280)" }, ...xAxis })), yAxis !== false && (_jsx(YAxis, { axisLine: false, tickLine: false, tick: { fontSize: 12, fill: "var(--kind-ui-chart-muted-foreground, #6b7280)" }, ...yAxis })), series !== undefined
75
+ ? series.map((item) => (_jsx(LineSeries, { type: curve ?? "monotone", material: material ?? "plain", dot: { r: 2.5 }, strokeWidth: 2, ...item }, item.seriesKey)))
76
+ : keys.map((key) => (_jsx(LineSeries, { dataKey: key, seriesKey: key, type: curve ?? "monotone", material: material ?? "plain", dot: { r: 2.5 }, strokeWidth: 2 }, key))), tooltip !== false && _jsx(Tooltip, { ...tooltip })] })) : (children) }), legend !== false && (generated || legend !== undefined) && _jsx(Legend, { ...legend })] }));
77
+ }
@@ -0,0 +1,44 @@
1
+ import { type ComponentPropsWithRef } from "react";
2
+ /** Identity is data-owned. scope separates independent plots within one Root. */
3
+ export type EmphasisTarget = {
4
+ kind: "category" | "sector" | "series";
5
+ key: string;
6
+ scope: string;
7
+ seriesKey?: string | undefined;
8
+ };
9
+ type Candidate = {
10
+ owner: string;
11
+ target: EmphasisTarget;
12
+ } | null;
13
+ type Channel = "pointer" | "keyboard";
14
+ type EmphasisState = {
15
+ enabled: boolean;
16
+ active: EmphasisTarget | null;
17
+ register: (owner: string, target: EmphasisTarget) => () => void;
18
+ set: (channel: Channel, candidate: Candidate) => void;
19
+ clear: (channel: Channel, owner?: string, target?: EmphasisTarget) => void;
20
+ reset: () => void;
21
+ clearScope: (scope: string) => void;
22
+ };
23
+ export declare function EmphasisProvider({ enabled, children, }: {
24
+ enabled: boolean;
25
+ children: React.ReactNode;
26
+ }): import("react").JSX.Element;
27
+ export declare function useEmphasisActions(): Omit<EmphasisState, "active">;
28
+ /** Explicit contract for custom and portaled marks; apply factor to an extra paint wrapper. */
29
+ export declare function useEmphasis(target: EmphasisTarget, enabled?: boolean): {
30
+ active: EmphasisTarget | null;
31
+ dimmed: boolean;
32
+ factor: number;
33
+ enter: (channel: Channel) => void;
34
+ leave: (channel: Channel) => void;
35
+ };
36
+ export type EmphasisMarkProps = Omit<ComponentPropsWithRef<"g">, "target"> & {
37
+ target: EmphasisTarget;
38
+ enabled?: boolean | undefined;
39
+ /** Adapter input from the engine, used only during native keyboard inspection. */
40
+ keyboardActive?: boolean | undefined;
41
+ };
42
+ /** Multiplicative paint layer; native geometry, consumer opacity and hit testing stay intact. */
43
+ export declare function EmphasisMark({ target, enabled, keyboardActive, children, ...props }: EmphasisMarkProps): import("react").JSX.Element;
44
+ export {};
@@ -0,0 +1,128 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, use, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, } from "react";
4
+ const Context = createContext(null);
5
+ const Actions = createContext(null);
6
+ const identity = (target) => JSON.stringify([target.kind, target.scope, target.key]);
7
+ export function EmphasisProvider({ enabled, children, }) {
8
+ const targets = useRef(new Map());
9
+ const [pointer, setPointer] = useState(null);
10
+ const [keyboard, setKeyboard] = useState(null);
11
+ const register = useCallback((owner, target) => {
12
+ targets.current.set(owner, target);
13
+ return () => {
14
+ targets.current.delete(owner);
15
+ // Recharts can transfer a mark between portals or reorder it within this commit.
16
+ // Reconcile after all layout registrations settle, without rerendering the engine.
17
+ queueMicrotask(() => {
18
+ const retain = (candidate) => candidate === null ||
19
+ [...targets.current.values()].some((item) => identity(item) === identity(candidate.target))
20
+ ? candidate
21
+ : null;
22
+ setPointer(retain);
23
+ setKeyboard(retain);
24
+ });
25
+ };
26
+ }, []);
27
+ const set = useCallback((channel, candidate) => {
28
+ (channel === "pointer" ? setPointer : setKeyboard)((old) => old === candidate ||
29
+ (old !== null && candidate !== null && identity(old.target) === identity(candidate.target))
30
+ ? old
31
+ : candidate);
32
+ }, []);
33
+ const clear = useCallback((channel, owner, target) => {
34
+ (channel === "pointer" ? setPointer : setKeyboard)((old) => owner === undefined ||
35
+ old?.owner === owner ||
36
+ (target && old && identity(target) === identity(old.target))
37
+ ? null
38
+ : old);
39
+ }, []);
40
+ const clearScope = useCallback((scope) => {
41
+ setPointer((old) => (old?.target.scope.startsWith(`${scope}/`) ? null : old));
42
+ }, []);
43
+ const reset = useCallback(() => {
44
+ setPointer(null);
45
+ setKeyboard(null);
46
+ }, []);
47
+ const valid = (candidate) => candidate !== null &&
48
+ [...targets.current.values()].some((target) => identity(target) === identity(candidate.target));
49
+ // Validate after registrations settle: reorder may transfer the same data ID to another mark.
50
+ useEffect(() => {
51
+ if (pointer && !valid(pointer))
52
+ setPointer(null);
53
+ if (keyboard && !valid(keyboard))
54
+ setKeyboard(null);
55
+ });
56
+ const active = enabled
57
+ ? ((valid(pointer) ? pointer?.target : valid(keyboard) ? keyboard?.target : null) ?? null)
58
+ : null;
59
+ const actions = useMemo(() => ({ enabled, register, set, clear, reset, clearScope }), [enabled, register, set, clear, reset, clearScope]);
60
+ return (_jsx(Actions, { value: actions, children: _jsx(Context, { value: active, children: children }) }));
61
+ }
62
+ export function useEmphasisActions() {
63
+ const value = use(Actions);
64
+ if (!value)
65
+ throw new Error("Emphasis marks must be inside Root");
66
+ return value;
67
+ }
68
+ function useEmphasisState() {
69
+ const actions = useEmphasisActions();
70
+ const active = use(Context);
71
+ return { ...actions, active };
72
+ }
73
+ /** Explicit contract for custom and portaled marks; apply factor to an extra paint wrapper. */
74
+ export function useEmphasis(target, enabled = true) {
75
+ const state = useEmphasisState();
76
+ const owner = useId();
77
+ const { kind, key, scope, seriesKey } = target;
78
+ useLayoutEffect(() => {
79
+ if (!enabled || !state.enabled)
80
+ return;
81
+ return state.register(owner, { kind, key, scope, seriesKey });
82
+ }, [enabled, state.enabled, state.register, owner, kind, key, scope, seriesKey]);
83
+ const active = state.active;
84
+ const related = active === null ||
85
+ (active.kind === "series" ? active.key === seriesKey : identity(active) === identity(target));
86
+ const applicable = active?.kind === "series" ? seriesKey !== undefined : active?.scope === scope;
87
+ const dimmed = enabled && state.enabled && applicable && !related;
88
+ const enter = useCallback((channel) => {
89
+ if (enabled && state.enabled) {
90
+ if (channel === "keyboard")
91
+ state.clear("pointer");
92
+ state.set(channel, { owner, target: { kind, key, scope, seriesKey } });
93
+ }
94
+ }, [enabled, state.enabled, state.clear, state.set, owner, kind, key, scope, seriesKey]);
95
+ const leave = useCallback((channel) => state.clear(channel, owner, { kind, key, scope, seriesKey }), [state.clear, owner, kind, key, scope, seriesKey]);
96
+ return { active, dimmed: Boolean(dimmed), factor: dimmed ? 0.28 : 1, enter, leave };
97
+ }
98
+ /** Multiplicative paint layer; native geometry, consumer opacity and hit testing stay intact. */
99
+ export function EmphasisMark({ target, enabled = true, keyboardActive = false, children, ...props }) {
100
+ const emphasis = useEmphasis(target, enabled);
101
+ // Only depend on identity and the engine's inspection edge, not the changing paint state.
102
+ useLayoutEffect(() => {
103
+ if (keyboardActive)
104
+ emphasis.enter("keyboard");
105
+ }, [keyboardActive, emphasis.enter]);
106
+ return (_jsx("g", { ...props, "data-kind-ui": "emphasis-mark", "data-emphasis": emphasis.dimmed ? "dimmed" : "baseline", style: props.style, onPointerEnter: (event) => {
107
+ if (event.pointerType !== "touch")
108
+ emphasis.enter("pointer");
109
+ props.onPointerEnter?.(event);
110
+ }, onPointerMove: (event) => {
111
+ if (event.pointerType !== "touch")
112
+ emphasis.enter("pointer");
113
+ props.onPointerMove?.(event);
114
+ }, onPointerLeave: (event) => {
115
+ emphasis.leave("pointer");
116
+ props.onPointerLeave?.(event);
117
+ }, onPointerCancel: (event) => {
118
+ emphasis.leave("pointer");
119
+ props.onPointerCancel?.(event);
120
+ }, onFocusCapture: (event) => {
121
+ if (event.target.matches(":focus-visible"))
122
+ emphasis.enter("keyboard");
123
+ props.onFocusCapture?.(event);
124
+ }, onBlurCapture: (event) => {
125
+ emphasis.leave("keyboard");
126
+ props.onBlurCapture?.(event);
127
+ }, children: _jsx("g", { "data-kind-ui": "emphasis-paint", style: { opacity: emphasis.factor }, children: children }) }));
128
+ }
@@ -0,0 +1,38 @@
1
+ export type HeatmapDatum = {
2
+ row: string;
3
+ column: string;
4
+ value: number | null;
5
+ };
6
+ export type HeatmapDuplicatePolicy = "error" | "first" | "last" | "sum";
7
+ export type HeatmapCell = {
8
+ row: string;
9
+ column: string;
10
+ rowIndex: number;
11
+ columnIndex: number;
12
+ value: number | null;
13
+ /** All original records at this coordinate, in input order. */
14
+ sources: readonly HeatmapDatum[];
15
+ };
16
+ export type HeatmapModel = {
17
+ rows: readonly string[];
18
+ columns: readonly string[];
19
+ cells: readonly (readonly HeatmapCell[])[];
20
+ };
21
+ export type HeatmapModelOptions = {
22
+ rows: readonly string[];
23
+ columns: readonly string[];
24
+ data: readonly HeatmapDatum[];
25
+ duplicates?: HeatmapDuplicatePolicy;
26
+ };
27
+ /** Explicit ordered domains; absent and null records are missing, never coerced to zero. */
28
+ export declare function createHeatmapModel({ rows, columns, data, duplicates, }: HeatmapModelOptions): HeatmapModel;
29
+ export type HeatmapScaleOptions = {
30
+ /** Finite ascending endpoints. A constant domain uses the palette midpoint. */
31
+ domain: readonly [number, number];
32
+ /** Evenly spaced opaque sRGB stops, each in #rrggbb format. */
33
+ colors: readonly string[];
34
+ };
35
+ export type HeatmapScale = HeatmapScaleOptions & {
36
+ color: (value: number) => string;
37
+ };
38
+ export declare function createHeatmapScale({ domain, colors }: HeatmapScaleOptions): HeatmapScale;
@@ -0,0 +1,73 @@
1
+ /** Explicit ordered domains; absent and null records are missing, never coerced to zero. */
2
+ export function createHeatmapModel({ rows, columns, data, duplicates = "error", }) {
3
+ if (!["error", "first", "last", "sum"].includes(duplicates))
4
+ throw new Error("Invalid heatmap duplicate policy");
5
+ for (const [name, domain] of [
6
+ ["row", rows],
7
+ ["column", columns],
8
+ ]) {
9
+ if (domain.some((key) => typeof key !== "string") || new Set(domain).size !== domain.length)
10
+ throw new Error(`Heatmap ${name} domain must contain unique strings`);
11
+ }
12
+ const rowMap = new Map(rows.map((key, index) => [key, index]));
13
+ const columnMap = new Map(columns.map((key, index) => [key, index]));
14
+ const cells = rows.map((row, rowIndex) => columns.map((column, columnIndex) => ({
15
+ row,
16
+ column,
17
+ rowIndex,
18
+ columnIndex,
19
+ value: null,
20
+ sources: [],
21
+ })));
22
+ for (const datum of data) {
23
+ const r = rowMap.get(datum.row);
24
+ const c = columnMap.get(datum.column);
25
+ if (r === undefined || c === undefined)
26
+ throw new Error("Heatmap datum is outside its explicit domains");
27
+ if (datum.value !== null && !Number.isFinite(datum.value))
28
+ throw new Error("Heatmap values must be finite numbers or null");
29
+ const cell = cells[r]?.[c];
30
+ if (!cell)
31
+ throw new Error("Invalid heatmap coordinate");
32
+ if (cell.sources.length && duplicates === "error")
33
+ throw new Error(`Duplicate heatmap cell: ${datum.row}, ${datum.column}`);
34
+ const first = cell.sources.length === 0;
35
+ cell.sources.push(datum);
36
+ if (first || duplicates === "last")
37
+ cell.value = datum.value;
38
+ else if (duplicates === "sum" && datum.value !== null)
39
+ cell.value = (cell.value ?? 0) + datum.value;
40
+ if (cell.value !== null && !Number.isFinite(cell.value))
41
+ throw new Error("Heatmap sum must remain finite");
42
+ }
43
+ return { rows: [...rows], columns: [...columns], cells };
44
+ }
45
+ export function createHeatmapScale({ domain, colors }) {
46
+ const [min, max] = domain;
47
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min > max || !Number.isFinite(max - min))
48
+ throw new Error("Heatmap scale requires a finite ascending domain");
49
+ if (colors.length < 2 || colors.some((color) => !/^#[0-9a-f]{6}$/i.test(color)))
50
+ throw new Error("Heatmap scale requires at least two opaque #rrggbb colors");
51
+ const palette = [...colors];
52
+ const channels = palette.map((color) => [1, 3, 5].map((i) => Number.parseInt(color.slice(i, i + 2), 16)));
53
+ return {
54
+ domain: [min, max],
55
+ colors: palette,
56
+ color(value) {
57
+ if (!Number.isFinite(value))
58
+ throw new Error("Heatmap color requires a finite value");
59
+ const t = min === max ? 0.5 : Math.max(0, Math.min(1, (value - min) / (max - min)));
60
+ const p = t * (palette.length - 1);
61
+ const i = Math.min(Math.floor(p), palette.length - 2);
62
+ const start = channels[i];
63
+ const end = channels[i + 1];
64
+ if (!start || !end)
65
+ throw new Error("Invalid heatmap palette");
66
+ return `#${start
67
+ .map((v, c) => Math.round(v + ((end[c] ?? v) - v) * (p - i))
68
+ .toString(16)
69
+ .padStart(2, "0"))
70
+ .join("")}`;
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,56 @@
1
+ import { type ComponentPropsWithRef, type ComponentType, type ReactNode } from "react";
2
+ import { type HeatmapCell, type HeatmapModelOptions, type HeatmapScale } from "./heatmap-model.js";
3
+ import type { TooltipContentProps } from "./tooltip-content.js";
4
+ export type HeatmapChartProps = ComponentPropsWithRef<"div"> & HeatmapModelOptions & {
5
+ scale: HeatmapScale;
6
+ formatValue?: (value: number) => string;
7
+ missingLabel?: string;
8
+ /** Diagonal cell entrance; quantitative colors and table geometry are unchanged. */
9
+ animate?: boolean;
10
+ };
11
+ /** Native table layout owns categorical geometry. No numeric scatter approximation or SVG overlay. */
12
+ export declare function HeatmapChart({ rows, columns, data, duplicates, scale, formatValue, missingLabel, animate, children, onPointerLeave, ...props }: HeatmapChartProps): import("react").JSX.Element;
13
+ export type HeatmapCellContentProps = {
14
+ cell: HeatmapCell;
15
+ fill: string;
16
+ formattedValue: string;
17
+ };
18
+ export declare function HeatmapCellContent({ fill, formattedValue }: HeatmapCellContentProps): import("react").JSX.Element;
19
+ export type HeatmapMaterial = "plain" | "paper" | "clay" | "glow";
20
+ export type HeatmapGridProps = Omit<ComponentPropsWithRef<"table">, "children"> & {
21
+ /** Required accessible name, also rendered as a native caption. */
22
+ caption: string;
23
+ /** Optional fixed square cells, spacing and visual labels; accessible headers remain. */
24
+ layout?: {
25
+ /** Positive CSS length or finite positive pixels. Omit to retain fluid cells. */
26
+ cellSize?: number | string;
27
+ /** Nonnegative CSS length or finite nonnegative pixels. Defaults to 3px. */
28
+ gap?: number | string;
29
+ rowLabels?: "visible" | "hidden";
30
+ columnLabels?: "visible" | "hidden";
31
+ };
32
+ /** Static edge treatment; the central 84% × 84% remains the exact scale color. */
33
+ material?: HeatmapMaterial;
34
+ Cell?: ComponentType<HeatmapCellContentProps>;
35
+ /** Native cell styles, refs and handlers; grid semantics and navigation remain owned by the table. */
36
+ cellProps?: (cell: HeatmapCell) => ComponentPropsWithRef<"td">;
37
+ rowLabel?: (row: string) => ReactNode;
38
+ columnLabel?: (column: string) => ReactNode;
39
+ };
40
+ export declare function HeatmapGrid({ caption, material, layout, Cell, cellProps, rowLabel, columnLabel, onKeyDown, onBlur, onPointerLeave, style, ...props }: HeatmapGridProps): import("react").JSX.Element;
41
+ export type HeatmapTooltipProps = Omit<ComponentPropsWithRef<"div">, "children" | "id"> & {
42
+ Content?: ComponentType<HeatmapCellContentProps>;
43
+ /** Opt in for the built-in value only; Content keeps complete presentation ownership. */
44
+ valueAnimation?: TooltipContentProps["valueAnimation"];
45
+ };
46
+ /** In-flow tooltip stays within narrow containers and remains available for hover, focus and touch. */
47
+ export declare function HeatmapTooltip({ Content, valueAnimation, ...props }: HeatmapTooltipProps): import("react").JSX.Element;
48
+ export type HeatmapLegendProps = Omit<ComponentPropsWithRef<"fieldset">, "children"> & {
49
+ label: string;
50
+ };
51
+ export declare function HeatmapLegend({ label, ...props }: HeatmapLegendProps): import("react").JSX.Element;
52
+ export type HeatmapDataTableProps = Omit<ComponentPropsWithRef<"table">, "children"> & {
53
+ caption: string;
54
+ };
55
+ /** Independent static native table; the host decides whether it is visible or in a details disclosure. */
56
+ export declare function HeatmapDataTable({ caption, ...props }: HeatmapDataTableProps): import("react").JSX.Element;