@zoijs/forms 0.1.0 → 0.1.2
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/CHANGELOG.md +21 -0
- package/README.md +25 -9
- package/package.json +4 -4
- package/src/index.d.ts +29 -11
- package/src/index.js +20 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@zoijs/forms` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.1.2 — 2026-10-05
|
|
6
|
+
|
|
7
|
+
### Documentation
|
|
8
|
+
- **CDN guidance uses exact, integrity-pinned URLs (SEC-7).** The README's no-install example
|
|
9
|
+
imported a floating `esm.sh/@zoijs/forms@0.1` URL; it now maps `@zoijs/core` and `@zoijs/forms` to
|
|
10
|
+
exact-version jsDelivr files with integrity hashes in an import map and imports by name. README
|
|
11
|
+
only — no code change.
|
|
12
|
+
|
|
13
|
+
## 0.1.1 — 2026-06-25
|
|
14
|
+
|
|
15
|
+
Consistency hardening — **non-breaking, additive only.**
|
|
16
|
+
|
|
17
|
+
- Added reader-style accessors that match the rest of the ecosystem
|
|
18
|
+
(`data()` / `loading()` / `value(name)` …): **`all()`** (all values),
|
|
19
|
+
**`allErrors()`** (all errors), **`allTouched()`** (all touched flags), and
|
|
20
|
+
**`isTouched(name)`** (one field's touched flag). All reactive inside a binding.
|
|
21
|
+
- The raw reactive state `values` / `errors` / `touched` is **kept** for backward
|
|
22
|
+
compatibility (now documented as advanced/direct access). No existing code breaks.
|
|
23
|
+
- Docs and the login example updated to prefer the reader methods.
|
|
24
|
+
- No behavior changes, no new form features, no core changes.
|
|
25
|
+
|
|
5
26
|
## 0.1.0 — 2026-06-25
|
|
6
27
|
|
|
7
28
|
Initial release of the tiny, native-forms-first helper for Zoijs.
|
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# @zoijs/forms
|
|
4
4
|
|
|
5
|
-
**A tiny, native-forms-first helper for [Zoijs](https://zoijs.
|
|
5
|
+
**A tiny, native-forms-first helper for [Zoijs](https://zoijs.dev).** Reactive values, errors, and touched state — without a form framework.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@zoijs/forms)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
-
[
|
|
10
|
+
[Documentation](https://zoijs.dev) · [Core package](https://www.npmjs.com/package/@zoijs/core)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -27,10 +27,12 @@ You can learn the whole thing in about 5 minutes.
|
|
|
27
27
|
npm install @zoijs/core @zoijs/forms
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Or with no install, from a CDN:
|
|
30
|
+
Or with no install, from a CDN: in an import map, point "@zoijs/core" and "@zoijs/forms" at
|
|
31
|
+
**exact-version** jsDelivr file URLs with integrity hashes, then import by name (so every
|
|
32
|
+
package shares one core). See the [CDN guide](https://zoijs.dev/installation#from-a-cdn).
|
|
31
33
|
|
|
32
34
|
```js
|
|
33
|
-
import { form } from "
|
|
35
|
+
import { form } from "@zoijs/forms";
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
## What `form()` does
|
|
@@ -43,34 +45,48 @@ import { form } from "@zoijs/forms";
|
|
|
43
45
|
|
|
44
46
|
const login = form({ email: "", password: "" });
|
|
45
47
|
|
|
46
|
-
login.
|
|
48
|
+
login.all(); // { email: "", password: "" } — all values (reactive)
|
|
47
49
|
login.value("email"); // one field (reactive)
|
|
48
50
|
login.set("email", "a@b.com"); // update one field
|
|
49
51
|
login.error("email"); // one field's error (reactive)
|
|
50
52
|
login.setError("email", "Required");
|
|
51
53
|
login.clearError("email");
|
|
54
|
+
login.isTouched("email"); // has this field been touched? (reactive)
|
|
52
55
|
login.touch("email"); // mark touched (e.g. on blur)
|
|
53
56
|
login.reset(); // restore initial values, clear errors + touched
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
These reader methods (`all()`, `value()`, `error()`, `isTouched()`, …) match the
|
|
60
|
+
rest of the ecosystem — the same shape as `data()`, `loading()`, and `pending()` —
|
|
61
|
+
so once you've learned one Zoijs package, the others read the same way.
|
|
62
|
+
|
|
56
63
|
## The API
|
|
57
64
|
|
|
58
65
|
| Member | What it does |
|
|
59
66
|
|---|---|
|
|
60
67
|
| `form(initialValues, options?)` | Create a form helper |
|
|
61
|
-
| `
|
|
68
|
+
| `all()` | Read all values (reactive) |
|
|
62
69
|
| `value(name)` | Read one field's value (reactive) |
|
|
63
70
|
| `set(name, value)` | Update one field |
|
|
64
|
-
| `
|
|
71
|
+
| `allErrors()` | Read all errors (reactive) |
|
|
65
72
|
| `error(name)` | Read one field's error (reactive) |
|
|
66
73
|
| `setError(name, message)` | Set one field's error |
|
|
67
74
|
| `clearError(name)` | Clear one field's error |
|
|
68
|
-
| `
|
|
75
|
+
| `allTouched()` | Read all touched flags (reactive) |
|
|
76
|
+
| `isTouched(name)` | Has one field been touched? (reactive) |
|
|
69
77
|
| `touch(name)` | Mark a field touched |
|
|
70
78
|
| `reset()` | Restore initial values; clear errors + touched |
|
|
71
79
|
| `validate(rules?)` | Run rules, set errors, return whether valid |
|
|
72
80
|
| `handleSubmit(fn)` | Wrap a submit handler: prevents reload, calls `fn(values)` |
|
|
73
81
|
|
|
82
|
+
### Advanced: raw state access
|
|
83
|
+
|
|
84
|
+
The underlying reactive state is also exposed as `values`, `errors`, and `touched`
|
|
85
|
+
(each a `createState`-shaped `{ get, set, peek }`). Prefer the reader methods above;
|
|
86
|
+
reach for the raw state only when you need direct access — e.g. `login.values.peek()`
|
|
87
|
+
to read without subscribing. These remain for backward compatibility and won't be
|
|
88
|
+
removed in 0.x.
|
|
89
|
+
|
|
74
90
|
## Login form example
|
|
75
91
|
|
|
76
92
|
Use native inputs — `value` reads from the form, `oninput` writes back, `onblur`
|
|
@@ -141,7 +157,7 @@ html`
|
|
|
141
157
|
<form onsubmit=${async (e) => {
|
|
142
158
|
e.preventDefault();
|
|
143
159
|
if (!login.validate(rules)) return;
|
|
144
|
-
await submitLogin.run(login.
|
|
160
|
+
await submitLogin.run(login.all());
|
|
145
161
|
}}>
|
|
146
162
|
...
|
|
147
163
|
<button disabled=${() => submitLogin.pending()}>Sign in</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoijs/forms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A tiny, native-forms-first helper for Zoijs: reactive values, errors, and touched state. No JSX, no build step.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=18"
|
|
23
23
|
},
|
|
24
|
-
"author": "Zoijs contributors (https://zoijs.
|
|
24
|
+
"author": "Zoijs contributors (https://zoijs.dev)",
|
|
25
25
|
"license": "MIT",
|
|
26
|
-
"homepage": "https://zoijs.
|
|
26
|
+
"homepage": "https://zoijs.dev",
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
29
|
"url": "git+https://github.com/Zoijs/zoijs.git",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"test": "node --test --import ./tests/setup-dom.js",
|
|
50
50
|
"test:types": "tsc --noEmit -p tsconfig.json",
|
|
51
51
|
"test:browser": "playwright test",
|
|
52
|
-
"dev": "
|
|
52
|
+
"dev": "node ../scripts/test-server.mjs .. 3700"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@playwright/test": "^1.61.1",
|
package/src/index.d.ts
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
// Authored in plain JavaScript; these declarations add editor autocomplete and
|
|
4
4
|
// optional type-checking without requiring TypeScript.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
6
|
+
// Source State from @zoijs/core (re-exported for convenience) so the raw
|
|
7
|
+
// values/errors/touched shape can never drift from `createState`. Other packages
|
|
8
|
+
// import core types the same way (e.g. @zoijs/router imports TemplateResult).
|
|
9
|
+
import type { State } from "@zoijs/core";
|
|
10
|
+
export type { State };
|
|
12
11
|
|
|
13
12
|
/** A single field rule: return a message when invalid, or a falsy value when valid. */
|
|
14
13
|
export type Rule<V = any, Values = Record<string, any>> = (
|
|
@@ -27,30 +26,49 @@ export interface FormOptions<V> {
|
|
|
27
26
|
|
|
28
27
|
/** A tiny reactive form helper created by {@link form}. */
|
|
29
28
|
export interface Form<V extends Record<string, any>> {
|
|
30
|
-
|
|
31
|
-
values
|
|
29
|
+
// --- values ---
|
|
30
|
+
/** All values (reactive). Reader-style, like `data()` / `loading()`. */
|
|
31
|
+
all(): V;
|
|
32
32
|
/** Read one field's value (reactive). */
|
|
33
33
|
value<K extends keyof V>(name: K): V[K];
|
|
34
34
|
/** Update one field's value. */
|
|
35
35
|
set<K extends keyof V>(name: K, value: V[K]): void;
|
|
36
|
+
|
|
37
|
+
// --- errors ---
|
|
36
38
|
/** All errors keyed by field name (reactive). */
|
|
37
|
-
|
|
39
|
+
allErrors(): Partial<Record<keyof V, string>>;
|
|
38
40
|
/** Read one field's error (reactive), or `undefined`. */
|
|
39
41
|
error(name: keyof V): string | undefined;
|
|
40
42
|
/** Set one field's error message. */
|
|
41
43
|
setError(name: keyof V, message: string): void;
|
|
42
44
|
/** Clear one field's error. */
|
|
43
45
|
clearError(name: keyof V): void;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
|
|
47
|
+
// --- touched ---
|
|
48
|
+
/** All touched flags keyed by field name (reactive). */
|
|
49
|
+
allTouched(): Partial<Record<keyof V, boolean>>;
|
|
50
|
+
/** Whether one field has been touched (reactive). */
|
|
51
|
+
isTouched(name: keyof V): boolean;
|
|
46
52
|
/** Mark a field touched (e.g. on blur). */
|
|
47
53
|
touch(name: keyof V): void;
|
|
54
|
+
|
|
55
|
+
// --- lifecycle ---
|
|
48
56
|
/** Restore initial values and clear errors + touched. */
|
|
49
57
|
reset(): void;
|
|
50
58
|
/** Run rules (or the option rules), set errors, and return whether valid. */
|
|
51
59
|
validate(rules?: Rules<V>): boolean;
|
|
52
60
|
/** Wrap a submit handler: prevents the default reload and calls `fn(values)`. */
|
|
53
61
|
handleSubmit(fn: (values: V, event?: Event) => unknown): (event?: Event) => unknown;
|
|
62
|
+
|
|
63
|
+
// --- raw reactive state (advanced / backward-compatible) ---
|
|
64
|
+
// Prefer all() / allErrors() / allTouched() above. These remain for direct
|
|
65
|
+
// state access (e.g. `values.peek()`), and won't be removed in 0.x.
|
|
66
|
+
/** Raw values state. Advanced — prefer {@link Form.all}. */
|
|
67
|
+
values: State<V>;
|
|
68
|
+
/** Raw errors state. Advanced — prefer {@link Form.allErrors}. */
|
|
69
|
+
errors: State<Partial<Record<keyof V, string>>>;
|
|
70
|
+
/** Raw touched state. Advanced — prefer {@link Form.allTouched}. */
|
|
71
|
+
touched: State<Partial<Record<keyof V, boolean>>>;
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
/**
|
package/src/index.js
CHANGED
|
@@ -88,18 +88,35 @@ export function form(initialValues = {}, options = {}) {
|
|
|
88
88
|
return fn(values.get(), event);
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
// Reader-style accessors for whole-form state, matching the rest of the
|
|
92
|
+
// ecosystem (data(), loading(), value(name), …). Reactive inside a binding.
|
|
93
|
+
const all = () => values.get();
|
|
94
|
+
const allErrors = () => errors.get();
|
|
95
|
+
const allTouched = () => touched.get();
|
|
96
|
+
const isTouched = (name) => !!touched.get()[name];
|
|
97
|
+
|
|
91
98
|
return {
|
|
92
|
-
values
|
|
99
|
+
// values
|
|
100
|
+
all,
|
|
93
101
|
value,
|
|
94
102
|
set,
|
|
95
|
-
errors
|
|
103
|
+
// errors
|
|
104
|
+
allErrors,
|
|
96
105
|
error,
|
|
97
106
|
setError,
|
|
98
107
|
clearError,
|
|
99
|
-
touched
|
|
108
|
+
// touched
|
|
109
|
+
allTouched,
|
|
110
|
+
isTouched,
|
|
100
111
|
touch,
|
|
112
|
+
// lifecycle
|
|
101
113
|
reset,
|
|
102
114
|
validate,
|
|
103
115
|
handleSubmit,
|
|
116
|
+
// raw reactive state — advanced / backward-compatible. Prefer all() /
|
|
117
|
+
// allErrors() / allTouched() above; these stay for direct state access.
|
|
118
|
+
values,
|
|
119
|
+
errors,
|
|
120
|
+
touched,
|
|
104
121
|
};
|
|
105
122
|
}
|