@torpor/ui 0.4.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +83 -7
  2. package/dist/Accordion/Accordion.torp +42 -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 +1 -1
  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 +24 -3
  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 +55 -144
  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 +85 -0
  43. package/dist/Charts/ChartYAxis.torp +107 -0
  44. package/dist/Charts/ColumnChart.torp +70 -0
  45. package/dist/Charts/GridLines.torp +4 -4
  46. package/dist/Charts/LineChart.torp +55 -178
  47. package/dist/Charts/PieChart.torp +14 -20
  48. package/dist/Charts/ScatterChart.torp +45 -38
  49. package/dist/Charts/SparkLine.torp +16 -19
  50. package/dist/Charts/XAxis.torp +14 -11
  51. package/dist/Charts/YAxis.torp +10 -8
  52. package/dist/Charts/index.d.ts +328 -79
  53. package/dist/Charts/index.js +17 -34
  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 +120 -0
  60. package/dist/ComboBox/ComboBoxInput.torp +161 -0
  61. package/dist/ComboBox/ComboBoxTrigger.torp +49 -0
  62. package/dist/ComboBox/ComboBoxTypes.ts +39 -0
  63. package/dist/ComboBox/index.d.ts +154 -0
  64. package/dist/ComboBox/index.js +6 -0
  65. package/dist/CommandPalette/CommandPalette.torp +223 -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 +32 -50
  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 +4 -17
  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 +315 -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 +24 -17
  91. package/dist/Dialog/DialogBody.torp +5 -9
  92. package/dist/Dialog/DialogButton.torp +12 -12
  93. package/dist/Dialog/DialogFooter.torp +3 -7
  94. package/dist/Dialog/DialogHeader.torp +6 -10
  95. package/dist/Dialog/index.d.ts +27 -27
  96. package/dist/Dialog/index.js +6 -22
  97. package/dist/Disclosure/Disclosure.torp +18 -18
  98. package/dist/Disclosure/DisclosureContent.torp +5 -6
  99. package/dist/Disclosure/DisclosureHeader.torp +7 -11
  100. package/dist/Disclosure/DisclosureTrigger.torp +4 -5
  101. package/dist/Disclosure/index.d.ts +14 -16
  102. package/dist/Disclosure/index.js +5 -18
  103. package/dist/Drawer/Drawer.torp +19 -49
  104. package/dist/Drawer/DrawerContent.torp +70 -13
  105. package/dist/Drawer/DrawerOverlay.torp +4 -4
  106. package/dist/Drawer/DrawerTrigger.torp +16 -4
  107. package/dist/Drawer/DrawerTypes.ts +3 -0
  108. package/dist/Drawer/index.d.ts +16 -20
  109. package/dist/Drawer/index.js +5 -18
  110. package/dist/FocusGroup/FocusGroup.torp +70 -0
  111. package/dist/FocusGroup/FocusGroupButton.torp +59 -0
  112. package/dist/FocusGroup/FocusGroupTypes.ts +14 -0
  113. package/dist/FocusGroup/index.d.ts +44 -0
  114. package/dist/FocusGroup/index.js +3 -0
  115. package/dist/Form/CheckBox.torp +27 -18
  116. package/dist/Form/Field.torp +17 -17
  117. package/dist/Form/File.torp +31 -22
  118. package/dist/Form/FileDrop.torp +187 -0
  119. package/dist/Form/Form.torp +24 -22
  120. package/dist/Form/FormTypes.ts +2 -0
  121. package/dist/Form/FormValues.torp +37 -0
  122. package/dist/Form/Hidden.torp +4 -12
  123. package/dist/Form/Input.torp +38 -32
  124. package/dist/Form/Label.torp +3 -3
  125. package/dist/Form/Message.torp +13 -5
  126. package/dist/Form/MessageSummary.torp +5 -4
  127. package/dist/Form/Option.torp +11 -7
  128. package/dist/Form/Select.torp +32 -28
  129. package/dist/Form/TextArea.torp +35 -31
  130. package/dist/Form/Toggle.torp +109 -0
  131. package/dist/Form/index.d.ts +178 -79
  132. package/dist/Form/index.js +16 -50
  133. package/dist/ListBox/ListBox.torp +253 -150
  134. package/dist/ListBox/ListBoxItem.torp +54 -30
  135. package/dist/ListBox/ListBoxSeparator.torp +5 -2
  136. package/dist/ListBox/ListBoxTypes.ts +23 -5
  137. package/dist/ListBox/LoadedListBoxItems.torp +41 -0
  138. package/dist/ListBox/index.d.ts +56 -17
  139. package/dist/ListBox/index.js +4 -14
  140. package/dist/MaskedInput/MaskedInput.torp +143 -0
  141. package/dist/MaskedInput/index.d.ts +52 -0
  142. package/dist/MaskedInput/index.js +2 -0
  143. package/dist/Menu/Menu.torp +86 -71
  144. package/dist/Menu/MenuButton.torp +17 -10
  145. package/dist/Menu/MenuCheck.torp +22 -18
  146. package/dist/Menu/MenuGroup.torp +4 -4
  147. package/dist/Menu/MenuIndicator.torp +5 -13
  148. package/dist/Menu/MenuPopout.torp +20 -50
  149. package/dist/Menu/MenuPopoutContent.torp +15 -66
  150. package/dist/Menu/MenuPopoutTrigger.torp +19 -100
  151. package/dist/Menu/MenuRadio.torp +25 -26
  152. package/dist/Menu/MenuRadioGroup.torp +25 -68
  153. package/dist/Menu/MenuSeparator.torp +3 -2
  154. package/dist/Menu/MenuTypes.ts +9 -19
  155. package/dist/Menu/index.d.ts +45 -51
  156. package/dist/Menu/index.js +12 -46
  157. package/dist/MenuBar/MenuBar.torp +31 -83
  158. package/dist/MenuBar/MenuBarItem.torp +33 -51
  159. package/dist/MenuBar/MenuBarItemContent.torp +15 -101
  160. package/dist/MenuBar/MenuBarItemTrigger.torp +20 -55
  161. package/dist/MenuBar/MenuBarTypes.ts +11 -10
  162. package/dist/MenuBar/index.d.ts +17 -19
  163. package/dist/MenuBar/index.js +5 -18
  164. package/dist/Modal/Modal.torp +22 -47
  165. package/dist/Modal/ModalContent.torp +21 -44
  166. package/dist/Modal/ModalOverlay.torp +4 -4
  167. package/dist/Modal/ModalTrigger.torp +16 -32
  168. package/dist/Modal/ModalTypes.ts +5 -16
  169. package/dist/Modal/index.d.ts +17 -29
  170. package/dist/Modal/index.js +29 -28
  171. package/dist/Modal/index.js.map +1 -1
  172. package/dist/Modal/showPrompt.ts +2 -2
  173. package/dist/NavMenu/NavMenu.torp +22 -111
  174. package/dist/NavMenu/NavMenuButton.torp +7 -8
  175. package/dist/NavMenu/NavMenuElement.torp +6 -7
  176. package/dist/NavMenu/NavMenuGroup.torp +5 -5
  177. package/dist/NavMenu/NavMenuGroupHeader.torp +5 -8
  178. package/dist/NavMenu/NavMenuLink.torp +7 -10
  179. package/dist/NavMenu/NavMenuPopout.torp +15 -60
  180. package/dist/NavMenu/NavMenuPopoutContent.torp +14 -73
  181. package/dist/NavMenu/NavMenuPopoutTrigger.torp +17 -99
  182. package/dist/NavMenu/NavMenuSeparator.torp +3 -7
  183. package/dist/NavMenu/NavMenuTypes.ts +4 -14
  184. package/dist/NavMenu/index.d.ts +40 -44
  185. package/dist/NavMenu/index.js +11 -42
  186. package/dist/Notification/Notification.torp +12 -24
  187. package/dist/Notification/NotificationClose.torp +6 -3
  188. package/dist/Notification/NotificationContainer.torp +3 -7
  189. package/dist/Notification/NotificationContent.torp +3 -7
  190. package/dist/Notification/NotificationTypes.ts +1 -1
  191. package/dist/Notification/index.d.ts +15 -12
  192. package/dist/Notification/index.js +22 -32
  193. package/dist/Notification/index.js.map +1 -1
  194. package/dist/NumberInput/NumberInput.torp +255 -0
  195. package/dist/NumberInput/index.d.ts +44 -0
  196. package/dist/NumberInput/index.js +2 -0
  197. package/dist/Pagination/Pagination.torp +41 -34
  198. package/dist/Pagination/PaginationItem.torp +50 -13
  199. package/dist/Pagination/PaginationTypes.ts +3 -6
  200. package/dist/Pagination/buildPages.ts +9 -5
  201. package/dist/Pagination/index.d.ts +19 -9
  202. package/dist/Pagination/index.js +3 -40
  203. package/dist/Popover/Popover.torp +32 -50
  204. package/dist/Popover/PopoverContent.torp +27 -76
  205. package/dist/Popover/PopoverHover.torp +90 -0
  206. package/dist/Popover/PopoverTrigger.torp +17 -40
  207. package/dist/Popover/PopoverTypes.ts +4 -17
  208. package/dist/Popover/index.d.ts +40 -25
  209. package/dist/Popover/index.js +18 -18
  210. package/dist/Popover/index.js.map +1 -1
  211. package/dist/Progress/Progress.torp +88 -0
  212. package/dist/Progress/index.d.ts +37 -0
  213. package/dist/Progress/index.js +2 -0
  214. package/dist/Rating/Rating.torp +225 -0
  215. package/dist/Rating/index.d.ts +44 -0
  216. package/dist/Rating/index.js +2 -0
  217. package/dist/SegmentedControl/SegmentedControl.torp +233 -0
  218. package/dist/SegmentedControl/SegmentedControlTypes.ts +26 -0
  219. package/dist/SegmentedControl/SegmentedItem.torp +78 -0
  220. package/dist/SegmentedControl/index.d.ts +74 -0
  221. package/dist/SegmentedControl/index.js +3 -0
  222. package/dist/SelectBox/SelectBox.torp +107 -53
  223. package/dist/SelectBox/SelectBoxContent.torp +49 -19
  224. package/dist/SelectBox/SelectBoxTrigger.torp +108 -8
  225. package/dist/SelectBox/SelectBoxTypes.ts +26 -11
  226. package/dist/SelectBox/index.d.ts +39 -54
  227. package/dist/SelectBox/index.js +5 -18
  228. package/dist/Slider/Slider.torp +245 -0
  229. package/dist/Slider/index.d.ts +45 -0
  230. package/dist/Slider/index.js +2 -0
  231. package/dist/Splitter/Splitter.torp +156 -0
  232. package/dist/Splitter/SplitterHandle.torp +161 -0
  233. package/dist/Splitter/SplitterPane.torp +63 -0
  234. package/dist/Splitter/SplitterTypes.ts +31 -0
  235. package/dist/Splitter/index.d.ts +103 -0
  236. package/dist/Splitter/index.js +4 -0
  237. package/dist/Stepper/Stepper.torp +146 -0
  238. package/dist/Stepper/StepperMarker.torp +24 -0
  239. package/dist/Stepper/StepperNext.torp +44 -0
  240. package/dist/Stepper/StepperPrevious.torp +44 -0
  241. package/dist/Stepper/StepperStep.torp +90 -0
  242. package/dist/Stepper/StepperTypes.ts +30 -0
  243. package/dist/Stepper/index.d.ts +149 -0
  244. package/dist/Stepper/index.js +6 -0
  245. package/dist/TabGroup/TabContent.torp +6 -6
  246. package/dist/TabGroup/TabGroup.torp +97 -113
  247. package/dist/TabGroup/TabGroupTypes.ts +1 -1
  248. package/dist/TabGroup/TabHeader.torp +23 -19
  249. package/dist/TabGroup/TabList.torp +8 -21
  250. package/dist/TabGroup/TabTrigger.torp +6 -7
  251. package/dist/TabGroup/index.d.ts +26 -26
  252. package/dist/TabGroup/index.js +6 -22
  253. package/dist/TagInput/TagInput.torp +378 -0
  254. package/dist/TagInput/index.d.ts +48 -0
  255. package/dist/TagInput/index.js +2 -0
  256. package/dist/TimePicker/TimePicker.torp +462 -0
  257. package/dist/TimePicker/index.d.ts +53 -0
  258. package/dist/TimePicker/index.js +2 -0
  259. package/dist/ToolBar/ToolBar.torp +29 -110
  260. package/dist/ToolBar/ToolBarButton.torp +7 -8
  261. package/dist/ToolBar/ToolBarElement.torp +7 -11
  262. package/dist/ToolBar/ToolBarPopout.torp +16 -68
  263. package/dist/ToolBar/ToolBarPopoutContent.torp +15 -73
  264. package/dist/ToolBar/ToolBarPopoutTrigger.torp +19 -110
  265. package/dist/ToolBar/ToolBarSeparator.torp +3 -7
  266. package/dist/ToolBar/ToolBarTypes.ts +7 -14
  267. package/dist/ToolBar/index.d.ts +30 -32
  268. package/dist/ToolBar/index.js +8 -30
  269. package/dist/Tree/Tree.torp +395 -0
  270. package/dist/Tree/TreeItem.torp +176 -0
  271. package/dist/Tree/TreeItemChildren.torp +60 -0
  272. package/dist/Tree/TreeItemContent.torp +46 -0
  273. package/dist/Tree/TreeItemLabel.torp +94 -0
  274. package/dist/Tree/TreeItemTrigger.torp +78 -0
  275. package/dist/Tree/TreeLoadedChildren.torp +122 -0
  276. package/dist/Tree/TreeTypes.ts +63 -0
  277. package/dist/Tree/index.d.ts +262 -0
  278. package/dist/Tree/index.js +7 -0
  279. package/dist/icons/ChevronDoubleLeft.torp +19 -0
  280. package/dist/icons/ChevronDoubleRight.torp +19 -0
  281. package/dist/icons/index.d.ts +61 -1
  282. package/dist/icons/index.js +17 -58
  283. package/dist/loader-DEeG5vFY.js +44 -0
  284. package/dist/loader-DEeG5vFY.js.map +1 -0
  285. package/dist/motion/index.d.ts +12 -8
  286. package/dist/motion/index.d.ts.map +1 -0
  287. package/dist/motion/index.js +70 -88
  288. package/dist/motion/index.js.map +1 -1
  289. package/dist/mount/draggable.ts +63 -0
  290. package/dist/mount/dropTarget.ts +108 -0
  291. package/dist/mount/hoverTrigger.ts +1 -1
  292. package/dist/utils/$handle.ts +23 -0
  293. package/dist/utils/LoadedItems.torp +47 -0
  294. package/dist/utils/PopoutClickTrigger.torp +99 -0
  295. package/dist/utils/PopoutTypes.ts +59 -12
  296. package/dist/utils/SubmenuPopout.torp +89 -0
  297. package/dist/utils/SubmenuPopoutContent.torp +87 -0
  298. package/dist/utils/SubmenuPopoutTrigger.torp +160 -0
  299. package/dist/utils/chartColors.ts +13 -13
  300. package/dist/utils/chartLayout.ts +143 -0
  301. package/dist/utils/colorUtils.ts +103 -0
  302. package/dist/utils/createItemGroup.ts +152 -0
  303. package/dist/utils/dateUtils.ts +8 -7
  304. package/dist/utils/documentEvents.ts +4 -4
  305. package/dist/utils/dragState.ts +86 -0
  306. package/dist/utils/focusGroup.ts +204 -0
  307. package/dist/utils/focusItem.ts +44 -0
  308. package/dist/utils/formField.ts +70 -0
  309. package/dist/utils/hoverOpen.ts +50 -0
  310. package/dist/utils/hoverReveal.ts +194 -0
  311. package/dist/utils/loader.ts +113 -0
  312. package/dist/utils/maskUtils.ts +98 -0
  313. package/dist/utils/popoutContent.ts +137 -0
  314. package/dist/utils/reorderList.ts +52 -0
  315. package/package.json +97 -22
  316. package/dist/Accordion/index.js.map +0 -1
  317. package/dist/Breadcrumb/index.js.map +0 -1
  318. package/dist/Calendar/index.js.map +0 -1
  319. package/dist/Charts/index.js.map +0 -1
  320. package/dist/Contextual/index.js.map +0 -1
  321. package/dist/Dialog/index.js.map +0 -1
  322. package/dist/Disclosure/index.js.map +0 -1
  323. package/dist/Drawer/index.js.map +0 -1
  324. package/dist/Form/index.js.map +0 -1
  325. package/dist/ListBox/index.js.map +0 -1
  326. package/dist/Menu/index.js.map +0 -1
  327. package/dist/MenuBar/index.js.map +0 -1
  328. package/dist/NavMenu/index.js.map +0 -1
  329. package/dist/Pagination/index.js.map +0 -1
  330. package/dist/SelectBox/SelectBoxInput.torp +0 -95
  331. package/dist/SelectBox/index.js.map +0 -1
  332. package/dist/TabGroup/index.js.map +0 -1
  333. package/dist/ToolBar/index.js.map +0 -1
  334. package/dist/icons/index.js.map +0 -1
  335. package/dist/index.d.ts +0 -19
  336. package/dist/metafile-esm.json +0 -1
  337. package/dist/motion/fade.ts +0 -16
  338. package/dist/motion/grow.ts +0 -23
  339. package/dist/motion/measure.ts +0 -28
  340. package/dist/motion/slide.ts +0 -45
  341. package/dist/mount/index.d.ts +0 -2
  342. package/dist/utils/index.d.ts +0 -2
@@ -0,0 +1,152 @@
1
+ import { $cache, $run } from "@torpor/view";
2
+
3
+ export interface ItemGroupItem {
4
+ index: number;
5
+ value: any;
6
+ parentDisabled?: boolean;
7
+ }
8
+
9
+ export interface ItemGroup<T extends ItemGroupItem> {
10
+ itemStates: T[];
11
+ registerItem: (item: T) => void;
12
+ removeItem: (itemIndex: number) => void;
13
+ toggleItem: (value: any) => void;
14
+ }
15
+
16
+ /**
17
+ * Creates a managed collection of selectable items for group-style components
18
+ * (Accordion, ListBox, TabGroup, Tree, etc.).
19
+ *
20
+ * Handles item registration, removal, and toggle logic. Each item's selection
21
+ * state is derived: registration installs a `$cache` getter over
22
+ * `selectedProperty` that computes from the group value, so selection can
23
+ * never drift out of sync with it — eliminating the boilerplate that was
24
+ * duplicated across ~5 components.
25
+ *
26
+ * The returned `itemStates` array is the source of truth; components should
27
+ * store it in their context for child items to access.
28
+ *
29
+ * NOTE: Items must be registered before their selection property is first
30
+ * read, so the derived getter can take the property over.
31
+ */
32
+ export function createItemGroup<T extends ItemGroupItem>(options: {
33
+ /** The reactive state containing the group value */
34
+ state: { value: any };
35
+ /** Selection type, or a getter for it if it can change */
36
+ type: "single" | "multiple" | (() => "single" | "multiple");
37
+ /** The boolean property on items that reflects selection state */
38
+ selectedProperty: keyof T;
39
+ /** Getter for parent disabled state */
40
+ disabled?: () => boolean | undefined;
41
+ /** Allow deselecting in single-select mode (default: true) */
42
+ allowDeselect?: boolean;
43
+ /** Called for each item after index assignment but before selection check */
44
+ onRegister?: (item: T) => void;
45
+ /** Callback after a toggle, for side effects like closing a popout */
46
+ onToggle?: (value: any) => void;
47
+ }): ItemGroup<T> {
48
+ const { state, selectedProperty } = options;
49
+ const getType = typeof options.type === "function" ? options.type : () => options.type;
50
+ const allowDeselect = options.allowDeselect ?? true;
51
+ const itemStates: T[] = [];
52
+ let nextIndex = 0;
53
+
54
+ // Propagate disabled state from parent
55
+ if (options.disabled) {
56
+ const disabledGetter = options.disabled;
57
+ $run(() => {
58
+ const disabled = disabledGetter() === true;
59
+ for (let item of itemStates) {
60
+ item.parentDisabled = disabled;
61
+ }
62
+ });
63
+ }
64
+
65
+ function isItemSelected(item: T, value: any): boolean {
66
+ if (value === undefined || value === null) return false;
67
+ if (getType() === "multiple") {
68
+ const values = Array.isArray(value) ? value : [value];
69
+ return values.some((v) => sameValue(v, item.value));
70
+ }
71
+ return sameValue(item.value, value);
72
+ }
73
+
74
+ function registerItem(item: T) {
75
+ if (item.index === -1 || item.index === undefined) {
76
+ item.index = nextIndex++;
77
+ }
78
+ if (options.onRegister) {
79
+ options.onRegister(item);
80
+ }
81
+ // Make the selection property derived: a cached getter computed from the
82
+ // group value, so it always reflects (and only reflects) that value
83
+ Object.defineProperty(item, selectedProperty, {
84
+ get() {
85
+ return $cache(() => isItemSelected(item, state.value));
86
+ },
87
+ enumerable: true,
88
+ configurable: true,
89
+ });
90
+ if (options.disabled) {
91
+ item.parentDisabled = options.disabled() === true;
92
+ }
93
+ itemStates.splice(item.index, 0, item);
94
+ }
95
+
96
+ function removeItem(itemIndex: number) {
97
+ const index = itemStates.findIndex((item) => item.index === itemIndex);
98
+ if (index !== -1) {
99
+ itemStates.splice(index, 1);
100
+ }
101
+ itemStates.forEach((item, i) => (item.index = i));
102
+ }
103
+
104
+ function toggleItem(value: any) {
105
+ switch (getType()) {
106
+ case "single": {
107
+ const item = itemStates.find((i) => sameValue(i.value, value));
108
+ if (allowDeselect) {
109
+ if (item) {
110
+ state.value = (item as any)[selectedProperty] ? undefined : item.value;
111
+ }
112
+ } else {
113
+ // Store the item's own value, so the group value keeps its
114
+ // canonical type
115
+ state.value = item ? item.value : value;
116
+ }
117
+ break;
118
+ }
119
+ case "multiple": {
120
+ const newValue: any[] = [];
121
+ for (let item of itemStates) {
122
+ const toggled = sameValue(item.value, value);
123
+ if (
124
+ (toggled && !(item as any)[selectedProperty]) ||
125
+ (!toggled && (item as any)[selectedProperty])
126
+ ) {
127
+ newValue.push(item.value);
128
+ }
129
+ }
130
+ state.value = newValue.sort((a, b) => a - b);
131
+ break;
132
+ }
133
+ }
134
+ if (options.onToggle) {
135
+ options.onToggle(value);
136
+ }
137
+ }
138
+
139
+ return { itemStates, registerItem, removeItem, toggleItem };
140
+ }
141
+
142
+ /**
143
+ * Compares an item value with a group/toggle value. Primitives are compared
144
+ * as strings — DOM values are always strings, while callers may pass e.g.
145
+ * numbers — and everything else by strict equality.
146
+ */
147
+ function sameValue(a: any, b: any): boolean {
148
+ if (a === b) return true;
149
+ if (a === null || b === null || a === undefined || b === undefined) return false;
150
+ if (typeof a === "object" || typeof b === "object") return false;
151
+ return String(a) === String(b);
152
+ }
@@ -25,7 +25,7 @@ const dayNames: string[] = [
25
25
  "Saturday",
26
26
  "Sunday",
27
27
  ];
28
- const shortDayNames: string[] = dayNames.map((n) => n.substring(0, 3).toLowerCase());
28
+ const shortDayNames: string[] = dayNames.map((n) => n.substring(0, 2));
29
29
 
30
30
  export { monthNames, shortMonthNames, dayNames, shortDayNames };
31
31
 
@@ -229,16 +229,17 @@ function formatMonth(date: Date, m: string): string {
229
229
  } else if (m.length === 3) {
230
230
  return monthNames[date.getMonth()].substring(0, 3);
231
231
  } else if (m.length == 2) {
232
- let text = date.getMonth().toString();
233
- return text.length < 2 ? "0" + text : text;
232
+ // getMonth is zero-based
233
+ return String(date.getMonth() + 1).padStart(2, "0");
234
234
  } else {
235
235
  return date.getMonth().toString();
236
236
  }
237
237
  }
238
238
 
239
239
  function formatYear(date: Date, y: string): string {
240
- return date
241
- .getFullYear()
242
- .toString()
243
- .substring(4 - y.length, y.length);
240
+ const year = date.getFullYear().toString();
241
+ if (y.length >= 4) {
242
+ return year;
243
+ }
244
+ return year.substring(4 - y.length);
244
245
  }
@@ -1,17 +1,17 @@
1
1
  export function addDocumentEvent<K extends keyof DocumentEventMap>(
2
2
  type: K,
3
- listener: (this: Document, ev: DocumentEventMap[K]) => any
3
+ listener: (this: Document, ev: DocumentEventMap[K]) => any,
4
4
  ): void {
5
- if (typeof document !== 'undefined') {
5
+ if (typeof document !== "undefined") {
6
6
  document.addEventListener(type, listener);
7
7
  }
8
8
  }
9
9
 
10
10
  export function removeDocumentEvent<K extends keyof DocumentEventMap>(
11
11
  type: K,
12
- listener: (this: Document, ev: DocumentEventMap[K]) => any
12
+ listener: (this: Document, ev: DocumentEventMap[K]) => any,
13
13
  ): void {
14
- if (typeof document !== 'undefined') {
14
+ if (typeof document !== "undefined") {
15
15
  document.removeEventListener(type, listener);
16
16
  }
17
17
  }
@@ -0,0 +1,86 @@
1
+ import { $watch } from "@torpor/view";
2
+
3
+ /**
4
+ * Shared state for HTML5 drag & drop. A source element starts a drag with
5
+ * `beginDrag`; drop targets consult `dragState.current` during dragover and
6
+ * drop. Because the dataTransfer API restricts what can be read outside
7
+ * drop, the store is the reliable channel -- the dataTransfer only carries
8
+ * the drag's id, which doubles as an integrity check and makes drags
9
+ * interoperable across windows when the type is allowed through.
10
+ *
11
+ * Drags don't nest, so a single module-level state is enough.
12
+ */
13
+
14
+ export interface DragInfo {
15
+ /** Generated identifier for this drag, mirrored into the dataTransfer */
16
+ id: string;
17
+ /** High-level kind ("list-item", "tree-item", "file"...), for filtering */
18
+ kind: string;
19
+ /** Arbitrary payload attached by the drag source */
20
+ data: any;
21
+ }
22
+
23
+ export interface DragState {
24
+ /** The active drag, if any */
25
+ current: DragInfo | undefined;
26
+ }
27
+
28
+ export const dragState: DragState = $watch({
29
+ current: undefined as DragInfo | undefined,
30
+ });
31
+
32
+ let nextId = 0;
33
+
34
+ /** Starts tracking a drag and returns its id */
35
+ export function beginDrag(kind: string, data: any): DragInfo {
36
+ const info: DragInfo = {
37
+ id: String(nextId++),
38
+ kind,
39
+ data,
40
+ };
41
+ dragState.current = info;
42
+ return info;
43
+ }
44
+
45
+ /** Clears the tracked drag */
46
+ export function endDrag(): void {
47
+ dragState.current = undefined;
48
+ }
49
+
50
+ const TYPE_PREFIX = "application/x-torpor-drag/";
51
+
52
+ /** Encodes a drag id into a dataTransfer-compatible mime type */
53
+ export function encodeDragType(id: string): string {
54
+ return TYPE_PREFIX + id;
55
+ }
56
+
57
+ /**
58
+ * Writes the drag's id into the event's dataTransfer. No-op when the event
59
+ * carries no transfer (some synthetic tests construct bare events).
60
+ */
61
+ export function writeDragType(e: DragEvent, id: string): void {
62
+ e.dataTransfer?.setData(encodeDragType(id), id);
63
+ }
64
+
65
+ /**
66
+ * Resolves which drag an incoming event refers to. Prefers the id encoded
67
+ * in the dataTransfer (verified against the live drag); falls back to the
68
+ * tracked drag itself for dragover events, whose transfers are locked.
69
+ */
70
+ export function resolveDrag(e: DragEvent): DragInfo | undefined {
71
+ const current = dragState.current;
72
+ if (!current) return undefined;
73
+
74
+ try {
75
+ const types: readonly string[] = e.dataTransfer?.types ?? [];
76
+ for (let type of types) {
77
+ if (type.startsWith(TYPE_PREFIX)) {
78
+ return type === encodeDragType(current.id) ? current : undefined;
79
+ }
80
+ }
81
+ } catch {
82
+ // Reading types can throw for foreign-origin drags; treat those as
83
+ // unmatched and fall back to the tracked drag below
84
+ }
85
+ return current;
86
+ }
@@ -0,0 +1,204 @@
1
+ import { $watch } from "@torpor/view";
2
+ import { focusItem, type FocusTarget } from "./focusItem";
3
+
4
+ /**
5
+ * A managed collection of focusable items with roving tabindex semantics,
6
+ * for group-style components where tabbing into the group lands on one item
7
+ * (the tab stop) and arrow keys move focus within it (ToolBar, MenuBar,
8
+ * button groups, Rating, SegmentedControl...).
9
+ *
10
+ * Owns the registration ledger (indexed states, ordered add/remove) that
11
+ * every list-like component duplicates today, plus the focus bookkeeping:
12
+ * `focusIndex` tracking via onItemFocus, first-item focus when the group
13
+ * itself takes focus, and keyboard movement over enabled items through
14
+ * utils/focusItem.
15
+ *
16
+ * Items are plain states carrying an index, a setFocused implementation and
17
+ * a disabled flag; each component renders its own elements and wires
18
+ * onfocus/onkeydown to the returned handlers.
19
+ */
20
+ export interface FocusItemState {
21
+ /** Assigned by the group in registration order */
22
+ index?: number;
23
+ /** Focuses this item's element; implemented by the component's item */
24
+ setFocused: () => void;
25
+ /** Disabled items are skipped by movement */
26
+ disabled?: boolean;
27
+ }
28
+
29
+ export interface FocusGroup<T extends FocusItemState> {
30
+ /** The registered item states, in order */
31
+ itemStates: T[];
32
+
33
+ /** Adds an item state, assigning its index from position; returns it */
34
+ registerItem: (item: T) => { index: number };
35
+
36
+ /** Removes an item by index, reindexing the rest */
37
+ removeItem: (itemIndex: number) => void;
38
+
39
+ /**
40
+ * Item onfocus handler: records the current tab stop
41
+ */
42
+ handleItemFocus: (index: number) => void;
43
+
44
+ /**
45
+ * Group-level key handler for a directional press at `index`. Handles
46
+ * left/right (+up/down when vertical), Home and End; arrow keys depend
47
+ * on orientation, matching ToolBar behavior.
48
+ */
49
+ handleGroupKey: (index: number, e: KeyboardEvent, options?: { vertical?: boolean }) => boolean;
50
+
51
+ /** Moves focus from `index` to the target item (disabled items skipped) */
52
+ moveFocus: (index: number, target: FocusTarget) => void;
53
+
54
+ /** Focuses the first / last enabled item (popout open behavior) */
55
+ focusFirstItem: () => void;
56
+ focusLastItem: () => void;
57
+
58
+ /**
59
+ * Whether any item is the current tab stop; components derive their
60
+ * per-item tabindex from this plus the tracked focus index
61
+ */
62
+ getFocusIndex: () => number;
63
+
64
+ /**
65
+ * Whether item `index` should be the group's single tab stop: when focus
66
+ * hasn't entered the group yet that is item 0, otherwise whichever item
67
+ * focus last rested on.
68
+ */
69
+ isTabStop: (index: number) => boolean;
70
+ }
71
+
72
+ export function createFocusGroup<T extends FocusItemState>(): FocusGroup<T> {
73
+ const itemStates: T[] = [];
74
+ // The tracked index is reactive, so components can derive each item's
75
+ // roving tabindex from it in their templates
76
+ const $state = $watch({ index: -1 });
77
+
78
+ function registerItem(item: T): { index: number } {
79
+ item.index = itemStates.length;
80
+ itemStates.push(item);
81
+ return { index: item.index };
82
+ }
83
+
84
+ function removeItem(itemIndex: number) {
85
+ const i = itemStates.findIndex((item) => item.index === itemIndex);
86
+ if (i !== -1) {
87
+ itemStates.splice(i, 1);
88
+ // Reindex the tail so indices stay dense
89
+ for (let j = i; j < itemStates.length; j++) {
90
+ itemStates[j].index = j;
91
+ }
92
+ if ($state.index > i) {
93
+ $state.index--;
94
+ } else if ($state.index === i) {
95
+ $state.index = -1;
96
+ }
97
+ }
98
+ }
99
+
100
+ function handleItemFocus(index: number) {
101
+ $state.index = index;
102
+ }
103
+
104
+ function getFocusIndex(): number {
105
+ return $state.index;
106
+ }
107
+
108
+ function isTabStop(index: number): boolean {
109
+ return ($state.index === -1 ? 0 : $state.index) === index;
110
+ }
111
+
112
+ function moveFocus(index: number, target: FocusTarget) {
113
+ if (itemStates.length === 0) return;
114
+ $state.index = focusItem(itemStates, index === -1 ? 0 : index, target);
115
+ }
116
+
117
+ function focusFirstItem() {
118
+ if (itemStates.length) {
119
+ $state.index = 0;
120
+ itemStates[0].setFocused();
121
+ }
122
+ }
123
+
124
+ function focusLastItem() {
125
+ if (itemStates.length > 0) {
126
+ $state.index = itemStates.length - 1;
127
+ itemStates.at(-1)!.setFocused();
128
+ }
129
+ }
130
+
131
+ function handleGroupKey(
132
+ index: number,
133
+ e: KeyboardEvent,
134
+ options?: { vertical?: boolean },
135
+ ): boolean {
136
+ const vertical = options?.vertical ?? false;
137
+ let handled = true;
138
+ switch (e.key) {
139
+ case "ArrowRight": {
140
+ if (!vertical) {
141
+ e.preventDefault();
142
+ moveFocus(index, "next");
143
+ } else {
144
+ handled = false;
145
+ }
146
+ break;
147
+ }
148
+ case "ArrowLeft": {
149
+ if (!vertical) {
150
+ e.preventDefault();
151
+ moveFocus(index, "previous");
152
+ } else {
153
+ handled = false;
154
+ }
155
+ break;
156
+ }
157
+ case "ArrowDown": {
158
+ if (vertical) {
159
+ e.preventDefault();
160
+ moveFocus(index, "next");
161
+ } else {
162
+ handled = false;
163
+ }
164
+ break;
165
+ }
166
+ case "ArrowUp": {
167
+ if (vertical) {
168
+ e.preventDefault();
169
+ moveFocus(index, "previous");
170
+ } else {
171
+ handled = false;
172
+ }
173
+ break;
174
+ }
175
+ case "Home": {
176
+ e.preventDefault();
177
+ moveFocus(index, "start");
178
+ break;
179
+ }
180
+ case "End": {
181
+ e.preventDefault();
182
+ moveFocus(index, "end");
183
+ break;
184
+ }
185
+ default: {
186
+ handled = false;
187
+ }
188
+ }
189
+ return handled;
190
+ }
191
+
192
+ return {
193
+ itemStates,
194
+ registerItem,
195
+ removeItem,
196
+ handleItemFocus,
197
+ handleGroupKey,
198
+ moveFocus,
199
+ focusFirstItem,
200
+ focusLastItem,
201
+ getFocusIndex,
202
+ isTabStop,
203
+ };
204
+ }
@@ -0,0 +1,44 @@
1
+ export type FocusTarget = "start" | "previous" | "next" | "end";
2
+
3
+ /**
4
+ * Standard keyboard navigation for a list of registered items: moves through
5
+ * the items in the given direction and focuses the first one that isn't
6
+ * disabled. Returns the new focus index, or the original when no focusable
7
+ * item was found.
8
+ */
9
+ export function focusItem<T extends { setFocused: () => void; disabled?: boolean }>(
10
+ items: T[],
11
+ current: number,
12
+ target: FocusTarget,
13
+ ): number {
14
+ if (items.length === 0) return current;
15
+ let index = current;
16
+ switch (target) {
17
+ case "start": {
18
+ index = 0;
19
+ break;
20
+ }
21
+ case "previous": {
22
+ index = Math.max(0, current - 1);
23
+ break;
24
+ }
25
+ case "next": {
26
+ index = Math.min(items.length - 1, current + 1);
27
+ break;
28
+ }
29
+ case "end": {
30
+ index = items.length - 1;
31
+ break;
32
+ }
33
+ }
34
+ // Skip disabled items, searching in the direction of travel
35
+ const step = target === "previous" ? -1 : 1;
36
+ while (index >= 0 && index < items.length && items[index].disabled) {
37
+ index += step;
38
+ }
39
+ if (index < 0 || index >= items.length) {
40
+ return current;
41
+ }
42
+ items[index].setFocused();
43
+ return index;
44
+ }
@@ -0,0 +1,70 @@
1
+ import type { FieldContext, FormContext } from "../Form/FormTypes";
2
+
3
+ export interface FormField {
4
+ /** The resolved form data field name: the component's name, or the enclosing Field's */
5
+ name: string | undefined;
6
+ /** Whether this component is contained within a Form Field */
7
+ inField: boolean;
8
+ /** Whether the Field's value is valid; always true outside a Field */
9
+ valid: boolean;
10
+ /** The ID of the Field's Message component, for aria-describedby */
11
+ messageId: string | undefined;
12
+ /** Called after the value has been changed by user interaction */
13
+ handleInput: () => void;
14
+ /** Called when the component loses focus */
15
+ handleBlur: () => void;
16
+ }
17
+
18
+ /**
19
+ * Wires a standalone input component into an enclosing Form and Field, in
20
+ * the same way as the components in the Form family: the Field's name is
21
+ * used when the component doesn't set one, validation runs as the user
22
+ * interacts, and the standard validity attributes can be applied.
23
+ *
24
+ * Usage inside a component:
25
+ *
26
+ * ```ts
27
+ * const form = createFormField(
28
+ * $props?.name,
29
+ * $context[FormContextName] as FormContext | undefined,
30
+ * $context[FieldContextName] as FieldContext | undefined,
31
+ * );
32
+ * ```
33
+ */
34
+ export default function createFormField(
35
+ name: string | undefined,
36
+ formContext: FormContext | undefined,
37
+ fieldContext: FieldContext | undefined,
38
+ ): FormField {
39
+ let blurred = false;
40
+
41
+ return {
42
+ get name() {
43
+ return name ?? fieldContext?.name;
44
+ },
45
+ get inField(): boolean {
46
+ return fieldContext !== undefined;
47
+ },
48
+ get valid(): boolean {
49
+ if (!fieldContext) return true;
50
+ return fieldContext.state.valid !== false;
51
+ },
52
+ get messageId(): string | undefined {
53
+ return fieldContext?.state.messageId;
54
+ },
55
+ handleInput() {
56
+ if (formContext) {
57
+ formContext.validate();
58
+ }
59
+ if (fieldContext && blurred) {
60
+ fieldContext.validate();
61
+ }
62
+ },
63
+ handleBlur() {
64
+ if (fieldContext) {
65
+ fieldContext.validate();
66
+ }
67
+ blurred = true;
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Shared hover-to-open behavior for popout triggers: opens after a delay,
3
+ * resetting the timer as the user moves within the element.
4
+ */
5
+ export function createHoverOpen(
6
+ open: () => void,
7
+ disabled: () => boolean,
8
+ delay: number,
9
+ ): {
10
+ handleMouseEnter: () => void;
11
+ handleMouseLeave: () => void;
12
+ handleMouseMove: () => void;
13
+ } {
14
+ let interval: ReturnType<typeof setTimeout> | undefined;
15
+
16
+ function schedule() {
17
+ interval = setTimeout(() => open(), delay);
18
+ }
19
+
20
+ function handleMouseEnter() {
21
+ if (disabled()) return;
22
+
23
+ // Handle the hover after a delay
24
+ if (!interval) {
25
+ schedule();
26
+ }
27
+ }
28
+
29
+ function handleMouseLeave() {
30
+ if (disabled()) return;
31
+
32
+ // Prevent short hovers or touches from firing the event
33
+ if (interval) {
34
+ clearTimeout(interval);
35
+ interval = undefined;
36
+ }
37
+ }
38
+
39
+ function handleMouseMove() {
40
+ if (disabled()) return;
41
+
42
+ // Reset the interval on mouse move
43
+ if (interval) {
44
+ clearTimeout(interval);
45
+ schedule();
46
+ }
47
+ }
48
+
49
+ return { handleMouseEnter, handleMouseLeave, handleMouseMove };
50
+ }