@deijose/nix-js 1.0.4 → 1.0.6

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.
@@ -0,0 +1,253 @@
1
+ import type { Signal } from "./reactivity";
2
+ import { NixComponent } from "./lifecycle";
3
+ import type { NixTemplate } from "./template";
4
+ /**
5
+ * Value returned (or resolved) by a navigation guard.
6
+ * - `false` — cancel the navigation.
7
+ * - `string` — redirect to that path.
8
+ * - `void` / `undefined` — allow the navigation.
9
+ */
10
+ export type NavigationGuardResult = void | undefined | false | string;
11
+ /**
12
+ * A navigation guard function.
13
+ *
14
+ * @param to Destination pathname (e.g. `"/admin"`).
15
+ * @param from Current pathname before the navigation.
16
+ * @returns `false` to cancel, a path string to redirect, or nothing to allow.
17
+ * May return a Promise for async guard logic.
18
+ *
19
+ * @example
20
+ * router.beforeEach((to, from) => {
21
+ * if (!auth.isLoggedIn && to !== "/login") return "/login";
22
+ * });
23
+ */
24
+ export type NavigationGuard = (to: string, from: string) => NavigationGuardResult | Promise<NavigationGuardResult>;
25
+ export interface RouteRecord {
26
+ /**
27
+ * Segmento de ruta. Soporta:
28
+ * - Literal: "/about", "/users"
29
+ * - Parámetro: "/users/:id", "/posts/:slug/comments/:cid"
30
+ * - Wildcard: "*" (fallback global o de prefijo con children)
31
+ *
32
+ * Los paths de children se concatenan con el del padre.
33
+ *
34
+ * @example
35
+ * { path: "/users/:id", component: UserDetail }
36
+ * // Navegar a "/users/42" → params.value = { id: "42" }
37
+ *
38
+ * @example
39
+ * { path: "/dash", component: DashLayout, children: [
40
+ * { path: "/users", component: UsersPage },
41
+ * ]}
42
+ * // Genera las rutas planas: /dash, /dash/users
43
+ */
44
+ path: string;
45
+ /** Factory que devuelve la vista a renderizar en este nivel */
46
+ component: () => NixTemplate | NixComponent;
47
+ /**
48
+ * Rutas hijas. Sus paths se unen con el del padre.
49
+ * El componente padre debe incluir `new RouterView(1)` para renderizarlas.
50
+ */
51
+ children?: RouteRecord[];
52
+ /**
53
+ * Guard de nivel de ruta. Se ejecuta solo al entrar en esta ruta concreta.
54
+ * Misma semántica de retorno que `beforeEach`.
55
+ *
56
+ * @example
57
+ * { path: "/admin", component: () => new AdminPage(),
58
+ * beforeEnter: (to, from) => {
59
+ * if (!isAdmin) return "/";
60
+ * }}
61
+ */
62
+ beforeEnter?: NavigationGuard;
63
+ }
64
+ /**
65
+ * Callback for `afterEach` hooks — receives the committed `to` and `from` paths.
66
+ */
67
+ export type AfterEachHook = (to: string, from: string) => void;
68
+ /**
69
+ * Result of `router.resolve(path)` — inspect what would match without navigating.
70
+ */
71
+ export interface ResolvedRoute {
72
+ /** Whether the path matched any registered route. */
73
+ matched: boolean;
74
+ /** Extracted route params (empty object if no match). */
75
+ params: Record<string, string>;
76
+ /** The matched route record, or `undefined` if no match. */
77
+ route: RouteRecord | undefined;
78
+ }
79
+ export interface Router {
80
+ /** Señal con la ruta activa actual (pathname, p.ej. "/users/42") */
81
+ readonly current: Signal<string>;
82
+ /**
83
+ * Señal con los parámetros dinámicos de la ruta activa (:id, :slug…).
84
+ * Se actualiza síncronamente con cada `navigate()`.
85
+ *
86
+ * @example
87
+ * // Ruta: "/users/:id" → navigate("/users/42")
88
+ * router.params.value // { id: "42" }
89
+ */
90
+ readonly params: Signal<Record<string, string>>;
91
+ /**
92
+ * Señal con los query params de la URL (?clave=valor…).
93
+ * Se actualiza síncronamente con cada `navigate()`.
94
+ *
95
+ * @example
96
+ * router.navigate("/users?page=2&sort=name")
97
+ * router.query.value // { page: "2", sort: "name" }
98
+ *
99
+ * @example
100
+ * router.navigate("/users", { page: 2, sort: "name" })
101
+ * router.query.value // { page: "2", sort: "name" }
102
+ */
103
+ readonly query: Signal<Record<string, string>>;
104
+ /**
105
+ * Navegar a una ruta nueva (pushState + actualiza señales).
106
+ * Si hay guards registrados, la navegación espera a que todos pasen.
107
+ *
108
+ * @param path Ruta destino. Puede incluir query string: "/users?page=2"
109
+ * @param query Query params como objeto. Se mezclan con los del path.
110
+ * Un valor `null`/`undefined` elimina el parámetro.
111
+ */
112
+ navigate(path: string, query?: Record<string, string | number | boolean | null | undefined>): void;
113
+ /**
114
+ * Navigate without adding an entry to the browser history.
115
+ * Uses `history.replaceState` instead of `pushState`.
116
+ * Guards still run normally.
117
+ *
118
+ * @param path Destination path. May include query string.
119
+ * @param query Query params as an object.
120
+ *
121
+ * @example
122
+ * // After login — pressing "back" won't return to /login
123
+ * router.replace("/home");
124
+ */
125
+ replace(path: string, query?: Record<string, string | number | boolean | null | undefined>): void;
126
+ /**
127
+ * Go back one entry in the browser history.
128
+ * Equivalent to `history.back()`.
129
+ *
130
+ * @example
131
+ * router.back();
132
+ */
133
+ back(): void;
134
+ /**
135
+ * Go forward one entry in the browser history.
136
+ * Equivalent to `history.forward()`.
137
+ *
138
+ * @example
139
+ * router.forward();
140
+ */
141
+ forward(): void;
142
+ /**
143
+ * Move `delta` entries in the browser history.
144
+ * Negative values go back, positive go forward.
145
+ *
146
+ * @example
147
+ * router.go(-2); // two pages back
148
+ * router.go(1); // same as forward()
149
+ */
150
+ go(delta: number): void;
151
+ /**
152
+ * Check if a path is currently active.
153
+ * By default performs an exact match.
154
+ *
155
+ * @param path The path to test.
156
+ * @param exact If `false`, matches when `current` starts with `path`.
157
+ * Default: `true`.
158
+ *
159
+ * @example
160
+ * router.isActive("/admin"); // exact match
161
+ * router.isActive("/admin", false); // prefix: /admin/users → true
162
+ */
163
+ isActive(path: string, exact?: boolean): boolean;
164
+ /**
165
+ * Inspect what route would match a given path without actually navigating.
166
+ *
167
+ * @example
168
+ * const info = router.resolve("/user/42");
169
+ * // { matched: true, params: { id: "42" }, route: { path: "/user/:id", ... } }
170
+ */
171
+ resolve(path: string): ResolvedRoute;
172
+ /** Árbol de rutas original (tal como se pasó a createRouter) */
173
+ readonly routes: RouteRecord[];
174
+ /**
175
+ * Registra un guard de navegación global.
176
+ * Se ejecuta (en orden de registro) antes de cada navegación.
177
+ *
178
+ * Retorna una función para eliminar el guard.
179
+ *
180
+ * @example
181
+ * const stop = router.beforeEach((to, from) => {
182
+ * if (!auth && to !== "/login") return "/login";
183
+ * });
184
+ * stop(); // elimina el guard
185
+ */
186
+ beforeEach(guard: NavigationGuard): () => void;
187
+ /**
188
+ * Register a hook that runs after every successful navigation.
189
+ * Useful for analytics, scroll reset, etc.
190
+ *
191
+ * Returns a function to remove the hook.
192
+ *
193
+ * @example
194
+ * const stop = router.afterEach((to, from) => {
195
+ * window.scrollTo(0, 0);
196
+ * });
197
+ * stop();
198
+ */
199
+ afterEach(hook: AfterEachHook): () => void;
200
+ }
201
+ /**
202
+ * Crea el router History API y lo establece como singleton activo del módulo.
203
+ * Usa `history.pushState` — URLs limpias sin `#`.
204
+ * `RouterView` y `Link` lo consumen automáticamente — no necesitan que se los pases.
205
+ *
206
+ * @note En producción el servidor debe responder con `index.html` para cualquier
207
+ * ruta no-archivo. Vite dev y `vite preview` lo hacen automáticamente.
208
+ */
209
+ export declare function createRouter(routes: RouteRecord[]): Router;
210
+ /**
211
+ * Devuelve el router activo (singleton).
212
+ * Útil dentro de componentes para acceder a `params` y `current` sin prop-drilling.
213
+ *
214
+ * @example
215
+ * class UserDetail extends NixComponent {
216
+ * render() {
217
+ * return html`<div>User: ${() => useRouter().params.value.id}</div>`;
218
+ * }
219
+ * }
220
+ */
221
+ export declare function useRouter(): Router;
222
+ /**
223
+ * @internal — Resets the router singleton. Used by tests to avoid
224
+ * "A router already exists" warnings between test cases.
225
+ */
226
+ export declare function _resetRouter(): void;
227
+ /**
228
+ * Renderiza el componente de la ruta activa en el nivel `depth`.
229
+ *
230
+ * - `new RouterView()` → nivel raíz (depth 0).
231
+ * - `new RouterView(1)` → primer nivel de rutas anidadas. Úsalo dentro del
232
+ * componente padre para que renderice el hijo correspondiente.
233
+ *
234
+ * Consume el router singleton — no requiere que se le pase el router.
235
+ */
236
+ export declare class RouterView extends NixComponent {
237
+ private _depth;
238
+ constructor(depth?: number);
239
+ render(): NixTemplate;
240
+ }
241
+ /**
242
+ * Enlace de navegación reactivo que se estiliza como activo/inactivo según la
243
+ * ruta actual. Consume el router singleton — no requiere que se le pase.
244
+ *
245
+ * @example
246
+ * new Link("/about", "About")
247
+ */
248
+ export declare class Link extends NixComponent {
249
+ private _to;
250
+ private _label;
251
+ constructor(to: string, label: string);
252
+ render(): NixTemplate;
253
+ }
@@ -0,0 +1,40 @@
1
+ import { Signal } from "./reactivity";
2
+ /** Transforma cada propiedad del estado inicial en su Signal correspondiente. */
3
+ export type StoreSignals<T extends Record<string, unknown>> = {
4
+ readonly [K in keyof T]: Signal<T[K]>;
5
+ };
6
+ /** Tipo del store tal como lo ve el usuario. */
7
+ export type Store<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>> = StoreSignals<T> & A & {
8
+ /**
9
+ * Restaura todos los signals a sus valores iniciales.
10
+ * Equivalente a hacer `signal.value = initialValue` en cada propiedad.
11
+ */
12
+ $reset(): void;
13
+ };
14
+ /**
15
+ * Crea un store reactivo global.
16
+ *
17
+ * @param initialState Objeto plano con los valores iniciales.
18
+ * @param actionsFactory Función que recibe los signals y retorna acciones.
19
+ *
20
+ * @example
21
+ * // Sin acciones
22
+ * const theme = createStore({ dark: false, fontSize: 16 });
23
+ * theme.dark.value = true;
24
+ *
25
+ * @example
26
+ * // Con acciones
27
+ * const cart = createStore(
28
+ * { items: [] as string[], total: 0 },
29
+ * (s) => ({
30
+ * add: (item: string) => s.items.update(arr => [...arr, item]),
31
+ * clear: () => cart.$reset(),
32
+ * })
33
+ * );
34
+ *
35
+ * cart.add("Manzana");
36
+ * cart.items.value; // ["Manzana"]
37
+ * cart.clear();
38
+ * cart.items.value; // []
39
+ */
40
+ export declare function createStore<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>>(initialState: T, actionsFactory?: (signals: StoreSignals<T>) => A): Store<T, A>;
@@ -0,0 +1,380 @@
1
+ import type { NixComponent } from "./lifecycle";
2
+ export interface NixTemplate {
3
+ readonly __isNixTemplate: true;
4
+ /** Monta el template en un contenedor (uso externo / raíz). */
5
+ mount(container: Element | string): NixMountHandle;
6
+ /**
7
+ * @internal — Renderiza el template antes del nodo `before` (o al final
8
+ * de `parent` si `before` es null). Retorna una función de limpieza.
9
+ */
10
+ _render(parent: Node, before: Node | null): () => void;
11
+ }
12
+ export interface NixMountHandle {
13
+ unmount(): void;
14
+ }
15
+ /**
16
+ * Contenedor para una referencia directa a un elemento DOM.
17
+ * Se asigna automáticamente cuando el template se monta y se limpia al
18
+ * desmontarse.
19
+ *
20
+ * @example
21
+ * const inputRef = ref<HTMLInputElement>();
22
+ * html`<input ref=${inputRef} />`
23
+ * // después del mount:
24
+ * inputRef.el?.focus();
25
+ */
26
+ export interface NixRef<T extends Element = Element> {
27
+ el: T | null;
28
+ }
29
+ /**
30
+ * Crea un objeto `NixRef` vacío.
31
+ * Pásalo como valor del atributo especial `ref` en un template para que
32
+ * Nix.js rellene automáticamente `ref.el` con el elemento real del DOM.
33
+ */
34
+ export declare function ref<T extends Element = Element>(): NixRef<T>;
35
+ /**
36
+ * Toggles the visibility of an element **without unmounting it** from the DOM
37
+ * (sets `style.display = "none"` when hidden, restores it when visible).
38
+ *
39
+ * Use the `show` or `hide` attribute bindings inside templates — or call
40
+ * this helper directly for imperative use outside of templates.
41
+ *
42
+ * ### Template usage
43
+ * ```html
44
+ * <!-- show: element is visible when condition is truthy -->
45
+ * <div show=${() => isVisible.value}>...</div>
46
+ *
47
+ * <!-- hide: element is hidden when condition is truthy (inverse of show) -->
48
+ * <div hide=${() => isLoading.value}>Submit</div>
49
+ * ```
50
+ *
51
+ * ### Difference from conditional rendering
52
+ * | | `show` / `hide` | conditional (`() => condition ? html\`…\` : null`) |
53
+ * |---|---|---|
54
+ * | DOM node kept | ✅ always | ❌ destroyed when hidden |
55
+ * | Lifecycle hooks | not called on toggle | called on every toggle |
56
+ * | Use when | hiding/showing frequently | rarely shown alternatives |
57
+ *
58
+ * ### Imperative usage (outside a template)
59
+ * ```typescript
60
+ * import { showWhen } from "@deijose/nix-js";
61
+ * import { effect } from "@deijose/nix-js";
62
+ *
63
+ * const el = document.getElementById("my-panel")!;
64
+ * // Reactively controlled:
65
+ * effect(() => showWhen(el, isVisible.value));
66
+ * ```
67
+ */
68
+ export declare function showWhen(el: HTMLElement, condition: boolean): void;
69
+ /**
70
+ * Resultado de `repeat()` — lista con keys para diffing eficiente.
71
+ * El template engine lo reconoce y solo añade/mueve/elimina los nodos
72
+ * que realmente cambiaron, preservando el DOM de los items estables.
73
+ */
74
+ export interface KeyedList<T = unknown> {
75
+ readonly __isKeyedList: true;
76
+ readonly items: T[];
77
+ readonly keyFn: (item: T, index: number) => string | number;
78
+ readonly renderFn: (item: T, index: number) => NixTemplate | NixComponent;
79
+ }
80
+ /**
81
+ * Crea una lista con keys para diffing eficiente.
82
+ * Úsalo en lugar de `.map()` cuando la lista cambia frecuentemente.
83
+ *
84
+ * @param items Array reactivo de datos
85
+ * @param keyFn Devuelve una clave única por item (p.ej. `item => item.id`)
86
+ * @param renderFn Devuelve el template/componente para cada item
87
+ *
88
+ * @example
89
+ * ${() => repeat(
90
+ * users.value,
91
+ * u => u.id,
92
+ * u => html`<li>${u.name}</li>`
93
+ * )}
94
+ */
95
+ export declare function repeat<T>(items: T[], keyFn: (item: T, index: number) => string | number, renderFn: (item: T, index: number) => NixTemplate | NixComponent): KeyedList<T>;
96
+ /**
97
+ * Renders `content` into `target` instead of the current position in the tree.
98
+ * The portal is cleaned up automatically when the parent template is unmounted.
99
+ *
100
+ * Use this to render modals, tooltips, notifications, or dropdowns outside of
101
+ * your component tree — typically into `document.body` — so they are not clipped
102
+ * by `overflow: hidden` or buried under other stacking contexts.
103
+ *
104
+ * The portal returns a `NixTemplate`, so it works as a node value anywhere in
105
+ * a template, including inside reactive conditionals: the portal is
106
+ * mounted/unmounted together with whatever controls its condition.
107
+ *
108
+ * @param content Template or component to render inside the portal.
109
+ * @param target CSS selector or `Element` to render into. Defaults to `document.body`.
110
+ *
111
+ * @example Reactive modal
112
+ * ```typescript
113
+ * import { signal, portal, html } from "@deijose/nix-js";
114
+ *
115
+ * const isOpen = signal(false);
116
+ *
117
+ * html`
118
+ * <button @click=${() => { isOpen.value = true; }}>Open</button>
119
+ *
120
+ * ${() => isOpen.value
121
+ * ? portal(html`
122
+ * <div class="overlay" @click=${() => { isOpen.value = false; }}>
123
+ * <div class="modal" @click.stop=${() => {}}>
124
+ * <h2>Hello from a portal!</h2>
125
+ * <button @click=${() => { isOpen.value = false; }}>Close</button>
126
+ * </div>
127
+ * </div>
128
+ * `)
129
+ * : null
130
+ * }
131
+ * `
132
+ * ```
133
+ *
134
+ * @example Custom target
135
+ * ```typescript
136
+ * portal(html`<div class="toast">Saved!</div>`, "#toast-root")
137
+ * portal(html`<Tooltip />`, document.getElementById("tooltip-layer")!)
138
+ * ```
139
+ */
140
+ /**
141
+ * Opaque token created by `createPortalOutlet()`.
142
+ *
143
+ * Pass it to `portalOutlet()` to declare the DOM anchor where portals targeting
144
+ * this outlet will render, and to `portal(content, outlet)` as the target.
145
+ *
146
+ * @see createPortalOutlet
147
+ * @see portalOutlet
148
+ */
149
+ export interface PortalOutlet {
150
+ readonly __isPortalOutlet: true;
151
+ /** @internal — resolved DOM container; set when `portalOutlet()` is mounted */
152
+ _container: Element | null;
153
+ }
154
+ /**
155
+ * Creates a `PortalOutlet` token — a lightweight, typed anchor point that
156
+ * decouples *where* a portal renders from direct DOM access.
157
+ * No CSS selectors, no `document.querySelector`, no manual element references.
158
+ *
159
+ * ### Workflow
160
+ * 1. Create the token at module or component scope.
161
+ * 2. Place `${portalOutlet(outlet)}` in your layout template to declare the anchor.
162
+ * 3. From any child: `portal(content, outlet)` renders into that anchor.
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * const modalOutlet = createPortalOutlet();
167
+ *
168
+ * // Layout:
169
+ * html`
170
+ * <main>${mainContent}</main>
171
+ * ${portalOutlet(modalOutlet)}
172
+ * `
173
+ *
174
+ * // Child (any depth):
175
+ * html`${() => show.value ? portal(html\`<Modal />\`, modalOutlet) : null}`
176
+ * ```
177
+ */
178
+ export declare function createPortalOutlet(): PortalOutlet;
179
+ /**
180
+ * Declares the DOM anchor for a `PortalOutlet` inside a template.
181
+ * Creates a `<div data-nix-outlet>` at this position; portals targeting
182
+ * `outlet` will render their content as children of that div.
183
+ *
184
+ * The anchor's lifecycle follows its parent template — when the parent
185
+ * unmounts, the outlet div and any portals inside it are cleaned up.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * mount(html`
190
+ * <div class="app">
191
+ * <main>${mainContent}</main>
192
+ * ${portalOutlet(modalOutlet)}
193
+ * </div>
194
+ * `, document.body);
195
+ * ```
196
+ */
197
+ export declare function portalOutlet(outlet: PortalOutlet): NixTemplate;
198
+ export declare function portal(content: NixTemplate | NixComponent, target?: Element | string | PortalOutlet | NixRef<Element>): NixTemplate;
199
+ /**
200
+ * Provides a `PortalOutlet` to descendant components via the inject system.
201
+ * Must be called inside `onInit()` of a `NixComponent`.
202
+ *
203
+ * Eliminates prop drilling: any descendant can call `injectOutlet()` to
204
+ * obtain the outlet without it being passed through every layer.
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * class AppLayout extends NixComponent {
209
+ * private outlet = createPortalOutlet();
210
+ * onInit() { provideOutlet(this.outlet); }
211
+ * render() {
212
+ * return html`
213
+ * <main>...</main>
214
+ * ${portalOutlet(this.outlet)}
215
+ * `;
216
+ * }
217
+ * }
218
+ * ```
219
+ */
220
+ export declare function provideOutlet(outlet: PortalOutlet): void;
221
+ /**
222
+ * Injects the nearest `PortalOutlet` provided by an ancestor component.
223
+ * Returns `undefined` if no ancestor has called `provideOutlet()`.
224
+ *
225
+ * Use `portal(content, injectOutlet())` to render into the ancestor's outlet
226
+ * with no CSS selectors, no `document.querySelector`, and no prop drilling.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * class ToastButton extends NixComponent {
231
+ * private outlet: PortalOutlet | undefined;
232
+ * private active = signal(false);
233
+ * onInit() { this.outlet = injectOutlet(); }
234
+ * render() {
235
+ * return html`
236
+ * <button @click=${() => { this.active.value = true; }}>Notify</button>
237
+ * ${() => this.active.value
238
+ * ? portal(html\`<div class="toast">Done!</div>\`, this.outlet)
239
+ * : null
240
+ * }
241
+ * `;
242
+ * }
243
+ * }
244
+ * ```
245
+ */
246
+ export declare function injectOutlet(): PortalOutlet | undefined;
247
+ /**
248
+ * Fallback value for `createErrorBoundary()`:
249
+ * - A static `NixTemplate` or `NixComponent` — always render this on error.
250
+ * - A function `(err) => NixTemplate | NixComponent` — render based on the error.
251
+ */
252
+ export type ErrorFallback = NixTemplate | NixComponent | ((err: unknown) => NixTemplate | NixComponent);
253
+ /**
254
+ * Wraps `content` in an error boundary. If any error is thrown during the
255
+ * **initial render** or during a **reactive update** inside `content`, the
256
+ * boundary automatically:
257
+ * 1. Tears down the broken subtree (effects, event listeners, DOM).
258
+ * 2. Renders `fallback` in its place — without crashing the rest of the app.
259
+ *
260
+ * Errors caught:
261
+ * - `onInit()` / `render()` throws in any `NixComponent` inside `content`
262
+ * - Throws inside `html\`\`` binding expressions during initial render
263
+ * - Reactive re-renders: effects created inside `content` that throw when
264
+ * a signal changes
265
+ *
266
+ * Not caught (same as React):
267
+ * - Event handler throws (wrap those with your own try/catch)
268
+ * - Async code (Promises, `setTimeout`, etc.)
269
+ * - Errors thrown inside `fallback` itself (propagate to the parent boundary)
270
+ *
271
+ * @example Basic usage
272
+ * ```typescript
273
+ * import { createErrorBoundary, html, signal } from "@deijose/nix-js";
274
+ *
275
+ * mount(
276
+ * createErrorBoundary(
277
+ * new MyWidget(),
278
+ * (err) => html`<div class="error">Widget failed: ${String(err)}</div>`
279
+ * ),
280
+ * "#app"
281
+ * );
282
+ * ```
283
+ *
284
+ * @example Static fallback
285
+ * ```typescript
286
+ * createErrorBoundary(
287
+ * html`${() => riskyValue.value}`,
288
+ * html`<p>Something went wrong.</p>`
289
+ * )
290
+ * ```
291
+ *
292
+ * @example Nested boundaries (inner catches first)
293
+ * ```typescript
294
+ * createErrorBoundary(
295
+ * html`
296
+ * <header>...</header>
297
+ * ${createErrorBoundary(new RiskyWidget(), html`<p>Widget error</p>`)}
298
+ * `,
299
+ * html`<p>App-level error</p>`
300
+ * )
301
+ * ```
302
+ */
303
+ export declare function createErrorBoundary(content: NixTemplate | NixComponent, fallback: ErrorFallback): NixTemplate;
304
+ /**
305
+ * Options for `transition()`. All class-name overrides are optional — by
306
+ * default they are derived from `name` (default `"nix"`).
307
+ *
308
+ * | phase | from class | active class | to class |
309
+ * |--------------|-------------------|---------------------|-----------------|
310
+ * | enter | `{n}-enter-from` | `{n}-enter-active` | `{n}-enter-to` |
311
+ * | leave | `{n}-leave-from` | `{n}-leave-active` | `{n}-leave-to` |
312
+ */
313
+ export interface TransitionOptions {
314
+ /**
315
+ * Prefix for all generated CSS classes. Default `"nix"`.
316
+ * e.g. `name: "fade"` generates `.fade-enter-from`, `.fade-leave-to`, …
317
+ */
318
+ name?: string;
319
+ /** Override the enter-from class individually. */
320
+ enterFrom?: string;
321
+ /** Override the enter-active class individually. */
322
+ enterActive?: string;
323
+ /** Override the enter-to class individually. */
324
+ enterTo?: string;
325
+ /** Override the leave-from class individually. */
326
+ leaveFrom?: string;
327
+ /** Override the leave-active class individually. */
328
+ leaveActive?: string;
329
+ /** Override the leave-to class individually. */
330
+ leaveTo?: string;
331
+ /**
332
+ * When `true` the enter transition also plays on the very first render
333
+ * (similar to Vue's `appear`). Default `false`.
334
+ */
335
+ appear?: boolean;
336
+ /**
337
+ * Fallback duration in **milliseconds** used when no `transition-duration`
338
+ * or `animation-duration` is found on the element via `getComputedStyle`.
339
+ */
340
+ duration?: number;
341
+ /** Called synchronously right before the enter classes are added. */
342
+ onBeforeEnter?: (el: Element) => void;
343
+ /** Called after the enter transition has fully completed. */
344
+ onAfterEnter?: (el: Element) => void;
345
+ /** Called synchronously right before the leave classes are added. */
346
+ onBeforeLeave?: (el: Element) => void;
347
+ /** Called after the leave transition has fully completed and the DOM is removed. */
348
+ onAfterLeave?: (el: Element) => void;
349
+ }
350
+ /** Content that can be wrapped with `transition()`. */
351
+ export type TransitionContent = NixTemplate | NixComponent | (() => NixTemplate | NixComponent | null);
352
+ /**
353
+ * Wraps `content` with CSS class-based enter / leave transitions.
354
+ *
355
+ * **Static content** (NixTemplate / NixComponent): plays the enter transition
356
+ * on mount (only if `appear: true`; otherwise instant), and cleans up
357
+ * immediately on unmount without a leave transition.
358
+ *
359
+ * **Reactive conditional** `() => Template | null`: plays the enter
360
+ * transition when the expression goes from `null` → value, and the leave
361
+ * transition when it goes from value → `null`. An in-progress leave is
362
+ * cancelled and the DOM is removed synchronously when new content enters.
363
+ *
364
+ * @example
365
+ * ```css
366
+ * .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; }
367
+ * .fade-enter-from, .fade-leave-to { opacity: 0; }
368
+ * ```
369
+ * ```typescript
370
+ * const show = signal(true);
371
+ *
372
+ * // Reactive — full enter + leave
373
+ * transition(() => show.value ? html`<p>Hello</p>` : null, { name: "fade" })
374
+ *
375
+ * // Static — only enter (if appear: true)
376
+ * transition(html`<span>Always here</span>`, { name: "slide", appear: true })
377
+ * ```
378
+ */
379
+ export declare function transition(content: TransitionContent, options?: TransitionOptions): NixTemplate;
380
+ export declare function html(strings: TemplateStringsArray, ...values: unknown[]): NixTemplate;