@asnewyla/form 0.2.2 → 0.3.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/README.md +137 -94
- package/dist/index.d.mts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +91 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +57 -19
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.js +41 -14
- package/dist/test-utils.js.map +1 -1
- package/dist/test-utils.mjs +9 -4
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,94 +1,137 @@
|
|
|
1
|
-
# @asnewyla/form
|
|
2
|
-
|
|
3
|
-
Form wrapper with real client-side validation, an accessible error summary,
|
|
4
|
-
and native `<form>` submission semantics.
|
|
5
|
-
|
|
6
|
-
## Install
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
npm install @asnewyla/form @asnewyla/input
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
`FormFieldInput` renders `@asnewyla/input`'s `Input` under the hood, so its
|
|
13
|
-
stylesheet also needs importing once, alongside `@asnewyla/form`'s own
|
|
14
|
-
(which styles the error summary):
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
import '@asnewyla/form/styles.css';
|
|
18
|
-
import '@asnewyla/input/styles.css';
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Usage
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
import { Form, FormFieldInput } from '@asnewyla/form';
|
|
25
|
-
|
|
26
|
-
<Form onSubmit={(values) => console.log(values)}>
|
|
27
|
-
<FormFieldInput label="Email" name="email" required pattern="^\S+@\S+$" />
|
|
28
|
-
<FormFieldInput label="Password" name="password" required minLength={8} />
|
|
29
|
-
<button type="submit">Submit</button>
|
|
30
|
-
</Form>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`Form` validates on blur and on submit, blocks `onSubmit` while any field is
|
|
34
|
-
invalid, and reads submitted values via native `FormData` — there's no
|
|
35
|
-
duplicated value state to keep in sync.
|
|
36
|
-
|
|
37
|
-
### `FormFieldInput`
|
|
38
|
-
|
|
39
|
-
Use `FormFieldInput`, not a plain `Input`, for any field you want `Form` to
|
|
40
|
-
register, validate, and collect. It accepts every `Input` prop plus a
|
|
41
|
-
required `name` and a small validation-rule vocabulary:
|
|
42
|
-
|
|
43
|
-
| Prop | Type | Notes |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| `name` | `string` (**required**) | Native `name`, also the key in `onSubmit`'s payload |
|
|
46
|
-
| `required` | `boolean \| string` | A string overrides the default error message |
|
|
47
|
-
| `pattern` | `string` | Regex source text, matching the native `<input pattern>` attribute |
|
|
48
|
-
| `minLength` / `maxLength` | `number` | |
|
|
49
|
-
| `min` / `max` | `string \| number` | Matching the native `<input min>`/`<input max>` attributes |
|
|
50
|
-
| `validate` | `(value: string) => string \| undefined` | Custom sync validator, runs after every other rule passes |
|
|
51
|
-
|
|
52
|
-
Rules run in that order — `required` → `pattern` → `minLength` → `maxLength`
|
|
53
|
-
→ `min` → `max` → `validate` — and the first one that fails wins; only one
|
|
54
|
-
error message shows per field.
|
|
55
|
-
|
|
56
|
-
Used outside a `Form`, `FormFieldInput` still renders correctly — it's a
|
|
57
|
-
normal `Input` with no ancestor to register with, and the native attributes
|
|
58
|
-
(`required`, `pattern`, etc.) still reach the underlying `<input>`.
|
|
59
|
-
|
|
60
|
-
A plain `Input` (not `FormFieldInput`) placed inside a `Form` is never
|
|
61
|
-
registered or validated, but its value is still collected into `onSubmit`'s
|
|
62
|
-
payload if it has a `name` — `Form` reads values via native `FormData`,
|
|
63
|
-
which picks up any named form control regardless of registration. This is
|
|
64
|
-
intentional, native `<form>` behavior, not a gap `FormFieldInput` needs to
|
|
65
|
-
close.
|
|
66
|
-
|
|
67
|
-
### Error display
|
|
68
|
-
|
|
69
|
-
Both an inline message and a summary show for an invalid field, not either:
|
|
70
|
-
|
|
71
|
-
- Each `FormFieldInput` shows its own inline error, linked via
|
|
72
|
-
`aria-describedby` — the same as a plain `Input`.
|
|
73
|
-
- `Form` additionally renders an error summary once a submit attempt has
|
|
74
|
-
failed: `role="alert"`, one link per invalid field pointing at that
|
|
75
|
-
field's real `id`, and focus moved to the summary on every failed submit.
|
|
76
|
-
Clicking a link moves focus to the corresponding field. The summary stays
|
|
77
|
-
in sync with corrections made afterward (including via blur) and
|
|
78
|
-
disappears once no errors remain — it doesn't require resubmitting to
|
|
79
|
-
update or go away.
|
|
80
|
-
|
|
81
|
-
The summary only appears after a submit has actually been attempted — a
|
|
82
|
-
field that's invalid purely from blur-driven validation (nobody has tried
|
|
83
|
-
to submit yet) shows its own inline error only.
|
|
84
|
-
|
|
85
|
-
###
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1
|
+
# @asnewyla/form
|
|
2
|
+
|
|
3
|
+
Form wrapper with real client-side validation, an accessible error summary,
|
|
4
|
+
and native `<form>` submission semantics.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @asnewyla/form @asnewyla/input
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`FormFieldInput` renders `@asnewyla/input`'s `Input` under the hood, so its
|
|
13
|
+
stylesheet also needs importing once, alongside `@asnewyla/form`'s own
|
|
14
|
+
(which styles the error summary):
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import '@asnewyla/form/styles.css';
|
|
18
|
+
import '@asnewyla/input/styles.css';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { Form, FormFieldInput } from '@asnewyla/form';
|
|
25
|
+
|
|
26
|
+
<Form onSubmit={(values) => console.log(values)}>
|
|
27
|
+
<FormFieldInput label="Email" name="email" required pattern="^\S+@\S+$" />
|
|
28
|
+
<FormFieldInput label="Password" name="password" required minLength={8} />
|
|
29
|
+
<button type="submit">Submit</button>
|
|
30
|
+
</Form>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`Form` validates on blur and on submit, blocks `onSubmit` while any field is
|
|
34
|
+
invalid, and reads submitted values via native `FormData` — there's no
|
|
35
|
+
duplicated value state to keep in sync.
|
|
36
|
+
|
|
37
|
+
### `FormFieldInput`
|
|
38
|
+
|
|
39
|
+
Use `FormFieldInput`, not a plain `Input`, for any field you want `Form` to
|
|
40
|
+
register, validate, and collect. It accepts every `Input` prop plus a
|
|
41
|
+
required `name` and a small validation-rule vocabulary:
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Notes |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `name` | `string` (**required**) | Native `name`, also the key in `onSubmit`'s payload |
|
|
46
|
+
| `required` | `boolean \| string` | A string overrides the default error message |
|
|
47
|
+
| `pattern` | `string` | Regex source text, matching the native `<input pattern>` attribute |
|
|
48
|
+
| `minLength` / `maxLength` | `number` | |
|
|
49
|
+
| `min` / `max` | `string \| number` | Matching the native `<input min>`/`<input max>` attributes |
|
|
50
|
+
| `validate` | `(value: string) => string \| undefined` | Custom sync validator, runs after every other rule passes |
|
|
51
|
+
|
|
52
|
+
Rules run in that order — `required` → `pattern` → `minLength` → `maxLength`
|
|
53
|
+
→ `min` → `max` → `validate` — and the first one that fails wins; only one
|
|
54
|
+
error message shows per field.
|
|
55
|
+
|
|
56
|
+
Used outside a `Form`, `FormFieldInput` still renders correctly — it's a
|
|
57
|
+
normal `Input` with no ancestor to register with, and the native attributes
|
|
58
|
+
(`required`, `pattern`, etc.) still reach the underlying `<input>`.
|
|
59
|
+
|
|
60
|
+
A plain `Input` (not `FormFieldInput`) placed inside a `Form` is never
|
|
61
|
+
registered or validated, but its value is still collected into `onSubmit`'s
|
|
62
|
+
payload if it has a `name` — `Form` reads values via native `FormData`,
|
|
63
|
+
which picks up any named form control regardless of registration. This is
|
|
64
|
+
intentional, native `<form>` behavior, not a gap `FormFieldInput` needs to
|
|
65
|
+
close.
|
|
66
|
+
|
|
67
|
+
### Error display
|
|
68
|
+
|
|
69
|
+
Both an inline message and a summary show for an invalid field, not either:
|
|
70
|
+
|
|
71
|
+
- Each `FormFieldInput` shows its own inline error, linked via
|
|
72
|
+
`aria-describedby` — the same as a plain `Input`.
|
|
73
|
+
- `Form` additionally renders an error summary once a submit attempt has
|
|
74
|
+
failed: `role="alert"`, one link per invalid field pointing at that
|
|
75
|
+
field's real `id`, and focus moved to the summary on every failed submit.
|
|
76
|
+
Clicking a link moves focus to the corresponding field. The summary stays
|
|
77
|
+
in sync with corrections made afterward (including via blur) and
|
|
78
|
+
disappears once no errors remain — it doesn't require resubmitting to
|
|
79
|
+
update or go away.
|
|
80
|
+
|
|
81
|
+
The summary only appears after a submit has actually been attempted — a
|
|
82
|
+
field that's invalid purely from blur-driven validation (nobody has tried
|
|
83
|
+
to submit yet) shows its own inline error only.
|
|
84
|
+
|
|
85
|
+
### Form actions and no-JS forms
|
|
86
|
+
|
|
87
|
+
`Form` passes every native `<form>` prop through (`action`, `method`,
|
|
88
|
+
`encType`, `aria-*`, `id`, `className`, ...) and forwards `ref` to the
|
|
89
|
+
`<form>` element. Validation runs first on every submit, and an invalid
|
|
90
|
+
submit is always blocked. A valid submit goes ahead natively unless you
|
|
91
|
+
handle it on the client:
|
|
92
|
+
|
|
93
|
+
| Props | Valid submit |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `onSubmit` only | Prevented; `onSubmit(values)` is called (client-only form) |
|
|
96
|
+
| `action` (with or without `onSubmit`) | Not prevented: the browser posts, or React 19 runs the action. `onSubmit(values)` is still called if given |
|
|
97
|
+
| neither | Not prevented: a plain form posting to its URL |
|
|
98
|
+
|
|
99
|
+
With a React 19 server action:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// actions.ts
|
|
103
|
+
'use server';
|
|
104
|
+
export async function signIn(formData: FormData) { /* ... */ }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// page.tsx: a server component, no wrapper needed
|
|
109
|
+
import { Button } from '@asnewyla/button';
|
|
110
|
+
import { Form, FormFieldInput } from '@asnewyla/form';
|
|
111
|
+
import { signIn } from './actions';
|
|
112
|
+
|
|
113
|
+
<Form action={signIn} aria-label="Sign in">
|
|
114
|
+
<FormFieldInput label="Email" name="email" type="email" required />
|
|
115
|
+
<Button type="submit">Sign in</Button>
|
|
116
|
+
</Form>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Before JavaScript loads, the browser's own submit is used, so keep
|
|
120
|
+
server-side validation. `noValidate` defaults to `true` so `Form`'s
|
|
121
|
+
validation and error summary can run. Pass `noValidate={false}` to
|
|
122
|
+
turn the browser's native validation UI back on.
|
|
123
|
+
|
|
124
|
+
### Props
|
|
125
|
+
|
|
126
|
+
Every native `<form>` prop, plus:
|
|
127
|
+
|
|
128
|
+
| Prop | Type |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `children` | `React.ReactNode` (**required**) |
|
|
131
|
+
| `onSubmit` | `(values: Record<string, string>) => void` (optional) |
|
|
132
|
+
| `noValidate` | `boolean` (default `true`) |
|
|
133
|
+
| `ref` | `Ref<HTMLFormElement>` |
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
2
3
|
import { InputProps } from '@asnewyla/input';
|
|
3
4
|
|
|
4
|
-
type FormProps = {
|
|
5
|
+
type FormProps = Omit<ComponentPropsWithoutRef<'form'>, 'onSubmit'> & {
|
|
5
6
|
children: React.ReactNode;
|
|
6
|
-
onSubmit
|
|
7
|
+
onSubmit?: (values: Record<string, string>) => void;
|
|
7
8
|
};
|
|
8
|
-
declare
|
|
9
|
+
declare const Form: react.ForwardRefExoticComponent<Omit<Omit<react.DetailedHTMLProps<react.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "ref">, "onSubmit"> & {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
onSubmit?: (values: Record<string, string>) => void;
|
|
12
|
+
} & react.RefAttributes<HTMLFormElement>>;
|
|
9
13
|
|
|
10
14
|
type FormFieldInputProps = Omit<InputProps, 'name'> & {
|
|
11
15
|
name: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
2
3
|
import { InputProps } from '@asnewyla/input';
|
|
3
4
|
|
|
4
|
-
type FormProps = {
|
|
5
|
+
type FormProps = Omit<ComponentPropsWithoutRef<'form'>, 'onSubmit'> & {
|
|
5
6
|
children: React.ReactNode;
|
|
6
|
-
onSubmit
|
|
7
|
+
onSubmit?: (values: Record<string, string>) => void;
|
|
7
8
|
};
|
|
8
|
-
declare
|
|
9
|
+
declare const Form: react.ForwardRefExoticComponent<Omit<Omit<react.DetailedHTMLProps<react.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "ref">, "onSubmit"> & {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
onSubmit?: (values: Record<string, string>) => void;
|
|
12
|
+
} & react.RefAttributes<HTMLFormElement>>;
|
|
9
13
|
|
|
10
14
|
type FormFieldInputProps = Omit<InputProps, 'name'> & {
|
|
11
15
|
name: string;
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
|
-
'use
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
2
20
|
|
|
3
|
-
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
Form: () => Form,
|
|
25
|
+
FormFieldInput: () => FormFieldInput
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
7
28
|
|
|
8
29
|
// src/components/Form.tsx
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
30
|
+
var import_input = require("@asnewyla/input");
|
|
31
|
+
var import_react3 = require("react");
|
|
32
|
+
var import_react_dom = require("react-dom");
|
|
33
|
+
|
|
34
|
+
// src/components/FormErrorSummary.tsx
|
|
35
|
+
var import_react = require("react");
|
|
36
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
37
|
+
var FormErrorSummary = (0, import_react.forwardRef)(function FormErrorSummary2({ invalidFields }, ref) {
|
|
38
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "xd-form-error-summary", role: "alert", tabIndex: -1, ref, children: [
|
|
39
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "xd-form-error-summary-heading", children: "There is a problem" }),
|
|
40
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: invalidFields.map(({ name, id, message }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
13
41
|
"a",
|
|
14
42
|
{
|
|
15
43
|
href: `#${id}`,
|
|
@@ -23,6 +51,9 @@ var FormErrorSummary = react.forwardRef(function FormErrorSummary2({ invalidFiel
|
|
|
23
51
|
] });
|
|
24
52
|
});
|
|
25
53
|
|
|
54
|
+
// src/components/useFieldRegistry.ts
|
|
55
|
+
var import_react2 = require("react");
|
|
56
|
+
|
|
26
57
|
// src/components/validation.ts
|
|
27
58
|
function validateValue(value, rules) {
|
|
28
59
|
if (!rules) return void 0;
|
|
@@ -52,15 +83,15 @@ function validateValue(value, rules) {
|
|
|
52
83
|
|
|
53
84
|
// src/components/useFieldRegistry.ts
|
|
54
85
|
function useFieldRegistry(formRef) {
|
|
55
|
-
const fieldsRef =
|
|
56
|
-
const [errors, setErrors] =
|
|
57
|
-
const registerField =
|
|
86
|
+
const fieldsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
87
|
+
const [errors, setErrors] = (0, import_react2.useState)({});
|
|
88
|
+
const registerField = (0, import_react2.useCallback)(
|
|
58
89
|
(name, registration) => {
|
|
59
90
|
fieldsRef.current.set(name, registration);
|
|
60
91
|
},
|
|
61
92
|
[]
|
|
62
93
|
);
|
|
63
|
-
const unregisterField =
|
|
94
|
+
const unregisterField = (0, import_react2.useCallback)((name) => {
|
|
64
95
|
fieldsRef.current.delete(name);
|
|
65
96
|
setErrors((prev) => {
|
|
66
97
|
if (!(name in prev)) return prev;
|
|
@@ -69,14 +100,14 @@ function useFieldRegistry(formRef) {
|
|
|
69
100
|
return next;
|
|
70
101
|
});
|
|
71
102
|
}, []);
|
|
72
|
-
const getFieldValue =
|
|
103
|
+
const getFieldValue = (0, import_react2.useCallback)(
|
|
73
104
|
(name) => {
|
|
74
105
|
if (!formRef.current) return "";
|
|
75
106
|
return String(new FormData(formRef.current).get(name) ?? "");
|
|
76
107
|
},
|
|
77
108
|
[formRef]
|
|
78
109
|
);
|
|
79
|
-
const validateField =
|
|
110
|
+
const validateField = (0, import_react2.useCallback)(
|
|
80
111
|
(name) => {
|
|
81
112
|
const field = fieldsRef.current.get(name);
|
|
82
113
|
const error = validateValue(getFieldValue(name), field?.rules);
|
|
@@ -85,11 +116,11 @@ function useFieldRegistry(formRef) {
|
|
|
85
116
|
},
|
|
86
117
|
[getFieldValue]
|
|
87
118
|
);
|
|
88
|
-
const fieldNames =
|
|
119
|
+
const fieldNames = (0, import_react2.useCallback)(
|
|
89
120
|
() => Array.from(fieldsRef.current.keys()),
|
|
90
121
|
[]
|
|
91
122
|
);
|
|
92
|
-
const invalidFields =
|
|
123
|
+
const invalidFields = (0, import_react2.useCallback)(() => {
|
|
93
124
|
const result = [];
|
|
94
125
|
for (const [name, message] of Object.entries(errors)) {
|
|
95
126
|
if (!message) continue;
|
|
@@ -108,9 +139,13 @@ function useFieldRegistry(formRef) {
|
|
|
108
139
|
invalidFields
|
|
109
140
|
};
|
|
110
141
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
142
|
+
|
|
143
|
+
// src/components/Form.tsx
|
|
144
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
145
|
+
function FormInner({ children, onSubmit, noValidate = true, ...restProps }, ref) {
|
|
146
|
+
const formRef = (0, import_react3.useRef)(null);
|
|
147
|
+
(0, import_react3.useImperativeHandle)(ref, () => formRef.current);
|
|
148
|
+
const summaryRef = (0, import_react3.useRef)(null);
|
|
114
149
|
const {
|
|
115
150
|
errors,
|
|
116
151
|
registerField,
|
|
@@ -119,20 +154,22 @@ function Form({ children, onSubmit }) {
|
|
|
119
154
|
fieldNames,
|
|
120
155
|
invalidFields
|
|
121
156
|
} = useFieldRegistry(formRef);
|
|
122
|
-
const [hasAttemptedSubmit, setHasAttemptedSubmit] =
|
|
157
|
+
const [hasAttemptedSubmit, setHasAttemptedSubmit] = (0, import_react3.useState)(false);
|
|
123
158
|
function handleSubmit(event) {
|
|
124
|
-
event.preventDefault();
|
|
125
159
|
let hasError = false;
|
|
126
|
-
|
|
160
|
+
(0, import_react_dom.flushSync)(() => {
|
|
127
161
|
for (const name of fieldNames()) {
|
|
128
162
|
if (validateField(name)) hasError = true;
|
|
129
163
|
}
|
|
130
164
|
setHasAttemptedSubmit(true);
|
|
131
165
|
});
|
|
132
166
|
if (hasError) {
|
|
167
|
+
event.preventDefault();
|
|
133
168
|
summaryRef.current?.focus();
|
|
134
169
|
return;
|
|
135
170
|
}
|
|
171
|
+
if (!onSubmit) return;
|
|
172
|
+
if (restProps.action === void 0) event.preventDefault();
|
|
136
173
|
const values = {};
|
|
137
174
|
for (const [key, value] of new FormData(event.currentTarget).entries()) {
|
|
138
175
|
values[key] = String(value);
|
|
@@ -140,15 +177,30 @@ function Form({ children, onSubmit }) {
|
|
|
140
177
|
onSubmit(values);
|
|
141
178
|
}
|
|
142
179
|
const invalid = hasAttemptedSubmit ? invalidFields() : [];
|
|
143
|
-
const contextValue =
|
|
180
|
+
const contextValue = (0, import_react3.useMemo)(
|
|
144
181
|
() => ({ registerField, unregisterField, validateField, errors }),
|
|
145
182
|
[registerField, unregisterField, validateField, errors]
|
|
146
183
|
);
|
|
147
|
-
return /* @__PURE__ */
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
185
|
+
"form",
|
|
186
|
+
{
|
|
187
|
+
...restProps,
|
|
188
|
+
ref: formRef,
|
|
189
|
+
onSubmit: handleSubmit,
|
|
190
|
+
noValidate,
|
|
191
|
+
children: [
|
|
192
|
+
invalid.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FormErrorSummary, { ref: summaryRef, invalidFields: invalid }),
|
|
193
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_input.FormFieldProvider, { value: contextValue, children })
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
);
|
|
151
197
|
}
|
|
198
|
+
var Form = (0, import_react3.forwardRef)(FormInner);
|
|
199
|
+
|
|
200
|
+
// src/components/FormFieldInput.tsx
|
|
201
|
+
var import_input2 = require("@asnewyla/input");
|
|
202
|
+
var import_react4 = require("react");
|
|
203
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
152
204
|
function FormFieldInputInner({
|
|
153
205
|
name,
|
|
154
206
|
required,
|
|
@@ -162,8 +214,8 @@ function FormFieldInputInner({
|
|
|
162
214
|
onBlur,
|
|
163
215
|
...rest
|
|
164
216
|
}, ref) {
|
|
165
|
-
const formField =
|
|
166
|
-
const inputRef =
|
|
217
|
+
const formField = (0, import_input2.useFormFieldContext)();
|
|
218
|
+
const inputRef = (0, import_react4.useRef)(null);
|
|
167
219
|
const rules = {
|
|
168
220
|
required,
|
|
169
221
|
pattern,
|
|
@@ -173,7 +225,7 @@ function FormFieldInputInner({
|
|
|
173
225
|
max,
|
|
174
226
|
validate
|
|
175
227
|
};
|
|
176
|
-
|
|
228
|
+
(0, import_react4.useEffect)(() => {
|
|
177
229
|
if (!formField) return;
|
|
178
230
|
formField.registerField(name, { ref: inputRef, rules });
|
|
179
231
|
return () => formField.unregisterField(name);
|
|
@@ -194,8 +246,8 @@ function FormFieldInputInner({
|
|
|
194
246
|
}
|
|
195
247
|
const formContextError = formField ? formField.errors[name] : void 0;
|
|
196
248
|
const formFieldError = error ?? formContextError;
|
|
197
|
-
return /* @__PURE__ */
|
|
198
|
-
|
|
249
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
250
|
+
import_input2.Input,
|
|
199
251
|
{
|
|
200
252
|
name,
|
|
201
253
|
required,
|
|
@@ -211,11 +263,12 @@ function FormFieldInputInner({
|
|
|
211
263
|
}
|
|
212
264
|
);
|
|
213
265
|
}
|
|
214
|
-
var FormFieldInput =
|
|
266
|
+
var FormFieldInput = (0, import_react4.forwardRef)(
|
|
215
267
|
FormFieldInputInner
|
|
216
268
|
);
|
|
217
|
-
|
|
218
|
-
exports
|
|
219
|
-
|
|
220
|
-
|
|
269
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
270
|
+
0 && (module.exports = {
|
|
271
|
+
Form,
|
|
272
|
+
FormFieldInput
|
|
273
|
+
});
|
|
221
274
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FormErrorSummary.tsx","../src/components/validation.ts","../src/components/useFieldRegistry.ts","../src/components/Form.tsx","../src/components/FormFieldInput.tsx"],"names":["forwardRef","FormErrorSummary","jsxs","jsx","useRef","useState","useCallback","flushSync","useMemo","FormFieldProvider","useFormFieldContext","useEffect","Input"],"mappings":"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","file":"index.js","sourcesContent":["import { forwardRef } from 'react';\nimport type { InvalidField } from './useFieldRegistry';\nimport './FormErrorSummary.css';\n\nexport type FormErrorSummaryProps = {\n invalidFields: InvalidField[];\n};\n\nexport const FormErrorSummary = forwardRef<\n HTMLDivElement,\n FormErrorSummaryProps\n>(function FormErrorSummary({ invalidFields }, ref) {\n return (\n <div className=\"xd-form-error-summary\" role=\"alert\" tabIndex={-1} ref={ref}>\n <p className=\"xd-form-error-summary-heading\">There is a problem</p>\n <ul>\n {invalidFields.map(({ name, id, message }) => (\n <li key={name}>\n <a\n href={`#${id}`}\n onClick={(event) => {\n event.preventDefault();\n document.getElementById(id)?.focus();\n }}\n >\n {message}\n </a>\n </li>\n ))}\n </ul>\n </div>\n );\n});\n","import type { ValidationRules } from '@asnewyla/input';\n\nexport function validateValue(\n value: string,\n rules?: ValidationRules,\n): string | undefined {\n if (!rules) return undefined;\n\n if (rules.required && value.trim() === '') {\n return typeof rules.required === 'string'\n ? rules.required\n : 'This field is required';\n }\n if (rules.pattern && value !== '' && !new RegExp(rules.pattern).test(value)) {\n return 'Invalid format';\n }\n if (rules.minLength !== undefined && value.length < rules.minLength) {\n return `Must be at least ${rules.minLength} characters`;\n }\n if (rules.maxLength !== undefined && value.length > rules.maxLength) {\n return `Must be at most ${rules.maxLength} characters`;\n }\n if (\n rules.min !== undefined &&\n value !== '' &&\n Number(value) < Number(rules.min)\n ) {\n return `Must be at least ${rules.min}`;\n }\n if (\n rules.max !== undefined &&\n value !== '' &&\n Number(value) > Number(rules.max)\n ) {\n return `Must be at most ${rules.max}`;\n }\n if (rules.validate) {\n return rules.validate(value);\n }\n return undefined;\n}\n","import type { FieldRegistration } from '@asnewyla/input';\nimport { type RefObject, useCallback, useRef, useState } from 'react';\nimport { validateValue } from './validation';\n\nexport interface InvalidField {\n name: string;\n id: string;\n message: string;\n}\n\nexport interface FieldRegistry {\n errors: Record<string, string | undefined>;\n registerField: (name: string, registration: FieldRegistration) => void;\n unregisterField: (name: string) => void;\n validateField: (name: string) => string | undefined;\n fieldNames: () => string[];\n invalidFields: () => InvalidField[];\n}\n\nexport function useFieldRegistry(\n formRef: RefObject<HTMLFormElement>,\n): FieldRegistry {\n const fieldsRef = useRef<Map<string, FieldRegistration>>(new Map());\n const [errors, setErrors] = useState<Record<string, string | undefined>>({});\n\n const registerField = useCallback(\n (name: string, registration: FieldRegistration) => {\n fieldsRef.current.set(name, registration);\n },\n [],\n );\n\n const unregisterField = useCallback((name: string) => {\n fieldsRef.current.delete(name);\n setErrors((prev) => {\n if (!(name in prev)) return prev;\n const next = { ...prev };\n delete next[name];\n return next;\n });\n }, []);\n\n const getFieldValue = useCallback(\n (name: string): string => {\n if (!formRef.current) return '';\n return String(new FormData(formRef.current).get(name) ?? '');\n },\n [formRef],\n );\n\n const validateField = useCallback(\n (name: string) => {\n const field = fieldsRef.current.get(name);\n const error = validateValue(getFieldValue(name), field?.rules);\n setErrors((prev) => ({ ...prev, [name]: error }));\n return error;\n },\n [getFieldValue],\n );\n\n const fieldNames = useCallback(\n () => Array.from(fieldsRef.current.keys()),\n [],\n );\n\n const invalidFields = useCallback((): InvalidField[] => {\n const result: InvalidField[] = [];\n for (const [name, message] of Object.entries(errors)) {\n if (!message) continue;\n const id = fieldsRef.current.get(name)?.ref.current?.id;\n if (!id) continue;\n result.push({ name, id, message });\n }\n return result;\n }, [errors]);\n\n return {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n };\n}\n","import { FormFieldProvider } from '@asnewyla/input';\nimport { useMemo, useRef, useState } from 'react';\nimport { flushSync } from 'react-dom';\nimport { FormErrorSummary } from './FormErrorSummary';\nimport { type InvalidField, useFieldRegistry } from './useFieldRegistry';\n\nexport type FormProps = {\n children: React.ReactNode;\n onSubmit: (values: Record<string, string>) => void;\n};\n\nexport function Form({ children, onSubmit }: FormProps) {\n const formRef = useRef<HTMLFormElement>(null);\n const summaryRef = useRef<HTMLDivElement>(null);\n const {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n } = useFieldRegistry(formRef);\n\n const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);\n\n function handleSubmit(event: React.FormEvent<HTMLFormElement>) {\n event.preventDefault();\n\n let hasError = false;\n // flushSync commits the validation state synchronously, so the\n // summary is already in the DOM by the time we call .focus() below.\n flushSync(() => {\n for (const name of fieldNames()) {\n if (validateField(name)) hasError = true;\n }\n setHasAttemptedSubmit(true);\n });\n if (hasError) {\n summaryRef.current?.focus();\n return;\n }\n\n const values: Record<string, string> = {};\n for (const [key, value] of new FormData(event.currentTarget).entries()) {\n values[key] = String(value);\n }\n onSubmit(values);\n }\n\n const invalid: InvalidField[] = hasAttemptedSubmit ? invalidFields() : [];\n\n const contextValue = useMemo(\n () => ({ registerField, unregisterField, validateField, errors }),\n [registerField, unregisterField, validateField, errors],\n );\n\n return (\n <form ref={formRef} onSubmit={handleSubmit} noValidate>\n {invalid.length > 0 && (\n <FormErrorSummary ref={summaryRef} invalidFields={invalid} />\n )}\n <FormFieldProvider value={contextValue}>{children}</FormFieldProvider>\n </form>\n );\n}\n","import {\n Input,\n type InputProps,\n type ValidationRules,\n useFormFieldContext,\n} from '@asnewyla/input';\nimport { type ForwardedRef, forwardRef, useEffect, useRef } from 'react';\n\nexport type FormFieldInputProps = Omit<InputProps, 'name'> & {\n name: string;\n validate?: (value: string) => string | undefined;\n};\n\nfunction FormFieldInputInner(\n {\n name,\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n error,\n onBlur,\n ...rest\n }: FormFieldInputProps,\n ref: ForwardedRef<HTMLInputElement>,\n) {\n // useFormFieldContext() returns null when there's no ancestor\n // FormFieldProvider — that's what makes FormFieldInput a no-op outside a\n // Form (still a perfectly usable Input, just without registration).\n const formField = useFormFieldContext();\n\n // A stable ref we always own, so registerField always gets a real node —\n // separate from `ref`, which is whatever (or nothing) the consumer passed.\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const rules: ValidationRules = {\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n };\n\n // Deliberately depends on formField?.registerField/unregisterField, not\n // on `formField` itself — Form's context value changes identity whenever\n // its `errors` state updates (that's correct: this component needs to\n // re-render to read the new error). But if this effect depended on the\n // whole object, every validation would also re-trigger registration:\n // cleanup would call the *previous* render's unregisterField, deleting\n // the field and wiping the error validateField just set, in the same\n // tick it was written. Depending on the two functions directly (stable\n // via Form's useCallback) keeps registration decoupled from errors\n // changing.\n // Also deliberately not depending on `rules` — it's a fresh object every\n // render, so registration happens once per mount (or when formField/name\n // change), not on every rule-prop tweak. Rule props are effectively fixed\n // at mount for v0.x; no test or use case yet needs them reactive.\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally narrowed deps — see comment above\n useEffect(() => {\n if (!formField) return;\n formField.registerField(name, { ref: inputRef, rules });\n return () => formField.unregisterField(name);\n }, [formField?.registerField, formField?.unregisterField, name]);\n\n // Merges `inputRef` (ours, for registration) with `ref` (the consumer's\n // forwarded one, if any) so both end up pointing at the same DOM node —\n // neither one silently loses the element to the other.\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n };\n\n function handleBlur(event: React.FocusEvent<HTMLInputElement>) {\n if (formField) {\n formField.validateField(name);\n }\n onBlur?.(event);\n }\n\n const formContextError = formField ? formField.errors[name] : undefined;\n const formFieldError = error ?? formContextError;\n\n return (\n <Input\n name={name}\n required={required}\n pattern={pattern}\n minLength={minLength}\n maxLength={maxLength}\n min={min}\n max={max}\n {...rest}\n error={formFieldError}\n onBlur={handleBlur}\n ref={setRefs}\n />\n );\n}\n\nexport const FormFieldInput = forwardRef<HTMLInputElement, FormFieldInputProps>(\n FormFieldInputInner,\n);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/Form.tsx","../src/components/FormErrorSummary.tsx","../src/components/useFieldRegistry.ts","../src/components/validation.ts","../src/components/FormFieldInput.tsx"],"sourcesContent":["export { type FormProps, Form } from './components';\nexport { type FormFieldInputProps, FormFieldInput } from './components';\n","import { FormFieldProvider } from '@asnewyla/input';\nimport {\n type ComponentPropsWithoutRef,\n type ForwardedRef,\n forwardRef,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { flushSync } from 'react-dom';\nimport { FormErrorSummary } from './FormErrorSummary';\nimport { type InvalidField, useFieldRegistry } from './useFieldRegistry';\n\nexport type FormProps = Omit<ComponentPropsWithoutRef<'form'>, 'onSubmit'> & {\n children: React.ReactNode;\n onSubmit?: (values: Record<string, string>) => void;\n};\n\nfunction FormInner(\n { children, onSubmit, noValidate = true, ...restProps }: FormProps,\n ref: ForwardedRef<HTMLFormElement>,\n) {\n const formRef = useRef<HTMLFormElement>(null);\n useImperativeHandle(ref, () => formRef.current as HTMLFormElement);\n const summaryRef = useRef<HTMLDivElement>(null);\n const {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n } = useFieldRegistry(formRef);\n\n const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);\n\n function handleSubmit(event: React.FormEvent<HTMLFormElement>) {\n let hasError = false;\n // flushSync commits the validation state synchronously, so the\n // summary is already in the DOM by the time we call .focus() below.\n flushSync(() => {\n for (const name of fieldNames()) {\n if (validateField(name)) hasError = true;\n }\n setHasAttemptedSubmit(true);\n });\n if (hasError) {\n event.preventDefault();\n summaryRef.current?.focus();\n return;\n }\n\n if (!onSubmit) return;\n // With an action, the native submit (or a React 19 form action) goes ahead.\n if (restProps.action === undefined) event.preventDefault();\n\n const values: Record<string, string> = {};\n for (const [key, value] of new FormData(event.currentTarget).entries()) {\n values[key] = String(value);\n }\n onSubmit(values);\n }\n\n const invalid: InvalidField[] = hasAttemptedSubmit ? invalidFields() : [];\n\n const contextValue = useMemo(\n () => ({ registerField, unregisterField, validateField, errors }),\n [registerField, unregisterField, validateField, errors],\n );\n\n return (\n <form\n {...restProps}\n ref={formRef}\n onSubmit={handleSubmit}\n noValidate={noValidate}\n >\n {invalid.length > 0 && (\n <FormErrorSummary ref={summaryRef} invalidFields={invalid} />\n )}\n <FormFieldProvider value={contextValue}>{children}</FormFieldProvider>\n </form>\n );\n}\n\nexport const Form = forwardRef(FormInner);\n","import { forwardRef } from 'react';\nimport type { InvalidField } from './useFieldRegistry';\nimport './FormErrorSummary.css';\n\nexport type FormErrorSummaryProps = {\n invalidFields: InvalidField[];\n};\n\nexport const FormErrorSummary = forwardRef<\n HTMLDivElement,\n FormErrorSummaryProps\n>(function FormErrorSummary({ invalidFields }, ref) {\n return (\n <div className=\"xd-form-error-summary\" role=\"alert\" tabIndex={-1} ref={ref}>\n <p className=\"xd-form-error-summary-heading\">There is a problem</p>\n <ul>\n {invalidFields.map(({ name, id, message }) => (\n <li key={name}>\n <a\n href={`#${id}`}\n onClick={(event) => {\n event.preventDefault();\n document.getElementById(id)?.focus();\n }}\n >\n {message}\n </a>\n </li>\n ))}\n </ul>\n </div>\n );\n});\n","import type { FieldRegistration } from '@asnewyla/input';\nimport { type RefObject, useCallback, useRef, useState } from 'react';\nimport { validateValue } from './validation';\n\nexport interface InvalidField {\n name: string;\n id: string;\n message: string;\n}\n\nexport interface FieldRegistry {\n errors: Record<string, string | undefined>;\n registerField: (name: string, registration: FieldRegistration) => void;\n unregisterField: (name: string) => void;\n validateField: (name: string) => string | undefined;\n fieldNames: () => string[];\n invalidFields: () => InvalidField[];\n}\n\nexport function useFieldRegistry(\n formRef: RefObject<HTMLFormElement>,\n): FieldRegistry {\n const fieldsRef = useRef<Map<string, FieldRegistration>>(new Map());\n const [errors, setErrors] = useState<Record<string, string | undefined>>({});\n\n const registerField = useCallback(\n (name: string, registration: FieldRegistration) => {\n fieldsRef.current.set(name, registration);\n },\n [],\n );\n\n const unregisterField = useCallback((name: string) => {\n fieldsRef.current.delete(name);\n setErrors((prev) => {\n if (!(name in prev)) return prev;\n const next = { ...prev };\n delete next[name];\n return next;\n });\n }, []);\n\n const getFieldValue = useCallback(\n (name: string): string => {\n if (!formRef.current) return '';\n return String(new FormData(formRef.current).get(name) ?? '');\n },\n [formRef],\n );\n\n const validateField = useCallback(\n (name: string) => {\n const field = fieldsRef.current.get(name);\n const error = validateValue(getFieldValue(name), field?.rules);\n setErrors((prev) => ({ ...prev, [name]: error }));\n return error;\n },\n [getFieldValue],\n );\n\n const fieldNames = useCallback(\n () => Array.from(fieldsRef.current.keys()),\n [],\n );\n\n const invalidFields = useCallback((): InvalidField[] => {\n const result: InvalidField[] = [];\n for (const [name, message] of Object.entries(errors)) {\n if (!message) continue;\n const id = fieldsRef.current.get(name)?.ref.current?.id;\n if (!id) continue;\n result.push({ name, id, message });\n }\n return result;\n }, [errors]);\n\n return {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n };\n}\n","import type { ValidationRules } from '@asnewyla/input';\n\nexport function validateValue(\n value: string,\n rules?: ValidationRules,\n): string | undefined {\n if (!rules) return undefined;\n\n if (rules.required && value.trim() === '') {\n return typeof rules.required === 'string'\n ? rules.required\n : 'This field is required';\n }\n if (rules.pattern && value !== '' && !new RegExp(rules.pattern).test(value)) {\n return 'Invalid format';\n }\n if (rules.minLength !== undefined && value.length < rules.minLength) {\n return `Must be at least ${rules.minLength} characters`;\n }\n if (rules.maxLength !== undefined && value.length > rules.maxLength) {\n return `Must be at most ${rules.maxLength} characters`;\n }\n if (\n rules.min !== undefined &&\n value !== '' &&\n Number(value) < Number(rules.min)\n ) {\n return `Must be at least ${rules.min}`;\n }\n if (\n rules.max !== undefined &&\n value !== '' &&\n Number(value) > Number(rules.max)\n ) {\n return `Must be at most ${rules.max}`;\n }\n if (rules.validate) {\n return rules.validate(value);\n }\n return undefined;\n}\n","import {\n Input,\n type InputProps,\n type ValidationRules,\n useFormFieldContext,\n} from '@asnewyla/input';\nimport { type ForwardedRef, forwardRef, useEffect, useRef } from 'react';\n\nexport type FormFieldInputProps = Omit<InputProps, 'name'> & {\n name: string;\n validate?: (value: string) => string | undefined;\n};\n\nfunction FormFieldInputInner(\n {\n name,\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n error,\n onBlur,\n ...rest\n }: FormFieldInputProps,\n ref: ForwardedRef<HTMLInputElement>,\n) {\n // useFormFieldContext() returns null when there's no ancestor\n // FormFieldProvider — that's what makes FormFieldInput a no-op outside a\n // Form (still a perfectly usable Input, just without registration).\n const formField = useFormFieldContext();\n\n // A stable ref we always own, so registerField always gets a real node —\n // separate from `ref`, which is whatever (or nothing) the consumer passed.\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const rules: ValidationRules = {\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n };\n\n // Deliberately depends on formField?.registerField/unregisterField, not\n // on `formField` itself — Form's context value changes identity whenever\n // its `errors` state updates (that's correct: this component needs to\n // re-render to read the new error). But if this effect depended on the\n // whole object, every validation would also re-trigger registration:\n // cleanup would call the *previous* render's unregisterField, deleting\n // the field and wiping the error validateField just set, in the same\n // tick it was written. Depending on the two functions directly (stable\n // via Form's useCallback) keeps registration decoupled from errors\n // changing.\n // Also deliberately not depending on `rules` — it's a fresh object every\n // render, so registration happens once per mount (or when formField/name\n // change), not on every rule-prop tweak. Rule props are effectively fixed\n // at mount for v0.x; no test or use case yet needs them reactive.\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally narrowed deps — see comment above\n useEffect(() => {\n if (!formField) return;\n formField.registerField(name, { ref: inputRef, rules });\n return () => formField.unregisterField(name);\n }, [formField?.registerField, formField?.unregisterField, name]);\n\n // Merges `inputRef` (ours, for registration) with `ref` (the consumer's\n // forwarded one, if any) so both end up pointing at the same DOM node —\n // neither one silently loses the element to the other.\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n };\n\n function handleBlur(event: React.FocusEvent<HTMLInputElement>) {\n if (formField) {\n formField.validateField(name);\n }\n onBlur?.(event);\n }\n\n const formContextError = formField ? formField.errors[name] : undefined;\n const formFieldError = error ?? formContextError;\n\n return (\n <Input\n name={name}\n required={required}\n pattern={pattern}\n minLength={minLength}\n maxLength={maxLength}\n min={min}\n max={max}\n {...rest}\n error={formFieldError}\n onBlur={handleBlur}\n ref={setRefs}\n />\n );\n}\n\nexport const FormFieldInput = forwardRef<HTMLInputElement, FormFieldInputProps>(\n FormFieldInputInner,\n);\n"],"mappings":"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","names":["import_react","FormErrorSummary","import_react","import_jsx_runtime","import_input","import_react","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
import { forwardRef, useRef, useEffect, useState, useMemo, useCallback } from 'react';
|
|
3
|
-
import { flushSync } from 'react-dom';
|
|
4
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
1
|
+
'use client';
|
|
5
2
|
|
|
6
3
|
// src/components/Form.tsx
|
|
4
|
+
import { FormFieldProvider } from "@asnewyla/input";
|
|
5
|
+
import {
|
|
6
|
+
forwardRef as forwardRef2,
|
|
7
|
+
useImperativeHandle,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef as useRef2,
|
|
10
|
+
useState as useState2
|
|
11
|
+
} from "react";
|
|
12
|
+
import { flushSync } from "react-dom";
|
|
13
|
+
|
|
14
|
+
// src/components/FormErrorSummary.tsx
|
|
15
|
+
import { forwardRef } from "react";
|
|
16
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
17
|
var FormErrorSummary = forwardRef(function FormErrorSummary2({ invalidFields }, ref) {
|
|
8
18
|
return /* @__PURE__ */ jsxs("div", { className: "xd-form-error-summary", role: "alert", tabIndex: -1, ref, children: [
|
|
9
19
|
/* @__PURE__ */ jsx("p", { className: "xd-form-error-summary-heading", children: "There is a problem" }),
|
|
@@ -21,6 +31,9 @@ var FormErrorSummary = forwardRef(function FormErrorSummary2({ invalidFields },
|
|
|
21
31
|
] });
|
|
22
32
|
});
|
|
23
33
|
|
|
34
|
+
// src/components/useFieldRegistry.ts
|
|
35
|
+
import { useCallback, useRef, useState } from "react";
|
|
36
|
+
|
|
24
37
|
// src/components/validation.ts
|
|
25
38
|
function validateValue(value, rules) {
|
|
26
39
|
if (!rules) return void 0;
|
|
@@ -106,9 +119,13 @@ function useFieldRegistry(formRef) {
|
|
|
106
119
|
invalidFields
|
|
107
120
|
};
|
|
108
121
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
|
|
123
|
+
// src/components/Form.tsx
|
|
124
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
125
|
+
function FormInner({ children, onSubmit, noValidate = true, ...restProps }, ref) {
|
|
126
|
+
const formRef = useRef2(null);
|
|
127
|
+
useImperativeHandle(ref, () => formRef.current);
|
|
128
|
+
const summaryRef = useRef2(null);
|
|
112
129
|
const {
|
|
113
130
|
errors,
|
|
114
131
|
registerField,
|
|
@@ -117,9 +134,8 @@ function Form({ children, onSubmit }) {
|
|
|
117
134
|
fieldNames,
|
|
118
135
|
invalidFields
|
|
119
136
|
} = useFieldRegistry(formRef);
|
|
120
|
-
const [hasAttemptedSubmit, setHasAttemptedSubmit] =
|
|
137
|
+
const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState2(false);
|
|
121
138
|
function handleSubmit(event) {
|
|
122
|
-
event.preventDefault();
|
|
123
139
|
let hasError = false;
|
|
124
140
|
flushSync(() => {
|
|
125
141
|
for (const name of fieldNames()) {
|
|
@@ -128,9 +144,12 @@ function Form({ children, onSubmit }) {
|
|
|
128
144
|
setHasAttemptedSubmit(true);
|
|
129
145
|
});
|
|
130
146
|
if (hasError) {
|
|
147
|
+
event.preventDefault();
|
|
131
148
|
summaryRef.current?.focus();
|
|
132
149
|
return;
|
|
133
150
|
}
|
|
151
|
+
if (!onSubmit) return;
|
|
152
|
+
if (restProps.action === void 0) event.preventDefault();
|
|
134
153
|
const values = {};
|
|
135
154
|
for (const [key, value] of new FormData(event.currentTarget).entries()) {
|
|
136
155
|
values[key] = String(value);
|
|
@@ -142,11 +161,29 @@ function Form({ children, onSubmit }) {
|
|
|
142
161
|
() => ({ registerField, unregisterField, validateField, errors }),
|
|
143
162
|
[registerField, unregisterField, validateField, errors]
|
|
144
163
|
);
|
|
145
|
-
return /* @__PURE__ */
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
return /* @__PURE__ */ jsxs2(
|
|
165
|
+
"form",
|
|
166
|
+
{
|
|
167
|
+
...restProps,
|
|
168
|
+
ref: formRef,
|
|
169
|
+
onSubmit: handleSubmit,
|
|
170
|
+
noValidate,
|
|
171
|
+
children: [
|
|
172
|
+
invalid.length > 0 && /* @__PURE__ */ jsx2(FormErrorSummary, { ref: summaryRef, invalidFields: invalid }),
|
|
173
|
+
/* @__PURE__ */ jsx2(FormFieldProvider, { value: contextValue, children })
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
);
|
|
149
177
|
}
|
|
178
|
+
var Form = forwardRef2(FormInner);
|
|
179
|
+
|
|
180
|
+
// src/components/FormFieldInput.tsx
|
|
181
|
+
import {
|
|
182
|
+
Input,
|
|
183
|
+
useFormFieldContext
|
|
184
|
+
} from "@asnewyla/input";
|
|
185
|
+
import { forwardRef as forwardRef3, useEffect, useRef as useRef3 } from "react";
|
|
186
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
150
187
|
function FormFieldInputInner({
|
|
151
188
|
name,
|
|
152
189
|
required,
|
|
@@ -161,7 +198,7 @@ function FormFieldInputInner({
|
|
|
161
198
|
...rest
|
|
162
199
|
}, ref) {
|
|
163
200
|
const formField = useFormFieldContext();
|
|
164
|
-
const inputRef =
|
|
201
|
+
const inputRef = useRef3(null);
|
|
165
202
|
const rules = {
|
|
166
203
|
required,
|
|
167
204
|
pattern,
|
|
@@ -192,7 +229,7 @@ function FormFieldInputInner({
|
|
|
192
229
|
}
|
|
193
230
|
const formContextError = formField ? formField.errors[name] : void 0;
|
|
194
231
|
const formFieldError = error ?? formContextError;
|
|
195
|
-
return /* @__PURE__ */
|
|
232
|
+
return /* @__PURE__ */ jsx3(
|
|
196
233
|
Input,
|
|
197
234
|
{
|
|
198
235
|
name,
|
|
@@ -209,10 +246,11 @@ function FormFieldInputInner({
|
|
|
209
246
|
}
|
|
210
247
|
);
|
|
211
248
|
}
|
|
212
|
-
var FormFieldInput =
|
|
249
|
+
var FormFieldInput = forwardRef3(
|
|
213
250
|
FormFieldInputInner
|
|
214
251
|
);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
252
|
+
export {
|
|
253
|
+
Form,
|
|
254
|
+
FormFieldInput
|
|
255
|
+
};
|
|
218
256
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FormErrorSummary.tsx","../src/components/validation.ts","../src/components/useFieldRegistry.ts","../src/components/Form.tsx","../src/components/FormFieldInput.tsx"],"names":["FormErrorSummary","useRef","useState","jsxs","jsx","forwardRef"],"mappings":"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","file":"index.mjs","sourcesContent":["import { forwardRef } from 'react';\nimport type { InvalidField } from './useFieldRegistry';\nimport './FormErrorSummary.css';\n\nexport type FormErrorSummaryProps = {\n invalidFields: InvalidField[];\n};\n\nexport const FormErrorSummary = forwardRef<\n HTMLDivElement,\n FormErrorSummaryProps\n>(function FormErrorSummary({ invalidFields }, ref) {\n return (\n <div className=\"xd-form-error-summary\" role=\"alert\" tabIndex={-1} ref={ref}>\n <p className=\"xd-form-error-summary-heading\">There is a problem</p>\n <ul>\n {invalidFields.map(({ name, id, message }) => (\n <li key={name}>\n <a\n href={`#${id}`}\n onClick={(event) => {\n event.preventDefault();\n document.getElementById(id)?.focus();\n }}\n >\n {message}\n </a>\n </li>\n ))}\n </ul>\n </div>\n );\n});\n","import type { ValidationRules } from '@asnewyla/input';\n\nexport function validateValue(\n value: string,\n rules?: ValidationRules,\n): string | undefined {\n if (!rules) return undefined;\n\n if (rules.required && value.trim() === '') {\n return typeof rules.required === 'string'\n ? rules.required\n : 'This field is required';\n }\n if (rules.pattern && value !== '' && !new RegExp(rules.pattern).test(value)) {\n return 'Invalid format';\n }\n if (rules.minLength !== undefined && value.length < rules.minLength) {\n return `Must be at least ${rules.minLength} characters`;\n }\n if (rules.maxLength !== undefined && value.length > rules.maxLength) {\n return `Must be at most ${rules.maxLength} characters`;\n }\n if (\n rules.min !== undefined &&\n value !== '' &&\n Number(value) < Number(rules.min)\n ) {\n return `Must be at least ${rules.min}`;\n }\n if (\n rules.max !== undefined &&\n value !== '' &&\n Number(value) > Number(rules.max)\n ) {\n return `Must be at most ${rules.max}`;\n }\n if (rules.validate) {\n return rules.validate(value);\n }\n return undefined;\n}\n","import type { FieldRegistration } from '@asnewyla/input';\nimport { type RefObject, useCallback, useRef, useState } from 'react';\nimport { validateValue } from './validation';\n\nexport interface InvalidField {\n name: string;\n id: string;\n message: string;\n}\n\nexport interface FieldRegistry {\n errors: Record<string, string | undefined>;\n registerField: (name: string, registration: FieldRegistration) => void;\n unregisterField: (name: string) => void;\n validateField: (name: string) => string | undefined;\n fieldNames: () => string[];\n invalidFields: () => InvalidField[];\n}\n\nexport function useFieldRegistry(\n formRef: RefObject<HTMLFormElement>,\n): FieldRegistry {\n const fieldsRef = useRef<Map<string, FieldRegistration>>(new Map());\n const [errors, setErrors] = useState<Record<string, string | undefined>>({});\n\n const registerField = useCallback(\n (name: string, registration: FieldRegistration) => {\n fieldsRef.current.set(name, registration);\n },\n [],\n );\n\n const unregisterField = useCallback((name: string) => {\n fieldsRef.current.delete(name);\n setErrors((prev) => {\n if (!(name in prev)) return prev;\n const next = { ...prev };\n delete next[name];\n return next;\n });\n }, []);\n\n const getFieldValue = useCallback(\n (name: string): string => {\n if (!formRef.current) return '';\n return String(new FormData(formRef.current).get(name) ?? '');\n },\n [formRef],\n );\n\n const validateField = useCallback(\n (name: string) => {\n const field = fieldsRef.current.get(name);\n const error = validateValue(getFieldValue(name), field?.rules);\n setErrors((prev) => ({ ...prev, [name]: error }));\n return error;\n },\n [getFieldValue],\n );\n\n const fieldNames = useCallback(\n () => Array.from(fieldsRef.current.keys()),\n [],\n );\n\n const invalidFields = useCallback((): InvalidField[] => {\n const result: InvalidField[] = [];\n for (const [name, message] of Object.entries(errors)) {\n if (!message) continue;\n const id = fieldsRef.current.get(name)?.ref.current?.id;\n if (!id) continue;\n result.push({ name, id, message });\n }\n return result;\n }, [errors]);\n\n return {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n };\n}\n","import { FormFieldProvider } from '@asnewyla/input';\nimport { useMemo, useRef, useState } from 'react';\nimport { flushSync } from 'react-dom';\nimport { FormErrorSummary } from './FormErrorSummary';\nimport { type InvalidField, useFieldRegistry } from './useFieldRegistry';\n\nexport type FormProps = {\n children: React.ReactNode;\n onSubmit: (values: Record<string, string>) => void;\n};\n\nexport function Form({ children, onSubmit }: FormProps) {\n const formRef = useRef<HTMLFormElement>(null);\n const summaryRef = useRef<HTMLDivElement>(null);\n const {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n } = useFieldRegistry(formRef);\n\n const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);\n\n function handleSubmit(event: React.FormEvent<HTMLFormElement>) {\n event.preventDefault();\n\n let hasError = false;\n // flushSync commits the validation state synchronously, so the\n // summary is already in the DOM by the time we call .focus() below.\n flushSync(() => {\n for (const name of fieldNames()) {\n if (validateField(name)) hasError = true;\n }\n setHasAttemptedSubmit(true);\n });\n if (hasError) {\n summaryRef.current?.focus();\n return;\n }\n\n const values: Record<string, string> = {};\n for (const [key, value] of new FormData(event.currentTarget).entries()) {\n values[key] = String(value);\n }\n onSubmit(values);\n }\n\n const invalid: InvalidField[] = hasAttemptedSubmit ? invalidFields() : [];\n\n const contextValue = useMemo(\n () => ({ registerField, unregisterField, validateField, errors }),\n [registerField, unregisterField, validateField, errors],\n );\n\n return (\n <form ref={formRef} onSubmit={handleSubmit} noValidate>\n {invalid.length > 0 && (\n <FormErrorSummary ref={summaryRef} invalidFields={invalid} />\n )}\n <FormFieldProvider value={contextValue}>{children}</FormFieldProvider>\n </form>\n );\n}\n","import {\n Input,\n type InputProps,\n type ValidationRules,\n useFormFieldContext,\n} from '@asnewyla/input';\nimport { type ForwardedRef, forwardRef, useEffect, useRef } from 'react';\n\nexport type FormFieldInputProps = Omit<InputProps, 'name'> & {\n name: string;\n validate?: (value: string) => string | undefined;\n};\n\nfunction FormFieldInputInner(\n {\n name,\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n error,\n onBlur,\n ...rest\n }: FormFieldInputProps,\n ref: ForwardedRef<HTMLInputElement>,\n) {\n // useFormFieldContext() returns null when there's no ancestor\n // FormFieldProvider — that's what makes FormFieldInput a no-op outside a\n // Form (still a perfectly usable Input, just without registration).\n const formField = useFormFieldContext();\n\n // A stable ref we always own, so registerField always gets a real node —\n // separate from `ref`, which is whatever (or nothing) the consumer passed.\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const rules: ValidationRules = {\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n };\n\n // Deliberately depends on formField?.registerField/unregisterField, not\n // on `formField` itself — Form's context value changes identity whenever\n // its `errors` state updates (that's correct: this component needs to\n // re-render to read the new error). But if this effect depended on the\n // whole object, every validation would also re-trigger registration:\n // cleanup would call the *previous* render's unregisterField, deleting\n // the field and wiping the error validateField just set, in the same\n // tick it was written. Depending on the two functions directly (stable\n // via Form's useCallback) keeps registration decoupled from errors\n // changing.\n // Also deliberately not depending on `rules` — it's a fresh object every\n // render, so registration happens once per mount (or when formField/name\n // change), not on every rule-prop tweak. Rule props are effectively fixed\n // at mount for v0.x; no test or use case yet needs them reactive.\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally narrowed deps — see comment above\n useEffect(() => {\n if (!formField) return;\n formField.registerField(name, { ref: inputRef, rules });\n return () => formField.unregisterField(name);\n }, [formField?.registerField, formField?.unregisterField, name]);\n\n // Merges `inputRef` (ours, for registration) with `ref` (the consumer's\n // forwarded one, if any) so both end up pointing at the same DOM node —\n // neither one silently loses the element to the other.\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n };\n\n function handleBlur(event: React.FocusEvent<HTMLInputElement>) {\n if (formField) {\n formField.validateField(name);\n }\n onBlur?.(event);\n }\n\n const formContextError = formField ? formField.errors[name] : undefined;\n const formFieldError = error ?? formContextError;\n\n return (\n <Input\n name={name}\n required={required}\n pattern={pattern}\n minLength={minLength}\n maxLength={maxLength}\n min={min}\n max={max}\n {...rest}\n error={formFieldError}\n onBlur={handleBlur}\n ref={setRefs}\n />\n );\n}\n\nexport const FormFieldInput = forwardRef<HTMLInputElement, FormFieldInputProps>(\n FormFieldInputInner,\n);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/Form.tsx","../src/components/FormErrorSummary.tsx","../src/components/useFieldRegistry.ts","../src/components/validation.ts","../src/components/FormFieldInput.tsx"],"sourcesContent":["import { FormFieldProvider } from '@asnewyla/input';\nimport {\n type ComponentPropsWithoutRef,\n type ForwardedRef,\n forwardRef,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { flushSync } from 'react-dom';\nimport { FormErrorSummary } from './FormErrorSummary';\nimport { type InvalidField, useFieldRegistry } from './useFieldRegistry';\n\nexport type FormProps = Omit<ComponentPropsWithoutRef<'form'>, 'onSubmit'> & {\n children: React.ReactNode;\n onSubmit?: (values: Record<string, string>) => void;\n};\n\nfunction FormInner(\n { children, onSubmit, noValidate = true, ...restProps }: FormProps,\n ref: ForwardedRef<HTMLFormElement>,\n) {\n const formRef = useRef<HTMLFormElement>(null);\n useImperativeHandle(ref, () => formRef.current as HTMLFormElement);\n const summaryRef = useRef<HTMLDivElement>(null);\n const {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n } = useFieldRegistry(formRef);\n\n const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);\n\n function handleSubmit(event: React.FormEvent<HTMLFormElement>) {\n let hasError = false;\n // flushSync commits the validation state synchronously, so the\n // summary is already in the DOM by the time we call .focus() below.\n flushSync(() => {\n for (const name of fieldNames()) {\n if (validateField(name)) hasError = true;\n }\n setHasAttemptedSubmit(true);\n });\n if (hasError) {\n event.preventDefault();\n summaryRef.current?.focus();\n return;\n }\n\n if (!onSubmit) return;\n // With an action, the native submit (or a React 19 form action) goes ahead.\n if (restProps.action === undefined) event.preventDefault();\n\n const values: Record<string, string> = {};\n for (const [key, value] of new FormData(event.currentTarget).entries()) {\n values[key] = String(value);\n }\n onSubmit(values);\n }\n\n const invalid: InvalidField[] = hasAttemptedSubmit ? invalidFields() : [];\n\n const contextValue = useMemo(\n () => ({ registerField, unregisterField, validateField, errors }),\n [registerField, unregisterField, validateField, errors],\n );\n\n return (\n <form\n {...restProps}\n ref={formRef}\n onSubmit={handleSubmit}\n noValidate={noValidate}\n >\n {invalid.length > 0 && (\n <FormErrorSummary ref={summaryRef} invalidFields={invalid} />\n )}\n <FormFieldProvider value={contextValue}>{children}</FormFieldProvider>\n </form>\n );\n}\n\nexport const Form = forwardRef(FormInner);\n","import { forwardRef } from 'react';\nimport type { InvalidField } from './useFieldRegistry';\nimport './FormErrorSummary.css';\n\nexport type FormErrorSummaryProps = {\n invalidFields: InvalidField[];\n};\n\nexport const FormErrorSummary = forwardRef<\n HTMLDivElement,\n FormErrorSummaryProps\n>(function FormErrorSummary({ invalidFields }, ref) {\n return (\n <div className=\"xd-form-error-summary\" role=\"alert\" tabIndex={-1} ref={ref}>\n <p className=\"xd-form-error-summary-heading\">There is a problem</p>\n <ul>\n {invalidFields.map(({ name, id, message }) => (\n <li key={name}>\n <a\n href={`#${id}`}\n onClick={(event) => {\n event.preventDefault();\n document.getElementById(id)?.focus();\n }}\n >\n {message}\n </a>\n </li>\n ))}\n </ul>\n </div>\n );\n});\n","import type { FieldRegistration } from '@asnewyla/input';\nimport { type RefObject, useCallback, useRef, useState } from 'react';\nimport { validateValue } from './validation';\n\nexport interface InvalidField {\n name: string;\n id: string;\n message: string;\n}\n\nexport interface FieldRegistry {\n errors: Record<string, string | undefined>;\n registerField: (name: string, registration: FieldRegistration) => void;\n unregisterField: (name: string) => void;\n validateField: (name: string) => string | undefined;\n fieldNames: () => string[];\n invalidFields: () => InvalidField[];\n}\n\nexport function useFieldRegistry(\n formRef: RefObject<HTMLFormElement>,\n): FieldRegistry {\n const fieldsRef = useRef<Map<string, FieldRegistration>>(new Map());\n const [errors, setErrors] = useState<Record<string, string | undefined>>({});\n\n const registerField = useCallback(\n (name: string, registration: FieldRegistration) => {\n fieldsRef.current.set(name, registration);\n },\n [],\n );\n\n const unregisterField = useCallback((name: string) => {\n fieldsRef.current.delete(name);\n setErrors((prev) => {\n if (!(name in prev)) return prev;\n const next = { ...prev };\n delete next[name];\n return next;\n });\n }, []);\n\n const getFieldValue = useCallback(\n (name: string): string => {\n if (!formRef.current) return '';\n return String(new FormData(formRef.current).get(name) ?? '');\n },\n [formRef],\n );\n\n const validateField = useCallback(\n (name: string) => {\n const field = fieldsRef.current.get(name);\n const error = validateValue(getFieldValue(name), field?.rules);\n setErrors((prev) => ({ ...prev, [name]: error }));\n return error;\n },\n [getFieldValue],\n );\n\n const fieldNames = useCallback(\n () => Array.from(fieldsRef.current.keys()),\n [],\n );\n\n const invalidFields = useCallback((): InvalidField[] => {\n const result: InvalidField[] = [];\n for (const [name, message] of Object.entries(errors)) {\n if (!message) continue;\n const id = fieldsRef.current.get(name)?.ref.current?.id;\n if (!id) continue;\n result.push({ name, id, message });\n }\n return result;\n }, [errors]);\n\n return {\n errors,\n registerField,\n unregisterField,\n validateField,\n fieldNames,\n invalidFields,\n };\n}\n","import type { ValidationRules } from '@asnewyla/input';\n\nexport function validateValue(\n value: string,\n rules?: ValidationRules,\n): string | undefined {\n if (!rules) return undefined;\n\n if (rules.required && value.trim() === '') {\n return typeof rules.required === 'string'\n ? rules.required\n : 'This field is required';\n }\n if (rules.pattern && value !== '' && !new RegExp(rules.pattern).test(value)) {\n return 'Invalid format';\n }\n if (rules.minLength !== undefined && value.length < rules.minLength) {\n return `Must be at least ${rules.minLength} characters`;\n }\n if (rules.maxLength !== undefined && value.length > rules.maxLength) {\n return `Must be at most ${rules.maxLength} characters`;\n }\n if (\n rules.min !== undefined &&\n value !== '' &&\n Number(value) < Number(rules.min)\n ) {\n return `Must be at least ${rules.min}`;\n }\n if (\n rules.max !== undefined &&\n value !== '' &&\n Number(value) > Number(rules.max)\n ) {\n return `Must be at most ${rules.max}`;\n }\n if (rules.validate) {\n return rules.validate(value);\n }\n return undefined;\n}\n","import {\n Input,\n type InputProps,\n type ValidationRules,\n useFormFieldContext,\n} from '@asnewyla/input';\nimport { type ForwardedRef, forwardRef, useEffect, useRef } from 'react';\n\nexport type FormFieldInputProps = Omit<InputProps, 'name'> & {\n name: string;\n validate?: (value: string) => string | undefined;\n};\n\nfunction FormFieldInputInner(\n {\n name,\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n error,\n onBlur,\n ...rest\n }: FormFieldInputProps,\n ref: ForwardedRef<HTMLInputElement>,\n) {\n // useFormFieldContext() returns null when there's no ancestor\n // FormFieldProvider — that's what makes FormFieldInput a no-op outside a\n // Form (still a perfectly usable Input, just without registration).\n const formField = useFormFieldContext();\n\n // A stable ref we always own, so registerField always gets a real node —\n // separate from `ref`, which is whatever (or nothing) the consumer passed.\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const rules: ValidationRules = {\n required,\n pattern,\n minLength,\n maxLength,\n min,\n max,\n validate,\n };\n\n // Deliberately depends on formField?.registerField/unregisterField, not\n // on `formField` itself — Form's context value changes identity whenever\n // its `errors` state updates (that's correct: this component needs to\n // re-render to read the new error). But if this effect depended on the\n // whole object, every validation would also re-trigger registration:\n // cleanup would call the *previous* render's unregisterField, deleting\n // the field and wiping the error validateField just set, in the same\n // tick it was written. Depending on the two functions directly (stable\n // via Form's useCallback) keeps registration decoupled from errors\n // changing.\n // Also deliberately not depending on `rules` — it's a fresh object every\n // render, so registration happens once per mount (or when formField/name\n // change), not on every rule-prop tweak. Rule props are effectively fixed\n // at mount for v0.x; no test or use case yet needs them reactive.\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally narrowed deps — see comment above\n useEffect(() => {\n if (!formField) return;\n formField.registerField(name, { ref: inputRef, rules });\n return () => formField.unregisterField(name);\n }, [formField?.registerField, formField?.unregisterField, name]);\n\n // Merges `inputRef` (ours, for registration) with `ref` (the consumer's\n // forwarded one, if any) so both end up pointing at the same DOM node —\n // neither one silently loses the element to the other.\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n };\n\n function handleBlur(event: React.FocusEvent<HTMLInputElement>) {\n if (formField) {\n formField.validateField(name);\n }\n onBlur?.(event);\n }\n\n const formContextError = formField ? formField.errors[name] : undefined;\n const formFieldError = error ?? formContextError;\n\n return (\n <Input\n name={name}\n required={required}\n pattern={pattern}\n minLength={minLength}\n maxLength={maxLength}\n min={min}\n max={max}\n {...rest}\n error={formFieldError}\n onBlur={handleBlur}\n ref={setRefs}\n />\n );\n}\n\nexport const FormFieldInput = forwardRef<HTMLInputElement, FormFieldInputProps>(\n FormFieldInputInner,\n);\n"],"mappings":"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","names":["forwardRef","useRef","useState","FormErrorSummary","jsx","jsxs","useRef","useState","forwardRef","forwardRef","useRef","jsx"]}
|
package/dist/test-utils.js
CHANGED
|
@@ -1,28 +1,55 @@
|
|
|
1
|
-
'use
|
|
2
|
-
|
|
3
|
-
var
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
4
20
|
|
|
5
21
|
// src/test-utils.ts
|
|
22
|
+
var test_utils_exports = {};
|
|
23
|
+
__export(test_utils_exports, {
|
|
24
|
+
getErrorSummary: () => getErrorSummary,
|
|
25
|
+
getFieldInput: () => getFieldInput,
|
|
26
|
+
getSubmitButton: () => getSubmitButton,
|
|
27
|
+
queryErrorSummary: () => queryErrorSummary,
|
|
28
|
+
submitForm: () => submitForm
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(test_utils_exports);
|
|
31
|
+
var import_react = require("@testing-library/react");
|
|
6
32
|
function getFieldInput(name, role = "textbox") {
|
|
7
|
-
return
|
|
33
|
+
return import_react.screen.getByRole(role, { name });
|
|
8
34
|
}
|
|
9
35
|
function getSubmitButton(name = "Save") {
|
|
10
|
-
return
|
|
36
|
+
return import_react.screen.getByRole("button", { name });
|
|
11
37
|
}
|
|
12
38
|
function submitForm(user, name = "Save") {
|
|
13
39
|
return user.click(getSubmitButton(name));
|
|
14
40
|
}
|
|
15
41
|
function getErrorSummary() {
|
|
16
|
-
return
|
|
42
|
+
return import_react.screen.getByRole("alert");
|
|
17
43
|
}
|
|
18
44
|
function queryErrorSummary() {
|
|
19
|
-
return
|
|
45
|
+
return import_react.screen.queryByRole("alert");
|
|
20
46
|
}
|
|
21
|
-
|
|
22
|
-
exports
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
47
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
48
|
+
0 && (module.exports = {
|
|
49
|
+
getErrorSummary,
|
|
50
|
+
getFieldInput,
|
|
51
|
+
getSubmitButton,
|
|
52
|
+
queryErrorSummary,
|
|
53
|
+
submitForm
|
|
54
|
+
});
|
|
28
55
|
//# sourceMappingURL=test-utils.js.map
|
package/dist/test-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/test-utils.ts"],"
|
|
1
|
+
{"version":3,"sources":["../src/test-utils.ts"],"sourcesContent":["import { screen } from '@testing-library/react';\nimport type { UserEvent } from '@testing-library/user-event';\n\ntype FieldRole = 'textbox' | 'spinbutton';\n\nexport function getFieldInput(\n name: string | RegExp,\n role: FieldRole = 'textbox',\n): HTMLElement {\n return screen.getByRole(role, { name });\n}\n\nexport function getSubmitButton(name: string | RegExp = 'Save'): HTMLElement {\n return screen.getByRole('button', { name });\n}\n\nexport function submitForm(\n user: UserEvent,\n name: string | RegExp = 'Save',\n): Promise<void> {\n return user.click(getSubmitButton(name));\n}\n\nexport function getErrorSummary(): HTMLElement {\n return screen.getByRole('alert');\n}\n\nexport function queryErrorSummary(): HTMLElement | null {\n return screen.queryByRole('alert');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAuB;AAKhB,SAAS,cACd,MACA,OAAkB,WACL;AACb,SAAO,oBAAO,UAAU,MAAM,EAAE,KAAK,CAAC;AACxC;AAEO,SAAS,gBAAgB,OAAwB,QAAqB;AAC3E,SAAO,oBAAO,UAAU,UAAU,EAAE,KAAK,CAAC;AAC5C;AAEO,SAAS,WACd,MACA,OAAwB,QACT;AACf,SAAO,KAAK,MAAM,gBAAgB,IAAI,CAAC;AACzC;AAEO,SAAS,kBAA+B;AAC7C,SAAO,oBAAO,UAAU,OAAO;AACjC;AAEO,SAAS,oBAAwC;AACtD,SAAO,oBAAO,YAAY,OAAO;AACnC;","names":[]}
|
package/dist/test-utils.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
3
|
// src/test-utils.ts
|
|
4
|
+
import { screen } from "@testing-library/react";
|
|
4
5
|
function getFieldInput(name, role = "textbox") {
|
|
5
6
|
return screen.getByRole(role, { name });
|
|
6
7
|
}
|
|
@@ -16,7 +17,11 @@ function getErrorSummary() {
|
|
|
16
17
|
function queryErrorSummary() {
|
|
17
18
|
return screen.queryByRole("alert");
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
export {
|
|
21
|
+
getErrorSummary,
|
|
22
|
+
getFieldInput,
|
|
23
|
+
getSubmitButton,
|
|
24
|
+
queryErrorSummary,
|
|
25
|
+
submitForm
|
|
26
|
+
};
|
|
22
27
|
//# sourceMappingURL=test-utils.mjs.map
|
package/dist/test-utils.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/test-utils.ts"],"
|
|
1
|
+
{"version":3,"sources":["../src/test-utils.ts"],"sourcesContent":["import { screen } from '@testing-library/react';\nimport type { UserEvent } from '@testing-library/user-event';\n\ntype FieldRole = 'textbox' | 'spinbutton';\n\nexport function getFieldInput(\n name: string | RegExp,\n role: FieldRole = 'textbox',\n): HTMLElement {\n return screen.getByRole(role, { name });\n}\n\nexport function getSubmitButton(name: string | RegExp = 'Save'): HTMLElement {\n return screen.getByRole('button', { name });\n}\n\nexport function submitForm(\n user: UserEvent,\n name: string | RegExp = 'Save',\n): Promise<void> {\n return user.click(getSubmitButton(name));\n}\n\nexport function getErrorSummary(): HTMLElement {\n return screen.getByRole('alert');\n}\n\nexport function queryErrorSummary(): HTMLElement | null {\n return screen.queryByRole('alert');\n}\n"],"mappings":";;;AAAA,SAAS,cAAc;AAKhB,SAAS,cACd,MACA,OAAkB,WACL;AACb,SAAO,OAAO,UAAU,MAAM,EAAE,KAAK,CAAC;AACxC;AAEO,SAAS,gBAAgB,OAAwB,QAAqB;AAC3E,SAAO,OAAO,UAAU,UAAU,EAAE,KAAK,CAAC;AAC5C;AAEO,SAAS,WACd,MACA,OAAwB,QACT;AACf,SAAO,KAAK,MAAM,gBAAgB,IAAI,CAAC;AACzC;AAEO,SAAS,kBAA+B;AAC7C,SAAO,OAAO,UAAU,OAAO;AACjC;AAEO,SAAS,oBAAwC;AACtD,SAAO,OAAO,YAAY,OAAO;AACnC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asnewyla/form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Form wrapper with real validation and an accessible error summary",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@asnewyla/input": "0.2.
|
|
57
|
+
"@asnewyla/input": "0.2.2"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@testing-library/jest-dom": "^6.4.0",
|