@nocobase/flow-engine 2.1.0-alpha.3 → 2.1.0-alpha.30

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 (160) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/JSRunner.d.ts +10 -1
  4. package/lib/JSRunner.js +50 -5
  5. package/lib/ViewScopedFlowEngine.js +5 -1
  6. package/lib/components/FieldModelRenderer.js +2 -2
  7. package/lib/components/FlowModelRenderer.d.ts +3 -1
  8. package/lib/components/FlowModelRenderer.js +12 -6
  9. package/lib/components/MobilePopup.js +6 -5
  10. package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
  11. package/lib/components/dnd/gridDragPlanner.js +601 -21
  12. package/lib/components/dnd/index.d.ts +19 -1
  13. package/lib/components/dnd/index.js +243 -23
  14. package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
  15. package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
  16. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
  17. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +68 -10
  18. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
  19. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
  20. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
  21. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
  22. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
  23. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
  24. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
  25. package/lib/components/subModel/AddSubModelButton.js +27 -1
  26. package/lib/components/subModel/index.d.ts +1 -0
  27. package/lib/components/subModel/index.js +19 -0
  28. package/lib/components/subModel/utils.d.ts +1 -1
  29. package/lib/components/subModel/utils.js +2 -2
  30. package/lib/data-source/index.d.ts +73 -0
  31. package/lib/data-source/index.js +211 -1
  32. package/lib/executor/FlowExecutor.js +31 -8
  33. package/lib/flowContext.d.ts +2 -0
  34. package/lib/flowContext.js +31 -1
  35. package/lib/flowEngine.d.ts +151 -1
  36. package/lib/flowEngine.js +389 -15
  37. package/lib/flowI18n.js +2 -1
  38. package/lib/flowSettings.d.ts +14 -6
  39. package/lib/flowSettings.js +34 -6
  40. package/lib/lazy-helper.d.ts +14 -0
  41. package/lib/lazy-helper.js +71 -0
  42. package/lib/locale/en-US.json +1 -0
  43. package/lib/locale/index.d.ts +2 -0
  44. package/lib/locale/zh-CN.json +1 -0
  45. package/lib/models/DisplayItemModel.d.ts +1 -1
  46. package/lib/models/EditableItemModel.d.ts +1 -1
  47. package/lib/models/FilterableItemModel.d.ts +1 -1
  48. package/lib/models/flowModel.d.ts +13 -10
  49. package/lib/models/flowModel.js +78 -18
  50. package/lib/provider.js +38 -23
  51. package/lib/reactive/observer.js +46 -16
  52. package/lib/runjs-context/registry.d.ts +1 -1
  53. package/lib/runjs-context/setup.js +20 -12
  54. package/lib/runjs-context/snippets/index.js +13 -2
  55. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
  56. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
  57. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
  58. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  59. package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
  60. package/lib/scheduler/ModelOperationScheduler.js +3 -2
  61. package/lib/types.d.ts +47 -1
  62. package/lib/utils/createCollectionContextMeta.js +6 -2
  63. package/lib/utils/index.d.ts +2 -2
  64. package/lib/utils/index.js +4 -0
  65. package/lib/utils/parsePathnameToViewParams.js +1 -1
  66. package/lib/utils/runjsTemplateCompat.js +1 -1
  67. package/lib/utils/runjsValue.js +41 -11
  68. package/lib/utils/schema-utils.d.ts +7 -1
  69. package/lib/utils/schema-utils.js +19 -0
  70. package/lib/views/FlowView.d.ts +7 -1
  71. package/lib/views/runViewBeforeClose.d.ts +10 -0
  72. package/lib/views/runViewBeforeClose.js +45 -0
  73. package/lib/views/useDialog.d.ts +2 -1
  74. package/lib/views/useDialog.js +20 -3
  75. package/lib/views/useDrawer.d.ts +2 -1
  76. package/lib/views/useDrawer.js +20 -3
  77. package/lib/views/usePage.d.ts +2 -1
  78. package/lib/views/usePage.js +10 -3
  79. package/package.json +6 -5
  80. package/src/JSRunner.ts +68 -4
  81. package/src/ViewScopedFlowEngine.ts +4 -0
  82. package/src/__tests__/JSRunner.test.ts +27 -1
  83. package/src/__tests__/flow-engine.test.ts +166 -0
  84. package/src/__tests__/flowContext.test.ts +65 -1
  85. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  86. package/src/__tests__/flowSettings.test.ts +94 -15
  87. package/src/__tests__/objectVariable.test.ts +24 -0
  88. package/src/__tests__/provider.test.tsx +24 -2
  89. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  90. package/src/__tests__/runjsContext.test.ts +16 -0
  91. package/src/__tests__/runjsContextRuntime.test.ts +2 -0
  92. package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
  93. package/src/__tests__/runjsSnippets.test.ts +21 -0
  94. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  95. package/src/components/FieldModelRenderer.tsx +2 -1
  96. package/src/components/FlowModelRenderer.tsx +18 -6
  97. package/src/components/MobilePopup.tsx +4 -2
  98. package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
  99. package/src/components/__tests__/dnd.test.ts +44 -0
  100. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
  101. package/src/components/__tests__/gridDragPlanner.test.ts +512 -3
  102. package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
  103. package/src/components/dnd/gridDragPlanner.ts +743 -19
  104. package/src/components/dnd/index.tsx +291 -27
  105. package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
  106. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +88 -10
  107. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
  108. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
  109. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
  110. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
  111. package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
  112. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
  113. package/src/components/subModel/AddSubModelButton.tsx +32 -2
  114. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
  115. package/src/components/subModel/index.ts +1 -0
  116. package/src/components/subModel/utils.ts +1 -1
  117. package/src/data-source/__tests__/index.test.ts +34 -1
  118. package/src/data-source/index.ts +258 -2
  119. package/src/executor/FlowExecutor.ts +34 -9
  120. package/src/executor/__tests__/flowExecutor.test.ts +57 -0
  121. package/src/flowContext.ts +37 -3
  122. package/src/flowEngine.ts +445 -11
  123. package/src/flowI18n.ts +2 -1
  124. package/src/flowSettings.ts +40 -6
  125. package/src/lazy-helper.tsx +57 -0
  126. package/src/locale/en-US.json +1 -0
  127. package/src/locale/zh-CN.json +1 -0
  128. package/src/models/DisplayItemModel.tsx +1 -1
  129. package/src/models/EditableItemModel.tsx +1 -1
  130. package/src/models/FilterableItemModel.tsx +1 -1
  131. package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
  132. package/src/models/__tests__/flowModel.test.ts +19 -3
  133. package/src/models/flowModel.tsx +119 -33
  134. package/src/provider.tsx +41 -25
  135. package/src/reactive/__tests__/observer.test.tsx +82 -0
  136. package/src/reactive/observer.tsx +87 -25
  137. package/src/runjs-context/registry.ts +1 -1
  138. package/src/runjs-context/setup.ts +22 -12
  139. package/src/runjs-context/snippets/index.ts +12 -1
  140. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  141. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  142. package/src/scheduler/ModelOperationScheduler.ts +14 -3
  143. package/src/types.ts +60 -0
  144. package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
  145. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
  146. package/src/utils/__tests__/runjsValue.test.ts +11 -0
  147. package/src/utils/__tests__/utils.test.ts +62 -0
  148. package/src/utils/createCollectionContextMeta.ts +6 -2
  149. package/src/utils/index.ts +2 -1
  150. package/src/utils/parsePathnameToViewParams.ts +2 -2
  151. package/src/utils/runjsTemplateCompat.ts +1 -1
  152. package/src/utils/runjsValue.ts +50 -11
  153. package/src/utils/schema-utils.ts +30 -1
  154. package/src/views/FlowView.tsx +11 -1
  155. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  156. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
  157. package/src/views/runViewBeforeClose.ts +19 -0
  158. package/src/views/useDialog.tsx +25 -3
  159. package/src/views/useDrawer.tsx +25 -3
  160. package/src/views/usePage.tsx +12 -3
@@ -7,15 +7,33 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { DndContextProps } from '@dnd-kit/core';
10
+ import type { Transform } from '@dnd-kit/utilities';
10
11
  import React, { FC } from 'react';
11
12
  import { FlowModel } from '../../models';
12
13
  import { PersistOptions } from '../../types';
13
14
  export * from './findModelUidPosition';
14
15
  export * from './gridDragPlanner';
15
16
  export declare const EMPTY_COLUMN_UID = "EMPTY_COLUMN";
17
+ export declare const TOOLBAR_DRAG_ACTIVITY_EVENT = "nb-toolbar-drag-activity";
18
+ type ToolbarDragAnchorPoint = {
19
+ x: number;
20
+ y: number;
21
+ };
22
+ export declare const resolveOverlayAnchorTransform: ({ activeId, active, transform, activeNodeRect, dragAnchorPoint, }: {
23
+ activeId: string | null;
24
+ active: {
25
+ id: string | number;
26
+ } | null | undefined;
27
+ transform: Transform;
28
+ activeNodeRect: {
29
+ top: number;
30
+ left: number;
31
+ } | null;
32
+ dragAnchorPoint: ToolbarDragAnchorPoint | null;
33
+ }) => Transform;
16
34
  export declare const DragHandler: FC<{
17
35
  model: FlowModel;
18
- children: React.ReactNode;
36
+ children?: React.ReactNode;
19
37
  }>;
20
38
  export declare const Droppable: FC<{
21
39
  model: FlowModel<any>;
@@ -41,7 +41,9 @@ __export(dnd_exports, {
41
41
  DndProvider: () => DndProvider,
42
42
  DragHandler: () => DragHandler,
43
43
  Droppable: () => Droppable,
44
- EMPTY_COLUMN_UID: () => EMPTY_COLUMN_UID
44
+ EMPTY_COLUMN_UID: () => EMPTY_COLUMN_UID,
45
+ TOOLBAR_DRAG_ACTIVITY_EVENT: () => TOOLBAR_DRAG_ACTIVITY_EVENT,
46
+ resolveOverlayAnchorTransform: () => resolveOverlayAnchorTransform
45
47
  });
46
48
  module.exports = __toCommonJS(dnd_exports);
47
49
  var import_icons = require("@ant-design/icons");
@@ -52,17 +54,186 @@ var import_provider = require("../../provider");
52
54
  __reExport(dnd_exports, require("./findModelUidPosition"), module.exports);
53
55
  __reExport(dnd_exports, require("./gridDragPlanner"), module.exports);
54
56
  const EMPTY_COLUMN_UID = "EMPTY_COLUMN";
57
+ const TOOLBAR_DRAG_ACTIVITY_EVENT = "nb-toolbar-drag-activity";
58
+ const TOOLBAR_DRAG_ANCHOR_EVENT = "nb-toolbar-drag-anchor";
59
+ const MENU_SUBMENU_POPUP_SELECTOR = ".ant-menu-submenu-popup";
60
+ const resolveOverlayAnchorTransform = /* @__PURE__ */ __name(({
61
+ activeId,
62
+ active,
63
+ transform,
64
+ activeNodeRect,
65
+ dragAnchorPoint
66
+ }) => {
67
+ if (!activeId || (active == null ? void 0 : active.id) !== activeId || !dragAnchorPoint || !activeNodeRect) {
68
+ return transform;
69
+ }
70
+ return {
71
+ ...transform,
72
+ x: transform.x + dragAnchorPoint.x - activeNodeRect.left,
73
+ y: transform.y + dragAnchorPoint.y - activeNodeRect.top
74
+ };
75
+ }, "resolveOverlayAnchorTransform");
76
+ const resolveDraggableHostNode = /* @__PURE__ */ __name((activatorNode) => {
77
+ const ownerDocument = activatorNode == null ? void 0 : activatorNode.ownerDocument;
78
+ const floatToolbarContainer = activatorNode == null ? void 0 : activatorNode.closest(".nb-toolbar-container[data-model-uid]");
79
+ const toolbarModelUid = floatToolbarContainer == null ? void 0 : floatToolbarContainer.getAttribute("data-model-uid");
80
+ if (!ownerDocument || !toolbarModelUid) {
81
+ return activatorNode;
82
+ }
83
+ const matchedHosts = Array.from(
84
+ ownerDocument.querySelectorAll(
85
+ `[data-has-float-menu="true"][data-float-menu-model-uid="${toolbarModelUid}"]`
86
+ )
87
+ );
88
+ const popupRoot = floatToolbarContainer == null ? void 0 : floatToolbarContainer.closest(MENU_SUBMENU_POPUP_SELECTOR);
89
+ if (popupRoot) {
90
+ return matchedHosts.find((hostNode) => hostNode.closest(MENU_SUBMENU_POPUP_SELECTOR) === popupRoot) || activatorNode;
91
+ }
92
+ return matchedHosts.find((hostNode) => !hostNode.closest(MENU_SUBMENU_POPUP_SELECTOR)) || matchedHosts[0] || activatorNode;
93
+ }, "resolveDraggableHostNode");
55
94
  const DragHandler = /* @__PURE__ */ __name(({
56
95
  model,
57
96
  children = /* @__PURE__ */ import_react.default.createElement(import_icons.DragOutlined, null)
58
97
  }) => {
59
- const { attributes, listeners, setNodeRef } = (0, import_core.useDraggable)({ id: model.uid });
98
+ const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef } = (0, import_core.useDraggable)({ id: model.uid });
99
+ const dragHandlerRef = (0, import_react.useRef)(null);
100
+ const draggableNodeRef = (0, import_react.useRef)(null);
101
+ const pointerPressCleanupRef = (0, import_react.useRef)(null);
102
+ const isDraggingRef = (0, import_react.useRef)(isDragging);
103
+ const isPointerPressActiveRef = (0, import_react.useRef)(false);
104
+ const isToolbarDragActiveRef = (0, import_react.useRef)(false);
105
+ const syncDraggableNodeRef = (0, import_react.useCallback)(
106
+ (activatorNode) => {
107
+ const nextNode = resolveDraggableHostNode(activatorNode);
108
+ if (draggableNodeRef.current === nextNode) {
109
+ return;
110
+ }
111
+ draggableNodeRef.current = nextNode;
112
+ setNodeRef(nextNode);
113
+ },
114
+ [setNodeRef]
115
+ );
116
+ const setDragHandlerNodeRef = (0, import_react.useCallback)(
117
+ (node) => {
118
+ dragHandlerRef.current = node;
119
+ setActivatorNodeRef(node);
120
+ syncDraggableNodeRef(node);
121
+ },
122
+ [setActivatorNodeRef, syncDraggableNodeRef]
123
+ );
124
+ const dispatchToolbarDragActivity = (0, import_react.useCallback)(
125
+ (active) => {
126
+ var _a;
127
+ const ownerDocument = (_a = dragHandlerRef.current) == null ? void 0 : _a.ownerDocument;
128
+ if (!ownerDocument) {
129
+ return;
130
+ }
131
+ ownerDocument.dispatchEvent(
132
+ new CustomEvent(TOOLBAR_DRAG_ACTIVITY_EVENT, {
133
+ detail: { active, modelUid: model.uid }
134
+ })
135
+ );
136
+ },
137
+ [model.uid]
138
+ );
139
+ const dispatchToolbarDragAnchor = (0, import_react.useCallback)(
140
+ (detail) => {
141
+ var _a;
142
+ const ownerDocument = (_a = dragHandlerRef.current) == null ? void 0 : _a.ownerDocument;
143
+ if (!ownerDocument) {
144
+ return;
145
+ }
146
+ ownerDocument.dispatchEvent(
147
+ new CustomEvent(TOOLBAR_DRAG_ANCHOR_EVENT, {
148
+ detail: { modelUid: model.uid, point: detail.point }
149
+ })
150
+ );
151
+ },
152
+ [model.uid]
153
+ );
154
+ const clearPointerPressListeners = (0, import_react.useCallback)(() => {
155
+ var _a;
156
+ (_a = pointerPressCleanupRef.current) == null ? void 0 : _a.call(pointerPressCleanupRef);
157
+ pointerPressCleanupRef.current = null;
158
+ }, []);
159
+ const syncToolbarDragActivity = (0, import_react.useCallback)(() => {
160
+ const nextActive = isPointerPressActiveRef.current || isDraggingRef.current;
161
+ if (nextActive === isToolbarDragActiveRef.current) {
162
+ return;
163
+ }
164
+ isToolbarDragActiveRef.current = nextActive;
165
+ dispatchToolbarDragActivity(nextActive);
166
+ }, [dispatchToolbarDragActivity]);
167
+ const handlePointerPressEnd = (0, import_react.useCallback)(() => {
168
+ isPointerPressActiveRef.current = false;
169
+ clearPointerPressListeners();
170
+ syncToolbarDragActivity();
171
+ }, [clearPointerPressListeners, syncToolbarDragActivity]);
172
+ const registerPointerPressListeners = (0, import_react.useCallback)(() => {
173
+ var _a;
174
+ const ownerDocument = (_a = dragHandlerRef.current) == null ? void 0 : _a.ownerDocument;
175
+ const ownerWindow = ownerDocument == null ? void 0 : ownerDocument.defaultView;
176
+ if (!ownerDocument) {
177
+ return;
178
+ }
179
+ clearPointerPressListeners();
180
+ const handlePointerEnd = /* @__PURE__ */ __name(() => {
181
+ handlePointerPressEnd();
182
+ }, "handlePointerEnd");
183
+ const handleKeyDown = /* @__PURE__ */ __name((event) => {
184
+ if (event.key === "Escape") {
185
+ handlePointerPressEnd();
186
+ }
187
+ }, "handleKeyDown");
188
+ ownerDocument.addEventListener("pointerup", handlePointerEnd, true);
189
+ ownerDocument.addEventListener("pointercancel", handlePointerEnd, true);
190
+ ownerDocument.addEventListener("keydown", handleKeyDown, true);
191
+ ownerWindow == null ? void 0 : ownerWindow.addEventListener("blur", handlePointerEnd);
192
+ pointerPressCleanupRef.current = () => {
193
+ ownerDocument.removeEventListener("pointerup", handlePointerEnd, true);
194
+ ownerDocument.removeEventListener("pointercancel", handlePointerEnd, true);
195
+ ownerDocument.removeEventListener("keydown", handleKeyDown, true);
196
+ ownerWindow == null ? void 0 : ownerWindow.removeEventListener("blur", handlePointerEnd);
197
+ };
198
+ }, [clearPointerPressListeners, handlePointerPressEnd]);
199
+ (0, import_react.useEffect)(() => {
200
+ syncDraggableNodeRef(dragHandlerRef.current);
201
+ }, [syncDraggableNodeRef]);
202
+ (0, import_react.useEffect)(() => {
203
+ isDraggingRef.current = isDragging;
204
+ syncToolbarDragActivity();
205
+ }, [isDragging, syncToolbarDragActivity]);
206
+ (0, import_react.useEffect)(() => {
207
+ return () => {
208
+ if (isToolbarDragActiveRef.current) {
209
+ dispatchToolbarDragActivity(false);
210
+ }
211
+ isPointerPressActiveRef.current = false;
212
+ isDraggingRef.current = false;
213
+ isToolbarDragActiveRef.current = false;
214
+ clearPointerPressListeners();
215
+ };
216
+ }, [clearPointerPressListeners, dispatchToolbarDragActivity]);
60
217
  return /* @__PURE__ */ import_react.default.createElement(
61
218
  "span",
62
219
  {
63
- ref: setNodeRef,
220
+ ref: setDragHandlerNodeRef,
64
221
  ...listeners,
65
222
  ...attributes,
223
+ onPointerDownCapture: (event) => {
224
+ if (event.button !== 0) {
225
+ return;
226
+ }
227
+ dispatchToolbarDragAnchor({
228
+ point: {
229
+ x: event.clientX,
230
+ y: event.clientY
231
+ }
232
+ });
233
+ isPointerPressActiveRef.current = true;
234
+ syncToolbarDragActivity();
235
+ registerPointerPressListeners();
236
+ },
66
237
  style: {
67
238
  cursor: "grab"
68
239
  }
@@ -104,21 +275,52 @@ const Droppable = /* @__PURE__ */ __name(({ model, children }) => {
104
275
  const DndProvider = /* @__PURE__ */ __name(({
105
276
  persist = true,
106
277
  children,
278
+ onDragStart,
107
279
  onDragEnd,
280
+ onDragCancel,
108
281
  ...restProps
109
282
  }) => {
110
283
  const [activeId, setActiveId] = (0, import_react.useState)(null);
284
+ const [dragAnchorPoint, setDragAnchorPoint] = (0, import_react.useState)(null);
111
285
  const flowEngine = (0, import_provider.useFlowEngine)();
286
+ (0, import_react.useEffect)(() => {
287
+ if (typeof document === "undefined") {
288
+ return;
289
+ }
290
+ const handleToolbarDragAnchor = /* @__PURE__ */ __name((event) => {
291
+ var _a;
292
+ const customEvent = event;
293
+ setDragAnchorPoint(((_a = customEvent.detail) == null ? void 0 : _a.point) || null);
294
+ }, "handleToolbarDragAnchor");
295
+ document.addEventListener(TOOLBAR_DRAG_ANCHOR_EVENT, handleToolbarDragAnchor);
296
+ return () => {
297
+ document.removeEventListener(TOOLBAR_DRAG_ANCHOR_EVENT, handleToolbarDragAnchor);
298
+ };
299
+ }, []);
300
+ const overlayAnchorModifier = (0, import_react.useCallback)(
301
+ ({ active, activeNodeRect, transform }) => {
302
+ const nextTransform = resolveOverlayAnchorTransform({
303
+ activeId,
304
+ active,
305
+ transform,
306
+ activeNodeRect,
307
+ dragAnchorPoint
308
+ });
309
+ return nextTransform;
310
+ },
311
+ [activeId, dragAnchorPoint]
312
+ );
112
313
  return /* @__PURE__ */ import_react.default.createElement(
113
314
  import_core.DndContext,
114
315
  {
316
+ ...restProps,
115
317
  onDragStart: (event) => {
116
- var _a;
117
318
  setActiveId(event.active.id);
118
- (_a = restProps.onDragStart) == null ? void 0 : _a.call(restProps, event);
319
+ onDragStart == null ? void 0 : onDragStart(event);
119
320
  },
120
321
  onDragEnd: (event) => {
121
322
  setActiveId(null);
323
+ setDragAnchorPoint(null);
122
324
  if (!onDragEnd) {
123
325
  if (event.over) {
124
326
  flowEngine.moveModel(event.active.id, event.over.id, { persist });
@@ -127,30 +329,46 @@ const DndProvider = /* @__PURE__ */ __name(({
127
329
  onDragEnd(event);
128
330
  }
129
331
  },
332
+ onDragCancel: (event) => {
333
+ setActiveId(null);
334
+ setDragAnchorPoint(null);
335
+ onDragCancel == null ? void 0 : onDragCancel(event);
336
+ },
130
337
  ...restProps
131
338
  },
132
339
  children,
133
- (0, import_react_dom.createPortal)(
134
- /* @__PURE__ */ import_react.default.createElement(import_core.DragOverlay, { dropAnimation: null, zIndex: 2e3 }, activeId && /* @__PURE__ */ import_react.default.createElement(
135
- "span",
340
+ typeof document !== "undefined" ? (0, import_react_dom.createPortal)(
341
+ /* @__PURE__ */ import_react.default.createElement(
342
+ import_core.DragOverlay,
136
343
  {
137
- style: {
138
- display: "inline-flex",
139
- alignItems: "center",
140
- whiteSpace: "nowrap",
141
- background: "#fff",
142
- border: "1px solid #1890ff",
143
- borderRadius: 4,
144
- padding: "4px 12px",
145
- color: "#1890ff",
146
- // fontSize: 18,
147
- boxShadow: "0 2px 8px rgba(0,0,0,0.15)"
148
- }
344
+ dropAnimation: null,
345
+ modifiers: [overlayAnchorModifier],
346
+ zIndex: 2e3,
347
+ style: { pointerEvents: "none" }
149
348
  },
150
- flowEngine.translate("Dragging")
151
- )),
349
+ activeId && /* @__PURE__ */ import_react.default.createElement(
350
+ "span",
351
+ {
352
+ "data-testid": "flow-drag-preview",
353
+ style: {
354
+ display: "inline-flex",
355
+ alignItems: "center",
356
+ whiteSpace: "nowrap",
357
+ background: "#fff",
358
+ border: "1px solid #1890ff",
359
+ borderRadius: 4,
360
+ padding: "4px 12px",
361
+ color: "#1890ff",
362
+ pointerEvents: "none",
363
+ // fontSize: 18,
364
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)"
365
+ }
366
+ },
367
+ flowEngine.translate("Dragging")
368
+ )
369
+ ),
152
370
  document.body
153
- )
371
+ ) : null
154
372
  );
155
373
  }, "DndProvider");
156
374
  // Annotate the CommonJS export names for ESM import in node:
@@ -159,6 +377,8 @@ const DndProvider = /* @__PURE__ */ __name(({
159
377
  DragHandler,
160
378
  Droppable,
161
379
  EMPTY_COLUMN_UID,
380
+ TOOLBAR_DRAG_ACTIVITY_EVENT,
381
+ resolveOverlayAnchorTransform,
162
382
  ...require("./findModelUidPosition"),
163
383
  ...require("./gridDragPlanner")
164
384
  });
@@ -15,5 +15,6 @@ export interface SelectWithTitleProps {
15
15
  itemKey?: string;
16
16
  onChange?: (...args: any[]) => void;
17
17
  dropdownRender?: any;
18
+ tooltip?: any;
18
19
  }
19
- export declare function SelectWithTitle({ title, getDefaultValue, onChange, options, fieldNames, itemKey, ...others }: SelectWithTitleProps): React.JSX.Element;
20
+ export declare function SelectWithTitle({ title, getDefaultValue, onChange, options, fieldNames, itemKey, tooltip, ...others }: SelectWithTitleProps): React.JSX.Element;
@@ -50,6 +50,7 @@ function SelectWithTitle({
50
50
  options,
51
51
  fieldNames,
52
52
  itemKey,
53
+ tooltip,
53
54
  ...others
54
55
  }) {
55
56
  const [open, setOpen] = (0, import_react.useState)(false);
@@ -80,6 +81,18 @@ function SelectWithTitle({
80
81
  setValue(val);
81
82
  onChange == null ? void 0 : onChange({ [itemKey]: val });
82
83
  }, "handleChange");
84
+ const titleNode = /* @__PURE__ */ import_react.default.createElement(
85
+ "span",
86
+ {
87
+ style: {
88
+ whiteSpace: "nowrap",
89
+ // 不换行
90
+ flexShrink: 0
91
+ // 不被挤压
92
+ }
93
+ },
94
+ title
95
+ );
83
96
  return /* @__PURE__ */ import_react.default.createElement(
84
97
  "div",
85
98
  {
@@ -94,18 +107,7 @@ function SelectWithTitle({
94
107
  }, 200);
95
108
  }
96
109
  },
97
- /* @__PURE__ */ import_react.default.createElement(
98
- "span",
99
- {
100
- style: {
101
- whiteSpace: "nowrap",
102
- // 不换行
103
- flexShrink: 0
104
- // 不被挤压
105
- }
106
- },
107
- title
108
- ),
110
+ tooltip ? /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: tooltip, placement: "top", destroyTooltipOnHide: true }, titleNode) : titleNode,
109
111
  /* @__PURE__ */ import_react.default.createElement(
110
112
  import_antd.Select,
111
113
  {
@@ -6,6 +6,7 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ import type { DropdownProps } from 'antd';
9
10
  import React from 'react';
10
11
  import { FlowModel } from '../../../../models';
11
12
  /**
@@ -18,6 +19,8 @@ interface DefaultSettingsIconProps {
18
19
  showCopyUidButton?: boolean;
19
20
  menuLevels?: number;
20
21
  flattenSubMenus?: boolean;
22
+ onDropdownVisibleChange?: (open: boolean) => void;
23
+ getPopupContainer?: DropdownProps['getPopupContainer'];
21
24
  [key: string]: any;
22
25
  }
23
26
  export declare const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps>;
@@ -41,6 +41,7 @@ __export(DefaultSettingsIcon_exports, {
41
41
  });
42
42
  module.exports = __toCommonJS(DefaultSettingsIcon_exports);
43
43
  var import_icons = require("@ant-design/icons");
44
+ var import_css = require("@emotion/css");
44
45
  var import_antd = require("antd");
45
46
  var import_react = __toESM(require("react"));
46
47
  var import_models = require("../../../../models");
@@ -48,6 +49,22 @@ var import_utils = require("../../../../utils");
48
49
  var import_hooks = require("../../../../hooks");
49
50
  var import_SwitchWithTitle = require("../component/SwitchWithTitle");
50
51
  var import_SelectWithTitle = require("../component/SelectWithTitle");
52
+ const findExtraMenuItemByKey = /* @__PURE__ */ __name((items, targetKey) => {
53
+ var _a;
54
+ for (const item of items) {
55
+ const itemKey = String((item == null ? void 0 : item.key) ?? "");
56
+ if (itemKey === targetKey) {
57
+ return item;
58
+ }
59
+ if ((_a = item.children) == null ? void 0 : _a.length) {
60
+ const matched = findExtraMenuItemByKey(item.children, targetKey);
61
+ if (matched) {
62
+ return matched;
63
+ }
64
+ }
65
+ }
66
+ return void 0;
67
+ }, "findExtraMenuItemByKey");
51
68
  const walkSubModels = /* @__PURE__ */ __name((rootModel, options, cb) => {
52
69
  const maxDepth = options.maxDepth;
53
70
  const arrayLimit = typeof options.arrayLimit === "number" ? options.arrayLimit : Number.POSITIVE_INFINITY;
@@ -149,13 +166,32 @@ const MenuLabelItem = /* @__PURE__ */ __name(({ title, uiMode, itemProps }) => {
149
166
  }
150
167
  return /* @__PURE__ */ import_react.default.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 } }, content, /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: disabledReason, placement: "right", destroyTooltipOnHide: true }, /* @__PURE__ */ import_react.default.createElement(import_icons.QuestionCircleOutlined, { style: { color: disabledIconColor } })));
151
168
  }, "MenuLabelItem");
169
+ const TOOLBAR_ICONS_SELECTOR = ".nb-toolbar-container-icons";
170
+ const TOOLBAR_CONTAINER_SELECTOR = ".nb-toolbar-container";
171
+ const TOOLBAR_DROPDOWN_OVERLAY_CLASS = import_css.css`
172
+ width: max-content;
173
+ min-width: max-content;
174
+
175
+ .ant-dropdown-menu {
176
+ width: max-content;
177
+ min-width: max-content;
178
+ }
179
+ `;
180
+ const getToolbarPopupContainer = /* @__PURE__ */ __name((triggerNode) => {
181
+ if (!triggerNode) {
182
+ return null;
183
+ }
184
+ return triggerNode.closest(TOOLBAR_ICONS_SELECTOR) || triggerNode.closest(TOOLBAR_CONTAINER_SELECTOR);
185
+ }, "getToolbarPopupContainer");
152
186
  const DefaultSettingsIcon = /* @__PURE__ */ __name(({
153
187
  model,
154
188
  showDeleteButton = true,
155
189
  showCopyUidButton = true,
156
190
  menuLevels = 1,
157
191
  // 默认一级菜单
158
- flattenSubMenus = true
192
+ flattenSubMenus = true,
193
+ onDropdownVisibleChange,
194
+ getPopupContainer
159
195
  }) => {
160
196
  const { message } = import_antd.App.useApp();
161
197
  const t = (0, import_react.useMemo)(() => (0, import_utils.getT)(model), [model]);
@@ -168,14 +204,30 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
168
204
  const [isLoading, setIsLoading] = (0, import_react.useState)(true);
169
205
  const closeDropdown = (0, import_react.useCallback)(() => {
170
206
  setVisible(false);
171
- }, []);
172
- const handleOpenChange = (0, import_react.useCallback)((nextOpen, info) => {
173
- if (info.source === "trigger" || nextOpen) {
174
- (0, import_react.startTransition)(() => {
175
- setVisible(nextOpen);
176
- });
177
- }
178
- }, []);
207
+ onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(false);
208
+ }, [onDropdownVisibleChange]);
209
+ const resolvePopupContainer = (0, import_react.useCallback)(
210
+ (triggerNode) => {
211
+ return getToolbarPopupContainer(triggerNode) || (getPopupContainer == null ? void 0 : getPopupContainer(triggerNode)) || (triggerNode == null ? void 0 : triggerNode.parentElement) || document.body;
212
+ },
213
+ [getPopupContainer]
214
+ );
215
+ const handleOpenChange = (0, import_react.useCallback)(
216
+ (nextOpen, info) => {
217
+ if (info.source === "trigger" || nextOpen) {
218
+ (0, import_react.startTransition)(() => {
219
+ setVisible(nextOpen);
220
+ });
221
+ onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(nextOpen);
222
+ }
223
+ },
224
+ [onDropdownVisibleChange]
225
+ );
226
+ (0, import_react.useEffect)(() => {
227
+ return () => {
228
+ onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(false);
229
+ };
230
+ }, [onDropdownVisibleChange]);
179
231
  const dropdownMaxHeight = (0, import_hooks.useNiceDropdownMaxHeight)([visible]);
180
232
  (0, import_react.useEffect)(() => {
181
233
  let mounted = true;
@@ -353,7 +405,10 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
353
405
  handleCopyPopupUid(cleanKey);
354
406
  return;
355
407
  }
356
- const extra = extraMenuItems.find((it) => (it == null ? void 0 : it.key) === originalKey || (it == null ? void 0 : it.key) === cleanKey);
408
+ const extra = findExtraMenuItemByKey(extraMenuItems, originalKey) || findExtraMenuItemByKey(extraMenuItems, cleanKey);
409
+ if (extra == null ? void 0 : extra.disabled) {
410
+ return;
411
+ }
357
412
  if (extra == null ? void 0 : extra.onClick) {
358
413
  closeDropdown();
359
414
  extra.onClick();
@@ -671,6 +726,9 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
671
726
  return /* @__PURE__ */ import_react.default.createElement(
672
727
  import_antd.Dropdown,
673
728
  {
729
+ getPopupContainer: resolvePopupContainer,
730
+ overlayClassName: TOOLBAR_DROPDOWN_OVERLAY_CLASS,
731
+ overlayStyle: { width: "max-content", minWidth: "max-content" },
674
732
  onOpenChange: handleOpenChange,
675
733
  open: visible,
676
734
  menu: {
@@ -9,13 +9,14 @@
9
9
  import React from 'react';
10
10
  import { FlowModel } from '../../../../models';
11
11
  import { ToolbarItemConfig } from '../../../../types';
12
- interface ModelProvidedProps {
13
- model: FlowModel<any>;
12
+ type ToolbarPosition = 'inside' | 'above' | 'below';
13
+ interface BaseFloatContextMenuProps {
14
14
  children?: React.ReactNode;
15
15
  enabled?: boolean;
16
16
  showDeleteButton?: boolean;
17
17
  showCopyUidButton?: boolean;
18
18
  containerStyle?: React.CSSProperties;
19
+ /** 自定义工具栏样式,`top/left/right/bottom` 会作为 portal overlay 的 inset 使用。 */
19
20
  toolbarStyle?: React.CSSProperties;
20
21
  className?: string;
21
22
  /**
@@ -42,68 +43,47 @@ interface ModelProvidedProps {
42
43
  * Extra toolbar items to add to this context menu instance
43
44
  */
44
45
  extraToolbarItems?: ToolbarItemConfig[];
45
- /**
46
- * @default 'inside'
47
- */
48
- toolbarPosition?: 'inside' | 'above' | 'below';
49
- }
50
- interface ModelByIdProps {
51
- uid: string;
52
- modelClassName: string;
53
- children?: React.ReactNode;
54
- enabled?: boolean;
55
- showDeleteButton?: boolean;
56
- showCopyUidButton?: boolean;
57
- containerStyle?: React.CSSProperties;
58
- className?: string;
59
46
  /**
60
47
  * @default true
61
48
  */
62
- showBorder?: boolean;
63
- /**
64
- * @default true
65
- */
66
- showBackground?: boolean;
67
- /**
68
- * @default false
69
- */
70
- showTitle?: boolean;
71
- /**
72
- * Settings menu levels: 1=current model only (default), 2=include sub-models
73
- */
74
- settingsMenuLevel?: number;
75
- /**
76
- * Extra toolbar items to add to this context menu instance
77
- */
78
- extraToolbarItems?: ToolbarItemConfig[];
49
+ showDynamicFlowsEditor?: boolean;
79
50
  /**
80
51
  * @default 'inside'
81
52
  */
82
- toolbarPosition?: 'inside' | 'above' | 'below';
53
+ toolbarPosition?: ToolbarPosition;
54
+ }
55
+ interface ModelProvidedProps extends BaseFloatContextMenuProps {
56
+ model: FlowModel<any>;
57
+ }
58
+ interface ModelByIdProps extends BaseFloatContextMenuProps {
59
+ uid: string;
60
+ modelClassName: string;
83
61
  }
84
62
  type FlowsFloatContextMenuProps = ModelProvidedProps | ModelByIdProps;
85
63
  /**
86
- * FlowsFloatContextMenu组件 - 悬浮配置图标组件
64
+ * FlowsFloatContextMenu组件 - 悬浮配置工具栏组件
87
65
  *
88
66
  * 功能特性:
89
67
  * - 鼠标悬浮显示右上角配置图标
90
68
  * - 点击图标显示配置菜单
91
69
  * - 支持删除功能
92
70
  * - Wrapper 模式支持
93
- * - 使用与 NocoBase x-settings 一致的样式
94
- * - 按flow分组显示steps
71
+ * - 使用 portal overlay 避免被宿主或祖先裁剪
72
+ * - 设置菜单与工具栏共享同一个 popup 容器
95
73
  *
96
74
  * 支持两种使用方式:
97
- * 1. 直接提供model: <FlowsFloatContextMenu model={myModel}>{children}</FlowsFloatContextMenu>
98
- * 2. 通过uid和modelClassName获取model: <FlowsFloatContextMenu uid="model1" modelClassName="MyModel">{children}</FlowsFloatContextMenu>
75
+ * 1. 直接提供 model: `<FlowsFloatContextMenu model={myModel}>{children}</FlowsFloatContextMenu>`
76
+ * 2. 通过 uid modelClassName 获取 model:
77
+ * `<FlowsFloatContextMenu uid="model1" modelClassName="MyModel">{children}</FlowsFloatContextMenu>`
99
78
  *
100
79
  * @param props.children 子组件,必须提供
101
- * @param props.enabled 是否启用悬浮菜单,默认为true
102
- * @param props.showDeleteButton 是否显示删除按钮,默认为true
103
- * @param props.showCopyUidButton 是否显示复制UID按钮,默认为true
80
+ * @param props.enabled 是否启用悬浮菜单,默认为 true
81
+ * @param props.showDeleteButton 是否显示删除按钮,默认为 true
82
+ * @param props.showCopyUidButton 是否显示复制 UID 按钮,默认为 true
104
83
  * @param props.containerStyle 容器自定义样式
84
+ * @param props.toolbarStyle 工具栏自定义样式;`top/left/right/bottom` 会作为 portal overlay 的 inset 使用
105
85
  * @param props.className 容器自定义类名
106
- * @param props.showTitle 是否在边框左上角显示模型title,默认为false
86
+ * @param props.showTitle 是否在边框左上角显示模型 title,默认为 false
107
87
  * @param props.settingsMenuLevel 设置菜单层级:1=仅当前模型(默认),2=包含子模型
108
88
  * @param props.extraToolbarItems 额外的工具栏项目,仅应用于此实例
109
89
  */