@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,319 @@
1
+ <script module lang="ts">
2
+ const monthFormatterCache: Record<string, Intl.DateTimeFormat> = Object.create(null);
3
+
4
+ function getMonthFormatter(locale: string): Intl.DateTimeFormat {
5
+ let formatter = monthFormatterCache[locale];
6
+ if (!formatter) {
7
+ formatter = new Intl.DateTimeFormat(locale, {
8
+ month: 'long',
9
+ timeZone: 'UTC'
10
+ });
11
+ monthFormatterCache[locale] = formatter;
12
+ }
13
+ return formatter;
14
+ }
15
+ </script>
16
+
17
+ <script lang="ts">
18
+ import type { HTMLAttributes } from 'svelte/elements';
19
+ import type { DatePickerSegmentPart } from '../root/context';
20
+ import { useDatePickerContext } from '../root/context';
21
+ import {
22
+ shouldShowFocusVisible,
23
+ trackInteractionModality
24
+ } from '../../primitives/input-modality';
25
+
26
+ type DatePickerSegmentProps = Omit<
27
+ HTMLAttributes<HTMLSpanElement>,
28
+ | 'children'
29
+ | 'class'
30
+ | 'id'
31
+ | 'role'
32
+ | 'contenteditable'
33
+ | 'tabindex'
34
+ | 'aria-label'
35
+ | 'aria-labelledby'
36
+ | 'aria-valuemin'
37
+ | 'aria-valuemax'
38
+ | 'aria-valuenow'
39
+ | 'aria-valuetext'
40
+ | 'aria-readonly'
41
+ | 'aria-disabled'
42
+ | 'onfocus'
43
+ | 'onblur'
44
+ | 'onmousedown'
45
+ | 'onclick'
46
+ | 'onselectstart'
47
+ | 'onkeydown'
48
+ > & {
49
+ segment: DatePickerSegmentPart;
50
+ class?: string;
51
+ };
52
+
53
+ let { segment, class: className = '', ...restProps }: DatePickerSegmentProps = $props();
54
+ let isFocused = $state(false);
55
+ let segmentRef: HTMLSpanElement | null = $state(null);
56
+
57
+ const datePicker = useDatePickerContext();
58
+
59
+ const isEditableSegment = $derived(segment.type !== 'literal');
60
+ const isActive = $derived(
61
+ isEditableSegment && (datePicker.activeSegment === segment.type || isFocused)
62
+ );
63
+ const isFocusVisible = $derived(isFocused && datePicker.focusVisible);
64
+ const segmentId = $props.id();
65
+
66
+ const currentNumericValue = $derived.by(() => {
67
+ if (segment.type === 'literal') return undefined;
68
+ const segmentValue = datePicker.getSegmentValue(segment.type);
69
+ if (segmentValue.length === 0) return undefined;
70
+ const value = Number(segmentValue);
71
+ return Number.isFinite(value) ? value : undefined;
72
+ });
73
+
74
+ const valueMin = $derived.by(() => {
75
+ if (segment.type === 'literal') return undefined;
76
+ if (segment.type === 'month') return 1;
77
+ if (segment.type === 'day') return 1;
78
+ return 1;
79
+ });
80
+
81
+ const valueMax = $derived.by(() => {
82
+ if (segment.type === 'literal') return undefined;
83
+ if (segment.type === 'month') return 12;
84
+ if (segment.type === 'day') return 31;
85
+ return 9999;
86
+ });
87
+
88
+ const valueText = $derived.by(() => {
89
+ if (segment.type === 'literal') return segment.text;
90
+ if (segment.type === 'month' && currentNumericValue) {
91
+ const monthLabel = getMonthFormatter(datePicker.locale).format(
92
+ new Date(Date.UTC(2030, currentNumericValue - 1, 1))
93
+ );
94
+ return `${currentNumericValue} - ${monthLabel}`;
95
+ }
96
+ return segment.text;
97
+ });
98
+
99
+ const segmentLabel = $derived.by(() => {
100
+ if (segment.type === 'literal') return undefined;
101
+ return datePicker.getSegmentLabel(segment.type);
102
+ });
103
+
104
+ $effect(() => {
105
+ if (segment.type === 'literal') return;
106
+ const segmentType = segment.type;
107
+ datePicker.registerSegmentRef(segmentType, segmentRef);
108
+ return () => {
109
+ datePicker.registerSegmentRef(segmentType, null);
110
+ };
111
+ });
112
+
113
+ function handleFocus(event: FocusEvent) {
114
+ if (segment.type === 'literal') return;
115
+ if (datePicker.isDisabled) {
116
+ isFocused = false;
117
+ return;
118
+ }
119
+ isFocused = true;
120
+ datePicker.syncFocusWithin();
121
+ datePicker.setFocusVisible(shouldShowFocusVisible(event.currentTarget as HTMLElement));
122
+ datePicker.setActiveSegment(segment.type);
123
+ }
124
+
125
+ function handleBlur() {
126
+ if (segment.type === 'literal') return;
127
+ isFocused = false;
128
+ queueMicrotask(() => {
129
+ datePicker.syncFocusWithin();
130
+ });
131
+ }
132
+
133
+ function handleMouseDown(event: MouseEvent) {
134
+ if (segment.type === 'literal') return;
135
+ if (datePicker.isDisabled) {
136
+ event.preventDefault();
137
+ return;
138
+ }
139
+ trackInteractionModality(event, event.currentTarget as HTMLElement);
140
+ datePicker.setFocusVisible(false);
141
+ event.preventDefault();
142
+ const target = event.currentTarget as HTMLElement;
143
+ target.focus();
144
+ datePicker.setActiveSegment(segment.type);
145
+ }
146
+
147
+ function handleClick(event: MouseEvent) {
148
+ if (segment.type === 'literal') return;
149
+ if (datePicker.isDisabled) {
150
+ event.preventDefault();
151
+ return;
152
+ }
153
+ const target = event.currentTarget as HTMLElement;
154
+ target.focus();
155
+ datePicker.setActiveSegment(segment.type);
156
+ }
157
+
158
+ function handleSelectStart(event: Event) {
159
+ if (!segment.isPlaceholder) return;
160
+ event.preventDefault();
161
+ }
162
+
163
+ function handleKeydown(event: KeyboardEvent) {
164
+ if (segment.type === 'literal') return;
165
+ if (datePicker.isDisabled) return;
166
+ trackInteractionModality(event, event.currentTarget as HTMLElement);
167
+ datePicker.setFocusVisible(true);
168
+
169
+ if (event.key === 'ArrowRight') {
170
+ event.preventDefault();
171
+ if (!datePicker.focusNextSegment(segment.type)) {
172
+ datePicker.triggerRef?.focus();
173
+ }
174
+ return;
175
+ }
176
+
177
+ if (event.key === 'ArrowLeft') {
178
+ event.preventDefault();
179
+ datePicker.focusPreviousSegment(segment.type);
180
+ return;
181
+ }
182
+
183
+ if (event.key === 'ArrowUp') {
184
+ event.preventDefault();
185
+ datePicker.adjustSegmentValue(segment.type, 1);
186
+ return;
187
+ }
188
+
189
+ if (event.key === 'ArrowDown') {
190
+ event.preventDefault();
191
+ datePicker.adjustSegmentValue(segment.type, -1);
192
+ return;
193
+ }
194
+
195
+ if (event.key === 'PageUp') {
196
+ event.preventDefault();
197
+ const step = segment.type === 'year' ? 10 : 5;
198
+ datePicker.adjustSegmentValue(segment.type, step);
199
+ return;
200
+ }
201
+
202
+ if (event.key === 'PageDown') {
203
+ event.preventDefault();
204
+ const step = segment.type === 'year' ? 10 : 5;
205
+ datePicker.adjustSegmentValue(segment.type, -step);
206
+ return;
207
+ }
208
+
209
+ if (event.key === 'Home') {
210
+ event.preventDefault();
211
+ if (segment.type === 'month') {
212
+ datePicker.setSegmentValue('month', '1');
213
+ } else if (segment.type === 'day') {
214
+ datePicker.setSegmentValue('day', '1');
215
+ } else {
216
+ datePicker.setSegmentValue('year', '1');
217
+ }
218
+ return;
219
+ }
220
+
221
+ if (event.key === 'End') {
222
+ event.preventDefault();
223
+ if (segment.type === 'month') {
224
+ datePicker.setSegmentValue('month', '12');
225
+ } else if (segment.type === 'day') {
226
+ datePicker.setSegmentValue('day', '31');
227
+ } else {
228
+ datePicker.setSegmentValue('year', '9999');
229
+ }
230
+ return;
231
+ }
232
+
233
+ if (event.key === 'Delete' || event.key === 'Backspace') {
234
+ event.preventDefault();
235
+ const currentValue = datePicker.getSegmentValue(segment.type);
236
+ if (currentValue.length === 0) {
237
+ if (event.key === 'Backspace') {
238
+ datePicker.focusPreviousSegment(segment.type);
239
+ }
240
+ return;
241
+ }
242
+ datePicker.setSegmentValue(segment.type, currentValue.slice(0, -1));
243
+ return;
244
+ }
245
+
246
+ if (event.key.length === 1 && /\d/.test(event.key)) {
247
+ event.preventDefault();
248
+ const didComplete = datePicker.typeSegmentDigit(segment.type, event.key);
249
+ if (didComplete) {
250
+ datePicker.focusNextSegment(segment.type);
251
+ }
252
+ return;
253
+ }
254
+
255
+ if (event.key === '/' || event.key === '-' || event.key === '.') {
256
+ event.preventDefault();
257
+ const currentValue = datePicker.getSegmentValue(segment.type);
258
+ if (currentValue.length === 0) {
259
+ return;
260
+ }
261
+ datePicker.focusNextSegment(segment.type);
262
+ return;
263
+ }
264
+
265
+ if (event.key === 'Tab') {
266
+ return;
267
+ }
268
+
269
+ event.preventDefault();
270
+ }
271
+ </script>
272
+
273
+ {#if segment.type === 'literal'}
274
+ <span
275
+ class={className}
276
+ {...restProps}
277
+ data-placeholder={segment.isPlaceholder || undefined}
278
+ data-type={segment.type}
279
+ aria-hidden="true"
280
+ >
281
+ {segment.text}
282
+ </span>
283
+ {:else}
284
+ <span
285
+ bind:this={segmentRef}
286
+ id={segmentId}
287
+ class={className}
288
+ {...restProps}
289
+ data-date-picker-segment="true"
290
+ data-placeholder={segment.isPlaceholder || undefined}
291
+ data-type={segment.type}
292
+ data-focused={isActive ? 'true' : undefined}
293
+ data-focus-visible={isFocusVisible ? 'true' : undefined}
294
+ role="spinbutton"
295
+ aria-valuetext={valueText}
296
+ aria-valuemin={valueMin}
297
+ aria-valuemax={valueMax}
298
+ aria-valuenow={currentNumericValue}
299
+ aria-label={segmentLabel}
300
+ aria-readonly={datePicker.isReadOnly || undefined}
301
+ aria-disabled={datePicker.isDisabled || undefined}
302
+ contenteditable={!datePicker.isDisabled && !datePicker.isReadOnly}
303
+ spellcheck="false"
304
+ enterkeyhint="next"
305
+ inputmode="numeric"
306
+ tabindex={datePicker.isDisabled ? -1 : 0}
307
+ style={segment.isPlaceholder
308
+ ? 'caret-color: transparent; user-select: none;'
309
+ : 'caret-color: transparent;'}
310
+ onfocus={handleFocus}
311
+ onblur={handleBlur}
312
+ onmousedown={handleMouseDown}
313
+ onclick={handleClick}
314
+ onselectstart={handleSelectStart}
315
+ onkeydown={handleKeydown}
316
+ >
317
+ {segment.text}
318
+ </span>
319
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { DatePickerSegmentPart } from '../root/context';
3
+ type DatePickerSegmentProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class' | 'id' | 'role' | 'contenteditable' | 'tabindex' | 'aria-label' | 'aria-labelledby' | 'aria-valuemin' | 'aria-valuemax' | 'aria-valuenow' | 'aria-valuetext' | 'aria-readonly' | 'aria-disabled' | 'onfocus' | 'onblur' | 'onmousedown' | 'onclick' | 'onselectstart' | 'onkeydown'> & {
4
+ segment: DatePickerSegmentPart;
5
+ class?: string;
6
+ };
7
+ declare const DatePickerSegment: import("svelte").Component<DatePickerSegmentProps, {}, "">;
8
+ type DatePickerSegment = ReturnType<typeof DatePickerSegment>;
9
+ export default DatePickerSegment;
@@ -0,0 +1,14 @@
1
+ # DatePicker Trigger
2
+
3
+ ## API reference
4
+
5
+ ### DatePicker.Trigger
6
+
7
+ Name: `DatePicker.Trigger`
8
+ Description: Button part that toggles `DatePicker.Popover` and anchors positioning/focus behavior.
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,110 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ import { useDatePickerContext } from '../root/context';
5
+ import {
6
+ shouldShowFocusVisible,
7
+ trackInteractionModality
8
+ } from '../../primitives/input-modality';
9
+
10
+ type DatePickerTriggerProps = Omit<
11
+ HTMLButtonAttributes,
12
+ 'type' | 'children' | 'class' | 'onclick' | 'aria-haspopup' | 'aria-expanded'
13
+ > & {
14
+ children?: Snippet;
15
+ class?: string;
16
+ };
17
+
18
+ let { children, class: className = '', ...restProps }: DatePickerTriggerProps = $props();
19
+
20
+ let buttonRef: HTMLButtonElement | null = $state(null);
21
+ let isFocused = $state(false);
22
+ const datePicker = useDatePickerContext();
23
+
24
+ $effect(() => {
25
+ if (datePicker.isReadOnly) {
26
+ datePicker.setTriggerRef(null);
27
+ return;
28
+ }
29
+ if (buttonRef) {
30
+ datePicker.setTriggerRef(buttonRef);
31
+ }
32
+ });
33
+
34
+ function handleFocus() {
35
+ if (datePicker.isDisabled) {
36
+ isFocused = false;
37
+ return;
38
+ }
39
+ if (buttonRef) {
40
+ datePicker.setTriggerRef(buttonRef);
41
+ datePicker.setActiveSegment(null);
42
+ isFocused = true;
43
+ datePicker.syncFocusWithin();
44
+ datePicker.setFocusVisible(shouldShowFocusVisible(buttonRef));
45
+ }
46
+ }
47
+
48
+ function handleBlur() {
49
+ isFocused = false;
50
+ queueMicrotask(() => {
51
+ datePicker.syncFocusWithin();
52
+ });
53
+ }
54
+
55
+ function handleMouseDown(event: MouseEvent) {
56
+ if (datePicker.isDisabled || datePicker.isReadOnly) return;
57
+ trackInteractionModality(event, buttonRef);
58
+ datePicker.setFocusVisible(false);
59
+ event.preventDefault();
60
+ }
61
+
62
+ function handleClick(event: MouseEvent) {
63
+ if (datePicker.isDisabled || datePicker.isReadOnly) return;
64
+ if (event.detail > 0) {
65
+ trackInteractionModality(event, buttonRef);
66
+ }
67
+ if (buttonRef) {
68
+ datePicker.setTriggerRef(buttonRef);
69
+ }
70
+ datePicker.togglePopover('trigger-press', event);
71
+ }
72
+
73
+ function handleKeydown(event: KeyboardEvent) {
74
+ if (datePicker.isDisabled) return;
75
+ trackInteractionModality(event, buttonRef);
76
+ if (event.key === 'Enter' || event.key === ' ') {
77
+ datePicker.setFocusVisible(true);
78
+ return;
79
+ }
80
+ if (event.key !== 'ArrowLeft') return;
81
+ datePicker.setFocusVisible(true);
82
+
83
+ event.preventDefault();
84
+ datePicker.focusLastSegment();
85
+ }
86
+ </script>
87
+
88
+ {#if !datePicker.isReadOnly}
89
+ <button
90
+ bind:this={buttonRef}
91
+ type="button"
92
+ disabled={datePicker.isDisabled}
93
+ class={className}
94
+ aria-haspopup="dialog"
95
+ aria-expanded={datePicker.open}
96
+ data-disabled={datePicker.isDisabled || undefined}
97
+ data-focused={isFocused || undefined}
98
+ data-focus-visible={isFocused && datePicker.focusVisible ? 'true' : undefined}
99
+ onmousedown={handleMouseDown}
100
+ onfocus={handleFocus}
101
+ onblur={handleBlur}
102
+ onkeydown={handleKeydown}
103
+ onclick={handleClick}
104
+ {...restProps}
105
+ >
106
+ {#if children}
107
+ {@render children()}
108
+ {/if}
109
+ </button>
110
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ type DatePickerTriggerProps = Omit<HTMLButtonAttributes, 'type' | 'children' | 'class' | 'onclick' | 'aria-haspopup' | 'aria-expanded'> & {
4
+ children?: Snippet;
5
+ class?: string;
6
+ };
7
+ declare const DatePickerTrigger: import("svelte").Component<DatePickerTriggerProps, {}, "">;
8
+ type DatePickerTrigger = ReturnType<typeof DatePickerTrigger>;
9
+ export default DatePickerTrigger;
@@ -45,16 +45,16 @@
45
45
  let dialogId: symbol | null = null;
46
46
  let dialogLevel = $state(0);
47
47
 
48
- function close() {
49
- dialogCtx.close();
48
+ function close(reason: 'escape-key' | 'outside-press' | 'imperative-action', event?: Event) {
49
+ dialogCtx.close(reason, event);
50
50
  }
51
51
 
52
52
  /**
53
53
  * Wrapper for close that only executes if this is the topmost dialog.
54
54
  */
55
- function closeIfTopmost() {
55
+ function closeIfTopmost(event: MouseEvent) {
56
56
  if (dialogId && isTopmostDialog(dialogId)) {
57
- close();
57
+ close('outside-press', event);
58
58
  }
59
59
  }
60
60
 
@@ -63,7 +63,7 @@
63
63
  // Only handle if this is the topmost dialog
64
64
  if (dialogId && isTopmostDialog(dialogId)) {
65
65
  event.preventDefault();
66
- close();
66
+ close('escape-key', event);
67
67
  }
68
68
  }
69
69
  }
@@ -71,7 +71,7 @@
71
71
  onMount(() => {
72
72
  if (!browser) return;
73
73
  // Register this dialog in the stack
74
- const { id, level } = pushDialog(close);
74
+ const { id, level } = pushDialog(() => close('imperative-action'));
75
75
  dialogId = id;
76
76
  dialogLevel = level;
77
77
  // Share level with overlay via context
@@ -1,3 +1,4 @@
1
+ export type DialogCloseReason = 'escape-key' | 'outside-press' | 'imperative-action' | 'none';
1
2
  /**
2
3
  * Context shared between Dialog components (Root, Trigger, Content).
3
4
  */
@@ -13,7 +14,7 @@ export type DialogContext = {
13
14
  /** Open the dialog */
14
15
  open: () => void;
15
16
  /** Close the dialog and return focus to trigger */
16
- close: () => void;
17
+ close: (reason?: DialogCloseReason, event?: Event) => void;
17
18
  /** Called when dialog open state changes */
18
19
  onOpenChange: (open: boolean) => void;
19
20
  /** Stack level for z-index calculation (set by Content) */
@@ -2,6 +2,11 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import { setDialogContext, type DialogContext } from './context';
4
4
  import type { DialogStateHelpers } from './types';
5
+ import {
6
+ focusWithModality,
7
+ resolveCloseInteractionModality
8
+ } from '../../primitives/input-modality';
9
+ import type { DialogCloseReason } from './context';
5
10
 
6
11
  /**
7
12
  * Dialog.Root - State management wrapper for Dialog components.
@@ -56,9 +61,11 @@
56
61
  setOpen(true);
57
62
  }
58
63
 
59
- function closeDialog() {
64
+ function closeDialog(reason: DialogCloseReason = 'imperative-action', event?: Event) {
60
65
  setOpen(false);
61
- triggerRef?.focus();
66
+ if (triggerRef) {
67
+ focusWithModality(triggerRef, resolveCloseInteractionModality(reason, event));
68
+ }
62
69
  }
63
70
 
64
71
  function setTriggerRef(el: HTMLElement | null) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { ComboBox } from './combobox/index.ts';
2
2
  export { Calendar } from './calendar/index.ts';
3
+ export { Clock } from './clock/index.ts';
4
+ export { DatePicker } from './datepicker/index.ts';
5
+ export { TimePicker } from './timepicker/index.ts';
3
6
  export { Dialog } from './dialog/index.ts';
4
7
  export { ListBox } from './listbox/index.ts';
5
8
  export { Popover } from './popover/index.ts';
@@ -10,8 +13,13 @@ export { Portal } from './portal/index.ts';
10
13
  export * from './locale-provider/index.ts';
11
14
  export * from './combobox/index.ts';
12
15
  export * from './calendar/index.ts';
16
+ export * from './clock/index.ts';
17
+ export * from './datepicker/index.ts';
18
+ export * from './timepicker/index.ts';
13
19
  export * from './dialog/index.ts';
14
20
  export * from './listbox/index.ts';
15
21
  export * from './popover/index.ts';
16
22
  export * from './primitives/index.ts';
17
23
  export { cn } from './utils/index.ts';
24
+ export * from './utils/index.ts';
25
+ export * from './utils/index.ts';
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  // Components (namespace exports)
3
3
  export { ComboBox } from './combobox/index.ts';
4
4
  export { Calendar } from './calendar/index.ts';
5
+ export { Clock } from './clock/index.ts';
6
+ export { DatePicker } from './datepicker/index.ts';
7
+ export { TimePicker } from './timepicker/index.ts';
5
8
  export { Dialog } from './dialog/index.ts';
6
9
  export { ListBox } from './listbox/index.ts';
7
10
  export { Popover } from './popover/index.ts';
@@ -14,6 +17,9 @@ export * from './locale-provider/index.ts';
14
17
  // Re-export named exports from components
15
18
  export * from './combobox/index.ts';
16
19
  export * from './calendar/index.ts';
20
+ export * from './clock/index.ts';
21
+ export * from './datepicker/index.ts';
22
+ export * from './timepicker/index.ts';
17
23
  export * from './dialog/index.ts';
18
24
  export * from './listbox/index.ts';
19
25
  export * from './popover/index.ts';
@@ -21,3 +27,5 @@ export * from './popover/index.ts';
21
27
  export * from './primitives/index.ts';
22
28
  // Utilities
23
29
  export { cn } from './utils/index.ts';
30
+ export * from './utils/index.ts';
31
+ export * from './utils/index.ts';
@@ -1,6 +1,10 @@
1
1
  <script lang="ts" generics="T extends object = object">
2
2
  import type { Snippet } from 'svelte';
3
3
  import { createListBoxContext, type ListBoxContext } from './context';
4
+ import {
5
+ shouldShowFocusVisible,
6
+ trackInteractionModality
7
+ } from '../../primitives/input-modality';
4
8
 
5
9
  /**
6
10
  * Props for the ListBox component.
@@ -114,6 +118,40 @@
114
118
 
115
119
  const itemsArray = $derived(items ? Array.from(items) : []);
116
120
  const hasItems = $derived(itemsArray.length > 0 || itemCount > 0);
121
+
122
+ let focusWithin = $state(false);
123
+ let focusVisible = $state(false);
124
+
125
+ function syncFocusWithin() {
126
+ focusWithin =
127
+ !!listboxElement &&
128
+ !!document.activeElement &&
129
+ listboxElement.contains(document.activeElement);
130
+ if (!focusWithin) {
131
+ focusVisible = false;
132
+ }
133
+ }
134
+
135
+ function handleFocusIn(event: FocusEvent) {
136
+ focusWithin = true;
137
+ focusVisible = shouldShowFocusVisible(event.target as HTMLElement | null);
138
+ }
139
+
140
+ function handleFocusOut() {
141
+ queueMicrotask(syncFocusWithin);
142
+ }
143
+
144
+ function handleMouseDown(event: MouseEvent) {
145
+ trackInteractionModality(event, event.target as HTMLElement | null);
146
+ focusVisible = false;
147
+ }
148
+
149
+ function handleKeyDown(event: KeyboardEvent) {
150
+ trackInteractionModality(event, event.target as HTMLElement | null);
151
+ if (focusWithin) {
152
+ focusVisible = true;
153
+ }
154
+ }
117
155
  </script>
118
156
 
119
157
  <div
@@ -124,7 +162,13 @@
124
162
  aria-label={ariaLabel}
125
163
  class={className}
126
164
  tabindex="0"
165
+ data-focus-within={focusWithin || undefined}
166
+ data-focus-visible={focusVisible || undefined}
127
167
  use:keyboardAction
168
+ onfocusin={handleFocusIn}
169
+ onfocusout={handleFocusOut}
170
+ onmousedown={handleMouseDown}
171
+ onkeydown={handleKeyDown}
128
172
  >
129
173
  {#if items && children}
130
174
  {#each itemsArray as item (item)}
@@ -12,6 +12,16 @@
12
12
  - Use `Popover.Content` inside `Popover.Root`, or in standalone mode with `open`, `triggerRef`, and `onOpenChange`.
13
13
  - Configure `isNonModal`, `shouldCloseOnInteractOutside`, and `shouldCloseOnBlur` to match your interaction model.
14
14
 
15
+ ## onOpenChange details
16
+
17
+ `Popover.Root` and standalone `Popover.Content` use:
18
+
19
+ - `onOpenChange(open, details)`
20
+ - `details.reason`: `trigger-press | imperative-action | none | escape-key | outside-press | focus-out | close-press`
21
+ - `details.event?`: native event that triggered the change when available
22
+ - `details.cancel()`: prevents the open-state transition
23
+ - `details.isCanceled`: reflects cancellation state inside the callback
24
+
15
25
  ## Anatomy
16
26
 
17
27
  Import the component and compose its parts: