svelte-comp 1.3.5 → 1.3.6
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.md +21 -21
- package/README.md +101 -101
- package/dist/App.svelte +1046 -1046
- package/dist/Container.svelte +59 -59
- package/dist/app.css +234 -234
- package/dist/app.d.ts +10 -10
- package/dist/lib/Accordion.svelte +155 -155
- package/dist/lib/Badge.svelte +44 -44
- package/dist/lib/Button.svelte +185 -185
- package/dist/lib/Calendar.svelte +384 -384
- package/dist/lib/Card.svelte +103 -103
- package/dist/lib/Carousel.svelte +293 -293
- package/dist/lib/CheckBox.svelte +210 -210
- package/dist/lib/CodeView.svelte +308 -308
- package/dist/lib/ColorPicker.svelte +159 -159
- package/dist/lib/ContextMenu.svelte +328 -328
- package/dist/lib/DatePicker.svelte +246 -246
- package/dist/lib/Dialog.svelte +233 -233
- package/dist/lib/Field.svelte +299 -299
- package/dist/lib/FilePicker.svelte +295 -295
- package/dist/lib/Form.svelte +438 -438
- package/dist/lib/Hamburger.svelte +217 -217
- package/dist/lib/InstallPWA.svelte +94 -94
- package/dist/lib/Menu.svelte +623 -623
- package/dist/lib/NoticeBase.svelte +140 -140
- package/dist/lib/PaginatedCard.svelte +73 -73
- package/dist/lib/Pagination.svelte +119 -119
- package/dist/lib/PrimaryColorSelect.svelte +111 -111
- package/dist/lib/ProgressBar.svelte +141 -141
- package/dist/lib/ProgressCircle.svelte +190 -190
- package/dist/lib/Radio.svelte +189 -189
- package/dist/lib/SearchInput.svelte +104 -104
- package/dist/lib/Select.svelte +524 -524
- package/dist/lib/Slider.svelte +253 -253
- package/dist/lib/Splitter.svelte +159 -159
- package/dist/lib/Switch.svelte +168 -168
- package/dist/lib/Table.svelte +299 -299
- package/dist/lib/Tabs.svelte +213 -213
- package/dist/lib/ThemeToggle.svelte +128 -128
- package/dist/lib/TimePicker.svelte +312 -312
- package/dist/lib/TimePickerNew.svelte +634 -634
- package/dist/lib/Toast.svelte +123 -123
- package/dist/lib/Tooltip.svelte +110 -110
- package/dist/lib/Topbar.svelte +112 -112
- package/dist/styles.css +234 -234
- package/package.json +52 -52
|
@@ -1,312 +1,312 @@
|
|
|
1
|
-
<!-- src/lib/TimePicker.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* @component TimePicker
|
|
5
|
-
* @description Simple time selector that stores values in ISO `HH:MM` format. Supports a fixed step and two display systems.
|
|
6
|
-
*
|
|
7
|
-
* @prop value {string | null} - Stored time in ISO `HH:MM` (bindable)
|
|
8
|
-
* @default null
|
|
9
|
-
*
|
|
10
|
-
* @prop step {number} - Step in seconds
|
|
11
|
-
* @default 60
|
|
12
|
-
*
|
|
13
|
-
* @prop label {string} - Label text
|
|
14
|
-
*
|
|
15
|
-
* @prop placeholder {string} - Placeholder when value is null
|
|
16
|
-
*
|
|
17
|
-
* @prop disabled {boolean} - Disable all interactions
|
|
18
|
-
* @default false
|
|
19
|
-
*
|
|
20
|
-
* @prop clearable {boolean} - Show the clear button
|
|
21
|
-
* @default true
|
|
22
|
-
*
|
|
23
|
-
* @prop initialSystem {"iso" | "english"} - Picker mode (24h vs 12h)
|
|
24
|
-
* @default "iso"
|
|
25
|
-
*
|
|
26
|
-
* @prop onChange {(value: string | null) => void} - Fired when value changes
|
|
27
|
-
*
|
|
28
|
-
* @prop class {string} - Wrapper classes
|
|
29
|
-
* @default ""
|
|
30
|
-
*
|
|
31
|
-
* @note ISO mode uses 24-hour time; English mode uses 12-hour time with AM/PM
|
|
32
|
-
* @note The stored value is always ISO (`HH:MM`)
|
|
33
|
-
* @note `step` defines the minute grid (derived from seconds)
|
|
34
|
-
* @note No locale or date-formatting APIs are used internally
|
|
35
|
-
*/
|
|
36
|
-
import type { HTMLAttributes } from "svelte/elements";
|
|
37
|
-
import Button from "./Button.svelte";
|
|
38
|
-
import Select from "./Select.svelte";
|
|
39
|
-
import { cx } from "../utils";
|
|
40
|
-
import { getComponentText, getLangContext, getLangKey } from "./lang-context";
|
|
41
|
-
|
|
42
|
-
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
43
|
-
value?: string | null;
|
|
44
|
-
step?: number;
|
|
45
|
-
label?: string;
|
|
46
|
-
placeholder?: string;
|
|
47
|
-
disabled?: boolean;
|
|
48
|
-
clearable?: boolean;
|
|
49
|
-
initialSystem?: "iso" | "english";
|
|
50
|
-
onChange?: (value: string | null) => void;
|
|
51
|
-
class?: string;
|
|
52
|
-
};
|
|
53
|
-
type Period = "AM" | "PM";
|
|
54
|
-
|
|
55
|
-
let {
|
|
56
|
-
value = $bindable<string | null>(null),
|
|
57
|
-
step = 60,
|
|
58
|
-
label,
|
|
59
|
-
placeholder,
|
|
60
|
-
disabled = false,
|
|
61
|
-
clearable = true,
|
|
62
|
-
initialSystem = "iso",
|
|
63
|
-
onChange,
|
|
64
|
-
class: externalClass = "",
|
|
65
|
-
...rest
|
|
66
|
-
}: Props = $props();
|
|
67
|
-
|
|
68
|
-
const langCtx = getLangContext();
|
|
69
|
-
const langKey = $derived(getLangKey(langCtx));
|
|
70
|
-
const L = $derived(getComponentText("timePicker", langKey));
|
|
71
|
-
|
|
72
|
-
const labelFinal = $derived(label ?? L.text);
|
|
73
|
-
const placeholderFinal = $derived(placeholder ?? L.placeholder);
|
|
74
|
-
|
|
75
|
-
const pickerClass = $derived(cx("inline-block w-full", externalClass));
|
|
76
|
-
|
|
77
|
-
let timeSystem = $state<"iso" | "english">("iso");
|
|
78
|
-
let didInitSystem = $state(false);
|
|
79
|
-
|
|
80
|
-
let hour = $state("00");
|
|
81
|
-
let minute = $state("00");
|
|
82
|
-
let period = $state<Period>("AM");
|
|
83
|
-
|
|
84
|
-
const hasValue = $derived(value != null);
|
|
85
|
-
|
|
86
|
-
const isoHours = Array.from({ length: 24 }, (_, i) => {
|
|
87
|
-
const h = i.toString().padStart(2, "0");
|
|
88
|
-
return { value: h, label: h };
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const englishHours = Array.from({ length: 12 }, (_, i) => {
|
|
92
|
-
const h = (i + 1).toString().padStart(2, "0");
|
|
93
|
-
return { value: h, label: h };
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const periodOptions: Array<{ value: Period; label: Period }> = [
|
|
97
|
-
{ value: "AM", label: "AM" },
|
|
98
|
-
{ value: "PM", label: "PM" },
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
const minuteIncrement = $derived(
|
|
102
|
-
!step || step <= 0 ? 1 : Math.min(60, Math.max(1, Math.round(step / 60)))
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const minuteOptions = $derived.by(() => {
|
|
106
|
-
const arr = [];
|
|
107
|
-
for (let i = 0; i < 60; i += minuteIncrement) {
|
|
108
|
-
const m = i.toString().padStart(2, "0");
|
|
109
|
-
arr.push({ value: m, label: m });
|
|
110
|
-
}
|
|
111
|
-
return arr;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
function normalize(x: string) {
|
|
115
|
-
return x.padStart(2, "0").slice(-2);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function toIsoHour(h: string, p: "AM" | "PM") {
|
|
119
|
-
const numeric = Number.parseInt(h, 10);
|
|
120
|
-
if (Number.isNaN(numeric)) return "00";
|
|
121
|
-
const base = numeric % 12;
|
|
122
|
-
const withPeriod = p === "PM" ? base + 12 : base;
|
|
123
|
-
return normalize(String(withPeriod));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function toEnglishHour(isoHour: string): { hour: string; period: Period } {
|
|
127
|
-
const numeric = Number.parseInt(isoHour, 10);
|
|
128
|
-
if (Number.isNaN(numeric)) {
|
|
129
|
-
return { hour: "12", period: "AM" as const };
|
|
130
|
-
}
|
|
131
|
-
const periodValue: Period = numeric >= 12 ? "PM" : "AM";
|
|
132
|
-
const normalized = numeric % 12 || 12;
|
|
133
|
-
return { hour: normalize(String(normalized)), period: periodValue };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function emit() {
|
|
137
|
-
const isoHour =
|
|
138
|
-
timeSystem === "english" ? toIsoHour(hour, period) : normalize(hour);
|
|
139
|
-
const v = `${isoHour}:${normalize(minute)}`;
|
|
140
|
-
value = v;
|
|
141
|
-
onChange?.(v);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function handleIsoHour(v: string) {
|
|
145
|
-
hour = normalize(v);
|
|
146
|
-
emit();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function handleMinute(v: string) {
|
|
150
|
-
minute = normalize(v);
|
|
151
|
-
emit();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function handleEnglishHour(v: string) {
|
|
155
|
-
hour = normalize(v);
|
|
156
|
-
emit();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function handlePeriod(v: string) {
|
|
160
|
-
period = v === "AM" || v === "PM" ? v : "AM";
|
|
161
|
-
emit();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function toggleSystem() {
|
|
165
|
-
if (disabled) return;
|
|
166
|
-
|
|
167
|
-
if (timeSystem === "iso") {
|
|
168
|
-
timeSystem = "english";
|
|
169
|
-
const mapped = toEnglishHour(hour);
|
|
170
|
-
hour = mapped.hour;
|
|
171
|
-
period = mapped.period;
|
|
172
|
-
} else {
|
|
173
|
-
timeSystem = "iso";
|
|
174
|
-
hour = toIsoHour(hour, period);
|
|
175
|
-
period = "AM";
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
emit();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function clearSelection() {
|
|
182
|
-
if (!clearable) return;
|
|
183
|
-
hour = "00";
|
|
184
|
-
minute = "00";
|
|
185
|
-
period = "AM";
|
|
186
|
-
value = null;
|
|
187
|
-
onChange?.(null);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const displayValue = $derived.by(() => {
|
|
191
|
-
if (!value) return "";
|
|
192
|
-
const [isoH, isoM] = value.split(":");
|
|
193
|
-
if (timeSystem === "english") {
|
|
194
|
-
const { hour: h, period: p } = toEnglishHour(isoH);
|
|
195
|
-
return `${h}:${normalize(isoM)} ${p}`;
|
|
196
|
-
}
|
|
197
|
-
return `${normalize(isoH)}:${normalize(isoM)}`;
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
$effect(() => {
|
|
201
|
-
if (didInitSystem) return;
|
|
202
|
-
didInitSystem = true;
|
|
203
|
-
timeSystem = initialSystem;
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
$effect(() => {
|
|
207
|
-
if (value == null) return;
|
|
208
|
-
|
|
209
|
-
let raw = value;
|
|
210
|
-
let parsedPeriod: Period | null = null;
|
|
211
|
-
|
|
212
|
-
if (raw.includes("AM") || raw.includes("PM")) {
|
|
213
|
-
parsedPeriod = raw.includes("PM") ? "PM" : "AM";
|
|
214
|
-
raw = raw.replace(" AM", "").replace(" PM", "");
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const [h, m] = raw.split(":");
|
|
218
|
-
const isoHour = normalize(h);
|
|
219
|
-
const isoMinute = normalize(m);
|
|
220
|
-
|
|
221
|
-
if (timeSystem === "english") {
|
|
222
|
-
const mapped = parsedPeriod
|
|
223
|
-
? { hour: normalize(h), period: parsedPeriod }
|
|
224
|
-
: toEnglishHour(isoHour);
|
|
225
|
-
hour = mapped.hour;
|
|
226
|
-
period = mapped.period;
|
|
227
|
-
} else {
|
|
228
|
-
hour = isoHour;
|
|
229
|
-
period = toEnglishHour(isoHour).period;
|
|
230
|
-
}
|
|
231
|
-
minute = isoMinute;
|
|
232
|
-
});
|
|
233
|
-
</script>
|
|
234
|
-
|
|
235
|
-
<div class={pickerClass} {...rest}>
|
|
236
|
-
<div class="text-md font-medium mb-2 [color:var(--color-text-default)]">
|
|
237
|
-
{labelFinal}
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<div class="flex flex-wrap items-start gap-4">
|
|
241
|
-
<div class="flex flex-wrap gap-3 min-w-[0] flex-1">
|
|
242
|
-
{#if timeSystem === "iso"}
|
|
243
|
-
<Select
|
|
244
|
-
label={L.hour}
|
|
245
|
-
options={isoHours}
|
|
246
|
-
value={hour}
|
|
247
|
-
onChange={handleIsoHour}
|
|
248
|
-
{disabled}
|
|
249
|
-
sz="sm"
|
|
250
|
-
/>
|
|
251
|
-
{:else}
|
|
252
|
-
<Select
|
|
253
|
-
label={L.hour}
|
|
254
|
-
options={englishHours}
|
|
255
|
-
value={hour}
|
|
256
|
-
onChange={handleEnglishHour}
|
|
257
|
-
{disabled}
|
|
258
|
-
sz="sm"
|
|
259
|
-
/>
|
|
260
|
-
|
|
261
|
-
<Select
|
|
262
|
-
label={L.period}
|
|
263
|
-
options={periodOptions}
|
|
264
|
-
value={period}
|
|
265
|
-
onChange={handlePeriod}
|
|
266
|
-
{disabled}
|
|
267
|
-
sz="sm"
|
|
268
|
-
/>
|
|
269
|
-
{/if}
|
|
270
|
-
|
|
271
|
-
<Select
|
|
272
|
-
label={L.minute}
|
|
273
|
-
options={minuteOptions}
|
|
274
|
-
value={minute}
|
|
275
|
-
onChange={handleMinute}
|
|
276
|
-
{disabled}
|
|
277
|
-
sz="sm"
|
|
278
|
-
/>
|
|
279
|
-
</div>
|
|
280
|
-
|
|
281
|
-
<div class="flex items-center gap-3 basis-full pt-2">
|
|
282
|
-
<Button onClick={toggleSystem} {disabled} sz="xs">
|
|
283
|
-
{timeSystem === "iso" ? L.switchTo12h : L.switchTo24h}
|
|
284
|
-
</Button>
|
|
285
|
-
|
|
286
|
-
{#if clearable}
|
|
287
|
-
<Button
|
|
288
|
-
onClick={clearSelection}
|
|
289
|
-
variant="danger"
|
|
290
|
-
disabled={!hasValue || disabled}
|
|
291
|
-
sz="xs"
|
|
292
|
-
>
|
|
293
|
-
{L.clear}
|
|
294
|
-
</Button>
|
|
295
|
-
{/if}
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
|
|
299
|
-
<div class="mt-4 p-4 bg-[var(--color-bg-surface)] text-center">
|
|
300
|
-
<p class="text-xs uppercase tracking-wide [color:var(--color-text-muted)]">
|
|
301
|
-
{L.selectedTime}
|
|
302
|
-
</p>
|
|
303
|
-
|
|
304
|
-
<p class="text-sm font-semibold mt-1 [color:var(--color-text-default)]">
|
|
305
|
-
{#if hasValue}
|
|
306
|
-
{displayValue}
|
|
307
|
-
{:else}
|
|
308
|
-
{placeholderFinal}
|
|
309
|
-
{/if}
|
|
310
|
-
</p>
|
|
311
|
-
</div>
|
|
312
|
-
</div>
|
|
1
|
+
<!-- src/lib/TimePicker.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* @component TimePicker
|
|
5
|
+
* @description Simple time selector that stores values in ISO `HH:MM` format. Supports a fixed step and two display systems.
|
|
6
|
+
*
|
|
7
|
+
* @prop value {string | null} - Stored time in ISO `HH:MM` (bindable)
|
|
8
|
+
* @default null
|
|
9
|
+
*
|
|
10
|
+
* @prop step {number} - Step in seconds
|
|
11
|
+
* @default 60
|
|
12
|
+
*
|
|
13
|
+
* @prop label {string} - Label text
|
|
14
|
+
*
|
|
15
|
+
* @prop placeholder {string} - Placeholder when value is null
|
|
16
|
+
*
|
|
17
|
+
* @prop disabled {boolean} - Disable all interactions
|
|
18
|
+
* @default false
|
|
19
|
+
*
|
|
20
|
+
* @prop clearable {boolean} - Show the clear button
|
|
21
|
+
* @default true
|
|
22
|
+
*
|
|
23
|
+
* @prop initialSystem {"iso" | "english"} - Picker mode (24h vs 12h)
|
|
24
|
+
* @default "iso"
|
|
25
|
+
*
|
|
26
|
+
* @prop onChange {(value: string | null) => void} - Fired when value changes
|
|
27
|
+
*
|
|
28
|
+
* @prop class {string} - Wrapper classes
|
|
29
|
+
* @default ""
|
|
30
|
+
*
|
|
31
|
+
* @note ISO mode uses 24-hour time; English mode uses 12-hour time with AM/PM
|
|
32
|
+
* @note The stored value is always ISO (`HH:MM`)
|
|
33
|
+
* @note `step` defines the minute grid (derived from seconds)
|
|
34
|
+
* @note No locale or date-formatting APIs are used internally
|
|
35
|
+
*/
|
|
36
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
37
|
+
import Button from "./Button.svelte";
|
|
38
|
+
import Select from "./Select.svelte";
|
|
39
|
+
import { cx } from "../utils";
|
|
40
|
+
import { getComponentText, getLangContext, getLangKey } from "./lang-context";
|
|
41
|
+
|
|
42
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
43
|
+
value?: string | null;
|
|
44
|
+
step?: number;
|
|
45
|
+
label?: string;
|
|
46
|
+
placeholder?: string;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
clearable?: boolean;
|
|
49
|
+
initialSystem?: "iso" | "english";
|
|
50
|
+
onChange?: (value: string | null) => void;
|
|
51
|
+
class?: string;
|
|
52
|
+
};
|
|
53
|
+
type Period = "AM" | "PM";
|
|
54
|
+
|
|
55
|
+
let {
|
|
56
|
+
value = $bindable<string | null>(null),
|
|
57
|
+
step = 60,
|
|
58
|
+
label,
|
|
59
|
+
placeholder,
|
|
60
|
+
disabled = false,
|
|
61
|
+
clearable = true,
|
|
62
|
+
initialSystem = "iso",
|
|
63
|
+
onChange,
|
|
64
|
+
class: externalClass = "",
|
|
65
|
+
...rest
|
|
66
|
+
}: Props = $props();
|
|
67
|
+
|
|
68
|
+
const langCtx = getLangContext();
|
|
69
|
+
const langKey = $derived(getLangKey(langCtx));
|
|
70
|
+
const L = $derived(getComponentText("timePicker", langKey));
|
|
71
|
+
|
|
72
|
+
const labelFinal = $derived(label ?? L.text);
|
|
73
|
+
const placeholderFinal = $derived(placeholder ?? L.placeholder);
|
|
74
|
+
|
|
75
|
+
const pickerClass = $derived(cx("inline-block w-full", externalClass));
|
|
76
|
+
|
|
77
|
+
let timeSystem = $state<"iso" | "english">("iso");
|
|
78
|
+
let didInitSystem = $state(false);
|
|
79
|
+
|
|
80
|
+
let hour = $state("00");
|
|
81
|
+
let minute = $state("00");
|
|
82
|
+
let period = $state<Period>("AM");
|
|
83
|
+
|
|
84
|
+
const hasValue = $derived(value != null);
|
|
85
|
+
|
|
86
|
+
const isoHours = Array.from({ length: 24 }, (_, i) => {
|
|
87
|
+
const h = i.toString().padStart(2, "0");
|
|
88
|
+
return { value: h, label: h };
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const englishHours = Array.from({ length: 12 }, (_, i) => {
|
|
92
|
+
const h = (i + 1).toString().padStart(2, "0");
|
|
93
|
+
return { value: h, label: h };
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const periodOptions: Array<{ value: Period; label: Period }> = [
|
|
97
|
+
{ value: "AM", label: "AM" },
|
|
98
|
+
{ value: "PM", label: "PM" },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const minuteIncrement = $derived(
|
|
102
|
+
!step || step <= 0 ? 1 : Math.min(60, Math.max(1, Math.round(step / 60)))
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const minuteOptions = $derived.by(() => {
|
|
106
|
+
const arr = [];
|
|
107
|
+
for (let i = 0; i < 60; i += minuteIncrement) {
|
|
108
|
+
const m = i.toString().padStart(2, "0");
|
|
109
|
+
arr.push({ value: m, label: m });
|
|
110
|
+
}
|
|
111
|
+
return arr;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
function normalize(x: string) {
|
|
115
|
+
return x.padStart(2, "0").slice(-2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function toIsoHour(h: string, p: "AM" | "PM") {
|
|
119
|
+
const numeric = Number.parseInt(h, 10);
|
|
120
|
+
if (Number.isNaN(numeric)) return "00";
|
|
121
|
+
const base = numeric % 12;
|
|
122
|
+
const withPeriod = p === "PM" ? base + 12 : base;
|
|
123
|
+
return normalize(String(withPeriod));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function toEnglishHour(isoHour: string): { hour: string; period: Period } {
|
|
127
|
+
const numeric = Number.parseInt(isoHour, 10);
|
|
128
|
+
if (Number.isNaN(numeric)) {
|
|
129
|
+
return { hour: "12", period: "AM" as const };
|
|
130
|
+
}
|
|
131
|
+
const periodValue: Period = numeric >= 12 ? "PM" : "AM";
|
|
132
|
+
const normalized = numeric % 12 || 12;
|
|
133
|
+
return { hour: normalize(String(normalized)), period: periodValue };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function emit() {
|
|
137
|
+
const isoHour =
|
|
138
|
+
timeSystem === "english" ? toIsoHour(hour, period) : normalize(hour);
|
|
139
|
+
const v = `${isoHour}:${normalize(minute)}`;
|
|
140
|
+
value = v;
|
|
141
|
+
onChange?.(v);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function handleIsoHour(v: string) {
|
|
145
|
+
hour = normalize(v);
|
|
146
|
+
emit();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function handleMinute(v: string) {
|
|
150
|
+
minute = normalize(v);
|
|
151
|
+
emit();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handleEnglishHour(v: string) {
|
|
155
|
+
hour = normalize(v);
|
|
156
|
+
emit();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function handlePeriod(v: string) {
|
|
160
|
+
period = v === "AM" || v === "PM" ? v : "AM";
|
|
161
|
+
emit();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function toggleSystem() {
|
|
165
|
+
if (disabled) return;
|
|
166
|
+
|
|
167
|
+
if (timeSystem === "iso") {
|
|
168
|
+
timeSystem = "english";
|
|
169
|
+
const mapped = toEnglishHour(hour);
|
|
170
|
+
hour = mapped.hour;
|
|
171
|
+
period = mapped.period;
|
|
172
|
+
} else {
|
|
173
|
+
timeSystem = "iso";
|
|
174
|
+
hour = toIsoHour(hour, period);
|
|
175
|
+
period = "AM";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
emit();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function clearSelection() {
|
|
182
|
+
if (!clearable) return;
|
|
183
|
+
hour = "00";
|
|
184
|
+
minute = "00";
|
|
185
|
+
period = "AM";
|
|
186
|
+
value = null;
|
|
187
|
+
onChange?.(null);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const displayValue = $derived.by(() => {
|
|
191
|
+
if (!value) return "";
|
|
192
|
+
const [isoH, isoM] = value.split(":");
|
|
193
|
+
if (timeSystem === "english") {
|
|
194
|
+
const { hour: h, period: p } = toEnglishHour(isoH);
|
|
195
|
+
return `${h}:${normalize(isoM)} ${p}`;
|
|
196
|
+
}
|
|
197
|
+
return `${normalize(isoH)}:${normalize(isoM)}`;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
$effect(() => {
|
|
201
|
+
if (didInitSystem) return;
|
|
202
|
+
didInitSystem = true;
|
|
203
|
+
timeSystem = initialSystem;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
$effect(() => {
|
|
207
|
+
if (value == null) return;
|
|
208
|
+
|
|
209
|
+
let raw = value;
|
|
210
|
+
let parsedPeriod: Period | null = null;
|
|
211
|
+
|
|
212
|
+
if (raw.includes("AM") || raw.includes("PM")) {
|
|
213
|
+
parsedPeriod = raw.includes("PM") ? "PM" : "AM";
|
|
214
|
+
raw = raw.replace(" AM", "").replace(" PM", "");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const [h, m] = raw.split(":");
|
|
218
|
+
const isoHour = normalize(h);
|
|
219
|
+
const isoMinute = normalize(m);
|
|
220
|
+
|
|
221
|
+
if (timeSystem === "english") {
|
|
222
|
+
const mapped = parsedPeriod
|
|
223
|
+
? { hour: normalize(h), period: parsedPeriod }
|
|
224
|
+
: toEnglishHour(isoHour);
|
|
225
|
+
hour = mapped.hour;
|
|
226
|
+
period = mapped.period;
|
|
227
|
+
} else {
|
|
228
|
+
hour = isoHour;
|
|
229
|
+
period = toEnglishHour(isoHour).period;
|
|
230
|
+
}
|
|
231
|
+
minute = isoMinute;
|
|
232
|
+
});
|
|
233
|
+
</script>
|
|
234
|
+
|
|
235
|
+
<div class={pickerClass} {...rest}>
|
|
236
|
+
<div class="text-md font-medium mb-2 [color:var(--color-text-default)]">
|
|
237
|
+
{labelFinal}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div class="flex flex-wrap items-start gap-4">
|
|
241
|
+
<div class="flex flex-wrap gap-3 min-w-[0] flex-1">
|
|
242
|
+
{#if timeSystem === "iso"}
|
|
243
|
+
<Select
|
|
244
|
+
label={L.hour}
|
|
245
|
+
options={isoHours}
|
|
246
|
+
value={hour}
|
|
247
|
+
onChange={handleIsoHour}
|
|
248
|
+
{disabled}
|
|
249
|
+
sz="sm"
|
|
250
|
+
/>
|
|
251
|
+
{:else}
|
|
252
|
+
<Select
|
|
253
|
+
label={L.hour}
|
|
254
|
+
options={englishHours}
|
|
255
|
+
value={hour}
|
|
256
|
+
onChange={handleEnglishHour}
|
|
257
|
+
{disabled}
|
|
258
|
+
sz="sm"
|
|
259
|
+
/>
|
|
260
|
+
|
|
261
|
+
<Select
|
|
262
|
+
label={L.period}
|
|
263
|
+
options={periodOptions}
|
|
264
|
+
value={period}
|
|
265
|
+
onChange={handlePeriod}
|
|
266
|
+
{disabled}
|
|
267
|
+
sz="sm"
|
|
268
|
+
/>
|
|
269
|
+
{/if}
|
|
270
|
+
|
|
271
|
+
<Select
|
|
272
|
+
label={L.minute}
|
|
273
|
+
options={minuteOptions}
|
|
274
|
+
value={minute}
|
|
275
|
+
onChange={handleMinute}
|
|
276
|
+
{disabled}
|
|
277
|
+
sz="sm"
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div class="flex items-center gap-3 basis-full pt-2">
|
|
282
|
+
<Button onClick={toggleSystem} {disabled} sz="xs">
|
|
283
|
+
{timeSystem === "iso" ? L.switchTo12h : L.switchTo24h}
|
|
284
|
+
</Button>
|
|
285
|
+
|
|
286
|
+
{#if clearable}
|
|
287
|
+
<Button
|
|
288
|
+
onClick={clearSelection}
|
|
289
|
+
variant="danger"
|
|
290
|
+
disabled={!hasValue || disabled}
|
|
291
|
+
sz="xs"
|
|
292
|
+
>
|
|
293
|
+
{L.clear}
|
|
294
|
+
</Button>
|
|
295
|
+
{/if}
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<div class="mt-4 p-4 bg-[var(--color-bg-surface)] text-center">
|
|
300
|
+
<p class="text-xs uppercase tracking-wide [color:var(--color-text-muted)]">
|
|
301
|
+
{L.selectedTime}
|
|
302
|
+
</p>
|
|
303
|
+
|
|
304
|
+
<p class="text-sm font-semibold mt-1 [color:var(--color-text-default)]">
|
|
305
|
+
{#if hasValue}
|
|
306
|
+
{displayValue}
|
|
307
|
+
{:else}
|
|
308
|
+
{placeholderFinal}
|
|
309
|
+
{/if}
|
|
310
|
+
</p>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|