@surflane/ui 0.11.0 → 0.13.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/dist/alert-dialog.d.ts +25 -7
- package/dist/alert-dialog.js +9 -4
- package/dist/app-shell.d.ts +10 -0
- package/dist/app-shell.js +7 -1
- package/dist/autocomplete.d.ts +2 -6
- package/dist/avatar.d.ts +7 -7
- package/dist/avatar.js +2 -2
- package/dist/bar-list.d.ts +31 -0
- package/dist/bar-list.js +31 -0
- package/dist/bottom-navigation.d.ts +6 -2
- package/dist/bottom-navigation.js +2 -2
- package/dist/button.d.ts +6 -11
- package/dist/button.js +8 -8
- package/dist/card.d.ts +15 -10
- package/dist/card.js +7 -2
- package/dist/chart.d.ts +58 -3
- package/dist/chart.js +330 -48
- package/dist/choice.d.ts +2 -2
- package/dist/class-names.d.ts +0 -4
- package/dist/class-names.js +0 -14
- package/dist/code.d.ts +19 -7
- package/dist/code.js +32 -38
- package/dist/combobox.d.ts +2 -8
- package/dist/combobox.js +2 -2
- package/dist/context-menu.d.ts +2 -8
- package/dist/context-menu.js +2 -2
- package/dist/css/bar-list.css +9 -0
- package/dist/css/chart.css +2 -0
- package/dist/css/dashboard.css +20 -0
- package/dist/css/rules/alert-dialog.css +8 -2
- package/dist/css/rules/app-shell.css +57 -10
- package/dist/css/rules/bar-list.css +101 -0
- package/dist/css/rules/bar.css +5 -12
- package/dist/css/rules/bottom-navigation.css +21 -2
- package/dist/css/rules/breadcrumbs.css +0 -6
- package/dist/css/rules/button.css +4 -12
- package/dist/css/rules/card.css +19 -0
- package/dist/css/rules/chart.css +87 -8
- package/dist/css/rules/choice.css +26 -2
- package/dist/css/rules/combobox.css +17 -16
- package/dist/css/rules/dashboard.css +118 -0
- package/dist/css/rules/date-picker.css +50 -27
- package/dist/css/rules/dialog.css +47 -7
- package/dist/css/rules/disclosure.css +17 -3
- package/dist/css/rules/field-control.css +9 -6
- package/dist/css/rules/file-upload.css +9 -5
- package/dist/css/rules/filter-bar.css +32 -2
- package/dist/css/rules/gauge.css +2 -2
- package/dist/css/rules/icon.css +0 -5
- package/dist/css/rules/input-group.css +23 -8
- package/dist/css/rules/link.css +0 -6
- package/dist/css/rules/menu.css +5 -2
- package/dist/css/rules/menubar.css +1 -1
- package/dist/css/rules/navigation-menu.css +31 -26
- package/dist/css/rules/number-field.css +8 -1
- package/dist/css/rules/otp-field.css +13 -3
- package/dist/css/rules/page-header.css +10 -0
- package/dist/css/rules/panel.css +59 -10
- package/dist/css/rules/popup.css +50 -0
- package/dist/css/rules/preview-card.css +0 -21
- package/dist/css/rules/scroll-area.css +20 -1
- package/dist/css/rules/select.css +29 -2
- package/dist/css/rules/sizing.css +25 -3
- package/dist/css/rules/skeleton.css +1 -1
- package/dist/css/rules/slider.css +16 -8
- package/dist/css/rules/spinner.css +1 -1
- package/dist/css/rules/stat.css +66 -0
- package/dist/css/rules/tabs.css +14 -6
- package/dist/css/rules/textarea.css +28 -0
- package/dist/css/rules/time-range-picker.css +14 -0
- package/dist/css/rules/toast.css +3 -1
- package/dist/css/rules/toggle.css +10 -3
- package/dist/css/rules/toolbar.css +10 -3
- package/dist/css/rules/tree.css +29 -4
- package/dist/css/styles.css +3 -0
- package/dist/css/time-range-picker.css +14 -0
- package/dist/dashboard.d.ts +58 -0
- package/dist/dashboard.js +60 -0
- package/dist/date-picker.d.ts +32 -3
- package/dist/date-picker.js +43 -4
- package/dist/dialog.d.ts +25 -7
- package/dist/dialog.js +9 -4
- package/dist/drawer.d.ts +10 -2
- package/dist/drawer.js +6 -3
- package/dist/icon.d.ts +15 -5
- package/dist/icon.js +16 -3
- package/dist/index.d.ts +9 -6
- package/dist/index.js +7 -4
- package/dist/inline-message.d.ts +2 -2
- package/dist/inline-message.js +7 -2
- package/dist/input.d.ts +2 -2
- package/dist/layout.d.ts +1 -3
- package/dist/layout.js +2 -2
- package/dist/native-select.d.ts +2 -2
- package/dist/navigation-menu.d.ts +16 -0
- package/dist/navigation-menu.js +6 -1
- package/dist/page-header.d.ts +10 -2
- package/dist/page-header.js +3 -3
- package/dist/panel.d.ts +6 -3
- package/dist/panel.js +2 -2
- package/dist/parts.d.ts +7 -3
- package/dist/parts.js +12 -4
- package/dist/popover.d.ts +6 -6
- package/dist/popover.js +3 -2
- package/dist/preview-card.d.ts +2 -8
- package/dist/preview-card.js +2 -2
- package/dist/select.d.ts +38 -8
- package/dist/select.js +110 -22
- package/dist/sheet.d.ts +10 -2
- package/dist/sheet.js +6 -3
- package/dist/size.d.ts +8 -6
- package/dist/size.js +2 -7
- package/dist/sizing.d.ts +13 -6
- package/dist/sizing.js +13 -4
- package/dist/sparkline.d.ts +2 -2
- package/dist/sparkline.js +2 -2
- package/dist/spinner.d.ts +2 -2
- package/dist/spinner.js +2 -2
- package/dist/stat.d.ts +24 -0
- package/dist/stat.js +15 -1
- package/dist/table.d.ts +12 -4
- package/dist/table.js +8 -3
- package/dist/textarea.d.ts +6 -3
- package/dist/textarea.js +2 -2
- package/dist/time-range-picker.d.ts +42 -0
- package/dist/time-range-picker.js +69 -0
- package/dist/toggle.d.ts +2 -2
- package/dist/toggle.js +2 -2
- package/dist/toolbar.d.ts +11 -10
- package/dist/toolbar.js +23 -1
- package/dist/tooltip.d.ts +1 -3
- package/dist/tooltip.js +4 -2
- package/dist/tree.d.ts +1 -3
- package/dist/tree.js +2 -2
- package/dist/typography.d.ts +10 -3
- package/dist/typography.js +4 -3
- package/package.json +18 -3
package/dist/chart.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { Area, Bar, CartesianGrid, ComposedChart, Line, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, useActiveTooltipCoordinate, useActiveTooltipLabel, useIsTooltipActive, usePlotArea, } from 'recharts';
|
|
4
|
+
import { Area, Bar, CartesianGrid, ComposedChart, Line, ReferenceArea, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, useActiveTooltipCoordinate, useActiveTooltipLabel, useIsTooltipActive, usePlotArea, } from 'recharts';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { Table } from './table.js';
|
|
7
7
|
import { Button } from './button.js';
|
|
8
|
+
import { Popover } from './popover.js';
|
|
8
9
|
import { cx } from './parts.js';
|
|
10
|
+
import { useComponentDensity } from './sizing.js';
|
|
9
11
|
const chartLabels = {
|
|
10
12
|
missing: 'Not available',
|
|
11
13
|
empty: 'No numeric measurements to display.',
|
|
@@ -20,6 +22,8 @@ const chartLabels = {
|
|
|
20
22
|
noRows: 'No rows.',
|
|
21
23
|
legend: (title) => `${title} series`,
|
|
22
24
|
series: (label) => `${label} series`,
|
|
25
|
+
moreSeries: (count) => `${count} more series`,
|
|
26
|
+
total: 'Total',
|
|
23
27
|
};
|
|
24
28
|
/** The round step at or above a raw step: 1, 2, 2.5 or 5 times a power of ten. */
|
|
25
29
|
function roundStep(raw) {
|
|
@@ -37,6 +41,112 @@ function axisStep(low, high) {
|
|
|
37
41
|
return step;
|
|
38
42
|
}
|
|
39
43
|
const dashes = [undefined, '7 4', '2 4', '10 3 2 3', '12 5', '4 3'];
|
|
44
|
+
/** A series' color: the caller's, or the palette's for its place. */
|
|
45
|
+
function seriesColor(index, palette, color) {
|
|
46
|
+
return (color ??
|
|
47
|
+
(palette === 'categorical'
|
|
48
|
+
? `var(--surf-chart-category-${(index % 12) + 1})`
|
|
49
|
+
: `var(--surf-chart-series-${(index % 3) + 1})`));
|
|
50
|
+
}
|
|
51
|
+
const toneColors = {
|
|
52
|
+
neutral: 'var(--surf-ink-muted)',
|
|
53
|
+
caution: 'var(--surf-status-caution-fg)',
|
|
54
|
+
danger: 'var(--surf-status-danger-fg)',
|
|
55
|
+
};
|
|
56
|
+
const MINUTE = 60_000, HOUR = 60 * MINUTE, DAY = 24 * HOUR;
|
|
57
|
+
const timeSteps = [
|
|
58
|
+
['minute', 1, MINUTE],
|
|
59
|
+
['minute', 5, 5 * MINUTE],
|
|
60
|
+
['minute', 15, 15 * MINUTE],
|
|
61
|
+
['minute', 30, 30 * MINUTE],
|
|
62
|
+
['hour', 1, HOUR],
|
|
63
|
+
['hour', 3, 3 * HOUR],
|
|
64
|
+
['hour', 6, 6 * HOUR],
|
|
65
|
+
['hour', 12, 12 * HOUR],
|
|
66
|
+
['day', 1, DAY],
|
|
67
|
+
['day', 2, 2 * DAY],
|
|
68
|
+
['day', 7, 7 * DAY],
|
|
69
|
+
['month', 1, 30 * DAY],
|
|
70
|
+
['month', 3, 91 * DAY],
|
|
71
|
+
['month', 6, 182 * DAY],
|
|
72
|
+
['year', 1, 365 * DAY],
|
|
73
|
+
];
|
|
74
|
+
/** Ticks on round local times, such as every 6 hours or the first of each month: at
|
|
75
|
+
* most six across the span, and no more than fit, so their steps stay even. */
|
|
76
|
+
function timeTicks(min, max, fits = () => true) {
|
|
77
|
+
const [unit, count] = timeSteps.find(([stepUnit, , ms]) => (max - min) / ms <= 6 && fits(stepUnit, ms)) ?? timeSteps[timeSteps.length - 1];
|
|
78
|
+
const date = new Date(min);
|
|
79
|
+
date.setSeconds(0, 0);
|
|
80
|
+
if (unit === 'minute')
|
|
81
|
+
date.setMinutes(Math.floor(date.getMinutes() / count) * count);
|
|
82
|
+
else
|
|
83
|
+
date.setMinutes(0);
|
|
84
|
+
if (unit === 'hour')
|
|
85
|
+
date.setHours(Math.floor(date.getHours() / count) * count);
|
|
86
|
+
else if (unit !== 'minute')
|
|
87
|
+
date.setHours(0);
|
|
88
|
+
if (unit === 'month')
|
|
89
|
+
date.setMonth(Math.floor(date.getMonth() / count) * count, 1);
|
|
90
|
+
if (unit === 'year')
|
|
91
|
+
date.setMonth(0, 1);
|
|
92
|
+
const ticks = [];
|
|
93
|
+
while (date.getTime() <= max && ticks.length < 100) {
|
|
94
|
+
if (date.getTime() >= min)
|
|
95
|
+
ticks.push(date.getTime());
|
|
96
|
+
if (unit === 'minute')
|
|
97
|
+
date.setMinutes(date.getMinutes() + count);
|
|
98
|
+
else if (unit === 'hour')
|
|
99
|
+
date.setHours(date.getHours() + count);
|
|
100
|
+
else if (unit === 'day')
|
|
101
|
+
date.setDate(date.getDate() + count);
|
|
102
|
+
else if (unit === 'month')
|
|
103
|
+
date.setMonth(date.getMonth() + count);
|
|
104
|
+
else
|
|
105
|
+
date.setFullYear(date.getFullYear() + count);
|
|
106
|
+
}
|
|
107
|
+
return { ticks, unit };
|
|
108
|
+
}
|
|
109
|
+
/** Powers of ten that span low to high, with 2 and 5 between them across one decade. */
|
|
110
|
+
function logTicks(low, high) {
|
|
111
|
+
const bottom = Math.floor(Math.log10(low)), top = Math.max(Math.ceil(Math.log10(high)), bottom + 1);
|
|
112
|
+
const ticks = [];
|
|
113
|
+
for (let power = bottom; power <= top; power++)
|
|
114
|
+
for (const factor of top - bottom === 1 ? [1, 2, 5] : [1])
|
|
115
|
+
if (power < top || factor === 1)
|
|
116
|
+
ticks.push(Number((factor * 10 ** power).toPrecision(12)));
|
|
117
|
+
return ticks;
|
|
118
|
+
}
|
|
119
|
+
const isNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
120
|
+
/** An element's content width, kept current as it resizes; 0 before it is measured. */
|
|
121
|
+
function useWidth() {
|
|
122
|
+
const [element, setElement] = React.useState(null);
|
|
123
|
+
const [width, setWidth] = React.useState(0);
|
|
124
|
+
React.useLayoutEffect(() => {
|
|
125
|
+
if (!element)
|
|
126
|
+
return;
|
|
127
|
+
setWidth(element.clientWidth);
|
|
128
|
+
if (typeof ResizeObserver === 'undefined')
|
|
129
|
+
return;
|
|
130
|
+
const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));
|
|
131
|
+
observer.observe(element);
|
|
132
|
+
return () => observer.disconnect();
|
|
133
|
+
}, [element]);
|
|
134
|
+
return [setElement, width, element];
|
|
135
|
+
}
|
|
136
|
+
let measureCanvas;
|
|
137
|
+
/** How wide a tick label draws, in the font it draws in. */
|
|
138
|
+
function textWidth(text, font) {
|
|
139
|
+
measureCanvas ??= document.createElement('canvas');
|
|
140
|
+
const context = measureCanvas.getContext('2d');
|
|
141
|
+
if (!context)
|
|
142
|
+
return text.length * 7.5;
|
|
143
|
+
context.font = font;
|
|
144
|
+
return context.measureText(text).width;
|
|
145
|
+
}
|
|
146
|
+
/** The gap Recharts keeps between tick labels. */
|
|
147
|
+
const TICK_GAP = 24;
|
|
148
|
+
/** The value axis and the plot's right margin: what the plot gives up of the frame. */
|
|
149
|
+
const PLOT_INSET = 56 + 20;
|
|
40
150
|
/** True once the font of the tick labels has loaded. Recharts measures a label to keep
|
|
41
151
|
* it inside the chart and caches that width; measured in the fallback font, the last
|
|
42
152
|
* label overflows by a few pixels once the real font arrives. */
|
|
@@ -79,6 +189,15 @@ function Pattern({ id, index, color, }) {
|
|
|
79
189
|
function SeriesMark({ type, index, color, pattern, }) {
|
|
80
190
|
return (_jsx("svg", { className: 'surf-chart-mark', viewBox: '0 0 32 20', "aria-hidden": 'true', focusable: 'false', children: type === 'bar' ? (_jsxs(_Fragment, { children: [_jsx("defs", { children: _jsx(Pattern, { id: pattern, color: color, index: index }) }), _jsx("rect", { x: '4', y: '3', width: '24', height: '14', rx: '2', fill: `url(#${pattern})` })] })) : (_jsxs(_Fragment, { children: [_jsx("path", { d: 'M1 10h30', stroke: color, strokeWidth: '2', strokeDasharray: dashes[index % dashes.length] }), _jsx(Marker, { cx: 16, cy: 10, index: index, color: color })] })) }));
|
|
81
191
|
}
|
|
192
|
+
function ReferenceMark({ reference }) {
|
|
193
|
+
const color = toneColors[reference.tone ?? 'neutral'];
|
|
194
|
+
return (_jsx("svg", { className: 'surf-chart-mark', viewBox: '0 0 32 20', "aria-hidden": 'true', focusable: 'false', children: 'y' in reference ? (_jsx("path", { d: 'M1 10h30', stroke: color, strokeWidth: '2', strokeDasharray: '4 3' })) : (_jsx("rect", { x: '4', y: '4', width: '24', height: '12', rx: '2', fill: color, fillOpacity: '0.2' })) }));
|
|
195
|
+
}
|
|
196
|
+
/** A series' swatch as Chart's legend draws it, for a legend or key of your own. */
|
|
197
|
+
export function ChartSeriesMark({ type = 'line', index, palette = 'neutral', color, }) {
|
|
198
|
+
const id = React.useId().replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
199
|
+
return (_jsx(SeriesMark, { type: type, index: index, color: seriesColor(index, palette, color), pattern: `${id}-mark` }));
|
|
200
|
+
}
|
|
82
201
|
/** The active point's values beside it, for pointer and touch users. The readout
|
|
83
202
|
* below the plot announces the same values, so this copy stays out of the
|
|
84
203
|
* accessibility tree. It sits on the side of the point with more room and stays
|
|
@@ -95,29 +214,54 @@ function FloatingValues({ host, hidden, render, }) {
|
|
|
95
214
|
if (shown && box.current)
|
|
96
215
|
setWidth(box.current.offsetWidth);
|
|
97
216
|
}, [shown, label, plot?.width]);
|
|
98
|
-
|
|
217
|
+
const content = shown ? render(String(label)) : null;
|
|
218
|
+
// A break in sampling has no values to show.
|
|
219
|
+
if (!shown || !point || !plot || content === null)
|
|
99
220
|
return null;
|
|
100
221
|
// The gap between the point and the box, in px: the --surf-space-3 step.
|
|
101
222
|
const gap = 12;
|
|
102
223
|
const start = point.x > plot.x + plot.width / 2;
|
|
103
224
|
const left = Math.min(Math.max(start ? point.x - gap - width : point.x + gap, plot.x), plot.x + plot.width - width);
|
|
104
|
-
return createPortal(_jsx("div", { ref: box, className: 'surf-chart-tooltip', "aria-hidden": 'true', style: { left, top: plot.y, visibility: width ? undefined : 'hidden' }, children:
|
|
225
|
+
return createPortal(_jsx("div", { ref: box, className: 'surf-chart-tooltip', "aria-hidden": 'true', style: { left, top: plot.y, visibility: width ? undefined : 'hidden' }, children: content }), host);
|
|
105
226
|
}
|
|
106
227
|
/** Cartesian trends with a persistent, keyboard-readable tooltip region and a
|
|
107
228
|
* full data-table alternative. Missing values are gaps, never fabricated zeros. */
|
|
108
|
-
export const Chart = function Chart({ title, description, data, series, unit, xLabel, yLabel, type = 'line', height
|
|
229
|
+
export const Chart = function Chart({ title, description, data, series, unit, xLabel, yLabel, type = 'line', height, includeZero = true, palette = 'neutral', xScale = 'category', yScale = 'linear', curve = 'linear', stacked = false, references = [], density: ownDensity, legendPosition = 'top', legendLimit, locale, formatValue, labels, hiddenSeries, defaultHiddenSeries = [], onHiddenSeriesChange, className, ref, ...props }) {
|
|
109
230
|
const text = { ...chartLabels, ...labels };
|
|
110
231
|
const id = React.useId().replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
111
|
-
const colorFor = (index) => palette
|
|
112
|
-
|
|
113
|
-
|
|
232
|
+
const colorFor = (index) => seriesColor(index, palette, series[index]?.color);
|
|
233
|
+
const timeScale = xScale === 'time';
|
|
234
|
+
const logScale = yScale === 'log';
|
|
235
|
+
const stacks = stacked && type !== 'line';
|
|
236
|
+
const density = useComponentDensity(ownDensity);
|
|
237
|
+
const compact = density === 'compact';
|
|
114
238
|
const [internalHidden, setInternalHidden] = React.useState(defaultHiddenSeries);
|
|
115
239
|
const hidden = new Set(hiddenSeries ?? internalHidden);
|
|
116
240
|
const visible = series.filter((s) => !hidden.has(s.key));
|
|
117
241
|
const [host, setHost] = React.useState(null);
|
|
118
242
|
const fontsReady = useFontReady(host, data.map((d) => d.label).join(''));
|
|
119
243
|
const [floatHost, setFloatHost] = React.useState(null);
|
|
244
|
+
const [setFrame, frameWidth, frame] = useWidth();
|
|
245
|
+
// legendLimit='auto': lay every series out, count those on the first line, then
|
|
246
|
+
// fold the rest, keeping room for the "+N" button. A new width lays them out again.
|
|
247
|
+
const legendList = React.useRef(null);
|
|
248
|
+
const [legendFit, setLegendFit] = React.useState();
|
|
249
|
+
React.useLayoutEffect(() => {
|
|
250
|
+
if (legendLimit === 'auto')
|
|
251
|
+
setLegendFit(undefined);
|
|
252
|
+
}, [legendLimit, frameWidth, series.length]);
|
|
253
|
+
React.useLayoutEffect(() => {
|
|
254
|
+
if (legendLimit !== 'auto' || legendFit !== undefined)
|
|
255
|
+
return;
|
|
256
|
+
const items = Array.from(legendList.current?.querySelectorAll(':scope > [data-series]') ?? []);
|
|
257
|
+
if (!items.length)
|
|
258
|
+
return;
|
|
259
|
+
const line = items.filter((item) => item.offsetTop === items[0].offsetTop).length;
|
|
260
|
+
setLegendFit(line >= items.length ? items.length : Math.max(1, line - 1));
|
|
261
|
+
});
|
|
120
262
|
const [dismissed, setDismissed] = React.useState(false);
|
|
263
|
+
// The table renders once it is first opened, so a page of charts stays light.
|
|
264
|
+
const [tableOpened, setTableOpened] = React.useState(false);
|
|
121
265
|
const firstLegend = React.useRef(null);
|
|
122
266
|
const [restoreRequested, setRestoreRequested] = React.useState(false);
|
|
123
267
|
React.useLayoutEffect(() => {
|
|
@@ -128,13 +272,20 @@ export const Chart = function Chart({ title, description, data, series, unit, xL
|
|
|
128
272
|
setRestoreRequested(false);
|
|
129
273
|
}, [restoreRequested, visible.length]);
|
|
130
274
|
const number = React.useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
|
|
131
|
-
const
|
|
275
|
+
const compactNumber = React.useMemo(() => new Intl.NumberFormat(locale, {
|
|
132
276
|
notation: 'compact',
|
|
133
277
|
maximumFractionDigits: 1,
|
|
134
278
|
}), [locale]);
|
|
135
|
-
const
|
|
136
|
-
?
|
|
137
|
-
|
|
279
|
+
const formatNumber = (value) => isNumber(value)
|
|
280
|
+
? formatValue
|
|
281
|
+
? formatValue(value)
|
|
282
|
+
: number.format(value)
|
|
283
|
+
: null;
|
|
284
|
+
// The readout names the unit after each value; the table names it once, in the header.
|
|
285
|
+
const format = (value) => {
|
|
286
|
+
const formatted = formatNumber(value);
|
|
287
|
+
return formatted === null ? text.missing : `${formatted} ${unit}`;
|
|
288
|
+
};
|
|
138
289
|
const rows = React.useMemo(() => {
|
|
139
290
|
const ids = new Set(), keys = new Set();
|
|
140
291
|
for (const s of series) {
|
|
@@ -142,38 +293,144 @@ export const Chart = function Chart({ title, description, data, series, unit, xL
|
|
|
142
293
|
throw new Error('Chart series keys must be non-empty and unique');
|
|
143
294
|
keys.add(s.key);
|
|
144
295
|
}
|
|
145
|
-
|
|
296
|
+
const result = data.map((row) => {
|
|
146
297
|
if (!row.id || ids.has(row.id))
|
|
147
298
|
throw new Error('Chart datum IDs must be non-empty and unique');
|
|
148
299
|
ids.add(row.id);
|
|
300
|
+
const time = row.time === undefined ? undefined : Number(row.time);
|
|
301
|
+
if (timeScale && !isNumber(time))
|
|
302
|
+
throw new Error('Chart rows need a time when xScale is "time"');
|
|
149
303
|
return {
|
|
150
304
|
...row,
|
|
305
|
+
time,
|
|
151
306
|
values: Object.fromEntries(series.map((s) => [
|
|
152
307
|
s.key,
|
|
153
|
-
|
|
154
|
-
Number.isFinite(row.values[s.key])
|
|
155
|
-
? row.values[s.key]
|
|
156
|
-
: null,
|
|
308
|
+
isNumber(row.values[s.key]) ? row.values[s.key] : null,
|
|
157
309
|
])),
|
|
158
310
|
};
|
|
159
311
|
});
|
|
160
|
-
|
|
312
|
+
return timeScale ? result.sort((a, b) => a.time - b.time) : result;
|
|
313
|
+
}, [data, series, timeScale]);
|
|
161
314
|
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
315
|
+
const byTime = new Map(rows.map((row) => [row.time, row]));
|
|
316
|
+
const rowFor = (label) => timeScale ? byTime.get(Number(label)) : byId.get(String(label));
|
|
317
|
+
// On a time axis, a wait of more than twice the usual interval is a break in
|
|
318
|
+
// sampling: an empty row there keeps the line from bridging it.
|
|
319
|
+
const interval = React.useMemo(() => {
|
|
320
|
+
if (!timeScale || rows.length < 3)
|
|
321
|
+
return 0;
|
|
322
|
+
const steps = rows
|
|
323
|
+
.slice(1)
|
|
324
|
+
.map((row, index) => row.time - rows[index].time)
|
|
325
|
+
.sort((a, b) => a - b);
|
|
326
|
+
return steps[Math.floor(steps.length / 2)];
|
|
327
|
+
}, [rows, timeScale]);
|
|
328
|
+
const plotRows = React.useMemo(() => {
|
|
329
|
+
if (!interval)
|
|
330
|
+
return rows;
|
|
331
|
+
const empty = Object.fromEntries(series.map((s) => [s.key, null]));
|
|
332
|
+
return rows.flatMap((row, index) => {
|
|
333
|
+
const next = rows[index + 1];
|
|
334
|
+
return next && next.time - row.time > interval * 2
|
|
335
|
+
? [
|
|
336
|
+
row,
|
|
337
|
+
{
|
|
338
|
+
...row,
|
|
339
|
+
id: `${row.id}-gap`,
|
|
340
|
+
time: row.time + interval,
|
|
341
|
+
values: empty,
|
|
342
|
+
},
|
|
343
|
+
]
|
|
344
|
+
: [row];
|
|
345
|
+
});
|
|
346
|
+
}, [rows, series, interval]);
|
|
347
|
+
const valueOf = (row, key) => {
|
|
348
|
+
const value = row.values[key];
|
|
349
|
+
return value === null || (logScale && value <= 0) ? null : value;
|
|
350
|
+
};
|
|
351
|
+
// The value axis spans the visible values (their sums when stacked) and the
|
|
352
|
+
// references, widened to round steps so its ticks read cleanly.
|
|
162
353
|
const values = rows
|
|
163
|
-
.flatMap((row) => visible.map((s) => row
|
|
164
|
-
.filter(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
(
|
|
176
|
-
|
|
354
|
+
.flatMap((row) => visible.map((s) => valueOf(row, s.key)))
|
|
355
|
+
.filter(isNumber);
|
|
356
|
+
const sums = stacks
|
|
357
|
+
? rows.flatMap((row) => {
|
|
358
|
+
const parts = visible.map((s) => valueOf(row, s.key)).filter(isNumber);
|
|
359
|
+
return [
|
|
360
|
+
parts.filter((v) => v > 0).reduce((a, b) => a + b, 0),
|
|
361
|
+
parts.filter((v) => v < 0).reduce((a, b) => a + b, 0),
|
|
362
|
+
];
|
|
363
|
+
})
|
|
364
|
+
: values;
|
|
365
|
+
const referenceValues = references
|
|
366
|
+
.flatMap((r) => ('y' in r ? [r.y] : [r.from, r.to]))
|
|
367
|
+
.filter((v) => isNumber(v) && (!logScale || v > 0));
|
|
368
|
+
const extent = [...sums, ...(values.length ? referenceValues : [])];
|
|
369
|
+
// Bars grow from zero, so they always keep it on a linear axis.
|
|
370
|
+
const keepZero = !logScale && (includeZero || type === 'bar');
|
|
371
|
+
const start = keepZero ? 0 : (extent[0] ?? 0);
|
|
372
|
+
const low = extent.reduce((a, b) => Math.min(a, b), start), high = extent.reduce((a, b) => Math.max(a, b), start);
|
|
373
|
+
let min = low, max = high, ticks;
|
|
374
|
+
if (logScale) {
|
|
375
|
+
ticks = logTicks(low > 0 ? low : 1, high > 0 ? high : 10);
|
|
376
|
+
min = ticks[0];
|
|
377
|
+
max = ticks[ticks.length - 1];
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
const step = axisStep(low, high);
|
|
381
|
+
if (step) {
|
|
382
|
+
min = Math.floor(low / step) * step;
|
|
383
|
+
max = Math.ceil(high / step) * step;
|
|
384
|
+
ticks = Array.from({ length: Math.round((max - min) / step) + 1 },
|
|
385
|
+
// Rounded, so float steps never print as 0.30000000000000004.
|
|
386
|
+
(_, index) => Number((min + index * step).toPrecision(12)));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const time = React.useMemo(() => {
|
|
390
|
+
if (!timeScale || !rows.length)
|
|
391
|
+
return undefined;
|
|
392
|
+
const first = rows[0].time, last = rows[rows.length - 1].time;
|
|
393
|
+
const formatFor = (unit) => new Intl.DateTimeFormat(locale, unit === 'minute' || unit === 'hour'
|
|
394
|
+
? { hour: 'numeric', minute: '2-digit' }
|
|
395
|
+
: unit === 'day'
|
|
396
|
+
? { month: 'short', day: 'numeric' }
|
|
397
|
+
: unit === 'month'
|
|
398
|
+
? { month: 'short', year: 'numeric' }
|
|
399
|
+
: { year: 'numeric' });
|
|
400
|
+
// Pick the step from the plot's width and the width its labels draw at, so a
|
|
401
|
+
// narrow chart steps every 2 or 3 hours instead of dropping uneven ticks.
|
|
402
|
+
const plotWidth = frameWidth - PLOT_INSET;
|
|
403
|
+
const font = frame && fontsReady
|
|
404
|
+
? `12px ${getComputedStyle(frame).fontFamily}`
|
|
405
|
+
: undefined;
|
|
406
|
+
const fits = (unit, ms) => {
|
|
407
|
+
if (plotWidth <= 0 || !font)
|
|
408
|
+
return true;
|
|
409
|
+
const label = Math.max(textWidth(formatFor(unit).format(new Date(first)), font), textWidth(formatFor(unit).format(new Date(2026, 9, 24, 22, 30)), font));
|
|
410
|
+
const count = Math.floor((last - first) / ms) + 1;
|
|
411
|
+
return count * (label + TICK_GAP) - TICK_GAP <= plotWidth;
|
|
412
|
+
};
|
|
413
|
+
const { ticks: timeAxisTicks, unit: timeUnit } = timeTicks(first, last, fits);
|
|
414
|
+
// Bars sit on their time, so half an interval on each side keeps the end ones whole.
|
|
415
|
+
const pad = type === 'bar' ? (interval || DAY) / 2 : 0;
|
|
416
|
+
const subDay = timeUnit === 'minute' || timeUnit === 'hour';
|
|
417
|
+
const tickFormat = formatFor(timeUnit);
|
|
418
|
+
const dayFormat = new Intl.DateTimeFormat(locale, {
|
|
419
|
+
month: 'short',
|
|
420
|
+
day: 'numeric',
|
|
421
|
+
});
|
|
422
|
+
return {
|
|
423
|
+
domain: [first - pad, last + pad],
|
|
424
|
+
ticks: timeAxisTicks,
|
|
425
|
+
// Within a day the ticks name hours; one at midnight names the day instead.
|
|
426
|
+
format: (value) => {
|
|
427
|
+
const date = new Date(value);
|
|
428
|
+
return subDay && date.getHours() === 0 && date.getMinutes() === 0
|
|
429
|
+
? dayFormat.format(date)
|
|
430
|
+
: tickFormat.format(date);
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}, [rows, timeScale, type, interval, locale, frameWidth, frame, fontsReady]);
|
|
177
434
|
const hasValues = values.length > 0;
|
|
178
435
|
const changeHidden = (next) => {
|
|
179
436
|
if (hiddenSeries === undefined)
|
|
@@ -183,14 +440,35 @@ export const Chart = function Chart({ title, description, data, series, unit, xL
|
|
|
183
440
|
const toggle = (key) => changeHidden(series
|
|
184
441
|
.filter((s) => (s.key === key ? !hidden.has(key) : hidden.has(s.key)))
|
|
185
442
|
.map((s) => s.key));
|
|
186
|
-
const chartHeight =
|
|
187
|
-
|
|
443
|
+
const chartHeight = height !== undefined && Number.isFinite(height)
|
|
444
|
+
? Math.max(compact ? 120 : 200, height)
|
|
445
|
+
: compact
|
|
446
|
+
? 180
|
|
447
|
+
: 280;
|
|
448
|
+
const total = (row) => visible
|
|
449
|
+
.map((s) => row.values[s.key])
|
|
450
|
+
.filter(isNumber)
|
|
451
|
+
.reduce((a, b) => a + b, 0);
|
|
452
|
+
const referenceText = (r) => 'y' in r
|
|
453
|
+
? `${r.label} ${format(r.y)}`
|
|
454
|
+
: `${r.label} ${formatNumber(r.from) ?? text.missing}–${format(r.to)}`;
|
|
455
|
+
const legendButton = (s, index) => (_jsxs("button", { ref: index === 0 ? firstLegend : undefined, type: 'button', className: 'surf-chart-legend-button', "aria-label": text.series(s.label), "aria-pressed": !hidden.has(s.key), onClick: () => toggle(s.key), children: [_jsx(SeriesMark, { type: type, index: index, color: colorFor(index), pattern: `${id}-legend-${index}` }), _jsx("span", { children: s.label })] }));
|
|
456
|
+
const limit = legendLimit === 'auto' ? legendFit : legendLimit;
|
|
457
|
+
const shown = limit !== undefined && limit < series.length
|
|
458
|
+
? series.slice(0, Math.max(0, limit))
|
|
459
|
+
: series;
|
|
460
|
+
const folded = series.slice(shown.length);
|
|
461
|
+
const legend = (_jsxs("ul", { ref: legendList, className: 'surf-chart-legend', "aria-label": text.legend(title), children: [shown.map((s, index) => (_jsx("li", { "data-series": '', children: legendButton(s, index) }, s.key))), folded.length > 0 && (_jsx("li", { children: _jsxs(Popover.Root, { children: [_jsxs(Popover.Trigger, { className: 'surf-chart-legend-button surf-chart-legend-more', "aria-label": text.moreSeries(folded.length), children: ["+", folded.length] }), _jsx(Popover.Content, { size: 'small', children: _jsx("ul", { className: 'surf-chart-legend-list', children: folded.map((s, index) => (_jsx("li", { children: legendButton(s, shown.length + index) }, s.key))) }) })] }) })), references.map((reference, index) => (_jsxs("li", { className: 'surf-chart-legend-reference', children: [_jsx(ReferenceMark, { reference: reference }), _jsx("span", { children: referenceText(reference) })] }, index)))] }));
|
|
462
|
+
return (_jsxs("figure", { ...props, ref: ref, "data-density": density, className: cx('surf-chart', className), "aria-labelledby": `${id}-title`, children: [_jsxs("figcaption", { className: 'surf-chart-caption', children: [_jsx("strong", { id: `${id}-title`, children: title }), _jsx("p", { id: `${id}-summary`, children: description })] }), _jsxs("div", { className: 'surf-chart-frame', ref: setFrame, children: [_jsxs("div", { className: 'surf-chart-key', children: [_jsxs("p", { className: 'surf-chart-axis-label', children: [yLabel, " (", unit, ")"] }), legendPosition === 'top' && legend] }), hasValues ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: 'surf-chart-plot', onKeyDownCapture: (event) => {
|
|
188
463
|
if (event.key === 'Escape')
|
|
189
464
|
setDismissed(true);
|
|
190
465
|
else if (event.key === 'ArrowLeft' ||
|
|
191
466
|
event.key === 'ArrowRight')
|
|
192
467
|
setDismissed(false);
|
|
193
|
-
}, onMouseMoveCapture: () => setDismissed(false), onTouchStartCapture: () => setDismissed(false), onFocusCapture: () => setDismissed(false), children: [_jsx(ResponsiveContainer, { width: '100%', height: chartHeight, minWidth: 0, initialDimension: { width: 320, height: chartHeight }, children: _jsxs(ComposedChart, { data:
|
|
468
|
+
}, onMouseMoveCapture: () => setDismissed(false), onTouchStartCapture: () => setDismissed(false), onFocusCapture: () => setDismissed(false), children: [_jsx(ResponsiveContainer, { width: '100%', height: chartHeight, minWidth: 0, initialDimension: { width: 320, height: chartHeight }, children: _jsxs(ComposedChart, { data: plotRows, accessibilityLayer: true, role: 'application', "aria-label": title, "aria-describedby": `${id}-summary ${id}-keys`, margin: { top: 16, right: 20, bottom: 8, left: 0 }, baseValue: logScale ? min : undefined, children: [_jsx("defs", { children: series.map((s, index) => (_jsx(Pattern, { id: `${id}-pattern-${index}`, index: index, color: colorFor(index) }, s.key))) }), _jsx(CartesianGrid, { vertical: false, stroke: 'var(--surf-line)' }), references.map((reference, index) => 'y' in reference ? null : (_jsx(ReferenceArea, { y1: reference.from, y2: reference.to, fill: toneColors[reference.tone ?? 'neutral'], fillOpacity: 0.12, stroke: 'none' }, index))), time ? (_jsx(XAxis, { dataKey: 'time', type: 'number', scale: 'time', domain: time.domain, ticks: time.ticks, tickFormatter: (value) => time.format(Number(value)), tick: fontsReady && {
|
|
469
|
+
fill: 'var(--surf-ink-muted)',
|
|
470
|
+
fontSize: 12,
|
|
471
|
+
}, tickLine: false, axisLine: { stroke: 'var(--surf-line)' }, minTickGap: 24, interval: 'preserveStartEnd' })) : (_jsx(XAxis, { dataKey: 'id', tickFormatter: (value) => {
|
|
194
472
|
const label = byId.get(String(value))?.label ?? String(value);
|
|
195
473
|
// minTickGap already drops ticks that would overlap; the cap
|
|
196
474
|
// only keeps one very long label from crowding the axis.
|
|
@@ -200,9 +478,9 @@ export const Chart = function Chart({ title, description, data, series, unit, xL
|
|
|
200
478
|
}, tick: fontsReady && {
|
|
201
479
|
fill: 'var(--surf-ink-muted)',
|
|
202
480
|
fontSize: 12,
|
|
203
|
-
}, tickLine: false, axisLine: { stroke: 'var(--surf-line)' }, minTickGap: 24, interval: 'preserveStartEnd' }), _jsx(YAxis, { width: 56, tickFormatter: (value) => formatValue
|
|
481
|
+
}, tickLine: false, axisLine: { stroke: 'var(--surf-line)' }, minTickGap: 24, interval: 'preserveStartEnd' })), _jsx(YAxis, { width: 56, scale: logScale ? 'log' : 'auto', tickFormatter: (value) => formatValue
|
|
204
482
|
? formatValue(Number(value))
|
|
205
|
-
:
|
|
483
|
+
: compactNumber.format(Number(value)), tick: { fill: 'var(--surf-ink-muted)', fontSize: 12 }, tickLine: false, axisLine: false, domain: [min, max === min ? max + 1 : max], ticks: ticks, allowDataOverflow: logScale }), !logScale && (_jsx(ReferenceLine, { y: 0, stroke: 'var(--surf-ink-muted)' })), host && (_jsx(Tooltip, { portal: host, isAnimationActive: false, active: dismissed ? false : undefined, filterNull: false, cursor: dismissed
|
|
206
484
|
? false
|
|
207
485
|
: {
|
|
208
486
|
stroke: 'var(--surf-control-border)',
|
|
@@ -213,29 +491,33 @@ export const Chart = function Chart({ title, description, data, series, unit, xL
|
|
|
213
491
|
pointerEvents: 'auto',
|
|
214
492
|
}, content: (tooltip) => {
|
|
215
493
|
const row = tooltip.active && !dismissed
|
|
216
|
-
?
|
|
494
|
+
? rowFor(tooltip.label)
|
|
217
495
|
: undefined;
|
|
218
|
-
return (_jsx("div", { className: 'surf-chart-readout', "data-chart-readout": row?.id, children: row ? (_jsxs(_Fragment, { children: [_jsx("strong", { children: row.label }),
|
|
496
|
+
return (_jsx("div", { className: 'surf-chart-readout', "data-chart-readout": row?.id, children: row ? (_jsxs(_Fragment, { children: [_jsx("strong", { children: row.label }), _jsxs("ul", { children: [visible.map((s) => (_jsxs("li", { children: [s.label, ": ", format(row.values[s.key])] }, s.key))), stacks && (_jsxs("li", { children: [text.total, ": ", format(total(row))] }))] })] })) : (_jsx("span", { children: text.inspectHint })) }));
|
|
219
497
|
} })), series.map((s, index) => {
|
|
220
498
|
const common = {
|
|
221
499
|
name: s.label,
|
|
222
|
-
dataKey: (row) => row
|
|
500
|
+
dataKey: (row) => valueOf(row, s.key),
|
|
223
501
|
hide: hidden.has(s.key),
|
|
224
502
|
isAnimationActive: false,
|
|
225
503
|
stroke: colorFor(index),
|
|
504
|
+
stackId: stacks ? 'total' : undefined,
|
|
226
505
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}), _jsx(FloatingValues, { host: floatHost, hidden: dismissed, render: (label) => {
|
|
233
|
-
const row =
|
|
506
|
+
const dot = rows.length > 40
|
|
507
|
+
? false
|
|
508
|
+
: (dot) => (_jsx(Marker, { cx: dot.cx, cy: dot.cy, color: colorFor(index), index: index }));
|
|
509
|
+
const shape = curve === 'step' ? 'stepAfter' : 'linear';
|
|
510
|
+
return type === 'bar' ? (_jsx(Bar, { ...common, fill: `url(#${id}-pattern-${index})`, maxBarSize: 48 }, s.key)) : type === 'area' ? (_jsx(Area, { ...common, type: shape, strokeWidth: 2, strokeDasharray: dashes[index % dashes.length], fill: `url(#${id}-pattern-${index})`, fillOpacity: stacks ? 0.32 : 0.12, connectNulls: false, dot: dot, activeDot: false }, s.key)) : (_jsx(Line, { ...common, type: shape, strokeWidth: 2, strokeDasharray: dashes[index % dashes.length], connectNulls: false, dot: dot, activeDot: false }, s.key));
|
|
511
|
+
}), references.map((reference, index) => 'y' in reference ? (_jsx(ReferenceLine, { y: reference.y, stroke: toneColors[reference.tone ?? 'neutral'], strokeWidth: 1.5, strokeDasharray: '4 3' }, index)) : null), _jsx(FloatingValues, { host: floatHost, hidden: dismissed, render: (label) => {
|
|
512
|
+
const row = rowFor(label);
|
|
234
513
|
if (!row)
|
|
235
514
|
return null;
|
|
236
|
-
return (_jsxs(_Fragment, { children: [_jsx("strong", { children: row.label }),
|
|
515
|
+
return (_jsxs(_Fragment, { children: [_jsx("strong", { children: row.label }), _jsxs("ul", { children: [series.map((s, index) => hidden.has(s.key) ? null : (_jsxs("li", { children: [_jsx(SeriesMark, { type: type, index: index, color: colorFor(index), pattern: `${id}-float-${index}` }), _jsx("span", { children: s.label }), _jsx("span", { className: 'surf-chart-tooltip-value', children: format(row.values[s.key]) })] }, s.key))), stacks && (_jsxs("li", { className: 'surf-chart-tooltip-total', children: [_jsx("span", { children: text.total }), _jsx("span", { className: 'surf-chart-tooltip-value', children: format(total(row)) })] }))] })] }));
|
|
237
516
|
} })] }) }), _jsx("div", { className: 'surf-chart-float', ref: setFloatHost })] }), _jsx("p", { className: 'surf-chart-axis-label surf-chart-x-label', children: xLabel })] })) : (_jsx("div", { className: 'surf-chart-empty', role: 'status', children: visible.length === 0 && series.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("p", { children: text.noSeries }), _jsx(Button, { variant: 'outline', onClick: () => {
|
|
238
517
|
setRestoreRequested(true);
|
|
239
518
|
changeHidden([]);
|
|
240
|
-
}, children: text.showAll })] })) : (_jsx("p", { children: text.empty })) }))] }), hasValues && (_jsx("p", { className: 'surf-chart-help', id: `${id}-keys`, children: text.keyboardHelp })), _jsx("div", { className: 'surf-chart-inspector', ref: setHost, role: 'status', "aria-live": 'polite', "aria-atomic": 'true', "aria-label": text.values(title), hidden: !hasValues }), _jsxs("details", { className: 'surf-chart-table',
|
|
519
|
+
}, children: text.showAll })] })) : (_jsx("p", { children: text.empty })) })), legendPosition === 'bottom' && (_jsx("div", { className: 'surf-chart-key', "data-position": 'bottom', children: legend }))] }), hasValues && (_jsx("p", { className: 'surf-chart-help', id: `${id}-keys`, children: text.keyboardHelp })), _jsx("div", { className: 'surf-chart-inspector', ref: setHost, role: 'status', "aria-live": 'polite', "aria-atomic": 'true', "aria-label": text.values(title), hidden: !hasValues }), _jsxs("details", { className: 'surf-chart-table', onToggle: (event) => {
|
|
520
|
+
if (event.currentTarget.open)
|
|
521
|
+
setTableOpened(true);
|
|
522
|
+
}, children: [_jsx("summary", { children: text.viewTable }), tableOpened && (_jsx(Table.Container, { label: text.table(title), orientation: 'both', scrollChain: 'contain', children: _jsxs(Table.Root, { density: density, children: [_jsx(Table.Caption, { children: text.tableCaption(unit) }), _jsx(Table.Head, { children: _jsxs(Table.Row, { children: [_jsx(Table.HeaderCell, { scope: 'col', children: xLabel }), series.map((s) => (_jsxs(Table.HeaderCell, { scope: 'col', numeric: true, children: [s.label, " (", unit, ")"] }, s.key)))] }) }), _jsx(Table.Body, { children: rows.length ? (rows.map((row) => (_jsxs(Table.Row, { children: [_jsx(Table.HeaderCell, { scope: 'row', children: row.label }), series.map((s) => (_jsx(Table.Cell, { numeric: true, children: formatNumber(row.values[s.key]) ?? text.missing }, s.key)))] }, row.id)))) : (_jsx(Table.Row, { children: _jsx(Table.Cell, { colSpan: series.length + 1, children: text.noRows }) })) })] }) }))] })] }));
|
|
241
523
|
};
|
package/dist/choice.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type ComponentSize } from './sizing.js';
|
|
3
3
|
export declare const ChoiceResetPaint: React.Context<number>;
|
|
4
4
|
/** Choice controls follow the surrounding size; `size` sets one control's scale. */
|
|
5
5
|
export type ChoiceSize = {
|
|
6
|
-
size?:
|
|
6
|
+
size?: ComponentSize;
|
|
7
7
|
};
|
package/dist/class-names.d.ts
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
1
|
/** Joins class names, skipping empty ones. */
|
|
2
2
|
export declare function cx(...classes: (string | false | null | undefined)[]): string;
|
|
3
|
-
/** The data-size value: a deprecated short spelling (sm, md, lg, icon-sm, icon-xs,
|
|
4
|
-
* icon-lg) renders as its full name, so both spellings meet the same CSS; other values
|
|
5
|
-
* such as xs or icon pass through. */
|
|
6
|
-
export declare function sizeName<Size extends string>(size?: Size): import("./size.js").ComponentSize | Size | "icon-small" | "icon-large" | undefined;
|
package/dist/class-names.js
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
// Class and data attribute helpers, with no React: server components use them too.
|
|
2
|
-
import { normalizeSize } from './size.js';
|
|
3
1
|
/** Joins class names, skipping empty ones. */
|
|
4
2
|
export function cx(...classes) {
|
|
5
3
|
return classes.filter(Boolean).join(' ');
|
|
6
4
|
}
|
|
7
|
-
/** The data-size value: a deprecated short spelling (sm, md, lg, icon-sm, icon-xs,
|
|
8
|
-
* icon-lg) renders as its full name, so both spellings meet the same CSS; other values
|
|
9
|
-
* such as xs or icon pass through. */
|
|
10
|
-
export function sizeName(size) {
|
|
11
|
-
return size === 'sm' || size === 'md' || size === 'lg'
|
|
12
|
-
? normalizeSize(size)
|
|
13
|
-
: size === 'icon-sm' || size === 'icon-xs'
|
|
14
|
-
? 'icon-small'
|
|
15
|
-
: size === 'icon-lg'
|
|
16
|
-
? 'icon-large'
|
|
17
|
-
: size;
|
|
18
|
-
}
|
package/dist/code.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { Button } from './button.js';
|
|
2
3
|
export type CodeBlockProps = Omit<React.ComponentPropsWithoutRef<'div'>, 'children' | 'onCopy'> & {
|
|
3
4
|
code: string;
|
|
4
5
|
label?: string;
|
|
@@ -6,12 +7,6 @@ export type CodeBlockProps = Omit<React.ComponentPropsWithoutRef<'div'>, 'childr
|
|
|
6
7
|
wrap?: boolean;
|
|
7
8
|
/** The copy button's text: the action, then the result. */
|
|
8
9
|
labels?: Partial<CodeBlockLabels>;
|
|
9
|
-
/** @deprecated Use `labels.copy`. Removed in a later minor. */
|
|
10
|
-
copyLabel?: string;
|
|
11
|
-
/** @deprecated Use `labels.copied`. Removed in a later minor. */
|
|
12
|
-
copiedLabel?: string;
|
|
13
|
-
/** @deprecated Use `labels.copyError`. Removed in a later minor. */
|
|
14
|
-
copyErrorLabel?: string;
|
|
15
10
|
onCopy?: (code: string) => void | Promise<void>;
|
|
16
11
|
};
|
|
17
12
|
export type CodeBlockLabels = {
|
|
@@ -19,6 +14,23 @@ export type CodeBlockLabels = {
|
|
|
19
14
|
copied: string;
|
|
20
15
|
copyError: string;
|
|
21
16
|
};
|
|
22
|
-
export
|
|
17
|
+
export type CopyButtonLabels = CodeBlockLabels;
|
|
18
|
+
export type CopyButtonProps = Omit<React.ComponentPropsWithoutRef<typeof Button>, 'children' | 'onClick' | 'onCopy' | 'value'> & {
|
|
19
|
+
/** What to copy: the text, or a function that returns it when pressed. */
|
|
20
|
+
value: string | (() => string | Promise<string>);
|
|
21
|
+
/** The button's text: the action, then the result. */
|
|
22
|
+
labels?: Partial<CopyButtonLabels>;
|
|
23
|
+
/** Copies instead of the clipboard write; throw to report a failure. */
|
|
24
|
+
onCopy?: (text: string) => void | Promise<void>;
|
|
25
|
+
/** Hears each outcome, such as to select the source after a failure. */
|
|
26
|
+
onResult?: (result: 'copied' | 'failed') => void;
|
|
27
|
+
};
|
|
28
|
+
/** Copies text and says what happened in its own label: the result replaces the
|
|
29
|
+
* action in place, as a polite live region, then the action returns after 2s.
|
|
30
|
+
* The button is as wide as its longest label, so nothing around it moves. */
|
|
31
|
+
export declare const CopyButton: ({ value, labels, onCopy, onResult, variant, size, className, ref, ...props }: CopyButtonProps & {
|
|
32
|
+
ref?: React.Ref<HTMLButtonElement>;
|
|
33
|
+
}) => React.JSX.Element;
|
|
34
|
+
export declare const CodeBlock: ({ code, label, language, wrap, labels, onCopy, className, ref, ...props }: CodeBlockProps & {
|
|
23
35
|
ref?: React.Ref<HTMLDivElement>;
|
|
24
36
|
}) => React.JSX.Element;
|