@deijose/nix-js 1.0.3 → 1.0.5
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/dist/lib/index.d.ts +2 -0
- package/dist/lib/nix/async.d.ts +69 -0
- package/dist/lib/nix/component.d.ts +13 -0
- package/dist/lib/nix/context.d.ts +61 -0
- package/dist/lib/nix/form.d.ts +258 -0
- package/dist/lib/nix/index.d.ts +17 -0
- package/dist/lib/nix/lifecycle.d.ts +124 -0
- package/dist/lib/nix/reactivity.d.ts +124 -0
- package/dist/lib/nix/router.d.ts +166 -0
- package/dist/lib/nix/store.d.ts +40 -0
- package/dist/lib/nix/template.d.ts +380 -0
- package/dist/lib/nix-js.cjs +5 -6
- package/dist/lib/nix-js.js +8 -1259
- package/package.json +2 -2
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { Signal, signal, effect, computed, batch, watch, untrack, nextTick, html, repeat, ref, showWhen, portal, createPortalOutlet, portalOutlet, provideOutlet, injectOutlet, createErrorBoundary, transition, mount, NixComponent, createStore, createRouter, RouterView, Link, useRouter, suspend, lazy, provide, inject, createInjectionKey, useField, createForm, required, minLength, maxLength, email, pattern, min, max, createValidator, validators, extendValidators, } from "./nix";
|
|
2
|
+
export type { WatchOptions, NixTemplate, NixMountHandle, KeyedList, NixRef, PortalOutlet, ErrorFallback, TransitionOptions, TransitionContent, Store, StoreSignals, Router, RouteRecord, NavigationGuard, NavigationGuardResult, SuspenseOptions, InjectionKey, Validator, FieldState, FieldErrors, FormState, FormOptions, ValidatorsBase, NixChildren, } from "./nix";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NixComponent } from "./lifecycle";
|
|
2
|
+
import type { NixTemplate } from "./template";
|
|
3
|
+
export interface SuspenseOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Template a mostrar mientras la promesa está pendiente.
|
|
6
|
+
* Por defecto: spinner de puntos animados.
|
|
7
|
+
*/
|
|
8
|
+
fallback?: NixTemplate;
|
|
9
|
+
/**
|
|
10
|
+
* Factory que recibe el error y devuelve el template de error.
|
|
11
|
+
* Por defecto: mensaje de error en rojo.
|
|
12
|
+
*/
|
|
13
|
+
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
|
+
*/
|
|
20
|
+
resetOnRefresh?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
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
|
+
* )
|
|
47
|
+
*/
|
|
48
|
+
export declare function suspend<T>(asyncFn: () => Promise<T>, renderFn: (data: T) => NixTemplate | NixComponent, options?: SuspenseOptions): NixComponent;
|
|
49
|
+
/**
|
|
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
|
+
* ])
|
|
66
|
+
*/
|
|
67
|
+
export declare function lazy(importFn: () => Promise<{
|
|
68
|
+
default: new () => NixComponent;
|
|
69
|
+
}>, fallback?: NixTemplate): () => NixComponent;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NixTemplate, NixMountHandle } from "./template";
|
|
2
|
+
import { type NixComponent } from "./lifecycle";
|
|
3
|
+
/**
|
|
4
|
+
* Monta un NixTemplate o NixComponent en el DOM.
|
|
5
|
+
*
|
|
6
|
+
* mount(App(), "#app"); // NixTemplate (función componente)
|
|
7
|
+
* mount(new Timer(), "#app"); // NixComponent (clase con lifecycle)
|
|
8
|
+
*
|
|
9
|
+
* @param component NixTemplate (resultado de html``) o instancia de NixComponent.
|
|
10
|
+
* @param container Selector CSS o HTMLElement donde se insertará.
|
|
11
|
+
* @returns { unmount() } para limpiar effects y remover el DOM.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mount(component: NixTemplate | NixComponent, container: Element | string): NixMountHandle;
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
*/
|
|
8
|
+
export type InjectionKey<T> = symbol & {
|
|
9
|
+
readonly __nixType?: T;
|
|
10
|
+
};
|
|
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
|
+
*/
|
|
17
|
+
export declare function createInjectionKey<T>(description?: string): InjectionKey<T>;
|
|
18
|
+
/** @internal — devuelve copia del stack (para capturar en closures de efectos). */
|
|
19
|
+
export declare function _captureContextSnapshot(): Map<unknown, unknown>[];
|
|
20
|
+
/** @internal — push de un contexto vacío para un nuevo componente (render estático). */
|
|
21
|
+
export declare function _pushComponentContext(): void;
|
|
22
|
+
/** @internal — pop del contexto del componente actual (render estático). */
|
|
23
|
+
export declare function _popComponentContext(): void;
|
|
24
|
+
/**
|
|
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()`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function _withComponentContext<T>(parentSnapshot: Map<unknown, unknown>[], fn: () => T): T;
|
|
32
|
+
/**
|
|
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
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
export declare function provide<T>(key: InjectionKey<T> | string | symbol, value: T): void;
|
|
47
|
+
/**
|
|
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
|
+
* }
|
|
60
|
+
*/
|
|
61
|
+
export declare function inject<T>(key: InjectionKey<T> | string | symbol): T | undefined;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { Signal } from "./reactivity";
|
|
2
|
+
/** A validator function. Return an error string, or null/undefined if valid. */
|
|
3
|
+
export type Validator<T> = (value: T) => string | null | undefined;
|
|
4
|
+
export declare function required(message?: string): Validator<unknown>;
|
|
5
|
+
export declare function minLength(n: number, message?: string): Validator<string>;
|
|
6
|
+
export declare function maxLength(n: number, message?: string): Validator<string>;
|
|
7
|
+
export declare function pattern(regex: RegExp, message?: string): Validator<string>;
|
|
8
|
+
export declare function email(message?: string): Validator<string>;
|
|
9
|
+
export declare function min(n: number, message?: string): Validator<number>;
|
|
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
|
+
*/
|
|
36
|
+
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
|
+
*/
|
|
59
|
+
export declare const validators: {
|
|
60
|
+
readonly required: typeof required;
|
|
61
|
+
readonly minLength: typeof minLength;
|
|
62
|
+
readonly maxLength: typeof maxLength;
|
|
63
|
+
readonly email: typeof email;
|
|
64
|
+
readonly pattern: typeof pattern;
|
|
65
|
+
readonly min: typeof min;
|
|
66
|
+
readonly max: typeof max;
|
|
67
|
+
};
|
|
68
|
+
/** Shape of the built-in `validators` namespace. Used as the base type for `extendValidators`. */
|
|
69
|
+
export type ValidatorsBase = typeof validators;
|
|
70
|
+
/**
|
|
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.
|
|
113
|
+
*/
|
|
114
|
+
export declare function extendValidators<E extends Record<string, (...args: any[]) => Validator<any>>>(base: ValidatorsBase, extensions: E): ValidatorsBase & E;
|
|
115
|
+
/** Public state of a single form field. */
|
|
116
|
+
export interface FieldState<T> {
|
|
117
|
+
/** Current value — read/write signal. */
|
|
118
|
+
value: Signal<T>;
|
|
119
|
+
/**
|
|
120
|
+
* Current error message, or null.
|
|
121
|
+
* Only non-null after the field has been touched (blur) or dirtied (input).
|
|
122
|
+
*/
|
|
123
|
+
readonly error: Signal<string | null>;
|
|
124
|
+
/** True after the input has lost focus at least once. */
|
|
125
|
+
touched: Signal<boolean>;
|
|
126
|
+
/** True after the user has typed at least once. */
|
|
127
|
+
dirty: Signal<boolean>;
|
|
128
|
+
/** Attach to `@input` on any `<input>`, `<select>`, `<textarea>`. */
|
|
129
|
+
readonly onInput: (e: Event) => void;
|
|
130
|
+
/** Attach to `@blur`. */
|
|
131
|
+
readonly onBlur: () => void;
|
|
132
|
+
/** Reset to initial value and clear touched/dirty/error state. */
|
|
133
|
+
reset(): void;
|
|
134
|
+
/**
|
|
135
|
+
* @internal — inject an external error message (server / schema validator).
|
|
136
|
+
* The error clears automatically when the user next edits the field.
|
|
137
|
+
*/
|
|
138
|
+
_setExternalError(msg: string | null): void;
|
|
139
|
+
}
|
|
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
|
+
*/
|
|
155
|
+
export declare function useField<T>(initialValue: T, validators?: Validator<T>[]): FieldState<T>;
|
|
156
|
+
/** Map of field-name → error message for external validation results. */
|
|
157
|
+
export type FieldErrors<T extends Record<string, unknown>> = {
|
|
158
|
+
[K in keyof T]?: string | null;
|
|
159
|
+
};
|
|
160
|
+
export interface FormState<T extends Record<string, unknown>> {
|
|
161
|
+
/** Individual field states — access value, error, event handlers. */
|
|
162
|
+
fields: {
|
|
163
|
+
[K in keyof T]: FieldState<T[K]>;
|
|
164
|
+
};
|
|
165
|
+
/** Computed snapshot of all current field values. */
|
|
166
|
+
readonly values: Signal<T>;
|
|
167
|
+
/** Computed map of all currently visible field errors. */
|
|
168
|
+
readonly errors: Signal<FieldErrors<T>>;
|
|
169
|
+
/** True when no field has a visible error (meaningful after submit / touch-all). */
|
|
170
|
+
readonly valid: Signal<boolean>;
|
|
171
|
+
/** True when at least one field has been modified. */
|
|
172
|
+
readonly dirty: Signal<boolean>;
|
|
173
|
+
/**
|
|
174
|
+
* Wraps a submit callback. Returned handler:
|
|
175
|
+
* 1. Calls `e.preventDefault()`
|
|
176
|
+
* 2. Touches all fields (revealing validation errors)
|
|
177
|
+
* 3. Runs `options.validate` if provided (Zod, etc.)
|
|
178
|
+
* 4. Only calls `fn(values)` if all validations pass
|
|
179
|
+
*/
|
|
180
|
+
handleSubmit(fn: (values: T) => void | Promise<void>): (e: Event) => void;
|
|
181
|
+
/** Reset all fields to their initial values. */
|
|
182
|
+
reset(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Inject external errors (e.g., from a server response) into specific fields.
|
|
185
|
+
* Each field's error clears automatically the next time the user edits it.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* form.setErrors({ email: "Email already in use" });
|
|
189
|
+
*/
|
|
190
|
+
setErrors(errors: FieldErrors<T>): void;
|
|
191
|
+
}
|
|
192
|
+
export interface FormOptions<T extends Record<string, unknown>> {
|
|
193
|
+
/** Per-field built-in validators. */
|
|
194
|
+
validators?: {
|
|
195
|
+
[K in keyof T]?: Validator<T[K]>[];
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Optional schema-level validator — runs on submit after built-in validators pass.
|
|
199
|
+
* Return `null` / `undefined` if valid, or a field→error map if not.
|
|
200
|
+
* String arrays are accepted (first element shown per field).
|
|
201
|
+
*
|
|
202
|
+
* @example Zod interop
|
|
203
|
+
* ```typescript
|
|
204
|
+
* validate(values) {
|
|
205
|
+
* const r = schema.safeParse(values);
|
|
206
|
+
* if (r.success) return null;
|
|
207
|
+
* return Object.fromEntries(
|
|
208
|
+
* Object.entries(r.error.flatten().fieldErrors)
|
|
209
|
+
* .map(([k, v]) => [k, v?.[0]])
|
|
210
|
+
* );
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*
|
|
214
|
+
* @example Valibot interop
|
|
215
|
+
* ```typescript
|
|
216
|
+
* validate(values) {
|
|
217
|
+
* const r = safeParse(schema, values);
|
|
218
|
+
* if (r.success) return null;
|
|
219
|
+
* const errs: Record<string, string> = {};
|
|
220
|
+
* for (const issue of r.issues)
|
|
221
|
+
* if (issue.path?.[0]?.key) errs[issue.path[0].key as string] = issue.message;
|
|
222
|
+
* return errs;
|
|
223
|
+
* }
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
validate?: (values: T) => {
|
|
227
|
+
[K in keyof T]?: string | string[] | null | undefined;
|
|
228
|
+
} | null | undefined;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 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
|
+
* `
|
|
257
|
+
*/
|
|
258
|
+
export declare function createForm<T extends Record<string, unknown>>(initialValues: T, options?: FormOptions<T>): FormState<T>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { Signal, signal, effect, computed, batch, watch, untrack, nextTick } from "./reactivity";
|
|
2
|
+
export type { WatchOptions } from "./reactivity";
|
|
3
|
+
export { html, repeat, ref, showWhen, portal, createPortalOutlet, portalOutlet, provideOutlet, injectOutlet, createErrorBoundary, transition } from "./template";
|
|
4
|
+
export type { NixTemplate, NixMountHandle, KeyedList, NixRef, PortalOutlet, ErrorFallback, TransitionOptions, TransitionContent } from "./template";
|
|
5
|
+
export { mount } from "./component";
|
|
6
|
+
export { NixComponent } from "./lifecycle";
|
|
7
|
+
export type { NixChildren } from "./lifecycle";
|
|
8
|
+
export { createStore } from "./store";
|
|
9
|
+
export type { Store, StoreSignals } from "./store";
|
|
10
|
+
export { createRouter, RouterView, Link, useRouter } from "./router";
|
|
11
|
+
export type { Router, RouteRecord, NavigationGuard, NavigationGuardResult } from "./router";
|
|
12
|
+
export { suspend, lazy } from "./async";
|
|
13
|
+
export type { SuspenseOptions } from "./async";
|
|
14
|
+
export { provide, inject, createInjectionKey } from "./context";
|
|
15
|
+
export type { InjectionKey } from "./context";
|
|
16
|
+
export { useField, createForm, required, minLength, maxLength, email, pattern, min, max, createValidator, validators, extendValidators, } from "./form";
|
|
17
|
+
export type { Validator, FieldState, FieldErrors, FormState, FormOptions, ValidatorsBase } from "./form";
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { NixTemplate } from "./template";
|
|
2
|
+
/**
|
|
3
|
+
* Tipos válidos para pasar como children a un componente.
|
|
4
|
+
* Acepta un template, un componente, arrays de ellos, o nada.
|
|
5
|
+
*/
|
|
6
|
+
export type NixChildren = NixTemplate | NixComponent | Array<NixTemplate | NixComponent> | null | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Clase base para componentes con lifecycle.
|
|
9
|
+
*
|
|
10
|
+
* Implementa `render()` y haz override de los hooks que necesites.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* class Timer extends NixComponent {
|
|
14
|
+
* ticks = signal(0);
|
|
15
|
+
*
|
|
16
|
+
* onMount() {
|
|
17
|
+
* const id = setInterval(() => this.ticks.update(n => n + 1), 1000);
|
|
18
|
+
* return () => clearInterval(id); // cleanup automático al desmontar
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* render() {
|
|
22
|
+
* return html`<span>${() => this.ticks.value}s</span>`;
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* mount(new Timer(), "#app");
|
|
27
|
+
*/
|
|
28
|
+
export declare abstract class NixComponent {
|
|
29
|
+
/** @internal – marca que identifica instancias NixComponent en el engine. */
|
|
30
|
+
readonly __isNixComponent: true;
|
|
31
|
+
/**
|
|
32
|
+
* Slot por defecto — contenido hijo que el componente padre inyecta.
|
|
33
|
+
*
|
|
34
|
+
* Úsalo en `render()` como cualquier valor interpolado:
|
|
35
|
+
* ```typescript
|
|
36
|
+
* render() {
|
|
37
|
+
* return html`<div class="card">${this.children}</div>`;
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
* Se puede asignar directamente o con el método fluido `setChildren()`.
|
|
41
|
+
*/
|
|
42
|
+
children?: NixChildren;
|
|
43
|
+
/** @internal */
|
|
44
|
+
private _slots;
|
|
45
|
+
/**
|
|
46
|
+
* Asigna el slot por defecto. Versión fluida de `this.children = content`.
|
|
47
|
+
* @returns `this` para encadenar con `setSlot()`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* html`${new Card().setChildren(html`<p>Body</p>`)}`
|
|
51
|
+
*/
|
|
52
|
+
setChildren(content: NixChildren): this;
|
|
53
|
+
/**
|
|
54
|
+
* Asigna un slot con nombre. El componente lo lee con `this.slot(name)`.
|
|
55
|
+
* @returns `this` para encadenar.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* new Card()
|
|
59
|
+
* .setSlot("header", html`<h1>Título</h1>`)
|
|
60
|
+
* .setSlot("footer", html`<small>Footer</small>`)
|
|
61
|
+
* .setChildren(html`<p>Cuerpo</p>`)
|
|
62
|
+
*/
|
|
63
|
+
setSlot(name: string, content: NixChildren): this;
|
|
64
|
+
/**
|
|
65
|
+
* Obtiene el contenido de un slot con nombre.
|
|
66
|
+
* Úsalo dentro de `render()`:
|
|
67
|
+
* ```typescript
|
|
68
|
+
* render() {
|
|
69
|
+
* return html`
|
|
70
|
+
* <div>
|
|
71
|
+
* <header>${this.slot("header")}</header>
|
|
72
|
+
* <main>${this.children}</main>
|
|
73
|
+
* <footer>${this.slot("footer")}</footer>
|
|
74
|
+
* </div>
|
|
75
|
+
* `;
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
slot(name: string): NixChildren;
|
|
80
|
+
/**
|
|
81
|
+
* Debe implementarse: retorna el template del componente.
|
|
82
|
+
* Se llama UNA sola vez al montar — las actualizaciones ocurren por signals.
|
|
83
|
+
*/
|
|
84
|
+
abstract render(): NixTemplate;
|
|
85
|
+
/**
|
|
86
|
+
* Llamado ANTES de `render()` — sin DOM todavía.
|
|
87
|
+
* Útil para inicializar estado complejo derivado de props u otras
|
|
88
|
+
* operaciones síncronas que render() necesita.
|
|
89
|
+
*
|
|
90
|
+
* Los errores aquí son capturados por `onError` si está implementado.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* onInit() {
|
|
94
|
+
* this.derived = computed(() => this.base.value * 2);
|
|
95
|
+
* }
|
|
96
|
+
*/
|
|
97
|
+
onInit?(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Llamado DESPUÉS de que el componente se inserta en el DOM.
|
|
100
|
+
* Si retorna una función, se usa como cleanup automático al desmontar.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* onMount() {
|
|
104
|
+
* const id = setInterval(() => this.count.update(n => n + 1), 1000);
|
|
105
|
+
* return () => clearInterval(id);
|
|
106
|
+
* }
|
|
107
|
+
*/
|
|
108
|
+
onMount?(): (() => void) | void;
|
|
109
|
+
/**
|
|
110
|
+
* Llamado ANTES de remover el componente del DOM.
|
|
111
|
+
* Se ejecuta siempre al desmontar, incluso si no se definió onMount.
|
|
112
|
+
*/
|
|
113
|
+
onUnmount?(): void;
|
|
114
|
+
/**
|
|
115
|
+
* Captura errores lanzados dentro de `onMount`.
|
|
116
|
+
* Si se implementa, el error queda absorbido y el componente permanece montado.
|
|
117
|
+
* Si no se implementa, el error se re-lanza.
|
|
118
|
+
*/
|
|
119
|
+
onError?(err: unknown): void;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* @internal – Verifica si un valor es una instancia de NixComponent.
|
|
123
|
+
*/
|
|
124
|
+
export declare function isNixComponent(v: unknown): v is NixComponent;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal — Register an error boundary handler. All `effect()` calls made
|
|
3
|
+
* synchronously while this handler is active will capture it. When those
|
|
4
|
+
* effects re-run and throw, the captured handler is invoked.
|
|
5
|
+
*/
|
|
6
|
+
export declare function _pushErrorHandler(h: (err: unknown) => void): void;
|
|
7
|
+
/** @internal — Restore the previous error boundary handler. */
|
|
8
|
+
export declare function _popErrorHandler(): void;
|
|
9
|
+
export declare class Signal<T> {
|
|
10
|
+
private _value;
|
|
11
|
+
private _subs;
|
|
12
|
+
constructor(initialValue: T);
|
|
13
|
+
/**
|
|
14
|
+
* Leer el valor.
|
|
15
|
+
* Si hay un effect activo, se suscribe automáticamente.
|
|
16
|
+
*/
|
|
17
|
+
get value(): T;
|
|
18
|
+
/**
|
|
19
|
+
* Escribir un nuevo valor.
|
|
20
|
+
* Si es diferente al actual, notifica a todos los effects suscritos.
|
|
21
|
+
*/
|
|
22
|
+
set value(newValue: T);
|
|
23
|
+
/**
|
|
24
|
+
* Modificar el valor con una función.
|
|
25
|
+
* count.update(n => n + 1)
|
|
26
|
+
*/
|
|
27
|
+
update(fn: (current: T) => T): void;
|
|
28
|
+
/**
|
|
29
|
+
* Leer SIN suscribirse.
|
|
30
|
+
* Útil cuando necesitas el valor pero no quieres
|
|
31
|
+
* que el effect se re-ejecute si cambia.
|
|
32
|
+
*/
|
|
33
|
+
peek(): T;
|
|
34
|
+
/** @internal */
|
|
35
|
+
_removeSub(sub: () => void): void;
|
|
36
|
+
private _notify;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
}
|
|
39
|
+
export declare function signal<T>(initialValue: T): Signal<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Ejecuta una función y la RE-EJECUTA cada vez que
|
|
42
|
+
* algún signal leído dentro de ella cambie.
|
|
43
|
+
*
|
|
44
|
+
* Retorna una función dispose() para destruir el effect.
|
|
45
|
+
*
|
|
46
|
+
* const dispose = effect(() => {
|
|
47
|
+
* console.log(count.value);
|
|
48
|
+
* return () => console.log("cleanup");
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* dispose();
|
|
52
|
+
*/
|
|
53
|
+
export declare function effect(fn: () => void | (() => void)): () => void;
|
|
54
|
+
/**
|
|
55
|
+
* Valor derivado que se recalcula automáticamente.
|
|
56
|
+
*
|
|
57
|
+
* const doubled = computed(() => count.value * 2);
|
|
58
|
+
*/
|
|
59
|
+
export declare function computed<T>(fn: () => T): Signal<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Agrupa múltiples cambios para que los effects
|
|
62
|
+
* se ejecuten UNA sola vez al final.
|
|
63
|
+
*
|
|
64
|
+
* batch(() => {
|
|
65
|
+
* x.value = 1;
|
|
66
|
+
* y.value = 2;
|
|
67
|
+
* });
|
|
68
|
+
*/
|
|
69
|
+
export declare function batch(fn: () => void): void;
|
|
70
|
+
/**
|
|
71
|
+
* Ejecuta `fn` sin suscribirse a ninguna signal que lea dentro de ella.
|
|
72
|
+
* Útil para leer valores reactivos sin que esas lecturas re-disparen el
|
|
73
|
+
* efecto actual (p.ej. en callbacks de `watch`).
|
|
74
|
+
*
|
|
75
|
+
* const total = untrack(() => price.value * qty.value); // no se suscribe
|
|
76
|
+
*/
|
|
77
|
+
export declare function untrack<T>(fn: () => T): T;
|
|
78
|
+
export interface WatchOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Si es `true`, el callback se ejecuta inmediatamente con el valor
|
|
81
|
+
* actual antes de que haya ningún cambio. Por defecto: `false`.
|
|
82
|
+
*/
|
|
83
|
+
immediate?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Si es `true`, el watcher se elimina automáticamente después de la
|
|
86
|
+
* primera vez que el callback se dispara. Por defecto: `false`.
|
|
87
|
+
*/
|
|
88
|
+
once?: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Observa una fuente reactiva y ejecuta `callback(newValue, oldValue)` cada
|
|
92
|
+
* vez que cambia.
|
|
93
|
+
*
|
|
94
|
+
* La fuente puede ser:
|
|
95
|
+
* - Un getter: `() => count.value + other.value`
|
|
96
|
+
* - Una Signal directamente: `count`
|
|
97
|
+
*
|
|
98
|
+
* Retorna una función `dispose()` para detener la observación.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* const stop = watch(
|
|
102
|
+
* () => user.value.name,
|
|
103
|
+
* (newName, oldName) => console.log(newName, oldName),
|
|
104
|
+
* { immediate: true }
|
|
105
|
+
* );
|
|
106
|
+
* stop(); // deja de observar
|
|
107
|
+
*/
|
|
108
|
+
export declare function watch<T>(source: Signal<T> | (() => T), callback: (newValue: T, oldValue: T | undefined) => void, options?: WatchOptions): () => void;
|
|
109
|
+
/**
|
|
110
|
+
* Espera a que todos los efectos síncronos pendientes hayan corrido y el
|
|
111
|
+
* DOM esté actualizado, devolviendo una promesa que resuelve en el próximo
|
|
112
|
+
* microtask.
|
|
113
|
+
*
|
|
114
|
+
* Úsala cuando necesitas leer el DOM *después* de un cambio reactivo:
|
|
115
|
+
*
|
|
116
|
+
* count.value++;
|
|
117
|
+
* await nextTick();
|
|
118
|
+
* console.log(el.textContent); // ya refleja el nuevo valor
|
|
119
|
+
*
|
|
120
|
+
* También acepta un callback opcional:
|
|
121
|
+
*
|
|
122
|
+
* nextTick(() => el.focus());
|
|
123
|
+
*/
|
|
124
|
+
export declare function nextTick(fn?: () => void): Promise<void>;
|