@torpor/ui 0.3.4 → 0.3.6

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 (226) hide show
  1. package/dist/Form/Form.torp +1 -0
  2. package/dist/metafile-esm.json +1 -1
  3. package/package.json +12 -9
  4. package/CHANGELOG.md +0 -338
  5. package/scripts/postbuild.ts +0 -127
  6. package/src/Accordion/Accordion.torp +0 -230
  7. package/src/Accordion/AccordionContent.torp +0 -46
  8. package/src/Accordion/AccordionHeader.torp +0 -45
  9. package/src/Accordion/AccordionItem.torp +0 -101
  10. package/src/Accordion/AccordionTrigger.torp +0 -70
  11. package/src/Accordion/AccordionTypes.ts +0 -36
  12. package/src/Accordion/index.ts +0 -7
  13. package/src/Breadcrumb/Breadcrumb.torp +0 -45
  14. package/src/Breadcrumb/BreadcrumbItem.torp +0 -30
  15. package/src/Breadcrumb/BreadcrumbSeparator.torp +0 -27
  16. package/src/Breadcrumb/index.ts +0 -5
  17. package/src/Calendar/Calendar.torp +0 -277
  18. package/src/Calendar/CalendarDay.torp +0 -78
  19. package/src/Calendar/CalendarTypes.ts +0 -22
  20. package/src/Calendar/index.ts +0 -4
  21. package/src/Contextual/Contextual.torp +0 -129
  22. package/src/Contextual/ContextualContent.torp +0 -112
  23. package/src/Contextual/ContextualTrigger.torp +0 -70
  24. package/src/Contextual/ContextualTypes.ts +0 -30
  25. package/src/Contextual/index.ts +0 -5
  26. package/src/Dialog/Dialog.torp +0 -136
  27. package/src/Dialog/DialogBody.torp +0 -44
  28. package/src/Dialog/DialogButton.torp +0 -66
  29. package/src/Dialog/DialogFooter.torp +0 -25
  30. package/src/Dialog/DialogHeader.torp +0 -45
  31. package/src/Dialog/DialogTypes.ts +0 -17
  32. package/src/Dialog/index.ts +0 -7
  33. package/src/Disclosure/Disclosure.torp +0 -82
  34. package/src/Disclosure/DisclosureContent.torp +0 -46
  35. package/src/Disclosure/DisclosureHeader.torp +0 -45
  36. package/src/Disclosure/DisclosureTrigger.torp +0 -47
  37. package/src/Disclosure/DisclosureTypes.ts +0 -13
  38. package/src/Disclosure/index.ts +0 -6
  39. package/src/Drawer/Drawer.torp +0 -127
  40. package/src/Drawer/DrawerContent.torp +0 -83
  41. package/src/Drawer/DrawerOverlay.torp +0 -42
  42. package/src/Drawer/DrawerTrigger.torp +0 -39
  43. package/src/Drawer/DrawerTypes.ts +0 -24
  44. package/src/Drawer/index.ts +0 -6
  45. package/src/Form/CheckBox.torp +0 -94
  46. package/src/Form/Field.torp +0 -95
  47. package/src/Form/File.torp +0 -102
  48. package/src/Form/Form.torp +0 -146
  49. package/src/Form/FormTypes.ts +0 -35
  50. package/src/Form/Hidden.torp +0 -25
  51. package/src/Form/Input.torp +0 -112
  52. package/src/Form/Label.torp +0 -35
  53. package/src/Form/Message.torp +0 -39
  54. package/src/Form/Option.torp +0 -28
  55. package/src/Form/Select.torp +0 -101
  56. package/src/Form/StandardSchemaV1.ts +0 -70
  57. package/src/Form/TextArea.torp +0 -105
  58. package/src/Form/index.ts +0 -13
  59. package/src/Menu/Menu.torp +0 -174
  60. package/src/Menu/MenuButton.torp +0 -74
  61. package/src/Menu/MenuCheck.torp +0 -89
  62. package/src/Menu/MenuGroup.torp +0 -31
  63. package/src/Menu/MenuIndicator.torp +0 -40
  64. package/src/Menu/MenuPopout.torp +0 -83
  65. package/src/Menu/MenuPopoutContent.torp +0 -84
  66. package/src/Menu/MenuPopoutTrigger.torp +0 -128
  67. package/src/Menu/MenuRadio.torp +0 -103
  68. package/src/Menu/MenuRadioGroup.torp +0 -109
  69. package/src/Menu/MenuSeparator.torp +0 -26
  70. package/src/Menu/MenuTypes.ts +0 -60
  71. package/src/Menu/index.ts +0 -25
  72. package/src/MenuBar/MenuBar.torp +0 -162
  73. package/src/MenuBar/MenuBarItem.torp +0 -116
  74. package/src/MenuBar/MenuBarItemContent.torp +0 -119
  75. package/src/MenuBar/MenuBarItemTrigger.torp +0 -99
  76. package/src/MenuBar/MenuBarTypes.ts +0 -36
  77. package/src/MenuBar/index.ts +0 -6
  78. package/src/Modal/Modal.torp +0 -122
  79. package/src/Modal/ModalContent.torp +0 -81
  80. package/src/Modal/ModalOverlay.torp +0 -42
  81. package/src/Modal/ModalTrigger.torp +0 -54
  82. package/src/Modal/ModalTypes.ts +0 -22
  83. package/src/Modal/index.ts +0 -10
  84. package/src/Modal/showModal.ts +0 -19
  85. package/src/Modal/showPrompt.ts +0 -23
  86. package/src/NavMenu/NavMenu.torp +0 -190
  87. package/src/NavMenu/NavMenuButton.torp +0 -55
  88. package/src/NavMenu/NavMenuElement.torp +0 -55
  89. package/src/NavMenu/NavMenuGroup.torp +0 -41
  90. package/src/NavMenu/NavMenuGroupHeader.torp +0 -41
  91. package/src/NavMenu/NavMenuLink.torp +0 -60
  92. package/src/NavMenu/NavMenuPopout.torp +0 -89
  93. package/src/NavMenu/NavMenuPopoutButton.torp +0 -129
  94. package/src/NavMenu/NavMenuPopoutContent.torp +0 -92
  95. package/src/NavMenu/NavMenuSeparator.torp +0 -24
  96. package/src/NavMenu/NavMenuTypes.ts +0 -32
  97. package/src/NavMenu/index.ts +0 -23
  98. package/src/Notification/Notification.torp +0 -71
  99. package/src/Notification/NotificationClose.torp +0 -39
  100. package/src/Notification/NotificationContainer.torp +0 -26
  101. package/src/Notification/NotificationContent.torp +0 -25
  102. package/src/Notification/NotificationTypes.ts +0 -5
  103. package/src/Notification/index.ts +0 -13
  104. package/src/Notification/showNotification.ts +0 -30
  105. package/src/Popover/Popover.torp +0 -129
  106. package/src/Popover/PopoverContent.torp +0 -117
  107. package/src/Popover/PopoverTrigger.torp +0 -62
  108. package/src/Popover/PopoverTypes.ts +0 -30
  109. package/src/Popover/index.ts +0 -6
  110. package/src/Popover/showPopover.ts +0 -24
  111. package/src/Slider/Slider.torp +0 -84
  112. package/src/Slider/index.ts +0 -3
  113. package/src/TabGroup/TabContent.torp +0 -49
  114. package/src/TabGroup/TabGroup.torp +0 -250
  115. package/src/TabGroup/TabGroupTypes.ts +0 -44
  116. package/src/TabGroup/TabHeader.torp +0 -81
  117. package/src/TabGroup/TabList.torp +0 -61
  118. package/src/TabGroup/TabTrigger.torp +0 -76
  119. package/src/TabGroup/index.ts +0 -7
  120. package/src/ambient.d.ts +0 -7
  121. package/src/icons/ArrowDown.torp +0 -18
  122. package/src/icons/ArrowLeft.torp +0 -18
  123. package/src/icons/ArrowRight.torp +0 -18
  124. package/src/icons/ArrowUp.torp +0 -18
  125. package/src/icons/Bars.torp +0 -20
  126. package/src/icons/Check.torp +0 -18
  127. package/src/icons/ChevronDown.torp +0 -18
  128. package/src/icons/ChevronLeft.torp +0 -18
  129. package/src/icons/ChevronRight.torp +0 -18
  130. package/src/icons/ChevronUp.torp +0 -18
  131. package/src/icons/Cross.torp +0 -19
  132. package/src/icons/IconTypes.ts +0 -10
  133. package/src/icons/Loading.torp +0 -19
  134. package/src/icons/Minus.torp +0 -18
  135. package/src/icons/Plus.torp +0 -19
  136. package/src/icons/index.ts +0 -31
  137. package/src/motion/fade.ts +0 -16
  138. package/src/motion/grow.ts +0 -23
  139. package/src/motion/index.ts +0 -6
  140. package/src/motion/measure.ts +0 -28
  141. package/src/motion/slide.ts +0 -45
  142. package/src/mount/clickPopover.ts +0 -51
  143. package/src/mount/contextPopover.ts +0 -51
  144. package/src/mount/disableScroll.ts +0 -28
  145. package/src/mount/hoverPopover.ts +0 -58
  146. package/src/mount/hoverTrigger.ts +0 -59
  147. package/src/mount/trapFocus.ts +0 -58
  148. package/src/utils/dateUtils.ts +0 -244
  149. package/src/utils/documentEvents.ts +0 -17
  150. package/src/utils/getId.ts +0 -6
  151. package/src/utils/getScrollParent.ts +0 -22
  152. package/src/utils/setContextPosition.ts +0 -31
  153. package/src/utils/setPopoverPosition.ts +0 -142
  154. package/test/Accordion/accessibility.test.ts +0 -59
  155. package/test/Accordion/components/AccordionAccessibility.torp +0 -44
  156. package/test/Accordion/components/AccordionKeyboard.torp +0 -32
  157. package/test/Accordion/components/AccordionList.torp +0 -23
  158. package/test/Accordion/components/AccordionMultiple.torp +0 -32
  159. package/test/Accordion/components/AccordionSingle.torp +0 -32
  160. package/test/Accordion/for-items.test.ts +0 -52
  161. package/test/Accordion/keyboard.test.ts +0 -64
  162. package/test/Accordion/multiple-item-toggle.test.ts +0 -32
  163. package/test/Accordion/multiple-item-value.test.ts +0 -17
  164. package/test/Accordion/single-item-toggle.test.ts +0 -31
  165. package/test/Accordion/single-item-value.test.ts +0 -17
  166. package/test/Breadcrumb/accessibility.test.ts +0 -29
  167. package/test/Breadcrumb/basic.test.ts +0 -17
  168. package/test/Breadcrumb/components/BreadcrumbTest.torp +0 -15
  169. package/test/Calendar/components/CalendarTest.torp +0 -9
  170. package/test/Calendar/default.test.ts +0 -18
  171. package/test/Dialog/accessibility-alert.test.ts +0 -40
  172. package/test/Dialog/accessibility-modal.test.ts +0 -52
  173. package/test/Dialog/components/DialogAccessibility.torp +0 -25
  174. package/test/Dialog/components/DialogFunction.torp +0 -36
  175. package/test/Dialog/components/DialogModal.torp +0 -30
  176. package/test/Dialog/components/DialogVariable.torp +0 -33
  177. package/test/Dialog/function.test.ts +0 -26
  178. package/test/Dialog/variable.test.ts +0 -27
  179. package/test/Disclosure/accessibility.test.ts +0 -31
  180. package/test/Disclosure/components/DisclosureTest.torp +0 -17
  181. package/test/Disclosure/default.test.ts +0 -15
  182. package/test/Disclosure/toggle.test.ts +0 -20
  183. package/test/Form/components/FieldTest.torp +0 -25
  184. package/test/Form/components/FormTest.torp +0 -31
  185. package/test/Form/components/InputTest.torp +0 -9
  186. package/test/Form/field.test.ts +0 -34
  187. package/test/Form/form.test.ts +0 -34
  188. package/test/Form/input.test.ts +0 -19
  189. package/test/Menu/accessibility.test.ts +0 -83
  190. package/test/Menu/basic.test.ts +0 -58
  191. package/test/Menu/components/MenuAccessibility.torp +0 -59
  192. package/test/Menu/components/MenuBasic.torp +0 -58
  193. package/test/MenuBar/accessibility.test.ts +0 -87
  194. package/test/MenuBar/basic.test.ts +0 -22
  195. package/test/MenuBar/components/MenuBarAccessibility.torp +0 -34
  196. package/test/MenuBar/components/MenuBarBasic.torp +0 -25
  197. package/test/NavMenu/accessibility.test.ts +0 -97
  198. package/test/NavMenu/basic.test.ts +0 -22
  199. package/test/NavMenu/components/NavMenuAccessibility.torp +0 -35
  200. package/test/NavMenu/components/NavMenuBasic.torp +0 -28
  201. package/test/Notification/accessibility.test.ts +0 -21
  202. package/test/Notification/components/CustomNotification.torp +0 -14
  203. package/test/Notification/components/NotificationAccessibility.torp +0 -14
  204. package/test/Notification/components/NotificationFunction.torp +0 -17
  205. package/test/Notification/components/NotificationVariable.torp +0 -22
  206. package/test/Notification/function.test.ts +0 -25
  207. package/test/Notification/variable.test.ts +0 -25
  208. package/test/Popover/components/CustomDialog.torp +0 -25
  209. package/test/Popover/components/PopoverFunction.torp +0 -37
  210. package/test/Popover/components/PopoverTrigger.torp +0 -36
  211. package/test/Popover/components/PopoverVariable.torp +0 -40
  212. package/test/Popover/function.test.ts +0 -21
  213. package/test/Popover/trigger.test.ts +0 -27
  214. package/test/Popover/variable.test.ts +0 -27
  215. package/test/TabGroup/accessibility.test.ts +0 -67
  216. package/test/TabGroup/components/TabGroupAccessibility.torp +0 -28
  217. package/test/TabGroup/components/TabGroupKeyboard.torp +0 -28
  218. package/test/TabGroup/components/TabGroupList.torp +0 -26
  219. package/test/TabGroup/components/TabGroupTrigger.torp +0 -28
  220. package/test/TabGroup/for-items.test.ts +0 -52
  221. package/test/TabGroup/item-trigger.test.ts +0 -33
  222. package/test/TabGroup/item-value.test.ts +0 -17
  223. package/test/TabGroup/keyboard.test.ts +0 -72
  224. package/tsconfig.json +0 -4
  225. package/tsup.config.ts +0 -37
  226. package/vitest.config.ts +0 -12
@@ -1,39 +0,0 @@
1
- import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { DrawerContextName, type DrawerContext } from "./DrawerTypes";
3
-
4
- interface DrawerTriggerProps {
5
- /** An ID for the root element */
6
- id?: string;
7
- /** Classes for the root element */
8
- class?: ClassValue,
9
- /** Styles for the root element */
10
- style?: StyleValue,
11
- }
12
-
13
- /**
14
- * The button that causes a Drawer to appear or disappear.
15
- */
16
- export default function DrawerTrigger($props: DrawerTriggerProps | undefined) {
17
- // Get stuff out of the DrawerContext
18
- const context = $context[DrawerContextName] as DrawerContext;
19
- if (!context) {
20
- throw new Error("DrawerTrigger must be contained within a Drawer");
21
- }
22
- let $state = context.state;
23
-
24
- function handleClick() {
25
- $state.visible = !$state.visible;
26
- }
27
-
28
- @render {
29
- <button
30
- type="button"
31
- {$props?.id}
32
- class={["torp-drawer-trigger", $props?.class]}
33
- {$props?.style}
34
- onclick={handleClick}
35
- >
36
- <slot />
37
- </button>
38
- }
39
- }
@@ -1,24 +0,0 @@
1
- export const DrawerContextName = "Drawer";
2
-
3
- /** The shared context for drawer components (Contextual, Prompt and Drawer) */
4
- export interface DrawerContext {
5
- handleButton: (type: "confirm" | "cancel" | undefined, value?: any) => void;
6
- // HACK: We need to be able to let children (i.e. Dialogs) override the click outside functionality
7
- // There may be a more elegant way to accomplish this too
8
- handleClickOutside?: (e: MouseEvent) => void;
9
- state: DrawerState;
10
- // HACK: focusFirstElement is necessary because if we focus the first element e.g. in a Dialog's
11
- // onMount, it will scroll the page to the focused element and mess up our careful positioning
12
- // We need to be able to manually set the order to
13
- // 1. set visible (so we have an element with content)
14
- // 2. set position (so the element is in the right place)
15
- // 3. set focus (for accessibility)
16
- // There may be a more elegant way to accomplish this!
17
- focusFirstElement?: () => void;
18
- }
19
-
20
- export interface DrawerState {
21
- visible: boolean;
22
- modal: boolean;
23
- position: "left" | "right" | "top" | "bottom";
24
- }
@@ -1,6 +0,0 @@
1
- import Drawer from "./Drawer.torp";
2
- import DrawerContent from "./DrawerContent.torp";
3
- import DrawerOverlay from "./DrawerOverlay.torp";
4
- import DrawerTrigger from "./DrawerTrigger.torp";
5
-
6
- export { Drawer, DrawerContent, DrawerOverlay, DrawerTrigger };
@@ -1,94 +0,0 @@
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
-
5
- interface CheckBoxProps {
6
- /** An ID for the root element */
7
- id?: string;
8
- /** Classes for the root element */
9
- class?: ClassValue,
10
- /** Styles for the root element */
11
- style?: StyleValue,
12
- /** The name of the checkbox's form data field */
13
- name?: string,
14
- /** Whether the checkbox is checked */
15
- checked?: boolean,
16
- /** An ARIA label for describing the checkbox to screen readers when there is no associated label element */
17
- ariaLabel?: string,
18
- /** Raised on change */
19
- onchange?: (e: Event) => void,
20
- /** Raised on input */
21
- oninput?: (e: Event) => void,
22
- };
23
-
24
- /**
25
- * A checkbox input in a form.
26
- */
27
- export default function CheckBox($props: CheckBoxProps | undefined) {
28
- let defaultId = getId();
29
-
30
- let name = $props?.name
31
-
32
- let blurred = false;
33
-
34
- // Get stuff out of the FormContext and FieldContext
35
- const formContext = $context[FormContextName] as FormContext;
36
- const fieldContext = $context[FieldContextName] as FieldContext;
37
- if (fieldContext) {
38
- name ??= fieldContext.name;
39
- }
40
-
41
- // Use the FieldContext's state, or create a new one
42
- let $state = fieldContext?.state ?? $watch({
43
- inputId: $props?.id ?? defaultId,
44
- value: $props?.checked,
45
- errors: {} as Record<string, string>,
46
- valid: false,
47
- message: "",
48
- });
49
-
50
- // Binding
51
- $run(() => {
52
- if ($props !== undefined) {
53
- $props.checked = $state.value === true;
54
- }
55
- });
56
-
57
- // Set the input ID now that we know it and it will get linked with the associated label
58
- $state.inputId = $props?.id ?? defaultId;
59
-
60
- function handleInput(e: Event) {
61
- if (formContext) {
62
- formContext.validate();
63
- }
64
- if (fieldContext && blurred) {
65
- fieldContext.validate();
66
- }
67
- if ($props?.oninput) {
68
- $props.oninput(e);
69
- }
70
- }
71
-
72
- function handleBlur() {
73
- if (fieldContext) {
74
- fieldContext.validate();
75
- }
76
- blurred = true;
77
- }
78
-
79
- @render {
80
- <input
81
- id={$props?.id ?? defaultId}
82
- class={`torp-check-box
83
- ${$props?.class ?? ""}`.trim()}
84
- type="checkbox"
85
- {name}
86
- &checked={$state.value}
87
- aria-label={$props?.ariaLabel}
88
- data-valid={$state.valid ? "valid" : "invalid"}
89
- onchange={$props?.onchange}
90
- oninput={handleInput}
91
- onblur={handleBlur}
92
- />
93
- }
94
- }
@@ -1,95 +0,0 @@
1
- import type { ClassValue, StyleValue } from "@torpor/view";
2
- import {
3
- FormContextName,
4
- FieldContextName,
5
- type ValidationIssue,
6
- type FormContext,
7
- type FieldContext,
8
- } from "./FormTypes";
9
- import { StandardSchemaV1 } from "./StandardSchemaV1"
10
-
11
- interface FieldProps {
12
- /** An ID for the root element */
13
- id?: string;
14
- /** Classes for the root element */
15
- class?: ClassValue,
16
- /** Styles for the root element */
17
- style?: StyleValue,
18
- /** The name of the data that this field contains */
19
- name: string,
20
- /** The data value that this field is bound to */
21
- value?: any,
22
- /** The schema validator to use for this form */
23
- schema?: StandardSchemaV1,
24
- };
25
-
26
- /**
27
- * A field in a form which automatically links up a Label and/or an input
28
- * component and/or a Message.
29
- */
30
- export default function Field($props: FieldProps) {
31
- let $state = $watch({
32
- inputId: "-1",
33
- value: $props.value,
34
- issues: [] as ValidationIssue[],
35
- get valid() {
36
- return this.issues.find((i) => i.path === $props.name) === undefined;
37
- },
38
- get message() {
39
- return this.issues.filter((i) => i.path === $props.name).map((i) => i.message).join("\n");
40
- },
41
- });
42
-
43
- // Binding
44
- $run(() => {
45
- $props.value = $state.value;
46
- });
47
-
48
- // Get stuff out of the FormContext, if applicable
49
- const formContext = $context[FormContextName] as FormContext;
50
- if (formContext) {
51
- $run(() => {
52
- $state.issues = formContext.state.issues;
53
- });
54
- }
55
-
56
- // Set the context to pass down to Labels, Inputs and Messages
57
- let context: FieldContext = {
58
- name: $props.name,
59
- state: $state,
60
- validate: handleValidate
61
- };
62
- $context[FieldContextName] = context;
63
-
64
- async function validate() {
65
- if ($props.schema) {
66
- const validator = $props.schema["~standard"];
67
- let result = validator.validate($props.value);
68
- if (result instanceof Promise) {
69
- result = await result;
70
- }
71
- let issues: ValidationIssue[] = [];
72
- if (result.issues) {
73
- for (let issue of result.issues) {
74
- issues.push({ path: $props.name, message: issue.message });
75
- }
76
- }
77
- $state.issues = $state.issues.filter((e) => e.path !== $props.name).concat(issues);
78
- }
79
- }
80
-
81
- async function handleValidate() {
82
- await validate();
83
- }
84
-
85
- @render {
86
- <div
87
- {$props?.id}
88
- class={["torp-field", $props?.class]}
89
- {$props?.style}
90
- data-valid={$state.valid ? "valid" : "invalid"}
91
- >
92
- <slot />
93
- </div>
94
- }
95
- }
@@ -1,102 +0,0 @@
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
-
5
- interface FileProps {
6
- /** An ID for the root element */
7
- id?: string;
8
- /** Classes for the root element */
9
- class?: ClassValue,
10
- /** Styles for the root element */
11
- style?: StyleValue,
12
- /** The name of the input's form data field */
13
- name?: string,
14
- /** The type of files that this input accepts e.g. `image/*` */
15
- accept?: string,
16
- /** Text to display when the input value is empty */
17
- placeholder?: string,
18
- /** Whether the input should be readonly */
19
- readonly?: boolean,
20
- /** The maximum number of characters allowed for the input value */
21
- maxlength?: number,
22
- /** An ARIA label for describing the input to screen readers when there is no associated label element */
23
- ariaLabel?: string,
24
- /** Raised on change */
25
- onchange?: (e: Event) => void,
26
- /** Raised on input */
27
- oninput?: (e: Event) => void,
28
- };
29
-
30
- /**
31
- * A file input in a form.
32
- */
33
- export default function File($props: FileProps | undefined) {
34
- let defaultId = getId();
35
-
36
- let name = $props?.name
37
-
38
- let blurred = false;
39
-
40
- // Get stuff out of the FormContext and FieldContext
41
- const formContext = $context[FormContextName] as FormContext;
42
- const fieldContext = $context[FieldContextName] as FieldContext;
43
- if (fieldContext) {
44
- name ??= fieldContext.name;
45
- }
46
-
47
- // This will change the form's enctype to `multipart/form-data`
48
- if (formContext) {
49
- formContext.state.hasFile = true;
50
- }
51
-
52
- // Use the FieldContext's state, or create a new one
53
- let $state = fieldContext?.state ?? $watch({
54
- inputId: $props?.id ?? defaultId,
55
- // You can't use value with a file input
56
- value: null,
57
- errors: {} as Record<string, string>,
58
- valid: false,
59
- message: "",
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 & { currentTarget: EventTarget & HTMLInputElement }) {
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-file", $props?.class]}
88
- {$props?.style}
89
- type="file"
90
- {name}
91
- {$props?.accept}
92
- {$props?.placeholder}
93
- {$props?.readonly}
94
- {$props?.maxlength}
95
- aria-label={$props?.ariaLabel}
96
- data-valid={$state.valid ? "valid" : "invalid"}
97
- onchange={$props?.onchange}
98
- oninput={handleInput}
99
- onblur={handleBlur}
100
- />
101
- }
102
- }
@@ -1,146 +0,0 @@
1
- import type { ClassValue, StyleValue } from "@torpor/view";
2
- import { FormContextName, type ValidationIssue, type FormContext } from "./FormTypes";
3
- import { StandardSchemaV1 } from "./StandardSchemaV1"
4
-
5
- interface FormProps {
6
- /** An ID for the root element */
7
- id?: string;
8
- /** Classes for the root element */
9
- class?: ClassValue,
10
- /** Styles for the root element */
11
- style?: StyleValue,
12
- /** The data to display in the form */
13
- data?: any,
14
- /** Where to post the form's data **/
15
- action?: string,
16
- /** The action to use when submitting the form, which defaults to POST */
17
- method?: string,
18
- /**
19
- * The form's encoding type e.g. `multipart/form-data` where there are file
20
- * inputs. This will be automatically set unless overridden
21
- */
22
- enctype?: string,
23
- /** The schema validator to use for this form */
24
- schema?: StandardSchemaV1,
25
- onsubmit?: (e: SubmitEvent) => boolean | void | Promise<boolean | void>,
26
- };
27
-
28
- /**
29
- * A form for submitting values.
30
- */
31
- export default function Form($props: FormProps | undefined) {
32
- let $state = $watch({
33
- data: $props?.data ?? {},
34
- hasFile: false,
35
- issues: [] as ValidationIssue[],
36
- get valid() {
37
- return this.issues.length === 0;
38
- }
39
- });
40
-
41
- let submitted = false;
42
-
43
- // TODO: Load context from build with a submit method that submits to a form action
44
-
45
- // Set the context to pass down to Fields
46
- let context: FormContext = {
47
- schema: $props?.schema,
48
- state: $state,
49
- validate: handleValidate
50
- };
51
-
52
- $context[FormContextName] = context;
53
-
54
- async function validate() {
55
- if ($props?.schema) {
56
- const validator = $props.schema["~standard"];
57
- let result = validator.validate($props.data);
58
- if (result instanceof Promise) {
59
- result = await result;
60
- }
61
- let issues: ValidationIssue[] = [];
62
- if (result.issues) {
63
- for (let issue of result.issues) {
64
- if (issue.path) {
65
- // TODO: arrays with `[0]` etc
66
- issues.push({
67
- // @ts-ignore
68
- path: issue.path.map((p) => p.key ?? p).join("."),
69
- message: issue.message
70
- });
71
- }
72
- }
73
- }
74
- $state.issues = issues;
75
- }
76
- }
77
-
78
- async function handleSubmit(e: SubmitEvent) {
79
- // Validate, and proceed with submission if there are no errors
80
- await validate();
81
- let proceed = $state.valid;
82
-
83
- // Changing inputs from now on will cause revalidation
84
- submitted = true;
85
-
86
- // If validation failed, don't continue
87
- if (!proceed) {
88
- e.preventDefault();
89
- return false;
90
- }
91
-
92
- // Dispatch the submit event for the user to handle
93
- if ($props?.onsubmit) {
94
- let userSubmit = $props.onsubmit(e);
95
- if (userSubmit instanceof Promise) {
96
- userSubmit = await userSubmit;
97
- }
98
- proceed = userSubmit !== false && !e.defaultPrevented;
99
- }
100
-
101
- // If the user handled the submit, don't continue
102
- if (!proceed) {
103
- e.preventDefault();
104
- return false;
105
- }
106
-
107
- // If there's a @torpor/build context, call its submit event
108
- let buildContext = $context["TorporBuildContext"];
109
- if (buildContext?.onformsubmit) {
110
- let buildSubmit = buildContext.onformsubmit(e);
111
- if (buildSubmit instanceof Promise) {
112
- buildSubmit = await buildSubmit;
113
- }
114
- proceed = buildSubmit !== false && !e.defaultPrevented;
115
- }
116
-
117
- // If @torpor/build handled the submit, don't continue
118
- if (!proceed) {
119
- e.preventDefault();
120
- return false;
121
- }
122
-
123
- // Otherwise just let normal form submission go ahead...
124
- }
125
-
126
- async function handleValidate() {
127
- if (submitted) {
128
- await validate();
129
- }
130
- }
131
-
132
- @render {
133
- <form
134
- {$props?.id}
135
- class={["torp-form", $props?.class]}
136
- {$props?.style}
137
- method={$props?.method ?? "POST"}
138
- enctype={$props?.enctype ?? ($state.hasFile ? "multipart/form-data" : undefined)}
139
- onsubmit={handleSubmit}
140
- @// Set novalidate only if JS is enabled (to override HTML validation)
141
- onmount={(e) => e.novalidate = true }
142
- >
143
- <slot />
144
- </form>
145
- }
146
- }
@@ -1,35 +0,0 @@
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
- /**
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;
19
- issues: ValidationIssue[];
20
- };
21
- validate: () => void;
22
- }
23
-
24
- export interface FieldContext {
25
- name: string;
26
- schema?: StandardSchemaV1;
27
- state: {
28
- inputId: string;
29
- value?: any;
30
- issues: ValidationIssue[];
31
- valid: boolean;
32
- message: string;
33
- };
34
- validate: () => void;
35
- }
@@ -1,25 +0,0 @@
1
- import getId from "../utils/getId";
2
- import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
3
-
4
- interface HiddenProps {
5
- /** An ID for the root element */
6
- id?: string;
7
- /** The name of the input's form data field */
8
- name?: string,
9
- /** The data value that this field is bound to */
10
- value?: any,
11
- };
12
-
13
- /**
14
- * A standard input in a form.
15
- */
16
- export default function Hidden($props: HiddenProps | undefined) {
17
- @render {
18
- <input
19
- {$props?.id}
20
- type="hidden"
21
- {$props?.name}
22
- {$props?.value}
23
- />
24
- }
25
- }
@@ -1,112 +0,0 @@
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
-
5
- interface InputProps {
6
- /** An ID for the root element */
7
- id?: string;
8
- /** Classes for the root element */
9
- class?: ClassValue,
10
- /** Styles for the root element */
11
- style?: StyleValue,
12
- /** The input type */
13
- type?: string, // TODO: "text" | "number" | "password" | "email" | "url" | "tel",
14
- /** The name of the input's form data field */
15
- name?: string,
16
- /** The data value that this field is bound to */
17
- value?: any,
18
- /** Text to display when the input value is empty */
19
- placeholder?: string,
20
- /** Whether the input should be readonly */
21
- readonly?: boolean,
22
- /** Whether the input's value is required */
23
- required?: boolean,
24
- /** The maximum number of characters allowed for the input value */
25
- maxlength?: number,
26
- /** An ARIA label for describing the input to screen readers when there is no associated label element */
27
- ariaLabel?: string,
28
- /** Raised on change */
29
- onchange?: (e: Event) => void,
30
- /** Raised on input */
31
- oninput?: (e: Event) => void,
32
- };
33
-
34
- /**
35
- * A standard input in a form.
36
- */
37
- export default function Input($props: InputProps | undefined) {
38
- let defaultId = getId();
39
-
40
- let type = $props?.type ?? "text";
41
- let name = $props?.name
42
-
43
- let blurred = false;
44
-
45
- // Get stuff out of the FormContext and FieldContext
46
- const formContext = $context[FormContextName] as FormContext;
47
- const fieldContext = $context[FieldContextName] as FieldContext;
48
- if (fieldContext) {
49
- name ??= fieldContext.name;
50
- }
51
-
52
- // 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
- });
67
-
68
- // Set the input ID now that we know it and it will get linked with the associated label
69
- $state.inputId = $props?.id ?? defaultId;
70
-
71
- // NOTE: Adapted this from https://stackoverflow.com/a/57393751,
72
- // not sure if it's still a good way to do things
73
- function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
74
- $state.value = $props!.type?.match(/^(number|range)$/) ? +e.currentTarget.value : e.currentTarget.value;
75
- if (formContext) {
76
- formContext.validate();
77
- }
78
- if (fieldContext && blurred) {
79
- fieldContext.validate();
80
- }
81
- if ($props?.oninput) {
82
- $props.oninput(e);
83
- }
84
- }
85
-
86
- function handleBlur() {
87
- if (fieldContext) {
88
- fieldContext.validate();
89
- }
90
- blurred = true;
91
- }
92
-
93
- @render {
94
- <input
95
- id={$props?.id ?? defaultId}
96
- class={["torp-input", $props?.class]}
97
- {$props?.style}
98
- {type}
99
- {name}
100
- &value={$state.value}
101
- {$props?.placeholder}
102
- {$props?.readonly}
103
- {$props?.required}
104
- {$props?.maxlength}
105
- aria-label={$props?.ariaLabel}
106
- data-valid={$state.valid ? "valid" : "invalid"}
107
- onchange={$props?.onchange}
108
- oninput={handleInput}
109
- onblur={handleBlur}
110
- />
111
- }
112
- }