@nocobase/client-v2 2.1.39 → 2.1.40
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/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
- package/es/index.mjs +58 -58
- package/lib/index.js +58 -58
- package/package.json +7 -7
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
- package/src/flow/actions/linkageRules.tsx +8 -3
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +129 -14
- package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +359 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
- package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -0
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +16 -0
package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { define, observable } from '@formily/reactive';
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { FilterFormSubmitActionModel } from '../FilterFormSubmitActionModel';
|
|
13
|
+
|
|
14
|
+
describe('FilterFormSubmitActionModel lifecycle', () => {
|
|
15
|
+
const createAction = (blockModel: { autoTriggerFilter: boolean }, hidden = false, lifecycleAction?: object) => {
|
|
16
|
+
const action = Object.create(FilterFormSubmitActionModel.prototype) as FilterFormSubmitActionModel;
|
|
17
|
+
|
|
18
|
+
Object.defineProperty(action, 'props', {
|
|
19
|
+
value: {},
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(action, 'context', {
|
|
22
|
+
value: { blockModel, model: lifecycleAction || action, defineProperty: () => undefined },
|
|
23
|
+
});
|
|
24
|
+
action.hidden = hidden;
|
|
25
|
+
define(action, {
|
|
26
|
+
hidden: observable,
|
|
27
|
+
});
|
|
28
|
+
action.onInit({});
|
|
29
|
+
|
|
30
|
+
return action;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
it('keeps automatic filtering disabled when the submit action remounts', () => {
|
|
34
|
+
const blockModel = { autoTriggerFilter: true };
|
|
35
|
+
const action = createAction(blockModel);
|
|
36
|
+
|
|
37
|
+
action.onMount();
|
|
38
|
+
expect(blockModel.autoTriggerFilter).toBe(false);
|
|
39
|
+
|
|
40
|
+
action.onUnmount();
|
|
41
|
+
expect(blockModel.autoTriggerFilter).toBe(true);
|
|
42
|
+
|
|
43
|
+
action.onMount();
|
|
44
|
+
expect(blockModel.autoTriggerFilter).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('keeps automatic filtering disabled until every mounted submit action unmounts', () => {
|
|
48
|
+
const blockModel = { autoTriggerFilter: true };
|
|
49
|
+
const firstAction = createAction(blockModel);
|
|
50
|
+
const secondAction = createAction(blockModel);
|
|
51
|
+
|
|
52
|
+
firstAction.onMount();
|
|
53
|
+
secondAction.onMount();
|
|
54
|
+
firstAction.onUnmount();
|
|
55
|
+
expect(blockModel.autoTriggerFilter).toBe(false);
|
|
56
|
+
|
|
57
|
+
secondAction.onUnmount();
|
|
58
|
+
expect(blockModel.autoTriggerFilter).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('only disables automatic filtering while a visible submit action is mounted', () => {
|
|
62
|
+
const blockModel = { autoTriggerFilter: true };
|
|
63
|
+
const action = createAction(blockModel, true);
|
|
64
|
+
|
|
65
|
+
action.onMount();
|
|
66
|
+
expect(blockModel.autoTriggerFilter).toBe(true);
|
|
67
|
+
|
|
68
|
+
action.hidden = false;
|
|
69
|
+
expect(blockModel.autoTriggerFilter).toBe(false);
|
|
70
|
+
|
|
71
|
+
action.hidden = true;
|
|
72
|
+
expect(blockModel.autoTriggerFilter).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('tracks fork lifecycles independently when submit actions share the same uid', () => {
|
|
76
|
+
const blockModel = { autoTriggerFilter: true };
|
|
77
|
+
const firstFork = {};
|
|
78
|
+
const secondFork = {};
|
|
79
|
+
const firstAction = createAction(blockModel, false, firstFork);
|
|
80
|
+
const secondAction = createAction(blockModel, false, secondFork);
|
|
81
|
+
|
|
82
|
+
firstAction.onMount();
|
|
83
|
+
secondAction.onMount();
|
|
84
|
+
firstAction.onUnmount();
|
|
85
|
+
expect(blockModel.autoTriggerFilter).toBe(false);
|
|
86
|
+
|
|
87
|
+
secondAction.onUnmount();
|
|
88
|
+
expect(blockModel.autoTriggerFilter).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Cascader, Space, Button } from 'antd';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
CollectionField,
|
|
13
|
+
EditableItemModel,
|
|
14
|
+
FilterableItemModel,
|
|
15
|
+
MultiRecordResource,
|
|
16
|
+
tExpr,
|
|
17
|
+
} from '@nocobase/flow-engine';
|
|
12
18
|
import { DeleteOutlined } from '@ant-design/icons';
|
|
13
19
|
import { css, cx } from '@emotion/css';
|
|
14
20
|
import { debounce, castArray, omit, last, isEqual } from 'lodash';
|
|
@@ -31,6 +37,23 @@ function normalizeLabelKeys(labelKeyOrKeys?: string | string[]): string[] {
|
|
|
31
37
|
return Array.from(new Set(list.filter((item): item is string => !!item && typeof item === 'string')));
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
function normalizeCascadeFieldKey(key: unknown): string | undefined {
|
|
41
|
+
if (typeof key === 'string' && key) {
|
|
42
|
+
return key;
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(key) && key.length === 1 && typeof key[0] === 'string') {
|
|
45
|
+
return key[0];
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDefaultCascadeFieldNames(field: CollectionField) {
|
|
51
|
+
const targetCollection = field?.targetCollection;
|
|
52
|
+
const value = normalizeCascadeFieldKey(targetCollection?.filterTargetKey) || 'id';
|
|
53
|
+
const label = targetCollection?.titleCollectionField?.name || value;
|
|
54
|
+
return { label, value };
|
|
55
|
+
}
|
|
56
|
+
|
|
34
57
|
function getRecordDisplayLabel(item: any, labelKeyOrKeys?: string | string[]): string | null {
|
|
35
58
|
if (!isPlainRecord(item)) return null;
|
|
36
59
|
const labelKeys = normalizeLabelKeys(labelKeyOrKeys);
|
|
@@ -815,6 +838,15 @@ EditableItemModel.bindModelToInterface('CascadeSelectFieldModel', ['m2o', 'o2o',
|
|
|
815
838
|
order: 60,
|
|
816
839
|
});
|
|
817
840
|
|
|
841
|
+
FilterableItemModel.bindModelToInterface('CascadeSelectFieldModel', ['m2o', 'o2o', 'oho', 'obo'], {
|
|
842
|
+
when: (ctx, field) => field.targetCollection?.template === 'tree',
|
|
843
|
+
isDefault: true,
|
|
844
|
+
order: 60,
|
|
845
|
+
defaultProps: (ctx, field) => ({
|
|
846
|
+
fieldNames: getDefaultCascadeFieldNames(field),
|
|
847
|
+
}),
|
|
848
|
+
});
|
|
849
|
+
|
|
818
850
|
EditableItemModel.bindModelToInterface('CascadeSelectListFieldModel', ['m2m', 'o2m', 'mbm'], {
|
|
819
851
|
when: (ctx, field) => field.targetCollection?.template === 'tree',
|
|
820
852
|
isDefault: true,
|
|
@@ -206,6 +206,11 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
|
|
|
206
206
|
return;
|
|
207
207
|
}
|
|
208
208
|
const selectedId = vals[0];
|
|
209
|
+
if (selectedId === undefined) {
|
|
210
|
+
onChange(undefined as unknown as AssociationOption);
|
|
211
|
+
handleClose();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
209
214
|
const record = optionMap.get(selectedId);
|
|
210
215
|
if (record) {
|
|
211
216
|
if (valueMode === 'value') {
|
|
@@ -307,6 +307,22 @@ describe('MobileLazySelect', () => {
|
|
|
307
307
|
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
+
it('clears the selected relation record when it is tapped again in single mode', () => {
|
|
311
|
+
const { onChange } = renderMobileLazySelect({
|
|
312
|
+
value: RELATION_OPTIONS[0],
|
|
313
|
+
multiple: false,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
openLazyPopup();
|
|
317
|
+
expect(mockState.checklistProps?.value).toEqual([RELATION_OPTIONS[0].uuid]);
|
|
318
|
+
|
|
319
|
+
selectValues([]);
|
|
320
|
+
|
|
321
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
322
|
+
expect(onChange).toHaveBeenCalledWith(undefined);
|
|
323
|
+
expect(screen.queryByTestId('popup')).not.toBeInTheDocument();
|
|
324
|
+
});
|
|
325
|
+
|
|
310
326
|
it('keeps pending relation records selected until confirm', () => {
|
|
311
327
|
const { onChange, rerender } = renderMobileLazySelect();
|
|
312
328
|
|