@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,229 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface TimeRange {
|
|
3
|
+
preset: string;
|
|
4
|
+
start: Date;
|
|
5
|
+
end: Date;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type TimeRangePreset = { id: string; label: string } & (
|
|
9
|
+
{ duration: number } | { range: (now: Date) => { start: Date; end: Date } }
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const day = 86_400_000;
|
|
13
|
+
|
|
14
|
+
export const defaultTimeRangePresets: readonly TimeRangePreset[] = [
|
|
15
|
+
{ id: "24h", label: "24 hours", duration: day },
|
|
16
|
+
{ id: "7d", label: "7 days", duration: 7 * day },
|
|
17
|
+
{ id: "30d", label: "30 days", duration: 30 * day },
|
|
18
|
+
{ id: "90d", label: "90 days", duration: 90 * day },
|
|
19
|
+
{
|
|
20
|
+
id: "12m",
|
|
21
|
+
label: "12 months",
|
|
22
|
+
range: (now) => {
|
|
23
|
+
const start = new Date(now);
|
|
24
|
+
start.setFullYear(start.getFullYear() - 1);
|
|
25
|
+
return { start, end: new Date(now) };
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function rangeFor(preset: TimeRangePreset, now: Date): TimeRange {
|
|
31
|
+
const { start, end } =
|
|
32
|
+
"range" in preset
|
|
33
|
+
? preset.range(now)
|
|
34
|
+
: {
|
|
35
|
+
start: new Date(now.getTime() - preset.duration),
|
|
36
|
+
end: new Date(now),
|
|
37
|
+
};
|
|
38
|
+
return { preset: preset.id, start, end };
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<script lang="ts">
|
|
43
|
+
import {
|
|
44
|
+
type DateValue,
|
|
45
|
+
fromDate,
|
|
46
|
+
getLocalTimeZone,
|
|
47
|
+
toCalendarDate,
|
|
48
|
+
today,
|
|
49
|
+
} from "@internationalized/date";
|
|
50
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
51
|
+
|
|
52
|
+
import { Button } from "../ui/button/index.js";
|
|
53
|
+
import * as Drawer from "../ui/drawer/index.js";
|
|
54
|
+
import * as Popover from "../ui/popover/index.js";
|
|
55
|
+
import { RangeCalendar } from "../ui/range-calendar/index.js";
|
|
56
|
+
import * as ToggleGroup from "../ui/toggle-group/index.js";
|
|
57
|
+
import { cn } from "../../utils.js";
|
|
58
|
+
|
|
59
|
+
const defaultMessages = {
|
|
60
|
+
label: "Time range",
|
|
61
|
+
custom: "Custom",
|
|
62
|
+
customTitle: "Custom range",
|
|
63
|
+
apply: "Apply",
|
|
64
|
+
cancel: "Cancel",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
interface Props {
|
|
68
|
+
presets?: readonly TimeRangePreset[];
|
|
69
|
+
value?: TimeRange;
|
|
70
|
+
onchange?: (value: TimeRange) => void;
|
|
71
|
+
messages?: Partial<typeof defaultMessages>;
|
|
72
|
+
class?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let {
|
|
76
|
+
presets = defaultTimeRangePresets,
|
|
77
|
+
value = $bindable(initialRange()),
|
|
78
|
+
onchange,
|
|
79
|
+
messages = {},
|
|
80
|
+
class: className,
|
|
81
|
+
}: Props = $props();
|
|
82
|
+
|
|
83
|
+
function initialRange(): TimeRange {
|
|
84
|
+
const now = new Date();
|
|
85
|
+
const preset = presets.find((entry) => entry.id === "30d") ?? presets[0];
|
|
86
|
+
return preset
|
|
87
|
+
? rangeFor(preset, now)
|
|
88
|
+
: { preset: "", start: now, end: now };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const timeZone = getLocalTimeZone();
|
|
92
|
+
const desktop = new MediaQuery("(min-width: 768px)");
|
|
93
|
+
const dayFormat = new Intl.DateTimeFormat(undefined, {
|
|
94
|
+
day: "numeric",
|
|
95
|
+
month: "short",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let open = $state(false);
|
|
99
|
+
let draft = $state<{
|
|
100
|
+
start: DateValue | undefined;
|
|
101
|
+
end: DateValue | undefined;
|
|
102
|
+
}>({ start: undefined, end: undefined });
|
|
103
|
+
let placeholder = $state<DateValue>(today(timeZone));
|
|
104
|
+
|
|
105
|
+
const text = $derived({ ...defaultMessages, ...messages });
|
|
106
|
+
const custom = $derived(value.preset === "custom");
|
|
107
|
+
const customLabel = $derived(
|
|
108
|
+
custom ? dayFormat.formatRange(value.start, value.end) : text.custom,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
function commit(next: TimeRange) {
|
|
112
|
+
value = next;
|
|
113
|
+
onchange?.(next);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function choose(id: string) {
|
|
117
|
+
const preset = presets.find((entry) => entry.id === id);
|
|
118
|
+
if (preset && id !== value.preset) {
|
|
119
|
+
commit(rangeFor(preset, new Date()));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resetDraft() {
|
|
124
|
+
const end = toCalendarDate(fromDate(value.end, timeZone));
|
|
125
|
+
draft = { start: toCalendarDate(fromDate(value.start, timeZone)), end };
|
|
126
|
+
placeholder = desktop.current ? end.subtract({ months: 1 }) : end;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function apply() {
|
|
130
|
+
if (!draft.start || !draft.end) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
commit({
|
|
134
|
+
preset: "custom",
|
|
135
|
+
start: draft.start.toDate(timeZone),
|
|
136
|
+
end: new Date(draft.end.add({ days: 1 }).toDate(timeZone).getTime() - 1),
|
|
137
|
+
});
|
|
138
|
+
open = false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const itemClass =
|
|
142
|
+
"text-muted-foreground hover:text-foreground data-[state=on]:bg-background data-[state=on]:text-foreground h-11 rounded-full px-4 hover:bg-transparent data-[state=on]:shadow-sm md:h-8 md:px-3";
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
{#snippet calendar(months: number)}
|
|
146
|
+
<RangeCalendar
|
|
147
|
+
bind:value={draft}
|
|
148
|
+
bind:placeholder
|
|
149
|
+
numberOfMonths={months}
|
|
150
|
+
maxValue={today(timeZone)}
|
|
151
|
+
class="mx-auto"
|
|
152
|
+
/>
|
|
153
|
+
{/snippet}
|
|
154
|
+
|
|
155
|
+
{#snippet actions()}
|
|
156
|
+
<Button variant="outline" class="h-11 md:h-9" onclick={() => (open = false)}
|
|
157
|
+
>{text.cancel}</Button
|
|
158
|
+
>
|
|
159
|
+
<Button
|
|
160
|
+
class="h-11 md:h-9"
|
|
161
|
+
disabled={!draft.start || !draft.end}
|
|
162
|
+
onclick={apply}>{text.apply}</Button
|
|
163
|
+
>
|
|
164
|
+
{/snippet}
|
|
165
|
+
|
|
166
|
+
<div class={cn("no-scrollbar w-full overflow-x-auto", className)}>
|
|
167
|
+
<div class="bg-muted inline-flex items-center gap-1 rounded-full p-1">
|
|
168
|
+
<ToggleGroup.Root
|
|
169
|
+
type="single"
|
|
170
|
+
spacing={1}
|
|
171
|
+
aria-label={text.label}
|
|
172
|
+
bind:value={() => value.preset, choose}
|
|
173
|
+
>
|
|
174
|
+
{#each presets as preset (preset.id)}
|
|
175
|
+
<ToggleGroup.Item value={preset.id} class={itemClass}
|
|
176
|
+
>{preset.label}</ToggleGroup.Item
|
|
177
|
+
>
|
|
178
|
+
{/each}
|
|
179
|
+
</ToggleGroup.Root>
|
|
180
|
+
{#if desktop.current}
|
|
181
|
+
<Popover.Root bind:open>
|
|
182
|
+
<Popover.Trigger
|
|
183
|
+
class={cn(
|
|
184
|
+
itemClass,
|
|
185
|
+
"text-sm font-medium whitespace-nowrap",
|
|
186
|
+
custom && "bg-background text-foreground shadow-sm",
|
|
187
|
+
)}
|
|
188
|
+
onclick={resetDraft}>{customLabel}</Popover.Trigger
|
|
189
|
+
>
|
|
190
|
+
<Popover.Content align="end" class="w-auto">
|
|
191
|
+
<Popover.Header>
|
|
192
|
+
<Popover.Title>{text.customTitle}</Popover.Title>
|
|
193
|
+
</Popover.Header>
|
|
194
|
+
{@render calendar(2)}
|
|
195
|
+
<div class="flex justify-end gap-2">
|
|
196
|
+
{@render actions()}
|
|
197
|
+
</div>
|
|
198
|
+
</Popover.Content>
|
|
199
|
+
</Popover.Root>
|
|
200
|
+
{:else}
|
|
201
|
+
<button
|
|
202
|
+
type="button"
|
|
203
|
+
class={cn(
|
|
204
|
+
itemClass,
|
|
205
|
+
"text-sm font-medium whitespace-nowrap",
|
|
206
|
+
custom && "bg-background text-foreground shadow-sm",
|
|
207
|
+
)}
|
|
208
|
+
aria-haspopup="dialog"
|
|
209
|
+
onclick={() => {
|
|
210
|
+
resetDraft();
|
|
211
|
+
open = true;
|
|
212
|
+
}}>{customLabel}</button
|
|
213
|
+
>
|
|
214
|
+
<Drawer.Root bind:open>
|
|
215
|
+
<Drawer.Content>
|
|
216
|
+
<Drawer.Header class="px-5 text-left">
|
|
217
|
+
<Drawer.Title>{text.customTitle}</Drawer.Title>
|
|
218
|
+
</Drawer.Header>
|
|
219
|
+
<div class="px-5">
|
|
220
|
+
{@render calendar(1)}
|
|
221
|
+
</div>
|
|
222
|
+
<Drawer.Footer class="pb-safe-bottom grid grid-cols-2 gap-2 px-5">
|
|
223
|
+
{@render actions()}
|
|
224
|
+
</Drawer.Footer>
|
|
225
|
+
</Drawer.Content>
|
|
226
|
+
</Drawer.Root>
|
|
227
|
+
{/if}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface TimeRange {
|
|
2
|
+
preset: string;
|
|
3
|
+
start: Date;
|
|
4
|
+
end: Date;
|
|
5
|
+
}
|
|
6
|
+
export type TimeRangePreset = {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
} & ({
|
|
10
|
+
duration: number;
|
|
11
|
+
} | {
|
|
12
|
+
range: (now: Date) => {
|
|
13
|
+
start: Date;
|
|
14
|
+
end: Date;
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
export declare const defaultTimeRangePresets: readonly TimeRangePreset[];
|
|
18
|
+
export declare function rangeFor(preset: TimeRangePreset, now: Date): TimeRange;
|
|
19
|
+
declare const TimeRangePicker: import("svelte").Component<{
|
|
20
|
+
presets?: readonly TimeRangePreset[];
|
|
21
|
+
value?: TimeRange;
|
|
22
|
+
onchange?: (value: TimeRange) => void;
|
|
23
|
+
messages?: Partial<{
|
|
24
|
+
label: string;
|
|
25
|
+
custom: string;
|
|
26
|
+
customTitle: string;
|
|
27
|
+
apply: string;
|
|
28
|
+
cancel: string;
|
|
29
|
+
}>;
|
|
30
|
+
class?: string;
|
|
31
|
+
}, {}, "value">;
|
|
32
|
+
type TimeRangePicker = ReturnType<typeof TimeRangePicker>;
|
|
33
|
+
export default TimeRangePicker;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
caption: string;
|
|
4
|
+
dateHeading: string;
|
|
5
|
+
names: string[];
|
|
6
|
+
rows: { time: number; date: string; values: string[] }[];
|
|
7
|
+
visible: boolean;
|
|
8
|
+
id: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { caption, dateHeading, names, rows, visible, id }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
{id}
|
|
16
|
+
class={visible ? "mt-3 max-h-72 overflow-auto rounded-xl border" : "sr-only"}
|
|
17
|
+
>
|
|
18
|
+
<table class="w-full text-sm">
|
|
19
|
+
<caption class="sr-only">{caption}</caption>
|
|
20
|
+
<thead class="bg-muted sticky top-0">
|
|
21
|
+
<tr>
|
|
22
|
+
<th scope="col" class="px-3 py-2 text-left font-medium"
|
|
23
|
+
>{dateHeading}</th
|
|
24
|
+
>
|
|
25
|
+
{#each names as name, index (index)}
|
|
26
|
+
<th scope="col" class="px-3 py-2 text-right font-medium">{name}</th>
|
|
27
|
+
{/each}
|
|
28
|
+
</tr>
|
|
29
|
+
</thead>
|
|
30
|
+
<tbody>
|
|
31
|
+
{#each rows as row (row.time)}
|
|
32
|
+
<tr class="border-t">
|
|
33
|
+
<th scope="row" class="px-3 py-1.5 text-left font-normal"
|
|
34
|
+
>{row.date}</th
|
|
35
|
+
>
|
|
36
|
+
{#each row.values as value, index (index)}
|
|
37
|
+
<td class="px-3 py-1.5 text-right tabular">{value}</td>
|
|
38
|
+
{/each}
|
|
39
|
+
</tr>
|
|
40
|
+
{/each}
|
|
41
|
+
</tbody>
|
|
42
|
+
</table>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
caption: string;
|
|
3
|
+
dateHeading: string;
|
|
4
|
+
names: string[];
|
|
5
|
+
rows: {
|
|
6
|
+
time: number;
|
|
7
|
+
date: string;
|
|
8
|
+
values: string[];
|
|
9
|
+
}[];
|
|
10
|
+
visible: boolean;
|
|
11
|
+
id: string;
|
|
12
|
+
}
|
|
13
|
+
declare const DataTable: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type DataTable = ReturnType<typeof DataTable>;
|
|
15
|
+
export default DataTable;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
entries: { name: string; color: string }[];
|
|
4
|
+
kind: "line" | "area" | "bar";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { entries, kind }: Props = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<ul class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
|
|
11
|
+
{#each entries as entry, index (index)}
|
|
12
|
+
<li class="flex items-center gap-2">
|
|
13
|
+
<span
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
class="shrink-0 {kind === 'line'
|
|
16
|
+
? 'h-0.5 w-4 rounded-full'
|
|
17
|
+
: 'size-2.5 rounded-[3px]'}"
|
|
18
|
+
style:background-color={entry.color}
|
|
19
|
+
></span>
|
|
20
|
+
{entry.name}
|
|
21
|
+
</li>
|
|
22
|
+
{/each}
|
|
23
|
+
</ul>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
areaPath,
|
|
4
|
+
linePath,
|
|
5
|
+
type Position,
|
|
6
|
+
} from "./scale.js";
|
|
7
|
+
|
|
8
|
+
interface LineGeometry {
|
|
9
|
+
color: string;
|
|
10
|
+
segments: Position[][];
|
|
11
|
+
end: (Position & { label: string }) | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface BarGeometry {
|
|
15
|
+
color: string;
|
|
16
|
+
bars: { path: string; column: number }[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
kind: "line" | "area" | "bar";
|
|
21
|
+
lines: LineGeometry[];
|
|
22
|
+
bars: BarGeometry[];
|
|
23
|
+
baseline: number;
|
|
24
|
+
activeColumn: number | null;
|
|
25
|
+
activeDots: (Position & { color: string })[];
|
|
26
|
+
directLabels: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
kind,
|
|
31
|
+
lines,
|
|
32
|
+
bars,
|
|
33
|
+
baseline,
|
|
34
|
+
activeColumn,
|
|
35
|
+
activeDots,
|
|
36
|
+
directLabels,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
{#if kind === "bar"}
|
|
41
|
+
{#each bars as entry, seriesIndex (seriesIndex)}
|
|
42
|
+
<g style:fill={entry.color}>
|
|
43
|
+
{#each entry.bars as bar (bar.column)}
|
|
44
|
+
<path
|
|
45
|
+
d={bar.path}
|
|
46
|
+
class="transition-opacity duration-150"
|
|
47
|
+
opacity={activeColumn === null || activeColumn === bar.column
|
|
48
|
+
? 1
|
|
49
|
+
: 0.45}
|
|
50
|
+
/>
|
|
51
|
+
{/each}
|
|
52
|
+
</g>
|
|
53
|
+
{/each}
|
|
54
|
+
{:else}
|
|
55
|
+
{#each lines as entry, seriesIndex (seriesIndex)}
|
|
56
|
+
<g style:color={entry.color}>
|
|
57
|
+
{#each entry.segments as segment, segmentIndex (segmentIndex)}
|
|
58
|
+
{#if segment.length > 1}
|
|
59
|
+
{#if kind === "area"}
|
|
60
|
+
<path
|
|
61
|
+
d={areaPath(segment, baseline)}
|
|
62
|
+
fill="currentColor"
|
|
63
|
+
fill-opacity="0.1"
|
|
64
|
+
/>
|
|
65
|
+
{/if}
|
|
66
|
+
<path
|
|
67
|
+
d={linePath(segment)}
|
|
68
|
+
fill="none"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
stroke-width="2"
|
|
71
|
+
stroke-linejoin="round"
|
|
72
|
+
stroke-linecap="round"
|
|
73
|
+
data-segment
|
|
74
|
+
/>
|
|
75
|
+
{:else if segment[0]}
|
|
76
|
+
<circle
|
|
77
|
+
cx={segment[0].x}
|
|
78
|
+
cy={segment[0].y}
|
|
79
|
+
r="4"
|
|
80
|
+
fill="currentColor"
|
|
81
|
+
class="stroke-background"
|
|
82
|
+
stroke-width="2"
|
|
83
|
+
data-segment
|
|
84
|
+
/>
|
|
85
|
+
{/if}
|
|
86
|
+
{/each}
|
|
87
|
+
{#if entry.end}
|
|
88
|
+
<circle
|
|
89
|
+
cx={entry.end.x}
|
|
90
|
+
cy={entry.end.y}
|
|
91
|
+
r="4"
|
|
92
|
+
fill="currentColor"
|
|
93
|
+
class="stroke-background"
|
|
94
|
+
stroke-width="2"
|
|
95
|
+
/>
|
|
96
|
+
{#if directLabels}
|
|
97
|
+
<text
|
|
98
|
+
x={entry.end.x + 9}
|
|
99
|
+
y={entry.end.y}
|
|
100
|
+
dominant-baseline="middle"
|
|
101
|
+
class="fill-foreground text-[11px] font-medium tabular"
|
|
102
|
+
>{entry.end.label}</text
|
|
103
|
+
>
|
|
104
|
+
{/if}
|
|
105
|
+
{/if}
|
|
106
|
+
</g>
|
|
107
|
+
{/each}
|
|
108
|
+
{/if}
|
|
109
|
+
{#each activeDots as dot, index (index)}
|
|
110
|
+
<circle
|
|
111
|
+
cx={dot.x}
|
|
112
|
+
cy={dot.y}
|
|
113
|
+
r="5"
|
|
114
|
+
style:fill={dot.color}
|
|
115
|
+
class="stroke-background pointer-events-none"
|
|
116
|
+
stroke-width="2"
|
|
117
|
+
/>
|
|
118
|
+
{/each}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Position } from "./scale.js";
|
|
2
|
+
interface LineGeometry {
|
|
3
|
+
color: string;
|
|
4
|
+
segments: Position[][];
|
|
5
|
+
end: (Position & {
|
|
6
|
+
label: string;
|
|
7
|
+
}) | null;
|
|
8
|
+
}
|
|
9
|
+
interface BarGeometry {
|
|
10
|
+
color: string;
|
|
11
|
+
bars: {
|
|
12
|
+
path: string;
|
|
13
|
+
column: number;
|
|
14
|
+
}[];
|
|
15
|
+
}
|
|
16
|
+
interface Props {
|
|
17
|
+
kind: "line" | "area" | "bar";
|
|
18
|
+
lines: LineGeometry[];
|
|
19
|
+
bars: BarGeometry[];
|
|
20
|
+
baseline: number;
|
|
21
|
+
activeColumn: number | null;
|
|
22
|
+
activeDots: (Position & {
|
|
23
|
+
color: string;
|
|
24
|
+
})[];
|
|
25
|
+
directLabels: boolean;
|
|
26
|
+
}
|
|
27
|
+
declare const Marks: import("svelte").Component<Props, {}, "">;
|
|
28
|
+
type Marks = ReturnType<typeof Marks>;
|
|
29
|
+
export default Marks;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const defaultMessages: {
|
|
2
|
+
empty: string;
|
|
3
|
+
noValue: string;
|
|
4
|
+
date: string;
|
|
5
|
+
peak: string;
|
|
6
|
+
average: string;
|
|
7
|
+
showTable: string;
|
|
8
|
+
hideTable: string;
|
|
9
|
+
keyboardHint: string;
|
|
10
|
+
summary: (title: string, start: string, end: string) => string;
|
|
11
|
+
seriesSummary: (name: string, low: string, high: string, latest: string) => string;
|
|
12
|
+
};
|
|
13
|
+
export type TimeSeriesChartMessages = typeof defaultMessages;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const defaultMessages = {
|
|
2
|
+
empty: "No data in this range yet.",
|
|
3
|
+
noValue: "No data",
|
|
4
|
+
date: "Date",
|
|
5
|
+
peak: "Peak",
|
|
6
|
+
average: "Average",
|
|
7
|
+
showTable: "Show data table",
|
|
8
|
+
hideTable: "Hide data table",
|
|
9
|
+
keyboardHint: "Use the arrow keys to read each value.",
|
|
10
|
+
summary: (title, start, end) => `${title}, from ${start} to ${end}.`,
|
|
11
|
+
seriesSummary: (name, low, high, latest) => `${name}: low ${low}, high ${high}, latest ${latest}.`,
|
|
12
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Row {
|
|
3
|
+
name: string;
|
|
4
|
+
color: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
label: string;
|
|
10
|
+
rows: Row[];
|
|
11
|
+
placement: "floating" | "above";
|
|
12
|
+
x: number;
|
|
13
|
+
width: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { label, rows, placement, x, width }: Props = $props();
|
|
17
|
+
|
|
18
|
+
const flip = $derived(x > width / 2);
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
data-readout
|
|
23
|
+
aria-hidden="true"
|
|
24
|
+
class="bg-popover text-popover-foreground pointer-events-none absolute z-10 rounded-xl border px-3 py-2 text-xs shadow-md {placement ===
|
|
25
|
+
'above'
|
|
26
|
+
? 'inset-x-0 bottom-full mb-2 flex flex-wrap items-baseline gap-x-4 gap-y-1'
|
|
27
|
+
: 'top-0 grid gap-1 whitespace-nowrap'}"
|
|
28
|
+
style:left={placement === "floating" && !flip ? `${x + 12}px` : undefined}
|
|
29
|
+
style:right={placement === "floating" && flip
|
|
30
|
+
? `${width - x + 12}px`
|
|
31
|
+
: undefined}
|
|
32
|
+
>
|
|
33
|
+
<p class="text-muted-foreground">{label}</p>
|
|
34
|
+
{#each rows as row, index (index)}
|
|
35
|
+
<p class="flex items-center gap-2">
|
|
36
|
+
<span
|
|
37
|
+
class="h-0.5 w-3 shrink-0 rounded-full"
|
|
38
|
+
style:background-color={row.color}
|
|
39
|
+
></span>
|
|
40
|
+
<span class="font-semibold tabular">{row.value}</span>
|
|
41
|
+
<span class="text-muted-foreground">{row.name}</span>
|
|
42
|
+
</p>
|
|
43
|
+
{/each}
|
|
44
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface Row {
|
|
2
|
+
name: string;
|
|
3
|
+
color: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
interface Props {
|
|
7
|
+
label: string;
|
|
8
|
+
rows: Row[];
|
|
9
|
+
placement: "floating" | "above";
|
|
10
|
+
x: number;
|
|
11
|
+
width: number;
|
|
12
|
+
}
|
|
13
|
+
declare const Readout: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Readout = ReturnType<typeof Readout>;
|
|
15
|
+
export default Readout;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface TimePoint {
|
|
2
|
+
at: Date;
|
|
3
|
+
value: number | null;
|
|
4
|
+
}
|
|
5
|
+
export interface TimeSeries {
|
|
6
|
+
name: string;
|
|
7
|
+
points: TimePoint[];
|
|
8
|
+
}
|
|
9
|
+
export interface Position {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function niceStep(span: number, count: number): number;
|
|
14
|
+
export declare function niceTicks(minimum: number, maximum: number, count: number): number[];
|
|
15
|
+
export declare function scaleLinear(domain: [number, number], range: [number, number]): (value: number) => number;
|
|
16
|
+
export declare function columnsOf(series: TimeSeries[]): number[];
|
|
17
|
+
export declare function valueMap(series: TimeSeries): Map<number, number | null>;
|
|
18
|
+
export declare function valueExtent(series: TimeSeries[]): [number, number] | null;
|
|
19
|
+
export declare function segmentsOf(points: TimePoint[]): TimePoint[][];
|
|
20
|
+
export declare function nearestIndex(times: number[], target: number): number;
|
|
21
|
+
export declare function medianSpacing(times: number[]): number;
|
|
22
|
+
export declare function linePath(positions: Position[]): string;
|
|
23
|
+
export declare function areaPath(positions: Position[], baseline: number): string;
|
|
24
|
+
export declare function barPath(left: number, width: number, top: number, baseline: number): string;
|