@nocobase/client-v2 2.1.0-alpha.25 → 2.1.0-alpha.27
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 +1 -0
- package/es/flow/actions/dataScopeFilter.d.ts +9 -0
- package/es/flow/components/Grid/index.d.ts +5 -3
- package/es/flow/components/code-editor/types.d.ts +1 -0
- package/es/flow/internal/utils/rebuildFieldSubModel.d.ts +2 -1
- package/es/flow/models/base/GridModel.d.ts +19 -2
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +7 -1
- package/es/flow/models/fields/JSFieldModel.d.ts +5 -0
- package/es/index.mjs +81 -81
- package/lib/index.js +73 -73
- package/package.json +5 -5
- package/src/BaseApplication.tsx +4 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +158 -0
- package/src/flow/actions/dataScope.tsx +6 -4
- package/src/flow/actions/dataScopeFilter.ts +70 -0
- package/src/flow/actions/setTargetDataScope.tsx +6 -5
- package/src/flow/components/Grid/index.tsx +66 -20
- package/src/flow/components/code-editor/__tests__/linter.test.ts +18 -0
- package/src/flow/components/code-editor/__tests__/runjsDiagnostics.test.ts +23 -0
- package/src/flow/components/code-editor/index.tsx +18 -17
- package/src/flow/components/code-editor/linter.ts +222 -158
- package/src/flow/components/code-editor/runjsDiagnostics.ts +161 -97
- package/src/flow/components/code-editor/types.ts +1 -0
- package/src/flow/internal/utils/__tests__/rebuildFieldSubModel.test.ts +77 -2
- package/src/flow/internal/utils/rebuildFieldSubModel.ts +21 -5
- package/src/flow/models/base/BlockGridModel.tsx +2 -2
- package/src/flow/models/base/GridModel.tsx +428 -195
- package/src/flow/models/base/__tests__/BlockGridModel.dragOverlayConfig.test.ts +44 -0
- package/src/flow/models/base/__tests__/GridModel.computeOverlayRect.test.ts +29 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +181 -2
- package/src/flow/models/base/__tests__/GridModel.resizeLayout.test.ts +124 -0
- package/src/flow/models/base/__tests__/GridModel.visibleLayout.test.ts +55 -15
- package/src/flow/models/blocks/details/DetailsGridModel.tsx +6 -6
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +9 -5
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +54 -14
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormBlockModel.cleanup.test.ts +138 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +45 -0
- package/src/flow/models/blocks/form/FormGridModel.tsx +6 -6
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +39 -16
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +22 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +30 -2
- package/src/flow/models/blocks/table/TableBlockModel.tsx +8 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +51 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +49 -0
- package/src/flow/models/fields/JSFieldModel.tsx +54 -14
|
@@ -0,0 +1,44 @@
|
|
|
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 { FlowEngine, LayoutSlot } from '@nocobase/flow-engine';
|
|
11
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
12
|
+
import { BlockGridModel } from '../BlockGridModel';
|
|
13
|
+
|
|
14
|
+
describe('BlockGridModel dragOverlayConfig', () => {
|
|
15
|
+
let model: BlockGridModel;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
const engine = new FlowEngine();
|
|
19
|
+
engine.registerModels({ BlockGridModel });
|
|
20
|
+
model = engine.createModel<BlockGridModel>({ use: 'BlockGridModel' });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('applies block insert overlay offsets from dragOverlayConfig', () => {
|
|
24
|
+
const beforeSlot: LayoutSlot = {
|
|
25
|
+
type: 'column',
|
|
26
|
+
rowId: 'row1',
|
|
27
|
+
columnIndex: 0,
|
|
28
|
+
insertIndex: 0,
|
|
29
|
+
position: 'before',
|
|
30
|
+
rect: { top: 100, left: 50, width: 200, height: 48 },
|
|
31
|
+
};
|
|
32
|
+
const afterSlot: LayoutSlot = {
|
|
33
|
+
type: 'column',
|
|
34
|
+
rowId: 'row1',
|
|
35
|
+
columnIndex: 0,
|
|
36
|
+
insertIndex: 1,
|
|
37
|
+
position: 'after',
|
|
38
|
+
rect: { top: 300, left: 50, width: 200, height: 48 },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
expect((model as any).computeOverlayRect(beforeSlot).top).toBe(88);
|
|
42
|
+
expect((model as any).computeOverlayRect(afterSlot).top).toBe(312);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -203,6 +203,35 @@ describe('GridModel computeOverlayRect', () => {
|
|
|
203
203
|
});
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
describe('Item-edge slot', () => {
|
|
207
|
+
it('should apply column-edge config to item-edge overlay', () => {
|
|
208
|
+
model.dragOverlayConfig = {
|
|
209
|
+
columnEdge: {
|
|
210
|
+
right: { width: 24, offsetLeft: 8 },
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const slot: LayoutSlot = {
|
|
215
|
+
type: 'item-edge',
|
|
216
|
+
rowId: 'row1',
|
|
217
|
+
columnIndex: 0,
|
|
218
|
+
itemIndex: 0,
|
|
219
|
+
itemUid: 'item-1',
|
|
220
|
+
direction: 'right',
|
|
221
|
+
rect: { top: 100, left: 250, width: 16, height: 120 },
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const result = (model as any).computeOverlayRect(slot);
|
|
225
|
+
|
|
226
|
+
expect(result).toEqual({
|
|
227
|
+
top: 100,
|
|
228
|
+
left: 258,
|
|
229
|
+
width: 24,
|
|
230
|
+
height: 120,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
206
235
|
describe('Row-gap slot', () => {
|
|
207
236
|
it('should use default rect when no config provided', () => {
|
|
208
237
|
const slot: LayoutSlot = {
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React from 'react';
|
|
11
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
12
10
|
import { FlowEngine } from '@nocobase/flow-engine';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
13
|
import { GridModel } from '../GridModel';
|
|
14
14
|
|
|
15
15
|
const createMockRect = ({ top, left, width, height }: { top: number; left: number; width: number; height: number }) => {
|
|
@@ -103,5 +103,184 @@ describe('GridModel drag snapshot container', () => {
|
|
|
103
103
|
(model as any).updateLayoutSnapshot();
|
|
104
104
|
|
|
105
105
|
expect((model as any).dragState?.slots?.length).toBeGreaterThan(0);
|
|
106
|
+
model.handleDragCancel({} as any);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('uses native pointer position instead of scroll-adjusted drag delta', () => {
|
|
110
|
+
const model = engine.createModel<GridModel>({
|
|
111
|
+
use: 'GridModel',
|
|
112
|
+
uid: 'grid-drag-pointer-position',
|
|
113
|
+
props: {},
|
|
114
|
+
structure: {} as any,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
(model as any).dragState = {
|
|
118
|
+
sourceUid: 'item-1',
|
|
119
|
+
snapshot: { rows: {}, sizes: {} },
|
|
120
|
+
slots: [],
|
|
121
|
+
containerEl: null,
|
|
122
|
+
containerRect: { top: 0, left: 0, width: 0, height: 0 },
|
|
123
|
+
pointerOrigin: { x: 100, y: 100 },
|
|
124
|
+
pointerPosition: { x: 120, y: 180 },
|
|
125
|
+
activeSlotKey: null,
|
|
126
|
+
previewLayout: undefined,
|
|
127
|
+
refreshTimer: null,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const point = (model as any).computePointerPosition({
|
|
131
|
+
delta: { x: 0, y: 600 },
|
|
132
|
+
over: null,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(point).toEqual({ x: 120, y: 180 });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('removes drag document listeners when dragging finishes', () => {
|
|
139
|
+
const model = engine.createModel<GridModel>({
|
|
140
|
+
use: 'GridModel',
|
|
141
|
+
uid: 'grid-drag-cleanup',
|
|
142
|
+
props: {},
|
|
143
|
+
structure: {} as any,
|
|
144
|
+
});
|
|
145
|
+
const cleanupListeners = vi.fn();
|
|
146
|
+
|
|
147
|
+
(model as any).dragState = {
|
|
148
|
+
sourceUid: 'item-1',
|
|
149
|
+
snapshot: { rows: {}, sizes: {} },
|
|
150
|
+
slots: [],
|
|
151
|
+
containerEl: null,
|
|
152
|
+
containerRect: { top: 0, left: 0, width: 0, height: 0 },
|
|
153
|
+
activeSlotKey: null,
|
|
154
|
+
previewLayout: undefined,
|
|
155
|
+
refreshTimer: null,
|
|
156
|
+
cleanupListeners,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
model.handleDragCancel({} as any);
|
|
160
|
+
|
|
161
|
+
expect(cleanupListeners).toHaveBeenCalledOnce();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('refreshes drag snapshot after the page scrolls during dragging', () => {
|
|
165
|
+
vi.useFakeTimers();
|
|
166
|
+
try {
|
|
167
|
+
const model = engine.createModel<GridModel>({
|
|
168
|
+
use: 'GridModel',
|
|
169
|
+
uid: 'grid-drag-scroll-refresh',
|
|
170
|
+
props: {},
|
|
171
|
+
structure: {} as any,
|
|
172
|
+
});
|
|
173
|
+
const updateLayoutSnapshot = vi.fn();
|
|
174
|
+
const refreshPreviewFromPointer = vi.fn();
|
|
175
|
+
|
|
176
|
+
(model as any).dragState = {
|
|
177
|
+
sourceUid: 'item-1',
|
|
178
|
+
snapshot: { rows: {}, sizes: {} },
|
|
179
|
+
slots: [],
|
|
180
|
+
containerEl: null,
|
|
181
|
+
containerRect: { top: 0, left: 0, width: 0, height: 0 },
|
|
182
|
+
pointerPosition: { x: 120, y: 180 },
|
|
183
|
+
activeSlotKey: null,
|
|
184
|
+
previewLayout: undefined,
|
|
185
|
+
refreshTimer: null,
|
|
186
|
+
};
|
|
187
|
+
(model as any).updateLayoutSnapshot = updateLayoutSnapshot;
|
|
188
|
+
(model as any).refreshPreviewFromPointer = refreshPreviewFromPointer;
|
|
189
|
+
|
|
190
|
+
(model as any).handleDragScroll();
|
|
191
|
+
vi.advanceTimersByTime(16);
|
|
192
|
+
|
|
193
|
+
expect(updateLayoutSnapshot).toHaveBeenCalledOnce();
|
|
194
|
+
expect(refreshPreviewFromPointer).toHaveBeenCalledOnce();
|
|
195
|
+
} finally {
|
|
196
|
+
vi.useRealTimers();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('uses overlay-sized hit area when resolving drag slot', () => {
|
|
201
|
+
const model = engine.createModel<GridModel>({
|
|
202
|
+
use: 'GridModel',
|
|
203
|
+
uid: 'grid-drag-hit-area',
|
|
204
|
+
props: {},
|
|
205
|
+
structure: {} as any,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
model.dragOverlayConfig = {
|
|
209
|
+
columnEdge: {
|
|
210
|
+
right: { width: 24, offsetLeft: 8 },
|
|
211
|
+
},
|
|
212
|
+
} as any;
|
|
213
|
+
|
|
214
|
+
(model as any).dragState = {
|
|
215
|
+
sourceUid: 'item-1',
|
|
216
|
+
snapshot: { rows: {}, sizes: {} },
|
|
217
|
+
slots: [
|
|
218
|
+
{
|
|
219
|
+
type: 'column-edge',
|
|
220
|
+
rowId: 'row-1',
|
|
221
|
+
columnIndex: 0,
|
|
222
|
+
direction: 'right',
|
|
223
|
+
rect: { top: 100, left: 200, width: 16, height: 120 },
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
containerEl: null,
|
|
227
|
+
containerRect: { top: 0, left: 0, width: 0, height: 0 },
|
|
228
|
+
activeSlotKey: null,
|
|
229
|
+
previewLayout: undefined,
|
|
230
|
+
refreshTimer: null,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const resolved = (model as any).resolveDragSlot({ x: 225, y: 140 });
|
|
234
|
+
|
|
235
|
+
expect(resolved).toMatchObject({
|
|
236
|
+
type: 'column-edge',
|
|
237
|
+
rowId: 'row-1',
|
|
238
|
+
columnIndex: 0,
|
|
239
|
+
direction: 'right',
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('keeps overlay coordinates relative to grid root when outer container is scrolled', () => {
|
|
244
|
+
const model = engine.createModel<GridModel>({
|
|
245
|
+
use: 'GridModel',
|
|
246
|
+
uid: 'grid-drag-overlay-scroll',
|
|
247
|
+
props: {},
|
|
248
|
+
structure: {} as any,
|
|
249
|
+
});
|
|
250
|
+
const container = document.createElement('div');
|
|
251
|
+
container.scrollTop = 40;
|
|
252
|
+
container.scrollLeft = 12;
|
|
253
|
+
(model.gridContainerRef as any).current = container;
|
|
254
|
+
|
|
255
|
+
(model as any).dragState = {
|
|
256
|
+
sourceUid: 'item-1',
|
|
257
|
+
snapshot: {
|
|
258
|
+
rows: { 'row-1': [['item-1', 'item-2']] },
|
|
259
|
+
sizes: { 'row-1': [24] },
|
|
260
|
+
},
|
|
261
|
+
slots: [],
|
|
262
|
+
containerEl: container,
|
|
263
|
+
containerRect: { top: 100, left: 50, width: 480, height: 280 },
|
|
264
|
+
activeSlotKey: null,
|
|
265
|
+
previewLayout: undefined,
|
|
266
|
+
refreshTimer: null,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
(model as any).applyPreview({
|
|
270
|
+
type: 'column',
|
|
271
|
+
rowId: 'row-1',
|
|
272
|
+
columnIndex: 0,
|
|
273
|
+
insertIndex: 1,
|
|
274
|
+
position: 'after',
|
|
275
|
+
rect: { top: 130, left: 90, width: 220, height: 60 },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(model.props.dragOverlayRect).toMatchObject({
|
|
279
|
+
top: 30,
|
|
280
|
+
left: 40,
|
|
281
|
+
width: 220,
|
|
282
|
+
height: 60,
|
|
283
|
+
type: 'column',
|
|
284
|
+
});
|
|
106
285
|
});
|
|
107
286
|
});
|
|
@@ -0,0 +1,124 @@
|
|
|
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 { FlowEngine } from '@nocobase/flow-engine';
|
|
11
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
12
|
+
import { GridModel } from '../GridModel';
|
|
13
|
+
|
|
14
|
+
describe('GridModel resize layout', () => {
|
|
15
|
+
let engine: FlowEngine;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
engine = new FlowEngine();
|
|
19
|
+
engine.registerModels({ GridModel });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('updates v2 layout sizes during resize so the saved layout keeps the new width', () => {
|
|
23
|
+
const itemA = engine.createModel({ use: 'FlowModel', uid: 'a' });
|
|
24
|
+
const itemB = engine.createModel({ use: 'FlowModel', uid: 'b' });
|
|
25
|
+
const model = engine.createModel<GridModel>({
|
|
26
|
+
uid: 'grid-resize-layout',
|
|
27
|
+
use: 'GridModel',
|
|
28
|
+
props: {
|
|
29
|
+
layout: {
|
|
30
|
+
version: 2,
|
|
31
|
+
rows: [
|
|
32
|
+
{
|
|
33
|
+
id: 'rowA',
|
|
34
|
+
cells: [
|
|
35
|
+
{ id: 'cellA', items: ['a'] },
|
|
36
|
+
{ id: 'cellB', items: ['b'] },
|
|
37
|
+
],
|
|
38
|
+
sizes: [12, 12],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
structure: {} as any,
|
|
44
|
+
});
|
|
45
|
+
(model as any).subModels = { items: [itemA, itemB] };
|
|
46
|
+
model.syncLayoutProps(model.getGridLayout());
|
|
47
|
+
|
|
48
|
+
const container = document.createElement('div');
|
|
49
|
+
Object.defineProperty(container, 'clientWidth', {
|
|
50
|
+
configurable: true,
|
|
51
|
+
value: 240,
|
|
52
|
+
});
|
|
53
|
+
(model.gridContainerRef as any).current = container;
|
|
54
|
+
model.onMount();
|
|
55
|
+
|
|
56
|
+
model.emitter.emit('onResizeRight', { resizeDistance: 60, model: itemA });
|
|
57
|
+
model.emitter.emit('onResizeEnd');
|
|
58
|
+
|
|
59
|
+
expect(model.props.layout.rows[0].sizes).toEqual([18, 6]);
|
|
60
|
+
expect(model.getStepParams('gridSettings', 'grid').layout.rows[0].sizes).toEqual([18, 6]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('uses the nested row container width when resizing v2 nested layouts', () => {
|
|
64
|
+
const itemA = engine.createModel({ use: 'FlowModel', uid: 'a' });
|
|
65
|
+
const itemB = engine.createModel({ use: 'FlowModel', uid: 'b' });
|
|
66
|
+
const model = engine.createModel<GridModel>({
|
|
67
|
+
uid: 'grid-resize-nested-layout',
|
|
68
|
+
use: 'GridModel',
|
|
69
|
+
props: {
|
|
70
|
+
layout: {
|
|
71
|
+
version: 2,
|
|
72
|
+
rows: [
|
|
73
|
+
{
|
|
74
|
+
id: 'outer',
|
|
75
|
+
cells: [
|
|
76
|
+
{
|
|
77
|
+
id: 'outer-cell',
|
|
78
|
+
rows: [
|
|
79
|
+
{
|
|
80
|
+
id: 'nested',
|
|
81
|
+
cells: [
|
|
82
|
+
{ id: 'nested-a', items: ['a'] },
|
|
83
|
+
{ id: 'nested-b', items: ['b'] },
|
|
84
|
+
],
|
|
85
|
+
sizes: [12, 12],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
sizes: [24],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
structure: {} as any,
|
|
96
|
+
});
|
|
97
|
+
(model as any).subModels = { items: [itemA, itemB] };
|
|
98
|
+
model.syncLayoutProps(model.getGridLayout());
|
|
99
|
+
|
|
100
|
+
const container = document.createElement('div');
|
|
101
|
+
Object.defineProperty(container, 'clientWidth', {
|
|
102
|
+
configurable: true,
|
|
103
|
+
value: 240,
|
|
104
|
+
});
|
|
105
|
+
const nestedRowParent = document.createElement('div');
|
|
106
|
+
Object.defineProperty(nestedRowParent, 'clientWidth', {
|
|
107
|
+
configurable: true,
|
|
108
|
+
value: 120,
|
|
109
|
+
});
|
|
110
|
+
const nestedRowElement = document.createElement('div');
|
|
111
|
+
nestedRowElement.dataset.gridRowId = 'nested';
|
|
112
|
+
nestedRowElement.dataset.gridPath = JSON.stringify([{ rowId: 'outer', cellId: 'outer-cell' }, { rowId: 'nested' }]);
|
|
113
|
+
nestedRowParent.appendChild(nestedRowElement);
|
|
114
|
+
container.appendChild(nestedRowParent);
|
|
115
|
+
(model.gridContainerRef as any).current = container;
|
|
116
|
+
model.onMount();
|
|
117
|
+
|
|
118
|
+
model.emitter.emit('onResizeRight', { resizeDistance: 30, model: itemA });
|
|
119
|
+
model.emitter.emit('onResizeEnd');
|
|
120
|
+
|
|
121
|
+
const nestedRow = model.props.layout.rows[0].cells[0].rows![0];
|
|
122
|
+
expect(nestedRow.sizes).toEqual([18, 6]);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
11
10
|
import { EMPTY_COLUMN_UID, FlowEngine } from '@nocobase/flow-engine';
|
|
11
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
12
12
|
import { GridModel } from '../GridModel';
|
|
13
13
|
|
|
14
14
|
describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
@@ -74,7 +74,7 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
74
74
|
// 第一列只有 hidden,应该被过滤掉,只保留包含 v 的那一列
|
|
75
75
|
expect(Object.keys(rows)).toEqual(['row1']);
|
|
76
76
|
expect(rows.row1).toEqual([['v']]);
|
|
77
|
-
expect(sizes.row1).toEqual([
|
|
77
|
+
expect(sizes.row1).toEqual([24]);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
it('removes entire row when all columns are hidden-only', async () => {
|
|
@@ -109,13 +109,14 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
109
109
|
it('uses rowOrder when provided to keep row sequence', async () => {
|
|
110
110
|
await engine.flowSettings.disable();
|
|
111
111
|
|
|
112
|
-
const
|
|
112
|
+
const visible1 = engine.createModel({ use: 'FlowModel', uid: 'v1' });
|
|
113
|
+
const visible2 = engine.createModel({ use: 'FlowModel', uid: 'v2' });
|
|
113
114
|
const model = engine.createModel<GridModel>({
|
|
114
115
|
use: 'GridModel',
|
|
115
116
|
uid: 'grid-4',
|
|
116
117
|
props: {
|
|
117
118
|
rows: {
|
|
118
|
-
second: [['
|
|
119
|
+
second: [['v2']],
|
|
119
120
|
first: [['v1']],
|
|
120
121
|
},
|
|
121
122
|
sizes: {
|
|
@@ -127,7 +128,7 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
127
128
|
structure: {} as any,
|
|
128
129
|
});
|
|
129
130
|
|
|
130
|
-
(model as any).subModels = { items: [
|
|
131
|
+
(model as any).subModels = { items: [visible1, visible2] };
|
|
131
132
|
|
|
132
133
|
const { rows } = (model as any).getVisibleLayout();
|
|
133
134
|
expect(Object.keys(rows)).toEqual(['first', 'second']);
|
|
@@ -136,13 +137,14 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
136
137
|
it('falls back to rows key order when rowOrder is missing', async () => {
|
|
137
138
|
await engine.flowSettings.disable();
|
|
138
139
|
|
|
139
|
-
const
|
|
140
|
+
const visible1 = engine.createModel({ use: 'FlowModel', uid: 'v1' });
|
|
141
|
+
const visible2 = engine.createModel({ use: 'FlowModel', uid: 'v2' });
|
|
140
142
|
const model = engine.createModel<GridModel>({
|
|
141
143
|
use: 'GridModel',
|
|
142
144
|
uid: 'grid-5',
|
|
143
145
|
props: {
|
|
144
146
|
rows: {
|
|
145
|
-
second: [['
|
|
147
|
+
second: [['v2']],
|
|
146
148
|
first: [['v1']],
|
|
147
149
|
},
|
|
148
150
|
sizes: {
|
|
@@ -153,7 +155,7 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
153
155
|
structure: {} as any,
|
|
154
156
|
});
|
|
155
157
|
|
|
156
|
-
(model as any).subModels = { items: [
|
|
158
|
+
(model as any).subModels = { items: [visible1, visible2] };
|
|
157
159
|
|
|
158
160
|
const { rows } = (model as any).getVisibleLayout();
|
|
159
161
|
expect(Object.keys(rows)).toEqual(['second', 'first']);
|
|
@@ -163,6 +165,7 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
163
165
|
await engine.flowSettings.disable();
|
|
164
166
|
|
|
165
167
|
const visible = engine.createModel({ use: 'FlowModel', uid: 'v' });
|
|
168
|
+
const visible2 = engine.createModel({ use: 'FlowModel', uid: 'v2' });
|
|
166
169
|
const hidden = engine.createModel({ use: 'FlowModel', uid: 'h' }) as any;
|
|
167
170
|
hidden.hidden = true;
|
|
168
171
|
|
|
@@ -171,7 +174,7 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
171
174
|
uid: 'grid-6',
|
|
172
175
|
props: {
|
|
173
176
|
rows: {
|
|
174
|
-
row1: [['h', 'v'], ['
|
|
177
|
+
row1: [['h', 'v'], ['v2']],
|
|
175
178
|
},
|
|
176
179
|
sizes: {
|
|
177
180
|
row1: [8, 16],
|
|
@@ -180,14 +183,51 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
180
183
|
structure: {} as any,
|
|
181
184
|
});
|
|
182
185
|
|
|
183
|
-
(model as any).subModels = { items: [hidden, visible] };
|
|
186
|
+
(model as any).subModels = { items: [hidden, visible, visible2] };
|
|
184
187
|
|
|
185
188
|
const { rows, sizes } = (model as any).getVisibleLayout();
|
|
186
189
|
// 第一个单元格中的 h 应被剔除,只剩 v;列宽保持不变
|
|
187
|
-
expect(rows.row1).toEqual([['v'], ['
|
|
190
|
+
expect(rows.row1).toEqual([['v'], ['v2']]);
|
|
188
191
|
expect(sizes.row1).toEqual([8, 16]);
|
|
189
192
|
});
|
|
190
193
|
|
|
194
|
+
it('preserves remaining column size ratios when filtering v2 layout columns', () => {
|
|
195
|
+
engine.flowSettings.disable();
|
|
196
|
+
|
|
197
|
+
const visible1 = engine.createModel({ use: 'FlowModel', uid: 'v1' });
|
|
198
|
+
const visible2 = engine.createModel({ use: 'FlowModel', uid: 'v2' });
|
|
199
|
+
const hidden = engine.createModel({ use: 'FlowModel', uid: 'h' }) as any;
|
|
200
|
+
hidden.hidden = true;
|
|
201
|
+
|
|
202
|
+
const model = engine.createModel<GridModel>({
|
|
203
|
+
use: 'GridModel',
|
|
204
|
+
uid: 'grid-7',
|
|
205
|
+
props: {
|
|
206
|
+
layout: {
|
|
207
|
+
version: 2,
|
|
208
|
+
rows: [
|
|
209
|
+
{
|
|
210
|
+
id: 'row1',
|
|
211
|
+
cells: [
|
|
212
|
+
{ id: 'cell1', items: ['h'] },
|
|
213
|
+
{ id: 'cell2', items: ['v1'] },
|
|
214
|
+
{ id: 'cell3', items: ['v2'] },
|
|
215
|
+
],
|
|
216
|
+
sizes: [4, 8, 12],
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
structure: {} as any,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
(model as any).subModels = { items: [hidden, visible1, visible2] };
|
|
225
|
+
|
|
226
|
+
const { rows, sizes } = (model as any).getVisibleLayout();
|
|
227
|
+
expect(rows.row1).toEqual([['v1'], ['v2']]);
|
|
228
|
+
expect(sizes.row1).toEqual([10, 14]);
|
|
229
|
+
});
|
|
230
|
+
|
|
191
231
|
it('ignores EMPTY_COLUMN uid in runtime mode without crashing', async () => {
|
|
192
232
|
await engine.flowSettings.disable();
|
|
193
233
|
|
|
@@ -195,7 +235,7 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
195
235
|
|
|
196
236
|
const model = engine.createModel<GridModel>({
|
|
197
237
|
use: 'GridModel',
|
|
198
|
-
uid: 'grid-
|
|
238
|
+
uid: 'grid-8',
|
|
199
239
|
props: {
|
|
200
240
|
rows: {
|
|
201
241
|
row1: [[EMPTY_COLUMN_UID, 'ghost', 'v'], ['ghost-2']],
|
|
@@ -210,8 +250,8 @@ describe('GridModel.getVisibleLayout (hidden items filtering)', () => {
|
|
|
210
250
|
(model as any).subModels = { items: [visible] };
|
|
211
251
|
|
|
212
252
|
const { rows, sizes } = (model as any).getVisibleLayout();
|
|
213
|
-
//
|
|
214
|
-
expect(rows.row1).toEqual([['
|
|
215
|
-
expect(sizes.row1).toEqual([
|
|
253
|
+
// 新布局归一化会移除不在 subModels.items 中的 uid,EMPTY_COLUMN_UID 也不会在运行态显示
|
|
254
|
+
expect(rows.row1).toEqual([['v']]);
|
|
255
|
+
expect(sizes.row1).toEqual([24]);
|
|
216
256
|
});
|
|
217
257
|
});
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { SettingOutlined } from '@ant-design/icons';
|
|
11
|
-
import { AddSubModelButton,
|
|
11
|
+
import { AddSubModelButton, DragOverlayConfig, FlowSettingsButton } from '@nocobase/flow-engine';
|
|
12
|
+
import { Skeleton } from 'antd';
|
|
12
13
|
import React from 'react';
|
|
13
14
|
import { FieldModel, GridModel } from '../../base';
|
|
14
15
|
import { DetailsBlockModel } from './DetailsBlockModel';
|
|
15
|
-
import { Skeleton } from 'antd';
|
|
16
16
|
|
|
17
17
|
export class DetailsGridModel extends GridModel<{
|
|
18
18
|
parent: DetailsBlockModel;
|
|
@@ -32,8 +32,8 @@ export class DetailsGridModel extends GridModel<{
|
|
|
32
32
|
dragOverlayConfig: DragOverlayConfig = {
|
|
33
33
|
// 列内插入
|
|
34
34
|
columnInsert: {
|
|
35
|
-
before: { offsetTop: -
|
|
36
|
-
after: { offsetTop:
|
|
35
|
+
before: { offsetTop: -6, height: 24 },
|
|
36
|
+
after: { offsetTop: 3, height: 24 },
|
|
37
37
|
},
|
|
38
38
|
// 列边缘
|
|
39
39
|
columnEdge: {
|
|
@@ -42,8 +42,8 @@ export class DetailsGridModel extends GridModel<{
|
|
|
42
42
|
},
|
|
43
43
|
// 行间隙
|
|
44
44
|
rowGap: {
|
|
45
|
-
above: { offsetTop:
|
|
46
|
-
below: { offsetTop: -
|
|
45
|
+
above: { offsetTop: -2, height: 24 },
|
|
46
|
+
below: { offsetTop: -12, height: 24 },
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
|
|
@@ -102,15 +102,19 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
102
102
|
// 首次进入页面:等待子模型 beforeRender 完成(例如 name 初始化),再应用表单级默认值并触发筛选
|
|
103
103
|
void this.applyDefaultsAndInitialFilter();
|
|
104
104
|
|
|
105
|
-
//
|
|
105
|
+
// 监听页面区块删除,自动清理已失效的筛选字段。
|
|
106
|
+
// 这里使用 onSubModelDestroyed 而不是 onSubModelRemoved,避免弹窗关闭时
|
|
107
|
+
// 的临时模型卸载被误判成“用户删除了目标区块”。
|
|
106
108
|
const blockGridModel = this.context.blockGridModel;
|
|
107
109
|
if (blockGridModel?.emitter) {
|
|
108
|
-
const
|
|
110
|
+
const handleTargetDestroyed = (model) => {
|
|
109
111
|
if (!model?.uid || model.uid === this.uid) return;
|
|
110
|
-
this.handleTargetBlockRemoved(model.uid)
|
|
112
|
+
void this.handleTargetBlockRemoved(model.uid).catch((error) => {
|
|
113
|
+
console.error('Failed to handle destroyed target block in FilterFormBlockModel:', error);
|
|
114
|
+
});
|
|
111
115
|
};
|
|
112
|
-
blockGridModel.emitter.on('
|
|
113
|
-
this.removeTargetBlockListener = () => blockGridModel.emitter.off('
|
|
116
|
+
blockGridModel.emitter.on('onSubModelDestroyed', handleTargetDestroyed);
|
|
117
|
+
this.removeTargetBlockListener = () => blockGridModel.emitter.off('onSubModelDestroyed', handleTargetDestroyed);
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
120
|
|