@torpor/ui 0.4.0 → 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 (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 +83 -0
  43. package/dist/Charts/ChartYAxis.torp +105 -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 +11 -11
  51. package/dist/Charts/YAxis.torp +8 -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 +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 +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 +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 +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 +67 -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 +45 -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 +248 -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 +72 -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,187 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import getId from "../utils/getId";
3
+ import {
4
+ type FieldContext,
5
+ FieldContextName,
6
+ type FormContext,
7
+ FormContextName,
8
+ } from "./FormTypes";
9
+
10
+ interface FileDropProps {
11
+ /** An ID for the root element */
12
+ id?: string;
13
+ /** Classes for the root element */
14
+ class?: ClassValue;
15
+ /** Styles for the root element */
16
+ style?: StyleValue;
17
+ /** The name of the input's form data field */
18
+ name?: string;
19
+ /** The type of files that this input accepts e.g. `image/*` */
20
+ accept?: string;
21
+ /** Whether the user can drop or select multiple files at once */
22
+ multiple?: boolean;
23
+ /** If set to true, the user cannot interact with this dropzone */
24
+ disabled?: boolean;
25
+ /** An accessible name for the dropzone */
26
+ ariaLabel?: string;
27
+ /** Raised when files have been dropped or selected */
28
+ onfiles?: (files: File[]) => void;
29
+ }
30
+
31
+ /**
32
+ * A file input in a form, where the user can drop one or more files onto it,
33
+ * or activate it to select files with a file dialog.
34
+ *
35
+ * See [the MDN docs for the Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API).
36
+ */
37
+ export default function FileDrop($props: FileDropProps | undefined) {
38
+ let defaultId = getId();
39
+ let inputId = `${$props?.id ?? defaultId}-input`;
40
+ let input: HTMLInputElement;
41
+
42
+ let name = $props?.name;
43
+
44
+ let blurred = false;
45
+
46
+ function getDisabled(): boolean {
47
+ return $props?.disabled === true;
48
+ }
49
+
50
+ // Get stuff out of the FormContext and FieldContext
51
+ const formContext = $context[FormContextName] as FormContext;
52
+ const fieldContext = $context[FieldContextName] as FieldContext;
53
+ if (fieldContext) {
54
+ name ??= fieldContext.name;
55
+ }
56
+
57
+ // This will change the form's enctype to `multipart/form-data`
58
+ if (formContext) {
59
+ formContext.state.hasFile = true;
60
+ }
61
+
62
+ // Use the FieldContext's state, or create a new one
63
+ let $state =
64
+ fieldContext?.state ??
65
+ $watch({
66
+ inputId,
67
+ value: null,
68
+ errors: {} as Record<string, string>,
69
+ valid: false,
70
+ message: "",
71
+ });
72
+
73
+ // Set the input ID now that we know it and it will get linked with the associated label
74
+ $state.inputId = inputId;
75
+
76
+ function handleFiles(files: FileList | File[] | undefined | null) {
77
+ if (getDisabled() || !files) return;
78
+ const all = Array.from(files);
79
+ if (all.length === 0) return;
80
+ const selected = $props?.multiple ? all : all.slice(0, 1);
81
+
82
+ if (formContext) {
83
+ formContext.validate();
84
+ }
85
+ if (fieldContext && blurred) {
86
+ fieldContext.validate();
87
+ }
88
+
89
+ $props?.onfiles?.(selected);
90
+ }
91
+
92
+ function openDialog() {
93
+ if (getDisabled()) return;
94
+ input?.click();
95
+ }
96
+
97
+ function handleClick(e: MouseEvent) {
98
+ e.preventDefault();
99
+ openDialog();
100
+ }
101
+
102
+ function handleKey(e: KeyboardEvent) {
103
+ if (e.key === "Enter" || e.key === " ") {
104
+ e.preventDefault();
105
+ openDialog();
106
+ }
107
+ }
108
+
109
+ function handleDrop(e: DragEvent) {
110
+ e.preventDefault();
111
+ if (getDisabled()) return;
112
+ (e.currentTarget as HTMLElement)?.removeAttribute("data-state");
113
+ handleFiles(e.dataTransfer?.files);
114
+ }
115
+
116
+ function handleDragOver(e: DragEvent) {
117
+ e.preventDefault();
118
+ if (getDisabled()) return;
119
+ (e.currentTarget as HTMLElement)?.setAttribute("data-state", "over");
120
+ }
121
+
122
+ function handleDragLeave(e: DragEvent) {
123
+ (e.currentTarget as HTMLElement)?.removeAttribute("data-state");
124
+ }
125
+
126
+ function handleChange(e: Event) {
127
+ handleFiles((e.currentTarget as HTMLInputElement).files);
128
+ }
129
+
130
+ function handleBlur() {
131
+ if (fieldContext) {
132
+ fieldContext.validate();
133
+ }
134
+ blurred = true;
135
+ }
136
+
137
+ @render {
138
+ <div
139
+ id={$props?.id ?? defaultId}
140
+ class={[
141
+ "torp-file-drop",
142
+ getDisabled() ? "torp-file-drop-disabled" : undefined,
143
+ $props?.class,
144
+ ]}
145
+ {$props?.style}
146
+ role="button"
147
+ tabindex={getDisabled() ? -1 : 0}
148
+ aria-label={$props?.ariaLabel}
149
+ aria-disabled={getDisabled() ? "true" : undefined}
150
+ data-valid={$state.valid ? "valid" : "invalid"}
151
+ aria-describedby={$state.messageId}
152
+ aria-invalid={fieldContext && !$state.valid ? "true" : undefined}
153
+ onclick={handleClick}
154
+ onkeydown={handleKey}
155
+ onblur={handleBlur}
156
+ ondrop={handleDrop}
157
+ ondragover={handleDragOver}
158
+ ondragleave={handleDragLeave}
159
+ >
160
+ <slot>
161
+ <span class="torp-file-drop-text">Drag and drop files here, or press Enter to browse</span>
162
+ </slot>
163
+ </div>
164
+
165
+ <input
166
+ id={inputId}
167
+ class="torp-file-drop-input"
168
+ type="file"
169
+ {name}
170
+ aria-hidden="true"
171
+ tabindex={-1}
172
+ multiple={$props?.multiple}
173
+ {$props?.accept}
174
+ disabled={getDisabled()}
175
+ &ref={input}
176
+ onchange={handleChange}
177
+ />
178
+ }
179
+
180
+ @style {
181
+ /* Headless: hide the input, since the dropzone is the interactive
182
+ element. All appearance is up to the consuming theme */
183
+ .torp-file-drop-input {
184
+ display: none;
185
+ }
186
+ }
187
+ }
@@ -1,41 +1,44 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { FormContextName, type ValidationIssue, type FormContext } from "./FormTypes";
3
- import { StandardSchemaV1 } from "./StandardSchemaV1"
2
+ import { type FormContext, FormContextName, type ValidationIssue } from "./FormTypes";
3
+ import { StandardSchemaV1 } from "./StandardSchemaV1";
4
4
 
5
- interface FormProps {
5
+ interface FormProps<Data = Record<string, any>> {
6
6
  /** An ID for the root element */
7
7
  id?: string;
8
8
  /** Classes for the root element */
9
- class?: ClassValue,
9
+ class?: ClassValue;
10
10
  /** Styles for the root element */
11
- style?: StyleValue,
12
- /** The data to display in the form */
13
- data?: any,
11
+ style?: StyleValue;
12
+ /**
13
+ * The data to display in the form. Type it with the page's form result
14
+ * (e.g. `PageForm<typeof server>`) to get field name autocomplete
15
+ */
16
+ data?: Data;
14
17
  /** Where to post the form's data **/
15
- action?: string,
18
+ action?: string;
16
19
  /** The action to use when submitting the form, which defaults to POST */
17
- method?: string,
20
+ method?: string;
18
21
  /**
19
22
  * The form's encoding type e.g. `multipart/form-data` where there are file
20
23
  * inputs. This will be automatically set unless overridden
21
24
  */
22
- enctype?: string,
25
+ enctype?: string;
23
26
  /** The schema validator to use for this form */
24
- schema?: StandardSchemaV1,
25
- onsubmit?: (e: SubmitEvent) => boolean | void | Promise<boolean | void>,
26
- };
27
+ schema?: StandardSchemaV1;
28
+ onsubmit?: (e: SubmitEvent) => boolean | void | Promise<boolean | void>;
29
+ }
27
30
 
28
31
  /**
29
32
  * A form for submitting values.
30
33
  */
31
- export default function Form($props: FormProps | undefined) {
34
+ export default function Form<Data = Record<string, any>>($props: FormProps<Data> | undefined) {
32
35
  let $state = $watch({
33
36
  data: $props?.data ?? {},
34
37
  hasFile: false,
35
38
  issues: [] as ValidationIssue[],
36
39
  get valid() {
37
40
  return this.issues.length === 0;
38
- }
41
+ },
39
42
  });
40
43
 
41
44
  let submitted = false;
@@ -46,9 +49,9 @@ export default function Form($props: FormProps | undefined) {
46
49
  let context: FormContext = {
47
50
  schema: $props?.schema,
48
51
  state: $state,
49
- validate: handleValidate
52
+ validate: handleValidate,
50
53
  };
51
-
54
+
52
55
  $context[FormContextName] = context;
53
56
 
54
57
  async function validate() {
@@ -63,10 +66,10 @@ export default function Form($props: FormProps | undefined) {
63
66
  for (let issue of result.issues) {
64
67
  if (issue.path) {
65
68
  // TODO: arrays with `[0]` etc
66
- issues.push({
69
+ issues.push({
67
70
  // @ts-ignore
68
71
  path: issue.path.map((p) => p.key ?? p).join("."),
69
- message: issue.message
72
+ message: issue.message,
70
73
  });
71
74
  }
72
75
  }
@@ -97,7 +100,7 @@ export default function Form($props: FormProps | undefined) {
97
100
  }
98
101
  proceed = userSubmit !== false && !e.defaultPrevented;
99
102
  }
100
-
103
+
101
104
  // If the user handled the submit, don't continue
102
105
  if (!proceed) {
103
106
  e.preventDefault();
@@ -105,7 +108,7 @@ export default function Form($props: FormProps | undefined) {
105
108
  }
106
109
 
107
110
  // If there's a @torpor/build context, call its submit event
108
- let buildContext = $context["TorporBuildContext"];
111
+ let buildContext = $context?.["TorporBuildContext"];
109
112
  if (buildContext?.onformsubmit) {
110
113
  let buildSubmit = buildContext.onformsubmit(e);
111
114
  if (buildSubmit instanceof Promise) {
@@ -138,7 +141,6 @@ export default function Form($props: FormProps | undefined) {
138
141
  method={$props?.method ?? "POST"}
139
142
  enctype={$props?.enctype ?? ($state.hasFile ? "multipart/form-data" : undefined)}
140
143
  onsubmit={handleSubmit}
141
- @// Set novalidate only if JS is enabled (to override HTML validation)
142
144
  onmount={(e) => e.novalidate = true }
143
145
  >
144
146
  <slot />
@@ -26,6 +26,8 @@ export interface FieldContext {
26
26
  schema?: StandardSchemaV1;
27
27
  state: {
28
28
  inputId: string;
29
+ /** The ID of the Message component in this field, if any, for use with aria-describedby */
30
+ messageId?: string;
29
31
  value?: any;
30
32
  issues: ValidationIssue[];
31
33
  valid: boolean;
@@ -0,0 +1,37 @@
1
+ interface FormValuesProps {
2
+ /** The name of the form data field(s) */
3
+ name?: string;
4
+ /** The value, or an array of values, to submit with the form */
5
+ value?: any;
6
+ }
7
+
8
+ /**
9
+ * Converts a form value to its text representation, without relying on
10
+ * implicit primitive conversion (which fails for reactive proxies).
11
+ */
12
+ function toText(value: any): string {
13
+ if (value == null) return "";
14
+ if (typeof value === "string") return value;
15
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
16
+ return JSON.stringify(value);
17
+ }
18
+
19
+ /**
20
+ * Renders hidden inputs for a component's value, so that it can take part
21
+ * in native form submission. One input per entry when the value is an array.
22
+ * Nothing is rendered when the value is null or undefined, so that the
23
+ * field is omitted from the submitted form data.
24
+ */
25
+ export default function FormValues($props: FormValuesProps | undefined) {
26
+ @render {
27
+ @if ($props?.name && $props.value != null) {
28
+ @if (Array.isArray($props.value)) {
29
+ @for (let item of $props.value) {
30
+ <input type="hidden" name={$props.name} value={toText(item)} />
31
+ }
32
+ } @else {
33
+ <input type="hidden" name={$props.name} value={toText($props.value)} />
34
+ }
35
+ }
36
+ }
37
+ }
@@ -1,25 +1,17 @@
1
- import getId from "../utils/getId";
2
- import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
-
4
1
  interface HiddenProps {
5
2
  /** An ID for the root element */
6
3
  id?: string;
7
4
  /** The name of the input's form data field */
8
- name?: string,
5
+ name?: string;
9
6
  /** The data value that this field is bound to */
10
- value?: any,
11
- };
7
+ value?: any;
8
+ }
12
9
 
13
10
  /**
14
11
  * A standard input in a form.
15
12
  */
16
13
  export default function Hidden($props: HiddenProps | undefined) {
17
14
  @render {
18
- <input
19
- {$props?.id}
20
- type="hidden"
21
- {$props?.name}
22
- {$props?.value}
23
- />
15
+ <input {$props?.id} type="hidden" {$props?.name} {$props?.value} />
24
16
  }
25
17
  }
@@ -1,35 +1,40 @@
1
- import type { ClassValue, StyleValue } from "@torpor/view";
1
+ import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
2
2
  import getId from "../utils/getId";
3
- import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
+ import {
4
+ type FieldContext,
5
+ FieldContextName,
6
+ type FormContext,
7
+ FormContextName,
8
+ } from "./FormTypes";
4
9
 
5
10
  interface InputProps {
6
11
  /** An ID for the root element */
7
12
  id?: string;
8
13
  /** Classes for the root element */
9
- class?: ClassValue,
14
+ class?: ClassValue;
10
15
  /** Styles for the root element */
11
- style?: StyleValue,
16
+ style?: StyleValue;
12
17
  /** The input type */
13
- type?: string, // TODO: "text" | "number" | "password" | "email" | "url" | "tel",
18
+ type?: string; // TODO: "text" | "number" | "password" | "email" | "url" | "tel",
14
19
  /** The name of the input's form data field */
15
- name?: string,
20
+ name?: string;
16
21
  /** The data value that this field is bound to */
17
- value?: any,
22
+ value: Bindable<any>;
18
23
  /** Text to display when the input value is empty */
19
- placeholder?: string,
24
+ placeholder?: string;
20
25
  /** Whether the input should be readonly */
21
- readonly?: boolean,
26
+ readonly?: boolean;
22
27
  /** Whether the input's value is required */
23
- required?: boolean,
28
+ required?: boolean;
24
29
  /** The maximum number of characters allowed for the input value */
25
- maxlength?: number,
30
+ maxlength?: number;
26
31
  /** An ARIA label for describing the input to screen readers when there is no associated label element */
27
- ariaLabel?: string,
32
+ ariaLabel?: string;
28
33
  /** Raised on change */
29
- onchange?: (e: Event) => void,
34
+ onchange?: (e: Event) => void;
30
35
  /** Raised on input */
31
- oninput?: (e: Event) => void,
32
- };
36
+ oninput?: (e: Event) => void;
37
+ }
33
38
 
34
39
  /**
35
40
  * A standard input in a form.
@@ -38,7 +43,7 @@ export default function Input($props: InputProps | undefined) {
38
43
  let defaultId = getId();
39
44
 
40
45
  let type = $props?.type ?? "text";
41
- let name = $props?.name
46
+ let name = $props?.name;
42
47
 
43
48
  let blurred = false;
44
49
 
@@ -50,28 +55,27 @@ export default function Input($props: InputProps | undefined) {
50
55
  }
51
56
 
52
57
  // Use the FieldContext's state, or create a new one
53
- let $state = fieldContext?.state ?? $watch({
54
- inputId: $props?.id ?? defaultId,
55
- value: $props?.value,
56
- errors: {} as Record<string, string>,
57
- valid: false,
58
- message: "",
59
- });
60
-
61
- // Binding
62
- $run(() => {
63
- if ($props !== undefined) {
64
- $props.value = $state.value;
65
- }
66
- });
58
+ let $state =
59
+ fieldContext?.state ??
60
+ $watch({
61
+ inputId: $props?.id ?? defaultId,
62
+ value: $props?.value,
63
+ errors: {} as Record<string, string>,
64
+ valid: false,
65
+ message: "",
66
+ });
67
67
 
68
68
  // Set the input ID now that we know it and it will get linked with the associated label
69
69
  $state.inputId = $props?.id ?? defaultId;
70
70
 
71
+ $bind($state, $props, "value");
72
+
71
73
  // NOTE: Adapted this from https://stackoverflow.com/a/57393751,
72
74
  // not sure if it's still a good way to do things
73
75
  function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
74
- $state.value = $props?.type?.match(/^(number|range)$/) ? +e.currentTarget.value : e.currentTarget.value;
76
+ $state.value = $props?.type?.match(/^(number|range)$/)
77
+ ? +e.currentTarget.value
78
+ : e.currentTarget.value;
75
79
  if (formContext) {
76
80
  formContext.validate();
77
81
  }
@@ -104,7 +108,9 @@ export default function Input($props: InputProps | undefined) {
104
108
  {$props?.maxlength}
105
109
  aria-label={$props?.ariaLabel}
106
110
  data-valid={$state.valid ? "valid" : "invalid"}
107
- onchange={$props?.onchange}
111
+ aria-describedby={$state.messageId}
112
+ aria-invalid={fieldContext && !$state.valid ? "true" : undefined}
113
+ {$props?.onchange}
108
114
  oninput={handleInput}
109
115
  onblur={handleBlur}
110
116
  />
@@ -1,13 +1,13 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { FieldContextName, type FieldContext } from "./FormTypes";
2
+ import { type FieldContext, FieldContextName } from "./FormTypes";
3
3
 
4
4
  interface LabelProps {
5
5
  /** An ID for the root element */
6
6
  id?: string;
7
7
  /** Classes for the root element */
8
- class?: ClassValue,
8
+ class?: ClassValue;
9
9
  /** Styles for the root element */
10
- style?: StyleValue,
10
+ style?: StyleValue;
11
11
  }
12
12
 
13
13
  /**
@@ -1,13 +1,14 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { FieldContextName, type FieldContext } from "./FormTypes";
2
+ import getId from "../utils/getId";
3
+ import { type FieldContext, FieldContextName } from "./FormTypes";
3
4
 
4
5
  interface MessageProps {
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;
11
12
  }
12
13
 
13
14
  /**
@@ -15,6 +16,8 @@ interface MessageProps {
15
16
  * problems that need to be fixed.
16
17
  */
17
18
  export default function Message($props: MessageProps | undefined) {
19
+ let defaultId = getId();
20
+
18
21
  // Get stuff out of the FieldContext
19
22
  const context = $context[FieldContextName] as FieldContext;
20
23
  if (!context) {
@@ -22,12 +25,17 @@ export default function Message($props: MessageProps | undefined) {
22
25
  }
23
26
  const { state: $state } = context;
24
27
 
28
+ // Set the message ID so that it can be used with aria-describedby in
29
+ // input components
30
+ $state.messageId = $props?.id ?? defaultId;
31
+
25
32
  @render {
26
33
  @if ($state.message) {
27
- <div
28
- {$props?.id}
34
+ <div
35
+ id={$props?.id ?? defaultId}
29
36
  class={["torp-message", $props?.class]}
30
37
  {$props?.style}
38
+ role="alert"
31
39
  data-valid={$state.valid ? "valid" : "invalid"}
32
40
  >
33
41
  <slot name="message">
@@ -1,13 +1,13 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { FormContextName, type FormContext } from "./FormTypes";
2
+ import { type FormContext, FormContextName } from "./FormTypes";
3
3
 
4
4
  interface MessageSummaryProps {
5
5
  /** An ID for the root element */
6
6
  id?: string;
7
7
  /** Classes for the root element */
8
- class?: ClassValue,
8
+ class?: ClassValue;
9
9
  /** Styles for the root element */
10
- style?: StyleValue,
10
+ style?: StyleValue;
11
11
  }
12
12
 
13
13
  /**
@@ -23,10 +23,11 @@ export default function MessageSummary($props: MessageSummaryProps | undefined)
23
23
 
24
24
  @render {
25
25
  @if ($state.issues.length > 0) {
26
- <ul
26
+ <ul
27
27
  {$props?.id}
28
28
  class={["torp-message-summary", $props?.class]}
29
29
  {$props?.style}
30
+ role="alert"
30
31
  >
31
32
  <slot />
32
33
  @for (let issue of $state.issues) {
@@ -1,28 +1,32 @@
1
1
  import type { ClassValue, StyleValue } from "@torpor/view";
2
- import getId from "../utils/getId";
3
- import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
4
2
 
5
3
  interface OptionProps {
6
4
  /** An ID for the root element */
7
5
  id?: string;
8
6
  /** Classes for the root element */
9
- class?: ClassValue,
7
+ class?: ClassValue;
10
8
  /** Styles for the root element */
11
- style?: StyleValue,
9
+ style?: StyleValue;
12
10
  /** The data value that this option has */
13
- value?: any,
14
- };
11
+ value?: any;
12
+ }
15
13
 
16
14
  /**
17
15
  * An option in a select.
18
16
  */
19
17
  export default function Option($props: OptionProps | undefined) {
18
+ let $state = $watch({
19
+ value: $props?.value,
20
+ });
21
+
22
+ $bind($state, $props, "value");
23
+
20
24
  @render {
21
25
  <option
22
26
  {$props?.id}
23
27
  class={["torp-option", $props?.class]}
24
28
  {$props?.style}
25
- &value={$props.value}
29
+ &value={$state.value}
26
30
  />
27
31
  }
28
32
  }