@nocobase/flow-engine 2.1.0-alpha.1 → 2.1.0-alpha.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/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/BlockScopedFlowEngine.js +0 -1
- package/lib/FlowDefinition.d.ts +6 -0
- package/lib/FlowSchemaRegistry.d.ts +154 -0
- package/lib/FlowSchemaRegistry.js +1427 -0
- package/lib/JSRunner.d.ts +15 -0
- package/lib/JSRunner.js +82 -7
- package/lib/ViewScopedFlowEngine.js +8 -1
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
- package/lib/components/dnd/gridDragPlanner.js +59 -3
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +21 -3
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/utils.js +2 -2
- package/lib/components/variables/VariableInput.js +9 -4
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/components/variables/utils.d.ts +7 -0
- package/lib/components/variables/utils.js +42 -2
- package/lib/data-source/index.d.ts +7 -27
- package/lib/data-source/index.js +84 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +190 -26
- package/lib/flow-schema-registry/fieldBinding.d.ts +32 -0
- package/lib/flow-schema-registry/fieldBinding.js +165 -0
- package/lib/flow-schema-registry/modelPatches.d.ts +16 -0
- package/lib/flow-schema-registry/modelPatches.js +235 -0
- package/lib/flow-schema-registry/schemaInference.d.ts +17 -0
- package/lib/flow-schema-registry/schemaInference.js +207 -0
- package/lib/flow-schema-registry/utils.d.ts +25 -0
- package/lib/flow-schema-registry/utils.js +293 -0
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2270 -148
- package/lib/flowEngine.d.ts +160 -1
- package/lib/flowEngine.js +387 -27
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +51 -17
- package/lib/index.d.ts +8 -1
- package/lib/index.js +24 -1
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +9 -2
- package/lib/locale/index.d.ts +14 -0
- package/lib/locale/zh-CN.json +8 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +7 -0
- package/lib/models/flowModel.js +83 -8
- package/lib/provider.js +7 -6
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +4 -3
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/base.js +706 -41
- package/lib/runjs-context/contributions.d.ts +33 -0
- package/lib/runjs-context/contributions.js +88 -0
- package/lib/runjs-context/helpers.js +12 -1
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +23 -9
- package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
- package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
- package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
- package/lib/runjs-context/snippets/index.d.ts +11 -1
- package/lib/runjs-context/snippets/index.js +61 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +7 -1
- package/lib/scheduler/ModelOperationScheduler.js +28 -23
- package/lib/server.d.ts +10 -0
- package/lib/server.js +32 -0
- package/lib/types.d.ts +296 -1
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/createEphemeralContext.js +2 -2
- package/lib/utils/dateVariable.d.ts +16 -0
- package/lib/utils/dateVariable.js +380 -0
- package/lib/utils/exceptions.d.ts +7 -0
- package/lib/utils/exceptions.js +10 -0
- package/lib/utils/index.d.ts +8 -3
- package/lib/utils/index.js +49 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/parsePathnameToViewParams.js +1 -1
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +18 -8
- package/lib/utils/safeGlobals.js +164 -17
- package/lib/utils/schema-utils.d.ts +17 -1
- package/lib/utils/schema-utils.js +80 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +28 -6
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +27 -5
- package/lib/views/usePage.d.ts +6 -1
- package/lib/views/usePage.js +53 -9
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +5 -5
- package/server.d.ts +1 -0
- package/server.js +1 -0
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/FlowSchemaRegistry.ts +1799 -0
- package/src/JSRunner.ts +111 -5
- package/src/ViewScopedFlowEngine.ts +8 -0
- package/src/__tests__/FlowSchemaRegistry.test.ts +1951 -0
- package/src/__tests__/JSRunner.test.ts +91 -1
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flow-engine.test.ts +48 -0
- package/src/__tests__/flowContext.test.ts +693 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowEngine.modelLoaders.test.ts +249 -0
- package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
- package/src/__tests__/flowRuntimeContext.test.ts +2 -1
- package/src/__tests__/flowSettings.open.test.tsx +123 -19
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/provider.test.tsx +0 -5
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +26 -7
- package/src/__tests__/runjsContextImplementations.test.ts +34 -3
- package/src/__tests__/runjsContextRuntime.test.ts +5 -3
- package/src/__tests__/runjsContributions.test.ts +89 -0
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsLocales.test.ts +4 -1
- package/src/__tests__/runjsPreprocessDefault.test.ts +72 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +3 -3
- package/src/components/__tests__/gridDragPlanner.test.ts +229 -1
- package/src/components/dnd/gridDragPlanner.ts +68 -2
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +31 -4
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +143 -32
- package/src/components/subModel/utils.ts +1 -1
- package/src/components/variables/VariableInput.tsx +12 -4
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/components/variables/__tests__/utils.test.ts +81 -3
- package/src/components/variables/utils.ts +67 -6
- package/src/data-source/index.ts +88 -110
- package/src/executor/FlowExecutor.ts +230 -28
- package/src/executor/__tests__/flowExecutor.test.ts +123 -0
- package/src/flow-schema-registry/fieldBinding.ts +171 -0
- package/src/flow-schema-registry/modelPatches.ts +260 -0
- package/src/flow-schema-registry/schemaInference.ts +210 -0
- package/src/flow-schema-registry/utils.ts +268 -0
- package/src/flowContext.ts +2989 -212
- package/src/flowEngine.ts +434 -23
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +58 -18
- package/src/index.ts +15 -1
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +9 -2
- package/src/locale/zh-CN.json +8 -1
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +768 -0
- package/src/models/__tests__/flowModel.clone.test.ts +416 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +112 -7
- package/src/provider.tsx +9 -7
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +11 -6
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/base.ts +715 -44
- package/src/runjs-context/contributions.ts +88 -0
- package/src/runjs-context/helpers.ts +11 -1
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +25 -9
- package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
- package/src/runjs-context/snippets/index.ts +75 -41
- package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +41 -24
- package/src/server.ts +11 -0
- package/src/types.ts +359 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +57 -2
- package/src/utils/__tests__/utils.test.ts +157 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/createEphemeralContext.ts +5 -4
- package/src/utils/dateVariable.ts +397 -0
- package/src/utils/exceptions.ts +11 -0
- package/src/utils/index.ts +38 -3
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +188 -17
- package/src/utils/schema-utils.ts +109 -1
- package/src/views/FlowView.tsx +11 -1
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +44 -16
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +34 -5
- package/src/views/useDrawer.tsx +33 -4
- package/src/views/usePage.tsx +63 -8
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import React, { useCallback, useRef, useMemo, useState, useEffect } from 'react';
|
|
11
|
-
import { Button, Cascader, Tooltip } from 'antd';
|
|
11
|
+
import { Button, Cascader, Input, Tooltip, theme } from 'antd';
|
|
12
12
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
|
13
13
|
import { cx, css } from '@emotion/css';
|
|
14
14
|
import type { ContextSelectorItem, FlowContextSelectorProps } from './variables/types';
|
|
15
15
|
import {
|
|
16
16
|
buildContextSelectorItems,
|
|
17
|
+
filterLoadedContextSelectorItems,
|
|
17
18
|
formatPathToValue,
|
|
18
19
|
parseValueToPath,
|
|
19
20
|
preloadContextSelectorPath,
|
|
@@ -34,6 +35,52 @@ const cascaderPopupAutoHeightClassName = css`
|
|
|
34
35
|
}
|
|
35
36
|
`;
|
|
36
37
|
|
|
38
|
+
type SelectedPathInfo = {
|
|
39
|
+
text: string;
|
|
40
|
+
meta?: ContextSelectorItem['meta'];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const normalizePath = (path: unknown): string[] | undefined => {
|
|
44
|
+
if (!Array.isArray(path)) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return path.map((segment) => String(segment));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getSelectedPathInfo = (path: string[] | undefined, options: ContextSelectorItem[]): SelectedPathInfo => {
|
|
52
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
53
|
+
return { text: '', meta: undefined };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const labels: string[] = [];
|
|
57
|
+
let currentOptions = options;
|
|
58
|
+
let selectedMeta: ContextSelectorItem['meta'] | undefined;
|
|
59
|
+
|
|
60
|
+
for (const segment of path) {
|
|
61
|
+
const matchedOption = currentOptions.find((item) => String(item.value) === String(segment));
|
|
62
|
+
if (!matchedOption) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const label =
|
|
67
|
+
typeof matchedOption.meta?.title === 'string'
|
|
68
|
+
? matchedOption.meta.title
|
|
69
|
+
: typeof matchedOption.label === 'string'
|
|
70
|
+
? matchedOption.label
|
|
71
|
+
: String(matchedOption.value);
|
|
72
|
+
|
|
73
|
+
labels.push(label);
|
|
74
|
+
selectedMeta = matchedOption.meta;
|
|
75
|
+
currentOptions = Array.isArray(matchedOption.children) ? matchedOption.children : [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
text: labels.join(' / '),
|
|
80
|
+
meta: selectedMeta,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
37
84
|
const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
38
85
|
value,
|
|
39
86
|
onChange,
|
|
@@ -47,13 +94,15 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
47
94
|
ignoreFieldNames,
|
|
48
95
|
...cascaderProps
|
|
49
96
|
}) => {
|
|
97
|
+
const { token } = theme.useToken();
|
|
98
|
+
|
|
50
99
|
// 记录最后点击的路径,用于双击检测
|
|
51
100
|
const lastSelectedRef = useRef<{ path: string; time: number } | null>(null);
|
|
52
101
|
|
|
53
102
|
const { resolvedMetaTree, loading } = useResolvedMetaTree(metaTree);
|
|
54
103
|
|
|
55
104
|
// 获取引擎上下文中的翻译函数,若不可用则回退为原文
|
|
56
|
-
const flowCtx = useFlowContext
|
|
105
|
+
const flowCtx = useFlowContext();
|
|
57
106
|
|
|
58
107
|
const translateOptions = useCallback(
|
|
59
108
|
(items: ContextSelectorItem[] | undefined): ContextSelectorItem[] => {
|
|
@@ -63,7 +112,9 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
63
112
|
const meta = o.meta;
|
|
64
113
|
const disabled = meta ? !!(typeof meta.disabled === 'function' ? meta.disabled() : meta.disabled) : false;
|
|
65
114
|
const disabledReason = meta
|
|
66
|
-
?
|
|
115
|
+
? typeof meta.disabledReason === 'function'
|
|
116
|
+
? meta.disabledReason()
|
|
117
|
+
: meta.disabledReason
|
|
67
118
|
: undefined;
|
|
68
119
|
|
|
69
120
|
// 文本国际化:仅当 label 为字符串时进行翻译
|
|
@@ -98,7 +149,11 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
98
149
|
|
|
99
150
|
// 用于强制重新渲染的状态
|
|
100
151
|
const [updateFlag, setUpdateFlag] = useState(0);
|
|
152
|
+
const [searchText, setSearchText] = useState('');
|
|
153
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
154
|
+
const inlineFocusByPointerRef = useRef(false);
|
|
101
155
|
const triggerUpdate = useCallback(() => setUpdateFlag((prev) => prev + 1), []);
|
|
156
|
+
const isSearchEnabled = showSearch || children === null;
|
|
102
157
|
|
|
103
158
|
// 构建选项
|
|
104
159
|
// 注意:rc-cascader 内部对 options 做了基于引用的缓存(useEntities)。
|
|
@@ -106,13 +161,22 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
106
161
|
// 触发 rc-cascader 重新构建 pathKeyEntities,避免二级节点未被索引导致的报错。
|
|
107
162
|
const options = useMemo(() => {
|
|
108
163
|
if (!resolvedMetaTree) return [];
|
|
164
|
+
const refreshSeq = updateFlag;
|
|
109
165
|
const base = buildContextSelectorItems(resolvedMetaTree);
|
|
110
|
-
|
|
166
|
+
const filtered = translateOptions(base).filter((item) => {
|
|
111
167
|
if (!ignoreFieldNames || ignoreFieldNames.length === 0) return true;
|
|
112
168
|
return !ignoreFieldNames.includes(item.meta?.name || '');
|
|
113
169
|
});
|
|
170
|
+
return refreshSeq >= 0 ? filtered : [];
|
|
114
171
|
}, [resolvedMetaTree, updateFlag, translateOptions, ignoreFieldNames]);
|
|
115
172
|
|
|
173
|
+
const displayOptions = useMemo(() => {
|
|
174
|
+
if (!isSearchEnabled || !searchText.trim()) {
|
|
175
|
+
return options;
|
|
176
|
+
}
|
|
177
|
+
return filterLoadedContextSelectorItems(options, searchText);
|
|
178
|
+
}, [isSearchEnabled, options, searchText]);
|
|
179
|
+
|
|
116
180
|
// 内部展开路径:在 onlyLeafSelectable=true 时,点击父节点不会触发 onChange,
|
|
117
181
|
// 但会触发 loadData。我们在此记录路径以在懒加载后保持展开。
|
|
118
182
|
const [tempSelectedPath, setTempSelectedPath] = useState<string[]>([]);
|
|
@@ -158,23 +222,36 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
158
222
|
triggerUpdate();
|
|
159
223
|
}
|
|
160
224
|
},
|
|
161
|
-
[triggerUpdate],
|
|
225
|
+
[triggerUpdate, translateOptions],
|
|
162
226
|
);
|
|
163
227
|
|
|
164
228
|
const currentPath = useMemo(() => {
|
|
165
|
-
return customParseValueToPath(value);
|
|
229
|
+
return normalizePath(customParseValueToPath(value));
|
|
166
230
|
}, [value, customParseValueToPath]);
|
|
167
231
|
|
|
168
232
|
// 当 metaTree 为子层(如 getPropertyMetaTree('{{ ctx.collection }}') 返回的是 collection 的子节点)
|
|
169
233
|
// 而 value path 仍包含根键(如 ['collection', 'field'])时,自动丢弃不存在的首段,确保级联能正确对齐。
|
|
170
234
|
const effectivePath = useMemo(() => {
|
|
171
235
|
if (!currentPath || currentPath.length === 0) return currentPath;
|
|
236
|
+
|
|
237
|
+
if (options.length === 0) {
|
|
238
|
+
return currentPath;
|
|
239
|
+
}
|
|
240
|
+
|
|
172
241
|
const topValues = new Set(options.map((o) => String(o.value)));
|
|
173
242
|
const needTrim = !topValues.has(String(currentPath[0]));
|
|
174
243
|
const fixed = needTrim ? currentPath.slice(1) : currentPath;
|
|
175
244
|
return fixed;
|
|
176
245
|
}, [currentPath, options]);
|
|
177
246
|
|
|
247
|
+
const cascaderValue = useMemo(() => {
|
|
248
|
+
if (tempSelectedPath.length > 0) {
|
|
249
|
+
return tempSelectedPath;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return Array.isArray(effectivePath) ? effectivePath : undefined;
|
|
253
|
+
}, [effectivePath, tempSelectedPath]);
|
|
254
|
+
|
|
178
255
|
// 预加载:当存在有效路径时,按路径逐级加载 children,保证默认展开和选中路径可用
|
|
179
256
|
const pathToPreload = useMemo(() => {
|
|
180
257
|
const finalPath = effectivePath && effectivePath.length > 0 ? effectivePath : tempSelectedPath;
|
|
@@ -251,21 +328,140 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
251
328
|
return cx(cascaderPopupAutoHeightClassName, cascaderProps.popupClassName);
|
|
252
329
|
}, [cascaderProps.popupClassName]);
|
|
253
330
|
|
|
331
|
+
const cascaderSearchInputClassName = useMemo(() => {
|
|
332
|
+
return css`
|
|
333
|
+
padding: 8px;
|
|
334
|
+
border-bottom: 1px solid ${token.colorSplit};
|
|
335
|
+
`;
|
|
336
|
+
}, [token.colorSplit]);
|
|
337
|
+
|
|
338
|
+
const {
|
|
339
|
+
onDropdownVisibleChange: cascaderOnDropdownVisibleChange,
|
|
340
|
+
dropdownRender: cascaderDropdownRender,
|
|
341
|
+
...restCascaderProps
|
|
342
|
+
} = cascaderProps;
|
|
343
|
+
|
|
344
|
+
const selectedPathInfo = useMemo(() => getSelectedPathInfo(effectivePath, options), [effectivePath, options]);
|
|
345
|
+
|
|
346
|
+
const mergedOpen = open !== undefined ? open : children === null ? dropdownOpen : undefined;
|
|
347
|
+
|
|
348
|
+
const isDropdownVisible = !!mergedOpen;
|
|
349
|
+
|
|
350
|
+
const handleDropdownVisibleChange = useCallback(
|
|
351
|
+
(visible: boolean) => {
|
|
352
|
+
if (open === undefined) {
|
|
353
|
+
setDropdownOpen(visible);
|
|
354
|
+
}
|
|
355
|
+
if (!visible) {
|
|
356
|
+
setSearchText('');
|
|
357
|
+
}
|
|
358
|
+
cascaderOnDropdownVisibleChange?.(visible);
|
|
359
|
+
},
|
|
360
|
+
[cascaderOnDropdownVisibleChange, open],
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const renderDropdown = useCallback(
|
|
364
|
+
(menu: React.ReactElement) => {
|
|
365
|
+
const cascaderMenuNode = cascaderDropdownRender ? cascaderDropdownRender(menu) : menu;
|
|
366
|
+
const cascaderMenu = React.isValidElement(cascaderMenuNode) ? cascaderMenuNode : <>{cascaderMenuNode}</>;
|
|
367
|
+
if (!isSearchEnabled || children === null) {
|
|
368
|
+
return cascaderMenu;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<>
|
|
373
|
+
<div className={cascaderSearchInputClassName}>
|
|
374
|
+
<Input
|
|
375
|
+
allowClear
|
|
376
|
+
size="small"
|
|
377
|
+
value={searchText}
|
|
378
|
+
placeholder={flowCtx.t('Search')}
|
|
379
|
+
onChange={(e) => setSearchText(e.target.value)}
|
|
380
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
381
|
+
/>
|
|
382
|
+
</div>
|
|
383
|
+
{cascaderMenu}
|
|
384
|
+
</>
|
|
385
|
+
);
|
|
386
|
+
},
|
|
387
|
+
[cascaderDropdownRender, cascaderSearchInputClassName, children, flowCtx, isSearchEnabled, searchText],
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const inlinePlaceholder =
|
|
391
|
+
typeof restCascaderProps.placeholder === 'string' ? restCascaderProps.placeholder : flowCtx.t('Search');
|
|
392
|
+
const hasSelectedPath = Array.isArray(effectivePath) && effectivePath.length > 0;
|
|
393
|
+
|
|
394
|
+
const handleInlineInputFocus = useCallback(() => {
|
|
395
|
+
if (open === undefined && !inlineFocusByPointerRef.current) {
|
|
396
|
+
setDropdownOpen(true);
|
|
397
|
+
}
|
|
398
|
+
}, [open]);
|
|
399
|
+
|
|
400
|
+
const markInlineFocusByPointer = useCallback(() => {
|
|
401
|
+
inlineFocusByPointerRef.current = true;
|
|
402
|
+
}, []);
|
|
403
|
+
|
|
404
|
+
const resetInlineFocusByPointer = useCallback(() => {
|
|
405
|
+
inlineFocusByPointerRef.current = false;
|
|
406
|
+
}, []);
|
|
407
|
+
|
|
408
|
+
const handleInlineInputChange = useCallback(
|
|
409
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
410
|
+
const nextValue = event.target.value;
|
|
411
|
+
|
|
412
|
+
// 下拉关闭态下点击清空:应清空真实已选值,而不是仅清空搜索词。
|
|
413
|
+
if (!isDropdownVisible && nextValue === '' && hasSelectedPath) {
|
|
414
|
+
setTempSelectedPath([]);
|
|
415
|
+
// 清空语义:传空 meta,确保上层(如 VariableInput)进入 clear 分支。
|
|
416
|
+
onChange?.('', undefined);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (open === undefined && !isDropdownVisible) {
|
|
421
|
+
setDropdownOpen(true);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
setSearchText(nextValue);
|
|
425
|
+
},
|
|
426
|
+
[hasSelectedPath, isDropdownVisible, onChange, open],
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const inlinePathText = Array.isArray(effectivePath) ? effectivePath.join(' / ') : '';
|
|
430
|
+
const inlineInputValue = isDropdownVisible ? searchText : selectedPathInfo.text || inlinePathText;
|
|
431
|
+
|
|
254
432
|
return (
|
|
255
433
|
<Cascader
|
|
256
|
-
{...
|
|
257
|
-
options={
|
|
258
|
-
value={
|
|
434
|
+
{...restCascaderProps}
|
|
435
|
+
options={displayOptions}
|
|
436
|
+
value={cascaderValue}
|
|
259
437
|
onChange={handleChange}
|
|
260
438
|
loadData={handleLoadData}
|
|
261
439
|
loading={loading}
|
|
262
440
|
changeOnSelect={!onlyLeafSelectable}
|
|
263
441
|
expandTrigger="click"
|
|
264
|
-
open={
|
|
265
|
-
showSearch={
|
|
442
|
+
open={mergedOpen}
|
|
443
|
+
showSearch={false}
|
|
266
444
|
popupClassName={mergedPopupClassName}
|
|
445
|
+
dropdownRender={renderDropdown}
|
|
446
|
+
onDropdownVisibleChange={handleDropdownVisibleChange}
|
|
267
447
|
>
|
|
268
|
-
{children === null ?
|
|
448
|
+
{children === null ? (
|
|
449
|
+
<Input
|
|
450
|
+
allowClear
|
|
451
|
+
value={inlineInputValue}
|
|
452
|
+
placeholder={inlinePlaceholder}
|
|
453
|
+
onMouseDown={markInlineFocusByPointer}
|
|
454
|
+
onMouseUp={resetInlineFocusByPointer}
|
|
455
|
+
onMouseLeave={resetInlineFocusByPointer}
|
|
456
|
+
onFocus={handleInlineInputFocus}
|
|
457
|
+
onBlur={resetInlineFocusByPointer}
|
|
458
|
+
onChange={handleInlineInputChange}
|
|
459
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
460
|
+
disabled={restCascaderProps.disabled}
|
|
461
|
+
/>
|
|
462
|
+
) : (
|
|
463
|
+
children || defaultChildren
|
|
464
|
+
)}
|
|
269
465
|
</Cascader>
|
|
270
466
|
);
|
|
271
467
|
};
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { ConfigProvider } from 'antd';
|
|
11
|
-
import { Popup } from 'antd-mobile';
|
|
12
11
|
import React, { FC, ReactNode, useMemo } from 'react';
|
|
13
|
-
import { CloseOutline } from 'antd-mobile-icons';
|
|
14
12
|
import { useMobileActionDrawerStyle } from './MobilePopup.style';
|
|
15
13
|
import { useTranslation } from 'react-i18next';
|
|
14
|
+
import { lazy } from '../lazy-helper';
|
|
15
|
+
|
|
16
|
+
const { Popup } = lazy(() => import('antd-mobile'), 'Popup');
|
|
17
|
+
const { CloseOutline } = lazy(() => import('antd-mobile-icons'), 'CloseOutline');
|
|
16
18
|
|
|
17
19
|
interface MobilePopupProps {
|
|
18
20
|
title?: string;
|
|
@@ -114,7 +114,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
const engine = new FlowEngine();
|
|
117
|
-
engine.flowSettings.forceEnable();
|
|
117
|
+
await engine.flowSettings.forceEnable();
|
|
118
118
|
engine.registerModels({ BrokenModel });
|
|
119
119
|
const model = engine.createModel({ use: 'BrokenModel', uid: 'broken-top-2' }) as BrokenModel;
|
|
120
120
|
// satisfy FlowsFloatContextMenu styles
|
|
@@ -154,7 +154,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
const engine = new FlowEngine();
|
|
157
|
-
engine.flowSettings.forceEnable();
|
|
157
|
+
await engine.flowSettings.forceEnable();
|
|
158
158
|
engine.registerModels({ ParentModel, BrokenChild });
|
|
159
159
|
const parent = engine.createModel({ use: 'ParentModel', uid: 'parent-3' }) as ParentModel;
|
|
160
160
|
const child = engine.createModel({ use: 'BrokenChild', uid: 'child-3' }) as BrokenChild;
|
|
@@ -200,7 +200,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
const engine = new FlowEngine();
|
|
203
|
-
engine.flowSettings.forceEnable();
|
|
203
|
+
await engine.flowSettings.forceEnable();
|
|
204
204
|
engine.registerModels({ ParentModel, RenderFnChild });
|
|
205
205
|
const parent = engine.createModel({ use: 'ParentModel', uid: 'parent-4' }) as ParentModel;
|
|
206
206
|
const child = engine.createModel({ use: 'RenderFnChild', uid: 'cell-4' }) as RenderFnChild;
|
|
@@ -15,13 +15,106 @@ import {
|
|
|
15
15
|
getSlotKey,
|
|
16
16
|
resolveDropIntent,
|
|
17
17
|
Point,
|
|
18
|
+
buildLayoutSnapshot,
|
|
18
19
|
} from '../dnd/gridDragPlanner';
|
|
19
20
|
|
|
20
21
|
const rect = { top: 0, left: 0, width: 100, height: 100 };
|
|
21
22
|
|
|
22
|
-
const createLayout = (
|
|
23
|
+
const createLayout = (
|
|
24
|
+
rows: Record<string, string[][]>,
|
|
25
|
+
sizes: Record<string, number[]>,
|
|
26
|
+
rowOrder?: string[],
|
|
27
|
+
): GridLayoutData => ({
|
|
23
28
|
rows,
|
|
24
29
|
sizes,
|
|
30
|
+
rowOrder,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const createDomRect = ({ top, left, width, height }: { top: number; left: number; width: number; height: number }) => {
|
|
34
|
+
return {
|
|
35
|
+
top,
|
|
36
|
+
left,
|
|
37
|
+
width,
|
|
38
|
+
height,
|
|
39
|
+
right: left + width,
|
|
40
|
+
bottom: top + height,
|
|
41
|
+
x: left,
|
|
42
|
+
y: top,
|
|
43
|
+
toJSON: () => ({}),
|
|
44
|
+
} as DOMRect;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const mockRect = (
|
|
48
|
+
element: Element,
|
|
49
|
+
rect: {
|
|
50
|
+
top: number;
|
|
51
|
+
left: number;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
},
|
|
55
|
+
) => {
|
|
56
|
+
Object.defineProperty(element, 'getBoundingClientRect', {
|
|
57
|
+
configurable: true,
|
|
58
|
+
value: () => createDomRect(rect),
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe('buildLayoutSnapshot', () => {
|
|
63
|
+
it('should ignore nested grid columns/items even when rowId is duplicated', () => {
|
|
64
|
+
const container = document.createElement('div');
|
|
65
|
+
const row = document.createElement('div');
|
|
66
|
+
row.setAttribute('data-grid-row-id', 'row-1');
|
|
67
|
+
container.appendChild(row);
|
|
68
|
+
|
|
69
|
+
const column = document.createElement('div');
|
|
70
|
+
column.setAttribute('data-grid-column-row-id', 'row-1');
|
|
71
|
+
column.setAttribute('data-grid-column-index', '0');
|
|
72
|
+
row.appendChild(column);
|
|
73
|
+
|
|
74
|
+
const item = document.createElement('div');
|
|
75
|
+
item.setAttribute('data-grid-item-row-id', 'row-1');
|
|
76
|
+
item.setAttribute('data-grid-column-index', '0');
|
|
77
|
+
item.setAttribute('data-grid-item-index', '0');
|
|
78
|
+
column.appendChild(item);
|
|
79
|
+
|
|
80
|
+
// 在外层 item 内构建一个嵌套 grid,并复用相同 rowId/columnIndex
|
|
81
|
+
const nestedRow = document.createElement('div');
|
|
82
|
+
nestedRow.setAttribute('data-grid-row-id', 'row-1');
|
|
83
|
+
item.appendChild(nestedRow);
|
|
84
|
+
|
|
85
|
+
const nestedColumn = document.createElement('div');
|
|
86
|
+
nestedColumn.setAttribute('data-grid-column-row-id', 'row-1');
|
|
87
|
+
nestedColumn.setAttribute('data-grid-column-index', '0');
|
|
88
|
+
nestedRow.appendChild(nestedColumn);
|
|
89
|
+
|
|
90
|
+
const nestedItem = document.createElement('div');
|
|
91
|
+
nestedItem.setAttribute('data-grid-item-row-id', 'row-1');
|
|
92
|
+
nestedItem.setAttribute('data-grid-column-index', '0');
|
|
93
|
+
nestedItem.setAttribute('data-grid-item-index', '0');
|
|
94
|
+
nestedColumn.appendChild(nestedItem);
|
|
95
|
+
|
|
96
|
+
mockRect(container, { top: 0, left: 0, width: 600, height: 600 });
|
|
97
|
+
mockRect(row, { top: 10, left: 10, width: 320, height: 120 });
|
|
98
|
+
mockRect(column, { top: 10, left: 10, width: 320, height: 120 });
|
|
99
|
+
mockRect(item, { top: 20, left: 20, width: 300, height: 80 });
|
|
100
|
+
|
|
101
|
+
// 嵌套 grid 给一个明显偏离的位置,用于判断是否被错误命中
|
|
102
|
+
mockRect(nestedRow, { top: 360, left: 360, width: 200, height: 120 });
|
|
103
|
+
mockRect(nestedColumn, { top: 360, left: 360, width: 200, height: 120 });
|
|
104
|
+
mockRect(nestedItem, { top: 370, left: 370, width: 180, height: 90 });
|
|
105
|
+
|
|
106
|
+
const snapshot = buildLayoutSnapshot({ container });
|
|
107
|
+
const columnEdgeSlots = snapshot.slots.filter((slot) => slot.type === 'column-edge');
|
|
108
|
+
const columnSlots = snapshot.slots.filter((slot) => slot.type === 'column');
|
|
109
|
+
|
|
110
|
+
// 外层单行单列单项应只有 6 个 slot:上/下 row-gap + 左/右 column-edge + before/after column
|
|
111
|
+
expect(snapshot.slots).toHaveLength(6);
|
|
112
|
+
expect(columnEdgeSlots).toHaveLength(2);
|
|
113
|
+
expect(columnSlots).toHaveLength(2);
|
|
114
|
+
|
|
115
|
+
// 不应混入嵌套 grid(其 top >= 360)
|
|
116
|
+
expect(snapshot.slots.every((slot) => slot.rect.top < 300)).toBe(true);
|
|
117
|
+
});
|
|
25
118
|
});
|
|
26
119
|
|
|
27
120
|
describe('getSlotKey', () => {
|
|
@@ -275,6 +368,7 @@ describe('simulateLayoutForSlot', () => {
|
|
|
275
368
|
rowA: [24],
|
|
276
369
|
rowB: [24],
|
|
277
370
|
},
|
|
371
|
+
['rowA', 'rowB'],
|
|
278
372
|
);
|
|
279
373
|
|
|
280
374
|
const slot: LayoutSlot = {
|
|
@@ -315,6 +409,33 @@ describe('simulateLayoutForSlot', () => {
|
|
|
315
409
|
expect(result.sizes['row-new']).toEqual([24]);
|
|
316
410
|
});
|
|
317
411
|
|
|
412
|
+
it('removes empty source row when moving item into empty container slot', () => {
|
|
413
|
+
const layout = createLayout(
|
|
414
|
+
{
|
|
415
|
+
rowA: [['block-x']],
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
rowA: [24],
|
|
419
|
+
},
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const slot: LayoutSlot = {
|
|
423
|
+
type: 'empty-row',
|
|
424
|
+
rect,
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const result = simulateLayoutForSlot({
|
|
428
|
+
slot,
|
|
429
|
+
sourceUid: 'block-x',
|
|
430
|
+
layout,
|
|
431
|
+
generateRowId: () => 'row-new',
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
expect(result.rows['row-new']).toEqual([['block-x']]);
|
|
435
|
+
expect(result.rows.rowA).toBeUndefined();
|
|
436
|
+
expect(result.sizes.rowA).toBeUndefined();
|
|
437
|
+
});
|
|
438
|
+
|
|
318
439
|
it('handles column slot with after position', () => {
|
|
319
440
|
const layout = createLayout(
|
|
320
441
|
{
|
|
@@ -373,6 +494,7 @@ describe('simulateLayoutForSlot', () => {
|
|
|
373
494
|
rowA: [24],
|
|
374
495
|
rowB: [24],
|
|
375
496
|
},
|
|
497
|
+
['rowA', 'rowB'],
|
|
376
498
|
);
|
|
377
499
|
|
|
378
500
|
const slot: LayoutSlot = {
|
|
@@ -392,6 +514,112 @@ describe('simulateLayoutForSlot', () => {
|
|
|
392
514
|
expect(Object.keys(result.rows)).toEqual(['rowA', 'row-inserted', 'rowB']);
|
|
393
515
|
});
|
|
394
516
|
|
|
517
|
+
it('inserts row into rowOrder when dropping below target row', () => {
|
|
518
|
+
const layout = createLayout(
|
|
519
|
+
{
|
|
520
|
+
rowA: [['a']],
|
|
521
|
+
rowB: [['b']],
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
rowA: [24],
|
|
525
|
+
rowB: [24],
|
|
526
|
+
},
|
|
527
|
+
['rowA', 'rowB'],
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const slot: LayoutSlot = {
|
|
531
|
+
type: 'row-gap',
|
|
532
|
+
targetRowId: 'rowA',
|
|
533
|
+
position: 'below',
|
|
534
|
+
rect,
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const result = simulateLayoutForSlot({
|
|
538
|
+
slot,
|
|
539
|
+
sourceUid: 'c',
|
|
540
|
+
layout,
|
|
541
|
+
generateRowId: () => 'row-new',
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
expect(result.rowOrder).toEqual(['rowA', 'row-new', 'rowB']);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('maintains rowOrder and inserts new row before target when provided', () => {
|
|
548
|
+
const layout = createLayout(
|
|
549
|
+
{
|
|
550
|
+
rowA: [['a']],
|
|
551
|
+
rowB: [['b']],
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
rowA: [24],
|
|
555
|
+
rowB: [24],
|
|
556
|
+
},
|
|
557
|
+
['rowA', 'rowB'],
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
const slot: LayoutSlot = {
|
|
561
|
+
type: 'row-gap',
|
|
562
|
+
targetRowId: 'rowB',
|
|
563
|
+
position: 'above',
|
|
564
|
+
rect,
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const result = simulateLayoutForSlot({
|
|
568
|
+
slot,
|
|
569
|
+
sourceUid: 'c',
|
|
570
|
+
layout,
|
|
571
|
+
generateRowId: () => 'row-new',
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
expect(result.rowOrder).toEqual(['rowA', 'row-new', 'rowB']);
|
|
575
|
+
expect(result.rows).toEqual({
|
|
576
|
+
rowA: [['a']],
|
|
577
|
+
'row-new': [['c']],
|
|
578
|
+
rowB: [['b']],
|
|
579
|
+
});
|
|
580
|
+
expect(result.sizes).toEqual({
|
|
581
|
+
rowA: [24],
|
|
582
|
+
'row-new': [24],
|
|
583
|
+
rowB: [24],
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('derives rowOrder from rows when missing and removes empty rows from order', () => {
|
|
588
|
+
const layout = createLayout(
|
|
589
|
+
{
|
|
590
|
+
row1: [['a']],
|
|
591
|
+
row2: [['b']],
|
|
592
|
+
row3: [['c']],
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
row1: [24],
|
|
596
|
+
row2: [24],
|
|
597
|
+
row3: [24],
|
|
598
|
+
},
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
const slot: LayoutSlot = {
|
|
602
|
+
type: 'column',
|
|
603
|
+
rowId: 'row1',
|
|
604
|
+
columnIndex: 0,
|
|
605
|
+
insertIndex: 0,
|
|
606
|
+
position: 'before',
|
|
607
|
+
rect,
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const result = simulateLayoutForSlot({ slot, sourceUid: 'b', layout });
|
|
611
|
+
|
|
612
|
+
expect(result.rowOrder).toEqual(['row1', 'row3']);
|
|
613
|
+
expect(result.rows).toEqual({
|
|
614
|
+
row1: [['b', 'a']],
|
|
615
|
+
row3: [['c']],
|
|
616
|
+
});
|
|
617
|
+
expect(result.sizes).toEqual({
|
|
618
|
+
row1: [24],
|
|
619
|
+
row3: [24],
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
|
|
395
623
|
it('handles empty-column slot by replacing empty column', () => {
|
|
396
624
|
const layout = createLayout(
|
|
397
625
|
{
|