@urbicon-ui/blocks 6.36.0 → 6.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,536 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte';
3
+ import { useBlocksI18n } from '../..';
4
+ import { getBlocksConfig, resolveSlotClasses } from '../../provider';
5
+ import { getTierContext } from '../../utils';
6
+ import { resolveIcon } from '../../icons';
7
+ import ClockIconDefault from '../../icons/ClockIcon.svelte';
8
+ import type { TimeInputProps } from './index';
9
+ import { timeInputVariants, type TimeInputVariants } from './time-input.variants';
10
+
11
+ const bt = useBlocksI18n();
12
+ const ClockIcon = resolveIcon('clock', ClockIconDefault);
13
+
14
+ let {
15
+ value = $bindable(null),
16
+ format = '24h',
17
+ withSeconds = false,
18
+ min,
19
+ max,
20
+ tier,
21
+ variant = 'outlined',
22
+ size = 'md',
23
+ intent = 'default',
24
+ disabled = false,
25
+ readonly = false,
26
+ required = false,
27
+ fullWidth = false,
28
+ showIcon = true,
29
+ label,
30
+ helper,
31
+ error,
32
+ icon,
33
+ onValueChange,
34
+ name,
35
+ class: className = '',
36
+ unstyled: unstyledProp = false,
37
+ slotClasses: slotClassesProp = {},
38
+ preset,
39
+ id: idProp,
40
+ 'aria-label': ariaLabel
41
+ }: TimeInputProps = $props();
42
+
43
+ const tierCtx = getTierContext();
44
+ const effectiveTier = $derived(tier ?? tierCtx?.tier ?? 'modify');
45
+
46
+ const blocksConfig = getBlocksConfig();
47
+ const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
48
+
49
+ const propsId = $props.id();
50
+ const fieldId = $derived(idProp ?? `timeinput-${propsId}`);
51
+ const labelId = $derived(`${fieldId}-label`);
52
+ const messageId = $derived(`${fieldId}-message`);
53
+ const describedBy = $derived(error || helper ? messageId : undefined);
54
+
55
+ const hourMax = $derived(format === '12h' ? 12 : 23);
56
+ const hourMin = $derived(format === '12h' ? 1 : 0);
57
+
58
+ // Segment display strings — the DOM source of truth. Kept unpadded while a
59
+ // single digit is mid-entry, padded once the segment commits, so typing "9"
60
+ // in the hour shows "9" (not "09") until the field advances.
61
+ let hourStr = $state('');
62
+ let minuteStr = $state('');
63
+ let secondStr = $state('');
64
+ let meridiem = $state<'AM' | 'PM'>('AM');
65
+
66
+ let hourEl = $state<HTMLInputElement>();
67
+ let minuteEl = $state<HTMLInputElement>();
68
+ let secondEl = $state<HTMLInputElement>();
69
+ let meridiemEl = $state<HTMLSpanElement>();
70
+
71
+ function pad(n: number): string {
72
+ return String(n).padStart(2, '0');
73
+ }
74
+
75
+ function num(str: string): number | null {
76
+ if (str === '') return null;
77
+ const n = Number.parseInt(str, 10);
78
+ return Number.isNaN(n) ? null : n;
79
+ }
80
+
81
+ function to24(h12: number, mer: 'AM' | 'PM'): number {
82
+ if (mer === 'AM') return h12 === 12 ? 0 : h12;
83
+ return h12 === 12 ? 12 : h12 + 12;
84
+ }
85
+
86
+ // Canonical 24-hour value the current segments describe, or null when any
87
+ // required segment is empty.
88
+ function canonicalFromSegments(): string | null {
89
+ const h = num(hourStr);
90
+ const m = num(minuteStr);
91
+ const s = withSeconds ? num(secondStr) : 0;
92
+ if (h === null || m === null || (withSeconds && s === null)) return null;
93
+ // A provisional "0" in a 12-hour hour maps to 12 (midnight/noon); the clamp
94
+ // to [1,12] is belt-and-suspenders so an out-of-range hour (e.g. a stale "13"
95
+ // left over from a runtime format flip) can never produce a 25:xx value.
96
+ const H = format === '12h' ? to24(Math.min(Math.max(h === 0 ? 12 : h, 1), 12), meridiem) : h;
97
+ const parts = [pad(H), pad(m)];
98
+ if (withSeconds) parts.push(pad(s as number));
99
+ return parts.join(':');
100
+ }
101
+
102
+ function setValue(next: string | null) {
103
+ if (next !== value) {
104
+ value = next;
105
+ onValueChange?.(next);
106
+ }
107
+ }
108
+
109
+ function syncFromValue(v: string | null) {
110
+ if (!v) {
111
+ hourStr = '';
112
+ minuteStr = '';
113
+ secondStr = '';
114
+ return;
115
+ }
116
+ const [hh = '', mm = '', ss = ''] = v.split(':');
117
+ const H = num(hh) ?? 0;
118
+ const m = num(mm) ?? 0;
119
+ if (format === '12h') {
120
+ meridiem = H < 12 ? 'AM' : 'PM';
121
+ const h12 = H % 12 === 0 ? 12 : H % 12;
122
+ hourStr = pad(h12);
123
+ } else {
124
+ hourStr = pad(H);
125
+ }
126
+ minuteStr = pad(m);
127
+ secondStr = withSeconds ? pad(num(ss) ?? 0) : '';
128
+ }
129
+
130
+ // Re-seed the segments from `value` on any external change. `format` and
131
+ // `withSeconds` are read (via `void`) purely to register them as dependencies:
132
+ // without that, a runtime 24h→12h flip would leave stale segments (e.g. an hour
133
+ // of "13" beside an AM button — a state the next meridiem toggle would turn into
134
+ // "25:30"). The `incoming !== current` guard both skips a local edit (which
135
+ // already set `value`) and fires the re-seed on a format switch, because the
136
+ // canonical computed under the new format no longer matches the raw value.
137
+ $effect(() => {
138
+ const incoming = value ?? null;
139
+ void format;
140
+ void withSeconds;
141
+ const current = untrack(() => canonicalFromSegments());
142
+ if (incoming !== current) {
143
+ untrack(() => syncFromValue(incoming));
144
+ }
145
+ });
146
+
147
+ const variantProps: TimeInputVariants = $derived({
148
+ tier: effectiveTier,
149
+ variant,
150
+ size,
151
+ intent,
152
+ disabled: disabled || undefined,
153
+ readonly: readonly || undefined,
154
+ error: !!error || undefined,
155
+ required: required || undefined,
156
+ fullWidth: fullWidth || undefined,
157
+ messageType: error ? 'error' : 'helper'
158
+ });
159
+
160
+ const styles = $derived(timeInputVariants(variantProps));
161
+ const slotClasses = $derived(
162
+ resolveSlotClasses(blocksConfig, 'TimeInput', preset, variantProps, slotClassesProp)
163
+ );
164
+
165
+ function segmentClass() {
166
+ return unstyled
167
+ ? (slotClasses?.segment ?? '')
168
+ : styles.segment({ class: slotClasses?.segment });
169
+ }
170
+
171
+ const order = $derived(
172
+ [
173
+ hourEl,
174
+ minuteEl,
175
+ withSeconds ? secondEl : undefined,
176
+ format === '12h' ? meridiemEl : undefined
177
+ ].filter(Boolean) as (HTMLInputElement | HTMLSpanElement)[]
178
+ );
179
+
180
+ function advanceFrom(el: HTMLElement) {
181
+ const idx = order.indexOf(el as HTMLInputElement);
182
+ const next = order[idx + 1];
183
+ if (next) {
184
+ next.focus();
185
+ if (next instanceof HTMLInputElement) next.select();
186
+ }
187
+ }
188
+
189
+ function stepBy(str: string, delta: number, segMin: number, segMax: number): string {
190
+ const cur = num(str);
191
+ const span = segMax - segMin + 1;
192
+ if (cur === null) return pad(delta > 0 ? segMin : segMax);
193
+ const next = ((((cur - segMin + delta) % span) + span) % span) + segMin;
194
+ return pad(next);
195
+ }
196
+
197
+ // Digit-entry state machine shared by the three numeric segments. Returns the
198
+ // new display string and whether the segment is complete (→ advance focus).
199
+ function applyDigit(el: HTMLInputElement, segMax: number): { next: string; complete: boolean } {
200
+ const digits = el.value.replace(/\D/g, '');
201
+ if (digits === '') return { next: '', complete: false };
202
+ const two = digits.slice(-2);
203
+ if (two.length === 2) {
204
+ const n = Number.parseInt(two, 10);
205
+ if (n > segMax) {
206
+ // second digit overflowed — treat it as a fresh single-digit entry
207
+ const last = Number.parseInt(two.slice(-1), 10);
208
+ return { next: String(last), complete: last * 10 > segMax };
209
+ }
210
+ return { next: pad(Math.max(n, 0)), complete: true };
211
+ }
212
+ const n = Number.parseInt(two, 10);
213
+ // No valid second digit is possible (e.g. hour "3" in 24h) — commit now.
214
+ if (n * 10 > segMax) return { next: pad(n), complete: true };
215
+ return { next: String(n), complete: false };
216
+ }
217
+
218
+ function handleHourInput(e: Event) {
219
+ const el = e.currentTarget as HTMLInputElement;
220
+ const { next, complete } = applyDigit(el, hourMax);
221
+ hourStr = next;
222
+ el.value = next;
223
+ setValue(canonicalFromSegments());
224
+ if (complete) advanceFrom(el);
225
+ }
226
+
227
+ function handleMinuteInput(e: Event) {
228
+ const el = e.currentTarget as HTMLInputElement;
229
+ const { next, complete } = applyDigit(el, 59);
230
+ minuteStr = next;
231
+ el.value = next;
232
+ setValue(canonicalFromSegments());
233
+ if (complete) advanceFrom(el);
234
+ }
235
+
236
+ function handleSecondInput(e: Event) {
237
+ const el = e.currentTarget as HTMLInputElement;
238
+ const { next, complete } = applyDigit(el, 59);
239
+ secondStr = next;
240
+ el.value = next;
241
+ setValue(canonicalFromSegments());
242
+ if (complete) advanceFrom(el);
243
+ }
244
+
245
+ function commitSegment(which: 'hour' | 'minute' | 'second') {
246
+ // Pad a provisional single digit once the segment loses the caret.
247
+ if (which === 'hour' && hourStr) hourStr = pad(num(hourStr) ?? 0);
248
+ if (which === 'minute' && minuteStr) minuteStr = pad(num(minuteStr) ?? 0);
249
+ if (which === 'second' && secondStr) secondStr = pad(num(secondStr) ?? 0);
250
+ }
251
+
252
+ type SegName = 'hour' | 'minute' | 'second';
253
+
254
+ function handleSegmentKeydown(seg: SegName, e: KeyboardEvent) {
255
+ if (disabled) return;
256
+ const el = e.currentTarget as HTMLInputElement;
257
+ const segMin = seg === 'hour' ? hourMin : 0;
258
+ const segMax = seg === 'hour' ? hourMax : 59;
259
+ const strOf = seg === 'hour' ? hourStr : seg === 'minute' ? minuteStr : secondStr;
260
+ const setStr = (v: string) => {
261
+ if (seg === 'hour') hourStr = v;
262
+ else if (seg === 'minute') minuteStr = v;
263
+ else secondStr = v;
264
+ };
265
+ switch (e.key) {
266
+ case 'ArrowUp':
267
+ e.preventDefault();
268
+ if (readonly) break;
269
+ setStr(stepBy(strOf, 1, segMin, segMax));
270
+ setValue(canonicalFromSegments());
271
+ break;
272
+ case 'ArrowDown':
273
+ e.preventDefault();
274
+ if (readonly) break;
275
+ setStr(stepBy(strOf, -1, segMin, segMax));
276
+ setValue(canonicalFromSegments());
277
+ break;
278
+ case 'ArrowLeft': {
279
+ e.preventDefault();
280
+ const idx = order.indexOf(el);
281
+ if (idx > 0) order[idx - 1].focus();
282
+ break;
283
+ }
284
+ case 'ArrowRight':
285
+ e.preventDefault();
286
+ advanceFrom(el);
287
+ break;
288
+ case 'Backspace':
289
+ if (readonly) break;
290
+ e.preventDefault();
291
+ if (strOf) {
292
+ setStr('');
293
+ el.value = '';
294
+ setValue(canonicalFromSegments());
295
+ } else {
296
+ const idx = order.indexOf(el);
297
+ if (idx > 0) order[idx - 1].focus();
298
+ }
299
+ break;
300
+ }
301
+ }
302
+
303
+ function handleSegmentFocus(e: FocusEvent) {
304
+ (e.currentTarget as HTMLInputElement).select();
305
+ }
306
+
307
+ function toggleMeridiem(next?: 'AM' | 'PM') {
308
+ if (disabled || readonly) return;
309
+ meridiem = next ?? (meridiem === 'AM' ? 'PM' : 'AM');
310
+ setValue(canonicalFromSegments());
311
+ }
312
+
313
+ function handleMeridiemKeydown(e: KeyboardEvent) {
314
+ if (disabled) return;
315
+ switch (e.key) {
316
+ case 'ArrowUp':
317
+ case 'ArrowDown':
318
+ // The segment is a spinbutton on a span (html-aria forbids the role on
319
+ // <button>), so Enter/Space activation is wired manually.
320
+ case 'Enter':
321
+ case ' ':
322
+ e.preventDefault();
323
+ toggleMeridiem();
324
+ break;
325
+ case 'a':
326
+ case 'A':
327
+ e.preventDefault();
328
+ toggleMeridiem('AM');
329
+ break;
330
+ case 'p':
331
+ case 'P':
332
+ e.preventDefault();
333
+ toggleMeridiem('PM');
334
+ break;
335
+ case 'ArrowLeft':
336
+ case 'Backspace': {
337
+ e.preventDefault();
338
+ const idx = order.indexOf(e.currentTarget as HTMLSpanElement);
339
+ if (idx > 0) order[idx - 1].focus();
340
+ break;
341
+ }
342
+ }
343
+ }
344
+
345
+ // Clamp to [min, max] once focus leaves the whole field. Canonical HH:MM(:SS)
346
+ // strings are zero-padded, so lexical comparison is chronological.
347
+ function handleFocusOut(e: FocusEvent) {
348
+ const nextTarget = e.relatedTarget as Node | null;
349
+ if (nextTarget && (e.currentTarget as HTMLElement).contains(nextTarget)) return;
350
+ commitSegment('hour');
351
+ commitSegment('minute');
352
+ commitSegment('second');
353
+ let canonical = canonicalFromSegments();
354
+ if (canonical !== null) {
355
+ if (min && canonical < min) canonical = min;
356
+ if (max && canonical > max) canonical = max;
357
+ if (canonical !== value) {
358
+ setValue(canonical);
359
+ syncFromValue(canonical);
360
+ }
361
+ }
362
+ }
363
+ </script>
364
+
365
+ <div
366
+ class={unstyled
367
+ ? [slotClasses?.wrapper, className].filter(Boolean).join(' ')
368
+ : styles.wrapper({ class: [slotClasses?.wrapper, className] })}
369
+ >
370
+ {#if label}
371
+ <span
372
+ id={labelId}
373
+ class={unstyled ? (slotClasses?.label ?? '') : styles.label({ class: slotClasses?.label })}
374
+ >
375
+ {label}
376
+ </span>
377
+ {/if}
378
+
379
+ <div
380
+ role="group"
381
+ aria-labelledby={label ? labelId : undefined}
382
+ aria-label={label ? undefined : ariaLabel}
383
+ aria-disabled={disabled ? 'true' : undefined}
384
+ class={unstyled ? (slotClasses?.field ?? '') : styles.field({ class: slotClasses?.field })}
385
+ onfocusout={handleFocusOut}
386
+ >
387
+ {#if showIcon}
388
+ <span
389
+ class={unstyled ? (slotClasses?.icon ?? '') : styles.icon({ class: slotClasses?.icon })}
390
+ >
391
+ {#if icon}
392
+ {@render icon()}
393
+ {:else}
394
+ <ClockIcon />
395
+ {/if}
396
+ </span>
397
+ {/if}
398
+
399
+ <input
400
+ bind:this={hourEl}
401
+ id={fieldId}
402
+ value={hourStr}
403
+ type="text"
404
+ inputmode="numeric"
405
+ maxlength="2"
406
+ placeholder="--"
407
+ autocomplete="off"
408
+ {disabled}
409
+ {readonly}
410
+ role="spinbutton"
411
+ aria-label={bt('accessibility.timeHours')}
412
+ aria-valuemin={hourMin}
413
+ aria-valuemax={hourMax}
414
+ aria-valuenow={num(hourStr) ?? undefined}
415
+ aria-invalid={error ? 'true' : undefined}
416
+ aria-describedby={describedBy}
417
+ class={segmentClass()}
418
+ oninput={handleHourInput}
419
+ onkeydown={(e) => handleSegmentKeydown('hour', e)}
420
+ onfocus={handleSegmentFocus}
421
+ />
422
+ <span
423
+ aria-hidden="true"
424
+ class={unstyled
425
+ ? (slotClasses?.separator ?? '')
426
+ : styles.separator({ class: slotClasses?.separator })}
427
+ >
428
+ :
429
+ </span>
430
+ <input
431
+ bind:this={minuteEl}
432
+ value={minuteStr}
433
+ type="text"
434
+ inputmode="numeric"
435
+ maxlength="2"
436
+ placeholder="--"
437
+ autocomplete="off"
438
+ {disabled}
439
+ {readonly}
440
+ role="spinbutton"
441
+ aria-label={bt('accessibility.timeMinutes')}
442
+ aria-valuemin={0}
443
+ aria-valuemax={59}
444
+ aria-valuenow={num(minuteStr) ?? undefined}
445
+ aria-invalid={error ? 'true' : undefined}
446
+ aria-describedby={describedBy}
447
+ class={segmentClass()}
448
+ oninput={handleMinuteInput}
449
+ onkeydown={(e) => handleSegmentKeydown('minute', e)}
450
+ onfocus={handleSegmentFocus}
451
+ />
452
+ {#if withSeconds}
453
+ <span
454
+ aria-hidden="true"
455
+ class={unstyled
456
+ ? (slotClasses?.separator ?? '')
457
+ : styles.separator({ class: slotClasses?.separator })}
458
+ >
459
+ :
460
+ </span>
461
+ <input
462
+ bind:this={secondEl}
463
+ value={secondStr}
464
+ type="text"
465
+ inputmode="numeric"
466
+ maxlength="2"
467
+ placeholder="--"
468
+ autocomplete="off"
469
+ {disabled}
470
+ {readonly}
471
+ role="spinbutton"
472
+ aria-label={bt('accessibility.timeSeconds')}
473
+ aria-valuemin={0}
474
+ aria-valuemax={59}
475
+ aria-valuenow={num(secondStr) ?? undefined}
476
+ aria-invalid={error ? 'true' : undefined}
477
+ aria-describedby={describedBy}
478
+ class={segmentClass()}
479
+ oninput={handleSecondInput}
480
+ onkeydown={(e) => handleSegmentKeydown('second', e)}
481
+ onfocus={handleSegmentFocus}
482
+ />
483
+ {/if}
484
+ {#if format === '12h'}
485
+ <!-- A spinbutton (not a button): aria-label on a button would OVERRIDE its
486
+ AM/PM content, so the current state was never announced. As a
487
+ spinbutton the state travels via aria-valuetext — same semantics as
488
+ the sibling segments — and html-aria only permits the role on a
489
+ non-button host, hence the span with manual focus/activation. -->
490
+ <span
491
+ bind:this={meridiemEl}
492
+ role="spinbutton"
493
+ tabindex={disabled ? -1 : 0}
494
+ aria-label={bt('accessibility.timeMeridiem')}
495
+ aria-valuemin={0}
496
+ aria-valuemax={1}
497
+ aria-valuenow={meridiem === 'AM' ? 0 : 1}
498
+ aria-valuetext={meridiem}
499
+ aria-disabled={disabled ? 'true' : undefined}
500
+ aria-readonly={readonly ? 'true' : undefined}
501
+ class={unstyled
502
+ ? (slotClasses?.meridiem ?? '')
503
+ : styles.meridiem({ class: slotClasses?.meridiem })}
504
+ onclick={() => toggleMeridiem()}
505
+ onkeydown={handleMeridiemKeydown}
506
+ >
507
+ {meridiem}
508
+ </span>
509
+ {/if}
510
+ </div>
511
+
512
+ {#if error}
513
+ <div
514
+ id={messageId}
515
+ role="alert"
516
+ class={unstyled
517
+ ? (slotClasses?.message ?? '')
518
+ : styles.message({ class: slotClasses?.message })}
519
+ >
520
+ {error}
521
+ </div>
522
+ {:else if helper}
523
+ <div
524
+ id={messageId}
525
+ class={unstyled
526
+ ? (slotClasses?.message ?? '')
527
+ : styles.message({ class: slotClasses?.message })}
528
+ >
529
+ {helper}
530
+ </div>
531
+ {/if}
532
+
533
+ {#if name}
534
+ <input type="hidden" {name} value={value ?? ''} />
535
+ {/if}
536
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { TimeInputProps } from './index.js';
2
+ declare const TimeInput: import("svelte").Component<TimeInputProps, {}, "value">;
3
+ type TimeInput = ReturnType<typeof TimeInput>;
4
+ export default TimeInput;
@@ -0,0 +1,93 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { TimeInputSlots, TimeInputVariants } from './time-input.variants.js';
3
+ /**
4
+ * @description Segmented time-of-day field — hour / minute (/ second) cells in a
5
+ * single unified control, with per-segment Arrow-key stepping, digit auto-advance,
6
+ * and 12- or 24-hour display. Fills the last form-family gap (Calendar, DatePicker
7
+ * and DateRangePicker cover dates; this covers time). The value is always a
8
+ * canonical 24-hour `HH:MM` (or `HH:MM:SS`) string regardless of display format,
9
+ * and `null` when empty.
10
+ *
11
+ * @tag form
12
+ * @related DatePicker
13
+ * @related NumberInput
14
+ * @related Input
15
+ * @stability beta
16
+ *
17
+ * @example
18
+ * ```svelte
19
+ * <script>
20
+ * let time = $state('09:30');
21
+ * </script>
22
+ * <TimeInput label="Start" bind:value={time} />
23
+ * ```
24
+ *
25
+ * @example 12-hour display with seconds and bounds
26
+ * ```svelte
27
+ * <TimeInput format="12h" withSeconds min="08:00" max="18:00" bind:value={time} />
28
+ * ```
29
+ */
30
+ export interface TimeInputProps extends Omit<TimeInputVariants, 'error'> {
31
+ /**
32
+ * Current time as a canonical 24-hour `HH:MM` / `HH:MM:SS` string; `null` when
33
+ * empty. The stored format never changes with `format`. Supports `bind:value`.
34
+ */
35
+ value?: string | null;
36
+ /** Display the hour as 12-hour with an AM/PM segment. The value stays 24-hour. @default '24h' */
37
+ format?: '12h' | '24h';
38
+ /** Add a seconds segment. @default false */
39
+ withSeconds?: boolean;
40
+ /**
41
+ * Earliest allowed time, canonical 24-hour `HH:MM`(`:SS`). Values below it are
42
+ * clamped up on blur.
43
+ */
44
+ min?: string;
45
+ /**
46
+ * Latest allowed time, canonical 24-hour `HH:MM`(`:SS`). Values above it are
47
+ * clamped down on blur.
48
+ */
49
+ max?: string;
50
+ /** @default false */
51
+ disabled?: boolean;
52
+ /** @default false */
53
+ readonly?: boolean;
54
+ /** Adds a required asterisk to the label. @default false */
55
+ required?: boolean;
56
+ /** Stretch the field to the full width of its container. @default false */
57
+ fullWidth?: boolean;
58
+ /** Show the leading clock icon. @default true */
59
+ showIcon?: boolean;
60
+ /** Label text displayed above the field, linked via `aria-labelledby`. */
61
+ label?: string;
62
+ /** Helper text below the field — hidden when `error` is present. */
63
+ helper?: string;
64
+ /**
65
+ * Error message below the field. When set it overrides `helper`, colours the
66
+ * field danger, and marks the segments `aria-invalid`.
67
+ */
68
+ error?: string;
69
+ /** A custom leading icon; replaces the default clock. */
70
+ icon?: Snippet;
71
+ /** Fires after any change with the canonical 24-hour value (or `null`). */
72
+ onValueChange?: (value: string | null) => void;
73
+ /** Name for a hidden input carrying the canonical value, for native form submission. */
74
+ name?: string;
75
+ /** Extra classes merged onto the root wrapper. */
76
+ class?: string;
77
+ /** Remove all default tv() classes — only user-provided classes apply. */
78
+ unstyled?: boolean;
79
+ /**
80
+ * Per-slot class overrides merged with tv() styles. Slots: wrapper (what
81
+ * `class` also targets) | label | field | icon | segment | separator |
82
+ * meridiem | message.
83
+ */
84
+ slotClasses?: Partial<Record<TimeInputSlots, string>>;
85
+ /** Apply a named preset registered via `<BlocksProvider presets={{ TimeInput: {...} }}>`. */
86
+ preset?: string;
87
+ /** Accessible name for the field group when no visible `label` is set. */
88
+ 'aria-label'?: string;
89
+ /** Root id; the segments derive their ids and ARIA wiring from it. */
90
+ id?: string;
91
+ }
92
+ export { default as TimeInput } from './TimeInput.svelte';
93
+ export { type TimeInputVariants, timeInputVariants } from './time-input.variants.js';
@@ -0,0 +1,2 @@
1
+ export { default as TimeInput } from './TimeInput.svelte';
2
+ export { timeInputVariants } from './time-input.variants.js';