basalt-ui 1.14.0 → 1.16.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 (176) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +38 -2
  4. package/agent/rules/basalt-charts.md +279 -108
  5. package/agent/rules/basalt-mantine.md +4 -2
  6. package/agent/skills/basalt-charts/SKILL.md +114 -64
  7. package/agent/skills/basalt-design/SKILL.md +4 -3
  8. package/agent/templates/CLAUDE-block.md.tpl +9 -6
  9. package/configs/oxlint-plugin.js +152 -0
  10. package/configs/oxlint-plugin.test.ts +107 -0
  11. package/configs/oxlint.json +2 -0
  12. package/dist/charts/cursor/resolve.d.ts +33 -0
  13. package/dist/charts/cursor/resolve.d.ts.map +1 -0
  14. package/dist/charts/cursor/resolve.js +61 -0
  15. package/dist/charts/cursor/resolve.test.d.ts +2 -0
  16. package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
  17. package/dist/charts/cursor/scope.d.ts +17 -0
  18. package/dist/charts/cursor/scope.d.ts.map +1 -0
  19. package/dist/charts/cursor/scope.js +20 -0
  20. package/dist/charts/cursor/store.d.ts +31 -0
  21. package/dist/charts/cursor/store.d.ts.map +1 -0
  22. package/dist/charts/cursor/store.js +25 -0
  23. package/dist/charts/cursor/store.test.d.ts +2 -0
  24. package/dist/charts/cursor/store.test.d.ts.map +1 -0
  25. package/dist/charts/hooks/useChartCursor.d.ts +39 -0
  26. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
  27. package/dist/charts/hooks/useChartCursor.js +104 -0
  28. package/dist/charts/hooks/useChartSize.d.ts +8 -4
  29. package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
  30. package/dist/charts/index.d.ts +16 -13
  31. package/dist/charts/index.d.ts.map +1 -1
  32. package/dist/charts/index.js +38 -19
  33. package/dist/charts/kinds/Bars.d.ts +45 -54
  34. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  35. package/dist/charts/kinds/Bars.js +235 -392
  36. package/dist/charts/kinds/Bars.test.d.ts +2 -0
  37. package/dist/charts/kinds/Bars.test.d.ts.map +1 -0
  38. package/dist/charts/kinds/Donut.d.ts +5 -4
  39. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  40. package/dist/charts/kinds/Donut.js +17 -14
  41. package/dist/charts/kinds/DualPanel.d.ts +13 -0
  42. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  43. package/dist/charts/kinds/DualPanel.js +185 -92
  44. package/dist/charts/kinds/DualPanel.test.d.ts +2 -0
  45. package/dist/charts/kinds/DualPanel.test.d.ts.map +1 -0
  46. package/dist/charts/kinds/Heatmap.d.ts +15 -11
  47. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  48. package/dist/charts/kinds/Heatmap.js +38 -22
  49. package/dist/charts/kinds/MultiLine.d.ts +27 -35
  50. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  51. package/dist/charts/kinds/MultiLine.js +122 -235
  52. package/dist/charts/kinds/MultiLine.test.d.ts +2 -0
  53. package/dist/charts/kinds/MultiLine.test.d.ts.map +1 -0
  54. package/dist/charts/kinds/StackedArea.d.ts +21 -10
  55. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  56. package/dist/charts/kinds/StackedArea.js +74 -132
  57. package/dist/charts/kinds/ZonedLine.d.ts +24 -40
  58. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  59. package/dist/charts/kinds/ZonedLine.js +104 -184
  60. package/dist/charts/kinds/ZonedLine.test.d.ts +2 -0
  61. package/dist/charts/kinds/ZonedLine.test.d.ts.map +1 -0
  62. package/dist/charts/layout/auto-margin.d.ts +60 -0
  63. package/dist/charts/layout/auto-margin.d.ts.map +1 -0
  64. package/dist/charts/layout/auto-margin.js +37 -0
  65. package/dist/charts/layout/auto-margin.test.d.ts +2 -0
  66. package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
  67. package/dist/charts/primitives/CartesianChart.d.ts +161 -0
  68. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
  69. package/dist/charts/primitives/CartesianChart.js +359 -0
  70. package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
  71. package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
  72. package/dist/charts/primitives/ChartFrame.d.ts +16 -5
  73. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  74. package/dist/charts/primitives/ChartFrame.js +17 -2
  75. package/dist/charts/primitives/ChartLegend.d.ts +15 -1
  76. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  77. package/dist/charts/primitives/ChartLegend.js +25 -3
  78. package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
  79. package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
  80. package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
  81. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  82. package/dist/charts/primitives/ChartTooltip.js +45 -19
  83. package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
  84. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  85. package/dist/charts/primitives/HoverOverlay.js +22 -1
  86. package/dist/charts/primitives/XZoneRects.d.ts +54 -0
  87. package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
  88. package/dist/charts/primitives/XZoneRects.js +65 -0
  89. package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
  90. package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
  91. package/dist/charts/series.d.ts +28 -3
  92. package/dist/charts/series.d.ts.map +1 -1
  93. package/dist/charts/series.js +5 -2
  94. package/dist/charts/series.test.d.ts +2 -0
  95. package/dist/charts/series.test.d.ts.map +1 -0
  96. package/dist/charts/utils/measure-text.d.ts +25 -0
  97. package/dist/charts/utils/measure-text.d.ts.map +1 -0
  98. package/dist/charts/utils/measure-text.js +64 -0
  99. package/dist/charts/utils/measure-text.test.d.ts +2 -0
  100. package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
  101. package/dist/content/fence-block.d.ts.map +1 -1
  102. package/dist/surfaces.d.ts +1 -1
  103. package/dist/surfaces.d.ts.map +1 -1
  104. package/dist/surfaces.js +1 -1
  105. package/dist/tokens/chart-margin.test.d.ts +2 -0
  106. package/dist/tokens/chart-margin.test.d.ts.map +1 -0
  107. package/dist/tokens/index.d.ts +29 -0
  108. package/dist/tokens/index.d.ts.map +1 -1
  109. package/dist/tokens/index.js +10 -0
  110. package/dist/tokens/palette.d.ts +4 -0
  111. package/dist/tokens/palette.d.ts.map +1 -1
  112. package/dist/tokens/palette.js +3 -0
  113. package/llms.txt +3 -3
  114. package/package.json +1 -1
  115. package/src/charts/cursor/resolve.test.ts +61 -0
  116. package/src/charts/cursor/resolve.ts +102 -0
  117. package/src/charts/cursor/scope.tsx +32 -0
  118. package/src/charts/cursor/store.test.ts +46 -0
  119. package/src/charts/cursor/store.ts +53 -0
  120. package/src/charts/hooks/useChartCursor.ts +163 -0
  121. package/src/charts/hooks/useChartSize.ts +8 -4
  122. package/src/charts/index.ts +35 -14
  123. package/src/charts/kinds/Bars.test.tsx +98 -0
  124. package/src/charts/kinds/Bars.tsx +350 -467
  125. package/src/charts/kinds/Donut.tsx +31 -23
  126. package/src/charts/kinds/DualPanel.test.tsx +131 -0
  127. package/src/charts/kinds/DualPanel.tsx +301 -135
  128. package/src/charts/kinds/Heatmap.tsx +72 -47
  129. package/src/charts/kinds/MultiLine.test.tsx +37 -0
  130. package/src/charts/kinds/MultiLine.tsx +171 -290
  131. package/src/charts/kinds/StackedArea.tsx +116 -175
  132. package/src/charts/kinds/ZonedLine.test.tsx +37 -0
  133. package/src/charts/kinds/ZonedLine.tsx +149 -255
  134. package/src/charts/layout/auto-margin.test.ts +42 -0
  135. package/src/charts/layout/auto-margin.ts +110 -0
  136. package/src/charts/primitives/CartesianChart.test.tsx +160 -0
  137. package/src/charts/primitives/CartesianChart.tsx +598 -0
  138. package/src/charts/primitives/ChartFrame.test.tsx +33 -0
  139. package/src/charts/primitives/ChartFrame.tsx +40 -4
  140. package/src/charts/primitives/ChartLegend.test.tsx +78 -0
  141. package/src/charts/primitives/ChartLegend.tsx +52 -2
  142. package/src/charts/primitives/ChartTooltip.tsx +67 -37
  143. package/src/charts/primitives/HoverOverlay.tsx +39 -3
  144. package/src/charts/primitives/XZoneRects.test.tsx +123 -0
  145. package/src/charts/primitives/XZoneRects.tsx +132 -0
  146. package/src/charts/series.test.ts +112 -0
  147. package/src/charts/series.ts +33 -5
  148. package/src/charts/utils/measure-text.test.ts +36 -0
  149. package/src/charts/utils/measure-text.ts +109 -0
  150. package/src/content/fence-block.tsx +2 -3
  151. package/src/surfaces.ts +2 -1
  152. package/src/theme/spacing.test.ts +2 -0
  153. package/src/tokens/chart-margin.test.ts +39 -0
  154. package/src/tokens/density.test.ts +1 -0
  155. package/src/tokens/index.ts +36 -0
  156. package/src/tokens/palette.ts +3 -0
  157. package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
  158. package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
  159. package/dist/charts/hooks/useChartTooltip.js +0 -26
  160. package/dist/charts/hooks/useHoverSync.d.ts +0 -46
  161. package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
  162. package/dist/charts/hooks/useHoverSync.js +0 -75
  163. package/dist/charts/hover-context.d.ts +0 -21
  164. package/dist/charts/hover-context.d.ts.map +0 -1
  165. package/dist/charts/hover-context.js +0 -12
  166. package/dist/charts/hover-sync.d.ts +0 -14
  167. package/dist/charts/hover-sync.d.ts.map +0 -1
  168. package/dist/charts/hover-sync.js +0 -17
  169. package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
  170. package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
  171. package/dist/charts/primitives/ResponsiveChart.js +0 -24
  172. package/src/charts/hooks/useChartTooltip.ts +0 -31
  173. package/src/charts/hooks/useHoverSync.ts +0 -152
  174. package/src/charts/hover-context.ts +0 -28
  175. package/src/charts/hover-sync.tsx +0 -37
  176. package/src/charts/primitives/ResponsiveChart.tsx +0 -64
@@ -0,0 +1,359 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { GridRows } from "@visx/grid";
3
+ import { Group } from "@visx/group";
4
+ import { scaleLinear, scalePoint } from "@visx/scale";
5
+ import { useMemo, useRef, useState } from "react";
6
+ import { VX } from "../../tokens/index.js";
7
+ import { useChartCursor } from "../hooks/useChartCursor.js";
8
+ import { autoMargin, probeAxisLabels } from "../layout/auto-margin.js";
9
+ import { deriveTooltipRows } from "../series.js";
10
+ import { padAutoLower } from "../utils/domain.js";
11
+ import { fmtAxisDate } from "../utils/format.js";
12
+ import { smartTicks, smartTicksEvery } from "../utils/ticks.js";
13
+ import { AxisBottomDate, AxisLeftNumeric, AxisRightNumeric } from "./Axes.js";
14
+ import { ChartFrame, resolveLegend } from "./ChartFrame.js";
15
+ import { ChartTooltipFloat, TooltipBody, TooltipHeader, TooltipRow } from "./ChartTooltip.js";
16
+ import { Crosshair, SeriesDot } from "./Crosshair.js";
17
+ import { HoverOverlay } from "./HoverOverlay.js";
18
+ import { XZoneRects } from "./XZoneRects.js";
19
+ import { ZoneRects } from "./ZoneRects.js";
20
+ const DEFAULT_TICKS = 5;
21
+ const DEFAULT_AUTO_PAD = 1.1;
22
+ function resolveAxisDomain(cfg, data, series, extraBounds = []) {
23
+ const domain = cfg?.domain ?? "auto";
24
+ if (Array.isArray(domain)) return domain;
25
+ if (typeof domain === "function") return domain(data, series);
26
+ let min = Infinity;
27
+ let max = -Infinity;
28
+ for (const d of data) {
29
+ for (const s of series) {
30
+ const v = s.getValue(d);
31
+ if (v === null) continue;
32
+ if (v < min) min = v;
33
+ if (v > max) max = v;
34
+ }
35
+ }
36
+ for (const v of extraBounds) {
37
+ if (!Number.isFinite(v)) continue;
38
+ if (v < min) min = v;
39
+ if (v > max) max = v;
40
+ }
41
+ if (min === Infinity) return [0, cfg?.autoMaxFloor ?? 1];
42
+ const pad = cfg?.autoPad ?? DEFAULT_AUTO_PAD;
43
+ const ceil = cfg?.autoMinCeil ?? 0;
44
+ const paddedMax = max > 0 ? max * pad : max < 0 ? max / pad : 0;
45
+ const upper = Math.max(paddedMax, cfg?.autoMaxFloor ?? -Infinity);
46
+ const lower = padAutoLower(Math.min(min, ceil), pad);
47
+ if (lower === upper) return [Math.min(lower, 0), upper === 0 ? 1 : upper * pad];
48
+ return [lower, upper];
49
+ }
50
+ function CartesianChart({
51
+ data,
52
+ chartId,
53
+ getX,
54
+ series,
55
+ y,
56
+ y2,
57
+ xTicks,
58
+ formatX = fmtAxisDate,
59
+ zones,
60
+ xZones,
61
+ refLines,
62
+ height,
63
+ aspectRatio,
64
+ fill,
65
+ legend,
66
+ tooltip,
67
+ margin: marginOverride,
68
+ cursorValue,
69
+ ariaLabel,
70
+ isPending,
71
+ children
72
+ }) {
73
+ const [highlighted, setHighlighted] = useState(null);
74
+ return /* @__PURE__ */ jsx(
75
+ ChartFrame,
76
+ {
77
+ series,
78
+ chartId,
79
+ ...height !== void 0 && { height },
80
+ ...aspectRatio !== void 0 && { aspectRatio },
81
+ ...fill !== void 0 && { fill },
82
+ ...ariaLabel !== void 0 && { ariaLabel },
83
+ ...isPending !== void 0 && { isPending },
84
+ legend: resolveLegend(legend, { highlighted, onHighlight: setHighlighted }),
85
+ children: (plot) => /* @__PURE__ */ jsx(
86
+ CartesianPlot,
87
+ {
88
+ data,
89
+ chartId,
90
+ getX,
91
+ series,
92
+ ...y !== void 0 && { y },
93
+ ...y2 !== void 0 && { y2 },
94
+ ...xTicks !== void 0 && { xTicks },
95
+ formatX,
96
+ ...zones !== void 0 && { zones },
97
+ ...xZones !== void 0 && { xZones },
98
+ ...refLines !== void 0 && { refLines },
99
+ ...tooltip !== void 0 && { tooltip },
100
+ ...marginOverride !== void 0 && { marginOverride },
101
+ ...cursorValue !== void 0 && { cursorValue },
102
+ ...ariaLabel !== void 0 && { ariaLabel },
103
+ plot,
104
+ highlighted,
105
+ children
106
+ }
107
+ )
108
+ }
109
+ );
110
+ }
111
+ function CartesianPlot({
112
+ data,
113
+ chartId,
114
+ getX,
115
+ series,
116
+ y,
117
+ y2,
118
+ xTicks,
119
+ formatX = fmtAxisDate,
120
+ zones,
121
+ xZones,
122
+ refLines,
123
+ tooltip,
124
+ marginOverride,
125
+ cursorValue,
126
+ ariaLabel,
127
+ plot,
128
+ highlighted,
129
+ children
130
+ }) {
131
+ const { hidden } = plot;
132
+ const visible = useMemo(() => series.filter((s) => !hidden.has(s.key)), [series, hidden]);
133
+ const leftSeries = useMemo(() => visible.filter((s) => s.axis !== "right"), [visible]);
134
+ const rightSeries = useMemo(() => visible.filter((s) => s.axis === "right"), [visible]);
135
+ const keys = useMemo(() => data.map(getX), [data, getX]);
136
+ const [leftBounds, rightBounds] = useMemo(() => {
137
+ const left = [];
138
+ const right = [];
139
+ for (const z of zones ?? []) (z.axisSide === "right" ? right : left).push(z.from, z.to);
140
+ for (const l of refLines ?? []) (l.axis === "right" ? right : left).push(l.value);
141
+ return [left, right];
142
+ }, [zones, refLines]);
143
+ const leftDomain = useMemo(
144
+ () => resolveAxisDomain(y, data, leftSeries, leftBounds),
145
+ [y, data, leftSeries, leftBounds]
146
+ );
147
+ const rightDomain = useMemo(
148
+ () => y2 === void 0 ? null : resolveAxisDomain(y2, data, rightSeries, rightBounds),
149
+ [y2, data, rightSeries, rightBounds]
150
+ );
151
+ const leftTicks = y?.ticks ?? DEFAULT_TICKS;
152
+ const rightTicks = y2?.ticks ?? DEFAULT_TICKS;
153
+ const { labels: leftLabels, format: leftFormat } = useMemo(
154
+ () => probeAxisLabels({
155
+ domain: leftDomain,
156
+ ticks: leftTicks,
157
+ nice: y?.nice ?? false,
158
+ ...y?.format !== void 0 && { format: y.format }
159
+ }),
160
+ [leftDomain, leftTicks, y?.format, y?.nice]
161
+ );
162
+ const { labels: rightLabels, format: rightFormat } = useMemo(
163
+ () => rightDomain === null ? { labels: [], format: (v) => String(v) } : probeAxisLabels({
164
+ domain: rightDomain,
165
+ ticks: rightTicks,
166
+ nice: y2?.nice ?? false,
167
+ ...y2?.format !== void 0 && { format: y2.format }
168
+ }),
169
+ [rightDomain, rightTicks, y2?.format, y2?.nice]
170
+ );
171
+ const xLabels = useMemo(() => keys.map(formatX), [keys, formatX]);
172
+ const margin = useMemo(
173
+ () => autoMargin({
174
+ left: leftLabels,
175
+ right: rightLabels,
176
+ bottom: xLabels,
177
+ ...marginOverride !== void 0 && { override: marginOverride }
178
+ }),
179
+ [leftLabels, rightLabels, xLabels, marginOverride]
180
+ );
181
+ const xMax = Math.max(plot.width - margin.left - margin.right, 0);
182
+ const yMax = Math.max(plot.height - margin.top - margin.bottom, 0);
183
+ const xScale = useMemo(
184
+ () => scalePoint({ domain: keys, range: [0, xMax], padding: 0.5 }),
185
+ [keys, xMax]
186
+ );
187
+ const yScale = useMemo(
188
+ () => scaleLinear({
189
+ domain: leftDomain,
190
+ range: [yMax, 0],
191
+ ...y?.nice === true && { nice: true }
192
+ }),
193
+ [leftDomain, yMax, y?.nice]
194
+ );
195
+ const y2Scale = useMemo(
196
+ () => rightDomain === null ? null : scaleLinear({
197
+ domain: rightDomain,
198
+ range: [yMax, 0],
199
+ ...y2?.nice === true && { nice: true }
200
+ }),
201
+ [rightDomain, yMax, y2?.nice]
202
+ );
203
+ const tickValues = useMemo(
204
+ () => xTicks === void 0 ? smartTicks(keys, xMax) : smartTicksEvery(keys, xTicks),
205
+ [keys, xMax, xTicks]
206
+ );
207
+ const cursor = useChartCursor({
208
+ data,
209
+ chartId,
210
+ getKey: getX,
211
+ xScale,
212
+ marginLeft: margin.left
213
+ });
214
+ const svgRef = useRef(null);
215
+ const tooltipEnabled = tooltip !== false;
216
+ const tooltipCfg = tooltip === false ? void 0 : tooltip;
217
+ const point = cursor.point;
218
+ const cursorX = point === null ? null : xScale(getX(point)) ?? null;
219
+ const badge = tooltipCfg?.label === void 0 || point === null ? null : tooltipCfg.label(point);
220
+ const tooltipSeries = useMemo(
221
+ () => visible.map(
222
+ (s) => s.formatValue === void 0 ? { ...s, formatValue: s.axis === "right" ? rightFormat : leftFormat } : s
223
+ ),
224
+ [visible, leftFormat, rightFormat]
225
+ );
226
+ const rows = tooltipEnabled && point !== null ? deriveTooltipRows(tooltipSeries, point, leftFormat) : [];
227
+ const anchorX = tooltipCfg?.follow === false && point !== null && cursorX !== null ? cursorX : null;
228
+ const svgRect = anchorX === null ? void 0 : svgRef.current?.getBoundingClientRect();
229
+ const tooltipAnchor = svgRect === void 0 || anchorX === null ? cursor.anchor : { x: svgRect.left + margin.left + anchorX, y: svgRect.top + margin.top };
230
+ const ctx = {
231
+ data,
232
+ visible,
233
+ hidden,
234
+ xScale,
235
+ yScale,
236
+ y2Scale,
237
+ xMax,
238
+ yMax,
239
+ margin,
240
+ cursorPoint: point,
241
+ highlighted
242
+ };
243
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
244
+ /* @__PURE__ */ jsx("svg", { ref: svgRef, width: plot.width, height: plot.height, children: /* @__PURE__ */ jsxs(Group, { left: margin.left, top: margin.top, children: [
245
+ (y?.grid ?? true) && /* @__PURE__ */ jsx(GridRows, { scale: yScale, width: xMax, stroke: VX.grid, numTicks: leftTicks }),
246
+ xZones !== void 0 && /* @__PURE__ */ jsx(XZoneRects, { zones: xZones, height: yMax, xScale }),
247
+ zones !== void 0 && /* @__PURE__ */ jsx(ZoneRects, { zones, width: xMax, leftScale: yScale, rightScale: y2Scale }),
248
+ children(ctx),
249
+ refLines?.map((line) => {
250
+ const scale = line.axis === "right" && y2Scale !== null ? y2Scale : yScale;
251
+ return /* @__PURE__ */ jsx(
252
+ "line",
253
+ {
254
+ x1: 0,
255
+ x2: xMax,
256
+ y1: scale(line.value),
257
+ y2: scale(line.value),
258
+ stroke: line.color,
259
+ strokeWidth: 1,
260
+ strokeDasharray: line.dashed === true ? VX.dashArray : void 0
261
+ },
262
+ `${line.axis ?? "left"}-${line.value}`
263
+ );
264
+ }),
265
+ tooltipEnabled && point !== null && cursorX !== null && /* @__PURE__ */ jsxs(Fragment, { children: [
266
+ /* @__PURE__ */ jsx(Crosshair, { x: cursorX, top: 0, bottom: yMax }),
267
+ visible.map((s) => {
268
+ if (s.mark === "bar") return null;
269
+ const value = cursorValue === void 0 ? s.getValue(point) : cursorValue(point, s, visible);
270
+ if (value === null) return null;
271
+ const scale = s.axis === "right" && y2Scale !== null ? y2Scale : yScale;
272
+ const marker = s.getMarker?.(point);
273
+ return /* @__PURE__ */ jsx(
274
+ SeriesDot,
275
+ {
276
+ cx: cursorX,
277
+ cy: scale(value),
278
+ color: marker?.color ?? s.color,
279
+ ...marker?.r !== void 0 && { r: marker.r }
280
+ },
281
+ s.key
282
+ );
283
+ })
284
+ ] }),
285
+ /* @__PURE__ */ jsx(
286
+ AxisLeftNumeric,
287
+ {
288
+ scale: yScale,
289
+ numTicks: leftTicks,
290
+ tickFormat: (v) => leftFormat(Number(v))
291
+ }
292
+ ),
293
+ y2Scale !== null && /* @__PURE__ */ jsx(
294
+ AxisRightNumeric,
295
+ {
296
+ scale: y2Scale,
297
+ left: xMax,
298
+ numTicks: rightTicks,
299
+ tickFormat: (v) => rightFormat(Number(v))
300
+ }
301
+ ),
302
+ /* @__PURE__ */ jsx(
303
+ AxisBottomDate,
304
+ {
305
+ top: yMax,
306
+ scale: xScale,
307
+ tickValues,
308
+ tickFormat: (v) => formatX(String(v))
309
+ }
310
+ ),
311
+ /* @__PURE__ */ jsx(
312
+ HoverOverlay,
313
+ {
314
+ width: xMax,
315
+ height: yMax,
316
+ onMove: cursor.onPointerMove,
317
+ onLeave: cursor.onPointerLeave,
318
+ onKeyDown: cursor.onKeyDown,
319
+ onBlur: cursor.onBlur,
320
+ valueMax: Math.max(data.length - 1, 0),
321
+ ...point !== null && {
322
+ valueNow: data.indexOf(point),
323
+ valueText: formatX(getX(point))
324
+ },
325
+ ...ariaLabel !== void 0 && { ariaLabel }
326
+ }
327
+ )
328
+ ] }) }),
329
+ tooltipEnabled && cursor.isSource && point !== null && /* @__PURE__ */ jsxs(ChartTooltipFloat, { anchor: tooltipAnchor, children: [
330
+ /* @__PURE__ */ jsx(
331
+ TooltipHeader,
332
+ {
333
+ date: getX(point),
334
+ ...badge !== null && { label: badge.text, labelColor: badge.color }
335
+ }
336
+ ),
337
+ /* @__PURE__ */ jsxs(TooltipBody, { children: [
338
+ tooltipCfg?.prependRows?.(point, { visible, hidden }),
339
+ rows.map((row) => /* @__PURE__ */ jsx(
340
+ TooltipRow,
341
+ {
342
+ color: row.color,
343
+ label: row.label,
344
+ value: row.value,
345
+ shape: row.shape,
346
+ dashed: row.dashed,
347
+ ...row.strokeWidth !== void 0 && { strokeWidth: row.strokeWidth }
348
+ },
349
+ row.key
350
+ )),
351
+ tooltipCfg?.extraRows?.(point, { visible, hidden })
352
+ ] })
353
+ ] })
354
+ ] });
355
+ }
356
+ export {
357
+ CartesianChart,
358
+ resolveAxisDomain
359
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=CartesianChart.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartesianChart.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/CartesianChart.test.tsx"],"names":[],"mappings":""}
@@ -12,6 +12,13 @@ export type ChartFrameLegend = {
12
12
  /** Hover-dim wiring lifted from the kind — optional. */
13
13
  highlighted?: string | null;
14
14
  onHighlight?: (key: string | null) => void;
15
+ /**
16
+ * Clicking a legend entry hides that series. Defaults to true whenever there is more than one
17
+ * entry to toggle between — hiding the only series a chart draws is never useful. The hidden
18
+ * key set reaches the marks through the `children` render prop, so a hidden series leaves the
19
+ * plot, the tooltip, and the auto domain together (`docs/CHARTS-SPEC.md` §5).
20
+ */
21
+ toggle?: boolean;
15
22
  };
16
23
  export type ChartFrameProps = {
17
24
  /** Series identity — drives the derived legend. Pass the SAME array the kind draws + tooltips from. */
@@ -41,11 +48,15 @@ export type ChartFrameProps = {
41
48
  * kind composing `ChartFrame` should accept and forward this from its own props.
42
49
  */
43
50
  ariaLabel?: string;
44
- /** Draw the SVG marks given the plot rect that already excludes the legend band. */
45
- children: (plot: {
46
- width: number;
47
- height: number;
48
- }) => ReactNode;
51
+ /** Draw the SVG marks given the plot rect that already excludes the legend band, plus the set
52
+ * of series keys the legend has toggled off. */
53
+ children: (plot: PlotRect) => ReactNode;
54
+ };
55
+ /** What `ChartFrame` hands its child: the resolved plot rect plus legend-toggle state. */
56
+ export type PlotRect = {
57
+ width: number;
58
+ height: number;
59
+ hidden: ReadonlySet<string>;
49
60
  };
50
61
  /** A kind's own hover-dim wiring, merged into the resolved `ChartFrameLegend`. Kinds without
51
62
  * per-series highlight state (single-series `ZonedLine`, `DualPanel`) omit this. */
@@ -1 +1 @@
1
- {"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAGrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAOhF;0EAC0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC3C,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,uGAAuG;IACvG,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAA;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oFAAoF;IACpF,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAA;CACjE,CAAA;AAcD;oFACoF;AACpF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC1C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,iBAAiB,GAAG,KAAK,GAAG,SAAS,EAC7C,KAAK,CAAC,EAAE,qBAAqB,GAC5B,gBAAgB,GAAG,KAAK,CAQ1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,EACN,WAAW,EACX,IAAY,EACZ,QAA4B,EAC5B,OAAO,EACP,MAAW,EACX,SAAS,EACT,SAAiB,EACjB,QAAQ,GACT,EAAE,eAAe,GAAG,SAAS,CAmD7B"}
1
+ {"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAIrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAOhF;0EAC0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,uGAAuG;IACvG,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAA;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;oDACgD;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,SAAS,CAAA;CACxC,CAAA;AAED,0FAA0F;AAC1F,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC5B,CAAA;AAcD;oFACoF;AACpF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC1C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,iBAAiB,GAAG,KAAK,GAAG,SAAS,EAC7C,KAAK,CAAC,EAAE,qBAAqB,GAC5B,gBAAgB,GAAG,KAAK,CAS1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,EACN,WAAW,EACX,IAAY,EACZ,QAA4B,EAC5B,OAAO,EACP,MAAW,EACX,SAAS,EACT,SAAiB,EACjB,QAAQ,GACT,EAAE,eAAe,GAAG,SAAS,CAsE7B"}
@@ -1,4 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useState } from "react";
2
3
  import { useChartSize } from "../hooks/useChartSize.js";
3
4
  import { deriveLegend } from "../series.js";
4
5
  import { ChartLegend } from "./ChartLegend.js";
@@ -21,6 +22,7 @@ function resolveLegend(config, hover) {
21
22
  placement: config?.placement ?? "bottom",
22
23
  ...config?.groups !== void 0 && { groups: config.groups },
23
24
  ...config?.maxRows !== void 0 && { maxRows: config.maxRows },
25
+ ...config?.toggle !== void 0 && { toggle: config.toggle },
24
26
  ...hover !== void 0 && { highlighted: hover.highlighted, onHighlight: hover.onHighlight }
25
27
  };
26
28
  }
@@ -38,6 +40,15 @@ function ChartFrame({
38
40
  }) {
39
41
  const { ref: containerRef, width: containerW, height: containerH } = useChartSize();
40
42
  const { ref: legendRef, width: legendW, height: legendH } = useChartSize();
43
+ const [hidden, setHidden] = useState(() => /* @__PURE__ */ new Set());
44
+ const toggleKey = useCallback((key) => {
45
+ setHidden((prev) => {
46
+ const next = new Set(prev);
47
+ if (next.has(key)) next.delete(key);
48
+ else next.add(key);
49
+ return next;
50
+ });
51
+ }, []);
41
52
  const placement = legend === false ? "bottom" : legend.placement ?? "bottom";
42
53
  const vertical = placement === "left" || placement === "right";
43
54
  const legendVisible = legend !== false && !isPending;
@@ -48,11 +59,15 @@ function ChartFrame({
48
59
  width: Math.max(containerW - sideLegendWidth, minWidth),
49
60
  height: resolvedHeight - topBottomLegendHeight
50
61
  };
62
+ const legendItems = legend === false ? [] : deriveLegend(series);
63
+ const togglable = legend !== false && (legend.toggle ?? legendItems.length > 1);
51
64
  const legendNode = legend === false || isPending ? null : /* @__PURE__ */ jsx("div", { ref: legendRef, style: legendWrapperStyle(vertical), children: /* @__PURE__ */ jsx(
52
65
  ChartLegend,
53
66
  {
54
- items: deriveLegend(series),
67
+ items: legendItems,
55
68
  placement,
69
+ hidden,
70
+ ...togglable && { onToggle: toggleKey },
56
71
  ...chartId !== void 0 && { chartId },
57
72
  ...legend.groups !== void 0 && { groups: legend.groups },
58
73
  ...legend.maxRows !== void 0 && { maxRows: legend.maxRows },
@@ -69,7 +84,7 @@ function ChartFrame({
69
84
  ...isPending && { "aria-busy": "true" },
70
85
  children: [
71
86
  legendNode !== null && (placement === "top" || placement === "left") && legendNode,
72
- plot.width > 0 && plot.height > 0 && (isPending ? /* @__PURE__ */ jsx(ChartPending, { width: plot.width, height: plot.height }) : children(plot)),
87
+ plot.width > 0 && plot.height > 0 && (isPending ? /* @__PURE__ */ jsx(ChartPending, { width: plot.width, height: plot.height }) : children({ ...plot, hidden })),
73
88
  legendNode !== null && (placement === "bottom" || placement === "right") && legendNode
74
89
  ]
75
90
  }
@@ -10,11 +10,17 @@ export type LegendEntry = {
10
10
  dashed?: boolean;
11
11
  /** bar swatch opacity — honored instead of a hardcoded value, so it cannot lie about the fill. */
12
12
  fillOpacity?: number;
13
+ /** line/splitLine swatch stroke opacity — parity with `fillOpacity` above, so a dimmed
14
+ * companion line's swatch cannot lie about its own opacity either. */
15
+ strokeOpacity?: number;
13
16
  /** Drives `groups` rendering (series → hairline divider → overlay → reference). */
14
17
  role?: SeriesRole;
15
18
  /** Companions folded under this entry via `parent` (`deriveLegend`) — rendered as compact,
16
19
  * subordinate sub-entries beside the parent's label instead of vanishing. */
17
20
  children?: LegendEntry[];
21
+ /** Short qualifier rendered after the label in muted text — e.g. a flat-at-zero series that is
22
+ * invisible in the plot. */
23
+ note?: string;
18
24
  };
19
25
  /**
20
26
  * Shared legend for all non-sparkline charts. `highlighted`/`onHighlight` are optional — omit for
@@ -23,11 +29,19 @@ export type LegendEntry = {
23
29
  * reference bands instead of one flat smear. `maxRows` caps the number of rendered entries and
24
30
  * rolls the remainder into a `+N more` chip (row-accurate wrap measurement is `ChartFrame`'s job —
25
31
  * this is an entry-count cap, a deliberate simplification until that primitive lands).
32
+ *
33
+ * `onToggle` makes an entry a real toggle: clicking it hides the series in the plot, the tooltip,
34
+ * and the auto y-domain together (`ChartFrame` owns the `hidden` set — see `docs/CHARTS-SPEC.md`
35
+ * §5). Omit it for a static legend; a hidden entry is announced via `aria-pressed`.
26
36
  */
27
- export declare function ChartLegend({ items, highlighted, onHighlight, chartId, placement, groups, maxRows, }: {
37
+ export declare function ChartLegend({ items, highlighted, onHighlight, hidden, onToggle, chartId, placement, groups, maxRows, }: {
28
38
  items: LegendEntry[];
29
39
  highlighted?: string | null;
30
40
  onHighlight?: (key: string | null) => void;
41
+ /** Keys currently toggled off. */
42
+ hidden?: ReadonlySet<string>;
43
+ /** Present = entries are clickable toggles. */
44
+ onToggle?: (key: string) => void;
31
45
  chartId?: string;
32
46
  placement?: LegendPlacement;
33
47
  groups?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;CACzB,CAAA;AA+MD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,+BAkDA"}
1
+ {"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAgOD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,+BAmEA"}
@@ -19,6 +19,7 @@ const LEGEND_ITEM_BUTTON = {
19
19
  textAlign: "inherit"
20
20
  };
21
21
  const GROUP_ORDER = ["series", "overlay", "reference"];
22
+ const NO_HIDDEN = /* @__PURE__ */ new Set();
22
23
  const roleOf = (item) => item.role ?? "series";
23
24
  function orderEntries(items, groups) {
24
25
  if (!groups) return { entries: items, dividerAfter: /* @__PURE__ */ new Set() };
@@ -69,6 +70,7 @@ function LegendSwatch({ item, idPrefix }) {
69
70
  y2: 7,
70
71
  stroke: item.color,
71
72
  strokeWidth: item.strokeWidth ?? 2.5,
73
+ strokeOpacity: item.strokeOpacity ?? 1,
72
74
  strokeDasharray: item.dashed ? VX.dashArray : void 0
73
75
  }
74
76
  ),
@@ -81,6 +83,7 @@ function LegendSwatch({ item, idPrefix }) {
81
83
  y2: 7,
82
84
  stroke: item.secondColor,
83
85
  strokeWidth: item.strokeWidth ?? 2.5,
86
+ strokeOpacity: item.strokeOpacity ?? 1,
84
87
  strokeDasharray: item.dashed ? VX.dashArray : void 0
85
88
  }
86
89
  )
@@ -123,11 +126,15 @@ function LegendSwatch({ item, idPrefix }) {
123
126
  y2: 3,
124
127
  stroke: item.color,
125
128
  strokeWidth: item.strokeWidth ?? 3,
129
+ strokeOpacity: item.strokeOpacity ?? 1,
126
130
  strokeLinecap: "round",
127
131
  strokeDasharray: item.dashed ? VX.dashArray : void 0
128
132
  }
129
133
  ) });
130
134
  }
135
+ const LEGEND_NOTE_STYLE = {
136
+ opacity: 0.75
137
+ };
131
138
  const LEGEND_CHILD_STYLE = {
132
139
  display: "flex",
133
140
  alignItems: "center",
@@ -162,6 +169,7 @@ function LegendChildSwatch({ item }) {
162
169
  y2: 5,
163
170
  stroke: item.color,
164
171
  strokeWidth: item.strokeWidth ?? 2,
172
+ strokeOpacity: item.strokeOpacity ?? 1,
165
173
  strokeDasharray: item.dashed ? VX.dashArray : void 0
166
174
  }
167
175
  ) });
@@ -169,13 +177,16 @@ function LegendChildSwatch({ item }) {
169
177
  function LegendChild({ item }) {
170
178
  return /* @__PURE__ */ jsxs("span", { style: LEGEND_CHILD_STYLE, children: [
171
179
  /* @__PURE__ */ jsx(LegendChildSwatch, { item }),
172
- /* @__PURE__ */ jsx("span", { children: item.label })
180
+ /* @__PURE__ */ jsx("span", { children: item.label }),
181
+ item.note ? /* @__PURE__ */ jsx("span", { children: item.note }) : null
173
182
  ] });
174
183
  }
175
184
  function ChartLegend({
176
185
  items,
177
186
  highlighted = null,
178
187
  onHighlight,
188
+ hidden = NO_HIDDEN,
189
+ onToggle,
179
190
  chartId = "",
180
191
  placement = "bottom",
181
192
  groups = false,
@@ -186,6 +197,10 @@ function ChartLegend({
186
197
  if (key !== void 0) onHighlight?.(key);
187
198
  };
188
199
  const handleLeave = () => onHighlight?.(null);
200
+ const handleToggle = (e) => {
201
+ const key = e.currentTarget.dataset["legendKey"];
202
+ if (key !== void 0) onToggle?.(key);
203
+ };
189
204
  const idPrefix = chartId ? `${chartId}-` : "";
190
205
  const vertical = placement === "left" || placement === "right";
191
206
  const { entries, dividerAfter } = orderEntries(items, groups);
@@ -199,11 +214,17 @@ function ChartLegend({
199
214
  {
200
215
  type: "button",
201
216
  "data-legend-key": item.key,
202
- "aria-label": item.label,
217
+ "aria-label": item.note ? `${item.label} \u2014 ${item.note}` : item.label,
218
+ ...onToggle !== void 0 && { "aria-pressed": !hidden.has(item.key) },
203
219
  style: {
204
220
  ...LEGEND_ITEM_BUTTON,
205
- opacity: highlighted === null || highlighted === item.key ? 1 : 0.3
221
+ cursor: onToggle === void 0 ? "default" : "pointer",
222
+ // Toggled-off wins over hover-dimming: a hidden series must read as hidden even while
223
+ // it is the one being hovered.
224
+ opacity: hidden.has(item.key) ? 0.35 : highlighted === null || highlighted === item.key ? 1 : 0.3,
225
+ textDecoration: hidden.has(item.key) ? "line-through" : "none"
206
226
  },
227
+ ...onToggle !== void 0 && { onClick: handleToggle },
207
228
  onMouseEnter: handleEnter,
208
229
  onMouseLeave: handleLeave,
209
230
  onFocus: handleEnter,
@@ -211,6 +232,7 @@ function ChartLegend({
211
232
  children: [
212
233
  /* @__PURE__ */ jsx(LegendSwatch, { item, idPrefix }),
213
234
  /* @__PURE__ */ jsx("span", { children: item.label }),
235
+ item.note ? /* @__PURE__ */ jsx("span", { style: LEGEND_NOTE_STYLE, children: item.note }) : null,
214
236
  item.children?.map((child) => /* @__PURE__ */ jsx(LegendChild, { item: child }, child.key))
215
237
  ]
216
238
  },
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ChartLegend.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartLegend.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.test.tsx"],"names":[],"mappings":""}
@@ -1,19 +1,4 @@
1
- import type { CSSProperties, ReactNode, RefObject } from 'react';
2
- /** Theme-aware tooltip container styles — use with useChartTooltip(). */
3
- export declare function useTooltipStyles(): CSSProperties;
4
- /**
5
- * Outer tooltip shell. Renders nothing when tip is null. Renders to `document.body` via a
6
- * portal, so it may be authored anywhere in the tree, including inside `<svg>`.
7
- */
8
- export declare function ChartTooltip({ tip, tooltipRef, styles, children, }: {
9
- tip: {
10
- x: number;
11
- y: number;
12
- } | null;
13
- tooltipRef?: RefObject<HTMLDivElement | null>;
14
- styles: CSSProperties;
15
- children: ReactNode;
16
- }): import("react").ReactPortal | null;
1
+ import type { ReactNode } from 'react';
17
2
  /** Tooltip header — shows formatted date + optional right-aligned label with color. */
18
3
  export declare function TooltipHeader({ date, label, labelColor, }: {
19
4
  date: string;
@@ -34,4 +19,19 @@ export declare function TooltipRow({ color, label, value, valueColor, shape, str
34
19
  export declare function TooltipBody({ children }: {
35
20
  children: ReactNode;
36
21
  }): import("react").JSX.Element;
22
+ /**
23
+ * Floating tooltip positioned against a viewport-space anchor, with flip + clamp handled once for
24
+ * every chart instead of per call site (this used to be recomputed inside each chart's own `useChartTooltip`, and only for
25
+ * charts that remembered to use it).
26
+ *
27
+ * The box is measured after mount via `useLayoutEffect`, so the first paint of a given tooltip is
28
+ * hidden rather than misplaced — a tooltip that flashes at the wrong corner reads as a bug.
29
+ */
30
+ export declare function ChartTooltipFloat({ anchor, children, }: {
31
+ anchor: {
32
+ x: number;
33
+ y: number;
34
+ } | null;
35
+ children: ReactNode;
36
+ }): import("react").ReactPortal | null;
37
37
  //# sourceMappingURL=ChartTooltip.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAsBhE,yEAAyE;AACzE,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,GAAG,EACH,UAAU,EACV,MAAM,EACN,QAAQ,GACT,EAAE;IACD,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACpC,UAAU,CAAC,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAA;IAC7C,MAAM,EAAE,aAAa,CAAA;IACrB,QAAQ,EAAE,SAAS,CAAA;CACpB,sCAeA;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,UAAU,GACX,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,+BAoCA;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,KAAK,EACL,UAAU,EACV,KAAK,EACL,WAAW,EACX,MAAM,GACP,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,+BA0CA;AAED,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEhE"}
1
+ {"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAuBrD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,UAAU,GACX,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,+BAoCA;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,KAAK,EACL,UAAU,EACV,KAAK,EACL,WAAW,EACX,MAAM,GACP,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,+BA0CA;AAED,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEhE;AAOD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACvC,QAAQ,EAAE,SAAS,CAAA;CACpB,sCA4CA"}