@cleverbrush/react-form 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -1
- package/dist/contexts.d.ts +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +22 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -430,7 +430,8 @@ Creates a form instance bound to a schema. Returns field binding and form lifecy
|
|
|
430
430
|
```tsx
|
|
431
431
|
const form = useSchemaForm(UserSchema, {
|
|
432
432
|
createMissingStructure: true, // default: true — auto-create parent objects when setting nested values
|
|
433
|
-
validateOnMount: false, // default: false — set to true to show errors immediately
|
|
433
|
+
validateOnMount: false, // default: false — set to true to show errors immediately
|
|
434
|
+
validateOnBlur: true, // opt in; touches only the blurred field on mount
|
|
434
435
|
validationDebounceMs: 300 // optional — debounce onChange validation (ms); validate()/submit() are always immediate
|
|
435
436
|
});
|
|
436
437
|
```
|
|
@@ -760,3 +761,48 @@ function App() {
|
|
|
760
761
|
## License
|
|
761
762
|
|
|
762
763
|
BSD-3-Clause
|
|
764
|
+
|
|
765
|
+
## Blur validation and rejected inline edits
|
|
766
|
+
|
|
767
|
+
Set `validateOnBlur: true` to validate immediately when a field blurs, even when
|
|
768
|
+
change validation is debounced. Only the blurred field becomes touched; errors on
|
|
769
|
+
other fields remain available without forcing their messages to appear. The
|
|
770
|
+
option defaults to false, preserving touch-only blur behavior for existing forms.
|
|
771
|
+
Explicit `validate()` and submissions continue to touch all fields.
|
|
772
|
+
|
|
773
|
+
Managed submission accepts an optional `onInvalid(result)` callback. It runs only
|
|
774
|
+
for current invalid validation and is awaited under the same duplicate-submission
|
|
775
|
+
lock as a successful submission. Values changed during validation, reset and
|
|
776
|
+
unmount suppress stale callbacks. Exceptions from `onInvalid` propagate and are
|
|
777
|
+
not translated by `onError`, because no write was attempted.
|
|
778
|
+
|
|
779
|
+
```tsx
|
|
780
|
+
const form = useSchemaForm(QuantitySchema, { validateOnBlur: true });
|
|
781
|
+
const quantity = form.useField(t => t.quantity);
|
|
782
|
+
const commit = form.handleSubmit(saveQuantity, {
|
|
783
|
+
onInvalid: () => form.reset(savedQuantity)
|
|
784
|
+
});
|
|
785
|
+
// Application policy: an inline editor can restore its saved baseline on blur.
|
|
786
|
+
// Explicit form submission can use a handler without onInvalid to keep errors.
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
The callback receives the native failed `ValidationResult`; custom validators
|
|
790
|
+
can leave `errors` undefined. No UI or reset policy is chosen by the package.
|
|
791
|
+
|
|
792
|
+
## Refreshing validation rules
|
|
793
|
+
|
|
794
|
+
Schemas are captured once by default. Set `refreshValidationRules: true` when
|
|
795
|
+
bounds or translated messages change while the field names and value types stay
|
|
796
|
+
fixed. Memoize the schema by those rule dependencies; do not construct a new
|
|
797
|
+
schema on every form-state render. A rule update immediately revalidates without
|
|
798
|
+
changing values, initial baselines, dirty state or which fields are touched. Older
|
|
799
|
+
validation and managed submission callbacks are ignored. Field renderers keep
|
|
800
|
+
the original binding schema; mount a new form when the field structure or types
|
|
801
|
+
change, rather than using this option for a different form model.
|
|
802
|
+
|
|
803
|
+
```tsx
|
|
804
|
+
const schema = useMemo(() => object({
|
|
805
|
+
quantity: number().min(minimum, minimumMessage)
|
|
806
|
+
}), [minimum, minimumMessage]);
|
|
807
|
+
const form = useSchemaForm(schema, { refreshValidationRules: true });
|
|
808
|
+
```
|
package/dist/contexts.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type FormContextValue = {
|
|
|
15
15
|
schema: ObjectSchemaBuilder<any, any, any>;
|
|
16
16
|
options: UseSchemaFormOptions;
|
|
17
17
|
pathMap: Map<PropertyDescriptorInner<any, any, any>, string>;
|
|
18
|
-
triggerValidation?: (markTouched: boolean) => Promise<unknown>;
|
|
18
|
+
triggerValidation?: (markTouched: boolean, immediate?: boolean) => Promise<unknown>;
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
21
|
* Context for per-form data — allows useField to work via context.
|
package/dist/hooks.d.ts
CHANGED
|
@@ -28,6 +28,6 @@ export type SchemaFormInstance<TSchema extends ObjectSchemaBuilder<any, any, any
|
|
|
28
28
|
/** Bind a schema to independently subscribed fields and a stable form instance. */
|
|
29
29
|
export declare function useSchemaForm<TSchema extends ObjectSchemaBuilder<any, any, any>>(schema: TSchema, options?: UseSchemaFormOptions): SchemaFormInstance<TSchema>;
|
|
30
30
|
/** Internal field binding shared by direct and context-based hooks. */
|
|
31
|
-
export declare function useFieldFromContext(formContext: FormContextValue, forProperty: (tree: any) => any, triggerValidation?: ((markTouched: boolean) => Promise<unknown>) | undefined): UseFieldResult;
|
|
31
|
+
export declare function useFieldFromContext(formContext: FormContextValue, forProperty: (tree: any) => any, triggerValidation?: ((markTouched: boolean, immediate?: boolean) => Promise<unknown>) | undefined): UseFieldResult;
|
|
32
32
|
/** Resolve type:variant first, falling back to the base type. */
|
|
33
33
|
export declare function resolveRenderer(config: FormSystemConfig | null, schema: SchemaBuilder<any, any, any, any>, variant?: string): FieldRenderer | undefined;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{useContext as Q,useMemo as
|
|
2
|
-
`)||void 0};i.submitting===
|
|
1
|
+
import{useContext as Q,useMemo as be}from"react";import{createContext as ae}from"react";var L=ae(null),Z=ae(null);import{ObjectSchemaBuilder as ue,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as ce}from"@cleverbrush/schema";function ee(o,n){let c=new Map,e=n.introspect();if(!e.properties)return c;for(let a of Object.keys(e.properties)){let u=o[a];if(!u||!(ce in u))continue;let p=u[ce];c.set(p,a);let s=e.properties[a];if(s instanceof ue){let l=ee(u,s);for(let[f,d]of l)c.set(f,`${a}.${d}`)}}return c}function U(o){return o.introspect()?.type??"unknown"}function re(o,n){let c=n.introspect();if(!c.properties)return o??{};let e=o!=null?{...o}:{};for(let a of Object.keys(c.properties)){let u=c.properties[a];u instanceof ue&&(e[a]=re(e[a],u))}return e}import{ObjectSchemaBuilder as Fe,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as he}from"@cleverbrush/schema";import{useCallback as T,useEffect as te,useLayoutEffect as ge,useMemo as de,useRef as O,useSyncExternalStore as me}from"react";import{deepClone as W,deepEqual as G}from"@cleverbrush/deep";function le(o){let n=W(o??{}),c=n,e=0,a=[],u,p=new Map,s={submitting:!1,error:void 0},l=new Map,f=new Map,d=new Map,F=new Set,R=0,I=new Set,j=!1;function P(r){R++;try{r()}finally{if(R--,R===0){let t=[...I];I.clear();let i=j;j=!1;for(let m of t)k(m);i&&S()}}}function S(){if(R){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 N(r){if(!f.has(r)){let t=l.get(r),i=B(t,n),m=B(t,c);f.set(r,{value:i,initialValue:m,dirty:!G(i,m),touched:a.some(D=>D.pointer===r),error:M(r),validating:!1})}return f.get(r)}function z(r,t){return l.has(r)||l.set(r,t),N(r)}function k(r){if(R){I.add(r);return}for(let t of d.get(r)??[])t()}function q(){for(let r of d.keys())k(r);S()}function $(r,t){let i=N(r);Object.entries(t).every(([m,D])=>Object.is(i[m],D))||(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 E(r){P(()=>{a=r.map(({pointer:t,detail:i})=>({pointer:t,detail:i}));for(let t of l.keys())$(t,{error:M(t)??p.get(t),...M(t)!==void 0?{touched:!0}:{}});h({})})}function J(r,t){P(()=>{t&&E(t),h({error:r})})}function Y(r){for(let[t,i]of l){let m=B(i,n),D=B(i,c);H(t,{value:m,initialValue:D,dirty:!G(m,D),validating:!1,...r?{touched:!1,error:void 0}:{}})}}function X(r,t){let i=d.get(r);return i||(i=new Set,d.set(r,i)),i.add(t),h({}),()=>{i.delete(t),i.size===0&&d.delete(r),h({})}}function y(r){return F.add(r),()=>{F.delete(r)}}function V(){return s}function h(r){Object.hasOwn(r,"error")&&(u=r.error);let t=[u,...a.filter(m=>m.pointer===""||!d.get(m.pointer)?.size).map(m=>m.detail)].filter(m=>!!m),i={...s,...r,error:[...new Set(t)].join(`
|
|
2
|
+
`)||void 0};i.submitting===s.submitting&&i.error===s.error||(s=i,S())}function K(){return n}function x(){return e}function w(r,t){let i=n;if(n=W(r??{}),e++,a.length>0&&!G(i,n)){let D=(C,v)=>{if(v==="")return C;if(!(!v.startsWith("/")||/~(?![01])/.test(v)))return v.slice(1).split("/").reduce((A,Te)=>{let ie=Te.replace(/~1/g,"/").replace(/~0/g,"~");return A!=null&&Object.hasOwn(A,ie)?A[ie]:void 0},C)},Se=[...l.keys()].filter(C=>Array.isArray(D(i,C))&&!G(D(i,C),D(n,C))&&(!t||t===C||C.startsWith(t+"/")));E(a.filter(C=>{let v=C.pointer;return v===""||t&&(v===t||v.startsWith(t+"/")||t.startsWith(v+"/"))||Se.some(A=>v===A||v.startsWith(A+"/"))?!1:G(D(i,v),D(n,v))}))}Y(!1)}function b(r,t,i){let m=W(n);l.get(r)?.setValue(m,W(t),{createMissingStructure:i})&&w(m,r)}function _(r){n=W(r??{}),c=n,e++,a=[],p.clear(),Y(!0),h({error:void 0})}function g(){return Array.from(l.keys())}return{registerField:z,getFieldState:N,updateFieldState:H,subscribe:X,subscribeGlobal:y,getValues:K,setValues:w,setFieldValue:b,resetAll:_,getAllFieldPaths:g,getRevision:x,getSubmissionState:V,setSubmissionState:h,setIssues:E,setSubmissionFailure:J,notifyAll:q}}var Re=typeof window>"u"?te:ge;function Pe(o,n){let c=O(null);c.current||(c.current=le({}));let e=c.current,a=O(null);a.current||(a.current=Fe.getPropertiesFor(o));let u=a.current,p=O(null);p.current||(p.current=ee(u,o));let s=p.current;for(let[y]of s)e.registerField(y.toJsonPointer(),y);let l=O(o),f=O({});f.current={createMissingStructure:!0,...n};let d=O(!0),F=O(0),R=O(0),I=O(!1),j=O(null),P=T(()=>{j.current!==null&&clearTimeout(j.current),j.current=null},[]);te(()=>(d.current=!0,()=>{d.current=!1,F.current++,R.current++,P()}),[P]);let S=T(async y=>{P();let V=++R.current,h=e.getRevision();for(let b of e.getAllFieldPaths())e.updateFieldState(b,{validating:!0});let K=re(e.getValues(),l.current),x;try{x=await l.current.validateAsync(K,{doNotStopOnFirstError:!0})}catch{x={valid:!1}}if(!d.current||V!==R.current||h!==e.getRevision())return x;for(let b of e.getAllFieldPaths())e.updateFieldState(b,{error:void 0,validating:!1,...y?{touched:!0}:{}});let w=x.getErrorsFor;if(typeof w=="function")for(let b of e.getAllFieldPaths()){let _=b.slice(1).split("/").map(g=>g.replace(/~1/g,"/").replace(/~0/g,"~"));try{let g=w(r=>_.reduce((t,i)=>t?.[i],r));Array.isArray(g?.errors)&&g.errors.length&&e.updateFieldState(b,{error:g.errors[0]})}catch{}}return x},[P,e]);Re(()=>{!n?.refreshValidationRules||l.current===o||(l.current=o,F.current++,R.current++,S(!1))},[o,n?.refreshValidationRules,S]);let B=T(()=>S(!0),[S]),N=T(()=>B(),[B]),z=T(y=>{F.current++,R.current++,P(),e.resetAll(y)},[e,P]),k=T(()=>e.getValues(),[e]),q=T(y=>{P(),e.setValues({...e.getValues(),...y})},[e,P]),$=T((y,V=!1)=>{P();let h=f.current.validationDebounceMs;return!V&&h!=null&&h>0?(j.current=setTimeout(()=>{j.current=null,S(y)},h),Promise.resolve()):S(y)},[S,P]),M=T((y,V)=>async h=>{if(h?.preventDefault(),I.current||!d.current)return;I.current=!0;let K=F.current,x=()=>d.current&&K===F.current;e.setIssues([]),e.setSubmissionState({submitting:!0,error:void 0});try{let w=e.getRevision(),b=await S(!0);if(!x()||w!==e.getRevision())return;if(!b.valid){await V?.onInvalid?.(b);return}if(b.object===void 0)return;let _=b.object,g;try{g=await y(_)}catch(r){if(!x())return;if(!V?.onError)throw r;let t=await V.onError(r);x()&&e.setSubmissionState({error:t});return}if(!x())return;if(g&&!g.ok){e.setSubmissionFailure(g.error,w===e.getRevision()?g.issues:void 0);return}await V?.onSuccess?.(g?.data,_)}finally{I.current=!1,d.current&&e.setSubmissionState({submitting:!1})}},[S,e]),H=de(()=>({store:e,descriptorTree:u,schema:l.current,pathMap:s,get options(){return f.current},triggerValidation:$}),[e,u,s,$]),E=O(H);E.current=H;let J=T(()=>E.current,[]),Y=T(y=>ne(E.current,y),[]),X=O(n?.validateOnMount);return te(()=>{X.current&&S(!0)},[S]),me(e.subscribeGlobal,e.getSubmissionState,e.getSubmissionState),de(()=>({useField:Y,submit:N,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,N,B,z,k,q,M,J,e])}function ne(o,n,c=o.triggerValidation){let{store:e,descriptorTree:a,options:u}=o,p=n(a)[he],s=p.toJsonPointer();e.registerField(s,p);let l=T(I=>e.subscribe(s,I),[e,s]),f=T(()=>e.getFieldState(s),[e,s]),d=me(l,f,f),F=T(I=>{e.setFieldValue(s,I,u.createMissingStructure!==!1),c?.(!1)},[e,s,u.createMissingStructure,c]),R=T(()=>{e.updateFieldState(s,{touched:!0}),u.validateOnBlur&&c?.(!1,!0)},[e,s,u.validateOnBlur,c]);return{...d,onChange:F,onBlur:R,setValue:F,schema:p.getSchema()}}function pe(o,n,c){if(!o?.renderers)return;let e=U(n);return(c?o.renderers[`${e}:${c}`]:void 0)??o.renderers[e]}import{jsx as ye}from"react/jsx-runtime";function oe({renderers:o,config:n,children:c}){let e=Q(L),a=be(()=>{let u={...n,renderers:{...n?.renderers,...o}};return e?{...e,...u,renderers:{...e.renderers,...u.renderers}}:u},[e,n,o]);return ye(L.Provider,{value:a,children:c})}function ve({form:o,children:n}){let c=o._getFormContext();return ye(Z.Provider,{value:c,children:n})}function xe(o){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 ne(n,o)}function De(){let o=Q(L);if(!o)throw new Error("useFormSystem must be used within a FormSystemProvider");return o}function se({forProperty:o,form:n,renderer:c,variant:e,label:a,name:u,fieldProps:p}){let s=n.useField(o),l=Q(L),f=c??pe(l,s.schema,e);if(!f){let d=U(s.schema),F=e?`"${d}:${e}" or "${d}"`:`"${d}"`;throw new Error(`No renderer found for schema type ${F}. Provide a renderer prop or configure one in FormSystemProvider.`)}return f({value:s.value,initialValue:s.initialValue,dirty:s.dirty,touched:s.touched,error:s.error,validating:s.validating,onChange:s.onChange,onBlur:s.onBlur,setValue:s.setValue,schema:s.schema,variant:e,label:a,name:u,fieldProps:p})}import{SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as Oe}from"@cleverbrush/schema";import{jsx as fe}from"react/jsx-runtime";function Ie(o){return o}function Ve(o){let n=Object.freeze({...o.renderers});function c(a){let u=a.forProperty(a.form._getFormContext().descriptorTree),p=U(u[Oe].getSchema()),s=a.variant?`${p}:${a.variant}`:p,l=n[s];if(!l)throw new Error(`No renderer registered for "${s}" in this form system.`);return fe(se,{...a,renderer:l})}function e({children:a}){return fe(oe,{renderers:n,children:a})}return{Field:c,Provider:e,renderers:n}}export{se as Field,ve as FormProvider,oe as FormSystemProvider,Ve as createFormSystem,Ie as defineFieldRenderer,xe as useField,De as useFormSystem,Pe as useSchemaForm};
|
|
3
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 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":"AAQA,OAAS,cAAAA,EAAY,WAAAC,OAAe,QCHpC,OAAS,iBAAAC,OAAqB,QAOvB,IAAMC,EAAoBD,GAAuC,IAAI,EAkB/DE,EAAcF,GAAuC,IAAI,ECzBtE,OACI,uBAAAG,GACA,qCAAAC,OACG,sBAMA,SAASC,GACZC,EACAC,EACmD,CACnD,IAAMC,EAAM,IAAI,IACVC,EAAeF,EAAO,WAAW,EACvC,GAAI,CAACE,EAAa,WAAY,OAAOD,EAErC,QAAWE,KAAY,OAAO,KAAKD,EAAa,UAAU,EAAG,CACzD,IAAME,EAAkBL,EAAaI,CAAQ,EAC7C,GACI,CAACC,GACD,EAAEP,MAAqCO,GAEvC,SAEJ,IAAMC,EAAQD,EACVP,EACJ,EACAI,EAAI,IAAII,EAAOF,CAAQ,EAGvB,IAAMG,EAAaJ,EAAa,WAAWC,CAAQ,EACnD,GAAIG,aAAsBV,GAAqB,CAC3C,IAAMW,EAAWT,GAAuBM,EAAgBE,CAAU,EAClE,OAAW,CAACE,EAAYC,CAAS,IAAKF,EAClCN,EAAI,IAAIO,EAAY,GAAGL,CAAQ,IAAIM,CAAS,EAAE,CAEtD,CACJ,CAEA,OAAOR,CACX,CAeO,SAASS,EACZC,EACM,CAEN,OADqBA,EAAO,WAAW,GAClB,MAAQ,SACjC,CA6CO,SAASC,GACZC,EACAC,EACG,CACH,IAAMC,EAAeD,EAAO,WAAW,EACvC,GAAI,CAACC,EAAa,WAAY,OAAOF,GAA0B,CAAC,EAEhE,IAAMG,EAASH,GAAU,KAAO,CAAE,GAAGA,CAAO,EAAI,CAAC,EACjD,QAAWI,KAAO,OAAO,KAAKF,EAAa,UAAU,EAAG,CACpD,IAAMG,EAAaH,EAAa,WAAWE,CAAG,EAC1CC,aAAsBC,KACtBH,EAAOC,CAAG,EAAIL,GAAsBI,EAAOC,CAAG,EAAGC,CAAU,EAEnE,CACA,OAAOF,CACX,CCtHA,OACI,uBAAAI,GACA,qCAAAC,OACG,sBACP,OACI,eAAAC,EACA,aAAAC,GACA,WAAAC,GACA,UAAAC,EACA,wBAAAC,OACG,QClBP,OAAS,aAAAC,EAAW,aAAAC,MAAiB,oBAS9B,SAASC,GAAgBC,EAAoB,CAChD,IAAIC,EAASJ,EAAUG,GAAiB,CAAC,CAAC,EACtCE,EAAWD,EACXE,EAAW,EACXC,EAA+B,CAAC,EAChCC,EACEC,EAAc,IAAI,IACpBC,EAAkC,CAClC,WAAY,GACZ,MAAO,MACX,EACMC,EAAW,IAAI,IACfC,EAAc,IAAI,IAClBC,EAAY,IAAI,IAChBC,EAAkB,IAAI,IACxBC,EAAW,EACTC,EAAe,IAAI,IACrBC,EAAgB,GACpB,SAASC,EAAMC,EAAsB,CACjCJ,IACA,GAAI,CACAI,EAAS,CACb,QAAE,CAEE,GADAJ,IACIA,IAAa,EAAG,CAChB,IAAMK,EAAQ,CAAC,GAAGJ,CAAY,EAC9BA,EAAa,MAAM,EACnB,IAAMK,EAASJ,EACfA,EAAgB,GAChB,QAAWK,KAAQF,EAAOG,EAAWD,CAAI,EACrCD,GAAQG,EAAa,CAC7B,CACJ,CACJ,CACA,SAASA,GAAe,CACpB,GAAIT,EAAU,CACVE,EAAgB,GAChB,MACJ,CACA,QAAWQ,KAAYX,EAAiBW,EAAS,CACrD,CAEA,SAASC,EAAKC,EAA8BC,EAAa,CACrD,IAAMC,EAASF,GAAS,SAASC,CAAM,EACvC,OAAOC,GAAQ,QAAUA,EAAO,MAAQ,MAC5C,CACA,SAASC,EAAcR,EAA0B,CAC7C,GAAI,CAACV,EAAY,IAAIU,CAAI,EAAG,CACxB,IAAMK,EAAUhB,EAAS,IAAIW,CAAI,EAC3BS,EAAQL,EAAKC,EAASvB,CAAM,EAC5B4B,EAAeN,EAAKC,EAAStB,CAAQ,EAC3CO,EAAY,IAAIU,EAAM,CAClB,MAAAS,EACA,aAAAC,EACA,MAAO,CAAC/B,EAAU8B,EAAOC,CAAY,EACrC,QAASzB,EAAO,KAAK0B,GAASA,EAAM,UAAYX,CAAI,EACpD,MAAOY,EAAcZ,CAAI,EACzB,WAAY,EAChB,CAAC,CACL,CACA,OAAOV,EAAY,IAAIU,CAAI,CAC/B,CACA,SAASa,EAAcb,EAAcK,EAAkB,CACnD,OAAKhB,EAAS,IAAIW,CAAI,GAAGX,EAAS,IAAIW,EAAMK,CAAO,EAC5CG,EAAcR,CAAI,CAC7B,CACA,SAASC,EAAWD,EAAc,CAC9B,GAAIP,EAAU,CACVC,EAAa,IAAIM,CAAI,EACrB,MACJ,CACA,QAAWG,KAAYZ,EAAU,IAAIS,CAAI,GAAK,CAAC,EAAGG,EAAS,CAC/D,CACA,SAASW,GAAY,CACjB,QAAWd,KAAQT,EAAU,KAAK,EAAGU,EAAWD,CAAI,EACpDE,EAAa,CACjB,CACA,SAASa,EAAiBf,EAAcgB,EAA4B,CAChE,IAAMC,EAAUT,EAAcR,CAAI,EAE9B,OAAO,QAAQgB,CAAK,EAAE,MAAM,CAAC,CAACE,EAAKT,CAAK,IACpC,OAAO,GAAGQ,EAAQC,CAAuB,EAAGT,CAAK,CACrD,IAGJnB,EAAY,IAAIU,EAAM,CAAE,GAAGiB,EAAS,GAAGD,CAAM,CAAC,EAC9Cf,EAAWD,CAAI,EACnB,CACA,SAASY,EAAcZ,EAAc,CACjC,OAAOA,IAAS,GACV,OACAf,EAAO,KAAK0B,GAASA,EAAM,UAAYX,CAAI,GAAG,MACxD,CACA,SAASmB,EAAiBnB,EAAcgB,EAA4B,CAC5D,OAAO,OAAOA,EAAO,OAAO,GAAG7B,EAAY,IAAIa,EAAMgB,EAAM,KAAK,EACpED,EAAiBf,EAAM,CACnB,GAAGgB,EACH,MAAOJ,EAAcZ,CAAI,GAAKb,EAAY,IAAIa,CAAI,CACtD,CAAC,CACL,CACA,SAASoB,EAAUC,EAA4B,CAC3CzB,EAAM,IAAM,CAERX,EAASoC,EAAK,IAAI,CAAC,CAAE,QAAAC,EAAS,OAAAC,CAAO,KAAO,CAAE,QAAAD,EAAS,OAAAC,CAAO,EAAE,EAChE,QAAWvB,KAAQX,EAAS,KAAK,EAC7B0B,EAAiBf,EAAM,CACnB,MAAOY,EAAcZ,CAAI,GAAKb,EAAY,IAAIa,CAAI,EAClD,GAAIY,EAAcZ,CAAI,IAAM,OACtB,CAAE,QAAS,EAAK,EAChB,CAAC,CACX,CAAC,EAELwB,EAAmB,CAAC,CAAC,CACzB,CAAC,CACL,CACA,SAASC,EAAqBC,EAAeL,EAA6B,CACtEzB,EAAM,IAAM,CACJyB,GAAMD,EAAUC,CAAI,EACxBG,EAAmB,CAAE,MAAAE,CAAM,CAAC,CAChC,CAAC,CACL,CACA,SAASC,EAAWC,EAAgB,CAChC,OAAW,CAAC5B,EAAMK,CAAO,IAAKhB,EAAU,CACpC,IAAMoB,EAAQL,EAAKC,EAASvB,CAAM,EAC5B4B,EAAeN,EAAKC,EAAStB,CAAQ,EAC3CoC,EAAiBnB,EAAM,CACnB,MAAAS,EACA,aAAAC,EACA,MAAO,CAAC/B,EAAU8B,EAAOC,CAAY,EACrC,WAAY,GACZ,GAAIkB,EAAQ,CAAE,QAAS,GAAO,MAAO,MAAU,EAAI,CAAC,CACxD,CAAC,CACL,CACJ,CACA,SAASC,EAAU7B,EAAcG,EAAkC,CAC/D,IAAI2B,EAAMvC,EAAU,IAAIS,CAAI,EAC5B,OAAK8B,IACDA,EAAM,IAAI,IACVvC,EAAU,IAAIS,EAAM8B,CAAG,GAE3BA,EAAI,IAAI3B,CAAQ,EAChBqB,EAAmB,CAAC,CAAC,EACd,IAAM,CACTM,EAAI,OAAO3B,CAAQ,EACf2B,EAAI,OAAS,GAAGvC,EAAU,OAAOS,CAAI,EACzCwB,EAAmB,CAAC,CAAC,CACzB,CACJ,CACA,SAASO,EAAgB5B,EAAkC,CACvD,OAAAX,EAAgB,IAAIW,CAAQ,EACrB,IAAM,CACTX,EAAgB,OAAOW,CAAQ,CACnC,CACJ,CACA,SAAS6B,GAAqB,CAC1B,OAAO5C,CACX,CACA,SAASoC,EAAmBR,EAAqC,CACzD,OAAO,OAAOA,EAAO,OAAO,IAAG9B,EAAkB8B,EAAM,OAC3D,IAAMiB,EAAW,CACb/C,EACA,GAAGD,EACE,OACG0B,GACIA,EAAM,UAAY,IAClB,CAACpB,EAAU,IAAIoB,EAAM,OAAO,GAAG,IACvC,EACC,IAAIA,GAASA,EAAM,MAAM,CAClC,EAAE,OAAQuB,GAA+B,CAAC,CAACA,CAAO,EAC5Cb,EAAO,CACT,GAAGjC,EACH,GAAG4B,EACH,MAAO,CAAC,GAAG,IAAI,IAAIiB,CAAQ,CAAC,EAAE,KAAK;AAAA,CAAI,GAAK,MAChD,EAEIZ,EAAK,aAAejC,EAAW,YAC/BiC,EAAK,QAAUjC,EAAW,QAG9BA,EAAaiC,EACbnB,EAAa,EACjB,CACA,SAASiC,GAAY,CACjB,OAAOrD,CACX,CACA,SAASsD,GAAc,CACnB,OAAOpD,CACX,CACA,SAASqD,EAAUC,EAAgBC,EAAsB,CACrD,IAAMC,EAAW1D,EAIjB,GAHAA,EAASJ,EAAU4D,GAAa,CAAC,CAAC,EAClCtD,IACgBC,EAAO,OAAS,GAAK,CAACN,EAAU6D,EAAU1D,CAAM,EACnD,CACT,IAAM2D,EAAc,CAACnC,EAAagB,IAAoB,CAClD,GAAIA,IAAY,GAAI,OAAOhB,EAC3B,GAAI,GAACgB,EAAQ,WAAW,GAAG,GAAK,YAAY,KAAKA,CAAO,GAExD,OAAOA,EACF,MAAM,CAAC,EACP,MAAM,GAAG,EACT,OAAO,CAACb,EAAOiC,KAAS,CACrB,IAAMxB,GAAMwB,GACP,QAAQ,MAAO,GAAG,EAClB,QAAQ,MAAO,GAAG,EACvB,OAAOjC,GAAS,MAAQ,OAAO,OAAOA,EAAOS,EAAG,EAC1CT,EAAMS,EAAG,EACT,MACV,EAAGZ,CAAM,CACjB,EAGMqC,GAAe,CAAC,GAAGtD,EAAS,KAAK,CAAC,EAAE,OACtCW,GACI,MAAM,QAAQyC,EAAYD,EAAUxC,CAAI,CAAC,GACzC,CAACrB,EACG8D,EAAYD,EAAUxC,CAAI,EAC1ByC,EAAY3D,EAAQkB,CAAI,CAC5B,IACC,CAACuC,GACEA,IAAgBvC,GAChBA,EAAK,WAAWuC,EAAc,GAAG,EAC7C,EACAnB,EACInC,EAAO,OAAO0B,GAAS,CACnB,IAAMX,EAAOW,EAAM,QASnB,OARIX,IAAS,IAETuC,IACCvC,IAASuC,GACNvC,EAAK,WAAWuC,EAAc,GAAG,GACjCA,EAAY,WAAWvC,EAAO,GAAG,IAIrC2C,GAAa,KACTC,GACI5C,IAAS4C,GAAS5C,EAAK,WAAW4C,EAAQ,GAAG,CACrD,EAEO,GACJjE,EACH8D,EAAYD,EAAUxC,CAAI,EAC1ByC,EAAY3D,EAAQkB,CAAI,CAC5B,CACJ,CAAC,CACL,CACJ,CACA2B,EAAW,EAAK,CACpB,CACA,SAASkB,EACL7C,EACAS,EACAqC,EACF,CACE,IAAMzB,EAAO3C,EAAUI,CAAM,EAEzBO,EACK,IAAIW,CAAI,GACP,SAASqB,EAAM3C,EAAU+B,CAAK,EAAG,CAAE,uBAAAqC,CAAuB,CAAC,GAEjET,EAAUhB,EAAMrB,CAAI,CAE5B,CACA,SAAS+C,EAASC,EAAwB,CACtClE,EAASJ,EAAUsE,GAAoB,CAAC,CAAC,EACzCjE,EAAWD,EACXE,IACAC,EAAS,CAAC,EACVE,EAAY,MAAM,EAClBwC,EAAW,EAAI,EACfH,EAAmB,CAAE,MAAO,MAAU,CAAC,CAC3C,CACA,SAASyB,GAAmB,CACxB,OAAO,MAAM,KAAK5D,EAAS,KAAK,CAAC,CACrC,CAEA,MAAO,CACH,cAAAwB,EACA,cAAAL,EACA,iBAAAW,EACA,UAAAU,EACA,gBAAAE,EACA,UAAAI,EACA,UAAAE,EACA,cAAAQ,EACA,SAAAE,EACA,iBAAAE,EACA,YAAAb,EACA,mBAAAJ,EACA,mBAAAR,EACA,UAAAJ,EACA,qBAAAK,EACA,UAAAX,CACJ,CACJ,CDrOO,SAASoC,GAGZC,EACAC,EAC2B,CAC3B,IAAMC,EAAWC,EAAyB,IAAI,EACzCD,EAAS,UAASA,EAAS,QAAUE,GAAgB,CAAC,CAAC,GAC5D,IAAMC,EAAQH,EAAS,QACjBI,EAAoBH,EAGhB,IAAI,EACTG,EAAkB,UACnBA,EAAkB,QAAUC,GAAoB,iBAC5CP,CACJ,GAEJ,IAAMQ,EAAiBF,EAAkB,QACnCG,EAAaN,EAGT,IAAI,EACTM,EAAW,UACZA,EAAW,QAAUC,GAAuBF,EAAgBR,CAAM,GAEtE,IAAMW,EAAUF,EAAW,QAC3B,OAAW,CAACG,CAAU,IAAKD,EACvBN,EAAM,cAAcO,EAAW,cAAc,EAAGA,CAAU,EAE9D,IAAMC,EAAYV,EAAOH,CAAM,EACzBc,EAAaX,EAA6B,CAAC,CAAC,EAClDW,EAAW,QAAU,CAAE,uBAAwB,GAAM,GAAGb,CAAQ,EAChE,IAAMc,EAAaZ,EAAO,EAAI,EACxBa,EAAWb,EAAO,CAAC,EACnBc,EAAmBd,EAAO,CAAC,EAC3Be,EAAoBf,EAAO,EAAK,EAChCgB,EAAWhB,EAA6C,IAAI,EAC5DiB,EAA4BC,EAAY,IAAM,CAC5CF,EAAS,UAAY,MAAM,aAAaA,EAAS,OAAO,EAC5DA,EAAS,QAAU,IACvB,EAAG,CAAC,CAAC,EACLG,GAAU,KACNP,EAAW,QAAU,GACd,IAAM,CACTA,EAAW,QAAU,GACrBC,EAAS,UACTC,EAAiB,UACjBG,EAA0B,CAC9B,GACD,CAACA,CAAyB,CAAC,EAE9B,IAAMG,EAAgBF,EAClB,MACIG,GACgD,CAChDJ,EAA0B,EAC1B,IAAMK,EAAM,EAAER,EAAiB,QACzBS,EAAWrB,EAAM,YAAY,EACnC,QAAWsB,KAAQtB,EAAM,iBAAiB,EACtCA,EAAM,iBAAiBsB,EAAM,CAAE,WAAY,EAAK,CAAC,EAErD,IAAMC,EAAaC,GACfxB,EAAM,UAAU,EAChBQ,EAAU,OACd,EACIiB,EACJ,GAAI,CACAA,EAAU,MAAMjB,EAAU,QAAQ,cAAce,EAAY,CACxD,sBAAuB,EAC3B,CAAC,CACL,MAAQ,CACJE,EAAS,CAAE,MAAO,EAAM,CAG5B,CAGA,GACI,CAACf,EAAW,SACZU,IAAQR,EAAiB,SACzBS,IAAarB,EAAM,YAAY,EAE/B,OAAOyB,EAEX,QAAWH,KAAQtB,EAAM,iBAAiB,EACtCA,EAAM,iBAAiBsB,EAAM,CACzB,MAAO,OACP,WAAY,GACZ,GAAIH,EAAc,CAAE,QAAS,EAAK,EAAI,CAAC,CAC3C,CAAC,EAEL,IAAMO,EAAgBD,EAAe,aACrC,GAAI,OAAOC,GAAiB,WACxB,QAAWJ,KAAQtB,EAAM,iBAAiB,EAAG,CACzC,IAAM2B,EAAQL,EACT,MAAM,CAAC,EACP,MAAM,GAAG,EACT,IAAIM,GACDA,EAAK,QAAQ,MAAO,GAAG,EAAE,QAAQ,MAAO,GAAG,CAC/C,EACJ,GAAI,CACA,IAAMC,EAAQH,EAAcI,GACxBH,EAAM,OACF,CAACI,EAAYC,IAAQD,IAAQC,CAAG,EAChCF,CACJ,CACJ,EAEI,MAAM,QAAQD,GAAO,MAAM,GAC3BA,EAAM,OAAO,QAEb7B,EAAM,iBAAiBsB,EAAM,CACzB,MAAOO,EAAM,OAAO,CAAC,CACzB,CAAC,CAET,MAAQ,CAGR,CACJ,CAEJ,OAAOJ,CACX,EACA,CAACV,EAA2Bf,CAAK,CACrC,EAEMiC,EAAWjB,EAAY,IAAME,EAAc,EAAI,EAAG,CAACA,CAAa,CAAC,EACjEgB,EAASlB,EAAY,IAAMiB,EAAS,EAAG,CAACA,CAAQ,CAAC,EACjDE,EAAQnB,EACToB,GAAyC,CACtCzB,EAAS,UACTC,EAAiB,UACjBG,EAA0B,EAC1Bf,EAAM,SAASoC,CAAM,CACzB,EACA,CAACpC,EAAOe,CAAyB,CACrC,EACMsB,EAAWrB,EACb,IAA0BhB,EAAM,UAAU,EAC1C,CAACA,CAAK,CACV,EACMsC,EAAWtB,EACZoB,GAAwC,CACrCrB,EAA0B,EAC1Bf,EAAM,UAAU,CAAE,GAAGA,EAAM,UAAU,EAAG,GAAGoC,CAAO,CAAC,CACvD,EACA,CAACpC,EAAOe,CAAyB,CACrC,EACMwB,EAAoBvB,EACrBG,GAAyB,CACtBJ,EAA0B,EAC1B,IAAMyB,EAAQ/B,EAAW,QAAQ,qBACjC,OAAI+B,GAAS,MAAQA,EAAQ,GACzB1B,EAAS,QAAU,WAAW,IAAM,CAChCA,EAAS,QAAU,KACdI,EAAcC,CAAW,CAClC,EAAGqB,CAAK,EACD,QAAQ,QAAQ,GAEpBtB,EAAcC,CAAW,CACpC,EACA,CAACD,EAAeH,CAAyB,CAC7C,EAEM0B,EAAezB,EACjB,CACI0B,EAGAC,IAEA,MAAMC,GAAS,CAEX,GADAA,GAAO,eAAe,EAClB/B,EAAkB,SAAW,CAACH,EAAW,QAAS,OACtDG,EAAkB,QAAU,GAC5B,IAAMgC,EAAQlC,EAAS,QACjBmC,EAAU,IACZpC,EAAW,SAAWmC,IAAUlC,EAAS,QAC7CX,EAAM,UAAU,CAAC,CAAC,EAClBA,EAAM,mBAAmB,CACrB,WAAY,GACZ,MAAO,MACX,CAAC,EACD,GAAI,CACA,IAAMqB,EAAWrB,EAAM,YAAY,EAC7ByB,EAAS,MAAMP,EAAc,EAAI,EACvC,GACI,CAAC4B,EAAQ,GACTzB,IAAarB,EAAM,YAAY,GAC/B,CAACyB,EAAO,OACRA,EAAO,SAAW,OAElB,OACJ,IAAMW,EAASX,EAAO,OAClBsB,EACJ,GAAI,CACAA,EAAU,MAAML,EAAQN,CAAM,CAClC,OAASY,EAAO,CACZ,GAAI,CAACF,EAAQ,EAAG,OAChB,GAAI,CAACH,GAAmB,QAAS,MAAMK,EACvC,IAAMC,EAAU,MAAMN,EAAkB,QAAQK,CAAK,EACjDF,EAAQ,GACR9C,EAAM,mBAAmB,CAAE,MAAOiD,CAAQ,CAAC,EAC/C,MACJ,CACA,GAAI,CAACH,EAAQ,EAAG,OAChB,GAAIC,GAAW,CAACA,EAAQ,GAAI,CACxB/C,EAAM,qBACF+C,EAAQ,MACR1B,IAAarB,EAAM,YAAY,EACzB+C,EAAQ,OACR,MACV,EACA,MACJ,CAGA,MAAMJ,GAAmB,YAAYI,GAAS,KAAMX,CAAM,CAC9D,QAAE,CACEvB,EAAkB,QAAU,GACxBH,EAAW,SACXV,EAAM,mBAAmB,CAAE,WAAY,EAAM,CAAC,CACtD,CACJ,EACJ,CAACkB,EAAelB,CAAK,CACzB,EAEMkD,EAAmBC,GACrB,KAAO,CACH,MAAAnD,EACA,eAAAG,EACA,OAAQK,EAAU,QAClB,QAAAF,EACA,IAAI,SAAU,CACV,OAAOG,EAAW,OACtB,EACA,kBAAA8B,CACJ,GACA,CAACvC,EAAOG,EAAgBG,EAASiC,CAAiB,CACtD,EACMa,EAAiBtD,EAAOoD,CAAgB,EAC9CE,EAAe,QAAUF,EACzB,IAAMG,EAAkBrC,EAAY,IAAMoC,EAAe,QAAS,CAAC,CAAC,EAC9DE,EAAetC,EAEbuC,GAIAC,GAAoBJ,EAAe,QAASG,CAAQ,EACxD,CAAC,CACL,EACME,EAAqB3D,EAAOF,GAAS,eAAe,EAC1D,OAAAqB,GAAU,IAAM,CACRwC,EAAmB,SAAcvC,EAAc,EAAI,CAC3D,EAAG,CAACA,CAAa,CAAC,EAGlBwC,GACI1D,EAAM,gBACNA,EAAM,mBACNA,EAAM,kBACV,EACOmD,GACH,KAAO,CACH,SAAUG,EACV,OAAApB,EACA,SAAAD,EACA,MAAAE,EACA,SAAAE,EACA,SAAAC,EACA,UAAWtC,EAAM,UACjB,aAAAyC,EACA,gBAAAY,EACA,IAAI,YAAa,CACb,OAAOrD,EAAM,mBAAmB,EAAE,UACtC,EACA,IAAI,OAAQ,CACR,OAAOA,EAAM,mBAAmB,EAAE,KACtC,CACJ,GACA,CACIsD,EACApB,EACAD,EACAE,EACAE,EACAC,EACAG,EACAY,EACArD,CACJ,CACJ,CACJ,CAGO,SAASwD,GACZG,EACAC,EACArB,EAAoBoB,EAAY,kBAClB,CACd,GAAM,CAAE,MAAA3D,EAAO,eAAAG,EAAgB,QAAAP,CAAQ,EAAI+D,EACrCE,EACFD,EAAYzD,CAAc,EAAE2D,EAAiC,EAC3DxC,EAAOuC,EAAM,cAAc,EACjC7D,EAAM,cAAcsB,EAAMuC,CAAK,EAC/B,IAAME,EAAY/C,EACbgD,GAAyBhE,EAAM,UAAUsB,EAAM0C,CAAQ,EACxD,CAAChE,EAAOsB,CAAI,CAChB,EACM2C,EAAcjD,EAChB,IAAMhB,EAAM,cAAcsB,CAAI,EAC9B,CAACtB,EAAOsB,CAAI,CAChB,EACM4C,EAAaR,GACfK,EACAE,EACAA,CACJ,EACME,EAAWnD,EACZe,GAAe,CACZ/B,EAAM,cACFsB,EACAS,EACAnC,EAAQ,yBAA2B,EACvC,EACK2C,IAAoB,EAAK,CAClC,EACA,CAACvC,EAAOsB,EAAM1B,EAAQ,uBAAwB2C,CAAiB,CACnE,EACM6B,EAASpD,EACX,IAAMhB,EAAM,iBAAiBsB,EAAM,CAAE,QAAS,EAAK,CAAC,EACpD,CAACtB,EAAOsB,CAAI,CAChB,EACA,MAAO,CACH,GAAG4C,EACH,SAAAC,EACA,OAAAC,EACA,SAAUD,EACV,OAAQN,EAAM,UAAU,CAC5B,CACJ,CAGO,SAASQ,GACZC,EACA3E,EACA4E,EACyB,CACzB,GAAI,CAACD,GAAQ,UAAW,OACxB,IAAME,EAAOC,EAAc9E,CAAM,EACjC,OACK4E,EAAUD,EAAO,UAAU,GAAGE,CAAI,IAAID,CAAO,EAAE,EAAI,SACpDD,EAAO,UAAUE,CAAI,CAE7B,CHtWQ,cAAAE,OAAA,oBA3BD,SAASC,GAAmB,CAC/B,UAAAC,EACA,OAAAC,EACA,SAAAC,CACJ,EAA6C,CACzC,IAAMC,EAAeC,EAAWC,CAAiB,EAE3CC,EAAmCC,GAAQ,IAAM,CACnD,IAAMC,EAA4B,CAC9B,GAAGP,EACH,UAAW,CACP,GAAGA,GAAQ,UACX,GAAGD,CACP,CACJ,EACA,OAAKG,EACE,CACH,GAAGA,EACH,GAAGK,EACH,UAAW,CACP,GAAGL,EAAa,UAChB,GAAGK,EAAQ,SACf,CACJ,EAR0BA,CAS9B,EAAG,CAACL,EAAcF,EAAQD,CAAS,CAAC,EAEpC,OACIF,GAACO,EAAkB,SAAlB,CAA2B,MAAOC,EAC9B,SAAAJ,EACL,CAER,CAcO,SAASO,GAEd,CAAE,KAAAC,EAAM,SAAAR,CAAS,EAAgD,CAC/D,IAAMS,EAAcD,EAAK,gBAAgB,EACzC,OACIZ,GAACc,EAAY,SAAZ,CAAqB,MAAOD,EACxB,SAAAT,EACL,CAER,CAcO,SAASW,GAIZC,EAG0C,CAC1C,IAAMH,EAAcP,EAAWQ,CAAW,EAC1C,GAAI,CAACD,EACD,MAAM,IAAI,MACN,wGAEJ,EAEJ,OAAOI,GAAoBJ,EAAaG,CAAW,CAGvD,CAOO,SAASE,IAAkC,CAC9C,IAAMf,EAASG,EAAWC,CAAiB,EAC3C,GAAI,CAACJ,EACD,MAAM,IAAI,MACN,wDACJ,EAEJ,OAAOA,CACX,CA8CO,SAASgB,GAA0D,CACtE,YAAAH,EACA,KAAAJ,EACA,SAAAQ,EACA,QAAAC,EACA,MAAAC,EACA,KAAAC,EACA,WAAAC,CACJ,EAAyC,CACrC,IAAMC,EAAcb,EAAK,SAASI,CAAW,EACvCU,EAAepB,EAAWC,CAAiB,EAE3CoB,EACFP,GAAYQ,GAAgBF,EAAcD,EAAY,OAAQJ,CAAO,EAEzE,GAAI,CAACM,EAAkB,CACnB,IAAME,EAAaC,EAAcL,EAAY,MAAM,EAC7CM,EAAQV,EACR,IAAIQ,CAAU,IAAIR,CAAO,SAASQ,CAAU,IAC5C,IAAIA,CAAU,IACpB,MAAM,IAAI,MACN,qCAAqCE,CAAK,mEAE9C,CACJ,CAEA,OAAOJ,EAAiB,CACpB,MAAOF,EAAY,MACnB,aAAcA,EAAY,aAC1B,MAAOA,EAAY,MACnB,QAASA,EAAY,QACrB,MAAOA,EAAY,MACnB,WAAYA,EAAY,WACxB,SAAUA,EAAY,SACtB,OAAQA,EAAY,OACpB,SAAUA,EAAY,SACtB,OAAQA,EAAY,OACpB,QAAAJ,EACA,MAAAC,EACA,KAAAC,EACA,WAAAC,CACJ,CAAC,CACL,CKrOA,OAAS,qCAAAQ,OAAyC,sBAiItC,cAAAC,OAAA,oBA5GL,SAASC,GACZC,EACkC,CAClC,OAAOA,CACX,CAgFO,SAASC,GAEdC,EAA8C,CAC5C,IAAMC,EAAY,OAAO,OAAO,CAAE,GAAGD,EAAO,SAAU,CAAC,EACvD,SAASE,EAGPC,EAA4D,CAC1D,IAAMC,EAAaD,EAAM,YACrBA,EAAM,KAAK,gBAAgB,EACtB,cACT,EACME,EAAOC,EACTF,EAAWG,EAAiC,EAAE,UAAU,CAC5D,EACMC,EAAML,EAAM,QAAU,GAAGE,CAAI,IAAIF,EAAM,OAAO,GAAKE,EACnDP,EAAWG,EAAUO,CAAG,EAC9B,GAAI,CAACV,EACD,MAAM,IAAI,MACN,+BAA+BU,CAAG,wBACtC,EAGJ,OACIZ,GAACa,GAAA,CACI,GAAIN,EACL,SAAUL,EACd,CAER,CACA,SAASY,EAAS,CAAE,SAAAC,CAAS,EAAuC,CAChE,OACIf,GAACgB,GAAA,CAAmB,UAAWX,EAC1B,SAAAU,EACL,CAER,CACA,MAAO,CAAE,MAAOT,EAAY,SAAAQ,EAAU,UAAAT,CAAU,CACpD","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"]}
|
|
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?: (\n markTouched: boolean,\n immediate?: boolean\n ) => 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 useLayoutEffect,\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\nconst useBeforePaint =\n typeof window === 'undefined' ? useEffect : useLayoutEffect;\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 useBeforePaint(() => {\n if (!options?.refreshValidationRules || schemaRef.current === schema)\n return;\n schemaRef.current = schema;\n epochRef.current++;\n validationGenRef.current++;\n void runValidation(false);\n }, [schema, options?.refreshValidationRules, runValidation]);\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, immediate = false) => {\n cancelScheduledValidation();\n const delay = optionsRef.current.validationDebounceMs;\n if (!immediate && 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 (!current() || revision !== store.getRevision()) return;\n if (!result.valid) {\n await submissionOptions?.onInvalid?.(result);\n return;\n }\n if (result.object === undefined) 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 if (options.validateOnBlur) void triggerValidation?.(false, true);\n }, [store, path, options.validateOnBlur, triggerValidation]);\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","useLayoutEffect","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","useBeforePaint","useEffect","useLayoutEffect","useSchemaForm","schema","options","storeRef","useRef","createFormStore","store","descriptorTreeRef","ObjectSchemaBuilder","descriptorTree","pathMapRef","buildDescriptorPathMap","pathMap","descriptor","schemaRef","optionsRef","mountedRef","epochRef","validationGenRef","submissionLockRef","timerRef","cancelScheduledValidation","useCallback","runValidation","markTouched","gen","revision","path","safeValues","ensureNestedStructure","result","getErrorsFor","parts","part","field","tree","value","key","validate","submit","reset","values","getValue","setValue","triggerValidation","immediate","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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SchemaBuilder } from '@cleverbrush/schema';
|
|
1
|
+
import type { SchemaBuilder, ValidationResult } from '@cleverbrush/schema';
|
|
2
2
|
import type { FormEvent, ReactNode } from 'react';
|
|
3
3
|
/**
|
|
4
4
|
* A renderer function that receives field state and returns a React node.
|
|
@@ -115,6 +115,21 @@ export type UseSchemaFormOptions = {
|
|
|
115
115
|
* @default false
|
|
116
116
|
*/
|
|
117
117
|
validateOnMount?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Validate immediately when a field blurs, cancelling pending debounced
|
|
120
|
+
* validation. Only that field becomes touched; other fields keep their state.
|
|
121
|
+
* Existing change-triggered validation is unaffected.
|
|
122
|
+
* @default false
|
|
123
|
+
*/
|
|
124
|
+
validateOnBlur?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Adopt changed validation rules without resetting values, touched or dirty.
|
|
127
|
+
* Field names and value types must stay fixed for the form's lifetime.
|
|
128
|
+
* Changes invalidate older validation/submission callbacks and revalidate
|
|
129
|
+
* immediately without touching additional fields.
|
|
130
|
+
* @default false
|
|
131
|
+
*/
|
|
132
|
+
refreshValidationRules?: boolean;
|
|
118
133
|
/**
|
|
119
134
|
* Debounce delay in milliseconds for onChange-triggered validation.
|
|
120
135
|
* When set, rapid field changes only trigger one validation run after
|
|
@@ -155,6 +170,12 @@ export type FormSubmitResult<TData = void> = void | {
|
|
|
155
170
|
};
|
|
156
171
|
/** Application-owned success UI and exception translation. Rethrows propagate. */
|
|
157
172
|
export type FormSubmitOptions<TValues, TData = void> = {
|
|
173
|
+
/**
|
|
174
|
+
* Awaited for current invalid validation, before releasing the submission
|
|
175
|
+
* lock. Values changed during validation, reset and unmount suppress it.
|
|
176
|
+
* Exceptions propagate instead of being translated as failed writes.
|
|
177
|
+
*/
|
|
178
|
+
onInvalid?: (result: ValidationResult<TValues>) => void | Promise<void>;
|
|
158
179
|
onSuccess?: (data: TData | undefined, values: TValues) => void | Promise<void>;
|
|
159
180
|
onError?: (error: unknown) => string | Promise<string>;
|
|
160
181
|
};
|
package/package.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"email": "andrew_zol@cleverbrush.com"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@cleverbrush/deep": "^5.
|
|
9
|
-
"@cleverbrush/schema": "^5.
|
|
8
|
+
"@cleverbrush/deep": "^5.1.0",
|
|
9
|
+
"@cleverbrush/schema": "^5.1.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/react": "^19.0.0",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
},
|
|
52
52
|
"type": "module",
|
|
53
53
|
"types": "./dist/index.d.ts",
|
|
54
|
-
"version": "5.
|
|
54
|
+
"version": "5.1.0"
|
|
55
55
|
}
|