bits-ui 1.8.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/dist/bits/accordion/accordion.svelte.d.ts +56 -53
  2. package/dist/bits/accordion/accordion.svelte.js +78 -89
  3. package/dist/bits/accordion/components/accordion-content.svelte +5 -3
  4. package/dist/bits/accordion/components/accordion-header.svelte +4 -2
  5. package/dist/bits/accordion/components/accordion-item.svelte +6 -3
  6. package/dist/bits/accordion/components/accordion-trigger.svelte +4 -2
  7. package/dist/bits/accordion/components/accordion.svelte +4 -2
  8. package/dist/bits/alert-dialog/components/alert-dialog-action.svelte +4 -2
  9. package/dist/bits/alert-dialog/components/alert-dialog-cancel.svelte +4 -2
  10. package/dist/bits/alert-dialog/components/alert-dialog-content.svelte +8 -3
  11. package/dist/bits/aspect-ratio/aspect-ratio.svelte.js +2 -2
  12. package/dist/bits/aspect-ratio/components/aspect-ratio.svelte +4 -2
  13. package/dist/bits/avatar/avatar.svelte.js +4 -4
  14. package/dist/bits/avatar/components/avatar-fallback.svelte +4 -2
  15. package/dist/bits/avatar/components/avatar-image.svelte +4 -2
  16. package/dist/bits/avatar/components/avatar.svelte +4 -2
  17. package/dist/bits/button/components/button.svelte +1 -1
  18. package/dist/bits/calendar/calendar.svelte.d.ts +1 -9
  19. package/dist/bits/calendar/calendar.svelte.js +47 -38
  20. package/dist/bits/calendar/components/calendar-cell.svelte +4 -2
  21. package/dist/bits/calendar/components/calendar-day.svelte +4 -2
  22. package/dist/bits/calendar/components/calendar-grid-body.svelte +4 -2
  23. package/dist/bits/calendar/components/calendar-grid-head.svelte +4 -2
  24. package/dist/bits/calendar/components/calendar-grid-row.svelte +4 -2
  25. package/dist/bits/calendar/components/calendar-grid.svelte +4 -2
  26. package/dist/bits/calendar/components/calendar-head-cell.svelte +4 -2
  27. package/dist/bits/calendar/components/calendar-header.svelte +4 -2
  28. package/dist/bits/calendar/components/calendar-heading.svelte +4 -2
  29. package/dist/bits/calendar/components/calendar-next-button.svelte +4 -2
  30. package/dist/bits/calendar/components/calendar-prev-button.svelte +4 -2
  31. package/dist/bits/checkbox/checkbox.svelte.js +4 -14
  32. package/dist/bits/checkbox/components/checkbox-group-label.svelte +4 -2
  33. package/dist/bits/checkbox/components/checkbox-group.svelte +4 -2
  34. package/dist/bits/checkbox/components/checkbox.svelte +4 -2
  35. package/dist/bits/collapsible/collapsible.svelte.js +4 -10
  36. package/dist/bits/collapsible/components/collapsible-content.svelte +5 -3
  37. package/dist/bits/collapsible/components/collapsible-trigger.svelte +4 -2
  38. package/dist/bits/collapsible/components/collapsible.svelte +4 -2
  39. package/dist/bits/combobox/components/combobox-input.svelte +1 -1
  40. package/dist/bits/combobox/components/combobox.svelte +1 -1
  41. package/dist/bits/command/command.svelte.js +13 -45
  42. package/dist/bits/command/components/_command-label.svelte +6 -7
  43. package/dist/bits/command/components/_command-label.svelte.d.ts +1 -1
  44. package/dist/bits/command/components/command-empty.svelte +4 -2
  45. package/dist/bits/command/components/command-group-heading.svelte +4 -2
  46. package/dist/bits/command/components/command-group-items.svelte +4 -2
  47. package/dist/bits/command/components/command-group.svelte +4 -2
  48. package/dist/bits/command/components/command-input.svelte +4 -2
  49. package/dist/bits/command/components/command-item.svelte +4 -2
  50. package/dist/bits/command/components/command-link-item.svelte +4 -2
  51. package/dist/bits/command/components/command-list.svelte +4 -2
  52. package/dist/bits/command/components/command-loading.svelte +4 -2
  53. package/dist/bits/command/components/command-separator.svelte +4 -2
  54. package/dist/bits/command/components/command-viewport.svelte +4 -2
  55. package/dist/bits/command/components/command.svelte +4 -2
  56. package/dist/bits/context-menu/components/context-menu-content-static.svelte +2 -3
  57. package/dist/bits/context-menu/components/context-menu-content.svelte +2 -3
  58. package/dist/bits/context-menu/components/context-menu-trigger.svelte +1 -1
  59. package/dist/bits/date-field/components/date-field-input.svelte +4 -2
  60. package/dist/bits/date-field/components/date-field-label.svelte +4 -2
  61. package/dist/bits/date-field/components/date-field-segment.svelte +4 -2
  62. package/dist/bits/date-field/date-field.svelte.d.ts +4 -4
  63. package/dist/bits/date-field/date-field.svelte.js +15 -33
  64. package/dist/bits/date-picker/components/date-picker-calendar.svelte +4 -2
  65. package/dist/bits/date-range-field/components/date-range-field-input.svelte +4 -2
  66. package/dist/bits/date-range-field/components/date-range-field-label.svelte +4 -2
  67. package/dist/bits/date-range-field/components/date-range-field.svelte +4 -2
  68. package/dist/bits/date-range-field/date-range-field.svelte.js +3 -13
  69. package/dist/bits/date-range-picker/components/date-range-picker-calendar.svelte +4 -2
  70. package/dist/bits/dialog/components/dialog-close.svelte +4 -2
  71. package/dist/bits/dialog/components/dialog-content.svelte +8 -2
  72. package/dist/bits/dialog/components/dialog-description.svelte +4 -2
  73. package/dist/bits/dialog/components/dialog-overlay.svelte +8 -3
  74. package/dist/bits/dialog/components/dialog-title.svelte +4 -2
  75. package/dist/bits/dialog/components/dialog-trigger.svelte +4 -2
  76. package/dist/bits/dialog/dialog.svelte.d.ts +1 -1
  77. package/dist/bits/dialog/dialog.svelte.js +19 -47
  78. package/dist/bits/dropdown-menu/components/dropdown-menu-content-static.svelte +6 -5
  79. package/dist/bits/dropdown-menu/components/dropdown-menu-content.svelte +6 -5
  80. package/dist/bits/index.d.ts +2 -0
  81. package/dist/bits/index.js +2 -0
  82. package/dist/bits/label/components/label.svelte +4 -2
  83. package/dist/bits/label/label.svelte.js +2 -2
  84. package/dist/bits/link-preview/components/link-preview-content-static.svelte +6 -2
  85. package/dist/bits/link-preview/components/link-preview-content.svelte +6 -2
  86. package/dist/bits/link-preview/components/link-preview-trigger.svelte +5 -3
  87. package/dist/bits/link-preview/link-preview.svelte.js +3 -14
  88. package/dist/bits/menu/components/menu-checkbox-group.svelte +4 -2
  89. package/dist/bits/menu/components/menu-checkbox-item.svelte +6 -4
  90. package/dist/bits/menu/components/menu-content-static.svelte +6 -5
  91. package/dist/bits/menu/components/menu-content.svelte +6 -5
  92. package/dist/bits/menu/components/menu-group-heading.svelte +4 -2
  93. package/dist/bits/menu/components/menu-group.svelte +4 -2
  94. package/dist/bits/menu/components/menu-item.svelte +4 -2
  95. package/dist/bits/menu/components/menu-radio-group.svelte +4 -2
  96. package/dist/bits/menu/components/menu-radio-item.svelte +4 -2
  97. package/dist/bits/menu/components/menu-separator.svelte +4 -2
  98. package/dist/bits/menu/components/menu-sub-content-static.svelte +6 -5
  99. package/dist/bits/menu/components/menu-sub-content.svelte +6 -5
  100. package/dist/bits/menu/components/menu-sub-trigger.svelte +6 -3
  101. package/dist/bits/menu/components/menu-trigger.svelte +5 -3
  102. package/dist/bits/menu/menu.svelte.d.ts +7 -20
  103. package/dist/bits/menu/menu.svelte.js +26 -54
  104. package/dist/bits/menubar/components/menubar-content-static.svelte +4 -2
  105. package/dist/bits/menubar/components/menubar-content.svelte +4 -2
  106. package/dist/bits/menubar/components/menubar-menu.svelte +4 -2
  107. package/dist/bits/menubar/components/menubar-trigger.svelte +14 -6
  108. package/dist/bits/menubar/components/menubar.svelte +4 -2
  109. package/dist/bits/menubar/menubar.svelte.d.ts +24 -20
  110. package/dist/bits/menubar/menubar.svelte.js +40 -56
  111. package/dist/bits/meter/components/meter.svelte +4 -2
  112. package/dist/bits/meter/meter.svelte.js +2 -2
  113. package/dist/bits/navigation-menu/components/navigation-menu-content-impl.svelte +5 -2
  114. package/dist/bits/navigation-menu/components/navigation-menu-content.svelte +8 -3
  115. package/dist/bits/navigation-menu/components/navigation-menu-indicator-impl.svelte +4 -2
  116. package/dist/bits/navigation-menu/components/navigation-menu-indicator.svelte +6 -4
  117. package/dist/bits/navigation-menu/components/navigation-menu-item.svelte +6 -3
  118. package/dist/bits/navigation-menu/components/navigation-menu-link.svelte +4 -2
  119. package/dist/bits/navigation-menu/components/navigation-menu-list.svelte +4 -2
  120. package/dist/bits/navigation-menu/components/navigation-menu-sub.svelte +4 -2
  121. package/dist/bits/navigation-menu/components/navigation-menu-trigger.svelte +4 -2
  122. package/dist/bits/navigation-menu/components/navigation-menu-viewport.svelte +5 -3
  123. package/dist/bits/navigation-menu/components/navigation-menu.svelte +4 -2
  124. package/dist/bits/navigation-menu/navigation-menu.svelte.js +13 -50
  125. package/dist/bits/pagination/components/pagination-next-button.svelte +4 -2
  126. package/dist/bits/pagination/components/pagination-page.svelte +4 -2
  127. package/dist/bits/pagination/components/pagination-prev-button.svelte +4 -2
  128. package/dist/bits/pagination/components/pagination.svelte +4 -2
  129. package/dist/bits/pagination/pagination.svelte.js +4 -4
  130. package/dist/bits/pin-input/components/pin-input-cell.svelte +4 -2
  131. package/dist/bits/pin-input/components/pin-input.svelte +5 -3
  132. package/dist/bits/pin-input/pin-input.svelte.js +4 -10
  133. package/dist/bits/popover/components/popover-close.svelte +4 -2
  134. package/dist/bits/popover/components/popover-content-static.svelte +6 -2
  135. package/dist/bits/popover/components/popover-content.svelte +6 -2
  136. package/dist/bits/popover/components/popover-trigger.svelte +5 -3
  137. package/dist/bits/popover/popover.svelte.js +4 -18
  138. package/dist/bits/progress/components/progress.svelte +4 -2
  139. package/dist/bits/progress/progress.svelte.js +2 -2
  140. package/dist/bits/radio-group/components/radio-group-item.svelte +4 -2
  141. package/dist/bits/radio-group/components/radio-group.svelte +4 -2
  142. package/dist/bits/radio-group/radio-group.svelte.js +4 -7
  143. package/dist/bits/range-calendar/components/range-calendar-cell.svelte +4 -2
  144. package/dist/bits/range-calendar/components/range-calendar-day.svelte +4 -2
  145. package/dist/bits/range-calendar/components/range-calendar.svelte +4 -2
  146. package/dist/bits/range-calendar/range-calendar.svelte.js +4 -4
  147. package/dist/bits/scroll-area/components/scroll-area-corner.svelte +8 -2
  148. package/dist/bits/scroll-area/components/scroll-area-scrollbar-auto.svelte +4 -1
  149. package/dist/bits/scroll-area/components/scroll-area-scrollbar-hover.svelte +1 -1
  150. package/dist/bits/scroll-area/components/scroll-area-scrollbar-scroll.svelte +5 -1
  151. package/dist/bits/scroll-area/components/scroll-area-scrollbar.svelte +4 -2
  152. package/dist/bits/scroll-area/components/scroll-area-thumb.svelte +8 -3
  153. package/dist/bits/scroll-area/components/scroll-area-viewport.svelte +4 -2
  154. package/dist/bits/scroll-area/components/scroll-area.svelte +4 -2
  155. package/dist/bits/scroll-area/scroll-area.svelte.js +13 -47
  156. package/dist/bits/select/components/select-content-static.svelte +6 -2
  157. package/dist/bits/select/components/select-content.svelte +6 -2
  158. package/dist/bits/select/components/select-group-heading.svelte +3 -2
  159. package/dist/bits/select/components/select-group.svelte +4 -2
  160. package/dist/bits/select/components/select-item.svelte +4 -2
  161. package/dist/bits/select/components/select-scroll-down-button.svelte +4 -2
  162. package/dist/bits/select/components/select-scroll-up-button.svelte +4 -2
  163. package/dist/bits/select/components/select-trigger.svelte +5 -3
  164. package/dist/bits/select/components/select-viewport.svelte +4 -2
  165. package/dist/bits/select/components/select.svelte +1 -1
  166. package/dist/bits/select/select.svelte.d.ts +0 -18
  167. package/dist/bits/select/select.svelte.js +10 -43
  168. package/dist/bits/separator/components/separator.svelte +4 -2
  169. package/dist/bits/separator/separator.svelte.js +2 -2
  170. package/dist/bits/slider/components/slider-range.svelte +4 -2
  171. package/dist/bits/slider/components/slider-thumb-label.svelte +50 -0
  172. package/dist/bits/slider/components/slider-thumb-label.svelte.d.ts +4 -0
  173. package/dist/bits/slider/components/slider-thumb.svelte +4 -2
  174. package/dist/bits/slider/components/slider-tick-label.svelte +50 -0
  175. package/dist/bits/slider/components/slider-tick-label.svelte.d.ts +4 -0
  176. package/dist/bits/slider/components/slider-tick.svelte +4 -2
  177. package/dist/bits/slider/components/slider.svelte +24 -5
  178. package/dist/bits/slider/exports.d.ts +3 -1
  179. package/dist/bits/slider/exports.js +2 -0
  180. package/dist/bits/slider/helpers.d.ts +14 -0
  181. package/dist/bits/slider/helpers.js +122 -0
  182. package/dist/bits/slider/slider.svelte.d.ts +91 -5
  183. package/dist/bits/slider/slider.svelte.js +194 -101
  184. package/dist/bits/slider/types.d.ts +105 -11
  185. package/dist/bits/switch/components/switch-thumb.svelte +4 -2
  186. package/dist/bits/switch/components/switch.svelte +4 -2
  187. package/dist/bits/switch/switch.svelte.js +3 -3
  188. package/dist/bits/tabs/components/tabs-content.svelte +4 -2
  189. package/dist/bits/tabs/components/tabs-list.svelte +4 -2
  190. package/dist/bits/tabs/components/tabs-trigger.svelte +4 -2
  191. package/dist/bits/tabs/components/tabs.svelte +4 -2
  192. package/dist/bits/tabs/tabs.svelte.js +6 -6
  193. package/dist/bits/time-field/components/time-field-hidden-input.svelte +10 -0
  194. package/dist/bits/{date-field/components/date-field-error.svelte.d.ts → time-field/components/time-field-hidden-input.svelte.d.ts} +6 -14
  195. package/dist/bits/time-field/components/time-field-input.svelte +39 -0
  196. package/dist/bits/time-field/components/time-field-input.svelte.d.ts +4 -0
  197. package/dist/bits/time-field/components/time-field-label.svelte +34 -0
  198. package/dist/bits/time-field/components/time-field-label.svelte.d.ts +4 -0
  199. package/dist/bits/time-field/components/time-field-segment.svelte +37 -0
  200. package/dist/bits/time-field/components/time-field-segment.svelte.d.ts +4 -0
  201. package/dist/bits/time-field/components/time-field.svelte +94 -0
  202. package/dist/bits/time-field/components/time-field.svelte.d.ts +20 -0
  203. package/dist/bits/time-field/exports.d.ts +5 -0
  204. package/dist/bits/time-field/exports.js +4 -0
  205. package/dist/bits/time-field/index.d.ts +1 -0
  206. package/dist/bits/time-field/index.js +1 -0
  207. package/dist/bits/time-field/time-field.svelte.d.ts +415 -0
  208. package/dist/bits/time-field/time-field.svelte.js +971 -0
  209. package/dist/bits/time-field/types.d.ts +137 -0
  210. package/dist/bits/time-field/types.js +1 -0
  211. package/dist/bits/time-range-field/components/time-range-field-input.svelte +43 -0
  212. package/dist/bits/time-range-field/components/time-range-field-input.svelte.d.ts +4 -0
  213. package/dist/bits/time-range-field/components/time-range-field-label.svelte +34 -0
  214. package/dist/bits/time-range-field/components/time-range-field-label.svelte.d.ts +4 -0
  215. package/dist/bits/time-range-field/components/time-range-field.svelte +144 -0
  216. package/dist/bits/time-range-field/components/time-range-field.svelte.d.ts +20 -0
  217. package/dist/bits/time-range-field/exports.d.ts +5 -0
  218. package/dist/bits/time-range-field/exports.js +4 -0
  219. package/dist/bits/time-range-field/index.d.ts +1 -0
  220. package/dist/bits/time-range-field/index.js +1 -0
  221. package/dist/bits/time-range-field/time-range-field.svelte.d.ts +90 -0
  222. package/dist/bits/time-range-field/time-range-field.svelte.js +210 -0
  223. package/dist/bits/time-range-field/types.d.ts +150 -0
  224. package/dist/bits/time-range-field/types.js +1 -0
  225. package/dist/bits/toggle/components/toggle.svelte +4 -2
  226. package/dist/bits/toggle/toggle.svelte.js +2 -2
  227. package/dist/bits/toggle-group/components/toggle-group-item.svelte +4 -2
  228. package/dist/bits/toggle-group/components/toggle-group.svelte +4 -2
  229. package/dist/bits/toggle-group/toggle-group.svelte.js +4 -4
  230. package/dist/bits/toolbar/components/toolbar-button.svelte +4 -2
  231. package/dist/bits/toolbar/components/toolbar-group-item.svelte +4 -2
  232. package/dist/bits/toolbar/components/toolbar-group.svelte +4 -2
  233. package/dist/bits/toolbar/components/toolbar-link.svelte +4 -2
  234. package/dist/bits/toolbar/components/toolbar.svelte +4 -2
  235. package/dist/bits/toolbar/toolbar.svelte.js +7 -7
  236. package/dist/bits/tooltip/components/tooltip-content-static.svelte +6 -2
  237. package/dist/bits/tooltip/components/tooltip-content.svelte +6 -2
  238. package/dist/bits/tooltip/components/tooltip-trigger.svelte +5 -3
  239. package/dist/bits/tooltip/tooltip.svelte.js +3 -14
  240. package/dist/bits/utilities/dismissible-layer/dismissible-layer.svelte +2 -0
  241. package/dist/bits/utilities/dismissible-layer/types.d.ts +2 -0
  242. package/dist/bits/utilities/dismissible-layer/use-dismissable-layer.svelte.d.ts +3 -3
  243. package/dist/bits/utilities/dismissible-layer/use-dismissable-layer.svelte.js +16 -25
  244. package/dist/bits/utilities/floating-layer/components/floating-layer-anchor.svelte +2 -1
  245. package/dist/bits/utilities/floating-layer/types.d.ts +1 -0
  246. package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.d.ts +3 -2
  247. package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.js +5 -26
  248. package/dist/bits/utilities/focus-scope/focus-scope.svelte +2 -0
  249. package/dist/bits/utilities/focus-scope/types.d.ts +2 -0
  250. package/dist/bits/utilities/focus-scope/use-focus-scope.svelte.d.ts +2 -1
  251. package/dist/bits/utilities/focus-scope/use-focus-scope.svelte.js +2 -8
  252. package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte +4 -0
  253. package/dist/bits/utilities/popper-layer/popper-layer.svelte +3 -1
  254. package/dist/bits/utilities/presence-layer/presence-layer.svelte +2 -2
  255. package/dist/bits/utilities/presence-layer/types.d.ts +2 -1
  256. package/dist/bits/utilities/presence-layer/use-presence.svelte.d.ts +1 -1
  257. package/dist/bits/utilities/presence-layer/use-presence.svelte.js +19 -36
  258. package/dist/bits/utilities/text-selection-layer/text-selection-layer.svelte +2 -0
  259. package/dist/bits/utilities/text-selection-layer/types.d.ts +2 -0
  260. package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.d.ts +3 -1
  261. package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.js +2 -8
  262. package/dist/index.d.ts +1 -1
  263. package/dist/index.js +1 -1
  264. package/dist/internal/create-id.d.ts +8 -0
  265. package/dist/internal/create-id.js +5 -0
  266. package/dist/internal/date-time/field/helpers.d.ts +1 -0
  267. package/dist/internal/date-time/field/helpers.js +8 -2
  268. package/dist/internal/date-time/field/parts.d.ts +3 -1
  269. package/dist/internal/date-time/field/parts.js +10 -2
  270. package/dist/internal/date-time/field/segments.d.ts +9 -0
  271. package/dist/internal/date-time/field/segments.js +65 -0
  272. package/dist/internal/date-time/field/time-helpers.d.ts +77 -0
  273. package/dist/internal/date-time/field/time-helpers.js +301 -0
  274. package/dist/internal/date-time/field/types.d.ts +2 -2
  275. package/dist/internal/date-time/formatter.d.ts +11 -1
  276. package/dist/internal/date-time/formatter.js +56 -0
  277. package/dist/internal/date-time/utils.d.ts +7 -2
  278. package/dist/internal/date-time/utils.js +15 -1
  279. package/dist/internal/dom-context.svelte.d.ts +9 -0
  280. package/dist/internal/dom-context.svelte.js +26 -0
  281. package/dist/internal/use-roving-focus.svelte.d.ts +3 -3
  282. package/dist/internal/use-roving-focus.svelte.js +10 -11
  283. package/dist/shared/date/types.d.ts +27 -4
  284. package/dist/shared/index.d.ts +2 -2
  285. package/dist/types.d.ts +2 -0
  286. package/package.json +18 -16
  287. package/dist/bits/date-field/components/date-field-error.svelte +0 -0
@@ -0,0 +1,971 @@
1
+ import { CalendarDateTime, Time, ZonedDateTime } from "@internationalized/date";
2
+ import { onDestroyEffect, attachRef, box } from "svelte-toolbelt";
3
+ import { onMount, untrack } from "svelte";
4
+ import { Context, watch } from "runed";
5
+ import { getAriaDisabled, getAriaHidden, getAriaInvalid, getAriaReadonly, getDataDisabled, getDataInvalid, getDataReadonly, } from "../../internal/attrs.js";
6
+ import { isBrowser, isNumberString } from "../../internal/is.js";
7
+ import { kbd } from "../../internal/kbd.js";
8
+ import { useId } from "../../internal/use-id.js";
9
+ import { createTimeFormatter } from "../../internal/date-time/formatter.js";
10
+ import { getAnnouncer } from "../../internal/date-time/announcer.js";
11
+ import { EDITABLE_TIME_SEGMENT_PARTS } from "../../internal/date-time/field/parts.js";
12
+ import { toDate } from "../../internal/date-time/utils.js";
13
+ import { areAllTimeSegmentsFilled, convertTimeValueToTime, createTimeContent, getISOTimeValue, getTimeValueFromSegments, initTimeSegmentStates, isFirstTimeSegment, isTimeBefore, removeTimeDescriptionElement, setTimeDescription, } from "../../internal/date-time/field/time-helpers.js";
14
+ import { getFirstTimeSegment, handleTimeSegmentNavigation, isSegmentNavigationKey, moveToNextTimeSegment, moveToPrevTimeSegment, } from "../../internal/date-time/field/segments.js";
15
+ import { getDefaultHourCycle, isAcceptableSegmentKey, } from "../../internal/date-time/field/helpers.js";
16
+ export const TIME_FIELD_INPUT_ATTR = "data-time-field-input";
17
+ const TIME_FIELD_LABEL_ATTR = "data-time-field-label";
18
+ const SEGMENT_CONFIGS = {
19
+ hour: {
20
+ min: (root) => (root.hourCycle === 12 ? 1 : 0),
21
+ max: (root) => {
22
+ if (root.hourCycle === 24)
23
+ return 23;
24
+ if ("dayPeriod" in root.segmentValues && root.segmentValues.dayPeriod !== null)
25
+ return 12;
26
+ return 23;
27
+ },
28
+ cycle: 1,
29
+ canBeZero: true,
30
+ padZero: true,
31
+ },
32
+ minute: {
33
+ min: 0,
34
+ max: 59,
35
+ cycle: 1,
36
+ canBeZero: true,
37
+ padZero: true,
38
+ },
39
+ second: {
40
+ min: 0,
41
+ max: 59,
42
+ cycle: 1,
43
+ canBeZero: true,
44
+ padZero: true,
45
+ },
46
+ };
47
+ export class TimeFieldRootState {
48
+ value;
49
+ placeholder;
50
+ validate;
51
+ minValue;
52
+ maxValue;
53
+ disabled;
54
+ readonly;
55
+ granularity;
56
+ readonlySegments;
57
+ hourCycleProp;
58
+ locale;
59
+ hideTimeZone;
60
+ required;
61
+ onInvalid;
62
+ errorMessageId;
63
+ isInvalidProp;
64
+ descriptionId = useId();
65
+ formatter;
66
+ initialSegments;
67
+ segmentValues = $state();
68
+ announcer;
69
+ readonlySegmentsSet = $derived.by(() => new Set(this.readonlySegments.current));
70
+ segmentStates = initTimeSegmentStates();
71
+ #fieldNode = $state(null);
72
+ #labelNode = $state(null);
73
+ descriptionNode = $state(null);
74
+ validationNode = $state(null);
75
+ states = initTimeSegmentStates();
76
+ dayPeriodNode = $state(null);
77
+ name = $state("");
78
+ maxValueTime = $derived.by(() => {
79
+ if (!this.maxValue.current)
80
+ return undefined;
81
+ return convertTimeValueToTime(this.maxValue.current);
82
+ });
83
+ minValueTime = $derived.by(() => {
84
+ if (!this.minValue.current)
85
+ return undefined;
86
+ return convertTimeValueToTime(this.minValue.current);
87
+ });
88
+ valueTime = $derived.by(() => {
89
+ if (!this.value.current)
90
+ return undefined;
91
+ return convertTimeValueToTime(this.value.current);
92
+ });
93
+ hourCycle = $derived.by(() => {
94
+ if (this.hourCycleProp.current)
95
+ return this.hourCycleProp.current;
96
+ return getDefaultHourCycle(this.locale.current);
97
+ });
98
+ rangeRoot = undefined;
99
+ constructor(props, rangeRoot) {
100
+ this.rangeRoot = rangeRoot;
101
+ /**
102
+ * Since the `TimeFieldRootState` can be used in two contexts, as a standalone
103
+ * field or as a field within a `TimeRangeField` component, we handle assigning
104
+ * the props based on that context.
105
+ */
106
+ this.value = props.value;
107
+ this.placeholder = rangeRoot ? rangeRoot.opts.placeholder : props.placeholder;
108
+ this.validate = rangeRoot ? box(undefined) : props.validate;
109
+ this.minValue = rangeRoot ? rangeRoot.opts.minValue : props.minValue;
110
+ this.maxValue = rangeRoot ? rangeRoot.opts.maxValue : props.maxValue;
111
+ this.disabled = rangeRoot ? rangeRoot.opts.disabled : props.disabled;
112
+ this.readonly = rangeRoot ? rangeRoot.opts.readonly : props.readonly;
113
+ this.granularity = rangeRoot ? rangeRoot.opts.granularity : props.granularity;
114
+ this.readonlySegments = rangeRoot
115
+ ? rangeRoot.opts.readonlySegments
116
+ : props.readonlySegments;
117
+ this.hourCycleProp = rangeRoot ? rangeRoot.opts.hourCycle : props.hourCycle;
118
+ this.locale = rangeRoot ? rangeRoot.opts.locale : props.locale;
119
+ this.hideTimeZone = rangeRoot ? rangeRoot.opts.hideTimeZone : props.hideTimeZone;
120
+ this.required = rangeRoot ? rangeRoot.opts.required : props.required;
121
+ this.onInvalid = rangeRoot ? rangeRoot.opts.onInvalid : props.onInvalid;
122
+ this.errorMessageId = rangeRoot ? rangeRoot.opts.errorMessageId : props.errorMessageId;
123
+ this.isInvalidProp = props.isInvalidProp;
124
+ this.formatter = createTimeFormatter(this.locale.current);
125
+ this.initialSegments = this.#initializeTimeSegmentValues();
126
+ this.segmentValues = this.initialSegments;
127
+ this.announcer = getAnnouncer();
128
+ this.getFieldNode = this.getFieldNode.bind(this);
129
+ this.updateSegment = this.updateSegment.bind(this);
130
+ this.handleSegmentClick = this.handleSegmentClick.bind(this);
131
+ this.getBaseSegmentAttrs = this.getBaseSegmentAttrs.bind(this);
132
+ $effect(() => {
133
+ untrack(() => {
134
+ this.initialSegments = this.#initializeTimeSegmentValues();
135
+ });
136
+ });
137
+ onMount(() => {
138
+ this.announcer = getAnnouncer();
139
+ });
140
+ onDestroyEffect(() => {
141
+ removeTimeDescriptionElement(this.descriptionId);
142
+ });
143
+ $effect(() => {
144
+ if (this.formatter.getLocale() === this.locale.current)
145
+ return;
146
+ this.formatter.setLocale(this.locale.current);
147
+ });
148
+ $effect(() => {
149
+ if (this.value.current) {
150
+ const descriptionId = untrack(() => this.descriptionId);
151
+ setTimeDescription(descriptionId, this.formatter, this.#toDateValue(this.value.current));
152
+ }
153
+ const placeholder = untrack(() => this.placeholder.current);
154
+ if (this.value.current && placeholder !== this.value.current) {
155
+ untrack(() => {
156
+ if (this.value.current) {
157
+ this.placeholder.current = this.value.current;
158
+ }
159
+ });
160
+ }
161
+ });
162
+ if (this.value.current) {
163
+ this.syncSegmentValues(this.value.current);
164
+ }
165
+ $effect(() => {
166
+ this.locale.current;
167
+ if (this.value.current) {
168
+ this.syncSegmentValues(this.value.current);
169
+ }
170
+ this.#clearUpdating();
171
+ });
172
+ $effect(() => {
173
+ if (this.value.current === undefined) {
174
+ this.segmentValues = this.#initializeTimeSegmentValues();
175
+ }
176
+ });
177
+ watch(() => this.validationStatus, () => {
178
+ if (this.validationStatus !== false) {
179
+ this.onInvalid.current?.(this.validationStatus.reason, this.validationStatus.message);
180
+ }
181
+ });
182
+ }
183
+ #initializeTimeSegmentValues() {
184
+ const granularity = this.inferredGranularity;
185
+ const segments = {
186
+ hour: null,
187
+ minute: null,
188
+ second: null,
189
+ dayPeriod: "AM",
190
+ };
191
+ if (granularity === "second") {
192
+ segments.second = null;
193
+ }
194
+ if (this.hourCycle === 24) {
195
+ segments.dayPeriod = null;
196
+ }
197
+ return segments;
198
+ }
199
+ #toDateValue(timeValue) {
200
+ if ("calendar" in timeValue) {
201
+ // CalendarDateTime or ZonedDateTime
202
+ return timeValue;
203
+ }
204
+ else {
205
+ return new CalendarDateTime(2000, 1, 1, timeValue.hour, timeValue.minute, timeValue.second, timeValue.millisecond);
206
+ }
207
+ }
208
+ #clearUpdating() {
209
+ this.states.hour.updating = null;
210
+ this.states.minute.updating = null;
211
+ this.states.second.updating = null;
212
+ this.states.dayPeriod.updating = null;
213
+ }
214
+ setName(name) {
215
+ this.name = name;
216
+ }
217
+ setFieldNode(node) {
218
+ this.#fieldNode = node;
219
+ }
220
+ /**
221
+ * Gets the correct field node for the time field regardless of whether it's being
222
+ * used in a standalone context or within a `TimeRangeField` component.
223
+ */
224
+ getFieldNode() {
225
+ /** If we're not within a TimeRangeField, we return this field. */
226
+ if (!this.rangeRoot) {
227
+ return this.#fieldNode;
228
+ }
229
+ else {
230
+ /**
231
+ * Otherwise, we return the rangeRoot's field node which
232
+ * contains both start and end fields.
233
+ */
234
+ return this.rangeRoot.fieldNode;
235
+ }
236
+ }
237
+ setLabelNode(node) {
238
+ this.#labelNode = node;
239
+ }
240
+ getLabelNode() {
241
+ return this.#labelNode;
242
+ }
243
+ setValue(value) {
244
+ this.value.current = value;
245
+ }
246
+ syncSegmentValues(value) {
247
+ const timeValues = EDITABLE_TIME_SEGMENT_PARTS.map((part) => {
248
+ if (part === "dayPeriod") {
249
+ if (this.states.dayPeriod.updating) {
250
+ return [part, this.states.dayPeriod.updating];
251
+ }
252
+ else {
253
+ return [part, this.formatter.dayPeriod(toDate(this.#toDateValue(value)))];
254
+ }
255
+ }
256
+ else if (part === "hour") {
257
+ if (this.states.hour.updating) {
258
+ return [part, this.states.hour.updating];
259
+ }
260
+ if (value[part] !== undefined && value[part] < 10) {
261
+ return [part, `0${value[part]}`];
262
+ }
263
+ if (value[part] === 0 && this.dayPeriodNode) {
264
+ return [part, "12"];
265
+ }
266
+ }
267
+ else if (part === "minute") {
268
+ if (this.states.minute.updating) {
269
+ return [part, this.states.minute.updating];
270
+ }
271
+ if (value[part] !== undefined && value[part] < 10) {
272
+ return [part, `0${value[part]}`];
273
+ }
274
+ }
275
+ else if (part === "second") {
276
+ if (this.states.second.updating) {
277
+ return [part, this.states.second.updating];
278
+ }
279
+ if (value[part] !== undefined && value[part] < 10) {
280
+ return [part, `0${value[part]}`];
281
+ }
282
+ }
283
+ return [part, `${value[part]}`];
284
+ });
285
+ this.segmentValues = Object.fromEntries(timeValues);
286
+ this.#clearUpdating();
287
+ }
288
+ validationStatus = $derived.by(() => {
289
+ const value = this.value.current;
290
+ if (!value)
291
+ return false;
292
+ const msg = this.validate.current?.(value);
293
+ if (msg) {
294
+ return {
295
+ reason: "custom",
296
+ message: msg,
297
+ };
298
+ }
299
+ if (!this.valueTime)
300
+ return false;
301
+ if (this.minValueTime && isTimeBefore(this.valueTime, this.minValueTime)) {
302
+ return {
303
+ reason: "min",
304
+ };
305
+ }
306
+ if (this.maxValueTime && isTimeBefore(this.maxValueTime, this.valueTime)) {
307
+ return {
308
+ reason: "max",
309
+ };
310
+ }
311
+ return false;
312
+ });
313
+ isInvalid = $derived.by(() => {
314
+ if (this.validationStatus === false)
315
+ return false;
316
+ if (this.isInvalidProp.current)
317
+ return true;
318
+ return true;
319
+ });
320
+ inferredGranularity = $derived.by(() => {
321
+ return this.granularity.current ?? "minute";
322
+ });
323
+ timeRef = $derived.by(() => this.value.current ?? this.placeholder.current);
324
+ allSegmentContent = $derived.by(() => createTimeContent({
325
+ segmentValues: this.segmentValues,
326
+ formatter: this.formatter,
327
+ locale: this.locale.current,
328
+ granularity: this.inferredGranularity,
329
+ timeRef: this.timeRef,
330
+ hideTimeZone: this.hideTimeZone.current,
331
+ hourCycle: this.hourCycle,
332
+ }));
333
+ segmentContents = $derived.by(() => this.allSegmentContent.arr);
334
+ sharedSegmentAttrs = {
335
+ role: "spinbutton",
336
+ contenteditable: "true",
337
+ tabindex: 0,
338
+ spellcheck: false,
339
+ inputmode: "numeric",
340
+ autocorrect: "off",
341
+ enterkeyhint: "next",
342
+ style: {
343
+ caretColor: "transparent",
344
+ },
345
+ };
346
+ #getLabelledBy(segmentId) {
347
+ return `${segmentId} ${this.getLabelNode()?.id ?? ""}`;
348
+ }
349
+ updateSegment(part, cb) {
350
+ const disabled = this.disabled.current;
351
+ const readonly = this.readonly.current;
352
+ const readonlySegmentsSet = this.readonlySegmentsSet;
353
+ if (disabled || readonly || readonlySegmentsSet.has(part))
354
+ return;
355
+ const prev = this.segmentValues;
356
+ let newSegmentValues = prev;
357
+ if (part === "dayPeriod") {
358
+ const next = cb(prev[part]);
359
+ this.states.dayPeriod.updating = next;
360
+ const value = this.value.current;
361
+ if (value && "hour" in value) {
362
+ const trueHour = value.hour;
363
+ if (next === "AM") {
364
+ if (trueHour >= 12) {
365
+ prev.hour = `${trueHour - 12}`;
366
+ }
367
+ }
368
+ else if (next === "PM") {
369
+ if (trueHour < 12) {
370
+ prev.hour = `${trueHour + 12}`;
371
+ }
372
+ }
373
+ }
374
+ newSegmentValues = { ...prev, [part]: next };
375
+ }
376
+ else if (part === "hour") {
377
+ const next = cb(prev[part]);
378
+ this.states.hour.updating = next;
379
+ if (next !== null && prev.dayPeriod !== null) {
380
+ const dayPeriod = this.formatter.dayPeriod(toDate(this.#toDateValue(this.timeRef.set({ hour: Number.parseInt(next) }))), this.hourCycle);
381
+ if (dayPeriod === "AM" || dayPeriod === "PM") {
382
+ prev.dayPeriod = dayPeriod;
383
+ }
384
+ }
385
+ newSegmentValues = { ...prev, [part]: next };
386
+ }
387
+ else if (part === "minute") {
388
+ const next = cb(prev[part]);
389
+ this.states.minute.updating = next;
390
+ newSegmentValues = { ...prev, [part]: next };
391
+ }
392
+ else if (part === "second") {
393
+ const next = cb(prev[part]);
394
+ this.states.second.updating = next;
395
+ newSegmentValues = { ...prev, [part]: next };
396
+ }
397
+ this.segmentValues = newSegmentValues;
398
+ if (areAllTimeSegmentsFilled(newSegmentValues, this.#fieldNode)) {
399
+ this.setValue(getTimeValueFromSegments({
400
+ segmentObj: newSegmentValues,
401
+ fieldNode: this.#fieldNode,
402
+ timeRef: this.timeRef,
403
+ }));
404
+ }
405
+ else {
406
+ // this.setValue(undefined);
407
+ // this.segmentValues = newSegmentValues;
408
+ }
409
+ }
410
+ handleSegmentClick(e) {
411
+ if (this.disabled.current) {
412
+ e.preventDefault();
413
+ }
414
+ }
415
+ getBaseSegmentAttrs(part, segmentId) {
416
+ const inReadonlySegments = this.readonlySegmentsSet.has(part);
417
+ const defaultAttrs = {
418
+ "aria-invalid": getAriaInvalid(this.isInvalid),
419
+ "aria-disabled": getAriaDisabled(this.disabled.current),
420
+ "aria-readonly": getAriaReadonly(this.readonly.current || inReadonlySegments),
421
+ "data-invalid": getDataInvalid(this.isInvalid),
422
+ "data-disabled": getDataDisabled(this.disabled.current),
423
+ "data-readonly": getDataReadonly(this.readonly.current || inReadonlySegments),
424
+ "data-segment": `${part}`,
425
+ };
426
+ if (part === "literal")
427
+ return defaultAttrs;
428
+ const descriptionId = this.descriptionNode?.id;
429
+ const hasDescription = isFirstTimeSegment(segmentId, this.#fieldNode) && descriptionId;
430
+ const errorMsgId = this.errorMessageId?.current;
431
+ const describedBy = hasDescription
432
+ ? `${descriptionId} ${this.isInvalid && errorMsgId ? errorMsgId : ""}`
433
+ : undefined;
434
+ const contenteditable = !(this.readonly.current ||
435
+ inReadonlySegments ||
436
+ this.disabled.current);
437
+ return {
438
+ ...defaultAttrs,
439
+ "aria-labelledby": this.#getLabelledBy(segmentId),
440
+ contenteditable: contenteditable ? "true" : undefined,
441
+ "aria-describedby": describedBy,
442
+ tabindex: this.disabled.current ? undefined : 0,
443
+ };
444
+ }
445
+ }
446
+ export class TimeFieldInputState {
447
+ opts;
448
+ root;
449
+ constructor(opts, root) {
450
+ this.opts = opts;
451
+ this.root = root;
452
+ $effect(() => {
453
+ this.root.setName(this.opts.name.current);
454
+ });
455
+ }
456
+ #ariaDescribedBy = $derived.by(() => {
457
+ if (!isBrowser)
458
+ return undefined;
459
+ const doesDescriptionExist = document.getElementById(this.root.descriptionId);
460
+ if (!doesDescriptionExist)
461
+ return undefined;
462
+ return this.root.descriptionId;
463
+ });
464
+ props = $derived.by(() => ({
465
+ id: this.opts.id.current,
466
+ role: "group",
467
+ "aria-labelledby": this.root.getLabelNode()?.id ?? undefined,
468
+ "aria-describedby": this.#ariaDescribedBy,
469
+ "aria-disabled": getAriaDisabled(this.root.disabled.current),
470
+ "data-invalid": this.root.isInvalid ? "" : undefined,
471
+ "data-disabled": getDataDisabled(this.root.disabled.current),
472
+ [TIME_FIELD_INPUT_ATTR]: "",
473
+ ...attachRef(this.opts.ref, (v) => this.root.setFieldNode(v)),
474
+ }));
475
+ }
476
+ class TimeFieldHiddenInputState {
477
+ root;
478
+ shouldRender = $derived.by(() => this.root.name !== "");
479
+ isoValue = $derived.by(() => this.root.value.current ? getISOTimeValue(this.root.value.current) : undefined);
480
+ constructor(root) {
481
+ this.root = root;
482
+ }
483
+ props = $derived.by(() => ({
484
+ name: this.root.name,
485
+ value: this.isoValue,
486
+ required: this.root.required.current,
487
+ }));
488
+ }
489
+ class TimeFieldLabelState {
490
+ opts;
491
+ root;
492
+ constructor(opts, root) {
493
+ this.opts = opts;
494
+ this.root = root;
495
+ this.onclick = this.onclick.bind(this);
496
+ }
497
+ onclick(_) {
498
+ if (this.root.disabled.current)
499
+ return;
500
+ const firstSegment = getFirstTimeSegment(this.root.getFieldNode());
501
+ if (!firstSegment)
502
+ return;
503
+ firstSegment.focus();
504
+ }
505
+ props = $derived.by(() => ({
506
+ id: this.opts.id.current,
507
+ "data-invalid": getDataInvalid(this.root.isInvalid),
508
+ "data-disabled": getDataDisabled(this.root.disabled.current),
509
+ [TIME_FIELD_LABEL_ATTR]: "",
510
+ onclick: this.onclick,
511
+ ...attachRef(this.opts.ref, (v) => this.root.setLabelNode(v)),
512
+ }));
513
+ }
514
+ // Base class for time segments - simplified from date-field version
515
+ class BaseTimeSegmentState {
516
+ opts;
517
+ root;
518
+ announcer;
519
+ part;
520
+ config;
521
+ constructor(opts, root, part, config) {
522
+ this.opts = opts;
523
+ this.root = root;
524
+ this.part = part;
525
+ this.config = config;
526
+ this.announcer = root.announcer;
527
+ this.onkeydown = this.onkeydown.bind(this);
528
+ this.onfocusout = this.onfocusout.bind(this);
529
+ }
530
+ #getMax() {
531
+ return typeof this.config.max === "function" ? this.config.max(this.root) : this.config.max;
532
+ }
533
+ #getMin() {
534
+ return typeof this.config.min === "function" ? this.config.min(this.root) : this.config.min;
535
+ }
536
+ #formatValue(value, forDisplay = true) {
537
+ const str = String(value);
538
+ if (forDisplay && this.config.padZero && str.length === 1) {
539
+ return `0${value}`;
540
+ }
541
+ return str;
542
+ }
543
+ onkeydown(e) {
544
+ const placeholder = this.root.value.current ?? this.root.placeholder.current;
545
+ if (e.ctrlKey || e.metaKey || this.root.disabled.current)
546
+ return;
547
+ if (e.key !== kbd.TAB)
548
+ e.preventDefault();
549
+ if (!isAcceptableSegmentKey(e.key))
550
+ return;
551
+ if (isArrowUp(e.key)) {
552
+ this.#handleArrowUp(placeholder);
553
+ return;
554
+ }
555
+ if (isArrowDown(e.key)) {
556
+ this.#handleArrowDown(placeholder);
557
+ return;
558
+ }
559
+ if (isNumberString(e.key)) {
560
+ this.#handleNumberKey(e);
561
+ return;
562
+ }
563
+ if (isBackspace(e.key)) {
564
+ this.#handleBackspace(e);
565
+ return;
566
+ }
567
+ if (isSegmentNavigationKey(e.key)) {
568
+ handleTimeSegmentNavigation(e, this.root.getFieldNode());
569
+ }
570
+ }
571
+ #handleArrowUp(placeholder) {
572
+ const stateKey = this.part;
573
+ if (stateKey in this.root.states) {
574
+ this.root.states[stateKey].hasLeftFocus = false;
575
+ }
576
+ // @ts-expect-error shhh
577
+ this.root.updateSegment(this.part, (prev) => {
578
+ if (prev === null) {
579
+ const next = placeholder[this.part];
580
+ this.announcer.announce(String(next));
581
+ return this.#formatValue(next);
582
+ }
583
+ const current = placeholder.set({
584
+ [this.part]: Number.parseInt(prev),
585
+ });
586
+ // @ts-expect-error shhh
587
+ const next = current.cycle(this.part, this.config.cycle)[this.part];
588
+ this.announcer.announce(String(next));
589
+ return this.#formatValue(next);
590
+ });
591
+ }
592
+ #handleArrowDown(placeholder) {
593
+ const stateKey = this.part;
594
+ if (stateKey in this.root.states) {
595
+ this.root.states[stateKey].hasLeftFocus = false;
596
+ }
597
+ // @ts-expect-error - this is a part
598
+ this.root.updateSegment(this.part, (prev) => {
599
+ if (prev === null) {
600
+ const next = placeholder[this.part];
601
+ this.announcer.announce(String(next));
602
+ return this.#formatValue(next);
603
+ }
604
+ const current = placeholder.set({
605
+ [this.part]: Number.parseInt(prev),
606
+ });
607
+ // @ts-expect-error shhh
608
+ const next = current.cycle(this.part, -this.config.cycle)[this.part];
609
+ this.announcer.announce(String(next));
610
+ return this.#formatValue(next);
611
+ });
612
+ }
613
+ #handleNumberKey(e) {
614
+ const num = Number.parseInt(e.key);
615
+ let moveToNext = false;
616
+ const max = this.#getMax();
617
+ const maxStart = Math.floor(max / 10);
618
+ const numIsZero = num === 0;
619
+ const stateKey = this.part;
620
+ // @ts-expect-error this is a part
621
+ this.root.updateSegment(this.part, (prev) => {
622
+ if (stateKey in this.root.states && this.root.states[stateKey].hasLeftFocus) {
623
+ prev = null;
624
+ this.root.states[stateKey].hasLeftFocus = false;
625
+ }
626
+ if (prev === null) {
627
+ if (numIsZero) {
628
+ if (stateKey in this.root.states) {
629
+ this.root.states[stateKey].lastKeyZero = true;
630
+ }
631
+ this.announcer.announce("0");
632
+ return "0";
633
+ }
634
+ if (stateKey in this.root.states &&
635
+ (this.root.states[stateKey].lastKeyZero || num > maxStart)) {
636
+ moveToNext = true;
637
+ }
638
+ if (stateKey in this.root.states) {
639
+ this.root.states[stateKey].lastKeyZero = false;
640
+ }
641
+ if (moveToNext && String(num).length === 1) {
642
+ this.announcer.announce(num);
643
+ return `0${num}`;
644
+ }
645
+ return `${num}`;
646
+ }
647
+ if (stateKey in this.root.states && this.root.states[stateKey].lastKeyZero) {
648
+ if (num !== 0) {
649
+ moveToNext = true;
650
+ this.root.states[stateKey].lastKeyZero = false;
651
+ return `0${num}`;
652
+ }
653
+ if (this.part === "hour" && num === 0 && this.root.hourCycle === 24) {
654
+ moveToNext = true;
655
+ this.root.states[stateKey].lastKeyZero = false;
656
+ return `00`;
657
+ }
658
+ if ((this.part === "minute" || this.part === "second") && num === 0) {
659
+ moveToNext = true;
660
+ this.root.states[stateKey].lastKeyZero = false;
661
+ return "00";
662
+ }
663
+ return prev;
664
+ }
665
+ const total = Number.parseInt(prev + num.toString());
666
+ if (total > max) {
667
+ moveToNext = true;
668
+ return `0${num}`;
669
+ }
670
+ moveToNext = true;
671
+ return `${total}`;
672
+ });
673
+ if (moveToNext) {
674
+ moveToNextTimeSegment(e, this.root.getFieldNode());
675
+ }
676
+ }
677
+ #handleBackspace(e) {
678
+ const stateKey = this.part;
679
+ if (stateKey in this.root.states) {
680
+ this.root.states[stateKey].hasLeftFocus = false;
681
+ }
682
+ let moveToPrev = false;
683
+ // @ts-expect-error this is a part
684
+ this.root.updateSegment(this.part, (prev) => {
685
+ if (prev === null) {
686
+ moveToPrev = true;
687
+ this.announcer.announce(null);
688
+ return null;
689
+ }
690
+ if (prev.length === 2 && prev.startsWith("0")) {
691
+ this.announcer.announce(null);
692
+ return null;
693
+ }
694
+ const str = prev.toString();
695
+ if (str.length === 1) {
696
+ this.announcer.announce(null);
697
+ return null;
698
+ }
699
+ const next = Number.parseInt(str.slice(0, -1));
700
+ this.announcer.announce(String(next));
701
+ return `${next}`;
702
+ });
703
+ if (moveToPrev) {
704
+ moveToPrevTimeSegment(e, this.root.getFieldNode());
705
+ }
706
+ }
707
+ onfocusout(_) {
708
+ const stateKey = this.part;
709
+ if (stateKey in this.root.states) {
710
+ this.root.states[stateKey].hasLeftFocus = true;
711
+ }
712
+ // Pad with zero if needed
713
+ if (this.config.padZero) {
714
+ // @ts-expect-error this is a part
715
+ this.root.updateSegment(this.part, (prev) => {
716
+ if (prev && prev.length === 1) {
717
+ return `0${prev}`;
718
+ }
719
+ return prev;
720
+ });
721
+ }
722
+ }
723
+ getSegmentProps() {
724
+ const segmentValues = this.root.segmentValues;
725
+ const placeholder = this.root.placeholder.current;
726
+ const isEmpty = segmentValues[this.part] === null;
727
+ let value = placeholder;
728
+ if (segmentValues[this.part]) {
729
+ value = placeholder.set({
730
+ [this.part]: Number.parseInt(segmentValues[this.part]),
731
+ });
732
+ }
733
+ const valueNow = value[this.part];
734
+ const valueMin = this.#getMin();
735
+ const valueMax = this.#getMax();
736
+ let valueText = isEmpty ? "Empty" : `${valueNow}`;
737
+ // special handling for hour segment with dayPeriod
738
+ if (this.part === "hour" && "dayPeriod" in segmentValues && segmentValues.dayPeriod) {
739
+ valueText = isEmpty ? "Empty" : `${valueNow} ${segmentValues.dayPeriod}`;
740
+ }
741
+ return {
742
+ "aria-label": `${this.part}, `,
743
+ "aria-valuemin": valueMin,
744
+ "aria-valuemax": valueMax,
745
+ "aria-valuenow": valueNow,
746
+ "aria-valuetext": valueText,
747
+ };
748
+ }
749
+ props = $derived.by(() => {
750
+ return {
751
+ ...this.root.sharedSegmentAttrs,
752
+ id: this.opts.id.current,
753
+ ...this.getSegmentProps(),
754
+ onkeydown: this.onkeydown,
755
+ onfocusout: this.onfocusout,
756
+ onclick: this.root.handleSegmentClick,
757
+ ...this.root.getBaseSegmentAttrs(this.part, this.opts.id.current),
758
+ ...attachRef(this.opts.ref),
759
+ };
760
+ });
761
+ }
762
+ class TimeFieldHourSegmentState extends BaseTimeSegmentState {
763
+ constructor(opts, root) {
764
+ super(opts, root, "hour", SEGMENT_CONFIGS.hour);
765
+ }
766
+ onkeydown(e) {
767
+ if (isNumberString(e.key)) {
768
+ const oldUpdateSegment = this.root.updateSegment.bind(this.root);
769
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
770
+ this.root.updateSegment = (part, cb) => {
771
+ const result = oldUpdateSegment(part, cb);
772
+ // after updating hour, check if we need to display "12" instead of "0"
773
+ if (part === "hour" && "hour" in this.root.segmentValues) {
774
+ const hourValue = this.root.segmentValues.hour;
775
+ if (hourValue === "0" &&
776
+ this.root.dayPeriodNode &&
777
+ this.root.hourCycle !== 24) {
778
+ this.root.segmentValues.hour = "12";
779
+ }
780
+ }
781
+ return result;
782
+ };
783
+ }
784
+ super.onkeydown(e);
785
+ this.root.updateSegment = this.root.updateSegment.bind(this.root);
786
+ }
787
+ }
788
+ class TimeFieldMinuteSegmentState extends BaseTimeSegmentState {
789
+ constructor(opts, root) {
790
+ super(opts, root, "minute", SEGMENT_CONFIGS.minute);
791
+ }
792
+ }
793
+ class TimeFieldSecondSegmentState extends BaseTimeSegmentState {
794
+ constructor(opts, root) {
795
+ super(opts, root, "second", SEGMENT_CONFIGS.second);
796
+ }
797
+ }
798
+ class TimeFieldDayPeriodSegmentState {
799
+ opts;
800
+ root;
801
+ #announcer;
802
+ constructor(opts, root) {
803
+ this.opts = opts;
804
+ this.root = root;
805
+ this.#announcer = this.root.announcer;
806
+ this.onkeydown = this.onkeydown.bind(this);
807
+ }
808
+ onkeydown(e) {
809
+ if (e.ctrlKey || e.metaKey || this.root.disabled.current)
810
+ return;
811
+ if (e.key !== kbd.TAB)
812
+ e.preventDefault();
813
+ if (!isAcceptableDayPeriodKey(e.key))
814
+ return;
815
+ if (isArrowUp(e.key) || isArrowDown(e.key)) {
816
+ this.root.updateSegment("dayPeriod", (prev) => {
817
+ if (prev === "AM") {
818
+ const next = "PM";
819
+ this.#announcer.announce(next);
820
+ return next;
821
+ }
822
+ const next = "AM";
823
+ this.#announcer.announce(next);
824
+ return next;
825
+ });
826
+ return;
827
+ }
828
+ if (isBackspace(e.key)) {
829
+ this.root.states.dayPeriod.hasLeftFocus = false;
830
+ this.root.updateSegment("dayPeriod", () => {
831
+ const next = "AM";
832
+ this.#announcer.announce(next);
833
+ return next;
834
+ });
835
+ }
836
+ if (e.key === kbd.A || e.key === kbd.P || e.key === kbd.a || e.key === kbd.p) {
837
+ this.root.updateSegment("dayPeriod", () => {
838
+ const next = e.key === kbd.A || e.key === kbd.a ? "AM" : "PM";
839
+ this.#announcer.announce(next);
840
+ return next;
841
+ });
842
+ }
843
+ if (isSegmentNavigationKey(e.key)) {
844
+ handleTimeSegmentNavigation(e, this.root.getFieldNode());
845
+ }
846
+ }
847
+ props = $derived.by(() => {
848
+ const segmentValues = this.root.segmentValues;
849
+ if (!("dayPeriod" in segmentValues))
850
+ return;
851
+ const valueMin = 0;
852
+ const valueMax = 12;
853
+ const valueNow = segmentValues.dayPeriod === "AM" ? 0 : 12;
854
+ const valueText = segmentValues.dayPeriod === "AM" ? "AM" : "PM";
855
+ return {
856
+ ...this.root.sharedSegmentAttrs,
857
+ id: this.opts.id.current,
858
+ inputmode: "text",
859
+ "aria-label": "AM/PM",
860
+ "aria-valuemin": valueMin,
861
+ "aria-valuemax": valueMax,
862
+ "aria-valuenow": valueNow,
863
+ "aria-valuetext": valueText,
864
+ onkeydown: this.onkeydown,
865
+ onclick: this.root.handleSegmentClick,
866
+ ...this.root.getBaseSegmentAttrs("dayPeriod", this.opts.id.current),
867
+ ...attachRef(this.opts.ref, (v) => (this.root.dayPeriodNode = v)),
868
+ };
869
+ });
870
+ }
871
+ class TimeFieldLiteralSegmentState {
872
+ opts;
873
+ root;
874
+ constructor(opts, root) {
875
+ this.opts = opts;
876
+ this.root = root;
877
+ }
878
+ props = $derived.by(() => ({
879
+ id: this.opts.id.current,
880
+ "aria-hidden": getAriaHidden(true),
881
+ ...this.root.getBaseSegmentAttrs("literal", this.opts.id.current),
882
+ ...attachRef(this.opts.ref),
883
+ }));
884
+ }
885
+ class TimeFieldTimeZoneSegmentState {
886
+ opts;
887
+ root;
888
+ constructor(opts, root) {
889
+ this.opts = opts;
890
+ this.root = root;
891
+ this.onkeydown = this.onkeydown.bind(this);
892
+ }
893
+ onkeydown(e) {
894
+ if (e.key !== kbd.TAB)
895
+ e.preventDefault();
896
+ if (this.root.disabled.current)
897
+ return;
898
+ if (isSegmentNavigationKey(e.key)) {
899
+ handleTimeSegmentNavigation(e, this.root.getFieldNode());
900
+ }
901
+ }
902
+ props = $derived.by(() => ({
903
+ role: "textbox",
904
+ id: this.opts.id.current,
905
+ "aria-label": "timezone, ",
906
+ style: {
907
+ caretColor: "transparent",
908
+ },
909
+ onkeydown: this.onkeydown,
910
+ tabindex: 0,
911
+ ...this.root.getBaseSegmentAttrs("timeZoneName", this.opts.id.current),
912
+ "data-readonly": getDataReadonly(true),
913
+ ...attachRef(this.opts.ref),
914
+ }));
915
+ }
916
+ // Utils/helpers
917
+ function isAcceptableDayPeriodKey(key) {
918
+ return (isAcceptableSegmentKey(key) ||
919
+ key === kbd.A ||
920
+ key === kbd.P ||
921
+ key === kbd.a ||
922
+ key === kbd.p);
923
+ }
924
+ function isArrowUp(key) {
925
+ return key === kbd.ARROW_UP;
926
+ }
927
+ function isArrowDown(key) {
928
+ return key === kbd.ARROW_DOWN;
929
+ }
930
+ function isBackspace(key) {
931
+ return key === kbd.BACKSPACE;
932
+ }
933
+ const TimeFieldRootContext = new Context("TimeField.Root");
934
+ export function useTimeFieldRoot(props, rangeRoot) {
935
+ return TimeFieldRootContext.set(new TimeFieldRootState(props, rangeRoot));
936
+ }
937
+ export function useTimeFieldInput(props) {
938
+ return new TimeFieldInputState(props, TimeFieldRootContext.get());
939
+ }
940
+ export function useTimeFieldHiddenInput() {
941
+ return new TimeFieldHiddenInputState(TimeFieldRootContext.get());
942
+ }
943
+ export function useTimeFieldSegment(part, props) {
944
+ return segmentPartToInstance({
945
+ part,
946
+ segmentProps: props,
947
+ root: TimeFieldRootContext.get(),
948
+ });
949
+ }
950
+ export function useTimeFieldLabel(props) {
951
+ return new TimeFieldLabelState(props, TimeFieldRootContext.get());
952
+ }
953
+ function segmentPartToInstance(props) {
954
+ switch (props.part) {
955
+ case "hour":
956
+ return new TimeFieldHourSegmentState(props.segmentProps, props.root);
957
+ case "minute":
958
+ return new TimeFieldMinuteSegmentState(props.segmentProps, props.root);
959
+ case "second":
960
+ return new TimeFieldSecondSegmentState(props.segmentProps, props.root);
961
+ case "dayPeriod":
962
+ return new TimeFieldDayPeriodSegmentState(props.segmentProps, props.root);
963
+ case "literal":
964
+ return new TimeFieldLiteralSegmentState(props.segmentProps, props.root);
965
+ case "timeZoneName":
966
+ return new TimeFieldTimeZoneSegmentState(props.segmentProps, props.root);
967
+ default:
968
+ // For any date-related parts that shouldn't appear in time field
969
+ throw new Error(`Invalid segment part for time field: ${props.part}`);
970
+ }
971
+ }