@zeno-lib/forms 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-zeno-form.d.mts +8 -2
- package/dist/create-zeno-form.mjs +4 -0
- package/dist/form-element.mjs +7 -5
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/lib/focus-first-invalid.d.mts +23 -0
- package/dist/lib/focus-first-invalid.mjs +37 -0
- package/dist/lib/submit-action.d.mts +8 -2
- package/dist/lib/submit-action.mjs +4 -2
- package/dist/lib/submit-error.d.mts +36 -0
- package/dist/lib/submit-error.mjs +66 -0
- package/dist/lib/use-field-control.d.mts +76 -0
- package/dist/lib/use-field-control.mjs +67 -0
- package/dist/lib/use-form-dialog.d.mts +15 -1
- package/dist/lib/use-form-dialog.mjs +20 -2
- package/package.json +1 -1
- package/src/control-field.test.tsx +338 -0
- package/src/create-form.tsx +5 -0
- package/src/create-zeno-form.tsx +32 -1
- package/src/fields/control-field.tsx +57 -0
- package/src/fields/field-shell.tsx +73 -0
- package/src/fields/form-error.tsx +61 -0
- package/src/fields/index.ts +3 -0
- package/src/fields/input-field.tsx +14 -48
- package/src/fields/textarea-field.tsx +14 -48
- package/src/form-dialog.test.tsx +493 -2
- package/src/form-dialog.tsx +217 -33
- package/src/form-element.tsx +13 -11
- package/src/form-error.test.tsx +255 -0
- package/src/index.ts +6 -0
- package/src/lib/focus-first-invalid.test.ts +22 -0
- package/src/lib/focus-first-invalid.ts +52 -0
- package/src/lib/submit-action.ts +18 -4
- package/src/lib/submit-error.ts +133 -0
- package/src/lib/use-field-control.ts +181 -0
- package/src/lib/use-form-dialog.ts +49 -1
- package/src/submit-action.test.tsx +103 -0
- package/src/use-app-fields.test-d.ts +51 -1
- package/src/use-form.focus-invalid.test.tsx +34 -0
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { describedBy } from "@zeno-lib/forms/lib/aria"
|
|
4
3
|
import { useFieldContext } from "@zeno-lib/forms/lib/contexts"
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
useIsFieldRequired,
|
|
8
|
-
useIsFieldRequiredBySchema,
|
|
9
|
-
useIsInvalid,
|
|
10
|
-
} from "@zeno-lib/forms/lib/use-is-invalid"
|
|
11
|
-
import { Children, type ComponentProps, type ReactNode, useId } from "react"
|
|
12
|
-
import {
|
|
13
|
-
Field,
|
|
14
|
-
FieldDescription,
|
|
15
|
-
FieldError,
|
|
16
|
-
FieldLabel,
|
|
17
|
-
} from "@/components/ui/field"
|
|
4
|
+
import { useFieldControl } from "@zeno-lib/forms/lib/use-field-control"
|
|
5
|
+
import { Children, type ComponentProps, type ReactNode } from "react"
|
|
18
6
|
import { Input } from "@/components/ui/input"
|
|
19
7
|
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
|
|
20
|
-
import {
|
|
8
|
+
import { FieldShell } from "./field-shell"
|
|
21
9
|
|
|
22
10
|
type InputFieldProps = Omit<
|
|
23
11
|
ComponentProps<typeof Input>,
|
|
@@ -41,28 +29,18 @@ function InputField({
|
|
|
41
29
|
...props
|
|
42
30
|
}: InputFieldProps) {
|
|
43
31
|
const field = useFieldContext<string>()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
// The label names the input through `htmlFor`, so the input needs no
|
|
33
|
+
// `aria-labelledby` and the hook doesn't get `label`.
|
|
34
|
+
const { controlProps, ...shell } = useFieldControl<string>({
|
|
35
|
+
description,
|
|
36
|
+
required,
|
|
37
|
+
})
|
|
50
38
|
const hasAddons = Children.count(children) > 0
|
|
51
|
-
const schemaRequired = useIsFieldRequired(field)
|
|
52
|
-
const isRequired = required ?? schemaRequired
|
|
53
|
-
const schemaRequiresValue = useIsFieldRequiredBySchema(field)
|
|
54
|
-
const requiresValue = required ?? schemaRequiresValue
|
|
55
39
|
|
|
40
|
+
// A text input keeps the `""` the user typed, where `controlProps.onChange`
|
|
41
|
+
// would store the field's empty value.
|
|
56
42
|
const inputProps = {
|
|
57
|
-
|
|
58
|
-
[description, descriptionId],
|
|
59
|
-
[showError, errorId]
|
|
60
|
-
),
|
|
61
|
-
"aria-invalid": isInvalid || undefined,
|
|
62
|
-
"aria-required": requiresValue || undefined,
|
|
63
|
-
id,
|
|
64
|
-
name: field.name,
|
|
65
|
-
onBlur: field.handleBlur,
|
|
43
|
+
...controlProps,
|
|
66
44
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
|
67
45
|
field.handleChange(event.target.value),
|
|
68
46
|
value: field.state.value ?? "",
|
|
@@ -70,13 +48,7 @@ function InputField({
|
|
|
70
48
|
}
|
|
71
49
|
|
|
72
50
|
return (
|
|
73
|
-
<
|
|
74
|
-
{label && (
|
|
75
|
-
<FieldLabel htmlFor={id}>
|
|
76
|
-
{label}
|
|
77
|
-
{isRequired && <RequiredIndicator />}
|
|
78
|
-
</FieldLabel>
|
|
79
|
-
)}
|
|
51
|
+
<FieldShell {...shell} description={description} label={label}>
|
|
80
52
|
{hasAddons ? (
|
|
81
53
|
<InputGroup>
|
|
82
54
|
<InputGroupInput {...inputProps} />
|
|
@@ -85,13 +57,7 @@ function InputField({
|
|
|
85
57
|
) : (
|
|
86
58
|
<Input {...inputProps} />
|
|
87
59
|
)}
|
|
88
|
-
|
|
89
|
-
<FieldDescription id={descriptionId}>{description}</FieldDescription>
|
|
90
|
-
)}
|
|
91
|
-
{showError && (
|
|
92
|
-
<FieldError errors={field.state.meta.errors} id={errorId} />
|
|
93
|
-
)}
|
|
94
|
-
</Field>
|
|
60
|
+
</FieldShell>
|
|
95
61
|
)
|
|
96
62
|
}
|
|
97
63
|
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { describedBy } from "@zeno-lib/forms/lib/aria"
|
|
4
3
|
import { useFieldContext } from "@zeno-lib/forms/lib/contexts"
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
useIsFieldRequired,
|
|
8
|
-
useIsFieldRequiredBySchema,
|
|
9
|
-
useIsInvalid,
|
|
10
|
-
} from "@zeno-lib/forms/lib/use-is-invalid"
|
|
11
|
-
import { Children, type ComponentProps, type ReactNode, useId } from "react"
|
|
12
|
-
import {
|
|
13
|
-
Field,
|
|
14
|
-
FieldDescription,
|
|
15
|
-
FieldError,
|
|
16
|
-
FieldLabel,
|
|
17
|
-
} from "@/components/ui/field"
|
|
4
|
+
import { useFieldControl } from "@zeno-lib/forms/lib/use-field-control"
|
|
5
|
+
import { Children, type ComponentProps, type ReactNode } from "react"
|
|
18
6
|
import { InputGroup, InputGroupTextarea } from "@/components/ui/input-group"
|
|
19
7
|
import { Textarea } from "@/components/ui/textarea"
|
|
20
|
-
import {
|
|
8
|
+
import { FieldShell } from "./field-shell"
|
|
21
9
|
|
|
22
10
|
type TextAreaFieldProps = Omit<
|
|
23
11
|
ComponentProps<typeof Textarea>,
|
|
@@ -41,28 +29,18 @@ function TextAreaField({
|
|
|
41
29
|
...props
|
|
42
30
|
}: TextAreaFieldProps) {
|
|
43
31
|
const field = useFieldContext<string>()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
// The label names the textarea through `htmlFor`, so the textarea needs no
|
|
33
|
+
// `aria-labelledby` and the hook doesn't get `label`.
|
|
34
|
+
const { controlProps, ...shell } = useFieldControl<string>({
|
|
35
|
+
description,
|
|
36
|
+
required,
|
|
37
|
+
})
|
|
50
38
|
const hasAddons = Children.count(children) > 0
|
|
51
|
-
const schemaRequired = useIsFieldRequired(field)
|
|
52
|
-
const isRequired = required ?? schemaRequired
|
|
53
|
-
const schemaRequiresValue = useIsFieldRequiredBySchema(field)
|
|
54
|
-
const requiresValue = required ?? schemaRequiresValue
|
|
55
39
|
|
|
40
|
+
// A textarea keeps the `""` the user typed, where `controlProps.onChange`
|
|
41
|
+
// would store the field's empty value.
|
|
56
42
|
const textareaProps = {
|
|
57
|
-
|
|
58
|
-
[description, descriptionId],
|
|
59
|
-
[showError, errorId]
|
|
60
|
-
),
|
|
61
|
-
"aria-invalid": isInvalid || undefined,
|
|
62
|
-
"aria-required": requiresValue || undefined,
|
|
63
|
-
id,
|
|
64
|
-
name: field.name,
|
|
65
|
-
onBlur: field.handleBlur,
|
|
43
|
+
...controlProps,
|
|
66
44
|
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) =>
|
|
67
45
|
field.handleChange(event.target.value),
|
|
68
46
|
value: field.state.value ?? "",
|
|
@@ -70,13 +48,7 @@ function TextAreaField({
|
|
|
70
48
|
}
|
|
71
49
|
|
|
72
50
|
return (
|
|
73
|
-
<
|
|
74
|
-
{label && (
|
|
75
|
-
<FieldLabel htmlFor={id}>
|
|
76
|
-
{label}
|
|
77
|
-
{isRequired && <RequiredIndicator />}
|
|
78
|
-
</FieldLabel>
|
|
79
|
-
)}
|
|
51
|
+
<FieldShell {...shell} description={description} label={label}>
|
|
80
52
|
{hasAddons ? (
|
|
81
53
|
<InputGroup>
|
|
82
54
|
<InputGroupTextarea {...textareaProps} />
|
|
@@ -85,13 +57,7 @@ function TextAreaField({
|
|
|
85
57
|
) : (
|
|
86
58
|
<Textarea {...textareaProps} />
|
|
87
59
|
)}
|
|
88
|
-
|
|
89
|
-
<FieldDescription id={descriptionId}>{description}</FieldDescription>
|
|
90
|
-
)}
|
|
91
|
-
{showError && (
|
|
92
|
-
<FieldError errors={field.state.meta.errors} id={errorId} />
|
|
93
|
-
)}
|
|
94
|
-
</Field>
|
|
60
|
+
</FieldShell>
|
|
95
61
|
)
|
|
96
62
|
}
|
|
97
63
|
|