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