@noahbaumgartner/svelte-ui 0.0.5 → 0.0.7
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/LICENSE +21 -0
- package/README.md +20 -5
- package/dist/components/accordion/Accordion.svelte +16 -6
- package/dist/components/accordion/Accordion.svelte.d.ts +3 -1
- package/dist/components/accordion/AccordionItem.svelte +106 -67
- package/dist/components/accordion/AccordionItem.svelte.d.ts +5 -1
- package/dist/components/alert-dialog/AlertDialog.svelte +108 -76
- package/dist/components/alert-dialog/AlertDialog.svelte.d.ts +5 -2
- package/dist/components/avatar/Avatar.svelte +71 -49
- package/dist/components/avatar/Avatar.svelte.d.ts +5 -2
- package/dist/components/avatar-group/AvatarGroup.svelte +72 -45
- package/dist/components/avatar-group/AvatarGroup.svelte.d.ts +5 -1
- package/dist/components/badge/Badge.svelte +50 -28
- package/dist/components/badge/Badge.svelte.d.ts +6 -4
- package/dist/components/breadcrumb/Breadcrumb.svelte +21 -17
- package/dist/components/breadcrumb/Breadcrumb.svelte.d.ts +3 -1
- package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte +17 -6
- package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte.d.ts +5 -2
- package/dist/components/breadcrumb/BreadcrumbItem.svelte +75 -36
- package/dist/components/breadcrumb/BreadcrumbItem.svelte.d.ts +8 -5
- package/dist/components/breadcrumb/BreadcrumbSeparator.svelte +32 -9
- package/dist/components/breadcrumb/BreadcrumbSeparator.svelte.d.ts +10 -17
- package/dist/components/button/Button.svelte +206 -168
- package/dist/components/button/Button.svelte.d.ts +9 -5
- package/dist/components/button-group/ButtonGroup.svelte +52 -55
- package/dist/components/button-group/ButtonGroup.svelte.d.ts +3 -1
- package/dist/components/calendar/Calendar.svelte +232 -144
- package/dist/components/calendar/Calendar.svelte.d.ts +19 -2
- package/dist/components/calendar/dates.d.ts +4 -1
- package/dist/components/calendar/dates.js +8 -2
- package/dist/components/card/Card.svelte +48 -32
- package/dist/components/card/Card.svelte.d.ts +3 -1
- package/dist/components/card/CardAction.svelte +11 -7
- package/dist/components/card/CardAction.svelte.d.ts +3 -1
- package/dist/components/card/CardContent.svelte +8 -4
- package/dist/components/card/CardContent.svelte.d.ts +3 -1
- package/dist/components/card/CardDescription.svelte +11 -7
- package/dist/components/card/CardDescription.svelte.d.ts +3 -1
- package/dist/components/card/CardFooter.svelte +15 -11
- package/dist/components/card/CardFooter.svelte.d.ts +3 -1
- package/dist/components/card/CardHeader.svelte +15 -11
- package/dist/components/card/CardHeader.svelte.d.ts +3 -1
- package/dist/components/card/CardTitle.svelte +11 -7
- package/dist/components/card/CardTitle.svelte.d.ts +3 -1
- package/dist/components/checkbox/Checkbox.svelte +152 -61
- package/dist/components/checkbox/Checkbox.svelte.d.ts +5 -2
- package/dist/components/color-picker/ColorPicker.svelte +131 -78
- package/dist/components/color-picker/ColorPicker.svelte.d.ts +10 -1
- package/dist/components/context-menu/ContextMenu.svelte +78 -61
- package/dist/components/context-menu/ContextMenu.svelte.d.ts +5 -2
- package/dist/components/context-menu/ContextMenuInfo.svelte +91 -0
- package/dist/components/context-menu/ContextMenuInfo.svelte.d.ts +19 -0
- package/dist/components/context-menu/ContextMenuItem.svelte +102 -57
- package/dist/components/context-menu/ContextMenuItem.svelte.d.ts +10 -5
- package/dist/components/context-menu/ContextMenuLabel.svelte +28 -0
- package/dist/components/context-menu/ContextMenuLabel.svelte.d.ts +10 -0
- package/dist/components/context-menu/ContextMenuSeparator.svelte +29 -0
- package/dist/components/context-menu/ContextMenuSeparator.svelte.d.ts +8 -0
- package/dist/components/dialog/Dialog.svelte +149 -108
- package/dist/components/dialog/Dialog.svelte.d.ts +7 -2
- package/dist/components/drawer/Drawer.svelte +240 -189
- package/dist/components/drawer/Drawer.svelte.d.ts +7 -2
- package/dist/components/empty-state/EmptyState.svelte +67 -47
- package/dist/components/empty-state/EmptyState.svelte.d.ts +7 -4
- package/dist/components/entry/Entry.svelte +53 -42
- package/dist/components/entry/Entry.svelte.d.ts +2 -1
- package/dist/components/field/Field.svelte +66 -45
- package/dist/components/field/Field.svelte.d.ts +2 -1
- package/dist/components/field/FieldContent.svelte +11 -8
- package/dist/components/field/FieldContent.svelte.d.ts +2 -1
- package/dist/components/field/FieldDescription.svelte +18 -15
- package/dist/components/field/FieldDescription.svelte.d.ts +2 -1
- package/dist/components/field/FieldError.svelte +22 -13
- package/dist/components/field/FieldError.svelte.d.ts +2 -1
- package/dist/components/field/FieldGroup.svelte +15 -12
- package/dist/components/field/FieldGroup.svelte.d.ts +2 -1
- package/dist/components/field/FieldLegend.svelte +23 -14
- package/dist/components/field/FieldLegend.svelte.d.ts +2 -1
- package/dist/components/field/FieldSeparator.svelte +12 -9
- package/dist/components/field/FieldSeparator.svelte.d.ts +2 -1
- package/dist/components/field/FieldSet.svelte +18 -15
- package/dist/components/field/FieldSet.svelte.d.ts +2 -1
- package/dist/components/footer/Footer.svelte +63 -47
- package/dist/components/footer/Footer.svelte.d.ts +2 -1
- package/dist/components/footer/FooterColumn.svelte +17 -14
- package/dist/components/footer/FooterColumn.svelte.d.ts +2 -1
- package/dist/components/image/Image.svelte +61 -0
- package/dist/components/image/Image.svelte.d.ts +18 -0
- package/dist/components/input/Input.svelte +307 -208
- package/dist/components/input/Input.svelte.d.ts +12 -1
- package/dist/components/input-group/InputGroup.svelte +101 -61
- package/dist/components/input-group/InputGroup.svelte.d.ts +2 -1
- package/dist/components/input-group/InputGroupAddon.svelte +58 -49
- package/dist/components/kbd/Kbd.svelte +39 -30
- package/dist/components/kbd/Kbd.svelte.d.ts +2 -1
- package/dist/components/kbd/KbdGroup.svelte +15 -12
- package/dist/components/kbd/KbdGroup.svelte.d.ts +2 -1
- package/dist/components/label/Label.svelte +47 -28
- package/dist/components/label/Label.svelte.d.ts +2 -1
- package/dist/components/link/Link.svelte +71 -17
- package/dist/components/link/Link.svelte.d.ts +2 -1
- package/dist/components/mesh-gradient/MeshGradient.svelte +33 -22
- package/dist/components/mesh-gradient/MeshGradient.svelte.d.ts +2 -3
- package/dist/components/navbar/Navbar.svelte +149 -99
- package/dist/components/navbar/Navbar.svelte.d.ts +4 -2
- package/dist/components/navigation-menu/NavigationMenu.svelte +37 -20
- package/dist/components/navigation-menu/NavigationMenu.svelte.d.ts +4 -2
- package/dist/components/navigation-menu/NavigationMenuItem.svelte +131 -89
- package/dist/components/navigation-menu/NavigationMenuItem.svelte.d.ts +4 -2
- package/dist/components/navigation-menu/NavigationMenuLink.svelte +90 -51
- package/dist/components/navigation-menu/NavigationMenuLink.svelte.d.ts +7 -5
- package/dist/components/number-stepper/NumberStepper.svelte +39 -20
- package/dist/components/number-stepper/NumberStepper.svelte.d.ts +6 -1
- package/dist/components/popover/Popover.svelte +85 -65
- package/dist/components/popover/Popover.svelte.d.ts +5 -2
- package/dist/components/preview-card/PreviewCard.svelte +116 -98
- package/dist/components/preview-card/PreviewCard.svelte.d.ts +9 -6
- package/dist/components/progress/Progress.svelte +61 -38
- package/dist/components/progress/Progress.svelte.d.ts +2 -1
- package/dist/components/radio-group/RadioGroup.svelte +24 -19
- package/dist/components/radio-group/RadioGroup.svelte.d.ts +2 -1
- package/dist/components/radio-group/RadioGroupItem.svelte +101 -54
- package/dist/components/radio-group/RadioGroupItem.svelte.d.ts +2 -1
- package/dist/components/section/Section.svelte +114 -0
- package/dist/components/section/Section.svelte.d.ts +22 -0
- package/dist/components/select/Select.svelte +216 -133
- package/dist/components/select/Select.svelte.d.ts +11 -2
- package/dist/components/separator/Separator.svelte +21 -10
- package/dist/components/separator/Separator.svelte.d.ts +2 -1
- package/dist/components/sidebar/Sidebar.svelte +142 -108
- package/dist/components/sidebar/Sidebar.svelte.d.ts +6 -1
- package/dist/components/sidebar/SidebarContent.svelte +15 -12
- package/dist/components/sidebar/SidebarContent.svelte.d.ts +2 -1
- package/dist/components/sidebar/SidebarFooter.svelte +10 -7
- package/dist/components/sidebar/SidebarFooter.svelte.d.ts +2 -1
- package/dist/components/sidebar/SidebarGroup.svelte +35 -25
- package/dist/components/sidebar/SidebarGroup.svelte.d.ts +2 -1
- package/dist/components/sidebar/SidebarHeader.svelte +21 -16
- package/dist/components/sidebar/SidebarHeader.svelte.d.ts +6 -1
- package/dist/components/sidebar/SidebarInset.svelte +11 -8
- package/dist/components/sidebar/SidebarInset.svelte.d.ts +2 -1
- package/dist/components/sidebar/SidebarMenu.svelte +13 -10
- package/dist/components/sidebar/SidebarMenu.svelte.d.ts +2 -1
- package/dist/components/sidebar/SidebarMenuItem.svelte +195 -114
- package/dist/components/sidebar/SidebarMenuItem.svelte.d.ts +11 -5
- package/dist/components/sidebar/SidebarProvider.svelte +18 -9
- package/dist/components/sidebar/SidebarProvider.svelte.d.ts +2 -1
- package/dist/components/sidebar/SidebarSeparator.svelte +6 -1
- package/dist/components/sidebar/SidebarSeparator.svelte.d.ts +6 -17
- package/dist/components/sidebar/SidebarTrigger.svelte +17 -5
- package/dist/components/sidebar/SidebarTrigger.svelte.d.ts +4 -2
- package/dist/components/skeleton/Skeleton.svelte +26 -16
- package/dist/components/skeleton/Skeleton.svelte.d.ts +4 -2
- package/dist/components/spinner/Spinner.svelte +71 -46
- package/dist/components/spinner/Spinner.svelte.d.ts +2 -1
- package/dist/components/switch/Switch.svelte +126 -65
- package/dist/components/switch/Switch.svelte.d.ts +4 -2
- package/dist/components/table/Table.svelte +135 -76
- package/dist/components/table/Table.svelte.d.ts +12 -2
- package/dist/components/tabs/Tabs.svelte +173 -116
- package/dist/components/tabs/Tabs.svelte.d.ts +10 -8
- package/dist/components/textarea/Textarea.svelte +76 -42
- package/dist/components/textarea/Textarea.svelte.d.ts +4 -1
- package/dist/components/time-picker/TimePicker.svelte +214 -85
- package/dist/components/time-picker/TimePicker.svelte.d.ts +18 -1
- package/dist/components/toaster/Toaster.svelte +132 -70
- package/dist/components/toaster/Toaster.svelte.d.ts +18 -2
- package/dist/components/toaster/toast.svelte.d.ts +3 -3
- package/dist/components/toggle/Toggle.svelte +25 -4
- package/dist/components/toggle/Toggle.svelte.d.ts +1 -1
- package/dist/components/tooltip/Tooltip.svelte +87 -58
- package/dist/components/tooltip/Tooltip.svelte.d.ts +4 -2
- package/dist/icon.d.ts +12 -0
- package/dist/icon.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/internal/icons/ArrowDown.svelte +14 -0
- package/dist/internal/icons/ArrowDown.svelte.d.ts +4 -0
- package/dist/internal/icons/ArrowUp.svelte +14 -0
- package/dist/internal/icons/ArrowUp.svelte.d.ts +4 -0
- package/dist/internal/icons/Calendar.svelte +16 -0
- package/dist/internal/icons/Calendar.svelte.d.ts +4 -0
- package/dist/internal/icons/Check.svelte +8 -0
- package/dist/internal/icons/Check.svelte.d.ts +4 -0
- package/dist/internal/icons/ChevronDown.svelte +8 -0
- package/dist/internal/icons/ChevronDown.svelte.d.ts +4 -0
- package/dist/internal/icons/ChevronLeft.svelte +8 -0
- package/dist/internal/icons/ChevronLeft.svelte.d.ts +4 -0
- package/dist/internal/icons/ChevronRight.svelte +8 -0
- package/dist/internal/icons/ChevronRight.svelte.d.ts +4 -0
- package/dist/internal/icons/ChevronUp.svelte +8 -0
- package/dist/internal/icons/ChevronUp.svelte.d.ts +4 -0
- package/dist/internal/icons/ChevronsUpDown.svelte +14 -0
- package/dist/internal/icons/ChevronsUpDown.svelte.d.ts +4 -0
- package/dist/internal/icons/Clock.svelte +14 -0
- package/dist/internal/icons/Clock.svelte.d.ts +4 -0
- package/dist/internal/icons/Ellipsis.svelte +15 -0
- package/dist/internal/icons/Ellipsis.svelte.d.ts +4 -0
- package/dist/internal/icons/Icon.svelte +33 -0
- package/dist/internal/icons/Icon.svelte.d.ts +8 -0
- package/dist/internal/icons/Minus.svelte +8 -0
- package/dist/internal/icons/Minus.svelte.d.ts +4 -0
- package/dist/internal/icons/PanelLeft.svelte +14 -0
- package/dist/internal/icons/PanelLeft.svelte.d.ts +4 -0
- package/dist/internal/icons/Plus.svelte +14 -0
- package/dist/internal/icons/Plus.svelte.d.ts +4 -0
- package/dist/internal/icons/User.svelte +14 -0
- package/dist/internal/icons/User.svelte.d.ts +4 -0
- package/dist/internal/icons/X.svelte +14 -0
- package/dist/internal/icons/X.svelte.d.ts +4 -0
- package/dist/styles/tokens.css +39 -41
- package/package.json +5 -10
- package/dist/icons.d.ts +0 -2
- package/dist/icons.js +0 -4
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type TimePickerLabels = {
|
|
3
|
+
/** Accessible name of the hours column. */
|
|
4
|
+
hours?: string;
|
|
5
|
+
/** Accessible name of the minutes column. */
|
|
6
|
+
minutes?: string;
|
|
7
|
+
/** Accessible name of the AM/PM column. */
|
|
8
|
+
period?: string;
|
|
9
|
+
am?: string;
|
|
10
|
+
pm?: string;
|
|
11
|
+
};
|
|
12
|
+
</script>
|
|
13
|
+
|
|
1
14
|
<script lang="ts">
|
|
2
15
|
import { tick } from 'svelte';
|
|
3
16
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
@@ -7,37 +20,96 @@
|
|
|
7
20
|
value?: string;
|
|
8
21
|
/** Minutes between the offered values. */
|
|
9
22
|
minuteStep?: number;
|
|
23
|
+
/** BCP 47 tag used for numerals, AM/PM text and the 12/24-hour default. Without it the picker shows 24 hours. */
|
|
24
|
+
locale?: string;
|
|
25
|
+
/** Forces 12-hour (`true`) or 24-hour (`false`) columns, overriding the locale. */
|
|
26
|
+
hour12?: boolean;
|
|
27
|
+
/** Accessible names of the columns and the AM/PM text. */
|
|
28
|
+
labels?: TimePickerLabels;
|
|
10
29
|
onselect?: (value: string) => void;
|
|
30
|
+
ref?: HTMLDivElement | null;
|
|
11
31
|
};
|
|
12
32
|
|
|
13
33
|
let {
|
|
14
34
|
value = $bindable(''),
|
|
15
35
|
minuteStep = 1,
|
|
36
|
+
locale,
|
|
37
|
+
hour12,
|
|
38
|
+
labels,
|
|
16
39
|
onselect,
|
|
40
|
+
ref = $bindable(null),
|
|
17
41
|
class: className,
|
|
18
42
|
...rest
|
|
19
43
|
}: Props = $props();
|
|
20
44
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(
|
|
27
|
-
|
|
45
|
+
type Column = 'hour' | 'minute' | 'period';
|
|
46
|
+
type Entry = { id: number; text: string };
|
|
47
|
+
|
|
48
|
+
let twelve = $derived(
|
|
49
|
+
hour12 ??
|
|
50
|
+
(locale !== undefined &&
|
|
51
|
+
['h11', 'h12'].includes(
|
|
52
|
+
new Intl.DateTimeFormat(locale, { hour: 'numeric' }).resolvedOptions().hourCycle ?? ''
|
|
53
|
+
))
|
|
28
54
|
);
|
|
29
55
|
|
|
56
|
+
let format = $derived(
|
|
57
|
+
new Intl.NumberFormat(locale, { minimumIntegerDigits: 2, useGrouping: false }).format
|
|
58
|
+
);
|
|
59
|
+
let formatPlain = $derived(new Intl.NumberFormat(locale, { useGrouping: false }).format);
|
|
60
|
+
|
|
61
|
+
function dayPeriod(hour: number, fallback: string) {
|
|
62
|
+
if (locale === undefined) return fallback;
|
|
63
|
+
return (
|
|
64
|
+
new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true })
|
|
65
|
+
.formatToParts(new Date(2000, 0, 1, hour))
|
|
66
|
+
.find((part) => part.type === 'dayPeriod')?.value ?? fallback
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let hoursLabel = $derived(labels?.hours ?? 'Hours');
|
|
71
|
+
let minutesLabel = $derived(labels?.minutes ?? 'Minutes');
|
|
72
|
+
let periodLabel = $derived(labels?.period ?? 'AM/PM');
|
|
73
|
+
let amLabel = $derived(labels?.am ?? dayPeriod(9, 'AM'));
|
|
74
|
+
let pmLabel = $derived(labels?.pm ?? dayPeriod(15, 'PM'));
|
|
75
|
+
|
|
76
|
+
let hourEntries: Entry[] = $derived(
|
|
77
|
+
twelve
|
|
78
|
+
? Array.from({ length: 12 }, (_, i) => {
|
|
79
|
+
const id = i === 0 ? 12 : i;
|
|
80
|
+
return { id, text: formatPlain(id) };
|
|
81
|
+
})
|
|
82
|
+
: Array.from({ length: 24 }, (_, i) => ({ id: i, text: format(i) }))
|
|
83
|
+
);
|
|
84
|
+
let minuteEntries: Entry[] = $derived(
|
|
85
|
+
Array.from({ length: Math.ceil(60 / Math.max(1, minuteStep)) }, (_, i) => {
|
|
86
|
+
const id = i * Math.max(1, minuteStep);
|
|
87
|
+
return { id, text: format(id) };
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
let periodEntries: Entry[] = $derived([
|
|
91
|
+
{ id: 0, text: amLabel },
|
|
92
|
+
{ id: 1, text: pmLabel }
|
|
93
|
+
]);
|
|
94
|
+
|
|
30
95
|
let hour = $derived(value ? Number(value.slice(0, 2)) : undefined);
|
|
31
96
|
let minute = $derived(value ? Number(value.slice(3, 5)) : undefined);
|
|
97
|
+
let period = $derived(hour === undefined ? undefined : hour >= 12 ? 1 : 0);
|
|
98
|
+
let shownHour = $derived(hour === undefined ? undefined : twelve ? hour % 12 || 12 : hour);
|
|
32
99
|
|
|
33
|
-
let
|
|
34
|
-
let minutesEl: HTMLElement | undefined = $state();
|
|
100
|
+
let columnEls: Partial<Record<Column, HTMLElement>> = $state({});
|
|
35
101
|
|
|
36
102
|
function set(nextHour: number, nextMinute: number) {
|
|
37
|
-
value = `${
|
|
103
|
+
value = `${String(nextHour).padStart(2, '0')}:${String(nextMinute).padStart(2, '0')}`;
|
|
38
104
|
onselect?.(value);
|
|
39
105
|
}
|
|
40
106
|
|
|
107
|
+
function choose(column: Column, id: number) {
|
|
108
|
+
if (column === 'minute') return set(hour ?? 0, id);
|
|
109
|
+
if (column === 'period') return set(((hour ?? 0) % 12) + id * 12, minute ?? 0);
|
|
110
|
+
set(twelve ? (id % 12) + (period ?? 0) * 12 : id, minute ?? 0);
|
|
111
|
+
}
|
|
112
|
+
|
|
41
113
|
function center(column: HTMLElement | undefined) {
|
|
42
114
|
const item = column?.querySelector<HTMLElement>('[tabindex="0"]');
|
|
43
115
|
if (column && item)
|
|
@@ -47,14 +119,16 @@
|
|
|
47
119
|
$effect(() => {
|
|
48
120
|
void value;
|
|
49
121
|
tick().then(() => {
|
|
50
|
-
center(
|
|
51
|
-
center(
|
|
122
|
+
center(columnEls.hour);
|
|
123
|
+
center(columnEls.minute);
|
|
52
124
|
});
|
|
53
125
|
});
|
|
54
126
|
|
|
55
|
-
async function handleKeydown(event: KeyboardEvent, column:
|
|
56
|
-
const list =
|
|
57
|
-
const
|
|
127
|
+
async function handleKeydown(event: KeyboardEvent, column: Column) {
|
|
128
|
+
const list = { hour: hourEntries, minute: minuteEntries, period: periodEntries }[column];
|
|
129
|
+
const selected = { hour: shownHour, minute, period }[column];
|
|
130
|
+
const found = list.findIndex((entry) => entry.id === (selected ?? list[0].id));
|
|
131
|
+
const current = found === -1 ? 0 : found;
|
|
58
132
|
const next = {
|
|
59
133
|
ArrowUp: current - 1,
|
|
60
134
|
ArrowDown: current + 1,
|
|
@@ -64,99 +138,154 @@
|
|
|
64
138
|
if (next === undefined) return;
|
|
65
139
|
|
|
66
140
|
event.preventDefault();
|
|
67
|
-
|
|
68
|
-
if (column === 'hour') set(clamped, minute ?? 0);
|
|
69
|
-
else set(hour ?? 0, clamped);
|
|
141
|
+
choose(column, list[Math.min(list.length - 1, Math.max(0, next))].id);
|
|
70
142
|
await tick();
|
|
71
|
-
(
|
|
72
|
-
?.querySelector<HTMLElement>('[tabindex="0"]')
|
|
73
|
-
?.focus();
|
|
143
|
+
columnEls[column]?.querySelector<HTMLElement>('[tabindex="0"]')?.focus();
|
|
74
144
|
}
|
|
75
145
|
</script>
|
|
76
146
|
|
|
77
|
-
{#snippet columnItems(list:
|
|
78
|
-
{#each list as item, i (item)}
|
|
147
|
+
{#snippet columnItems(list: Entry[], current: number | undefined, column: Column)}
|
|
148
|
+
{#each list as item, i (item.id)}
|
|
79
149
|
<button
|
|
80
150
|
type="button"
|
|
81
|
-
class={['time-picker-item', { 'time-picker-item--selected': item === current }]}
|
|
82
|
-
tabindex={item === current || (current === undefined && i === 0) ? 0 : -1}
|
|
83
|
-
aria-pressed={item === current}
|
|
84
|
-
onclick={() => (column
|
|
151
|
+
class={['time-picker-item', { 'time-picker-item--selected': item.id === current }]}
|
|
152
|
+
tabindex={item.id === current || (current === undefined && i === 0) ? 0 : -1}
|
|
153
|
+
aria-pressed={item.id === current}
|
|
154
|
+
onclick={() => choose(column, item.id)}
|
|
85
155
|
onkeydown={(event) => handleKeydown(event, column)}
|
|
86
156
|
>
|
|
87
|
-
{
|
|
157
|
+
{item.text}
|
|
88
158
|
</button>
|
|
89
159
|
{/each}
|
|
90
160
|
{/snippet}
|
|
91
161
|
|
|
92
|
-
<div {...rest} class={['time-picker', className]}>
|
|
93
|
-
<div class="time-picker-column" role="group" aria-label=
|
|
94
|
-
{@render columnItems(
|
|
162
|
+
<div {...rest} bind:this={ref} class={['time-picker', className]}>
|
|
163
|
+
<div class="time-picker-column" role="group" aria-label={hoursLabel} bind:this={columnEls.hour}>
|
|
164
|
+
{@render columnItems(hourEntries, shownHour, 'hour')}
|
|
95
165
|
</div>
|
|
96
|
-
<div
|
|
97
|
-
|
|
166
|
+
<div
|
|
167
|
+
class="time-picker-column"
|
|
168
|
+
role="group"
|
|
169
|
+
aria-label={minutesLabel}
|
|
170
|
+
bind:this={columnEls.minute}
|
|
171
|
+
>
|
|
172
|
+
{@render columnItems(minuteEntries, minute, 'minute')}
|
|
98
173
|
</div>
|
|
174
|
+
{#if twelve}
|
|
175
|
+
<div
|
|
176
|
+
class="time-picker-column"
|
|
177
|
+
role="group"
|
|
178
|
+
aria-label={periodLabel}
|
|
179
|
+
bind:this={columnEls.period}
|
|
180
|
+
>
|
|
181
|
+
{@render columnItems(periodEntries, period, 'period')}
|
|
182
|
+
</div>
|
|
183
|
+
{/if}
|
|
99
184
|
</div>
|
|
100
185
|
|
|
101
186
|
<style>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
187
|
+
@layer svelte-ui {
|
|
188
|
+
.time-picker {
|
|
189
|
+
display: flex;
|
|
190
|
+
gap: 4px;
|
|
191
|
+
height: 232px;
|
|
192
|
+
font-size: 13px;
|
|
193
|
+
color: var(--color-text);
|
|
194
|
+
user-select: none;
|
|
195
|
+
}
|
|
110
196
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
197
|
+
.time-picker-column {
|
|
198
|
+
display: flex;
|
|
199
|
+
flex-direction: column;
|
|
200
|
+
gap: 2px;
|
|
201
|
+
width: 48px;
|
|
202
|
+
padding: 0 2px;
|
|
203
|
+
overflow-y: auto;
|
|
204
|
+
scrollbar-width: none;
|
|
205
|
+
scroll-snap-type: y proximity;
|
|
206
|
+
}
|
|
121
207
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
208
|
+
.time-picker-column + .time-picker-column {
|
|
209
|
+
padding-left: 6px;
|
|
210
|
+
border-left: 1px solid var(--color-border);
|
|
211
|
+
}
|
|
126
212
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
213
|
+
.time-picker-item {
|
|
214
|
+
flex-shrink: 0;
|
|
215
|
+
height: 28px;
|
|
216
|
+
padding: 0;
|
|
217
|
+
font-family: inherit;
|
|
218
|
+
font-size: 13px;
|
|
219
|
+
font-variant-numeric: tabular-nums;
|
|
220
|
+
color: var(--color-text);
|
|
221
|
+
background-color: transparent;
|
|
222
|
+
border: none;
|
|
223
|
+
border-radius: 8px;
|
|
224
|
+
outline: none;
|
|
225
|
+
cursor: pointer;
|
|
226
|
+
scroll-snap-align: center;
|
|
227
|
+
transition: background-color 200ms ease;
|
|
228
|
+
}
|
|
143
229
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
230
|
+
.time-picker-item:hover {
|
|
231
|
+
background-color: var(--color-surface);
|
|
232
|
+
}
|
|
147
233
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
234
|
+
.time-picker-item:focus-visible {
|
|
235
|
+
outline: 2px solid var(--color-accent);
|
|
236
|
+
outline-offset: -2px;
|
|
237
|
+
}
|
|
152
238
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
239
|
+
.time-picker-item--selected,
|
|
240
|
+
.time-picker-item--selected:hover {
|
|
241
|
+
color: var(--color-accent-foreground);
|
|
242
|
+
background-color: var(--color-accent);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.time-picker-item--selected:focus-visible {
|
|
246
|
+
outline-color: var(--color-accent-foreground);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@media (pointer: coarse) {
|
|
250
|
+
.time-picker-column {
|
|
251
|
+
width: 56px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.time-picker-item {
|
|
255
|
+
min-height: 44px;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@media (forced-colors: active) {
|
|
260
|
+
.time-picker-item {
|
|
261
|
+
color: ButtonText;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.time-picker-item--selected,
|
|
265
|
+
.time-picker-item--selected:hover {
|
|
266
|
+
color: HighlightText;
|
|
267
|
+
background-color: Highlight;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.time-picker-item:hover:not(.time-picker-item--selected) {
|
|
271
|
+
outline: 1px solid Highlight;
|
|
272
|
+
outline-offset: -1px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.time-picker-item:focus-visible,
|
|
276
|
+
.time-picker-item--selected:focus-visible {
|
|
277
|
+
outline: 2px solid CanvasText;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@media (prefers-reduced-motion: reduce) {
|
|
282
|
+
.time-picker-item {
|
|
283
|
+
transition: none;
|
|
284
|
+
}
|
|
158
285
|
|
|
159
|
-
|
|
160
|
-
|
|
286
|
+
.time-picker-column {
|
|
287
|
+
scroll-behavior: auto;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
161
290
|
}
|
|
162
291
|
</style>
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
+
export type TimePickerLabels = {
|
|
2
|
+
/** Accessible name of the hours column. */
|
|
3
|
+
hours?: string;
|
|
4
|
+
/** Accessible name of the minutes column. */
|
|
5
|
+
minutes?: string;
|
|
6
|
+
/** Accessible name of the AM/PM column. */
|
|
7
|
+
period?: string;
|
|
8
|
+
am?: string;
|
|
9
|
+
pm?: string;
|
|
10
|
+
};
|
|
1
11
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
12
|
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'onselect'> & {
|
|
3
13
|
/** Bindable, `HH:MM` in 24-hour time like `<input type="time">`. */
|
|
4
14
|
value?: string;
|
|
5
15
|
/** Minutes between the offered values. */
|
|
6
16
|
minuteStep?: number;
|
|
17
|
+
/** BCP 47 tag used for numerals, AM/PM text and the 12/24-hour default. Without it the picker shows 24 hours. */
|
|
18
|
+
locale?: string;
|
|
19
|
+
/** Forces 12-hour (`true`) or 24-hour (`false`) columns, overriding the locale. */
|
|
20
|
+
hour12?: boolean;
|
|
21
|
+
/** Accessible names of the columns and the AM/PM text. */
|
|
22
|
+
labels?: TimePickerLabels;
|
|
7
23
|
onselect?: (value: string) => void;
|
|
24
|
+
ref?: HTMLDivElement | null;
|
|
8
25
|
};
|
|
9
|
-
declare const TimePicker: import("svelte").Component<Props, {}, "value">;
|
|
26
|
+
declare const TimePicker: import("svelte").Component<Props, {}, "ref" | "value">;
|
|
10
27
|
type TimePicker = ReturnType<typeof TimePicker>;
|
|
11
28
|
export default TimePicker;
|
|
@@ -1,16 +1,49 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type ToasterLabels = {
|
|
3
|
+
/** Accessible name of the notification region. */
|
|
4
|
+
region?: string;
|
|
5
|
+
/** Hint shown on a toast that closes when clicked. */
|
|
6
|
+
dismiss?: string;
|
|
7
|
+
};
|
|
8
|
+
</script>
|
|
9
|
+
|
|
1
10
|
<script lang="ts">
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
13
|
import type { Attachment } from 'svelte/attachments';
|
|
3
14
|
import { fly } from 'svelte/transition';
|
|
4
15
|
import Button from '../button/Button.svelte';
|
|
5
16
|
import Spinner from '../spinner/Spinner.svelte';
|
|
6
17
|
import { toast, toasts, type ToastData } from './toast.svelte.js';
|
|
7
18
|
|
|
8
|
-
type Props = {
|
|
9
|
-
/** Accessible name of the notification region. */
|
|
19
|
+
type Props = Omit<HTMLAttributes<HTMLOListElement>, 'children'> & {
|
|
20
|
+
/** Accessible name of the notification region. Shorthand for `labels.region`. */
|
|
10
21
|
label?: string;
|
|
22
|
+
/** Texts of the region. */
|
|
23
|
+
labels?: ToasterLabels;
|
|
24
|
+
/** BCP 47 tag of the toast messages; sets `lang` on the region. */
|
|
25
|
+
locale?: string;
|
|
26
|
+
/** Replaces the message of each toast. */
|
|
27
|
+
message?: Snippet<[toast: ToastData]>;
|
|
28
|
+
ref?: HTMLOListElement | null;
|
|
11
29
|
};
|
|
12
30
|
|
|
13
|
-
let {
|
|
31
|
+
let {
|
|
32
|
+
label,
|
|
33
|
+
labels,
|
|
34
|
+
locale,
|
|
35
|
+
message,
|
|
36
|
+
ref = $bindable(null),
|
|
37
|
+
class: className,
|
|
38
|
+
...rest
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const regionLabel = $derived(label ?? labels?.region ?? 'Notifications');
|
|
42
|
+
const dismissLabel = $derived(labels?.dismiss ?? 'Dismiss');
|
|
43
|
+
|
|
44
|
+
function reducedMotion() {
|
|
45
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
46
|
+
}
|
|
14
47
|
|
|
15
48
|
// Hovering or focusing the toast pauses its timer; they restart on leave.
|
|
16
49
|
let paused = $state(false);
|
|
@@ -23,11 +56,13 @@
|
|
|
23
56
|
};
|
|
24
57
|
|
|
25
58
|
const exitDuration = 150;
|
|
59
|
+
const enterDuration = 200;
|
|
26
60
|
|
|
27
61
|
// Waits for the outgoing toast, still in the DOM, to slide out first.
|
|
28
62
|
function enter(node: HTMLElement) {
|
|
29
63
|
const replacing = node.parentElement!.childElementCount > 1;
|
|
30
|
-
return fly(node, { y:
|
|
64
|
+
if (reducedMotion()) return fly(node, { y: 0, duration: 0 });
|
|
65
|
+
return fly(node, { y: 24, duration: enterDuration, delay: replacing ? exitDuration : 0 });
|
|
31
66
|
}
|
|
32
67
|
|
|
33
68
|
function timer(data: ToastData): Attachment {
|
|
@@ -41,8 +76,11 @@
|
|
|
41
76
|
</script>
|
|
42
77
|
|
|
43
78
|
<ol
|
|
44
|
-
|
|
45
|
-
|
|
79
|
+
{...rest}
|
|
80
|
+
bind:this={ref}
|
|
81
|
+
class={['toaster', className]}
|
|
82
|
+
lang={locale}
|
|
83
|
+
aria-label={regionLabel}
|
|
46
84
|
aria-live="polite"
|
|
47
85
|
popover="manual"
|
|
48
86
|
onpointerenter={() => (paused = true)}
|
|
@@ -56,18 +94,21 @@
|
|
|
56
94
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
57
95
|
<li
|
|
58
96
|
class="toast"
|
|
97
|
+
title={dismissLabel}
|
|
59
98
|
onclick={() => toast.dismiss(data.id)}
|
|
60
99
|
aria-busy={data.loading || undefined}
|
|
61
100
|
{@attach timer(data)}
|
|
62
101
|
in:enter
|
|
63
|
-
out:fly={{ y: 24, duration: exitDuration }}
|
|
102
|
+
out:fly={{ y: 24, duration: reducedMotion() ? 0 : exitDuration }}
|
|
64
103
|
>
|
|
65
104
|
{#if data.loading}
|
|
66
105
|
<Spinner class="toast-spinner" aria-hidden="true" />
|
|
67
106
|
{:else if data.icon}
|
|
68
107
|
<data.icon class="toast-icon" aria-hidden="true" />
|
|
69
108
|
{/if}
|
|
70
|
-
<span class="toast-message">
|
|
109
|
+
<span class="toast-message">
|
|
110
|
+
{#if message}{@render message(data)}{:else}{data.message}{/if}
|
|
111
|
+
</span>
|
|
71
112
|
{#if data.action}
|
|
72
113
|
{@const action = data.action}
|
|
73
114
|
<Button class="toast-action" variant="secondary" size="sm" onclick={action.onclick}>
|
|
@@ -79,73 +120,94 @@
|
|
|
79
120
|
</ol>
|
|
80
121
|
|
|
81
122
|
<style>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
@layer svelte-ui {
|
|
124
|
+
.toaster {
|
|
125
|
+
position: fixed;
|
|
126
|
+
inset: auto 0 16px;
|
|
127
|
+
/* One cell: the outgoing toast overlaps the incoming one instead of stacking */
|
|
128
|
+
display: grid;
|
|
129
|
+
/* minmax(0, …) keeps a long message from widening the column past the screen */
|
|
130
|
+
grid-template-columns: minmax(0, 1fr);
|
|
131
|
+
justify-items: center;
|
|
132
|
+
align-items: end;
|
|
133
|
+
width: auto;
|
|
134
|
+
height: auto;
|
|
135
|
+
margin: 0;
|
|
136
|
+
padding: 0 16px;
|
|
137
|
+
overflow: visible;
|
|
138
|
+
list-style: none;
|
|
139
|
+
background: none;
|
|
140
|
+
border: none;
|
|
141
|
+
pointer-events: none;
|
|
142
|
+
}
|
|
101
143
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
144
|
+
.toast {
|
|
145
|
+
grid-area: 1 / 1;
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: 10px;
|
|
149
|
+
box-sizing: border-box;
|
|
150
|
+
max-width: min(420px, 100%);
|
|
151
|
+
height: 44px;
|
|
152
|
+
padding: 8px 16px 8px 14px;
|
|
153
|
+
font-size: 13px;
|
|
154
|
+
line-height: 1.4;
|
|
155
|
+
background-color: var(--color-bg);
|
|
156
|
+
color: var(--color-text);
|
|
157
|
+
border: 1px solid var(--color-border);
|
|
158
|
+
border-radius: 16px;
|
|
159
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
|
160
|
+
pointer-events: auto;
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
}
|
|
121
163
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
164
|
+
/* Same gap to the right edge as above and below the 28px button: (44 - 2 - 28) / 2 */
|
|
165
|
+
.toast:has(:global(.toast-action)) {
|
|
166
|
+
padding-right: 7px;
|
|
167
|
+
}
|
|
126
168
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
169
|
+
/* Same sizes as in a Button */
|
|
170
|
+
.toast :global(.toast-icon) {
|
|
171
|
+
width: 16px;
|
|
172
|
+
height: 16px;
|
|
173
|
+
flex-shrink: 0;
|
|
174
|
+
}
|
|
133
175
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
176
|
+
.toast :global(.toast-spinner) {
|
|
177
|
+
--spinner-size: 14px;
|
|
178
|
+
margin: 0 1px;
|
|
179
|
+
}
|
|
138
180
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
181
|
+
.toast-message {
|
|
182
|
+
flex: 1;
|
|
183
|
+
min-width: 0;
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
white-space: nowrap;
|
|
186
|
+
text-overflow: ellipsis;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.toast :global(.toast-action) {
|
|
190
|
+
flex-shrink: 0;
|
|
191
|
+
margin-left: 6px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@media (pointer: coarse) {
|
|
195
|
+
.toast {
|
|
196
|
+
height: auto;
|
|
197
|
+
min-height: 56px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.toast :global(.toast-action) {
|
|
201
|
+
min-width: 44px;
|
|
202
|
+
min-height: 44px;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
146
205
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
206
|
+
@media (forced-colors: active) {
|
|
207
|
+
.toast {
|
|
208
|
+
border-color: CanvasText;
|
|
209
|
+
box-shadow: none;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
150
212
|
}
|
|
151
213
|
</style>
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
type
|
|
1
|
+
export type ToasterLabels = {
|
|
2
2
|
/** Accessible name of the notification region. */
|
|
3
|
+
region?: string;
|
|
4
|
+
/** Hint shown on a toast that closes when clicked. */
|
|
5
|
+
dismiss?: string;
|
|
6
|
+
};
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
9
|
+
import { type ToastData } from './toast.svelte.js';
|
|
10
|
+
type Props = Omit<HTMLAttributes<HTMLOListElement>, 'children'> & {
|
|
11
|
+
/** Accessible name of the notification region. Shorthand for `labels.region`. */
|
|
3
12
|
label?: string;
|
|
13
|
+
/** Texts of the region. */
|
|
14
|
+
labels?: ToasterLabels;
|
|
15
|
+
/** BCP 47 tag of the toast messages; sets `lang` on the region. */
|
|
16
|
+
locale?: string;
|
|
17
|
+
/** Replaces the message of each toast. */
|
|
18
|
+
message?: Snippet<[toast: ToastData]>;
|
|
19
|
+
ref?: HTMLOListElement | null;
|
|
4
20
|
};
|
|
5
|
-
declare const Toaster: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
declare const Toaster: import("svelte").Component<Props, {}, "ref">;
|
|
6
22
|
type Toaster = ReturnType<typeof Toaster>;
|
|
7
23
|
export default Toaster;
|