@spaethtech/svelte-ui 0.1.10 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -42
- package/dist/components/Alert.svelte +119 -0
- package/dist/components/Alert.svelte.d.ts +29 -0
- package/dist/components/Badge/Badge.svelte +142 -69
- package/dist/components/Badge/Badge.svelte.d.ts +26 -6
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Banner.svelte +133 -0
- package/dist/components/Banner.svelte.d.ts +31 -0
- package/dist/components/Button.svelte +240 -0
- package/dist/components/Button.svelte.d.ts +33 -0
- package/dist/components/ButtonDropdown.svelte +145 -0
- package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
- package/dist/components/Calendar.svelte +259 -0
- package/dist/components/Calendar.svelte.d.ts +27 -0
- package/dist/components/Card.svelte +78 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CardBody.svelte +41 -0
- package/dist/components/CardBody.svelte.d.ts +17 -0
- package/dist/components/CardFooter.svelte +49 -0
- package/dist/components/CardFooter.svelte.d.ts +16 -0
- package/dist/components/CardHeader.svelte +49 -0
- package/dist/components/CardHeader.svelte.d.ts +16 -0
- package/dist/components/Checkbox.svelte +131 -0
- package/dist/components/Checkbox.svelte.d.ts +16 -0
- package/dist/components/DataTable.svelte +518 -0
- package/dist/components/DataTable.svelte.d.ts +74 -0
- package/dist/components/DatePicker.svelte +312 -0
- package/dist/components/DatePicker.svelte.d.ts +32 -0
- package/dist/components/DateTimeInput.svelte +93 -0
- package/dist/components/DateTimeInput.svelte.d.ts +20 -0
- package/dist/components/Dialog.svelte +132 -0
- package/dist/components/Dialog.svelte.d.ts +24 -0
- package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
- package/dist/components/EmailInput.svelte.d.ts +21 -0
- package/dist/components/Input.svelte +369 -0
- package/dist/components/Input.svelte.d.ts +35 -0
- package/dist/components/{List/List.svelte → List.svelte} +194 -133
- package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
- package/dist/components/Menu.svelte +117 -0
- package/dist/components/Menu.svelte.d.ts +20 -0
- package/dist/components/NotesEditor.svelte +127 -0
- package/dist/components/NotesEditor.svelte.d.ts +17 -0
- package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
- package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
- package/dist/components/PasswordInput.svelte +52 -0
- package/dist/components/PasswordInput.svelte.d.ts +20 -0
- package/dist/components/Popup.svelte +140 -0
- package/dist/components/Popup.svelte.d.ts +31 -0
- package/dist/components/Query.svelte +284 -0
- package/dist/components/Query.svelte.d.ts +15 -0
- package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
- package/dist/components/Rating.svelte.d.ts +13 -0
- package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
- package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
- package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
- package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
- package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
- package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
- package/dist/components/SideBarMenu/index.d.ts +2 -0
- package/dist/components/SideBarMenu/index.js +1 -0
- package/dist/components/TabStrip/TabStrip.svelte +384 -0
- package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
- package/dist/components/TabStrip/index.d.ts +3 -0
- package/dist/components/TabStrip/index.js +2 -0
- package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
- package/dist/components/TextArea.svelte.d.ts +38 -0
- package/dist/components/ThemeSelector.svelte +81 -0
- package/dist/components/ThemeSelector.svelte.d.ts +10 -0
- package/dist/components/TimePicker.svelte +148 -0
- package/dist/components/TimePicker.svelte.d.ts +28 -0
- package/dist/components/TimeRangeInput.svelte +203 -0
- package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
- package/dist/components/TimeSpinner.svelte +202 -0
- package/dist/components/TimeSpinner.svelte.d.ts +26 -0
- package/dist/components/Toast/Toaster.svelte +51 -0
- package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
- package/dist/components/Toast/toast.svelte.d.ts +29 -0
- package/dist/components/Toast/toast.svelte.js +27 -0
- package/dist/components/Toggle.svelte +93 -0
- package/dist/components/Toggle.svelte.d.ts +15 -0
- package/dist/data/dataset.d.ts +42 -0
- package/dist/data/dataset.js +3 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/data/query/ast.d.ts +62 -0
- package/dist/data/query/ast.js +12 -0
- package/dist/data/query/index.d.ts +3 -0
- package/dist/data/query/index.js +3 -0
- package/dist/data/query/lexer.d.ts +33 -0
- package/dist/data/query/lexer.js +225 -0
- package/dist/data/query/parser.d.ts +11 -0
- package/dist/data/query/parser.js +252 -0
- package/dist/data/table/grid.svelte.d.ts +57 -0
- package/dist/data/table/grid.svelte.js +139 -0
- package/dist/data/table/index.d.ts +2 -0
- package/dist/data/table/index.js +2 -0
- package/dist/data/table/types.d.ts +79 -0
- package/dist/index.d.ts +48 -22
- package/dist/index.js +45 -21
- package/dist/positioning/anchored.d.ts +61 -0
- package/dist/positioning/anchored.js +122 -0
- package/dist/positioning/tooltip.d.ts +41 -0
- package/dist/positioning/tooltip.js +147 -0
- package/dist/theme.css +205 -0
- package/dist/types/breakpoints.d.ts +24 -0
- package/dist/types/breakpoints.js +13 -0
- package/dist/types/responsive.d.ts +32 -0
- package/dist/types/responsive.js +54 -0
- package/dist/types/sizes.d.ts +10 -3
- package/dist/types/time.d.ts +19 -0
- package/dist/types/time.js +10 -0
- package/dist/types/variants.d.ts +8 -1
- package/dist/types/variants.js +16 -1
- package/package.json +93 -63
- package/dist/components/Button/Button.svelte +0 -172
- package/dist/components/Button/Button.svelte.d.ts +0 -32
- package/dist/components/Button/index.d.ts +0 -1
- package/dist/components/Button/index.js +0 -1
- package/dist/components/Dialog/Dialog.svelte +0 -101
- package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
- package/dist/components/Dialog/index.d.ts +0 -1
- package/dist/components/Dialog/index.js +0 -1
- package/dist/components/ElementManager/ElementManager.svelte +0 -397
- package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
- package/dist/components/ElementManager/index.d.ts +0 -1
- package/dist/components/ElementManager/index.js +0 -1
- package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
- package/dist/components/EmailInput/index.d.ts +0 -1
- package/dist/components/EmailInput/index.js +0 -1
- package/dist/components/Icon/Icon.svelte +0 -74
- package/dist/components/Icon/Icon.svelte.d.ts +0 -13
- package/dist/components/Icon/index.d.ts +0 -1
- package/dist/components/Icon/index.js +0 -1
- package/dist/components/Input/Input.svelte +0 -268
- package/dist/components/Input/Input.svelte.d.ts +0 -18
- package/dist/components/Input/index.d.ts +0 -1
- package/dist/components/Input/index.js +0 -1
- package/dist/components/List/index.d.ts +0 -1
- package/dist/components/List/index.js +0 -1
- package/dist/components/ListItem/ListItem.svelte +0 -175
- package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
- package/dist/components/ListItem/index.d.ts +0 -2
- package/dist/components/ListItem/index.js +0 -2
- package/dist/components/ListView/ListView.svelte +0 -463
- package/dist/components/ListView/ListView.svelte.d.ts +0 -37
- package/dist/components/ListView/index.d.ts +0 -2
- package/dist/components/ListView/index.js +0 -2
- package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
- package/dist/components/NodeGraph/BaseNode.js +0 -30
- package/dist/components/NodeGraph/Edge.svelte +0 -60
- package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
- package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
- package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
- package/dist/components/NodeGraph/Node.svelte +0 -100
- package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
- package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
- package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
- package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
- package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
- package/dist/components/NodeGraph/NodePort.svelte +0 -75
- package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
- package/dist/components/NodeGraph/decorators.d.ts +0 -81
- package/dist/components/NodeGraph/decorators.js +0 -148
- package/dist/components/NodeGraph/index.d.ts +0 -9
- package/dist/components/NodeGraph/index.js +0 -9
- package/dist/components/NodeGraph/types.d.ts +0 -94
- package/dist/components/NodeGraph/types.js +0 -33
- package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
- package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
- package/dist/components/NotesEditor/index.d.ts +0 -1
- package/dist/components/NotesEditor/index.js +0 -1
- package/dist/components/NumberInput/index.d.ts +0 -1
- package/dist/components/NumberInput/index.js +0 -1
- package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
- package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
- package/dist/components/PasswordInput/index.d.ts +0 -1
- package/dist/components/PasswordInput/index.js +0 -1
- package/dist/components/Popup/index.d.ts +0 -1
- package/dist/components/Popup/index.js +0 -1
- package/dist/components/Rating/Rating.svelte.d.ts +0 -7
- package/dist/components/Rating/index.d.ts +0 -1
- package/dist/components/Rating/index.js +0 -1
- package/dist/components/ScrollView/ScrollView.svelte +0 -285
- package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
- package/dist/components/ScrollView/index.d.ts +0 -1
- package/dist/components/ScrollView/index.js +0 -1
- package/dist/components/SearchInput/index.d.ts +0 -1
- package/dist/components/SearchInput/index.js +0 -1
- package/dist/components/Select/index.d.ts +0 -1
- package/dist/components/Select/index.js +0 -1
- package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
- package/dist/components/TextArea/index.d.ts +0 -1
- package/dist/components/TextArea/index.js +0 -1
- package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
- package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
- package/dist/components/ThemeSelector/index.d.ts +0 -1
- package/dist/components/ThemeSelector/index.js +0 -1
- package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
- package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
- package/dist/components/TooltipHandler/index.d.ts +0 -1
- package/dist/components/TooltipHandler/index.js +0 -1
- package/dist/styles/sizes.d.ts +0 -78
- package/dist/styles/sizes.js +0 -120
- package/dist/styles/variants.d.ts +0 -106
- package/dist/styles/variants.js +0 -194
- package/dist/types/ManagerTypes.d.ts +0 -42
- /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* TimePicker — a themed time field: an {@link Input}-framed trigger (clock icon) that opens a compact
|
|
4
|
+
* {@link TimeSpinner} (iOS-style hour/minute/AM·PM stepper) in a {@link Popup} (top layer, dismisses on
|
|
5
|
+
* Escape/outside). The field shows a locale-formatted time; a hidden input carries the 24h `HH:MM`
|
|
6
|
+
* value for forms. Use this instead of a bare `<input type=time>` so the field AND its popup match.
|
|
7
|
+
*
|
|
8
|
+
* The footer mirrors DatePicker's Today/Done: "Now" sets the current time, "Done" closes.
|
|
9
|
+
*/
|
|
10
|
+
-->
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
13
|
+
import type { Size } from "../types/sizes.js";
|
|
14
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
15
|
+
import { resolveStep, type StepSpec } from "../types/time.js";
|
|
16
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
17
|
+
import Input from "./Input.svelte";
|
|
18
|
+
import Popup from "./Popup.svelte";
|
|
19
|
+
import TimeSpinner from "./TimeSpinner.svelte";
|
|
20
|
+
import IconClock from "~icons/mdi/clock-outline";
|
|
21
|
+
|
|
22
|
+
interface Props extends Omit<HTMLInputAttributes, "value" | "size" | "min" | "max" | "step"> {
|
|
23
|
+
/** 24h `HH:MM` (or `HH:MM:SS` when `seconds`), or ''. Bindable. */
|
|
24
|
+
value?: string;
|
|
25
|
+
/** Column interval: a number = minute step, or `{ hour, minute, second }`. Default 1 (minute). */
|
|
26
|
+
step?: StepSpec;
|
|
27
|
+
/** Add a seconds column; value becomes `HH:MM:SS`. Default false. */
|
|
28
|
+
seconds?: boolean;
|
|
29
|
+
min?: string;
|
|
30
|
+
max?: string;
|
|
31
|
+
/** Show the AM/PM meridiem (12h). `false` = 24h. Default true; supply from the app's own config. */
|
|
32
|
+
meridiem?: boolean;
|
|
33
|
+
locale?: string;
|
|
34
|
+
size?: Responsive<Size>;
|
|
35
|
+
/** Accent for the popup (focus ring, "Now" action, current value). Default primary. */
|
|
36
|
+
variant?: Variant;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
name?: string;
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
class?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
value = $bindable(""),
|
|
45
|
+
step = 1,
|
|
46
|
+
seconds = false,
|
|
47
|
+
min = "00:00",
|
|
48
|
+
max = "23:59",
|
|
49
|
+
meridiem = true,
|
|
50
|
+
locale = typeof navigator !== "undefined" ? navigator.language : "en-US",
|
|
51
|
+
size = "md",
|
|
52
|
+
variant = "primary",
|
|
53
|
+
disabled = false,
|
|
54
|
+
name,
|
|
55
|
+
placeholder = "Select a time",
|
|
56
|
+
class: cls = "",
|
|
57
|
+
...rest
|
|
58
|
+
}: Props = $props();
|
|
59
|
+
|
|
60
|
+
const accent = $derived(`var(${variantToken[variant]})`);
|
|
61
|
+
// Popup surface padding + text scale with size, so the popup honours `size` like the field does.
|
|
62
|
+
const pad = $derived(responsiveClasses(size, { sm: "p-1.5", md: "p-2", lg: "p-3" }));
|
|
63
|
+
const footText = $derived(
|
|
64
|
+
responsiveClasses(size, { sm: "text-xs", md: "text-sm", lg: "text-base" }),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
let open = $state(false);
|
|
68
|
+
let anchorEl = $state<HTMLDivElement>();
|
|
69
|
+
|
|
70
|
+
const display = $derived.by(() => {
|
|
71
|
+
if (!value) return "";
|
|
72
|
+
const [h, m, s] = value.split(":").map(Number);
|
|
73
|
+
return new Intl.DateTimeFormat(locale, {
|
|
74
|
+
hour: "numeric",
|
|
75
|
+
minute: "2-digit",
|
|
76
|
+
second: seconds ? "2-digit" : undefined,
|
|
77
|
+
hour12: meridiem,
|
|
78
|
+
}).format(new Date(2000, 0, 1, h, m, s || 0));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function toggle() {
|
|
82
|
+
if (!disabled) open = !open;
|
|
83
|
+
}
|
|
84
|
+
function onKey(e: KeyboardEvent) {
|
|
85
|
+
if (disabled) return;
|
|
86
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
open = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function now() {
|
|
92
|
+
const d = new Date();
|
|
93
|
+
// Snap minutes to the nearest minute-step so "Now" lands on a real spinner value; keep real seconds.
|
|
94
|
+
const ms = resolveStep(step).minute;
|
|
95
|
+
const mins = Math.round((d.getHours() * 60 + d.getMinutes()) / ms) * ms;
|
|
96
|
+
const hhmm = `${String(Math.floor(mins / 60) % 24).padStart(2, "0")}:${String(mins % 60).padStart(2, "0")}`;
|
|
97
|
+
value = seconds ? `${hhmm}:${String(d.getSeconds()).padStart(2, "0")}` : hhmm;
|
|
98
|
+
open = false; // a time is a complete selection — set and close (like DatePicker's Today)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const subtle = "[color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]";
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<div bind:this={anchorEl} class="w-full {cls}">
|
|
105
|
+
<Input
|
|
106
|
+
value={display}
|
|
107
|
+
readonly
|
|
108
|
+
{size}
|
|
109
|
+
{disabled}
|
|
110
|
+
{placeholder}
|
|
111
|
+
icon={clockIcon}
|
|
112
|
+
iconPosition="right"
|
|
113
|
+
iconClickable
|
|
114
|
+
onIconClick={toggle}
|
|
115
|
+
onclick={toggle}
|
|
116
|
+
onkeydown={onKey}
|
|
117
|
+
inputClass="cursor-pointer"
|
|
118
|
+
{...rest}
|
|
119
|
+
/>
|
|
120
|
+
{#if name}<input type="hidden" {name} value={value ?? ""} />{/if}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{#snippet clockIcon()}<IconClock />{/snippet}
|
|
124
|
+
|
|
125
|
+
<Popup anchor={anchorEl} bind:open offset={6}>
|
|
126
|
+
<div
|
|
127
|
+
class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
|
|
128
|
+
style="--accent: {accent}"
|
|
129
|
+
>
|
|
130
|
+
<div class="grid place-items-center py-1">
|
|
131
|
+
<TimeSpinner bind:value {step} {seconds} {min} {max} {meridiem} {locale} {size} {variant} />
|
|
132
|
+
</div>
|
|
133
|
+
<div
|
|
134
|
+
class="mt-2 flex items-center justify-between border-t pt-2 {footText} [border-color:var(--ui-border-color)]"
|
|
135
|
+
>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
class="rounded px-2 py-1 [color:var(--accent)] hover:underline"
|
|
139
|
+
onclick={now}>Now</button
|
|
140
|
+
>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
class="rounded px-2 py-1 {subtle} hover:underline"
|
|
144
|
+
onclick={() => (open = false)}>Done</button
|
|
145
|
+
>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</Popup>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import { type Responsive } from "../types/responsive.js";
|
|
4
|
+
import { type StepSpec } from "../types/time.js";
|
|
5
|
+
import { type Variant } from "../types/variants.js";
|
|
6
|
+
interface Props extends Omit<HTMLInputAttributes, "value" | "size" | "min" | "max" | "step"> {
|
|
7
|
+
/** 24h `HH:MM` (or `HH:MM:SS` when `seconds`), or ''. Bindable. */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** Column interval: a number = minute step, or `{ hour, minute, second }`. Default 1 (minute). */
|
|
10
|
+
step?: StepSpec;
|
|
11
|
+
/** Add a seconds column; value becomes `HH:MM:SS`. Default false. */
|
|
12
|
+
seconds?: boolean;
|
|
13
|
+
min?: string;
|
|
14
|
+
max?: string;
|
|
15
|
+
/** Show the AM/PM meridiem (12h). `false` = 24h. Default true; supply from the app's own config. */
|
|
16
|
+
meridiem?: boolean;
|
|
17
|
+
locale?: string;
|
|
18
|
+
size?: Responsive<Size>;
|
|
19
|
+
/** Accent for the popup (focus ring, "Now" action, current value). Default primary. */
|
|
20
|
+
variant?: Variant;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
name?: string;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
class?: string;
|
|
25
|
+
}
|
|
26
|
+
declare const TimePicker: import("svelte").Component<Props, {}, "value">;
|
|
27
|
+
type TimePicker = ReturnType<typeof TimePicker>;
|
|
28
|
+
export default TimePicker;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* TimeRangeInput — a themed time-interval field: an {@link Input}-framed trigger (clock icon) that
|
|
4
|
+
* opens two compact {@link TimeSpinner}s (Start / End) side by side in a {@link Popup}. The field
|
|
5
|
+
* shows "9:00 AM – 5:00 PM"; the spinners are cross-bounded so End can't precede Start. Value is
|
|
6
|
+
* `{ start, end }` with 24h `HH:MM` strings (mirrors DatePicker's range shape).
|
|
7
|
+
*
|
|
8
|
+
* The footer mirrors DatePicker's Today/Done: "Now" sets the start to the current time, "Done" closes.
|
|
9
|
+
*/
|
|
10
|
+
-->
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import type { Size } from "../types/sizes.js";
|
|
13
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
14
|
+
import { resolveStep, type StepSpec } from "../types/time.js";
|
|
15
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
16
|
+
import Input from "./Input.svelte";
|
|
17
|
+
import Popup from "./Popup.svelte";
|
|
18
|
+
import TimeSpinner from "./TimeSpinner.svelte";
|
|
19
|
+
import IconClock from "~icons/mdi/clock-outline";
|
|
20
|
+
|
|
21
|
+
type RangeValue = { start: string | null; end: string | null };
|
|
22
|
+
interface Props {
|
|
23
|
+
/** `{ start, end }` with 24h `HH:MM` (or `HH:MM:SS` when `seconds`), or null. Bindable. */
|
|
24
|
+
value?: RangeValue | null;
|
|
25
|
+
/** Column interval: a number = minute step, or `{ hour, minute, second }`. Default 1 (minute). */
|
|
26
|
+
step?: StepSpec;
|
|
27
|
+
/** Add a seconds column to both spinners; values become `HH:MM:SS`. Default false. */
|
|
28
|
+
seconds?: boolean;
|
|
29
|
+
min?: string;
|
|
30
|
+
max?: string;
|
|
31
|
+
/** Show the AM/PM meridiem (12h). `false` = 24h. Default true; supply from the app's own config. */
|
|
32
|
+
meridiem?: boolean;
|
|
33
|
+
locale?: string;
|
|
34
|
+
size?: Responsive<Size>;
|
|
35
|
+
variant?: Variant;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
class?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
value = $bindable(),
|
|
43
|
+
step = 1,
|
|
44
|
+
seconds = false,
|
|
45
|
+
min = "00:00",
|
|
46
|
+
max = "23:59",
|
|
47
|
+
meridiem = true,
|
|
48
|
+
locale = typeof navigator !== "undefined" ? navigator.language : "en-US",
|
|
49
|
+
size = "md",
|
|
50
|
+
variant = "primary",
|
|
51
|
+
disabled = false,
|
|
52
|
+
placeholder = "Select a time range",
|
|
53
|
+
class: cls = "",
|
|
54
|
+
...rest
|
|
55
|
+
}: Props = $props();
|
|
56
|
+
|
|
57
|
+
const accent = $derived(`var(${variantToken[variant]})`);
|
|
58
|
+
const pad = $derived(responsiveClasses(size, { sm: "p-1.5", md: "p-2", lg: "p-3" }));
|
|
59
|
+
const footText = $derived(
|
|
60
|
+
responsiveClasses(size, { sm: "text-xs", md: "text-sm", lg: "text-base" }),
|
|
61
|
+
);
|
|
62
|
+
const subtle = "[color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]";
|
|
63
|
+
|
|
64
|
+
const rv = $derived(value ?? { start: null, end: null });
|
|
65
|
+
|
|
66
|
+
let open = $state(false);
|
|
67
|
+
let anchorEl = $state<HTMLDivElement>();
|
|
68
|
+
|
|
69
|
+
const fmt = (t: string | null) => {
|
|
70
|
+
if (!t) return "";
|
|
71
|
+
const [h, m, s] = t.split(":").map(Number);
|
|
72
|
+
return new Intl.DateTimeFormat(locale, {
|
|
73
|
+
hour: "numeric",
|
|
74
|
+
minute: "2-digit",
|
|
75
|
+
second: seconds ? "2-digit" : undefined,
|
|
76
|
+
hour12: meridiem,
|
|
77
|
+
}).format(new Date(2000, 0, 1, h, m, s || 0));
|
|
78
|
+
};
|
|
79
|
+
const label = $derived(rv.start ? `${fmt(rv.start)} – ${rv.end ? fmt(rv.end) : "…"}` : "");
|
|
80
|
+
const hasValue = $derived(!!(rv.start || rv.end));
|
|
81
|
+
|
|
82
|
+
// Keep the interval ordered: nudging start past end drags end along, and vice-versa.
|
|
83
|
+
function setStart(t: string) {
|
|
84
|
+
value = { start: t, end: rv.end && rv.end < t ? t : rv.end };
|
|
85
|
+
}
|
|
86
|
+
function setEnd(t: string) {
|
|
87
|
+
value = { start: rv.start && rv.start > t ? t : rv.start, end: t };
|
|
88
|
+
}
|
|
89
|
+
function now() {
|
|
90
|
+
const d = new Date();
|
|
91
|
+
const ms = resolveStep(step).minute;
|
|
92
|
+
const mins = Math.round((d.getHours() * 60 + d.getMinutes()) / ms) * ms;
|
|
93
|
+
const hhmm = `${String(Math.floor(mins / 60) % 24).padStart(2, "0")}:${String(mins % 60).padStart(2, "0")}`;
|
|
94
|
+
setStart(seconds ? `${hhmm}:${String(d.getSeconds()).padStart(2, "0")}` : hhmm);
|
|
95
|
+
}
|
|
96
|
+
function clear() {
|
|
97
|
+
value = { start: null, end: null };
|
|
98
|
+
open = false;
|
|
99
|
+
}
|
|
100
|
+
function toggle() {
|
|
101
|
+
if (!disabled) open = !open;
|
|
102
|
+
}
|
|
103
|
+
function onKey(e: KeyboardEvent) {
|
|
104
|
+
if (disabled) return;
|
|
105
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
open = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<div bind:this={anchorEl} class="w-full {cls}">
|
|
113
|
+
<Input
|
|
114
|
+
value={label}
|
|
115
|
+
readonly
|
|
116
|
+
{size}
|
|
117
|
+
{disabled}
|
|
118
|
+
{placeholder}
|
|
119
|
+
onclick={toggle}
|
|
120
|
+
onkeydown={onKey}
|
|
121
|
+
inputClass="cursor-pointer"
|
|
122
|
+
actions={trailing}
|
|
123
|
+
{...rest}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{#snippet trailing()}
|
|
128
|
+
{#if hasValue && !disabled}
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
aria-label="Clear"
|
|
132
|
+
tabindex="-1"
|
|
133
|
+
class="grid size-5 place-items-center rounded {subtle} hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"
|
|
134
|
+
onclick={(e) => {
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
clear();
|
|
137
|
+
}}>×</button
|
|
138
|
+
>
|
|
139
|
+
{/if}
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
tabindex="-1"
|
|
143
|
+
aria-label="Open time range"
|
|
144
|
+
{disabled}
|
|
145
|
+
class="inline-flex items-center [color:var(--ui-color-secondary)] disabled:opacity-50"
|
|
146
|
+
onclick={toggle}><IconClock /></button
|
|
147
|
+
>
|
|
148
|
+
{/snippet}
|
|
149
|
+
|
|
150
|
+
<Popup anchor={anchorEl} bind:open offset={6}>
|
|
151
|
+
<div
|
|
152
|
+
class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
|
|
153
|
+
style="--accent: {accent}"
|
|
154
|
+
>
|
|
155
|
+
<div class="flex flex-col gap-2">
|
|
156
|
+
<div class="flex items-center gap-3">
|
|
157
|
+
<span class="w-10 shrink-0 text-xs {subtle}">Start</span>
|
|
158
|
+
<TimeSpinner
|
|
159
|
+
value={rv.start ?? ""}
|
|
160
|
+
{step}
|
|
161
|
+
{seconds}
|
|
162
|
+
{min}
|
|
163
|
+
max={rv.end ?? max}
|
|
164
|
+
{meridiem}
|
|
165
|
+
{locale}
|
|
166
|
+
{size}
|
|
167
|
+
{variant}
|
|
168
|
+
onchange={setStart}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="border-t [border-color:var(--ui-border-color)]"></div>
|
|
172
|
+
<div class="flex items-center gap-3">
|
|
173
|
+
<span class="w-10 shrink-0 text-xs {subtle}">End</span>
|
|
174
|
+
<TimeSpinner
|
|
175
|
+
value={rv.end ?? ""}
|
|
176
|
+
{step}
|
|
177
|
+
{seconds}
|
|
178
|
+
min={rv.start ?? min}
|
|
179
|
+
{max}
|
|
180
|
+
{meridiem}
|
|
181
|
+
{locale}
|
|
182
|
+
{size}
|
|
183
|
+
{variant}
|
|
184
|
+
onchange={setEnd}
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
<div
|
|
189
|
+
class="mt-2 flex items-center justify-between border-t pt-2 {footText} [border-color:var(--ui-border-color)]"
|
|
190
|
+
>
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
class="rounded px-2 py-1 [color:var(--accent)] hover:underline"
|
|
194
|
+
onclick={now}>Now</button
|
|
195
|
+
>
|
|
196
|
+
<button
|
|
197
|
+
type="button"
|
|
198
|
+
class="rounded px-2 py-1 {subtle} hover:underline"
|
|
199
|
+
onclick={() => (open = false)}>Done</button
|
|
200
|
+
>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</Popup>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Size } from "../types/sizes.js";
|
|
2
|
+
import { type Responsive } from "../types/responsive.js";
|
|
3
|
+
import { type StepSpec } from "../types/time.js";
|
|
4
|
+
import { type Variant } from "../types/variants.js";
|
|
5
|
+
type RangeValue = {
|
|
6
|
+
start: string | null;
|
|
7
|
+
end: string | null;
|
|
8
|
+
};
|
|
9
|
+
interface Props {
|
|
10
|
+
/** `{ start, end }` with 24h `HH:MM` (or `HH:MM:SS` when `seconds`), or null. Bindable. */
|
|
11
|
+
value?: RangeValue | null;
|
|
12
|
+
/** Column interval: a number = minute step, or `{ hour, minute, second }`. Default 1 (minute). */
|
|
13
|
+
step?: StepSpec;
|
|
14
|
+
/** Add a seconds column to both spinners; values become `HH:MM:SS`. Default false. */
|
|
15
|
+
seconds?: boolean;
|
|
16
|
+
min?: string;
|
|
17
|
+
max?: string;
|
|
18
|
+
/** Show the AM/PM meridiem (12h). `false` = 24h. Default true; supply from the app's own config. */
|
|
19
|
+
meridiem?: boolean;
|
|
20
|
+
locale?: string;
|
|
21
|
+
size?: Responsive<Size>;
|
|
22
|
+
variant?: Variant;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
class?: string;
|
|
26
|
+
}
|
|
27
|
+
declare const TimeRangeInput: import("svelte").Component<Props, {}, "value">;
|
|
28
|
+
type TimeRangeInput = ReturnType<typeof TimeRangeInput>;
|
|
29
|
+
export default TimeRangeInput;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* TimeSpinner — a compact iOS-style time stepper: a single row of HOUR : MINUTE : (AM·PM) columns,
|
|
4
|
+
* each showing just the current value with an up-chevron above and a down-chevron below. Tiny footprint,
|
|
5
|
+
* so it tucks under a Calendar in a combined date/time picker. 24h mode drops the meridiem column.
|
|
6
|
+
*
|
|
7
|
+
* Each column steps its own unit (wrapping), driven by the chevrons, the mouse wheel, or ↑/↓ when
|
|
8
|
+
* focused. Values are 24h `HH:MM`; labels follow the locale. Minute steps by `step`; `min`/`max`
|
|
9
|
+
* disable the chevrons that would leave range. Sibling of {@link TimeList} (the flat slot list).
|
|
10
|
+
*/
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import type { Size } from "../types/sizes.js";
|
|
14
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
15
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
16
|
+
import { resolveStep, type StepSpec } from "../types/time.js";
|
|
17
|
+
import IconUp from "~icons/mdi/chevron-up";
|
|
18
|
+
import IconDown from "~icons/mdi/chevron-down";
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
/** Selected time, 24h `HH:MM` (or `HH:MM:SS` when `seconds`), or ''. Bindable. */
|
|
22
|
+
value?: string;
|
|
23
|
+
/** Column interval: a number = minute step, or `{ hour, minute, second }`. Default 1 (minute). */
|
|
24
|
+
step?: StepSpec;
|
|
25
|
+
/** Show a seconds column; value becomes `HH:MM:SS`. Default false. */
|
|
26
|
+
seconds?: boolean;
|
|
27
|
+
min?: string;
|
|
28
|
+
max?: string;
|
|
29
|
+
/** Show the AM/PM meridiem column (12h). `false` = 24h. Default true; the app supplies this from
|
|
30
|
+
* its own (server-side) locale/format config rather than sniffing the browser. */
|
|
31
|
+
meridiem?: boolean;
|
|
32
|
+
locale?: string;
|
|
33
|
+
size?: Responsive<Size>;
|
|
34
|
+
/** Accent for the focus ring. Default primary. */
|
|
35
|
+
variant?: Variant;
|
|
36
|
+
class?: string;
|
|
37
|
+
onchange?: (value: string) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let {
|
|
41
|
+
value = $bindable(""),
|
|
42
|
+
step = 1,
|
|
43
|
+
seconds = false,
|
|
44
|
+
min,
|
|
45
|
+
max,
|
|
46
|
+
meridiem = true,
|
|
47
|
+
locale = typeof navigator !== "undefined" ? navigator.language : "en-US",
|
|
48
|
+
size = "md",
|
|
49
|
+
variant = "primary",
|
|
50
|
+
class: cls = "",
|
|
51
|
+
onchange,
|
|
52
|
+
}: Props = $props();
|
|
53
|
+
|
|
54
|
+
const accent = $derived(`var(${variantToken[variant]})`);
|
|
55
|
+
|
|
56
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
57
|
+
// Compose a value in the active format (adds :SS only when `seconds`).
|
|
58
|
+
const mk = (h: number, m: number, s: number = curS) =>
|
|
59
|
+
seconds ? `${pad(h)}:${pad(m)}:${pad(s)}` : `${pad(h)}:${pad(m)}`;
|
|
60
|
+
// Bounds may be given as HH:MM even in seconds mode — normalize so the string compare lines up.
|
|
61
|
+
const normB = (b: string | undefined) =>
|
|
62
|
+
b && seconds && b.split(":").length === 2 ? `${b}:00` : b;
|
|
63
|
+
const lo = $derived(normB(min));
|
|
64
|
+
const hi = $derived(normB(max));
|
|
65
|
+
// Pull a candidate into range. Independent columns mean a single step can momentarily land out of
|
|
66
|
+
// range (e.g. AM→PM keeping the hour) — clamp instead of rejecting, so it settles on the nearest
|
|
67
|
+
// valid time rather than trapping the user.
|
|
68
|
+
const clamp = (t: string) => (lo && t < lo ? lo : hi && t > hi ? hi : t);
|
|
69
|
+
|
|
70
|
+
const is12 = $derived(meridiem);
|
|
71
|
+
|
|
72
|
+
const base = $derived(value || min || (seconds ? "12:00:00" : "12:00"));
|
|
73
|
+
const curH = $derived(Number(base.split(":")[0])); // 0..23
|
|
74
|
+
const curM = $derived(Number(base.split(":")[1]));
|
|
75
|
+
const curS = $derived(Number(base.split(":")[2] ?? 0));
|
|
76
|
+
const curMer = $derived<"AM" | "PM">(curH < 12 ? "AM" : "PM");
|
|
77
|
+
const dispHour = $derived(is12 ? (curH % 12 === 0 ? 12 : curH % 12) : curH);
|
|
78
|
+
|
|
79
|
+
const steps = $derived(resolveStep(step));
|
|
80
|
+
// A 0..59 grid on `by`, always including the current (possibly off-grid) value so it stays selectable.
|
|
81
|
+
const grid60 = (by: number, cur: number) => {
|
|
82
|
+
const out: number[] = [];
|
|
83
|
+
for (let n = 0; n < 60; n += by) out.push(n);
|
|
84
|
+
if (!out.includes(cur)) {
|
|
85
|
+
out.push(cur);
|
|
86
|
+
out.sort((a, b) => a - b);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
};
|
|
90
|
+
const minutes = $derived(grid60(steps.minute, curM));
|
|
91
|
+
const secondsGrid = $derived(grid60(steps.second, curS));
|
|
92
|
+
|
|
93
|
+
// Candidate value after stepping one column. HOUR steps in 24h space (wrapping 0–23) so it crosses
|
|
94
|
+
// noon naturally — 11 AM → 12 PM → 1 PM … — with the AM/PM display flipping as a consequence, rather
|
|
95
|
+
// than looping within one half-day. AM/PM flips a half-day (±12h). Minute/second wrap within their grid.
|
|
96
|
+
const stepHour = (dir: 1 | -1) => mk((curH + dir * steps.hour + 24) % 24, curM);
|
|
97
|
+
const stepMinute = (dir: 1 | -1) => {
|
|
98
|
+
const i = minutes.indexOf(curM);
|
|
99
|
+
return mk(curH, minutes[(i + dir + minutes.length) % minutes.length]);
|
|
100
|
+
};
|
|
101
|
+
const stepSecond = (dir: 1 | -1) => {
|
|
102
|
+
const i = secondsGrid.indexOf(curS);
|
|
103
|
+
return mk(curH, curM, secondsGrid[(i + dir + secondsGrid.length) % secondsGrid.length]);
|
|
104
|
+
};
|
|
105
|
+
const stepMer = () => mk((curH + 12) % 24, curM);
|
|
106
|
+
|
|
107
|
+
type Col = { display: string; label: string; up: string; down: string; colon?: boolean };
|
|
108
|
+
const cols = $derived.by<Col[]>(() => {
|
|
109
|
+
const list: Col[] = [
|
|
110
|
+
{
|
|
111
|
+
display: is12 ? String(dispHour) : pad(curH),
|
|
112
|
+
label: "Hour",
|
|
113
|
+
up: stepHour(1),
|
|
114
|
+
down: stepHour(-1),
|
|
115
|
+
},
|
|
116
|
+
{ display: pad(curM), label: "Minute", up: stepMinute(1), down: stepMinute(-1), colon: true },
|
|
117
|
+
];
|
|
118
|
+
if (seconds)
|
|
119
|
+
list.push({
|
|
120
|
+
display: pad(curS),
|
|
121
|
+
label: "Second",
|
|
122
|
+
up: stepSecond(1),
|
|
123
|
+
down: stepSecond(-1),
|
|
124
|
+
colon: true,
|
|
125
|
+
});
|
|
126
|
+
if (is12) list.push({ display: curMer, label: "AM/PM", up: stepMer(), down: stepMer() });
|
|
127
|
+
return list;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function apply(cand: string) {
|
|
131
|
+
value = clamp(cand);
|
|
132
|
+
onchange?.(value);
|
|
133
|
+
}
|
|
134
|
+
// A chevron is dead only when stepping can't move off the current value (already at that bound).
|
|
135
|
+
const noStep = (cand: string) => clamp(cand) === base;
|
|
136
|
+
|
|
137
|
+
// Each field is a per-Size class map → responsiveClasses() so `size` may be scalar OR responsive.
|
|
138
|
+
const valMap: Record<Size, string> = {
|
|
139
|
+
sm: "text-base w-9 py-0.5",
|
|
140
|
+
md: "text-lg w-11 py-1",
|
|
141
|
+
lg: "text-2xl w-14 py-1.5",
|
|
142
|
+
};
|
|
143
|
+
const chevMap: Record<Size, string> = { sm: "size-5", md: "size-6", lg: "size-7" };
|
|
144
|
+
const colMap: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-sm" };
|
|
145
|
+
const sizing = $derived({
|
|
146
|
+
val: responsiveClasses(size, valMap),
|
|
147
|
+
chev: responsiveClasses(size, chevMap),
|
|
148
|
+
col: responsiveClasses(size, colMap),
|
|
149
|
+
});
|
|
150
|
+
const chevBtn =
|
|
151
|
+
"grid place-items-center rounded-[var(--ui-border-radius)] [color:color-mix(in_srgb,var(--ui-color-text)_55%,transparent)] transition-colors enabled:hover:[background-color:var(--ui-color-hover)] enabled:active:[background-color:var(--ui-color-active)] enabled:hover:[color:var(--ui-color-text)] disabled:opacity-25 disabled:cursor-not-allowed";
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<div
|
|
155
|
+
class="inline-flex items-center gap-1 [color:var(--ui-color-text)] {cls}"
|
|
156
|
+
style="--accent: {accent}"
|
|
157
|
+
>
|
|
158
|
+
{#each cols as c (c.label)}
|
|
159
|
+
{#if c.colon}<span class="{sizing.val} grid place-items-center pb-0 font-semibold opacity-60"
|
|
160
|
+
>:</span
|
|
161
|
+
>{/if}
|
|
162
|
+
<div class="flex flex-col items-center">
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
aria-label={`${c.label} up`}
|
|
166
|
+
class="{chevBtn} {sizing.chev} w-full"
|
|
167
|
+
disabled={noStep(c.up)}
|
|
168
|
+
onclick={() => apply(c.up)}><IconUp /></button
|
|
169
|
+
>
|
|
170
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
171
|
+
<div
|
|
172
|
+
role="spinbutton"
|
|
173
|
+
aria-label={c.label}
|
|
174
|
+
aria-valuetext={c.display}
|
|
175
|
+
tabindex="0"
|
|
176
|
+
class="{sizing.val} grid cursor-ns-resize place-items-center rounded-[var(--ui-border-radius)] text-center font-semibold tabular-nums select-none focus:outline-none focus-visible:[outline:2px_solid_var(--accent)]"
|
|
177
|
+
onwheel={(e) => {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
apply(e.deltaY > 0 ? c.down : c.up);
|
|
180
|
+
}}
|
|
181
|
+
onkeydown={(e) => {
|
|
182
|
+
if (e.key === "ArrowUp") {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
apply(c.up);
|
|
185
|
+
} else if (e.key === "ArrowDown") {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
apply(c.down);
|
|
188
|
+
}
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
{c.display}
|
|
192
|
+
</div>
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
aria-label={`${c.label} down`}
|
|
196
|
+
class="{chevBtn} {sizing.chev} w-full"
|
|
197
|
+
disabled={noStep(c.down)}
|
|
198
|
+
onclick={() => apply(c.down)}><IconDown /></button
|
|
199
|
+
>
|
|
200
|
+
</div>
|
|
201
|
+
{/each}
|
|
202
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Size } from "../types/sizes.js";
|
|
2
|
+
import { type Responsive } from "../types/responsive.js";
|
|
3
|
+
import { type Variant } from "../types/variants.js";
|
|
4
|
+
import { type StepSpec } from "../types/time.js";
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Selected time, 24h `HH:MM` (or `HH:MM:SS` when `seconds`), or ''. Bindable. */
|
|
7
|
+
value?: string;
|
|
8
|
+
/** Column interval: a number = minute step, or `{ hour, minute, second }`. Default 1 (minute). */
|
|
9
|
+
step?: StepSpec;
|
|
10
|
+
/** Show a seconds column; value becomes `HH:MM:SS`. Default false. */
|
|
11
|
+
seconds?: boolean;
|
|
12
|
+
min?: string;
|
|
13
|
+
max?: string;
|
|
14
|
+
/** Show the AM/PM meridiem column (12h). `false` = 24h. Default true; the app supplies this from
|
|
15
|
+
* its own (server-side) locale/format config rather than sniffing the browser. */
|
|
16
|
+
meridiem?: boolean;
|
|
17
|
+
locale?: string;
|
|
18
|
+
size?: Responsive<Size>;
|
|
19
|
+
/** Accent for the focus ring. Default primary. */
|
|
20
|
+
variant?: Variant;
|
|
21
|
+
class?: string;
|
|
22
|
+
onchange?: (value: string) => void;
|
|
23
|
+
}
|
|
24
|
+
declare const TimeSpinner: import("svelte").Component<Props, {}, "value">;
|
|
25
|
+
type TimeSpinner = ReturnType<typeof TimeSpinner>;
|
|
26
|
+
export default TimeSpinner;
|