@deijose/nix-js 1.0.6 → 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 +46 -279
- package/dist/lib/nix/async.d.ts +8 -54
- package/dist/lib/nix/context.d.ts +11 -45
- package/dist/lib/nix/form.d.ts +6 -129
- package/dist/lib/nix/lifecycle.d.ts +13 -107
- package/dist/lib/nix/reactivity.d.ts +16 -88
- package/dist/lib/nix/router.d.ts +23 -186
- package/dist/lib/nix/store.d.ts +5 -30
- package/dist/lib/nix/template.d.ts +25 -289
- package/dist/lib/nix-js.cjs +3 -3
- package/dist/lib/nix-js.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,322 +1,89 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Nix.js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@deijose/nix-js)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[]()
|
|
6
|
+
[]()
|
|
7
|
+
[]()
|
|
4
8
|
|
|
5
|
-
|
|
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
|
-
~
|
|
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
|
-
##
|
|
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
|
-
|
|
25
|
-
const count = signal(0);
|
|
26
|
+
// --- Reactive counter ---
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
const router = useRouter();
|
|
44
|
+
return html`<h1>User: ${() => router.params.value.id}</h1>`;
|
|
69
45
|
}
|
|
70
46
|
}
|
|
71
47
|
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Stores
|
|
48
|
+
// --- Router + App shell ---
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
## What's Included
|
|
177
68
|
|
|
178
|
-
|
|
179
|
-
preserving its state, listeners, and child components.
|
|
69
|
+
Everything ships in a single zero-dependency import:
|
|
180
70
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
84
|
+
Full API reference, guides, and examples:
|
|
318
85
|
|
|
319
|
-
**→ [
|
|
86
|
+
**→ [github.com/DeijoseDevelop/nix-js](https://github.com/DeijoseDevelop/nix-js)**
|
|
320
87
|
|
|
321
88
|
## License
|
|
322
89
|
|
package/dist/lib/nix/async.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
24
|
-
*
|
|
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
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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 —
|
|
7
|
+
/** @internal — returns a copy of the stack for capturing in effect closures. */
|
|
19
8
|
export declare function _captureContextSnapshot(): Map<unknown, unknown>[];
|
|
20
|
-
/** @internal —
|
|
9
|
+
/** @internal — pushes an empty context for a new component (static render). */
|
|
21
10
|
export declare function _pushComponentContext(): void;
|
|
22
|
-
/** @internal —
|
|
11
|
+
/** @internal — pops the current component context (static render). */
|
|
23
12
|
export declare function _popComponentContext(): void;
|
|
24
13
|
/**
|
|
25
|
-
* @internal —
|
|
26
|
-
*
|
|
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
|
-
*
|
|
34
|
-
*
|
|
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
|
-
*
|
|
49
|
-
*
|
|
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;
|
package/dist/lib/nix/form.d.ts
CHANGED
|
@@ -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
|
|
72
|
-
* a
|
|
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
|
|
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>;
|