@deijose/nix-js 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,322 +1,89 @@
1
- # ❄️ Nix.js
1
+ # Nix.js
2
2
 
3
- A lightweight, fully reactive micro-framework for building modern web UIs — no virtual DOM, no compiler, no build-time magic. Just signals, tagged templates, and pure TypeScript.
3
+ [![npm version](https://img.shields.io/npm/v/@deijose/nix-js.svg)](https://www.npmjs.com/package/@deijose/nix-js)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Tests](https://img.shields.io/badge/tests-152%20passing-brightgreen.svg)]()
6
+ [![Bundle size](https://img.shields.io/badge/min%2Bgzip-~8%20KB-orange.svg)]()
7
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-success.svg)]()
4
8
 
5
- The `@deijose/nix-js` package provides everything you need to build reactive UIs: signals, template engine, components, stores, router, and dependency injection in a single zero-dependency bundle.
9
+ A lightweight, fully reactive micro-framework for building modern web UIs no virtual DOM, no compiler, no build-time magic. Just signals, tagged templates, and pure TypeScript.
6
10
 
7
11
  ```
8
- ~14 KB minified · zero dependencies · TypeScript-first · ES2022
12
+ ~24 KB minified · ~8 KB gzipped · zero dependencies · TypeScript-first · ES2022
9
13
  ```
10
14
 
11
15
  ## Installation
12
16
 
13
17
  ```bash
14
18
  npm install @deijose/nix-js
15
- # or
16
- bun add @deijose/nix-js
17
19
  ```
18
20
 
19
- ## Usage
21
+ ## Quick Start
20
22
 
21
23
  ```typescript
22
- import { signal, html, mount } from "@deijose/nix-js";
24
+ import { signal, html, NixComponent, mount, createRouter, RouterView, Link, useRouter } from "@deijose/nix-js";
23
25
 
24
- function Counter() {
25
- const count = signal(0);
26
+ // --- Reactive counter ---
26
27
 
27
- return html`
28
- <div>
29
- <h1>${() => count.value}</h1>
30
- <button @click=${() => count.value++}>Increment</button>
31
- </div>
32
- `;
28
+ class HomePage extends NixComponent {
29
+ render() {
30
+ const count = signal(0);
31
+ return html`
32
+ <h1>Home</h1>
33
+ <p>Count: ${() => count.value}</p>
34
+ <button @click=${() => count.value++}>+1</button>
35
+ `;
36
+ }
33
37
  }
34
38
 
35
- mount(Counter, document.getElementById("app")!);
36
- ```
37
-
38
- ### Signals & reactivity
39
-
40
- ```typescript
41
- import { signal, computed, effect, watch } from "@deijose/nix-js";
42
-
43
- const price = signal(10);
44
- const qty = signal(3);
45
- const total = computed(() => price.value * qty.value);
46
-
47
- effect(() => console.log("Total:", total.value)); // logs on every change
48
-
49
- watch(total, (next, prev) => console.log(prev, "→", next));
50
-
51
- price.value = 20; // effect & watch fire automatically
52
- ```
53
-
54
- ### Components
55
-
56
- ```typescript
57
- import { NixComponent, mount, html } from "@deijose/nix-js";
58
- import { signal } from "@deijose/nix-js";
59
-
60
- class MyComponent extends NixComponent {
61
- private msg = signal("Hello");
62
-
63
- onInit() {
64
- setTimeout(() => (this.msg.value = "World"), 1000);
65
- }
39
+ // --- Dynamic route params ---
66
40
 
41
+ class UserPage extends NixComponent {
67
42
  render() {
68
- return html`<p>${() => this.msg.value}</p>`;
43
+ const router = useRouter();
44
+ return html`<h1>User: ${() => router.params.value.id}</h1>`;
69
45
  }
70
46
  }
71
47
 
72
- mount(MyComponent, document.body);
73
- ```
74
-
75
- ### Stores
48
+ // --- Router + App shell ---
76
49
 
77
- ```typescript
78
- import { createStore } from "@deijose/nix-js";
79
-
80
- const counter = createStore({ count: 0 }, (state) => ({
81
- increment() { state.count.value++; },
82
- reset() { state.count.value = 0; },
83
- }));
84
-
85
- counter.count.value; // reactive signal
86
- counter.increment();
87
- ```
88
-
89
- ### Router
90
-
91
- ```typescript
92
- import { createRouter, RouterView, Link, html } from "@deijose/nix-js";
93
-
94
- const router = createRouter([
95
- { path: "/", component: Home },
96
- { path: "/about", component: About },
50
+ createRouter([
51
+ { path: "/", component: () => new HomePage() },
52
+ { path: "/user/:id", component: () => new UserPage() },
97
53
  ]);
98
54
 
99
- const App = () => html`
100
- <nav>
101
- ${Link({ href: "/" }, "Home")}
102
- ${Link({ href: "/about" }, "About")}
103
- </nav>
104
- ${RouterView(router)}
105
- `;
106
- ```
107
-
108
- ### Forms
109
-
110
- ```typescript
111
- import { createForm, required, email, minLength, min } from "@deijose/nix-js";
112
-
113
- const form = createForm(
114
- { name: "", email: "", age: 0 },
115
- {
116
- validators: {
117
- name: [required(), minLength(2)],
118
- email: [required(), email()],
119
- age: [required(), min(18)],
120
- },
121
- // Optional: plug in Zod, Valibot, Yup, or any schema library
122
- validate(values) {
123
- const r = zodSchema.safeParse(values);
124
- if (r.success) return null;
125
- return Object.fromEntries(
126
- Object.entries(r.error.flatten().fieldErrors).map(([k, v]) => [k, v?.[0]])
127
- );
128
- },
129
- }
130
- );
131
-
132
- html`
133
- <form @submit=${form.handleSubmit(onSubmit)}>
134
- <input value=${() => form.fields.name.value.value}
135
- @input=${form.fields.name.onInput}
136
- @blur=${form.fields.name.onBlur} />
137
- ${() => form.fields.name.error.value
138
- ? html`<p class="err">${form.fields.name.error.value}</p>`
139
- : null}
140
- <button type="submit">Submit</button>
141
- </form>
142
- `
143
-
144
- // Inject server errors after a failed API call:
145
- form.setErrors({ email: "Email already in use" });
146
- ```
147
-
148
- ### Children & slots
149
-
150
- ```typescript
151
- import type { NixChildren } from "@deijose/nix-js";
152
-
153
- class Card extends NixComponent {
55
+ class App extends NixComponent {
154
56
  render() {
155
57
  return html`
156
- <div class="card">
157
- <header>${this.slot("header")}</header>
158
- <main>${this.children}</main>
159
- <footer>${this.slot("footer") ?? html`<small>Footer</small>`}</footer>
160
- </div>
58
+ <nav>${new Link("/", "Home")} ${new Link("/user/42", "User 42")}</nav>
59
+ ${new RouterView()}
161
60
  `;
162
61
  }
163
62
  }
164
63
 
165
- const app = new Card()
166
- .setSlot("header", html`<h1>Título</h1>`)
167
- .setChildren(html`<p>Cuerpo del card</p>`)
168
- .setSlot("footer", html`<small>© 2026</small>`);
169
-
170
- // Function component style:
171
- function Box({ children }: { children?: NixChildren }) {
172
- return html`<div class="box">${children}</div>`;
173
- }
64
+ mount(new App(), "#app");
174
65
  ```
175
66
 
176
- ### show / hide directive
67
+ ## What's Included
177
68
 
178
- Toggle visibility **without unmounting** the element stays in the DOM,
179
- preserving its state, listeners, and child components.
69
+ Everything ships in a single zero-dependency import:
180
70
 
181
- ```typescript
182
- import { signal } from "@deijose/nix-js";
183
-
184
- const isOpen = signal(false);
185
- const loading = signal(false);
186
-
187
- html`
188
- <!-- visible when truthy -->
189
- <div show=${() => isOpen.value}>Panel content (kept in DOM)</div>
190
-
191
- <!-- hidden when truthy (inverse of show) -->
192
- <form hide=${() => loading.value}>...</form>
193
- <div show=${() => loading.value}>⏳ Submitting…</div>
194
- `
195
- ```
196
-
197
- Imperative use outside a template:
198
-
199
- ```typescript
200
- import { showWhen, effect } from "@deijose/nix-js";
201
-
202
- const panel = document.getElementById("panel") as HTMLElement;
203
- effect(() => showWhen(panel, isOpen.value));
204
- ```
205
-
206
- ### Portal
207
-
208
- Render a template into `document.body` (or any target) — escaping
209
- `overflow: hidden` and stacking contexts. Ideal for modals, tooltips, toasts.
210
- The portal is cleaned up automatically when its parent unmounts.
211
-
212
- ```typescript
213
- import { signal, portal, html } from "@deijose/nix-js";
214
-
215
- const isOpen = signal(false);
216
-
217
- html`
218
- <button @click=${() => { isOpen.value = true; }}>Open modal</button>
219
-
220
- ${() => isOpen.value
221
- ? portal(html`
222
- <div class="overlay" @click=${() => { isOpen.value = false; }}>
223
- <div class="modal" @click.stop=${() => {}}>
224
- <h2>Hello!</h2>
225
- <button @click=${() => { isOpen.value = false; }}>Close</button>
226
- </div>
227
- </div>
228
- `) // renders into document.body by default
229
- : null
230
- }
231
- `
232
-
233
- // Custom target:
234
- portal(html`<div class="toast">Saved!</div>`, "#toast-root")
235
- portal(html`<Tooltip />`, document.getElementById("tooltip-layer")!)
236
- ```
237
-
238
- ### Error Boundaries
239
-
240
- ```typescript
241
- import { createErrorBoundary, html, mount, signal } from "@deijose/nix-js";
242
-
243
- // Catch render and reactive errors in a subtree
244
- mount(
245
- createErrorBoundary(
246
- html`<div>${() => riskyData()}</div>`,
247
- (err) => html`<div class="error">Failed: ${String(err)}</div>`
248
- ),
249
- "#app"
250
- );
251
-
252
- // Catches onInit / render / onMount / reactive effects
253
- createErrorBoundary(new DataWidget(), html`<p>Service unavailable</p>`)
254
-
255
- // Nested: inner catches first, outer is app-level
256
- createErrorBoundary(
257
- html`
258
- ${createErrorBoundary(new RiskyWidget(), html`<p>Widget error</p>`)}
259
- `,
260
- html`<p>App crashed</p>`
261
- )
262
- ```
263
-
264
- ### Transitions
265
-
266
- ```typescript
267
- import { signal, html, transition, mount } from "@deijose/nix-js";
268
-
269
- const show = signal(true);
270
-
271
- /* CSS in your stylesheet:
272
- .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; }
273
- .fade-enter-from, .fade-leave-to { opacity: 0; }
274
- */
275
-
276
- mount(
277
- transition(
278
- () => show.value ? html`<p>Hello!</p>` : null,
279
- { name: "fade" }
280
- ),
281
- "#app"
282
- );
283
-
284
- // Animate on first render too
285
- transition(html`<header>App</header>`, { name: "slide-down", appear: true });
286
-
287
- // JS hooks
288
- transition(content, {
289
- name: "fade",
290
- onBeforeEnter: (el) => el.setAttribute("aria-hidden", "false"),
291
- onAfterLeave: (el) => el.setAttribute("aria-hidden", "true"),
292
- });
293
- ```
294
-
295
- ### Dependency injection
296
-
297
- ```typescript
298
- import { provide, inject, createInjectionKey } from "@deijose/nix-js";
299
-
300
- const ThemeKey = createInjectionKey<string>("theme");
301
-
302
- // Parent component
303
- function App() {
304
- provide(ThemeKey, "dark");
305
- return html`${ThemedCard()}`;
306
- }
307
-
308
- // Child component
309
- function ThemedCard() {
310
- const theme = inject(ThemeKey); // "dark"
311
- return html`<div class="card-${theme}">...</div>`;
312
- }
313
- ```
71
+ | Category | APIs |
72
+ |---|---|
73
+ | **Reactivity** | `signal`, `computed`, `effect`, `batch`, `watch`, `untrack`, `nextTick` |
74
+ | **Templates** | `` html` ` ``, `repeat`, `ref`, `portal`, `transition`, `showWhen` |
75
+ | **Components** | `NixComponent`, `mount`, lifecycle hooks, children & named slots |
76
+ | **Router** | `createRouter`, `RouterView`, `Link`, `useRouter`, guards, nested routes |
77
+ | **Forms** | `useField`, `createForm`, built-in validators, Zod/Valibot interop |
78
+ | **State** | `createStore`, `provide`, `inject`, `createInjectionKey` |
79
+ | **Async** | `suspend`, `lazy` |
80
+ | **Error handling** | `createErrorBoundary` |
314
81
 
315
82
  ## Documentation
316
83
 
317
- For the complete API reference, guides, and all features (async/lazy, lifecycle hooks, event modifiers, keyed lists, query params, nested routes, and more):
84
+ Full API reference, guides, and examples:
318
85
 
319
- **→ [Full documentation on GitHub](https://github.com/DeijoseDevelop/nix-js)**
86
+ **→ [github.com/DeijoseDevelop/nix-js](https://github.com/DeijoseDevelop/nix-js)**
320
87
 
321
88
  ## License
322
89
 
@@ -1,68 +1,22 @@
1
1
  import { NixComponent } from "./lifecycle";
2
2
  import type { NixTemplate } from "./template";
3
3
  export interface SuspenseOptions {
4
- /**
5
- * Template a mostrar mientras la promesa está pendiente.
6
- * Por defecto: spinner de puntos animados.
7
- */
4
+ /** Template shown while the promise is pending. */
8
5
  fallback?: NixTemplate;
9
- /**
10
- * Factory que recibe el error y devuelve el template de error.
11
- * Por defecto: mensaje de error en rojo.
12
- */
6
+ /** Factory receiving the error, returns the error template. */
13
7
  errorFallback?: (err: unknown) => NixTemplate;
14
- /**
15
- * Si `true`, mantiene el fallback visible mientras `asyncFn` vuelve a
16
- * ejecutarse tras un cambio reactivo. Si `false` (por defecto), durante
17
- * las recargas se sigue mostrando el contenido anterior hasta que la nueva
18
- * promesa se resuelva.
19
- */
8
+ /** If `true`, shows fallback during re-fetches instead of stale content. */
20
9
  resetOnRefresh?: boolean;
21
10
  }
22
11
  /**
23
- * Ejecuta una función async y renderiza según su estado (pending / resolved /
24
- * error). Equivale al patrón <Suspense> de otros frameworks.
25
- *
26
- * @param asyncFn Función que devuelve una promesa con los datos.
27
- * @param renderFn Recibe los datos resueltos y devuelve el template/componente.
28
- * @param options `fallback`, `errorFallback`, `resetOnRefresh`.
29
- *
30
- * @example
31
- * // Uso simple con fallback por defecto
32
- * const userView = suspend(
33
- * () => fetchUser(userId.value),
34
- * user => html`<div>${user.name}</div>`
35
- * );
36
- *
37
- * @example
38
- * // Con fallback personalizado y manejo de error
39
- * suspend(
40
- * () => api.getItems(),
41
- * items => html`<ul>${items.map(i => html`<li>${i}</li>`)}</ul>`,
42
- * {
43
- * fallback: html`<span>Cargando items…</span>`,
44
- * errorFallback: err => html`<p style="color:red">Error: ${String(err)}</p>`,
45
- * }
46
- * )
12
+ * Runs an async function and renders based on its state (pending/resolved/error).
13
+ * Equivalent to the Suspense pattern in other frameworks.
47
14
  */
48
15
  export declare function suspend<T>(asyncFn: () => Promise<T>, renderFn: (data: T) => NixTemplate | NixComponent, options?: SuspenseOptions): NixComponent;
49
16
  /**
50
- * Envuelve un import dinámico para lazy loading de componentes de ruta.
51
- * El módulo se carga una sola vez (cacheado) y mientras tanto muestra el
52
- * fallback. Compatible directamente con el campo `component` de `RouteRecord`.
53
- *
54
- * El módulo importado debe exportar el componente como **export default**.
55
- *
56
- * @param importFn Función que hace el import dinámico.
57
- * @param fallback Template opcional mientras se descarga el chunk.
58
- *
59
- * @example
60
- * createRouter([
61
- * { path: "/", component: lazy(() => import("./pages/Home")) },
62
- * { path: "/about", component: lazy(() => import("./pages/About")) },
63
- * { path: "/admin", component: lazy(() => import("./pages/Admin"),
64
- * html`<p>Cargando panel…</p>`) },
65
- * ])
17
+ * Wraps a dynamic import for lazy-loading route components.
18
+ * The module is loaded once (cached). Compatible with `RouteRecord.component`.
19
+ * The imported module must use a default export.
66
20
  */
67
21
  export declare function lazy(importFn: () => Promise<{
68
22
  default: new () => NixComponent;
@@ -1,61 +1,27 @@
1
- /**
2
- * Clave tipada para provide/inject.
3
- * El parámetro genérico T garantiza coherencia entre proveedor y consumidor.
4
- *
5
- * @example
6
- * const THEME_KEY = createInjectionKey<Signal<string>>("theme");
7
- */
1
+ /** Typed key for provide/inject. Generic `T` enforces type safety between provider and consumer. */
8
2
  export type InjectionKey<T> = symbol & {
9
3
  readonly __nixType?: T;
10
4
  };
11
- /**
12
- * Crea una InjectionKey única y tipada.
13
- * Cada llamada produce un símbolo distinto, evitando colisiones.
14
- *
15
- * @param description Nombre descriptivo (aparece en Symbol.toString())
16
- */
5
+ /** Creates a unique typed InjectionKey. */
17
6
  export declare function createInjectionKey<T>(description?: string): InjectionKey<T>;
18
- /** @internal — devuelve copia del stack (para capturar en closures de efectos). */
7
+ /** @internal — returns a copy of the stack for capturing in effect closures. */
19
8
  export declare function _captureContextSnapshot(): Map<unknown, unknown>[];
20
- /** @internal — push de un contexto vacío para un nuevo componente (render estático). */
9
+ /** @internal — pushes an empty context for a new component (static render). */
21
10
  export declare function _pushComponentContext(): void;
22
- /** @internal — pop del contexto del componente actual (render estático). */
11
+ /** @internal — pops the current component context (static render). */
23
12
  export declare function _popComponentContext(): void;
24
13
  /**
25
- * @internal — ejecuta `fn` con `parentSnapshot` como ancestros y un nuevo
26
- * contexto vacío en el tope, luego restaura el stack previo.
27
- *
28
- * Usado por efectos reactivos que pueden re-ejecutarse fuera del árbol de
29
- * rendering original (p.ej. NixComponents dentro de `() => new MyComp()`).
14
+ * @internal — executes `fn` with `parentSnapshot` as ancestors and a fresh
15
+ * empty context on top, then restores the previous stack.
30
16
  */
31
17
  export declare function _withComponentContext<T>(parentSnapshot: Map<unknown, unknown>[], fn: () => T): T;
32
18
  /**
33
- * Registra un valor para que los componentes descendientes puedan obtenerlo
34
- * con `inject()`.
35
- *
36
- * Debe llamarse en `onInit()` o en el constructor de un `NixComponent`.
37
- * Si se llama fuera del contexto de render de un componente, lanza un error.
38
- *
39
- * @example
40
- * class ThemeProvider extends NixComponent {
41
- * theme = signal("dark");
42
- * onInit() { provide(THEME_KEY, this.theme); } // ← aquí
43
- * render() { return html`${new ThemedButton()}`; }
44
- * }
19
+ * Registers a value so descendant components can retrieve it via `inject()`.
20
+ * Must be called inside `onInit()` of a NixComponent.
45
21
  */
46
22
  export declare function provide<T>(key: InjectionKey<T> | string | symbol, value: T): void;
47
23
  /**
48
- * Obtiene un valor provisto por un componente ancestro.
49
- * Busca de hijo a padre; retorna `undefined` si la clave no fue provista.
50
- *
51
- * Úsalo como propiedad de clase o dentro de `onInit()`.
52
- *
53
- * @example
54
- * class ThemedButton extends NixComponent {
55
- * theme = inject(THEME_KEY); // Signal<string> | undefined
56
- * render() {
57
- * return html`<button class=${() => this.theme?.value ?? "light"}>OK</button>`;
58
- * }
59
- * }
24
+ * Retrieves a value provided by an ancestor component.
25
+ * Searches child-to-parent; returns `undefined` if the key was not provided.
60
26
  */
61
27
  export declare function inject<T>(key: InjectionKey<T> | string | symbol): T | undefined;
@@ -8,54 +8,9 @@ export declare function pattern(regex: RegExp, message?: string): Validator<stri
8
8
  export declare function email(message?: string): Validator<string>;
9
9
  export declare function min(n: number, message?: string): Validator<number>;
10
10
  export declare function max(n: number, message?: string): Validator<number>;
11
- /**
12
- * Creates a typed custom validator, fully compatible with `useField` and
13
- * `createForm`. The preferred way to define your own rules without importing
14
- * the `Validator<T>` type manually.
15
- *
16
- * @example Simple rule
17
- * ```typescript
18
- * const noSpaces = createValidator<string>((v) =>
19
- * v.includes(" ") ? "No spaces allowed" : null
20
- * );
21
- *
22
- * useField("", [noSpaces]);
23
- * ```
24
- *
25
- * @example Configurable rule (same pattern as built-ins)
26
- * ```typescript
27
- * const phone = (msg = "Invalid phone number") =>
28
- * createValidator<string>((v) =>
29
- * /^\+?\d{7,15}$/.test(v) ? null : msg
30
- * );
31
- *
32
- * useField("", [phone()]);
33
- * useField("", [phone("Enter your mobile number")]);
34
- * ```
35
- */
11
+ /** Creates a typed custom validator compatible with `useField` and `createForm`. */
36
12
  export declare function createValidator<T>(fn: (value: T) => string | null | undefined): Validator<T>;
37
- /**
38
- * All built-in validators grouped as a namespace object.
39
- *
40
- * Use this when you prefer `validators.required()` over individual named
41
- * imports. Combine with `extendValidators` to add your own rules.
42
- *
43
- * @example
44
- * ```typescript
45
- * import { validators } from "@deijose/nix-js";
46
- *
47
- * createForm(
48
- * { name: "", email: "", age: 0 },
49
- * {
50
- * validators: {
51
- * name: [validators.required(), validators.minLength(2)],
52
- * email: [validators.required(), validators.email()],
53
- * age: [validators.required(), validators.min(18)],
54
- * },
55
- * }
56
- * );
57
- * ```
58
- */
13
+ /** All built-in validators grouped as a namespace. Extensible via `extendValidators`. */
59
14
  export declare const validators: {
60
15
  readonly required: typeof required;
61
16
  readonly minLength: typeof minLength;
@@ -68,48 +23,8 @@ export declare const validators: {
68
23
  /** Shape of the built-in `validators` namespace. Used as the base type for `extendValidators`. */
69
24
  export type ValidatorsBase = typeof validators;
70
25
  /**
71
- * Merges your own validator factories into the `validators` namespace, returning
72
- * a fully typed object that includes both built-ins and custom rules.
73
- *
74
- * The original `validators` object is **never mutated** — a new object is returned.
75
- *
76
- * @example
77
- * ```typescript
78
- * import { validators, extendValidators, createValidator } from "@deijose/nix-js";
79
- *
80
- * const myValidators = extendValidators(validators, {
81
- * // Configurable rule (custom message support)
82
- * phone: (msg = "Invalid phone number") =>
83
- * createValidator<string>((v) => /^\+?\d{7,15}$/.test(v) ? null : msg),
84
- *
85
- * // Rule reusing a built-in internally
86
- * slug: (msg = "Only lowercase letters, numbers and hyphens") =>
87
- * createValidator<string>((v) =>
88
- * /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(v) ? null : msg
89
- * ),
90
- * });
91
- *
92
- * // IDE auto-complete covers built-ins and custom rules alike:
93
- * myValidators.required() // ✅ built-in
94
- * myValidators.email() // ✅ built-in
95
- * myValidators.phone() // ✅ custom
96
- * myValidators.slug("Bad slug") // ✅ custom, custom message
97
- *
98
- * // Use in a form like any other validators:
99
- * createForm(
100
- * { phone: "", slug: "" },
101
- * {
102
- * validators: {
103
- * phone: [myValidators.required(), myValidators.phone()],
104
- * slug: [myValidators.required(), myValidators.slug()],
105
- * },
106
- * }
107
- * );
108
- * ```
109
- *
110
- * @param base The base validators namespace (pass `validators`).
111
- * @param extensions An object whose values are validator factory functions.
112
- * @returns A new merged namespace object.
26
+ * Merges custom validator factories into the built-in namespace.
27
+ * Returns a new object the original is never mutated.
113
28
  */
114
29
  export declare function extendValidators<E extends Record<string, (...args: any[]) => Validator<any>>>(base: ValidatorsBase, extensions: E): ValidatorsBase & E;
115
30
  /** Public state of a single form field. */
@@ -137,21 +52,7 @@ export interface FieldState<T> {
137
52
  */
138
53
  _setExternalError(msg: string | null): void;
139
54
  }
140
- /**
141
- * Creates a standalone reactive form field with optional validators.
142
- *
143
- * @example
144
- * const name = useField("", [required(), minLength(2)]);
145
- *
146
- * html`
147
- * <input value=${() => name.value.value}
148
- * @input=${name.onInput}
149
- * @blur=${name.onBlur} />
150
- * ${() => name.error.value
151
- * ? html`<p class="err">${name.error.value}</p>`
152
- * : null}
153
- * `
154
- */
55
+ /** Creates a standalone reactive form field with optional validators. */
155
56
  export declare function useField<T>(initialValue: T, validators?: Validator<T>[]): FieldState<T>;
156
57
  /** Map of field-name → error message for external validation results. */
157
58
  export type FieldErrors<T extends Record<string, unknown>> = {
@@ -229,30 +130,6 @@ export interface FormOptions<T extends Record<string, unknown>> {
229
130
  }
230
131
  /**
231
132
  * Creates a managed form with reactive fields, built-in validation,
232
- * schema-level validation (Zod / Valibot / Yup / custom), and submit handling.
233
- *
234
- * @example
235
- * const form = createForm(
236
- * { name: "", email: "", age: 0 },
237
- * {
238
- * validators: {
239
- * name: [required(), minLength(2)],
240
- * email: [required(), email()],
241
- * age: [required(), min(18)],
242
- * },
243
- * }
244
- * );
245
- *
246
- * html`
247
- * <form @submit=${form.handleSubmit(onSubmit)}>
248
- * <input value=${() => form.fields.name.value.value}
249
- * @input=${form.fields.name.onInput}
250
- * @blur=${form.fields.name.onBlur} />
251
- * ${() => form.fields.name.error.value
252
- * ? html`<p class="err">${form.fields.name.error.value}</p>`
253
- * : null}
254
- * <button type="submit">Submit</button>
255
- * </form>
256
- * `
133
+ * schema-level validation (Zod/Valibot/Yup/custom), and submit handling.
257
134
  */
258
135
  export declare function createForm<T extends Record<string, unknown>>(initialValues: T, options?: FormOptions<T>): FormState<T>;