@flowgram-vue/form 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/index.cjs +0 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
- package/src/composables/index.ts +13 -0
- package/src/composables/use-current-field-state.ts +29 -0
- package/src/composables/use-current-field.ts +31 -0
- package/src/composables/use-field-validate.ts +25 -0
- package/src/composables/use-field.ts +47 -0
- package/src/composables/use-form-model.ts +17 -0
- package/src/composables/use-form-state.ts +25 -0
- package/src/composables/use-form.ts +16 -0
- package/src/composables/use-watch.ts +34 -0
- package/src/constants.ts +34 -0
- package/src/core/create-form.ts +58 -0
- package/src/core/field-array-model.ts +333 -0
- package/src/core/field-model.ts +396 -0
- package/src/core/form-model.ts +362 -0
- package/src/core/index.ts +14 -0
- package/src/core/path.ts +125 -0
- package/src/core/store.ts +33 -0
- package/src/core/to-field-array.ts +52 -0
- package/src/core/to-field.ts +80 -0
- package/src/core/to-form.ts +64 -0
- package/src/core/utils.ts +123 -0
- package/src/env.d.ts +10 -0
- package/src/index.ts +30 -0
- package/src/types/common.ts +6 -0
- package/src/types/field.ts +193 -0
- package/src/types/form.ts +145 -0
- package/src/types/index.ts +8 -0
- package/src/types/validate.ts +88 -0
- package/src/utils/dom.ts +31 -0
- package/src/utils/event.ts +32 -0
- package/src/utils/glob.ts +279 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/object.ts +107 -0
- package/src/utils/validate.ts +42 -0
- package/src/vue/context.ts +12 -0
- package/src/vue/field-array.vue +117 -0
- package/src/vue/field.vue +127 -0
- package/src/vue/form.vue +61 -0
- package/src/vue/index.ts +11 -0
- package/src/vue/types.ts +45 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { onBeforeUnmount } from 'vue';
|
|
7
|
+
|
|
8
|
+
import { useRefresh } from '@flowgram-vue/utils';
|
|
9
|
+
|
|
10
|
+
import { FieldName, FieldValue } from '../types';
|
|
11
|
+
import { useFormModel } from './use-form-model';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Listen to the field data change and refresh the Vue component.
|
|
15
|
+
* @param name the field's uniq name (path)
|
|
16
|
+
*/
|
|
17
|
+
export function useWatch<TValue = FieldValue>(name: FieldName): TValue {
|
|
18
|
+
const refresh = useRefresh();
|
|
19
|
+
|
|
20
|
+
const formModel = useFormModel();
|
|
21
|
+
|
|
22
|
+
if (!formModel) {
|
|
23
|
+
throw new Error('[Form] error in useWatch, formModel not found');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const disposable = formModel.onFormValuesUpdated(({ name: updatedName }) => {
|
|
27
|
+
if (updatedName === name) {
|
|
28
|
+
refresh();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
onBeforeUnmount(() => disposable.dispose());
|
|
32
|
+
|
|
33
|
+
return formModel.getValueIn<TValue>(name);
|
|
34
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FieldModelState } from './types/field';
|
|
7
|
+
import { FormModelState } from './types';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_FIELD_STATE: FieldModelState = {
|
|
10
|
+
invalid: false,
|
|
11
|
+
isDirty: false,
|
|
12
|
+
isTouched: false,
|
|
13
|
+
isValidating: false,
|
|
14
|
+
};
|
|
15
|
+
export const DEFAULT_FORM_STATE: FormModelState = {
|
|
16
|
+
invalid: false,
|
|
17
|
+
isDirty: false,
|
|
18
|
+
isTouched: false,
|
|
19
|
+
isValidating: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function createFormModelState(initialState?: Partial<FormModelState>) {
|
|
23
|
+
if (!initialState) {
|
|
24
|
+
return { ...DEFAULT_FORM_STATE };
|
|
25
|
+
}
|
|
26
|
+
return { ...DEFAULT_FORM_STATE, ...initialState };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createFieldModelState(initialState?: Partial<FieldModelState>): FieldModelState {
|
|
30
|
+
if (!initialState) {
|
|
31
|
+
return { ...DEFAULT_FIELD_STATE };
|
|
32
|
+
}
|
|
33
|
+
return { ...DEFAULT_FIELD_STATE, ...initialState };
|
|
34
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CreateFormReturn, FormOptions } from '../types/form';
|
|
7
|
+
import { Field, FieldArray, FieldName, FieldValue } from '../types';
|
|
8
|
+
import { toForm } from './to-form';
|
|
9
|
+
import { toFieldArray } from './to-field-array';
|
|
10
|
+
import { toField } from './to-field';
|
|
11
|
+
import { FormModel } from './form-model';
|
|
12
|
+
import { FieldModel } from './field-model';
|
|
13
|
+
import { FieldArrayModel } from './field-array-model';
|
|
14
|
+
|
|
15
|
+
// export interface CreateFormOptions<TValues = any> extends FormOptions<TValues> {
|
|
16
|
+
// parentContainer?: interfaces.Container;
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
export type CreateFormOptions<T = any> = FormOptions<T> & {
|
|
20
|
+
/**
|
|
21
|
+
* 为 true 时,createForm 不会对form 初始化, 用户需要手动调用 control.init()
|
|
22
|
+
* 该配置主要为了解决,用户需要去监听一些form 的初始化事件,那么他需要再配置完监听后再初始化。
|
|
23
|
+
* 该配置默认为 false
|
|
24
|
+
**/
|
|
25
|
+
disableAutoInit?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function createForm<TValues>(
|
|
29
|
+
options?: CreateFormOptions<TValues>
|
|
30
|
+
): CreateFormReturn<TValues> {
|
|
31
|
+
const { disableAutoInit = false, ...formOptions } = options || {};
|
|
32
|
+
const formModel = new FormModel();
|
|
33
|
+
|
|
34
|
+
if (!disableAutoInit) {
|
|
35
|
+
formModel.init(formOptions || {});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
form: toForm(formModel),
|
|
40
|
+
control: {
|
|
41
|
+
_formModel: formModel,
|
|
42
|
+
getField: <
|
|
43
|
+
TFieldValue = FieldValue,
|
|
44
|
+
TFieldModel extends Field<TFieldValue> | FieldArray<TFieldValue> = Field
|
|
45
|
+
>(
|
|
46
|
+
name: FieldName
|
|
47
|
+
) => {
|
|
48
|
+
const fieldModel = formModel.getField(name);
|
|
49
|
+
if (fieldModel) {
|
|
50
|
+
return fieldModel instanceof FieldArrayModel
|
|
51
|
+
? toFieldArray<TFieldValue>(fieldModel as unknown as FieldArrayModel<TFieldValue>)
|
|
52
|
+
: toField<TFieldValue>(fieldModel as unknown as FieldModel<TFieldValue>);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
init: () => formModel.init(formOptions || {}),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Emitter } from '@flowgram-vue/utils';
|
|
7
|
+
|
|
8
|
+
import { FieldValue } from '../types';
|
|
9
|
+
import { Path } from './path';
|
|
10
|
+
import { FieldModel } from './field-model';
|
|
11
|
+
|
|
12
|
+
export class FieldArrayModel<TValue = FieldValue> extends FieldModel<Array<TValue>> {
|
|
13
|
+
protected onAppendEmitter = new Emitter<{
|
|
14
|
+
index: number;
|
|
15
|
+
value: TValue | undefined;
|
|
16
|
+
arrayValue: Array<TValue>;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
readonly onAppend = this.onAppendEmitter.event;
|
|
20
|
+
|
|
21
|
+
protected onDeleteEmitter = new Emitter<{
|
|
22
|
+
arrayValue: Array<TValue> | undefined;
|
|
23
|
+
index: number;
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
readonly onDelete = this.onDeleteEmitter.event;
|
|
27
|
+
|
|
28
|
+
get children() {
|
|
29
|
+
const fields: FieldModel[] = [];
|
|
30
|
+
this.form.fieldMap.forEach((field, name: string) => {
|
|
31
|
+
if (this.path.isChild(name)) {
|
|
32
|
+
fields.push(field);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 按 index 排序
|
|
37
|
+
return fields.sort((f1, f2) => {
|
|
38
|
+
const p1 = f1.path.value;
|
|
39
|
+
const p2 = f2.path.value;
|
|
40
|
+
const i1 = parseInt(p1[p1.length - 1]);
|
|
41
|
+
const i2 = parseInt(p2[p2.length - 1]);
|
|
42
|
+
return i1 - i2;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
map<T>(cb: (f: FieldModel, index: number, arr: FieldModel[]) => T) {
|
|
47
|
+
const fields = (this.value || []).map((v: TValue, i: number) => {
|
|
48
|
+
const pathString = this.path.concat(i).toString();
|
|
49
|
+
let field = this.form.getField(pathString);
|
|
50
|
+
if (!field) {
|
|
51
|
+
field = this.form.createField(pathString);
|
|
52
|
+
}
|
|
53
|
+
return field;
|
|
54
|
+
});
|
|
55
|
+
return fields.map(cb);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
append(value?: TValue) {
|
|
59
|
+
const curLength = this.value?.length || 0;
|
|
60
|
+
const newElemPath = this.path.concat(curLength).toString();
|
|
61
|
+
const newElemField = this.form.createField(newElemPath);
|
|
62
|
+
const newArrayValue = this.value ? [...this.value, value] : [value];
|
|
63
|
+
|
|
64
|
+
const prevFormValues = this.form.values;
|
|
65
|
+
|
|
66
|
+
// 设置新的数组值并触发事件
|
|
67
|
+
this.form.store.setIn(new Path(this.name), newArrayValue);
|
|
68
|
+
this.form.fireOnFormValuesChange({
|
|
69
|
+
values: this.form.values,
|
|
70
|
+
prevValues: prevFormValues,
|
|
71
|
+
name: this.name,
|
|
72
|
+
options: {
|
|
73
|
+
action: 'array-append',
|
|
74
|
+
indexes: [curLength],
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
// 触发新元素的初始值变更
|
|
78
|
+
this.form.fireOnFormValuesInit({
|
|
79
|
+
values: this.form.values,
|
|
80
|
+
prevValues: prevFormValues,
|
|
81
|
+
name: newElemPath,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.onAppendEmitter.fire({
|
|
85
|
+
value,
|
|
86
|
+
arrayValue: this.value as Array<TValue>,
|
|
87
|
+
index: this.value!.length - 1,
|
|
88
|
+
});
|
|
89
|
+
return newElemField;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Delete the element in given index and delete the corresponding FieldModel as well
|
|
94
|
+
* @param index
|
|
95
|
+
*/
|
|
96
|
+
delete(index: number) {
|
|
97
|
+
// const field = this.form.getField(name);
|
|
98
|
+
// if (!field) {
|
|
99
|
+
// throw new Error(
|
|
100
|
+
// `[Form] Error in FieldArrayModel.delete: delete failed, no field found for name ${name}`,
|
|
101
|
+
// );
|
|
102
|
+
// }
|
|
103
|
+
// const index = field.path.getArrayIndex(this.path);
|
|
104
|
+
this._splice(index, 1);
|
|
105
|
+
|
|
106
|
+
this.onDeleteEmitter.fire({ arrayValue: this.value, index });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_splice(start: number, deleteCount = 1) {
|
|
110
|
+
if (start < 0 || deleteCount < 0) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`[Form] Error in FieldArrayModel.splice: Invalid Params, start and deleteCount should > 0`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!this.value || this.value.length === 0 || deleteCount > this.value.length) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`[Form] Error in FieldArrayModel.splice: delete count exceeds array length, tried to delete ${deleteCount} elements, but array length is ${
|
|
119
|
+
this.value?.length || 0
|
|
120
|
+
}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const oldFormValues = this.form.values;
|
|
124
|
+
|
|
125
|
+
const tempValue = [...this.value];
|
|
126
|
+
tempValue.splice(start, deleteCount);
|
|
127
|
+
|
|
128
|
+
// 设置数组值并触发事件
|
|
129
|
+
this.form.store.setIn(new Path(this.name), tempValue);
|
|
130
|
+
|
|
131
|
+
this.form.fireOnFormValuesChange({
|
|
132
|
+
values: this.form.values,
|
|
133
|
+
prevValues: oldFormValues,
|
|
134
|
+
name: this.name,
|
|
135
|
+
options: {
|
|
136
|
+
action: 'array-splice',
|
|
137
|
+
indexes: Array.from({ length: deleteCount }, (_, i) => i + start),
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const children = this.children;
|
|
142
|
+
|
|
143
|
+
// 如果要删除的元素都在数组末端, 直接删除
|
|
144
|
+
if (start + deleteCount >= children.length) {
|
|
145
|
+
for (let i = start; i < children.length; i++) {
|
|
146
|
+
this.form.disposeField(children[i].name);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const toDispose: FieldModel[] = [];
|
|
151
|
+
const newFieldMap = new Map<string, FieldModel>(this.form.fieldMap);
|
|
152
|
+
|
|
153
|
+
const recursiveHandleChildField = (field: FieldModel, index: number) => {
|
|
154
|
+
if (field.children?.length) {
|
|
155
|
+
field.children.forEach((cField) => {
|
|
156
|
+
recursiveHandleChildField(cField, index);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// start 以前的项不变
|
|
160
|
+
if (index < start) {
|
|
161
|
+
newFieldMap.set(field.name, field);
|
|
162
|
+
}
|
|
163
|
+
// 要删除的项, 放入toDispose
|
|
164
|
+
else if (index < start + deleteCount) {
|
|
165
|
+
toDispose.push(field);
|
|
166
|
+
}
|
|
167
|
+
// 剩余的项 index 向前移动 {deleteCount} 位, 并触发变更事件
|
|
168
|
+
else {
|
|
169
|
+
const originName = field.name;
|
|
170
|
+
const targetName = field.path
|
|
171
|
+
.replaceParent(this.path.concat(index), this.path.concat(index - deleteCount))
|
|
172
|
+
.toString();
|
|
173
|
+
newFieldMap.set(targetName, field);
|
|
174
|
+
if (!field.children.length) {
|
|
175
|
+
field.updateNameForLeafState(targetName);
|
|
176
|
+
field.bubbleState();
|
|
177
|
+
}
|
|
178
|
+
field.name = targetName;
|
|
179
|
+
|
|
180
|
+
// 最后 {deleteCount} 项,需要fire 被变更为undefined, 并从 newMap 中删除
|
|
181
|
+
if (index > children.length - deleteCount - 1) {
|
|
182
|
+
newFieldMap.delete(originName);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// 对数组所有子项做删除或 index 移动操作
|
|
188
|
+
children.map((field, index) => {
|
|
189
|
+
recursiveHandleChildField(field, index);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
toDispose.forEach((f) => {
|
|
193
|
+
f.dispose();
|
|
194
|
+
});
|
|
195
|
+
this.form.fieldMap = newFieldMap;
|
|
196
|
+
this.form.alignStateWithFieldMap();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
swap(from: number, to: number) {
|
|
200
|
+
if (!this.value) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (from < 0 || to < 0 || from > this.value.length - 1 || to > this.value.length - 1) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`[Form]: FieldArrayModel.swap Error: invalid params 'form' and 'to', form=${from} to=${to}. expect the value between 0 to ${
|
|
207
|
+
length - 1
|
|
208
|
+
}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const oldFormValues = this.form.values;
|
|
213
|
+
const tempValue = [...this.value];
|
|
214
|
+
|
|
215
|
+
const fromValue = tempValue[from];
|
|
216
|
+
const toValue = tempValue[to];
|
|
217
|
+
|
|
218
|
+
tempValue[to] = fromValue;
|
|
219
|
+
tempValue[from] = toValue;
|
|
220
|
+
|
|
221
|
+
this.form.store.setIn(this.path, tempValue);
|
|
222
|
+
this.form.fireOnFormValuesChange({
|
|
223
|
+
values: this.form.values,
|
|
224
|
+
prevValues: oldFormValues,
|
|
225
|
+
name: this.name,
|
|
226
|
+
options: {
|
|
227
|
+
action: 'array-swap',
|
|
228
|
+
indexes: [from, to],
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// swap related FieldModels
|
|
233
|
+
const newFieldMap = new Map<string, FieldModel>(this.form.fieldMap);
|
|
234
|
+
|
|
235
|
+
const fromFields = this.findAllFieldsAt(from);
|
|
236
|
+
const toFields = this.findAllFieldsAt(to);
|
|
237
|
+
const fromRootPath = this.getPathAt(from);
|
|
238
|
+
const toRootPath = this.getPathAt(to);
|
|
239
|
+
const leafFieldsModified: FieldModel[] = [];
|
|
240
|
+
fromFields.forEach((f) => {
|
|
241
|
+
const newName = f.path.replaceParent(fromRootPath, toRootPath).toString();
|
|
242
|
+
f.name = newName;
|
|
243
|
+
if (!f.children.length) {
|
|
244
|
+
f.updateNameForLeafState(newName);
|
|
245
|
+
leafFieldsModified.push(f);
|
|
246
|
+
}
|
|
247
|
+
newFieldMap.set(newName, f);
|
|
248
|
+
});
|
|
249
|
+
toFields.forEach((f) => {
|
|
250
|
+
const newName = f.path.replaceParent(toRootPath, fromRootPath).toString();
|
|
251
|
+
f.name = newName;
|
|
252
|
+
if (!f.children.length) {
|
|
253
|
+
f.updateNameForLeafState(newName);
|
|
254
|
+
}
|
|
255
|
+
newFieldMap.set(newName, f);
|
|
256
|
+
leafFieldsModified.push(f);
|
|
257
|
+
});
|
|
258
|
+
this.form.fieldMap = newFieldMap;
|
|
259
|
+
leafFieldsModified.forEach((f) => f.bubbleState());
|
|
260
|
+
this.form.alignStateWithFieldMap();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
move(from: number, to: number) {
|
|
264
|
+
if (!this.value) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (from < 0 || to < 0 || from > this.value.length - 1 || to > this.value.length - 1) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`[Form]: FieldArrayModel.move Error: invalid params 'form' and 'to', form=${from} to=${to}. expect the value between 0 to ${
|
|
271
|
+
length - 1
|
|
272
|
+
}`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const tempValue = [...this.value];
|
|
277
|
+
|
|
278
|
+
const fromValue = tempValue[from];
|
|
279
|
+
|
|
280
|
+
tempValue.splice(from, 1);
|
|
281
|
+
tempValue.splice(to, 0, fromValue);
|
|
282
|
+
|
|
283
|
+
this.form.setValueIn(this.name, tempValue);
|
|
284
|
+
|
|
285
|
+
// todo(fix): should move fields in order to make sure fields' state is also moved
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
protected insertAt(index: number, value: TValue) {
|
|
289
|
+
if (!this.value) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (index < 0 || index > this.value.length) {
|
|
294
|
+
throw new Error(`[Form]: FieldArrayModel.insertAt Error: index exceeds array boundary`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const tempValue = [...this.value];
|
|
298
|
+
tempValue.splice(index, 0, value);
|
|
299
|
+
this.form.setValueIn(this.name, tempValue);
|
|
300
|
+
|
|
301
|
+
// todo: should move field in order to make sure field state is also moved
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* get element path at given index
|
|
306
|
+
* @param index
|
|
307
|
+
* @protected
|
|
308
|
+
*/
|
|
309
|
+
protected getPathAt(index: number) {
|
|
310
|
+
return this.path.concat(index);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* find all fields including child and grandchild fields at given index.
|
|
315
|
+
* @param index
|
|
316
|
+
* @protected
|
|
317
|
+
*/
|
|
318
|
+
protected findAllFieldsAt(index: number) {
|
|
319
|
+
const rootPath = this.getPathAt(index);
|
|
320
|
+
const rootPathString = rootPath.toString();
|
|
321
|
+
|
|
322
|
+
const res: FieldModel[] = this.form.fieldMap.get(rootPathString)
|
|
323
|
+
? [this.form.fieldMap.get(rootPathString)!]
|
|
324
|
+
: [];
|
|
325
|
+
|
|
326
|
+
this.form.fieldMap.forEach((field, fieldName) => {
|
|
327
|
+
if (rootPath.isChildOrGrandChild(fieldName)) {
|
|
328
|
+
res.push(field);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
return res;
|
|
332
|
+
}
|
|
333
|
+
}
|