@nocobase/client-v2 2.2.0-beta.1 → 2.2.0-beta.11
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/BaseApplication.d.ts +3 -0
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +29 -6
- package/es/RouterManager.d.ts +15 -0
- package/es/authRedirect.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +1 -0
- package/es/entry-actions/EntryActionManager.d.ts +24 -0
- package/es/entry-actions/index.d.ts +9 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +4 -1
- package/es/index.mjs +315 -167
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +315 -167
- package/package.json +8 -7
- package/src/BaseApplication.tsx +12 -5
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +151 -24
- package/src/RouterManager.tsx +142 -1
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +239 -0
- package/src/__tests__/RouterManager.test.ts +125 -0
- package/src/__tests__/app.test.tsx +55 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/authRedirect.ts +38 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +145 -2
- package/src/components/form/ScanInput/useCodeScanner.ts +154 -2
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +1 -0
- package/src/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/FlowPage.tsx +38 -3
- package/src/flow/__tests__/FlowPage.test.tsx +201 -27
- package/src/flow/__tests__/FlowRoute.test.tsx +1088 -105
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +20 -1
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/linkageRules.tsx +123 -35
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/actions/openView.tsx +59 -5
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -13
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
- package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +15 -11
- package/src/flow/components/FlowRoute.tsx +186 -25
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +1 -1
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +21 -8
- package/src/flow/models/base/ActionModelCore.tsx +11 -4
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +32 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +111 -0
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
- package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
- package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +30 -6
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +10 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +51 -15
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
- package/src/utils/markdownSanitize.ts +88 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { FlowEngine } from '@nocobase/flow-engine';
|
|
11
11
|
import React from 'react';
|
|
12
12
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
|
-
import { GridModel } from '../GridModel';
|
|
13
|
+
import { GridModel, transformRowsToSingleColumn } from '../GridModel';
|
|
14
14
|
|
|
15
15
|
const createMockRect = ({ top, left, width, height }: { top: number; left: number; width: number; height: number }) => {
|
|
16
16
|
return {
|
|
@@ -66,6 +66,83 @@ const createGridContainer = () => {
|
|
|
66
66
|
return container;
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
const createGridContainerWithEmptyColumn = () => {
|
|
70
|
+
const container = document.createElement('div');
|
|
71
|
+
container.setAttribute('data-grid-root', '');
|
|
72
|
+
const row = document.createElement('div');
|
|
73
|
+
row.setAttribute('data-grid-row-id', 'row-1');
|
|
74
|
+
container.appendChild(row);
|
|
75
|
+
|
|
76
|
+
const firstColumn = document.createElement('div');
|
|
77
|
+
firstColumn.setAttribute('data-grid-column-row-id', 'row-1');
|
|
78
|
+
firstColumn.setAttribute('data-grid-column-index', '0');
|
|
79
|
+
row.appendChild(firstColumn);
|
|
80
|
+
|
|
81
|
+
const item = document.createElement('div');
|
|
82
|
+
item.setAttribute('data-grid-item-row-id', 'row-1');
|
|
83
|
+
item.setAttribute('data-grid-column-index', '0');
|
|
84
|
+
item.setAttribute('data-grid-item-index', '0');
|
|
85
|
+
item.setAttribute('data-grid-item-uid', 'item-1');
|
|
86
|
+
firstColumn.appendChild(item);
|
|
87
|
+
|
|
88
|
+
const secondColumn = document.createElement('div');
|
|
89
|
+
secondColumn.setAttribute('data-grid-column-row-id', 'row-1');
|
|
90
|
+
secondColumn.setAttribute('data-grid-column-index', '1');
|
|
91
|
+
row.appendChild(secondColumn);
|
|
92
|
+
|
|
93
|
+
mockRect(container, { top: 0, left: 0, width: 480, height: 280 });
|
|
94
|
+
mockRect(row, { top: 20, left: 20, width: 440, height: 120 });
|
|
95
|
+
mockRect(firstColumn, { top: 20, left: 20, width: 220, height: 120 });
|
|
96
|
+
mockRect(item, { top: 30, left: 30, width: 200, height: 100 });
|
|
97
|
+
mockRect(secondColumn, { top: 20, left: 260, width: 200, height: 120 });
|
|
98
|
+
return container;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const createProjectedMobileGridContainer = (rows: Record<string, string[][]>) => {
|
|
102
|
+
const container = document.createElement('div');
|
|
103
|
+
container.setAttribute('data-grid-root', '');
|
|
104
|
+
mockRect(container, { top: 0, left: 0, width: 480, height: 320 });
|
|
105
|
+
|
|
106
|
+
let top = 20;
|
|
107
|
+
Object.entries(transformRowsToSingleColumn(rows)).forEach(([rowId, columns]) => {
|
|
108
|
+
const row = document.createElement('div');
|
|
109
|
+
row.setAttribute('data-grid-row-id', rowId);
|
|
110
|
+
container.appendChild(row);
|
|
111
|
+
|
|
112
|
+
const column = document.createElement('div');
|
|
113
|
+
column.setAttribute('data-grid-column-row-id', rowId);
|
|
114
|
+
column.setAttribute('data-grid-column-index', '0');
|
|
115
|
+
row.appendChild(column);
|
|
116
|
+
|
|
117
|
+
const items = columns[0] || [];
|
|
118
|
+
items.forEach((itemUid, itemIndex) => {
|
|
119
|
+
const item = document.createElement('div');
|
|
120
|
+
item.setAttribute('data-grid-item-row-id', rowId);
|
|
121
|
+
item.setAttribute('data-grid-column-index', '0');
|
|
122
|
+
item.setAttribute('data-grid-item-index', String(itemIndex));
|
|
123
|
+
item.setAttribute('data-grid-item-uid', itemUid);
|
|
124
|
+
column.appendChild(item);
|
|
125
|
+
mockRect(item, { top: top + 10 + itemIndex * 36, left: 30, width: 420, height: 32 });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
mockRect(row, { top, left: 20, width: 440, height: 52 });
|
|
129
|
+
mockRect(column, { top, left: 20, width: 440, height: 52 });
|
|
130
|
+
top += 72;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return container;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const setMobileGridItems = (engine: FlowEngine, model: GridModel, itemUids: string[]) => {
|
|
137
|
+
(model as any).subModels = {
|
|
138
|
+
items: itemUids.map((itemUid) => engine.createModel({ use: 'FlowModel', uid: itemUid })),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const getSavedTopLevelItemOrder = (model: GridModel) => {
|
|
143
|
+
return Object.values(model.props.rows || {}).map((columns: string[][]) => columns[0]?.[0]);
|
|
144
|
+
};
|
|
145
|
+
|
|
69
146
|
describe('GridModel drag snapshot container', () => {
|
|
70
147
|
let engine: FlowEngine;
|
|
71
148
|
|
|
@@ -106,6 +183,167 @@ describe('GridModel drag snapshot container', () => {
|
|
|
106
183
|
model.handleDragCancel({} as any);
|
|
107
184
|
});
|
|
108
185
|
|
|
186
|
+
it('filters multi-column drop slots from mobile drag snapshots', () => {
|
|
187
|
+
const model = engine.createModel<GridModel>({
|
|
188
|
+
use: 'GridModel',
|
|
189
|
+
uid: 'grid-mobile-drag-slots',
|
|
190
|
+
props: {
|
|
191
|
+
rows: {
|
|
192
|
+
'row-1': [['item-1'], []],
|
|
193
|
+
},
|
|
194
|
+
sizes: {
|
|
195
|
+
'row-1': [12, 12],
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
structure: {} as any,
|
|
199
|
+
});
|
|
200
|
+
model.context.defineProperty('isMobileLayout', { value: true });
|
|
201
|
+
|
|
202
|
+
const container = createGridContainerWithEmptyColumn();
|
|
203
|
+
(model.gridContainerRef as any).current = container;
|
|
204
|
+
|
|
205
|
+
model.handleDragStart({
|
|
206
|
+
active: { id: 'item-1' },
|
|
207
|
+
activatorEvent: { clientX: 80, clientY: 80 },
|
|
208
|
+
} as any);
|
|
209
|
+
|
|
210
|
+
const slotTypes = ((model as any).dragState?.slots || []).map((slot) => slot.type);
|
|
211
|
+
expect(slotTypes).toContain('column');
|
|
212
|
+
expect(slotTypes).toContain('row-gap');
|
|
213
|
+
expect(slotTypes).not.toContain('column-edge');
|
|
214
|
+
expect(slotTypes).not.toContain('item-edge');
|
|
215
|
+
expect(slotTypes).not.toContain('empty-column');
|
|
216
|
+
model.handleDragCancel({} as any);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('keeps empty-row slot available for the first mobile grid item', () => {
|
|
220
|
+
const model = engine.createModel<GridModel>({
|
|
221
|
+
use: 'GridModel',
|
|
222
|
+
uid: 'grid-mobile-empty-drag-slots',
|
|
223
|
+
props: {},
|
|
224
|
+
structure: {} as any,
|
|
225
|
+
});
|
|
226
|
+
model.context.defineProperty('isMobileLayout', { value: true });
|
|
227
|
+
|
|
228
|
+
const container = document.createElement('div');
|
|
229
|
+
mockRect(container, { top: 0, left: 0, width: 480, height: 280 });
|
|
230
|
+
(model.gridContainerRef as any).current = container;
|
|
231
|
+
|
|
232
|
+
model.handleDragStart({
|
|
233
|
+
active: { id: 'item-1' },
|
|
234
|
+
activatorEvent: { clientX: 80, clientY: 80 },
|
|
235
|
+
} as any);
|
|
236
|
+
|
|
237
|
+
expect(((model as any).dragState?.slots || []).map((slot) => slot.type)).toEqual(['empty-row']);
|
|
238
|
+
model.handleDragCancel({} as any);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('saves mobile vertical sorting when moving the second item above the first one', () => {
|
|
242
|
+
const rows = {
|
|
243
|
+
'row-a': [['item-a']],
|
|
244
|
+
'row-b': [['item-b']],
|
|
245
|
+
};
|
|
246
|
+
const model = engine.createModel<GridModel>({
|
|
247
|
+
use: 'GridModel',
|
|
248
|
+
uid: 'grid-mobile-sort-second-above-first',
|
|
249
|
+
props: {
|
|
250
|
+
rows,
|
|
251
|
+
sizes: {
|
|
252
|
+
'row-a': [24],
|
|
253
|
+
'row-b': [24],
|
|
254
|
+
},
|
|
255
|
+
rowOrder: ['row-a', 'row-b'],
|
|
256
|
+
},
|
|
257
|
+
structure: {} as any,
|
|
258
|
+
});
|
|
259
|
+
model.context.defineProperty('isMobileLayout', { value: true });
|
|
260
|
+
setMobileGridItems(engine, model, ['item-a', 'item-b']);
|
|
261
|
+
(model as any).saveStepParams = vi.fn();
|
|
262
|
+
|
|
263
|
+
const container = createProjectedMobileGridContainer(rows);
|
|
264
|
+
(model.gridContainerRef as any).current = container;
|
|
265
|
+
|
|
266
|
+
model.handleDragStart({
|
|
267
|
+
active: { id: 'item-b' },
|
|
268
|
+
activatorEvent: { clientX: 240, clientY: 118 },
|
|
269
|
+
} as any);
|
|
270
|
+
model.handleDragEnd({
|
|
271
|
+
delta: { x: 0, y: -112 },
|
|
272
|
+
} as any);
|
|
273
|
+
|
|
274
|
+
expect(getSavedTopLevelItemOrder(model)).toEqual(['item-b', 'item-a']);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('saves mobile vertical sorting when moving the first item below the last one', () => {
|
|
278
|
+
const rows = {
|
|
279
|
+
'row-a': [['item-a']],
|
|
280
|
+
'row-b': [['item-b']],
|
|
281
|
+
'row-c': [['item-c']],
|
|
282
|
+
};
|
|
283
|
+
const model = engine.createModel<GridModel>({
|
|
284
|
+
use: 'GridModel',
|
|
285
|
+
uid: 'grid-mobile-sort-first-below-last',
|
|
286
|
+
props: {
|
|
287
|
+
rows,
|
|
288
|
+
sizes: {
|
|
289
|
+
'row-a': [24],
|
|
290
|
+
'row-b': [24],
|
|
291
|
+
'row-c': [24],
|
|
292
|
+
},
|
|
293
|
+
rowOrder: ['row-a', 'row-b', 'row-c'],
|
|
294
|
+
},
|
|
295
|
+
structure: {} as any,
|
|
296
|
+
});
|
|
297
|
+
model.context.defineProperty('isMobileLayout', { value: true });
|
|
298
|
+
setMobileGridItems(engine, model, ['item-a', 'item-b', 'item-c']);
|
|
299
|
+
(model as any).saveStepParams = vi.fn();
|
|
300
|
+
|
|
301
|
+
const container = createProjectedMobileGridContainer(rows);
|
|
302
|
+
(model.gridContainerRef as any).current = container;
|
|
303
|
+
|
|
304
|
+
model.handleDragStart({
|
|
305
|
+
active: { id: 'item-a' },
|
|
306
|
+
activatorEvent: { clientX: 240, clientY: 46 },
|
|
307
|
+
} as any);
|
|
308
|
+
model.handleDragEnd({
|
|
309
|
+
delta: { x: 0, y: 184 },
|
|
310
|
+
} as any);
|
|
311
|
+
|
|
312
|
+
expect(getSavedTopLevelItemOrder(model)).toEqual(['item-b', 'item-c', 'item-a']);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('does not filter multi-column drop slots from desktop drag snapshots', () => {
|
|
316
|
+
const model = engine.createModel<GridModel>({
|
|
317
|
+
use: 'GridModel',
|
|
318
|
+
uid: 'grid-desktop-drag-slots',
|
|
319
|
+
props: {
|
|
320
|
+
rows: {
|
|
321
|
+
'row-1': [['item-1'], []],
|
|
322
|
+
},
|
|
323
|
+
sizes: {
|
|
324
|
+
'row-1': [12, 12],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
structure: {} as any,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const container = createGridContainerWithEmptyColumn();
|
|
331
|
+
(model.gridContainerRef as any).current = container;
|
|
332
|
+
|
|
333
|
+
model.handleDragStart({
|
|
334
|
+
active: { id: 'item-1' },
|
|
335
|
+
activatorEvent: { clientX: 80, clientY: 80 },
|
|
336
|
+
} as any);
|
|
337
|
+
|
|
338
|
+
const slotTypes = ((model as any).dragState?.slots || []).map((slot) => slot.type);
|
|
339
|
+
expect(slotTypes).toContain('column');
|
|
340
|
+
expect(slotTypes).toContain('row-gap');
|
|
341
|
+
expect(slotTypes).toContain('column-edge');
|
|
342
|
+
expect(slotTypes).toContain('item-edge');
|
|
343
|
+
expect(slotTypes).toContain('empty-column');
|
|
344
|
+
model.handleDragCancel({} as any);
|
|
345
|
+
});
|
|
346
|
+
|
|
109
347
|
it('uses native pointer position instead of scroll-adjusted drag delta', () => {
|
|
110
348
|
const model = engine.createModel<GridModel>({
|
|
111
349
|
use: 'GridModel',
|
|
@@ -35,6 +35,36 @@ describe('transformRowsToSingleColumn', () => {
|
|
|
35
35
|
expect(all).toEqual([['a'], ['b', 'c'], ['d'], ['e']]);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
it('should preserve row ids for ordinary single-column mobile rows', () => {
|
|
39
|
+
const rows = {
|
|
40
|
+
row_a: [['a']],
|
|
41
|
+
row_b: [['b']],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
expect(transformRowsToSingleColumn(rows)).toEqual(rows);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should avoid adding another mobile prefix to ordinary single-column projected rows', () => {
|
|
48
|
+
const rows = {
|
|
49
|
+
'mobile:row_a:0': [['a']],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
expect(Object.keys(transformRowsToSingleColumn(rows))).toEqual(['mobile:row_a:0']);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should keep split multi-column row keys stable and unique', () => {
|
|
56
|
+
const rows = {
|
|
57
|
+
row_a: [['a'], ['b']],
|
|
58
|
+
};
|
|
59
|
+
const result = transformRowsToSingleColumn(rows);
|
|
60
|
+
const keys = Object.keys(result);
|
|
61
|
+
|
|
62
|
+
expect(keys).toHaveLength(2);
|
|
63
|
+
expect(new Set(keys).size).toBe(2);
|
|
64
|
+
expect(keys.every((key) => key.startsWith('mobile:'))).toBe(true);
|
|
65
|
+
expect(Object.values(result).map((row) => row[0])).toEqual([['a'], ['b']]);
|
|
66
|
+
});
|
|
67
|
+
|
|
38
68
|
it('should skip columns that only contain EMPTY_COLUMN_UID', () => {
|
|
39
69
|
const rows = buildRows([[[EMPTY_COLUMN_UID], ['a']]]);
|
|
40
70
|
const result = transformRowsToSingleColumn(rows);
|
|
@@ -55,4 +85,22 @@ describe('transformRowsToSingleColumn', () => {
|
|
|
55
85
|
const all = Object.values(result).map((r) => r[0]);
|
|
56
86
|
expect(all).toEqual([['x'], ['y']]);
|
|
57
87
|
});
|
|
88
|
+
|
|
89
|
+
it('should keep generated row keys stable for the same input', () => {
|
|
90
|
+
const rows = buildRows([
|
|
91
|
+
[['a'], ['b']],
|
|
92
|
+
[['c'], ['d']],
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
expect(Object.keys(transformRowsToSingleColumn(rows))).toEqual(Object.keys(transformRowsToSingleColumn(rows)));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should keep existing row keys stable when appending new mobile rows', () => {
|
|
99
|
+
const before = buildRows([[['a'], ['b']]]);
|
|
100
|
+
const after = buildRows([[['a'], ['b']], [['c']]]);
|
|
101
|
+
const beforeKeys = Object.keys(transformRowsToSingleColumn(before));
|
|
102
|
+
const afterKeys = Object.keys(transformRowsToSingleColumn(after));
|
|
103
|
+
|
|
104
|
+
expect(afterKeys.slice(0, beforeKeys.length)).toEqual(beforeKeys);
|
|
105
|
+
});
|
|
58
106
|
});
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
Droppable,
|
|
18
18
|
isRunJSValue,
|
|
19
19
|
normalizeRunJSValue,
|
|
20
|
-
runjsWithSafeGlobals,
|
|
21
20
|
tExpr,
|
|
22
21
|
FlowModelRenderer,
|
|
23
22
|
FlowSettingsButton,
|
|
@@ -231,6 +230,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
231
230
|
private initialDefaultsPromise?: Promise<void>;
|
|
232
231
|
private initialRefreshHandledTargetIds = new Set<string>();
|
|
233
232
|
private lastDefaultValueByFieldName = new Map<string, any>();
|
|
233
|
+
private userEditedFieldNames = new Set<string>();
|
|
234
234
|
private defaultValuesRefreshSeq = 0;
|
|
235
235
|
|
|
236
236
|
get form() {
|
|
@@ -431,11 +431,34 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
431
431
|
|
|
432
432
|
private canApplyFormDefaultValue(name: string, current: any, force?: boolean) {
|
|
433
433
|
if (force) return true;
|
|
434
|
+
if (this.userEditedFieldNames.has(name)) return false;
|
|
434
435
|
if (isEmptyValue(current)) return true;
|
|
435
436
|
if (!this.lastDefaultValueByFieldName.has(name)) return false;
|
|
436
437
|
return isEqual(current, this.lastDefaultValueByFieldName.get(name));
|
|
437
438
|
}
|
|
438
439
|
|
|
440
|
+
private canApplyFormOverrideValue(name: string, force?: boolean) {
|
|
441
|
+
if (force) return true;
|
|
442
|
+
return !this.userEditedFieldNames.has(name);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private normalizeFieldValueMode(mode: unknown): 'default' | 'assign' | 'override' {
|
|
446
|
+
if (mode === 'assign') return 'assign';
|
|
447
|
+
if (mode === 'override') return 'override';
|
|
448
|
+
return 'default';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private markFilterFormUserEditedFields(changedValues: any) {
|
|
452
|
+
if (!changedValues || typeof changedValues !== 'object' || Array.isArray(changedValues)) return;
|
|
453
|
+
for (const name of Object.keys(changedValues)) {
|
|
454
|
+
this.userEditedFieldNames.add(String(name));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private resetFilterFormUserEditedFields() {
|
|
459
|
+
this.userEditedFieldNames.clear();
|
|
460
|
+
}
|
|
461
|
+
|
|
439
462
|
private async matchDefaultValueCondition(condition: any) {
|
|
440
463
|
if (!condition) return true;
|
|
441
464
|
|
|
@@ -458,6 +481,10 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
458
481
|
if (!form) return appliedValues;
|
|
459
482
|
|
|
460
483
|
const force = options?.force === true;
|
|
484
|
+
if (force) {
|
|
485
|
+
this.resetFilterFormUserEditedFields();
|
|
486
|
+
}
|
|
487
|
+
|
|
461
488
|
const params = this.getStepParams?.('formFilterBlockModelSettings', 'defaultValues');
|
|
462
489
|
const rules = (params?.value || []) as any[];
|
|
463
490
|
if (!Array.isArray(rules) || rules.length === 0) return appliedValues;
|
|
@@ -466,7 +493,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
466
493
|
// RunJS support
|
|
467
494
|
if (isRunJSValue(raw)) {
|
|
468
495
|
const { code, version } = normalizeRunJSValue(raw);
|
|
469
|
-
const ret = await
|
|
496
|
+
const ret = await this.context.runjs(code, undefined, { version });
|
|
470
497
|
return ret?.success ? ret.value : undefined;
|
|
471
498
|
}
|
|
472
499
|
|
|
@@ -499,8 +526,9 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
499
526
|
|
|
500
527
|
const operator = getDefaultOperator(itemModel as any);
|
|
501
528
|
const normalized = normalizeFilterValueByOperator(operator, resolved);
|
|
502
|
-
const mode =
|
|
529
|
+
const mode = this.normalizeFieldValueMode(rule.mode);
|
|
503
530
|
if (mode === 'default' && !this.canApplyFormDefaultValue(String(name), current, force)) continue;
|
|
531
|
+
if (mode === 'override' && !this.canApplyFormOverrideValue(String(name), force)) continue;
|
|
504
532
|
if (isEqual(current, normalized)) {
|
|
505
533
|
if (mode === 'default') {
|
|
506
534
|
this.lastDefaultValueByFieldName.set(String(name), normalized);
|
|
@@ -527,6 +555,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
527
555
|
}
|
|
528
556
|
|
|
529
557
|
private handleFilterFormValuesChange(changedValues: any, allValues: any) {
|
|
558
|
+
this.markFilterFormUserEditedFields(changedValues);
|
|
530
559
|
const refreshSeq = ++this.defaultValuesRefreshSeq;
|
|
531
560
|
void (async () => {
|
|
532
561
|
const appliedValues = await this.applyFormDefaultValues({ refreshSeq });
|
|
@@ -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',
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { tExpr } from '@nocobase/flow-engine';
|
|
11
11
|
import { CodeEditor } from '../../../components/code-editor';
|
|
12
12
|
import { FilterFormActionModel } from './FilterFormActionModel';
|
|
13
13
|
import { resolveRunJsParams } from '../../utils/resolveRunJsParams';
|
|
@@ -60,12 +60,7 @@ FilterFormJSActionModel.registerFlow({
|
|
|
60
60
|
},
|
|
61
61
|
async handler(ctx, params) {
|
|
62
62
|
const { code, version } = resolveRunJsParams(ctx, params);
|
|
63
|
-
|
|
64
|
-
await ctx.runjs(
|
|
65
|
-
code,
|
|
66
|
-
{ window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
|
|
67
|
-
{ version },
|
|
68
|
-
);
|
|
63
|
+
await ctx.runjs(code, undefined, { version });
|
|
69
64
|
},
|
|
70
65
|
},
|
|
71
66
|
},
|
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({
|
|
@@ -50,6 +50,7 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
|
|
|
50
50
|
const model = {
|
|
51
51
|
defaultValuesRefreshSeq: 0,
|
|
52
52
|
lastDefaultValueByFieldName: new Map<string, any>(),
|
|
53
|
+
userEditedFieldNames: new Set<string>(),
|
|
53
54
|
form: {
|
|
54
55
|
getFieldsValue: () => ({ ...values }),
|
|
55
56
|
getFieldValue: (name: string) => values[name],
|
|
@@ -86,6 +87,10 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
|
|
|
86
87
|
return undefined;
|
|
87
88
|
}),
|
|
88
89
|
canApplyFormDefaultValue: (FilterFormBlockModel.prototype as any).canApplyFormDefaultValue,
|
|
90
|
+
canApplyFormOverrideValue: (FilterFormBlockModel.prototype as any).canApplyFormOverrideValue,
|
|
91
|
+
normalizeFieldValueMode: (FilterFormBlockModel.prototype as any).normalizeFieldValueMode,
|
|
92
|
+
markFilterFormUserEditedFields: (FilterFormBlockModel.prototype as any).markFilterFormUserEditedFields,
|
|
93
|
+
resetFilterFormUserEditedFields: (FilterFormBlockModel.prototype as any).resetFilterFormUserEditedFields,
|
|
89
94
|
matchDefaultValueCondition: (FilterFormBlockModel.prototype as any).matchDefaultValueCondition,
|
|
90
95
|
applyFormDefaultValues: FilterFormBlockModel.prototype.applyFormDefaultValues,
|
|
91
96
|
handleFilterFormValuesChange: (FilterFormBlockModel.prototype as any).handleFilterFormValuesChange,
|
|
@@ -296,6 +301,40 @@ describe('filter-form defaultValues wiring', () => {
|
|
|
296
301
|
expect(values.username_user).toBe('Manual');
|
|
297
302
|
});
|
|
298
303
|
|
|
304
|
+
it('does not reapply a filter form default value after user clears the field', async () => {
|
|
305
|
+
const { model, values } = createFilterFormDefaultValuesModel([
|
|
306
|
+
{
|
|
307
|
+
key: 'username-default',
|
|
308
|
+
enable: true,
|
|
309
|
+
targetPath: 'username',
|
|
310
|
+
mode: 'default',
|
|
311
|
+
value: 'admin',
|
|
312
|
+
},
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
316
|
+
expect(values.username_user).toBe('admin');
|
|
317
|
+
|
|
318
|
+
values.username_user = undefined;
|
|
319
|
+
(model as any).handleFilterFormValuesChange({ username_user: undefined }, { username_user: undefined });
|
|
320
|
+
|
|
321
|
+
await waitFor(() => {
|
|
322
|
+
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
323
|
+
'formValuesChange',
|
|
324
|
+
{
|
|
325
|
+
changedValues: {
|
|
326
|
+
username_user: undefined,
|
|
327
|
+
},
|
|
328
|
+
allValues: {
|
|
329
|
+
username_user: undefined,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{ debounce: true },
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
expect(values.username_user).toBeUndefined();
|
|
336
|
+
});
|
|
337
|
+
|
|
299
338
|
it('applies fixed values even when the target filter field already has a value', async () => {
|
|
300
339
|
const { model, values } = createFilterFormDefaultValuesModel(
|
|
301
340
|
[
|
|
@@ -315,6 +354,78 @@ describe('filter-form defaultValues wiring', () => {
|
|
|
315
354
|
expect(values.username_user).toBe('Bob');
|
|
316
355
|
});
|
|
317
356
|
|
|
357
|
+
it('applies override values until the target filter field is changed by user', async () => {
|
|
358
|
+
const { model, values } = createFilterFormDefaultValuesModel(
|
|
359
|
+
[
|
|
360
|
+
{
|
|
361
|
+
key: 'username-override',
|
|
362
|
+
enable: true,
|
|
363
|
+
targetPath: 'username',
|
|
364
|
+
mode: 'override',
|
|
365
|
+
value: '{{ ctx.formValues.nickname_nick }}',
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
{ nickname_nick: 'Bob', username_user: 'Manual' },
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
372
|
+
expect(values.username_user).toBe('Bob');
|
|
373
|
+
|
|
374
|
+
values.nickname_nick = 'Carol';
|
|
375
|
+
model.defaultValuesRefreshSeq += 1;
|
|
376
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, {
|
|
377
|
+
refreshSeq: model.defaultValuesRefreshSeq,
|
|
378
|
+
});
|
|
379
|
+
expect(values.username_user).toBe('Carol');
|
|
380
|
+
|
|
381
|
+
values.username_user = 'User value';
|
|
382
|
+
(model as any).handleFilterFormValuesChange(
|
|
383
|
+
{ username_user: 'User value' },
|
|
384
|
+
{ nickname_nick: 'Carol', username_user: 'User value' },
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
await waitFor(() => {
|
|
388
|
+
expect(values.username_user).toBe('User value');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
values.nickname_nick = 'Dora';
|
|
392
|
+
model.defaultValuesRefreshSeq += 1;
|
|
393
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, {
|
|
394
|
+
refreshSeq: model.defaultValuesRefreshSeq,
|
|
395
|
+
});
|
|
396
|
+
expect(values.username_user).toBe('User value');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('clears filter form user-edited override state on forced apply', async () => {
|
|
400
|
+
const { model, values } = createFilterFormDefaultValuesModel(
|
|
401
|
+
[
|
|
402
|
+
{
|
|
403
|
+
key: 'username-override',
|
|
404
|
+
enable: true,
|
|
405
|
+
targetPath: 'username',
|
|
406
|
+
mode: 'override',
|
|
407
|
+
condition: {
|
|
408
|
+
logic: '$and',
|
|
409
|
+
items: [{ path: '{{ ctx.formValues.nickname_nick }}', operator: '$eq', value: 'allow' }],
|
|
410
|
+
},
|
|
411
|
+
value: '{{ ctx.formValues.nickname_nick }}',
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
{ nickname_nick: 'deny', username_user: 'User value' },
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
model.userEditedFieldNames.add('username_user');
|
|
418
|
+
|
|
419
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, { force: true });
|
|
420
|
+
expect(values.username_user).toBe('User value');
|
|
421
|
+
expect(model.userEditedFieldNames.has('username_user')).toBe(false);
|
|
422
|
+
|
|
423
|
+
values.nickname_nick = 'allow';
|
|
424
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
425
|
+
|
|
426
|
+
expect(values.username_user).toBe('allow');
|
|
427
|
+
});
|
|
428
|
+
|
|
318
429
|
it('skips filter form field values when the rule condition does not match', async () => {
|
|
319
430
|
const { model, values } = createFilterFormDefaultValuesModel(
|
|
320
431
|
[
|