@@ -8,17 +8,31 @@ export type FormkitDevtoolsEvent =
88 | { type : "snapshot" ; payload : FormkitDevtoolsSnapshot } ;
99
1010export interface FormkitDevtoolsFieldSnapshot {
11+ key ?: string ;
1112 path ?: string ;
1213 name ?: string ;
14+ id ?: string ;
1315 type ?: string ;
1416 value ?: any ;
1517 default ?: any ;
1618 initial ?: any ;
1719 error ?: any ;
20+ label ?: string ;
21+ placeholder ?: string ;
22+ autoFocus ?: boolean ;
23+ related ?: any ;
24+ rules ?: any ;
25+ options ?: any ;
26+ bindings ?: any ;
27+ extra ?: any ;
28+ checked ?: any ;
29+ validators ?: any ;
30+ validatedWith ?: any ;
1831 hasError ?: boolean ;
1932 isValid ?: boolean ;
2033 isDirty ?: boolean ;
2134 isPristine ?: boolean ;
35+ isDefault ?: boolean ;
2236 isEmpty ?: boolean ;
2337 disabled ?: boolean ;
2438 touched ?: boolean ;
@@ -27,6 +41,12 @@ export interface FormkitDevtoolsFieldSnapshot {
2741 changed ?: boolean ;
2842 deleted ?: boolean ;
2943 validating ?: boolean ;
44+ clearing ?: boolean ;
45+ resetting ?: boolean ;
46+ submitting ?: boolean ;
47+ size ?: number ;
48+ submitted ?: boolean ;
49+ validated ?: boolean ;
3050 fields ?: FormkitDevtoolsFieldSnapshot [ ] ;
3151}
3252
@@ -38,7 +58,23 @@ export interface FormkitDevtoolsSnapshot {
3858 validated ?: boolean ;
3959 submitting ?: boolean ;
4060 validating ?: boolean ;
61+ clearing ?: boolean ;
62+ resetting ?: boolean ;
63+ hasError ?: boolean ;
64+ isValid ?: boolean ;
65+ isDirty ?: boolean ;
66+ isPristine ?: boolean ;
67+ isDefault ?: boolean ;
68+ isEmpty ?: boolean ;
69+ disabled ?: boolean ;
70+ deleted ?: boolean ;
71+ touched ?: boolean ;
72+ focused ?: boolean ;
73+ blurred ?: boolean ;
74+ changed ?: boolean ;
75+ error ?: any ;
4176 options ?: Record < string , any > ;
77+ helpers ?: Record < string , any > ;
4278 fields : FormkitDevtoolsFieldSnapshot [ ] ;
4379}
4480
@@ -55,29 +91,115 @@ export interface FormkitDevtoolsHook {
5591}
5692
5793const HOOK_KEY = "__MOBX_FORMKIT_DEVTOOLS_HOOK__" ;
94+
5895const FIELD_PROPS = [
59- "path" ,
96+ "key" ,
97+ "id" ,
6098 "name" ,
99+ "path" ,
61100 "type" ,
62- "value" ,
101+ "bindings" ,
102+ "options" ,
63103 "default" ,
64104 "initial" ,
65- "error" ,
105+ "value" ,
106+ "label" ,
107+ "placeholder" ,
108+ "autoFocus" ,
109+ "related" ,
110+ "rules" ,
111+ "extra" ,
112+ "checked" ,
113+ "validators" ,
114+ "validatedWith" ,
115+ "clearing" ,
116+ "resetting" ,
66117 "hasError" ,
67118 "isValid" ,
68119 "isDirty" ,
69120 "isPristine" ,
121+ "isDefault" ,
70122 "isEmpty" ,
71123 "disabled" ,
124+ "deleted" ,
72125 "touched" ,
73126 "focused" ,
74127 "blurred" ,
75128 "changed" ,
76- "deleted" ,
129+ "error" ,
130+ "size" ,
131+ "submitted" ,
132+ "validated" ,
133+ "submitting" ,
77134 "validating" ,
78135] as const ;
79136
80- const FORM_PROPS = [ "name" , "size" , "submitted" , "validated" , "submitting" , "validating" ] as const ;
137+ const FORM_PROPS = [
138+ "name" ,
139+ "size" ,
140+ "submitted" ,
141+ "validated" ,
142+ "submitting" ,
143+ "validating" ,
144+ "clearing" ,
145+ "resetting" ,
146+ "hasError" ,
147+ "isValid" ,
148+ "isDirty" ,
149+ "isPristine" ,
150+ "isDefault" ,
151+ "isEmpty" ,
152+ "disabled" ,
153+ "deleted" ,
154+ "touched" ,
155+ "focused" ,
156+ "blurred" ,
157+ "changed" ,
158+ "error" ,
159+ ] as const ;
160+
161+ const FIELD_HELPER_PROPS = [
162+ "error" ,
163+ "label" ,
164+ "placeholder" ,
165+ "default" ,
166+ "initial" ,
167+ "type" ,
168+ "disabled" ,
169+ "checked" ,
170+ "related" ,
171+ "rules" ,
172+ "options" ,
173+ "extra" ,
174+ "bindings" ,
175+ "validators" ,
176+ "validatedWith" ,
177+ ] as const ;
178+
179+ /**
180+ * Recursively convert a value into a plain, structured-clone-safe shape:
181+ * observable structures become plain objects/arrays and functions become
182+ * name strings (functions cannot cross the postMessage boundary).
183+ */
184+ const serialize = ( value : any , depth : number = 0 ) : any => {
185+ if ( depth > 8 ) return undefined ;
186+ if ( typeof value === "function" ) {
187+ return `[Function${ value . name ? `: ${ value . name } ` : "" } ]` ;
188+ }
189+ if ( value == null ) return value ;
190+ const plain = toJS ( value ) ;
191+ if ( Array . isArray ( plain ) ) {
192+ return plain . map ( ( item : any ) => serialize ( item , depth + 1 ) ) ;
193+ }
194+ if ( typeof plain === "object" ) {
195+ const out : Record < string , any > = { } ;
196+ for ( const key of Object . keys ( plain ) ) {
197+ out [ key ] = serialize ( plain [ key ] , depth + 1 ) ;
198+ }
199+ return out ;
200+ }
201+ return plain ;
202+ } ;
81203
82204const isArrayMap = ( target : any ) => Boolean ( target && typeof target === "object" && target . _isArrayMap ) ;
83205
@@ -93,7 +215,7 @@ const pick = (source: any, props: readonly string[]) => {
93215 const out : Record < string , any > = { } ;
94216 for ( const prop of props ) {
95217 const value = source && typeof source === "object" ? source [ prop ] : undefined ;
96- if ( value !== undefined ) out [ prop ] = toJS ( value ) ;
218+ if ( value !== undefined ) out [ prop ] = serialize ( value ) ;
97219 }
98220 return out ;
99221} ;
@@ -108,39 +230,82 @@ const serializeField = (field: any, key?: string): FormkitDevtoolsFieldSnapshot
108230 return out ;
109231} ;
110232
233+ const fieldKey = ( field : FormkitDevtoolsFieldSnapshot , index : number ) : string =>
234+ ( field as any ) . key ?? field . path ?? field . name ?? `field-${ index } ` ;
235+
236+ const collectProp = ( fields : FormkitDevtoolsFieldSnapshot [ ] , prop : string ) : Record < string , any > => {
237+ const out : Record < string , any > = { } ;
238+ fields . forEach ( ( field , index ) => {
239+ const key = fieldKey ( field , index ) ;
240+ out [ key ] = field . fields && field . fields . length
241+ ? collectProp ( field . fields , prop )
242+ : ( field as any ) [ prop ] ;
243+ } ) ;
244+ return out ;
245+ } ;
246+
247+ const collectHelpers = ( form : any , fields : FormkitDevtoolsFieldSnapshot [ ] ) : Record < string , any > => {
248+ const out : Record < string , any > = { } ;
249+ for ( const prop of FIELD_HELPER_PROPS ) {
250+ out [ prop ] = collectProp ( fields , prop ) ;
251+ }
252+ out . hooks = serialize ( form . $hooks ) ;
253+ out . handlers = serialize ( form . $handlers ) ;
254+ return out ;
255+ } ;
256+
257+ const collectBooleanOptions = ( form : any ) : Record < string , boolean > => {
258+ const out : Record < string , boolean > = { } ;
259+ const options = form ?. state ?. options ?. options ;
260+ if ( ! options || typeof options !== "object" ) return out ;
261+ for ( const key of Object . keys ( options ) ) {
262+ const value = options [ key ] ;
263+ if ( typeof value === "boolean" ) out [ key ] = value ;
264+ }
265+ return out ;
266+ } ;
267+
111268const serializeForm = ( key : string , form : any ) : FormkitDevtoolsSnapshot => {
112- const out : FormkitDevtoolsSnapshot = { key, ...pick ( form , FORM_PROPS ) } ;
269+ const out = { key, ...pick ( form , FORM_PROPS ) } as FormkitDevtoolsSnapshot ;
113270 out . fields = pairsOf ( form . fields ) . map ( ( [ fieldKey , field ] : [ string , any ] ) => serializeField ( field , fieldKey ) ) ;
271+ out . helpers = collectHelpers ( form , out . fields ) ;
272+ out . options = collectBooleanOptions ( form ) ;
114273 return out ;
115274} ;
116275
117276const createHook = ( ) : FormkitDevtoolsHook => {
118277 const registry = new Map < string , any > ( ) ;
119278 const listeners = new Set < ( event : FormkitDevtoolsEvent ) => void > ( ) ;
120279 const disposers = new Map < string , ( ) => void > ( ) ;
280+ const caches = new Map < string , FormkitDevtoolsSnapshot > ( ) ;
281+ const dirty = new Set < string > ( ) ;
121282 let connected = false ;
122- let pending = false ;
283+ let flushTimer : ReturnType < typeof setTimeout > | null = null ;
123284
124285 const emit : FormkitDevtoolsHook [ "emit" ] = ( event ) => {
125286 for ( const listener of listeners ) listener ( event ) ;
126287 } ;
127288
128289 const flush = ( ) => {
129- pending = false ;
130- for ( const [ key , form ] of registry ) {
131- emit ( { type : "snapshot" , payload : serializeForm ( key , form ) } ) ;
290+ flushTimer = null ;
291+ const keys = [ ...dirty ] ;
292+ dirty . clear ( ) ;
293+ for ( const key of keys ) {
294+ const snapshot = caches . get ( key ) ;
295+ if ( snapshot ) emit ( { type : "snapshot" , payload : snapshot } ) ;
132296 }
133297 } ;
134298
135- const requestFlush = ( ) => {
136- if ( pending ) return ;
137- pending = true ;
138- setTimeout ( flush , 0 ) ;
299+ const scheduleFlush = ( key : string ) => {
300+ dirty . add ( key ) ;
301+ if ( flushTimer == null ) flushTimer = setTimeout ( flush , 0 ) ;
139302 } ;
140303
141304 const dispose = ( key : string ) => {
142305 disposers . get ( key ) ?.( ) ;
143306 disposers . delete ( key ) ;
307+ dirty . delete ( key ) ;
308+ caches . delete ( key ) ;
144309 } ;
145310
146311 const setup = ( key : string ) => {
@@ -150,12 +315,20 @@ const createHook = (): FormkitDevtoolsHook => {
150315 if ( ! form ) return ;
151316 disposers . set (
152317 key ,
153- autorun ( ( ) => requestFlush ( ) )
318+ autorun ( ( ) => {
319+ // Reading observables inside serializeForm makes the autorun re-run
320+ // on every field/form change; the result is cached and emitted once
321+ // (batched) by flush.
322+ caches . set ( key , serializeForm ( key , form ) ) ;
323+ scheduleFlush ( key ) ;
324+ } )
154325 ) ;
155326 } ;
156327
157328 return {
158- connected,
329+ get connected ( ) {
330+ return connected ;
331+ } ,
159332 registry,
160333 emit,
161334 subscribe ( listener ) {
@@ -189,7 +362,7 @@ const createHook = (): FormkitDevtoolsHook => {
189362 } ,
190363 requestSnapshot ( ) {
191364 for ( const [ key , form ] of registry ) {
192- emit ( { type : "snapshot" , payload : serializeForm ( key , form ) } ) ;
365+ emit ( { type : "snapshot" , payload : caches . get ( key ) ?? serializeForm ( key , form ) } ) ;
193366 }
194367 } ,
195368 } ;
@@ -216,4 +389,4 @@ hook.subscribe((event) => {
216389 if ( event . type === "form:new" && ! hook . registry . has ( event . key ) ) {
217390 hook . register ( event . key || event . form ?. name || `form-${ hook . registry . size + 1 } ` , event . form ) ;
218391 }
219- } ) ;
392+ } ) ;
0 commit comments