@torpor/ui 0.1.7 → 0.2.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 (313) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/Accordion/Accordion.torp +25 -13
  3. package/dist/Accordion/AccordionContent.torp +15 -8
  4. package/dist/Accordion/AccordionHeader.torp +16 -7
  5. package/dist/Accordion/AccordionItem.torp +24 -10
  6. package/dist/Accordion/AccordionTrigger.torp +16 -8
  7. package/dist/Accordion/AccordionTypes.ts +2 -0
  8. package/dist/Accordion/index.d.ts +157 -0
  9. package/dist/Breadcrumb/Breadcrumb.torp +42 -0
  10. package/dist/Breadcrumb/BreadcrumbItem.torp +28 -0
  11. package/dist/Breadcrumb/BreadcrumbSeparator.torp +25 -0
  12. package/dist/Breadcrumb/index.d.ts +84 -0
  13. package/dist/Breadcrumb/index.js +14 -0
  14. package/dist/Calendar/Calendar.torp +273 -0
  15. package/dist/Calendar/CalendarDay.torp +75 -0
  16. package/dist/Calendar/CalendarTypes.ts +22 -0
  17. package/dist/Calendar/index.d.ts +36 -0
  18. package/dist/Calendar/index.js +10 -0
  19. package/dist/Calendar/index.js.map +1 -0
  20. package/dist/Dialog/Dialog.torp +16 -6
  21. package/dist/Dialog/DialogBody.torp +13 -5
  22. package/dist/Dialog/DialogButton.torp +18 -6
  23. package/dist/Dialog/DialogFooter.torp +13 -3
  24. package/dist/Dialog/DialogHeader.torp +13 -4
  25. package/dist/Dialog/index.d.ts +135 -0
  26. package/dist/Disclosure/Disclosure.torp +79 -0
  27. package/dist/Disclosure/DisclosureContent.torp +43 -0
  28. package/dist/Disclosure/DisclosureHeader.torp +43 -0
  29. package/dist/Disclosure/DisclosureTrigger.torp +44 -0
  30. package/dist/Disclosure/DisclosureTypes.ts +14 -0
  31. package/dist/Disclosure/index.d.ts +113 -0
  32. package/dist/Disclosure/index.js +18 -0
  33. package/dist/Disclosure/index.js.map +1 -0
  34. package/{src/Offscreen/Offscreen.torp → dist/Drawer/Drawer.torp} +20 -19
  35. package/dist/Drawer/index.d.ts +21 -0
  36. package/dist/Drawer/index.js +6 -0
  37. package/dist/Drawer/index.js.map +1 -0
  38. package/dist/Form/Field.torp +93 -0
  39. package/dist/Form/Form.torp +131 -0
  40. package/dist/Form/FormTypes.ts +30 -0
  41. package/dist/Form/Input.torp +94 -0
  42. package/dist/Form/Label.torp +31 -0
  43. package/dist/Form/Message.torp +35 -0
  44. package/dist/Form/StandardSchemaV1.ts +70 -0
  45. package/dist/Form/index.d.ts +117 -0
  46. package/dist/Form/index.js +22 -0
  47. package/dist/Form/index.js.map +1 -0
  48. package/dist/Menu/Menu.torp +169 -0
  49. package/dist/Menu/MenuButton.torp +64 -0
  50. package/dist/Menu/MenuCheck.torp +81 -0
  51. package/dist/Menu/MenuGroup.torp +29 -0
  52. package/dist/Menu/MenuIndicator.torp +38 -0
  53. package/dist/Menu/MenuPopout.torp +82 -0
  54. package/dist/Menu/MenuPopoutContent.torp +83 -0
  55. package/dist/Menu/MenuPopoutTrigger.torp +126 -0
  56. package/dist/Menu/MenuRadio.torp +99 -0
  57. package/dist/Menu/MenuRadioGroup.torp +97 -0
  58. package/dist/Menu/MenuSeparator.torp +24 -0
  59. package/dist/Menu/MenuTypes.ts +58 -0
  60. package/dist/Menu/index.d.ts +293 -0
  61. package/dist/Menu/index.js +46 -0
  62. package/dist/Menu/index.js.map +1 -0
  63. package/dist/MenuBar/MenuBar.torp +159 -0
  64. package/dist/MenuBar/MenuBarItem.torp +115 -0
  65. package/dist/MenuBar/MenuBarItemContent.torp +117 -0
  66. package/dist/MenuBar/MenuBarItemTrigger.torp +98 -0
  67. package/dist/MenuBar/MenuBarTypes.ts +36 -0
  68. package/dist/MenuBar/index.d.ts +125 -0
  69. package/dist/MenuBar/index.js +18 -0
  70. package/dist/MenuBar/index.js.map +1 -0
  71. package/dist/NavMenu/NavMenu.torp +15 -6
  72. package/dist/NavMenu/NavMenuButton.torp +14 -7
  73. package/dist/NavMenu/NavMenuElement.torp +16 -8
  74. package/dist/NavMenu/NavMenuGroup.torp +14 -5
  75. package/dist/NavMenu/NavMenuGroupHeader.torp +14 -6
  76. package/dist/NavMenu/NavMenuLink.torp +18 -10
  77. package/dist/NavMenu/NavMenuPopout.torp +13 -5
  78. package/dist/NavMenu/NavMenuPopoutButton.torp +17 -11
  79. package/dist/NavMenu/NavMenuPopoutContent.torp +42 -30
  80. package/dist/NavMenu/NavMenuSeparator.torp +12 -3
  81. package/dist/NavMenu/index.d.ts +259 -0
  82. package/dist/Notification/Notification.torp +58 -0
  83. package/dist/Notification/NotificationClose.torp +37 -0
  84. package/dist/Notification/NotificationContainer.torp +24 -0
  85. package/dist/Notification/NotificationContent.torp +23 -0
  86. package/dist/Notification/NotificationTypes.ts +5 -0
  87. package/dist/Notification/index.d.ts +92 -0
  88. package/dist/Notification/index.js +36 -0
  89. package/dist/Notification/index.js.map +1 -0
  90. package/dist/Notification/showNotification.ts +30 -0
  91. package/dist/Popover/Popover.torp +22 -8
  92. package/dist/Popover/PopoverContent.torp +41 -29
  93. package/dist/Popover/PopoverContentAnchored.torp +52 -44
  94. package/dist/Popover/PopoverContentContextual.torp +49 -37
  95. package/dist/Popover/PopoverOverlay.torp +13 -4
  96. package/dist/Popover/PopoverTrigger.torp +12 -3
  97. package/dist/Popover/PopoverTriggerContextual.torp +12 -3
  98. package/dist/Popover/PopoverTriggerHover.torp +14 -5
  99. package/dist/Popover/index.d.ts +191 -0
  100. package/dist/TabGroup/TabContent.torp +13 -7
  101. package/dist/TabGroup/TabGroup.torp +23 -11
  102. package/dist/TabGroup/TabHeader.torp +16 -7
  103. package/dist/TabGroup/TabList.torp +15 -6
  104. package/dist/TabGroup/TabTrigger.torp +14 -6
  105. package/dist/TabGroup/index.d.ts +150 -0
  106. package/dist/icons/ArrowDown.torp +15 -0
  107. package/dist/icons/ArrowLeft.torp +15 -0
  108. package/dist/icons/ArrowRight.torp +15 -0
  109. package/dist/icons/ArrowUp.torp +15 -0
  110. package/dist/icons/Bars.torp +17 -0
  111. package/dist/icons/Check.torp +15 -0
  112. package/dist/icons/ChevronDown.torp +15 -0
  113. package/dist/icons/ChevronLeft.torp +15 -0
  114. package/dist/icons/ChevronRight.torp +15 -0
  115. package/dist/icons/ChevronUp.torp +15 -0
  116. package/dist/icons/Cross.torp +16 -0
  117. package/dist/icons/Loading.torp +16 -0
  118. package/dist/icons/Minus.torp +15 -0
  119. package/dist/icons/Plus.torp +16 -0
  120. package/dist/icons/index.d.ts +142 -0
  121. package/dist/icons/index.js +58 -0
  122. package/dist/icons/index.js.map +1 -0
  123. package/dist/{motion.d.cts → index.d.ts} +2 -2
  124. package/dist/metafile-esm.json +1 -1
  125. package/dist/motion/fade.ts +7 -0
  126. package/dist/motion/index.d.ts +2 -0
  127. package/dist/{motion.js → motion/index.js} +1 -1
  128. package/dist/motion/index.js.map +1 -0
  129. package/dist/mount/index.d.ts +2 -0
  130. package/dist/utils/dateUtils.ts +244 -0
  131. package/dist/utils/index.d.ts +2 -0
  132. package/dist/utils/setPopoverPosition.ts +4 -0
  133. package/package.json +60 -35
  134. package/scripts/postbuild.ts +42 -5
  135. package/src/Accordion/Accordion.torp +25 -13
  136. package/src/Accordion/AccordionContent.torp +15 -8
  137. package/src/Accordion/AccordionHeader.torp +16 -7
  138. package/src/Accordion/AccordionItem.torp +24 -10
  139. package/src/Accordion/AccordionTrigger.torp +16 -8
  140. package/src/Accordion/AccordionTypes.ts +2 -0
  141. package/src/Breadcrumb/Breadcrumb.torp +42 -0
  142. package/src/Breadcrumb/BreadcrumbItem.torp +28 -0
  143. package/src/Breadcrumb/BreadcrumbSeparator.torp +25 -0
  144. package/src/Breadcrumb/index.ts +5 -0
  145. package/src/Calendar/Calendar.torp +273 -0
  146. package/src/Calendar/CalendarDay.torp +75 -0
  147. package/src/Calendar/CalendarTypes.ts +22 -0
  148. package/src/Calendar/index.ts +4 -0
  149. package/src/Dialog/Dialog.torp +16 -6
  150. package/src/Dialog/DialogBody.torp +13 -5
  151. package/src/Dialog/DialogButton.torp +18 -6
  152. package/src/Dialog/DialogFooter.torp +13 -3
  153. package/src/Dialog/DialogHeader.torp +13 -4
  154. package/src/Disclosure/Disclosure.torp +79 -0
  155. package/src/Disclosure/DisclosureContent.torp +43 -0
  156. package/src/Disclosure/DisclosureHeader.torp +43 -0
  157. package/src/Disclosure/DisclosureTrigger.torp +44 -0
  158. package/src/Disclosure/DisclosureTypes.ts +14 -0
  159. package/src/Disclosure/index.ts +6 -0
  160. package/{dist/Offscreen/Offscreen.torp → src/Drawer/Drawer.torp} +20 -19
  161. package/src/Drawer/index.ts +3 -0
  162. package/src/Form/Field.torp +93 -0
  163. package/src/Form/Form.torp +131 -0
  164. package/src/Form/FormTypes.ts +30 -0
  165. package/src/Form/Input.torp +94 -0
  166. package/src/Form/Label.torp +31 -0
  167. package/src/Form/Message.torp +35 -0
  168. package/src/Form/StandardSchemaV1.ts +70 -0
  169. package/src/Form/index.ts +7 -0
  170. package/src/Menu/Menu.torp +169 -0
  171. package/src/Menu/MenuButton.torp +64 -0
  172. package/src/Menu/MenuCheck.torp +81 -0
  173. package/src/Menu/MenuGroup.torp +29 -0
  174. package/src/Menu/MenuIndicator.torp +38 -0
  175. package/src/Menu/MenuPopout.torp +82 -0
  176. package/src/Menu/MenuPopoutContent.torp +83 -0
  177. package/src/Menu/MenuPopoutTrigger.torp +126 -0
  178. package/src/Menu/MenuRadio.torp +99 -0
  179. package/src/Menu/MenuRadioGroup.torp +97 -0
  180. package/src/Menu/MenuSeparator.torp +24 -0
  181. package/src/Menu/MenuTypes.ts +58 -0
  182. package/src/Menu/index.ts +25 -0
  183. package/src/MenuBar/MenuBar.torp +159 -0
  184. package/src/MenuBar/MenuBarItem.torp +115 -0
  185. package/src/MenuBar/MenuBarItemContent.torp +117 -0
  186. package/src/MenuBar/MenuBarItemTrigger.torp +98 -0
  187. package/src/MenuBar/MenuBarTypes.ts +36 -0
  188. package/src/MenuBar/index.ts +6 -0
  189. package/src/NavMenu/NavMenu.torp +15 -6
  190. package/src/NavMenu/NavMenuButton.torp +14 -7
  191. package/src/NavMenu/NavMenuElement.torp +16 -8
  192. package/src/NavMenu/NavMenuGroup.torp +14 -5
  193. package/src/NavMenu/NavMenuGroupHeader.torp +14 -6
  194. package/src/NavMenu/NavMenuLink.torp +18 -10
  195. package/src/NavMenu/NavMenuPopout.torp +13 -5
  196. package/src/NavMenu/NavMenuPopoutButton.torp +17 -11
  197. package/src/NavMenu/NavMenuPopoutContent.torp +42 -30
  198. package/src/NavMenu/NavMenuSeparator.torp +12 -3
  199. package/src/Notification/Notification.torp +58 -0
  200. package/src/Notification/NotificationClose.torp +37 -0
  201. package/src/Notification/NotificationContainer.torp +24 -0
  202. package/src/Notification/NotificationContent.torp +23 -0
  203. package/src/Notification/NotificationTypes.ts +5 -0
  204. package/src/Notification/index.ts +13 -0
  205. package/src/Notification/showNotification.ts +30 -0
  206. package/src/Popover/Popover.torp +22 -8
  207. package/src/Popover/PopoverContent.torp +41 -29
  208. package/src/Popover/PopoverContentAnchored.torp +52 -44
  209. package/src/Popover/PopoverContentContextual.torp +49 -37
  210. package/src/Popover/PopoverOverlay.torp +13 -4
  211. package/src/Popover/PopoverTrigger.torp +12 -3
  212. package/src/Popover/PopoverTriggerContextual.torp +12 -3
  213. package/src/Popover/PopoverTriggerHover.torp +14 -5
  214. package/src/Slider/Slider.torp +83 -0
  215. package/src/Slider/index.ts +3 -0
  216. package/src/TabGroup/TabContent.torp +13 -7
  217. package/src/TabGroup/TabGroup.torp +23 -11
  218. package/src/TabGroup/TabHeader.torp +16 -7
  219. package/src/TabGroup/TabList.torp +15 -6
  220. package/src/TabGroup/TabTrigger.torp +14 -6
  221. package/src/icons/ArrowDown.torp +15 -0
  222. package/src/icons/ArrowLeft.torp +15 -0
  223. package/src/icons/ArrowRight.torp +15 -0
  224. package/src/icons/ArrowUp.torp +15 -0
  225. package/src/icons/Bars.torp +17 -0
  226. package/src/icons/Check.torp +15 -0
  227. package/src/icons/ChevronDown.torp +15 -0
  228. package/src/icons/ChevronLeft.torp +15 -0
  229. package/src/icons/ChevronRight.torp +15 -0
  230. package/src/icons/ChevronUp.torp +15 -0
  231. package/src/icons/Cross.torp +16 -0
  232. package/src/icons/Loading.torp +16 -0
  233. package/src/icons/Minus.torp +15 -0
  234. package/src/icons/Plus.torp +16 -0
  235. package/src/icons/index.ts +31 -0
  236. package/src/motion/fade.ts +2 -2
  237. package/src/motion/index.ts +3 -0
  238. package/src/utils/dateUtils.ts +244 -0
  239. package/src/utils/setPopoverPosition.ts +4 -0
  240. package/test/Accordion/components/AccordionAccessibility.torp +2 -0
  241. package/test/Accordion/components/AccordionKeyboard.torp +2 -0
  242. package/test/Accordion/components/AccordionList.torp +2 -0
  243. package/test/Accordion/components/AccordionMultiple.torp +2 -0
  244. package/test/Accordion/components/AccordionSingle.torp +2 -0
  245. package/test/Breadcrumb/accessibility.test.ts +29 -0
  246. package/test/Breadcrumb/basic.test.ts +17 -0
  247. package/test/Breadcrumb/components/BreadcrumbTest.torp +15 -0
  248. package/test/Calendar/components/CalendarTest.torp +9 -0
  249. package/test/Calendar/default.test.ts +18 -0
  250. package/test/Dialog/components/DialogAccessibility.torp +5 -5
  251. package/test/Disclosure/accessibility.test.ts +31 -0
  252. package/test/Disclosure/components/DisclosureTest.torp +19 -0
  253. package/test/Disclosure/default.test.ts +15 -0
  254. package/test/Disclosure/toggle.test.ts +20 -0
  255. package/test/Form/components/FieldTest.torp +25 -0
  256. package/test/Form/components/FormTest.torp +31 -0
  257. package/test/Form/components/InputTest.torp +9 -0
  258. package/test/Form/field.test.ts +34 -0
  259. package/test/Form/form.test.ts +34 -0
  260. package/test/Form/input.test.ts +19 -0
  261. package/test/Menu/accessibility.test.ts +83 -0
  262. package/test/Menu/basic.test.ts +58 -0
  263. package/test/Menu/components/MenuAccessibility.torp +59 -0
  264. package/test/Menu/components/MenuBasic.torp +58 -0
  265. package/test/MenuBar/accessibility.test.ts +87 -0
  266. package/test/MenuBar/basic.test.ts +22 -0
  267. package/test/MenuBar/components/MenuBarAccessibility.torp +34 -0
  268. package/test/MenuBar/components/MenuBarBasic.torp +25 -0
  269. package/test/NavMenu/basic.test.ts +1 -1
  270. package/test/NavMenu/components/{NavMenu.torp → NavMenuBasic.torp} +1 -1
  271. package/test/Notification/accessibility.test.ts +21 -0
  272. package/test/Notification/components/CustomNotification.torp +14 -0
  273. package/test/Notification/components/NotificationAccessibility.torp +14 -0
  274. package/test/Notification/components/NotificationFunction.torp +20 -0
  275. package/test/Notification/components/NotificationVariable.torp +22 -0
  276. package/test/Notification/function.test.ts +25 -0
  277. package/test/Notification/variable.test.ts +25 -0
  278. package/test/Popover/components/CustomDialog.torp +25 -0
  279. package/test/Popover/components/PromptFunction.torp +19 -0
  280. package/test/Popover/components/PromptVariable.torp +38 -0
  281. package/test/Popover/function.test.ts +26 -0
  282. package/test/Popover/variable.test.ts +27 -0
  283. package/test/TabGroup/components/TabGroupAccessibility.torp +2 -0
  284. package/test/TabGroup/components/TabGroupKeyboard.torp +2 -0
  285. package/test/TabGroup/components/TabGroupList.torp +2 -0
  286. package/test/TabGroup/components/TabGroupTrigger.torp +2 -0
  287. package/tsup.config.ts +12 -4
  288. package/dist/Accordion/index.cjs +0 -53
  289. package/dist/Accordion/index.cjs.map +0 -1
  290. package/dist/Dialog/index.cjs +0 -53
  291. package/dist/Dialog/index.cjs.map +0 -1
  292. package/dist/NavMenu/index.cjs +0 -78
  293. package/dist/NavMenu/index.cjs.map +0 -1
  294. package/dist/Offscreen/index.cjs +0 -33
  295. package/dist/Offscreen/index.cjs.map +0 -1
  296. package/dist/Offscreen/index.js +0 -6
  297. package/dist/Popover/index.cjs +0 -222
  298. package/dist/Popover/index.cjs.map +0 -1
  299. package/dist/TabGroup/index.cjs +0 -53
  300. package/dist/TabGroup/index.cjs.map +0 -1
  301. package/dist/metafile-cjs.json +0 -1
  302. package/dist/motion.cjs +0 -34
  303. package/dist/motion.cjs.map +0 -1
  304. package/dist/motion.d.ts +0 -6
  305. package/dist/motion.js.map +0 -1
  306. package/src/Accordion.ts +0 -7
  307. package/src/Dialog.ts +0 -7
  308. package/src/NavMenu.ts +0 -23
  309. package/src/Offscreen/index.ts +0 -3
  310. package/src/Popover.ts +0 -29
  311. package/src/TabGroup.ts +0 -7
  312. package/src/motion.ts +0 -3
  313. /package/dist/{Offscreen → Breadcrumb}/index.js.map +0 -0
@@ -0,0 +1,93 @@
1
+ import {
2
+ FormContextName,
3
+ FieldContextName,
4
+ type ValidationIssue,
5
+ type FormContext,
6
+ type FieldContext,
7
+ } from "./FormTypes";
8
+ import { StandardSchemaV1 } from "./StandardSchemaV1"
9
+
10
+ interface FieldProps {
11
+ /** An ID for the root element */
12
+ id?: string;
13
+ /** Class name(s) for the root element */
14
+ class?: string,
15
+ /** The name of the data that this field contains */
16
+ name: string,
17
+ /** The data value that this field is bound to */
18
+ value?: any,
19
+ /** The schema validator to use for this form */
20
+ schema?: StandardSchemaV1,
21
+ };
22
+
23
+ /**
24
+ * A field in a form which automatically links up a Label and/or an input
25
+ * component and/or a Message.
26
+ */
27
+ export default function Field($props: FieldProps) {
28
+ $props ??= $watch({ name: "" });
29
+
30
+ let $state = $watch({
31
+ inputId: "-1",
32
+ value: $props.value,
33
+ issues: [] as ValidationIssue[],
34
+ get valid() {
35
+ return this.issues.find((i) => i.path === $props.name) === undefined;
36
+ },
37
+ get message() {
38
+ return this.issues.filter((i) => i.path === $props.name).map((i) => i.message).join("\n");
39
+ },
40
+ });
41
+
42
+ // Binding
43
+ $run(() => {
44
+ $props.value = $state.value;
45
+ });
46
+
47
+ // Get stuff out of the FormContext, if applicable
48
+ const formContext = $context[FormContextName] as FormContext;
49
+ if (formContext) {
50
+ $run(() => {
51
+ $state.issues = formContext.state.issues;
52
+ });
53
+ }
54
+
55
+ // Set the context to pass down to Labels, Inputs and Messages
56
+ let context: FieldContext = {
57
+ name: $props.name,
58
+ state: $state,
59
+ validate: handleValidate
60
+ };
61
+ $context[FieldContextName] = context;
62
+
63
+ async function validate() {
64
+ if ($props.schema) {
65
+ const validator = $props.schema["~standard"];
66
+ let result = validator.validate($props.value);
67
+ if (result instanceof Promise) {
68
+ result = await result;
69
+ }
70
+ let issues: ValidationIssue[] = [];
71
+ if (result.issues) {
72
+ for (let issue of result.issues) {
73
+ issues.push({ path: $props.name, message: issue.message });
74
+ }
75
+ }
76
+ $state.issues = $state.issues.filter((e) => e.path !== $props.name).concat(issues);
77
+ }
78
+ }
79
+
80
+ async function handleValidate() {
81
+ await validate();
82
+ }
83
+
84
+ @render {
85
+ <div
86
+ {$props.id}
87
+ class={`torp-field ${$props.class ?? ""}`.trim()}
88
+ data-valid={$state.valid ? "valid" : "invalid"}
89
+ >
90
+ <slot />
91
+ </div>
92
+ }
93
+ }
@@ -0,0 +1,131 @@
1
+ import { FormContextName, type ValidationIssue, type FormContext } from "./FormTypes";
2
+ import { StandardSchemaV1 } from "./StandardSchemaV1"
3
+
4
+ interface FormProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Class name(s) for the root element */
8
+ class?: string,
9
+ /** The data to display in the form */
10
+ data?: any,
11
+ /** Where to post the form's data **/
12
+ action?: string,
13
+ /** The schema validator to use for this form */
14
+ schema?: StandardSchemaV1,
15
+ onsubmit?: (e: SubmitEvent) => boolean | void | Promise<boolean | void>,
16
+ };
17
+
18
+ /**
19
+ * A form for submitting values.
20
+ */
21
+ export default function Form($props: FormProps) {
22
+ $props ??= $watch({});
23
+
24
+ let $state = $watch({
25
+ data: $props.data ?? {},
26
+ issues: [] as ValidationIssue[],
27
+ get valid() {
28
+ return this.issues.length === 0;
29
+ }
30
+ });
31
+
32
+ let submitted = false;
33
+
34
+ // TODO: Load context from build with a submit method that submits to a form action
35
+
36
+ // Set the context to pass down to Fields
37
+ let context: FormContext = {
38
+ schema: $props.schema,
39
+ state: $state,
40
+ validate: handleValidate
41
+ };
42
+
43
+ $context[FormContextName] = context;
44
+
45
+ async function validate() {
46
+ if ($props.schema) {
47
+ const validator = $props.schema["~standard"];
48
+ let result = validator.validate($props.data);
49
+ if (result instanceof Promise) {
50
+ result = await result;
51
+ }
52
+ let issues: ValidationIssue[] = [];
53
+ if (result.issues) {
54
+ for (let issue of result.issues) {
55
+ if (issue.path) {
56
+ // TODO: arrays with `[0]`
57
+ issues.push({ path: issue.path.join("."), message: issue.message });
58
+ }
59
+ }
60
+ }
61
+ $state.issues = issues;
62
+ }
63
+ }
64
+
65
+ async function handleSubmit(e: SubmitEvent) {
66
+ // Validate, and proceed with submission if there are no errors
67
+ await validate();
68
+ let proceed = $state.valid;
69
+
70
+ // Changing inputs from now on will cause revalidation
71
+ submitted = true;
72
+
73
+ // If validation failed, don't continue
74
+ if (!proceed) {
75
+ e.preventDefault();
76
+ return false;
77
+ }
78
+
79
+ // Dispatch the submit event for the user to handle
80
+ if ($props.onsubmit) {
81
+ let userSubmit = $props.onsubmit(e);
82
+ if (userSubmit instanceof Promise) {
83
+ userSubmit = await userSubmit;
84
+ }
85
+ proceed = userSubmit !== false && !e.defaultPrevented;
86
+ }
87
+
88
+ // If the user handled the submit, don't continue
89
+ if (!proceed) {
90
+ e.preventDefault();
91
+ return false;
92
+ }
93
+
94
+ // If there's a @torpor/build context, call its submit event
95
+ let buildContext = $context["TorporBuildContext"];
96
+ if (buildContext?.onformsubmit) {
97
+ let buildSubmit = buildContext.onformsubmit(e);
98
+ if (buildSubmit instanceof Promise) {
99
+ buildSubmit = await buildSubmit;
100
+ }
101
+ proceed = buildSubmit !== false && !e.defaultPrevented;
102
+ }
103
+
104
+ // If @torpor/build handled the submit, don't continue
105
+ if (!proceed) {
106
+ e.preventDefault();
107
+ return false;
108
+ }
109
+
110
+ // Otherwise just let normal form submission go ahead...
111
+ }
112
+
113
+ async function handleValidate() {
114
+ if (submitted) {
115
+ await validate();
116
+ }
117
+ }
118
+
119
+ @render {
120
+ <form
121
+ {$props.id}
122
+ class={`torp-form ${$props.class ?? ""}`.trim()}
123
+ method="POST"
124
+ onsubmit={handleSubmit}
125
+ @// Set novalidate only if JS is enabled (to override HTML validation)
126
+ onmount={(e) => e.novalidate = true }
127
+ >
128
+ <slot />
129
+ </form>
130
+ }
131
+ }
@@ -0,0 +1,30 @@
1
+ import { type StandardSchemaV1 } from "./StandardSchemaV1";
2
+
3
+ export const FormContextName: unique symbol = Symbol("Form");
4
+ export const FieldContextName: unique symbol = Symbol("Field");
5
+
6
+ export interface ValidationIssue {
7
+ path: string;
8
+ message: string;
9
+ }
10
+
11
+ export interface FormContext {
12
+ schema?: StandardSchemaV1;
13
+ state: {
14
+ issues: ValidationIssue[];
15
+ };
16
+ validate: () => void;
17
+ }
18
+
19
+ export interface FieldContext {
20
+ name: string;
21
+ schema?: StandardSchemaV1;
22
+ state: {
23
+ inputId: string;
24
+ value?: any;
25
+ issues: ValidationIssue[];
26
+ valid: boolean;
27
+ message: string;
28
+ };
29
+ validate: () => void;
30
+ }
@@ -0,0 +1,94 @@
1
+ import getId from "../utils/getId";
2
+ import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
+
4
+ interface InputProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Class name(s) for the root element */
8
+ class?: string,
9
+ /** The input type */
10
+ type?: "text" | "number" | "password" | "email" | "url" | "tel",
11
+ /** The name of the input's form data field */
12
+ name?: 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
+ };
24
+
25
+ /**
26
+ * A standard input in a form.
27
+ */
28
+ export default function Input($props: InputProps) {
29
+ $props ??= $watch({});
30
+ $props.id ??= getId();
31
+ $props.type ??= "text";
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
+ $props.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,
45
+ value: $props.value,
46
+ errors: {} as Record<string, string>,
47
+ valid: false,
48
+ message: "",
49
+ });
50
+
51
+ // Binding
52
+ $run(() => {
53
+ $props.value = $state.value;
54
+ });
55
+
56
+ // Set the input ID now that we know it and it will get linked with the associated label
57
+ $state.inputId = $props.id;
58
+
59
+ // NOTE: Adapted this from https://stackoverflow.com/a/57393751,
60
+ // not sure if it's still a good way to do things
61
+ function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
62
+ $state.value = $props!.type?.match(/^(number|range)$/) ? +e.currentTarget.value : e.currentTarget.value;
63
+ if (formContext) {
64
+ formContext.validate();
65
+ }
66
+ if (fieldContext && blurred) {
67
+ fieldContext.validate();
68
+ }
69
+ }
70
+
71
+ function handleBlur() {
72
+ if (fieldContext) {
73
+ fieldContext.validate();
74
+ }
75
+ blurred = true;
76
+ }
77
+
78
+ @render {
79
+ <input
80
+ {$props.id}
81
+ class={`torp-input ${$props.class ?? ""}`.trim()}
82
+ {$props.name}
83
+ &value={$state.value}
84
+ {$props.type}
85
+ {$props.placeholder}
86
+ {$props.readonly}
87
+ {$props.maxlength}
88
+ aria-label={$props.ariaLabel}
89
+ data-valid={$state.valid ? "valid" : "invalid"}
90
+ oninput={handleInput}
91
+ onblur={handleBlur}
92
+ />
93
+ }
94
+ }
@@ -0,0 +1,31 @@
1
+ import { FieldContextName, type FieldContext } from "./FormTypes";
2
+
3
+ interface LabelProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Class name(s) for the root element */
7
+ class?: string,
8
+ }
9
+
10
+ /**
11
+ * A label to describe the contents of an input in a form.
12
+ */
13
+ export default function Label($props?: LabelProps) {
14
+ // Get stuff out of the FieldContext
15
+ const context = $context[FieldContextName] as FieldContext;
16
+ if (!context) {
17
+ throw new Error("Label must be contained within a Field");
18
+ }
19
+ let { state: $state } = context;
20
+
21
+ @render {
22
+ <label
23
+ {$props?.id}
24
+ class={`torp-label ${$props?.class ?? ""}`.trim()}
25
+ for={$state.inputId}
26
+ data-valid={$state.valid ? "valid" : "invalid"}
27
+ >
28
+ <slot />
29
+ </label>
30
+ }
31
+ }
@@ -0,0 +1,35 @@
1
+ import { FieldContextName, type FieldContext } from "./FormTypes";
2
+
3
+ interface MessageProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Class name(s) for the root element */
7
+ class?: string,
8
+ }
9
+
10
+ /**
11
+ * A validation message to show when the contents of an input component have
12
+ * problems that need to be fixed.
13
+ */
14
+ export default function Message($props?: MessageProps) {
15
+ // Get stuff out of the FieldContext
16
+ const context = $context[FieldContextName] as FieldContext;
17
+ if (!context) {
18
+ throw new Error("Message must be contained within a Field");
19
+ }
20
+ const { state: $state } = context;
21
+
22
+ @render {
23
+ @if ($state.message) {
24
+ <div
25
+ {$props?.id}
26
+ class={`torp-message ${$props?.class ?? ""}`.trim()}
27
+ data-valid={$state.valid ? "valid" : "invalid"}
28
+ >
29
+ <slot name="message">
30
+ {$state.message}
31
+ </slot>
32
+ </div>
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,70 @@
1
+ // From https://github.com/standard-schema/standard-schema
2
+
3
+ /** The Standard Schema interface. */
4
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
5
+ /** The Standard Schema properties. */
6
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
7
+ }
8
+
9
+ export declare namespace StandardSchemaV1 {
10
+ /** The Standard Schema properties interface. */
11
+ export interface Props<Input = unknown, Output = Input> {
12
+ /** The version number of the standard. */
13
+ readonly version: 1;
14
+ /** The vendor name of the schema library. */
15
+ readonly vendor: string;
16
+ /** Validates unknown input values. */
17
+ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
18
+ /** Inferred types associated with the schema. */
19
+ readonly types?: Types<Input, Output> | undefined;
20
+ }
21
+
22
+ /** The result interface of the validate function. */
23
+ export type Result<Output> = SuccessResult<Output> | FailureResult;
24
+
25
+ /** The result interface if validation succeeds. */
26
+ export interface SuccessResult<Output> {
27
+ /** The typed output value. */
28
+ readonly value: Output;
29
+ /** The non-existent issues. */
30
+ readonly issues?: undefined;
31
+ }
32
+
33
+ /** The result interface if validation fails. */
34
+ export interface FailureResult {
35
+ /** The issues of failed validation. */
36
+ readonly issues: ReadonlyArray<Issue>;
37
+ }
38
+
39
+ /** The issue interface of the failure output. */
40
+ export interface Issue {
41
+ /** The error message of the issue. */
42
+ readonly message: string;
43
+ /** The path of the issue, if any. */
44
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
45
+ }
46
+
47
+ /** The path segment interface of the issue. */
48
+ export interface PathSegment {
49
+ /** The key representing a path segment. */
50
+ readonly key: PropertyKey;
51
+ }
52
+
53
+ /** The Standard Schema types interface. */
54
+ export interface Types<Input = unknown, Output = Input> {
55
+ /** The input type of the schema. */
56
+ readonly input: Input;
57
+ /** The output type of the schema. */
58
+ readonly output: Output;
59
+ }
60
+
61
+ /** Infers the input type of a Standard Schema. */
62
+ export type InferInput<Schema extends StandardSchemaV1> = NonNullable<
63
+ Schema["~standard"]["types"]
64
+ >["input"];
65
+
66
+ /** Infers the output type of a Standard Schema. */
67
+ export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<
68
+ Schema["~standard"]["types"]
69
+ >["output"];
70
+ }
@@ -0,0 +1,117 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+
4
+ interface FieldProps {
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 data that this field contains */
10
+ name: string,
11
+ /** The data value that this field is bound to */
12
+ value?: any,
13
+ /** The schema validator to use for this form */
14
+ schema?: StandardSchemaV1,
15
+ }
16
+
17
+ /**
18
+ * A field in a form which automatically links up a Label and/or an input
19
+ * component and/or a Message.
20
+ */
21
+ declare function Field(
22
+ $parent: ParentNode,
23
+ $anchor: Node | null,
24
+ $props: FieldProps,
25
+ $context?: Record<PropertyKey, any>,
26
+ $slots?: Record<string, SlotRender>,
27
+ ): void;
28
+
29
+
30
+
31
+ interface FormProps {
32
+ /** An ID for the root element */
33
+ id?: string;
34
+ /** Class name(s) for the root element */
35
+ class?: string,
36
+ /** The data to display in the form */
37
+ data?: any,
38
+ /** Where to post the form's data **/
39
+ action?: string,
40
+ /** The schema validator to use for this form */
41
+ schema?: StandardSchemaV1,
42
+ onsubmit?: (e: SubmitEvent) => boolean | void | Promise<boolean | void>,
43
+ }
44
+
45
+ /**
46
+ * A form for submitting values.
47
+ */
48
+ declare function Form(
49
+ $parent: ParentNode,
50
+ $anchor: Node | null,
51
+ $props: FormProps,
52
+ $context?: Record<PropertyKey, any>,
53
+ $slots?: Record<string, SlotRender>,
54
+ ): void;
55
+
56
+
57
+
58
+ interface InputProps {
59
+ /** An ID for the root element */
60
+ id?: string;
61
+ /** Class name(s) for the root element */
62
+ class?: string,
63
+ /** The input type */
64
+ type?: "text" | "number" | "password" | "email" | "url" | "tel",
65
+ /** The name of the input's form data field */
66
+ name?: string,
67
+ /** The data value that this field is bound to */
68
+ value?: any,
69
+ /** Text to display when the input value is empty */
70
+ placeholder?: string,
71
+ /** Whether the input should be readonly */
72
+ readonly?: boolean,
73
+ /** The maximum number of characters allowed for the input value */
74
+ maxlength?: number,
75
+ /** An ARIA label for describing the input to screen readers when there is no associated label element */
76
+ ariaLabel?: string,
77
+ }
78
+
79
+ /**
80
+ * A standard input in a form.
81
+ */
82
+ declare function Input(
83
+ $parent: ParentNode,
84
+ $anchor: Node | null,
85
+ $props: InputProps,
86
+ $context?: Record<PropertyKey, any>,
87
+ $slots?: Record<string, SlotRender>,
88
+ ): void;
89
+
90
+
91
+
92
+ /**
93
+ * A label to describe the contents of an input in a form.
94
+ */
95
+ declare function Label(
96
+ $parent: ParentNode,
97
+ $anchor: Node | null,
98
+ $props?: LabelProps,
99
+ $context?: Record<PropertyKey, any>,
100
+ $slots?: Record<string, SlotRender>,
101
+ ): void;
102
+
103
+
104
+
105
+ /**
106
+ * A validation message to show when the contents of an input component have
107
+ * problems that need to be fixed.
108
+ */
109
+ declare function Message(
110
+ $parent: ParentNode,
111
+ $anchor: Node | null,
112
+ $props?: MessageProps,
113
+ $context?: Record<PropertyKey, any>,
114
+ $slots?: Record<string, SlotRender>,
115
+ ): void;
116
+
117
+ export { Field, Form, Input, Label, Message };
@@ -0,0 +1,22 @@
1
+ // src/Form/Field.torp
2
+ import Field_default from "./Field.torp";
3
+
4
+ // src/Form/Form.torp
5
+ import Form_default from "./Form.torp";
6
+
7
+ // src/Form/Input.torp
8
+ import Input_default from "./Input.torp";
9
+
10
+ // src/Form/Label.torp
11
+ import Label_default from "./Label.torp";
12
+
13
+ // src/Form/Message.torp
14
+ import Message_default from "./Message.torp";
15
+ export {
16
+ Field_default as Field,
17
+ Form_default as Form,
18
+ Input_default as Input,
19
+ Label_default as Label,
20
+ Message_default as Message
21
+ };
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}