@human-kit/svelte-components 1.0.0-alpha.2 → 1.0.0-alpha.4

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.
Files changed (217) hide show
  1. package/dist/FOCUS_STATE_CONTRACT.md +63 -0
  2. package/dist/FOCUS_STATE_REVIEW_TEMPLATE.md +70 -0
  3. package/dist/calendar/README.md +2 -1
  4. package/dist/calendar/TODO.md +21 -107
  5. package/dist/calendar/body-cell/README.md +15 -0
  6. package/dist/calendar/body-cell/calendar-body-cell.svelte +116 -41
  7. package/dist/calendar/grid/README.md +13 -0
  8. package/dist/calendar/grid-body/README.md +13 -0
  9. package/dist/calendar/grid-header/README.md +13 -0
  10. package/dist/calendar/header-cell/README.md +14 -0
  11. package/dist/calendar/heading/README.md +13 -0
  12. package/dist/calendar/root/README.md +24 -0
  13. package/dist/calendar/root/calendar-root-test.svelte +4 -0
  14. package/dist/calendar/root/calendar-root-test.svelte.d.ts +1 -0
  15. package/dist/calendar/root/calendar-root.svelte +3 -0
  16. package/dist/calendar/root/calendar-root.svelte.d.ts +1 -0
  17. package/dist/calendar/root/context.d.ts +4 -0
  18. package/dist/calendar/root/context.js +28 -25
  19. package/dist/calendar/root/date-utils.d.ts +1 -1
  20. package/dist/calendar/root/date-utils.js +16 -26
  21. package/dist/calendar/trigger-next/README.md +14 -0
  22. package/dist/calendar/trigger-previous/README.md +14 -0
  23. package/dist/clock/README.md +75 -0
  24. package/dist/clock/axis/README.md +24 -0
  25. package/dist/clock/axis/clock-axis.svelte +37 -0
  26. package/dist/clock/axis/clock-axis.svelte.d.ts +8 -0
  27. package/dist/clock/hooks/use-wheel-scroll.svelte.d.ts +16 -0
  28. package/dist/clock/hooks/use-wheel-scroll.svelte.js +336 -0
  29. package/dist/clock/index.d.ts +10 -0
  30. package/dist/clock/index.js +10 -0
  31. package/dist/clock/index.parts.d.ts +4 -0
  32. package/dist/clock/index.parts.js +4 -0
  33. package/dist/clock/root/README.md +38 -0
  34. package/dist/clock/root/clock-root-test.svelte +62 -0
  35. package/dist/clock/root/clock-root-test.svelte.d.ts +14 -0
  36. package/dist/clock/root/clock-root.svelte +329 -0
  37. package/dist/clock/root/clock-root.svelte.d.ts +25 -0
  38. package/dist/clock/root/context.d.ts +22 -0
  39. package/dist/clock/root/context.js +15 -0
  40. package/dist/clock/root/resolve-visible-columns.d.ts +7 -0
  41. package/dist/clock/root/resolve-visible-columns.js +16 -0
  42. package/dist/clock/root/time-utils.d.ts +48 -0
  43. package/dist/clock/root/time-utils.js +314 -0
  44. package/dist/clock/root/wheel-options.d.ts +17 -0
  45. package/dist/clock/root/wheel-options.js +63 -0
  46. package/dist/clock/wheel-column/README.md +25 -0
  47. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte +16 -0
  48. package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte.d.ts +3 -0
  49. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte +29 -0
  50. package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte.d.ts +6 -0
  51. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte +11 -0
  52. package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte.d.ts +3 -0
  53. package/dist/clock/wheel-column/clock-wheel-column-test.svelte +38 -0
  54. package/dist/clock/wheel-column/clock-wheel-column-test.svelte.d.ts +12 -0
  55. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte +38 -0
  56. package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte.d.ts +12 -0
  57. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte +29 -0
  58. package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte.d.ts +6 -0
  59. package/dist/clock/wheel-column/clock-wheel-column.svelte +499 -0
  60. package/dist/clock/wheel-column/clock-wheel-column.svelte.d.ts +17 -0
  61. package/dist/clock/wheel-item/README.md +17 -0
  62. package/dist/clock/wheel-item/clock-wheel-item.svelte +49 -0
  63. package/dist/clock/wheel-item/clock-wheel-item.svelte.d.ts +17 -0
  64. package/dist/combobox/TODO.md +28 -175
  65. package/dist/combobox/button/combobox-button.svelte +2 -0
  66. package/dist/combobox/root/combobox.svelte +30 -0
  67. package/dist/datepicker/README.md +100 -0
  68. package/dist/datepicker/TODO.md +28 -0
  69. package/dist/datepicker/calendar/README.md +19 -0
  70. package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte +60 -0
  71. package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte.d.ts +3 -0
  72. package/dist/datepicker/calendar/date-picker-calendar.svelte +65 -0
  73. package/dist/datepicker/calendar/date-picker-calendar.svelte.d.ts +10 -0
  74. package/dist/datepicker/index.d.ts +18 -0
  75. package/dist/datepicker/index.js +18 -0
  76. package/dist/datepicker/index.parts.d.ts +14 -0
  77. package/dist/datepicker/index.parts.js +14 -0
  78. package/dist/datepicker/input/README.md +15 -0
  79. package/dist/datepicker/input/date-picker-input.svelte +108 -0
  80. package/dist/datepicker/input/date-picker-input.svelte.d.ts +11 -0
  81. package/dist/datepicker/internal/strict-props.d.ts +2 -0
  82. package/dist/datepicker/internal/strict-props.js +28 -0
  83. package/dist/datepicker/popover/README.md +20 -0
  84. package/dist/datepicker/popover/date-picker-popover-handler-test.svelte +57 -0
  85. package/dist/datepicker/popover/date-picker-popover-handler-test.svelte.d.ts +3 -0
  86. package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte +45 -0
  87. package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte.d.ts +18 -0
  88. package/dist/datepicker/popover/date-picker-popover.svelte +87 -0
  89. package/dist/datepicker/popover/date-picker-popover.svelte.d.ts +7 -0
  90. package/dist/datepicker/root/README.md +38 -0
  91. package/dist/datepicker/root/context.d.ts +43 -0
  92. package/dist/datepicker/root/context.js +15 -0
  93. package/dist/datepicker/root/date-picker-bindable-empty-test.svelte +24 -0
  94. package/dist/datepicker/root/date-picker-bindable-empty-test.svelte.d.ts +3 -0
  95. package/dist/datepicker/root/date-picker-bindable-test.svelte +41 -0
  96. package/dist/datepicker/root/date-picker-bindable-test.svelte.d.ts +3 -0
  97. package/dist/datepicker/root/date-picker-empty-test.svelte +47 -0
  98. package/dist/datepicker/root/date-picker-empty-test.svelte.d.ts +3 -0
  99. package/dist/datepicker/root/date-picker-locale-typing-test.svelte +47 -0
  100. package/dist/datepicker/root/date-picker-locale-typing-test.svelte.d.ts +3 -0
  101. package/dist/datepicker/root/date-picker-open-cancel-test.svelte +54 -0
  102. package/dist/datepicker/root/date-picker-open-cancel-test.svelte.d.ts +8 -0
  103. package/dist/datepicker/root/date-picker-root.svelte +495 -0
  104. package/dist/datepicker/root/date-picker-root.svelte.d.ts +24 -0
  105. package/dist/datepicker/root/date-picker-test.svelte +86 -0
  106. package/dist/datepicker/root/date-picker-test.svelte.d.ts +13 -0
  107. package/dist/datepicker/root/date-utils.d.ts +17 -0
  108. package/dist/datepicker/root/date-utils.js +138 -0
  109. package/dist/datepicker/root/draft-evaluation.d.ts +13 -0
  110. package/dist/datepicker/root/draft-evaluation.js +56 -0
  111. package/dist/datepicker/root/focus-controller.d.ts +3 -0
  112. package/dist/datepicker/root/focus-controller.js +15 -0
  113. package/dist/datepicker/root/open-change.d.ts +5 -0
  114. package/dist/datepicker/root/open-change.js +13 -0
  115. package/dist/datepicker/root/open-controller.d.ts +7 -0
  116. package/dist/datepicker/root/open-controller.js +15 -0
  117. package/dist/datepicker/root/segment-controller.d.ts +8 -0
  118. package/dist/datepicker/root/segment-controller.js +53 -0
  119. package/dist/datepicker/root/segment-state.d.ts +18 -0
  120. package/dist/datepicker/root/segment-state.js +134 -0
  121. package/dist/datepicker/root/value-commit.d.ts +4 -0
  122. package/dist/datepicker/root/value-commit.js +8 -0
  123. package/dist/datepicker/segment/README.md +14 -0
  124. package/dist/datepicker/segment/date-picker-segment.svelte +319 -0
  125. package/dist/datepicker/segment/date-picker-segment.svelte.d.ts +9 -0
  126. package/dist/datepicker/trigger/README.md +14 -0
  127. package/dist/datepicker/trigger/date-picker-trigger.svelte +110 -0
  128. package/dist/datepicker/trigger/date-picker-trigger.svelte.d.ts +9 -0
  129. package/dist/dialog/content/dialog-content.svelte +6 -6
  130. package/dist/dialog/root/context.d.ts +2 -1
  131. package/dist/dialog/root/dialog-root.svelte +9 -2
  132. package/dist/index.d.ts +8 -0
  133. package/dist/index.js +8 -0
  134. package/dist/listbox/root/listbox.svelte +44 -0
  135. package/dist/popover/README.md +10 -0
  136. package/dist/popover/content/popover-content-standalone-test.svelte +28 -0
  137. package/dist/popover/content/popover-content-standalone-test.svelte.d.ts +6 -0
  138. package/dist/popover/content/popover-content-test.svelte +2 -1
  139. package/dist/popover/content/popover-content-test.svelte.d.ts +2 -1
  140. package/dist/popover/content/popover-content.svelte +91 -18
  141. package/dist/popover/content/popover-content.svelte.d.ts +5 -1
  142. package/dist/popover/index.d.ts +1 -1
  143. package/dist/popover/index.js +1 -3
  144. package/dist/popover/root/README.md +10 -15
  145. package/dist/popover/root/context.d.ts +16 -7
  146. package/dist/popover/root/context.js +0 -2
  147. package/dist/popover/root/focus-state.d.ts +4 -0
  148. package/dist/popover/root/focus-state.js +33 -0
  149. package/dist/popover/root/popover-root.svelte +90 -17
  150. package/dist/popover/root/popover-root.svelte.d.ts +2 -1
  151. package/dist/popover/root/popover-test.svelte +2 -1
  152. package/dist/popover/root/popover-test.svelte.d.ts +2 -1
  153. package/dist/popover/trigger/popover-trigger-button.svelte +4 -4
  154. package/dist/popover/trigger/popover-trigger.svelte +1 -1
  155. package/dist/portal/portal.svelte +3 -1
  156. package/dist/primitives/click-outside.d.ts +1 -1
  157. package/dist/primitives/click-outside.js +1 -1
  158. package/dist/primitives/focus-trap.d.ts +7 -2
  159. package/dist/primitives/focus-trap.js +50 -17
  160. package/dist/primitives/index.d.ts +1 -0
  161. package/dist/primitives/index.js +1 -0
  162. package/dist/primitives/input-modality.d.ts +7 -0
  163. package/dist/primitives/input-modality.js +125 -0
  164. package/dist/test-utils/focus-contract.d.ts +3 -0
  165. package/dist/test-utils/focus-contract.js +26 -0
  166. package/dist/timepicker/IMPLEMENTATION_PLAN.md +254 -0
  167. package/dist/timepicker/README.md +97 -0
  168. package/dist/timepicker/TODO.md +86 -0
  169. package/dist/timepicker/clock/README.md +14 -0
  170. package/dist/timepicker/clock/time-picker-clock-test.svelte +45 -0
  171. package/dist/timepicker/clock/time-picker-clock-test.svelte.d.ts +11 -0
  172. package/dist/timepicker/clock/time-picker-clock.svelte +65 -0
  173. package/dist/timepicker/clock/time-picker-clock.svelte.d.ts +10 -0
  174. package/dist/timepicker/index.d.ts +14 -0
  175. package/dist/timepicker/index.js +14 -0
  176. package/dist/timepicker/index.parts.d.ts +8 -0
  177. package/dist/timepicker/index.parts.js +8 -0
  178. package/dist/timepicker/input/README.md +15 -0
  179. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte +40 -0
  180. package/dist/timepicker/input/time-picker-input-forwarding-test.svelte.d.ts +3 -0
  181. package/dist/timepicker/input/time-picker-input.svelte +109 -0
  182. package/dist/timepicker/input/time-picker-input.svelte.d.ts +11 -0
  183. package/dist/timepicker/internal/strict-props.d.ts +4 -0
  184. package/dist/timepicker/internal/strict-props.js +51 -0
  185. package/dist/timepicker/popover/README.md +20 -0
  186. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte +22 -0
  187. package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte.d.ts +3 -0
  188. package/dist/timepicker/popover/time-picker-popover.svelte +89 -0
  189. package/dist/timepicker/popover/time-picker-popover.svelte.d.ts +7 -0
  190. package/dist/timepicker/root/README.md +42 -0
  191. package/dist/timepicker/root/context.d.ts +51 -0
  192. package/dist/timepicker/root/context.js +15 -0
  193. package/dist/timepicker/root/time-picker-12h-test.svelte +22 -0
  194. package/dist/timepicker/root/time-picker-12h-test.svelte.d.ts +3 -0
  195. package/dist/timepicker/root/time-picker-bindable-test.svelte +25 -0
  196. package/dist/timepicker/root/time-picker-bindable-test.svelte.d.ts +3 -0
  197. package/dist/timepicker/root/time-picker-empty-test.svelte +20 -0
  198. package/dist/timepicker/root/time-picker-empty-test.svelte.d.ts +3 -0
  199. package/dist/timepicker/root/time-picker-root.svelte +625 -0
  200. package/dist/timepicker/root/time-picker-root.svelte.d.ts +28 -0
  201. package/dist/timepicker/root/time-picker-test.svelte +72 -0
  202. package/dist/timepicker/root/time-picker-test.svelte.d.ts +15 -0
  203. package/dist/timepicker/root/time-utils.d.ts +1 -0
  204. package/dist/timepicker/root/time-utils.js +3 -0
  205. package/dist/timepicker/segment/README.md +14 -0
  206. package/dist/timepicker/segment/time-picker-segment.svelte +365 -0
  207. package/dist/timepicker/segment/time-picker-segment.svelte.d.ts +9 -0
  208. package/dist/timepicker/trigger/README.md +14 -0
  209. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte +35 -0
  210. package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte.d.ts +3 -0
  211. package/dist/timepicker/trigger/time-picker-trigger.svelte +122 -0
  212. package/dist/timepicker/trigger/time-picker-trigger.svelte.d.ts +9 -0
  213. package/dist/utils/date-only.d.ts +11 -0
  214. package/dist/utils/date-only.js +53 -0
  215. package/dist/utils/index.d.ts +1 -0
  216. package/dist/utils/index.js +1 -0
  217. package/package.json +16 -1
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import TimePicker from '../index';
3
+
4
+ type Props = {
5
+ defaultValue?: string;
6
+ defaultOpen?: boolean;
7
+ isDisabled?: boolean;
8
+ isReadOnly?: boolean;
9
+ hourCycle?: 12 | 24;
10
+ granularity?: 'hour' | 'minute' | 'second';
11
+ isRequired?: boolean;
12
+ minValue?: string;
13
+ maxValue?: string;
14
+ popoverAriaLabel?: string;
15
+ };
16
+
17
+ let {
18
+ defaultValue = '14:30',
19
+ defaultOpen = false,
20
+ isDisabled = false,
21
+ isReadOnly = false,
22
+ hourCycle = 24,
23
+ granularity = 'minute',
24
+ isRequired = false,
25
+ minValue,
26
+ maxValue,
27
+ popoverAriaLabel = 'Time picker'
28
+ }: Props = $props();
29
+
30
+ let selectedValue = $state<string | null>('');
31
+ let openState = $state((() => defaultOpen)());
32
+ let openReason = $state('');
33
+ </script>
34
+
35
+ <TimePicker.Root
36
+ {defaultValue}
37
+ {defaultOpen}
38
+ {isDisabled}
39
+ {isReadOnly}
40
+ {hourCycle}
41
+ {granularity}
42
+ {isRequired}
43
+ {minValue}
44
+ {maxValue}
45
+ onChange={(nextValue) => {
46
+ selectedValue = nextValue;
47
+ }}
48
+ onOpenChange={(nextOpen, details) => {
49
+ openState = nextOpen;
50
+ openReason = details.reason;
51
+ }}
52
+ >
53
+ <TimePicker.Input class="time-picker-input" aria-label="Time input">
54
+ {#snippet children(segment)}
55
+ <TimePicker.Segment class="time-picker-segment" {segment} />
56
+ {/snippet}
57
+ </TimePicker.Input>
58
+ <TimePicker.Trigger class="time-picker-trigger">Open time picker</TimePicker.Trigger>
59
+
60
+ <TimePicker.Popover class="time-picker-popover" aria-label={popoverAriaLabel}>
61
+ <TimePicker.Clock class="time-picker-columns">
62
+ {#snippet column(col)}
63
+ <TimePicker.WheelColumn type={col.type} class="time-picker-column h-44" />
64
+ {/snippet}
65
+ </TimePicker.Clock>
66
+ </TimePicker.Popover>
67
+ </TimePicker.Root>
68
+
69
+ <p data-testid="time-picker-value">{selectedValue}</p>
70
+ <p data-testid="time-picker-open">{String(openState)}</p>
71
+ <p data-testid="time-picker-open-reason">{openReason}</p>
72
+ <button type="button" data-testid="outside-button">Outside</button>
@@ -0,0 +1,15 @@
1
+ type Props = {
2
+ defaultValue?: string;
3
+ defaultOpen?: boolean;
4
+ isDisabled?: boolean;
5
+ isReadOnly?: boolean;
6
+ hourCycle?: 12 | 24;
7
+ granularity?: 'hour' | 'minute' | 'second';
8
+ isRequired?: boolean;
9
+ minValue?: string;
10
+ maxValue?: string;
11
+ popoverAriaLabel?: string;
12
+ };
13
+ declare const TimePickerTest: import("svelte").Component<Props, {}, "">;
14
+ type TimePickerTest = ReturnType<typeof TimePickerTest>;
15
+ export default TimePickerTest;
@@ -0,0 +1 @@
1
+ export { type TimePickerGranularity, type TimePickerHourCycle, type TimePickerTimeValue, type TimePickerSegmentType, type TimePickerEditableSegmentType, type TimePickerSegmentPart, type TimePickerDraft, type TimeParts, createEmptyTimePickerDraft, isValidTimePickerValue, parseTimePickerValue, formatTimePickerValue, toDraftFromTimeValue, getRequiredSegments, normalizeSegmentNumberInput, isSegmentValueEmpty, buildTimePartsFromDraft, compareTimeParts, isTimeOutOfRange, clampToStep, adjustSegmentWithStep, buildTimePickerSegments, getEditableSegmentOrder, getSegmentLabel } from '../../clock/root/time-utils';
@@ -0,0 +1,3 @@
1
+ // Canonical source lives in clock/root/time-utils.ts.
2
+ // This barrel re-exports everything so existing TimePicker imports keep working.
3
+ export { createEmptyTimePickerDraft, isValidTimePickerValue, parseTimePickerValue, formatTimePickerValue, toDraftFromTimeValue, getRequiredSegments, normalizeSegmentNumberInput, isSegmentValueEmpty, buildTimePartsFromDraft, compareTimeParts, isTimeOutOfRange, clampToStep, adjustSegmentWithStep, buildTimePickerSegments, getEditableSegmentOrder, getSegmentLabel } from '../../clock/root/time-utils';
@@ -0,0 +1,14 @@
1
+ # TimePicker Segment
2
+
3
+ ## API reference
4
+
5
+ ### TimePicker.Segment
6
+
7
+ Name: `TimePicker.Segment`
8
+ Description: Editable (or literal) segment renderer used inside `TimePicker.Input`.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | --------------------------------- | ---------- | ------------------------------------------------------- |
12
+ | `segment` | `TimePickerSegmentPart` | `required` | Segment metadata and rendered text payload. |
13
+ | `class` | `string` | `''` | CSS class names for the segment span element. |
14
+ | `...restProps` | `HTMLAttributes<HTMLSpanElement>` | `-` | Additional attributes forwarded to the segment element. |
@@ -0,0 +1,365 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { TimePickerSegmentPart } from '../root/context';
4
+ import { useTimePickerContext } from '../root/context';
5
+ import {
6
+ shouldShowFocusVisible,
7
+ trackInteractionModality
8
+ } from '../../primitives/input-modality';
9
+
10
+ type TimePickerSegmentProps = Omit<
11
+ HTMLAttributes<HTMLSpanElement>,
12
+ | 'children'
13
+ | 'class'
14
+ | 'id'
15
+ | 'role'
16
+ | 'contenteditable'
17
+ | 'tabindex'
18
+ | 'aria-label'
19
+ | 'aria-valuemin'
20
+ | 'aria-valuemax'
21
+ | 'aria-valuenow'
22
+ | 'aria-valuetext'
23
+ | 'aria-readonly'
24
+ | 'aria-disabled'
25
+ | 'onfocus'
26
+ | 'onblur'
27
+ | 'onmousedown'
28
+ | 'onclick'
29
+ | 'onselectstart'
30
+ | 'onkeydown'
31
+ | 'onbeforeinput'
32
+ | 'oninput'
33
+ | 'onpaste'
34
+ | 'oncompositionend'
35
+ > & {
36
+ segment: TimePickerSegmentPart;
37
+ class?: string;
38
+ };
39
+
40
+ let { segment, class: className = '', ...restProps }: TimePickerSegmentProps = $props();
41
+ let isFocused = $state(false);
42
+ let segmentRef: HTMLSpanElement | null = $state(null);
43
+ let domResetVersion = $state(0);
44
+
45
+ const timePicker = useTimePickerContext();
46
+ const segmentId = $props.id();
47
+
48
+ const isEditableSegment = $derived(segment.type !== 'literal');
49
+ const isActive = $derived(
50
+ isEditableSegment && (timePicker.activeSegment === segment.type || isFocused)
51
+ );
52
+ const isFocusVisible = $derived(isFocused && timePicker.focusVisible);
53
+
54
+ const valueMin = $derived.by(() => {
55
+ if (segment.type === 'literal') return undefined;
56
+ if (segment.type === 'hour') return timePicker.hourCycle === 12 ? 1 : 0;
57
+ if (segment.type === 'minute' || segment.type === 'second') return 0;
58
+ if (segment.type === 'dayPeriod') return 0;
59
+ return undefined;
60
+ });
61
+
62
+ const valueMax = $derived.by(() => {
63
+ if (segment.type === 'literal') return undefined;
64
+ if (segment.type === 'hour') return timePicker.hourCycle === 12 ? 12 : 23;
65
+ if (segment.type === 'minute' || segment.type === 'second') return 59;
66
+ if (segment.type === 'dayPeriod') return 1;
67
+ return undefined;
68
+ });
69
+
70
+ const valueNow = $derived.by(() => {
71
+ if (segment.type === 'literal') return undefined;
72
+ if (segment.type === 'dayPeriod') {
73
+ const value = (timePicker.getSegmentValue('dayPeriod') || '').toUpperCase();
74
+ if (!value) return undefined;
75
+ return value === 'PM' ? 1 : 0;
76
+ }
77
+ const value = Number(timePicker.getSegmentValue(segment.type));
78
+ return Number.isFinite(value) ? value : undefined;
79
+ });
80
+
81
+ const valueText = $derived.by(() => {
82
+ if (segment.type === 'literal') return segment.text;
83
+ return timePicker.getSegmentValue(segment.type) || segment.text;
84
+ });
85
+
86
+ const segmentLabel = $derived.by(() => {
87
+ if (segment.type === 'literal') return undefined;
88
+ return timePicker.getSegmentLabel(segment.type);
89
+ });
90
+
91
+ $effect(() => {
92
+ if (segment.type === 'literal') return;
93
+ const segmentType = segment.type;
94
+ timePicker.registerSegmentRef(segmentType, segmentRef);
95
+ return () => {
96
+ timePicker.registerSegmentRef(segmentType, null);
97
+ };
98
+ });
99
+
100
+ function handleFocus(event: FocusEvent) {
101
+ if (segment.type === 'literal') return;
102
+ if (timePicker.isDisabled) {
103
+ isFocused = false;
104
+ return;
105
+ }
106
+ isFocused = true;
107
+ timePicker.syncFocusWithin();
108
+ timePicker.setFocusVisible(shouldShowFocusVisible(event.currentTarget as HTMLElement));
109
+ timePicker.setActiveSegment(segment.type);
110
+ }
111
+
112
+ function handleBlur() {
113
+ if (segment.type === 'literal') return;
114
+ isFocused = false;
115
+ queueMicrotask(() => {
116
+ timePicker.syncFocusWithin();
117
+ });
118
+ }
119
+
120
+ function handleMouseDown(event: MouseEvent) {
121
+ if (segment.type === 'literal') return;
122
+ if (timePicker.isDisabled) {
123
+ event.preventDefault();
124
+ return;
125
+ }
126
+ trackInteractionModality(event, event.currentTarget as HTMLElement);
127
+ timePicker.setFocusVisible(false);
128
+ event.preventDefault();
129
+ (event.currentTarget as HTMLElement).focus();
130
+ timePicker.setActiveSegment(segment.type);
131
+ }
132
+
133
+ function handleClick(event: MouseEvent) {
134
+ if (segment.type === 'literal') return;
135
+ if (timePicker.isDisabled) {
136
+ event.preventDefault();
137
+ return;
138
+ }
139
+ (event.currentTarget as HTMLElement).focus();
140
+ timePicker.setActiveSegment(segment.type);
141
+ }
142
+
143
+ function handleSelectStart(event: Event) {
144
+ if (!segment.isPlaceholder) return;
145
+ event.preventDefault();
146
+ }
147
+
148
+ function requestDomReset() {
149
+ domResetVersion += 1;
150
+ }
151
+
152
+ function shouldResetDomForDrift(target: EventTarget | null | undefined): boolean {
153
+ if (segment.type === 'literal') return false;
154
+ const element = target instanceof HTMLElement ? target : segmentRef;
155
+ if (!element) return false;
156
+ return (element.textContent ?? '') !== segment.text;
157
+ }
158
+
159
+ function handleBeforeInput(event: InputEvent) {
160
+ if (segment.type === 'literal') return;
161
+ if (timePicker.isDisabled || timePicker.isReadOnly) {
162
+ event.preventDefault();
163
+ return;
164
+ }
165
+
166
+ // Segment edits are keyboard-driven through `onkeydown` and root state.
167
+ // Block direct DOM mutations from paste/drop/IME/beforeinput paths.
168
+ event.preventDefault();
169
+ }
170
+
171
+ function handleInput(event: Event) {
172
+ if (!shouldResetDomForDrift(event.currentTarget)) return;
173
+ requestDomReset();
174
+ }
175
+
176
+ function handlePaste(event: ClipboardEvent) {
177
+ if (segment.type === 'literal') return;
178
+ event.preventDefault();
179
+ if (!shouldResetDomForDrift(event.currentTarget)) return;
180
+ requestDomReset();
181
+ }
182
+
183
+ function handleCompositionEnd(event: CompositionEvent) {
184
+ if (!shouldResetDomForDrift(event.currentTarget)) return;
185
+ requestDomReset();
186
+ }
187
+
188
+ function handleKeydown(event: KeyboardEvent) {
189
+ if (segment.type === 'literal') return;
190
+ if (timePicker.isDisabled) return;
191
+ trackInteractionModality(event, event.currentTarget as HTMLElement);
192
+ timePicker.setFocusVisible(true);
193
+
194
+ if (segment.type === 'dayPeriod') {
195
+ if (event.key.toLowerCase() === 'a') {
196
+ event.preventDefault();
197
+ timePicker.setSegmentValue('dayPeriod', 'AM');
198
+ return;
199
+ }
200
+ if (event.key.toLowerCase() === 'p') {
201
+ event.preventDefault();
202
+ timePicker.setSegmentValue('dayPeriod', 'PM');
203
+ return;
204
+ }
205
+ }
206
+
207
+ if (event.key === 'ArrowRight') {
208
+ event.preventDefault();
209
+ if (!timePicker.focusNextSegment(segment.type)) {
210
+ timePicker.triggerRef?.focus();
211
+ }
212
+ return;
213
+ }
214
+
215
+ if (event.key === 'ArrowLeft') {
216
+ event.preventDefault();
217
+ timePicker.focusPreviousSegment(segment.type);
218
+ return;
219
+ }
220
+
221
+ if (event.key === 'ArrowUp') {
222
+ event.preventDefault();
223
+ timePicker.adjustSegmentValue(segment.type, 1);
224
+ return;
225
+ }
226
+
227
+ if (event.key === 'ArrowDown') {
228
+ event.preventDefault();
229
+ timePicker.adjustSegmentValue(segment.type, -1);
230
+ return;
231
+ }
232
+
233
+ if (event.key === 'PageUp') {
234
+ event.preventDefault();
235
+ const step = segment.type === 'hour' ? 1 : 5;
236
+ timePicker.adjustSegmentValue(segment.type, step);
237
+ return;
238
+ }
239
+
240
+ if (event.key === 'PageDown') {
241
+ event.preventDefault();
242
+ const step = segment.type === 'hour' ? 1 : 5;
243
+ timePicker.adjustSegmentValue(segment.type, -step);
244
+ return;
245
+ }
246
+
247
+ if (event.key === 'Home') {
248
+ event.preventDefault();
249
+ if (segment.type === 'hour') {
250
+ timePicker.setSegmentValue('hour', timePicker.hourCycle === 12 ? '1' : '0');
251
+ } else if (segment.type === 'minute') {
252
+ timePicker.setSegmentValue('minute', '0');
253
+ } else if (segment.type === 'second') {
254
+ timePicker.setSegmentValue('second', '0');
255
+ } else {
256
+ timePicker.setSegmentValue('dayPeriod', 'AM');
257
+ }
258
+ return;
259
+ }
260
+
261
+ if (event.key === 'End') {
262
+ event.preventDefault();
263
+ if (segment.type === 'hour') {
264
+ timePicker.setSegmentValue('hour', timePicker.hourCycle === 12 ? '12' : '23');
265
+ } else if (segment.type === 'minute') {
266
+ timePicker.setSegmentValue('minute', '59');
267
+ } else if (segment.type === 'second') {
268
+ timePicker.setSegmentValue('second', '59');
269
+ } else {
270
+ timePicker.setSegmentValue('dayPeriod', 'PM');
271
+ }
272
+ return;
273
+ }
274
+
275
+ if (event.key === 'Delete' || event.key === 'Backspace') {
276
+ event.preventDefault();
277
+ const currentValue = timePicker.getSegmentValue(segment.type);
278
+ if (currentValue.length === 0) {
279
+ if (event.key === 'Backspace') {
280
+ timePicker.focusPreviousSegment(segment.type);
281
+ }
282
+ return;
283
+ }
284
+ timePicker.setSegmentValue(segment.type, currentValue.slice(0, -1));
285
+ return;
286
+ }
287
+
288
+ if (event.key.length === 1 && /\d/.test(event.key)) {
289
+ event.preventDefault();
290
+ const didComplete = timePicker.typeSegmentDigit(segment.type, event.key);
291
+ if (didComplete) {
292
+ timePicker.focusNextSegment(segment.type);
293
+ }
294
+ return;
295
+ }
296
+
297
+ if (event.key === '/' || event.key === '-' || event.key === '.' || event.key === ':') {
298
+ event.preventDefault();
299
+ const currentValue = timePicker.getSegmentValue(segment.type);
300
+ if (currentValue.length === 0) return;
301
+ timePicker.focusNextSegment(segment.type);
302
+ return;
303
+ }
304
+
305
+ if (event.key === 'Tab') {
306
+ return;
307
+ }
308
+
309
+ event.preventDefault();
310
+ }
311
+ </script>
312
+
313
+ {#if segment.type === 'literal'}
314
+ <span
315
+ class={className}
316
+ {...restProps}
317
+ data-placeholder={segment.isPlaceholder || undefined}
318
+ data-type={segment.type}
319
+ aria-hidden="true"
320
+ >
321
+ {segment.text}
322
+ </span>
323
+ {:else}
324
+ {#key `${segmentId}-${domResetVersion}`}
325
+ <span
326
+ bind:this={segmentRef}
327
+ id={segmentId}
328
+ class={className}
329
+ {...restProps}
330
+ data-time-picker-segment="true"
331
+ data-placeholder={segment.isPlaceholder || undefined}
332
+ data-type={segment.type}
333
+ data-focused={isActive ? 'true' : undefined}
334
+ data-focus-visible={isFocusVisible ? 'true' : undefined}
335
+ role="spinbutton"
336
+ aria-valuetext={valueText}
337
+ aria-valuemin={valueMin}
338
+ aria-valuemax={valueMax}
339
+ aria-valuenow={valueNow}
340
+ aria-label={segmentLabel}
341
+ aria-readonly={timePicker.isReadOnly || undefined}
342
+ aria-disabled={timePicker.isDisabled || undefined}
343
+ contenteditable={!timePicker.isDisabled && !timePicker.isReadOnly}
344
+ spellcheck="false"
345
+ enterkeyhint="next"
346
+ inputmode={segment.type === 'dayPeriod' ? 'text' : 'numeric'}
347
+ tabindex={timePicker.isDisabled ? -1 : 0}
348
+ style={segment.isPlaceholder
349
+ ? 'caret-color: transparent; user-select: none;'
350
+ : 'caret-color: transparent;'}
351
+ onfocus={handleFocus}
352
+ onblur={handleBlur}
353
+ onmousedown={handleMouseDown}
354
+ onclick={handleClick}
355
+ onselectstart={handleSelectStart}
356
+ onkeydown={handleKeydown}
357
+ onbeforeinput={handleBeforeInput}
358
+ oninput={handleInput}
359
+ onpaste={handlePaste}
360
+ oncompositionend={handleCompositionEnd}
361
+ >
362
+ {segment.text}
363
+ </span>
364
+ {/key}
365
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { TimePickerSegmentPart } from '../root/context';
3
+ type TimePickerSegmentProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class' | 'id' | 'role' | 'contenteditable' | 'tabindex' | 'aria-label' | 'aria-valuemin' | 'aria-valuemax' | 'aria-valuenow' | 'aria-valuetext' | 'aria-readonly' | 'aria-disabled' | 'onfocus' | 'onblur' | 'onmousedown' | 'onclick' | 'onselectstart' | 'onkeydown' | 'onbeforeinput' | 'oninput' | 'onpaste' | 'oncompositionend'> & {
4
+ segment: TimePickerSegmentPart;
5
+ class?: string;
6
+ };
7
+ declare const TimePickerSegment: import("svelte").Component<TimePickerSegmentProps, {}, "">;
8
+ type TimePickerSegment = ReturnType<typeof TimePickerSegment>;
9
+ export default TimePickerSegment;
@@ -0,0 +1,14 @@
1
+ # TimePicker Trigger
2
+
3
+ ## API reference
4
+
5
+ ### TimePicker.Trigger
6
+
7
+ Name: `TimePicker.Trigger`
8
+ Description: Button part that toggles `TimePicker.Popover` and synchronizes focus modality with root state.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | ---------------------- | ----------- | ------------------------------------------------------ |
12
+ | `children` | `Snippet` | `undefined` | Optional trigger content. |
13
+ | `class` | `string` | `''` | CSS class names for the trigger button. |
14
+ | `...restProps` | `HTMLButtonAttributes` | `-` | Additional button attributes forwarded to the trigger. |
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import TimePicker from '../index';
3
+
4
+ let mouseDownCount = $state(0);
5
+ let clickCount = $state(0);
6
+ let keyCount = $state(0);
7
+ </script>
8
+
9
+ <TimePicker.Root defaultValue="14:30">
10
+ <TimePicker.Input aria-label="Time input">
11
+ {#snippet children(segment)}
12
+ <TimePicker.Segment {segment} />
13
+ {/snippet}
14
+ </TimePicker.Input>
15
+ <TimePicker.Trigger
16
+ onmousedown={() => {
17
+ mouseDownCount += 1;
18
+ }}
19
+ onclick={() => {
20
+ clickCount += 1;
21
+ }}
22
+ onkeydown={() => {
23
+ keyCount += 1;
24
+ }}
25
+ >
26
+ Open time picker
27
+ </TimePicker.Trigger>
28
+ <TimePicker.Popover>
29
+ <TimePicker.Clock />
30
+ </TimePicker.Popover>
31
+ </TimePicker.Root>
32
+
33
+ <p data-testid="trigger-mousedown-count">{mouseDownCount}</p>
34
+ <p data-testid="trigger-click-count">{clickCount}</p>
35
+ <p data-testid="trigger-key-count">{keyCount}</p>
@@ -0,0 +1,3 @@
1
+ declare const TimePickerTriggerForwardingTest: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TimePickerTriggerForwardingTest = ReturnType<typeof TimePickerTriggerForwardingTest>;
3
+ export default TimePickerTriggerForwardingTest;
@@ -0,0 +1,122 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ import { useTimePickerContext } from '../root/context';
5
+ import {
6
+ shouldShowFocusVisible,
7
+ trackInteractionModality
8
+ } from '../../primitives/input-modality';
9
+ import { composeEventHandlers } from '../internal/strict-props';
10
+
11
+ type TimePickerTriggerProps = Omit<
12
+ HTMLButtonAttributes,
13
+ 'type' | 'children' | 'class' | 'aria-haspopup' | 'aria-expanded'
14
+ > & {
15
+ children?: Snippet;
16
+ class?: string;
17
+ };
18
+
19
+ let {
20
+ children,
21
+ class: className = '',
22
+ onmousedown: onMouseDownExternal,
23
+ onfocus: onFocusExternal,
24
+ onblur: onBlurExternal,
25
+ onkeydown: onKeydownExternal,
26
+ onclick: onClickExternal,
27
+ ...restProps
28
+ }: TimePickerTriggerProps = $props();
29
+
30
+ let buttonRef: HTMLButtonElement | null = $state(null);
31
+ let isFocused = $state(false);
32
+ const timePicker = useTimePickerContext();
33
+
34
+ $effect(() => {
35
+ if (timePicker.isReadOnly) {
36
+ timePicker.setTriggerRef(null);
37
+ return;
38
+ }
39
+ if (buttonRef) {
40
+ timePicker.setTriggerRef(buttonRef);
41
+ }
42
+ });
43
+
44
+ function handleFocus() {
45
+ if (timePicker.isDisabled) {
46
+ isFocused = false;
47
+ return;
48
+ }
49
+ if (!buttonRef) return;
50
+ timePicker.setTriggerRef(buttonRef);
51
+ timePicker.setActiveSegment(null);
52
+ isFocused = true;
53
+ timePicker.syncFocusWithin();
54
+ timePicker.setFocusVisible(shouldShowFocusVisible(buttonRef));
55
+ }
56
+
57
+ function handleBlur() {
58
+ isFocused = false;
59
+ queueMicrotask(() => {
60
+ timePicker.syncFocusWithin();
61
+ });
62
+ }
63
+
64
+ function handleMouseDown(event: MouseEvent) {
65
+ if (timePicker.isDisabled || timePicker.isReadOnly) return;
66
+ trackInteractionModality(event, buttonRef);
67
+ timePicker.setFocusVisible(false);
68
+ event.preventDefault();
69
+ }
70
+
71
+ function handleClick(event: MouseEvent) {
72
+ if (timePicker.isDisabled || timePicker.isReadOnly) return;
73
+ if (event.detail > 0) {
74
+ trackInteractionModality(event, buttonRef);
75
+ }
76
+ if (buttonRef) {
77
+ timePicker.setTriggerRef(buttonRef);
78
+ }
79
+ timePicker.togglePopover('trigger-press', event);
80
+ }
81
+
82
+ function handleKeydown(event: KeyboardEvent) {
83
+ if (timePicker.isDisabled) return;
84
+ trackInteractionModality(event, buttonRef);
85
+ if (event.key === 'Enter' || event.key === ' ') {
86
+ timePicker.setFocusVisible(true);
87
+ return;
88
+ }
89
+ if (event.key !== 'ArrowLeft') return;
90
+ timePicker.setFocusVisible(true);
91
+ event.preventDefault();
92
+ timePicker.focusLastSegment();
93
+ }
94
+ </script>
95
+
96
+ {#if !timePicker.isReadOnly}
97
+ <button
98
+ bind:this={buttonRef}
99
+ type="button"
100
+ disabled={timePicker.isDisabled}
101
+ class={className}
102
+ aria-haspopup="dialog"
103
+ aria-expanded={timePicker.open}
104
+ data-disabled={timePicker.isDisabled || undefined}
105
+ data-focused={isFocused || undefined}
106
+ data-focus-visible={isFocused && timePicker.focusVisible ? 'true' : undefined}
107
+ {...restProps}
108
+ onmousedown={composeEventHandlers(handleMouseDown, onMouseDownExternal ?? undefined)}
109
+ onfocus={composeEventHandlers(handleFocus, onFocusExternal ?? undefined)}
110
+ onblur={composeEventHandlers(handleBlur, onBlurExternal ?? undefined)}
111
+ onkeydown={composeEventHandlers(handleKeydown, onKeydownExternal ?? undefined, {
112
+ skipExternalOnDefaultPrevented: true
113
+ })}
114
+ onclick={composeEventHandlers(handleClick, onClickExternal ?? undefined, {
115
+ skipExternalOnDefaultPrevented: true
116
+ })}
117
+ >
118
+ {#if children}
119
+ {@render children()}
120
+ {/if}
121
+ </button>
122
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ type TimePickerTriggerProps = Omit<HTMLButtonAttributes, 'type' | 'children' | 'class' | 'aria-haspopup' | 'aria-expanded'> & {
4
+ children?: Snippet;
5
+ class?: string;
6
+ };
7
+ declare const TimePickerTrigger: import("svelte").Component<TimePickerTriggerProps, {}, "">;
8
+ type TimePickerTrigger = ReturnType<typeof TimePickerTrigger>;
9
+ export default TimePickerTrigger;
@@ -0,0 +1,11 @@
1
+ export type DateOnlyValue = string;
2
+ export declare function createUtcDate(year: number, monthIndex: number, day: number): Date;
3
+ export declare function parseDateOnlyParts(value: string): {
4
+ year: number;
5
+ month: number;
6
+ day: number;
7
+ } | null;
8
+ export declare function isValidDateOnlyValue(value: string | undefined): value is DateOnlyValue;
9
+ export declare function parseDateOnlyValue(value: string): Date | null;
10
+ export declare function formatDateOnlyValue(date: Date): DateOnlyValue;
11
+ export declare function compareDateOnlyValues(left: DateOnlyValue, right: DateOnlyValue): number;