@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.
- package/CHANGELOG.md +47 -0
- package/dist/Accordion/Accordion.torp +25 -13
- package/dist/Accordion/AccordionContent.torp +15 -8
- package/dist/Accordion/AccordionHeader.torp +16 -7
- package/dist/Accordion/AccordionItem.torp +24 -10
- package/dist/Accordion/AccordionTrigger.torp +16 -8
- package/dist/Accordion/AccordionTypes.ts +2 -0
- package/dist/Accordion/index.d.ts +157 -0
- package/dist/Breadcrumb/Breadcrumb.torp +42 -0
- package/dist/Breadcrumb/BreadcrumbItem.torp +28 -0
- package/dist/Breadcrumb/BreadcrumbSeparator.torp +25 -0
- package/dist/Breadcrumb/index.d.ts +84 -0
- package/dist/Breadcrumb/index.js +14 -0
- package/dist/Calendar/Calendar.torp +273 -0
- package/dist/Calendar/CalendarDay.torp +75 -0
- package/dist/Calendar/CalendarTypes.ts +22 -0
- package/dist/Calendar/index.d.ts +36 -0
- package/dist/Calendar/index.js +10 -0
- package/dist/Calendar/index.js.map +1 -0
- package/dist/Dialog/Dialog.torp +16 -6
- package/dist/Dialog/DialogBody.torp +13 -5
- package/dist/Dialog/DialogButton.torp +18 -6
- package/dist/Dialog/DialogFooter.torp +13 -3
- package/dist/Dialog/DialogHeader.torp +13 -4
- package/dist/Dialog/index.d.ts +135 -0
- package/dist/Disclosure/Disclosure.torp +79 -0
- package/dist/Disclosure/DisclosureContent.torp +43 -0
- package/dist/Disclosure/DisclosureHeader.torp +43 -0
- package/dist/Disclosure/DisclosureTrigger.torp +44 -0
- package/dist/Disclosure/DisclosureTypes.ts +14 -0
- package/dist/Disclosure/index.d.ts +113 -0
- package/dist/Disclosure/index.js +18 -0
- package/dist/Disclosure/index.js.map +1 -0
- package/{src/Offscreen/Offscreen.torp → dist/Drawer/Drawer.torp} +20 -19
- package/dist/Drawer/index.d.ts +21 -0
- package/dist/Drawer/index.js +6 -0
- package/dist/Drawer/index.js.map +1 -0
- package/dist/Form/Field.torp +93 -0
- package/dist/Form/Form.torp +131 -0
- package/dist/Form/FormTypes.ts +30 -0
- package/dist/Form/Input.torp +94 -0
- package/dist/Form/Label.torp +31 -0
- package/dist/Form/Message.torp +35 -0
- package/dist/Form/StandardSchemaV1.ts +70 -0
- package/dist/Form/index.d.ts +117 -0
- package/dist/Form/index.js +22 -0
- package/dist/Form/index.js.map +1 -0
- package/dist/Menu/Menu.torp +169 -0
- package/dist/Menu/MenuButton.torp +64 -0
- package/dist/Menu/MenuCheck.torp +81 -0
- package/dist/Menu/MenuGroup.torp +29 -0
- package/dist/Menu/MenuIndicator.torp +38 -0
- package/dist/Menu/MenuPopout.torp +82 -0
- package/dist/Menu/MenuPopoutContent.torp +83 -0
- package/dist/Menu/MenuPopoutTrigger.torp +126 -0
- package/dist/Menu/MenuRadio.torp +99 -0
- package/dist/Menu/MenuRadioGroup.torp +97 -0
- package/dist/Menu/MenuSeparator.torp +24 -0
- package/dist/Menu/MenuTypes.ts +58 -0
- package/dist/Menu/index.d.ts +293 -0
- package/dist/Menu/index.js +46 -0
- package/dist/Menu/index.js.map +1 -0
- package/dist/MenuBar/MenuBar.torp +159 -0
- package/dist/MenuBar/MenuBarItem.torp +115 -0
- package/dist/MenuBar/MenuBarItemContent.torp +117 -0
- package/dist/MenuBar/MenuBarItemTrigger.torp +98 -0
- package/dist/MenuBar/MenuBarTypes.ts +36 -0
- package/dist/MenuBar/index.d.ts +125 -0
- package/dist/MenuBar/index.js +18 -0
- package/dist/MenuBar/index.js.map +1 -0
- package/dist/NavMenu/NavMenu.torp +15 -6
- package/dist/NavMenu/NavMenuButton.torp +14 -7
- package/dist/NavMenu/NavMenuElement.torp +16 -8
- package/dist/NavMenu/NavMenuGroup.torp +14 -5
- package/dist/NavMenu/NavMenuGroupHeader.torp +14 -6
- package/dist/NavMenu/NavMenuLink.torp +18 -10
- package/dist/NavMenu/NavMenuPopout.torp +13 -5
- package/dist/NavMenu/NavMenuPopoutButton.torp +17 -11
- package/dist/NavMenu/NavMenuPopoutContent.torp +42 -30
- package/dist/NavMenu/NavMenuSeparator.torp +12 -3
- package/dist/NavMenu/index.d.ts +259 -0
- package/dist/Notification/Notification.torp +58 -0
- package/dist/Notification/NotificationClose.torp +37 -0
- package/dist/Notification/NotificationContainer.torp +24 -0
- package/dist/Notification/NotificationContent.torp +23 -0
- package/dist/Notification/NotificationTypes.ts +5 -0
- package/dist/Notification/index.d.ts +92 -0
- package/dist/Notification/index.js +36 -0
- package/dist/Notification/index.js.map +1 -0
- package/dist/Notification/showNotification.ts +30 -0
- package/dist/Popover/Popover.torp +22 -8
- package/dist/Popover/PopoverContent.torp +41 -29
- package/dist/Popover/PopoverContentAnchored.torp +52 -44
- package/dist/Popover/PopoverContentContextual.torp +49 -37
- package/dist/Popover/PopoverOverlay.torp +13 -4
- package/dist/Popover/PopoverTrigger.torp +12 -3
- package/dist/Popover/PopoverTriggerContextual.torp +12 -3
- package/dist/Popover/PopoverTriggerHover.torp +14 -5
- package/dist/Popover/index.d.ts +191 -0
- package/dist/TabGroup/TabContent.torp +13 -7
- package/dist/TabGroup/TabGroup.torp +23 -11
- package/dist/TabGroup/TabHeader.torp +16 -7
- package/dist/TabGroup/TabList.torp +15 -6
- package/dist/TabGroup/TabTrigger.torp +14 -6
- package/dist/TabGroup/index.d.ts +150 -0
- package/dist/icons/ArrowDown.torp +15 -0
- package/dist/icons/ArrowLeft.torp +15 -0
- package/dist/icons/ArrowRight.torp +15 -0
- package/dist/icons/ArrowUp.torp +15 -0
- package/dist/icons/Bars.torp +17 -0
- package/dist/icons/Check.torp +15 -0
- package/dist/icons/ChevronDown.torp +15 -0
- package/dist/icons/ChevronLeft.torp +15 -0
- package/dist/icons/ChevronRight.torp +15 -0
- package/dist/icons/ChevronUp.torp +15 -0
- package/dist/icons/Cross.torp +16 -0
- package/dist/icons/Loading.torp +16 -0
- package/dist/icons/Minus.torp +15 -0
- package/dist/icons/Plus.torp +16 -0
- package/dist/icons/index.d.ts +142 -0
- package/dist/icons/index.js +58 -0
- package/dist/icons/index.js.map +1 -0
- package/dist/{motion.d.cts → index.d.ts} +2 -2
- package/dist/metafile-esm.json +1 -1
- package/dist/motion/fade.ts +7 -0
- package/dist/motion/index.d.ts +2 -0
- package/dist/{motion.js → motion/index.js} +1 -1
- package/dist/motion/index.js.map +1 -0
- package/dist/mount/index.d.ts +2 -0
- package/dist/utils/dateUtils.ts +244 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/setPopoverPosition.ts +4 -0
- package/package.json +60 -35
- package/scripts/postbuild.ts +42 -5
- package/src/Accordion/Accordion.torp +25 -13
- package/src/Accordion/AccordionContent.torp +15 -8
- package/src/Accordion/AccordionHeader.torp +16 -7
- package/src/Accordion/AccordionItem.torp +24 -10
- package/src/Accordion/AccordionTrigger.torp +16 -8
- package/src/Accordion/AccordionTypes.ts +2 -0
- package/src/Breadcrumb/Breadcrumb.torp +42 -0
- package/src/Breadcrumb/BreadcrumbItem.torp +28 -0
- package/src/Breadcrumb/BreadcrumbSeparator.torp +25 -0
- package/src/Breadcrumb/index.ts +5 -0
- package/src/Calendar/Calendar.torp +273 -0
- package/src/Calendar/CalendarDay.torp +75 -0
- package/src/Calendar/CalendarTypes.ts +22 -0
- package/src/Calendar/index.ts +4 -0
- package/src/Dialog/Dialog.torp +16 -6
- package/src/Dialog/DialogBody.torp +13 -5
- package/src/Dialog/DialogButton.torp +18 -6
- package/src/Dialog/DialogFooter.torp +13 -3
- package/src/Dialog/DialogHeader.torp +13 -4
- package/src/Disclosure/Disclosure.torp +79 -0
- package/src/Disclosure/DisclosureContent.torp +43 -0
- package/src/Disclosure/DisclosureHeader.torp +43 -0
- package/src/Disclosure/DisclosureTrigger.torp +44 -0
- package/src/Disclosure/DisclosureTypes.ts +14 -0
- package/src/Disclosure/index.ts +6 -0
- package/{dist/Offscreen/Offscreen.torp → src/Drawer/Drawer.torp} +20 -19
- package/src/Drawer/index.ts +3 -0
- package/src/Form/Field.torp +93 -0
- package/src/Form/Form.torp +131 -0
- package/src/Form/FormTypes.ts +30 -0
- package/src/Form/Input.torp +94 -0
- package/src/Form/Label.torp +31 -0
- package/src/Form/Message.torp +35 -0
- package/src/Form/StandardSchemaV1.ts +70 -0
- package/src/Form/index.ts +7 -0
- package/src/Menu/Menu.torp +169 -0
- package/src/Menu/MenuButton.torp +64 -0
- package/src/Menu/MenuCheck.torp +81 -0
- package/src/Menu/MenuGroup.torp +29 -0
- package/src/Menu/MenuIndicator.torp +38 -0
- package/src/Menu/MenuPopout.torp +82 -0
- package/src/Menu/MenuPopoutContent.torp +83 -0
- package/src/Menu/MenuPopoutTrigger.torp +126 -0
- package/src/Menu/MenuRadio.torp +99 -0
- package/src/Menu/MenuRadioGroup.torp +97 -0
- package/src/Menu/MenuSeparator.torp +24 -0
- package/src/Menu/MenuTypes.ts +58 -0
- package/src/Menu/index.ts +25 -0
- package/src/MenuBar/MenuBar.torp +159 -0
- package/src/MenuBar/MenuBarItem.torp +115 -0
- package/src/MenuBar/MenuBarItemContent.torp +117 -0
- package/src/MenuBar/MenuBarItemTrigger.torp +98 -0
- package/src/MenuBar/MenuBarTypes.ts +36 -0
- package/src/MenuBar/index.ts +6 -0
- package/src/NavMenu/NavMenu.torp +15 -6
- package/src/NavMenu/NavMenuButton.torp +14 -7
- package/src/NavMenu/NavMenuElement.torp +16 -8
- package/src/NavMenu/NavMenuGroup.torp +14 -5
- package/src/NavMenu/NavMenuGroupHeader.torp +14 -6
- package/src/NavMenu/NavMenuLink.torp +18 -10
- package/src/NavMenu/NavMenuPopout.torp +13 -5
- package/src/NavMenu/NavMenuPopoutButton.torp +17 -11
- package/src/NavMenu/NavMenuPopoutContent.torp +42 -30
- package/src/NavMenu/NavMenuSeparator.torp +12 -3
- package/src/Notification/Notification.torp +58 -0
- package/src/Notification/NotificationClose.torp +37 -0
- package/src/Notification/NotificationContainer.torp +24 -0
- package/src/Notification/NotificationContent.torp +23 -0
- package/src/Notification/NotificationTypes.ts +5 -0
- package/src/Notification/index.ts +13 -0
- package/src/Notification/showNotification.ts +30 -0
- package/src/Popover/Popover.torp +22 -8
- package/src/Popover/PopoverContent.torp +41 -29
- package/src/Popover/PopoverContentAnchored.torp +52 -44
- package/src/Popover/PopoverContentContextual.torp +49 -37
- package/src/Popover/PopoverOverlay.torp +13 -4
- package/src/Popover/PopoverTrigger.torp +12 -3
- package/src/Popover/PopoverTriggerContextual.torp +12 -3
- package/src/Popover/PopoverTriggerHover.torp +14 -5
- package/src/Slider/Slider.torp +83 -0
- package/src/Slider/index.ts +3 -0
- package/src/TabGroup/TabContent.torp +13 -7
- package/src/TabGroup/TabGroup.torp +23 -11
- package/src/TabGroup/TabHeader.torp +16 -7
- package/src/TabGroup/TabList.torp +15 -6
- package/src/TabGroup/TabTrigger.torp +14 -6
- package/src/icons/ArrowDown.torp +15 -0
- package/src/icons/ArrowLeft.torp +15 -0
- package/src/icons/ArrowRight.torp +15 -0
- package/src/icons/ArrowUp.torp +15 -0
- package/src/icons/Bars.torp +17 -0
- package/src/icons/Check.torp +15 -0
- package/src/icons/ChevronDown.torp +15 -0
- package/src/icons/ChevronLeft.torp +15 -0
- package/src/icons/ChevronRight.torp +15 -0
- package/src/icons/ChevronUp.torp +15 -0
- package/src/icons/Cross.torp +16 -0
- package/src/icons/Loading.torp +16 -0
- package/src/icons/Minus.torp +15 -0
- package/src/icons/Plus.torp +16 -0
- package/src/icons/index.ts +31 -0
- package/src/motion/fade.ts +2 -2
- package/src/motion/index.ts +3 -0
- package/src/utils/dateUtils.ts +244 -0
- package/src/utils/setPopoverPosition.ts +4 -0
- package/test/Accordion/components/AccordionAccessibility.torp +2 -0
- package/test/Accordion/components/AccordionKeyboard.torp +2 -0
- package/test/Accordion/components/AccordionList.torp +2 -0
- package/test/Accordion/components/AccordionMultiple.torp +2 -0
- package/test/Accordion/components/AccordionSingle.torp +2 -0
- package/test/Breadcrumb/accessibility.test.ts +29 -0
- package/test/Breadcrumb/basic.test.ts +17 -0
- package/test/Breadcrumb/components/BreadcrumbTest.torp +15 -0
- package/test/Calendar/components/CalendarTest.torp +9 -0
- package/test/Calendar/default.test.ts +18 -0
- package/test/Dialog/components/DialogAccessibility.torp +5 -5
- package/test/Disclosure/accessibility.test.ts +31 -0
- package/test/Disclosure/components/DisclosureTest.torp +19 -0
- package/test/Disclosure/default.test.ts +15 -0
- package/test/Disclosure/toggle.test.ts +20 -0
- package/test/Form/components/FieldTest.torp +25 -0
- package/test/Form/components/FormTest.torp +31 -0
- package/test/Form/components/InputTest.torp +9 -0
- package/test/Form/field.test.ts +34 -0
- package/test/Form/form.test.ts +34 -0
- package/test/Form/input.test.ts +19 -0
- package/test/Menu/accessibility.test.ts +83 -0
- package/test/Menu/basic.test.ts +58 -0
- package/test/Menu/components/MenuAccessibility.torp +59 -0
- package/test/Menu/components/MenuBasic.torp +58 -0
- package/test/MenuBar/accessibility.test.ts +87 -0
- package/test/MenuBar/basic.test.ts +22 -0
- package/test/MenuBar/components/MenuBarAccessibility.torp +34 -0
- package/test/MenuBar/components/MenuBarBasic.torp +25 -0
- package/test/NavMenu/basic.test.ts +1 -1
- package/test/NavMenu/components/{NavMenu.torp → NavMenuBasic.torp} +1 -1
- package/test/Notification/accessibility.test.ts +21 -0
- package/test/Notification/components/CustomNotification.torp +14 -0
- package/test/Notification/components/NotificationAccessibility.torp +14 -0
- package/test/Notification/components/NotificationFunction.torp +20 -0
- package/test/Notification/components/NotificationVariable.torp +22 -0
- package/test/Notification/function.test.ts +25 -0
- package/test/Notification/variable.test.ts +25 -0
- package/test/Popover/components/CustomDialog.torp +25 -0
- package/test/Popover/components/PromptFunction.torp +19 -0
- package/test/Popover/components/PromptVariable.torp +38 -0
- package/test/Popover/function.test.ts +26 -0
- package/test/Popover/variable.test.ts +27 -0
- package/test/TabGroup/components/TabGroupAccessibility.torp +2 -0
- package/test/TabGroup/components/TabGroupKeyboard.torp +2 -0
- package/test/TabGroup/components/TabGroupList.torp +2 -0
- package/test/TabGroup/components/TabGroupTrigger.torp +2 -0
- package/tsup.config.ts +12 -4
- package/dist/Accordion/index.cjs +0 -53
- package/dist/Accordion/index.cjs.map +0 -1
- package/dist/Dialog/index.cjs +0 -53
- package/dist/Dialog/index.cjs.map +0 -1
- package/dist/NavMenu/index.cjs +0 -78
- package/dist/NavMenu/index.cjs.map +0 -1
- package/dist/Offscreen/index.cjs +0 -33
- package/dist/Offscreen/index.cjs.map +0 -1
- package/dist/Offscreen/index.js +0 -6
- package/dist/Popover/index.cjs +0 -222
- package/dist/Popover/index.cjs.map +0 -1
- package/dist/TabGroup/index.cjs +0 -53
- package/dist/TabGroup/index.cjs.map +0 -1
- package/dist/metafile-cjs.json +0 -1
- package/dist/motion.cjs +0 -34
- package/dist/motion.cjs.map +0 -1
- package/dist/motion.d.ts +0 -6
- package/dist/motion.js.map +0 -1
- package/src/Accordion.ts +0 -7
- package/src/Dialog.ts +0 -7
- package/src/NavMenu.ts +0 -23
- package/src/Offscreen/index.ts +0 -3
- package/src/Popover.ts +0 -29
- package/src/TabGroup.ts +0 -7
- package/src/motion.ts +0 -3
- /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":[]}
|