solid-tiny-form 0.2.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/LICENSE +21 -21
- package/README.md +54 -6
- package/dist/index.d.mts +3 -6
- package/dist/index.mjs +111 -121
- package/dist/resolvers/arktype.d.mts +2 -6
- package/dist/resolvers/arktype.mjs +44 -45
- package/dist/{types-DL1ynFcc.d.mts → types-xBmBF3iK.d.mts} +20 -25
- package/dist/{utils-jVw2-Xts.mjs → utils-0Ihz8msV.mjs} +9 -25
- package/package.json +47 -48
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 solid tiny
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 solid tiny
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# solid-tiny-form
|
|
2
2
|
|
|
3
|
-
A tiny SolidJS form state manager
|
|
3
|
+
A tiny SolidJS form state manager designed around Solid's fine-grained reactivity and `createStore`.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- `createForm` API for
|
|
7
|
+
- `createForm` API for creating fields, setting values, submitting, resetting, and validating.
|
|
8
8
|
- Form state is stored in a Solid store from `solid-js/store`.
|
|
9
9
|
- Pluggable resolver interface for schema validation libraries.
|
|
10
10
|
- Built-in ArkType resolver via `arktypeResolver`.
|
|
@@ -36,24 +36,43 @@ const schema = type({
|
|
|
36
36
|
age: "number >= 18",
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
type FormValues = { email: string; age: number | undefined };
|
|
40
|
+
|
|
39
41
|
function Example() {
|
|
40
|
-
const form = createForm({
|
|
42
|
+
const form = createForm<FormValues>({
|
|
41
43
|
defaultValues: {
|
|
42
44
|
email: "",
|
|
43
45
|
age: 18,
|
|
44
46
|
},
|
|
45
|
-
resolver: arktypeResolver(schema),
|
|
47
|
+
resolver: arktypeResolver<FormValues>(schema),
|
|
46
48
|
mode: "onBlur",
|
|
47
49
|
});
|
|
48
50
|
|
|
51
|
+
const email = form.createField("email");
|
|
52
|
+
const age = form.createField("age");
|
|
53
|
+
|
|
49
54
|
const onSubmit = form.handleSubmit((values) => {
|
|
50
55
|
console.log(values);
|
|
51
56
|
});
|
|
52
57
|
|
|
53
58
|
return (
|
|
54
59
|
<form onSubmit={onSubmit}>
|
|
55
|
-
<input
|
|
56
|
-
|
|
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
|
+
/>
|
|
57
76
|
<button disabled={form.formState.isSubmitting}>Submit</button>
|
|
58
77
|
</form>
|
|
59
78
|
);
|
|
@@ -77,6 +96,35 @@ const form = createForm({
|
|
|
77
96
|
|
|
78
97
|
Validation modes are `onSubmit`, `onBlur`, `onChange`, and `all`.
|
|
79
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
|
+
|
|
80
128
|
### Resolver interface
|
|
81
129
|
|
|
82
130
|
Resolvers receive current values and return parsed values plus field errors.
|
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,6 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as setPath, i as isEqual, n as getPath, r as isEmptyObject, t as cloneValue } from "./utils-0Ihz8msV.mjs";
|
|
2
2
|
import { createStore, produce, reconcile, unwrap } from "solid-js/store";
|
|
3
|
-
|
|
3
|
+
// src/create-form/async.ts
|
|
4
4
|
/** Run a promise from a synchronous event handler and surface failures globally. */
|
|
5
5
|
function runAsync(task) {
|
|
6
6
|
task.catch((error) => {
|
|
@@ -9,8 +9,7 @@ function runAsync(task) {
|
|
|
9
9
|
});
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
//#region src/create-form/state.ts
|
|
12
|
+
// src/create-form/state.ts
|
|
14
13
|
/** Create the initial form state object. */
|
|
15
14
|
function createInitialFormState(defaultValues) {
|
|
16
15
|
return {
|
|
@@ -34,24 +33,22 @@ function mergeValidationErrors(currentErrors, name, errors) {
|
|
|
34
33
|
...unwrap(currentErrors),
|
|
35
34
|
[name]: errors[name]
|
|
36
35
|
};
|
|
37
|
-
if (!errors[name])
|
|
36
|
+
if (!errors[name]) Reflect.deleteProperty(nextErrors, name);
|
|
38
37
|
return nextErrors;
|
|
39
38
|
}
|
|
40
39
|
/** Calculate the next dirty field map after one field value changes. */
|
|
41
40
|
function createNextDirtyFields(dirtyFields, values, defaultValues, name) {
|
|
42
41
|
const nextDirtyFields = { ...unwrap(dirtyFields) };
|
|
43
42
|
if (!isEqual(getPath(values, name), getPath(defaultValues, name))) nextDirtyFields[name] = true;
|
|
44
|
-
else
|
|
43
|
+
else Reflect.deleteProperty(nextDirtyFields, name);
|
|
45
44
|
return {
|
|
46
45
|
dirtyFields: nextDirtyFields,
|
|
47
46
|
isDirty: !isEmptyObject(nextDirtyFields)
|
|
48
47
|
};
|
|
49
48
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async function validateField(name, value, values, options) {
|
|
54
|
-
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;
|
|
55
52
|
}
|
|
56
53
|
function validateRequired(name, value, options) {
|
|
57
54
|
if (!(options.required && isEmpty(value))) return;
|
|
@@ -60,6 +57,13 @@ function validateRequired(name, value, options) {
|
|
|
60
57
|
type: "required"
|
|
61
58
|
};
|
|
62
59
|
}
|
|
60
|
+
function readRule(rule) {
|
|
61
|
+
if (typeof rule === "number") return {
|
|
62
|
+
message: void 0,
|
|
63
|
+
value: rule
|
|
64
|
+
};
|
|
65
|
+
return rule;
|
|
66
|
+
}
|
|
63
67
|
function validateMin(name, value, options) {
|
|
64
68
|
const min = readRule(options.min);
|
|
65
69
|
if (!min || isEmpty(value) || Number(value) >= min.value) return;
|
|
@@ -76,6 +80,13 @@ function validateMax(name, value, options) {
|
|
|
76
80
|
type: "max"
|
|
77
81
|
};
|
|
78
82
|
}
|
|
83
|
+
function readPattern(rule) {
|
|
84
|
+
if (rule instanceof RegExp) return {
|
|
85
|
+
message: void 0,
|
|
86
|
+
value: rule
|
|
87
|
+
};
|
|
88
|
+
return rule;
|
|
89
|
+
}
|
|
79
90
|
function validatePattern(name, value, options) {
|
|
80
91
|
const pattern = readPattern(options.pattern);
|
|
81
92
|
if (!pattern || typeof value !== "string" || pattern.value.test(value)) return;
|
|
@@ -98,59 +109,62 @@ async function validateCustom(name, value, values, options) {
|
|
|
98
109
|
};
|
|
99
110
|
return result;
|
|
100
111
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
function readRule(rule) {
|
|
105
|
-
if (typeof rule === "number") return {
|
|
106
|
-
value: rule,
|
|
107
|
-
message: void 0
|
|
108
|
-
};
|
|
109
|
-
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);
|
|
110
115
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
116
|
-
return rule;
|
|
116
|
+
// src/create-form.ts
|
|
117
|
+
function pickFieldErrors(errors, name) {
|
|
118
|
+
if (!errors[name]) return {};
|
|
119
|
+
return { [name]: errors[name] };
|
|
117
120
|
}
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region src/create-form.ts
|
|
120
121
|
/** Create a Solid-first form controller backed by createStore. */
|
|
121
122
|
function createForm(options = {}) {
|
|
122
123
|
const mode = options.mode ?? "onSubmit";
|
|
123
124
|
const fieldOptions = /* @__PURE__ */ new Map();
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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;
|
|
148
151
|
}
|
|
149
152
|
function getValues() {
|
|
150
153
|
return unwrap(formState.values);
|
|
151
154
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
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);
|
|
154
168
|
}
|
|
155
169
|
async function setValue(name, value, setValueOptions = {}) {
|
|
156
170
|
setFormState("values", setPath(getValues(), name, value));
|
|
@@ -160,6 +174,33 @@ function createForm(options = {}) {
|
|
|
160
174
|
if (setValueOptions.shouldDirty ?? true) updateDirtyField(name);
|
|
161
175
|
if (setValueOptions.shouldValidate) await trigger(name);
|
|
162
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
|
+
}
|
|
163
204
|
function setError(name, error) {
|
|
164
205
|
setFormState("errors", produce((errors) => {
|
|
165
206
|
errors[name] = error;
|
|
@@ -168,56 +209,25 @@ function createForm(options = {}) {
|
|
|
168
209
|
}
|
|
169
210
|
function clearErrors(name) {
|
|
170
211
|
if (name) setFormState("errors", produce((errors) => {
|
|
171
|
-
|
|
212
|
+
Reflect.deleteProperty(errors, name);
|
|
172
213
|
}));
|
|
173
214
|
else setFormState("errors", reconcile({}));
|
|
174
215
|
setFormState("isValid", isEmptyObject(formState.errors));
|
|
175
216
|
}
|
|
176
|
-
async function trigger(name) {
|
|
177
|
-
setFormState("isValidating", true);
|
|
178
|
-
const errors = await collectErrors(name, getValues());
|
|
179
|
-
const nextErrors = mergeValidationErrors(formState.errors, name, errors);
|
|
180
|
-
setFormState("errors", reconcile(nextErrors));
|
|
181
|
-
setFormState("isValid", isEmptyObject(nextErrors));
|
|
182
|
-
setFormState("isValidating", false);
|
|
183
|
-
return name ? !nextErrors[name] : isEmptyObject(errors);
|
|
184
|
-
}
|
|
185
|
-
async function collectErrors(name, values) {
|
|
186
|
-
const errors = {};
|
|
187
|
-
await collectRegisteredErrors(errors, name, values);
|
|
188
|
-
await collectResolverErrors(errors, name, values);
|
|
189
|
-
return errors;
|
|
190
|
-
}
|
|
191
|
-
async function collectRegisteredErrors(errors, name, values) {
|
|
192
|
-
for (const [fieldName, registerOptions] of fieldOptions) {
|
|
193
|
-
if (name && fieldName !== name) continue;
|
|
194
|
-
const error = await validateField(fieldName, getPath(values, fieldName), values, registerOptions);
|
|
195
|
-
if (error) errors[fieldName] = error;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
async function collectResolverErrors(errors, name, values) {
|
|
199
|
-
if (!options.resolver) return;
|
|
200
|
-
const result = await options.resolver(values, {
|
|
201
|
-
name,
|
|
202
|
-
context: options.context
|
|
203
|
-
});
|
|
204
|
-
Object.assign(errors, name ? pickFieldErrors(result.errors, name) : result.errors);
|
|
205
|
-
if (isEmptyObject(result.errors)) setFormState("values", cloneValue(result.values));
|
|
206
|
-
}
|
|
207
217
|
function reset(values) {
|
|
208
218
|
const nextDefaultValues = cloneValue(values ?? options.defaultValues ?? {});
|
|
209
219
|
setFormState({
|
|
210
|
-
values: cloneValue(nextDefaultValues),
|
|
211
220
|
defaultValues: nextDefaultValues,
|
|
212
|
-
errors: {},
|
|
213
|
-
touchedFields: {},
|
|
214
221
|
dirtyFields: {},
|
|
222
|
+
errors: {},
|
|
215
223
|
isDirty: false,
|
|
224
|
+
isSubmitted: false,
|
|
225
|
+
isSubmitting: false,
|
|
216
226
|
isValid: true,
|
|
217
227
|
isValidating: false,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
submitCount: 0,
|
|
229
|
+
touchedFields: {},
|
|
230
|
+
values: cloneValue(nextDefaultValues)
|
|
221
231
|
});
|
|
222
232
|
}
|
|
223
233
|
function handleSubmit(onValid, onInvalid) {
|
|
@@ -228,44 +238,24 @@ function createForm(options = {}) {
|
|
|
228
238
|
setFormState("isSubmitting", true);
|
|
229
239
|
const isValid = await trigger();
|
|
230
240
|
try {
|
|
231
|
-
|
|
232
|
-
else await onInvalid?.(formState.errors, event);
|
|
241
|
+
await (isValid ? onValid(getValues(), event) : onInvalid?.(formState.errors, event));
|
|
233
242
|
} finally {
|
|
234
243
|
setFormState("isSubmitting", false);
|
|
235
244
|
}
|
|
236
245
|
};
|
|
237
246
|
}
|
|
238
|
-
function updateDirtyField(name) {
|
|
239
|
-
const nextDirty = createNextDirtyFields(formState.dirtyFields, formState.values, formState.defaultValues, name);
|
|
240
|
-
setFormState("dirtyFields", reconcile(nextDirty.dirtyFields));
|
|
241
|
-
setFormState("isDirty", nextDirty.isDirty);
|
|
242
|
-
}
|
|
243
|
-
function readInputValue(name, event) {
|
|
244
|
-
return transformValue(name, getEventValue(event));
|
|
245
|
-
}
|
|
246
|
-
function transformValue(name, value) {
|
|
247
|
-
const options = fieldOptions.get(name);
|
|
248
|
-
if (options?.setValueAs) return options.setValueAs(value);
|
|
249
|
-
if (options?.valueAsNumber) return value === "" || value == null ? void 0 : Number(value);
|
|
250
|
-
return value;
|
|
251
|
-
}
|
|
252
247
|
return {
|
|
248
|
+
clearErrors,
|
|
249
|
+
createField,
|
|
253
250
|
formState,
|
|
254
|
-
setFormState,
|
|
255
|
-
register,
|
|
256
|
-
getValues,
|
|
257
251
|
getValue,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
clearErrors,
|
|
261
|
-
trigger,
|
|
252
|
+
getValues,
|
|
253
|
+
handleSubmit,
|
|
262
254
|
reset,
|
|
263
|
-
|
|
255
|
+
setError,
|
|
256
|
+
setFormState,
|
|
257
|
+
setValue,
|
|
258
|
+
trigger
|
|
264
259
|
};
|
|
265
260
|
}
|
|
266
|
-
function pickFieldErrors(errors, name) {
|
|
267
|
-
if (!errors[name]) return {};
|
|
268
|
-
return { [name]: errors[name] };
|
|
269
|
-
}
|
|
270
|
-
//#endregion
|
|
271
261
|
export { createForm };
|
|
@@ -1,8 +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
3
|
/** Create a resolver for ArkType schemas. */
|
|
6
|
-
declare function arktypeResolver<TValues extends FieldValues>(schema: Type<TValues>): Resolver<TValues>;
|
|
7
|
-
//#endregion
|
|
8
|
-
export { arktypeResolver };
|
|
4
|
+
export declare function arktypeResolver<TValues extends FieldValues>(schema: Type<TValues>): Resolver<TValues>;
|
|
@@ -1,31 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return (values, ctx) => {
|
|
6
|
-
if (ctx.name) return validateField(schema, values, ctx.name);
|
|
7
|
-
const result = schema(values);
|
|
8
|
-
const errors = readArkTypeErrors(result);
|
|
9
|
-
return {
|
|
10
|
-
errors: errors ?? {},
|
|
11
|
-
values: errors ? values : result
|
|
12
|
-
};
|
|
13
|
-
};
|
|
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";
|
|
14
5
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
values
|
|
21
|
-
};
|
|
22
|
-
} catch (err) {
|
|
23
|
-
console.error(err);
|
|
24
|
-
return {
|
|
25
|
-
errors: {},
|
|
26
|
-
values
|
|
27
|
-
};
|
|
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";
|
|
28
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"
|
|
18
|
+
};
|
|
29
19
|
}
|
|
30
20
|
function readArkTypeErrors(result) {
|
|
31
21
|
if (!isArkErrors(result)) return;
|
|
@@ -37,30 +27,39 @@ function readArkTypeErrors(result) {
|
|
|
37
27
|
};
|
|
38
28
|
return errors;
|
|
39
29
|
}
|
|
40
|
-
function prefixFieldErrors(name, errors) {
|
|
41
|
-
if (errors.root) return { [name]: errors.root };
|
|
42
|
-
return Object.fromEntries(Object.entries(errors).map(([path, error]) => [joinPath(name, path), error]));
|
|
43
|
-
}
|
|
44
30
|
function joinPath(name, path) {
|
|
45
31
|
if (!path || path === "root") return name;
|
|
46
32
|
return `${name}.${path}`;
|
|
47
33
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
function collectByPath(errors, byPath) {
|
|
52
|
-
for (const [path, value] of Object.entries(byPath)) errors[path || "root"] = {
|
|
53
|
-
message: readMessage(value),
|
|
54
|
-
type: "arktype"
|
|
55
|
-
};
|
|
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]));
|
|
56
37
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
};
|
|
62
51
|
}
|
|
63
|
-
return "Invalid field value";
|
|
64
52
|
}
|
|
65
|
-
|
|
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
|
+
}
|
|
66
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. */
|
|
@@ -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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/utils.ts
|
|
2
2
|
/** Return a dot path split into store path segments. */
|
|
3
3
|
function toPath(name) {
|
|
4
4
|
return name.split(".").filter(Boolean);
|
|
@@ -7,11 +7,16 @@ function toPath(name) {
|
|
|
7
7
|
function getPath(source, name) {
|
|
8
8
|
let value = source;
|
|
9
9
|
for (const key of toPath(name)) {
|
|
10
|
-
if (value
|
|
10
|
+
if (value === null || typeof value !== "object") return;
|
|
11
11
|
value = value[key];
|
|
12
12
|
}
|
|
13
13
|
return value;
|
|
14
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
|
+
}
|
|
15
20
|
/** Set a nested value on a cloned object by dot path. */
|
|
16
21
|
function setPath(source, name, value) {
|
|
17
22
|
const root = cloneValue(source);
|
|
@@ -23,16 +28,11 @@ function setPath(source, name, value) {
|
|
|
23
28
|
break;
|
|
24
29
|
}
|
|
25
30
|
const next = cursor[key];
|
|
26
|
-
if (next
|
|
31
|
+
if (next === null || typeof next !== "object" || Array.isArray(next)) cursor[key] = {};
|
|
27
32
|
cursor = cursor[key];
|
|
28
33
|
}
|
|
29
34
|
return root;
|
|
30
35
|
}
|
|
31
|
-
/** Create a JSON-compatible deep clone. */
|
|
32
|
-
function cloneValue(value) {
|
|
33
|
-
if (value == null || typeof value !== "object") return value;
|
|
34
|
-
return JSON.parse(JSON.stringify(value));
|
|
35
|
-
}
|
|
36
36
|
/** Compare two JSON-compatible values. */
|
|
37
37
|
function isEqual(left, right) {
|
|
38
38
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
@@ -41,20 +41,4 @@ function isEqual(left, right) {
|
|
|
41
41
|
function isEmptyObject(value) {
|
|
42
42
|
return Object.keys(value).length === 0;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
function getEventValue(valueOrEvent) {
|
|
46
|
-
if (!isEventLike(valueOrEvent)) return valueOrEvent;
|
|
47
|
-
const target = valueOrEvent.currentTarget ?? valueOrEvent.target;
|
|
48
|
-
if (!isValueTarget(target)) return;
|
|
49
|
-
if (target.type === "checkbox") return Boolean(target.checked);
|
|
50
|
-
if (target.type === "number") return target.value === "" ? void 0 : Number(target.value);
|
|
51
|
-
return target.value;
|
|
52
|
-
}
|
|
53
|
-
function isEventLike(value) {
|
|
54
|
-
return value != null && typeof value === "object" && ("target" in value || "currentTarget" in value);
|
|
55
|
-
}
|
|
56
|
-
function isValueTarget(target) {
|
|
57
|
-
return target != null && typeof target === "object" && ("value" in target || "checked" in target);
|
|
58
|
-
}
|
|
59
|
-
//#endregion
|
|
60
|
-
export { isEqual as a, isEmptyObject as i, getEventValue as n, setPath as o, getPath as r, cloneValue as t };
|
|
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.2.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
|
}
|