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 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/json-schema'; | ||||
| export { | ||||
| @ -35,8 +50,238 @@ export { | ||||
|   useForm, | ||||
|   useFormEffects, | ||||
| } from '@formily/react'; | ||||
| export { useAttach } from '@formily/react/esm/hooks/useAttach'; | ||||
| export * from '@formily/reactive'; | ||||
| export * from '@formily/reactive-react'; | ||||
| export * from '@formily/shared'; | ||||
| 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