@orochibraru/ui 1.0.0 → 1.0.1
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 +84 -3
- package/dist/components/blocks/accent-toggle.svelte +7 -3
- package/dist/components/blocks/accent-toggle.svelte.d.ts +1 -1
- package/dist/components/blocks/bottom-nav.svelte +1 -1
- package/dist/components/blocks/command-palette.svelte +268 -0
- package/dist/components/blocks/command-palette.svelte.d.ts +37 -0
- package/dist/components/blocks/error-page.svelte +23 -7
- package/dist/components/blocks/error-page.svelte.d.ts +4 -0
- package/dist/components/blocks/fab.svelte +1 -1
- package/dist/components/blocks/install-prompt.svelte +14 -8
- package/dist/components/blocks/install-prompt.svelte.d.ts +2 -1
- package/dist/components/blocks/music-player/labelled-slider.svelte +3 -12
- package/dist/components/blocks/music-player/labelled-slider.svelte.d.ts +1 -2
- package/dist/components/blocks/music-player.svelte +13 -12
- package/dist/components/blocks/otp-field.svelte +77 -33
- package/dist/components/blocks/otp-field.svelte.d.ts +7 -0
- package/dist/components/blocks/password-input.svelte +56 -16
- package/dist/components/blocks/password-input.svelte.d.ts +3 -0
- package/dist/components/blocks/stepper/stepper-phone-header.svelte +93 -0
- package/dist/components/blocks/stepper/stepper-phone-header.svelte.d.ts +13 -0
- package/dist/components/blocks/stepper.svelte +175 -0
- package/dist/components/blocks/stepper.svelte.d.ts +30 -0
- package/dist/components/blocks/time-range-picker.svelte +229 -0
- package/dist/components/blocks/time-range-picker.svelte.d.ts +33 -0
- package/dist/components/blocks/time-series-chart/data-table.svelte +43 -0
- package/dist/components/blocks/time-series-chart/data-table.svelte.d.ts +15 -0
- package/dist/components/blocks/time-series-chart/legend.svelte +23 -0
- package/dist/components/blocks/time-series-chart/legend.svelte.d.ts +10 -0
- package/dist/components/blocks/time-series-chart/marks.svelte +118 -0
- package/dist/components/blocks/time-series-chart/marks.svelte.d.ts +29 -0
- package/dist/components/blocks/time-series-chart/messages.d.ts +13 -0
- package/dist/components/blocks/time-series-chart/messages.js +12 -0
- package/dist/components/blocks/time-series-chart/readout.svelte +44 -0
- package/dist/components/blocks/time-series-chart/readout.svelte.d.ts +15 -0
- package/dist/components/blocks/time-series-chart/scale.d.ts +24 -0
- package/dist/components/blocks/time-series-chart/scale.js +136 -0
- package/dist/components/blocks/time-series-chart/time.d.ts +7 -0
- package/dist/components/blocks/time-series-chart/time.js +96 -0
- package/dist/components/blocks/time-series-chart.svelte +492 -0
- package/dist/components/blocks/time-series-chart.svelte.d.ts +18 -0
- package/dist/components/blocks/top-loading-bar.svelte +1 -1
- package/dist/components/blocks/video-player/center-controls.svelte +7 -7
- package/dist/components/blocks/video-player/control-bar.svelte +41 -35
- package/dist/components/blocks/video-player/control-button.svelte +1 -1
- package/dist/components/blocks/video-player/option-menu.svelte +1 -1
- package/dist/components/blocks/video-player/scrub-bar.svelte +39 -12
- package/dist/components/blocks/video-player/scrub-bar.svelte.d.ts +2 -0
- package/dist/components/blocks/video-player.svelte +2 -2
- package/dist/components/kit/accordion.svelte +70 -0
- package/dist/components/kit/accordion.svelte.d.ts +37 -0
- package/dist/components/kit/avatar-group.svelte +84 -0
- package/dist/components/kit/avatar-group.svelte.d.ts +14 -0
- package/dist/components/kit/bar-list.svelte +104 -0
- package/dist/components/kit/bar-list.svelte.d.ts +22 -0
- package/dist/components/kit/breadcrumbs.svelte +102 -0
- package/dist/components/kit/breadcrumbs.svelte.d.ts +14 -0
- package/dist/components/kit/checkbox-list.svelte +98 -0
- package/dist/components/kit/checkbox-list.svelte.d.ts +15 -0
- package/dist/components/kit/checkbox.svelte +84 -0
- package/dist/components/kit/checkbox.svelte.d.ts +8 -0
- package/dist/components/kit/confirm-dialog.svelte +127 -0
- package/dist/components/kit/confirm-dialog.svelte.d.ts +19 -0
- package/dist/components/kit/data-table.svelte +280 -0
- package/dist/components/kit/data-table.svelte.d.ts +46 -0
- package/dist/components/kit/date-field.svelte +210 -0
- package/dist/components/kit/date-field.svelte.d.ts +14 -0
- package/dist/components/kit/field-frame.svelte +71 -0
- package/dist/components/kit/field-frame.svelte.d.ts +26 -0
- package/dist/components/kit/fieldset-frame.svelte +69 -0
- package/dist/components/kit/fieldset-frame.svelte.d.ts +14 -0
- package/dist/components/kit/file-dropzone/files.d.ts +14 -0
- package/dist/components/kit/file-dropzone/files.js +69 -0
- package/dist/components/kit/file-dropzone.svelte +253 -0
- package/dist/components/kit/file-dropzone.svelte.d.ts +25 -0
- package/dist/components/kit/form.svelte +335 -0
- package/dist/components/kit/form.svelte.d.ts +61 -0
- package/dist/components/kit/input.svelte +73 -0
- package/dist/components/kit/input.svelte.d.ts +9 -0
- package/dist/components/kit/menu.svelte +163 -0
- package/dist/components/kit/menu.svelte.d.ts +28 -0
- package/dist/components/kit/pagination.svelte +134 -0
- package/dist/components/kit/pagination.svelte.d.ts +17 -0
- package/dist/components/kit/radio-list.svelte +131 -0
- package/dist/components/kit/radio-list.svelte.d.ts +15 -0
- package/dist/components/kit/segmented-control/segment.svelte +47 -0
- package/dist/components/kit/segmented-control/segment.svelte.d.ts +11 -0
- package/dist/components/kit/segmented-control.svelte +70 -0
- package/dist/components/kit/segmented-control.svelte.d.ts +16 -0
- package/dist/components/kit/select.svelte +244 -0
- package/dist/components/kit/select.svelte.d.ts +26 -0
- package/dist/components/kit/status-badge.svelte +54 -0
- package/dist/components/kit/status-badge.svelte.d.ts +31 -0
- package/dist/components/kit/switch.svelte +84 -0
- package/dist/components/kit/switch.svelte.d.ts +8 -0
- package/dist/components/kit/tabs.svelte +86 -0
- package/dist/components/kit/tabs.svelte.d.ts +20 -0
- package/dist/components/kit/textarea.svelte +92 -0
- package/dist/components/kit/textarea.svelte.d.ts +11 -0
- package/dist/components/ui/drawer/drawer-content.svelte +1 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/sonner/sonner.svelte +2 -4
- package/dist/mcp-content.json +1 -1
- package/dist/mcp.js +11 -11
- package/dist/server/hooks.d.ts +9 -0
- package/dist/server/hooks.js +41 -0
- package/dist/server/logger.d.ts +39 -0
- package/dist/server/logger.js +144 -0
- package/dist/server/telemetry.d.ts +23 -0
- package/dist/server/telemetry.js +82 -0
- package/dist/theme.css +18 -6
- package/package.json +40 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type {
|
|
3
|
+
TimePoint,
|
|
4
|
+
TimeSeries,
|
|
5
|
+
} from "./time-series-chart/scale.js";
|
|
6
|
+
export type { TimeSeriesChartMessages } from "./time-series-chart/messages.js";
|
|
7
|
+
|
|
8
|
+
export type TimeSeriesKind = "line" | "area" | "bar";
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import DataTable from "./time-series-chart/data-table.svelte";
|
|
13
|
+
import Legend from "./time-series-chart/legend.svelte";
|
|
14
|
+
import Marks from "./time-series-chart/marks.svelte";
|
|
15
|
+
import {
|
|
16
|
+
defaultMessages,
|
|
17
|
+
type TimeSeriesChartMessages,
|
|
18
|
+
} from "./time-series-chart/messages.js";
|
|
19
|
+
import Readout from "./time-series-chart/readout.svelte";
|
|
20
|
+
import {
|
|
21
|
+
barPath,
|
|
22
|
+
columnsOf,
|
|
23
|
+
medianSpacing,
|
|
24
|
+
nearestIndex,
|
|
25
|
+
niceTicks,
|
|
26
|
+
scaleLinear,
|
|
27
|
+
segmentsOf,
|
|
28
|
+
type TimeSeries,
|
|
29
|
+
valueExtent,
|
|
30
|
+
valueMap,
|
|
31
|
+
} from "./time-series-chart/scale.js";
|
|
32
|
+
import {
|
|
33
|
+
formatInstant,
|
|
34
|
+
formatTick,
|
|
35
|
+
timeTicks,
|
|
36
|
+
} from "./time-series-chart/time.js";
|
|
37
|
+
import { cn } from "../../utils.js";
|
|
38
|
+
|
|
39
|
+
interface Props {
|
|
40
|
+
series: TimeSeries[];
|
|
41
|
+
title: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
kind?: TimeSeriesKind;
|
|
44
|
+
format?: (value: number) => string;
|
|
45
|
+
height?: number;
|
|
46
|
+
messages?: Partial<TimeSeriesChartMessages>;
|
|
47
|
+
class?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
series,
|
|
52
|
+
title,
|
|
53
|
+
description,
|
|
54
|
+
kind = "line",
|
|
55
|
+
format,
|
|
56
|
+
height = 240,
|
|
57
|
+
messages = {},
|
|
58
|
+
class: className,
|
|
59
|
+
}: Props = $props();
|
|
60
|
+
|
|
61
|
+
const id = $props.id();
|
|
62
|
+
const numberFormat = new Intl.NumberFormat();
|
|
63
|
+
const compactFormat = new Intl.NumberFormat(undefined, {
|
|
64
|
+
notation: "compact",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let width = $state(0);
|
|
68
|
+
let activeIndex = $state<number | null>(null);
|
|
69
|
+
let placement = $state<"floating" | "above">("floating");
|
|
70
|
+
let tableVisible = $state(false);
|
|
71
|
+
|
|
72
|
+
const text = $derived({ ...defaultMessages, ...messages });
|
|
73
|
+
const formatValue = $derived(
|
|
74
|
+
format ?? ((value: number) => numberFormat.format(value)),
|
|
75
|
+
);
|
|
76
|
+
const formatAxis = $derived(
|
|
77
|
+
format ?? ((value: number) => compactFormat.format(value)),
|
|
78
|
+
);
|
|
79
|
+
const colors = $derived(
|
|
80
|
+
series.map((_, index) =>
|
|
81
|
+
index < 4 ? `var(--series-${index + 1})` : "var(--series-other)",
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const columns = $derived(columnsOf(series));
|
|
86
|
+
const maps = $derived(series.map((entry) => valueMap(entry)));
|
|
87
|
+
const extent = $derived(valueExtent(series));
|
|
88
|
+
const spacing = $derived(medianSpacing(columns));
|
|
89
|
+
const first = $derived(columns[0] ?? 0);
|
|
90
|
+
const last = $derived(columns.at(-1) ?? 0);
|
|
91
|
+
|
|
92
|
+
const top = 10;
|
|
93
|
+
const bottom = 28;
|
|
94
|
+
const plotHeight = $derived(Math.max(40, height - top - bottom));
|
|
95
|
+
const yTicks = $derived(
|
|
96
|
+
extent
|
|
97
|
+
? niceTicks(
|
|
98
|
+
extent[0],
|
|
99
|
+
extent[1],
|
|
100
|
+
Math.max(2, Math.floor(plotHeight / 44)),
|
|
101
|
+
)
|
|
102
|
+
: [0, 1],
|
|
103
|
+
);
|
|
104
|
+
const y = $derived(
|
|
105
|
+
scaleLinear([yTicks[0] ?? 0, yTicks.at(-1) ?? 1], [top + plotHeight, top]),
|
|
106
|
+
);
|
|
107
|
+
const baseline = $derived(y(0));
|
|
108
|
+
const left = $derived(
|
|
109
|
+
Math.max(...yTicks.map((tick) => formatAxis(tick).length)) * 7 + 10,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const ends = $derived(
|
|
113
|
+
series.map((entry) => {
|
|
114
|
+
const segment = segmentsOf(entry.points).at(-1);
|
|
115
|
+
const point = segment?.at(-1);
|
|
116
|
+
return point && point.value !== null
|
|
117
|
+
? { time: point.at.getTime(), value: point.value }
|
|
118
|
+
: null;
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
const directLabels = $derived.by(() => {
|
|
122
|
+
if (kind === "bar" || series.length > 4 || width < 360) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const heights = ends
|
|
126
|
+
.filter((end) => end !== null)
|
|
127
|
+
.map((end) => y(end.value))
|
|
128
|
+
.sort((above, below) => above - below);
|
|
129
|
+
return heights.every(
|
|
130
|
+
(value, index) => index === 0 || value - (heights[index - 1] ?? 0) >= 14,
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
const right = $derived(
|
|
134
|
+
directLabels
|
|
135
|
+
? Math.max(
|
|
136
|
+
...ends.map((end) => (end ? formatValue(end.value).length : 0)),
|
|
137
|
+
) *
|
|
138
|
+
7 +
|
|
139
|
+
16
|
|
140
|
+
: 8,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const plotWidth = $derived(Math.max(0, width - left - right));
|
|
144
|
+
const slot = $derived(plotWidth / Math.max(1, columns.length));
|
|
145
|
+
const xRange = $derived<[number, number]>(
|
|
146
|
+
kind === "bar"
|
|
147
|
+
? [left + slot / 2, left + plotWidth - slot / 2]
|
|
148
|
+
: [left, left + plotWidth],
|
|
149
|
+
);
|
|
150
|
+
const x = $derived(scaleLinear([first, last], xRange));
|
|
151
|
+
const xTicks = $derived(
|
|
152
|
+
timeTicks(
|
|
153
|
+
new Date(first),
|
|
154
|
+
new Date(last),
|
|
155
|
+
Math.max(2, Math.floor(plotWidth / 84)),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const lines = $derived(
|
|
160
|
+
series.map((entry, index) => {
|
|
161
|
+
const end = ends[index];
|
|
162
|
+
return {
|
|
163
|
+
color: colors[index] ?? "",
|
|
164
|
+
segments: segmentsOf(entry.points).map((segment) =>
|
|
165
|
+
segment.map((point) => ({
|
|
166
|
+
x: x(point.at.getTime()),
|
|
167
|
+
y: y(point.value ?? 0),
|
|
168
|
+
})),
|
|
169
|
+
),
|
|
170
|
+
end: end
|
|
171
|
+
? { x: x(end.time), y: y(end.value), label: formatValue(end.value) }
|
|
172
|
+
: null,
|
|
173
|
+
};
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
const bars = $derived.by(() => {
|
|
177
|
+
if (kind !== "bar") {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
const count = Math.max(1, series.length);
|
|
181
|
+
const gap = slot >= 4 ? 2 : 0;
|
|
182
|
+
const group = Math.min(slot - gap, count * 24 + (count - 1) * gap);
|
|
183
|
+
const barWidth = Math.max(0.5, (group - (count - 1) * gap) / count);
|
|
184
|
+
return series.map((entry, index) => ({
|
|
185
|
+
color: colors[index] ?? "",
|
|
186
|
+
bars: entry.points
|
|
187
|
+
.filter((point) => point.value !== null)
|
|
188
|
+
.map((point) => {
|
|
189
|
+
const column = point.at.getTime();
|
|
190
|
+
const barLeft = x(column) - group / 2 + index * (barWidth + gap);
|
|
191
|
+
return {
|
|
192
|
+
column,
|
|
193
|
+
path: barPath(barLeft, barWidth, y(point.value ?? 0), baseline),
|
|
194
|
+
};
|
|
195
|
+
}),
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const activeColumn = $derived(
|
|
200
|
+
activeIndex === null ? null : (columns[activeIndex] ?? null),
|
|
201
|
+
);
|
|
202
|
+
const activeRows = $derived(
|
|
203
|
+
series.map((entry, index) => {
|
|
204
|
+
const value =
|
|
205
|
+
activeColumn === null ? null : (maps[index]?.get(activeColumn) ?? null);
|
|
206
|
+
return {
|
|
207
|
+
name: entry.name,
|
|
208
|
+
color: colors[index] ?? "",
|
|
209
|
+
raw: value,
|
|
210
|
+
value: value === null ? text.noValue : formatValue(value),
|
|
211
|
+
};
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
const activeDots = $derived(
|
|
215
|
+
activeColumn === null || kind === "bar"
|
|
216
|
+
? []
|
|
217
|
+
: activeRows
|
|
218
|
+
.filter((row) => row.raw !== null)
|
|
219
|
+
.map((row) => ({
|
|
220
|
+
x: x(activeColumn),
|
|
221
|
+
y: y(row.raw ?? 0),
|
|
222
|
+
color: row.color,
|
|
223
|
+
})),
|
|
224
|
+
);
|
|
225
|
+
const activeLabel = $derived(
|
|
226
|
+
activeColumn === null ? "" : formatInstant(new Date(activeColumn), spacing),
|
|
227
|
+
);
|
|
228
|
+
const valueText = $derived(
|
|
229
|
+
activeColumn === null
|
|
230
|
+
? text.keyboardHint
|
|
231
|
+
: `${activeLabel}: ${activeRows.map((row) => `${row.name} ${row.value}`).join(", ")}`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const summary = $derived.by(() => {
|
|
235
|
+
if (!extent) {
|
|
236
|
+
return title;
|
|
237
|
+
}
|
|
238
|
+
const parts = series.map((entry, index) => {
|
|
239
|
+
const values = entry.points
|
|
240
|
+
.map((point) => point.value)
|
|
241
|
+
.filter((value) => value !== null);
|
|
242
|
+
const end = ends[index];
|
|
243
|
+
return values.length && end
|
|
244
|
+
? text.seriesSummary(
|
|
245
|
+
entry.name,
|
|
246
|
+
formatValue(Math.min(...values)),
|
|
247
|
+
formatValue(Math.max(...values)),
|
|
248
|
+
formatValue(end.value),
|
|
249
|
+
)
|
|
250
|
+
: "";
|
|
251
|
+
});
|
|
252
|
+
return [
|
|
253
|
+
text.summary(
|
|
254
|
+
title,
|
|
255
|
+
formatInstant(new Date(first), spacing),
|
|
256
|
+
formatInstant(new Date(last), spacing),
|
|
257
|
+
),
|
|
258
|
+
...parts,
|
|
259
|
+
]
|
|
260
|
+
.filter(Boolean)
|
|
261
|
+
.join(" ");
|
|
262
|
+
});
|
|
263
|
+
const headline = $derived.by(() => {
|
|
264
|
+
const values = (series[0]?.points ?? [])
|
|
265
|
+
.map((point) => point.value)
|
|
266
|
+
.filter((value) => value !== null);
|
|
267
|
+
if (series.length !== 1 || !values.length) {
|
|
268
|
+
return "";
|
|
269
|
+
}
|
|
270
|
+
const average =
|
|
271
|
+
values.reduce((total, value) => total + value, 0) / values.length;
|
|
272
|
+
return `${text.peak} ${formatValue(Math.max(...values))} · ${text.average} ${formatValue(Math.round(average * 10) / 10)}`;
|
|
273
|
+
});
|
|
274
|
+
const tableRows = $derived(
|
|
275
|
+
columns.map((time) => ({
|
|
276
|
+
time,
|
|
277
|
+
date: formatInstant(new Date(time), spacing),
|
|
278
|
+
values: maps.map((map) => {
|
|
279
|
+
const value = map.get(time) ?? null;
|
|
280
|
+
return value === null ? text.noValue : formatValue(value);
|
|
281
|
+
}),
|
|
282
|
+
})),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
function indexAt(event: PointerEvent): number {
|
|
286
|
+
const target = event.currentTarget as HTMLElement;
|
|
287
|
+
const offset = event.clientX - target.getBoundingClientRect().left + left;
|
|
288
|
+
const [rangeStart, rangeEnd] = xRange;
|
|
289
|
+
const ratio =
|
|
290
|
+
rangeEnd === rangeStart
|
|
291
|
+
? 0
|
|
292
|
+
: (offset - rangeStart) / (rangeEnd - rangeStart);
|
|
293
|
+
return nearestIndex(columns, first + ratio * (last - first));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function onpointer(event: PointerEvent) {
|
|
297
|
+
placement = event.pointerType === "touch" ? "above" : "floating";
|
|
298
|
+
activeIndex = indexAt(event);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function onkeydown(event: KeyboardEvent) {
|
|
302
|
+
const current = activeIndex ?? columns.length - 1;
|
|
303
|
+
const next = {
|
|
304
|
+
ArrowLeft: current - 1,
|
|
305
|
+
ArrowDown: current - 1,
|
|
306
|
+
ArrowRight: current + 1,
|
|
307
|
+
ArrowUp: current + 1,
|
|
308
|
+
Home: 0,
|
|
309
|
+
End: columns.length - 1,
|
|
310
|
+
}[event.key];
|
|
311
|
+
if (next === undefined) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
placement = "floating";
|
|
316
|
+
activeIndex = Math.min(columns.length - 1, Math.max(0, next));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function onfocus() {
|
|
320
|
+
if (activeIndex === null) {
|
|
321
|
+
placement = "floating";
|
|
322
|
+
activeIndex = columns.length - 1;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
</script>
|
|
326
|
+
|
|
327
|
+
<figure class={cn("time-series-chart w-full min-w-0", className)}>
|
|
328
|
+
<figcaption
|
|
329
|
+
class="mb-3 flex flex-wrap items-baseline justify-between gap-x-4"
|
|
330
|
+
>
|
|
331
|
+
<span class="grid gap-0.5">
|
|
332
|
+
<span class="text-sm font-medium">{title}</span>
|
|
333
|
+
{#if description}
|
|
334
|
+
<span class="text-muted-foreground text-sm">{description}</span>
|
|
335
|
+
{/if}
|
|
336
|
+
</span>
|
|
337
|
+
{#if headline}
|
|
338
|
+
<span class="text-muted-foreground text-xs tabular">{headline}</span>
|
|
339
|
+
{/if}
|
|
340
|
+
</figcaption>
|
|
341
|
+
{#if !extent}
|
|
342
|
+
<div
|
|
343
|
+
class="text-muted-foreground flex items-center justify-center rounded-xl border border-dashed text-sm"
|
|
344
|
+
style:height="{height}px"
|
|
345
|
+
>
|
|
346
|
+
{text.empty}
|
|
347
|
+
</div>
|
|
348
|
+
{:else}
|
|
349
|
+
<div class="relative" style:height="{height}px" bind:clientWidth={width}>
|
|
350
|
+
{#if width > 0}
|
|
351
|
+
<svg
|
|
352
|
+
{width}
|
|
353
|
+
{height}
|
|
354
|
+
viewBox="0 0 {width} {height}"
|
|
355
|
+
role="img"
|
|
356
|
+
aria-label={summary}
|
|
357
|
+
class="block overflow-visible"
|
|
358
|
+
>
|
|
359
|
+
{#each yTicks as tick (tick)}
|
|
360
|
+
{@const level = Math.round(y(tick)) + 0.5}
|
|
361
|
+
<line
|
|
362
|
+
x1={left}
|
|
363
|
+
x2={left + plotWidth}
|
|
364
|
+
y1={level}
|
|
365
|
+
y2={level}
|
|
366
|
+
class={tick === 0
|
|
367
|
+
? "stroke-muted-foreground/40"
|
|
368
|
+
: "stroke-border"}
|
|
369
|
+
/>
|
|
370
|
+
<text
|
|
371
|
+
x={left - 8}
|
|
372
|
+
y={level}
|
|
373
|
+
text-anchor="end"
|
|
374
|
+
dominant-baseline="middle"
|
|
375
|
+
class="fill-muted-foreground text-[11px] tabular"
|
|
376
|
+
>{formatAxis(tick)}</text
|
|
377
|
+
>
|
|
378
|
+
{/each}
|
|
379
|
+
{#each xTicks.ticks as tick (tick.getTime())}
|
|
380
|
+
{@const position = x(tick.getTime())}
|
|
381
|
+
<text
|
|
382
|
+
x={position}
|
|
383
|
+
y={top + plotHeight + 18}
|
|
384
|
+
text-anchor={position - left < 24
|
|
385
|
+
? "start"
|
|
386
|
+
: left + plotWidth - position < 24
|
|
387
|
+
? "end"
|
|
388
|
+
: "middle"}
|
|
389
|
+
class="fill-muted-foreground text-[11px] tabular"
|
|
390
|
+
>{formatTick(tick, xTicks.unit)}</text
|
|
391
|
+
>
|
|
392
|
+
{/each}
|
|
393
|
+
{#if activeColumn !== null && kind !== "bar"}
|
|
394
|
+
{@const position = Math.round(x(activeColumn)) + 0.5}
|
|
395
|
+
<line
|
|
396
|
+
x1={position}
|
|
397
|
+
x2={position}
|
|
398
|
+
y1={top}
|
|
399
|
+
y2={top + plotHeight}
|
|
400
|
+
class="stroke-muted-foreground/60"
|
|
401
|
+
/>
|
|
402
|
+
{/if}
|
|
403
|
+
<Marks
|
|
404
|
+
{kind}
|
|
405
|
+
{lines}
|
|
406
|
+
{bars}
|
|
407
|
+
{baseline}
|
|
408
|
+
{activeColumn}
|
|
409
|
+
{activeDots}
|
|
410
|
+
{directLabels}
|
|
411
|
+
/>
|
|
412
|
+
</svg>
|
|
413
|
+
<div
|
|
414
|
+
role="slider"
|
|
415
|
+
tabindex="0"
|
|
416
|
+
aria-label={title}
|
|
417
|
+
aria-valuemin={0}
|
|
418
|
+
aria-valuemax={Math.max(0, columns.length - 1)}
|
|
419
|
+
aria-valuenow={activeIndex ?? Math.max(0, columns.length - 1)}
|
|
420
|
+
aria-valuetext={valueText}
|
|
421
|
+
class="focus-visible:ring-ring/50 absolute cursor-crosshair touch-pan-y rounded-md outline-none select-none focus-visible:ring-3"
|
|
422
|
+
style:left="{left}px"
|
|
423
|
+
style:top="{top}px"
|
|
424
|
+
style:width="{plotWidth}px"
|
|
425
|
+
style:height="{plotHeight}px"
|
|
426
|
+
onpointerdown={onpointer}
|
|
427
|
+
onpointermove={onpointer}
|
|
428
|
+
onpointerleave={() => (activeIndex = null)}
|
|
429
|
+
onpointercancel={() => (activeIndex = null)}
|
|
430
|
+
{onkeydown}
|
|
431
|
+
{onfocus}
|
|
432
|
+
onblur={() => (activeIndex = null)}
|
|
433
|
+
></div>
|
|
434
|
+
{#if activeColumn !== null}
|
|
435
|
+
<Readout
|
|
436
|
+
label={activeLabel}
|
|
437
|
+
rows={activeRows}
|
|
438
|
+
{placement}
|
|
439
|
+
x={x(activeColumn)}
|
|
440
|
+
{width}
|
|
441
|
+
/>
|
|
442
|
+
{/if}
|
|
443
|
+
{/if}
|
|
444
|
+
</div>
|
|
445
|
+
<div class="mt-3 flex flex-wrap items-center justify-between gap-2">
|
|
446
|
+
{#if series.length > 1}
|
|
447
|
+
<Legend
|
|
448
|
+
entries={series.map((entry, index) => ({
|
|
449
|
+
name: entry.name,
|
|
450
|
+
color: colors[index] ?? "",
|
|
451
|
+
}))}
|
|
452
|
+
{kind}
|
|
453
|
+
/>
|
|
454
|
+
{:else}
|
|
455
|
+
<span></span>
|
|
456
|
+
{/if}
|
|
457
|
+
<button
|
|
458
|
+
type="button"
|
|
459
|
+
aria-expanded={tableVisible}
|
|
460
|
+
aria-controls="{id}-table"
|
|
461
|
+
class="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 -mr-2 h-11 rounded-md px-2 text-xs outline-none focus-visible:ring-3 md:h-8"
|
|
462
|
+
onclick={() => (tableVisible = !tableVisible)}
|
|
463
|
+
>{tableVisible ? text.hideTable : text.showTable}</button
|
|
464
|
+
>
|
|
465
|
+
</div>
|
|
466
|
+
<DataTable
|
|
467
|
+
id="{id}-table"
|
|
468
|
+
caption={title}
|
|
469
|
+
dateHeading={text.date}
|
|
470
|
+
names={series.map((entry) => entry.name)}
|
|
471
|
+
rows={tableRows}
|
|
472
|
+
visible={tableVisible}
|
|
473
|
+
/>
|
|
474
|
+
{/if}
|
|
475
|
+
</figure>
|
|
476
|
+
|
|
477
|
+
<style>
|
|
478
|
+
.time-series-chart {
|
|
479
|
+
--series-1: oklch(0.58 0.16 var(--hue));
|
|
480
|
+
--series-2: oklch(0.75 0.16 calc(var(--hue) + 255));
|
|
481
|
+
--series-3: oklch(0.5 0.16 calc(var(--hue) + 75));
|
|
482
|
+
--series-4: oklch(0.69 0.16 calc(var(--hue) + 135));
|
|
483
|
+
--series-other: var(--muted-foreground);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
:global(.dark) .time-series-chart {
|
|
487
|
+
--series-1: oklch(0.66 0.16 var(--hue));
|
|
488
|
+
--series-2: oklch(0.66 0.16 calc(var(--hue) + 255));
|
|
489
|
+
--series-3: oklch(0.5 0.16 calc(var(--hue) + 75));
|
|
490
|
+
--series-4: oklch(0.66 0.16 calc(var(--hue) + 135));
|
|
491
|
+
}
|
|
492
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type { TimePoint, TimeSeries, } from "./time-series-chart/scale.js";
|
|
2
|
+
export type { TimeSeriesChartMessages } from "./time-series-chart/messages.js";
|
|
3
|
+
export type TimeSeriesKind = "line" | "area" | "bar";
|
|
4
|
+
import { type TimeSeriesChartMessages } from "./time-series-chart/messages.js";
|
|
5
|
+
import { type TimeSeries } from "./time-series-chart/scale.js";
|
|
6
|
+
interface Props {
|
|
7
|
+
series: TimeSeries[];
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
kind?: TimeSeriesKind;
|
|
11
|
+
format?: (value: number) => string;
|
|
12
|
+
height?: number;
|
|
13
|
+
messages?: Partial<TimeSeriesChartMessages>;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
declare const TimeSeriesChart: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type TimeSeriesChart = ReturnType<typeof TimeSeriesChart>;
|
|
18
|
+
export default TimeSeriesChart;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
const { player, paused, text }: Props = $props();
|
|
18
18
|
|
|
19
19
|
const side =
|
|
20
|
-
"
|
|
20
|
+
"pointer-events-auto flex size-14 items-center justify-center rounded-full bg-black/25 text-white ring-1 ring-white/10 backdrop-blur-md transition hover:scale-105 hover:bg-black/45 active:scale-90 [&_svg]:size-7";
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
23
|
<div class="flex items-center justify-center gap-8 sm:gap-12">
|
|
@@ -27,20 +27,20 @@
|
|
|
27
27
|
class={side}
|
|
28
28
|
onclick={() => player.seek(-SEEK_SECONDS)}
|
|
29
29
|
>
|
|
30
|
-
<RotateCcwIcon
|
|
30
|
+
<RotateCcwIcon />
|
|
31
31
|
</button>
|
|
32
32
|
<button
|
|
33
33
|
type="button"
|
|
34
34
|
aria-label={player.ended ? text.replay : paused ? text.play : text.pause}
|
|
35
|
-
class="
|
|
35
|
+
class="pointer-events-auto flex size-16 items-center justify-center rounded-full bg-black/40 text-white shadow-lg ring-1 ring-white/20 backdrop-blur-md transition hover:scale-105 hover:bg-black/55 active:scale-90 sm:size-18 [&_svg]:size-11 sm:[&_svg]:size-12"
|
|
36
36
|
onclick={player.togglePlay}
|
|
37
37
|
>
|
|
38
38
|
{#if player.ended}
|
|
39
|
-
<RotateCcwIcon
|
|
39
|
+
<RotateCcwIcon />
|
|
40
40
|
{:else if paused}
|
|
41
|
-
<PlayIcon class="
|
|
41
|
+
<PlayIcon class="fill-current" />
|
|
42
42
|
{:else}
|
|
43
|
-
<PauseIcon class="
|
|
43
|
+
<PauseIcon class="fill-current" />
|
|
44
44
|
{/if}
|
|
45
45
|
</button>
|
|
46
46
|
<button
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
class={side}
|
|
50
50
|
onclick={() => player.seek(SEEK_SECONDS)}
|
|
51
51
|
>
|
|
52
|
-
<RotateCwIcon
|
|
52
|
+
<RotateCwIcon />
|
|
53
53
|
</button>
|
|
54
54
|
</div>
|
|
@@ -58,10 +58,12 @@
|
|
|
58
58
|
</script>
|
|
59
59
|
|
|
60
60
|
<div
|
|
61
|
-
class="
|
|
61
|
+
class="pointer-events-auto mx-3 mb-3 flex flex-col gap-2 rounded-3xl bg-linear-to-b from-white/12 to-black/55 px-4 pt-3.5 pb-2.5 text-white shadow-[inset_0_1px_0_rgb(255_255_255/0.15),0_24px_60px_-12px_rgb(0_0_0/0.7)] ring-1 ring-white/10 backdrop-blur-2xl backdrop-saturate-150 sm:mx-auto sm:mb-4 sm:w-[min(64rem,calc(100%-3rem))] sm:px-5"
|
|
62
62
|
>
|
|
63
|
-
<div
|
|
64
|
-
|
|
63
|
+
<div
|
|
64
|
+
class="flex items-center gap-3 text-xs font-medium text-white/80 tabular"
|
|
65
|
+
>
|
|
66
|
+
<span class="w-12 shrink-0">{formatTime(currentTime)}</span>
|
|
65
67
|
<div class="min-w-0 flex-1">
|
|
66
68
|
<ScrubBar
|
|
67
69
|
{currentTime}
|
|
@@ -72,54 +74,58 @@
|
|
|
72
74
|
onseek={player.seekTo}
|
|
73
75
|
/>
|
|
74
76
|
</div>
|
|
75
|
-
<span class="
|
|
77
|
+
<span class="w-12 shrink-0 text-right text-white/55"
|
|
76
78
|
>-{formatTime(Math.max(0, player.duration - currentTime))}</span
|
|
77
79
|
>
|
|
78
80
|
</div>
|
|
79
|
-
<div class="flex items-center gap-
|
|
81
|
+
<div class="flex items-center gap-2 sm:gap-3">
|
|
80
82
|
<ControlButton
|
|
81
83
|
label={playLabel}
|
|
82
84
|
shortcut="K"
|
|
83
85
|
{portalTarget}
|
|
84
86
|
onclick={player.togglePlay}
|
|
85
|
-
class="bg-
|
|
87
|
+
class="size-10 bg-white text-black shadow-[0_0_20px_rgb(255_255_255/0.25)] transition-transform hover:scale-105 hover:bg-white/85 hover:text-black md:size-10 dark:hover:bg-white/85"
|
|
86
88
|
>
|
|
87
89
|
{#if player.ended}
|
|
88
90
|
<RotateCcwIcon />
|
|
89
91
|
{:else if paused}
|
|
90
|
-
<PlayIcon class="
|
|
92
|
+
<PlayIcon class="fill-current" />
|
|
91
93
|
{:else}
|
|
92
|
-
<PauseIcon class="
|
|
94
|
+
<PauseIcon class="fill-current" />
|
|
93
95
|
{/if}
|
|
94
96
|
</ControlButton>
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
<div class="flex items-center gap-1.5">
|
|
98
|
+
<ControlButton
|
|
99
|
+
label={silent ? text.unmute : text.mute}
|
|
100
|
+
shortcut="M"
|
|
101
|
+
{portalTarget}
|
|
102
|
+
onclick={player.toggleMute}
|
|
103
|
+
>
|
|
104
|
+
{#if silent}
|
|
105
|
+
<VolumeXIcon />
|
|
106
|
+
{:else if player.volume < 0.5}
|
|
107
|
+
<Volume1Icon />
|
|
108
|
+
{:else}
|
|
109
|
+
<Volume2Icon />
|
|
110
|
+
{/if}
|
|
111
|
+
</ControlButton>
|
|
112
|
+
<input
|
|
113
|
+
type="range"
|
|
114
|
+
min="0"
|
|
115
|
+
max="1"
|
|
116
|
+
step="0.05"
|
|
117
|
+
value={player.muted ? 0 : player.volume}
|
|
118
|
+
aria-label={text.volume}
|
|
119
|
+
oninput={(event) => {
|
|
120
|
+
player.volume = Number(event.currentTarget.value);
|
|
121
|
+
player.muted = player.volume === 0;
|
|
122
|
+
}}
|
|
123
|
+
class="pointer-fine:block hidden h-1 w-20 cursor-pointer appearance-none rounded-full bg-white/20 accent-white [&::-moz-range-thumb]:size-3 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-white [&::-webkit-slider-thumb]:size-3 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
<div
|
|
127
|
+
class="ml-auto flex items-center gap-0.5 rounded-full bg-white/5 p-1 ring-1 ring-white/10 sm:gap-1"
|
|
100
128
|
>
|
|
101
|
-
{#if silent}
|
|
102
|
-
<VolumeXIcon />
|
|
103
|
-
{:else if player.volume < 0.5}
|
|
104
|
-
<Volume1Icon />
|
|
105
|
-
{:else}
|
|
106
|
-
<Volume2Icon />
|
|
107
|
-
{/if}
|
|
108
|
-
</ControlButton>
|
|
109
|
-
<input
|
|
110
|
-
type="range"
|
|
111
|
-
min="0"
|
|
112
|
-
max="1"
|
|
113
|
-
step="0.05"
|
|
114
|
-
value={player.muted ? 0 : player.volume}
|
|
115
|
-
aria-label={text.volume}
|
|
116
|
-
oninput={(event) => {
|
|
117
|
-
player.volume = Number(event.currentTarget.value);
|
|
118
|
-
player.muted = player.volume === 0;
|
|
119
|
-
}}
|
|
120
|
-
class="accent-primary pointer-fine:block hidden h-1 w-20 cursor-pointer"
|
|
121
|
-
/>
|
|
122
|
-
<div class="ml-auto flex items-center gap-0.5">
|
|
123
129
|
{#if subtitles.length > 0}
|
|
124
130
|
<OptionMenu
|
|
125
131
|
label={text.subtitles}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
aria-pressed={pressed}
|
|
38
38
|
{onclick}
|
|
39
39
|
class={cn(
|
|
40
|
-
"size-11 rounded-full md:size-9 [&_svg:not([class*='size-'])]:size-5",
|
|
40
|
+
"size-11 rounded-full hover:bg-white/15 hover:text-white md:size-9 dark:hover:bg-white/15 [&_svg:not([class*='size-'])]:size-5",
|
|
41
41
|
className,
|
|
42
42
|
)}
|
|
43
43
|
>
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
size="icon"
|
|
44
44
|
aria-label={label}
|
|
45
45
|
class={cn(
|
|
46
|
-
"size-11 rounded-full md:size-9 [&_svg]:size-5",
|
|
46
|
+
"size-11 rounded-full hover:bg-white/15 hover:text-white md:size-9 dark:hover:bg-white/15 [&_svg:not([class*='size-'])]:size-5",
|
|
47
47
|
active && "text-primary",
|
|
48
48
|
)}
|
|
49
49
|
>
|