@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,225 @@
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 createFormField from "../utils/formField";
11
+
12
+ interface RatingProps {
13
+ /** An ID for the root element */
14
+ id?: string;
15
+ /** Classes for the root element */
16
+ class?: ClassValue;
17
+ /** Styles for the root element */
18
+ style?: StyleValue;
19
+ /** The maximum rating, i.e. the number of stars (default 5) */
20
+ max?: number;
21
+ /** The current rating */
22
+ value?: Bindable<number>;
23
+ /** If set to true, the user cannot interact with this rating */
24
+ disabled?: boolean;
25
+ /** If set to false, clicking or re-selecting the current rating does not clear it (default true) */
26
+ allowClear?: boolean;
27
+ /** An accessible name for the rating */
28
+ ariaLabel?: string;
29
+ /** The name of the component's form data field */
30
+ name?: string;
31
+ /** Raised when the rating has changed */
32
+ onchange?: (value: number) => void;
33
+ }
34
+
35
+ /**
36
+ * A star rating input, where the user selects a value from 1 to a maximum
37
+ * number of stars by clicking or by using the keyboard.
38
+ *
39
+ * Implements the radio group pattern: each star is a radio button, the star
40
+ * matching the current rating is checked, and arrow keys move the rating.
41
+ *
42
+ * See [the WAI ARIA guidelines for Rating](https://www.w3.org/WAI/ARIA/apg/patterns/rating/).
43
+ */
44
+ export default function Rating($props: RatingProps | undefined) {
45
+ let stars: (HTMLButtonElement | undefined)[] = [];
46
+ let root: HTMLElement;
47
+
48
+ // Wire up the enclosing Form and Field, if any
49
+ const form = createFormField(
50
+ $props?.name,
51
+ $context[FormContextName] as FormContext | undefined,
52
+ $context[FieldContextName] as FieldContext | undefined,
53
+ );
54
+
55
+ function getMax(): number {
56
+ const max = $props?.max ?? 5;
57
+ return max > 0 ? Math.floor(max) : 5;
58
+ }
59
+ function getDisabled(): boolean {
60
+ return $props?.disabled === true;
61
+ }
62
+
63
+ let $state = $watch({
64
+ value: 0,
65
+ hover: 0,
66
+ get stars(): number[] {
67
+ return Array.from({ length: getMax() }, (_, i) => i + 1);
68
+ },
69
+ });
70
+
71
+ $bind($state, $props, "value");
72
+
73
+ // Keep the stored value clamped, so that out-of-range initial values
74
+ // don't later surface as user changes
75
+ $run(() => {
76
+ const clamped = clamp($state.value);
77
+ if (clamped !== $state.value) {
78
+ $state.value = clamped;
79
+ }
80
+ });
81
+
82
+ // Dispatch the change event when the value changes
83
+ $handle((first) => {
84
+ const value = $state.value;
85
+ if (!first) $props?.onchange?.(value);
86
+ });
87
+
88
+ function clamp(value: number): number {
89
+ return Math.min(getMax(), Math.max(0, value));
90
+ }
91
+
92
+ function getValue(): number {
93
+ return clamp($state.value);
94
+ }
95
+
96
+ function setValue(value: number, focus = false) {
97
+ const next = clamp(value);
98
+ if (next !== $state.value) {
99
+ $state.value = next;
100
+ }
101
+ if (focus) {
102
+ stars[next - 1]?.focus();
103
+ }
104
+ }
105
+
106
+ // The number of filled stars to display, taking the hovered star into
107
+ // account for the mouse preview
108
+ function getDisplayValue(): number {
109
+ return getDisabled() ? getValue() : Math.max(getValue(), $state.hover);
110
+ }
111
+
112
+ function handleClick(value: number) {
113
+ if (getDisabled()) return;
114
+ if (value === $state.value && ($props?.allowClear ?? true)) {
115
+ setValue(0);
116
+ } else {
117
+ setValue(value);
118
+ }
119
+ form.handleInput();
120
+ }
121
+
122
+ function handleKey(e: KeyboardEvent) {
123
+ if (getDisabled()) return;
124
+
125
+ switch (e.key) {
126
+ case "ArrowRight":
127
+ case "ArrowUp": {
128
+ e.preventDefault();
129
+ setValue(Math.max(1, $state.value + 1), true);
130
+ form.handleInput();
131
+ break;
132
+ }
133
+ case "ArrowLeft":
134
+ case "ArrowDown": {
135
+ e.preventDefault();
136
+ setValue($state.value <= 1 ? 1 : $state.value - 1, true);
137
+ form.handleInput();
138
+ break;
139
+ }
140
+ case "Home": {
141
+ e.preventDefault();
142
+ setValue(1, true);
143
+ form.handleInput();
144
+ break;
145
+ }
146
+ case "End": {
147
+ e.preventDefault();
148
+ setValue(getMax(), true);
149
+ form.handleInput();
150
+ break;
151
+ }
152
+ }
153
+ }
154
+
155
+ // Only blur when focus leaves the whole rating
156
+ function handleFocusOut(e: FocusEvent) {
157
+ if (!root.contains(e.relatedTarget as Node | null)) {
158
+ form.handleBlur();
159
+ }
160
+ }
161
+
162
+ // Roving tabindex: the star matching the current rating (or the first
163
+ // star when there is no rating) is the single tab stop
164
+ function tabStop(value: number): boolean {
165
+ return value === ($state.value > 0 ? $state.value : 1);
166
+ }
167
+
168
+ @render {
169
+ <div
170
+ {$props?.id}
171
+ class={["torp-rating", getDisabled() ? "torp-rating-disabled" : undefined, $props?.class]}
172
+ {$props?.style}
173
+ role="radiogroup"
174
+ aria-label={$props?.ariaLabel}
175
+ aria-disabled={getDisabled() ? "true" : undefined}
176
+ data-valid={form.valid ? "valid" : "invalid"}
177
+ aria-describedby={form.messageId}
178
+ aria-invalid={form.inField && !form.valid ? "true" : undefined}
179
+ onmouseleave={() => ($state.hover = 0)}
180
+ onfocusout={handleFocusOut}
181
+ &ref={root}
182
+ >
183
+ @for (let value of $state.stars) {
184
+ @key = value
185
+ <button
186
+ type="button"
187
+ class="torp-rating-star"
188
+ role="radio"
189
+ tabindex={tabStop(value) ? 0 : -1}
190
+ aria-checked={value === getValue() ? "true" : "false"}
191
+ aria-label={`${value} ${value === 1 ? "star" : "stars"}`}
192
+ aria-disabled={getDisabled() ? "true" : undefined}
193
+ data-state={value <= getDisplayValue() ? "filled" : "empty"}
194
+ onmount={(el) => {
195
+ stars[value - 1] = el;
196
+ return () => {
197
+ if (stars[value - 1] === el) {
198
+ stars[value - 1] = undefined;
199
+ }
200
+ };
201
+ }}
202
+ onclick={() => handleClick(value)}
203
+ onkeydown={handleKey}
204
+ onmouseenter={() => {
205
+ if (!getDisabled()) $state.hover = value;
206
+ }}
207
+ >
208
+ <slot item={{ value: value, filled: value <= getDisplayValue() }}>
209
+ <span aria-hidden="true">{value <= getDisplayValue() ? "★" : "☆"}</span>
210
+ </slot>
211
+ </button>
212
+ }
213
+ </div>
214
+
215
+ <FormValues name={form.name} value={getValue() > 0 ? getValue() : undefined} />
216
+ }
217
+
218
+ @style {
219
+ /* Headless: layout only, so the stars sit in a row. All appearance is
220
+ up to the consuming theme */
221
+ .torp-rating {
222
+ display: inline-flex;
223
+ }
224
+ }
225
+ }
@@ -0,0 +1,44 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+
4
+ interface RatingProps {
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 maximum rating, i.e. the number of stars (default 5) */
12
+ max?: number;
13
+ /** The current rating */
14
+ value?: Bindable<number>;
15
+ /** If set to true, the user cannot interact with this rating */
16
+ disabled?: boolean;
17
+ /** If set to false, clicking or re-selecting the current rating does not clear it (default true) */
18
+ allowClear?: boolean;
19
+ /** An accessible name for the rating */
20
+ ariaLabel?: string;
21
+ /** The name of the component's form data field */
22
+ name?: string;
23
+ /** Raised when the rating has changed */
24
+ onchange?: (value: number) => void;
25
+ }
26
+
27
+ /**
28
+ * A star rating input, where the user selects a value from 1 to a maximum
29
+ * number of stars by clicking or by using the keyboard.
30
+ *
31
+ * Implements the radio group pattern: each star is a radio button, the star
32
+ * matching the current rating is checked, and arrow keys move the rating.
33
+ *
34
+ * See [the WAI ARIA guidelines for Rating](https://www.w3.org/WAI/ARIA/apg/patterns/rating/).
35
+ */
36
+ declare function Rating(
37
+ $parent: ParentNode,
38
+ $anchor: Node | null,
39
+ $props: RatingProps | undefined,
40
+ $context?: Record<PropertyKey, any>,
41
+ $slots?: Record<string, SlotRender>,
42
+ ): void;
43
+
44
+ export { Rating };
@@ -0,0 +1,2 @@
1
+ import Rating from "./Rating.torp";
2
+ export { Rating };
@@ -0,0 +1,233 @@
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 { createFocusGroup } from "../utils/focusGroup";
11
+ import { createItemGroup } from "../utils/createItemGroup";
12
+ import createFormField from "../utils/formField";
13
+ import type { FocusTarget } from "../utils/focusItem";
14
+ import {
15
+ type ItemState,
16
+ type SegmentedControlContext,
17
+ SegmentedControlContextName,
18
+ } from "./SegmentedControlTypes";
19
+
20
+ interface SegmentedControlProps {
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
+ /** The value of the active segment */
28
+ value?: Bindable<string | undefined>;
29
+ /** If set to true, the user cannot interact with any segments */
30
+ disabled?: boolean;
31
+ /** An accessible name for the control */
32
+ ariaLabel?: string;
33
+ /** The name of the component's form data field */
34
+ name?: string;
35
+ /** Raised when the active segment has changed */
36
+ onchange?: (value: string) => void;
37
+ }
38
+
39
+ /**
40
+ * A compact group of segments, of which exactly one is active. The user moves
41
+ * between segments with the arrow keys, or selects one directly.
42
+ *
43
+ * See [the WAI ARIA guidelines for Radio Groups](https://www.w3.org/WAI/ARIA/apg/patterns/radiogroup/).
44
+ *
45
+ * The structure of a SegmentedControl is:
46
+ *
47
+ * ```
48
+ * <SegmentedControl ariaLabel="View">
49
+ * <SegmentedItem value="day">Day</SegmentedItem>
50
+ * <SegmentedItem value="week">Week</SegmentedItem>
51
+ * </SegmentedControl>
52
+ * ```
53
+ */
54
+ export default function SegmentedControl($props: SegmentedControlProps | undefined) {
55
+ let nextItemIndex = 0;
56
+ let root: HTMLElement;
57
+
58
+ // Wire up the enclosing Form and Field, if any
59
+ const form = createFormField(
60
+ $props?.name,
61
+ $context[FormContextName] as FormContext | undefined,
62
+ $context[FieldContextName] as FieldContext | undefined,
63
+ );
64
+
65
+ let $state = $watch({
66
+ value: $props?.value ?? "",
67
+ });
68
+
69
+ $bind($state, $props, "value");
70
+
71
+ const group = createItemGroup<ItemState>({
72
+ state: $state,
73
+ type: "single",
74
+ selectedProperty: "active",
75
+ allowDeselect: false,
76
+ disabled: () => $props?.disabled,
77
+ // Select the first enabled item as items register, when nothing is
78
+ // selected yet; this is a default, not a user change, so it doesn't
79
+ // raise onchange
80
+ onRegister: (item) => {
81
+ if ($state.value === "" && !item.disabled) {
82
+ skipNextChange = true;
83
+ $state.value = item.value;
84
+ }
85
+ },
86
+ });
87
+
88
+ // The focus group owns the roving-tabindex bookkeeping; the selection
89
+ // group above stays the source of truth for the active segment
90
+ const focusGroup = createFocusGroup<ItemState>();
91
+
92
+ // Set the context to pass down to items
93
+ const context: SegmentedControlContext = {
94
+ registerItem,
95
+ removeItem: (itemIndex) => {
96
+ group.removeItem(itemIndex);
97
+ focusGroup.removeItem(itemIndex);
98
+ },
99
+ triggerItem: (value) => {
100
+ group.toggleItem(value);
101
+ form.handleInput();
102
+ },
103
+ handleItemKey,
104
+ };
105
+ $context[SegmentedControlContextName] = context;
106
+
107
+ // Dispatch the change event when the value changes
108
+ let skipNextChange = false;
109
+ $handle((first) => {
110
+ const value = $state.value;
111
+ if (skipNextChange) {
112
+ skipNextChange = false;
113
+ return;
114
+ }
115
+ if (!first && value !== "") $props?.onchange?.(value);
116
+ });
117
+
118
+ function registerItem(
119
+ index: number | undefined,
120
+ value: string | undefined,
121
+ disabled: boolean | undefined,
122
+ ): ItemState {
123
+ // If an index wasn't set for the item, set it automatically
124
+ if (index === undefined) {
125
+ index = nextItemIndex++;
126
+ }
127
+
128
+ // If we already have an item with this index, just return it
129
+ const existing = group.itemStates.find((item) => item.index === index);
130
+ if (existing) {
131
+ return existing;
132
+ }
133
+
134
+ // If a value wasn't set for the item, set it to the index
135
+ if (value === undefined) {
136
+ value = index.toString();
137
+ }
138
+
139
+ const itemState: ItemState = $watch({
140
+ index,
141
+ value,
142
+ active: false,
143
+ disabled: disabled ?? false,
144
+ parentDisabled: $props?.disabled ?? false,
145
+ setFocused: () => {},
146
+ });
147
+ group.registerItem(itemState);
148
+ focusGroup.registerItem(itemState);
149
+
150
+ return itemState;
151
+ }
152
+
153
+ function handleItemKey(index: number, e: KeyboardEvent) {
154
+ switch (e.key) {
155
+ case "ArrowRight":
156
+ case "ArrowDown": {
157
+ e.preventDefault();
158
+ move(index, "next");
159
+ break;
160
+ }
161
+ case "ArrowLeft":
162
+ case "ArrowUp": {
163
+ e.preventDefault();
164
+ move(index, "previous");
165
+ break;
166
+ }
167
+ case "Home": {
168
+ e.preventDefault();
169
+ move(index, "start");
170
+ break;
171
+ }
172
+ case "End": {
173
+ e.preventDefault();
174
+ move(index, "end");
175
+ break;
176
+ }
177
+ }
178
+ }
179
+
180
+ // Moving with the keyboard both selects and focuses the target segment,
181
+ // like a radio group
182
+ function move(index: number, target: FocusTarget) {
183
+ const before = focusGroup.getFocusIndex();
184
+ focusGroup.moveFocus(index, target);
185
+ const next = focusGroup.getFocusIndex();
186
+ if (next !== before) {
187
+ const item = focusGroup.itemStates[next];
188
+ if (item) {
189
+ group.toggleItem(item.value);
190
+ form.handleInput();
191
+ }
192
+ }
193
+ }
194
+
195
+ // Only blur when focus leaves the whole control
196
+ function handleFocusOut(e: FocusEvent) {
197
+ if (!root.contains(e.relatedTarget as Node | null)) {
198
+ form.handleBlur();
199
+ }
200
+ }
201
+
202
+ @render {
203
+ <div
204
+ {$props?.id}
205
+ class={[
206
+ "torp-segmented-control",
207
+ $props?.disabled ? "torp-segmented-control-disabled" : undefined,
208
+ $props?.class,
209
+ ]}
210
+ {$props?.style}
211
+ role="radiogroup"
212
+ aria-label={$props?.ariaLabel}
213
+ aria-disabled={$props?.disabled ? "true" : undefined}
214
+ data-valid={form.valid ? "valid" : "invalid"}
215
+ aria-describedby={form.messageId}
216
+ aria-invalid={form.inField && !form.valid ? "true" : undefined}
217
+ onfocusout={handleFocusOut}
218
+ &ref={root}
219
+ >
220
+ <slot />
221
+ </div>
222
+
223
+ <FormValues name={form.name} value={$state.value} />
224
+ }
225
+
226
+ @style {
227
+ /* Headless: layout only, so the segments sit in a row. All appearance
228
+ is up to the consuming theme */
229
+ .torp-segmented-control {
230
+ display: inline-flex;
231
+ }
232
+ }
233
+ }
@@ -0,0 +1,26 @@
1
+ export const SegmentedControlContextName: unique symbol = Symbol.for("torp.SegmentedControl");
2
+
3
+ export interface SegmentedControlContext {
4
+ /** Called from an item when it is added */
5
+ registerItem: (
6
+ index: number | undefined,
7
+ value: string | undefined,
8
+ disabled: boolean | undefined,
9
+ ) => ItemState;
10
+ /** Called from an item when it is removed */
11
+ removeItem: (index: number) => void;
12
+ /** Called from an item when it is selected */
13
+ triggerItem: (value: string) => void;
14
+ /** Called from an item when it receives a keyboard event */
15
+ handleItemKey: (index: number, e: KeyboardEvent) => void;
16
+ }
17
+
18
+ export interface ItemState {
19
+ index: number;
20
+ value: string;
21
+ active: boolean;
22
+ disabled: boolean;
23
+ parentDisabled: boolean;
24
+ /** Called in the SegmentedControl when an item needs to be focused */
25
+ setFocused: () => void;
26
+ }
@@ -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 };