@hjmds/react 1.20.0 → 1.22.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 (81) hide show
  1. package/dist/actions.d.ts +10 -0
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +9 -3
  4. package/dist/actions.js.map +1 -1
  5. package/dist/authoring-tools.d.ts +38 -0
  6. package/dist/authoring-tools.d.ts.map +1 -0
  7. package/dist/authoring-tools.js +65 -0
  8. package/dist/authoring-tools.js.map +1 -0
  9. package/dist/calendar.d.ts.map +1 -1
  10. package/dist/calendar.js +1 -0
  11. package/dist/calendar.js.map +1 -1
  12. package/dist/chart.d.ts +157 -0
  13. package/dist/chart.d.ts.map +1 -0
  14. package/dist/chart.js +467 -0
  15. package/dist/chart.js.map +1 -0
  16. package/dist/collection-editing.d.ts +66 -0
  17. package/dist/collection-editing.d.ts.map +1 -0
  18. package/dist/collection-editing.js +126 -0
  19. package/dist/collection-editing.js.map +1 -0
  20. package/dist/communication.d.ts +51 -0
  21. package/dist/communication.d.ts.map +1 -0
  22. package/dist/communication.js +71 -0
  23. package/dist/communication.js.map +1 -0
  24. package/dist/data-table.d.ts +21 -1
  25. package/dist/data-table.d.ts.map +1 -1
  26. package/dist/data-table.js +105 -10
  27. package/dist/data-table.js.map +1 -1
  28. package/dist/data-workflows.d.ts +67 -0
  29. package/dist/data-workflows.d.ts.map +1 -0
  30. package/dist/data-workflows.js +60 -0
  31. package/dist/data-workflows.js.map +1 -0
  32. package/dist/evidence.d.ts +1 -1
  33. package/dist/evidence.js +1 -1
  34. package/dist/evidence.js.map +1 -1
  35. package/dist/internal/use-composition-action.d.ts +17 -0
  36. package/dist/internal/use-composition-action.d.ts.map +1 -0
  37. package/dist/internal/use-composition-action.js +18 -0
  38. package/dist/internal/use-composition-action.js.map +1 -0
  39. package/dist/media-transport.d.ts +22 -0
  40. package/dist/media-transport.d.ts.map +1 -0
  41. package/dist/media-transport.js +50 -0
  42. package/dist/media-transport.js.map +1 -0
  43. package/dist/navigation.d.ts +3 -1
  44. package/dist/navigation.d.ts.map +1 -1
  45. package/dist/navigation.js +29 -5
  46. package/dist/navigation.js.map +1 -1
  47. package/dist/screen-flows.d.ts +3 -1
  48. package/dist/screen-flows.d.ts.map +1 -1
  49. package/dist/screen-flows.js +6 -4
  50. package/dist/screen-flows.js.map +1 -1
  51. package/dist/screens.d.ts +8 -2
  52. package/dist/screens.d.ts.map +1 -1
  53. package/dist/screens.js +11 -5
  54. package/dist/screens.js.map +1 -1
  55. package/dist/social-compositions.d.ts +52 -0
  56. package/dist/social-compositions.d.ts.map +1 -0
  57. package/dist/social-compositions.js +41 -0
  58. package/dist/social-compositions.js.map +1 -0
  59. package/dist/streaming-answer.d.ts +24 -0
  60. package/dist/streaming-answer.d.ts.map +1 -0
  61. package/dist/streaming-answer.js +57 -0
  62. package/dist/streaming-answer.js.map +1 -0
  63. package/dist/styles.css +145 -1
  64. package/dist/styles.layered.css +145 -1
  65. package/dist/table-family.d.ts +68 -0
  66. package/dist/table-family.d.ts.map +1 -0
  67. package/dist/table-family.js +75 -0
  68. package/dist/table-family.js.map +1 -0
  69. package/dist/task-controls.d.ts +47 -0
  70. package/dist/task-controls.d.ts.map +1 -0
  71. package/dist/task-controls.js +46 -0
  72. package/dist/task-controls.js.map +1 -0
  73. package/dist/toggle-feedback.d.ts +26 -0
  74. package/dist/toggle-feedback.d.ts.map +1 -0
  75. package/dist/toggle-feedback.js +47 -0
  76. package/dist/toggle-feedback.js.map +1 -0
  77. package/dist/transfer-task.d.ts +18 -0
  78. package/dist/transfer-task.d.ts.map +1 -0
  79. package/dist/transfer-task.js +56 -0
  80. package/dist/transfer-task.js.map +1 -0
  81. package/package.json +58 -3
package/dist/chart.js ADDED
@@ -0,0 +1,467 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useId, useLayoutEffect, useMemo, useRef, useState, } from "react";
3
+ import { resolveDatavizSeriesColor } from "@hjmds/design-contracts/dataviz";
4
+ import { prepareChartLineMorph, areaPath, chartRecipe, describeChart, isChartNumber, linePath, nearestChartIndex, nearestScatterPoint, observedIndices, resolveBarGeometry, resolveLineGeometry, resolvePieGeometry, resolvePieVisibility, resolveRadarGeometry, resolveScatterGeometry, scaleLinear, seriesExtremes, splitSegments, stackValues, stepChartIndex, validateChartSeries, } from "@hjmds/design-contracts/components/chart";
5
+ import { spacing } from "@hjmds/design-contracts/foundations";
6
+ import { useOptionalHjmTheme } from "./provider.js";
7
+ import { classNames } from "./internal.js";
8
+ /*
9
+ * Web chart renderers (2026-10-14). Geometry, domains, stacking, gaps and the text alternative come
10
+ * from `@hjmds/design-contracts/components/chart`; this file only paints plain SVG and translates
11
+ * pointer/keyboard input into the contract's focus index. Prop names mirror
12
+ * `@hjmds/react-native/chart` so products write one chart description for both surfaces.
13
+ * Interaction follows react-native-wagmi-charts (press-and-drag cursor, release returns to the
14
+ * latest value) with pointer events and `touch-action: pan-y` so vertical page scrolling still wins.
15
+ * Optional data morphing updates SVG hosts directly and respects reduced motion.
16
+ */
17
+ // ---------------------------------------------------------------------------------------------
18
+ // Shared helpers
19
+ // ---------------------------------------------------------------------------------------------
20
+ function kebab(value) {
21
+ return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
22
+ }
23
+ /**
24
+ * Chrome colors stay recipe references resolved through the provider's CSS variables, so a theme
25
+ * switch repaints without re-rendering and the recipe (not styles.css) owns the choice.
26
+ */
27
+ function cssColor(reference) {
28
+ const name = reference.source === "theme"
29
+ ? `--hjm-color-${kebab(reference.key)}`
30
+ : reference.source === "accent" ? `--hjm-accent-${kebab(reference.key)}` : `--hjm-accent-fill-${kebab(reference.key)}`;
31
+ return reference.alpha === undefined || reference.alpha === 1
32
+ ? `var(${name})`
33
+ : `color-mix(in srgb, var(${name}) ${Math.round(reference.alpha * 100)}%, transparent)`;
34
+ }
35
+ const chrome = chartRecipe.chrome;
36
+ const chromeStyle = {
37
+ "--hjm-chart-axis-line": cssColor(chrome.axisLine),
38
+ "--hjm-chart-axis-label": cssColor(chrome.axisLabel),
39
+ "--hjm-chart-grid-line": cssColor(chrome.gridLine),
40
+ "--hjm-chart-grid-width": `${chrome.gridWidth}px`,
41
+ "--hjm-chart-legend-label": cssColor(chrome.legendLabel),
42
+ "--hjm-chart-tooltip-surface": cssColor(chrome.tooltipSurface),
43
+ "--hjm-chart-tooltip-border": cssColor(chrome.tooltipBorder),
44
+ "--hjm-chart-tooltip-content": cssColor(chrome.tooltipContent),
45
+ };
46
+ function useSeriesColor() {
47
+ const theme = useOptionalHjmTheme()?.environment.theme ?? "light";
48
+ return useCallback((series, index) => series.color ?? resolveDatavizSeriesColor(theme, index), [theme]);
49
+ }
50
+ function useSvgId() {
51
+ return useId().replace(/[^a-zA-Z0-9_-]/g, "");
52
+ }
53
+ /** Responsive width: `fallback` renders on the server and before the first measurement. */
54
+ function useMeasuredWidth(fallback) {
55
+ const [width, setWidth] = useState(fallback);
56
+ const [node, setNode] = useState(null);
57
+ useLayoutEffect(() => {
58
+ if (!node)
59
+ return;
60
+ const update = (next) => { if (next > 0)
61
+ setWidth(Math.round(next)); };
62
+ update(node.getBoundingClientRect().width);
63
+ if (typeof ResizeObserver === "undefined")
64
+ return;
65
+ const observer = new ResizeObserver((entries) => update(entries[0]?.contentRect.width ?? 0));
66
+ observer.observe(node);
67
+ return () => observer.disconnect();
68
+ }, [node]);
69
+ return [setNode, width];
70
+ }
71
+ /** Controlled/uncontrolled focus plus a transient scrub index (same model as the Native renderer). */
72
+ function useFocus({ focusIndex, defaultFocusIndex = null, onFocusChange, holdOnRelease = false }, fallback, categories) {
73
+ const categoryKey = (index) => index === null || categories?.[index] === undefined ? null : JSON.stringify([categories[index], categories.slice(0, index).filter(value => value === categories[index]).length]);
74
+ const selectedKey = useRef(categoryKey(defaultFocusIndex));
75
+ const [own, setOwn] = useState(defaultFocusIndex);
76
+ const [scrub, setScrub] = useState(null);
77
+ const last = useRef(null);
78
+ const mapped = categories && selectedKey.current !== null ? categories.findIndex((_, index) => categoryKey(index) === selectedKey.current) : own;
79
+ const ownSelection = mapped === -1 ? null : mapped;
80
+ const settled = focusIndex !== undefined ? (categories && (focusIndex === null || categories[focusIndex] === undefined) ? null : focusIndex) : ownSelection;
81
+ const focus = scrub ?? settled ?? fallback;
82
+ const set = useCallback((index, scrubbing) => {
83
+ if (scrubbing) {
84
+ setScrub(index);
85
+ last.current = index;
86
+ }
87
+ else {
88
+ setScrub(null);
89
+ if (focusIndex === undefined) {
90
+ selectedKey.current = categoryKey(index);
91
+ setOwn(index);
92
+ }
93
+ }
94
+ onFocusChange?.(index, { scrubbing });
95
+ }, [focusIndex, onFocusChange, categories]);
96
+ const release = useCallback(() => set(holdOnRelease ? last.current : null, false), [set, holdOnRelease]);
97
+ return { focus, scrubbing: scrub !== null, set, release };
98
+ }
99
+ /** Press-and-drag; the pointer is captured so a drag that leaves the plot keeps scrubbing. */
100
+ function useScrub(pick, onScrub, onEnd) {
101
+ const active = useRef(null);
102
+ const at = (event) => {
103
+ const rect = event.currentTarget.getBoundingClientRect();
104
+ return pick(event.clientX - rect.left, event.clientY - rect.top);
105
+ };
106
+ return {
107
+ onPointerDown(event) {
108
+ if (event.button !== 0)
109
+ return;
110
+ active.current = event.pointerId;
111
+ event.currentTarget.setPointerCapture?.(event.pointerId);
112
+ onScrub(at(event));
113
+ },
114
+ onPointerMove(event) {
115
+ if (active.current !== event.pointerId)
116
+ return;
117
+ onScrub(at(event));
118
+ },
119
+ onPointerUp(event) {
120
+ if (active.current !== event.pointerId)
121
+ return;
122
+ active.current = null;
123
+ onEnd();
124
+ },
125
+ onPointerCancel(event) {
126
+ if (active.current !== event.pointerId)
127
+ return;
128
+ active.current = null;
129
+ onEnd();
130
+ },
131
+ };
132
+ }
133
+ function valueText(categories, series, index, format, missing) {
134
+ const values = series.map((item) => { const v = item.values[index]; return isChartNumber(v) ? format(v) : missing; });
135
+ return series.length === 1 ? `${categories[index]} ${values[0]}` : `${categories[index]} ${series.map((item, s) => `${item.label} ${values[s]}`).join(", ")}`;
136
+ }
137
+ /** Arrow/Home/End stepping over observed indices only; returns undefined for unrelated keys. */
138
+ function keyStep(event, available, focus, nextKeys, previousKeys) {
139
+ if (nextKeys.includes(event.key))
140
+ return stepChartIndex(available, focus, 1);
141
+ if (previousKeys.includes(event.key))
142
+ return stepChartIndex(available, focus, -1);
143
+ if (event.key === "Home")
144
+ return available[0] ?? null;
145
+ if (event.key === "End")
146
+ return available.at(-1) ?? null;
147
+ return undefined;
148
+ }
149
+ function CursorBubble({ x, width, text }) {
150
+ // Clamped like the Native bubble so the label never leaves the plot.
151
+ const half = 44, center = Math.min(width - half, Math.max(half, x));
152
+ return _jsx("span", { className: "hjm-chart__bubble", "aria-hidden": "true", style: { left: center }, children: text });
153
+ }
154
+ // Paths/rectangles update their host attributes directly; values, axes and accessibility
155
+ // always come from the latest real data. No per-frame React reconciliation.
156
+ function MotionLinePath({ points, lower, floor, curve, animate, ...rest }) {
157
+ const reducedMotion = useOptionalHjmTheme()?.environment.reducedMotion;
158
+ const node = useRef(null);
159
+ const previous = useRef(null);
160
+ const path = (p, base, edge) => base === undefined ? linePath(p, curve) : areaPath(p, base, curve, edge);
161
+ const signature = JSON.stringify([points, lower, floor, curve]);
162
+ useLayoutEffect(() => {
163
+ const before = previous.current;
164
+ const target = { points, lower, floor };
165
+ previous.current = target;
166
+ // A bounded morph avoids multiplying frame work for dense historical datasets.
167
+ const morph = animate && !reducedMotion && !document.hidden && before && points.length + before.points.length <= 1024 ? prepareChartLineMorph(before.points, points) : null;
168
+ if (!morph) {
169
+ node.current?.setAttribute("d", path(points, floor, lower));
170
+ return;
171
+ }
172
+ const lowerMorph = before?.lower && lower ? prepareChartLineMorph(before.lower, lower) : null;
173
+ let frame = 0, start;
174
+ const paint = (t) => {
175
+ const current = t === 1 ? target : { points: morph(t), lower: lowerMorph?.(t) ?? lower, floor: floor === undefined ? undefined : (before?.floor ?? floor) + (floor - (before?.floor ?? floor)) * t };
176
+ previous.current = current;
177
+ const d = path(current.points, current.floor, current.lower);
178
+ node.current?.setAttribute("d", d);
179
+ };
180
+ const tick = (now) => { start ??= now; const t = Math.min(1, (now - start) / 220); paint(1 - (1 - t) ** 3); if (t < 1)
181
+ frame = requestAnimationFrame(tick); };
182
+ paint(0);
183
+ frame = requestAnimationFrame(tick);
184
+ const visibility = () => { if (document.hidden) {
185
+ cancelAnimationFrame(frame);
186
+ paint(1);
187
+ } };
188
+ document.addEventListener("visibilitychange", visibility);
189
+ return () => { cancelAnimationFrame(frame); document.removeEventListener("visibilitychange", visibility); };
190
+ }, [signature, animate, reducedMotion]);
191
+ return _jsx("path", { ...rest, ref: node, d: path(points, floor, lower) });
192
+ }
193
+ function MotionBar({ bar, animate, vertical = true, rounded = true, ...rest }) {
194
+ const reducedMotion = useOptionalHjmTheme()?.environment.reducedMotion;
195
+ const node = useRef(null), previous = useRef(null);
196
+ const signature = JSON.stringify([bar, vertical, rounded]);
197
+ useLayoutEffect(() => {
198
+ const before = previous.current;
199
+ previous.current = bar;
200
+ if (!animate || reducedMotion || !before || document.hidden) {
201
+ node.current?.setAttribute("d", barPath(bar, vertical, bar.value < 0, rounded));
202
+ return;
203
+ }
204
+ let frame = 0, start;
205
+ const paint = (t) => {
206
+ const value = { ...bar, x: before.x + (bar.x - before.x) * t, y: before.y + (bar.y - before.y) * t, width: before.width + (bar.width - before.width) * t, height: before.height + (bar.height - before.height) * t };
207
+ previous.current = value;
208
+ node.current?.setAttribute("d", barPath(value, vertical, value.value < 0, rounded));
209
+ };
210
+ const tick = (now) => { start ??= now; const t = Math.min(1, (now - start) / 220); paint(1 - (1 - t) ** 3); if (t < 1)
211
+ frame = requestAnimationFrame(tick); };
212
+ paint(0);
213
+ frame = requestAnimationFrame(tick);
214
+ const visibility = () => { if (document.hidden) {
215
+ cancelAnimationFrame(frame);
216
+ paint(1);
217
+ } };
218
+ document.addEventListener("visibilitychange", visibility);
219
+ return () => { cancelAnimationFrame(frame); document.removeEventListener("visibilitychange", visibility); };
220
+ }, [signature, animate, reducedMotion]);
221
+ return _jsx("path", { ...rest, ref: node, d: barPath(bar, vertical, bar.value < 0, rounded) });
222
+ }
223
+ export function LineChart({ categories, series, accessibilityLabel, formatValue, missingLabel, height = chartRecipe.heights.regular, width: fallbackWidth = 320, curve = "linear", area, stacked = false, invert = false, domain, includeZero, referenceValue, extremes, showAxis = false, valueAxis = false, ticks: tickOverride, cursorLabel, animateChanges = false, scrubEmphasis, className, layoutStyle, ...focusProps }) {
224
+ validateChartSeries(categories, series);
225
+ const colorOf = useSeriesColor(), id = useSvgId(), descriptionId = `${id}-description`;
226
+ const [measure, width] = useMeasuredWidth(fallbackWidth);
227
+ const available = useMemo(() => observedIndices(series), [series]);
228
+ const { focus, scrubbing, set, release } = useFocus(focusProps, available.at(-1) ?? null, categories);
229
+ const insets = showAxis || valueAxis ? chartRecipe.insets.axis : chartRecipe.insets.plot;
230
+ const g = useMemo(() => resolveLineGeometry({
231
+ categories, series, width, height, insets, invert, stacked,
232
+ ...(domain ? { domain } : {}), ...(includeZero === undefined ? {} : { includeZero }),
233
+ }), [categories, series, width, height, insets, invert, stacked, domain, includeZero]);
234
+ // Release returns to the default (latest) focus unless holdOnRelease keeps the scrubbed point.
235
+ const handlers = useScrub((x) => nearestChartIndex(g.xs, x, available), (index) => set(index, true), release);
236
+ const showArea = area ?? series.length === 1;
237
+ const reference = referenceValue === "first" ? series[0]?.values.find(isChartNumber) : referenceValue;
238
+ const marks = extremes && !scrubbing ? seriesExtremes(series[0]?.values ?? []) : null;
239
+ const onKeyDown = (event) => {
240
+ const next = keyStep(event, available, focus, ["ArrowRight", "ArrowUp"], ["ArrowLeft", "ArrowDown"]);
241
+ if (next === undefined)
242
+ return;
243
+ event.preventDefault();
244
+ set(next, false);
245
+ };
246
+ return (_jsxs("div", { ref: measure, className: classNames("hjm-chart", "hjm-chart--line", className), style: { ...chromeStyle, ...layoutStyle }, "data-scrubbing": scrubbing || undefined, children: [_jsxs("div", { className: "hjm-chart__plot", role: "slider", tabIndex: 0, "aria-label": accessibilityLabel, "aria-describedby": descriptionId, "aria-valuemin": 0, "aria-valuemax": Math.max(0, categories.length - 1), "aria-valuenow": focus ?? 0, "aria-valuetext": focus === null ? missingLabel : valueText(categories, series, focus, formatValue, missingLabel), style: { blockSize: height }, onKeyDown: onKeyDown, ...handlers, children: [_jsxs("svg", { width: width, height: height, viewBox: `0 0 ${width} ${height}`, "aria-hidden": "true", focusable: "false", children: [_jsxs("defs", { children: [_jsx("clipPath", { id: `${id}-scrub`, children: _jsx("rect", { x: scrubEmphasis === "after" && focus !== null ? g.xs[focus] ?? g.plot.left : g.plot.left, y: 0, width: focus === null ? width : scrubEmphasis === "after" ? g.plot.right - (g.xs[focus] ?? g.plot.left) : (g.xs[focus] ?? g.plot.right) - g.plot.left, height: height }) }), series.map((item, s) => (_jsxs("linearGradient", { id: `${id}-area-${s}`, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0", stopColor: colorOf(item, s), stopOpacity: chartRecipe.area.topOpacity }), _jsx("stop", { offset: "1", stopColor: colorOf(item, s), stopOpacity: chartRecipe.area.bottomOpacity })] }, item.id)))] }), (tickOverride ?? g.ticks).map((tick) => _jsx("line", { className: "hjm-chart__grid", x1: g.plot.left, x2: g.plot.right, y1: g.y(tick), y2: g.y(tick) }, tick)), showAxis || valueAxis ? (tickOverride ?? g.ticks).map((tick) => _jsx("text", { className: "hjm-chart__label", x: g.plot.left - 6, y: g.y(tick), dominantBaseline: "middle", textAnchor: "end", children: formatValue(tick) }, `t${tick}`)) : null, isChartNumber(reference) ? (_jsx("line", { className: "hjm-chart__reference", x1: g.plot.left, x2: g.plot.right, y1: g.y(reference), y2: g.y(reference), strokeDasharray: chartRecipe.reference.dash.join(" ") })) : null, series.map((item, s) => {
247
+ const color = colorOf(item, s), segments = splitSegments(g.plots[s]);
248
+ const lower = g.lower?.[s];
249
+ return (_jsxs("g", { "data-series": item.id, children: [showArea || stacked ? segments.map((segment, i) => (_jsx(MotionLinePath, { animate: animateChanges, points: segment, floor: g.plot.bottom, curve: curve, lower: lower ? segment.map((p) => lower[p.index]) : undefined, className: "hjm-chart__area", fill: `url(#${id}-area-${s})` }, `a${i}`))) : null, segments.map((segment, i) => (_jsxs("g", { children: [_jsx(MotionLinePath, { animate: animateChanges, points: segment, curve: curve, className: "hjm-chart__line", stroke: scrubEmphasis && focus !== null ? "var(--hjm-chart-axis-label)" : color, strokeWidth: chartRecipe.line.strokeWidth, fill: "none", strokeLinejoin: "round", strokeLinecap: "round" }), scrubEmphasis && focus !== null && _jsx("g", { clipPath: `url(#${id}-scrub)`, children: _jsx(MotionLinePath, { animate: animateChanges, points: segment, curve: curve, className: "hjm-chart__line", stroke: color, strokeWidth: chartRecipe.line.strokeWidth, fill: "none", strokeLinejoin: "round", strokeLinecap: "round" }) })] }, `l${i}`))), segments.filter((segment) => segment.length === 1).map((segment) => (_jsx("circle", { cx: segment[0].x, cy: segment[0].y, r: 3, fill: color }, `d${segment[0].index}`)))] }, item.id));
250
+ }), focus !== null && g.xs[focus] !== undefined ? (_jsxs("g", { className: "hjm-chart__focus", children: [_jsx("line", { className: "hjm-chart__cursor", x1: g.xs[focus], x2: g.xs[focus], y1: g.plot.top - 4, y2: g.plot.bottom, strokeWidth: chartRecipe.cursor.strokeWidth, strokeDasharray: scrubbing ? undefined : chartRecipe.cursor.dash.join(" ") }), series.map((item, s) => {
251
+ const p = g.plots[s][focus];
252
+ if (!p)
253
+ return null;
254
+ const color = colorOf(item, s);
255
+ return (_jsxs("g", { children: [_jsx("circle", { cx: p.x, cy: p.y, r: chartRecipe.line.focusHaloRadius, fill: color, opacity: chartRecipe.line.focusHaloOpacity }), _jsx("circle", { className: "hjm-chart__dot", cx: p.x, cy: p.y, r: chartRecipe.line.pointRadius, fill: color, strokeWidth: 2 })] }, item.id));
256
+ })] })) : null, marks && extremes ? [[marks.max, extremes.high, -10], [marks.min, extremes.low, 16]]
257
+ .filter(([index], i) => i === 0 || index !== marks.max)
258
+ .map(([index, label, dy]) => {
259
+ const p = g.plots[0][index];
260
+ if (!p)
261
+ return null;
262
+ return (_jsx("text", { className: "hjm-chart__label hjm-chart__extreme", x: Math.min(g.plot.right - 4, Math.max(g.plot.left + 4, p.x)), y: Math.max(10, Math.min(height - 2, p.y + dy)), textAnchor: p.x < width * 0.2 ? "start" : p.x > width * 0.8 ? "end" : "middle", children: `${label} ${formatValue(p.value)}` }, label));
263
+ }) : null, showAxis && categories.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("text", { className: "hjm-chart__label", x: g.xs[0], y: height - 4, textAnchor: "start", children: categories[0] }), categories.length > 1 ? _jsx("text", { className: "hjm-chart__label", x: g.xs.at(-1), y: height - 4, textAnchor: "end", children: categories.at(-1) }) : null] })) : null] }), scrubbing && focus !== null && g.xs[focus] !== undefined ? _jsx(CursorBubble, { x: g.xs[focus], width: width, text: cursorLabel?.(focus) ?? categories[focus] }) : null] }), _jsx("p", { id: descriptionId, className: "hjm-visually-hidden", children: describeChart(categories, series, formatValue, missingLabel) })] }));
264
+ }
265
+ /** Rounds only the value end of a bar so stacks read as one column. */
266
+ function barPath(bar, vertical, negative, round) {
267
+ const { x, y, width: w, height: h } = bar;
268
+ const r = round ? Math.min(chartRecipe.bar.radius, vertical ? w / 2 : h / 2, vertical ? h : w) : 0;
269
+ const n = (value) => Number(value.toFixed(2));
270
+ if (r <= 0)
271
+ return `M${n(x)},${n(y)}h${n(w)}v${n(h)}h${n(-w)}Z`;
272
+ if (vertical && !negative)
273
+ return `M${n(x)},${n(y + h)}V${n(y + r)}Q${n(x)},${n(y)} ${n(x + r)},${n(y)}H${n(x + w - r)}Q${n(x + w)},${n(y)} ${n(x + w)},${n(y + r)}V${n(y + h)}Z`;
274
+ if (vertical)
275
+ return `M${n(x)},${n(y)}H${n(x + w)}V${n(y + h - r)}Q${n(x + w)},${n(y + h)} ${n(x + w - r)},${n(y + h)}H${n(x + r)}Q${n(x)},${n(y + h)} ${n(x)},${n(y + h - r)}Z`;
276
+ if (!negative)
277
+ return `M${n(x)},${n(y)}H${n(x + w - r)}Q${n(x + w)},${n(y)} ${n(x + w)},${n(y + r)}V${n(y + h - r)}Q${n(x + w)},${n(y + h)} ${n(x + w - r)},${n(y + h)}H${n(x)}Z`;
278
+ return `M${n(x + w)},${n(y)}V${n(y + h)}H${n(x + r)}Q${n(x)},${n(y + h)} ${n(x)},${n(y + h - r)}V${n(y + r)}Q${n(x)},${n(y)} ${n(x + r)},${n(y)}Z`;
279
+ }
280
+ export function BarChart({ categories, series, accessibilityLabel, formatValue, missingLabel, height, width: fallbackWidth = 320, orientation = "vertical", layout = "grouped", domain, showAxis = false, showValues = false, animateChanges = false, className, layoutStyle, ...focusProps }) {
281
+ validateChartSeries(categories, series);
282
+ const colorOf = useSeriesColor(), id = useSvgId(), descriptionId = `${id}-description`;
283
+ const [measure, width] = useMeasuredWidth(fallbackWidth);
284
+ const vertical = orientation === "vertical";
285
+ // Horizontal bars grow with the category count so labels never overlap (same rule as Native).
286
+ const resolvedHeight = height ?? (vertical ? chartRecipe.heights.regular : Math.max(chartRecipe.heights.compact, categories.length * 36 + spacing.lg));
287
+ const available = useMemo(() => observedIndices(series), [series]);
288
+ const { focus, scrubbing, set } = useFocus(focusProps, null, categories);
289
+ const insets = vertical
290
+ ? (showAxis ? chartRecipe.insets.axis : { ...chartRecipe.insets.plot, bottom: spacing.lg })
291
+ : { top: spacing.xs, right: showValues ? 48 : spacing.sm, bottom: spacing.xs, left: 72 };
292
+ const g = useMemo(() => resolveBarGeometry({ categories, series, width, height: resolvedHeight, insets, orientation, layout, ...(domain ? { domain } : {}) }), [categories, series, width, resolvedHeight, insets, orientation, layout, domain]);
293
+ const categoryKeys = useMemo(() => {
294
+ const occurrences = new Map();
295
+ return categories.map(label => { const occurrence = occurrences.get(label) ?? 0; occurrences.set(label, occurrence + 1); return JSON.stringify([label, occurrence]); });
296
+ }, [categories]);
297
+ // Only the outermost segment of each stack (per sign) gets rounded corners.
298
+ const outer = useMemo(() => {
299
+ if (layout !== "stacked")
300
+ return null;
301
+ const stacks = stackValues(series), result = new Set();
302
+ categories.forEach((_, i) => {
303
+ let positive = -1, negative = -1;
304
+ series.forEach((item, s) => { const v = item.values[i]; if (!isChartNumber(v) || !stacks[s][i])
305
+ return; if (v >= 0)
306
+ positive = s;
307
+ else
308
+ negative = s; });
309
+ if (positive >= 0)
310
+ result.add(`${positive}-${i}`);
311
+ if (negative >= 0)
312
+ result.add(`${negative}-${i}`);
313
+ });
314
+ return result;
315
+ }, [categories, series, layout]);
316
+ const handlers = useScrub((x, y) => nearestChartIndex(g.centers, vertical ? x : y, available), (index) => set(index, true), () => set(focus, false));
317
+ const onKeyDown = (event) => {
318
+ if (event.key === "Escape" && focus !== null) {
319
+ event.preventDefault();
320
+ set(null, false);
321
+ return;
322
+ }
323
+ const next = vertical
324
+ ? keyStep(event, available, focus, ["ArrowRight", "ArrowUp"], ["ArrowLeft", "ArrowDown"])
325
+ : keyStep(event, available, focus, ["ArrowDown", "ArrowRight"], ["ArrowUp", "ArrowLeft"]);
326
+ if (next === undefined)
327
+ return;
328
+ event.preventDefault();
329
+ set(next, false);
330
+ };
331
+ const totals = layout === "stacked" && showValues
332
+ ? categories.map((_, i) => series.reduce((sum, item) => sum + (isChartNumber(item.values[i]) ? item.values[i] : 0), 0))
333
+ : null;
334
+ return (_jsxs("div", { ref: measure, className: classNames("hjm-chart", "hjm-chart--bar", className), style: { ...chromeStyle, ...layoutStyle }, "data-orientation": orientation, "data-scrubbing": scrubbing || undefined, children: [_jsxs("div", { className: "hjm-chart__plot", role: "slider", tabIndex: 0, "aria-label": accessibilityLabel, "aria-describedby": descriptionId, "aria-orientation": vertical ? "horizontal" : "vertical", "aria-valuemin": 0, "aria-valuemax": Math.max(0, categories.length - 1), "aria-valuenow": focus ?? 0, "aria-valuetext": focus === null ? accessibilityLabel : valueText(categories, series, focus, formatValue, missingLabel), style: { blockSize: resolvedHeight }, onKeyDown: onKeyDown, ...handlers, children: [_jsxs("svg", { width: width, height: resolvedHeight, viewBox: `0 0 ${width} ${resolvedHeight}`, "aria-hidden": "true", focusable: "false", children: [g.ticks.map((tick) => vertical
335
+ ? _jsx("line", { className: "hjm-chart__grid", x1: g.plot.left, x2: g.plot.right, y1: g.value(tick), y2: g.value(tick) }, tick)
336
+ : _jsx("line", { className: "hjm-chart__grid", y1: g.plot.top, y2: g.plot.bottom, x1: g.value(tick), x2: g.value(tick) }, tick)), showAxis && vertical ? g.ticks.map((tick) => _jsx("text", { className: "hjm-chart__label", x: g.plot.left - 6, y: g.value(tick), dominantBaseline: "middle", textAnchor: "end", children: formatValue(tick) }, `t${tick}`)) : null, g.bars.map((bar) => (_jsx(MotionBar, { bar: bar, animate: animateChanges && g.bars.length <= 200, vertical: vertical, rounded: outer ? outer.has(`${bar.seriesIndex}-${bar.index}`) : true, className: "hjm-chart__bar", "data-index": bar.index, fill: colorOf(series[bar.seriesIndex], bar.seriesIndex), opacity: focus === null || focus === bar.index ? 1 : chartRecipe.bar.restOpacity }, `${series[bar.seriesIndex].id}-${categoryKeys[bar.index]}`))), showValues && layout === "grouped" ? g.bars.map((bar) => (_jsx("text", { className: "hjm-chart__label hjm-chart__value", x: vertical ? bar.x + bar.width / 2 : bar.x + bar.width + 4, y: vertical ? bar.y - 4 : bar.y + bar.height / 2, dominantBaseline: vertical ? undefined : "middle", textAnchor: vertical ? "middle" : "start", children: formatValue(bar.value) }, `v${bar.seriesIndex}-${bar.index}`))) : null, totals ? totals.map((total, i) => {
337
+ if (!available.includes(i))
338
+ return null;
339
+ const end = g.value(Math.max(0, ...stackValues(series).map((row) => row[i]?.[1] ?? 0)));
340
+ return (_jsx("text", { className: "hjm-chart__label hjm-chart__value", x: vertical ? g.centers[i] : end + 4, y: vertical ? end - 4 : g.centers[i], dominantBaseline: vertical ? undefined : "middle", textAnchor: vertical ? "middle" : "start", children: formatValue(total) }, `total${i}`));
341
+ }) : null, categories.map((category, i) => vertical
342
+ ? (categories.length <= 12 || i % Math.ceil(categories.length / 12) === 0)
343
+ ? _jsx("text", { className: "hjm-chart__label", "data-focused": focus === i || undefined, x: g.centers[i], y: resolvedHeight - 4, textAnchor: "middle", children: category }, `c${i}`)
344
+ : null
345
+ : _jsx("text", { className: "hjm-chart__label", "data-focused": focus === i || undefined, x: g.plot.left - 8, y: g.centers[i], dominantBaseline: "middle", textAnchor: "end", children: category }, `c${i}`))] }), scrubbing && focus !== null && vertical && g.centers[focus] !== undefined
346
+ ? _jsx(CursorBubble, { x: g.centers[focus], width: width, text: valueText(categories, series, focus, formatValue, missingLabel) })
347
+ : null] }), _jsx("p", { id: descriptionId, className: "hjm-visually-hidden", children: describeChart(categories, series, formatValue, missingLabel) })] }));
348
+ }
349
+ /** Swatch + label (+ value). Color is never the only encoding: every row carries its label. */
350
+ export function ChartLegend({ items, selectedId, onSelect, exclusion, className, layoutStyle }) {
351
+ return (_jsx("ul", { className: classNames("hjm-chart-legend", className), style: { ...chromeStyle, ...layoutStyle }, "data-interactive": onSelect ? "" : undefined, children: items.map((item) => {
352
+ const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: "hjm-chart-legend__swatch", "aria-hidden": "true", style: { background: item.color } }), _jsx("span", { className: "hjm-chart-legend__label", children: item.label }), item.value !== undefined ? _jsx("span", { className: "hjm-chart-legend__value", children: item.value }) : null] }));
353
+ const dimmed = selectedId != null && selectedId !== item.id;
354
+ return (_jsxs("li", { className: "hjm-chart-legend__item", "data-dimmed": dimmed || undefined, children: [exclusion ? _jsx("input", { type: "checkbox", checked: !exclusion.excludedIds.includes(item.id), "aria-label": exclusion.label(item, !exclusion.excludedIds.includes(item.id)), onChange: () => exclusion.onToggle(item.id) }) : null, onSelect ? (_jsx("button", { type: "button", className: "hjm-chart-legend__button", "aria-pressed": selectedId === item.id, onClick: () => onSelect(selectedId === item.id ? null : item.id), children: content })) : content] }, item.id));
355
+ }) }));
356
+ }
357
+ export function PieChart({ data, accessibilityLabel, formatValue, donut = true, size = 180, centerContent, selectedId, defaultSelectedId = null, onSelect, showLegend = true, exclusion, className, layoutStyle, }) {
358
+ const colorOf = useSeriesColor(), id = useSvgId(), descriptionId = `${id}-description`;
359
+ const [own, setOwn] = useState(defaultSelectedId);
360
+ const candidate = selectedId !== undefined ? selectedId : own;
361
+ const selected = candidate !== null && exclusion?.excludedIds.includes(candidate) ? null : candidate;
362
+ const interactive = onSelect !== undefined || selectedId !== undefined;
363
+ const select = (next) => { if (selectedId === undefined)
364
+ setOwn(next); onSelect?.(next); };
365
+ const toggle = (next) => select(selected === next ? null : next);
366
+ // The selected slice moves outward by this much, so it is reserved inside the box.
367
+ const lift = 4;
368
+ const radius = size / 2 - lift - 2, inner = donut ? radius * chartRecipe.pie.donutRatio : 0;
369
+ const visibility = resolvePieVisibility(data, exclusion?.excludedIds);
370
+ const slices = resolvePieGeometry(visibility.values, { cx: size / 2, cy: size / 2, radius, innerRadius: inner });
371
+ const center = typeof centerContent === "function" ? centerContent(visibility) : centerContent;
372
+ const legendExclusion = exclusion ? { excludedIds: exclusion.excludedIds, label: (item, included) => exclusion.label(data.find(datum => datum.id === item.id), included),
373
+ onToggle: (id) => exclusion.onExcludedIdsChange(exclusion.excludedIds.includes(id) ? exclusion.excludedIds.filter(key => key !== id) : [...exclusion.excludedIds, id]) } : undefined;
374
+ const colorAt = (index) => colorOf(data[index], index);
375
+ const percentOf = (index) => slices.find((slice) => slice.index === index)?.percent;
376
+ const percentText = (index) => { const p = percentOf(index); return p === undefined ? "" : `${Math.round(p)}%`; };
377
+ const rowValue = (d, i) => isChartNumber(d.value) ? `${formatValue(d.value)}${percentOf(i) !== undefined ? ` · ${percentText(i)}` : ""}` : "—";
378
+ const summary = data.map((d, i) => exclusion?.excludedIds.includes(d.id) ? "" : `${d.label} ${isChartNumber(d.value) ? formatValue(d.value) : ""}${percentOf(i) !== undefined ? ` (${percentText(i)})` : ""}`.trim()).filter(Boolean).join(", ");
379
+ // With a legend, its rows are the keyboard path; slices stay pointer targets so each item has
380
+ // one tab stop. Without a legend the slices themselves become the buttons.
381
+ const sliceButtons = interactive && !showLegend;
382
+ return (_jsxs("div", { className: classNames("hjm-chart", "hjm-chart--pie", className), style: { ...chromeStyle, ...layoutStyle }, children: [_jsxs("div", { className: "hjm-chart__pie", role: sliceButtons ? "group" : "img", "aria-label": accessibilityLabel, "aria-describedby": descriptionId, style: { inlineSize: size, blockSize: size }, children: [_jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": sliceButtons ? undefined : true, focusable: "false", children: [slices.map((slice) => {
383
+ const datum = data[slice.index], isSelected = selected === datum.id;
384
+ const mid = (slice.startAngle + slice.endAngle) / 2;
385
+ return (_jsx("path", { className: "hjm-chart__slice", "data-id": datum.id, "data-selected": isSelected || undefined, d: slice.path, fill: colorAt(slice.index), opacity: selected !== null && !isSelected ? chartRecipe.bar.restOpacity : 1, transform: isSelected ? `translate(${(Math.sin(mid) * lift).toFixed(2)} ${(-Math.cos(mid) * lift).toFixed(2)})` : undefined, ...(interactive ? { onClick: () => toggle(datum.id) } : {}), ...(sliceButtons ? {
386
+ role: "button",
387
+ tabIndex: 0,
388
+ "aria-pressed": isSelected,
389
+ "aria-label": `${datum.label} ${rowValue(datum, slice.index)}`,
390
+ onKeyDown: (event) => {
391
+ if (event.key !== "Enter" && event.key !== " ")
392
+ return;
393
+ event.preventDefault();
394
+ toggle(datum.id);
395
+ },
396
+ } : {}) }, datum.id));
397
+ }), !donut ? slices.filter((slice) => slice.percent >= chartRecipe.pie.minLabelPercent * 2).map((slice) => (_jsx("text", { className: "hjm-chart__slice-label", x: slice.centroid.x, y: slice.centroid.y, dominantBaseline: "middle", textAnchor: "middle", children: percentText(slice.index) }, `t${slice.index}`))) : null] }), donut && center != null ? _jsx("div", { className: "hjm-chart__pie-center", children: center }) : null] }), showLegend ? (_jsx(ChartLegend, { items: data.map((d, i) => ({ id: d.id, label: d.label, color: colorAt(i), value: rowValue(d, i) })), selectedId: selected, ...(legendExclusion ? { exclusion: legendExclusion } : {}), ...(interactive ? { onSelect: select } : {}) })) : null, _jsx("p", { id: descriptionId, className: "hjm-visually-hidden", children: summary })] }));
398
+ }
399
+ export function RadarChart({ categories, series, accessibilityLabel, formatValue, missingLabel, domain, levels = chartRecipe.radar.levels, size = 240, showLegend, className, layoutStyle, }) {
400
+ validateChartSeries(categories, series);
401
+ const colorOf = useSeriesColor(), id = useSvgId(), descriptionId = `${id}-description`;
402
+ // Room around the polygon for axis labels (same reservation as Native).
403
+ const pad = 44, center = size / 2;
404
+ const g = resolveRadarGeometry({ categories, series, cx: center, cy: center, radius: center - pad, levels, ...(domain ? { domain } : {}) });
405
+ return (_jsxs("div", { className: classNames("hjm-chart", "hjm-chart--radar", className), style: { ...chromeStyle, ...layoutStyle }, children: [_jsx("div", { className: "hjm-chart__radar", role: "img", "aria-label": accessibilityLabel, "aria-describedby": descriptionId, style: { inlineSize: size, blockSize: size }, children: _jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", focusable: "false", children: [g.rings.map((ring, i) => _jsx("path", { className: "hjm-chart__grid", d: ring, fill: "none" }, i)), g.axes.map((axis) => _jsx("line", { className: "hjm-chart__grid", x1: center, y1: center, x2: axis.end.x, y2: axis.end.y }, axis.index)), g.polygons.map((polygon) => {
406
+ const color = colorOf(series[polygon.seriesIndex], polygon.seriesIndex);
407
+ return (_jsxs("g", { "data-series": series[polygon.seriesIndex].id, children: [_jsx("path", { className: "hjm-chart__polygon", d: polygon.path, fill: color, fillOpacity: chartRecipe.radar.fillOpacity, stroke: color, strokeWidth: chartRecipe.radar.strokeWidth, strokeLinejoin: "round" }), polygon.points.map((p) => p.value !== null ? _jsx("circle", { cx: p.x, cy: p.y, r: 3, fill: color }, p.index) : null)] }, polygon.seriesIndex));
408
+ }), g.axes.map((axis) => {
409
+ const first = series[0]?.values[axis.index];
410
+ return (_jsxs("g", { children: [_jsx("text", { className: "hjm-chart__label hjm-chart__axis-name", x: axis.labelAt.x, y: axis.labelAt.y, dominantBaseline: "middle", textAnchor: axis.anchor, children: axis.label }), series.length === 1 ? (_jsx("text", { className: "hjm-chart__label", x: axis.labelAt.x, y: axis.labelAt.y + 14, dominantBaseline: "middle", textAnchor: axis.anchor, children: isChartNumber(first) ? formatValue(first) : missingLabel })) : null] }, `l${axis.index}`));
411
+ })] }) }), showLegend ?? series.length > 1 ? _jsx(ChartLegend, { items: series.map((item, s) => ({ id: item.id, label: item.label, color: colorOf(item, s) })) }) : null, _jsx("p", { id: descriptionId, className: "hjm-visually-hidden", children: describeChart(categories, series, formatValue, missingLabel) })] }));
412
+ }
413
+ export function ScatterChart({ points, accessibilityLabel, xLabel, yLabel, formatX, formatY, height = chartRecipe.heights.tall, width: fallbackWidth = 320, seriesLabels, focusId, onFocusChange, className, layoutStyle, }) {
414
+ const colorOf = useSeriesColor(), id = useSvgId(), descriptionId = `${id}-description`;
415
+ const [measure, width] = useMeasuredWidth(fallbackWidth);
416
+ const [own, setOwn] = useState(null);
417
+ const focused = focusId !== undefined ? focusId : own;
418
+ const choose = (next) => { if (focusId === undefined)
419
+ setOwn(next); onFocusChange?.(next); };
420
+ const g = useMemo(() => resolveScatterGeometry({ points, width, height }), [points, width, height]);
421
+ // Keyboard order follows the x axis so arrows read left to right.
422
+ const ordered = useMemo(() => [...points].sort((a, b) => a.x - b.x || a.y - b.y), [points]);
423
+ const handlers = useScrub((x, y) => nearestScatterPoint(g.points, x, y), (index) => { if (index !== null)
424
+ choose(points[index].id); }, () => undefined);
425
+ const focusPoint = g.points.find((p) => p.id === focused);
426
+ const sx = scaleLinear(g.xDomain, [g.plot.left, g.plot.right]), sy = scaleLinear(g.yDomain, [g.plot.bottom, g.plot.top]);
427
+ const describe = (p) => `${p.label}, ${xLabel} ${formatX(p.x)}, ${yLabel} ${formatY(p.y)}`;
428
+ const position = ordered.findIndex((p) => p.id === focused);
429
+ const onKeyDown = (event) => {
430
+ let next;
431
+ if (event.key === "ArrowRight" || event.key === "ArrowUp")
432
+ next = ordered[Math.min(ordered.length - 1, position + 1)];
433
+ else if (event.key === "ArrowLeft" || event.key === "ArrowDown")
434
+ next = ordered[position < 0 ? ordered.length - 1 : Math.max(0, position - 1)];
435
+ else if (event.key === "Home")
436
+ next = ordered[0];
437
+ else if (event.key === "End")
438
+ next = ordered.at(-1);
439
+ else if (event.key === "Escape" && focused !== null) {
440
+ event.preventDefault();
441
+ choose(null);
442
+ return;
443
+ }
444
+ else
445
+ return;
446
+ event.preventDefault();
447
+ if (next)
448
+ choose(next.id);
449
+ };
450
+ return (_jsxs("div", { ref: measure, className: classNames("hjm-chart", "hjm-chart--scatter", className), style: { ...chromeStyle, ...layoutStyle }, children: [_jsxs("div", { className: "hjm-chart__plot", role: "slider", tabIndex: 0, "aria-label": accessibilityLabel, "aria-describedby": descriptionId, "aria-valuemin": 0, "aria-valuemax": Math.max(0, points.length - 1), "aria-valuenow": Math.max(0, position), "aria-valuetext": focusPoint ? describe(focusPoint) : accessibilityLabel, style: { blockSize: height }, onKeyDown: onKeyDown, ...handlers, children: [_jsxs("svg", { width: width, height: height, viewBox: `0 0 ${width} ${height}`, "aria-hidden": "true", focusable: "false", children: [g.yTicks.map((tick) => (_jsxs("g", { children: [_jsx("line", { className: "hjm-chart__grid", x1: g.plot.left, x2: g.plot.right, y1: sy(tick), y2: sy(tick) }), _jsx("text", { className: "hjm-chart__label", x: g.plot.left - 6, y: sy(tick), dominantBaseline: "middle", textAnchor: "end", children: formatY(tick) })] }, `y${tick}`))), g.xTicks.map((tick) => _jsx("text", { className: "hjm-chart__label", x: sx(tick), y: height - 4, textAnchor: "middle", children: formatX(tick) }, `x${tick}`)), g.points.map((p) => {
451
+ const isFocused = p.id === focused;
452
+ return (_jsx("circle", { className: classNames("hjm-chart__point", isFocused && "hjm-chart__dot"), "data-id": p.id, cx: p.px, cy: p.py, r: isFocused ? chartRecipe.scatter.focusRadius : chartRecipe.scatter.radius, fill: colorOf({}, p.seriesIndex ?? 0), opacity: focused !== null && !isFocused ? 0.45 : 0.9, strokeWidth: isFocused ? 2 : 0 }, p.id));
453
+ })] }), focusPoint ? _jsx(CursorBubble, { x: focusPoint.px, width: width, text: `${focusPoint.label} · ${formatX(focusPoint.x)} / ${formatY(focusPoint.y)}` }) : null] }), _jsxs("div", { className: "hjm-chart__axis-titles", "aria-hidden": "true", children: [_jsxs("span", { children: [yLabel, " \u2191"] }), _jsxs("span", { children: [xLabel, " \u2192"] })] }), seriesLabels && seriesLabels.length > 1 ? _jsx(ChartLegend, { items: seriesLabels.map((label, i) => ({ id: String(i), label, color: colorOf({}, i) })) }) : null, _jsx("p", { id: descriptionId, className: "hjm-visually-hidden", children: points.map(describe).join(". ") })] }));
454
+ }
455
+ export function Sparkline({ values, color, invert = false, domain, width = chartRecipe.sparkline.width, height = chartRecipe.sparkline.height, accessibilityLabel, className, layoutStyle, }) {
456
+ const colorOf = useSeriesColor(), id = useSvgId();
457
+ const stroke = color ?? colorOf({}, 0);
458
+ const pad = chartRecipe.sparkline.padding;
459
+ const g = resolveLineGeometry({
460
+ categories: values.map((_, i) => String(i)), series: [{ id: "s", label: "s", values }], width, height,
461
+ insets: { top: pad, right: pad, bottom: pad, left: pad }, invert, ...(domain ? { domain } : {}),
462
+ });
463
+ const segments = splitSegments(g.plots[0]);
464
+ const labelled = accessibilityLabel !== undefined && accessibilityLabel.trim().length > 0;
465
+ return (_jsxs("svg", { className: classNames("hjm-sparkline", className), style: layoutStyle, width: width, height: height, viewBox: `0 0 ${width} ${height}`, focusable: "false", ...(labelled ? { role: "img", "aria-label": accessibilityLabel } : { "aria-hidden": true }), children: [_jsx("defs", { children: _jsxs("linearGradient", { id: `${id}-spark`, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0", stopColor: stroke, stopOpacity: 0.28 }), _jsx("stop", { offset: "1", stopColor: stroke, stopOpacity: 0 })] }) }), segments.map((segment, i) => _jsx("path", { d: areaPath(segment, height), fill: `url(#${id}-spark)` }, `a${i}`)), segments.map((segment, i) => _jsx("path", { className: "hjm-chart__line", d: linePath(segment), stroke: stroke, strokeWidth: chartRecipe.sparkline.strokeWidth, fill: "none", strokeLinejoin: "round", strokeLinecap: "round" }, `l${i}`))] }));
466
+ }
467
+ //# sourceMappingURL=chart.js.map