solid-tiny-form 0.1.0 → 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 +144 -96
- package/dist/index.d.mts +3 -6
- package/dist/index.mjs +111 -179
- package/dist/resolvers/arktype.d.mts +2 -8
- package/dist/resolvers/arktype.mjs +48 -24
- package/dist/{types-CzHtMISe.d.mts → types-xBmBF3iK.d.mts} +21 -26
- package/dist/utils-0Ihz8msV.mjs +44 -0
- package/package.json +47 -48
package/README.md
CHANGED
|
@@ -1,96 +1,144 @@
|
|
|
1
|
-
# solid-tiny-form
|
|
2
|
-
|
|
3
|
-
A tiny SolidJS form state manager
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- `createForm` API for
|
|
8
|
-
- Form state is stored in a Solid store from `solid-js/store`.
|
|
9
|
-
- Pluggable resolver interface for schema validation libraries.
|
|
10
|
-
- Built-in ArkType resolver via `arktypeResolver`.
|
|
11
|
-
- Lightweight field-level rules for common cases.
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
pnpm add solid-tiny-form solid-js
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Install ArkType when using the optional ArkType resolver:
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
pnpm add arktype
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The ArkType adapter supports `arktype@^2.1.27` and is exposed from `solid-tiny-form/resolvers/arktype`.
|
|
26
|
-
|
|
27
|
-
## Usage
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { type } from "arktype";
|
|
31
|
-
import { createForm } from "solid-tiny-form";
|
|
32
|
-
import { arktypeResolver } from "solid-tiny-form/resolvers/arktype";
|
|
33
|
-
|
|
34
|
-
const schema = type({
|
|
35
|
-
email: "string.email",
|
|
36
|
-
age: "number >= 18",
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
1
|
+
# solid-tiny-form
|
|
2
|
+
|
|
3
|
+
A tiny SolidJS form state manager designed around Solid's fine-grained reactivity and `createStore`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- `createForm` API for creating fields, setting values, submitting, resetting, and validating.
|
|
8
|
+
- Form state is stored in a Solid store from `solid-js/store`.
|
|
9
|
+
- Pluggable resolver interface for schema validation libraries.
|
|
10
|
+
- Built-in ArkType resolver via `arktypeResolver`.
|
|
11
|
+
- Lightweight field-level rules for common cases.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm add solid-tiny-form solid-js
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Install ArkType when using the optional ArkType resolver:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm add arktype
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The ArkType adapter supports `arktype@^2.1.27` and is exposed from `solid-tiny-form/resolvers/arktype`.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { type } from "arktype";
|
|
31
|
+
import { createForm } from "solid-tiny-form";
|
|
32
|
+
import { arktypeResolver } from "solid-tiny-form/resolvers/arktype";
|
|
33
|
+
|
|
34
|
+
const schema = type({
|
|
35
|
+
email: "string.email",
|
|
36
|
+
age: "number >= 18",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
type FormValues = { email: string; age: number | undefined };
|
|
40
|
+
|
|
41
|
+
function Example() {
|
|
42
|
+
const form = createForm<FormValues>({
|
|
43
|
+
defaultValues: {
|
|
44
|
+
email: "",
|
|
45
|
+
age: 18,
|
|
46
|
+
},
|
|
47
|
+
resolver: arktypeResolver<FormValues>(schema),
|
|
48
|
+
mode: "onBlur",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const email = form.createField("email");
|
|
52
|
+
const age = form.createField("age");
|
|
53
|
+
|
|
54
|
+
const onSubmit = form.handleSubmit((values) => {
|
|
55
|
+
console.log(values);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<form onSubmit={onSubmit}>
|
|
60
|
+
<input
|
|
61
|
+
name={email.name}
|
|
62
|
+
value={email.value}
|
|
63
|
+
onInput={(event) => email.handleChange(event.currentTarget.value)}
|
|
64
|
+
onBlur={email.handleBlur}
|
|
65
|
+
/>
|
|
66
|
+
<input
|
|
67
|
+
type="number"
|
|
68
|
+
name={age.name}
|
|
69
|
+
value={age.value ?? ""}
|
|
70
|
+
onInput={(event) => {
|
|
71
|
+
const value = event.currentTarget.value;
|
|
72
|
+
age.handleChange(value === "" ? undefined : Number(value));
|
|
73
|
+
}}
|
|
74
|
+
onBlur={age.handleBlur}
|
|
75
|
+
/>
|
|
76
|
+
<button disabled={form.formState.isSubmitting}>Submit</button>
|
|
77
|
+
</form>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## API
|
|
83
|
+
|
|
84
|
+
### `createForm(options)`
|
|
85
|
+
|
|
86
|
+
Creates a form controller.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const form = createForm({
|
|
90
|
+
defaultValues,
|
|
91
|
+
resolver,
|
|
92
|
+
context,
|
|
93
|
+
mode: "onSubmit",
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Validation modes are `onSubmit`, `onBlur`, `onChange`, and `all`.
|
|
98
|
+
|
|
99
|
+
### `form.createField(name, options?)`
|
|
100
|
+
|
|
101
|
+
Use value callbacks with third-party or custom components:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
const form = createForm<{ country: string }>({
|
|
105
|
+
defaultValues: { country: "" },
|
|
106
|
+
mode: "onBlur",
|
|
107
|
+
});
|
|
108
|
+
const country = form.createField("country", { required: "Choose a country" });
|
|
109
|
+
|
|
110
|
+
// Select is a third-party component with a value callback.
|
|
111
|
+
<Select
|
|
112
|
+
name={country.name}
|
|
113
|
+
value={country.value}
|
|
114
|
+
onValueChange={country.handleChange}
|
|
115
|
+
onBlur={country.handleBlur}
|
|
116
|
+
/>
|
|
117
|
+
<span>{country.error?.message}</span>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Paths and values are typed with `FieldPath` and `FieldPathValue`. `value`, `error`, `touched`, and `dirty` are reactive getters: read them in JSX or another reactive scope; **do not destructure them**. They also reflect programmatic updates and reset.
|
|
121
|
+
|
|
122
|
+
Both handlers return `void`. Changes update dirty state; blur marks touched. Validation follows the form's mode. Rules persist across reset and subsequent bindings created without options. `handleChange` accepts the final value, including booleans and objects with a `target` property. It performs no event extraction or value conversion.
|
|
123
|
+
|
|
124
|
+
`FieldOptions<TValue, TValues>` supports `required`, `min`, `max`, `pattern`, and `validate`. Convert values explicitly at the component boundary, as shown above for numbers (empty input becomes `undefined`). For checkboxes, bind `checked={field.value}` and pass `event.currentTarget.checked` to `handleChange`. Bind `name` explicitly. Do not spread a controller into input props or destructure its getters.
|
|
125
|
+
|
|
126
|
+
This is a breaking API change: `createField` is the only field-binding API; no compatibility aliases are provided.
|
|
127
|
+
|
|
128
|
+
### Resolver interface
|
|
129
|
+
|
|
130
|
+
Resolvers receive current values and return parsed values plus field errors.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
type Resolver<TValues, TContext> = (
|
|
134
|
+
values: TValues,
|
|
135
|
+
context: {
|
|
136
|
+
name?: string;
|
|
137
|
+
context?: TContext;
|
|
138
|
+
}
|
|
139
|
+
) =>
|
|
140
|
+
| { values: TValues; errors: Record<string, FieldError> }
|
|
141
|
+
| Promise<{ values: TValues; errors: Record<string, FieldError> }>;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This keeps the core form independent from any validation library.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
3
|
-
//#region src/create-form.d.ts
|
|
1
|
+
import { a as FieldFlags, c as FieldPathValue, d as FormState, f as Resolver, g as ValidationMode, h as SetValueOptions, i as FieldErrors, l as FieldValues, m as ResolverResult, n as FieldController, o as FieldOptions, p as ResolverContext, r as FieldError, s as FieldPath, t as CreateFormOptions, u as Form } from "./types-xBmBF3iK.mjs";
|
|
4
2
|
/** Create a Solid-first form controller backed by createStore. */
|
|
5
|
-
declare function createForm<TValues extends FieldValues = FieldValues, TContext = unknown>(options?: CreateFormOptions<TValues, TContext>): Form<TValues>;
|
|
6
|
-
|
|
7
|
-
export { type CreateFormOptions, type FieldError, type FieldErrors, type FieldFlags, type FieldPath, type FieldPathValue, type FieldRegistration, type FieldValues, type Form, type FormState, type RegisterOptions, type Resolver, type ResolverContext, type ResolverResult, type SetValueOptions, type ValidationMode, createForm };
|
|
3
|
+
export declare function createForm<TValues extends FieldValues = FieldValues, TContext = unknown>(options?: CreateFormOptions<TValues, TContext>): Form<TValues>;
|
|
4
|
+
export type { CreateFormOptions, FieldController, FieldError, FieldErrors, FieldFlags, FieldOptions, FieldPath, FieldPathValue, FieldValues, Form, FormState, Resolver, ResolverContext, ResolverResult, SetValueOptions, ValidationMode };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { a as setPath, i as isEqual, n as getPath, r as isEmptyObject, t as cloneValue } from "./utils-0Ihz8msV.mjs";
|
|
1
2
|
import { createStore, produce, reconcile, unwrap } from "solid-js/store";
|
|
2
|
-
|
|
3
|
+
// src/create-form/async.ts
|
|
3
4
|
/** Run a promise from a synchronous event handler and surface failures globally. */
|
|
4
5
|
function runAsync(task) {
|
|
5
6
|
task.catch((error) => {
|
|
@@ -8,67 +9,7 @@ function runAsync(task) {
|
|
|
8
9
|
});
|
|
9
10
|
});
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
//#region src/utils.ts
|
|
13
|
-
/** Return a dot path split into store path segments. */
|
|
14
|
-
function toPath(name) {
|
|
15
|
-
return name.split(".").filter(Boolean);
|
|
16
|
-
}
|
|
17
|
-
/** Read a nested value from an object by dot path. */
|
|
18
|
-
function getPath(source, name) {
|
|
19
|
-
let value = source;
|
|
20
|
-
for (const key of toPath(name)) {
|
|
21
|
-
if (value == null || typeof value !== "object") return;
|
|
22
|
-
value = value[key];
|
|
23
|
-
}
|
|
24
|
-
return value;
|
|
25
|
-
}
|
|
26
|
-
/** Set a nested value on a cloned object by dot path. */
|
|
27
|
-
function setPath(source, name, value) {
|
|
28
|
-
const root = cloneValue(source);
|
|
29
|
-
const path = toPath(name);
|
|
30
|
-
let cursor = root;
|
|
31
|
-
for (const [index, key] of path.entries()) {
|
|
32
|
-
if (index === path.length - 1) {
|
|
33
|
-
cursor[key] = value;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
const next = cursor[key];
|
|
37
|
-
if (next == null || typeof next !== "object" || Array.isArray(next)) cursor[key] = {};
|
|
38
|
-
cursor = cursor[key];
|
|
39
|
-
}
|
|
40
|
-
return root;
|
|
41
|
-
}
|
|
42
|
-
/** Create a JSON-compatible deep clone. */
|
|
43
|
-
function cloneValue(value) {
|
|
44
|
-
if (value == null || typeof value !== "object") return value;
|
|
45
|
-
return JSON.parse(JSON.stringify(value));
|
|
46
|
-
}
|
|
47
|
-
/** Compare two JSON-compatible values. */
|
|
48
|
-
function isEqual(left, right) {
|
|
49
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
50
|
-
}
|
|
51
|
-
/** Return true when an object has no own enumerable keys. */
|
|
52
|
-
function isEmptyObject(value) {
|
|
53
|
-
return Object.keys(value).length === 0;
|
|
54
|
-
}
|
|
55
|
-
/** Extract a form value from a DOM event or raw value. */
|
|
56
|
-
function getEventValue(valueOrEvent) {
|
|
57
|
-
if (!isEventLike(valueOrEvent)) return valueOrEvent;
|
|
58
|
-
const target = valueOrEvent.currentTarget ?? valueOrEvent.target;
|
|
59
|
-
if (!isValueTarget(target)) return;
|
|
60
|
-
if (target.type === "checkbox") return Boolean(target.checked);
|
|
61
|
-
if (target.type === "number") return target.value === "" ? void 0 : Number(target.value);
|
|
62
|
-
return target.value;
|
|
63
|
-
}
|
|
64
|
-
function isEventLike(value) {
|
|
65
|
-
return value != null && typeof value === "object" && ("target" in value || "currentTarget" in value);
|
|
66
|
-
}
|
|
67
|
-
function isValueTarget(target) {
|
|
68
|
-
return target != null && typeof target === "object" && ("value" in target || "checked" in target);
|
|
69
|
-
}
|
|
70
|
-
//#endregion
|
|
71
|
-
//#region src/create-form/state.ts
|
|
12
|
+
// src/create-form/state.ts
|
|
72
13
|
/** Create the initial form state object. */
|
|
73
14
|
function createInitialFormState(defaultValues) {
|
|
74
15
|
return {
|
|
@@ -92,24 +33,22 @@ function mergeValidationErrors(currentErrors, name, errors) {
|
|
|
92
33
|
...unwrap(currentErrors),
|
|
93
34
|
[name]: errors[name]
|
|
94
35
|
};
|
|
95
|
-
if (!errors[name])
|
|
36
|
+
if (!errors[name]) Reflect.deleteProperty(nextErrors, name);
|
|
96
37
|
return nextErrors;
|
|
97
38
|
}
|
|
98
39
|
/** Calculate the next dirty field map after one field value changes. */
|
|
99
40
|
function createNextDirtyFields(dirtyFields, values, defaultValues, name) {
|
|
100
41
|
const nextDirtyFields = { ...unwrap(dirtyFields) };
|
|
101
42
|
if (!isEqual(getPath(values, name), getPath(defaultValues, name))) nextDirtyFields[name] = true;
|
|
102
|
-
else
|
|
43
|
+
else Reflect.deleteProperty(nextDirtyFields, name);
|
|
103
44
|
return {
|
|
104
45
|
dirtyFields: nextDirtyFields,
|
|
105
46
|
isDirty: !isEmptyObject(nextDirtyFields)
|
|
106
47
|
};
|
|
107
48
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
async function validateField(name, value, values, options) {
|
|
112
|
-
return validateRequired(name, value, options) ?? validateMin(name, value, options) ?? validateMax(name, value, options) ?? validatePattern(name, value, options) ?? await validateCustom(name, value, values, options);
|
|
49
|
+
// src/validation/rules.ts
|
|
50
|
+
function isEmpty(value) {
|
|
51
|
+
return value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
113
52
|
}
|
|
114
53
|
function validateRequired(name, value, options) {
|
|
115
54
|
if (!(options.required && isEmpty(value))) return;
|
|
@@ -118,6 +57,13 @@ function validateRequired(name, value, options) {
|
|
|
118
57
|
type: "required"
|
|
119
58
|
};
|
|
120
59
|
}
|
|
60
|
+
function readRule(rule) {
|
|
61
|
+
if (typeof rule === "number") return {
|
|
62
|
+
message: void 0,
|
|
63
|
+
value: rule
|
|
64
|
+
};
|
|
65
|
+
return rule;
|
|
66
|
+
}
|
|
121
67
|
function validateMin(name, value, options) {
|
|
122
68
|
const min = readRule(options.min);
|
|
123
69
|
if (!min || isEmpty(value) || Number(value) >= min.value) return;
|
|
@@ -134,6 +80,13 @@ function validateMax(name, value, options) {
|
|
|
134
80
|
type: "max"
|
|
135
81
|
};
|
|
136
82
|
}
|
|
83
|
+
function readPattern(rule) {
|
|
84
|
+
if (rule instanceof RegExp) return {
|
|
85
|
+
message: void 0,
|
|
86
|
+
value: rule
|
|
87
|
+
};
|
|
88
|
+
return rule;
|
|
89
|
+
}
|
|
137
90
|
function validatePattern(name, value, options) {
|
|
138
91
|
const pattern = readPattern(options.pattern);
|
|
139
92
|
if (!pattern || typeof value !== "string" || pattern.value.test(value)) return;
|
|
@@ -156,59 +109,62 @@ async function validateCustom(name, value, values, options) {
|
|
|
156
109
|
};
|
|
157
110
|
return result;
|
|
158
111
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
function readRule(rule) {
|
|
163
|
-
if (typeof rule === "number") return {
|
|
164
|
-
value: rule,
|
|
165
|
-
message: void 0
|
|
166
|
-
};
|
|
167
|
-
return rule;
|
|
112
|
+
/** Validate one field with built-in field rules. */
|
|
113
|
+
async function validateField(name, value, values, options) {
|
|
114
|
+
return validateRequired(name, value, options) ?? validateMin(name, value, options) ?? validateMax(name, value, options) ?? validatePattern(name, value, options) ?? await validateCustom(name, value, values, options);
|
|
168
115
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
};
|
|
174
|
-
return rule;
|
|
116
|
+
// src/create-form.ts
|
|
117
|
+
function pickFieldErrors(errors, name) {
|
|
118
|
+
if (!errors[name]) return {};
|
|
119
|
+
return { [name]: errors[name] };
|
|
175
120
|
}
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/create-form.ts
|
|
178
121
|
/** Create a Solid-first form controller backed by createStore. */
|
|
179
122
|
function createForm(options = {}) {
|
|
180
123
|
const mode = options.mode ?? "onSubmit";
|
|
181
124
|
const fieldOptions = /* @__PURE__ */ new Map();
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
125
|
+
const defaultValues = cloneValue(options.defaultValues ?? {});
|
|
126
|
+
const [formState, setFormState] = createStore(createInitialFormState(defaultValues));
|
|
127
|
+
function getValue(name) {
|
|
128
|
+
return getPath(formState.values, name);
|
|
129
|
+
}
|
|
130
|
+
async function collectFieldErrors(errors, name, values) {
|
|
131
|
+
for (const [fieldName, rules] of fieldOptions) {
|
|
132
|
+
if (name && fieldName !== name) continue;
|
|
133
|
+
const error = await validateField(fieldName, getPath(values, fieldName), values, rules);
|
|
134
|
+
if (error) errors[fieldName] = error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function collectResolverErrors(errors, name, values) {
|
|
138
|
+
if (!options.resolver) return;
|
|
139
|
+
const result = await options.resolver(values, {
|
|
140
|
+
context: options.context,
|
|
141
|
+
name
|
|
142
|
+
});
|
|
143
|
+
Object.assign(errors, name ? pickFieldErrors(result.errors, name) : result.errors);
|
|
144
|
+
if (isEmptyObject(result.errors)) setFormState("values", cloneValue(result.values));
|
|
145
|
+
}
|
|
146
|
+
async function collectErrors(name, values) {
|
|
147
|
+
const errors = {};
|
|
148
|
+
await collectFieldErrors(errors, name, values);
|
|
149
|
+
await collectResolverErrors(errors, name, values);
|
|
150
|
+
return errors;
|
|
206
151
|
}
|
|
207
152
|
function getValues() {
|
|
208
153
|
return unwrap(formState.values);
|
|
209
154
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
155
|
+
async function trigger(name) {
|
|
156
|
+
setFormState("isValidating", true);
|
|
157
|
+
const errors = await collectErrors(name, getValues());
|
|
158
|
+
const nextErrors = mergeValidationErrors(formState.errors, name, errors);
|
|
159
|
+
setFormState("errors", reconcile(nextErrors));
|
|
160
|
+
setFormState("isValid", isEmptyObject(nextErrors));
|
|
161
|
+
setFormState("isValidating", false);
|
|
162
|
+
return name ? !nextErrors[name] : isEmptyObject(errors);
|
|
163
|
+
}
|
|
164
|
+
function updateDirtyField(name) {
|
|
165
|
+
const nextDirty = createNextDirtyFields(formState.dirtyFields, formState.values, formState.defaultValues, name);
|
|
166
|
+
setFormState("dirtyFields", reconcile(nextDirty.dirtyFields));
|
|
167
|
+
setFormState("isDirty", nextDirty.isDirty);
|
|
212
168
|
}
|
|
213
169
|
async function setValue(name, value, setValueOptions = {}) {
|
|
214
170
|
setFormState("values", setPath(getValues(), name, value));
|
|
@@ -218,6 +174,33 @@ function createForm(options = {}) {
|
|
|
218
174
|
if (setValueOptions.shouldDirty ?? true) updateDirtyField(name);
|
|
219
175
|
if (setValueOptions.shouldValidate) await trigger(name);
|
|
220
176
|
}
|
|
177
|
+
function createField(name, options) {
|
|
178
|
+
if (options) fieldOptions.set(name, options);
|
|
179
|
+
return {
|
|
180
|
+
get dirty() {
|
|
181
|
+
return Boolean(formState.dirtyFields[name]);
|
|
182
|
+
},
|
|
183
|
+
get error() {
|
|
184
|
+
return formState.errors[name];
|
|
185
|
+
},
|
|
186
|
+
handleBlur() {
|
|
187
|
+
setFormState("touchedFields", produce((touchedFields) => {
|
|
188
|
+
touchedFields[name] = true;
|
|
189
|
+
}));
|
|
190
|
+
if (mode === "onBlur" || mode === "all") runAsync(trigger(name));
|
|
191
|
+
},
|
|
192
|
+
handleChange(value) {
|
|
193
|
+
runAsync(setValue(name, value, { shouldValidate: mode === "onChange" || mode === "all" }));
|
|
194
|
+
},
|
|
195
|
+
name,
|
|
196
|
+
get touched() {
|
|
197
|
+
return Boolean(formState.touchedFields[name]);
|
|
198
|
+
},
|
|
199
|
+
get value() {
|
|
200
|
+
return getValue(name);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
221
204
|
function setError(name, error) {
|
|
222
205
|
setFormState("errors", produce((errors) => {
|
|
223
206
|
errors[name] = error;
|
|
@@ -226,56 +209,25 @@ function createForm(options = {}) {
|
|
|
226
209
|
}
|
|
227
210
|
function clearErrors(name) {
|
|
228
211
|
if (name) setFormState("errors", produce((errors) => {
|
|
229
|
-
|
|
212
|
+
Reflect.deleteProperty(errors, name);
|
|
230
213
|
}));
|
|
231
214
|
else setFormState("errors", reconcile({}));
|
|
232
215
|
setFormState("isValid", isEmptyObject(formState.errors));
|
|
233
216
|
}
|
|
234
|
-
async function trigger(name) {
|
|
235
|
-
setFormState("isValidating", true);
|
|
236
|
-
const errors = await collectErrors(name, getValues());
|
|
237
|
-
const nextErrors = mergeValidationErrors(formState.errors, name, errors);
|
|
238
|
-
setFormState("errors", reconcile(nextErrors));
|
|
239
|
-
setFormState("isValid", isEmptyObject(nextErrors));
|
|
240
|
-
setFormState("isValidating", false);
|
|
241
|
-
return name ? !nextErrors[name] : isEmptyObject(errors);
|
|
242
|
-
}
|
|
243
|
-
async function collectErrors(name, values) {
|
|
244
|
-
const errors = {};
|
|
245
|
-
await collectRegisteredErrors(errors, name, values);
|
|
246
|
-
await collectResolverErrors(errors, name, values);
|
|
247
|
-
return errors;
|
|
248
|
-
}
|
|
249
|
-
async function collectRegisteredErrors(errors, name, values) {
|
|
250
|
-
for (const [fieldName, registerOptions] of fieldOptions) {
|
|
251
|
-
if (name && fieldName !== name) continue;
|
|
252
|
-
const error = await validateField(fieldName, getPath(values, fieldName), values, registerOptions);
|
|
253
|
-
if (error) errors[fieldName] = error;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
async function collectResolverErrors(errors, name, values) {
|
|
257
|
-
if (!options.resolver) return;
|
|
258
|
-
const result = await options.resolver(values, {
|
|
259
|
-
name,
|
|
260
|
-
context: options.context
|
|
261
|
-
});
|
|
262
|
-
Object.assign(errors, name ? pickFieldErrors(result.errors, name) : result.errors);
|
|
263
|
-
if (isEmptyObject(result.errors)) setFormState("values", cloneValue(result.values));
|
|
264
|
-
}
|
|
265
217
|
function reset(values) {
|
|
266
218
|
const nextDefaultValues = cloneValue(values ?? options.defaultValues ?? {});
|
|
267
219
|
setFormState({
|
|
268
|
-
values: cloneValue(nextDefaultValues),
|
|
269
220
|
defaultValues: nextDefaultValues,
|
|
270
|
-
errors: {},
|
|
271
|
-
touchedFields: {},
|
|
272
221
|
dirtyFields: {},
|
|
222
|
+
errors: {},
|
|
273
223
|
isDirty: false,
|
|
224
|
+
isSubmitted: false,
|
|
225
|
+
isSubmitting: false,
|
|
274
226
|
isValid: true,
|
|
275
227
|
isValidating: false,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
228
|
+
submitCount: 0,
|
|
229
|
+
touchedFields: {},
|
|
230
|
+
values: cloneValue(nextDefaultValues)
|
|
279
231
|
});
|
|
280
232
|
}
|
|
281
233
|
function handleSubmit(onValid, onInvalid) {
|
|
@@ -286,44 +238,24 @@ function createForm(options = {}) {
|
|
|
286
238
|
setFormState("isSubmitting", true);
|
|
287
239
|
const isValid = await trigger();
|
|
288
240
|
try {
|
|
289
|
-
|
|
290
|
-
else await onInvalid?.(formState.errors, event);
|
|
241
|
+
await (isValid ? onValid(getValues(), event) : onInvalid?.(formState.errors, event));
|
|
291
242
|
} finally {
|
|
292
243
|
setFormState("isSubmitting", false);
|
|
293
244
|
}
|
|
294
245
|
};
|
|
295
246
|
}
|
|
296
|
-
function updateDirtyField(name) {
|
|
297
|
-
const nextDirty = createNextDirtyFields(formState.dirtyFields, formState.values, formState.defaultValues, name);
|
|
298
|
-
setFormState("dirtyFields", reconcile(nextDirty.dirtyFields));
|
|
299
|
-
setFormState("isDirty", nextDirty.isDirty);
|
|
300
|
-
}
|
|
301
|
-
function readInputValue(name, event) {
|
|
302
|
-
return transformValue(name, getEventValue(event));
|
|
303
|
-
}
|
|
304
|
-
function transformValue(name, value) {
|
|
305
|
-
const options = fieldOptions.get(name);
|
|
306
|
-
if (options?.setValueAs) return options.setValueAs(value);
|
|
307
|
-
if (options?.valueAsNumber) return value === "" || value == null ? void 0 : Number(value);
|
|
308
|
-
return value;
|
|
309
|
-
}
|
|
310
247
|
return {
|
|
248
|
+
clearErrors,
|
|
249
|
+
createField,
|
|
311
250
|
formState,
|
|
312
|
-
setFormState,
|
|
313
|
-
register,
|
|
314
|
-
getValues,
|
|
315
251
|
getValue,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
clearErrors,
|
|
319
|
-
trigger,
|
|
252
|
+
getValues,
|
|
253
|
+
handleSubmit,
|
|
320
254
|
reset,
|
|
321
|
-
|
|
255
|
+
setError,
|
|
256
|
+
setFormState,
|
|
257
|
+
setValue,
|
|
258
|
+
trigger
|
|
322
259
|
};
|
|
323
260
|
}
|
|
324
|
-
function pickFieldErrors(errors, name) {
|
|
325
|
-
if (!errors[name]) return {};
|
|
326
|
-
return { [name]: errors[name] };
|
|
327
|
-
}
|
|
328
|
-
//#endregion
|
|
329
261
|
export { createForm };
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as Resolver, l as FieldValues } from "../types-xBmBF3iK.mjs";
|
|
2
2
|
import { Type } from "arktype";
|
|
3
|
-
|
|
4
|
-
//#region src/resolvers/arktype.d.ts
|
|
5
|
-
/** A supported ArkType schema. */
|
|
6
|
-
type ArkTypeSchema<TValues extends FieldValues = FieldValues> = Type<TValues>;
|
|
7
3
|
/** Create a resolver for ArkType schemas. */
|
|
8
|
-
declare function arktypeResolver<TValues extends FieldValues>(schema:
|
|
9
|
-
//#endregion
|
|
10
|
-
export { ArkTypeSchema, arktypeResolver };
|
|
4
|
+
export declare function arktypeResolver<TValues extends FieldValues>(schema: Type<TValues>): Resolver<TValues>;
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function
|
|
4
|
-
return (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { n as getPath } from "../utils-0Ihz8msV.mjs";
|
|
2
|
+
// src/resolvers/arktype.ts
|
|
3
|
+
function isArkErrors(value) {
|
|
4
|
+
return Array.isArray(value) && "byPath" in value && typeof value.summary === "string";
|
|
5
|
+
}
|
|
6
|
+
function readMessage(value) {
|
|
7
|
+
if (typeof value === "string") return value;
|
|
8
|
+
if (value && typeof value === "object") {
|
|
9
|
+
const candidate = value;
|
|
10
|
+
return candidate.message ?? candidate.summary ?? candidate.toString?.() ?? "Invalid field value";
|
|
11
|
+
}
|
|
12
|
+
return "Invalid field value";
|
|
13
|
+
}
|
|
14
|
+
function collectByPath(errors, byPath) {
|
|
15
|
+
for (const [path, value] of Object.entries(byPath)) errors[path || "root"] = {
|
|
16
|
+
message: readMessage(value),
|
|
17
|
+
type: "arktype"
|
|
11
18
|
};
|
|
12
19
|
}
|
|
13
20
|
function readArkTypeErrors(result) {
|
|
@@ -20,22 +27,39 @@ function readArkTypeErrors(result) {
|
|
|
20
27
|
};
|
|
21
28
|
return errors;
|
|
22
29
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
30
|
+
function joinPath(name, path) {
|
|
31
|
+
if (!path || path === "root") return name;
|
|
32
|
+
return `${name}.${path}`;
|
|
25
33
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
type: "arktype"
|
|
30
|
-
};
|
|
34
|
+
function prefixFieldErrors(name, errors) {
|
|
35
|
+
if (errors.root) return { [name]: errors.root };
|
|
36
|
+
return Object.fromEntries(Object.entries(errors).map(([path, error]) => [joinPath(name, path), error]));
|
|
31
37
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
function validateField(schema, values, name) {
|
|
39
|
+
try {
|
|
40
|
+
const errors = readArkTypeErrors(schema.get(...name.split("."))(getPath(values, name)));
|
|
41
|
+
return {
|
|
42
|
+
errors: errors ? prefixFieldErrors(name, errors) : {},
|
|
43
|
+
values
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error);
|
|
47
|
+
return {
|
|
48
|
+
errors: {},
|
|
49
|
+
values
|
|
50
|
+
};
|
|
37
51
|
}
|
|
38
|
-
return "Invalid field value";
|
|
39
52
|
}
|
|
40
|
-
|
|
53
|
+
/** Create a resolver for ArkType schemas. */
|
|
54
|
+
function arktypeResolver(schema) {
|
|
55
|
+
return (values, ctx) => {
|
|
56
|
+
if (ctx.name) return validateField(schema, values, ctx.name);
|
|
57
|
+
const result = schema(values);
|
|
58
|
+
const errors = readArkTypeErrors(result);
|
|
59
|
+
return {
|
|
60
|
+
errors: errors ?? {},
|
|
61
|
+
values: errors ? values : result
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}
|
|
41
65
|
export { arktypeResolver };
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { SetStoreFunction, Store } from "solid-js/store";
|
|
2
|
-
|
|
3
|
-
//#region src/types.d.ts
|
|
4
2
|
/** The object shape managed by a form. */
|
|
5
3
|
type FieldValues = object;
|
|
6
4
|
/** A dot-separated field path. */
|
|
7
5
|
type FieldPath<TValues extends FieldValues = FieldValues> = keyof TValues extends never ? string : ObjectPath<TValues>;
|
|
8
6
|
/** The value type represented by a dot-separated field path. */
|
|
9
7
|
type FieldPathValue<TValues extends FieldValues, TName extends FieldPath<TValues>> = PathValue<TValues, TName>;
|
|
10
|
-
type ObjectPath<TValue> = TValue extends FieldValues ? { [TKey in Extract<keyof TValue, string>]: TValue[TKey] extends FieldValues ? TKey | `${TKey}.${ObjectPath<TValue[TKey]>}` : TKey }[Extract<keyof TValue, string>] : never;
|
|
8
|
+
type ObjectPath<TValue> = TValue extends FieldValues ? { [TKey in Extract<keyof TValue, string>]: TValue[TKey] extends FieldValues ? TKey | `${TKey}.${ObjectPath<TValue[TKey]>}` : TKey; }[Extract<keyof TValue, string>] : never;
|
|
11
9
|
type PathValue<TValue, TName> = TName extends `${infer TKey}.${infer TRest}` ? TKey extends keyof TValue ? PathValue<TValue[TKey], TRest> : unknown : TName extends keyof TValue ? TValue[TName] : unknown;
|
|
12
10
|
/** A validation error associated with a field path. */
|
|
13
11
|
interface FieldError {
|
|
@@ -62,7 +60,7 @@ interface ResolverContext<TValues extends FieldValues = FieldValues, TContext =
|
|
|
62
60
|
/** Pluggable validation function for schema libraries or custom logic. */
|
|
63
61
|
type Resolver<TValues extends FieldValues = FieldValues, TContext = unknown> = (values: TValues, context: ResolverContext<TValues, TContext>) => ResolverResult<TValues> | Promise<ResolverResult<TValues>>;
|
|
64
62
|
/** Built-in field-level validation rules. */
|
|
65
|
-
interface
|
|
63
|
+
interface FieldOptions<TValue = unknown, TValues extends FieldValues = FieldValues> {
|
|
66
64
|
/** Maximum string length or numeric value. */
|
|
67
65
|
max?: number | {
|
|
68
66
|
value: number;
|
|
@@ -80,26 +78,24 @@ interface RegisterOptions<TValue = unknown, TValues extends FieldValues = FieldV
|
|
|
80
78
|
};
|
|
81
79
|
/** Whether the field is required. */
|
|
82
80
|
required?: boolean | string;
|
|
83
|
-
/** Custom value transform before storing. */
|
|
84
|
-
setValueAs?: (value: unknown) => TValue;
|
|
85
81
|
/** Custom synchronous or asynchronous validation rule. */
|
|
86
82
|
validate?: (value: TValue, values: TValues) => boolean | string | FieldError | Promise<boolean | string | FieldError>;
|
|
87
|
-
/** Convert input values to numbers before storing them. */
|
|
88
|
-
valueAsNumber?: boolean;
|
|
89
83
|
}
|
|
90
|
-
/**
|
|
91
|
-
interface
|
|
92
|
-
/**
|
|
93
|
-
readonly
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
/**
|
|
84
|
+
/** Value-driven custom component bindings. Read getters in a reactive scope. */
|
|
85
|
+
interface FieldController<TValue = unknown, TName extends string = string> {
|
|
86
|
+
/** Whether the field differs from its default. */
|
|
87
|
+
readonly dirty: boolean;
|
|
88
|
+
/** Current validation error. */
|
|
89
|
+
readonly error: FieldError | undefined;
|
|
90
|
+
/** Mark touched and validate according to mode. */
|
|
91
|
+
handleBlur: () => void;
|
|
92
|
+
/** Store a final value without DOM extraction or conversion. */
|
|
93
|
+
handleChange: (value: TValue) => void;
|
|
94
|
+
/** Field path. */
|
|
95
|
+
readonly name: TName;
|
|
96
|
+
/** Whether the field has been blurred. */
|
|
97
|
+
readonly touched: boolean;
|
|
98
|
+
/** Current field value. */
|
|
103
99
|
readonly value: TValue;
|
|
104
100
|
}
|
|
105
101
|
/** Validation trigger mode. */
|
|
@@ -109,7 +105,7 @@ interface CreateFormOptions<TValues extends FieldValues = FieldValues, TContext
|
|
|
109
105
|
/** Extra context passed to the resolver. */
|
|
110
106
|
context?: TContext;
|
|
111
107
|
/** Initial form values. */
|
|
112
|
-
defaultValues?: Partial<TValues
|
|
108
|
+
defaultValues?: Partial<NoInfer<TValues>>;
|
|
113
109
|
/** When validation should run automatically. Defaults to onSubmit. */
|
|
114
110
|
mode?: ValidationMode;
|
|
115
111
|
/** Schema or custom validation resolver. */
|
|
@@ -119,6 +115,8 @@ interface CreateFormOptions<TValues extends FieldValues = FieldValues, TContext
|
|
|
119
115
|
interface Form<TValues extends FieldValues = FieldValues> {
|
|
120
116
|
/** Clear all errors or one field error. */
|
|
121
117
|
clearErrors: (name?: FieldPath<TValues>) => void;
|
|
118
|
+
/** Configure rules and receive value-driven component bindings. */
|
|
119
|
+
createField: <TName extends FieldPath<TValues>>(name: TName, options?: FieldOptions<FieldPathValue<TValues, TName>, TValues>) => FieldController<FieldPathValue<TValues, TName>, TName>;
|
|
122
120
|
/** Reactive form state. */
|
|
123
121
|
formState: Store<FormState<TValues>>;
|
|
124
122
|
/** Read a single field value. */
|
|
@@ -127,8 +125,6 @@ interface Form<TValues extends FieldValues = FieldValues> {
|
|
|
127
125
|
getValues: () => TValues;
|
|
128
126
|
/** Submit helper that validates before calling the valid handler. */
|
|
129
127
|
handleSubmit: (onValid: (values: TValues, event?: Event) => void | Promise<void>, onInvalid?: (errors: FieldErrors<TValues>, event?: Event) => void | Promise<void>) => (event?: Event) => Promise<void>;
|
|
130
|
-
/** Register a field and receive Solid-friendly input props. */
|
|
131
|
-
register: <TName extends FieldPath<TValues>>(name: TName, options?: RegisterOptions<FieldPathValue<TValues, TName>, TValues>) => FieldRegistration<FieldPathValue<TValues, TName>>;
|
|
132
128
|
/** Reset values and form state. */
|
|
133
129
|
reset: (values?: Partial<TValues>) => void;
|
|
134
130
|
/** Set a field error. */
|
|
@@ -149,5 +145,4 @@ interface SetValueOptions {
|
|
|
149
145
|
/** Trigger validation after the value is set. */
|
|
150
146
|
shouldValidate?: boolean;
|
|
151
147
|
}
|
|
152
|
-
|
|
153
|
-
export { FieldPath as a, FieldValues as c, RegisterOptions as d, Resolver as f, ValidationMode as g, SetValueOptions as h, FieldFlags as i, Form as l, ResolverResult as m, FieldError as n, FieldPathValue as o, ResolverContext as p, FieldErrors as r, FieldRegistration as s, CreateFormOptions as t, FormState as u };
|
|
148
|
+
export { FieldFlags as a, FieldPathValue as c, FormState as d, Resolver as f, ValidationMode as g, SetValueOptions as h, FieldErrors as i, FieldValues as l, ResolverResult as m, FieldController as n, FieldOptions as o, ResolverContext as p, FieldError as r, FieldPath as s, CreateFormOptions as t, Form as u };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
/** Return a dot path split into store path segments. */
|
|
3
|
+
function toPath(name) {
|
|
4
|
+
return name.split(".").filter(Boolean);
|
|
5
|
+
}
|
|
6
|
+
/** Read a nested value from an object by dot path. */
|
|
7
|
+
function getPath(source, name) {
|
|
8
|
+
let value = source;
|
|
9
|
+
for (const key of toPath(name)) {
|
|
10
|
+
if (value === null || typeof value !== "object") return;
|
|
11
|
+
value = value[key];
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
/** Create a JSON-compatible deep clone. */
|
|
16
|
+
function cloneValue(value) {
|
|
17
|
+
if (value === null || typeof value !== "object") return value;
|
|
18
|
+
return JSON.parse(JSON.stringify(value));
|
|
19
|
+
}
|
|
20
|
+
/** Set a nested value on a cloned object by dot path. */
|
|
21
|
+
function setPath(source, name, value) {
|
|
22
|
+
const root = cloneValue(source);
|
|
23
|
+
const path = toPath(name);
|
|
24
|
+
let cursor = root;
|
|
25
|
+
for (const [index, key] of path.entries()) {
|
|
26
|
+
if (index === path.length - 1) {
|
|
27
|
+
cursor[key] = value;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
const next = cursor[key];
|
|
31
|
+
if (next === null || typeof next !== "object" || Array.isArray(next)) cursor[key] = {};
|
|
32
|
+
cursor = cursor[key];
|
|
33
|
+
}
|
|
34
|
+
return root;
|
|
35
|
+
}
|
|
36
|
+
/** Compare two JSON-compatible values. */
|
|
37
|
+
function isEqual(left, right) {
|
|
38
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
39
|
+
}
|
|
40
|
+
/** Return true when an object has no own enumerable keys. */
|
|
41
|
+
function isEmptyObject(value) {
|
|
42
|
+
return Object.keys(value).length === 0;
|
|
43
|
+
}
|
|
44
|
+
export { setPath as a, isEqual as i, getPath as n, isEmptyObject as r, cloneValue as t };
|
package/package.json
CHANGED
|
@@ -1,72 +1,71 @@
|
|
|
1
1
|
{
|
|
2
|
-
"devDependencies": {
|
|
3
|
-
"@biomejs/biome": "2.4.16",
|
|
4
|
-
"@solidjs/testing-library": "^0.8.10",
|
|
5
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
6
|
-
"@types/node": "^25.9.2",
|
|
7
|
-
"@typescript/native-preview": "7.0.0-dev.20260608.1",
|
|
8
|
-
"@vitest/ui": "^4.1.8",
|
|
9
|
-
"arktype": "^2.2.0",
|
|
10
|
-
"bumpp": "^11.1.0",
|
|
11
|
-
"jsdom": "^29.1.1",
|
|
12
|
-
"tsdown": "^0.22.2",
|
|
13
|
-
"typescript": "^5.9.3",
|
|
14
|
-
"ultracite": "7.8.2",
|
|
15
|
-
"vite": "^8.0.16",
|
|
16
|
-
"vite-plugin-solid": "^2.11.12",
|
|
17
|
-
"vitest": "^4.1.8"
|
|
18
|
-
},
|
|
19
2
|
"name": "solid-tiny-form",
|
|
20
|
-
"
|
|
21
|
-
"arktype": "^2.1.27",
|
|
22
|
-
"solid-js": "^1.9.7"
|
|
23
|
-
},
|
|
24
|
-
"type": "module",
|
|
25
|
-
"version": "0.1.0",
|
|
26
|
-
"peerDependenciesMeta": {
|
|
27
|
-
"arktype": {
|
|
28
|
-
"optional": true
|
|
29
|
-
}
|
|
30
|
-
},
|
|
3
|
+
"version": "0.3.0",
|
|
31
4
|
"description": "A tiny SolidJS form state manager with pluggable validation",
|
|
32
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"arktype",
|
|
7
|
+
"form",
|
|
8
|
+
"reactive",
|
|
9
|
+
"solid",
|
|
10
|
+
"solidjs",
|
|
11
|
+
"validation"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://codeberg.org/solid-tiny/solid-tiny-form#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://codeberg.org/solid-tiny/solid-tiny-form/issues"
|
|
16
|
+
},
|
|
33
17
|
"license": "MIT",
|
|
34
|
-
"
|
|
18
|
+
"author": "solid tiny",
|
|
35
19
|
"repository": {
|
|
36
20
|
"type": "git",
|
|
37
|
-
"url": "git+https://
|
|
38
|
-
},
|
|
39
|
-
"bugs": {
|
|
40
|
-
"url": "https://github.com/sxl-cc/solid-tiny-form/issues"
|
|
21
|
+
"url": "git+https://codeberg.org/solid-tiny/solid-tiny-form.git"
|
|
41
22
|
},
|
|
42
23
|
"files": [
|
|
43
24
|
"dist"
|
|
44
25
|
],
|
|
26
|
+
"type": "module",
|
|
45
27
|
"sideEffects": false,
|
|
46
|
-
"keywords": [
|
|
47
|
-
"solidjs",
|
|
48
|
-
"solid",
|
|
49
|
-
"form",
|
|
50
|
-
"reactive",
|
|
51
|
-
"validation",
|
|
52
|
-
"arktype"
|
|
53
|
-
],
|
|
54
28
|
"types": "./dist/index.d.mts",
|
|
55
29
|
"exports": {
|
|
56
30
|
".": "./dist/index.mjs",
|
|
57
31
|
"./resolvers/arktype": "./dist/resolvers/arktype.mjs",
|
|
58
32
|
"./package.json": "./package.json"
|
|
59
33
|
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@solidjs/testing-library": "^0.8.10",
|
|
36
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
37
|
+
"@types/node": "^25.9.9",
|
|
38
|
+
"arktype": "^2.2.9",
|
|
39
|
+
"bumpp": "^12.3.0",
|
|
40
|
+
"jsdom": "^29.1.1",
|
|
41
|
+
"oxfmt": "^0.75.0",
|
|
42
|
+
"oxlint": "^1.93.0",
|
|
43
|
+
"tsdown": "^0.23.7",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"ultracite": "8.1.1",
|
|
46
|
+
"vite": "^8.3.7",
|
|
47
|
+
"vite-plugin-solid": "^2.11.15",
|
|
48
|
+
"vitest": "^5.1.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"arktype": "^2.2.3",
|
|
52
|
+
"solid-js": "^1.9.7"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"arktype": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
60
59
|
"scripts": {
|
|
61
60
|
"build": "tsdown",
|
|
62
|
-
"dev": "vite -c ./playground/vite.config.ts",
|
|
63
|
-
"test": "vitest run",
|
|
64
|
-
"test:ui": "vitest --ui",
|
|
65
|
-
"tc": "tsgo --noEmit --skipLibCheck",
|
|
66
61
|
"bump": "pnpm bumpp --no-push",
|
|
67
|
-
"prepublish": "pnpm build",
|
|
68
62
|
"check": "ultracite check",
|
|
69
63
|
"check:error": "ultracite check --diagnostic-level error",
|
|
70
|
-
"
|
|
64
|
+
"dev": "vite -c ./playground/vite.config.ts",
|
|
65
|
+
"fix": "ultracite fix",
|
|
66
|
+
"prepublish": "pnpm build",
|
|
67
|
+
"tc": "tsc --noEmit --skipLibCheck",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"test:ui": "vitest --ui"
|
|
71
70
|
}
|
|
72
71
|
}
|