@misoto22/design 0.5.0 → 0.6.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.
- package/README.md +124 -0
- package/bin/misoto22-design.mjs +205 -0
- package/dist/agent/Accordion.md +41 -0
- package/dist/agent/Alert.md +42 -0
- package/dist/agent/AppShell.md +40 -0
- package/dist/agent/AreaChart.md +68 -0
- package/dist/agent/Article.md +41 -0
- package/dist/agent/Avatar.md +32 -0
- package/dist/agent/Badge.md +40 -0
- package/dist/agent/BarChart.md +65 -0
- package/dist/agent/BarList.md +40 -0
- package/dist/agent/BigNumber.md +45 -0
- package/dist/agent/BoxPlot.md +58 -0
- package/dist/agent/Breadcrumb.md +32 -0
- package/dist/agent/BulletChart.md +48 -0
- package/dist/agent/Button.md +56 -0
- package/dist/agent/Calendar.md +42 -0
- package/dist/agent/Card.md +49 -0
- package/dist/agent/Checkbox.md +35 -0
- package/dist/agent/Collapsible.md +46 -0
- package/dist/agent/Combobox.md +50 -0
- package/dist/agent/Command.md +93 -0
- package/dist/agent/ComposedChart.md +57 -0
- package/dist/agent/ContextMenu.md +59 -0
- package/dist/agent/DatePicker.md +75 -0
- package/dist/agent/Diagram.md +46 -0
- package/dist/agent/Dialog.md +52 -0
- package/dist/agent/DropdownMenu.md +70 -0
- package/dist/agent/EmptyState.md +37 -0
- package/dist/agent/ErrorState.md +41 -0
- package/dist/agent/Facet.md +82 -0
- package/dist/agent/Field.md +37 -0
- package/dist/agent/FigureBand.md +37 -0
- package/dist/agent/FloatingIconButton.md +38 -0
- package/dist/agent/FunnelChart.md +52 -0
- package/dist/agent/Heatmap.md +53 -0
- package/dist/agent/Histogram.md +63 -0
- package/dist/agent/Input.md +30 -0
- package/dist/agent/Kbd.md +29 -0
- package/dist/agent/LineChart.md +60 -0
- package/dist/agent/LinkArrow.md +29 -0
- package/dist/agent/NativeSelect.md +40 -0
- package/dist/agent/NavItem.md +35 -0
- package/dist/agent/Pagination.md +43 -0
- package/dist/agent/PieChart.md +55 -0
- package/dist/agent/Popover.md +57 -0
- package/dist/agent/Progress.md +33 -0
- package/dist/agent/RadarChart.md +54 -0
- package/dist/agent/RadialChart.md +57 -0
- package/dist/agent/RadioGroup.md +44 -0
- package/dist/agent/SankeyChart.md +61 -0
- package/dist/agent/ScatterChart.md +54 -0
- package/dist/agent/ScrollArea.md +40 -0
- package/dist/agent/SearchableMenu.md +46 -0
- package/dist/agent/Select.md +75 -0
- package/dist/agent/Separator.md +35 -0
- package/dist/agent/Sheet.md +57 -0
- package/dist/agent/Skeleton.md +73 -0
- package/dist/agent/Slider.md +39 -0
- package/dist/agent/Sparkline.md +45 -0
- package/dist/agent/Spinner.md +39 -0
- package/dist/agent/StatusDot.md +36 -0
- package/dist/agent/StatusPill.md +28 -0
- package/dist/agent/Steps.md +44 -0
- package/dist/agent/Switch.md +29 -0
- package/dist/agent/Table.md +85 -0
- package/dist/agent/Tabs.md +48 -0
- package/dist/agent/Tag.md +35 -0
- package/dist/agent/Textarea.md +26 -0
- package/dist/agent/Toast.md +26 -0
- package/dist/agent/ToggleGroup.md +44 -0
- package/dist/agent/Tooltip.md +51 -0
- package/dist/agent/TreemapChart.md +52 -0
- package/dist/agent/WaterfallChart.md +62 -0
- package/dist/agent/catalog.json +1791 -0
- package/dist/agent/index.md +131 -0
- package/dist/charts/AreaChart/AreaChart.d.ts +191 -0
- package/dist/charts/AreaChart/AreaChart.js +480 -0
- package/dist/charts/AreaChart/AreaChart.js.map +1 -0
- package/dist/charts/BarChart/BarChart.d.ts +179 -0
- package/dist/charts/BarChart/BarChart.js +591 -0
- package/dist/charts/BarChart/BarChart.js.map +1 -0
- package/dist/charts/BarList/BarList.d.ts +57 -0
- package/dist/charts/BarList/BarList.js +71 -0
- package/dist/charts/BarList/BarList.js.map +1 -0
- package/dist/charts/BigNumber/BigNumber.d.ts +56 -0
- package/dist/charts/BigNumber/BigNumber.js +31 -0
- package/dist/charts/BigNumber/BigNumber.js.map +1 -0
- package/dist/charts/BoxPlot/BoxPlot.d.ts +177 -0
- package/dist/charts/BoxPlot/BoxPlot.js +495 -0
- package/dist/charts/BoxPlot/BoxPlot.js.map +1 -0
- package/dist/charts/BulletChart/BulletChart.d.ts +100 -0
- package/dist/charts/BulletChart/BulletChart.js +162 -0
- package/dist/charts/BulletChart/BulletChart.js.map +1 -0
- package/dist/charts/ComposedChart/ComposedChart.d.ts +171 -0
- package/dist/charts/ComposedChart/ComposedChart.js +667 -0
- package/dist/charts/ComposedChart/ComposedChart.js.map +1 -0
- package/dist/charts/Facet/Facet.d.ts +240 -0
- package/dist/charts/Facet/Facet.js +133 -0
- package/dist/charts/Facet/Facet.js.map +1 -0
- package/dist/charts/Facet/panels.d.ts +187 -0
- package/dist/charts/Facet/panels.js +203 -0
- package/dist/charts/Facet/panels.js.map +1 -0
- package/dist/charts/FunnelChart/FunnelChart.d.ts +101 -0
- package/dist/charts/FunnelChart/FunnelChart.js +147 -0
- package/dist/charts/FunnelChart/FunnelChart.js.map +1 -0
- package/dist/charts/Heatmap/Heatmap.d.ts +78 -0
- package/dist/charts/Heatmap/Heatmap.js +108 -0
- package/dist/charts/Heatmap/Heatmap.js.map +1 -0
- package/dist/charts/Histogram/Histogram.d.ts +165 -0
- package/dist/charts/Histogram/Histogram.js +350 -0
- package/dist/charts/Histogram/Histogram.js.map +1 -0
- package/dist/charts/LineChart/LineChart.d.ts +167 -0
- package/dist/charts/LineChart/LineChart.js +486 -0
- package/dist/charts/LineChart/LineChart.js.map +1 -0
- package/dist/charts/PieChart/PieChart.d.ts +128 -0
- package/dist/charts/PieChart/PieChart.js +309 -0
- package/dist/charts/PieChart/PieChart.js.map +1 -0
- package/dist/charts/RadarChart/RadarChart.d.ts +123 -0
- package/dist/charts/RadarChart/RadarChart.js +299 -0
- package/dist/charts/RadarChart/RadarChart.js.map +1 -0
- package/dist/charts/RadialChart/RadialChart.d.ts +124 -0
- package/dist/charts/RadialChart/RadialChart.js +257 -0
- package/dist/charts/RadialChart/RadialChart.js.map +1 -0
- package/dist/charts/SankeyChart/SankeyChart.d.ts +137 -0
- package/dist/charts/SankeyChart/SankeyChart.js +501 -0
- package/dist/charts/SankeyChart/SankeyChart.js.map +1 -0
- package/dist/charts/ScatterChart/ScatterChart.d.ts +128 -0
- package/dist/charts/ScatterChart/ScatterChart.js +269 -0
- package/dist/charts/ScatterChart/ScatterChart.js.map +1 -0
- package/dist/charts/Sparkline/Sparkline.d.ts +53 -0
- package/dist/charts/Sparkline/Sparkline.js +93 -0
- package/dist/charts/Sparkline/Sparkline.js.map +1 -0
- package/dist/charts/TreemapChart/TreemapChart.d.ts +93 -0
- package/dist/charts/TreemapChart/TreemapChart.js +118 -0
- package/dist/charts/TreemapChart/TreemapChart.js.map +1 -0
- package/dist/charts/WaterfallChart/WaterfallChart.d.ts +164 -0
- package/dist/charts/WaterfallChart/WaterfallChart.js +319 -0
- package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -0
- package/dist/charts/index.d.ts +41 -0
- package/dist/charts/index.js +128 -0
- package/dist/charts/index.js.map +1 -0
- package/dist/charts/lib/annotations.d.ts +103 -0
- package/dist/charts/lib/annotations.js +99 -0
- package/dist/charts/lib/annotations.js.map +1 -0
- package/dist/charts/lib/axis.d.ts +18 -0
- package/dist/charts/lib/axis.js +21 -0
- package/dist/charts/lib/axis.js.map +1 -0
- package/dist/charts/lib/background.d.ts +33 -0
- package/dist/charts/lib/background.js +173 -0
- package/dist/charts/lib/background.js.map +1 -0
- package/dist/charts/lib/brush.d.ts +94 -0
- package/dist/charts/lib/brush.js +535 -0
- package/dist/charts/lib/brush.js.map +1 -0
- package/dist/charts/lib/chart.d.ts +94 -0
- package/dist/charts/lib/chart.js +134 -0
- package/dist/charts/lib/chart.js.map +1 -0
- package/dist/charts/lib/dot.d.ts +29 -0
- package/dist/charts/lib/dot.js +78 -0
- package/dist/charts/lib/dot.js.map +1 -0
- package/dist/charts/lib/empty.d.ts +31 -0
- package/dist/charts/lib/empty.js +13 -0
- package/dist/charts/lib/empty.js.map +1 -0
- package/dist/charts/lib/export.d.ts +103 -0
- package/dist/charts/lib/export.js +204 -0
- package/dist/charts/lib/export.js.map +1 -0
- package/dist/charts/lib/figure.d.ts +61 -0
- package/dist/charts/lib/figure.js +59 -0
- package/dist/charts/lib/figure.js.map +1 -0
- package/dist/charts/lib/format.d.ts +46 -0
- package/dist/charts/lib/format.js +56 -0
- package/dist/charts/lib/format.js.map +1 -0
- package/dist/charts/lib/legend.d.ts +45 -0
- package/dist/charts/lib/legend.js +117 -0
- package/dist/charts/lib/legend.js.map +1 -0
- package/dist/charts/lib/loading.d.ts +48 -0
- package/dist/charts/lib/loading.js +105 -0
- package/dist/charts/lib/loading.js.map +1 -0
- package/dist/charts/lib/paint.d.ts +154 -0
- package/dist/charts/lib/paint.js +551 -0
- package/dist/charts/lib/paint.js.map +1 -0
- package/dist/charts/lib/sonify-control.d.ts +163 -0
- package/dist/charts/lib/sonify-control.js +172 -0
- package/dist/charts/lib/sonify-control.js.map +1 -0
- package/dist/charts/lib/sonify.d.ts +248 -0
- package/dist/charts/lib/sonify.js +262 -0
- package/dist/charts/lib/sonify.js.map +1 -0
- package/dist/charts/lib/toolbar.d.ts +143 -0
- package/dist/charts/lib/toolbar.js +156 -0
- package/dist/charts/lib/toolbar.js.map +1 -0
- package/dist/charts/lib/tooltip.d.ts +59 -0
- package/dist/charts/lib/tooltip.js +135 -0
- package/dist/charts/lib/tooltip.js.map +1 -0
- package/dist/charts/lib/values.d.ts +56 -0
- package/dist/charts/lib/values.js +71 -0
- package/dist/charts/lib/values.js.map +1 -0
- package/dist/charts/lib/zoom.d.ts +139 -0
- package/dist/charts/lib/zoom.js +314 -0
- package/dist/charts/lib/zoom.js.map +1 -0
- package/dist/semantic.css +19 -0
- package/dist/styles.css +1 -1
- package/dist/themes.css +50 -0
- package/dist/tokens.css +118 -0
- package/dist/tokens.d.ts +17 -0
- package/dist/tokens.js +101 -0
- package/dist/tokens.json +101 -0
- package/package.json +26 -4
- package/skills/misoto22-design/SKILL.md +171 -0
- package/skills/misoto22-design/evals/claims.json +95 -0
- package/skills/misoto22-design/rules/a11y.md +115 -0
- package/skills/misoto22-design/rules/composition.md +108 -0
- package/skills/misoto22-design/rules/forms.md +128 -0
- package/skills/misoto22-design/rules/naming.md +155 -0
- package/skills/misoto22-design/rules/tokens.md +131 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
startTransition,
|
|
5
|
+
useCallback,
|
|
6
|
+
useDeferredValue,
|
|
7
|
+
useEffect,
|
|
8
|
+
useId,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState
|
|
12
|
+
} from "react";
|
|
13
|
+
import { cn } from "../../lib/cn.js";
|
|
14
|
+
const ZOOM_STEP = 0.5;
|
|
15
|
+
const MIN_SPAN = 2;
|
|
16
|
+
const DRAG_THRESHOLD = 8;
|
|
17
|
+
function clamp(value, low, high) {
|
|
18
|
+
return Math.max(low, Math.min(value, high));
|
|
19
|
+
}
|
|
20
|
+
function clampWindow(range, total, minSpan = MIN_SPAN) {
|
|
21
|
+
const maxIndex = Math.max(0, total - 1);
|
|
22
|
+
if (maxIndex === 0) return { startIndex: 0, endIndex: 0 };
|
|
23
|
+
const limit = Math.min(minSpan, maxIndex);
|
|
24
|
+
let startIndex = clamp(Math.round(range.startIndex), 0, maxIndex);
|
|
25
|
+
let endIndex = clamp(Math.round(range.endIndex), 0, maxIndex);
|
|
26
|
+
if (endIndex < startIndex) [startIndex, endIndex] = [endIndex, startIndex];
|
|
27
|
+
if (endIndex - startIndex < limit) {
|
|
28
|
+
endIndex = Math.min(startIndex + limit, maxIndex);
|
|
29
|
+
startIndex = Math.max(0, endIndex - limit);
|
|
30
|
+
}
|
|
31
|
+
return { startIndex, endIndex };
|
|
32
|
+
}
|
|
33
|
+
function zoomWindow(range, total, factor, minSpan = MIN_SPAN, anchor) {
|
|
34
|
+
const maxIndex = Math.max(0, total - 1);
|
|
35
|
+
if (maxIndex === 0) return { startIndex: 0, endIndex: 0 };
|
|
36
|
+
const current = clampWindow(range, total, minSpan);
|
|
37
|
+
const span = current.endIndex - current.startIndex;
|
|
38
|
+
const limit = Math.min(minSpan, maxIndex);
|
|
39
|
+
const raw = span * factor;
|
|
40
|
+
const nextSpan = clamp(factor < 1 ? Math.floor(raw) : Math.ceil(raw), limit, maxIndex);
|
|
41
|
+
const pivot = clamp(anchor ?? (current.startIndex + current.endIndex) / 2, 0, maxIndex);
|
|
42
|
+
const ratio = span > 0 ? (pivot - current.startIndex) / span : 0.5;
|
|
43
|
+
const startIndex = clamp(Math.round(pivot - ratio * nextSpan), 0, maxIndex - nextSpan);
|
|
44
|
+
return { startIndex, endIndex: startIndex + nextSpan };
|
|
45
|
+
}
|
|
46
|
+
function panWindow(range, total, delta) {
|
|
47
|
+
const maxIndex = Math.max(0, total - 1);
|
|
48
|
+
if (maxIndex === 0) return { startIndex: 0, endIndex: 0 };
|
|
49
|
+
const current = clampWindow(range, total, 0);
|
|
50
|
+
const span = current.endIndex - current.startIndex;
|
|
51
|
+
const startIndex = clamp(current.startIndex + Math.round(delta), 0, maxIndex - span);
|
|
52
|
+
return { startIndex, endIndex: startIndex + span };
|
|
53
|
+
}
|
|
54
|
+
function useChartZoom({
|
|
55
|
+
data,
|
|
56
|
+
xDataKey,
|
|
57
|
+
minSpan = MIN_SPAN,
|
|
58
|
+
step = ZOOM_STEP,
|
|
59
|
+
onZoomChange
|
|
60
|
+
}) {
|
|
61
|
+
const total = data.length;
|
|
62
|
+
const maxIndex = Math.max(0, total - 1);
|
|
63
|
+
const [range, setRange] = useState({ startIndex: 0, endIndex: maxIndex });
|
|
64
|
+
const committed = useRef(range);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const whole = { startIndex: 0, endIndex: Math.max(0, data.length - 1) };
|
|
67
|
+
committed.current = whole;
|
|
68
|
+
setRange(whole);
|
|
69
|
+
}, [data.length]);
|
|
70
|
+
const commit = useCallback(
|
|
71
|
+
(next) => {
|
|
72
|
+
const clamped = clampWindow(next, total, minSpan);
|
|
73
|
+
const last = committed.current;
|
|
74
|
+
if (last.startIndex === clamped.startIndex && last.endIndex === clamped.endIndex) return;
|
|
75
|
+
committed.current = clamped;
|
|
76
|
+
setRange(clamped);
|
|
77
|
+
startTransition(() => onZoomChange?.(clamped));
|
|
78
|
+
},
|
|
79
|
+
[minSpan, onZoomChange, total]
|
|
80
|
+
);
|
|
81
|
+
const zoomIn = useCallback(
|
|
82
|
+
(anchor) => commit(zoomWindow(committed.current, total, step, minSpan, anchor)),
|
|
83
|
+
[commit, minSpan, step, total]
|
|
84
|
+
);
|
|
85
|
+
const zoomOut = useCallback(
|
|
86
|
+
(anchor) => commit(zoomWindow(committed.current, total, 1 / step, minSpan, anchor)),
|
|
87
|
+
[commit, minSpan, step, total]
|
|
88
|
+
);
|
|
89
|
+
const reset = useCallback(
|
|
90
|
+
() => commit({ startIndex: 0, endIndex: Math.max(0, total - 1) }),
|
|
91
|
+
[commit, total]
|
|
92
|
+
);
|
|
93
|
+
const panBy = useCallback(
|
|
94
|
+
(delta) => commit(panWindow(committed.current, total, delta)),
|
|
95
|
+
[commit, total]
|
|
96
|
+
);
|
|
97
|
+
const zoomTo = useCallback((next) => commit(next), [commit]);
|
|
98
|
+
const labelAt = useCallback(
|
|
99
|
+
(index) => {
|
|
100
|
+
if (!xDataKey) return String(index + 1);
|
|
101
|
+
const value = data[index]?.[xDataKey];
|
|
102
|
+
return value === null || value === void 0 ? String(index + 1) : String(value);
|
|
103
|
+
},
|
|
104
|
+
[data, xDataKey]
|
|
105
|
+
);
|
|
106
|
+
const deferred = useDeferredValue(range);
|
|
107
|
+
const visibleData = useMemo(
|
|
108
|
+
() => data.slice(deferred.startIndex, deferred.endIndex + 1),
|
|
109
|
+
[data, deferred.endIndex, deferred.startIndex]
|
|
110
|
+
);
|
|
111
|
+
const brushProps = useMemo(
|
|
112
|
+
() => ({ startIndex: range.startIndex, endIndex: range.endIndex, onChange: commit }),
|
|
113
|
+
[commit, range.endIndex, range.startIndex]
|
|
114
|
+
);
|
|
115
|
+
const isZoomed = range.startIndex > 0 || range.endIndex < maxIndex;
|
|
116
|
+
return useMemo(
|
|
117
|
+
() => ({
|
|
118
|
+
range,
|
|
119
|
+
visibleData,
|
|
120
|
+
isZoomed,
|
|
121
|
+
canZoomIn: range.endIndex - range.startIndex > Math.min(minSpan, maxIndex),
|
|
122
|
+
canZoomOut: isZoomed,
|
|
123
|
+
zoomIn,
|
|
124
|
+
zoomOut,
|
|
125
|
+
reset,
|
|
126
|
+
panBy,
|
|
127
|
+
zoomTo,
|
|
128
|
+
total,
|
|
129
|
+
labelAt,
|
|
130
|
+
brushProps
|
|
131
|
+
}),
|
|
132
|
+
[
|
|
133
|
+
brushProps,
|
|
134
|
+
isZoomed,
|
|
135
|
+
labelAt,
|
|
136
|
+
maxIndex,
|
|
137
|
+
minSpan,
|
|
138
|
+
panBy,
|
|
139
|
+
range,
|
|
140
|
+
reset,
|
|
141
|
+
total,
|
|
142
|
+
visibleData,
|
|
143
|
+
zoomIn,
|
|
144
|
+
zoomOut,
|
|
145
|
+
zoomTo
|
|
146
|
+
]
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
function ChartZoomSurface({
|
|
150
|
+
zoom,
|
|
151
|
+
label,
|
|
152
|
+
enabled = true,
|
|
153
|
+
ref,
|
|
154
|
+
children
|
|
155
|
+
}) {
|
|
156
|
+
const fallbackRef = useRef(null);
|
|
157
|
+
const surfaceRef = ref ?? fallbackRef;
|
|
158
|
+
const hintId = `chart-zoom-hint-${useId().replace(/:/g, "")}`;
|
|
159
|
+
const drag = useRef(null);
|
|
160
|
+
const [selection, setSelection] = useState(null);
|
|
161
|
+
const indexAt = useCallback(
|
|
162
|
+
(clientX) => {
|
|
163
|
+
const node = surfaceRef.current;
|
|
164
|
+
const box = node ? plotBox(node) : null;
|
|
165
|
+
if (!box) return void 0;
|
|
166
|
+
const fraction = clamp((clientX - box.left) / box.width, 0, 1);
|
|
167
|
+
return zoom.range.startIndex + fraction * (zoom.range.endIndex - zoom.range.startIndex);
|
|
168
|
+
},
|
|
169
|
+
[surfaceRef, zoom.range.endIndex, zoom.range.startIndex]
|
|
170
|
+
);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
const node = surfaceRef.current;
|
|
173
|
+
if (!node || !enabled) return;
|
|
174
|
+
const onWheel = (event) => {
|
|
175
|
+
if (!event.ctrlKey && !event.metaKey) return;
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
const anchor = indexAt(event.clientX);
|
|
178
|
+
if (event.deltaY < 0) zoom.zoomIn(anchor);
|
|
179
|
+
else zoom.zoomOut(anchor);
|
|
180
|
+
};
|
|
181
|
+
node.addEventListener("wheel", onWheel, { passive: false });
|
|
182
|
+
return () => node.removeEventListener("wheel", onWheel);
|
|
183
|
+
}, [enabled, indexAt, surfaceRef, zoom]);
|
|
184
|
+
const onPointerDown = (event) => {
|
|
185
|
+
if (!enabled || event.button !== 0) return;
|
|
186
|
+
drag.current = { pointerId: event.pointerId, originX: event.clientX };
|
|
187
|
+
};
|
|
188
|
+
const onPointerMove = (event) => {
|
|
189
|
+
const state = drag.current;
|
|
190
|
+
if (!state || state.pointerId !== event.pointerId) return;
|
|
191
|
+
const travelled = Math.abs(event.clientX - state.originX);
|
|
192
|
+
if (travelled < DRAG_THRESHOLD) return;
|
|
193
|
+
const node = surfaceRef.current;
|
|
194
|
+
const box = node ? plotBox(node) : null;
|
|
195
|
+
if (!node || !box) return;
|
|
196
|
+
if (!node.hasPointerCapture(event.pointerId)) node.setPointerCapture(event.pointerId);
|
|
197
|
+
const from = Math.min(state.originX, event.clientX);
|
|
198
|
+
setSelection({ start: from - box.left, width: travelled });
|
|
199
|
+
};
|
|
200
|
+
const endDrag = (event) => {
|
|
201
|
+
const state = drag.current;
|
|
202
|
+
drag.current = null;
|
|
203
|
+
setSelection(null);
|
|
204
|
+
const node = surfaceRef.current;
|
|
205
|
+
if (node?.hasPointerCapture(event.pointerId)) node.releasePointerCapture(event.pointerId);
|
|
206
|
+
if (!state || Math.abs(event.clientX - state.originX) < DRAG_THRESHOLD) return;
|
|
207
|
+
const from = indexAt(Math.min(state.originX, event.clientX));
|
|
208
|
+
const to = indexAt(Math.max(state.originX, event.clientX));
|
|
209
|
+
if (from === void 0 || to === void 0) return;
|
|
210
|
+
zoom.zoomTo({ startIndex: Math.floor(from), endIndex: Math.ceil(to) });
|
|
211
|
+
};
|
|
212
|
+
const onKeyDown = (event) => {
|
|
213
|
+
if (!enabled) return;
|
|
214
|
+
const span = zoom.range.endIndex - zoom.range.startIndex;
|
|
215
|
+
switch (event.key) {
|
|
216
|
+
case "ArrowLeft":
|
|
217
|
+
case "ArrowDown":
|
|
218
|
+
zoom.panBy(-1);
|
|
219
|
+
break;
|
|
220
|
+
case "ArrowRight":
|
|
221
|
+
case "ArrowUp":
|
|
222
|
+
zoom.panBy(1);
|
|
223
|
+
break;
|
|
224
|
+
case "PageDown":
|
|
225
|
+
zoom.panBy(-Math.max(1, span));
|
|
226
|
+
break;
|
|
227
|
+
case "PageUp":
|
|
228
|
+
zoom.panBy(Math.max(1, span));
|
|
229
|
+
break;
|
|
230
|
+
case "Home":
|
|
231
|
+
zoom.panBy(-zoom.total);
|
|
232
|
+
break;
|
|
233
|
+
case "End":
|
|
234
|
+
zoom.panBy(zoom.total);
|
|
235
|
+
break;
|
|
236
|
+
case "+":
|
|
237
|
+
case "=":
|
|
238
|
+
zoom.zoomIn();
|
|
239
|
+
break;
|
|
240
|
+
case "-":
|
|
241
|
+
case "_":
|
|
242
|
+
zoom.zoomOut();
|
|
243
|
+
break;
|
|
244
|
+
case "0":
|
|
245
|
+
zoom.reset();
|
|
246
|
+
break;
|
|
247
|
+
default:
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
};
|
|
252
|
+
const shown = zoom.range.endIndex - zoom.range.startIndex + 1;
|
|
253
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 w-full flex-1 flex-col", children: [
|
|
254
|
+
/* @__PURE__ */ jsxs(
|
|
255
|
+
"div",
|
|
256
|
+
{
|
|
257
|
+
ref: surfaceRef,
|
|
258
|
+
role: "group",
|
|
259
|
+
"aria-label": `${label} \u2014 plot`,
|
|
260
|
+
"aria-describedby": enabled ? hintId : void 0,
|
|
261
|
+
tabIndex: enabled ? 0 : void 0,
|
|
262
|
+
onKeyDown,
|
|
263
|
+
onPointerDown,
|
|
264
|
+
onPointerMove,
|
|
265
|
+
onPointerUp: endDrag,
|
|
266
|
+
onPointerCancel: endDrag,
|
|
267
|
+
className: cn(
|
|
268
|
+
"relative flex min-h-0 w-full flex-1 flex-col rounded-(--radius-sm)",
|
|
269
|
+
// Vertical scrolling still belongs to the page; horizontal movement
|
|
270
|
+
// is the pan gesture, so the browser must not claim it first.
|
|
271
|
+
enabled && "touch-pan-y",
|
|
272
|
+
selection && "select-none"
|
|
273
|
+
),
|
|
274
|
+
children: [
|
|
275
|
+
children,
|
|
276
|
+
selection && // Positioned with inline style rather than `start-`/`end-` classes,
|
|
277
|
+
// for the same reason the brush is: the axis being selected along is
|
|
278
|
+
// the DATA's, and a cartesian plot runs left-to-right under
|
|
279
|
+
// `dir="rtl"` too.
|
|
280
|
+
/* @__PURE__ */ jsx(
|
|
281
|
+
"div",
|
|
282
|
+
{
|
|
283
|
+
"aria-hidden": true,
|
|
284
|
+
className: "pointer-events-none absolute inset-y-0 z-(--z-rule) border-x border-(--rule-2) bg-(--ink)/8",
|
|
285
|
+
style: { left: selection.start, width: selection.width }
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
),
|
|
291
|
+
enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
292
|
+
/* @__PURE__ */ jsx("p", { id: hintId, className: "sr-only", children: "Arrow keys pan by one point, Page Up and Page Down by a screenful, Home and End jump to the ends. Plus and minus zoom, zero resets. Ctrl and the wheel zoom around the pointer, and dragging across the plot zooms to that span." }),
|
|
293
|
+
/* @__PURE__ */ jsx("p", { "aria-live": "polite", className: "sr-only", children: `Showing ${zoom.labelAt(zoom.range.startIndex)} to ${zoom.labelAt(
|
|
294
|
+
zoom.range.endIndex
|
|
295
|
+
)}, ${shown} of ${zoom.total} points` })
|
|
296
|
+
] })
|
|
297
|
+
] });
|
|
298
|
+
}
|
|
299
|
+
function plotBox(surface) {
|
|
300
|
+
const grid = surface.querySelector(".recharts-cartesian-grid");
|
|
301
|
+
const gridBox = grid?.getBoundingClientRect();
|
|
302
|
+
if (gridBox && gridBox.width > 0) return gridBox;
|
|
303
|
+
const svg = surface.querySelector("svg.recharts-surface");
|
|
304
|
+
const svgBox = svg?.getBoundingClientRect();
|
|
305
|
+
return svgBox && svgBox.width > 0 ? svgBox : null;
|
|
306
|
+
}
|
|
307
|
+
export {
|
|
308
|
+
ChartZoomSurface,
|
|
309
|
+
clampWindow,
|
|
310
|
+
panWindow,
|
|
311
|
+
useChartZoom,
|
|
312
|
+
zoomWindow
|
|
313
|
+
};
|
|
314
|
+
//# sourceMappingURL=zoom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/charts/lib/zoom.tsx"],"sourcesContent":["'use client'\n\nimport {\n startTransition,\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n type RefObject,\n} from 'react'\nimport { cn } from '../../lib/cn'\nimport type { ChartBrushRange } from './brush'\n\n/**\n * How far a single zoom step closes the window. Half is the step every map and\n * every chart toolbar has trained readers to expect from a `+`.\n */\nconst ZOOM_STEP = 0.5\n\n/** The fewest rows a zoom will leave on screen — the brush's floor, so the two agree. */\nconst MIN_SPAN = 2\n\n/**\n * How far the pointer has to travel before a press counts as a region\n * selection. Below this it is a click on the plot, which belongs to the tooltip\n * and to a series' own `isClickable`.\n */\nconst DRAG_THRESHOLD = 8\n\nfunction clamp(value: number, low: number, high: number): number {\n return Math.max(low, Math.min(value, high))\n}\n\n/**\n * A range pulled back inside the data, with the minimum span enforced.\n *\n * Every other function here ends by calling this, so an inverted range, an\n * index past the end and a window narrower than `minSpan` all have exactly one\n * definition of what they become.\n */\nexport function clampWindow(\n range: ChartBrushRange,\n total: number,\n minSpan: number = MIN_SPAN,\n): ChartBrushRange {\n const maxIndex = Math.max(0, total - 1)\n if (maxIndex === 0) return { startIndex: 0, endIndex: 0 }\n\n const limit = Math.min(minSpan, maxIndex)\n let startIndex = clamp(Math.round(range.startIndex), 0, maxIndex)\n let endIndex = clamp(Math.round(range.endIndex), 0, maxIndex)\n if (endIndex < startIndex) [startIndex, endIndex] = [endIndex, startIndex]\n\n if (endIndex - startIndex < limit) {\n endIndex = Math.min(startIndex + limit, maxIndex)\n startIndex = Math.max(0, endIndex - limit)\n }\n\n return { startIndex, endIndex }\n}\n\n/**\n * The window after one zoom step.\n *\n * `factor` below 1 closes the window and above 1 opens it. `anchor` is the row\n * index that must stay where it is on screen — the pointer's row for a wheel\n * zoom, the window's own centre for a button — because a zoom that re-centres\n * is a zoom that loses the point the reader was looking at.\n *\n * The span is floored when closing and ceiled when opening rather than rounded\n * in both directions, so a step always moves: rounding leaves a three-row\n * window stuck at three when the step is a half.\n */\nexport function zoomWindow(\n range: ChartBrushRange,\n total: number,\n factor: number,\n minSpan: number = MIN_SPAN,\n anchor?: number,\n): ChartBrushRange {\n const maxIndex = Math.max(0, total - 1)\n if (maxIndex === 0) return { startIndex: 0, endIndex: 0 }\n\n const current = clampWindow(range, total, minSpan)\n const span = current.endIndex - current.startIndex\n const limit = Math.min(minSpan, maxIndex)\n const raw = span * factor\n const nextSpan = clamp(factor < 1 ? Math.floor(raw) : Math.ceil(raw), limit, maxIndex)\n\n const pivot = clamp(anchor ?? (current.startIndex + current.endIndex) / 2, 0, maxIndex)\n // Where the pivot sits across the window, 0 at the start and 1 at the end.\n // Holding that fraction is what keeps the anchored row under the pointer.\n const ratio = span > 0 ? (pivot - current.startIndex) / span : 0.5\n const startIndex = clamp(Math.round(pivot - ratio * nextSpan), 0, maxIndex - nextSpan)\n\n return { startIndex, endIndex: startIndex + nextSpan }\n}\n\n/**\n * The window slid along by `delta` rows, keeping its span.\n *\n * At either end it stops rather than shrinking. A pan that narrowed itself\n * against the edge would change the scale under the reader, which is a\n * different gesture from the one they made.\n */\nexport function panWindow(range: ChartBrushRange, total: number, delta: number): ChartBrushRange {\n const maxIndex = Math.max(0, total - 1)\n if (maxIndex === 0) return { startIndex: 0, endIndex: 0 }\n\n const current = clampWindow(range, total, 0)\n const span = current.endIndex - current.startIndex\n const startIndex = clamp(current.startIndex + Math.round(delta), 0, maxIndex - span)\n\n return { startIndex, endIndex: startIndex + span }\n}\n\nexport interface ChartZoomOptions<TData extends Record<string, unknown>> {\n /** The whole dataset. The window is expressed as indexes into it. */\n data: TData[]\n /** The row field behind a row's name, used by the spoken window description. */\n xDataKey?: keyof TData & string\n /** The fewest rows a zoom will leave on screen. */\n minSpan?: number\n /** The fraction of the window one zoom step keeps. */\n step?: number\n /** Fires whenever the window changes, from any of the four inputs. */\n onZoomChange?: (range: ChartBrushRange) => void\n}\n\nexport interface ChartZoom<TData extends Record<string, unknown> = Record<string, unknown>> {\n /** The rows on screen, as indexes into the whole dataset. */\n range: ChartBrushRange\n /** The rows the plot should draw. */\n visibleData: TData[]\n /** Whether anything is hidden — what \"reset zoom\" is enabled by. */\n isZoomed: boolean\n canZoomIn: boolean\n canZoomOut: boolean\n /** Closes the window one step around `anchor`, or around its own centre. */\n zoomIn: (anchor?: number) => void\n /** Opens the window one step around `anchor`, or around its own centre. */\n zoomOut: (anchor?: number) => void\n /** Back to the whole dataset. */\n reset: () => void\n /** Slides the window by a number of rows, keeping its span. */\n panBy: (delta: number) => void\n /** Jumps straight to a window — what a drag across the plot commits. */\n zoomTo: (range: ChartBrushRange) => void\n /** How many rows there are in total. */\n total: number\n /** A row's name, for an announcement or a label. */\n labelAt: (index: number) => string\n /** Spread onto `<ChartBrush>` to make it a controlled view of this window. */\n brushProps: {\n startIndex: number\n endIndex: number\n onChange: (range: ChartBrushRange) => void\n }\n}\n\n/**\n * A windowed view of a chart's rows, driven by the toolbar, the wheel, a drag\n * across the plot, and the keyboard.\n *\n * ## How this composes with `<Chart.Brush>`\n *\n * They are two views of ONE window, not two windows. This hook owns\n * `{ startIndex, endIndex }`; the brush is rendered controlled from it through\n * `brushProps` and reports back through the same commit path the toolbar\n * buttons use. So dragging a brush handle moves the toolbar's idea of the\n * window, \"reset zoom\" snaps the handles back to the full range, and neither\n * part has to know the other exists.\n *\n * The alternative — a zoom window layered on top of a brushed slice — was\n * rejected because it needs a composition rule (\"zoom is relative to the\n * brush\") that nobody can predict from the call site, and because it makes\n * reset ambiguous: reset the zoom, the brush, or both.\n *\n * This is a superset of `useChartBrush`, so a chart that can carry a toolbar\n * uses it in place of that hook rather than running the two side by side.\n *\n * @example\n * const zoom = useChartZoom({ data, xDataKey: 'month' })\n * <ChartBrush data={data} config={config} {...zoom.brushProps} />\n */\nexport function useChartZoom<TData extends Record<string, unknown>>({\n data,\n xDataKey,\n minSpan = MIN_SPAN,\n step = ZOOM_STEP,\n onZoomChange,\n}: ChartZoomOptions<TData>): ChartZoom<TData> {\n const total = data.length\n const maxIndex = Math.max(0, total - 1)\n\n const [range, setRange] = useState<ChartBrushRange>({ startIndex: 0, endIndex: maxIndex })\n\n // The committed window, mirrored in a ref so the commands below can stay\n // stable across renders. The wheel listener is attached natively and would\n // otherwise be torn down and re-attached on every step.\n const committed = useRef(range)\n\n useEffect(() => {\n const whole = { startIndex: 0, endIndex: Math.max(0, data.length - 1) }\n committed.current = whole\n setRange(whole)\n }, [data.length])\n\n const commit = useCallback(\n (next: ChartBrushRange) => {\n const clamped = clampWindow(next, total, minSpan)\n const last = committed.current\n if (last.startIndex === clamped.startIndex && last.endIndex === clamped.endIndex) return\n\n committed.current = clamped\n setRange(clamped)\n // The control that moved has to keep up with the pointer; the plot does\n // not. Deferring lets React drop intermediate windows during a drag.\n startTransition(() => onZoomChange?.(clamped))\n },\n [minSpan, onZoomChange, total],\n )\n\n const zoomIn = useCallback(\n (anchor?: number) => commit(zoomWindow(committed.current, total, step, minSpan, anchor)),\n [commit, minSpan, step, total],\n )\n\n const zoomOut = useCallback(\n (anchor?: number) => commit(zoomWindow(committed.current, total, 1 / step, minSpan, anchor)),\n [commit, minSpan, step, total],\n )\n\n const reset = useCallback(\n () => commit({ startIndex: 0, endIndex: Math.max(0, total - 1) }),\n [commit, total],\n )\n\n const panBy = useCallback(\n (delta: number) => commit(panWindow(committed.current, total, delta)),\n [commit, total],\n )\n\n const zoomTo = useCallback((next: ChartBrushRange) => commit(next), [commit])\n\n const labelAt = useCallback(\n (index: number) => {\n if (!xDataKey) return String(index + 1)\n const value = data[index]?.[xDataKey]\n return value === null || value === undefined ? String(index + 1) : String(value)\n },\n [data, xDataKey],\n )\n\n // The slice runs on a deferred copy of the window, so the control that moved\n // re-renders at pointer cadence and the plot at whatever it can manage.\n const deferred = useDeferredValue(range)\n const visibleData = useMemo(\n () => data.slice(deferred.startIndex, deferred.endIndex + 1),\n [data, deferred.endIndex, deferred.startIndex],\n )\n\n const brushProps = useMemo(\n () => ({ startIndex: range.startIndex, endIndex: range.endIndex, onChange: commit }),\n [commit, range.endIndex, range.startIndex],\n )\n\n const isZoomed = range.startIndex > 0 || range.endIndex < maxIndex\n\n return useMemo(\n () => ({\n range,\n visibleData,\n isZoomed,\n canZoomIn: range.endIndex - range.startIndex > Math.min(minSpan, maxIndex),\n canZoomOut: isZoomed,\n zoomIn,\n zoomOut,\n reset,\n panBy,\n zoomTo,\n total,\n labelAt,\n brushProps,\n }),\n [\n brushProps,\n isZoomed,\n labelAt,\n maxIndex,\n minSpan,\n panBy,\n range,\n reset,\n total,\n visibleData,\n zoomIn,\n zoomOut,\n zoomTo,\n ],\n )\n}\n\nexport interface ChartZoomSurfaceProps {\n /** The window this surface drives. */\n zoom: ChartZoom\n /** Names the region for a screen reader — pass the figure's title. */\n label: string\n /**\n * Off leaves the plot inert while the toolbar buttons keep working. Use it\n * for a chart whose only control is export.\n */\n enabled?: boolean\n /** The plot's box, handed out because the PNG export needs the measured one. */\n ref?: RefObject<HTMLDivElement | null>\n children: ReactNode\n}\n\n/**\n * The plot, made zoomable and pannable by pointer AND by keyboard.\n *\n * The keyboard half is not a nicety. A chart whose only zoom is a drag gesture\n * is a chart a keyboard user can watch and cannot operate, which is the exact\n * failure the brush was rewritten to fix — so the surface is focusable, carries\n * its instructions in an `aria-describedby` note, and announces the window it\n * moved to through a polite live region.\n *\n * Arrow keys pan by a row, `PageUp`/`PageDown` by a screenful, `Home`/`End`\n * jump to the ends, `+`/`-` zoom and `0` resets. `ArrowLeft` means EARLIER in\n * every writing direction, matching the brush: the rendering engine lays a\n * cartesian plot out left-to-right whatever `dir` says, so mirroring the keys\n * would put \"earlier\" on the side the data does not run to.\n *\n * The wheel is deliberately gated behind Ctrl or Cmd. A chart that swallowed a\n * plain wheel event traps the page's scroll under the pointer, which is the\n * single most complained-about behaviour in an embedded map.\n */\nexport function ChartZoomSurface({\n zoom,\n label,\n enabled = true,\n ref,\n children,\n}: ChartZoomSurfaceProps) {\n const fallbackRef = useRef<HTMLDivElement>(null)\n const surfaceRef = ref ?? fallbackRef\n const hintId = `chart-zoom-hint-${useId().replace(/:/g, '')}`\n\n const drag = useRef<{ pointerId: number; originX: number } | null>(null)\n const [selection, setSelection] = useState<{ start: number; width: number } | null>(null)\n\n /** The row index under a client x, read from the plot's own measured box. */\n const indexAt = useCallback(\n (clientX: number): number | undefined => {\n const node = surfaceRef.current\n const box = node ? plotBox(node) : null\n if (!box) return undefined\n const fraction = clamp((clientX - box.left) / box.width, 0, 1)\n return zoom.range.startIndex + fraction * (zoom.range.endIndex - zoom.range.startIndex)\n },\n [surfaceRef, zoom.range.endIndex, zoom.range.startIndex],\n )\n\n useEffect(() => {\n const node = surfaceRef.current\n if (!node || !enabled) return\n\n // Attached natively rather than through `onWheel`, because React registers\n // its wheel listener passively — `preventDefault` from a React handler is a\n // console warning and a page that zooms anyway.\n const onWheel = (event: WheelEvent) => {\n if (!event.ctrlKey && !event.metaKey) return\n event.preventDefault()\n const anchor = indexAt(event.clientX)\n if (event.deltaY < 0) zoom.zoomIn(anchor)\n else zoom.zoomOut(anchor)\n }\n\n node.addEventListener('wheel', onWheel, { passive: false })\n return () => node.removeEventListener('wheel', onWheel)\n }, [enabled, indexAt, surfaceRef, zoom])\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (!enabled || event.button !== 0) return\n // No `preventDefault` here on purpose: it would cancel the compatibility\n // mouse events the tooltip and a mark's own click handler run on.\n drag.current = { pointerId: event.pointerId, originX: event.clientX }\n }\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const state = drag.current\n if (!state || state.pointerId !== event.pointerId) return\n\n const travelled = Math.abs(event.clientX - state.originX)\n if (travelled < DRAG_THRESHOLD) return\n\n const node = surfaceRef.current\n const box = node ? plotBox(node) : null\n if (!node || !box) return\n\n if (!node.hasPointerCapture(event.pointerId)) node.setPointerCapture(event.pointerId)\n\n const from = Math.min(state.originX, event.clientX)\n setSelection({ start: from - box.left, width: travelled })\n }\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n const state = drag.current\n drag.current = null\n setSelection(null)\n\n const node = surfaceRef.current\n if (node?.hasPointerCapture(event.pointerId)) node.releasePointerCapture(event.pointerId)\n if (!state || Math.abs(event.clientX - state.originX) < DRAG_THRESHOLD) return\n\n const from = indexAt(Math.min(state.originX, event.clientX))\n const to = indexAt(Math.max(state.originX, event.clientX))\n if (from === undefined || to === undefined) return\n\n zoom.zoomTo({ startIndex: Math.floor(from), endIndex: Math.ceil(to) })\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (!enabled) return\n const span = zoom.range.endIndex - zoom.range.startIndex\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowDown':\n zoom.panBy(-1)\n break\n case 'ArrowRight':\n case 'ArrowUp':\n zoom.panBy(1)\n break\n case 'PageDown':\n zoom.panBy(-Math.max(1, span))\n break\n case 'PageUp':\n zoom.panBy(Math.max(1, span))\n break\n case 'Home':\n zoom.panBy(-zoom.total)\n break\n case 'End':\n zoom.panBy(zoom.total)\n break\n case '+':\n case '=':\n zoom.zoomIn()\n break\n case '-':\n case '_':\n zoom.zoomOut()\n break\n case '0':\n zoom.reset()\n break\n default:\n return\n }\n\n event.preventDefault()\n }\n\n const shown = zoom.range.endIndex - zoom.range.startIndex + 1\n\n return (\n <div className=\"flex min-h-0 w-full flex-1 flex-col\">\n <div\n ref={surfaceRef}\n role=\"group\"\n aria-label={`${label} — plot`}\n aria-describedby={enabled ? hintId : undefined}\n tabIndex={enabled ? 0 : undefined}\n onKeyDown={onKeyDown}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n className={cn(\n 'relative flex min-h-0 w-full flex-1 flex-col rounded-(--radius-sm)',\n // Vertical scrolling still belongs to the page; horizontal movement\n // is the pan gesture, so the browser must not claim it first.\n enabled && 'touch-pan-y',\n selection && 'select-none',\n )}\n >\n {children}\n\n {selection && (\n // Positioned with inline style rather than `start-`/`end-` classes,\n // for the same reason the brush is: the axis being selected along is\n // the DATA's, and a cartesian plot runs left-to-right under\n // `dir=\"rtl\"` too.\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 z-(--z-rule) border-x border-(--rule-2) bg-(--ink)/8\"\n style={{ left: selection.start, width: selection.width }}\n />\n )}\n </div>\n\n {enabled && (\n <>\n <p id={hintId} className=\"sr-only\">\n Arrow keys pan by one point, Page Up and Page Down by a screenful, Home and End jump to\n the ends. Plus and minus zoom, zero resets. Ctrl and the wheel zoom around the pointer,\n and dragging across the plot zooms to that span.\n </p>\n <p aria-live=\"polite\" className=\"sr-only\">\n {`Showing ${zoom.labelAt(zoom.range.startIndex)} to ${zoom.labelAt(\n zoom.range.endIndex,\n )}, ${shown} of ${zoom.total} points`}\n </p>\n </>\n )}\n </div>\n )\n}\n\n/**\n * The measured box of the PLOT, not of the wrapper around it.\n *\n * The wrapper carries the toolbar row and the brush footer, and the plot's own\n * `<svg>` carries the axis gutter, so mapping a pointer through either is off\n * by enough to feel wrong at the ends of a drag. The grid is the one element\n * whose box IS the plot area, so it is tried first; the surface is the fallback\n * for a chart composed without `<Chart.Grid>`.\n */\nfunction plotBox(surface: HTMLElement): DOMRect | null {\n const grid = surface.querySelector('.recharts-cartesian-grid')\n const gridBox = grid?.getBoundingClientRect()\n if (gridBox && gridBox.width > 0) return gridBox\n\n const svg = surface.querySelector('svg.recharts-surface')\n const svgBox = svg?.getBoundingClientRect()\n return svgBox && svgBox.width > 0 ? svgBox : null\n}\n"],"mappings":"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","names":[]}
|
package/dist/semantic.css
CHANGED
|
@@ -82,6 +82,25 @@
|
|
|
82
82
|
rather than the semantic token (which inverts). */
|
|
83
83
|
--photo-scrim: #101010;
|
|
84
84
|
|
|
85
|
+
/* ─── Data ─────────────────────────────────────────────────────────────
|
|
86
|
+
What a chart is allowed to read. The series ramp itself stays a
|
|
87
|
+
primitive — a chart names a SERIES by index, and an index has no role to
|
|
88
|
+
alias — but everything around the marks does have one, and naming it here
|
|
89
|
+
is what keeps a chart from reaching for --rule or --ink directly.
|
|
90
|
+
|
|
91
|
+
--chart-cursor is deliberately heavier than --chart-grid: the crosshair
|
|
92
|
+
has to be findable against the grid it crosses, and at hairline weight on
|
|
93
|
+
a dashed grid it was not. */
|
|
94
|
+
--chart-surface: var(--paper);
|
|
95
|
+
--chart-grid: var(--rule);
|
|
96
|
+
--chart-cursor: var(--rule-2);
|
|
97
|
+
--chart-axis: var(--ink-3-aa);
|
|
98
|
+
--chart-track: var(--series-track);
|
|
99
|
+
/* The decorative plate behind the plot — a dot field, a hatch, a grid of
|
|
100
|
+
plus marks. One step quieter than the grid, because it sits BEHIND data
|
|
101
|
+
and must never compete with it for the same read. */
|
|
102
|
+
--chart-pattern: var(--rule);
|
|
103
|
+
|
|
85
104
|
/* ─── Depth ────────────────────────────────────────────────────────────
|
|
86
105
|
Law 2 — a box-shadow is never blurred. The system has no elevation ramp;
|
|
87
106
|
depth is a hard offset in ink, applied per component. These three resolve
|