refactor: migrate formily internal methods into @nocobase/schema
This commit is contained in:
		
							parent
							
								
									96d06b5919
								
							
						
					
					
						commit
						110143d91b
					
				| @ -1,6 +1,21 @@ | |||||||
|  | import { | ||||||
|  |   ArrayField, | ||||||
|  |   FormPathPattern, | ||||||
|  |   GeneralField, | ||||||
|  |   IExchangeArrayStateProps, | ||||||
|  |   INodePatch, | ||||||
|  |   ISpliceArrayStateProps, | ||||||
|  |   LifeCycleTypes, | ||||||
|  |   isArrayField, | ||||||
|  |   isDataField, | ||||||
|  |   isVoidField, | ||||||
|  | } from '@formily/core'; | ||||||
|  | import { batch } from '@formily/reactive'; | ||||||
|  | import { unstable_useCompatEffect } from '@formily/reactive-react'; | ||||||
|  | import { FormPath, each, isNumberLike } from '@formily/shared'; | ||||||
|  | 
 | ||||||
| export * from '@formily/core'; | export * from '@formily/core'; | ||||||
| export type { ArrayField, Field, FormPathPattern, GeneralField, ObjectField } from '@formily/core'; | export type { ArrayField, Field, FormPathPattern, GeneralField, ObjectField } from '@formily/core'; | ||||||
| export { exchangeArrayState, spliceArrayState } from '@formily/core/esm/shared/internals'; |  | ||||||
| export * from '@formily/grid'; | export * from '@formily/grid'; | ||||||
| export * from '@formily/json-schema'; | export * from '@formily/json-schema'; | ||||||
| export { | export { | ||||||
| @ -35,8 +50,238 @@ export { | |||||||
|   useForm, |   useForm, | ||||||
|   useFormEffects, |   useFormEffects, | ||||||
| } from '@formily/react'; | } from '@formily/react'; | ||||||
| export { useAttach } from '@formily/react/esm/hooks/useAttach'; |  | ||||||
| export * from '@formily/reactive'; | export * from '@formily/reactive'; | ||||||
| export * from '@formily/reactive-react'; | export * from '@formily/reactive-react'; | ||||||
| export * from '@formily/shared'; | export * from '@formily/shared'; | ||||||
| export * from '@formily/validator'; | export * from '@formily/validator'; | ||||||
|  | interface IRecycleTarget { | ||||||
|  |   onMount: () => void; | ||||||
|  |   onUnmount: () => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const useAttach = <T extends IRecycleTarget>(target: T): T => { | ||||||
|  |   unstable_useCompatEffect(() => { | ||||||
|  |     target.onMount(); | ||||||
|  |     return () => target.onUnmount(); | ||||||
|  |   }, [target]); | ||||||
|  |   return target; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const NumberIndexReg = /^\.(\d+)/; | ||||||
|  | export const exchangeArrayState = (field: ArrayField, props: IExchangeArrayStateProps) => { | ||||||
|  |   const { fromIndex, toIndex } = { | ||||||
|  |     fromIndex: 0, | ||||||
|  |     toIndex: 0, | ||||||
|  |     ...props, | ||||||
|  |   }; | ||||||
|  |   const address = field.address.toString(); | ||||||
|  |   const fields = field.form.fields; | ||||||
|  |   const addrLength = address.length; | ||||||
|  |   const fieldPatches: INodePatch<GeneralField>[] = []; | ||||||
|  |   const isArrayChildren = (identifier: string) => { | ||||||
|  |     return identifier.indexOf(address) === 0 && identifier.length > addrLength; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const isDown = fromIndex < toIndex; | ||||||
|  | 
 | ||||||
|  |   const isMoveNode = (identifier: string) => { | ||||||
|  |     const afterStr = identifier.slice(address.length); | ||||||
|  |     const number = afterStr.match(NumberIndexReg)?.[1]; | ||||||
|  |     if (number === undefined) return false; | ||||||
|  |     const index = Number(number); | ||||||
|  |     return isDown ? index > fromIndex && index <= toIndex : index < fromIndex && index >= toIndex; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const isFromNode = (identifier: string) => { | ||||||
|  |     const afterStr = identifier.substring(addrLength); | ||||||
|  |     const number = afterStr.match(NumberIndexReg)?.[1]; | ||||||
|  |     if (number === undefined) return false; | ||||||
|  |     const index = Number(number); | ||||||
|  |     return index === fromIndex; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const moveIndex = (identifier: string) => { | ||||||
|  |     const preStr = identifier.substring(0, addrLength); | ||||||
|  |     const afterStr = identifier.substring(addrLength); | ||||||
|  |     const number = afterStr.match(NumberIndexReg)[1]; | ||||||
|  |     const current = Number(number); | ||||||
|  |     let index = current; | ||||||
|  |     if (index === fromIndex) { | ||||||
|  |       index = toIndex; | ||||||
|  |     } else { | ||||||
|  |       index += isDown ? -1 : 1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return `${preStr}${afterStr.replace(/^\.\d+/, `.${index}`)}`; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   batch(() => { | ||||||
|  |     each(fields, (field, identifier) => { | ||||||
|  |       if (isArrayChildren(identifier)) { | ||||||
|  |         if (isMoveNode(identifier) || isFromNode(identifier)) { | ||||||
|  |           const newIdentifier = moveIndex(identifier); | ||||||
|  |           fieldPatches.push({ | ||||||
|  |             type: 'update', | ||||||
|  |             address: newIdentifier, | ||||||
|  |             oldAddress: identifier, | ||||||
|  |             payload: field, | ||||||
|  |           }); | ||||||
|  |           if (!fields[newIdentifier]) { | ||||||
|  |             fieldPatches.push({ | ||||||
|  |               type: 'remove', | ||||||
|  |               address: identifier, | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     patchFieldStates(fields, fieldPatches); | ||||||
|  |   }); | ||||||
|  |   field.form.notify(LifeCycleTypes.ON_FORM_GRAPH_CHANGE); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const patchFieldStates = (target: Record<string, GeneralField>, patches: INodePatch<GeneralField>[]) => { | ||||||
|  |   patches.forEach(({ type, address, oldAddress, payload }) => { | ||||||
|  |     if (type === 'remove') { | ||||||
|  |       destroy(target, address, false); | ||||||
|  |     } else if (type === 'update') { | ||||||
|  |       if (payload) { | ||||||
|  |         target[address] = payload; | ||||||
|  |         if (target[oldAddress] === payload) { | ||||||
|  |           target[oldAddress] = undefined; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (address && payload) { | ||||||
|  |         locateNode(payload, address); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const destroy = (target: Record<string, GeneralField>, address: string, forceClear = true) => { | ||||||
|  |   const field = target[address]; | ||||||
|  |   field?.dispose(); | ||||||
|  |   if (isDataField(field) && forceClear) { | ||||||
|  |     const form = field.form; | ||||||
|  |     const path = field.path; | ||||||
|  |     form.deleteValuesIn(path); | ||||||
|  |     form.deleteInitialValuesIn(path); | ||||||
|  |   } | ||||||
|  |   delete target[address]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const locateNode = (field: GeneralField, address: FormPathPattern) => { | ||||||
|  |   field.address = FormPath.parse(address); | ||||||
|  |   field.path = buildFieldPath(field); | ||||||
|  |   field.form.indexes[field.path.toString()] = field.address.toString(); | ||||||
|  |   return field; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const buildFieldPath = (field: GeneralField) => { | ||||||
|  |   return buildDataPath(field.form.fields, field.address); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const buildDataPath = (fields: Record<string, GeneralField>, pattern: FormPath) => { | ||||||
|  |   let prevArray = false; | ||||||
|  |   const segments = pattern.segments; | ||||||
|  |   const path = segments.reduce((path: string[], key: string, index: number) => { | ||||||
|  |     const currentPath = path.concat(key); | ||||||
|  |     const currentAddress = segments.slice(0, index + 1); | ||||||
|  |     const current = fields[currentAddress.join('.')]; | ||||||
|  |     if (prevArray) { | ||||||
|  |       if (!isVoidField(current)) { | ||||||
|  |         prevArray = false; | ||||||
|  |       } | ||||||
|  |       return path; | ||||||
|  |     } | ||||||
|  |     if (index >= segments.length - 1) { | ||||||
|  |       return currentPath; | ||||||
|  |     } | ||||||
|  |     if (isVoidField(current)) { | ||||||
|  |       const parentAddress = segments.slice(0, index); | ||||||
|  |       const parent = fields[parentAddress.join('.')]; | ||||||
|  |       if (isArrayField(parent) && isNumberLike(key)) { | ||||||
|  |         prevArray = true; | ||||||
|  |         return currentPath; | ||||||
|  |       } | ||||||
|  |       return path; | ||||||
|  |     } else { | ||||||
|  |       prevArray = false; | ||||||
|  |     } | ||||||
|  |     return currentPath; | ||||||
|  |   }, []); | ||||||
|  |   return new FormPath(path); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const spliceArrayState = (field: ArrayField, props?: ISpliceArrayStateProps) => { | ||||||
|  |   const { startIndex, deleteCount, insertCount } = { | ||||||
|  |     startIndex: 0, | ||||||
|  |     deleteCount: 0, | ||||||
|  |     insertCount: 0, | ||||||
|  |     ...props, | ||||||
|  |   }; | ||||||
|  |   const address = field.address.toString(); | ||||||
|  |   const addrLength = address.length; | ||||||
|  |   const form = field.form; | ||||||
|  |   const fields = form.fields; | ||||||
|  |   const fieldPatches: INodePatch<GeneralField>[] = []; | ||||||
|  |   const offset = insertCount - deleteCount; | ||||||
|  |   const isArrayChildren = (identifier: string) => { | ||||||
|  |     return identifier.indexOf(address) === 0 && identifier.length > addrLength; | ||||||
|  |   }; | ||||||
|  |   const isAfterNode = (identifier: string) => { | ||||||
|  |     const afterStr = identifier.substring(addrLength); | ||||||
|  |     const number = afterStr.match(NumberIndexReg)?.[1]; | ||||||
|  |     if (number === undefined) return false; | ||||||
|  |     const index = Number(number); | ||||||
|  |     return index > startIndex + deleteCount - 1; | ||||||
|  |   }; | ||||||
|  |   const isInsertNode = (identifier: string) => { | ||||||
|  |     const afterStr = identifier.substring(addrLength); | ||||||
|  |     const number = afterStr.match(NumberIndexReg)?.[1]; | ||||||
|  |     if (number === undefined) return false; | ||||||
|  |     const index = Number(number); | ||||||
|  |     return index >= startIndex && index < startIndex + insertCount; | ||||||
|  |   }; | ||||||
|  |   const isDeleteNode = (identifier: string) => { | ||||||
|  |     const preStr = identifier.substring(0, addrLength); | ||||||
|  |     const afterStr = identifier.substring(addrLength); | ||||||
|  |     const number = afterStr.match(NumberIndexReg)?.[1]; | ||||||
|  |     if (number === undefined) return false; | ||||||
|  |     const index = Number(number); | ||||||
|  |     return ( | ||||||
|  |       (index > startIndex && !fields[`${preStr}${afterStr.replace(/^\.\d+/, `.${index + deleteCount}`)}`]) || | ||||||
|  |       index === startIndex | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  |   const moveIndex = (identifier: string) => { | ||||||
|  |     if (offset === 0) return identifier; | ||||||
|  |     const preStr = identifier.substring(0, addrLength); | ||||||
|  |     const afterStr = identifier.substring(addrLength); | ||||||
|  |     const number = afterStr.match(NumberIndexReg)?.[1]; | ||||||
|  |     if (number === undefined) return identifier; | ||||||
|  |     const index = Number(number) + offset; | ||||||
|  |     return `${preStr}${afterStr.replace(/^\.\d+/, `.${index}`)}`; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   batch(() => { | ||||||
|  |     each(fields, (field, identifier) => { | ||||||
|  |       if (isArrayChildren(identifier)) { | ||||||
|  |         if (isAfterNode(identifier)) { | ||||||
|  |           const newIdentifier = moveIndex(identifier); | ||||||
|  |           fieldPatches.push({ | ||||||
|  |             type: 'update', | ||||||
|  |             address: newIdentifier, | ||||||
|  |             oldAddress: identifier, | ||||||
|  |             payload: field, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |         if (isInsertNode(identifier) || isDeleteNode(identifier)) { | ||||||
|  |           fieldPatches.push({ type: 'remove', address: identifier }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     patchFieldStates(fields, fieldPatches); | ||||||
|  |   }); | ||||||
|  |   field.form.notify(LifeCycleTypes.ON_FORM_GRAPH_CHANGE); | ||||||
|  | }; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user