@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 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.com).** Reactive values, errors, and touched state — without a form framework.
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
  [![npm](https://img.shields.io/npm/v/@zoijs/forms.svg)](https://www.npmjs.com/package/@zoijs/forms)
8
8
  [![license](https://img.shields.io/npm/l/@zoijs/forms.svg)](LICENSE)
9
9
 
10
- [Website](https://zoijs.com) · [Documentation](https://zoijs.dev) · [Core package](https://www.npmjs.com/package/@zoijs/core)
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 "https://esm.sh/@zoijs/forms";
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.values.get(); // { email: "", password: "" }
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
- | `values` | Reactive state of all values — `values.get()` returns the object |
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
- | `errors` | Reactive state of all errors |
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
- | `touched` | Reactive state of touched fields |
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.values.get());
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.0",
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.com)",
24
+ "author": "Zoijs contributors (https://zoijs.dev)",
25
25
  "license": "MIT",
26
- "homepage": "https://zoijs.com",
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": "npx serve -l 3700 .."
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
- /** A reactive value (same shape as core's createState). */
7
- export interface State<T> {
8
- get(): T;
9
- set(value: T): void;
10
- peek(): T;
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
- /** All values (reactive). `values.get()` returns the whole object. */
31
- values: State<V>;
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
- errors: State<Partial<Record<keyof V, string>>>;
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
- /** Which fields have been touched (reactive). */
45
- touched: State<Partial<Record<keyof V, boolean>>>;
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
  }