mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
fix: fix FieldArrayModel.swap state issue with doc example covered (#70)
* feat: add get values to FormModel * fix: fix FieldArrayModel.swap state issue with doc example added * doc: fix array demo code
This commit is contained in:
parent
3dc0b94dd8
commit
6dd4e2d3ab
@ -4,7 +4,7 @@ import './field-wrapper.css';
|
||||
|
||||
interface FieldWrapperProps {
|
||||
required?: boolean;
|
||||
title: string;
|
||||
title?: string;
|
||||
children?: React.ReactNode;
|
||||
error?: string;
|
||||
note?: string;
|
||||
|
||||
@ -4,7 +4,7 @@ import './field-wrapper.css';
|
||||
|
||||
interface FieldWrapperProps {
|
||||
required?: boolean;
|
||||
title: string;
|
||||
title?: string;
|
||||
children?: React.ReactNode;
|
||||
error?: string;
|
||||
note?: string;
|
||||
|
||||
7
apps/docs/components/node-form/array/index.css
Normal file
7
apps/docs/components/node-form/array/index.css
Normal file
@ -0,0 +1,7 @@
|
||||
.array-item-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icon-button-popover {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
116
apps/docs/components/node-form/array/node-registry.tsx
Normal file
116
apps/docs/components/node-form/array/node-registry.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import {
|
||||
DataEvent,
|
||||
EffectFuncProps,
|
||||
Field,
|
||||
FieldRenderProps,
|
||||
FormMeta,
|
||||
ValidateTrigger,
|
||||
WorkflowNodeRegistry,
|
||||
FieldArray,
|
||||
FieldArrayRenderProps,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
import { FieldWrapper } from '@flowgram.ai/demo-node-form';
|
||||
import { Input, Button, Popover } from '@douyinfe/semi-ui';
|
||||
import { IconPlus, IconCrossCircleStroked, IconArrowDown } from '@douyinfe/semi-icons';
|
||||
import './index.css';
|
||||
import '../index.css';
|
||||
|
||||
export const render = () => (
|
||||
<div className="demo-node-content">
|
||||
<div className="demo-node-title">Array Examples</div>
|
||||
<FieldArray name="array">
|
||||
{({ field, fieldState }: FieldArrayRenderProps<string>) => (
|
||||
<FieldWrapper title={'My Array'}>
|
||||
{field.map((child, index) => (
|
||||
<Field name={child.name} key={child.key}>
|
||||
{({ field: childField, fieldState: childState }: FieldRenderProps<string>) => (
|
||||
<FieldWrapper error={childState.errors?.[0]?.message}>
|
||||
<div className="array-item-wrapper">
|
||||
<Input {...childField} size={'small'} />
|
||||
{index < field.value!.length - 1 ? (
|
||||
<Popover
|
||||
content={'swap with next element'}
|
||||
className={'icon-button-popover'}
|
||||
showArrow
|
||||
position={'topLeft'}
|
||||
>
|
||||
<Button
|
||||
theme="borderless"
|
||||
size={'small'}
|
||||
icon={<IconArrowDown />}
|
||||
onClick={() => field.swap(index, index + 1)}
|
||||
/>
|
||||
</Popover>
|
||||
) : null}
|
||||
<Popover
|
||||
content={'delete current element'}
|
||||
className={'icon-button-popover'}
|
||||
showArrow
|
||||
position={'topLeft'}
|
||||
>
|
||||
<Button
|
||||
theme="borderless"
|
||||
size={'small'}
|
||||
icon={<IconCrossCircleStroked />}
|
||||
onClick={() => field.delete(index)}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
</FieldWrapper>
|
||||
)}
|
||||
</Field>
|
||||
))}
|
||||
<div>
|
||||
<Button
|
||||
size={'small'}
|
||||
theme="borderless"
|
||||
icon={<IconPlus />}
|
||||
onClick={() => field.append('default')}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</FieldWrapper>
|
||||
)}
|
||||
</FieldArray>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface FormData {
|
||||
array: string[];
|
||||
}
|
||||
|
||||
const formMeta: FormMeta<FormData> = {
|
||||
render,
|
||||
validateTrigger: ValidateTrigger.onChange,
|
||||
defaultValues: {
|
||||
array: ['default'],
|
||||
},
|
||||
validate: {
|
||||
'array.*': ({ value }) =>
|
||||
value.length > 8 ? 'max length exceeded: current length is ' + value.length : undefined,
|
||||
},
|
||||
effect: {
|
||||
'array.*': [
|
||||
{
|
||||
event: DataEvent.onValueInit,
|
||||
effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
|
||||
console.log(name + ' value init to ', value);
|
||||
},
|
||||
},
|
||||
{
|
||||
event: DataEvent.onValueChange,
|
||||
effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
|
||||
console.log(name + ' value changed to ', value);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const nodeRegistry: WorkflowNodeRegistry = {
|
||||
type: 'custom',
|
||||
meta: {},
|
||||
defaultPorts: [{ type: 'output' }, { type: 'input' }],
|
||||
formMeta,
|
||||
};
|
||||
146
apps/docs/components/node-form/array/preview.tsx
Normal file
146
apps/docs/components/node-form/array/preview.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import {
|
||||
DEFAULT_INITIAL_DATA,
|
||||
defaultInitialDataTs,
|
||||
fieldWrapperCss,
|
||||
fieldWrapperTs,
|
||||
} from '@flowgram.ai/demo-node-form';
|
||||
|
||||
import { Editor } from '../editor.tsx';
|
||||
import { PreviewEditor } from '../../preview-editor.tsx';
|
||||
import { nodeRegistry } from './node-registry.tsx';
|
||||
|
||||
const nodeRegistryFile = {
|
||||
code: `import {
|
||||
DataEvent,
|
||||
EffectFuncProps,
|
||||
Field,
|
||||
FieldRenderProps,
|
||||
FormMeta,
|
||||
ValidateTrigger,
|
||||
WorkflowNodeRegistry,
|
||||
FieldArray,
|
||||
FieldArrayRenderProps,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
import { FieldWrapper } from '@flowgram.ai/demo-node-form';
|
||||
import { Input, Button, Popover } from '@douyinfe/semi-ui';
|
||||
import { IconPlus, IconCrossCircleStroked, IconArrowDown } from '@douyinfe/semi-icons';
|
||||
import './index.css';
|
||||
import '../index.css';
|
||||
|
||||
export const render = () => (
|
||||
<div className="demo-node-content">
|
||||
<div className="demo-node-title">Array Examples</div>
|
||||
<FieldArray name="array">
|
||||
{({ field, fieldState }: FieldArrayRenderProps<string>) => (
|
||||
<FieldWrapper title={'My Array'}>
|
||||
{field.map((child, index) => (
|
||||
<Field name={child.name} key={child.key}>
|
||||
{({ field: childField, fieldState: childState }: FieldRenderProps<string>) => (
|
||||
<FieldWrapper error={childState.errors?.[0]?.message}>
|
||||
<div className="array-item-wrapper">
|
||||
<Input {...childField} size={'small'} />
|
||||
{index < field.value!.length - 1 ? (
|
||||
<Popover
|
||||
content={'swap with next element'}
|
||||
className={'icon-button-popover'}
|
||||
showArrow
|
||||
position={'topLeft'}
|
||||
>
|
||||
<Button
|
||||
theme="borderless"
|
||||
size={'small'}
|
||||
icon={<IconArrowDown />}
|
||||
onClick={() => field.swap(index, index + 1)}
|
||||
/>
|
||||
</Popover>
|
||||
) : null}
|
||||
<Popover
|
||||
content={'delete current element'}
|
||||
className={'icon-button-popover'}
|
||||
showArrow
|
||||
position={'topLeft'}
|
||||
>
|
||||
<Button
|
||||
theme="borderless"
|
||||
size={'small'}
|
||||
icon={<IconCrossCircleStroked />}
|
||||
onClick={() => field.delete(index)}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
</FieldWrapper>
|
||||
)}
|
||||
</Field>
|
||||
))}
|
||||
<div>
|
||||
<Button
|
||||
size={'small'}
|
||||
theme="borderless"
|
||||
icon={<IconPlus />}
|
||||
onClick={() => field.append('default')}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</FieldWrapper>
|
||||
)}
|
||||
</FieldArray>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface FormData {
|
||||
array: string[];
|
||||
}
|
||||
|
||||
const formMeta: FormMeta<FormData> = {
|
||||
render,
|
||||
validateTrigger: ValidateTrigger.onChange,
|
||||
defaultValues: {
|
||||
array: ['default'],
|
||||
},
|
||||
validate: {
|
||||
'array.*': ({ value }) =>
|
||||
value.length > 8 ? 'max length exceeded: current length is ' + value.length : undefined,
|
||||
},
|
||||
effect: {
|
||||
'array.*': [
|
||||
{
|
||||
event: DataEvent.onValueInit,
|
||||
effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
|
||||
console.log(name + ' value init to ', value);
|
||||
},
|
||||
},
|
||||
{
|
||||
event: DataEvent.onValueChange,
|
||||
effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
|
||||
console.log(name + ' value changed to ', value);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const nodeRegistry: WorkflowNodeRegistry = {
|
||||
type: 'custom',
|
||||
meta: {},
|
||||
defaultPorts: [{ type: 'output' }, { type: 'input' }],
|
||||
formMeta,
|
||||
};
|
||||
|
||||
`,
|
||||
active: true,
|
||||
};
|
||||
|
||||
export const NodeFormArrayPreview = () => {
|
||||
const files = {
|
||||
'node-registry.tsx': nodeRegistryFile,
|
||||
'initial-data.ts': { code: defaultInitialDataTs, active: true },
|
||||
'field-wrapper.tsx': { code: fieldWrapperTs, active: true },
|
||||
'field-wrapper.css': { code: fieldWrapperCss, active: true },
|
||||
};
|
||||
return (
|
||||
<PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
|
||||
<Editor registry={nodeRegistry} initialData={DEFAULT_INITIAL_DATA} />
|
||||
</PreviewEditor>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
[
|
||||
"basic",
|
||||
"effect"
|
||||
"effect",
|
||||
"array"
|
||||
]
|
||||
|
||||
10
apps/docs/src/zh/examples/node-form/array.mdx
Normal file
10
apps/docs/src/zh/examples/node-form/array.mdx
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
outline: false
|
||||
---
|
||||
|
||||
|
||||
# 数组
|
||||
|
||||
import { NodeFormArrayPreview } from '../../../../components/node-form/array/preview';
|
||||
|
||||
<NodeFormArrayPreview />
|
||||
@ -4,6 +4,8 @@ import { Errors, ValidateTrigger, Warnings } from '@/types';
|
||||
import { FormModel } from '@/core/form-model';
|
||||
import { type FieldArrayModel } from '@/core/field-array-model';
|
||||
|
||||
import { FeedbackLevel } from '../src/types';
|
||||
|
||||
describe('FormArrayModel', () => {
|
||||
let formModel = new FormModel();
|
||||
describe('children', () => {
|
||||
@ -580,53 +582,171 @@ describe('FormArrayModel', () => {
|
||||
|
||||
it('can swap from 0 to middle index', () => {
|
||||
const arrayField = formModel.createFieldArray('arr');
|
||||
arrayField!.append('a');
|
||||
arrayField!.append('b');
|
||||
arrayField!.append('c');
|
||||
const a = arrayField!.append('a');
|
||||
const b = arrayField!.append('b');
|
||||
const c = arrayField!.append('c');
|
||||
|
||||
formModel.init({});
|
||||
|
||||
a.state.errors = {
|
||||
'arr.0': [{ name: 'arr.0', message: 'err0', level: FeedbackLevel.Error }],
|
||||
};
|
||||
b.state.errors = {
|
||||
'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
|
||||
};
|
||||
|
||||
expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
|
||||
arrayField.swap(0, 1);
|
||||
expect(formModel.values).toEqual({ arr: ['b', 'a', 'c'] });
|
||||
expect(formModel.getField('arr.0').state.errors).toEqual({
|
||||
'arr.0': [{ name: 'arr.0', message: 'err1', level: FeedbackLevel.Error }],
|
||||
});
|
||||
expect(formModel.getField('arr.1').state.errors).toEqual({
|
||||
'arr.1': [{ name: 'arr.1', message: 'err0', level: FeedbackLevel.Error }],
|
||||
});
|
||||
});
|
||||
|
||||
it('can swap from 0 to last index', () => {
|
||||
const arrayField = formModel.createFieldArray('arr');
|
||||
arrayField!.append('a');
|
||||
arrayField!.append('b');
|
||||
arrayField!.append('c');
|
||||
const a = arrayField!.append('a');
|
||||
const b = arrayField!.append('b');
|
||||
const c = arrayField!.append('c');
|
||||
|
||||
formModel.init({});
|
||||
|
||||
a.state.errors = {
|
||||
'arr.0': [{ name: 'arr.0', message: 'err0', level: FeedbackLevel.Error }],
|
||||
};
|
||||
c.state.errors = {
|
||||
'arr.2': [{ name: 'arr.2', message: 'err2', level: FeedbackLevel.Error }],
|
||||
};
|
||||
|
||||
expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
|
||||
arrayField.swap(0, 2);
|
||||
expect(formModel.values).toEqual({ arr: ['c', 'b', 'a'] });
|
||||
expect(formModel.getField('arr.0').state.errors).toEqual({
|
||||
'arr.0': [{ name: 'arr.0', message: 'err2', level: FeedbackLevel.Error }],
|
||||
});
|
||||
expect(formModel.getField('arr.2').state.errors).toEqual({
|
||||
'arr.2': [{ name: 'arr.2', message: 'err0', level: FeedbackLevel.Error }],
|
||||
});
|
||||
});
|
||||
it('can swap from middle index to last index', () => {
|
||||
const arrayField = formModel.createFieldArray('arr');
|
||||
arrayField!.append('a');
|
||||
arrayField!.append('b');
|
||||
arrayField!.append('c');
|
||||
const a = arrayField!.append('a');
|
||||
const b = arrayField!.append('b');
|
||||
const c = arrayField!.append('c');
|
||||
|
||||
formModel.init({});
|
||||
|
||||
b.state.errors = {
|
||||
'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
|
||||
};
|
||||
c.state.errors = {
|
||||
'arr.2': [{ name: 'arr.2', message: 'err2', level: FeedbackLevel.Error }],
|
||||
};
|
||||
|
||||
expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
|
||||
arrayField.swap(1, 2);
|
||||
expect(formModel.values).toEqual({ arr: ['a', 'c', 'b'] });
|
||||
expect(formModel.getField('arr.1').state.errors).toEqual({
|
||||
'arr.1': [{ name: 'arr.1', message: 'err2', level: FeedbackLevel.Error }],
|
||||
});
|
||||
expect(formModel.getField('arr.2').state.errors).toEqual({
|
||||
'arr.2': [{ name: 'arr.2', message: 'err1', level: FeedbackLevel.Error }],
|
||||
});
|
||||
});
|
||||
it('can swap from middle index to another middle index', () => {
|
||||
const arrayField = formModel.createFieldArray('arr');
|
||||
arrayField!.append('a');
|
||||
arrayField!.append('b');
|
||||
arrayField!.append('c');
|
||||
const b = arrayField!.append('b');
|
||||
const c = arrayField!.append('c');
|
||||
arrayField!.append('d');
|
||||
|
||||
formModel.init({});
|
||||
|
||||
b.state.errors = {
|
||||
'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
|
||||
};
|
||||
c.state.errors = {
|
||||
'arr.2': [{ name: 'arr.2', message: 'err2', level: FeedbackLevel.Error }],
|
||||
};
|
||||
|
||||
expect(formModel.values).toEqual({ arr: ['a', 'b', 'c', 'd'] });
|
||||
arrayField.swap(1, 2);
|
||||
expect(formModel.values).toEqual({ arr: ['a', 'c', 'b', 'd'] });
|
||||
expect(formModel.getField('arr.1').state.errors).toEqual({
|
||||
'arr.1': [{ name: 'arr.1', message: 'err2', level: FeedbackLevel.Error }],
|
||||
});
|
||||
expect(formModel.getField('arr.2').state.errors).toEqual({
|
||||
'arr.2': [{ name: 'arr.2', message: 'err1', level: FeedbackLevel.Error }],
|
||||
});
|
||||
});
|
||||
|
||||
it('can swap for nested array', () => {
|
||||
const arrayField = formModel.createFieldArray('arr');
|
||||
const a = arrayField!.append({ x: 'x0', y: 'y0' });
|
||||
const b = arrayField!.append({ x: 'x1', y: 'y1' });
|
||||
const ax = formModel.createField('arr.0.x');
|
||||
const ay = formModel.createField('arr.0.y');
|
||||
const bx = formModel.createField('arr.1.x');
|
||||
const by = formModel.createField('arr.1.y');
|
||||
|
||||
formModel.init({});
|
||||
|
||||
ax.state.errors = {
|
||||
'arr.0.x': [{ name: 'arr.0.x', message: 'err0x', level: FeedbackLevel.Error }],
|
||||
};
|
||||
bx.state.errors = {
|
||||
'arr.1.x': [{ name: 'arr.1.x', message: 'err1x', level: FeedbackLevel.Error }],
|
||||
};
|
||||
|
||||
expect(formModel.values).toEqual({
|
||||
arr: [
|
||||
{ x: 'x0', y: 'y0' },
|
||||
{ x: 'x1', y: 'y1' },
|
||||
],
|
||||
});
|
||||
arrayField.swap(0, 1);
|
||||
expect(formModel.values).toEqual({
|
||||
arr: [
|
||||
{ x: 'x1', y: 'y1' },
|
||||
{ x: 'x0', y: 'y0' },
|
||||
],
|
||||
});
|
||||
expect(formModel.getField('arr.0.x').state.errors).toEqual({
|
||||
'arr.0.x': [{ name: 'arr.0.x', message: 'err1x', level: FeedbackLevel.Error }],
|
||||
});
|
||||
expect(formModel.getField('arr.1.x').state.errors).toEqual({
|
||||
'arr.1.x': [{ name: 'arr.1.x', message: 'err0x', level: FeedbackLevel.Error }],
|
||||
});
|
||||
|
||||
// assert form.state.errors
|
||||
expect(formModel.state.errors['arr.0.x']).toEqual([
|
||||
{ name: 'arr.0.x', message: 'err1x', level: FeedbackLevel.Error },
|
||||
]);
|
||||
expect(formModel.state.errors['arr.1.x']).toEqual([
|
||||
{ name: 'arr.1.x', message: 'err0x', level: FeedbackLevel.Error },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have correct form.state.errors after swapping invalid field with valid field', () => {
|
||||
const arrayField = formModel.createFieldArray('arr');
|
||||
const a = arrayField!.append('a');
|
||||
const b = arrayField!.append('b');
|
||||
arrayField!.append('c');
|
||||
|
||||
formModel.init({});
|
||||
|
||||
b.state.errors = {
|
||||
'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
|
||||
};
|
||||
|
||||
arrayField.swap(0, 1);
|
||||
expect(formModel.getField('arr.0').state.errors).toEqual({
|
||||
'arr.0': [{ name: 'arr.0', message: 'err1', level: FeedbackLevel.Error }],
|
||||
});
|
||||
expect(formModel.getField('arr.1').state.errors).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should trigger array effect and child effect', () => {
|
||||
|
||||
@ -204,6 +204,7 @@ export class FieldArrayModel<TValue = FieldValue> extends FieldModel<Array<TValu
|
||||
);
|
||||
}
|
||||
|
||||
const oldFormValues = this.form.values;
|
||||
const tempValue = [...this.value];
|
||||
|
||||
const fromValue = tempValue[from];
|
||||
@ -212,7 +213,46 @@ export class FieldArrayModel<TValue = FieldValue> extends FieldModel<Array<TValu
|
||||
tempValue[to] = fromValue;
|
||||
tempValue[from] = toValue;
|
||||
|
||||
this.form.setValueIn(this.name, tempValue);
|
||||
this.form.store.setIn(this.path, tempValue);
|
||||
this.form.fireOnFormValuesChange({
|
||||
values: this.form.values,
|
||||
prevValues: oldFormValues,
|
||||
name: this.name,
|
||||
options: {
|
||||
action: 'array-swap',
|
||||
indexes: [from, to],
|
||||
},
|
||||
});
|
||||
|
||||
// swap related FieldModels
|
||||
const newFieldMap = new Map<string, FieldModel>(this.form.fieldMap);
|
||||
|
||||
const fromFields = this.findAllFieldsAt(from);
|
||||
const toFields = this.findAllFieldsAt(to);
|
||||
const fromRootPath = this.getPathAt(from);
|
||||
const toRootPath = this.getPathAt(to);
|
||||
const leafFieldsModified: FieldModel[] = [];
|
||||
fromFields.forEach((f) => {
|
||||
const newName = f.path.replaceParent(fromRootPath, toRootPath).toString();
|
||||
f.name = newName;
|
||||
if (!f.children.length) {
|
||||
f.updateNameForLeafState(newName);
|
||||
leafFieldsModified.push(f);
|
||||
}
|
||||
newFieldMap.set(newName, f);
|
||||
});
|
||||
toFields.forEach((f) => {
|
||||
const newName = f.path.replaceParent(toRootPath, fromRootPath).toString();
|
||||
f.name = newName;
|
||||
if (!f.children.length) {
|
||||
f.updateNameForLeafState(newName);
|
||||
}
|
||||
newFieldMap.set(newName, f);
|
||||
leafFieldsModified.push(f);
|
||||
});
|
||||
this.form.fieldMap = newFieldMap;
|
||||
leafFieldsModified.forEach((f) => f.bubbleState());
|
||||
this.form.alignStateWithFieldMap();
|
||||
}
|
||||
|
||||
move(from: number, to: number) {
|
||||
@ -236,5 +276,53 @@ export class FieldArrayModel<TValue = FieldValue> extends FieldModel<Array<TValu
|
||||
tempValue.splice(to, 0, fromValue);
|
||||
|
||||
this.form.setValueIn(this.name, tempValue);
|
||||
|
||||
// todo(fix): should move fields in order to make sure fields' state is also moved
|
||||
}
|
||||
|
||||
protected insertAt(index: number, value: TValue) {
|
||||
if (!this.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index > this.value.length) {
|
||||
throw new Error(`[Form]: FieldArrayModel.insertAt Error: index exceeds array boundary`);
|
||||
}
|
||||
|
||||
const tempValue = [...this.value];
|
||||
tempValue.splice(index, 0, value);
|
||||
this.form.setValueIn(this.name, tempValue);
|
||||
|
||||
// todo: should move field in order to make sure field state is also moved
|
||||
}
|
||||
|
||||
/**
|
||||
* get element path at given index
|
||||
* @param index
|
||||
* @protected
|
||||
*/
|
||||
protected getPathAt(index: number) {
|
||||
return this.path.concat(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* find all fields including child and grandchild fields at given index.
|
||||
* @param index
|
||||
* @protected
|
||||
*/
|
||||
protected findAllFieldsAt(index: number) {
|
||||
const rootPath = this.getPathAt(index);
|
||||
const rootPathString = rootPath.toString();
|
||||
|
||||
const res: FieldModel[] = this.form.fieldMap.get(rootPathString)
|
||||
? [this.form.fieldMap.get(rootPathString)!]
|
||||
: [];
|
||||
|
||||
this.form.fieldMap.forEach((field, fieldName) => {
|
||||
if (rootPath.isChildOrGrandChild(fieldName)) {
|
||||
res.push(field);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { Errors, Feedback, OnFormValuesChangePayload, ValidateTrigger, Warnings
|
||||
import { Path } from './path';
|
||||
|
||||
export function updateFeedbacksName(feedbacks: Feedback<any>[], name: string) {
|
||||
return feedbacks.map((f) => ({
|
||||
return (feedbacks || []).map((f) => ({
|
||||
...f,
|
||||
name,
|
||||
}));
|
||||
@ -91,7 +91,7 @@ export namespace FieldEventUtils {
|
||||
) {
|
||||
const { name: changedName, options } = payload;
|
||||
|
||||
if (options?.action === 'array-splice') {
|
||||
if (options?.action === 'array-splice' || options?.action === 'array-swap') {
|
||||
// const splicedIndexes = options?.indexes || [];
|
||||
//
|
||||
// const splicedPaths = splicedIndexes.map(index => new Path(changedName).concat(index));
|
||||
@ -109,7 +109,7 @@ export namespace FieldEventUtils {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// splice 情况下仅触发数组field的校验
|
||||
// splice 和 swap 都属于数组跟级别的变更,仅需触发数组field的校验, 无需校验子项
|
||||
return fieldName === changedName;
|
||||
}
|
||||
|
||||
|
||||
@ -108,7 +108,7 @@ export interface CreateFormReturn<TValues> {
|
||||
}
|
||||
|
||||
export interface OnFormValuesChangeOptions {
|
||||
action?: 'array-append' | 'array-splice';
|
||||
action?: 'array-append' | 'array-splice' | 'array-swap';
|
||||
indexes?: number[];
|
||||
}
|
||||
|
||||
|
||||
@ -135,6 +135,10 @@ export class FormModelV2 extends FormModel implements Disposable {
|
||||
return this.node.getNodeRegistry().formMeta;
|
||||
}
|
||||
|
||||
get values() {
|
||||
return this.nativeFormModel?.values;
|
||||
}
|
||||
|
||||
protected _feedbacks: FormFeedback[] = [];
|
||||
|
||||
get feedbacks(): FormFeedback[] {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user