@deijose/nix-js 2.0.2 → 2.2.0
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 +4 -4
- package/dist/lib/form.cjs +1 -1
- package/dist/lib/form.js +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/nix/form.d.ts +57 -10
- package/dist/lib/nix/index.d.ts +2 -2
- package/dist/lib/nix/router.d.ts +1 -1
- package/dist/lib/nix/store.d.ts +45 -15
- package/dist/lib/nix-js.cjs +1 -1
- package/dist/lib/nix-js.js +1 -1
- package/dist/lib/router.cjs +1 -1
- package/dist/lib/router.js +1 -1
- package/dist/lib/store.cjs +1 -1
- package/dist/lib/store.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ This is optional: `import { ... } from "@deijose/nix-js"` remains fully supporte
|
|
|
44
44
|
## Quick Start
|
|
45
45
|
|
|
46
46
|
```typescript
|
|
47
|
-
import { signal, html, NixTemplate, NixComponent, mount, createRouter, RouterView, Link,
|
|
47
|
+
import { signal, html, NixTemplate, NixComponent, mount, createRouter, RouterView, Link, nixRouter } from "@deijose/nix-js";
|
|
48
48
|
|
|
49
49
|
// --- Pages as function components (NixTemplate) ---
|
|
50
50
|
// Plain functions returning html`` are recommended for pages and
|
|
@@ -60,7 +60,7 @@ function HomePage(): NixTemplate {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function UserPage(): NixTemplate {
|
|
63
|
-
const router =
|
|
63
|
+
const router = nixRouter();
|
|
64
64
|
return html`<h1>User: ${() => router.params.value.id}</h1>`;
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -112,8 +112,8 @@ Everything ships in a single zero-dependency import:
|
|
|
112
112
|
| **Reactivity** | `signal`, `computed`, `effect`, `batch`, `watch`, `untrack`, `nextTick` |
|
|
113
113
|
| **Templates** | `` html` ` ``, `repeat`, `ref`, `portal`, `transition`, `showWhen` |
|
|
114
114
|
| **Components** | `NixTemplate` (function components), `NixComponent` (lifecycle class), `mount`, children & named slots |
|
|
115
|
-
| **Router** | `createRouter`, `RouterView`, `Link`, `
|
|
116
|
-
| **Forms** | `
|
|
115
|
+
| **Router** | `createRouter`, `RouterView`, `Link`, `nixRouter`, `RouterKey`, guards, nested routes, named routes (`name` + `navigate({ name })`), `mount(..., { router })` |
|
|
116
|
+
| **Forms** | `nixField`, `createForm`, built-in validators, Zod/Valibot interop |
|
|
117
117
|
| **State** | `createStore`, `provide`, `inject`, `createInjectionKey` |
|
|
118
118
|
| **Async** | `suspend` (with `invalidate` for re-fetching), `lazy` |
|
|
119
119
|
| **Error handling** | `createErrorBoundary` |
|
package/dist/lib/form.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");function t(e="Required"){return t=>null==t||""===t||Array.isArray(t)&&0===t.length?e:null}function n(e,t){return r=>"string"==typeof r&&r.length<e?t??`Minimum ${e} characters`:null}function r(e,t){return r=>"string"==typeof r&&r.length>e?t??`Maximum ${e} characters`:null}function i(e,t="Invalid format"){return r=>"string"!=typeof r||e.test(r)?null:t}function a(e="Invalid email"){return i(/^[^\s@]+@[^\s@]+\.[^\s@]+$/,e)}function o(e,t){return r=>"number"==typeof r&&r<e?t??`Minimum value is ${e}`:null}function s(e,t){return r=>"number"==typeof r&&r>e?t??`Maximum value is ${e}`:null}function c(e){return e}var l={required:t,minLength:n,maxLength:r,email:a,pattern:i,min:o,max:s};function u(e,t){return{...e,...t}}function d(t,r=[],n="blur",l){let u=e.signal(t),i=e.signal(!1),o=e.signal(!1),a=e.signal(null),s=e.signal(!1),f=e.
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");function t(e="Required"){return t=>null==t||""===t||Array.isArray(t)&&0===t.length?e:null}function n(e,t){return r=>"string"==typeof r&&r.length<e?t??`Minimum ${e} characters`:null}function r(e,t){return r=>"string"==typeof r&&r.length>e?t??`Maximum ${e} characters`:null}function i(e,t="Invalid format"){return r=>"string"!=typeof r||e.test(r)?null:t}function a(e="Invalid email"){return i(/^[^\s@]+@[^\s@]+\.[^\s@]+$/,e)}function o(e,t){return r=>"number"==typeof r&&r<e?t??`Minimum value is ${e}`:null}function s(e,t){return r=>"number"==typeof r&&r>e?t??`Maximum value is ${e}`:null}function c(e){return e}var l={required:t,minLength:n,maxLength:r,email:a,pattern:i,min:o,max:s};function u(e,t){return{...e,...t}}function d(t,r=[],n="blur",l){let u=e.signal(t),i=e.signal(!1),o=e.signal(!1),a=e.signal(null),s=e.signal(!1),f=!0,c=e.effect(()=>{u.value,f?f=!1:null!==a.peek()&&(a.value=null)}),v=e.computed(()=>{if(a.value)return a.value;let e=l?.();for(let t of r){let r=t(u.value,e);if(r)return r}return null}),p=e.computed(()=>("input"===n?o.value||i.value:"submit"===n?s.value:i.value)?v.value:null);return{value:u,error:p,rawError:v,touched:i,dirty:o,onInput:e=>{u.value=function(e){if(!e||!("value"in e))return t;let r=e;return"boolean"==typeof t?r.checked:"number"==typeof t?Number(r.value):r.value}(e.target),o.value=!0},onBlur:()=>{i.value=!0},reset:function(){e.batch(()=>{u.value=t,i.value=!1,o.value=!1,a.value=null,s.value=!1})},_setExternalError:function(e){a.value=e,e&&(i.value=!0)},_forceVisible:function(){i.value=!0,s.value=!0},_dispose:function(){c(),p.dispose(),v.dispose()}}}function f(t,r={},n="blur"){function l(e){let t={};for(let l in e){let u=r[l]??[];t[l]=d(e[l],u,n)}return t}let u=e.signal(t.map(l)),i=e.computed(()=>u.value.length);return{fields:u,append:function(e){u.value=[...u.value,l(e)]},remove:function(e){let t=u.value;if(!(e<0||e>=t.length)){for(let r in t[e])t[e][r]._dispose();u.value=t.filter((t,r)=>r!==e)}},move:function(e,t){let r=[...u.value];if(e<0||e>=r.length||t<0||t>=r.length||e===t)return;let[n]=r.splice(e,1);r.splice(t,0,n),u.value=r},replace:function(e,t){let r=[...u.value];if(!(e<0||e>=r.length)){for(let t in r[e])r[e][t]._dispose();r[e]=l(t),u.value=r}},length:i,reset:function(){for(let e of u.value)for(let t in e)e[t]._dispose();u.value=t.map(l)},_dispose:function(){for(let e of u.value)for(let t in e)e[t]._dispose();i.dispose()}}}function p(e){return"object"==typeof e&&!!e&&!Array.isArray(e)}function m(e,t="",r=[]){for(let[n,l]of Object.entries(e)){let e=t?`${t}.${n}`:n;p(l)&&Object.keys(l).length>0?m(l,e,r):r.push([e,l])}return r}function h(e,t,r){let n=t.split("."),l=e;for(let e=0;e<n.length-1;e++){let t=n[e];p(l[t])||(l[t]={}),l=l[t]}l[n[n.length-1]]=r}function g(t,r={}){let n=r.validateOn??"blur",l={},u=r.validators;function i(){let e={};for(let t in l)h(e,t,l[t].value.value);return e}for(let[e,r]of m(t))l[e]=d(r,u?.[e]??[],n,i);let o=e.signal(!1),a=e.signal(0),s=e.computed(()=>i()),f=e.computed(()=>{let e={};for(let t in l){let r=l[t].error.value;r&&(e[t]=r)}return e}),c=e.computed(()=>{for(let e in l)if(l[e].rawError.value)return!1;return!0}),v=e.computed(()=>{for(let e in l)if(l[e].error.value)return!1;return!0}),p=e.computed(()=>{for(let e in l)if(l[e].dirty.value)return!0;return!1}),g=e.computed(()=>{for(let e in l)if(l[e].touched.value)return!0;return!1});function x(e){for(let t in e)l[t]?._setExternalError(e[t]??null)}return{fields:l,values:s,errors:f,canSubmit:c,valid:v,dirty:p,touched:g,isSubmitting:o,submitCount:a,handleSubmit:function(e){return t=>{t.preventDefault(),a.value++;for(let e in l)l[e]._forceVisible();let n=s.value;if(r.validate){let e=r.validate(n);if(e){let t={},r=!1;for(let n in e){let l=e[n],u=Array.isArray(l)?l[0]??null:l??null;u&&(t[n]=u,r=!0)}if(r)return void x(t)}}for(let e in l)if(l[e].rawError.value)return;let u=e(n);u instanceof Promise&&(o.value=!0,u.finally(()=>{o.value=!1}).catch(()=>{}))}},reset:function(){for(let e in l)l[e].reset();o.value=!1,a.value=0},setErrors:x,dispose:function(){s.dispose(),f.dispose(),c.dispose(),v.dispose(),p.dispose(),g.dispose();for(let e in l)l[e]._dispose()}}}exports.createForm=g,exports.createValidator=c,exports.email=a,exports.extendValidators=u,exports.max=s,exports.maxLength=r,exports.min=o,exports.minLength=n,exports.nixField=d,exports.nixFieldArray=f,exports.pattern=i,exports.required=t,exports.validators=l;
|
package/dist/lib/form.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{batch as e,computed as t,effect as n,signal as r}from"./signals.js";function i(e="Required"){return t=>null==t||""===t||Array.isArray(t)&&0===t.length?e:null}function a(e,t){return r=>"string"==typeof r&&r.length<e?t??`Minimum ${e} characters`:null}function o(e,t){return r=>"string"==typeof r&&r.length>e?t??`Maximum ${e} characters`:null}function s(e,t="Invalid format"){return r=>"string"!=typeof r||e.test(r)?null:t}function c(e="Invalid email"){return s(/^[^\s@]+@[^\s@]+\.[^\s@]+$/,e)}function l(e,t){return r=>"number"==typeof r&&r<e?t??`Minimum value is ${e}`:null}function u(e,t){return r=>"number"==typeof r&&r>e?t??`Maximum value is ${e}`:null}function d(e){return e}var f={required:i,minLength:a,maxLength:o,email:c,pattern:s,min:l,max:u};function p(e,t){return{...e,...t}}function m(l,u=[],i="blur",a){let o=r(l),s=r(!1),f=r(!1),v=r(null),c=r(!1),d=!0,p=n(()=>{o.value,d?d=!1:null!==v.peek()&&(v.value=null)}),m=t(()=>{if(v.value)return v.value;let e=a?.();for(let t of u){let r=t(o.value,e);if(r)return r}return null}),h=t(()=>("input"===i?f.value||s.value:"submit"===i?c.value:s.value)?m.value:null);return{value:o,error:h,rawError:m,touched:s,dirty:f,onInput:e=>{o.value=function(e){if(!e||!("value"in e))return l;let t=e;return"boolean"==typeof l?t.checked:"number"==typeof l?Number(t.value):t.value}(e.target),f.value=!0},onBlur:()=>{s.value=!0},reset:function(){e(()=>{o.value=l,s.value=!1,f.value=!1,v.value=null,c.value=!1})},_setExternalError:function(e){v.value=e,e&&(s.value=!0)},_forceVisible:function(){s.value=!0,c.value=!0},_dispose:function(){p(),h.dispose(),m.dispose()}}}function h(e,n={},l="blur"){function u(e){let t={};for(let r in e){let u=n[r]??[];t[r]=m(e[r],u,l)}return t}let i=r(e.map(u)),a=t(()=>i.value.length);return{fields:i,append:function(e){i.value=[...i.value,u(e)]},remove:function(e){let t=i.value;if(!(e<0||e>=t.length)){for(let r in t[e])t[e][r]._dispose();i.value=t.filter((t,r)=>r!==e)}},move:function(e,t){let r=[...i.value];if(e<0||e>=r.length||t<0||t>=r.length||e===t)return;let[n]=r.splice(e,1);r.splice(t,0,n),i.value=r},replace:function(e,t){let r=[...i.value];if(!(e<0||e>=r.length)){for(let t in r[e])r[e][t]._dispose();r[e]=u(t),i.value=r}},length:a,reset:function(){for(let e of i.value)for(let t in e)e[t]._dispose();i.value=e.map(u)},_dispose:function(){for(let e of i.value)for(let t in e)e[t]._dispose();a.dispose()}}}function g(e){return"object"==typeof e&&!!e&&!Array.isArray(e)}function _(e,t="",r=[]){for(let[n,l]of Object.entries(e)){let e=t?`${t}.${n}`:n;g(l)&&Object.keys(l).length>0?_(l,e,r):r.push([e,l])}return r}function v(e,t,r){let n=t.split("."),l=e;for(let e=0;e<n.length-1;e++){let t=n[e];g(l[t])||(l[t]={}),l=l[t]}l[n[n.length-1]]=r}function y(e,n={}){let l=n.validateOn??"blur",u={},i=n.validators;function a(){let e={};for(let t in u)v(e,t,u[t].value.value);return e}for(let[t,r]of _(e))u[t]=m(r,i?.[t]??[],l,a);let o=r(!1),s=r(0),f=t(()=>a()),c=t(()=>{let e={};for(let t in u){let r=u[t].error.value;r&&(e[t]=r)}return e}),d=t(()=>{for(let e in u)if(u[e].rawError.value)return!1;return!0}),p=t(()=>{for(let e in u)if(u[e].error.value)return!1;return!0}),h=t(()=>{for(let e in u)if(u[e].dirty.value)return!0;return!1}),g=t(()=>{for(let e in u)if(u[e].touched.value)return!0;return!1});function y(e){for(let t in e)u[t]?._setExternalError(e[t]??null)}return{fields:u,values:f,errors:c,canSubmit:d,valid:p,dirty:h,touched:g,isSubmitting:o,submitCount:s,handleSubmit:function(e){return t=>{t.preventDefault(),s.value++;for(let e in u)u[e]._forceVisible();let r=f.value;if(n.validate){let e=n.validate(r);if(e){let t={},r=!1;for(let n in e){let l=e[n],u=Array.isArray(l)?l[0]??null:l??null;u&&(t[n]=u,r=!0)}if(r)return void y(t)}}for(let e in u)if(u[e].rawError.value)return;let l=e(r);l instanceof Promise&&(o.value=!0,l.finally(()=>{o.value=!1}).catch(()=>{}))}},reset:function(){for(let e in u)u[e].reset();o.value=!1,s.value=0},setErrors:y,dispose:function(){f.dispose(),c.dispose(),d.dispose(),p.dispose(),h.dispose(),g.dispose();for(let e in u)u[e]._dispose()}}}export{y as createForm,d as createValidator,c as email,p as extendValidators,u as max,o as maxLength,l as min,a as minLength,m as nixField,h as nixFieldArray,s as pattern,i as required,f as validators};
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,2 +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,
|
|
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, nixRouter, RouterKey, suspend, lazy, provide, inject, createInjectionKey, nixField, nixFieldArray, createForm, required, minLength, maxLength, email, pattern, min, max, createValidator, validators, extendValidators, } from "./nix";
|
|
2
2
|
export type { WatchOptions, NixTemplate, NixMountHandle, MountOptions, KeyedList, NixRef, PortalOutlet, ErrorFallback, TransitionOptions, TransitionContent, Store, StoreSignals, Router, NamedRouteLocation, RouteLocation, RouteRecord, RouterOptions, NavigationGuard, NavigationGuardResult, AfterEachHook, ResolvedRoute, ScrollPosition, ScrollBehavior, RouterMode, SuspenseOptions, InjectionKey, Validator, ValidateOn, FieldState, FieldArrayState, FieldErrors, FormState, FormOptions, ValidatorsBase, NixChildren, } from "./nix";
|
package/dist/lib/nix/form.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { Signal } from "./reactivity";
|
|
2
2
|
/**
|
|
3
|
-
* A validator function. Return an error string, or null/
|
|
3
|
+
* A validator function. Return an error string when invalid, or `null` /
|
|
4
|
+
* `undefined` when valid.
|
|
4
5
|
*
|
|
5
|
-
* `allValues` is
|
|
6
|
+
* `allValues` is provided when the validator runs inside `createForm`, enabling
|
|
7
|
+
* cross-field validation (e.g., "confirm password matches password").
|
|
8
|
+
*
|
|
9
|
+
* **Validators must be pure.** They are invoked reactively whenever the field
|
|
10
|
+
* value changes — potentially many times per keystroke in forms with
|
|
11
|
+
* cross-field rules — and may run inside computed signals that assume
|
|
12
|
+
* deterministic output. Do not perform I/O, mutate external state, or rely on
|
|
13
|
+
* `Date.now()` / `Math.random()` inside a validator. For asynchronous checks
|
|
14
|
+
* (uniqueness, server-side rules), submit the form and inject the result via
|
|
15
|
+
* {@link FormState.setErrors}.
|
|
6
16
|
*/
|
|
7
17
|
export type Validator<T, AllValues = unknown> = (value: T, allValues?: AllValues) => string | null | undefined;
|
|
8
18
|
export declare function required(message?: string): Validator<unknown>;
|
|
@@ -12,7 +22,7 @@ export declare function pattern(regex: RegExp, message?: string): Validator<stri
|
|
|
12
22
|
export declare function email(message?: string): Validator<string>;
|
|
13
23
|
export declare function min(n: number, message?: string): Validator<number>;
|
|
14
24
|
export declare function max(n: number, message?: string): Validator<number>;
|
|
15
|
-
/** Creates a typed custom validator compatible with `
|
|
25
|
+
/** Creates a typed custom validator compatible with `nixField` and `createForm`. */
|
|
16
26
|
export declare function createValidator<T, AllValues = unknown>(fn: (value: T, allValues?: AllValues) => string | null | undefined): Validator<T, AllValues>;
|
|
17
27
|
/** All built-in validators grouped as a namespace. Extensible via `extendValidators`. */
|
|
18
28
|
export declare const validators: {
|
|
@@ -42,11 +52,13 @@ export type ValidateOn = "blur" | "input" | "submit";
|
|
|
42
52
|
export interface FieldState<T> {
|
|
43
53
|
/** Current value — read/write signal. */
|
|
44
54
|
value: Signal<T>;
|
|
55
|
+
/** Error visible según validateOn. Para UI. */
|
|
56
|
+
readonly error: Signal<string | null>;
|
|
45
57
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
58
|
+
* Error real, ignorando validateOn/touched/dirty.
|
|
59
|
+
* Para lógica de habilitación de botones, validez global, etc.
|
|
48
60
|
*/
|
|
49
|
-
readonly
|
|
61
|
+
readonly rawError: Signal<string | null>;
|
|
50
62
|
/** True after the input has lost focus at least once. */
|
|
51
63
|
touched: Signal<boolean>;
|
|
52
64
|
/** True after the user has typed at least once. */
|
|
@@ -68,7 +80,7 @@ export interface FieldState<T> {
|
|
|
68
80
|
_dispose(): void;
|
|
69
81
|
}
|
|
70
82
|
/** Creates a standalone reactive form field with optional validators. */
|
|
71
|
-
export declare function
|
|
83
|
+
export declare function nixField<T, AllValues = unknown>(initialValue: T, fieldValidators?: Validator<T, AllValues>[], validateOn?: ValidateOn, getAllValues?: () => AllValues): FieldState<T>;
|
|
72
84
|
/** Public state of a field array (dynamic list of field groups). */
|
|
73
85
|
export interface FieldArrayState<T extends Record<string, unknown>> {
|
|
74
86
|
/** Reactive list of field group states. */
|
|
@@ -97,13 +109,13 @@ export interface FieldArrayState<T extends Record<string, unknown>> {
|
|
|
97
109
|
* Creates a reactive array of field groups for dynamic list forms.
|
|
98
110
|
*
|
|
99
111
|
* @example
|
|
100
|
-
* const items =
|
|
112
|
+
* const items = nixFieldArray([{ name: "" }], {
|
|
101
113
|
* name: [required()],
|
|
102
114
|
* });
|
|
103
115
|
* items.append({ name: "nuevo" });
|
|
104
116
|
* items.remove(0);
|
|
105
117
|
*/
|
|
106
|
-
export declare function
|
|
118
|
+
export declare function nixFieldArray<T extends Record<string, unknown>>(initialItems: T[], fieldValidators?: {
|
|
107
119
|
[K in keyof T]?: Validator<T[K], unknown>[];
|
|
108
120
|
}, validateOn?: ValidateOn): FieldArrayState<T>;
|
|
109
121
|
/** Field-name map that supports top-level keys and dot-path nested keys. */
|
|
@@ -125,8 +137,43 @@ export interface FormState<T extends Record<string, unknown>> {
|
|
|
125
137
|
readonly values: Signal<T>;
|
|
126
138
|
/** Computed map of all currently visible field errors. */
|
|
127
139
|
readonly errors: Signal<FieldErrors<T>>;
|
|
128
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* True when no field has a *visible* error.
|
|
142
|
+
*
|
|
143
|
+
* Visibility follows `validateOn`, so this signal reflects what the user
|
|
144
|
+
* currently sees in the UI — not the underlying validity of the data.
|
|
145
|
+
* Use it to drive error summaries, banners, or any UI that should only
|
|
146
|
+
* react to errors the user has been shown.
|
|
147
|
+
*
|
|
148
|
+
* For enabling or disabling submit buttons, use {@link canSubmit} instead.
|
|
149
|
+
*/
|
|
129
150
|
readonly valid: Signal<boolean>;
|
|
151
|
+
/**
|
|
152
|
+
* True when every per-field validator passes against the current values,
|
|
153
|
+
* regardless of `touched`, `dirty`, or `validateOn`.
|
|
154
|
+
*
|
|
155
|
+
* This is the signal to bind to submit buttons:
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* <button disabled=${() => !form.canSubmit.value || form.isSubmitting.value}>
|
|
159
|
+
* Save
|
|
160
|
+
* </button>
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* Unlike {@link valid}, `canSubmit` does not depend on whether errors are
|
|
164
|
+
* currently visible — a pristine form with empty required fields starts
|
|
165
|
+
* as `false` and flips to `true` the moment all validators pass.
|
|
166
|
+
*
|
|
167
|
+
* External errors injected via {@link setErrors} also flip `canSubmit` to
|
|
168
|
+
* `false` until the user edits the affected field.
|
|
169
|
+
*
|
|
170
|
+
* **Note:** `canSubmit` does not execute `options.validate` (schema-level
|
|
171
|
+
* validators such as Zod). Schema validation runs only on submit, by design,
|
|
172
|
+
* to keep per-keystroke cost predictable. If you need a schema rule to
|
|
173
|
+
* affect `canSubmit` reactively, express it as a per-field validator in
|
|
174
|
+
* `options.validators` instead.
|
|
175
|
+
*/
|
|
176
|
+
readonly canSubmit: Signal<boolean>;
|
|
130
177
|
/** True when at least one field has been modified. */
|
|
131
178
|
readonly dirty: Signal<boolean>;
|
|
132
179
|
/** True when at least one field has been touched (lost focus). */
|
package/dist/lib/nix/index.d.ts
CHANGED
|
@@ -8,11 +8,11 @@ export { NixComponent } from "./lifecycle";
|
|
|
8
8
|
export type { NixChildren } from "./lifecycle";
|
|
9
9
|
export { createStore } from "./store";
|
|
10
10
|
export type { Store, StoreSignals } from "./store";
|
|
11
|
-
export { createRouter, RouterView, Link,
|
|
11
|
+
export { createRouter, RouterView, Link, nixRouter, RouterKey } from "./router";
|
|
12
12
|
export type { Router, NamedRouteLocation, RouteLocation, RouteRecord, RouterOptions, NavigationGuard, NavigationGuardResult, AfterEachHook, ResolvedRoute, ScrollPosition, ScrollBehavior, RouterMode, } from "./router";
|
|
13
13
|
export { suspend, lazy } from "./async";
|
|
14
14
|
export type { SuspenseOptions } from "./async";
|
|
15
15
|
export { provide, inject, createInjectionKey } from "./context";
|
|
16
16
|
export type { InjectionKey } from "./context";
|
|
17
|
-
export {
|
|
17
|
+
export { nixField, nixFieldArray, createForm, required, minLength, maxLength, email, pattern, min, max, createValidator, validators, extendValidators, } from "./form";
|
|
18
18
|
export type { Validator, ValidateOn, FieldState, FieldArrayState, FieldErrors, FormState, FormOptions, ValidatorsBase } from "./form";
|
package/dist/lib/nix/router.d.ts
CHANGED
|
@@ -144,7 +144,7 @@ export interface _RouterDebugInternal {
|
|
|
144
144
|
*/
|
|
145
145
|
export declare function createRouter(routes: RouteRecord[], options?: RouterOptions): Router;
|
|
146
146
|
/** Returns the active router singleton. */
|
|
147
|
-
export declare function
|
|
147
|
+
export declare function nixRouter(): Router;
|
|
148
148
|
/**
|
|
149
149
|
* @internal — Resets the router singleton. Used by tests to avoid
|
|
150
150
|
* "A router already exists" warnings between test cases.
|
package/dist/lib/nix/store.d.ts
CHANGED
|
@@ -1,24 +1,54 @@
|
|
|
1
|
-
import { Signal } from "./reactivity";
|
|
2
|
-
/** Maps each state property to its corresponding Signal. */
|
|
1
|
+
import { Signal, type WatchOptions } from "./reactivity";
|
|
3
2
|
export type StoreSignals<T extends Record<string, unknown>> = {
|
|
4
3
|
readonly [K in keyof T]: Signal<T[K]>;
|
|
5
4
|
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
export type ReadonlySignal<T> = Omit<Signal<T>, "value" | "update" | "dispose"> & {
|
|
6
|
+
readonly value: T;
|
|
7
|
+
readonly dispose: never;
|
|
8
|
+
};
|
|
9
|
+
export type StoreGetters<G extends Record<string, Signal<unknown>>> = {
|
|
10
|
+
readonly [K in keyof G]: ReadonlySignal<G[K] extends Signal<infer V> ? V : never>;
|
|
11
|
+
};
|
|
12
|
+
export type Store<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> = StoreSignals<T> & A & StoreGetters<G> & {
|
|
13
|
+
readonly $id: string;
|
|
14
|
+
/** Current snapshot — reading inside effect/computed creates subscription. */
|
|
11
15
|
readonly $state: T;
|
|
12
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The computed Signal that backs $state.
|
|
18
|
+
* Plugins receive this to compose new reactive nodes on top.
|
|
19
|
+
* This is what makes the plugin system reactive-native:
|
|
20
|
+
* no hooks, just signals all the way down.
|
|
21
|
+
*/
|
|
22
|
+
readonly $stateSignal: ReadonlySignal<T>;
|
|
23
|
+
/** Reset to initial values (batched). */
|
|
13
24
|
$reset(): void;
|
|
14
|
-
/**
|
|
25
|
+
/** Partial update (batched). */
|
|
15
26
|
$patch(partial: Partial<T>): void;
|
|
16
|
-
/**
|
|
17
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Watches state changes. This is exactly watch() from reactivity.ts —
|
|
29
|
+
* no new primitive to learn.
|
|
30
|
+
*/
|
|
31
|
+
$watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;
|
|
32
|
+
/** Disposes the store and runs all plugin cleanups. */
|
|
33
|
+
$dispose(): void;
|
|
18
34
|
};
|
|
19
35
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
36
|
+
* A NixPlugin is a function that receives the assembled store and
|
|
37
|
+
* optionally returns a cleanup function.
|
|
38
|
+
*
|
|
39
|
+
* There are NO lifecycle hooks. The plugin extends the signal graph directly:
|
|
40
|
+
*
|
|
41
|
+
* watch(store.$stateSignal, ...) — react to any state change
|
|
42
|
+
* computed(() => store.someSignal.value) — derive new nodes
|
|
43
|
+
* store.mySignal = signal(...) — attach new reactive state
|
|
44
|
+
* wrapMethod(store, '$patch', ...) — intercept mutations
|
|
45
|
+
*
|
|
46
|
+
* Because plugins only use Nix.js primitives, they compose with each other
|
|
47
|
+
* naturally — one plugin can observe a signal that another plugin added.
|
|
23
48
|
*/
|
|
24
|
-
export
|
|
49
|
+
export type NixPlugin<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> = (store: Store<T, A, G>) => (() => void) | void;
|
|
50
|
+
export interface CreateStoreOptions<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> {
|
|
51
|
+
name?: string;
|
|
52
|
+
plugins?: NixPlugin<T, A, G>[];
|
|
53
|
+
}
|
|
54
|
+
export declare function createStore<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>>(initialState: T, actionsFactory?: (signals: StoreSignals<T>) => A, gettersFactory?: (signals: StoreSignals<T>) => G, options?: CreateStoreOptions<T, A, G>): Store<T, A, G>;
|
package/dist/lib/nix-js.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs"),t=require("./template2.cjs"),n=require("./lifecycle.cjs"),r=require("./context.cjs"),i=require("./router.cjs"),a=require("./component.cjs"),o=require("./store.cjs"),s=require("./async.cjs"),c=require("./form.cjs");exports.Link=i.Link,exports.NixComponent=n.NixComponent,exports.RouterKey=i.RouterKey,exports.RouterView=i.RouterView,exports.Signal=e.Signal,exports.batch=e.batch,exports.computed=e.computed,exports.createErrorBoundary=t.t,exports.createForm=c.createForm,exports.createInjectionKey=r.createInjectionKey,exports.createPortalOutlet=t.n,exports.createRouter=i.createRouter,exports.createStore=o.createStore,exports.createValidator=c.createValidator,exports.effect=e.effect,exports.email=c.email,exports.extendValidators=c.extendValidators,exports.html=t.l,exports.inject=r.inject,exports.injectOutlet=t.r,exports.lazy=s.lazy,exports.max=c.max,exports.maxLength=c.maxLength,exports.min=c.min,exports.minLength=c.minLength,exports.mount=a.mount,exports.nextTick=e.nextTick,exports.pattern=c.pattern,exports.portal=t.i,exports.portalOutlet=t.a,exports.provide=r.provide,exports.provideOutlet=t.o,exports.ref=t.h,exports.repeat=t.d,exports.required=c.required,exports.showWhen=t.u,exports.signal=e.signal,exports.suspend=s.suspend,exports.transition=t.s,exports.untrack=e.untrack,exports.
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs"),t=require("./template2.cjs"),n=require("./lifecycle.cjs"),r=require("./context.cjs"),i=require("./router.cjs"),a=require("./component.cjs"),o=require("./store.cjs"),s=require("./async.cjs"),c=require("./form.cjs");exports.Link=i.Link,exports.NixComponent=n.NixComponent,exports.RouterKey=i.RouterKey,exports.RouterView=i.RouterView,exports.Signal=e.Signal,exports.batch=e.batch,exports.computed=e.computed,exports.createErrorBoundary=t.t,exports.createForm=c.createForm,exports.createInjectionKey=r.createInjectionKey,exports.createPortalOutlet=t.n,exports.createRouter=i.createRouter,exports.createStore=o.createStore,exports.createValidator=c.createValidator,exports.effect=e.effect,exports.email=c.email,exports.extendValidators=c.extendValidators,exports.html=t.l,exports.inject=r.inject,exports.injectOutlet=t.r,exports.lazy=s.lazy,exports.max=c.max,exports.maxLength=c.maxLength,exports.min=c.min,exports.minLength=c.minLength,exports.mount=a.mount,exports.nextTick=e.nextTick,exports.nixField=c.nixField,exports.nixFieldArray=c.nixFieldArray,exports.nixRouter=i.nixRouter,exports.pattern=c.pattern,exports.portal=t.i,exports.portalOutlet=t.a,exports.provide=r.provide,exports.provideOutlet=t.o,exports.ref=t.h,exports.repeat=t.d,exports.required=c.required,exports.showWhen=t.u,exports.signal=e.signal,exports.suspend=s.suspend,exports.transition=t.s,exports.untrack=e.untrack,exports.validators=c.validators,exports.watch=e.watch;
|
package/dist/lib/nix-js.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Signal as e,batch as t,computed as n,effect as r,nextTick as i,signal as a,untrack as o,watch as s}from"./signals.js";import{a as c,d as l,h as u,i as d,l as f,n as p,o as m,r as h,s as g,t as _,u as v}from"./template2.js";import{NixComponent as y}from"./lifecycle.js";import{createInjectionKey as b,inject as x,provide as S}from"./context.js";import{Link as C,RouterKey as w,RouterView as T,createRouter as E,
|
|
1
|
+
import{Signal as e,batch as t,computed as n,effect as r,nextTick as i,signal as a,untrack as o,watch as s}from"./signals.js";import{a as c,d as l,h as u,i as d,l as f,n as p,o as m,r as h,s as g,t as _,u as v}from"./template2.js";import{NixComponent as y}from"./lifecycle.js";import{createInjectionKey as b,inject as x,provide as S}from"./context.js";import{Link as C,RouterKey as w,RouterView as T,createRouter as E,nixRouter as D}from"./router.js";import{mount as O}from"./component.js";import{createStore as k}from"./store.js";import{lazy as A,suspend as j}from"./async.js";import{createForm as M,createValidator as N,email as P,extendValidators as F,max as I,maxLength as L,min as R,minLength as z,nixField as B,nixFieldArray as V,pattern as H,required as U,validators as W}from"./form.js";export{C as Link,y as NixComponent,w as RouterKey,T as RouterView,e as Signal,t as batch,n as computed,_ as createErrorBoundary,M as createForm,b as createInjectionKey,p as createPortalOutlet,E as createRouter,k as createStore,N as createValidator,r as effect,P as email,F as extendValidators,f as html,x as inject,h as injectOutlet,A as lazy,I as max,L as maxLength,R as min,z as minLength,O as mount,i as nextTick,B as nixField,V as nixFieldArray,D as nixRouter,H as pattern,d as portal,c as portalOutlet,S as provide,m as provideOutlet,u as ref,l as repeat,U as required,v as showWhen,a as signal,j as suspend,g as transition,o as untrack,W as validators,s as watch};
|
package/dist/lib/router.cjs
CHANGED
|
@@ -4,4 +4,4 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=requi
|
|
|
4
4
|
href=${"hash"===n._mode?"#"+o:o}
|
|
5
5
|
style=${()=>n.current.value===e?"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a":"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px"}
|
|
6
6
|
@click=${t=>{t.preventDefault(),n.navigate(e)}}
|
|
7
|
-
>${r}</a>`}};exports.Link=D,exports.RouterKey=i,exports.RouterView=E,exports._debugGetRouterInternal=T,exports._resetRouter=w,exports.createRouter=S,exports.
|
|
7
|
+
>${r}</a>`}};exports.Link=D,exports.RouterKey=i,exports.RouterView=E,exports._debugGetRouterInternal=T,exports._resetRouter=w,exports.createRouter=S,exports.nixRouter=C;
|
package/dist/lib/router.js
CHANGED
|
@@ -4,4 +4,4 @@ import{signal as e}from"./signals.js";import{l as t}from"./template2.js";import{
|
|
|
4
4
|
href=${"hash"===n._mode?"#"+a:a}
|
|
5
5
|
style=${()=>n.current.value===e?"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a":"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px"}
|
|
6
6
|
@click=${t=>{t.preventDefault(),n.navigate(e)}}
|
|
7
|
-
>${r}</a>`}};export{O as Link,a as RouterKey,D as RouterView,E as _debugGetRouterInternal,T as _resetRouter,C as createRouter,w as
|
|
7
|
+
>${r}</a>`}};export{O as Link,a as RouterKey,D as RouterView,E as _debugGetRouterInternal,T as _resetRouter,C as createRouter,w as nixRouter};
|
package/dist/lib/store.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");var t=new Set(["$id","$state","$stateSignal","$reset","$patch","$watch","$dispose"]);function n(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(t.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function r(e,r){return!t.has(e)||(console.warn(`[Nix] Store ${r} "${e}" is reserved and will be ignored.`),!1)}function i(e,t){let r=Object.create(e);return Object.defineProperty(r,"dispose",{value:()=>{throw Error(`[Nix] Cannot dispose readonly getter "${t}". Dispose the store instead with store.$dispose().`)},writable:!1,configurable:!1}),Object.defineProperty(r,"value",{get:()=>e.value,set(){throw Error(`[Nix] "${t}" is read-only.`)},configurable:!1}),Object.defineProperty(r,"update",{value:()=>{throw Error(`[Nix] "${t}" is read-only.`)},writable:!1,configurable:!1}),r}function a(o,a,l,s={}){let{name:c="store",plugins:f=[]}=s,u=Object.keys(o),d={};for(let t of u)n(t),d[t]=e.signal(o[t]);let b,$=d,g=e.computed(()=>{let e={};for(let t of u)e[t]=d[t].value;return e}),p=i(g,`store "${c}".$stateSignal`);try{b=structuredClone(o)}catch(e){throw Error(`[Nix] Store "${c}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let h=Object.assign(Object.create(null),$,{$reset:function(){e.batch(()=>{for(let e of u)d[e].value=b[e]})},$patch:function(t){e.batch(()=>{for(let e of Object.keys(t))Object.prototype.hasOwnProperty.call(d,e)&&(d[e].value=t[e])})},$watch:function(t,r){return e.watch(g,t,r)}});Object.defineProperty(h,"$id",{value:c,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(h,"$state",{get:()=>g.value,enumerable:!0,configurable:!1}),Object.defineProperty(h,"$stateSignal",{value:p,writable:!1,enumerable:!1,configurable:!1});let w=new Set([...u,...Array.from(t)]);if(a){let e=a($);for(let t of Object.keys(e))if(r(t,"action")){if(w.has(t)){console.warn(`[Nix] Store "${c}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}w.add(t),h[t]=e[t]}}if(l){let t=l($);for(let o of Object.keys(t)){if(!r(o,"getter"))continue;if(w.has(o)){console.warn(`[Nix] Store "${c}": getter "${o}" collides with an existing signal or action and will be ignored.`);continue}let n=t[o];if(!(n instanceof e.Signal))throw TypeError(`[Nix] Store "${c}": getter "${o}" must return a Signal (wrap it with computed()). Got: ${typeof n}`);w.add(o),h[o]=i(n,`getter "${o}" in store "${c}"`)}}let y=[()=>g.dispose()];for(let e of f)try{let t=e(h);"function"==typeof t&&y.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${c}":`,e)}return h.$dispose=()=>{for(let e of y)e()},h}exports.createStore=a;
|
package/dist/lib/store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{Signal as e,batch as t,computed as n,signal as r,watch as i}from"./signals.js";var a=new Set(["$id","$state","$stateSignal","$reset","$patch","$watch","$dispose"]);function o(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(a.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function s(e,t){return!a.has(e)||(console.warn(`[Nix] Store ${t} "${e}" is reserved and will be ignored.`),!1)}function c(e,t){let r=Object.create(e);return Object.defineProperty(r,"dispose",{value:()=>{throw Error(`[Nix] Cannot dispose readonly getter "${t}". Dispose the store instead with store.$dispose().`)},writable:!1,configurable:!1}),Object.defineProperty(r,"value",{get:()=>e.value,set(){throw Error(`[Nix] "${t}" is read-only.`)},configurable:!1}),Object.defineProperty(r,"update",{value:()=>{throw Error(`[Nix] "${t}" is read-only.`)},writable:!1,configurable:!1}),r}function l(l,f,u,d={}){let{name:$="store",plugins:b=[]}=d,g=Object.keys(l),p={};for(let e of g)o(e),p[e]=r(l[e]);let w,h=p,y=n(()=>{let e={};for(let t of g)e[t]=p[t].value;return e}),O=c(y,`store "${$}".$stateSignal`);try{w=structuredClone(l)}catch(e){throw Error(`[Nix] Store "${$}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let S=Object.assign(Object.create(null),h,{$reset:function(){t(()=>{for(let e of g)p[e].value=w[e]})},$patch:function(e){t(()=>{for(let t of Object.keys(e))Object.prototype.hasOwnProperty.call(p,t)&&(p[t].value=e[t])})},$watch:function(e,t){return i(y,e,t)}});Object.defineProperty(S,"$id",{value:$,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(S,"$state",{get:()=>y.value,enumerable:!0,configurable:!1}),Object.defineProperty(S,"$stateSignal",{value:O,writable:!1,enumerable:!1,configurable:!1});let j=new Set([...g,...Array.from(a)]);if(f){let e=f(h);for(let t of Object.keys(e))if(s(t,"action")){if(j.has(t)){console.warn(`[Nix] Store "${$}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}j.add(t),S[t]=e[t]}}if(u){let t=u(h);for(let r of Object.keys(t)){if(!s(r,"getter"))continue;if(j.has(r)){console.warn(`[Nix] Store "${$}": getter "${r}" collides with an existing signal or action and will be ignored.`);continue}let o=t[r];if(!(o instanceof e))throw TypeError(`[Nix] Store "${$}": getter "${r}" must return a Signal (wrap it with computed()). Got: ${typeof o}`);j.add(r),S[r]=c(o,`getter "${r}" in store "${$}"`)}}let v=[()=>y.dispose()];for(let e of b)try{let t=e(S);"function"==typeof t&&v.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${$}":`,e)}return S.$dispose=()=>{for(let e of v)e()},S}export{l as createStore};
|
package/package.json
CHANGED