@nocobase/client-v2 2.1.21 → 2.1.22
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/form/QuickEditFormModel.d.ts +17 -1
- package/es/index.mjs +61 -61
- package/lib/index.js +61 -61
- package/package.json +7 -7
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +177 -19
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +320 -1
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.22",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@formily/antd-v5": "1.2.3",
|
|
28
28
|
"@formily/react": "^2.2.27",
|
|
29
29
|
"@formily/shared": "^2.2.27",
|
|
30
|
-
"@nocobase/evaluators": "2.1.
|
|
31
|
-
"@nocobase/flow-engine": "2.1.
|
|
32
|
-
"@nocobase/sdk": "2.1.
|
|
33
|
-
"@nocobase/shared": "2.1.
|
|
34
|
-
"@nocobase/utils": "2.1.
|
|
30
|
+
"@nocobase/evaluators": "2.1.22",
|
|
31
|
+
"@nocobase/flow-engine": "2.1.22",
|
|
32
|
+
"@nocobase/sdk": "2.1.22",
|
|
33
|
+
"@nocobase/shared": "2.1.22",
|
|
34
|
+
"@nocobase/utils": "2.1.22",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"react-i18next": "^11.15.1",
|
|
47
47
|
"react-router-dom": "^6.30.1"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "811fc3d272389739babdfa4338faf843d5bf04ff"
|
|
50
50
|
}
|
|
@@ -62,9 +62,25 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
62
62
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
interface CollectionFieldWithAssociationMarker {
|
|
66
|
+
isAssociationField?: () => boolean;
|
|
67
|
+
target?: unknown;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hasAssociationMarker(value: unknown): value is CollectionFieldWithAssociationMarker {
|
|
71
|
+
return isRecord(value) && (typeof value.isAssociationField === 'function' || 'target' in value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isAssociationCollectionField = (collectionField: unknown) => {
|
|
75
|
+
if (!hasAssociationMarker(collectionField)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return collectionField.isAssociationField ? collectionField.isAssociationField() : Boolean(collectionField.target);
|
|
79
|
+
};
|
|
80
|
+
|
|
65
81
|
const normalizeAssociationDefaultFilterValue = (value: any, fieldModel: any) => {
|
|
66
82
|
const collectionField = fieldModel?.context?.collectionField;
|
|
67
|
-
if (!collectionField
|
|
83
|
+
if (!isAssociationCollectionField(collectionField)) {
|
|
68
84
|
return value;
|
|
69
85
|
}
|
|
70
86
|
|
|
@@ -171,8 +187,7 @@ const buildFilterFormFieldItem = ({
|
|
|
171
187
|
if (!binding) {
|
|
172
188
|
return;
|
|
173
189
|
}
|
|
174
|
-
const isAssociation =
|
|
175
|
-
typeof field?.isAssociationField === 'function' ? field.isAssociationField() : Boolean(field?.target);
|
|
190
|
+
const isAssociation = isAssociationCollectionField(field);
|
|
176
191
|
const fieldModel =
|
|
177
192
|
isAssociation && ctxWithFlags.engine?.getModelClass?.('FilterFormRecordSelectFieldModel')
|
|
178
193
|
? 'FilterFormRecordSelectFieldModel'
|
|
@@ -507,10 +522,7 @@ export class FilterFormItemModel extends FilterableItemModel<{
|
|
|
507
522
|
const formValue = this.context.form?.getFieldValue(this.props.name);
|
|
508
523
|
const modelValue = fieldModel.getFilterValue ? fieldModel.getFilterValue() : formValue;
|
|
509
524
|
const collectionField = (fieldModel as any)?.context?.collectionField;
|
|
510
|
-
const isAssociationField =
|
|
511
|
-
typeof collectionField?.isAssociationField === 'function'
|
|
512
|
-
? collectionField.isAssociationField()
|
|
513
|
-
: !!collectionField?.target;
|
|
525
|
+
const isAssociationField = isAssociationCollectionField(collectionField);
|
|
514
526
|
const shouldUseFormValue =
|
|
515
527
|
isAssociationField &&
|
|
516
528
|
!this.mounted &&
|
|
@@ -547,11 +559,7 @@ export class FilterFormItemModel extends FilterableItemModel<{
|
|
|
547
559
|
return value;
|
|
548
560
|
}
|
|
549
561
|
const collectionField = (fieldModel as any)?.context?.collectionField;
|
|
550
|
-
|
|
551
|
-
typeof collectionField?.isAssociationField === 'function'
|
|
552
|
-
? collectionField.isAssociationField()
|
|
553
|
-
: !!collectionField?.target;
|
|
554
|
-
if (!isAssociation) {
|
|
562
|
+
if (!isAssociationCollectionField(collectionField)) {
|
|
555
563
|
return value;
|
|
556
564
|
}
|
|
557
565
|
const normalizedFieldNames = normalizeAssociationFieldNames(
|
|
@@ -798,6 +806,13 @@ FilterFormItemModel.registerFlow({
|
|
|
798
806
|
},
|
|
799
807
|
defaultOperator: {
|
|
800
808
|
use: 'defaultOperator',
|
|
809
|
+
hideInSettings(ctx) {
|
|
810
|
+
const collectionField =
|
|
811
|
+
ctx.collectionField ||
|
|
812
|
+
ctx.model?.context?.collectionField ||
|
|
813
|
+
ctx.model?.subModels?.field?.context?.collectionField;
|
|
814
|
+
return isAssociationCollectionField(collectionField);
|
|
815
|
+
},
|
|
801
816
|
},
|
|
802
817
|
operatorComponentProps: {
|
|
803
818
|
use: 'operatorComponentProps',
|
package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts
CHANGED
|
@@ -33,6 +33,42 @@ class DummyCollectionBlockModel extends CollectionBlockModel {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
describe('FilterFormItemModel defineChildren association fields', () => {
|
|
36
|
+
it('hides default operator setting for association filter fields', () => {
|
|
37
|
+
const engine = new FlowEngine();
|
|
38
|
+
engine.registerModels({
|
|
39
|
+
FilterFormItemModel,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const filterItem = engine.createModel<FilterFormItemModel>({
|
|
43
|
+
uid: 'association-filter-item-settings',
|
|
44
|
+
use: 'FilterFormItemModel',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const defaultOperatorStep = filterItem.getFlow('filterFormItemSettings')?.steps?.defaultOperator as {
|
|
48
|
+
hideInSettings?: (ctx: {
|
|
49
|
+
collectionField?: unknown;
|
|
50
|
+
model?: {
|
|
51
|
+
subModels?: {
|
|
52
|
+
field?: {
|
|
53
|
+
context?: {
|
|
54
|
+
collectionField?: unknown;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}) => boolean;
|
|
60
|
+
};
|
|
61
|
+
expect(defaultOperatorStep?.hideInSettings?.({ collectionField: { isAssociationField: () => true } })).toBe(true);
|
|
62
|
+
expect(
|
|
63
|
+
defaultOperatorStep?.hideInSettings?.({
|
|
64
|
+
model: { subModels: { field: { context: { collectionField: { target: 'departments' } } } } },
|
|
65
|
+
}),
|
|
66
|
+
).toBe(true);
|
|
67
|
+
expect(defaultOperatorStep?.hideInSettings?.({ collectionField: { interface: 'input', type: 'string' } })).toBe(
|
|
68
|
+
false,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
36
72
|
it('groups association target fields and supports recursive paths', async () => {
|
|
37
73
|
const engine = new FlowEngine();
|
|
38
74
|
engine.registerModels({
|
|
@@ -30,6 +30,37 @@ import { FormItemModel } from './FormItemModel';
|
|
|
30
30
|
export const QUICK_EDIT_POPOVER_MAX_HEIGHT = 'calc(100vh - 96px)';
|
|
31
31
|
export const QUICK_EDIT_FORM_MAX_HEIGHT = 'calc(100vh - 160px)';
|
|
32
32
|
export const QUICK_EDIT_MARKDOWN_HEIGHT = 'min(480px, calc(100vh - 320px))';
|
|
33
|
+
const QUICK_EDIT_MOBILE_DRAWER_HEIGHT = '50vh';
|
|
34
|
+
const QUICK_EDIT_MOBILE_FORM_MAX_HEIGHT = 'calc(50vh - var(--nb-mobile-page-header-height, 40px) - 132px)';
|
|
35
|
+
const QUICK_EDIT_MOBILE_CONTENT_PADDING = '8px var(--nb-mobile-page-tabs-content-padding, 12px) 0';
|
|
36
|
+
const QUICK_EDIT_MOBILE_ACTIONS_PADDING =
|
|
37
|
+
'8px var(--nb-mobile-page-tabs-content-padding, 12px) calc(80px + env(safe-area-inset-bottom, 0px))';
|
|
38
|
+
const QUICK_EDIT_MOBILE_MEDIA_QUERY = '(max-width: 768px)';
|
|
39
|
+
|
|
40
|
+
type QuickEditViewBeforeClosePayload = {
|
|
41
|
+
result?: unknown;
|
|
42
|
+
force?: boolean;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type QuickEditViewBeforeCloseHandler = (
|
|
47
|
+
payload: QuickEditViewBeforeClosePayload,
|
|
48
|
+
) => Promise<boolean | void> | boolean | void;
|
|
49
|
+
|
|
50
|
+
type QuickEditViewUpdateConfig = {
|
|
51
|
+
preventClose?: boolean;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type QuickEditViewContainer = {
|
|
56
|
+
close: (result?: unknown, force?: boolean) => Promise<boolean | void> | boolean | void;
|
|
57
|
+
update?: (newConfig: QuickEditViewUpdateConfig) => unknown;
|
|
58
|
+
beforeClose?: QuickEditViewBeforeCloseHandler;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type QuickEditViewContext = {
|
|
62
|
+
defineProperty?: (key: string, options: { value: unknown }) => void;
|
|
63
|
+
};
|
|
33
64
|
|
|
34
65
|
export function getQuickEditFieldProps(collectionField: CollectionField, fieldProps?: Record<string, any>) {
|
|
35
66
|
const nextProps = { ...collectionField.getComponentProps(), ...(fieldProps || {}) };
|
|
@@ -39,13 +70,92 @@ export function getQuickEditFieldProps(collectionField: CollectionField, fieldPr
|
|
|
39
70
|
return nextProps;
|
|
40
71
|
}
|
|
41
72
|
|
|
73
|
+
function getQuickEditMobileLayoutState(flowEngine: FlowEngine, sourceFieldModel?: FlowModel) {
|
|
74
|
+
const sourceMobileLayout = sourceFieldModel?.context?.isMobileLayout;
|
|
75
|
+
if (typeof sourceMobileLayout === 'boolean') {
|
|
76
|
+
return { isMobileLayout: sourceMobileLayout, inheritsMobileContext: sourceMobileLayout };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const engineMobileLayout = flowEngine.context?.isMobileLayout;
|
|
80
|
+
if (typeof engineMobileLayout === 'boolean') {
|
|
81
|
+
return { isMobileLayout: engineMobileLayout, inheritsMobileContext: engineMobileLayout };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
85
|
+
return { isMobileLayout: false, inheritsMobileContext: false };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
isMobileLayout: window.matchMedia(QUICK_EDIT_MOBILE_MEDIA_QUERY).matches,
|
|
90
|
+
inheritsMobileContext: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getQuickEditTitle(
|
|
95
|
+
flowEngine: FlowEngine,
|
|
96
|
+
dataSourceKey: string,
|
|
97
|
+
collectionName: string,
|
|
98
|
+
fieldPath: string,
|
|
99
|
+
sourceFieldModel?: FlowModel,
|
|
100
|
+
fieldProps?: Record<string, unknown>,
|
|
101
|
+
): React.ReactNode {
|
|
102
|
+
const sourceColumnTitle = sourceFieldModel?.parent?.props?.title;
|
|
103
|
+
if (sourceColumnTitle) {
|
|
104
|
+
return sourceColumnTitle;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const fieldPropsTitle = fieldProps?.title;
|
|
108
|
+
if (fieldPropsTitle) {
|
|
109
|
+
return fieldPropsTitle as React.ReactNode;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const collectionField = flowEngine.context.dataSourceManager.getCollectionField(
|
|
113
|
+
`${dataSourceKey}.${collectionName}.${fieldPath}`,
|
|
114
|
+
) as CollectionField | undefined;
|
|
115
|
+
return collectionField?.title || fieldPath;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createQuickEditViewContainer(view: QuickEditViewContainer): QuickEditViewContainer {
|
|
119
|
+
let preventClose = false;
|
|
120
|
+
let nextBeforeClose = view.beforeClose;
|
|
121
|
+
const beforeClose: QuickEditViewBeforeCloseHandler = async (payload) => {
|
|
122
|
+
if (preventClose && !payload?.force) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = await nextBeforeClose?.(payload);
|
|
127
|
+
return result !== false;
|
|
128
|
+
};
|
|
129
|
+
view.beforeClose = beforeClose;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
...view,
|
|
133
|
+
close(result, force) {
|
|
134
|
+
return view.close(result, force);
|
|
135
|
+
},
|
|
136
|
+
update(newConfig) {
|
|
137
|
+
if (Object.prototype.hasOwnProperty.call(newConfig, 'preventClose')) {
|
|
138
|
+
preventClose = !!newConfig.preventClose;
|
|
139
|
+
}
|
|
140
|
+
return view.update?.(newConfig);
|
|
141
|
+
},
|
|
142
|
+
get beforeClose() {
|
|
143
|
+
return view.beforeClose;
|
|
144
|
+
},
|
|
145
|
+
set beforeClose(value) {
|
|
146
|
+
nextBeforeClose = value;
|
|
147
|
+
view.beforeClose = beforeClose;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
42
152
|
export class QuickEditFormModel extends FlowModel {
|
|
43
153
|
fieldPath: string;
|
|
44
154
|
|
|
45
155
|
declare resource: SingleRecordResource;
|
|
46
156
|
declare collection: Collection;
|
|
47
157
|
|
|
48
|
-
viewContainer:
|
|
158
|
+
declare viewContainer: QuickEditViewContainer;
|
|
49
159
|
__onSubmitSuccess;
|
|
50
160
|
_fieldProps: any;
|
|
51
161
|
_onOk: any;
|
|
@@ -103,6 +213,59 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
103
213
|
sourceFieldModel ? { delegate: sourceFieldModel.context } : undefined,
|
|
104
214
|
) as QuickEditFormModel;
|
|
105
215
|
|
|
216
|
+
const bodyStyles = {
|
|
217
|
+
maxHeight: QUICK_EDIT_POPOVER_MAX_HEIGHT,
|
|
218
|
+
overflowY: 'auto',
|
|
219
|
+
overscrollBehavior: 'contain',
|
|
220
|
+
};
|
|
221
|
+
const mobileBodyStyles = {
|
|
222
|
+
...bodyStyles,
|
|
223
|
+
maxHeight: QUICK_EDIT_MOBILE_DRAWER_HEIGHT,
|
|
224
|
+
};
|
|
225
|
+
const content = (view: QuickEditViewContainer, viewContext?: QuickEditViewContext) => {
|
|
226
|
+
if (mobileLayoutState.isMobileLayout) {
|
|
227
|
+
viewContext?.defineProperty?.('isMobileLayout', { value: true });
|
|
228
|
+
}
|
|
229
|
+
model.viewContainer = createQuickEditViewContainer(view);
|
|
230
|
+
model.__onSubmitSuccess = onSuccess;
|
|
231
|
+
model._fieldProps = fieldProps;
|
|
232
|
+
model._onOk = onOk;
|
|
233
|
+
return (
|
|
234
|
+
<FlowModelRenderer
|
|
235
|
+
fallback={<Skeleton.Input size="small" />}
|
|
236
|
+
model={model}
|
|
237
|
+
inputArgs={{ filterByTk, record, sourceFieldModelUid }}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
const mobileLayoutState = getQuickEditMobileLayoutState(flowEngine, sourceFieldModel);
|
|
242
|
+
if (mobileLayoutState.isMobileLayout) {
|
|
243
|
+
model.context.defineProperty('isMobileLayout', { value: true });
|
|
244
|
+
const viewer = sourceFieldModel?.context?.viewer || flowEngine.context.viewer;
|
|
245
|
+
const title = getQuickEditTitle(
|
|
246
|
+
flowEngine,
|
|
247
|
+
dataSourceKey,
|
|
248
|
+
collectionName,
|
|
249
|
+
fieldPath,
|
|
250
|
+
sourceFieldModel,
|
|
251
|
+
fieldProps,
|
|
252
|
+
);
|
|
253
|
+
await viewer.open({
|
|
254
|
+
type: 'drawer',
|
|
255
|
+
title,
|
|
256
|
+
closable: true,
|
|
257
|
+
placement: 'bottom',
|
|
258
|
+
inputArgs: {
|
|
259
|
+
isMobileLayout: true,
|
|
260
|
+
},
|
|
261
|
+
styles: {
|
|
262
|
+
body: mobileBodyStyles,
|
|
263
|
+
},
|
|
264
|
+
content,
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
106
269
|
await flowEngine.context.viewer.open({
|
|
107
270
|
type: 'popover',
|
|
108
271
|
target,
|
|
@@ -110,24 +273,10 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
110
273
|
styles: {
|
|
111
274
|
body: {
|
|
112
275
|
width: 420,
|
|
113
|
-
|
|
114
|
-
overflowY: 'auto',
|
|
115
|
-
overscrollBehavior: 'contain',
|
|
276
|
+
...bodyStyles,
|
|
116
277
|
},
|
|
117
278
|
},
|
|
118
|
-
content
|
|
119
|
-
model.viewContainer = popover;
|
|
120
|
-
model.__onSubmitSuccess = onSuccess;
|
|
121
|
-
model._fieldProps = fieldProps;
|
|
122
|
-
model._onOk = onOk;
|
|
123
|
-
return (
|
|
124
|
-
<FlowModelRenderer
|
|
125
|
-
fallback={<Skeleton.Input size="small" />}
|
|
126
|
-
model={model}
|
|
127
|
-
inputArgs={{ filterByTk, record, sourceFieldModelUid }}
|
|
128
|
-
/>
|
|
129
|
-
);
|
|
130
|
-
},
|
|
279
|
+
content,
|
|
131
280
|
});
|
|
132
281
|
}
|
|
133
282
|
|
|
@@ -168,13 +317,15 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
168
317
|
}
|
|
169
318
|
|
|
170
319
|
render() {
|
|
320
|
+
const isMobileLayout = this.context.isMobileLayout;
|
|
171
321
|
return (
|
|
172
322
|
<FormComponent model={this}>
|
|
173
323
|
<div
|
|
174
324
|
style={{
|
|
175
325
|
minHeight: 0,
|
|
176
326
|
overflowY: 'auto',
|
|
177
|
-
maxHeight: QUICK_EDIT_FORM_MAX_HEIGHT,
|
|
327
|
+
maxHeight: isMobileLayout ? QUICK_EDIT_MOBILE_FORM_MAX_HEIGHT : QUICK_EDIT_FORM_MAX_HEIGHT,
|
|
328
|
+
padding: isMobileLayout ? QUICK_EDIT_MOBILE_CONTENT_PADDING : undefined,
|
|
178
329
|
}}
|
|
179
330
|
>
|
|
180
331
|
{this.mapSubModels('fields', (field) => {
|
|
@@ -191,7 +342,14 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
191
342
|
);
|
|
192
343
|
})}
|
|
193
344
|
</div>
|
|
194
|
-
<Space
|
|
345
|
+
<Space
|
|
346
|
+
style={{
|
|
347
|
+
display: 'flex',
|
|
348
|
+
justifyContent: 'flex-end',
|
|
349
|
+
flexShrink: 0,
|
|
350
|
+
padding: isMobileLayout ? QUICK_EDIT_MOBILE_ACTIONS_PADDING : undefined,
|
|
351
|
+
}}
|
|
352
|
+
>
|
|
195
353
|
<Button
|
|
196
354
|
onClick={() => {
|
|
197
355
|
this.viewContainer.close();
|