@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,78 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import { type SegmentedControlContext, SegmentedControlContextName } from "./SegmentedControlTypes";
3
+
4
+ interface SegmentedItemProps {
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
+ /** The index of this item */
12
+ index?: number;
13
+ /** The value for this item */
14
+ value?: string;
15
+ /** If set to true, the user cannot interact with this item */
16
+ disabled?: boolean;
17
+ }
18
+
19
+ /**
20
+ * A segment in a SegmentedControl, one of which is always active.
21
+ */
22
+ export default function SegmentedItem($props: SegmentedItemProps | undefined) {
23
+ let button: HTMLButtonElement;
24
+
25
+ // Get stuff out of the SegmentedControl context
26
+ const context = $context[SegmentedControlContextName] as SegmentedControlContext;
27
+ if (!context) {
28
+ throw new Error("SegmentedItem must be contained within a SegmentedControl");
29
+ }
30
+
31
+ // Register this item with the parent SegmentedControl
32
+ let $state = context.registerItem($props?.index, $props?.value, $props?.disabled);
33
+
34
+ $run(() => {
35
+ return () => {
36
+ context.removeItem($state.index);
37
+ };
38
+ });
39
+
40
+ // This function is called by the parent SegmentedControl to focus this item
41
+ function setFocused() {
42
+ button.focus();
43
+ }
44
+ $state.setFocused = setFocused;
45
+
46
+ function getDisabled(): boolean {
47
+ return $state.disabled || $state.parentDisabled;
48
+ }
49
+
50
+ function handleClick() {
51
+ if (getDisabled()) return;
52
+ context.triggerItem($state.value);
53
+ }
54
+
55
+ function handleKey(e: KeyboardEvent) {
56
+ if (getDisabled()) return;
57
+ context.handleItemKey($state.index, e);
58
+ }
59
+
60
+ @render {
61
+ <button
62
+ type="button"
63
+ {$props?.id}
64
+ class={["torp-segmented-item", $props?.class]}
65
+ {$props?.style}
66
+ role="radio"
67
+ tabindex={$state.active ? 0 : -1}
68
+ aria-checked={$state.active ? "true" : "false"}
69
+ aria-disabled={getDisabled() ? "true" : undefined}
70
+ data-state={$state.active ? "active" : "inactive"}
71
+ onclick={handleClick}
72
+ onkeydown={handleKey}
73
+ &ref={button}
74
+ >
75
+ <slot />
76
+ </button>
77
+ }
78
+ }
@@ -0,0 +1,74 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+
4
+ interface SegmentedControlProps {
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
+ /** The value of the active segment */
12
+ value?: Bindable<string | undefined>;
13
+ /** If set to true, the user cannot interact with any segments */
14
+ disabled?: boolean;
15
+ /** An accessible name for the control */
16
+ ariaLabel?: string;
17
+ /** The name of the component's form data field */
18
+ name?: string;
19
+ /** Raised when the active segment has changed */
20
+ onchange?: (value: string) => void;
21
+ }
22
+
23
+ /**
24
+ * A compact group of segments, of which exactly one is active. The user moves
25
+ * between segments with the arrow keys, or selects one directly.
26
+ *
27
+ * See [the WAI ARIA guidelines for Radio Groups](https://www.w3.org/WAI/ARIA/apg/patterns/radiogroup/).
28
+ *
29
+ * The structure of a SegmentedControl is:
30
+ *
31
+ * ```
32
+ * <SegmentedControl ariaLabel="View">
33
+ * <SegmentedItem value="day">Day</SegmentedItem>
34
+ * <SegmentedItem value="week">Week</SegmentedItem>
35
+ * </SegmentedControl>
36
+ * ```
37
+ */
38
+ declare function SegmentedControl(
39
+ $parent: ParentNode,
40
+ $anchor: Node | null,
41
+ $props: SegmentedControlProps | undefined,
42
+ $context?: Record<PropertyKey, any>,
43
+ $slots?: Record<string, SlotRender>,
44
+ ): void;
45
+
46
+
47
+
48
+ interface SegmentedItemProps {
49
+ /** An ID for the root element */
50
+ id?: string;
51
+ /** Classes for the root element */
52
+ class?: ClassValue;
53
+ /** Styles for the root element */
54
+ style?: StyleValue;
55
+ /** The index of this item */
56
+ index?: number;
57
+ /** The value for this item */
58
+ value?: string;
59
+ /** If set to true, the user cannot interact with this item */
60
+ disabled?: boolean;
61
+ }
62
+
63
+ /**
64
+ * A segment in a SegmentedControl, one of which is always active.
65
+ */
66
+ declare function SegmentedItem(
67
+ $parent: ParentNode,
68
+ $anchor: Node | null,
69
+ $props: SegmentedItemProps | undefined,
70
+ $context?: Record<PropertyKey, any>,
71
+ $slots?: Record<string, SlotRender>,
72
+ ): void;
73
+
74
+ export { SegmentedControl, SegmentedItem };
@@ -0,0 +1,3 @@
1
+ import SegmentedControl from "./SegmentedControl.torp";
2
+ import SegmentedItem from "./SegmentedItem.torp";
3
+ export { SegmentedControl, SegmentedItem };
@@ -1,26 +1,54 @@
1
- import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { SelectBoxContextName, type SelectBoxContext, type SelectBoxState } from "./SelectBoxTypes";
3
- import { PopoutContextName, type PopoutContext } from "../utils/PopoutTypes";
1
+ import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
2
+ import $handle from "../utils/$handle";
3
+ import {
4
+ type FieldContext,
5
+ FieldContextName,
6
+ type FormContext,
7
+ FormContextName,
8
+ } from "../Form/FormTypes";
9
+ import FormValues from "../Form/FormValues.torp";
10
+ import { type LoadResult, type Loader, createItemLoader } from "../utils/loader";
11
+ import { type PopoutContext, PopoutContextName } from "../utils/PopoutTypes";
12
+ import createFormField from "../utils/formField";
13
+ import { type SelectBoxContext, SelectBoxContextName, type SelectBoxState } from "./SelectBoxTypes";
4
14
 
5
15
  interface SelectBoxProps {
6
16
  /** An ID for the root element */
7
17
  id?: string;
8
18
  /** Classes for the root element */
9
- class?: ClassValue,
19
+ class?: ClassValue;
10
20
  /** Styles for the root element */
11
- style?: StyleValue,
12
- /** The data value selected by the user */
13
- value?: any,
14
- /** Whether the popout is visible */
15
- visible?: boolean,
16
- /** A function to be called when the user selects a response */
17
- callback?: ((value: any) => void),
18
- /** Raised when the popout is opened or closed */
19
- ontoggle?: (open: boolean) => void,
21
+ style?: StyleValue;
22
+ /** The name of the input's form data field */
23
+ name?: string;
24
+ /** The data value selected by the user, or an array of values if multiple is set */
25
+ value: Bindable<any>;
26
+ /** Whether multiple values can be selected at the same time */
27
+ multiple?: boolean;
28
+ /**
29
+ * Reads options from the network (or anywhere async) when the box is
30
+ * shown. When set and no content is slotted in, the options are rendered
31
+ * automatically.
32
+ */
33
+ load?: Loader;
34
+ /** Called after each successful load with the normalized result */
35
+ onload?: (result: LoadResult) => void;
36
+ /** Extracts the display text for a loaded item (defaults to `label ?? text ?? item`) */
37
+ getItemLabel?: (item: any) => any;
38
+ /** Extracts the value for a loaded item (defaults to `value ?? id`) */
39
+ getItemValue?: (item: any) => any;
40
+ /** Text to display when the input value is empty */
41
+ placeholder?: string;
42
+ ///** Whether the input's value is required */
43
+ //required?: boolean,
44
+ /** Whether the popout is visible; only bind this to control it from outside */
45
+ visible?: Bindable<boolean | undefined>;
46
+ /** Raised when the value has changed */
47
+ onchange?: (value: any) => void;
20
48
  /** Raised when the popout has opened */
21
- onopen?: () => void,
49
+ onopen?: () => void;
22
50
  /** Raised when the popout has closed */
23
- onclose?: (result: any) => void,
51
+ onclose?: (result: any) => void;
24
52
  }
25
53
 
26
54
  /**
@@ -32,28 +60,42 @@ interface SelectBoxProps {
32
60
  *
33
61
  * ```
34
62
  * <SelectBox>
35
- * <SelectBoxInput />
36
- * <SelectBoxTrigger /> (optional)
63
+ * <SelectBoxTrigger />
37
64
  * <SelectBoxContent />
38
65
  * </SelectBox>
39
66
  * ```
40
67
  */
41
68
  export default function SelectBox($props: SelectBoxProps | undefined) {
69
+ // Wire up the enclosing Form and Field, if any
70
+ const form = createFormField(
71
+ $props?.name,
72
+ $context[FormContextName] as FormContext | undefined,
73
+ $context[FieldContextName] as FieldContext | undefined,
74
+ );
75
+
42
76
  let $state: SelectBoxState = $watch({
43
77
  visible: false,
44
78
  value: $props?.value,
79
+ contentId: undefined,
80
+ triggerId: undefined,
81
+ contentRole: undefined,
45
82
  });
46
83
 
47
- // Sync props and state
48
- $run(() => {
49
- $state.visible = $props?.visible ?? false;
50
- });
51
- $run(() => {
52
- if ($props !== undefined) {
53
- $props.visible = $state.visible;
54
- $props.value = $state.value;
55
- }
56
- });
84
+ $bind($state, $props, "visible", "value");
85
+
86
+ if ($props?.load !== undefined && typeof $props.load !== "function") {
87
+ throw new Error("SelectBox: `load` must be a function");
88
+ }
89
+ const hasLoader = typeof $props?.load === "function";
90
+
91
+ // Load options from the network (lazily, on first read)
92
+ const $load = hasLoader
93
+ ? createItemLoader({
94
+ load: $props!.load!,
95
+ getRequest: () => ({}),
96
+ onload: (r) => $props?.onload?.(r),
97
+ })
98
+ : undefined;
57
99
 
58
100
  let result: any;
59
101
 
@@ -61,7 +103,20 @@ export default function SelectBox($props: SelectBoxProps | undefined) {
61
103
  let context: SelectBoxContext = {
62
104
  handleButton,
63
105
  handleClickOutside,
106
+ get placeholder() {
107
+ return $props?.placeholder;
108
+ },
109
+ get multiple() {
110
+ return $props?.multiple === true;
111
+ },
112
+ inField: () => form.inField,
113
+ valid: () => form.valid,
114
+ messageId: () => form.messageId,
115
+ handleBlur: () => form.handleBlur(),
64
116
  state: $state,
117
+ getLoadedItems: $load ? () => $load.items : undefined,
118
+ getItemLabel: $props?.getItemLabel,
119
+ getItemValue: $props?.getItemValue,
65
120
  };
66
121
  $context[SelectBoxContextName] = context;
67
122
 
@@ -69,22 +124,23 @@ export default function SelectBox($props: SelectBoxProps | undefined) {
69
124
  let popoutContext: PopoutContext = context;
70
125
  $context[PopoutContextName] = popoutContext;
71
126
 
72
- // Dispatch the toggle event when the popout is opened or closed
73
- let firstToggle = true;
74
- $run(() => {
75
- const ontoggle = $props?.ontoggle;
76
- const onopen = $props?.onopen;
77
- const onclose = $props?.onclose;
127
+ // Dispatch the change event when the value changes
128
+ $handle((first) => {
129
+ const value = $state.value;
130
+ if (!first) {
131
+ form.handleInput();
132
+ $props?.onchange?.(value);
133
+ }
134
+ });
135
+
136
+ // Dispatch the open/close events when the popout is opened or closed
137
+ $handle((first) => {
78
138
  const visible = $state.visible;
79
- if (firstToggle) {
80
- firstToggle = false;
139
+ if (first) return;
140
+ if (visible) {
141
+ $props?.onopen?.();
81
142
  } else {
82
- if (ontoggle) ontoggle(visible);
83
- if (visible) {
84
- if (onopen) onopen()
85
- } else {
86
- if (onclose) onclose(result);
87
- }
143
+ $props?.onclose?.(result);
88
144
  }
89
145
  });
90
146
 
@@ -110,27 +166,25 @@ export default function SelectBox($props: SelectBoxProps | undefined) {
110
166
  $state.value = result;
111
167
  }
112
168
 
113
- // Hide the popout
114
- $state.visible = false;
115
-
116
- // Call the callback
117
- if ($props?.callback) {
118
- $props?.callback(result);
169
+ // Hide the popout and focus the button
170
+ if (type === "confirm" || type === "cancel") {
171
+ $state.visible = false;
172
+ if (context.focusTrigger) {
173
+ context.focusTrigger();
174
+ }
119
175
  }
120
176
  }
121
177
 
122
- function handleClickOutside(e: MouseEvent) {
178
+ function handleClickOutside(e: MouseEvent | KeyboardEvent) {
123
179
  e.preventDefault();
124
180
  $state.visible = false;
125
181
  }
126
182
 
127
183
  @render {
128
- <div
129
- {$props?.id}
130
- class={["torp-select-box", $props?.class]}
131
- {$props?.style}
132
- >
184
+ <div {$props?.id} class={["torp-select-box", $props?.class]} {$props?.style}>
133
185
  <slot />
134
186
  </div>
187
+
188
+ <FormValues name={form.name} value={$state.value} />
135
189
  }
136
190
  }
@@ -1,14 +1,15 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
2
  import { addDocumentEvent, removeDocumentEvent } from "../utils/documentEvents";
3
- import { SelectBoxContextName, type SelectBoxContext } from "./SelectBoxTypes";
3
+ import LoadedItems from "../utils/LoadedItems.torp";
4
+ import { type SelectBoxContext, SelectBoxContextName } from "./SelectBoxTypes";
4
5
 
5
6
  interface SelectBoxContentProps {
6
7
  /** An ID for the root element */
7
8
  id?: string;
8
9
  /** Classes for the root element */
9
- class?: ClassValue,
10
+ class?: ClassValue;
10
11
  /** Styles for the root element */
11
- style?: StyleValue,
12
+ style?: StyleValue;
12
13
  }
13
14
 
14
15
  /**
@@ -28,7 +29,9 @@ export default function SelectBoxContent($props: SelectBoxContentProps | undefin
28
29
  let $state = context.state;
29
30
 
30
31
  $run(() => {
31
- if (!$state.visible) {
32
+ if ($state.visible) {
33
+ show();
34
+ } else {
32
35
  hide();
33
36
  }
34
37
  });
@@ -55,27 +58,54 @@ export default function SelectBoxContent($props: SelectBoxContentProps | undefin
55
58
  }
56
59
 
57
60
  function handleDocumentClick(e: MouseEvent) {
58
- if (
59
- closeOnClick &&
60
- div &&
61
- !div.contains(e.target as HTMLElement)
62
- ) {
61
+ const target = e.target as HTMLElement;
62
+ // See DateRangePicker: a re-rendered (detached) target came from
63
+ // inside the content
64
+ if (closeOnClick && div && target.isConnected && !div.contains(target)) {
63
65
  e.preventDefault();
64
66
  context.handleClickOutside!(e);
65
67
  }
66
68
  }
67
69
 
70
+ // HACK: We need to keep the items in the document so that we can search through them
68
71
  @render {
69
- @if ($state.visible) {
70
- <div
71
- {$props?.id}
72
- class={["torp-select-box-content", $props?.class]}
73
- {$props?.style}
74
- &ref={div}
75
- onmount={show}
76
- >
77
- <slot />
78
- </div>
72
+ @//if ($state.visible) {
73
+ <div
74
+ {$props?.id}
75
+ class={["torp-select-box-content", $props?.class, $state.visible ? undefined : "hidden"]}
76
+ {$props?.style}
77
+ aria-hidden={$state.visible ? undefined : "true"}
78
+ &ref={div}
79
+ >
80
+ <slot>
81
+ @if (context.getLoadedItems) {
82
+ @// Options loaded from a network loader, rendered automatically
83
+ @try {
84
+ @await {
85
+ <LoadedItems
86
+ items={context.getLoadedItems!()}
87
+ value={$state.value}
88
+ multiple={context.multiple}
89
+ getItemLabel={context.getItemLabel}
90
+ getItemValue={context.getItemValue}
91
+ />
92
+ } with {
93
+ <span class="torp-select-box-loading">Loading…</span>
94
+ }
95
+ } catch (err) {
96
+ <span class="torp-select-box-error" role="alert">
97
+ {err instanceof Error ? err.message : String(err)}
98
+ </span>
99
+ }
100
+ }
101
+ </slot>
102
+ </div>
103
+ @//}
104
+ }
105
+
106
+ @style {
107
+ .torp-select-box-content.hidden {
108
+ display: none;
79
109
  }
80
110
  }
81
111
  }
@@ -1,41 +1,141 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { SelectBoxContextName, type SelectBoxContext } from "./SelectBoxTypes";
2
+ import getId from "../utils/getId";
3
+ import { type SelectBoxContext, SelectBoxContextName } from "./SelectBoxTypes";
3
4
 
4
5
  interface SelectBoxTriggerProps {
5
6
  /** An ID for the root element */
6
7
  id?: string;
7
8
  /** Classes for the root element */
8
- class?: ClassValue,
9
+ class?: ClassValue;
9
10
  /** Styles for the root element */
10
- style?: StyleValue,
11
+ style?: StyleValue;
12
+ /** An ARIA label for describing the trigger to screen readers when there is no associated label element */
13
+ ariaLabel?: string;
11
14
  }
12
15
 
13
16
  /**
14
- * The button that causes a SelectBox to appear or disappear.
17
+ * The input that causes a SelectBox to appear or disappear.
15
18
  */
16
19
  export default function SelectBoxTrigger($props: SelectBoxTriggerProps | undefined) {
20
+ let defaultId = getId();
21
+ let button: HTMLButtonElement;
22
+
17
23
  // Get stuff out of the SelectBoxContext
18
24
  const context = $context[SelectBoxContextName] as SelectBoxContext;
19
25
  if (!context) {
20
26
  throw new Error("SelectBoxTrigger must be contained within a SelectBox");
21
27
  }
22
28
  let $state = context.state;
29
+ context.focusTrigger = focusTrigger;
30
+
31
+ // Set the trigger ID so that it can be used with aria-labelledby
32
+ $state.triggerId = $props?.id ?? defaultId;
33
+
34
+ function focusTrigger() {
35
+ button.focus();
36
+ }
37
+
38
+ function displayValue(): string | undefined {
39
+ const value = $state.value;
40
+ if (value === undefined || value === null || value === "") {
41
+ return undefined;
42
+ }
43
+ return Array.isArray(value) ? (value as any[]).join(", ") : String(value);
44
+ }
23
45
 
24
46
  function handleClick() {
25
47
  $state.visible = !$state.visible;
48
+ if (!$state.visible) {
49
+ context.searchText = "";
50
+ }
51
+ }
52
+
53
+ function handleKey(e: KeyboardEvent) {
54
+ switch (e.key) {
55
+ case "ArrowDown": {
56
+ e.preventDefault();
57
+ $state.visible = true;
58
+ if (context.focusFirstElement) {
59
+ context.focusFirstElement();
60
+ }
61
+ break;
62
+ }
63
+ case "ArrowUp": {
64
+ e.preventDefault();
65
+ $state.visible = true;
66
+ if (context.focusLastElement) {
67
+ context.focusLastElement();
68
+ }
69
+ break;
70
+ }
71
+ case "Escape": {
72
+ if ($state.visible) {
73
+ e.preventDefault();
74
+ // NOTE: This may be too much of a departure from standards
75
+ if (context.searchText !== undefined && context.searchText !== "") {
76
+ context.searchText = "";
77
+ } else {
78
+ $state.visible = false;
79
+ }
80
+ }
81
+ break;
82
+ }
83
+ case "Backspace": {
84
+ if (context.searchText && context.searchItems) {
85
+ e.preventDefault();
86
+ const shorten = (str: string) => str.substring(0, str.length - 1);
87
+ context.searchText = shorten(context.searchText!);
88
+ if (context.searchText.length > 0) {
89
+ const found = context.searchItems(context.searchText);
90
+ if (found) {
91
+ found.setFocused();
92
+ }
93
+ }
94
+ }
95
+ break;
96
+ }
97
+ default: {
98
+ // TODO: Handle unicode
99
+ if (e.key.length === 1 && context.searchItems) {
100
+ e.preventDefault();
101
+ context.searchText ??= "";
102
+ context.searchText += e.key;
103
+ const found = context.searchItems(context.searchText);
104
+ if (found !== undefined) {
105
+ $state.visible = true;
106
+ found.setFocused();
107
+ }
108
+ }
109
+ }
110
+ }
26
111
  }
27
112
 
28
113
  @render {
29
114
  <button
30
115
  type="button"
31
- {$props?.id}
116
+ role="combobox"
117
+ id={$props?.id ?? defaultId}
32
118
  class={["torp-select-box-trigger", $props?.class]}
33
119
  {$props?.style}
34
- tabindex="-1"
120
+ aria-controls={$state.contentId}
121
+ aria-haspopup={$state.contentRole}
122
+ aria-expanded={$state.visible ? "true" : "false"}
123
+ aria-label={$props?.ariaLabel}
124
+ data-valid={context.valid?.() ? "valid" : "invalid"}
125
+ aria-describedby={context.messageId?.()}
126
+ aria-invalid={context.inField?.() && !context.valid?.() ? "true" : undefined}
127
+ onblur={() => context.handleBlur?.()}
35
128
  onclick={handleClick}
36
- &ref={context.anchorElement}
129
+ onkeydown={handleKey}
130
+ &ref={button}
37
131
  >
38
- <slot />
132
+ <slot>
133
+ @if (displayValue()) {
134
+ <span>{displayValue()}</span>
135
+ } @else {
136
+ <span>{context.placeholder || "-"}</span>
137
+ }
138
+ </slot>
39
139
  </button>
40
140
  }
41
141
  }
@@ -1,18 +1,33 @@
1
+ import { type PopoutContext, type PopoutState } from "../utils/PopoutTypes";
2
+ import { type LoadResult, type Loader } from "../utils/loader";
3
+
1
4
  export const SelectBoxContextName: unique symbol = Symbol.for("torp.SelectBox");
2
5
 
3
- /** The shared context for modal components (Contextual, Prompt and SelectBox) */
4
- export interface SelectBoxContext {
5
- handleButton: (type: "confirm" | "cancel" | undefined, value?: any) => void;
6
- // HACK: We need to be able to let children (i.e. Dialogs) override the click outside functionality
7
- // There may be a more elegant way to accomplish this too
8
- handleClickOutside?: (e: MouseEvent) => void;
6
+ /** The shared context for SelectBox components */
7
+ export interface SelectBoxContext extends PopoutContext {
8
+ placeholder?: string;
9
+ multiple?: boolean;
9
10
  state: SelectBoxState;
10
- focusFirstElement?: () => void;
11
- focusLastElement?: () => void;
11
+ focusTrigger?: () => void;
12
+ /** Whether the component is contained within a Form Field */
13
+ inField?: () => boolean;
14
+ /** Whether the Field's value is valid, for data-valid / aria-invalid */
15
+ valid?: () => boolean;
16
+ /** The ID of the Field's Message component, for aria-describedby */
17
+ messageId?: () => string | undefined;
18
+ /** Called by the trigger when it loses focus, to run Field validation */
19
+ handleBlur?: () => void;
20
+ /** Returns the items loaded from the network loader, when one is set */
21
+ getLoadedItems?: () => any[];
22
+ /** Extracts the display text for a loaded item */
23
+ getItemLabel?: (item: any) => any;
24
+ /** Extracts the value for a loaded item */
25
+ getItemValue?: (item: any) => any;
12
26
  }
13
27
 
14
- export interface SelectBoxState {
15
- visible: boolean;
28
+ export interface SelectBoxState extends PopoutState {
16
29
  value: any;
17
- contentId?: string;
30
+ name?: string;
18
31
  }
32
+
33
+ export type { LoadResult, Loader };