@nocobase/flow-engine 2.1.0-beta.9 → 2.1.1

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.
Files changed (215) hide show
  1. package/lib/FlowContextProvider.d.ts +5 -1
  2. package/lib/FlowContextProvider.js +9 -2
  3. package/lib/components/FieldModelRenderer.js +2 -2
  4. package/lib/components/FlowModelRenderer.d.ts +3 -1
  5. package/lib/components/FlowModelRenderer.js +12 -6
  6. package/lib/components/FormItem.d.ts +6 -0
  7. package/lib/components/FormItem.js +11 -3
  8. package/lib/components/MobilePopup.js +6 -5
  9. package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
  10. package/lib/components/dnd/gridDragPlanner.js +607 -19
  11. package/lib/components/dnd/index.d.ts +31 -2
  12. package/lib/components/dnd/index.js +244 -23
  13. package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
  14. package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
  15. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
  16. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +152 -42
  17. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
  18. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
  19. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
  20. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
  21. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
  22. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
  23. package/lib/components/subModel/AddSubModelButton.js +12 -1
  24. package/lib/components/subModel/LazyDropdown.js +301 -52
  25. package/lib/components/subModel/index.d.ts +1 -0
  26. package/lib/components/subModel/index.js +19 -0
  27. package/lib/components/subModel/utils.d.ts +2 -1
  28. package/lib/components/subModel/utils.js +15 -5
  29. package/lib/components/variables/VariableHybridInput.d.ts +27 -0
  30. package/lib/components/variables/VariableHybridInput.js +499 -0
  31. package/lib/components/variables/index.d.ts +2 -0
  32. package/lib/components/variables/index.js +3 -0
  33. package/lib/data-source/index.d.ts +84 -0
  34. package/lib/data-source/index.js +269 -7
  35. package/lib/executor/FlowExecutor.js +6 -3
  36. package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
  37. package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
  38. package/lib/flow-registry/index.d.ts +1 -0
  39. package/lib/flow-registry/index.js +3 -1
  40. package/lib/flowContext.d.ts +9 -1
  41. package/lib/flowContext.js +77 -6
  42. package/lib/flowEngine.d.ts +136 -4
  43. package/lib/flowEngine.js +429 -51
  44. package/lib/flowI18n.js +2 -1
  45. package/lib/flowSettings.d.ts +14 -6
  46. package/lib/flowSettings.js +34 -6
  47. package/lib/index.d.ts +2 -0
  48. package/lib/index.js +7 -0
  49. package/lib/lazy-helper.d.ts +14 -0
  50. package/lib/lazy-helper.js +71 -0
  51. package/lib/locale/en-US.json +1 -0
  52. package/lib/locale/index.d.ts +2 -0
  53. package/lib/locale/zh-CN.json +1 -0
  54. package/lib/models/DisplayItemModel.d.ts +1 -1
  55. package/lib/models/EditableItemModel.d.ts +1 -1
  56. package/lib/models/FilterableItemModel.d.ts +1 -1
  57. package/lib/models/flowModel.d.ts +13 -10
  58. package/lib/models/flowModel.js +126 -34
  59. package/lib/provider.js +38 -23
  60. package/lib/reactive/observer.js +46 -16
  61. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
  62. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
  63. package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
  64. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
  65. package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
  66. package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
  67. package/lib/runjs-context/contexts/base.js +464 -29
  68. package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
  69. package/lib/runjs-context/contexts/elementDoc.js +152 -0
  70. package/lib/runjs-context/setup.js +1 -0
  71. package/lib/runjs-context/snippets/index.js +13 -2
  72. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
  73. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
  74. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
  75. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  76. package/lib/types.d.ts +50 -2
  77. package/lib/types.js +1 -0
  78. package/lib/utils/createCollectionContextMeta.js +6 -2
  79. package/lib/utils/index.d.ts +3 -2
  80. package/lib/utils/index.js +7 -0
  81. package/lib/utils/loadedPageCache.d.ts +24 -0
  82. package/lib/utils/loadedPageCache.js +139 -0
  83. package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
  84. package/lib/utils/parsePathnameToViewParams.js +28 -4
  85. package/lib/utils/randomId.d.ts +39 -0
  86. package/lib/utils/randomId.js +45 -0
  87. package/lib/utils/runjsTemplateCompat.js +1 -1
  88. package/lib/utils/runjsValue.js +41 -11
  89. package/lib/utils/schema-utils.d.ts +7 -1
  90. package/lib/utils/schema-utils.js +19 -0
  91. package/lib/views/FlowView.d.ts +7 -1
  92. package/lib/views/FlowView.js +11 -1
  93. package/lib/views/PageComponent.js +8 -6
  94. package/lib/views/ViewNavigation.d.ts +12 -2
  95. package/lib/views/ViewNavigation.js +28 -9
  96. package/lib/views/createViewMeta.js +114 -50
  97. package/lib/views/inheritLayoutContext.d.ts +10 -0
  98. package/lib/views/inheritLayoutContext.js +50 -0
  99. package/lib/views/runViewBeforeClose.d.ts +10 -0
  100. package/lib/views/runViewBeforeClose.js +45 -0
  101. package/lib/views/useDialog.d.ts +2 -1
  102. package/lib/views/useDialog.js +12 -3
  103. package/lib/views/useDrawer.d.ts +2 -1
  104. package/lib/views/useDrawer.js +12 -3
  105. package/lib/views/usePage.d.ts +5 -11
  106. package/lib/views/usePage.js +304 -144
  107. package/package.json +5 -4
  108. package/src/FlowContextProvider.tsx +9 -1
  109. package/src/__tests__/createViewMeta.popup.test.ts +115 -1
  110. package/src/__tests__/flow-engine.test.ts +166 -0
  111. package/src/__tests__/flowContext.test.ts +105 -1
  112. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  113. package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
  114. package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
  115. package/src/__tests__/flowSettings.test.ts +94 -15
  116. package/src/__tests__/objectVariable.test.ts +24 -0
  117. package/src/__tests__/provider.test.tsx +24 -2
  118. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  119. package/src/__tests__/runjsContext.test.ts +21 -0
  120. package/src/__tests__/runjsContextImplementations.test.ts +9 -2
  121. package/src/__tests__/runjsContextRuntime.test.ts +2 -0
  122. package/src/__tests__/runjsLocales.test.ts +6 -5
  123. package/src/__tests__/runjsSnippets.test.ts +21 -0
  124. package/src/__tests__/viewScopedFlowEngine.test.ts +136 -3
  125. package/src/components/FieldModelRenderer.tsx +2 -1
  126. package/src/components/FlowModelRenderer.tsx +18 -6
  127. package/src/components/FormItem.tsx +7 -1
  128. package/src/components/MobilePopup.tsx +4 -2
  129. package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
  130. package/src/components/__tests__/FormItem.test.tsx +25 -0
  131. package/src/components/__tests__/dnd.test.ts +44 -0
  132. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
  133. package/src/components/__tests__/gridDragPlanner.test.ts +472 -5
  134. package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
  135. package/src/components/dnd/gridDragPlanner.ts +750 -17
  136. package/src/components/dnd/index.tsx +305 -28
  137. package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
  138. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +178 -48
  139. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
  140. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +344 -8
  141. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
  142. package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
  143. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
  144. package/src/components/subModel/AddSubModelButton.tsx +16 -2
  145. package/src/components/subModel/LazyDropdown.tsx +341 -56
  146. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +524 -38
  147. package/src/components/subModel/__tests__/utils.test.ts +24 -0
  148. package/src/components/subModel/index.ts +1 -0
  149. package/src/components/subModel/utils.ts +13 -2
  150. package/src/components/variables/VariableHybridInput.tsx +531 -0
  151. package/src/components/variables/index.ts +2 -0
  152. package/src/data-source/__tests__/collection.test.ts +41 -2
  153. package/src/data-source/__tests__/index.test.ts +69 -2
  154. package/src/data-source/index.ts +332 -8
  155. package/src/executor/FlowExecutor.ts +6 -3
  156. package/src/executor/__tests__/flowExecutor.test.ts +57 -0
  157. package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
  158. package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
  159. package/src/flow-registry/index.ts +1 -0
  160. package/src/flowContext.ts +85 -6
  161. package/src/flowEngine.ts +484 -45
  162. package/src/flowI18n.ts +2 -1
  163. package/src/flowSettings.ts +40 -6
  164. package/src/index.ts +2 -0
  165. package/src/lazy-helper.tsx +57 -0
  166. package/src/locale/en-US.json +1 -0
  167. package/src/locale/zh-CN.json +1 -0
  168. package/src/models/DisplayItemModel.tsx +1 -1
  169. package/src/models/EditableItemModel.tsx +1 -1
  170. package/src/models/FilterableItemModel.tsx +1 -1
  171. package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
  172. package/src/models/__tests__/flowModel.test.ts +65 -37
  173. package/src/models/flowModel.tsx +184 -65
  174. package/src/provider.tsx +41 -25
  175. package/src/reactive/__tests__/observer.test.tsx +82 -0
  176. package/src/reactive/observer.tsx +87 -25
  177. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +4 -3
  178. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
  179. package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
  180. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
  181. package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
  182. package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
  183. package/src/runjs-context/contexts/base.ts +467 -31
  184. package/src/runjs-context/contexts/elementDoc.ts +130 -0
  185. package/src/runjs-context/setup.ts +1 -0
  186. package/src/runjs-context/snippets/index.ts +12 -1
  187. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  188. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  189. package/src/types.ts +62 -0
  190. package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
  191. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +21 -0
  192. package/src/utils/__tests__/runjsValue.test.ts +11 -0
  193. package/src/utils/__tests__/utils.test.ts +62 -0
  194. package/src/utils/createCollectionContextMeta.ts +6 -2
  195. package/src/utils/index.ts +5 -1
  196. package/src/utils/loadedPageCache.ts +147 -0
  197. package/src/utils/parsePathnameToViewParams.ts +45 -5
  198. package/src/utils/randomId.ts +48 -0
  199. package/src/utils/runjsTemplateCompat.ts +1 -1
  200. package/src/utils/runjsValue.ts +50 -11
  201. package/src/utils/schema-utils.ts +30 -1
  202. package/src/views/FlowView.tsx +22 -2
  203. package/src/views/PageComponent.tsx +7 -4
  204. package/src/views/ViewNavigation.ts +46 -9
  205. package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
  206. package/src/views/__tests__/ViewNavigation.test.ts +52 -0
  207. package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
  208. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  209. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +12 -12
  210. package/src/views/createViewMeta.ts +106 -34
  211. package/src/views/inheritLayoutContext.ts +26 -0
  212. package/src/views/runViewBeforeClose.ts +19 -0
  213. package/src/views/useDialog.tsx +13 -3
  214. package/src/views/useDrawer.tsx +13 -3
  215. package/src/views/usePage.tsx +367 -180
@@ -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 React, { FC, useState } from 'react';
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: React.ReactNode }> = ({
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={setNodeRef}
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<DndContextProps & PersistOptions> = ({
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
- restProps.onDragStart?.(event);
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
- {createPortal(
104
- <DragOverlay dropAnimation={null} zIndex={2000}>
105
- {activeId && (
106
- <span
107
- style={{
108
- display: 'inline-flex',
109
- alignItems: 'center',
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
- {flowEngine.translate('Dragging')}
121
- </span>
122
- )}
123
- </DragOverlay>,
124
- document.body,
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
- <span
83
- style={{
84
- whiteSpace: 'nowrap', // 不换行
85
- flexShrink: 0, // 不被挤压
86
- }}
87
- >
88
- {title}
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}