@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,374 @@
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 { createItemGroup } from "../utils/createItemGroup";
12
+ import getId from "../utils/getId";
13
+ import createFormField from "../utils/formField";
14
+ import { type HandleButtonPress, type PopoutContext, PopoutContextName } from "../utils/PopoutTypes";
15
+ import LoadedListBoxItems from "./LoadedListBoxItems.torp";
16
+ import { type ItemState, type ListBoxContext, ListBoxContextName } from "./ListBoxTypes";
17
+
18
+ // TODO: orientation
19
+
20
+ interface ListBoxProps {
21
+ /** An ID for the root element */
22
+ id?: string;
23
+ /** Classes for the root element */
24
+ class?: ClassValue;
25
+ /** Styles for the root element */
26
+ style?: StyleValue;
27
+ /** Whether a single ListBoxItem or multiple ListBoxItems can be selected at the same time */
28
+ type?: "single" | "multiple";
29
+ /** The value of the selected ListBoxItem, or an array of values if the type is "multiple" */
30
+ value: Bindable<any | any[]>;
31
+ /** If set to true, the user cannot interact with any items in the ListBox */
32
+ disabled?: boolean;
33
+ /** An ARIA label for describing the ListBox to screen readers */
34
+ ariaLabel?: string | undefined;
35
+ /** An ID of an element that labels the ListBox */
36
+ ariaLabelledBy?: string | undefined;
37
+ /** The orientation of the ListBox */
38
+ orientation?: "vertical" | "horizontal";
39
+ /** Raised when the selected item(s) have changed */
40
+ /** The name of the component's form data field */
41
+ name?: string;
42
+ /**
43
+ * Reads options from the network (or anywhere async), receiving
44
+ * `{ searchText }` whenever the bound `searchText` changes. When set and
45
+ * no ListBoxItems are slotted in, the options are rendered automatically
46
+ */
47
+ load?: Loader;
48
+ /** Called after each successful load with the normalized result */
49
+ onload?: (result: LoadResult) => void;
50
+ /** Extracts the display text for a loaded item (defaults to `label ?? text ?? item`) */
51
+ getItemLabel?: (item: any) => any;
52
+ /** Extracts the value for a loaded item (defaults to `value ?? id`) */
53
+ getItemValue?: (item: any) => any;
54
+ /** Filter text for a `load` function; two-way bindable */
55
+ searchText?: Bindable<string | undefined>;
56
+ onchange?: (value: any | any[]) => void;
57
+ }
58
+
59
+ /**
60
+ * A list of options that a user can select.
61
+ *
62
+ * See [the WAI ARIA guidelines for ListBoxes](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/).
63
+ *
64
+ * The structure of an ListBox is:
65
+ *
66
+ * ```
67
+ * <ListBox>
68
+ * <ListBoxItem />
69
+ * <ListBoxSeparator /> (optional)
70
+ * </ListBox>
71
+ * ```
72
+ */
73
+ export default function ListBox($props: ListBoxProps | undefined) {
74
+ let defaultId = getId();
75
+ let root: HTMLElement;
76
+
77
+ // Wire up the enclosing Form and Field, if any
78
+ const form = createFormField(
79
+ $props?.name,
80
+ $context[FormContextName] as FormContext | undefined,
81
+ $context[FieldContextName] as FieldContext | undefined,
82
+ );
83
+
84
+ // Get the current selection type, re-reading $props so changes are picked up
85
+ function getType(): "single" | "multiple" {
86
+ return $props?.type ?? "single";
87
+ }
88
+
89
+ // This may come from a PopoutContext, or we may need to handle it ourselves
90
+ let searchText: string | undefined;
91
+
92
+ let $state = $watch({
93
+ value: $props?.value ?? (getType() === "single" ? undefined : []),
94
+ searchText: undefined as string | undefined,
95
+ });
96
+
97
+ $bind($state, $props, "value", "searchText");
98
+
99
+ if ($props?.load !== undefined && typeof $props.load !== "function") {
100
+ throw new Error("ListBox: `load` must be a function");
101
+ }
102
+ const hasLoader = typeof $props?.load === "function";
103
+
104
+ // Load options whenever the search text changes; pass the loaded items
105
+ // and getters through the context for the slot fallback to render
106
+ const $load = hasLoader
107
+ ? createItemLoader({
108
+ load: $props!.load!,
109
+ getRequest: () => ({ searchText: $state.searchText }),
110
+ onload: (r) => $props?.onload?.(r),
111
+ })
112
+ : undefined;
113
+
114
+ // Maybe get stuff out of a popout context
115
+ let handleButton: HandleButtonPress | undefined = undefined;
116
+ const popoutContext = $context[PopoutContextName] as PopoutContext;
117
+ if (popoutContext) {
118
+ handleButton = popoutContext.handleButton;
119
+
120
+ //popoutContext.handleClickOutside = handleClickOutside;
121
+ // Set the focusFirstElement method in the PopoutContext so that it can be
122
+ // called after everything has been shown
123
+ popoutContext.focusFirstElement = focusFirstElement;
124
+ popoutContext.focusLastElement = focusLastElement;
125
+ popoutContext.markElement = markElement;
126
+ popoutContext.selectMarkedElement = selectMarkedElement;
127
+ popoutContext.searchItems = searchItems;
128
+
129
+ // Set the content ID and role so that triggers can use them with
130
+ // aria-controls / aria-haspopup
131
+ popoutContext.state.contentId = $props?.id ?? defaultId;
132
+ popoutContext.state.contentRole = "listbox";
133
+ }
134
+
135
+ const group = createItemGroup<ItemState>({
136
+ state: $state,
137
+ type: getType,
138
+ selectedProperty: "selected",
139
+ disabled: () => $props?.disabled,
140
+ onToggle: () => {
141
+ // Clear the keyboard focus marker
142
+ for (let item of group.itemStates) {
143
+ item.active = false;
144
+ }
145
+ form.handleInput();
146
+ // Pass this along to the containing popout if applicable
147
+ if (handleButton) {
148
+ if (getType() === "single") {
149
+ handleButton("confirm", $state.value);
150
+ } else {
151
+ handleButton(undefined, $state.value);
152
+ }
153
+ }
154
+ },
155
+ });
156
+
157
+ // Set the context to pass down to items, with functions for each item to call
158
+ let context: ListBoxContext = {
159
+ get type() {
160
+ return getType();
161
+ },
162
+ state: $state,
163
+ registerItem: group.registerItem,
164
+ removeItem: group.removeItem,
165
+ toggleItem: group.toggleItem,
166
+ handleKey,
167
+ setActiveDescendant: popoutContext ? (id) => { popoutContext.state.activeDescendant = id; } : undefined,
168
+ getLoadedItems: $load ? () => $load.items : undefined,
169
+ getItemLabel: $props?.getItemLabel,
170
+ getItemValue: $props?.getItemValue,
171
+ };
172
+ $context[ListBoxContextName] = context;
173
+
174
+ // Dispatch the change event when the value changes
175
+ $handle((first) => {
176
+ const value = $state.value;
177
+ if (!first) $props?.onchange?.(value);
178
+ });
179
+
180
+ function focusFirstElement() {
181
+ if (group.itemStates.length > 0) {
182
+ group.itemStates[0].setFocused();
183
+ }
184
+ }
185
+
186
+ function focusLastElement() {
187
+ if (group.itemStates.length > 0) {
188
+ group.itemStates.at(-1)!.setFocused();
189
+ }
190
+ }
191
+
192
+ function markElement(id: string) {
193
+ // selected stays derived from the group value; active marks the
194
+ // keyboard-focus preview
195
+ group.itemStates.forEach((i) => {
196
+ i.active = i.id === id;
197
+ });
198
+ }
199
+
200
+ function selectMarkedElement() {
201
+ const marked = group.itemStates.find((i) => i.active === true);
202
+ if (marked && handleButton) {
203
+ handleButton("confirm", marked.value);
204
+ }
205
+ }
206
+
207
+ function searchItems(searchText: string) {
208
+ const normalizedSearchText = searchText.toLocaleLowerCase();
209
+ for (let item of group.itemStates) {
210
+ if (item.text.toLocaleLowerCase().includes(normalizedSearchText)) {
211
+ return item;
212
+ }
213
+ }
214
+ }
215
+
216
+ function focusSelectedElement() {
217
+ // Called when the list box receives focus: focus the selected option,
218
+ // or the first option if none is selected
219
+ const itemStates = group.itemStates;
220
+ const target = itemStates.find((i) => i.selected) ?? itemStates[0];
221
+ target?.setFocused();
222
+ }
223
+
224
+ function handleKey(index: number, e: KeyboardEvent) {
225
+ const itemStates = group.itemStates;
226
+ // Finds the nearest focusable item, skipping disabled ones
227
+ const findFocusable = (from: number, step: number): number => {
228
+ let i = from;
229
+ while (i >= 0 && i < itemStates.length && itemStates[i].disabled) {
230
+ i += step;
231
+ }
232
+ return i;
233
+ };
234
+ const clearSearch = () => {
235
+ if (popoutContext) {
236
+ popoutContext.searchText = undefined;
237
+ } else {
238
+ searchText = undefined;
239
+ }
240
+ };
241
+ switch (e.key) {
242
+ case "ArrowUp": {
243
+ const target = findFocusable(index - 1, -1);
244
+ if (target >= 0) {
245
+ e.preventDefault();
246
+ itemStates[target].setFocused();
247
+ clearSearch();
248
+ }
249
+ break;
250
+ }
251
+ case "ArrowDown": {
252
+ const target = findFocusable(index + 1, 1);
253
+ if (target < itemStates.length) {
254
+ e.preventDefault();
255
+ itemStates[target].setFocused();
256
+ clearSearch();
257
+ }
258
+ break;
259
+ }
260
+ case "Home": {
261
+ if (index > 0) {
262
+ e.preventDefault();
263
+ itemStates[findFocusable(0, 1)].setFocused();
264
+ clearSearch();
265
+ }
266
+ break;
267
+ }
268
+ case "End": {
269
+ if (index < itemStates.length - 1) {
270
+ e.preventDefault();
271
+ itemStates[findFocusable(itemStates.length - 1, -1)].setFocused();
272
+ clearSearch();
273
+ }
274
+ break;
275
+ }
276
+ case "Escape": {
277
+ if (handleButton) {
278
+ handleButton("cancel");
279
+ if (popoutContext) {
280
+ popoutContext.searchText = undefined;
281
+ } else {
282
+ searchText = undefined;
283
+ }
284
+ }
285
+ break;
286
+ }
287
+ case "Backspace": {
288
+ if (popoutContext?.searchText || searchText) {
289
+ e.preventDefault();
290
+ const shorten = (str: string) => str.substring(0, str.length - 1);
291
+ if (popoutContext) {
292
+ popoutContext.searchText = shorten(popoutContext.searchText!);
293
+ searchText = popoutContext.searchText;
294
+ } else {
295
+ searchText = shorten(searchText!);
296
+ }
297
+ if (searchText.length > 0) {
298
+ const found = searchItems(searchText);
299
+ if (found) {
300
+ found.setFocused();
301
+ }
302
+ }
303
+ }
304
+ break;
305
+ }
306
+ default: {
307
+ // TODO: Handle unicode
308
+ if (e.key.length === 1) {
309
+ e.preventDefault();
310
+ const lengthen = (str: string) => (str ??= "") + e.key;
311
+ if (popoutContext) {
312
+ popoutContext.searchText = lengthen(popoutContext.searchText!);
313
+ searchText = popoutContext.searchText;
314
+ } else {
315
+ searchText = lengthen(searchText!);
316
+ }
317
+ const found = searchItems(searchText);
318
+ if (found) {
319
+ found.setFocused();
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ @render {
327
+ <div
328
+ id={$props?.id ?? defaultId}
329
+ class={["torp-list-box", $props?.class]}
330
+ {$props?.style}
331
+ role="listbox"
332
+ tabindex="0"
333
+ onfocus={focusSelectedElement}
334
+ aria-label={$props?.ariaLabel}
335
+ aria-labelledby={$props?.ariaLabelledBy}
336
+ aria-multiselectable={getType() === "multiple" ? "true" : undefined}
337
+ aria-disabled={$props?.disabled ? "true" : undefined}
338
+ aria-orientation={$props?.orientation}
339
+ data-valid={form.valid ? "valid" : "invalid"}
340
+ aria-describedby={form.messageId}
341
+ aria-invalid={form.inField && !form.valid ? "true" : undefined}
342
+ onfocusout={(e: FocusEvent) => {
343
+ // Only validate when focus leaves the whole list box
344
+ if (!root.contains(e.relatedTarget as Node | null)) {
345
+ form.handleBlur();
346
+ }
347
+ }}
348
+ &ref={root}
349
+ >
350
+ <slot>
351
+ @if (context.getLoadedItems) {
352
+ @// Options loaded from a network loader, rendered automatically
353
+ @try {
354
+ @await {
355
+ <LoadedListBoxItems
356
+ items={context.getLoadedItems!()}
357
+ getItemLabel={context.getItemLabel}
358
+ getItemValue={context.getItemValue}
359
+ />
360
+ } with {
361
+ <div class="torp-list-box-loading" role="status">Loading…</div>
362
+ }
363
+ } catch (err) {
364
+ <div class="torp-list-box-error" role="alert">
365
+ {err instanceof Error ? err.message : String(err)}
366
+ </div>
367
+ }
368
+ }
369
+ </slot>
370
+ </div>
371
+
372
+ <FormValues name={form.name} value={$state.value} />
373
+ }
374
+ }
@@ -0,0 +1,131 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import $handle from "../utils/$handle";
3
+ import getId from "../utils/getId";
4
+ import { type ItemState, type ListBoxContext, ListBoxContextName } from "./ListBoxTypes";
5
+
6
+ interface ListBoxItemProps {
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
+ /** The index of this item in the ListBox. Set this if you will be inserting, removing or sorting items. If you don't set this, it will be set automatically */
14
+ index?: number;
15
+ /** The text for the item. If not set, this will come from it's text content */
16
+ text?: string;
17
+ /** A value for the item. Set this if you need more control over which item(s) are selected. If you don't set this, the value will be set to the item's initial index */
18
+ value?: string;
19
+ /** If set to true, the user cannot interact with this ListBoxItem */
20
+ disabled?: boolean;
21
+ /** Raised when this item's selected state has changed */
22
+ onchange?: (selected: boolean) => void;
23
+ }
24
+
25
+ /**
26
+ * An item in an ListBox.
27
+ */
28
+ export default function ListBoxItem($props: ListBoxItemProps | undefined) {
29
+ let defaultId = getId();
30
+
31
+ let $state: ItemState = $watch({
32
+ id: $props?.id ?? defaultId,
33
+ index: -1,
34
+ text: $props?.text ?? "",
35
+ value: $props?.value,
36
+ selected: false,
37
+ active: false,
38
+ disabled: $props?.disabled ?? false,
39
+ parentDisabled: false,
40
+ setFocused,
41
+ });
42
+
43
+ let button: HTMLButtonElement;
44
+
45
+ // Get stuff out of the ListBoxContext
46
+ const context = $context[ListBoxContextName] as ListBoxContext;
47
+ if (!context) {
48
+ throw new Error("ListBoxItem must be contained within a ListBox");
49
+ }
50
+
51
+ // Register this item with the parent ListBox, which will handle toggling selected for all items
52
+ context.registerItem($state);
53
+
54
+ // Remove the item from the parent ListBox when this item is destroyed
55
+ $run(() => {
56
+ return () => {
57
+ context.removeItem($state.index);
58
+ };
59
+ });
60
+
61
+ // Dispatch the change event when the value of selected changes
62
+ $handle((first) => {
63
+ const selected = $state.selected;
64
+ if (!first) $props?.onchange?.(selected);
65
+ });
66
+
67
+ // This function is called by the parent ListBox to focus this item's button
68
+ function setFocused() {
69
+ button.focus();
70
+ }
71
+
72
+ function handleClick() {
73
+ // Pass the value to the toggleItem method in the parent ListBox which will handle toggling
74
+ // the value of selected for all items (e.g. to unselect other items if necessary)
75
+ context.toggleItem($state.value);
76
+ }
77
+
78
+ function handleFocus() {
79
+ if (context.setActiveDescendant) {
80
+ context.setActiveDescendant($state.id);
81
+ }
82
+ }
83
+
84
+ function handleKey(e: KeyboardEvent) {
85
+ switch (e.key) {
86
+ case "Enter": {
87
+ e.preventDefault();
88
+ context.toggleItem($state.value);
89
+ break;
90
+ }
91
+ default: {
92
+ // Pass the key press up to the parent ListBox which will handle
93
+ // setting the focused button
94
+ context.handleKey($state.index, e);
95
+ break;
96
+ }
97
+ }
98
+ }
99
+
100
+ function handleMount(el: HTMLButtonElement) {
101
+ // If the item has no value, set it to the text content; selected is
102
+ // derived from the group value, so it picks this up automatically
103
+ if (!$state.text) {
104
+ $state.text = el.textContent.trim();
105
+ }
106
+ if ($state.value === undefined || $state.value === null) {
107
+ $state.value = $state.text;
108
+ }
109
+ }
110
+
111
+ @render {
112
+ <button
113
+ type="button"
114
+ {$state.id}
115
+ class={["torp-list-box-item", $props?.class]}
116
+ {$props?.style}
117
+ role="option"
118
+ tabindex="-1"
119
+ aria-selected={$state.selected ? "true" : "false"}
120
+ aria-disabled={$state.disabled || $state.parentDisabled ? "true" : undefined}
121
+ data-state={$state.selected || $state.active ? "active" : undefined}
122
+ onclick={handleClick}
123
+ onkeydown={handleKey}
124
+ onfocus={handleFocus}
125
+ onmount={handleMount}
126
+ &ref={button}
127
+ >
128
+ <slot />
129
+ </button>
130
+ }
131
+ }
@@ -0,0 +1,29 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+
3
+ interface ListBoxSeparatorProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Classes for the root element */
7
+ class?: ClassValue;
8
+ /** Styles for the root element */
9
+ style?: StyleValue;
10
+ /** The orientation of the separator */
11
+ orientation?: "vertical" | "horizontal";
12
+ }
13
+
14
+ /**
15
+ * An element in a list box that separates logical sets of items.
16
+ */
17
+ export default function ListBoxSeparator($props: ListBoxSeparatorProps | undefined) {
18
+ @render {
19
+ <div
20
+ {$props?.id}
21
+ class={["torp-list-box-separator", $props?.class]}
22
+ {$props?.style}
23
+ role="separator"
24
+ aria-orientation={$props?.orientation ?? "horizontal"}
25
+ >
26
+ <slot />
27
+ </div>
28
+ }
29
+ }
@@ -0,0 +1,41 @@
1
+ export const ListBoxContextName: unique symbol = Symbol.for("torp.ListBox");
2
+ export const ListBoxItemContextName: unique symbol = Symbol.for("torp.ListBoxItem");
3
+
4
+ export interface ListBoxContext {
5
+ type: "single" | "multiple";
6
+ state: ListBoxState;
7
+ /** Called from a ListBoxItem when it is added */
8
+ registerItem: (state: ItemState) => void;
9
+ /** Called from a ListBoxItem when it is removed */
10
+ removeItem: (index: number) => void;
11
+ /** Called from a ListBoxItem when it is toggled */
12
+ toggleItem: (value: string) => void;
13
+ /** Called from a ListBoxItem when it receives a keyboard event */
14
+ handleKey: (index: number, e: KeyboardEvent) => void;
15
+ /** Called from a ListBoxItem when it receives focus */
16
+ setActiveDescendant?: (id: string) => void;
17
+ /** Reads the items loaded from the network loader, when one is set */
18
+ getLoadedItems?: () => any[];
19
+ /** Extracts the display text for a loaded item */
20
+ getItemLabel?: (item: any) => any;
21
+ /** Extracts the value for a loaded item */
22
+ getItemValue?: (item: any) => any;
23
+ }
24
+
25
+ export interface ListBoxState {
26
+ value: any;
27
+ }
28
+
29
+ export interface ItemState {
30
+ id: string;
31
+ index: number;
32
+ text: string;
33
+ value: any;
34
+ selected: boolean;
35
+ /** Used for styling when item has keyboard focus */
36
+ active: boolean;
37
+ disabled: boolean;
38
+ parentDisabled: boolean;
39
+ /** Called in the ListBox when an item needs to be focused */
40
+ setFocused: () => void;
41
+ }
@@ -0,0 +1,41 @@
1
+ import ListBoxItem from "./ListBoxItem.torp";
2
+
3
+ interface LoadedListBoxItemsProps {
4
+ /** The loaded items */
5
+ items: any[];
6
+ /** Extracts the display text for an item (defaults to `label ?? text ?? item`) */
7
+ getItemLabel?: (item: any) => any;
8
+ /** Extracts the value for an item (defaults to `value ?? id`) */
9
+ getItemValue?: (item: any) => any;
10
+ }
11
+
12
+ function defaultLabel(item: any): string {
13
+ const label = item?.label ?? item?.text ?? item;
14
+ return typeof label === "string" ? label : String(label);
15
+ }
16
+
17
+ function defaultValue(item: any): any {
18
+ return item?.value ?? item?.id;
19
+ }
20
+
21
+ /**
22
+ * The internally rendered options for a ListBox that loads them from a
23
+ * network loader. Used as the slot fallback when no ListBoxItems are slotted
24
+ * in; not part of the public API.
25
+ */
26
+ export default function LoadedListBoxItems($props?: LoadedListBoxItemsProps) {
27
+ const getLabel = $props?.getItemLabel ?? defaultLabel;
28
+ const getValue = $props?.getItemValue ?? defaultValue;
29
+
30
+ @render {
31
+ @for (let item of $props!.items) {
32
+ @key = getValue(item)
33
+ <ListBoxItem value={getValue(item)}>
34
+ {getLabel(item)}
35
+ </ListBoxItem>
36
+ }
37
+ @if ($props!.items.length === 0) {
38
+ <span class="torp-list-box-empty">No results.</span>
39
+ }
40
+ }
41
+ }