@torpor/ui 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/Accordion/Accordion.torp +29 -22
  3. package/dist/Accordion/AccordionContent.torp +7 -8
  4. package/dist/Accordion/AccordionHeader.torp +4 -6
  5. package/dist/Accordion/AccordionItem.torp +15 -12
  6. package/dist/Accordion/AccordionTrigger.torp +6 -7
  7. package/dist/Accordion/AccordionTypes.ts +4 -3
  8. package/dist/Accordion/index.d.ts +5 -5
  9. package/dist/Breadcrumb/Breadcrumb.torp +9 -10
  10. package/dist/Breadcrumb/BreadcrumbItem.torp +5 -7
  11. package/dist/Breadcrumb/BreadcrumbSeparator.torp +3 -5
  12. package/dist/Breadcrumb/index.d.ts +8 -7
  13. package/dist/Calendar/Calendar.torp +12 -13
  14. package/dist/Calendar/CalendarDay.torp +3 -3
  15. package/dist/Contextual/Contextual.torp +125 -0
  16. package/dist/{Popover/PopoverContentContextual.torp → Contextual/ContextualContent.torp} +15 -15
  17. package/dist/Contextual/ContextualTrigger.torp +66 -0
  18. package/dist/Contextual/ContextualTypes.ts +30 -0
  19. package/dist/Contextual/index.d.ts +90 -0
  20. package/dist/Contextual/index.js +14 -0
  21. package/dist/Contextual/index.js.map +1 -0
  22. package/dist/Dialog/Dialog.torp +38 -72
  23. package/dist/Dialog/DialogBody.torp +6 -8
  24. package/dist/Dialog/DialogButton.torp +14 -12
  25. package/dist/Dialog/DialogFooter.torp +3 -5
  26. package/dist/Dialog/DialogHeader.torp +6 -8
  27. package/dist/Dialog/DialogTypes.ts +3 -2
  28. package/dist/Dialog/index.d.ts +16 -11
  29. package/dist/Disclosure/Disclosure.torp +22 -23
  30. package/dist/Disclosure/DisclosureContent.torp +7 -8
  31. package/dist/Disclosure/DisclosureHeader.torp +5 -7
  32. package/dist/Disclosure/DisclosureTrigger.torp +7 -8
  33. package/dist/Disclosure/DisclosureTypes.ts +0 -1
  34. package/dist/Disclosure/index.d.ts +12 -12
  35. package/dist/Drawer/Drawer.torp +101 -70
  36. package/dist/Drawer/DrawerContent.torp +80 -0
  37. package/dist/Drawer/DrawerOverlay.torp +38 -0
  38. package/dist/Drawer/DrawerTrigger.torp +35 -0
  39. package/dist/Drawer/DrawerTypes.ts +24 -0
  40. package/dist/Drawer/index.d.ts +90 -3
  41. package/dist/Drawer/index.js +13 -1
  42. package/dist/Form/CheckBox.torp +91 -0
  43. package/dist/Form/Field.torp +2 -4
  44. package/dist/Form/File.torp +106 -0
  45. package/dist/Form/Form.torp +17 -10
  46. package/dist/Form/FormTypes.ts +5 -0
  47. package/dist/Form/Input.torp +28 -17
  48. package/dist/Form/Label.torp +1 -1
  49. package/dist/Form/Message.torp +1 -1
  50. package/dist/Form/Option.torp +24 -0
  51. package/dist/Form/Select.torp +97 -0
  52. package/dist/Form/TextArea.torp +101 -0
  53. package/dist/Form/index.d.ts +192 -5
  54. package/dist/Form/index.js +21 -1
  55. package/dist/Menu/Menu.torp +16 -15
  56. package/dist/Menu/MenuButton.torp +19 -13
  57. package/dist/Menu/MenuCheck.torp +18 -14
  58. package/dist/Menu/MenuGroup.torp +4 -6
  59. package/dist/Menu/MenuIndicator.torp +5 -7
  60. package/dist/Menu/MenuPopout.torp +12 -15
  61. package/dist/Menu/MenuPopoutContent.torp +6 -8
  62. package/dist/Menu/MenuPopoutTrigger.torp +12 -14
  63. package/dist/Menu/MenuRadio.torp +14 -14
  64. package/dist/Menu/MenuRadioGroup.torp +22 -14
  65. package/dist/Menu/MenuSeparator.torp +3 -5
  66. package/dist/Menu/MenuTypes.ts +7 -5
  67. package/dist/Menu/index.d.ts +28 -22
  68. package/dist/MenuBar/MenuBar.torp +13 -14
  69. package/dist/MenuBar/MenuBarItem.torp +13 -16
  70. package/dist/MenuBar/MenuBarItemContent.torp +7 -9
  71. package/dist/MenuBar/MenuBarItemTrigger.torp +6 -9
  72. package/dist/MenuBar/index.d.ts +16 -16
  73. package/dist/Modal/Modal.torp +118 -0
  74. package/dist/Modal/ModalContent.torp +77 -0
  75. package/dist/Modal/ModalOverlay.torp +38 -0
  76. package/dist/Modal/ModalTrigger.torp +50 -0
  77. package/dist/Modal/ModalTypes.ts +22 -0
  78. package/dist/Modal/index.d.ts +108 -0
  79. package/dist/Modal/index.js +33 -0
  80. package/dist/Modal/index.js.map +1 -0
  81. package/dist/Modal/showModal.ts +19 -0
  82. package/dist/Modal/showPrompt.ts +23 -0
  83. package/dist/NavMenu/NavMenu.torp +7 -8
  84. package/dist/NavMenu/NavMenuButton.torp +6 -8
  85. package/dist/NavMenu/NavMenuElement.torp +8 -8
  86. package/dist/NavMenu/NavMenuGroup.torp +4 -6
  87. package/dist/NavMenu/NavMenuGroupHeader.torp +5 -6
  88. package/dist/NavMenu/NavMenuLink.torp +8 -11
  89. package/dist/NavMenu/NavMenuPopout.torp +25 -17
  90. package/dist/NavMenu/NavMenuPopoutButton.torp +12 -13
  91. package/dist/NavMenu/NavMenuPopoutContent.torp +12 -12
  92. package/dist/NavMenu/NavMenuSeparator.torp +3 -5
  93. package/dist/NavMenu/index.d.ts +17 -11
  94. package/dist/Notification/Notification.torp +22 -13
  95. package/dist/Notification/NotificationClose.torp +3 -5
  96. package/dist/Notification/NotificationContainer.torp +3 -5
  97. package/dist/Notification/NotificationContent.torp +3 -5
  98. package/dist/Notification/index.d.ts +4 -4
  99. package/dist/Popover/Popover.torp +31 -25
  100. package/dist/Popover/PopoverContent.torp +35 -10
  101. package/dist/Popover/PopoverTrigger.torp +24 -4
  102. package/dist/Popover/PopoverTypes.ts +1 -1
  103. package/dist/Popover/index.d.ts +16 -124
  104. package/dist/Popover/index.js +1 -163
  105. package/dist/Popover/index.js.map +1 -1
  106. package/dist/TabGroup/TabContent.torp +12 -9
  107. package/dist/TabGroup/TabGroup.torp +41 -35
  108. package/dist/TabGroup/TabGroupTypes.ts +10 -5
  109. package/dist/TabGroup/TabHeader.torp +15 -8
  110. package/dist/TabGroup/TabList.torp +4 -6
  111. package/dist/TabGroup/TabTrigger.torp +8 -9
  112. package/dist/TabGroup/index.d.ts +14 -6
  113. package/dist/icons/ArrowDown.torp +3 -1
  114. package/dist/icons/ArrowLeft.torp +3 -1
  115. package/dist/icons/ArrowRight.torp +3 -1
  116. package/dist/icons/ArrowUp.torp +3 -1
  117. package/dist/icons/Bars.torp +3 -1
  118. package/dist/icons/Check.torp +3 -1
  119. package/dist/icons/ChevronDown.torp +3 -1
  120. package/dist/icons/ChevronLeft.torp +3 -1
  121. package/dist/icons/ChevronRight.torp +3 -1
  122. package/dist/icons/ChevronUp.torp +3 -1
  123. package/dist/icons/Cross.torp +3 -1
  124. package/dist/icons/IconTypes.ts +6 -0
  125. package/dist/icons/Loading.torp +3 -1
  126. package/dist/icons/Minus.torp +3 -1
  127. package/dist/icons/Plus.torp +3 -1
  128. package/dist/icons/index.d.ts +271 -14
  129. package/dist/index.d.ts +17 -4
  130. package/dist/metafile-esm.json +1 -1
  131. package/dist/motion/fade.ts +15 -6
  132. package/dist/motion/grow.ts +23 -0
  133. package/dist/motion/index.d.ts +19 -2
  134. package/dist/motion/index.js +93 -3
  135. package/dist/motion/index.js.map +1 -1
  136. package/dist/motion/measure.ts +28 -0
  137. package/dist/motion/slide.ts +45 -0
  138. package/dist/mount/clickPopover.ts +51 -0
  139. package/dist/mount/contextPopover.ts +51 -0
  140. package/dist/mount/hoverPopover.ts +58 -0
  141. package/package.json +21 -14
  142. package/scripts/postbuild.ts +11 -1
  143. package/src/Accordion/Accordion.torp +29 -22
  144. package/src/Accordion/AccordionContent.torp +7 -8
  145. package/src/Accordion/AccordionHeader.torp +4 -6
  146. package/src/Accordion/AccordionItem.torp +15 -12
  147. package/src/Accordion/AccordionTrigger.torp +6 -7
  148. package/src/Accordion/AccordionTypes.ts +4 -3
  149. package/src/Breadcrumb/Breadcrumb.torp +9 -10
  150. package/src/Breadcrumb/BreadcrumbItem.torp +5 -7
  151. package/src/Breadcrumb/BreadcrumbSeparator.torp +3 -5
  152. package/src/Calendar/Calendar.torp +12 -13
  153. package/src/Calendar/CalendarDay.torp +3 -3
  154. package/src/Contextual/Contextual.torp +125 -0
  155. package/src/{Popover/PopoverContentContextual.torp → Contextual/ContextualContent.torp} +15 -15
  156. package/src/Contextual/ContextualTrigger.torp +66 -0
  157. package/src/Contextual/ContextualTypes.ts +30 -0
  158. package/src/Contextual/index.ts +5 -0
  159. package/src/Dialog/Dialog.torp +38 -72
  160. package/src/Dialog/DialogBody.torp +6 -8
  161. package/src/Dialog/DialogButton.torp +14 -12
  162. package/src/Dialog/DialogFooter.torp +3 -5
  163. package/src/Dialog/DialogHeader.torp +6 -8
  164. package/src/Dialog/DialogTypes.ts +3 -2
  165. package/src/Disclosure/Disclosure.torp +22 -23
  166. package/src/Disclosure/DisclosureContent.torp +7 -8
  167. package/src/Disclosure/DisclosureHeader.torp +5 -7
  168. package/src/Disclosure/DisclosureTrigger.torp +7 -8
  169. package/src/Disclosure/DisclosureTypes.ts +0 -1
  170. package/src/Drawer/Drawer.torp +101 -70
  171. package/src/Drawer/DrawerContent.torp +80 -0
  172. package/src/Drawer/DrawerOverlay.torp +38 -0
  173. package/src/Drawer/DrawerTrigger.torp +35 -0
  174. package/src/Drawer/DrawerTypes.ts +24 -0
  175. package/src/Drawer/index.ts +4 -1
  176. package/src/Form/CheckBox.torp +91 -0
  177. package/src/Form/Field.torp +2 -4
  178. package/src/Form/File.torp +106 -0
  179. package/src/Form/Form.torp +17 -10
  180. package/src/Form/FormTypes.ts +5 -0
  181. package/src/Form/Input.torp +28 -17
  182. package/src/Form/Label.torp +1 -1
  183. package/src/Form/Message.torp +1 -1
  184. package/src/Form/Option.torp +24 -0
  185. package/src/Form/Select.torp +97 -0
  186. package/src/Form/TextArea.torp +101 -0
  187. package/src/Form/index.ts +6 -1
  188. package/src/Menu/Menu.torp +16 -15
  189. package/src/Menu/MenuButton.torp +19 -13
  190. package/src/Menu/MenuCheck.torp +18 -14
  191. package/src/Menu/MenuGroup.torp +4 -6
  192. package/src/Menu/MenuIndicator.torp +5 -7
  193. package/src/Menu/MenuPopout.torp +12 -15
  194. package/src/Menu/MenuPopoutContent.torp +6 -8
  195. package/src/Menu/MenuPopoutTrigger.torp +12 -14
  196. package/src/Menu/MenuRadio.torp +14 -14
  197. package/src/Menu/MenuRadioGroup.torp +22 -14
  198. package/src/Menu/MenuSeparator.torp +3 -5
  199. package/src/Menu/MenuTypes.ts +7 -5
  200. package/src/MenuBar/MenuBar.torp +13 -14
  201. package/src/MenuBar/MenuBarItem.torp +13 -16
  202. package/src/MenuBar/MenuBarItemContent.torp +7 -9
  203. package/src/MenuBar/MenuBarItemTrigger.torp +6 -9
  204. package/src/Modal/Modal.torp +118 -0
  205. package/src/Modal/ModalContent.torp +77 -0
  206. package/src/Modal/ModalOverlay.torp +38 -0
  207. package/src/Modal/ModalTrigger.torp +50 -0
  208. package/src/Modal/ModalTypes.ts +22 -0
  209. package/src/Modal/index.ts +10 -0
  210. package/src/Modal/showModal.ts +19 -0
  211. package/src/Modal/showPrompt.ts +23 -0
  212. package/src/NavMenu/NavMenu.torp +7 -8
  213. package/src/NavMenu/NavMenuButton.torp +6 -8
  214. package/src/NavMenu/NavMenuElement.torp +8 -8
  215. package/src/NavMenu/NavMenuGroup.torp +4 -6
  216. package/src/NavMenu/NavMenuGroupHeader.torp +5 -6
  217. package/src/NavMenu/NavMenuLink.torp +8 -11
  218. package/src/NavMenu/NavMenuPopout.torp +25 -17
  219. package/src/NavMenu/NavMenuPopoutButton.torp +12 -13
  220. package/src/NavMenu/NavMenuPopoutContent.torp +12 -12
  221. package/src/NavMenu/NavMenuSeparator.torp +3 -5
  222. package/src/Notification/Notification.torp +22 -13
  223. package/src/Notification/NotificationClose.torp +3 -5
  224. package/src/Notification/NotificationContainer.torp +3 -5
  225. package/src/Notification/NotificationContent.torp +3 -5
  226. package/src/Popover/Popover.torp +31 -25
  227. package/src/Popover/PopoverContent.torp +35 -10
  228. package/src/Popover/PopoverTrigger.torp +24 -4
  229. package/src/Popover/PopoverTypes.ts +1 -1
  230. package/src/Popover/index.ts +1 -24
  231. package/src/Slider/Slider.torp +3 -3
  232. package/src/TabGroup/TabContent.torp +12 -9
  233. package/src/TabGroup/TabGroup.torp +41 -35
  234. package/src/TabGroup/TabGroupTypes.ts +10 -5
  235. package/src/TabGroup/TabHeader.torp +15 -8
  236. package/src/TabGroup/TabList.torp +4 -6
  237. package/src/TabGroup/TabTrigger.torp +8 -9
  238. package/src/icons/ArrowDown.torp +3 -1
  239. package/src/icons/ArrowLeft.torp +3 -1
  240. package/src/icons/ArrowRight.torp +3 -1
  241. package/src/icons/ArrowUp.torp +3 -1
  242. package/src/icons/Bars.torp +3 -1
  243. package/src/icons/Check.torp +3 -1
  244. package/src/icons/ChevronDown.torp +3 -1
  245. package/src/icons/ChevronLeft.torp +3 -1
  246. package/src/icons/ChevronRight.torp +3 -1
  247. package/src/icons/ChevronUp.torp +3 -1
  248. package/src/icons/Cross.torp +3 -1
  249. package/src/icons/IconTypes.ts +6 -0
  250. package/src/icons/Loading.torp +3 -1
  251. package/src/icons/Minus.torp +3 -1
  252. package/src/icons/Plus.torp +3 -1
  253. package/src/motion/fade.ts +15 -6
  254. package/src/motion/grow.ts +23 -0
  255. package/src/motion/index.ts +4 -1
  256. package/src/motion/measure.ts +28 -0
  257. package/src/motion/slide.ts +45 -0
  258. package/src/mount/clickPopover.ts +51 -0
  259. package/src/mount/contextPopover.ts +51 -0
  260. package/src/mount/hoverPopover.ts +58 -0
  261. package/test/Accordion/components/AccordionAccessibility.torp +1 -3
  262. package/test/Accordion/components/AccordionKeyboard.torp +1 -3
  263. package/test/Accordion/components/AccordionList.torp +1 -3
  264. package/test/Accordion/components/AccordionMultiple.torp +2 -4
  265. package/test/Accordion/components/AccordionSingle.torp +2 -4
  266. package/test/Dialog/accessibility-modal.test.ts +2 -2
  267. package/test/Dialog/components/DialogAccessibility.torp +8 -14
  268. package/test/Dialog/components/DialogFunction.torp +20 -4
  269. package/test/Dialog/components/DialogModal.torp +30 -0
  270. package/test/Dialog/components/DialogVariable.torp +7 -7
  271. package/test/Disclosure/components/DisclosureTest.torp +1 -3
  272. package/test/Menu/components/MenuAccessibility.torp +5 -5
  273. package/test/Menu/components/MenuBasic.torp +5 -5
  274. package/test/Notification/components/NotificationFunction.torp +0 -3
  275. package/test/Notification/components/NotificationVariable.torp +1 -1
  276. package/test/Popover/components/PopoverFunction.torp +37 -0
  277. package/test/Popover/components/PopoverTrigger.torp +36 -0
  278. package/test/Popover/components/{PromptVariable.torp → PopoverVariable.torp} +5 -3
  279. package/test/Popover/function.test.ts +2 -7
  280. package/test/Popover/trigger.test.ts +27 -0
  281. package/test/Popover/variable.test.ts +2 -2
  282. package/test/TabGroup/components/TabGroupAccessibility.torp +1 -3
  283. package/test/TabGroup/components/TabGroupKeyboard.torp +1 -3
  284. package/test/TabGroup/components/TabGroupList.torp +1 -3
  285. package/test/TabGroup/components/TabGroupTrigger.torp +1 -3
  286. package/tsup.config.ts +2 -0
  287. package/dist/Popover/PopoverContentAnchored.torp +0 -113
  288. package/dist/Popover/PopoverOverlay.torp +0 -39
  289. package/dist/Popover/PopoverTriggerContextual.torp +0 -46
  290. package/dist/Popover/PopoverTriggerHover.torp +0 -47
  291. package/dist/Popover/mountClickPopover.ts +0 -50
  292. package/dist/Popover/mountContextPopover.ts +0 -50
  293. package/dist/Popover/mountHoverPopover.ts +0 -57
  294. package/dist/Popover/showPopoverPrompt.ts +0 -23
  295. package/src/Popover/PopoverContentAnchored.torp +0 -113
  296. package/src/Popover/PopoverOverlay.torp +0 -39
  297. package/src/Popover/PopoverTriggerContextual.torp +0 -46
  298. package/src/Popover/PopoverTriggerHover.torp +0 -47
  299. package/src/Popover/mountClickPopover.ts +0 -50
  300. package/src/Popover/mountContextPopover.ts +0 -50
  301. package/src/Popover/mountHoverPopover.ts +0 -57
  302. package/src/Popover/showPopoverPrompt.ts +0 -23
  303. package/test/Dialog/components/CustomDialog.torp +0 -20
  304. package/test/Popover/components/PromptFunction.torp +0 -19
@@ -0,0 +1,91 @@
1
+ import getId from "../utils/getId";
2
+ import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
+
4
+ interface CheckBoxProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Class name(s) for the root element */
8
+ class?: string,
9
+ /** The name of the checkbox's form data field */
10
+ name?: string,
11
+ /** Whether the checkbox is checked */
12
+ checked?: boolean,
13
+ /** An ARIA label for describing the checkbox to screen readers when there is no associated label element */
14
+ ariaLabel?: string,
15
+ /** Raised on change */
16
+ onchange?: (e: Event) => void,
17
+ /** Raised on input */
18
+ oninput?: (e: Event) => void,
19
+ };
20
+
21
+ /**
22
+ * A checkbox input in a form.
23
+ */
24
+ export default function CheckBox($props: CheckBoxProps | undefined) {
25
+ let defaultId = getId();
26
+
27
+ let name = $props?.name
28
+
29
+ let blurred = false;
30
+
31
+ // Get stuff out of the FormContext and FieldContext
32
+ const formContext = $context[FormContextName] as FormContext;
33
+ const fieldContext = $context[FieldContextName] as FieldContext;
34
+ if (fieldContext) {
35
+ name ??= fieldContext.name;
36
+ }
37
+
38
+ // Use the FieldContext's state, or create a new one
39
+ let $state = fieldContext?.state ?? $watch({
40
+ inputId: $props?.id ?? defaultId,
41
+ value: $props?.checked,
42
+ errors: {} as Record<string, string>,
43
+ valid: false,
44
+ message: "",
45
+ });
46
+
47
+ // Binding
48
+ $run(() => {
49
+ if ($props !== undefined) {
50
+ $props.checked = $state.value === true;
51
+ }
52
+ });
53
+
54
+ // Set the input ID now that we know it and it will get linked with the associated label
55
+ $state.inputId = $props?.id ?? defaultId;
56
+
57
+ function handleInput(e: Event) {
58
+ if (formContext) {
59
+ formContext.validate();
60
+ }
61
+ if (fieldContext && blurred) {
62
+ fieldContext.validate();
63
+ }
64
+ if ($props?.oninput) {
65
+ $props.oninput(e);
66
+ }
67
+ }
68
+
69
+ function handleBlur() {
70
+ if (fieldContext) {
71
+ fieldContext.validate();
72
+ }
73
+ blurred = true;
74
+ }
75
+
76
+ @render {
77
+ <input
78
+ id={$props?.id ?? defaultId}
79
+ class={`torp-check-box
80
+ ${$props?.class ?? ""}`.trim()}
81
+ type="checkbox"
82
+ {name}
83
+ &checked={$state.checked}
84
+ aria-label={$props?.ariaLabel}
85
+ data-valid={$state.valid ? "valid" : "invalid"}
86
+ onchange={$props?.onchange}
87
+ oninput={handleInput}
88
+ onblur={handleBlur}
89
+ />
90
+ }
91
+ }
@@ -25,8 +25,6 @@ interface FieldProps {
25
25
  * component and/or a Message.
26
26
  */
27
27
  export default function Field($props: FieldProps) {
28
- $props ??= $watch({ name: "" });
29
-
30
28
  let $state = $watch({
31
29
  inputId: "-1",
32
30
  value: $props.value,
@@ -83,8 +81,8 @@ export default function Field($props: FieldProps) {
83
81
 
84
82
  @render {
85
83
  <div
86
- {$props.id}
87
- class={`torp-field ${$props.class ?? ""}`.trim()}
84
+ {$props?.id}
85
+ class={`torp-field ${$props?.class ?? ""}`.trim()}
88
86
  data-valid={$state.valid ? "valid" : "invalid"}
89
87
  >
90
88
  <slot />
@@ -0,0 +1,106 @@
1
+ import getId from "../utils/getId";
2
+ import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
+
4
+ interface FileProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Class name(s) for the root element */
8
+ class?: string,
9
+ /** The name of the input's form data field */
10
+ name?: string,
11
+ /** The type of files that this input accepts e.g. `image/*` */
12
+ accepts?: string,
13
+ /** The data value that this field is bound to */
14
+ value?: any,
15
+ /** Text to display when the input value is empty */
16
+ placeholder?: string,
17
+ /** Whether the input should be readonly */
18
+ readonly?: boolean,
19
+ /** The maximum number of characters allowed for the input value */
20
+ maxlength?: number,
21
+ /** An ARIA label for describing the input to screen readers when there is no associated label element */
22
+ ariaLabel?: string,
23
+ /** Raised on change */
24
+ onchange?: (e: Event) => void,
25
+ /** Raised on input */
26
+ oninput?: (e: Event) => void,
27
+ };
28
+
29
+ /**
30
+ * A file input in a form.
31
+ */
32
+ export default function Input($props: FileProps | undefined) {
33
+ let defaultId = getId();
34
+
35
+ let name = $props?.name
36
+
37
+ let blurred = false;
38
+
39
+ // Get stuff out of the FormContext and FieldContext
40
+ const formContext = $context[FormContextName] as FormContext;
41
+ const fieldContext = $context[FieldContextName] as FieldContext;
42
+ if (fieldContext) {
43
+ name ??= fieldContext.name;
44
+ }
45
+
46
+ // This will change the form's enctype to `multipart/form-data`
47
+ if (formContext) {
48
+ formContext.state.hasFile = true;
49
+ }
50
+
51
+ // Use the FieldContext's state, or create a new one
52
+ let $state = fieldContext?.state ?? $watch({
53
+ inputId: $props?.id ?? defaultId,
54
+ value: $props?.value,
55
+ errors: {} as Record<string, string>,
56
+ valid: false,
57
+ message: "",
58
+ });
59
+
60
+ // Binding
61
+ $run(() => {
62
+ if ($props !== undefined) {
63
+ $props.value = $state.value;
64
+ }
65
+ });
66
+
67
+ // Set the input ID now that we know it and it will get linked with the associated label
68
+ $state.inputId = $props?.id ?? defaultId;
69
+
70
+ function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
71
+ if (formContext) {
72
+ formContext.validate();
73
+ }
74
+ if (fieldContext && blurred) {
75
+ fieldContext.validate();
76
+ }
77
+ if ($props?.oninput) {
78
+ $props.oninput(e);
79
+ }
80
+ }
81
+
82
+ function handleBlur() {
83
+ if (fieldContext) {
84
+ fieldContext.validate();
85
+ }
86
+ blurred = true;
87
+ }
88
+
89
+ @render {
90
+ <input
91
+ id={$props?.id ?? defaultId}
92
+ class={`torp-file ${$props?.class ?? ""}`.trim()}
93
+ type="file"
94
+ {name}
95
+ &value={$state.value}
96
+ {$props?.placeholder}
97
+ {$props?.readonly}
98
+ {$props?.maxlength}
99
+ aria-label={$props?.ariaLabel}
100
+ data-valid={$state.valid ? "valid" : "invalid"}
101
+ onchange={$props?.onchange}
102
+ oninput={handleInput}
103
+ onblur={handleBlur}
104
+ />
105
+ }
106
+ }
@@ -10,6 +10,13 @@ interface FormProps {
10
10
  data?: any,
11
11
  /** Where to post the form's data **/
12
12
  action?: string,
13
+ /** The action to use when submitting the form, which defaults to POST */
14
+ method?: string,
15
+ /**
16
+ * The form's encoding type e.g. `multipart/form-data` where there are file
17
+ * inputs. This will be automatically set unless overridden
18
+ */
19
+ enctype?: string,
13
20
  /** The schema validator to use for this form */
14
21
  schema?: StandardSchemaV1,
15
22
  onsubmit?: (e: SubmitEvent) => boolean | void | Promise<boolean | void>,
@@ -18,11 +25,10 @@ interface FormProps {
18
25
  /**
19
26
  * A form for submitting values.
20
27
  */
21
- export default function Form($props: FormProps) {
22
- $props ??= $watch({});
23
-
28
+ export default function Form($props: FormProps | undefined) {
24
29
  let $state = $watch({
25
- data: $props.data ?? {},
30
+ data: $props?.data ?? {},
31
+ hasFile: false,
26
32
  issues: [] as ValidationIssue[],
27
33
  get valid() {
28
34
  return this.issues.length === 0;
@@ -35,7 +41,7 @@ export default function Form($props: FormProps) {
35
41
 
36
42
  // Set the context to pass down to Fields
37
43
  let context: FormContext = {
38
- schema: $props.schema,
44
+ schema: $props?.schema,
39
45
  state: $state,
40
46
  validate: handleValidate
41
47
  };
@@ -43,7 +49,7 @@ export default function Form($props: FormProps) {
43
49
  $context[FormContextName] = context;
44
50
 
45
51
  async function validate() {
46
- if ($props.schema) {
52
+ if ($props?.schema) {
47
53
  const validator = $props.schema["~standard"];
48
54
  let result = validator.validate($props.data);
49
55
  if (result instanceof Promise) {
@@ -77,7 +83,7 @@ export default function Form($props: FormProps) {
77
83
  }
78
84
 
79
85
  // Dispatch the submit event for the user to handle
80
- if ($props.onsubmit) {
86
+ if ($props?.onsubmit) {
81
87
  let userSubmit = $props.onsubmit(e);
82
88
  if (userSubmit instanceof Promise) {
83
89
  userSubmit = await userSubmit;
@@ -118,9 +124,10 @@ export default function Form($props: FormProps) {
118
124
 
119
125
  @render {
120
126
  <form
121
- {$props.id}
122
- class={`torp-form ${$props.class ?? ""}`.trim()}
123
- method="POST"
127
+ {$props?.id}
128
+ class={`torp-form ${$props?.class ?? ""}`.trim()}
129
+ method={$props?.method ?? "POST"}
130
+ enctype={$props?.enctype ?? ($state.hasFile ? "multipart/form-data" : undefined)}
124
131
  onsubmit={handleSubmit}
125
132
  @// Set novalidate only if JS is enabled (to override HTML validation)
126
133
  onmount={(e) => e.novalidate = true }
@@ -11,6 +11,11 @@ export interface ValidationIssue {
11
11
  export interface FormContext {
12
12
  schema?: StandardSchemaV1;
13
13
  state: {
14
+ /**
15
+ * Whether there is a file input in the form. If so, the form's enctype
16
+ * will be set to `multipart/form-data`
17
+ */
18
+ hasFile: boolean;
14
19
  issues: ValidationIssue[];
15
20
  };
16
21
  validate: () => void;
@@ -20,15 +20,20 @@ interface InputProps {
20
20
  maxlength?: number,
21
21
  /** An ARIA label for describing the input to screen readers when there is no associated label element */
22
22
  ariaLabel?: string,
23
+ /** Raised on change */
24
+ onchange?: (e: Event) => void,
25
+ /** Raised on input */
26
+ oninput?: (e: Event) => void,
23
27
  };
24
28
 
25
29
  /**
26
30
  * A standard input in a form.
27
31
  */
28
- export default function Input($props: InputProps) {
29
- $props ??= $watch({});
30
- $props.id ??= getId();
31
- $props.type ??= "text";
32
+ export default function Input($props: InputProps | undefined) {
33
+ let defaultId = getId();
34
+
35
+ let type = $props?.type ?? "text";
36
+ let name = $props?.name
32
37
 
33
38
  let blurred = false;
34
39
 
@@ -36,13 +41,13 @@ export default function Input($props: InputProps) {
36
41
  const formContext = $context[FormContextName] as FormContext;
37
42
  const fieldContext = $context[FieldContextName] as FieldContext;
38
43
  if (fieldContext) {
39
- $props.name ??= fieldContext.name;
44
+ name ??= fieldContext.name;
40
45
  }
41
46
 
42
47
  // Use the FieldContext's state, or create a new one
43
48
  let $state = fieldContext?.state ?? $watch({
44
- inputId: $props.id,
45
- value: $props.value,
49
+ inputId: $props?.id ?? defaultId,
50
+ value: $props?.value,
46
51
  errors: {} as Record<string, string>,
47
52
  valid: false,
48
53
  message: "",
@@ -50,11 +55,13 @@ export default function Input($props: InputProps) {
50
55
 
51
56
  // Binding
52
57
  $run(() => {
53
- $props.value = $state.value;
58
+ if ($props !== undefined) {
59
+ $props.value = $state.value;
60
+ }
54
61
  });
55
62
 
56
63
  // Set the input ID now that we know it and it will get linked with the associated label
57
- $state.inputId = $props.id;
64
+ $state.inputId = $props?.id ?? defaultId;
58
65
 
59
66
  // NOTE: Adapted this from https://stackoverflow.com/a/57393751,
60
67
  // not sure if it's still a good way to do things
@@ -66,6 +73,9 @@ export default function Input($props: InputProps) {
66
73
  if (fieldContext && blurred) {
67
74
  fieldContext.validate();
68
75
  }
76
+ if ($props?.oninput) {
77
+ $props.oninput(e);
78
+ }
69
79
  }
70
80
 
71
81
  function handleBlur() {
@@ -77,16 +87,17 @@ export default function Input($props: InputProps) {
77
87
 
78
88
  @render {
79
89
  <input
80
- {$props.id}
81
- class={`torp-input ${$props.class ?? ""}`.trim()}
82
- {$props.name}
90
+ id={$props?.id ?? defaultId}
91
+ class={`torp-input ${$props?.class ?? ""}`.trim()}
92
+ {type}
93
+ {name}
83
94
  &value={$state.value}
84
- {$props.type}
85
- {$props.placeholder}
86
- {$props.readonly}
87
- {$props.maxlength}
88
- aria-label={$props.ariaLabel}
95
+ {$props?.placeholder}
96
+ {$props?.readonly}
97
+ {$props?.maxlength}
98
+ aria-label={$props?.ariaLabel}
89
99
  data-valid={$state.valid ? "valid" : "invalid"}
100
+ onchange={$props?.onchange}
90
101
  oninput={handleInput}
91
102
  onblur={handleBlur}
92
103
  />
@@ -10,7 +10,7 @@ interface LabelProps {
10
10
  /**
11
11
  * A label to describe the contents of an input in a form.
12
12
  */
13
- export default function Label($props?: LabelProps) {
13
+ export default function Label($props: LabelProps | undefined) {
14
14
  // Get stuff out of the FieldContext
15
15
  const context = $context[FieldContextName] as FieldContext;
16
16
  if (!context) {
@@ -11,7 +11,7 @@ interface MessageProps {
11
11
  * A validation message to show when the contents of an input component have
12
12
  * problems that need to be fixed.
13
13
  */
14
- export default function Message($props?: MessageProps) {
14
+ export default function Message($props: MessageProps | undefined) {
15
15
  // Get stuff out of the FieldContext
16
16
  const context = $context[FieldContextName] as FieldContext;
17
17
  if (!context) {
@@ -0,0 +1,24 @@
1
+ import getId from "../utils/getId";
2
+ import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
+
4
+ interface OptionProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Class name(s) for the root element */
8
+ class?: string,
9
+ /** The data value that this option has */
10
+ value?: any,
11
+ };
12
+
13
+ /**
14
+ * An option in a select.
15
+ */
16
+ export default function Option($props: OptionProps | undefined) {
17
+ @render {
18
+ <option
19
+ {$props?.id}
20
+ class={`torp-option ${$props?.class ?? ""}`.trim()}
21
+ &value={$state.value}
22
+ />
23
+ }
24
+ }
@@ -0,0 +1,97 @@
1
+ import getId from "../utils/getId";
2
+ import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
+
4
+ interface SelectProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Class name(s) for the root element */
8
+ class?: string,
9
+ /** The name of the input's form data field */
10
+ name?: string,
11
+ /** The data value that this field is bound to */
12
+ value?: any,
13
+ /** Text to display when the input value is empty */
14
+ placeholder?: string,
15
+ /** Whether the input should be readonly */
16
+ readonly?: boolean,
17
+ /** An ARIA label for describing the input to screen readers when there is no associated label element */
18
+ ariaLabel?: string,
19
+ /** Raised on change */
20
+ onchange?: (e: Event) => void,
21
+ /** Raised on input */
22
+ oninput?: (e: Event) => void,
23
+ };
24
+
25
+ /**
26
+ * A select in a form.
27
+ */
28
+ export default function Select($props: SelectProps | undefined) {
29
+ let defaultId = getId();
30
+
31
+ let name = $props?.name
32
+
33
+ let blurred = false;
34
+
35
+ // Get stuff out of the FormContext and FieldContext
36
+ const formContext = $context[FormContextName] as FormContext;
37
+ const fieldContext = $context[FieldContextName] as FieldContext;
38
+ if (fieldContext) {
39
+ name ??= fieldContext.name;
40
+ }
41
+
42
+ // Use the FieldContext's state, or create a new one
43
+ let $state = fieldContext?.state ?? $watch({
44
+ inputId: $props?.id ?? defaultId,
45
+ value: $props?.value,
46
+ errors: {} as Record<string, string>,
47
+ valid: false,
48
+ message: "",
49
+ });
50
+
51
+ // Binding
52
+ $run(() => {
53
+ if ($props !== undefined) {
54
+ $props.value = $state.value;
55
+ }
56
+ });
57
+
58
+ // Set the input ID now that we know it and it will get linked with the associated label
59
+ $state.inputId = $props?.id ?? defaultId;
60
+
61
+ function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
62
+ if (formContext) {
63
+ formContext.validate();
64
+ }
65
+ if (fieldContext && blurred) {
66
+ fieldContext.validate();
67
+ }
68
+ if ($props?.oninput) {
69
+ $props.oninput(e);
70
+ }
71
+ }
72
+
73
+ function handleBlur() {
74
+ if (fieldContext) {
75
+ fieldContext.validate();
76
+ }
77
+ blurred = true;
78
+ }
79
+
80
+ @render {
81
+ <select
82
+ id={$props?.id ?? defaultId}
83
+ class={`torp-select ${$props?.class ?? ""}`.trim()}
84
+ {name}
85
+ &value={$state.value}
86
+ {$props?.placeholder}
87
+ {$props?.readonly}
88
+ aria-label={$props?.ariaLabel}
89
+ data-valid={$state.valid ? "valid" : "invalid"}
90
+ onchange={$props?.onchange}
91
+ oninput={handleInput}
92
+ onblur={handleBlur}
93
+ >
94
+ <slot />
95
+ </select>
96
+ }
97
+ }
@@ -0,0 +1,101 @@
1
+ import getId from "../utils/getId";
2
+ import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
+
4
+ interface TextAreaProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Class name(s) for the root element */
8
+ class?: string,
9
+ /** The name of the input's form data field */
10
+ name?: string,
11
+ /** The data value that this field is bound to */
12
+ value?: any,
13
+ /** The number of rows to show in the input */
14
+ rows?: number,
15
+ /** Text to display when the input's value is empty */
16
+ placeholder?: string,
17
+ /** Whether the input should be readonly */
18
+ readonly?: boolean,
19
+ /** The maximum number of characters allowed for the input value */
20
+ maxlength?: number,
21
+ /** An ARIA label for describing the input to screen readers when there is no associated label element */
22
+ ariaLabel?: string,
23
+ /** Raised on change */
24
+ onchange?: (e: Event) => void,
25
+ /** Raised on input */
26
+ oninput?: (e: Event) => void,
27
+ };
28
+
29
+ /**
30
+ * A standard input in a form.
31
+ */
32
+ export default function TextArea($props: TextAreaProps | undefined) {
33
+ let defaultId = getId();
34
+
35
+ let name = $props?.name
36
+
37
+ let blurred = false;
38
+
39
+ // Get stuff out of the FormContext and FieldContext
40
+ const formContext = $context[FormContextName] as FormContext;
41
+ const fieldContext = $context[FieldContextName] as FieldContext;
42
+ if (fieldContext) {
43
+ name ??= fieldContext.name;
44
+ }
45
+
46
+ // Use the FieldContext's state, or create a new one
47
+ let $state = fieldContext?.state ?? $watch({
48
+ inputId: $props?.id ?? defaultId,
49
+ value: $props?.value,
50
+ errors: {} as Record<string, string>,
51
+ valid: false,
52
+ message: "",
53
+ });
54
+
55
+ // Binding
56
+ $run(() => {
57
+ if ($props !== undefined) {
58
+ $props.value = $state.value;
59
+ }
60
+ });
61
+
62
+ // Set the input ID now that we know it and it will get linked with the associated label
63
+ $state.inputId = $props?.id ?? defaultId;
64
+
65
+ function handleInput(e: Event) {
66
+ if (formContext) {
67
+ formContext.validate();
68
+ }
69
+ if (fieldContext && blurred) {
70
+ fieldContext.validate();
71
+ }
72
+ if ($props?.oninput) {
73
+ $props.oninput(e);
74
+ }
75
+ }
76
+
77
+ function handleBlur() {
78
+ if (fieldContext) {
79
+ fieldContext.validate();
80
+ }
81
+ blurred = true;
82
+ }
83
+
84
+ @render {
85
+ <input
86
+ id={$props?.id ?? defaultId}
87
+ class={`torp-text-area ${$props?.class ?? ""}`.trim()}
88
+ {name}
89
+ &value={$state.value}
90
+ {$props?.rows}
91
+ {$props?.placeholder}
92
+ {$props?.readonly}
93
+ {$props?.maxlength}
94
+ aria-label={$props?.ariaLabel}
95
+ data-valid={$state.valid ? "valid" : "invalid"}
96
+ onchange={$props?.onchange}
97
+ oninput={handleInput}
98
+ onblur={handleBlur}
99
+ />
100
+ }
101
+ }
package/src/Form/index.ts CHANGED
@@ -1,7 +1,12 @@
1
+ import CheckBox from "./CheckBox.torp";
1
2
  import Field from "./Field.torp";
3
+ import File from "./File.torp";
2
4
  import Form from "./Form.torp";
3
5
  import Input from "./Input.torp";
4
6
  import Label from "./Label.torp";
5
7
  import Message from "./Message.torp";
8
+ import Option from "./Option.torp";
9
+ import Select from "./Select.torp";
10
+ import TextArea from "./TextArea.torp";
6
11
 
7
- export { Form, Field, Label, Input, Message };
12
+ export { CheckBox, Field, File, Form, Input, Label, Message, Option, Select, TextArea };