From 36143ef0b324ab1dabda8f9eb3ec63e6debef605 Mon Sep 17 00:00:00 2001 From: Janry Date: Tue, 18 Jan 2022 23:43:44 +0800 Subject: [PATCH] feat(core): support index/indexes properties (#2769) --- packages/core/docs/api/models/Field.md | 96 ++++++++++---------- packages/core/docs/api/models/Field.zh-CN.md | 96 ++++++++++---------- packages/core/src/__tests__/array.spec.ts | 9 +- packages/core/src/__tests__/field.spec.ts | 2 +- packages/core/src/models/BaseField.ts | 16 +++- packages/core/src/models/Field.ts | 3 +- packages/core/src/models/VoidField.ts | 4 +- packages/core/src/shared/internals.ts | 7 +- 8 files changed, 125 insertions(+), 108 deletions(-) diff --git a/packages/core/docs/api/models/Field.md b/packages/core/docs/api/models/Field.md index 9139c087c39..47241f3b3df 100644 --- a/packages/core/docs/api/models/Field.md +++ b/packages/core/docs/api/models/Field.md @@ -10,53 +10,55 @@ All model attributes are listed below. If the attribute is writable, then we can ## Attributes -| Property | Description | Type | Read-only or not | Default value | -| -------------- | ------------------------------------------------- | -------------------------------------------------- | ---------------- | ------------- | -| initialized | Has the field been initialized | Boolean | No | `false` | -| mounted | Is the field mounted | Boolean | No | `false` | -| unmounted | Is the field unmounted | Boolean | No | `false` | -| address | Field node path | [FormPath](/api/entry/form-path) | Yes | | -| path | Field data path | [FormPath](/api/entry/form-path) | Yes | | -| title | Field Title | [FieldMessage](#fieldmessage) | No | `""` | -| description | Field description | [FieldMessage](#fieldmessage) | No | `""` | -| loading | Field loading status | Boolean | No | `false` | -| validating | Is the field being validated | Boolean | No | `false` | -| modified | Whether the field tree has been manually modified | Boolean | No | `false` | -| selfModified | Whether the field has been manually modified | Boolean | No | `false` | -| active | Is the field active | Boolean | No | `false` | -| visited | Whether the field has been visited | Boolean | No | `false` | -| inputValue | Field input value | Any | No | `null` | -| inputValues | Field input value collection | Array | No | `[]` | -| dataSource | Field data source | Array | No | `[]` | -| validator | Field validator | [FieldValidator](#fieldvalidator) | No | `null` | -| decorator | field decorator | Any[] | No | `null` | -| component | Field component | Any[] | No | `null` | -| feedbacks | Field feedback information | [IFieldFeedback](#ifieldfeedback)[] | No | `[]` | -| parent | Parent field | [GeneralField](#generalfield) | yes | `null` | -| errors | Field all error message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | -| warnings | Field all warning message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | -| successes | Field all success message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | -| valid | Is the all field valid(include children) | Boolean | Yes | `true` | -| invalid | Is the all field illegal(include children) | Boolean | Yes | `false` | -| value | Field value | Any | No | | -| initialValue | Field default value | Any | No | | -| display | Field display status | [FieldDisplayTypes](#fielddisplaytypes) | No | `"visible"` | -| pattern | Field interaction mode | [FieldPatternTypes](#fieldpatterntypes) | No | `"editable"` | -| required | Is the field required | Boolean | No | `false` | -| hidden | Whether the field is hidden | Boolean | No | `false` | -| visible | Whether the field is displayed | Boolean | No | `true` | -| disabled | Whether the field is disabled | Boolean | No | `false` | -| readOnly | Is the field read-only | Boolean | No | `false` | -| readPretty | Whether the field is in the reading state | Boolean | No | `false` | -| editable | Field is editable | Boolean | No | `true` | -| validateStatus | Field validation status | [FieldValidateStatus](#fieldvalidatestatus) | yes | `null` | -| content | Field content, usually as a child node | any | No | `null` | -| data | Field extends properties | Object | No | `null` | -| selfErrors | Field own error message | [FieldMessage](#fieldmessage)[] | No | `[]` | -| selfWarnings | Field own warning message | [FieldMessage](#fieldmessage)[] | No | `[]` | -| selfSuccesses | Success message of the field itself | [FieldMessage](#fieldmessage)[] | No | `[]` | -| selfValid | Is the field valid | Boolean | Yes | `true` | -| selfInvalid | Is the field itself illegal | Boolean | Yes | `false` | +| Property | Description | Type | Read-only or not | Default value | +| -------------- | --------------------------------------------------- | -------------------------------------------------- | ---------------- | ------------- | +| initialized | Has the field been initialized | Boolean | No | `false` | +| mounted | Is the field mounted | Boolean | No | `false` | +| unmounted | Is the field unmounted | Boolean | No | `false` | +| address | Field node path | [FormPath](/api/entry/form-path) | Yes | | +| path | Field data path | [FormPath](/api/entry/form-path) | Yes | | +| title | Field Title | [FieldMessage](#fieldmessage) | No | `""` | +| description | Field description | [FieldMessage](#fieldmessage) | No | `""` | +| loading | Field loading status | Boolean | No | `false` | +| validating | Is the field being validated | Boolean | No | `false` | +| modified | Whether the field tree has been manually modified | Boolean | No | `false` | +| selfModified | Whether the field has been manually modified | Boolean | No | `false` | +| active | Is the field active | Boolean | No | `false` | +| visited | Whether the field has been visited | Boolean | No | `false` | +| inputValue | Field input value | Any | No | `null` | +| inputValues | Field input value collection | Array | No | `[]` | +| dataSource | Field data source | Array | No | `[]` | +| validator | Field validator | [FieldValidator](#fieldvalidator) | No | `null` | +| decorator | field decorator | Any[] | No | `null` | +| component | Field component | Any[] | No | `null` | +| feedbacks | Field feedback information | [IFieldFeedback](#ifieldfeedback)[] | No | `[]` | +| parent | Parent field | [GeneralField](#generalfield) | yes | `null` | +| errors | Field all error message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | +| warnings | Field all warning message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | +| successes | Field all success message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | +| valid | Is the all field valid(include children) | Boolean | Yes | `true` | +| invalid | Is the all field illegal(include children) | Boolean | Yes | `false` | +| value | Field value | Any | No | | +| initialValue | Field default value | Any | No | | +| display | Field display status | [FieldDisplayTypes](#fielddisplaytypes) | No | `"visible"` | +| pattern | Field interaction mode | [FieldPatternTypes](#fieldpatterntypes) | No | `"editable"` | +| required | Is the field required | Boolean | No | `false` | +| hidden | Whether the field is hidden | Boolean | No | `false` | +| visible | Whether the field is displayed | Boolean | No | `true` | +| disabled | Whether the field is disabled | Boolean | No | `false` | +| readOnly | Is the field read-only | Boolean | No | `false` | +| readPretty | Whether the field is in the reading state | Boolean | No | `false` | +| editable | Field is editable | Boolean | No | `true` | +| validateStatus | Field validation status | [FieldValidateStatus](#fieldvalidatestatus) | yes | `null` | +| content | Field content, usually as a child node | any | No | `null` | +| data | Field extends properties | Object | No | `null` | +| selfErrors | Field own error message | [FieldMessage](#fieldmessage)[] | No | `[]` | +| selfWarnings | Field own warning message | [FieldMessage](#fieldmessage)[] | No | `[]` | +| selfSuccesses | Success message of the field itself | [FieldMessage](#fieldmessage)[] | No | `[]` | +| selfValid | Is the field valid | Boolean | Yes | `true` | +| selfInvalid | Is the field itself illegal | Boolean | Yes | `false` | +| indexes | collection of field numeric indexes | Number | yes | `-` | +| index | field numeric index, take the last index of indexes | Number | Yes | `-` | #### explain in detail diff --git a/packages/core/docs/api/models/Field.zh-CN.md b/packages/core/docs/api/models/Field.zh-CN.md index 87645701182..51ea922589a 100644 --- a/packages/core/docs/api/models/Field.zh-CN.md +++ b/packages/core/docs/api/models/Field.zh-CN.md @@ -10,53 +10,55 @@ order: 1 ## 属性 -| 属性 | 描述 | 类型 | 是否只读 | 默认值 | -| -------------- | ---------------------------- | -------------------------------------------------- | -------- | ------------ | -| initialized | 字段是否已被初始化 | Boolean | 否 | `false` | -| mounted | 字段是否已挂载 | Boolean | 否 | `false` | -| unmounted | 字段是否已卸载 | Boolean | 否 | `false` | -| address | 字段节点路径 | [FormPath](/api/entry/form-path) | 是 | | -| path | 字段数据路径 | [FormPath](/api/entry/form-path) | 是 | | -| title | 字段标题 | [FieldMessage](#fieldmessage) | 否 | `""` | -| description | 字段描述 | [FieldMessage](#fieldmessage) | 否 | `""` | -| loading | 字段加载状态 | Boolean | 否 | `false` | -| validating | 字段是否正在校验 | Boolean | 否 | `false` | -| modified | 字段子树是否被手动修改过 | Boolean | 否 | `false` | -| selfModified | 字段自身是否被手动修改过 | Boolean | 否 | `false` | -| active | 字段是否处于激活态 | Boolean | 否 | `false` | -| visited | 字段是否被浏览过 | Boolean | 否 | `false` | -| inputValue | 字段输入值 | Any | 否 | `null` | -| inputValues | 字段输入值集合 | Array | 否 | `[]` | -| dataSource | 字段数据源 | Array | 否 | `[]` | -| validator | 字段校验器 | [FieldValidator](#fieldvalidator) | 否 | `null` | -| decorator | 字段装饰器 | Any[] | 否 | `null` | -| component | 字段组件 | Any[] | 否 | `null` | -| feedbacks | 字段反馈信息 | [IFieldFeedback](#ifieldfeedback)[] | 否 | `[]` | -| parent | 父级字段 | [GeneralField](#generalfield) | 是 | `null` | -| errors | 字段汇总(包含子节点)错误消息 | [IFormFeedback](/api/models/form/#iformfeedback)[] | 是 | `[]` | -| warnings | 字段汇总(包含子节点)警告消息 | [IFormFeedback](/api/models/form/#iformfeedback)[] | 是 | `[]` | -| successes | 字段汇总(包含子节点)成功消息 | [IFormFeedback](/api/models/form/#iformfeedback)[] | 是 | `[]` | -| valid | 字段是否合法(包含子节点) | Boolean | 否 | `true` | -| invalid | 字段是否非法(包含子节点) | Boolean | 否 | `false` | -| value | 字段值 | Any | 否 | | -| initialValue | 字段默认值 | Any | 否 | | -| display | 字段展示状态 | [FieldDisplayTypes](#fielddisplaytypes) | 否 | `"visible"` | -| pattern | 字段交互模式 | [FieldPatternTypes](#fieldpatterntypes) | 否 | `"editable"` | -| required | 字段是否必填 | Boolean | 否 | `false` | -| hidden | 字段是否隐藏 | Boolean | 否 | `false` | -| visible | 字段是否显示 | Boolean | 否 | `true` | -| disabled | 字段是否禁用 | Boolean | 否 | `false` | -| readOnly | 字段是否只读 | Boolean | 否 | `false` | -| readPretty | 字段是否为阅读态 | Boolean | 否 | `false` | -| editable | 字段是可编辑 | Boolean | 否 | `true` | -| validateStatus | 字段校验状态 | [FieldValidateStatus](#fieldvalidatestatus) | 是 | `null` | -| content | 字段内容,一般作为子节点 | any | 否 | `null` | -| data | 字段扩展属性 | Object | 否 | `null` | -| selfErrors | 字段自身错误消息 | [FieldMessage](#fieldmessage)[] | 否 | `[]` | -| selfWarnings | 字段自身警告消息 | [FieldMessage](#fieldmessage)[] | 否 | `[]` | -| selfSuccesses | 字段自身成功消息 | [FieldMessage](#fieldmessage)[] | 否 | `[]` | -| selfValid | 字段自身是否合法 | Boolean | 否 | `true` | -| selfInvalid | 字段自身是否非法 | Boolean | 否 | `false` | +| 属性 | 描述 | 类型 | 是否只读 | 默认值 | +| -------------- | --------------------------------- | -------------------------------------------------- | -------- | ------------ | +| initialized | 字段是否已被初始化 | Boolean | 否 | `false` | +| mounted | 字段是否已挂载 | Boolean | 否 | `false` | +| unmounted | 字段是否已卸载 | Boolean | 否 | `false` | +| address | 字段节点路径 | [FormPath](/api/entry/form-path) | 是 | | +| path | 字段数据路径 | [FormPath](/api/entry/form-path) | 是 | | +| title | 字段标题 | [FieldMessage](#fieldmessage) | 否 | `""` | +| description | 字段描述 | [FieldMessage](#fieldmessage) | 否 | `""` | +| loading | 字段加载状态 | Boolean | 否 | `false` | +| validating | 字段是否正在校验 | Boolean | 否 | `false` | +| modified | 字段子树是否被手动修改过 | Boolean | 否 | `false` | +| selfModified | 字段自身是否被手动修改过 | Boolean | 否 | `false` | +| active | 字段是否处于激活态 | Boolean | 否 | `false` | +| visited | 字段是否被浏览过 | Boolean | 否 | `false` | +| inputValue | 字段输入值 | Any | 否 | `null` | +| inputValues | 字段输入值集合 | Array | 否 | `[]` | +| dataSource | 字段数据源 | Array | 否 | `[]` | +| validator | 字段校验器 | [FieldValidator](#fieldvalidator) | 否 | `null` | +| decorator | 字段装饰器 | Any[] | 否 | `null` | +| component | 字段组件 | Any[] | 否 | `null` | +| feedbacks | 字段反馈信息 | [IFieldFeedback](#ifieldfeedback)[] | 否 | `[]` | +| parent | 父级字段 | [GeneralField](#generalfield) | 是 | `null` | +| errors | 字段汇总(包含子节点)错误消息 | [IFormFeedback](/api/models/form/#iformfeedback)[] | 是 | `[]` | +| warnings | 字段汇总(包含子节点)警告消息 | [IFormFeedback](/api/models/form/#iformfeedback)[] | 是 | `[]` | +| successes | 字段汇总(包含子节点)成功消息 | [IFormFeedback](/api/models/form/#iformfeedback)[] | 是 | `[]` | +| valid | 字段是否合法(包含子节点) | Boolean | 否 | `true` | +| invalid | 字段是否非法(包含子节点) | Boolean | 否 | `false` | +| value | 字段值 | Any | 否 | | +| initialValue | 字段默认值 | Any | 否 | | +| display | 字段展示状态 | [FieldDisplayTypes](#fielddisplaytypes) | 否 | `"visible"` | +| pattern | 字段交互模式 | [FieldPatternTypes](#fieldpatterntypes) | 否 | `"editable"` | +| required | 字段是否必填 | Boolean | 否 | `false` | +| hidden | 字段是否隐藏 | Boolean | 否 | `false` | +| visible | 字段是否显示 | Boolean | 否 | `true` | +| disabled | 字段是否禁用 | Boolean | 否 | `false` | +| readOnly | 字段是否只读 | Boolean | 否 | `false` | +| readPretty | 字段是否为阅读态 | Boolean | 否 | `false` | +| editable | 字段是可编辑 | Boolean | 否 | `true` | +| validateStatus | 字段校验状态 | [FieldValidateStatus](#fieldvalidatestatus) | 是 | `null` | +| content | 字段内容,一般作为子节点 | any | 否 | `null` | +| data | 字段扩展属性 | Object | 否 | `null` | +| selfErrors | 字段自身错误消息 | [FieldMessage](#fieldmessage)[] | 否 | `[]` | +| selfWarnings | 字段自身警告消息 | [FieldMessage](#fieldmessage)[] | 否 | `[]` | +| selfSuccesses | 字段自身成功消息 | [FieldMessage](#fieldmessage)[] | 否 | `[]` | +| selfValid | 字段自身是否合法 | Boolean | 否 | `true` | +| selfInvalid | 字段自身是否非法 | Boolean | 否 | `false` | +| indexes | 字段数字索引集合 | Number | 是 | `-` | +| index | 字段数字索引,取 indexes 最后一个 | Number | 是 | `-` | #### 详细解释 diff --git a/packages/core/src/__tests__/array.spec.ts b/packages/core/src/__tests__/array.spec.ts index 82a31113825..1b85ba4fee7 100644 --- a/packages/core/src/__tests__/array.spec.ts +++ b/packages/core/src/__tests__/array.spec.ts @@ -488,14 +488,14 @@ test('nest array remove', async () => { }) ) - attach( + const obj00 = attach( form.createObjectField({ name: '0', basePath: 'metrics.0.content', }) ) - attach( + const obj10 = attach( form.createObjectField({ name: '0', basePath: 'metrics.1.content', @@ -517,7 +517,10 @@ test('nest array remove', async () => { initialValue: '123', }) ) - + expect(obj00.indexes[0]).toBe(0) + expect(obj00.index).toBe(0) + expect(obj10.index).toBe(0) + expect(obj10.indexes[0]).toBe(1) await (form.query('metrics.1.content').take() as any).remove(0) expect(form.fields['metrics.0.content.0.attr']).not.toBeUndefined() await metrics.remove(1) diff --git a/packages/core/src/__tests__/field.spec.ts b/packages/core/src/__tests__/field.spec.ts index 7626ee4f57b..bdb4a7c0f0d 100644 --- a/packages/core/src/__tests__/field.spec.ts +++ b/packages/core/src/__tests__/field.spec.ts @@ -1849,7 +1849,7 @@ test('path change will update computed value', () => { value(input.value) }) batch(() => { - input.makeIndexes('select') + input.locate('select') input.value = '123' }) expect(value).nthCalledWith(2, '123') diff --git a/packages/core/src/models/BaseField.ts b/packages/core/src/models/BaseField.ts index 9c281dfff0c..6be380bbbbd 100644 --- a/packages/core/src/models/BaseField.ts +++ b/packages/core/src/models/BaseField.ts @@ -7,7 +7,7 @@ import { FieldDecorator, FieldComponent, } from '../types' -import { buildNodeIndexes, destroy, initFieldUpdate } from '../shared/internals' +import { locateNode, destroy, initFieldUpdate } from '../shared/internals' import { Form } from './Form' import { Query } from './Query' @@ -37,9 +37,19 @@ export class BaseField { disposers: (() => void)[] = [] - makeIndexes(address: FormPathPattern) { + locate(address: FormPathPattern) { this.form.fields[address.toString()] = this as any - buildNodeIndexes(this as any, address) + locateNode(this as any, address) + } + + get indexes() { + return this.path.transform(/\d/, (...args) => + args.map((index) => Number(index)) + ) + } + + get index() { + return this.indexes[this.indexes.length - 1] } get component() { diff --git a/packages/core/src/models/Field.ts b/packages/core/src/models/Field.ts index 8a8c0b6240a..63db0a6c44c 100644 --- a/packages/core/src/models/Field.ts +++ b/packages/core/src/models/Field.ts @@ -89,7 +89,7 @@ export class Field< this.props = props this.designable = designable initializeStart() - this.makeIndexes(address) + this.locate(address) this.initialize() this.makeObservable() this.makeReactive() @@ -187,6 +187,7 @@ export class Field< readOnly: observable.computed, readPretty: observable.computed, editable: observable.computed, + indexes: observable.computed, setDisplay: action, setTitle: action, setDescription: action, diff --git a/packages/core/src/models/VoidField.ts b/packages/core/src/models/VoidField.ts index 0345d9e0e73..5bc7dc5f83e 100644 --- a/packages/core/src/models/VoidField.ts +++ b/packages/core/src/models/VoidField.ts @@ -35,7 +35,7 @@ export class VoidField< this.props = props this.designable = designable initializeStart() - this.makeIndexes(address) + this.locate(address) this.initialize() this.makeObservable() this.makeReactive() @@ -66,6 +66,7 @@ export class VoidField< protected makeObservable() { if (this.designable) return define(this, { + path: observable.ref, title: observable.ref, description: observable.ref, selfDisplay: observable.ref, @@ -89,6 +90,7 @@ export class VoidField< editable: observable.computed, component: observable.computed, decorator: observable.computed, + indexes: observable.computed, setTitle: action, setDescription: action, setDisplay: action, diff --git a/packages/core/src/shared/internals.ts b/packages/core/src/shared/internals.ts index 411f0608d9b..fb9974073f6 100644 --- a/packages/core/src/shared/internals.ts +++ b/packages/core/src/shared/internals.ts @@ -141,10 +141,7 @@ export const buildFieldPath = (field: GeneralField) => { return new FormPath(path) } -export const buildNodeIndexes = ( - field: GeneralField, - address: FormPathPattern -) => { +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() @@ -164,7 +161,7 @@ export const patchFieldStates = ( if (target[oldAddress] === payload) delete target[oldAddress] } if (address && payload) { - buildNodeIndexes(payload, address) + locateNode(payload, address) } } })