@torpor/ui 0.3.7 → 1.0.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 (346) hide show
  1. package/README.md +83 -7
  2. package/dist/Accordion/Accordion.torp +43 -165
  3. package/dist/Accordion/AccordionContent.torp +6 -6
  4. package/dist/Accordion/AccordionHeader.torp +7 -11
  5. package/dist/Accordion/AccordionItem.torp +24 -24
  6. package/dist/Accordion/AccordionTrigger.torp +17 -8
  7. package/dist/Accordion/AccordionTypes.ts +6 -6
  8. package/dist/Accordion/index.d.ts +23 -25
  9. package/dist/Accordion/index.js +6 -22
  10. package/dist/Breadcrumb/Breadcrumb.torp +12 -10
  11. package/dist/Breadcrumb/BreadcrumbItem.torp +6 -6
  12. package/dist/Breadcrumb/BreadcrumbList.torp +21 -0
  13. package/dist/Breadcrumb/BreadcrumbSeparator.torp +4 -4
  14. package/dist/Breadcrumb/index.d.ts +39 -15
  15. package/dist/Breadcrumb/index.js +5 -14
  16. package/dist/Calendar/Calendar.torp +163 -156
  17. package/dist/Calendar/CalendarDay.torp +91 -45
  18. package/dist/Calendar/CalendarDayHeader.torp +36 -0
  19. package/dist/Calendar/CalendarGrid.torp +104 -0
  20. package/dist/Calendar/CalendarGridHeader.torp +43 -0
  21. package/dist/Calendar/CalendarHeader.torp +48 -0
  22. package/dist/Calendar/CalendarHeaderTrigger.torp +54 -0
  23. package/dist/Calendar/CalendarTypes.ts +25 -4
  24. package/dist/Calendar/buildDays.ts +54 -0
  25. package/dist/Calendar/index.d.ts +191 -5
  26. package/dist/Calendar/index.js +8 -10
  27. package/dist/Carousel/Carousel.torp +180 -0
  28. package/dist/Carousel/CarouselIndicators.torp +65 -0
  29. package/dist/Carousel/CarouselNext.torp +43 -0
  30. package/dist/Carousel/CarouselPrevious.torp +44 -0
  31. package/dist/Carousel/CarouselSlide.torp +57 -0
  32. package/dist/Carousel/CarouselTypes.ts +28 -0
  33. package/dist/Carousel/index.d.ts +147 -0
  34. package/dist/Carousel/index.js +6 -0
  35. package/dist/Charts/BarChart.torp +70 -0
  36. package/dist/Charts/Chart.torp +120 -0
  37. package/dist/Charts/ChartBars.torp +78 -0
  38. package/dist/Charts/ChartColumns.torp +66 -0
  39. package/dist/Charts/ChartGrid.torp +54 -0
  40. package/dist/Charts/ChartLines.torp +60 -0
  41. package/dist/Charts/ChartTypes.ts +25 -0
  42. package/dist/Charts/ChartXAxis.torp +83 -0
  43. package/dist/Charts/ChartYAxis.torp +105 -0
  44. package/dist/Charts/ColumnChart.torp +70 -0
  45. package/dist/{Chart → Charts}/GridLines.torp +7 -4
  46. package/dist/Charts/LineChart.torp +73 -0
  47. package/dist/{Chart → Charts}/PieChart.torp +14 -20
  48. package/dist/{Chart → Charts}/ScatterChart.torp +45 -38
  49. package/dist/{Chart → Charts}/SparkLine.torp +16 -19
  50. package/dist/{Chart → Charts}/XAxis.torp +18 -12
  51. package/dist/{Chart → Charts}/YAxis.torp +15 -9
  52. package/dist/Charts/index.d.ts +469 -0
  53. package/dist/Charts/index.js +17 -0
  54. package/dist/ColorPicker/ColorPalette.torp +154 -0
  55. package/dist/ColorPicker/ColorPicker.torp +157 -0
  56. package/dist/ColorPicker/index.d.ts +85 -0
  57. package/dist/ColorPicker/index.js +3 -0
  58. package/dist/ComboBox/ComboBox.torp +205 -0
  59. package/dist/ComboBox/ComboBoxContent.torp +112 -0
  60. package/dist/ComboBox/ComboBoxInput.torp +145 -0
  61. package/dist/ComboBox/ComboBoxTrigger.torp +49 -0
  62. package/dist/ComboBox/ComboBoxTypes.ts +39 -0
  63. package/dist/ComboBox/index.d.ts +152 -0
  64. package/dist/ComboBox/index.js +6 -0
  65. package/dist/CommandPalette/CommandPalette.torp +220 -0
  66. package/dist/CommandPalette/CommandTypes.ts +14 -0
  67. package/dist/CommandPalette/index.d.ts +45 -0
  68. package/dist/CommandPalette/index.js +2 -0
  69. package/dist/Contextual/Contextual.torp +39 -52
  70. package/dist/Contextual/ContextualContent.torp +29 -73
  71. package/dist/Contextual/ContextualHover.torp +112 -0
  72. package/dist/Contextual/ContextualTrigger.torp +31 -42
  73. package/dist/Contextual/ContextualTypes.ts +5 -18
  74. package/dist/Contextual/index.d.ts +44 -27
  75. package/dist/Contextual/index.js +5 -14
  76. package/dist/DataGrid/DataGrid.torp +496 -0
  77. package/dist/DataGrid/DataGridTypes.ts +25 -0
  78. package/dist/DataGrid/index.d.ts +90 -0
  79. package/dist/DataGrid/index.js +3 -0
  80. package/dist/DataGrid/sortRows.ts +32 -0
  81. package/dist/DatePicker/DatePicker.torp +222 -0
  82. package/dist/DatePicker/DatePickerHeader.torp +20 -0
  83. package/dist/DatePicker/index.d.ts +55 -0
  84. package/dist/DatePicker/index.js +2 -0
  85. package/dist/DateRangePicker/DateRangePicker.torp +311 -0
  86. package/dist/DateRangePicker/DateRangePickerDay.torp +137 -0
  87. package/dist/DateRangePicker/DateRangePickerTypes.ts +21 -0
  88. package/dist/DateRangePicker/index.d.ts +76 -0
  89. package/dist/DateRangePicker/index.js +3 -0
  90. package/dist/Dialog/Dialog.torp +33 -30
  91. package/dist/Dialog/DialogBody.torp +5 -9
  92. package/dist/Dialog/DialogButton.torp +14 -11
  93. package/dist/Dialog/DialogFooter.torp +3 -7
  94. package/dist/Dialog/DialogHeader.torp +6 -10
  95. package/dist/Dialog/DialogTypes.ts +2 -2
  96. package/dist/Dialog/index.d.ts +28 -26
  97. package/dist/Dialog/index.js +6 -22
  98. package/dist/Disclosure/Disclosure.torp +18 -18
  99. package/dist/Disclosure/DisclosureContent.torp +5 -6
  100. package/dist/Disclosure/DisclosureHeader.torp +7 -11
  101. package/dist/Disclosure/DisclosureTrigger.torp +4 -5
  102. package/dist/Disclosure/DisclosureTypes.ts +1 -1
  103. package/dist/Disclosure/index.d.ts +14 -16
  104. package/dist/Disclosure/index.js +5 -18
  105. package/dist/Drawer/Drawer.torp +21 -51
  106. package/dist/Drawer/DrawerContent.torp +67 -13
  107. package/dist/Drawer/DrawerOverlay.torp +4 -4
  108. package/dist/Drawer/DrawerTrigger.torp +16 -4
  109. package/dist/Drawer/DrawerTypes.ts +4 -1
  110. package/dist/Drawer/index.d.ts +16 -20
  111. package/dist/Drawer/index.js +5 -18
  112. package/dist/FocusGroup/FocusGroup.torp +70 -0
  113. package/dist/FocusGroup/FocusGroupButton.torp +59 -0
  114. package/dist/FocusGroup/FocusGroupTypes.ts +14 -0
  115. package/dist/FocusGroup/index.d.ts +44 -0
  116. package/dist/FocusGroup/index.js +3 -0
  117. package/dist/Form/CheckBox.torp +28 -20
  118. package/dist/Form/Field.torp +17 -17
  119. package/dist/Form/File.torp +31 -22
  120. package/dist/Form/FileDrop.torp +187 -0
  121. package/dist/Form/Form.torp +24 -22
  122. package/dist/Form/FormTypes.ts +2 -0
  123. package/dist/Form/FormValues.torp +37 -0
  124. package/dist/Form/Hidden.torp +4 -12
  125. package/dist/Form/Input.torp +38 -32
  126. package/dist/Form/Label.torp +3 -3
  127. package/dist/Form/Message.torp +13 -5
  128. package/dist/Form/MessageSummary.torp +39 -0
  129. package/dist/Form/Option.torp +11 -7
  130. package/dist/Form/Select.torp +32 -28
  131. package/dist/Form/TextArea.torp +35 -31
  132. package/dist/Form/Toggle.torp +109 -0
  133. package/dist/Form/index.d.ts +198 -77
  134. package/dist/Form/index.js +16 -46
  135. package/dist/ListBox/ListBox.torp +374 -0
  136. package/dist/ListBox/ListBoxItem.torp +131 -0
  137. package/dist/ListBox/ListBoxSeparator.torp +29 -0
  138. package/dist/ListBox/ListBoxTypes.ts +41 -0
  139. package/dist/ListBox/LoadedListBoxItems.torp +41 -0
  140. package/dist/ListBox/index.d.ts +136 -0
  141. package/dist/ListBox/index.js +4 -0
  142. package/dist/MaskedInput/MaskedInput.torp +143 -0
  143. package/dist/MaskedInput/index.d.ts +52 -0
  144. package/dist/MaskedInput/index.js +2 -0
  145. package/dist/Menu/Menu.torp +90 -70
  146. package/dist/Menu/MenuButton.torp +17 -10
  147. package/dist/Menu/MenuCheck.torp +22 -18
  148. package/dist/Menu/MenuGroup.torp +4 -4
  149. package/dist/Menu/MenuIndicator.torp +5 -13
  150. package/dist/Menu/MenuPopout.torp +20 -50
  151. package/dist/Menu/MenuPopoutContent.torp +15 -66
  152. package/dist/Menu/MenuPopoutTrigger.torp +19 -100
  153. package/dist/Menu/MenuRadio.torp +25 -26
  154. package/dist/Menu/MenuRadioGroup.torp +25 -68
  155. package/dist/Menu/MenuSeparator.torp +3 -2
  156. package/dist/Menu/MenuTypes.ts +12 -21
  157. package/dist/Menu/index.d.ts +45 -51
  158. package/dist/Menu/index.js +12 -46
  159. package/dist/MenuBar/MenuBar.torp +31 -83
  160. package/dist/MenuBar/MenuBarItem.torp +34 -55
  161. package/dist/MenuBar/MenuBarItemContent.torp +15 -101
  162. package/dist/MenuBar/MenuBarItemTrigger.torp +20 -55
  163. package/dist/MenuBar/MenuBarTypes.ts +13 -12
  164. package/dist/MenuBar/index.d.ts +17 -19
  165. package/dist/MenuBar/index.js +5 -18
  166. package/dist/Modal/Modal.torp +28 -48
  167. package/dist/Modal/ModalContent.torp +21 -44
  168. package/dist/Modal/ModalOverlay.torp +4 -4
  169. package/dist/Modal/ModalTrigger.torp +16 -32
  170. package/dist/Modal/ModalTypes.ts +6 -17
  171. package/dist/Modal/index.d.ts +17 -29
  172. package/dist/Modal/index.js +29 -28
  173. package/dist/Modal/index.js.map +1 -1
  174. package/dist/Modal/showPrompt.ts +2 -2
  175. package/dist/NavMenu/NavMenu.torp +22 -104
  176. package/dist/NavMenu/NavMenuButton.torp +10 -8
  177. package/dist/NavMenu/NavMenuElement.torp +6 -7
  178. package/dist/NavMenu/NavMenuGroup.torp +5 -5
  179. package/dist/NavMenu/NavMenuGroupHeader.torp +5 -8
  180. package/dist/NavMenu/NavMenuLink.torp +7 -10
  181. package/dist/NavMenu/NavMenuPopout.torp +16 -61
  182. package/dist/NavMenu/NavMenuPopoutContent.torp +14 -73
  183. package/dist/NavMenu/NavMenuPopoutTrigger.torp +47 -0
  184. package/dist/NavMenu/NavMenuSeparator.torp +3 -6
  185. package/dist/NavMenu/NavMenuTypes.ts +6 -15
  186. package/dist/NavMenu/index.d.ts +55 -55
  187. package/dist/NavMenu/index.js +11 -42
  188. package/dist/Notification/Notification.torp +12 -24
  189. package/dist/Notification/NotificationClose.torp +6 -3
  190. package/dist/Notification/NotificationContainer.torp +3 -7
  191. package/dist/Notification/NotificationContent.torp +3 -7
  192. package/dist/Notification/NotificationTypes.ts +1 -1
  193. package/dist/Notification/index.d.ts +15 -12
  194. package/dist/Notification/index.js +22 -32
  195. package/dist/Notification/index.js.map +1 -1
  196. package/dist/NumberInput/NumberInput.torp +255 -0
  197. package/dist/NumberInput/index.d.ts +44 -0
  198. package/dist/NumberInput/index.js +2 -0
  199. package/dist/Pagination/Pagination.torp +152 -0
  200. package/dist/Pagination/PaginationItem.torp +137 -0
  201. package/dist/Pagination/PaginationTypes.ts +22 -0
  202. package/dist/Pagination/buildPages.ts +61 -0
  203. package/dist/Pagination/index.d.ts +63 -0
  204. package/dist/Pagination/index.js +3 -0
  205. package/dist/Popover/Popover.torp +39 -52
  206. package/dist/Popover/PopoverContent.torp +27 -76
  207. package/dist/Popover/PopoverHover.torp +90 -0
  208. package/dist/Popover/PopoverTrigger.torp +17 -40
  209. package/dist/Popover/PopoverTypes.ts +5 -18
  210. package/dist/Popover/index.d.ts +40 -25
  211. package/dist/Popover/index.js +18 -18
  212. package/dist/Popover/index.js.map +1 -1
  213. package/dist/Progress/Progress.torp +88 -0
  214. package/dist/Progress/index.d.ts +37 -0
  215. package/dist/Progress/index.js +2 -0
  216. package/dist/Rating/Rating.torp +225 -0
  217. package/dist/Rating/index.d.ts +44 -0
  218. package/dist/Rating/index.js +2 -0
  219. package/dist/SegmentedControl/SegmentedControl.torp +233 -0
  220. package/dist/SegmentedControl/SegmentedControlTypes.ts +26 -0
  221. package/dist/SegmentedControl/SegmentedItem.torp +78 -0
  222. package/dist/SegmentedControl/index.d.ts +74 -0
  223. package/dist/SegmentedControl/index.js +3 -0
  224. package/dist/SelectBox/SelectBox.torp +190 -0
  225. package/dist/SelectBox/SelectBoxContent.torp +107 -0
  226. package/dist/SelectBox/SelectBoxTrigger.torp +141 -0
  227. package/dist/SelectBox/SelectBoxTypes.ts +33 -0
  228. package/dist/SelectBox/index.d.ts +112 -0
  229. package/dist/SelectBox/index.js +5 -0
  230. package/dist/Slider/Slider.torp +248 -0
  231. package/dist/Slider/index.d.ts +45 -0
  232. package/dist/Slider/index.js +2 -0
  233. package/dist/Splitter/Splitter.torp +156 -0
  234. package/dist/Splitter/SplitterHandle.torp +161 -0
  235. package/dist/Splitter/SplitterPane.torp +63 -0
  236. package/dist/Splitter/SplitterTypes.ts +31 -0
  237. package/dist/Splitter/index.d.ts +103 -0
  238. package/dist/Splitter/index.js +4 -0
  239. package/dist/Stepper/Stepper.torp +146 -0
  240. package/dist/Stepper/StepperMarker.torp +24 -0
  241. package/dist/Stepper/StepperNext.torp +44 -0
  242. package/dist/Stepper/StepperPrevious.torp +44 -0
  243. package/dist/Stepper/StepperStep.torp +90 -0
  244. package/dist/Stepper/StepperTypes.ts +30 -0
  245. package/dist/Stepper/index.d.ts +149 -0
  246. package/dist/Stepper/index.js +6 -0
  247. package/dist/TabGroup/TabContent.torp +6 -6
  248. package/dist/TabGroup/TabGroup.torp +97 -113
  249. package/dist/TabGroup/TabGroupTypes.ts +3 -3
  250. package/dist/TabGroup/TabHeader.torp +23 -19
  251. package/dist/TabGroup/TabList.torp +8 -21
  252. package/dist/TabGroup/TabTrigger.torp +6 -7
  253. package/dist/TabGroup/index.d.ts +26 -26
  254. package/dist/TabGroup/index.js +6 -22
  255. package/dist/TagInput/TagInput.torp +378 -0
  256. package/dist/TagInput/index.d.ts +48 -0
  257. package/dist/TagInput/index.js +2 -0
  258. package/dist/TimePicker/TimePicker.torp +462 -0
  259. package/dist/TimePicker/index.d.ts +53 -0
  260. package/dist/TimePicker/index.js +2 -0
  261. package/dist/ToolBar/ToolBar.torp +117 -0
  262. package/dist/ToolBar/ToolBarButton.torp +56 -0
  263. package/dist/ToolBar/ToolBarElement.torp +51 -0
  264. package/dist/ToolBar/ToolBarPopout.torp +45 -0
  265. package/dist/ToolBar/ToolBarPopoutContent.torp +34 -0
  266. package/dist/ToolBar/ToolBarPopoutTrigger.torp +47 -0
  267. package/dist/ToolBar/ToolBarSeparator.torp +21 -0
  268. package/dist/ToolBar/ToolBarTypes.ts +28 -0
  269. package/dist/ToolBar/index.d.ts +210 -0
  270. package/dist/ToolBar/index.js +8 -0
  271. package/dist/Tree/Tree.torp +395 -0
  272. package/dist/Tree/TreeItem.torp +176 -0
  273. package/dist/Tree/TreeItemChildren.torp +60 -0
  274. package/dist/Tree/TreeItemContent.torp +46 -0
  275. package/dist/Tree/TreeItemLabel.torp +94 -0
  276. package/dist/Tree/TreeItemTrigger.torp +78 -0
  277. package/dist/Tree/TreeLoadedChildren.torp +122 -0
  278. package/dist/Tree/TreeTypes.ts +63 -0
  279. package/dist/Tree/index.d.ts +262 -0
  280. package/dist/Tree/index.js +7 -0
  281. package/dist/icons/ChevronDoubleLeft.torp +19 -0
  282. package/dist/icons/ChevronDoubleRight.torp +19 -0
  283. package/dist/icons/index.d.ts +61 -1
  284. package/dist/icons/index.js +17 -58
  285. package/dist/loader-DEeG5vFY.js +44 -0
  286. package/dist/loader-DEeG5vFY.js.map +1 -0
  287. package/dist/motion/index.d.ts +12 -8
  288. package/dist/motion/index.d.ts.map +1 -0
  289. package/dist/motion/index.js +72 -88
  290. package/dist/motion/index.js.map +1 -1
  291. package/dist/mount/draggable.ts +63 -0
  292. package/dist/mount/dropTarget.ts +108 -0
  293. package/dist/mount/hoverTrigger.ts +1 -1
  294. package/dist/utils/$handle.ts +23 -0
  295. package/dist/utils/LoadedItems.torp +47 -0
  296. package/dist/utils/PopoutClickTrigger.torp +99 -0
  297. package/dist/utils/PopoutTypes.ts +82 -0
  298. package/dist/utils/SubmenuPopout.torp +89 -0
  299. package/dist/utils/SubmenuPopoutContent.torp +87 -0
  300. package/dist/utils/SubmenuPopoutTrigger.torp +160 -0
  301. package/dist/utils/chartColors.ts +13 -13
  302. package/dist/utils/chartLayout.ts +143 -0
  303. package/dist/utils/chartUtils.ts +1 -1
  304. package/dist/utils/colorUtils.ts +103 -0
  305. package/dist/utils/createItemGroup.ts +152 -0
  306. package/dist/utils/dateUtils.ts +8 -7
  307. package/dist/utils/documentEvents.ts +4 -4
  308. package/dist/utils/dragState.ts +86 -0
  309. package/dist/utils/focusGroup.ts +204 -0
  310. package/dist/utils/focusItem.ts +44 -0
  311. package/dist/utils/formField.ts +70 -0
  312. package/dist/utils/hoverOpen.ts +50 -0
  313. package/dist/utils/hoverReveal.ts +194 -0
  314. package/dist/utils/loader.ts +113 -0
  315. package/dist/utils/maskUtils.ts +98 -0
  316. package/dist/utils/popoutContent.ts +137 -0
  317. package/dist/utils/reorderList.ts +52 -0
  318. package/package.json +116 -25
  319. package/dist/Accordion/index.js.map +0 -1
  320. package/dist/Breadcrumb/index.js.map +0 -1
  321. package/dist/Calendar/index.js.map +0 -1
  322. package/dist/Chart/BarChart.torp +0 -159
  323. package/dist/Chart/LineChart.torp +0 -196
  324. package/dist/Chart/index.d.ts +0 -220
  325. package/dist/Chart/index.js +0 -34
  326. package/dist/Chart/index.js.map +0 -1
  327. package/dist/Contextual/index.js.map +0 -1
  328. package/dist/Dialog/index.js.map +0 -1
  329. package/dist/Disclosure/index.js.map +0 -1
  330. package/dist/Drawer/index.js.map +0 -1
  331. package/dist/Form/index.js.map +0 -1
  332. package/dist/Menu/index.js.map +0 -1
  333. package/dist/MenuBar/index.js.map +0 -1
  334. package/dist/NavMenu/NavMenuPopoutButton.torp +0 -129
  335. package/dist/NavMenu/index.js.map +0 -1
  336. package/dist/TabGroup/index.js.map +0 -1
  337. package/dist/icons/index.js.map +0 -1
  338. package/dist/index.d.ts +0 -19
  339. package/dist/metafile-esm.json +0 -1
  340. package/dist/motion/fade.ts +0 -16
  341. package/dist/motion/grow.ts +0 -23
  342. package/dist/motion/measure.ts +0 -28
  343. package/dist/motion/slide.ts +0 -45
  344. package/dist/mount/index.d.ts +0 -2
  345. package/dist/utils/index.d.ts +0 -2
  346. /package/dist/{Chart → Charts}/DataSeries.ts +0 -0
@@ -0,0 +1,462 @@
1
+ import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
2
+ import {
3
+ type FieldContext,
4
+ FieldContextName,
5
+ type FormContext,
6
+ FormContextName,
7
+ } from "../Form/FormTypes";
8
+ import FormValues from "../Form/FormValues.torp";
9
+ import createFormField from "../utils/formField";
10
+
11
+ interface TimePickerProps {
12
+ /** An ID for the root element */
13
+ id?: string;
14
+ /** Classes for the root element */
15
+ class?: ClassValue;
16
+ /** Styles for the root element */
17
+ style?: StyleValue;
18
+ /**
19
+ * The current time as "HH:mm" or "HH:mm:ss" (24-hour), or undefined if
20
+ * the field is empty
21
+ */
22
+ value?: Bindable<string | undefined>;
23
+ /** If set to true, times are displayed and entered in 12-hour format with an AM/PM toggle */
24
+ hour12?: boolean;
25
+ /** If set to true, a seconds field is shown */
26
+ seconds?: boolean;
27
+ /** The amount by which the minute field steps (default 1) */
28
+ step?: number;
29
+ /** The earliest allowed time, as "HH:mm" or "HH:mm:ss" */
30
+ min?: string;
31
+ /** The latest allowed time, as "HH:mm" or "HH:mm:ss" */
32
+ max?: string;
33
+ /** If set to true, the user cannot interact with this input */
34
+ disabled?: boolean;
35
+ /** An accessible name for the whole input */
36
+ ariaLabel?: string;
37
+ /** The name of the component's form data field */
38
+ name?: string;
39
+ /** Raised when the value has changed */
40
+ onchange?: (value: string | undefined) => void;
41
+ }
42
+
43
+ type Part = "hour" | "minute" | "second";
44
+
45
+ const PAD = [2, 2, 2];
46
+
47
+ /**
48
+ * An input for entering a time of day, made of hour, minute and (optionally)
49
+ * second fields that follow the WAI ARIA spinbutton pattern -- each can be
50
+ * typed into or stepped with the arrow keys -- plus an AM/PM toggle in
51
+ * 12-hour mode.
52
+ *
53
+ * The value is always the canonical 24-hour form ("HH:mm" or "HH:mm:ss"),
54
+ * regardless of display format.
55
+ */
56
+ export default function TimePicker($props: TimePickerProps | undefined) {
57
+ let hourInput: HTMLInputElement;
58
+ let minuteInput: HTMLInputElement;
59
+ let secondInput: HTMLInputElement;
60
+ let periodButton: HTMLButtonElement;
61
+
62
+ // Wire up the enclosing Form and Field, if any
63
+ const form = createFormField(
64
+ $props?.name,
65
+ $context[FormContextName] as FormContext | undefined,
66
+ $context[FieldContextName] as FieldContext | undefined,
67
+ );
68
+
69
+ function getSeconds(): boolean {
70
+ return $props?.seconds === true;
71
+ }
72
+ function getDisabled(): boolean {
73
+ return $props?.disabled === true;
74
+ }
75
+
76
+ function parseTime(text: string | undefined): { h: number; m: number; s: number } | undefined {
77
+ if (!text) return undefined;
78
+ const match = text.match(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/);
79
+ if (!match) return undefined;
80
+ return {
81
+ h: Number(match[1]),
82
+ m: Number(match[2]),
83
+ s: match[3] ? Number(match[3]) : 0,
84
+ };
85
+ }
86
+
87
+ function clampPart(part: Part, n: number): number {
88
+ switch (part) {
89
+ case "hour":
90
+ return Math.min(23, Math.max(0, n));
91
+ case "minute":
92
+ return Math.min(59, Math.max(0, n));
93
+ case "second":
94
+ return Math.min(59, Math.max(0, n));
95
+ }
96
+ }
97
+
98
+ function format(n: number): string {
99
+ return String(n).padStart(2, "0");
100
+ }
101
+
102
+ function formatValue(h: number, m: number, s: number): string {
103
+ return getSeconds() ? `${format(h)}:${format(m)}:${format(s)}` : `${format(h)}:${format(m)}`;
104
+ }
105
+
106
+ let $state = $watch({
107
+ value: undefined as string | undefined,
108
+
109
+ // The text being typed in each segment; committed on completion
110
+ hourText: "",
111
+ minuteText: "",
112
+ secondText: "",
113
+ });
114
+
115
+ $bind($state, $props, "value");
116
+
117
+ // The committed parts of the current value; missing parts are zero
118
+ function parts(): { h: number; m: number; s: number } {
119
+ const parsed = parseTime($state.value);
120
+ return parsed ?? { h: 0, m: 0, s: 0 };
121
+ }
122
+
123
+ function getMin(): number {
124
+ const parsed = parseTime($props?.min);
125
+ if (!parsed) return 0;
126
+ return parsed.h * 3600 + parsed.m * 60 + parsed.s;
127
+ }
128
+
129
+ function getMax(): number {
130
+ const parsed = parseTime($props?.max);
131
+ if (!parsed) return 24 * 3600 - (getSeconds() ? 1 : 60);
132
+ return parsed.h * 3600 + parsed.m * 60 + parsed.s;
133
+ }
134
+
135
+ function totalSeconds(h: number, m: number, s: number): number {
136
+ return h * 3600 + m * 60 + s;
137
+ }
138
+
139
+ function setParts(h: number, m: number, s: number) {
140
+ // Clamp the assembled time between min and max
141
+ let t = totalSeconds(
142
+ clampPart("hour", h),
143
+ clampPart("minute", m),
144
+ clampPart("second", s),
145
+ );
146
+ t = Math.min(getMax(), Math.max(getMin(), t));
147
+
148
+ const next = formatValue(Math.floor(t / 3600), Math.floor((t % 3600) / 60), t % 60);
149
+ if (next !== $state.value) {
150
+ $state.value = next;
151
+ form.handleInput();
152
+ $props?.onchange?.(next);
153
+ }
154
+ }
155
+
156
+ // --- Spinbutton segments ---
157
+
158
+ function partDisplay(part: Part): string {
159
+ const p = parts();
160
+ const n = part === "hour" ? p.h : part === "minute" ? p.m : p.s;
161
+ if (part !== "hour") {
162
+ return format(n);
163
+ }
164
+ if ($props?.hour12) {
165
+ const twelve = n % 12 === 0 ? 12 : n % 12;
166
+ return format(twelve);
167
+ }
168
+ return format(n);
169
+ }
170
+
171
+ function isPM(): boolean {
172
+ return parts().h >= 12;
173
+ }
174
+
175
+ function commitBuffer(part: Part, buffer: string): boolean {
176
+ if (buffer.trim() === "") return false;
177
+ const n = Number(buffer);
178
+ if (!Number.isFinite(n)) return false;
179
+ setPartValue(part, n, false);
180
+ return true;
181
+ }
182
+
183
+ /**
184
+ * Steps a segment by delta hours/minutes/seconds, wrapping within its
185
+ * range like a clock face
186
+ */
187
+ function stepPart(part: Part, delta: number) {
188
+ if (getDisabled()) return;
189
+ const p = parts();
190
+ let h = p.h;
191
+ let m = p.m;
192
+ let s = p.s;
193
+ switch (part) {
194
+ case "hour": {
195
+ h = ((h + delta) % 24 + 24) % 24;
196
+ break;
197
+ }
198
+ case "minute": {
199
+ m = ((m + delta) % 60 + 60) % 60;
200
+ break;
201
+ }
202
+ case "second": {
203
+ s = ((s + delta) % 60 + 60) % 60;
204
+ break;
205
+ }
206
+ }
207
+ setParts(h, m, s);
208
+ syncInputs();
209
+ }
210
+
211
+ function setPartValue(part: Part, n: number, sync: boolean) {
212
+ const p = parts();
213
+ switch (part) {
214
+ case "hour":
215
+ setParts(clampPart("hour", n), p.m, p.s);
216
+ break;
217
+ case "minute":
218
+ setParts(p.h, clampPart("minute", n), p.s);
219
+ break;
220
+ case "second":
221
+ setParts(p.h, p.m, clampPart("second", n));
222
+ break;
223
+ }
224
+ if (sync) syncInputs();
225
+ }
226
+
227
+ // Keep segment inputs showing the committed values unless typing there
228
+ function syncInputs() {
229
+ if (!hourInput || !minuteInput) return;
230
+ const editing = (el: HTMLInputElement, buffer: string) =>
231
+ document.activeElement === el && el.value !== buffer;
232
+ if (!editing(hourInput, $state.hourText)) {
233
+ hourInput.value = partDisplay("hour");
234
+ }
235
+ if (!editing(minuteInput, $state.minuteText)) {
236
+ minuteInput.value = partDisplay("minute");
237
+ }
238
+ if (secondInput && !editing(secondInput, $state.secondText)) {
239
+ secondInput.value = partDisplay("second");
240
+ }
241
+ }
242
+
243
+ function handleInput(part: Part, e: Event) {
244
+ if (getDisabled()) return;
245
+ const el = e.currentTarget as HTMLInputElement;
246
+
247
+ // Accept digits only; strip everything else as it is typed
248
+ let text = el.value.replace(/\D/g, "");
249
+ const limit = part === "hour" ? ($props?.hour12 ? 12 : 23) : 59;
250
+ while (text.length > 0 && Number(text) > limit && text.length > 1) {
251
+ text = text.slice(1);
252
+ }
253
+
254
+ el.value = text;
255
+ setBuffer(part, text);
256
+
257
+ // Two digits fills the segment; move on to the next one
258
+ if (text.length >= (PAD[partIndex(part)] ?? 2)) {
259
+ commitBuffer(part, text);
260
+ focusNextSegment(part);
261
+ }
262
+ }
263
+
264
+ function handleBlur(part: Part, e: FocusEvent) {
265
+ const el = e.target as HTMLInputElement;
266
+ commitBuffer(part, el.value);
267
+ setBuffer(part, "");
268
+ el.value = partDisplay(part);
269
+ form.handleBlur();
270
+ }
271
+
272
+ function handleSegmentKey(part: Part, e: KeyboardEvent) {
273
+ if (getDisabled()) return;
274
+ const el = e.currentTarget as HTMLInputElement;
275
+
276
+ switch (e.key) {
277
+ case "ArrowUp": {
278
+ e.preventDefault();
279
+ const s = $props?.step ?? 1;
280
+ stepPart(part, part === "minute" ? s : 1);
281
+ break;
282
+ }
283
+ case "ArrowDown": {
284
+ e.preventDefault();
285
+ const s = $props?.step ?? 1;
286
+ stepPart(part, -(part === "minute" ? s : 1));
287
+ break;
288
+ }
289
+ case "Home": {
290
+ e.preventDefault();
291
+ setPartValue(part, 0, true);
292
+ break;
293
+ }
294
+ case "End": {
295
+ e.preventDefault();
296
+ setPartValue(part, part === "hour" ? 23 : 59, true);
297
+ break;
298
+ }
299
+ case "Enter": {
300
+ e.preventDefault();
301
+ commitBuffer(part, el.value);
302
+ setBuffer(part, "");
303
+ el.value = partDisplay(part);
304
+ break;
305
+ }
306
+ }
307
+ }
308
+
309
+ function setBuffer(part: Part, text: string) {
310
+ switch (part) {
311
+ case "hour":
312
+ $state.hourText = text;
313
+ break;
314
+ case "minute":
315
+ $state.minuteText = text;
316
+ break;
317
+ case "second":
318
+ $state.secondText = text;
319
+ break;
320
+ }
321
+ }
322
+
323
+ function partIndex(part: Part): number {
324
+ switch (part) {
325
+ case "hour":
326
+ return 0;
327
+ case "minute":
328
+ return 1;
329
+ case "second":
330
+ return 2;
331
+ }
332
+ }
333
+
334
+ function focusNextSegment(part: Part) {
335
+ if (part === "hour") {
336
+ minuteInput?.focus();
337
+ } else if (part === "minute" && getSeconds()) {
338
+ secondInput?.focus();
339
+ } else if (part === "minute" && $props?.hour12) {
340
+ periodButton?.focus();
341
+ }
342
+ }
343
+
344
+ // --- AM/PM toggle ---
345
+
346
+ function togglePeriod() {
347
+ if (getDisabled()) return;
348
+ const p = parts();
349
+ setParts(isPM() ? p.h - 12 : p.h + 12, p.m, p.s);
350
+ }
351
+
352
+ function handlePeriodKey(e: KeyboardEvent) {
353
+ if (getDisabled()) return;
354
+ switch (e.key) {
355
+ case "ArrowUp":
356
+ case "ArrowDown": {
357
+ e.preventDefault();
358
+ togglePeriod();
359
+ break;
360
+ }
361
+ }
362
+ }
363
+
364
+ function ariaNow(part: Part): number {
365
+ const p = parts();
366
+ return part === "hour" ? p.h : part === "minute" ? p.m : p.s;
367
+ }
368
+
369
+ function ariaText(part: Part): string {
370
+ if (part === "hour" && $props?.hour12) {
371
+ return `${partDisplay("hour")} ${isPM() ? "PM" : "AM"}`;
372
+ }
373
+ return String(ariaNow(part));
374
+ }
375
+
376
+ @render {
377
+ <div
378
+ id={$props?.id}
379
+ class={["torp-time-picker", $props?.class]}
380
+ {$props?.style}
381
+ role="group"
382
+ aria-label={$props?.ariaLabel}
383
+ data-valid={form.valid ? "valid" : "invalid"}
384
+ aria-describedby={form.messageId}
385
+ >
386
+ <input
387
+ &ref={hourInput}
388
+ class="torp-time-picker-part"
389
+ type="text"
390
+ inputmode="numeric"
391
+ role="spinbutton"
392
+ value={partDisplay("hour")}
393
+ aria-label={$props?.hour12 ? "Hour (12-hour)" : "Hour"}
394
+ aria-valuemin={0}
395
+ aria-valuemax={23}
396
+ aria-valuenow={ariaNow("hour")}
397
+ aria-valuetext={ariaText("hour")}
398
+ disabled={getDisabled()}
399
+ tabindex={getDisabled() ? -1 : 0}
400
+ oninput={(e: Event) => handleInput("hour", e)}
401
+ onblur={(e: FocusEvent) => handleBlur("hour", e)}
402
+ onkeydown={(e: KeyboardEvent) => handleSegmentKey("hour", e)}
403
+ />
404
+ <span class="torp-time-picker-separator">:</span>
405
+ <input
406
+ &ref={minuteInput}
407
+ class="torp-time-picker-part"
408
+ type="text"
409
+ inputmode="numeric"
410
+ role="spinbutton"
411
+ value={partDisplay("minute")}
412
+ aria-label="Minute"
413
+ aria-valuemin={0}
414
+ aria-valuemax={59}
415
+ aria-valuenow={ariaNow("minute")}
416
+ aria-valuetext={ariaText("minute")}
417
+ disabled={getDisabled()}
418
+ tabindex={getDisabled() ? -1 : 0}
419
+ oninput={(e: Event) => handleInput("minute", e)}
420
+ onblur={(e: FocusEvent) => handleBlur("minute", e)}
421
+ onkeydown={(e: KeyboardEvent) => handleSegmentKey("minute", e)}
422
+ />
423
+ @if (getSeconds()) {
424
+ <span class="torp-time-picker-separator">:</span>
425
+ <input
426
+ &ref={secondInput}
427
+ class="torp-time-picker-part"
428
+ type="text"
429
+ inputmode="numeric"
430
+ role="spinbutton"
431
+ value={partDisplay("second")}
432
+ aria-label="Second"
433
+ aria-valuemin={0}
434
+ aria-valuemax={59}
435
+ aria-valuenow={ariaNow("second")}
436
+ aria-valuetext={ariaText("second")}
437
+ disabled={getDisabled()}
438
+ tabindex={getDisabled() ? -1 : 0}
439
+ oninput={(e: Event) => handleInput("second", e)}
440
+ onblur={(e: FocusEvent) => handleBlur("second", e)}
441
+ onkeydown={(e: KeyboardEvent) => handleSegmentKey("second", e)}
442
+ />
443
+ }
444
+ @if ($props?.hour12) {
445
+ <button
446
+ &ref={periodButton}
447
+ type="button"
448
+ class="torp-time-picker-period"
449
+ disabled={getDisabled()}
450
+ tabindex={getDisabled() ? -1 : 0}
451
+ aria-label={isPM() ? "PM" : "AM"}
452
+ onclick={togglePeriod}
453
+ onkeydown={handlePeriodKey}
454
+ >
455
+ {isPM() ? "PM" : "AM"}
456
+ </button>
457
+ }
458
+ </div>
459
+
460
+ <FormValues name={form.name} value={$state.value} />
461
+ }
462
+ }
@@ -0,0 +1,53 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+
4
+ interface TimePickerProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue;
9
+ /** Styles for the root element */
10
+ style?: StyleValue;
11
+ /**
12
+ * The current time as "HH:mm" or "HH:mm:ss" (24-hour), or undefined if
13
+ * the field is empty
14
+ */
15
+ value?: Bindable<string | undefined>;
16
+ /** If set to true, times are displayed and entered in 12-hour format with an AM/PM toggle */
17
+ hour12?: boolean;
18
+ /** If set to true, a seconds field is shown */
19
+ seconds?: boolean;
20
+ /** The amount by which the minute field steps (default 1) */
21
+ step?: number;
22
+ /** The earliest allowed time, as "HH:mm" or "HH:mm:ss" */
23
+ min?: string;
24
+ /** The latest allowed time, as "HH:mm" or "HH:mm:ss" */
25
+ max?: string;
26
+ /** If set to true, the user cannot interact with this input */
27
+ disabled?: boolean;
28
+ /** An accessible name for the whole input */
29
+ ariaLabel?: string;
30
+ /** The name of the component's form data field */
31
+ name?: string;
32
+ /** Raised when the value has changed */
33
+ onchange?: (value: string | undefined) => void;
34
+ }
35
+
36
+ /**
37
+ * An input for entering a time of day, made of hour, minute and (optionally)
38
+ * second fields that follow the WAI ARIA spinbutton pattern -- each can be
39
+ * typed into or stepped with the arrow keys -- plus an AM/PM toggle in
40
+ * 12-hour mode.
41
+ *
42
+ * The value is always the canonical 24-hour form ("HH:mm" or "HH:mm:ss"),
43
+ * regardless of display format.
44
+ */
45
+ declare function TimePicker(
46
+ $parent: ParentNode,
47
+ $anchor: Node | null,
48
+ $props: TimePickerProps | undefined,
49
+ $context?: Record<PropertyKey, any>,
50
+ $slots?: Record<string, SlotRender>,
51
+ ): void;
52
+
53
+ export { TimePicker };
@@ -0,0 +1,2 @@
1
+ import TimePicker from "./TimePicker.torp";
2
+ export { TimePicker };
@@ -0,0 +1,117 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { type PopoutContext, PopoutContextName } from "../utils/PopoutTypes";
3
+ import { createFocusGroup } from "../utils/focusGroup";
4
+ import { type ItemState, type ToolBarContext, ToolBarContextName } from "./ToolBarTypes";
5
+
6
+ interface ToolBarProps {
7
+ /** An ID for the root element */
8
+ id?: string;
9
+ /** Classes for the root element */
10
+ class?: ClassValue;
11
+ /** Styles for the root element */
12
+ style?: StyleValue;
13
+ /** An ARIA label for describing the menu to screen readers */
14
+ ariaLabel?: string | undefined;
15
+ /** The orientation of the menu, which affects keyboard navigation */
16
+ orientation?: "horizontal" | "vertical";
17
+ }
18
+
19
+ /**
20
+ * A toolbar is a container for grouping a set of controls, such as buttons, menubuttons, or checkboxes.
21
+ *
22
+ * See [the WAI ARIA guidelines for ToolBars](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/).
23
+ *
24
+ * The structure of a ToolBar is:
25
+ *
26
+ * ```
27
+ * <ToolBar>
28
+ * <ToolBarButton />
29
+ * <ToolBarSeparator /> (optional)
30
+ * <ToolBarPopout /> (optional)
31
+ * ...
32
+ * </ToolBar>
33
+ * ```
34
+ */
35
+ export default function ToolBar($props: ToolBarProps | undefined) {
36
+ let orientation = $props?.orientation ?? "horizontal";
37
+
38
+ // The focus group owns the item ledger and roving-tabindex bookkeeping
39
+ const group = createFocusGroup<ItemState>();
40
+
41
+ // Check whether this is a menu in a popout
42
+ const popoutContext = $context[PopoutContextName] as PopoutContext;
43
+ const inPopout = !!popoutContext;
44
+ let popoutState = popoutContext?.state;
45
+
46
+ // Set the focusFirstElement method in the PopoverContext(s) so that it can be called after everything has been shown
47
+ if (popoutContext) {
48
+ popoutContext.focusFirstElement = group.focusFirstItem;
49
+ popoutContext.focusLastElement = group.focusLastItem;
50
+ }
51
+
52
+ // Set the context to pass down to items
53
+ let context: ToolBarContext = {
54
+ handleItemFocus,
55
+ handleItemKey,
56
+ registerItem,
57
+ handlePopout,
58
+ orientation,
59
+ };
60
+ $context[ToolBarContextName] = context;
61
+
62
+ // If the ToolBar is not in a popout, focus its first item. Otherwise the popout will call focusFirstItem, below
63
+ function handleFocus() {
64
+ if (!popoutContext && group.itemStates.length) {
65
+ group.focusFirstItem();
66
+ }
67
+ }
68
+
69
+ function registerItem(item: ItemState) {
70
+ return group.registerItem(item);
71
+ }
72
+
73
+ function handleItemFocus(index: number) {
74
+ group.handleItemFocus(index);
75
+ }
76
+
77
+ function handleItemKey(e: KeyboardEvent) {
78
+ const index = group.getFocusIndex();
79
+ switch (e.key) {
80
+ case "Escape": {
81
+ if (inPopout) {
82
+ e.preventDefault();
83
+ popoutState.visible = false;
84
+ }
85
+ break;
86
+ }
87
+ default: {
88
+ group.handleGroupKey(index, e, { vertical: orientation === "vertical" });
89
+ break;
90
+ }
91
+ }
92
+ }
93
+
94
+ function handlePopout(index: number) {
95
+ // Hide all other popouts
96
+ for (let item of group.itemStates) {
97
+ if (item.index !== index) {
98
+ item.setVisible?.(false);
99
+ }
100
+ }
101
+ }
102
+
103
+ @render {
104
+ <nav
105
+ {$props?.id}
106
+ class={["torp-tool-bar", $props?.class]}
107
+ {$props?.style}
108
+ tabindex="0"
109
+ role="toolbar"
110
+ aria-label={$props?.ariaLabel}
111
+ aria-orientation={orientation === "vertical" ? "vertical" : undefined}
112
+ onfocus={handleFocus}
113
+ >
114
+ <slot />
115
+ </nav>
116
+ }
117
+ }
@@ -0,0 +1,56 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { type ToolBarContext, ToolBarContextName } from "./ToolBarTypes";
3
+
4
+ interface ToolBarButtonProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue;
9
+ /** Styles for the root element */
10
+ style?: StyleValue;
11
+ /** Whether this button is disabled */
12
+ disabled?: boolean;
13
+ /** Raised when the button is clicked */
14
+ onclick?: (e: MouseEvent) => void;
15
+ /** Raised when a key is pressed down */
16
+ onkeydown?: (e: KeyboardEvent) => void;
17
+ }
18
+
19
+ /**
20
+ * A button that is placed in a ToolBar.
21
+ *
22
+ */
23
+ export default function ToolBarButton($props: ToolBarButtonProps | undefined) {
24
+ let button: HTMLButtonElement;
25
+
26
+ // Get stuff out of the ToolBarContext
27
+ const menuContext = $context[ToolBarContextName] as ToolBarContext;
28
+ if (!menuContext) {
29
+ throw new Error("ToolBarButton must be contained within a ToolBar");
30
+ }
31
+ const { registerItem, handleItemKey, handleItemFocus } = menuContext;
32
+
33
+ // Register this item with the parent ToolBar and store the index
34
+ const { index } = registerItem({ setFocused, disabled: $props?.disabled });
35
+
36
+ function setFocused() {
37
+ button.focus();
38
+ }
39
+
40
+ @render {
41
+ <button
42
+ {$props?.id}
43
+ class={["torp-tool-bar-button", $props?.class]}
44
+ {$props?.style}
45
+ type="button"
46
+ {$props?.disabled}
47
+ aria-disabled={$props?.disabled ? "true" : undefined}
48
+ {$props?.onclick}
49
+ onkeydown={handleItemKey}
50
+ onfocus={() => handleItemFocus(index)}
51
+ &ref={button}
52
+ >
53
+ <slot />
54
+ </button>
55
+ }
56
+ }