@cleverbrush/react-form 0.0.0-beta-20260929112752 → 0.0.0-beta-20260930081248
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 +111 -0
- package/dist/FormStore.d.ts +4 -2
- package/dist/hooks.d.ts +8 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,6 +8,117 @@
|
|
|
8
8
|
|
|
9
9
|
A headless, schema-driven form system for React based on `@cleverbrush/schema`. Uses PropertyDescriptors for type-safe field binding, supports global UI renderer configuration via a provider, and is completely UI-agnostic — works with plain HTML, MUI, Ant Design, or any component library.
|
|
10
10
|
|
|
11
|
+
## Server validation issues
|
|
12
|
+
|
|
13
|
+
Return `{ ok: false, error: string, issues }` from `handleSubmit` to apply a
|
|
14
|
+
submission message and field issues together. An issue is plain
|
|
15
|
+
`{ pointer: string, detail: string }` data: `pointer` is a **form-relative JSON
|
|
16
|
+
Pointer**, not a dotted path. `''` means a form-level error. No HTTP, client, or
|
|
17
|
+
Next.js dependency is needed by this package.
|
|
18
|
+
|
|
19
|
+
### A multi-file client/action/form example
|
|
20
|
+
|
|
21
|
+
The API contract, server handler, action and component remain separate. The
|
|
22
|
+
following assumes your application exports a configured typed `client` for
|
|
23
|
+
`api`; authentication, persistence and safe fallback messages stay application-owned.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// profile-contract.ts (shared)
|
|
27
|
+
import { object, string } from '@cleverbrush/schema';
|
|
28
|
+
import { defineApi, endpoint } from '@cleverbrush/server/contract';
|
|
29
|
+
export const Profile = object({ name: string().minLength(2) });
|
|
30
|
+
export const api = defineApi({ profiles: {
|
|
31
|
+
save: endpoint.post('/profiles').body(Profile).responses({ 200: Profile })
|
|
32
|
+
} });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// profile-handler.ts (server)
|
|
37
|
+
import type { Handler } from '@cleverbrush/server';
|
|
38
|
+
import { api } from './profile-contract';
|
|
39
|
+
export const saveProfileHandler: Handler<typeof api.profiles.save> =
|
|
40
|
+
async ({ body }) => body; // Replace with application persistence.
|
|
41
|
+
// Register this handler with your application's implementation scope.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// save-profile.ts (server action / application boundary)
|
|
46
|
+
import { decodeValidationIssues } from '@cleverbrush/client';
|
|
47
|
+
import type { InferType } from '@cleverbrush/schema';
|
|
48
|
+
import { Profile } from './profile-contract';
|
|
49
|
+
import { client } from './api-client';
|
|
50
|
+
export async function saveProfile(values: InferType<typeof Profile>) {
|
|
51
|
+
try {
|
|
52
|
+
return { ok: true as const, data: await client.profiles.save({ body: values }) };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const issues = decodeValidationIssues(error, { source: 'body' });
|
|
55
|
+
if (issues) return { ok: false as const, error: 'Check your input.', issues };
|
|
56
|
+
// Add application-owned handling for expected general errors here.
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// ProfileForm.tsx (browser; add your framework's client directive if needed)
|
|
64
|
+
import { useSchemaForm } from '@cleverbrush/react-form';
|
|
65
|
+
import { Profile } from './profile-contract';
|
|
66
|
+
import { saveProfile } from './save-profile';
|
|
67
|
+
export function ProfileForm() {
|
|
68
|
+
const form = useSchemaForm(Profile);
|
|
69
|
+
const name = form.useField(t => t.name);
|
|
70
|
+
return <form onSubmit={form.handleSubmit(saveProfile)}>
|
|
71
|
+
<label>Name <input value={name.value ?? ''}
|
|
72
|
+
onChange={e => name.onChange(e.target.value)} onBlur={name.onBlur}
|
|
73
|
+
aria-invalid={name.touched && !!name.error} aria-describedby="name-error" /></label>
|
|
74
|
+
<span id="name-error">{name.touched && name.error}</span>
|
|
75
|
+
{form.error && <p role="alert">{form.error}</p>}
|
|
76
|
+
<button disabled={form.submitting}>Save</button>
|
|
77
|
+
</form>;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Ordinary shared-schema errors are already caught locally. This bridge handles
|
|
82
|
+
structured server rejections (including schema-version differences); it does
|
|
83
|
+
not manufacture field errors from business-error strings. Never serialize an
|
|
84
|
+
`ApiError` instance to the browser. Only its decoded issue data crosses the action
|
|
85
|
+
boundary. A form with different property names must explicitly map the returned
|
|
86
|
+
pointers to its own paths before returning the failure result.
|
|
87
|
+
|
|
88
|
+
### Manual issues, indexed fields and lifecycle
|
|
89
|
+
|
|
90
|
+
Use `form.setIssues(issues)` for custom flows; it replaces the external issue set.
|
|
91
|
+
`form.setIssues([])` clears only external errors, not local schema errors. Prefer
|
|
92
|
+
returned issues for asynchronous submissions: `handleSubmit` ignores stale issues
|
|
93
|
+
after any value update, reset or unmount. Manual callers own that race protection.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
const form = useSchemaForm(object({ addresses: array(object({ city: string() })) }));
|
|
97
|
+
const city = form.useField(t => t.addresses[0].city); // string | undefined
|
|
98
|
+
form.setIssues([{ pointer: '/addresses/0/city', detail: 'Choose another city.' }]);
|
|
99
|
+
// Equivalent with Field / a typed form system:
|
|
100
|
+
// <Field form={form} forProperty={t => t.addresses[0].city} />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- Matched fields become touched and show their first external message through
|
|
104
|
+
`field.error`. External messages take precedence until cleared.
|
|
105
|
+
- Local validation, including debounced validation, does not erase external
|
|
106
|
+
issues on unchanged fields. Editing clears related ancestor/descendant issues,
|
|
107
|
+
not unrelated fields. Root issues clear when values change.
|
|
108
|
+
- `setValue` clears issues for changed values. Replacing/reordering an array
|
|
109
|
+
clears its indexed issues: indices are positions, not stable item identities.
|
|
110
|
+
- Reset and the next submission clear all external issues. Existing string-only
|
|
111
|
+
submission errors continue to work and clear on reset/resubmission.
|
|
112
|
+
- Root, unknown, and currently unbound field issues are included in `form.error`
|
|
113
|
+
alongside the application message, joined by newlines. Render that summary even
|
|
114
|
+
when using inline messages. Binding/unbinding a field updates the summary.
|
|
115
|
+
- Indexed selectors support object, primitive and nested arrays, but are not
|
|
116
|
+
native arrays: no `push`, negative indices, or add/remove/reorder UI helpers.
|
|
117
|
+
|
|
118
|
+
JSON Pointer escaping preserves distinct names: `/a.b`, `/a/b`, `/a~1b`, and
|
|
119
|
+
`/a~0b` target a dotted name, a nested field, a slash in a name, and a tilde in a
|
|
120
|
+
name respectively. Unknown paths never create values or mutate the form.
|
|
121
|
+
|
|
11
122
|
## Why @cleverbrush/react-form?
|
|
12
123
|
|
|
13
124
|
**The problem:** Every popular React form library — React Hook Form, Formik, React Final Form — requires you to reference fields by **string names**: `register("email")`, `<Field name="address.city" />`. The moment you pass a field name as a string, you lose TypeScript's type safety. Rename a property in your data model and the compiler stays silent — your form just silently breaks at runtime. The larger your codebase, the more of these invisible string references you accumulate, and the more fragile every refactor becomes.
|
package/dist/FormStore.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldState, FormSubmissionState } from './types.js';
|
|
1
|
+
import type { FieldState, FormIssue, FormSubmissionState } from './types.js';
|
|
2
2
|
type Binding = {
|
|
3
3
|
getValue: (values: any) => {
|
|
4
4
|
success: boolean;
|
|
@@ -14,13 +14,15 @@ export declare function createFormStore(initialValues: any): {
|
|
|
14
14
|
subscribe: (path: string, listener: () => void) => () => void;
|
|
15
15
|
subscribeGlobal: (listener: () => void) => () => void;
|
|
16
16
|
getValues: () => any;
|
|
17
|
-
setValues: (newValues: any) => void;
|
|
17
|
+
setValues: (newValues: any, changedPath?: string) => void;
|
|
18
18
|
setFieldValue: (path: string, value: any, createMissingStructure: boolean) => void;
|
|
19
19
|
resetAll: (newInitialValues?: any) => void;
|
|
20
20
|
getAllFieldPaths: () => string[];
|
|
21
21
|
getRevision: () => number;
|
|
22
22
|
getSubmissionState: () => FormSubmissionState;
|
|
23
23
|
setSubmissionState: (patch: Partial<FormSubmissionState>) => void;
|
|
24
|
+
setIssues: (next: readonly FormIssue[]) => void;
|
|
25
|
+
setSubmissionFailure: (error: string, next?: readonly FormIssue[]) => void;
|
|
24
26
|
notifyAll: () => void;
|
|
25
27
|
};
|
|
26
28
|
export type FormStore = ReturnType<typeof createFormStore>;
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { InferType, PropertyDescriptor, PropertyDescriptorTree, SchemaBuilder, ValidationResult } from '@cleverbrush/schema';
|
|
2
2
|
import { ObjectSchemaBuilder } from '@cleverbrush/schema';
|
|
3
3
|
import type { FormContextValue } from './contexts.js';
|
|
4
|
-
import type { FieldRenderer, FormSubmissionState, FormSubmitHandler, FormSubmitOptions, FormSubmitResult, FormSystemConfig, UseFieldResult, UseSchemaFormOptions } from './types.js';
|
|
4
|
+
import type { FieldRenderer, FormIssue, FormSubmissionState, FormSubmitHandler, FormSubmitOptions, FormSubmitResult, FormSystemConfig, UseFieldResult, UseSchemaFormOptions } from './types.js';
|
|
5
5
|
/** A stable form controller with reactive field and submission subscriptions. */
|
|
6
6
|
export type SchemaFormInstance<TSchema extends ObjectSchemaBuilder<any, any, any>> = {
|
|
7
7
|
useField: <TPropertySchema extends SchemaBuilder<any, any, any, any>>(forProperty: (tree: PropertyDescriptorTree<TSchema, TSchema>) => PropertyDescriptor<TSchema, TPropertySchema, any>) => UseFieldResult<InferType<TPropertySchema>>;
|
|
@@ -13,6 +13,13 @@ export type SchemaFormInstance<TSchema extends ObjectSchemaBuilder<any, any, any
|
|
|
13
13
|
getValue: () => InferType<TSchema>;
|
|
14
14
|
/** Shallow-merge values without marking fields touched. */
|
|
15
15
|
setValue: (values: Partial<InferType<TSchema>>) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Replace external issues without changing values. Empty pointers and unbound
|
|
18
|
+
* fields appear in form.error; bound fields receive the first issue and are
|
|
19
|
+
* marked touched. Prefer returned submission issues for async race protection.
|
|
20
|
+
* Pass [] to clear external issues without clearing local validation errors.
|
|
21
|
+
*/
|
|
22
|
+
setIssues: (issues: readonly FormIssue[]) => void;
|
|
16
23
|
/** Validate and submit once; repeated calls while pending are ignored. */
|
|
17
24
|
handleSubmit: <TData = void>(onValid: (values: InferType<TSchema>) => FormSubmitResult<TData> | Promise<FormSubmitResult<TData>>, options?: FormSubmitOptions<InferType<TSchema>, TData>) => FormSubmitHandler;
|
|
18
25
|
/** @internal — Used by FormProvider and Field to access internal context. */
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export type { SchemaFormInstance } from './hooks.js';
|
|
|
4
4
|
export { useSchemaForm } from './hooks.js';
|
|
5
5
|
export type { TypedFieldComponent, TypedFieldProps, TypedFieldRenderer, TypedFormSystem, TypedRendererRegistry } from './system.js';
|
|
6
6
|
export { createFormSystem, defineFieldRenderer } from './system.js';
|
|
7
|
-
export type { FieldRenderer, FieldRenderProps, FieldState, FormSubmissionState, FormSubmitHandler, FormSubmitOptions, FormSubmitResult, FormSystemConfig, UseFieldResult, UseSchemaFormOptions } from './types.js';
|
|
7
|
+
export type { FieldRenderer, FieldRenderProps, FieldState, FormIssue, FormSubmissionState, FormSubmitHandler, FormSubmitOptions, FormSubmitResult, FormSystemConfig, UseFieldResult, UseSchemaFormOptions } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import{useContext as
|
|
1
|
+
import{useContext as Q,useMemo as Re}from"react";import{createContext as ie}from"react";var W=ie(null),Z=ie(null);import{ObjectSchemaBuilder as ce,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as ae}from"@cleverbrush/schema";function ee(s,n){let c=new Map,e=n.introspect();if(!e.properties)return c;for(let a of Object.keys(e.properties)){let u=s[a];if(!u||!(ae in u))continue;let p=u[ae];c.set(p,a);let o=e.properties[a];if(o instanceof ce){let d=ee(u,o);for(let[f,l]of d)c.set(f,`${a}.${l}`)}}return c}function U(s){return s.introspect()?.type??"unknown"}function re(s,n){let c=n.introspect();if(!c.properties)return s??{};let e=s!=null?{...s}:{};for(let a of Object.keys(c.properties)){let u=c.properties[a];u instanceof ce&&(e[a]=re(e[a],u))}return e}import{ObjectSchemaBuilder as Fe,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as he}from"@cleverbrush/schema";import{useCallback as S,useEffect as de,useMemo as le,useRef as D,useSyncExternalStore as me}from"react";import{deepClone as L,deepEqual as G}from"@cleverbrush/deep";function ue(s){let n=L(s??{}),c=n,e=0,a=[],u,p=new Map,o={submitting:!1,error:void 0},d=new Map,f=new Map,l=new Map,F=new Set,P=0,O=new Set,j=!1;function h(r){P++;try{r()}finally{if(P--,P===0){let t=[...O];O.clear();let i=j;j=!1;for(let m of t)k(m);i&&g()}}}function g(){if(P){j=!0;return}for(let r of F)r()}function B(r,t){let i=r?.getValue(t);return i?.success?i.value:void 0}function E(r){if(!f.has(r)){let t=d.get(r),i=B(t,n),m=B(t,c);f.set(r,{value:i,initialValue:m,dirty:!G(i,m),touched:a.some(v=>v.pointer===r),error:M(r),validating:!1})}return f.get(r)}function z(r,t){return d.has(r)||d.set(r,t),E(r)}function k(r){if(P){O.add(r);return}for(let t of l.get(r)??[])t()}function q(){for(let r of l.keys())k(r);g()}function $(r,t){let i=E(r);Object.entries(t).every(([m,v])=>Object.is(i[m],v))||(f.set(r,{...i,...t}),k(r))}function M(r){return r===""?void 0:a.find(t=>t.pointer===r)?.detail}function H(r,t){Object.hasOwn(t,"error")&&p.set(r,t.error),$(r,{...t,error:M(r)??p.get(r)})}function N(r){h(()=>{a=r.map(({pointer:t,detail:i})=>({pointer:t,detail:i}));for(let t of d.keys())$(t,{error:M(t)??p.get(t),...M(t)!==void 0?{touched:!0}:{}});I({})})}function J(r,t){h(()=>{t&&N(t),I({error:r})})}function Y(r){for(let[t,i]of d){let m=B(i,n),v=B(i,c);H(t,{value:m,initialValue:v,dirty:!G(m,v),validating:!1,...r?{touched:!1,error:void 0}:{}})}}function X(r,t){let i=l.get(r);return i||(i=new Set,l.set(r,i)),i.add(t),I({}),()=>{i.delete(t),i.size===0&&l.delete(r),I({})}}function y(r){return F.add(r),()=>{F.delete(r)}}function V(){return o}function I(r){Object.hasOwn(r,"error")&&(u=r.error);let t=[u,...a.filter(m=>m.pointer===""||!l.get(m.pointer)?.size).map(m=>m.detail)].filter(m=>!!m),i={...o,...r,error:[...new Set(t)].join(`
|
|
2
|
+
`)||void 0};i.submitting===o.submitting&&i.error===o.error||(o=i,g())}function K(){return n}function b(){return e}function w(r,t){let i=n;if(n=L(r??{}),e++,a.length>0&&!G(i,n)){let v=(C,R)=>{if(R==="")return C;if(!(!R.startsWith("/")||/~(?![01])/.test(R)))return R.slice(1).split("/").reduce((A,Te)=>{let se=Te.replace(/~1/g,"/").replace(/~0/g,"~");return A!=null&&Object.hasOwn(A,se)?A[se]:void 0},C)},Se=[...d.keys()].filter(C=>Array.isArray(v(i,C))&&!G(v(i,C),v(n,C))&&(!t||t===C||C.startsWith(t+"/")));N(a.filter(C=>{let R=C.pointer;return R===""||t&&(R===t||R.startsWith(t+"/")||t.startsWith(R+"/"))||Se.some(A=>R===A||R.startsWith(A+"/"))?!1:G(v(i,R),v(n,R))}))}Y(!1)}function x(r,t,i){let m=L(n);d.get(r)?.setValue(m,L(t),{createMissingStructure:i})&&w(m,r)}function _(r){n=L(r??{}),c=n,e++,a=[],p.clear(),Y(!0),I({error:void 0})}function T(){return Array.from(d.keys())}return{registerField:z,getFieldState:E,updateFieldState:H,subscribe:X,subscribeGlobal:y,getValues:K,setValues:w,setFieldValue:x,resetAll:_,getAllFieldPaths:T,getRevision:b,getSubmissionState:V,setSubmissionState:I,setIssues:N,setSubmissionFailure:J,notifyAll:q}}function ge(s,n){let c=D(null);c.current||(c.current=ue({}));let e=c.current,a=D(null);a.current||(a.current=Fe.getPropertiesFor(s));let u=a.current,p=D(null);p.current||(p.current=ee(u,s));let o=p.current;for(let[y]of o)e.registerField(y.toJsonPointer(),y);let d=D(s),f=D({});f.current={createMissingStructure:!0,...n};let l=D(!0),F=D(0),P=D(0),O=D(!1),j=D(null),h=S(()=>{j.current!==null&&clearTimeout(j.current),j.current=null},[]);de(()=>(l.current=!0,()=>{l.current=!1,F.current++,P.current++,h()}),[h]);let g=S(async y=>{h();let V=++P.current,I=e.getRevision();for(let x of e.getAllFieldPaths())e.updateFieldState(x,{validating:!0});let K=re(e.getValues(),d.current),b;try{b=await d.current.validateAsync(K,{doNotStopOnFirstError:!0})}catch{b={valid:!1}}if(!l.current||V!==P.current||I!==e.getRevision())return b;for(let x of e.getAllFieldPaths())e.updateFieldState(x,{error:void 0,validating:!1,...y?{touched:!0}:{}});let w=b.getErrorsFor;if(typeof w=="function")for(let x of e.getAllFieldPaths()){let _=x.slice(1).split("/").map(T=>T.replace(/~1/g,"/").replace(/~0/g,"~"));try{let T=w(r=>_.reduce((t,i)=>t?.[i],r));Array.isArray(T?.errors)&&T.errors.length&&e.updateFieldState(x,{error:T.errors[0]})}catch{}}return b},[h,e]),B=S(()=>g(!0),[g]),E=S(()=>B(),[B]),z=S(y=>{F.current++,P.current++,h(),e.resetAll(y)},[e,h]),k=S(()=>e.getValues(),[e]),q=S(y=>{h(),e.setValues({...e.getValues(),...y})},[e,h]),$=S(y=>{h();let V=f.current.validationDebounceMs;return V!=null&&V>0?(j.current=setTimeout(()=>{j.current=null,g(y)},V),Promise.resolve()):g(y)},[g,h]),M=S((y,V)=>async I=>{if(I?.preventDefault(),O.current||!l.current)return;O.current=!0;let K=F.current,b=()=>l.current&&K===F.current;e.setIssues([]),e.setSubmissionState({submitting:!0,error:void 0});try{let w=e.getRevision(),x=await g(!0);if(!b()||w!==e.getRevision()||!x.valid||x.object===void 0)return;let _=x.object,T;try{T=await y(_)}catch(r){if(!b())return;if(!V?.onError)throw r;let t=await V.onError(r);b()&&e.setSubmissionState({error:t});return}if(!b())return;if(T&&!T.ok){e.setSubmissionFailure(T.error,w===e.getRevision()?T.issues:void 0);return}await V?.onSuccess?.(T?.data,_)}finally{O.current=!1,l.current&&e.setSubmissionState({submitting:!1})}},[g,e]),H=le(()=>({store:e,descriptorTree:u,schema:d.current,pathMap:o,get options(){return f.current},triggerValidation:$}),[e,u,o,$]),N=D(H);N.current=H;let J=S(()=>N.current,[]),Y=S(y=>te(N.current,y),[]),X=D(n?.validateOnMount);return de(()=>{X.current&&g(!0)},[g]),me(e.subscribeGlobal,e.getSubmissionState,e.getSubmissionState),le(()=>({useField:Y,submit:E,validate:B,reset:z,getValue:k,setValue:q,setIssues:e.setIssues,handleSubmit:M,_getFormContext:J,get submitting(){return e.getSubmissionState().submitting},get error(){return e.getSubmissionState().error}}),[Y,E,B,z,k,q,M,J,e])}function te(s,n,c=s.triggerValidation){let{store:e,descriptorTree:a,options:u}=s,p=n(a)[he],o=p.toJsonPointer();e.registerField(o,p);let d=S(O=>e.subscribe(o,O),[e,o]),f=S(()=>e.getFieldState(o),[e,o]),l=me(d,f,f),F=S(O=>{e.setFieldValue(o,O,u.createMissingStructure!==!1),c?.(!1)},[e,o,u.createMissingStructure,c]),P=S(()=>e.updateFieldState(o,{touched:!0}),[e,o]);return{...l,onChange:F,onBlur:P,setValue:F,schema:p.getSchema()}}function pe(s,n,c){if(!s?.renderers)return;let e=U(n);return(c?s.renderers[`${e}:${c}`]:void 0)??s.renderers[e]}import{jsx as ye}from"react/jsx-runtime";function ne({renderers:s,config:n,children:c}){let e=Q(W),a=Re(()=>{let u={...n,renderers:{...n?.renderers,...s}};return e?{...e,...u,renderers:{...e.renderers,...u.renderers}}:u},[e,n,s]);return ye(W.Provider,{value:a,children:c})}function Pe({form:s,children:n}){let c=s._getFormContext();return ye(Z.Provider,{value:c,children:n})}function be(s){let n=Q(Z);if(!n)throw new Error("useField must be used within a FormProvider. Wrap your component tree with <FormProvider form={form}>.");return te(n,s)}function xe(){let s=Q(W);if(!s)throw new Error("useFormSystem must be used within a FormSystemProvider");return s}function oe({forProperty:s,form:n,renderer:c,variant:e,label:a,name:u,fieldProps:p}){let o=n.useField(s),d=Q(W),f=c??pe(d,o.schema,e);if(!f){let l=U(o.schema),F=e?`"${l}:${e}" or "${l}"`:`"${l}"`;throw new Error(`No renderer found for schema type ${F}. Provide a renderer prop or configure one in FormSystemProvider.`)}return f({value:o.value,initialValue:o.initialValue,dirty:o.dirty,touched:o.touched,error:o.error,validating:o.validating,onChange:o.onChange,onBlur:o.onBlur,setValue:o.setValue,schema:o.schema,variant:e,label:a,name:u,fieldProps:p})}import{SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as ve}from"@cleverbrush/schema";import{jsx as fe}from"react/jsx-runtime";function De(s){return s}function Oe(s){let n=Object.freeze({...s.renderers});function c(a){let u=a.forProperty(a.form._getFormContext().descriptorTree),p=U(u[ve].getSchema()),o=a.variant?`${p}:${a.variant}`:p,d=n[o];if(!d)throw new Error(`No renderer registered for "${o}" in this form system.`);return fe(oe,{...a,renderer:d})}function e({children:a}){return fe(ne,{renderers:n,children:a})}return{Field:c,Provider:e,renderers:n}}export{oe as Field,Pe as FormProvider,ne as FormSystemProvider,Oe as createFormSystem,De as defineFieldRenderer,be as useField,xe as useFormSystem,ge as useSchemaForm};
|
|
2
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components.tsx","../src/contexts.ts","../src/helpers.ts","../src/hooks.ts","../src/FormStore.ts","../src/system.tsx"],"sourcesContent":["import type {\n InferType,\n ObjectSchemaBuilder,\n PropertyDescriptor,\n PropertyDescriptorTree,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport type React from 'react';\nimport { useContext, useMemo } from 'react';\nimport { FormContext, FormSystemContext } from './contexts.js';\nimport { getSchemaType } from './helpers.js';\nimport type { SchemaFormInstance } from './hooks.js';\nimport { resolveRenderer, useFieldFromContext } from './hooks.js';\nimport type {\n FieldRenderer,\n FormSystemConfig,\n UseFieldResult\n} from './types.js';\n\n// ─── FormSystemProvider ──────────────────────────────────────────────────────\n\nexport type FormSystemProviderProps = {\n /**\n * Renderer registry mapping schema types to renderer functions.\n * Example: `{ string: (props) => <input .../>, number: (props) => <input type=\"number\" .../> }`\n */\n renderers?: Record<string, FieldRenderer>;\n /**\n * Full configuration object (for future extensibility).\n * If both `renderers` and `config.renderers` are provided, `renderers` takes precedence.\n */\n config?: FormSystemConfig;\n children: React.ReactNode;\n};\n\n/**\n * Provides global renderer configuration via React Context.\n * Supports nesting — inner provider overrides outer.\n *\n * @example\n * ```tsx\n * <FormSystemProvider renderers={htmlRenderers}>\n * <App />\n * </FormSystemProvider>\n * ```\n */\nexport function FormSystemProvider({\n renderers,\n config,\n children\n}: FormSystemProviderProps): React.ReactNode {\n const parentConfig = useContext(FormSystemContext);\n\n const resolvedConfig: FormSystemConfig = useMemo(() => {\n const current: FormSystemConfig = {\n ...config,\n renderers: {\n ...config?.renderers,\n ...renderers\n }\n };\n if (!parentConfig) return current;\n return {\n ...parentConfig,\n ...current,\n renderers: {\n ...parentConfig.renderers,\n ...current.renderers\n }\n };\n }, [parentConfig, config, renderers]);\n\n return (\n <FormSystemContext.Provider value={resolvedConfig}>\n {children}\n </FormSystemContext.Provider>\n );\n}\n\n// ─── FormProvider ────────────────────────────────────────────────────────────\n\nexport type FormProviderProps<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n> = {\n form: SchemaFormInstance<TSchema>;\n children: React.ReactNode;\n};\n\n/**\n * Provides form context to children so they can use the context-based useField.\n */\nexport function FormProvider<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n>({ form, children }: FormProviderProps<TSchema>): React.ReactNode {\n const formContext = form._getFormContext();\n return (\n <FormContext.Provider value={formContext}>\n {children}\n </FormContext.Provider>\n );\n}\n\n// ─── Context-based useField ──────────────────────────────────────────────────\n\n/**\n * Context-based useField — can be used inside a FormProvider.\n * For best IntelliSense, prefer `form.useField()` which infers field types from the schema.\n * When using this context-based version, specify the schema type explicitly:\n *\n * @example\n * ```tsx\n * const name = useField<typeof MySchema>((t) => t.name);\n * ```\n */\nexport function useField<\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TPropertySchema extends SchemaBuilder<any, any, any, any> = any\n>(\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, TPropertySchema, any>\n): UseFieldResult<InferType<TPropertySchema>> {\n const formContext = useContext(FormContext);\n if (!formContext) {\n throw new Error(\n 'useField must be used within a FormProvider. ' +\n 'Wrap your component tree with <FormProvider form={form}>.'\n );\n }\n return useFieldFromContext(formContext, forProperty) as UseFieldResult<\n InferType<TPropertySchema>\n >;\n}\n\n// ─── useFormSystem ───────────────────────────────────────────────────────────\n\n/**\n * Hook to access FormSystem configuration.\n */\nexport function useFormSystem(): FormSystemConfig {\n const config = useContext(FormSystemContext);\n if (!config) {\n throw new Error(\n 'useFormSystem must be used within a FormSystemProvider'\n );\n }\n return config;\n}\n\n// ─── Field Component ─────────────────────────────────────────────────────────\n\nexport type FieldProps<TSchema extends ObjectSchemaBuilder<any, any, any>> = {\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, any, any>;\n form: SchemaFormInstance<TSchema>;\n renderer?: FieldRenderer;\n /**\n * Rendering variant hint. Participates in renderer resolution:\n * the registry is checked for `\"type:variant\"` (e.g. `\"string:password\"`)\n * before falling back to the base `\"type\"` key.\n * Also forwarded to the renderer via `FieldRenderProps.variant`.\n */\n variant?: string;\n /** Visible label text forwarded to the renderer via `FieldRenderProps.label`. */\n label?: string;\n /** HTML `name` attribute forwarded to the renderer via `FieldRenderProps.name`. */\n name?: string;\n /**\n * Bag of extra renderer-specific props forwarded to the renderer via\n * `FieldRenderProps.fieldProps` (e.g. `placeholder`, `autoComplete`).\n */\n fieldProps?: Record<string, unknown>;\n};\n\n/**\n * UI-agnostic Field component.\n *\n * Resolves a renderer from:\n * 1. Explicit `renderer` prop\n * 2. FormSystemProvider registry — checked for `\"type:variant\"` first,\n * then for the base `\"type\"`\n *\n * All rendering hints (`variant`, `label`, `name`, `fieldProps`) are\n * forwarded to the resolved renderer via `FieldRenderProps`.\n *\n * @example\n * ```tsx\n * <Field forProperty={(t) => t.password} form={form}\n * variant=\"password\" label=\"Password\"\n * fieldProps={{ placeholder: \"Enter password\", autoComplete: \"current-password\" }} />\n * ```\n */\nexport function Field<TSchema extends ObjectSchemaBuilder<any, any, any>>({\n forProperty,\n form,\n renderer,\n variant,\n label,\n name,\n fieldProps\n}: FieldProps<TSchema>): React.ReactNode {\n const fieldResult = form.useField(forProperty);\n const systemConfig = useContext(FormSystemContext);\n\n const resolvedRenderer =\n renderer ?? resolveRenderer(systemConfig, fieldResult.schema, variant);\n\n if (!resolvedRenderer) {\n const schemaType = getSchemaType(fieldResult.schema);\n const tried = variant\n ? `\"${schemaType}:${variant}\" or \"${schemaType}\"`\n : `\"${schemaType}\"`;\n throw new Error(\n `No renderer found for schema type ${tried}. ` +\n 'Provide a renderer prop or configure one in FormSystemProvider.'\n );\n }\n\n return resolvedRenderer({\n value: fieldResult.value,\n initialValue: fieldResult.initialValue,\n dirty: fieldResult.dirty,\n touched: fieldResult.touched,\n error: fieldResult.error,\n validating: fieldResult.validating,\n onChange: fieldResult.onChange,\n onBlur: fieldResult.onBlur,\n setValue: fieldResult.setValue,\n schema: fieldResult.schema,\n variant,\n label,\n name,\n fieldProps\n });\n}\n","import type {\n ObjectSchemaBuilder,\n PropertyDescriptorInner,\n PropertyDescriptorTree\n} from '@cleverbrush/schema';\nimport { createContext } from 'react';\nimport type { FormStore } from './FormStore.js';\nimport type { FormSystemConfig, UseSchemaFormOptions } from './types.js';\n\n/**\n * Context for global form system configuration (renderers, etc).\n */\nexport const FormSystemContext = createContext<FormSystemConfig | null>(null);\n\n/**\n * Internal form context value — used to bridge useSchemaForm store\n * to the context-based useField and Field component.\n */\nexport type FormContextValue = {\n store: FormStore;\n descriptorTree: PropertyDescriptorTree<any, any>;\n schema: ObjectSchemaBuilder<any, any, any>;\n options: UseSchemaFormOptions;\n pathMap: Map<PropertyDescriptorInner<any, any, any>, string>;\n triggerValidation?: (markTouched: boolean) => Promise<unknown>;\n};\n\n/**\n * Context for per-form data — allows useField to work via context.\n */\nexport const FormContext = createContext<FormContextValue | null>(null);\n","import type {\n PropertyDescriptorInner,\n PropertyDescriptorTree,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\n\n/**\n * Builds a map from PropertyDescriptorInner identity → dot-separated path\n * by traversing the descriptor tree recursively.\n */\nexport function buildDescriptorPathMap(\n tree: PropertyDescriptorTree<any, any>,\n schema: ObjectSchemaBuilder<any, any, any>\n): Map<PropertyDescriptorInner<any, any, any>, string> {\n const map = new Map<PropertyDescriptorInner<any, any, any>, string>();\n const introspected = schema.introspect();\n if (!introspected.properties) return map;\n\n for (const propName of Object.keys(introspected.properties)) {\n const propDescriptor = (tree as any)[propName];\n if (\n !propDescriptor ||\n !(SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in propDescriptor)\n ) {\n continue;\n }\n const inner = propDescriptor[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ] as PropertyDescriptorInner<any, any, any>;\n map.set(inner, propName);\n\n // Recurse into nested objects\n const propSchema = introspected.properties[propName];\n if (propSchema instanceof ObjectSchemaBuilder) {\n const childMap = buildDescriptorPathMap(propDescriptor, propSchema);\n for (const [childInner, childPath] of childMap) {\n map.set(childInner, `${propName}.${childPath}`);\n }\n }\n }\n\n return map;\n}\n\n/**\n * Looks up the path for a given descriptor from the pre-built map.\n */\nexport function getDescriptorPath(\n inner: PropertyDescriptorInner<any, any, any>,\n pathMap: Map<PropertyDescriptorInner<any, any, any>, string>\n): string {\n return pathMap.get(inner) ?? '';\n}\n\n/**\n * Returns the schema type string (e.g. \"string\", \"number\", \"object\").\n */\nexport function getSchemaType(\n schema: SchemaBuilder<any, any, any, any>\n): string {\n const introspected = schema.introspect();\n return introspected?.type ?? 'unknown';\n}\n\n/**\n * Builds a selector function from a dot-separated path string.\n * The selector traverses the PropertyDescriptorTree by property access.\n * Example: \"customer.address.city\" → (tree) => tree.customer.address.city\n */\nexport function buildSelectorFromPath(path: string): (tree: any) => any {\n const parts = path.split('.');\n return (tree: any) => {\n let current = tree;\n for (const part of parts) {\n if (current == null) return undefined;\n current = current[part];\n }\n return current;\n };\n}\n\n/**\n * Checks whether a validation error path matches a field's expected error path.\n * Matches exact path, path with nested suffix, or path with validator suffix.\n * Example: isErrorPathMatch(\"$.user.name\", \"$.user.name\") → true\n * isErrorPathMatch(\"$.user.name($validators[0])\", \"$.user.name\") → true\n */\nexport function isErrorPathMatch(\n errPath: string,\n fieldErrorPath: string\n): boolean {\n return (\n errPath === fieldErrorPath ||\n errPath.startsWith(fieldErrorPath + '.') ||\n errPath.startsWith(fieldErrorPath + '(')\n );\n}\n\n/**\n * Ensures all nested object structures exist in the values object\n * by traversing the schema and creating empty objects where needed.\n * This prevents ObjectSchemaBuilder.validate() from throwing when\n * nested object properties are undefined.\n *\n * Example: ensureNestedStructure({}, OrderSchema)\n * → { customer: { address: {} } }\n */\nexport function ensureNestedStructure(\n values: any,\n schema: ObjectSchemaBuilder<any, any, any>\n): any {\n const introspected = schema.introspect();\n if (!introspected.properties) return values != null ? values : {};\n\n const result = values != null ? { ...values } : {};\n for (const key of Object.keys(introspected.properties)) {\n const propSchema = introspected.properties[key];\n if (propSchema instanceof ObjectSchemaBuilder) {\n result[key] = ensureNestedStructure(result[key], propSchema);\n }\n }\n return result;\n}\n","import type {\n InferType,\n PropertyDescriptor,\n PropertyDescriptorInner,\n PropertyDescriptorTree,\n SchemaBuilder,\n ValidationResult\n} from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore\n} from 'react';\nimport type { FormContextValue } from './contexts.js';\nimport type { FormStore } from './FormStore.js';\nimport { createFormStore } from './FormStore.js';\nimport {\n buildDescriptorPathMap,\n ensureNestedStructure,\n getSchemaType\n} from './helpers.js';\nimport type {\n FieldRenderer,\n FormSubmissionState,\n FormSubmitHandler,\n FormSubmitOptions,\n FormSubmitResult,\n FormSystemConfig,\n UseFieldResult,\n UseSchemaFormOptions\n} from './types.js';\n\n/** A stable form controller with reactive field and submission subscriptions. */\nexport type SchemaFormInstance<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n> = {\n useField: <TPropertySchema extends SchemaBuilder<any, any, any, any>>(\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, TPropertySchema, any>\n ) => UseFieldResult<InferType<TPropertySchema>>;\n submit: () => Promise<ValidationResult<InferType<TSchema>>>;\n validate: () => Promise<ValidationResult<InferType<TSchema>>>;\n /** Clear values, or establish supplied values as the new clean baseline. */\n reset: (values?: Partial<InferType<TSchema>>) => void;\n /** Read the current snapshot. Use setters rather than mutating it. */\n getValue: () => InferType<TSchema>;\n /** Shallow-merge values without marking fields touched. */\n setValue: (values: Partial<InferType<TSchema>>) => void;\n /** Validate and submit once; repeated calls while pending are ignored. */\n handleSubmit: <TData = void>(\n onValid: (\n values: InferType<TSchema>\n ) => FormSubmitResult<TData> | Promise<FormSubmitResult<TData>>,\n options?: FormSubmitOptions<InferType<TSchema>, TData>\n ) => FormSubmitHandler;\n /** @internal — Used by FormProvider and Field to access internal context. */\n _getFormContext: () => FormContextValue;\n} & FormSubmissionState;\n\n/** Bind a schema to independently subscribed fields and a stable form instance. */\nexport function useSchemaForm<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n>(\n schema: TSchema,\n options?: UseSchemaFormOptions\n): SchemaFormInstance<TSchema> {\n const storeRef = useRef<FormStore | null>(null);\n if (!storeRef.current) storeRef.current = createFormStore({});\n const store = storeRef.current;\n const descriptorTreeRef = useRef<PropertyDescriptorTree<\n TSchema,\n TSchema\n > | null>(null);\n if (!descriptorTreeRef.current) {\n descriptorTreeRef.current = ObjectSchemaBuilder.getPropertiesFor(\n schema\n ) as PropertyDescriptorTree<TSchema, TSchema>;\n }\n const descriptorTree = descriptorTreeRef.current;\n const pathMapRef = useRef<Map<\n PropertyDescriptorInner<any, any, any>,\n string\n > | null>(null);\n if (!pathMapRef.current) {\n pathMapRef.current = buildDescriptorPathMap(descriptorTree, schema);\n }\n const pathMap = pathMapRef.current;\n for (const [descriptor] of pathMap) {\n store.registerField(descriptor.toJsonPointer(), descriptor);\n }\n const schemaRef = useRef(schema);\n const optionsRef = useRef<UseSchemaFormOptions>({});\n optionsRef.current = { createMissingStructure: true, ...options };\n const mountedRef = useRef(true);\n const epochRef = useRef(0);\n const validationGenRef = useRef(0);\n const submissionLockRef = useRef(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelScheduledValidation = useCallback(() => {\n if (timerRef.current !== null) clearTimeout(timerRef.current);\n timerRef.current = null;\n }, []);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n epochRef.current++;\n validationGenRef.current++;\n cancelScheduledValidation();\n };\n }, [cancelScheduledValidation]);\n\n const runValidation = useCallback(\n async (\n markTouched: boolean\n ): Promise<ValidationResult<InferType<TSchema>>> => {\n cancelScheduledValidation();\n const gen = ++validationGenRef.current;\n const revision = store.getRevision();\n for (const path of store.getAllFieldPaths()) {\n store.updateFieldState(path, { validating: true });\n }\n const safeValues = ensureNestedStructure(\n store.getValues(),\n schemaRef.current\n );\n let result: ValidationResult<InferType<TSchema>>;\n try {\n result = (await schemaRef.current.validateAsync(safeValues, {\n doNotStopOnFirstError: true\n })) as ValidationResult<InferType<TSchema>>;\n } catch {\n result = { valid: false } as ValidationResult<\n InferType<TSchema>\n >;\n }\n // A value update invalidates validation immediately, even while the next\n // validation is only scheduled (debounced) and has not started yet.\n if (\n !mountedRef.current ||\n gen !== validationGenRef.current ||\n revision !== store.getRevision()\n )\n return result;\n\n for (const path of store.getAllFieldPaths()) {\n store.updateFieldState(path, {\n error: undefined,\n validating: false,\n ...(markTouched ? { touched: true } : {})\n });\n }\n const getErrorsFor = (result as any).getErrorsFor;\n if (typeof getErrorsFor === 'function') {\n for (const [descriptor] of pathMap) {\n const path = descriptor.toJsonPointer();\n const parts = path\n .slice(1)\n .split('/')\n .map(part =>\n part.replace(/~1/g, '/').replace(/~0/g, '~')\n );\n try {\n const field = getErrorsFor((tree: any) =>\n parts.reduce(\n (value: any, key) => value?.[key],\n tree\n )\n );\n if (\n Array.isArray(field?.errors) &&\n field.errors.length\n ) {\n store.updateFieldState(path, {\n error: field.errors[0]\n });\n }\n } catch {\n // A missing error selector must not prevent other fields\n // from receiving their validation results.\n }\n }\n }\n return result;\n },\n [cancelScheduledValidation, store, pathMap]\n );\n\n const validate = useCallback(() => runValidation(true), [runValidation]);\n const submit = useCallback(() => validate(), [validate]);\n const reset = useCallback(\n (values?: Partial<InferType<TSchema>>) => {\n epochRef.current++;\n validationGenRef.current++;\n cancelScheduledValidation();\n store.resetAll(values);\n },\n [store, cancelScheduledValidation]\n );\n const getValue = useCallback(\n (): InferType<TSchema> => store.getValues(),\n [store]\n );\n const setValue = useCallback(\n (values: Partial<InferType<TSchema>>) => {\n cancelScheduledValidation();\n store.setValues({ ...store.getValues(), ...values });\n },\n [store, cancelScheduledValidation]\n );\n const triggerValidation = useCallback(\n (markTouched: boolean) => {\n cancelScheduledValidation();\n const delay = optionsRef.current.validationDebounceMs;\n if (delay != null && delay > 0) {\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n void runValidation(markTouched);\n }, delay);\n return Promise.resolve();\n }\n return runValidation(markTouched);\n },\n [runValidation, cancelScheduledValidation]\n );\n\n const handleSubmit = useCallback(\n <TData>(\n onValid: (\n values: InferType<TSchema>\n ) => FormSubmitResult<TData> | Promise<FormSubmitResult<TData>>,\n submissionOptions?: FormSubmitOptions<InferType<TSchema>, TData>\n ): FormSubmitHandler =>\n async event => {\n event?.preventDefault();\n if (submissionLockRef.current || !mountedRef.current) return;\n submissionLockRef.current = true;\n const epoch = epochRef.current;\n const current = () =>\n mountedRef.current && epoch === epochRef.current;\n store.setSubmissionState({\n submitting: true,\n error: undefined\n });\n try {\n const revision = store.getRevision();\n const result = await runValidation(true);\n if (\n !current() ||\n revision !== store.getRevision() ||\n !result.valid ||\n result.object === undefined\n )\n return;\n const values = result.object;\n let outcome: FormSubmitResult<TData>;\n try {\n outcome = await onValid(values);\n } catch (error) {\n if (!current()) return;\n if (!submissionOptions?.onError) throw error;\n const message = await submissionOptions.onError(error);\n if (current())\n store.setSubmissionState({ error: message });\n return;\n }\n if (!current()) return;\n if (outcome && !outcome.ok) {\n store.setSubmissionState({ error: outcome.error });\n return;\n }\n // Errors from success callbacks (including redirects) propagate;\n // they are not translated as failures of a completed submission.\n await submissionOptions?.onSuccess?.(outcome?.data, values);\n } finally {\n submissionLockRef.current = false;\n if (mountedRef.current)\n store.setSubmissionState({ submitting: false });\n }\n },\n [runValidation, store]\n );\n\n const formContextValue = useMemo<FormContextValue>(\n () => ({\n store,\n descriptorTree,\n schema: schemaRef.current,\n pathMap,\n get options() {\n return optionsRef.current;\n },\n triggerValidation\n }),\n [store, descriptorTree, pathMap, triggerValidation]\n );\n const formContextRef = useRef(formContextValue);\n formContextRef.current = formContextValue;\n const _getFormContext = useCallback(() => formContextRef.current, []);\n const useFieldHook = useCallback(\n <TPropertySchema extends SchemaBuilder<any, any, any, any>>(\n selector: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, TPropertySchema, any>\n ): UseFieldResult<InferType<TPropertySchema>> =>\n useFieldFromContext(formContextRef.current, selector),\n []\n );\n const validateOnMountRef = useRef(options?.validateOnMount);\n useEffect(() => {\n if (validateOnMountRef.current) void runValidation(true);\n }, [runValidation]);\n\n // Subscribe to submission state without changing the controller's identity.\n useSyncExternalStore(\n store.subscribeGlobal,\n store.getSubmissionState,\n store.getSubmissionState\n );\n return useMemo(\n () => ({\n useField: useFieldHook,\n submit,\n validate,\n reset,\n getValue,\n setValue,\n handleSubmit,\n _getFormContext,\n get submitting() {\n return store.getSubmissionState().submitting;\n },\n get error() {\n return store.getSubmissionState().error;\n }\n }),\n [\n useFieldHook,\n submit,\n validate,\n reset,\n getValue,\n setValue,\n handleSubmit,\n _getFormContext,\n store\n ]\n );\n}\n\n/** Internal field binding shared by direct and context-based hooks. */\nexport function useFieldFromContext(\n formContext: FormContextValue,\n forProperty: (tree: any) => any,\n triggerValidation = formContext.triggerValidation\n): UseFieldResult {\n const { store, descriptorTree, options } = formContext;\n const inner =\n forProperty(descriptorTree)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR];\n const path = inner.toJsonPointer();\n store.registerField(path, inner);\n const subscribe = useCallback(\n (listener: () => void) => store.subscribe(path, listener),\n [store, path]\n );\n const getSnapshot = useCallback(\n () => store.getFieldState(path),\n [store, path]\n );\n const fieldState = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getSnapshot\n );\n const onChange = useCallback(\n (value: any) => {\n store.setFieldValue(\n path,\n value,\n options.createMissingStructure !== false\n );\n void triggerValidation?.(false);\n },\n [store, path, options.createMissingStructure, triggerValidation]\n );\n const onBlur = useCallback(\n () => store.updateFieldState(path, { touched: true }),\n [store, path]\n );\n return {\n ...fieldState,\n onChange,\n onBlur,\n setValue: onChange,\n schema: inner.getSchema()\n };\n}\n\n/** Resolve type:variant first, falling back to the base type. */\nexport function resolveRenderer(\n config: FormSystemConfig | null,\n schema: SchemaBuilder<any, any, any, any>,\n variant?: string\n): FieldRenderer | undefined {\n if (!config?.renderers) return undefined;\n const type = getSchemaType(schema);\n return (\n (variant ? config.renderers[`${type}:${variant}`] : undefined) ??\n config.renderers[type]\n );\n}\n","import { deepClone, deepEqual } from '@cleverbrush/deep';\nimport type { FieldState, FormSubmissionState } from './types.js';\n\ntype Binding = {\n getValue: (values: any) => { success: boolean; value?: any };\n setValue: (values: any, value: any, options: any) => boolean;\n};\n\n/** Internal immutable snapshots shared by all bindings of a form. */\nexport function createFormStore(initialValues: any) {\n let values = deepClone(initialValues ?? {});\n let baseline = values;\n let revision = 0;\n let submission: FormSubmissionState = {\n submitting: false,\n error: undefined\n };\n const bindings = new Map<string, Binding>();\n const fieldStates = new Map<string, FieldState>();\n const listeners = new Map<string, Set<() => void>>();\n const globalListeners = new Set<() => void>();\n\n function read(binding: Binding | undefined, source: any) {\n const result = binding?.getValue(source);\n return result?.success ? result.value : undefined;\n }\n function getFieldState(path: string): FieldState {\n if (!fieldStates.has(path)) {\n const binding = bindings.get(path);\n const value = read(binding, values);\n const initialValue = read(binding, baseline);\n fieldStates.set(path, {\n value,\n initialValue,\n dirty: !deepEqual(value, initialValue),\n touched: false,\n error: undefined,\n validating: false\n });\n }\n return fieldStates.get(path)!;\n }\n function registerField(path: string, binding: Binding) {\n if (!bindings.has(path)) bindings.set(path, binding);\n return getFieldState(path);\n }\n function notifyPath(path: string) {\n for (const listener of listeners.get(path) ?? []) listener();\n }\n function notifyAll() {\n for (const path of listeners.keys()) notifyPath(path);\n for (const listener of globalListeners) listener();\n }\n function updateFieldState(path: string, patch: Partial<FieldState>) {\n const current = getFieldState(path);\n if (\n Object.entries(patch).every(([key, value]) =>\n Object.is(current[key as keyof FieldState], value)\n )\n )\n return;\n fieldStates.set(path, { ...current, ...patch });\n notifyPath(path);\n }\n function syncFields(reset: boolean) {\n for (const [path, binding] of bindings) {\n const value = read(binding, values);\n const initialValue = read(binding, baseline);\n updateFieldState(path, {\n value,\n initialValue,\n dirty: !deepEqual(value, initialValue),\n validating: false,\n ...(reset ? { touched: false, error: undefined } : {})\n });\n }\n }\n function subscribe(path: string, listener: () => void): () => void {\n let set = listeners.get(path);\n if (!set) {\n set = new Set();\n listeners.set(path, set);\n }\n set.add(listener);\n return () => {\n set.delete(listener);\n if (set.size === 0) listeners.delete(path);\n };\n }\n function subscribeGlobal(listener: () => void): () => void {\n globalListeners.add(listener);\n return () => {\n globalListeners.delete(listener);\n };\n }\n function getSubmissionState() {\n return submission;\n }\n function setSubmissionState(patch: Partial<FormSubmissionState>) {\n const next = { ...submission, ...patch };\n if (\n next.submitting === submission.submitting &&\n next.error === submission.error\n )\n return;\n submission = next;\n for (const listener of globalListeners) listener();\n }\n function getValues() {\n return values;\n }\n function getRevision() {\n return revision;\n }\n function setValues(newValues: any) {\n values = deepClone(newValues ?? {});\n revision++;\n syncFields(false);\n }\n function setFieldValue(\n path: string,\n value: any,\n createMissingStructure: boolean\n ) {\n const next = deepClone(values);\n if (\n bindings\n .get(path)\n ?.setValue(next, deepClone(value), { createMissingStructure })\n ) {\n setValues(next);\n }\n }\n function resetAll(newInitialValues?: any) {\n values = deepClone(newInitialValues ?? {});\n baseline = values;\n revision++;\n syncFields(true);\n setSubmissionState({ error: undefined });\n }\n function getAllFieldPaths() {\n return Array.from(bindings.keys());\n }\n\n return {\n registerField,\n getFieldState,\n updateFieldState,\n subscribe,\n subscribeGlobal,\n getValues,\n setValues,\n setFieldValue,\n resetAll,\n getAllFieldPaths,\n getRevision,\n getSubmissionState,\n setSubmissionState,\n notifyAll\n };\n}\n\nexport type FormStore = ReturnType<typeof createFormStore>;\n","import type {\n InferType,\n ObjectSchemaBuilder,\n PropertyDescriptorTree,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport { SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema';\nimport type { ReactNode } from 'react';\nimport { Field, type FieldProps, FormSystemProvider } from './components.js';\nimport { getSchemaType } from './helpers.js';\nimport type { SchemaFormInstance } from './hooks.js';\nimport type { FieldRenderer } from './types.js';\n\ndeclare const rendererTypes: unique symbol;\n\n/** A renderer whose accepted values and custom props survive registration. */\nexport type TypedFieldRenderer<TValue, TProps> = FieldRenderer<\n TValue,\n TProps\n> & {\n readonly [rendererTypes]: { value: TValue; props: TProps };\n};\n\n/**\n * Attach type information without adding a UI dependency or runtime wrapper.\n * Include null in TValue when the renderer supports nullable fields.\n */\nexport function defineFieldRenderer<TValue, TProps = Record<string, never>>(\n renderer: FieldRenderer<TValue, TProps>\n): TypedFieldRenderer<TValue, TProps> {\n return renderer as TypedFieldRenderer<TValue, TProps>;\n}\n\nexport type TypedRendererRegistry = Readonly<\n Record<string, TypedFieldRenderer<any, any>>\n>;\n\ntype ValueOf<T> = T extends TypedFieldRenderer<infer V, any> ? V : never;\ntype PropsOf<T> = T extends TypedFieldRenderer<any, infer P> ? P : never;\ntype KindOf<T> = 0 extends 1 & T\n ? string\n : [NonNullable<T>] extends [string]\n ? 'string'\n : [NonNullable<T>] extends [number]\n ? 'number'\n : [NonNullable<T>] extends [boolean]\n ? 'boolean'\n : [NonNullable<T>] extends [Date]\n ? 'date'\n : [NonNullable<T>] extends [readonly unknown[]]\n ? 'array'\n : [NonNullable<T>] extends [object]\n ? 'object'\n : never;\ntype CustomProps<P> = {} extends P ? { fieldProps?: P } : { fieldProps: P };\ntype RendererChoice<R extends TypedRendererRegistry, V> = {\n [K in keyof R & string]: [Exclude<V, undefined>] extends [ValueOf<R[K]>]\n ? K extends `${KindOf<V>}:${infer Variant}`\n ? { variant: Variant } & CustomProps<PropsOf<R[K]>>\n : K extends KindOf<V>\n ? { variant?: undefined } & CustomProps<PropsOf<R[K]>>\n : never\n : never;\n}[keyof R & string];\n\ntype FieldDescriptor = {\n readonly [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {\n getSchema: () => SchemaBuilder<any, any, any, any>;\n };\n};\ntype DescriptorValue<D extends FieldDescriptor> = InferType<\n ReturnType<D[typeof SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]['getSchema']>\n>;\n\n/** Props are inferred from the selected schema property and registered variant. */\nexport type TypedFieldProps<\n R extends TypedRendererRegistry,\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TDescriptor extends FieldDescriptor\n> = {\n form: SchemaFormInstance<TSchema>;\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => TDescriptor;\n label?: string;\n name?: string;\n} & RendererChoice<R, NoInfer<DescriptorValue<TDescriptor>>>;\n\n/** Named callable type also supports exporting system.Field from UI packages. */\nexport type TypedFieldComponent<R extends TypedRendererRegistry> = <\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TDescriptor extends FieldDescriptor\n>(\n props: TypedFieldProps<R, TSchema, TDescriptor>\n) => ReactNode;\n\n/** Named return type keeps exported consumer registries declaration-safe. */\nexport type TypedFormSystem<R extends TypedRendererRegistry> = {\n Field: TypedFieldComponent<R>;\n Provider: (props: { children: ReactNode }) => ReactNode;\n renderers: Readonly<R>;\n};\n\n/**\n * Create a typed Field and a provider for an application's renderer registry.\n * The typed Field resolves from this factory's closed registry, so an untyped\n * ancestor provider cannot silently replace a renderer with incompatible props.\n * The Provider also configures legacy Field consumers and supports nesting.\n * Extend a system with createFormSystem({ renderers: { ...system.renderers,\n * 'string:custom': customRenderer } }). Existing global APIs remain available.\n */\nexport function createFormSystem<\n const R extends TypedRendererRegistry\n>(config: { renderers: R }): TypedFormSystem<R> {\n const renderers = Object.freeze({ ...config.renderers });\n function TypedField<\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TDescriptor extends FieldDescriptor\n >(props: TypedFieldProps<R, TSchema, TDescriptor>): ReactNode {\n const descriptor = props.forProperty(\n props.form._getFormContext()\n .descriptorTree as PropertyDescriptorTree<TSchema, TSchema>\n );\n const type = getSchemaType(\n descriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR].getSchema()\n );\n const key = props.variant ? `${type}:${props.variant}` : type;\n const renderer = renderers[key];\n if (!renderer)\n throw new Error(\n `No renderer registered for \"${key}\" in this form system.`\n );\n // Field's legacy signature erases descriptor parent types. The factory\n // has already checked the selected value and renderer-specific props.\n return (\n <Field\n {...(props as unknown as FieldProps<TSchema>)}\n renderer={renderer}\n />\n );\n }\n function Provider({ children }: { children: ReactNode }): ReactNode {\n return (\n <FormSystemProvider renderers={renderers}>\n {children}\n </FormSystemProvider>\n );\n }\n return { Field: TypedField, Provider, renderers };\n}\n"],"mappings":"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","names":["useContext","useMemo","createContext","FormSystemContext","FormContext","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","buildDescriptorPathMap","tree","schema","map","introspected","propName","propDescriptor","inner","propSchema","childMap","childInner","childPath","getSchemaType","schema","ensureNestedStructure","values","schema","introspected","result","key","propSchema","ObjectSchemaBuilder","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","useCallback","useEffect","useMemo","useRef","useSyncExternalStore","deepClone","deepEqual","createFormStore","initialValues","values","baseline","revision","submission","bindings","fieldStates","listeners","globalListeners","read","binding","source","result","getFieldState","path","value","initialValue","registerField","notifyPath","listener","notifyAll","updateFieldState","patch","current","key","syncFields","reset","subscribe","set","subscribeGlobal","getSubmissionState","setSubmissionState","next","getValues","getRevision","setValues","newValues","setFieldValue","createMissingStructure","resetAll","newInitialValues","getAllFieldPaths","useSchemaForm","schema","options","storeRef","useRef","createFormStore","store","descriptorTreeRef","ObjectSchemaBuilder","descriptorTree","pathMapRef","buildDescriptorPathMap","pathMap","descriptor","schemaRef","optionsRef","mountedRef","epochRef","validationGenRef","submissionLockRef","timerRef","cancelScheduledValidation","useCallback","useEffect","runValidation","markTouched","gen","revision","path","safeValues","ensureNestedStructure","result","getErrorsFor","parts","part","field","tree","value","key","validate","submit","reset","values","getValue","setValue","triggerValidation","delay","handleSubmit","onValid","submissionOptions","event","epoch","current","outcome","error","message","formContextValue","useMemo","formContextRef","_getFormContext","useFieldHook","selector","useFieldFromContext","validateOnMountRef","useSyncExternalStore","formContext","forProperty","inner","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","subscribe","listener","getSnapshot","fieldState","onChange","onBlur","resolveRenderer","config","variant","type","getSchemaType","jsx","FormSystemProvider","renderers","config","children","parentConfig","useContext","FormSystemContext","resolvedConfig","useMemo","current","FormProvider","form","formContext","FormContext","useField","forProperty","useFieldFromContext","useFormSystem","Field","renderer","variant","label","name","fieldProps","fieldResult","systemConfig","resolvedRenderer","resolveRenderer","schemaType","getSchemaType","tried","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","jsx","defineFieldRenderer","renderer","createFormSystem","config","renderers","TypedField","props","descriptor","type","getSchemaType","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","key","Field","Provider","children","FormSystemProvider"]}
|
|
1
|
+
{"version":3,"sources":["../src/components.tsx","../src/contexts.ts","../src/helpers.ts","../src/hooks.ts","../src/FormStore.ts","../src/system.tsx"],"sourcesContent":["import type {\n InferType,\n ObjectSchemaBuilder,\n PropertyDescriptor,\n PropertyDescriptorTree,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport type React from 'react';\nimport { useContext, useMemo } from 'react';\nimport { FormContext, FormSystemContext } from './contexts.js';\nimport { getSchemaType } from './helpers.js';\nimport type { SchemaFormInstance } from './hooks.js';\nimport { resolveRenderer, useFieldFromContext } from './hooks.js';\nimport type {\n FieldRenderer,\n FormSystemConfig,\n UseFieldResult\n} from './types.js';\n\n// ─── FormSystemProvider ──────────────────────────────────────────────────────\n\nexport type FormSystemProviderProps = {\n /**\n * Renderer registry mapping schema types to renderer functions.\n * Example: `{ string: (props) => <input .../>, number: (props) => <input type=\"number\" .../> }`\n */\n renderers?: Record<string, FieldRenderer>;\n /**\n * Full configuration object (for future extensibility).\n * If both `renderers` and `config.renderers` are provided, `renderers` takes precedence.\n */\n config?: FormSystemConfig;\n children: React.ReactNode;\n};\n\n/**\n * Provides global renderer configuration via React Context.\n * Supports nesting — inner provider overrides outer.\n *\n * @example\n * ```tsx\n * <FormSystemProvider renderers={htmlRenderers}>\n * <App />\n * </FormSystemProvider>\n * ```\n */\nexport function FormSystemProvider({\n renderers,\n config,\n children\n}: FormSystemProviderProps): React.ReactNode {\n const parentConfig = useContext(FormSystemContext);\n\n const resolvedConfig: FormSystemConfig = useMemo(() => {\n const current: FormSystemConfig = {\n ...config,\n renderers: {\n ...config?.renderers,\n ...renderers\n }\n };\n if (!parentConfig) return current;\n return {\n ...parentConfig,\n ...current,\n renderers: {\n ...parentConfig.renderers,\n ...current.renderers\n }\n };\n }, [parentConfig, config, renderers]);\n\n return (\n <FormSystemContext.Provider value={resolvedConfig}>\n {children}\n </FormSystemContext.Provider>\n );\n}\n\n// ─── FormProvider ────────────────────────────────────────────────────────────\n\nexport type FormProviderProps<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n> = {\n form: SchemaFormInstance<TSchema>;\n children: React.ReactNode;\n};\n\n/**\n * Provides form context to children so they can use the context-based useField.\n */\nexport function FormProvider<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n>({ form, children }: FormProviderProps<TSchema>): React.ReactNode {\n const formContext = form._getFormContext();\n return (\n <FormContext.Provider value={formContext}>\n {children}\n </FormContext.Provider>\n );\n}\n\n// ─── Context-based useField ──────────────────────────────────────────────────\n\n/**\n * Context-based useField — can be used inside a FormProvider.\n * For best IntelliSense, prefer `form.useField()` which infers field types from the schema.\n * When using this context-based version, specify the schema type explicitly:\n *\n * @example\n * ```tsx\n * const name = useField<typeof MySchema>((t) => t.name);\n * ```\n */\nexport function useField<\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TPropertySchema extends SchemaBuilder<any, any, any, any> = any\n>(\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, TPropertySchema, any>\n): UseFieldResult<InferType<TPropertySchema>> {\n const formContext = useContext(FormContext);\n if (!formContext) {\n throw new Error(\n 'useField must be used within a FormProvider. ' +\n 'Wrap your component tree with <FormProvider form={form}>.'\n );\n }\n return useFieldFromContext(formContext, forProperty) as UseFieldResult<\n InferType<TPropertySchema>\n >;\n}\n\n// ─── useFormSystem ───────────────────────────────────────────────────────────\n\n/**\n * Hook to access FormSystem configuration.\n */\nexport function useFormSystem(): FormSystemConfig {\n const config = useContext(FormSystemContext);\n if (!config) {\n throw new Error(\n 'useFormSystem must be used within a FormSystemProvider'\n );\n }\n return config;\n}\n\n// ─── Field Component ─────────────────────────────────────────────────────────\n\nexport type FieldProps<TSchema extends ObjectSchemaBuilder<any, any, any>> = {\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, any, any>;\n form: SchemaFormInstance<TSchema>;\n renderer?: FieldRenderer;\n /**\n * Rendering variant hint. Participates in renderer resolution:\n * the registry is checked for `\"type:variant\"` (e.g. `\"string:password\"`)\n * before falling back to the base `\"type\"` key.\n * Also forwarded to the renderer via `FieldRenderProps.variant`.\n */\n variant?: string;\n /** Visible label text forwarded to the renderer via `FieldRenderProps.label`. */\n label?: string;\n /** HTML `name` attribute forwarded to the renderer via `FieldRenderProps.name`. */\n name?: string;\n /**\n * Bag of extra renderer-specific props forwarded to the renderer via\n * `FieldRenderProps.fieldProps` (e.g. `placeholder`, `autoComplete`).\n */\n fieldProps?: Record<string, unknown>;\n};\n\n/**\n * UI-agnostic Field component.\n *\n * Resolves a renderer from:\n * 1. Explicit `renderer` prop\n * 2. FormSystemProvider registry — checked for `\"type:variant\"` first,\n * then for the base `\"type\"`\n *\n * All rendering hints (`variant`, `label`, `name`, `fieldProps`) are\n * forwarded to the resolved renderer via `FieldRenderProps`.\n *\n * @example\n * ```tsx\n * <Field forProperty={(t) => t.password} form={form}\n * variant=\"password\" label=\"Password\"\n * fieldProps={{ placeholder: \"Enter password\", autoComplete: \"current-password\" }} />\n * ```\n */\nexport function Field<TSchema extends ObjectSchemaBuilder<any, any, any>>({\n forProperty,\n form,\n renderer,\n variant,\n label,\n name,\n fieldProps\n}: FieldProps<TSchema>): React.ReactNode {\n const fieldResult = form.useField(forProperty);\n const systemConfig = useContext(FormSystemContext);\n\n const resolvedRenderer =\n renderer ?? resolveRenderer(systemConfig, fieldResult.schema, variant);\n\n if (!resolvedRenderer) {\n const schemaType = getSchemaType(fieldResult.schema);\n const tried = variant\n ? `\"${schemaType}:${variant}\" or \"${schemaType}\"`\n : `\"${schemaType}\"`;\n throw new Error(\n `No renderer found for schema type ${tried}. ` +\n 'Provide a renderer prop or configure one in FormSystemProvider.'\n );\n }\n\n return resolvedRenderer({\n value: fieldResult.value,\n initialValue: fieldResult.initialValue,\n dirty: fieldResult.dirty,\n touched: fieldResult.touched,\n error: fieldResult.error,\n validating: fieldResult.validating,\n onChange: fieldResult.onChange,\n onBlur: fieldResult.onBlur,\n setValue: fieldResult.setValue,\n schema: fieldResult.schema,\n variant,\n label,\n name,\n fieldProps\n });\n}\n","import type {\n ObjectSchemaBuilder,\n PropertyDescriptorInner,\n PropertyDescriptorTree\n} from '@cleverbrush/schema';\nimport { createContext } from 'react';\nimport type { FormStore } from './FormStore.js';\nimport type { FormSystemConfig, UseSchemaFormOptions } from './types.js';\n\n/**\n * Context for global form system configuration (renderers, etc).\n */\nexport const FormSystemContext = createContext<FormSystemConfig | null>(null);\n\n/**\n * Internal form context value — used to bridge useSchemaForm store\n * to the context-based useField and Field component.\n */\nexport type FormContextValue = {\n store: FormStore;\n descriptorTree: PropertyDescriptorTree<any, any>;\n schema: ObjectSchemaBuilder<any, any, any>;\n options: UseSchemaFormOptions;\n pathMap: Map<PropertyDescriptorInner<any, any, any>, string>;\n triggerValidation?: (markTouched: boolean) => Promise<unknown>;\n};\n\n/**\n * Context for per-form data — allows useField to work via context.\n */\nexport const FormContext = createContext<FormContextValue | null>(null);\n","import type {\n PropertyDescriptorInner,\n PropertyDescriptorTree,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\n\n/**\n * Builds a map from PropertyDescriptorInner identity → dot-separated path\n * by traversing the descriptor tree recursively.\n */\nexport function buildDescriptorPathMap(\n tree: PropertyDescriptorTree<any, any>,\n schema: ObjectSchemaBuilder<any, any, any>\n): Map<PropertyDescriptorInner<any, any, any>, string> {\n const map = new Map<PropertyDescriptorInner<any, any, any>, string>();\n const introspected = schema.introspect();\n if (!introspected.properties) return map;\n\n for (const propName of Object.keys(introspected.properties)) {\n const propDescriptor = (tree as any)[propName];\n if (\n !propDescriptor ||\n !(SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in propDescriptor)\n ) {\n continue;\n }\n const inner = propDescriptor[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ] as PropertyDescriptorInner<any, any, any>;\n map.set(inner, propName);\n\n // Recurse into nested objects\n const propSchema = introspected.properties[propName];\n if (propSchema instanceof ObjectSchemaBuilder) {\n const childMap = buildDescriptorPathMap(propDescriptor, propSchema);\n for (const [childInner, childPath] of childMap) {\n map.set(childInner, `${propName}.${childPath}`);\n }\n }\n }\n\n return map;\n}\n\n/**\n * Looks up the path for a given descriptor from the pre-built map.\n */\nexport function getDescriptorPath(\n inner: PropertyDescriptorInner<any, any, any>,\n pathMap: Map<PropertyDescriptorInner<any, any, any>, string>\n): string {\n return pathMap.get(inner) ?? '';\n}\n\n/**\n * Returns the schema type string (e.g. \"string\", \"number\", \"object\").\n */\nexport function getSchemaType(\n schema: SchemaBuilder<any, any, any, any>\n): string {\n const introspected = schema.introspect();\n return introspected?.type ?? 'unknown';\n}\n\n/**\n * Builds a selector function from a dot-separated path string.\n * The selector traverses the PropertyDescriptorTree by property access.\n * Example: \"customer.address.city\" → (tree) => tree.customer.address.city\n */\nexport function buildSelectorFromPath(path: string): (tree: any) => any {\n const parts = path.split('.');\n return (tree: any) => {\n let current = tree;\n for (const part of parts) {\n if (current == null) return undefined;\n current = current[part];\n }\n return current;\n };\n}\n\n/**\n * Checks whether a validation error path matches a field's expected error path.\n * Matches exact path, path with nested suffix, or path with validator suffix.\n * Example: isErrorPathMatch(\"$.user.name\", \"$.user.name\") → true\n * isErrorPathMatch(\"$.user.name($validators[0])\", \"$.user.name\") → true\n */\nexport function isErrorPathMatch(\n errPath: string,\n fieldErrorPath: string\n): boolean {\n return (\n errPath === fieldErrorPath ||\n errPath.startsWith(fieldErrorPath + '.') ||\n errPath.startsWith(fieldErrorPath + '(')\n );\n}\n\n/**\n * Ensures all nested object structures exist in the values object\n * by traversing the schema and creating empty objects where needed.\n * This prevents ObjectSchemaBuilder.validate() from throwing when\n * nested object properties are undefined.\n *\n * Example: ensureNestedStructure({}, OrderSchema)\n * → { customer: { address: {} } }\n */\nexport function ensureNestedStructure(\n values: any,\n schema: ObjectSchemaBuilder<any, any, any>\n): any {\n const introspected = schema.introspect();\n if (!introspected.properties) return values != null ? values : {};\n\n const result = values != null ? { ...values } : {};\n for (const key of Object.keys(introspected.properties)) {\n const propSchema = introspected.properties[key];\n if (propSchema instanceof ObjectSchemaBuilder) {\n result[key] = ensureNestedStructure(result[key], propSchema);\n }\n }\n return result;\n}\n","import type {\n InferType,\n PropertyDescriptor,\n PropertyDescriptorInner,\n PropertyDescriptorTree,\n SchemaBuilder,\n ValidationResult\n} from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore\n} from 'react';\nimport type { FormContextValue } from './contexts.js';\nimport type { FormStore } from './FormStore.js';\nimport { createFormStore } from './FormStore.js';\nimport {\n buildDescriptorPathMap,\n ensureNestedStructure,\n getSchemaType\n} from './helpers.js';\nimport type {\n FieldRenderer,\n FormIssue,\n FormSubmissionState,\n FormSubmitHandler,\n FormSubmitOptions,\n FormSubmitResult,\n FormSystemConfig,\n UseFieldResult,\n UseSchemaFormOptions\n} from './types.js';\n\n/** A stable form controller with reactive field and submission subscriptions. */\nexport type SchemaFormInstance<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n> = {\n useField: <TPropertySchema extends SchemaBuilder<any, any, any, any>>(\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, TPropertySchema, any>\n ) => UseFieldResult<InferType<TPropertySchema>>;\n submit: () => Promise<ValidationResult<InferType<TSchema>>>;\n validate: () => Promise<ValidationResult<InferType<TSchema>>>;\n /** Clear values, or establish supplied values as the new clean baseline. */\n reset: (values?: Partial<InferType<TSchema>>) => void;\n /** Read the current snapshot. Use setters rather than mutating it. */\n getValue: () => InferType<TSchema>;\n /** Shallow-merge values without marking fields touched. */\n setValue: (values: Partial<InferType<TSchema>>) => void;\n /**\n * Replace external issues without changing values. Empty pointers and unbound\n * fields appear in form.error; bound fields receive the first issue and are\n * marked touched. Prefer returned submission issues for async race protection.\n * Pass [] to clear external issues without clearing local validation errors.\n */\n setIssues: (issues: readonly FormIssue[]) => void;\n /** Validate and submit once; repeated calls while pending are ignored. */\n handleSubmit: <TData = void>(\n onValid: (\n values: InferType<TSchema>\n ) => FormSubmitResult<TData> | Promise<FormSubmitResult<TData>>,\n options?: FormSubmitOptions<InferType<TSchema>, TData>\n ) => FormSubmitHandler;\n /** @internal — Used by FormProvider and Field to access internal context. */\n _getFormContext: () => FormContextValue;\n} & FormSubmissionState;\n\n/** Bind a schema to independently subscribed fields and a stable form instance. */\nexport function useSchemaForm<\n TSchema extends ObjectSchemaBuilder<any, any, any>\n>(\n schema: TSchema,\n options?: UseSchemaFormOptions\n): SchemaFormInstance<TSchema> {\n const storeRef = useRef<FormStore | null>(null);\n if (!storeRef.current) storeRef.current = createFormStore({});\n const store = storeRef.current;\n const descriptorTreeRef = useRef<PropertyDescriptorTree<\n TSchema,\n TSchema\n > | null>(null);\n if (!descriptorTreeRef.current) {\n descriptorTreeRef.current = ObjectSchemaBuilder.getPropertiesFor(\n schema\n ) as PropertyDescriptorTree<TSchema, TSchema>;\n }\n const descriptorTree = descriptorTreeRef.current;\n const pathMapRef = useRef<Map<\n PropertyDescriptorInner<any, any, any>,\n string\n > | null>(null);\n if (!pathMapRef.current) {\n pathMapRef.current = buildDescriptorPathMap(descriptorTree, schema);\n }\n const pathMap = pathMapRef.current;\n for (const [descriptor] of pathMap) {\n store.registerField(descriptor.toJsonPointer(), descriptor);\n }\n const schemaRef = useRef(schema);\n const optionsRef = useRef<UseSchemaFormOptions>({});\n optionsRef.current = { createMissingStructure: true, ...options };\n const mountedRef = useRef(true);\n const epochRef = useRef(0);\n const validationGenRef = useRef(0);\n const submissionLockRef = useRef(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelScheduledValidation = useCallback(() => {\n if (timerRef.current !== null) clearTimeout(timerRef.current);\n timerRef.current = null;\n }, []);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n epochRef.current++;\n validationGenRef.current++;\n cancelScheduledValidation();\n };\n }, [cancelScheduledValidation]);\n\n const runValidation = useCallback(\n async (\n markTouched: boolean\n ): Promise<ValidationResult<InferType<TSchema>>> => {\n cancelScheduledValidation();\n const gen = ++validationGenRef.current;\n const revision = store.getRevision();\n for (const path of store.getAllFieldPaths()) {\n store.updateFieldState(path, { validating: true });\n }\n const safeValues = ensureNestedStructure(\n store.getValues(),\n schemaRef.current\n );\n let result: ValidationResult<InferType<TSchema>>;\n try {\n result = (await schemaRef.current.validateAsync(safeValues, {\n doNotStopOnFirstError: true\n })) as ValidationResult<InferType<TSchema>>;\n } catch {\n result = { valid: false } as ValidationResult<\n InferType<TSchema>\n >;\n }\n // A value update invalidates validation immediately, even while the next\n // validation is only scheduled (debounced) and has not started yet.\n if (\n !mountedRef.current ||\n gen !== validationGenRef.current ||\n revision !== store.getRevision()\n )\n return result;\n\n for (const path of store.getAllFieldPaths()) {\n store.updateFieldState(path, {\n error: undefined,\n validating: false,\n ...(markTouched ? { touched: true } : {})\n });\n }\n const getErrorsFor = (result as any).getErrorsFor;\n if (typeof getErrorsFor === 'function') {\n for (const path of store.getAllFieldPaths()) {\n const parts = path\n .slice(1)\n .split('/')\n .map(part =>\n part.replace(/~1/g, '/').replace(/~0/g, '~')\n );\n try {\n const field = getErrorsFor((tree: any) =>\n parts.reduce(\n (value: any, key) => value?.[key],\n tree\n )\n );\n if (\n Array.isArray(field?.errors) &&\n field.errors.length\n ) {\n store.updateFieldState(path, {\n error: field.errors[0]\n });\n }\n } catch {\n // A missing error selector must not prevent other fields\n // from receiving their validation results.\n }\n }\n }\n return result;\n },\n [cancelScheduledValidation, store]\n );\n\n const validate = useCallback(() => runValidation(true), [runValidation]);\n const submit = useCallback(() => validate(), [validate]);\n const reset = useCallback(\n (values?: Partial<InferType<TSchema>>) => {\n epochRef.current++;\n validationGenRef.current++;\n cancelScheduledValidation();\n store.resetAll(values);\n },\n [store, cancelScheduledValidation]\n );\n const getValue = useCallback(\n (): InferType<TSchema> => store.getValues(),\n [store]\n );\n const setValue = useCallback(\n (values: Partial<InferType<TSchema>>) => {\n cancelScheduledValidation();\n store.setValues({ ...store.getValues(), ...values });\n },\n [store, cancelScheduledValidation]\n );\n const triggerValidation = useCallback(\n (markTouched: boolean) => {\n cancelScheduledValidation();\n const delay = optionsRef.current.validationDebounceMs;\n if (delay != null && delay > 0) {\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n void runValidation(markTouched);\n }, delay);\n return Promise.resolve();\n }\n return runValidation(markTouched);\n },\n [runValidation, cancelScheduledValidation]\n );\n\n const handleSubmit = useCallback(\n <TData>(\n onValid: (\n values: InferType<TSchema>\n ) => FormSubmitResult<TData> | Promise<FormSubmitResult<TData>>,\n submissionOptions?: FormSubmitOptions<InferType<TSchema>, TData>\n ): FormSubmitHandler =>\n async event => {\n event?.preventDefault();\n if (submissionLockRef.current || !mountedRef.current) return;\n submissionLockRef.current = true;\n const epoch = epochRef.current;\n const current = () =>\n mountedRef.current && epoch === epochRef.current;\n store.setIssues([]);\n store.setSubmissionState({\n submitting: true,\n error: undefined\n });\n try {\n const revision = store.getRevision();\n const result = await runValidation(true);\n if (\n !current() ||\n revision !== store.getRevision() ||\n !result.valid ||\n result.object === undefined\n )\n return;\n const values = result.object;\n let outcome: FormSubmitResult<TData>;\n try {\n outcome = await onValid(values);\n } catch (error) {\n if (!current()) return;\n if (!submissionOptions?.onError) throw error;\n const message = await submissionOptions.onError(error);\n if (current())\n store.setSubmissionState({ error: message });\n return;\n }\n if (!current()) return;\n if (outcome && !outcome.ok) {\n store.setSubmissionFailure(\n outcome.error,\n revision === store.getRevision()\n ? outcome.issues\n : undefined\n );\n return;\n }\n // Errors from success callbacks (including redirects) propagate;\n // they are not translated as failures of a completed submission.\n await submissionOptions?.onSuccess?.(outcome?.data, values);\n } finally {\n submissionLockRef.current = false;\n if (mountedRef.current)\n store.setSubmissionState({ submitting: false });\n }\n },\n [runValidation, store]\n );\n\n const formContextValue = useMemo<FormContextValue>(\n () => ({\n store,\n descriptorTree,\n schema: schemaRef.current,\n pathMap,\n get options() {\n return optionsRef.current;\n },\n triggerValidation\n }),\n [store, descriptorTree, pathMap, triggerValidation]\n );\n const formContextRef = useRef(formContextValue);\n formContextRef.current = formContextValue;\n const _getFormContext = useCallback(() => formContextRef.current, []);\n const useFieldHook = useCallback(\n <TPropertySchema extends SchemaBuilder<any, any, any, any>>(\n selector: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => PropertyDescriptor<TSchema, TPropertySchema, any>\n ): UseFieldResult<InferType<TPropertySchema>> =>\n useFieldFromContext(formContextRef.current, selector),\n []\n );\n const validateOnMountRef = useRef(options?.validateOnMount);\n useEffect(() => {\n if (validateOnMountRef.current) void runValidation(true);\n }, [runValidation]);\n\n // Subscribe to submission state without changing the controller's identity.\n useSyncExternalStore(\n store.subscribeGlobal,\n store.getSubmissionState,\n store.getSubmissionState\n );\n return useMemo(\n () => ({\n useField: useFieldHook,\n submit,\n validate,\n reset,\n getValue,\n setValue,\n setIssues: store.setIssues,\n handleSubmit,\n _getFormContext,\n get submitting() {\n return store.getSubmissionState().submitting;\n },\n get error() {\n return store.getSubmissionState().error;\n }\n }),\n [\n useFieldHook,\n submit,\n validate,\n reset,\n getValue,\n setValue,\n handleSubmit,\n _getFormContext,\n store\n ]\n );\n}\n\n/** Internal field binding shared by direct and context-based hooks. */\nexport function useFieldFromContext(\n formContext: FormContextValue,\n forProperty: (tree: any) => any,\n triggerValidation = formContext.triggerValidation\n): UseFieldResult {\n const { store, descriptorTree, options } = formContext;\n const inner =\n forProperty(descriptorTree)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR];\n const path = inner.toJsonPointer();\n store.registerField(path, inner);\n const subscribe = useCallback(\n (listener: () => void) => store.subscribe(path, listener),\n [store, path]\n );\n const getSnapshot = useCallback(\n () => store.getFieldState(path),\n [store, path]\n );\n const fieldState = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getSnapshot\n );\n const onChange = useCallback(\n (value: any) => {\n store.setFieldValue(\n path,\n value,\n options.createMissingStructure !== false\n );\n void triggerValidation?.(false);\n },\n [store, path, options.createMissingStructure, triggerValidation]\n );\n const onBlur = useCallback(\n () => store.updateFieldState(path, { touched: true }),\n [store, path]\n );\n return {\n ...fieldState,\n onChange,\n onBlur,\n setValue: onChange,\n schema: inner.getSchema()\n };\n}\n\n/** Resolve type:variant first, falling back to the base type. */\nexport function resolveRenderer(\n config: FormSystemConfig | null,\n schema: SchemaBuilder<any, any, any, any>,\n variant?: string\n): FieldRenderer | undefined {\n if (!config?.renderers) return undefined;\n const type = getSchemaType(schema);\n return (\n (variant ? config.renderers[`${type}:${variant}`] : undefined) ??\n config.renderers[type]\n );\n}\n","import { deepClone, deepEqual } from '@cleverbrush/deep';\nimport type { FieldState, FormIssue, FormSubmissionState } from './types.js';\n\ntype Binding = {\n getValue: (values: any) => { success: boolean; value?: any };\n setValue: (values: any, value: any, options: any) => boolean;\n};\n\n/** Internal immutable snapshots shared by all bindings of a form. */\nexport function createFormStore(initialValues: any) {\n let values = deepClone(initialValues ?? {});\n let baseline = values;\n let revision = 0;\n let issues: readonly FormIssue[] = [];\n let submissionError: string | undefined;\n const localErrors = new Map<string, string | undefined>();\n let submission: FormSubmissionState = {\n submitting: false,\n error: undefined\n };\n const bindings = new Map<string, Binding>();\n const fieldStates = new Map<string, FieldState>();\n const listeners = new Map<string, Set<() => void>>();\n const globalListeners = new Set<() => void>();\n let batching = 0;\n const pendingPaths = new Set<string>();\n let pendingGlobal = false;\n function batch(callback: () => void) {\n batching++;\n try {\n callback();\n } finally {\n batching--;\n if (batching === 0) {\n const paths = [...pendingPaths];\n pendingPaths.clear();\n const global = pendingGlobal;\n pendingGlobal = false;\n for (const path of paths) notifyPath(path);\n if (global) notifyGlobal();\n }\n }\n }\n function notifyGlobal() {\n if (batching) {\n pendingGlobal = true;\n return;\n }\n for (const listener of globalListeners) listener();\n }\n\n function read(binding: Binding | undefined, source: any) {\n const result = binding?.getValue(source);\n return result?.success ? result.value : undefined;\n }\n function getFieldState(path: string): FieldState {\n if (!fieldStates.has(path)) {\n const binding = bindings.get(path);\n const value = read(binding, values);\n const initialValue = read(binding, baseline);\n fieldStates.set(path, {\n value,\n initialValue,\n dirty: !deepEqual(value, initialValue),\n touched: issues.some(issue => issue.pointer === path),\n error: externalError(path),\n validating: false\n });\n }\n return fieldStates.get(path)!;\n }\n function registerField(path: string, binding: Binding) {\n if (!bindings.has(path)) bindings.set(path, binding);\n return getFieldState(path);\n }\n function notifyPath(path: string) {\n if (batching) {\n pendingPaths.add(path);\n return;\n }\n for (const listener of listeners.get(path) ?? []) listener();\n }\n function notifyAll() {\n for (const path of listeners.keys()) notifyPath(path);\n notifyGlobal();\n }\n function commitFieldState(path: string, patch: Partial<FieldState>) {\n const current = getFieldState(path);\n if (\n Object.entries(patch).every(([key, value]) =>\n Object.is(current[key as keyof FieldState], value)\n )\n )\n return;\n fieldStates.set(path, { ...current, ...patch });\n notifyPath(path);\n }\n function externalError(path: string) {\n return path === ''\n ? undefined\n : issues.find(issue => issue.pointer === path)?.detail;\n }\n function updateFieldState(path: string, patch: Partial<FieldState>) {\n if (Object.hasOwn(patch, 'error')) localErrors.set(path, patch.error);\n commitFieldState(path, {\n ...patch,\n error: externalError(path) ?? localErrors.get(path)\n });\n }\n function setIssues(next: readonly FormIssue[]) {\n batch(() => {\n // Copy caller-owned data; later mutations must not change store snapshots.\n issues = next.map(({ pointer, detail }) => ({ pointer, detail }));\n for (const path of bindings.keys()) {\n commitFieldState(path, {\n error: externalError(path) ?? localErrors.get(path),\n ...(externalError(path) !== undefined\n ? { touched: true }\n : {})\n });\n }\n setSubmissionState({});\n });\n }\n function setSubmissionFailure(error: string, next?: readonly FormIssue[]) {\n batch(() => {\n if (next) setIssues(next);\n setSubmissionState({ error });\n });\n }\n function syncFields(reset: boolean) {\n for (const [path, binding] of bindings) {\n const value = read(binding, values);\n const initialValue = read(binding, baseline);\n updateFieldState(path, {\n value,\n initialValue,\n dirty: !deepEqual(value, initialValue),\n validating: false,\n ...(reset ? { touched: false, error: undefined } : {})\n });\n }\n }\n function subscribe(path: string, listener: () => void): () => void {\n let set = listeners.get(path);\n if (!set) {\n set = new Set();\n listeners.set(path, set);\n }\n set.add(listener);\n setSubmissionState({});\n return () => {\n set.delete(listener);\n if (set.size === 0) listeners.delete(path);\n setSubmissionState({});\n };\n }\n function subscribeGlobal(listener: () => void): () => void {\n globalListeners.add(listener);\n return () => {\n globalListeners.delete(listener);\n };\n }\n function getSubmissionState() {\n return submission;\n }\n function setSubmissionState(patch: Partial<FormSubmissionState>) {\n if (Object.hasOwn(patch, 'error')) submissionError = patch.error;\n const messages = [\n submissionError,\n ...issues\n .filter(\n issue =>\n issue.pointer === '' ||\n !listeners.get(issue.pointer)?.size\n )\n .map(issue => issue.detail)\n ].filter((message): message is string => !!message);\n const next = {\n ...submission,\n ...patch,\n error: [...new Set(messages)].join('\\n') || undefined\n };\n if (\n next.submitting === submission.submitting &&\n next.error === submission.error\n )\n return;\n submission = next;\n notifyGlobal();\n }\n function getValues() {\n return values;\n }\n function getRevision() {\n return revision;\n }\n function setValues(newValues: any, changedPath?: string) {\n const previous = values;\n values = deepClone(newValues ?? {});\n revision++;\n const changed = issues.length > 0 && !deepEqual(previous, values);\n if (changed) {\n const readPointer = (source: any, pointer: string) => {\n if (pointer === '') return source;\n if (!pointer.startsWith('/') || /~(?![01])/.test(pointer))\n return undefined;\n return pointer\n .slice(1)\n .split('/')\n .reduce((value, part) => {\n const key = part\n .replace(/~1/g, '/')\n .replace(/~0/g, '~');\n return value != null && Object.hasOwn(value, key)\n ? value[key]\n : undefined;\n }, source);\n };\n // Arrays are positional: replacing/reordering a container invalidates\n // all of its indexed issues, even when two items have equal values.\n const arrayChanges = [...bindings.keys()].filter(\n path =>\n Array.isArray(readPointer(previous, path)) &&\n !deepEqual(\n readPointer(previous, path),\n readPointer(values, path)\n ) &&\n (!changedPath ||\n changedPath === path ||\n path.startsWith(changedPath + '/'))\n );\n setIssues(\n issues.filter(issue => {\n const path = issue.pointer;\n if (path === '') return false;\n if (\n changedPath &&\n (path === changedPath ||\n path.startsWith(changedPath + '/') ||\n changedPath.startsWith(path + '/'))\n )\n return false;\n if (\n arrayChanges.some(\n array =>\n path === array || path.startsWith(array + '/')\n )\n )\n return false;\n return deepEqual(\n readPointer(previous, path),\n readPointer(values, path)\n );\n })\n );\n }\n syncFields(false);\n }\n function setFieldValue(\n path: string,\n value: any,\n createMissingStructure: boolean\n ) {\n const next = deepClone(values);\n if (\n bindings\n .get(path)\n ?.setValue(next, deepClone(value), { createMissingStructure })\n ) {\n setValues(next, path);\n }\n }\n function resetAll(newInitialValues?: any) {\n values = deepClone(newInitialValues ?? {});\n baseline = values;\n revision++;\n issues = [];\n localErrors.clear();\n syncFields(true);\n setSubmissionState({ error: undefined });\n }\n function getAllFieldPaths() {\n return Array.from(bindings.keys());\n }\n\n return {\n registerField,\n getFieldState,\n updateFieldState,\n subscribe,\n subscribeGlobal,\n getValues,\n setValues,\n setFieldValue,\n resetAll,\n getAllFieldPaths,\n getRevision,\n getSubmissionState,\n setSubmissionState,\n setIssues,\n setSubmissionFailure,\n notifyAll\n };\n}\n\nexport type FormStore = ReturnType<typeof createFormStore>;\n","import type {\n InferType,\n ObjectSchemaBuilder,\n PropertyDescriptorTree,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport { SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema';\nimport type { ReactNode } from 'react';\nimport { Field, type FieldProps, FormSystemProvider } from './components.js';\nimport { getSchemaType } from './helpers.js';\nimport type { SchemaFormInstance } from './hooks.js';\nimport type { FieldRenderer } from './types.js';\n\ndeclare const rendererTypes: unique symbol;\n\n/** A renderer whose accepted values and custom props survive registration. */\nexport type TypedFieldRenderer<TValue, TProps> = FieldRenderer<\n TValue,\n TProps\n> & {\n readonly [rendererTypes]: { value: TValue; props: TProps };\n};\n\n/**\n * Attach type information without adding a UI dependency or runtime wrapper.\n * Include null in TValue when the renderer supports nullable fields.\n */\nexport function defineFieldRenderer<TValue, TProps = Record<string, never>>(\n renderer: FieldRenderer<TValue, TProps>\n): TypedFieldRenderer<TValue, TProps> {\n return renderer as TypedFieldRenderer<TValue, TProps>;\n}\n\nexport type TypedRendererRegistry = Readonly<\n Record<string, TypedFieldRenderer<any, any>>\n>;\n\ntype ValueOf<T> = T extends TypedFieldRenderer<infer V, any> ? V : never;\ntype PropsOf<T> = T extends TypedFieldRenderer<any, infer P> ? P : never;\ntype KindOf<T> = 0 extends 1 & T\n ? string\n : [NonNullable<T>] extends [string]\n ? 'string'\n : [NonNullable<T>] extends [number]\n ? 'number'\n : [NonNullable<T>] extends [boolean]\n ? 'boolean'\n : [NonNullable<T>] extends [Date]\n ? 'date'\n : [NonNullable<T>] extends [readonly unknown[]]\n ? 'array'\n : [NonNullable<T>] extends [object]\n ? 'object'\n : never;\ntype CustomProps<P> = {} extends P ? { fieldProps?: P } : { fieldProps: P };\ntype RendererChoice<R extends TypedRendererRegistry, V> = {\n [K in keyof R & string]: [Exclude<V, undefined>] extends [ValueOf<R[K]>]\n ? K extends `${KindOf<V>}:${infer Variant}`\n ? { variant: Variant } & CustomProps<PropsOf<R[K]>>\n : K extends KindOf<V>\n ? { variant?: undefined } & CustomProps<PropsOf<R[K]>>\n : never\n : never;\n}[keyof R & string];\n\ntype FieldDescriptor = {\n readonly [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {\n getSchema: () => SchemaBuilder<any, any, any, any>;\n };\n};\ntype DescriptorValue<D extends FieldDescriptor> = InferType<\n ReturnType<D[typeof SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]['getSchema']>\n>;\n\n/** Props are inferred from the selected schema property and registered variant. */\nexport type TypedFieldProps<\n R extends TypedRendererRegistry,\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TDescriptor extends FieldDescriptor\n> = {\n form: SchemaFormInstance<TSchema>;\n forProperty: (\n tree: PropertyDescriptorTree<TSchema, TSchema>\n ) => TDescriptor;\n label?: string;\n name?: string;\n} & RendererChoice<R, NoInfer<DescriptorValue<TDescriptor>>>;\n\n/** Named callable type also supports exporting system.Field from UI packages. */\nexport type TypedFieldComponent<R extends TypedRendererRegistry> = <\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TDescriptor extends FieldDescriptor\n>(\n props: TypedFieldProps<R, TSchema, TDescriptor>\n) => ReactNode;\n\n/** Named return type keeps exported consumer registries declaration-safe. */\nexport type TypedFormSystem<R extends TypedRendererRegistry> = {\n Field: TypedFieldComponent<R>;\n Provider: (props: { children: ReactNode }) => ReactNode;\n renderers: Readonly<R>;\n};\n\n/**\n * Create a typed Field and a provider for an application's renderer registry.\n * The typed Field resolves from this factory's closed registry, so an untyped\n * ancestor provider cannot silently replace a renderer with incompatible props.\n * The Provider also configures legacy Field consumers and supports nesting.\n * Extend a system with createFormSystem({ renderers: { ...system.renderers,\n * 'string:custom': customRenderer } }). Existing global APIs remain available.\n */\nexport function createFormSystem<\n const R extends TypedRendererRegistry\n>(config: { renderers: R }): TypedFormSystem<R> {\n const renderers = Object.freeze({ ...config.renderers });\n function TypedField<\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TDescriptor extends FieldDescriptor\n >(props: TypedFieldProps<R, TSchema, TDescriptor>): ReactNode {\n const descriptor = props.forProperty(\n props.form._getFormContext()\n .descriptorTree as PropertyDescriptorTree<TSchema, TSchema>\n );\n const type = getSchemaType(\n descriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR].getSchema()\n );\n const key = props.variant ? `${type}:${props.variant}` : type;\n const renderer = renderers[key];\n if (!renderer)\n throw new Error(\n `No renderer registered for \"${key}\" in this form system.`\n );\n // Field's legacy signature erases descriptor parent types. The factory\n // has already checked the selected value and renderer-specific props.\n return (\n <Field\n {...(props as unknown as FieldProps<TSchema>)}\n renderer={renderer}\n />\n );\n }\n function Provider({ children }: { children: ReactNode }): ReactNode {\n return (\n <FormSystemProvider renderers={renderers}>\n {children}\n </FormSystemProvider>\n );\n }\n return { Field: TypedField, Provider, renderers };\n}\n"],"mappings":"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","names":["useContext","useMemo","createContext","FormSystemContext","FormContext","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","buildDescriptorPathMap","tree","schema","map","introspected","propName","propDescriptor","inner","propSchema","childMap","childInner","childPath","getSchemaType","schema","ensureNestedStructure","values","schema","introspected","result","key","propSchema","ObjectSchemaBuilder","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","useCallback","useEffect","useMemo","useRef","useSyncExternalStore","deepClone","deepEqual","createFormStore","initialValues","values","baseline","revision","issues","submissionError","localErrors","submission","bindings","fieldStates","listeners","globalListeners","batching","pendingPaths","pendingGlobal","batch","callback","paths","global","path","notifyPath","notifyGlobal","listener","read","binding","source","result","getFieldState","value","initialValue","issue","externalError","registerField","notifyAll","commitFieldState","patch","current","key","updateFieldState","setIssues","next","pointer","detail","setSubmissionState","setSubmissionFailure","error","syncFields","reset","subscribe","set","subscribeGlobal","getSubmissionState","messages","message","getValues","getRevision","setValues","newValues","changedPath","previous","readPointer","part","arrayChanges","array","setFieldValue","createMissingStructure","resetAll","newInitialValues","getAllFieldPaths","useSchemaForm","schema","options","storeRef","useRef","createFormStore","store","descriptorTreeRef","ObjectSchemaBuilder","descriptorTree","pathMapRef","buildDescriptorPathMap","pathMap","descriptor","schemaRef","optionsRef","mountedRef","epochRef","validationGenRef","submissionLockRef","timerRef","cancelScheduledValidation","useCallback","useEffect","runValidation","markTouched","gen","revision","path","safeValues","ensureNestedStructure","result","getErrorsFor","parts","part","field","tree","value","key","validate","submit","reset","values","getValue","setValue","triggerValidation","delay","handleSubmit","onValid","submissionOptions","event","epoch","current","outcome","error","message","formContextValue","useMemo","formContextRef","_getFormContext","useFieldHook","selector","useFieldFromContext","validateOnMountRef","useSyncExternalStore","formContext","forProperty","inner","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","subscribe","listener","getSnapshot","fieldState","onChange","onBlur","resolveRenderer","config","variant","type","getSchemaType","jsx","FormSystemProvider","renderers","config","children","parentConfig","useContext","FormSystemContext","resolvedConfig","useMemo","current","FormProvider","form","formContext","FormContext","useField","forProperty","useFieldFromContext","useFormSystem","Field","renderer","variant","label","name","fieldProps","fieldResult","systemConfig","resolvedRenderer","resolveRenderer","schemaType","getSchemaType","tried","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","jsx","defineFieldRenderer","renderer","createFormSystem","config","renderers","TypedField","props","descriptor","type","getSchemaType","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","key","Field","Provider","children","FormSystemProvider"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -130,6 +130,15 @@ export type UseSchemaFormOptions = {
|
|
|
130
130
|
*/
|
|
131
131
|
validationDebounceMs?: number;
|
|
132
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Plain external validation issue. `pointer` is a form-relative JSON Pointer
|
|
135
|
+
* (RFC 6901), or '' for a form-level issue. Safe to serialize through actions.
|
|
136
|
+
* The form does not decode HTTP responses or infer paths from message text.
|
|
137
|
+
*/
|
|
138
|
+
export type FormIssue = {
|
|
139
|
+
readonly pointer: string;
|
|
140
|
+
readonly detail: string;
|
|
141
|
+
};
|
|
133
142
|
/** Reactive state for handleSubmit; independent of any UI kit. */
|
|
134
143
|
export type FormSubmissionState = {
|
|
135
144
|
readonly submitting: boolean;
|
|
@@ -142,6 +151,7 @@ export type FormSubmitResult<TData = void> = void | {
|
|
|
142
151
|
} | {
|
|
143
152
|
ok: false;
|
|
144
153
|
error: string;
|
|
154
|
+
issues?: readonly FormIssue[];
|
|
145
155
|
};
|
|
146
156
|
/** Application-owned success UI and exception translation. Rethrows propagate. */
|
|
147
157
|
export type FormSubmitOptions<TValues, TData = void> = {
|
package/package.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"email": "andrew_zol@cleverbrush.com"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@cleverbrush/deep": "0.0.0-beta-
|
|
9
|
-
"@cleverbrush/schema": "0.0.0-beta-
|
|
8
|
+
"@cleverbrush/deep": "0.0.0-beta-20260930081248",
|
|
9
|
+
"@cleverbrush/schema": "0.0.0-beta-20260930081248"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/react": "^19.0.0",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
},
|
|
49
49
|
"type": "module",
|
|
50
50
|
"types": "./dist/index.d.ts",
|
|
51
|
-
"version": "0.0.0-beta-
|
|
51
|
+
"version": "0.0.0-beta-20260930081248"
|
|
52
52
|
}
|