@nocobase/flow-engine 2.1.0-alpha.4 → 2.1.0-alpha.45
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/FlowContextProvider.d.ts +5 -1
- package/lib/FlowContextProvider.js +9 -2
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/FormItem.d.ts +6 -0
- package/lib/components/FormItem.js +11 -3
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +613 -21
- package/lib/components/dnd/index.d.ts +31 -2
- package/lib/components/dnd/index.js +244 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -11
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/LazyDropdown.js +293 -52
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +9 -3
- package/lib/components/variables/VariableHybridInput.d.ts +27 -0
- package/lib/components/variables/VariableHybridInput.js +499 -0
- package/lib/components/variables/index.d.ts +2 -0
- package/lib/components/variables/index.js +3 -0
- package/lib/data-source/index.d.ts +84 -0
- package/lib/data-source/index.js +259 -5
- package/lib/executor/FlowExecutor.js +32 -9
- package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
- package/lib/flow-registry/index.d.ts +1 -0
- package/lib/flow-registry/index.js +3 -1
- package/lib/flowContext.d.ts +3 -0
- package/lib/flowContext.js +46 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +392 -18
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/index.d.ts +2 -0
- package/lib/index.js +7 -0
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- 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 +13 -10
- package/lib/models/flowModel.js +81 -21
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +50 -2
- package/lib/types.js +1 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +3 -2
- package/lib/utils/index.js +7 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
- package/lib/utils/parsePathnameToViewParams.js +29 -5
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/FlowView.js +11 -1
- package/lib/views/PageComponent.js +8 -6
- package/lib/views/ViewNavigation.d.ts +12 -2
- package/lib/views/ViewNavigation.js +28 -9
- package/lib/views/createViewMeta.js +114 -50
- package/lib/views/inheritLayoutContext.d.ts +10 -0
- package/lib/views/inheritLayoutContext.js +50 -0
- 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 +22 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +22 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +304 -144
- package/package.json +6 -5
- package/src/FlowContextProvider.tsx +9 -1
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/createViewMeta.popup.test.ts +115 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +82 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/FormItem.tsx +7 -1
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/FormItem.test.tsx +25 -0
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +558 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +758 -19
- package/src/components/dnd/index.tsx +305 -28
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +99 -11
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +194 -5
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/LazyDropdown.tsx +332 -56
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +522 -37
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +7 -1
- package/src/components/variables/VariableHybridInput.tsx +531 -0
- package/src/components/variables/index.ts +2 -0
- package/src/data-source/__tests__/collection.test.ts +41 -2
- package/src/data-source/__tests__/index.test.ts +68 -1
- package/src/data-source/index.ts +322 -6
- package/src/executor/FlowExecutor.ts +35 -10
- package/src/executor/__tests__/flowExecutor.test.ts +85 -0
- package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
- package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
- package/src/flow-registry/index.ts +1 -0
- package/src/flowContext.ts +50 -3
- package/src/flowEngine.ts +449 -14
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/index.ts +2 -0
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- 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 +214 -0
- package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
- package/src/models/__tests__/flowModel.test.ts +80 -37
- package/src/models/flowModel.tsx +122 -36
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +28 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +5 -1
- package/src/utils/parsePathnameToViewParams.ts +47 -7
- package/src/utils/randomId.ts +48 -0
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +22 -2
- package/src/views/PageComponent.tsx +7 -4
- package/src/views/ViewNavigation.ts +46 -9
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/ViewNavigation.test.ts +52 -0
- package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/createViewMeta.ts +106 -34
- package/src/views/inheritLayoutContext.ts +26 -0
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +27 -3
- package/src/views/useDrawer.tsx +27 -3
- package/src/views/usePage.tsx +367 -179
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { DragOutlined } from '@ant-design/icons';
|
|
11
|
+
import type { Modifier } from '@dnd-kit/core';
|
|
11
12
|
import { DndContext, DndContextProps, DragOverlay, useDraggable, useDroppable } from '@dnd-kit/core';
|
|
12
|
-
import
|
|
13
|
+
import type { Transform } from '@dnd-kit/utilities';
|
|
14
|
+
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
|
13
15
|
import { createPortal } from 'react-dom';
|
|
14
16
|
import { FlowModel } from '../../models';
|
|
15
17
|
import { useFlowEngine } from '../../provider';
|
|
@@ -19,18 +21,229 @@ export * from './findModelUidPosition';
|
|
|
19
21
|
export * from './gridDragPlanner';
|
|
20
22
|
|
|
21
23
|
export const EMPTY_COLUMN_UID = 'EMPTY_COLUMN';
|
|
24
|
+
export const TOOLBAR_DRAG_ACTIVITY_EVENT = 'nb-toolbar-drag-activity';
|
|
25
|
+
const TOOLBAR_DRAG_ANCHOR_EVENT = 'nb-toolbar-drag-anchor';
|
|
26
|
+
const MENU_SUBMENU_POPUP_SELECTOR = '.ant-menu-submenu-popup';
|
|
27
|
+
|
|
28
|
+
type ToolbarDragAnchorPoint = {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ToolbarDragAnchorDetail = {
|
|
34
|
+
modelUid: string;
|
|
35
|
+
point: ToolbarDragAnchorPoint | null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const resolveOverlayAnchorTransform = ({
|
|
39
|
+
activeId,
|
|
40
|
+
active,
|
|
41
|
+
transform,
|
|
42
|
+
activeNodeRect,
|
|
43
|
+
dragAnchorPoint,
|
|
44
|
+
}: {
|
|
45
|
+
activeId: string | null;
|
|
46
|
+
active: { id: string | number } | null | undefined;
|
|
47
|
+
transform: Transform;
|
|
48
|
+
activeNodeRect: { top: number; left: number } | null;
|
|
49
|
+
dragAnchorPoint: ToolbarDragAnchorPoint | null;
|
|
50
|
+
}): Transform => {
|
|
51
|
+
if (!activeId || active?.id !== activeId || !dragAnchorPoint || !activeNodeRect) {
|
|
52
|
+
return transform;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...transform,
|
|
57
|
+
x: transform.x + dragAnchorPoint.x - activeNodeRect.left,
|
|
58
|
+
y: transform.y + dragAnchorPoint.y - activeNodeRect.top,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resolveDraggableHostNode = (activatorNode: HTMLElement | null) => {
|
|
63
|
+
const ownerDocument = activatorNode?.ownerDocument;
|
|
64
|
+
const floatToolbarContainer = activatorNode?.closest<HTMLElement>('.nb-toolbar-container[data-model-uid]');
|
|
65
|
+
const toolbarModelUid = floatToolbarContainer?.getAttribute('data-model-uid');
|
|
66
|
+
|
|
67
|
+
if (!ownerDocument || !toolbarModelUid) {
|
|
68
|
+
return activatorNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const matchedHosts = Array.from(
|
|
72
|
+
ownerDocument.querySelectorAll<HTMLElement>(
|
|
73
|
+
`[data-has-float-menu="true"][data-float-menu-model-uid="${toolbarModelUid}"]`,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
const popupRoot = floatToolbarContainer?.closest<HTMLElement>(MENU_SUBMENU_POPUP_SELECTOR);
|
|
77
|
+
|
|
78
|
+
if (popupRoot) {
|
|
79
|
+
return (
|
|
80
|
+
matchedHosts.find((hostNode) => hostNode.closest(MENU_SUBMENU_POPUP_SELECTOR) === popupRoot) || activatorNode
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
matchedHosts.find((hostNode) => !hostNode.closest(MENU_SUBMENU_POPUP_SELECTOR)) || matchedHosts[0] || activatorNode
|
|
86
|
+
);
|
|
87
|
+
};
|
|
22
88
|
|
|
23
89
|
// 可拖拽图标组件
|
|
24
|
-
export const DragHandler: FC<{ model: FlowModel; children
|
|
90
|
+
export const DragHandler: FC<{ model: FlowModel; children?: React.ReactNode }> = ({
|
|
25
91
|
model,
|
|
26
92
|
children = <DragOutlined />,
|
|
27
93
|
}) => {
|
|
28
|
-
const { attributes, listeners, setNodeRef } = useDraggable({ id: model.uid });
|
|
94
|
+
const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef } = useDraggable({ id: model.uid });
|
|
95
|
+
const dragHandlerRef = useRef<HTMLSpanElement | null>(null);
|
|
96
|
+
const draggableNodeRef = useRef<HTMLElement | null>(null);
|
|
97
|
+
const pointerPressCleanupRef = useRef<(() => void) | null>(null);
|
|
98
|
+
const isDraggingRef = useRef(isDragging);
|
|
99
|
+
const isPointerPressActiveRef = useRef(false);
|
|
100
|
+
const isToolbarDragActiveRef = useRef(false);
|
|
101
|
+
const syncDraggableNodeRef = useCallback(
|
|
102
|
+
(activatorNode: HTMLElement | null) => {
|
|
103
|
+
const nextNode = resolveDraggableHostNode(activatorNode);
|
|
104
|
+
|
|
105
|
+
if (draggableNodeRef.current === nextNode) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
draggableNodeRef.current = nextNode;
|
|
110
|
+
setNodeRef(nextNode);
|
|
111
|
+
},
|
|
112
|
+
[setNodeRef],
|
|
113
|
+
);
|
|
114
|
+
const setDragHandlerNodeRef = useCallback(
|
|
115
|
+
(node: HTMLSpanElement | null) => {
|
|
116
|
+
dragHandlerRef.current = node;
|
|
117
|
+
setActivatorNodeRef(node);
|
|
118
|
+
syncDraggableNodeRef(node);
|
|
119
|
+
},
|
|
120
|
+
[setActivatorNodeRef, syncDraggableNodeRef],
|
|
121
|
+
);
|
|
122
|
+
const dispatchToolbarDragActivity = useCallback(
|
|
123
|
+
(active: boolean) => {
|
|
124
|
+
const ownerDocument = dragHandlerRef.current?.ownerDocument;
|
|
125
|
+
if (!ownerDocument) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ownerDocument.dispatchEvent(
|
|
130
|
+
new CustomEvent(TOOLBAR_DRAG_ACTIVITY_EVENT, {
|
|
131
|
+
detail: { active, modelUid: model.uid },
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
[model.uid],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const dispatchToolbarDragAnchor = useCallback(
|
|
139
|
+
(detail: Pick<ToolbarDragAnchorDetail, 'point'>) => {
|
|
140
|
+
const ownerDocument = dragHandlerRef.current?.ownerDocument;
|
|
141
|
+
if (!ownerDocument) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
ownerDocument.dispatchEvent(
|
|
146
|
+
new CustomEvent<ToolbarDragAnchorDetail>(TOOLBAR_DRAG_ANCHOR_EVENT, {
|
|
147
|
+
detail: { modelUid: model.uid, point: detail.point },
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
[model.uid],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const clearPointerPressListeners = useCallback(() => {
|
|
155
|
+
pointerPressCleanupRef.current?.();
|
|
156
|
+
pointerPressCleanupRef.current = null;
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const syncToolbarDragActivity = useCallback(() => {
|
|
160
|
+
const nextActive = isPointerPressActiveRef.current || isDraggingRef.current;
|
|
161
|
+
if (nextActive === isToolbarDragActiveRef.current) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
isToolbarDragActiveRef.current = nextActive;
|
|
166
|
+
dispatchToolbarDragActivity(nextActive);
|
|
167
|
+
}, [dispatchToolbarDragActivity]);
|
|
168
|
+
|
|
169
|
+
const handlePointerPressEnd = useCallback(() => {
|
|
170
|
+
isPointerPressActiveRef.current = false;
|
|
171
|
+
clearPointerPressListeners();
|
|
172
|
+
syncToolbarDragActivity();
|
|
173
|
+
}, [clearPointerPressListeners, syncToolbarDragActivity]);
|
|
174
|
+
|
|
175
|
+
const registerPointerPressListeners = useCallback(() => {
|
|
176
|
+
const ownerDocument = dragHandlerRef.current?.ownerDocument;
|
|
177
|
+
const ownerWindow = ownerDocument?.defaultView;
|
|
178
|
+
if (!ownerDocument) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
clearPointerPressListeners();
|
|
183
|
+
|
|
184
|
+
const handlePointerEnd = () => {
|
|
185
|
+
handlePointerPressEnd();
|
|
186
|
+
};
|
|
187
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
188
|
+
if (event.key === 'Escape') {
|
|
189
|
+
handlePointerPressEnd();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
ownerDocument.addEventListener('pointerup', handlePointerEnd, true);
|
|
194
|
+
ownerDocument.addEventListener('pointercancel', handlePointerEnd, true);
|
|
195
|
+
ownerDocument.addEventListener('keydown', handleKeyDown, true);
|
|
196
|
+
ownerWindow?.addEventListener('blur', handlePointerEnd);
|
|
197
|
+
|
|
198
|
+
pointerPressCleanupRef.current = () => {
|
|
199
|
+
ownerDocument.removeEventListener('pointerup', handlePointerEnd, true);
|
|
200
|
+
ownerDocument.removeEventListener('pointercancel', handlePointerEnd, true);
|
|
201
|
+
ownerDocument.removeEventListener('keydown', handleKeyDown, true);
|
|
202
|
+
ownerWindow?.removeEventListener('blur', handlePointerEnd);
|
|
203
|
+
};
|
|
204
|
+
}, [clearPointerPressListeners, handlePointerPressEnd]);
|
|
205
|
+
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
syncDraggableNodeRef(dragHandlerRef.current);
|
|
208
|
+
}, [syncDraggableNodeRef]);
|
|
209
|
+
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
isDraggingRef.current = isDragging;
|
|
212
|
+
syncToolbarDragActivity();
|
|
213
|
+
}, [isDragging, syncToolbarDragActivity]);
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
return () => {
|
|
217
|
+
if (isToolbarDragActiveRef.current) {
|
|
218
|
+
dispatchToolbarDragActivity(false);
|
|
219
|
+
}
|
|
220
|
+
isPointerPressActiveRef.current = false;
|
|
221
|
+
isDraggingRef.current = false;
|
|
222
|
+
isToolbarDragActiveRef.current = false;
|
|
223
|
+
clearPointerPressListeners();
|
|
224
|
+
};
|
|
225
|
+
}, [clearPointerPressListeners, dispatchToolbarDragActivity]);
|
|
226
|
+
|
|
29
227
|
return (
|
|
30
228
|
<span
|
|
31
|
-
ref={
|
|
229
|
+
ref={setDragHandlerNodeRef}
|
|
32
230
|
{...listeners}
|
|
33
231
|
{...attributes}
|
|
232
|
+
onPointerDownCapture={(event) => {
|
|
233
|
+
if (event.button !== 0) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
dispatchToolbarDragAnchor({
|
|
238
|
+
point: {
|
|
239
|
+
x: event.clientX,
|
|
240
|
+
y: event.clientY,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
isPointerPressActiveRef.current = true;
|
|
244
|
+
syncToolbarDragActivity();
|
|
245
|
+
registerPointerPressListeners();
|
|
246
|
+
}}
|
|
34
247
|
style={{
|
|
35
248
|
cursor: 'grab',
|
|
36
249
|
}}
|
|
@@ -70,23 +283,73 @@ export const Droppable: FC<{ model: FlowModel<any>; children: React.ReactNode }>
|
|
|
70
283
|
);
|
|
71
284
|
};
|
|
72
285
|
|
|
286
|
+
export interface DndProviderProps extends DndContextProps, PersistOptions {
|
|
287
|
+
/**
|
|
288
|
+
* Whether to render the built-in `DragOverlay` (the "Dragging" pill that
|
|
289
|
+
* follows the cursor). Defaults to `true` for backwards compatibility with
|
|
290
|
+
* existing flow-engine drag interactions. Set to `false` when the
|
|
291
|
+
* surrounding UI already gives clear visual feedback (e.g. a drag-sort
|
|
292
|
+
* table that highlights the drop position) and the floating pill would be
|
|
293
|
+
* redundant.
|
|
294
|
+
*/
|
|
295
|
+
showDragOverlay?: boolean;
|
|
296
|
+
}
|
|
297
|
+
|
|
73
298
|
// 提供一个封装了 DragOverlay 的 DndProvider 组件,继承 DndContext 的所有 props
|
|
74
|
-
export const DndProvider: FC<
|
|
299
|
+
export const DndProvider: FC<DndProviderProps> = ({
|
|
75
300
|
persist = true,
|
|
301
|
+
showDragOverlay = true,
|
|
76
302
|
children,
|
|
303
|
+
onDragStart,
|
|
77
304
|
onDragEnd,
|
|
305
|
+
onDragCancel,
|
|
78
306
|
...restProps
|
|
79
307
|
}) => {
|
|
80
308
|
const [activeId, setActiveId] = useState<string | null>(null);
|
|
309
|
+
const [dragAnchorPoint, setDragAnchorPoint] = useState<ToolbarDragAnchorDetail['point']>(null);
|
|
81
310
|
const flowEngine = useFlowEngine();
|
|
311
|
+
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
if (typeof document === 'undefined') {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const handleToolbarDragAnchor = (event: Event) => {
|
|
318
|
+
const customEvent = event as CustomEvent<ToolbarDragAnchorDetail>;
|
|
319
|
+
setDragAnchorPoint(customEvent.detail?.point || null);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
document.addEventListener(TOOLBAR_DRAG_ANCHOR_EVENT, handleToolbarDragAnchor as EventListener);
|
|
323
|
+
return () => {
|
|
324
|
+
document.removeEventListener(TOOLBAR_DRAG_ANCHOR_EVENT, handleToolbarDragAnchor as EventListener);
|
|
325
|
+
};
|
|
326
|
+
}, []);
|
|
327
|
+
|
|
328
|
+
const overlayAnchorModifier = useCallback<Modifier>(
|
|
329
|
+
({ active, activeNodeRect, transform }) => {
|
|
330
|
+
const nextTransform: Transform = resolveOverlayAnchorTransform({
|
|
331
|
+
activeId,
|
|
332
|
+
active,
|
|
333
|
+
transform,
|
|
334
|
+
activeNodeRect,
|
|
335
|
+
dragAnchorPoint,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
return nextTransform;
|
|
339
|
+
},
|
|
340
|
+
[activeId, dragAnchorPoint],
|
|
341
|
+
);
|
|
342
|
+
|
|
82
343
|
return (
|
|
83
344
|
<DndContext
|
|
345
|
+
{...restProps}
|
|
84
346
|
onDragStart={(event) => {
|
|
85
347
|
setActiveId(event.active.id as string);
|
|
86
|
-
|
|
348
|
+
onDragStart?.(event);
|
|
87
349
|
}}
|
|
88
350
|
onDragEnd={(event) => {
|
|
89
351
|
setActiveId(null);
|
|
352
|
+
setDragAnchorPoint(null);
|
|
90
353
|
// 如果没有 onDragEnd 回调,则默认调用 flowEngine 的 moveModel 方法
|
|
91
354
|
if (!onDragEnd) {
|
|
92
355
|
if (event.over) {
|
|
@@ -97,32 +360,46 @@ export const DndProvider: FC<DndContextProps & PersistOptions> = ({
|
|
|
97
360
|
onDragEnd(event);
|
|
98
361
|
}
|
|
99
362
|
}}
|
|
363
|
+
onDragCancel={(event) => {
|
|
364
|
+
setActiveId(null);
|
|
365
|
+
setDragAnchorPoint(null);
|
|
366
|
+
onDragCancel?.(event);
|
|
367
|
+
}}
|
|
100
368
|
{...restProps}
|
|
101
369
|
>
|
|
102
370
|
{children}
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
whiteSpace: 'nowrap',
|
|
111
|
-
background: '#fff',
|
|
112
|
-
border: '1px solid #1890ff',
|
|
113
|
-
borderRadius: 4,
|
|
114
|
-
padding: '4px 12px',
|
|
115
|
-
color: '#1890ff',
|
|
116
|
-
// fontSize: 18,
|
|
117
|
-
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
|
118
|
-
}}
|
|
371
|
+
{showDragOverlay && typeof document !== 'undefined'
|
|
372
|
+
? createPortal(
|
|
373
|
+
<DragOverlay
|
|
374
|
+
dropAnimation={null}
|
|
375
|
+
modifiers={[overlayAnchorModifier]}
|
|
376
|
+
zIndex={2000}
|
|
377
|
+
style={{ pointerEvents: 'none' }}
|
|
119
378
|
>
|
|
120
|
-
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
379
|
+
{activeId && (
|
|
380
|
+
<span
|
|
381
|
+
data-testid="flow-drag-preview"
|
|
382
|
+
style={{
|
|
383
|
+
display: 'inline-flex',
|
|
384
|
+
alignItems: 'center',
|
|
385
|
+
whiteSpace: 'nowrap',
|
|
386
|
+
background: '#fff',
|
|
387
|
+
border: '1px solid #1890ff',
|
|
388
|
+
borderRadius: 4,
|
|
389
|
+
padding: '4px 12px',
|
|
390
|
+
color: '#1890ff',
|
|
391
|
+
pointerEvents: 'none',
|
|
392
|
+
// fontSize: 18,
|
|
393
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
|
394
|
+
}}
|
|
395
|
+
>
|
|
396
|
+
{flowEngine.translate('Dragging')}
|
|
397
|
+
</span>
|
|
398
|
+
)}
|
|
399
|
+
</DragOverlay>,
|
|
400
|
+
document.body,
|
|
401
|
+
)
|
|
402
|
+
: null}
|
|
126
403
|
</DndContext>
|
|
127
404
|
);
|
|
128
405
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Select } from 'antd';
|
|
10
|
+
import { Select, Tooltip } from 'antd';
|
|
11
11
|
import React, { useEffect, useRef, useState } from 'react';
|
|
12
12
|
import { useFlowEngineContext } from '../../../../provider';
|
|
13
13
|
|
|
@@ -19,6 +19,7 @@ export interface SelectWithTitleProps {
|
|
|
19
19
|
itemKey?: string;
|
|
20
20
|
onChange?: (...args: any[]) => void;
|
|
21
21
|
dropdownRender?: any;
|
|
22
|
+
tooltip?: any;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function SelectWithTitle({
|
|
@@ -28,6 +29,7 @@ export function SelectWithTitle({
|
|
|
28
29
|
options,
|
|
29
30
|
fieldNames,
|
|
30
31
|
itemKey,
|
|
32
|
+
tooltip,
|
|
31
33
|
...others
|
|
32
34
|
}: SelectWithTitleProps) {
|
|
33
35
|
const [open, setOpen] = useState(false);
|
|
@@ -66,6 +68,17 @@ export function SelectWithTitle({
|
|
|
66
68
|
setValue(val);
|
|
67
69
|
onChange?.({ [itemKey]: val });
|
|
68
70
|
};
|
|
71
|
+
const titleNode = (
|
|
72
|
+
<span
|
|
73
|
+
style={{
|
|
74
|
+
whiteSpace: 'nowrap', // 不换行
|
|
75
|
+
flexShrink: 0, // 不被挤压
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{title}
|
|
79
|
+
</span>
|
|
80
|
+
);
|
|
81
|
+
|
|
69
82
|
return (
|
|
70
83
|
<div
|
|
71
84
|
style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}
|
|
@@ -79,14 +92,13 @@ export function SelectWithTitle({
|
|
|
79
92
|
}, 200);
|
|
80
93
|
}}
|
|
81
94
|
>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
</span>
|
|
95
|
+
{tooltip ? (
|
|
96
|
+
<Tooltip title={tooltip} placement="top" destroyTooltipOnHide>
|
|
97
|
+
{titleNode}
|
|
98
|
+
</Tooltip>
|
|
99
|
+
) : (
|
|
100
|
+
titleNode
|
|
101
|
+
)}
|
|
90
102
|
<Select
|
|
91
103
|
{...others}
|
|
92
104
|
open={open}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { ExclamationCircleOutlined, MenuOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
|
11
|
+
import { css } from '@emotion/css';
|
|
11
12
|
import type { DropdownProps, MenuProps } from 'antd';
|
|
12
13
|
import { App, Dropdown, Modal, Tooltip, theme } from 'antd';
|
|
13
14
|
import React, { startTransition, useCallback, useEffect, useMemo, useState, FC } from 'react';
|
|
@@ -26,6 +27,26 @@ import { useNiceDropdownMaxHeight } from '../../../../hooks';
|
|
|
26
27
|
import { SwitchWithTitle } from '../component/SwitchWithTitle';
|
|
27
28
|
import { SelectWithTitle } from '../component/SelectWithTitle';
|
|
28
29
|
import type { FlowSettingsContext } from '../../../../flowContext';
|
|
30
|
+
|
|
31
|
+
const findExtraMenuItemByKey = (
|
|
32
|
+
items: FlowModelExtraMenuItem[],
|
|
33
|
+
targetKey: string,
|
|
34
|
+
): FlowModelExtraMenuItem | undefined => {
|
|
35
|
+
for (const item of items) {
|
|
36
|
+
const itemKey = String(item?.key ?? '');
|
|
37
|
+
if (itemKey === targetKey) {
|
|
38
|
+
return item;
|
|
39
|
+
}
|
|
40
|
+
if (item.children?.length) {
|
|
41
|
+
const matched = findExtraMenuItemByKey(item.children, targetKey);
|
|
42
|
+
if (matched) {
|
|
43
|
+
return matched;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
29
50
|
// Type definitions for better type safety
|
|
30
51
|
interface StepInfo {
|
|
31
52
|
stepKey: string;
|
|
@@ -188,15 +209,51 @@ interface DefaultSettingsIconProps {
|
|
|
188
209
|
showCopyUidButton?: boolean;
|
|
189
210
|
menuLevels?: number; // Menu levels: 1=current model only (default), 2=include sub-models
|
|
190
211
|
flattenSubMenus?: boolean; // Whether to flatten sub-menus: false=group by model (default), true=flatten all
|
|
212
|
+
onDropdownVisibleChange?: (open: boolean) => void;
|
|
213
|
+
getPopupContainer?: DropdownProps['getPopupContainer'];
|
|
191
214
|
[key: string]: any; // Allow additional props
|
|
192
215
|
}
|
|
193
216
|
|
|
217
|
+
const TOOLBAR_ICONS_SELECTOR = '.nb-toolbar-container-icons';
|
|
218
|
+
const TOOLBAR_CONTAINER_SELECTOR = '.nb-toolbar-container';
|
|
219
|
+
const TOOLBAR_DROPDOWN_OVERLAY_CLASS = css`
|
|
220
|
+
width: max-content;
|
|
221
|
+
min-width: max-content;
|
|
222
|
+
|
|
223
|
+
.ant-dropdown-menu {
|
|
224
|
+
width: max-content;
|
|
225
|
+
min-width: max-content;
|
|
226
|
+
}
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
const getToolbarPopupContainer = (triggerNode?: HTMLElement | null) => {
|
|
230
|
+
if (!triggerNode) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
(triggerNode.closest(TOOLBAR_ICONS_SELECTOR) as HTMLElement | null) ||
|
|
236
|
+
(triggerNode.closest(TOOLBAR_CONTAINER_SELECTOR) as HTMLElement | null)
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const removeExtraMenuItemClickHandlers = (item: FlowModelExtraMenuItem): FlowModelExtraMenuItem => {
|
|
241
|
+
const { onClick: _onClick, children, ...rest } = item;
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
...rest,
|
|
245
|
+
children: children?.length ? children.map(removeExtraMenuItemClickHandlers) : undefined,
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
|
|
194
249
|
export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
195
250
|
model,
|
|
196
251
|
showDeleteButton = true,
|
|
197
252
|
showCopyUidButton = true,
|
|
198
253
|
menuLevels = 1, // 默认一级菜单
|
|
199
254
|
flattenSubMenus = true,
|
|
255
|
+
onDropdownVisibleChange,
|
|
256
|
+
getPopupContainer,
|
|
200
257
|
}) => {
|
|
201
258
|
const { message } = App.useApp();
|
|
202
259
|
const t = useMemo(() => getT(model), [model]);
|
|
@@ -210,15 +267,38 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
210
267
|
const [isLoading, setIsLoading] = useState(true);
|
|
211
268
|
const closeDropdown = useCallback(() => {
|
|
212
269
|
setVisible(false);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
270
|
+
onDropdownVisibleChange?.(false);
|
|
271
|
+
}, [onDropdownVisibleChange]);
|
|
272
|
+
const resolvePopupContainer = useCallback<NonNullable<DropdownProps['getPopupContainer']>>(
|
|
273
|
+
(triggerNode) => {
|
|
274
|
+
// 工具栏自身容器必须优先,保证鼠标从 icon 移到菜单时仍处于同一 hover 树。
|
|
275
|
+
// 弹窗场景的裁剪问题由 useFloatToolbarPortal 负责把 toolbar 挂到正确的 popup host。
|
|
276
|
+
return (
|
|
277
|
+
getToolbarPopupContainer(triggerNode) ||
|
|
278
|
+
getPopupContainer?.(triggerNode) ||
|
|
279
|
+
triggerNode?.parentElement ||
|
|
280
|
+
document.body
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
[getPopupContainer],
|
|
284
|
+
);
|
|
285
|
+
const handleOpenChange: DropdownProps['onOpenChange'] = useCallback(
|
|
286
|
+
(nextOpen: boolean, info) => {
|
|
287
|
+
if (info.source === 'trigger' || nextOpen) {
|
|
288
|
+
// 当鼠标快速滑过时,终止菜单的渲染,防止卡顿
|
|
289
|
+
startTransition(() => {
|
|
290
|
+
setVisible(nextOpen);
|
|
291
|
+
});
|
|
292
|
+
onDropdownVisibleChange?.(nextOpen);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
[onDropdownVisibleChange],
|
|
296
|
+
);
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
return () => {
|
|
299
|
+
onDropdownVisibleChange?.(false);
|
|
300
|
+
};
|
|
301
|
+
}, [onDropdownVisibleChange]);
|
|
222
302
|
const dropdownMaxHeight = useNiceDropdownMaxHeight([visible]);
|
|
223
303
|
useEffect(() => {
|
|
224
304
|
let mounted = true;
|
|
@@ -422,7 +502,11 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
422
502
|
return;
|
|
423
503
|
}
|
|
424
504
|
|
|
425
|
-
const extra =
|
|
505
|
+
const extra =
|
|
506
|
+
findExtraMenuItemByKey(extraMenuItems, originalKey) || findExtraMenuItemByKey(extraMenuItems, cleanKey);
|
|
507
|
+
if (extra?.disabled) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
426
510
|
if (extra?.onClick) {
|
|
427
511
|
closeDropdown();
|
|
428
512
|
extra.onClick();
|
|
@@ -795,7 +879,8 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
795
879
|
// });
|
|
796
880
|
|
|
797
881
|
if (commonExtras.length > 0) {
|
|
798
|
-
|
|
882
|
+
// Antd Menu 会同时触发 item.onClick 和 menu.onClick,这里统一交给 handleMenuClick 执行。
|
|
883
|
+
items.push(...(commonExtras.map(removeExtraMenuItemClickHandlers) as MenuProps['items']));
|
|
799
884
|
}
|
|
800
885
|
|
|
801
886
|
// 添加复制uid按钮
|
|
@@ -833,6 +918,9 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
833
918
|
|
|
834
919
|
return (
|
|
835
920
|
<Dropdown
|
|
921
|
+
getPopupContainer={resolvePopupContainer}
|
|
922
|
+
overlayClassName={TOOLBAR_DROPDOWN_OVERLAY_CLASS}
|
|
923
|
+
overlayStyle={{ width: 'max-content', minWidth: 'max-content' }}
|
|
836
924
|
onOpenChange={handleOpenChange}
|
|
837
925
|
open={visible}
|
|
838
926
|
menu={{
|