@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 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
- ### Props
86
-
87
- | Prop | Type |
88
- |---|---|
89
- | `children` | `React.ReactNode` (**required**) |
90
- | `onSubmit` | `(values: Record<string, string>) => void` (**required**) |
91
-
92
- ## License
93
-
94
- MIT
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: (values: Record<string, string>) => void;
7
+ onSubmit?: (values: Record<string, string>) => void;
7
8
  };
8
- declare function Form({ children, onSubmit }: FormProps): react.JSX.Element;
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: (values: Record<string, string>) => void;
7
+ onSubmit?: (values: Record<string, string>) => void;
7
8
  };
8
- declare function Form({ children, onSubmit }: FormProps): react.JSX.Element;
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 strict';
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
- var input = require('@asnewyla/input');
4
- var react = require('react');
5
- var reactDom = require('react-dom');
6
- var jsxRuntime = require('react/jsx-runtime');
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 FormErrorSummary = react.forwardRef(function FormErrorSummary2({ invalidFields }, ref) {
10
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "xd-form-error-summary", role: "alert", tabIndex: -1, ref, children: [
11
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "xd-form-error-summary-heading", children: "There is a problem" }),
12
- /* @__PURE__ */ jsxRuntime.jsx("ul", { children: invalidFields.map(({ name, id, message }) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
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 = react.useRef(/* @__PURE__ */ new Map());
56
- const [errors, setErrors] = react.useState({});
57
- const registerField = react.useCallback(
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 = react.useCallback((name) => {
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 = react.useCallback(
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 = react.useCallback(
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 = react.useCallback(
119
+ const fieldNames = (0, import_react2.useCallback)(
89
120
  () => Array.from(fieldsRef.current.keys()),
90
121
  []
91
122
  );
92
- const invalidFields = react.useCallback(() => {
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
- function Form({ children, onSubmit }) {
112
- const formRef = react.useRef(null);
113
- const summaryRef = react.useRef(null);
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] = react.useState(false);
157
+ const [hasAttemptedSubmit, setHasAttemptedSubmit] = (0, import_react3.useState)(false);
123
158
  function handleSubmit(event) {
124
- event.preventDefault();
125
159
  let hasError = false;
126
- reactDom.flushSync(() => {
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 = react.useMemo(
180
+ const contextValue = (0, import_react3.useMemo)(
144
181
  () => ({ registerField, unregisterField, validateField, errors }),
145
182
  [registerField, unregisterField, validateField, errors]
146
183
  );
147
- return /* @__PURE__ */ jsxRuntime.jsxs("form", { ref: formRef, onSubmit: handleSubmit, noValidate: true, children: [
148
- invalid.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(FormErrorSummary, { ref: summaryRef, invalidFields: invalid }),
149
- /* @__PURE__ */ jsxRuntime.jsx(input.FormFieldProvider, { value: contextValue, children })
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 = input.useFormFieldContext();
166
- const inputRef = react.useRef(null);
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
- react.useEffect(() => {
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__ */ jsxRuntime.jsx(
198
- input.Input,
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 = react.forwardRef(
266
+ var FormFieldInput = (0, import_react4.forwardRef)(
215
267
  FormFieldInputInner
216
268
  );
217
-
218
- exports.Form = Form;
219
- exports.FormFieldInput = FormFieldInput;
220
- //# sourceMappingURL=index.js.map
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
- import { useFormFieldContext, Input, FormFieldProvider } from '@asnewyla/input';
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
- function Form({ children, onSubmit }) {
110
- const formRef = useRef(null);
111
- const summaryRef = useRef(null);
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] = useState(false);
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__ */ jsxs("form", { ref: formRef, onSubmit: handleSubmit, noValidate: true, children: [
146
- invalid.length > 0 && /* @__PURE__ */ jsx(FormErrorSummary, { ref: summaryRef, invalidFields: invalid }),
147
- /* @__PURE__ */ jsx(FormFieldProvider, { value: contextValue, children })
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 = useRef(null);
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__ */ jsx(
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 = forwardRef(
249
+ var FormFieldInput = forwardRef3(
213
250
  FormFieldInputInner
214
251
  );
215
-
216
- export { Form, FormFieldInput };
217
- //# sourceMappingURL=index.mjs.map
252
+ export {
253
+ Form,
254
+ FormFieldInput
255
+ };
218
256
  //# sourceMappingURL=index.mjs.map
@@ -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"]}
@@ -1,28 +1,55 @@
1
- 'use strict';
2
-
3
- var react = require('@testing-library/react');
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 react.screen.getByRole(role, { name });
33
+ return import_react.screen.getByRole(role, { name });
8
34
  }
9
35
  function getSubmitButton(name = "Save") {
10
- return react.screen.getByRole("button", { name });
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 react.screen.getByRole("alert");
42
+ return import_react.screen.getByRole("alert");
17
43
  }
18
44
  function queryErrorSummary() {
19
- return react.screen.queryByRole("alert");
45
+ return import_react.screen.queryByRole("alert");
20
46
  }
21
-
22
- exports.getErrorSummary = getErrorSummary;
23
- exports.getFieldInput = getFieldInput;
24
- exports.getSubmitButton = getSubmitButton;
25
- exports.queryErrorSummary = queryErrorSummary;
26
- exports.submitForm = submitForm;
27
- //# sourceMappingURL=test-utils.js.map
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
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/test-utils.ts"],"names":["screen"],"mappings":";;;;;AAKO,SAAS,aAAA,CACd,IAAA,EACA,IAAA,GAAkB,SAAA,EACL;AACb,EAAA,OAAOA,YAAA,CAAO,SAAA,CAAU,IAAA,EAAM,EAAE,MAAM,CAAA;AACxC;AAEO,SAAS,eAAA,CAAgB,OAAwB,MAAA,EAAqB;AAC3E,EAAA,OAAOA,YAAA,CAAO,SAAA,CAAU,QAAA,EAAU,EAAE,MAAM,CAAA;AAC5C;AAEO,SAAS,UAAA,CACd,IAAA,EACA,IAAA,GAAwB,MAAA,EACT;AACf,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,eAAA,CAAgB,IAAI,CAAC,CAAA;AACzC;AAEO,SAAS,eAAA,GAA+B;AAC7C,EAAA,OAAOA,YAAA,CAAO,UAAU,OAAO,CAAA;AACjC;AAEO,SAAS,iBAAA,GAAwC;AACtD,EAAA,OAAOA,YAAA,CAAO,YAAY,OAAO,CAAA;AACnC","file":"test-utils.js","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"]}
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":[]}
@@ -1,6 +1,7 @@
1
- import { screen } from '@testing-library/react';
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
- export { getErrorSummary, getFieldInput, getSubmitButton, queryErrorSummary, submitForm };
21
- //# sourceMappingURL=test-utils.mjs.map
20
+ export {
21
+ getErrorSummary,
22
+ getFieldInput,
23
+ getSubmitButton,
24
+ queryErrorSummary,
25
+ submitForm
26
+ };
22
27
  //# sourceMappingURL=test-utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/test-utils.ts"],"names":[],"mappings":";;;AAKO,SAAS,aAAA,CACd,IAAA,EACA,IAAA,GAAkB,SAAA,EACL;AACb,EAAA,OAAO,MAAA,CAAO,SAAA,CAAU,IAAA,EAAM,EAAE,MAAM,CAAA;AACxC;AAEO,SAAS,eAAA,CAAgB,OAAwB,MAAA,EAAqB;AAC3E,EAAA,OAAO,MAAA,CAAO,SAAA,CAAU,QAAA,EAAU,EAAE,MAAM,CAAA;AAC5C;AAEO,SAAS,UAAA,CACd,IAAA,EACA,IAAA,GAAwB,MAAA,EACT;AACf,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,eAAA,CAAgB,IAAI,CAAC,CAAA;AACzC;AAEO,SAAS,eAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,UAAU,OAAO,CAAA;AACjC;AAEO,SAAS,iBAAA,GAAwC;AACtD,EAAA,OAAO,MAAA,CAAO,YAAY,OAAO,CAAA;AACnC","file":"test-utils.mjs","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"]}
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.2.2",
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.1"
57
+ "@asnewyla/input": "0.2.2"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@testing-library/jest-dom": "^6.4.0",