@nocobase/flow-engine 2.1.0-beta.8 → 2.2.0-alpha.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
@@ -54,9 +54,107 @@ var import_viewEvents = require("./viewEvents");
54
54
  var import_provider = require("../provider");
55
55
  var import_ViewScopedFlowEngine = require("../ViewScopedFlowEngine");
56
56
  var import_variablesParams = require("../utils/variablesParams");
57
+ var import_runViewBeforeClose = require("./runViewBeforeClose");
58
+ var import_inheritLayoutContext = require("./inheritLayoutContext");
57
59
  let uuid = 0;
58
60
  const GLOBAL_EMBED_CONTAINER_ID = "nocobase-embed-container";
59
61
  const EMBED_REPLACING_DATA_KEY = "nocobaseEmbedReplacing";
62
+ function isPromiseLike(value) {
63
+ return !!value && typeof value.then === "function";
64
+ }
65
+ __name(isPromiseLike, "isPromiseLike");
66
+ function closeReplacingGlobalEmbed(target, activeView) {
67
+ target.dataset[EMBED_REPLACING_DATA_KEY] = "1";
68
+ try {
69
+ const closeResult = activeView.close();
70
+ if (isPromiseLike(closeResult)) {
71
+ return Promise.resolve(closeResult).finally(() => {
72
+ delete target.dataset[EMBED_REPLACING_DATA_KEY];
73
+ });
74
+ }
75
+ delete target.dataset[EMBED_REPLACING_DATA_KEY];
76
+ return closeResult;
77
+ } catch (error) {
78
+ delete target.dataset[EMBED_REPLACING_DATA_KEY];
79
+ throw error;
80
+ }
81
+ }
82
+ __name(closeReplacingGlobalEmbed, "closeReplacingGlobalEmbed");
83
+ function createPendingGlobalEmbedView(openedPromise, inputArgs, preventClose) {
84
+ let openedPage;
85
+ const pendingActions = {};
86
+ const readyPromise = openedPromise.then(({ page }) => {
87
+ openedPage = page;
88
+ if (openedPage) {
89
+ if ("beforeClose" in pendingActions) {
90
+ openedPage.beforeClose = pendingActions.beforeClose;
91
+ }
92
+ if ("update" in pendingActions) {
93
+ openedPage.update(pendingActions.update);
94
+ }
95
+ if ("footer" in pendingActions) {
96
+ openedPage.setFooter(pendingActions.footer);
97
+ }
98
+ if ("header" in pendingActions) {
99
+ openedPage.setHeader(pendingActions.header);
100
+ }
101
+ if (pendingActions.destroyed) {
102
+ openedPage.destroy(pendingActions.destroyed.result);
103
+ }
104
+ }
105
+ return { page };
106
+ });
107
+ return Object.assign(
108
+ readyPromise.then(({ page }) => page ? page : false),
109
+ {
110
+ type: "embed",
111
+ inputArgs,
112
+ preventClose,
113
+ Header: null,
114
+ Footer: null,
115
+ get beforeClose() {
116
+ return (openedPage == null ? void 0 : openedPage.beforeClose) ?? pendingActions.beforeClose;
117
+ },
118
+ set beforeClose(value) {
119
+ if (openedPage) {
120
+ openedPage.beforeClose = value;
121
+ } else {
122
+ pendingActions.beforeClose = value;
123
+ }
124
+ },
125
+ close: /* @__PURE__ */ __name((result, force) => readyPromise.then(({ page }) => page ? page.close(result, force) : false), "close"),
126
+ destroy: /* @__PURE__ */ __name((result) => {
127
+ if (openedPage) {
128
+ openedPage.destroy(result);
129
+ } else {
130
+ pendingActions.destroyed = { result };
131
+ }
132
+ }, "destroy"),
133
+ update: /* @__PURE__ */ __name((newConfig) => {
134
+ if (openedPage) {
135
+ openedPage.update(newConfig);
136
+ } else {
137
+ pendingActions.update = { ...pendingActions.update, ...newConfig };
138
+ }
139
+ }, "update"),
140
+ setFooter: /* @__PURE__ */ __name((footer) => {
141
+ if (openedPage) {
142
+ openedPage.setFooter(footer);
143
+ } else {
144
+ pendingActions.footer = footer;
145
+ }
146
+ }, "setFooter"),
147
+ setHeader: /* @__PURE__ */ __name((header) => {
148
+ if (openedPage) {
149
+ openedPage.setHeader(header);
150
+ } else {
151
+ pendingActions.header = header;
152
+ }
153
+ }, "setHeader")
154
+ }
155
+ );
156
+ }
157
+ __name(createPendingGlobalEmbedView, "createPendingGlobalEmbedView");
60
158
  const PageElementsHolder = import_react.default.memo(
61
159
  import_react.default.forwardRef((props, ref) => {
62
160
  const [elements, patchElement] = (0, import_usePatchElement.default)();
@@ -67,168 +165,230 @@ const PageElementsHolder = import_react.default.memo(
67
165
  function usePage() {
68
166
  const holderRef = import_react.default.useRef(null);
69
167
  const globalEmbedActiveRef = import_react.default.useRef(null);
168
+ const globalEmbedReplacementTokenRef = import_react.default.useRef(0);
70
169
  const open = /* @__PURE__ */ __name((config, flowContext) => {
71
- var _a, _b, _c;
72
- const parentEngine = flowContext == null ? void 0 : flowContext.engine;
73
- uuid += 1;
74
- const pageRef = import_react.default.createRef();
75
- let closeFunc;
76
- let resolvePromise;
77
- const promise = new Promise((resolve) => {
78
- resolvePromise = resolve;
79
- });
80
- const FooterComponent = /* @__PURE__ */ __name(({ children }) => {
81
- import_react.default.useEffect(() => {
82
- var _a2;
83
- (_a2 = pageRef.current) == null ? void 0 : _a2.setFooter(children);
84
- return () => {
85
- var _a3;
86
- (_a3 = pageRef.current) == null ? void 0 : _a3.setFooter(null);
87
- };
88
- }, [children]);
89
- return null;
90
- }, "FooterComponent");
91
- const HeaderComponent = /* @__PURE__ */ __name((props) => {
92
- import_react.default.useEffect(() => {
93
- var _a2;
94
- (_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(props);
95
- return () => {
96
- var _a3;
97
- (_a3 = pageRef.current) == null ? void 0 : _a3.setHeader(null);
98
- };
99
- }, [props]);
100
- return null;
101
- }, "HeaderComponent");
102
170
  const {
103
171
  target,
104
172
  content,
105
173
  preventClose,
106
174
  inheritContext = true,
107
175
  inputArgs: viewInputArgs = {},
176
+ onOpenCancelled,
108
177
  ...restConfig
109
178
  } = config;
110
179
  const isGlobalEmbedContainer = target instanceof HTMLElement && target.id === GLOBAL_EMBED_CONTAINER_ID;
111
- if (isGlobalEmbedContainer && globalEmbedActiveRef.current) {
112
- try {
113
- target.dataset[EMBED_REPLACING_DATA_KEY] = "1";
114
- globalEmbedActiveRef.current.destroy();
115
- } finally {
116
- delete target.dataset[EMBED_REPLACING_DATA_KEY];
117
- globalEmbedActiveRef.current = null;
180
+ const openCurrentPage = /* @__PURE__ */ __name(() => {
181
+ var _a, _b, _c;
182
+ const parentEngine = flowContext == null ? void 0 : flowContext.engine;
183
+ uuid += 1;
184
+ const pageRef = import_react.default.createRef();
185
+ let closeFunc;
186
+ let resolvePromise;
187
+ const promise = new Promise((resolve) => {
188
+ resolvePromise = resolve;
189
+ });
190
+ const FooterComponent = /* @__PURE__ */ __name(({ children }) => {
191
+ import_react.default.useEffect(() => {
192
+ var _a2;
193
+ (_a2 = pageRef.current) == null ? void 0 : _a2.setFooter(children);
194
+ return () => {
195
+ var _a3;
196
+ (_a3 = pageRef.current) == null ? void 0 : _a3.setFooter(null);
197
+ };
198
+ }, [children]);
199
+ return null;
200
+ }, "FooterComponent");
201
+ const HeaderComponent = /* @__PURE__ */ __name((props) => {
202
+ import_react.default.useEffect(() => {
203
+ var _a2;
204
+ (_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(props);
205
+ return () => {
206
+ var _a3;
207
+ (_a3 = pageRef.current) == null ? void 0 : _a3.setHeader(null);
208
+ };
209
+ }, [props]);
210
+ return null;
211
+ }, "HeaderComponent");
212
+ const ctx = new import_flowContext.FlowContext();
213
+ const scopedEngine = (0, import_ViewScopedFlowEngine.createViewScopedEngine)(flowContext.engine);
214
+ const openerEngine = (0, import_viewEvents.resolveOpenerEngine)(parentEngine, scopedEngine);
215
+ ctx.defineProperty("engine", { value: scopedEngine });
216
+ ctx.addDelegate(scopedEngine.context);
217
+ if (inheritContext) {
218
+ ctx.addDelegate(flowContext);
219
+ } else {
220
+ ctx.addDelegate(flowContext.engine.context);
221
+ (0, import_inheritLayoutContext.inheritLayoutContextForDetachedView)(ctx, flowContext);
118
222
  }
119
- }
120
- const ctx = new import_flowContext.FlowContext();
121
- const scopedEngine = (0, import_ViewScopedFlowEngine.createViewScopedEngine)(flowContext.engine);
122
- const openerEngine = (0, import_viewEvents.resolveOpenerEngine)(parentEngine, scopedEngine);
123
- ctx.defineProperty("engine", { value: scopedEngine });
124
- ctx.addDelegate(scopedEngine.context);
125
- if (inheritContext) {
126
- ctx.addDelegate(flowContext);
127
- } else {
128
- ctx.addDelegate(flowContext.engine.context);
129
- }
130
- const currentPage = {
131
- type: "embed",
132
- inputArgs: viewInputArgs,
133
- preventClose: !!config.preventClose,
134
- destroy: /* @__PURE__ */ __name((result) => {
135
- var _a2, _b2, _c2, _d, _e;
136
- (_a2 = config.onClose) == null ? void 0 : _a2.call(config);
137
- resolvePromise == null ? void 0 : resolvePromise(result);
138
- (_b2 = pageRef.current) == null ? void 0 : _b2.destroy();
139
- closeFunc == null ? void 0 : closeFunc();
140
- if (isGlobalEmbedContainer) {
141
- globalEmbedActiveRef.current = null;
142
- }
143
- const isReplacing = isGlobalEmbedContainer && target instanceof HTMLElement && ((_c2 = target.dataset) == null ? void 0 : _c2[EMBED_REPLACING_DATA_KEY]) === "1";
144
- if (!isReplacing) {
145
- const openerEmitter = openerEngine == null ? void 0 : openerEngine.emitter;
146
- (0, import_viewEvents.bumpViewActivatedVersion)(openerEmitter);
147
- (_e = openerEmitter == null ? void 0 : openerEmitter.emit) == null ? void 0 : _e.call(openerEmitter, import_viewEvents.VIEW_ACTIVATED_EVENT, { type: "embed", viewUid: (_d = currentPage == null ? void 0 : currentPage.inputArgs) == null ? void 0 : _d.viewUid });
148
- }
149
- scopedEngine.unlinkFromStack();
150
- }, "destroy"),
151
- update: /* @__PURE__ */ __name((newConfig) => {
152
- var _a2;
153
- return (_a2 = pageRef.current) == null ? void 0 : _a2.update(newConfig);
154
- }, "update"),
155
- close: /* @__PURE__ */ __name((result, force) => {
156
- var _a2, _b2;
157
- if (preventClose && !force) {
158
- return;
223
+ let destroyed = false;
224
+ let closingPromise;
225
+ const currentPage = {
226
+ type: "embed",
227
+ inputArgs: viewInputArgs,
228
+ preventClose: !!config.preventClose,
229
+ beforeClose: void 0,
230
+ destroy: /* @__PURE__ */ __name((result) => {
231
+ var _a2, _b2, _c2, _d, _e;
232
+ if (destroyed) return;
233
+ destroyed = true;
234
+ (_a2 = config.onClose) == null ? void 0 : _a2.call(config);
235
+ resolvePromise == null ? void 0 : resolvePromise(result);
236
+ (_b2 = pageRef.current) == null ? void 0 : _b2.destroy();
237
+ closeFunc == null ? void 0 : closeFunc();
238
+ if (isGlobalEmbedContainer && globalEmbedActiveRef.current === currentPage) {
239
+ globalEmbedActiveRef.current = null;
240
+ }
241
+ const isReplacing = isGlobalEmbedContainer && target instanceof HTMLElement && ((_c2 = target.dataset) == null ? void 0 : _c2[EMBED_REPLACING_DATA_KEY]) === "1";
242
+ if (!isReplacing) {
243
+ const openerEmitter = openerEngine == null ? void 0 : openerEngine.emitter;
244
+ (0, import_viewEvents.bumpViewActivatedVersion)(openerEmitter);
245
+ (_e = openerEmitter == null ? void 0 : openerEmitter.emit) == null ? void 0 : _e.call(openerEmitter, import_viewEvents.VIEW_ACTIVATED_EVENT, { type: "embed", viewUid: (_d = currentPage == null ? void 0 : currentPage.inputArgs) == null ? void 0 : _d.viewUid });
246
+ }
247
+ scopedEngine.unlinkFromStack();
248
+ }, "destroy"),
249
+ update: /* @__PURE__ */ __name((newConfig) => {
250
+ var _a2;
251
+ return (_a2 = pageRef.current) == null ? void 0 : _a2.update(newConfig);
252
+ }, "update"),
253
+ close: /* @__PURE__ */ __name((result, force) => {
254
+ if (destroyed) {
255
+ return Promise.resolve(true);
256
+ }
257
+ if (closingPromise) {
258
+ return closingPromise;
259
+ }
260
+ closingPromise = (async () => {
261
+ var _a2, _b2;
262
+ try {
263
+ if (preventClose && !force) {
264
+ closingPromise = void 0;
265
+ return false;
266
+ }
267
+ const shouldClose = await (0, import_runViewBeforeClose.runViewBeforeClose)(currentPage, { result, force });
268
+ if (destroyed) {
269
+ return true;
270
+ }
271
+ if (!shouldClose) {
272
+ closingPromise = void 0;
273
+ return false;
274
+ }
275
+ if (config.triggerByRouter && ((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.navigation) == null ? void 0 : _b2.back)) {
276
+ config.inputArgs.navigation.back();
277
+ return true;
278
+ }
279
+ currentPage.destroy(result);
280
+ return true;
281
+ } catch (error) {
282
+ if (!destroyed) {
283
+ closingPromise = void 0;
284
+ }
285
+ throw error;
286
+ }
287
+ })();
288
+ return closingPromise;
289
+ }, "close"),
290
+ Header: HeaderComponent,
291
+ Footer: FooterComponent,
292
+ setFooter: /* @__PURE__ */ __name((footer) => {
293
+ var _a2;
294
+ (_a2 = pageRef.current) == null ? void 0 : _a2.setFooter(footer);
295
+ }, "setFooter"),
296
+ setHeader: /* @__PURE__ */ __name((header) => {
297
+ var _a2;
298
+ (_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(header);
299
+ }, "setHeader"),
300
+ navigation: (_a = config.inputArgs) == null ? void 0 : _a.navigation,
301
+ get record() {
302
+ return (0, import_variablesParams.getViewRecordFromParent)(flowContext, ctx);
159
303
  }
160
- if (config.triggerByRouter && ((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.navigation) == null ? void 0 : _b2.back)) {
161
- config.inputArgs.navigation.back();
162
- return;
304
+ };
305
+ ctx.defineProperty("view", {
306
+ get: /* @__PURE__ */ __name(() => currentPage, "get"),
307
+ // 仅当访问关联字段或前端无本地记录数据时,才交给服务端解析
308
+ resolveOnServer: (0, import_variablesParams.createViewRecordResolveOnServer)(ctx, () => (0, import_variablesParams.getViewRecordFromParent)(flowContext, ctx))
309
+ });
310
+ (0, import_createViewMeta.registerPopupVariable)(ctx, currentPage);
311
+ const PageWithContext = (0, import__.observer)(
312
+ () => {
313
+ var _a2, _b2, _c2, _d;
314
+ const mountedRef = import_react.default.useRef(false);
315
+ const pageContent = import_react.default.useMemo(
316
+ () => typeof content === "function" ? content(currentPage, ctx) : content,
317
+ []
318
+ );
319
+ void ctx.themeToken;
320
+ import_react.default.useEffect(() => {
321
+ var _a3;
322
+ (_a3 = config.onOpen) == null ? void 0 : _a3.call(config, currentPage, ctx);
323
+ }, []);
324
+ if (((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.hidden) == null ? void 0 : _b2.value) && !mountedRef.current) {
325
+ return null;
326
+ }
327
+ mountedRef.current = true;
328
+ return /* @__PURE__ */ import_react.default.createElement(
329
+ import_PageComponent.PageComponent,
330
+ {
331
+ ref: pageRef,
332
+ hidden: (_d = (_c2 = config.inputArgs) == null ? void 0 : _c2.hidden) == null ? void 0 : _d.value,
333
+ ...restConfig,
334
+ onClose: () => {
335
+ return currentPage.close(config.result);
336
+ }
337
+ },
338
+ pageContent
339
+ );
340
+ },
341
+ {
342
+ displayName: "PageWithContext"
163
343
  }
164
- currentPage.destroy(result);
165
- }, "close"),
166
- Header: HeaderComponent,
167
- Footer: FooterComponent,
168
- setFooter: /* @__PURE__ */ __name((footer) => {
169
- var _a2;
170
- (_a2 = pageRef.current) == null ? void 0 : _a2.setFooter(footer);
171
- }, "setFooter"),
172
- setHeader: /* @__PURE__ */ __name((header) => {
173
- var _a2;
174
- (_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(header);
175
- }, "setHeader"),
176
- navigation: (_a = config.inputArgs) == null ? void 0 : _a.navigation,
177
- get record() {
178
- return (0, import_variablesParams.getViewRecordFromParent)(flowContext, ctx);
344
+ );
345
+ const key = (viewInputArgs == null ? void 0 : viewInputArgs.viewUid) || `page-${uuid}`;
346
+ const page = /* @__PURE__ */ import_react.default.createElement(import_provider.FlowEngineProvider, { key, engine: scopedEngine }, /* @__PURE__ */ import_react.default.createElement(import_FlowContextProvider.FlowViewContextProvider, { context: ctx }, /* @__PURE__ */ import_react.default.createElement(PageWithContext, null)));
347
+ if (target && target instanceof HTMLElement) {
348
+ closeFunc = (_b = holderRef.current) == null ? void 0 : _b.patchElement(import_react_dom.default.createPortal(page, target, key));
349
+ } else {
350
+ closeFunc = (_c = holderRef.current) == null ? void 0 : _c.patchElement(page);
179
351
  }
180
- };
181
- ctx.defineProperty("view", {
182
- get: /* @__PURE__ */ __name(() => currentPage, "get"),
183
- // 仅当访问关联字段或前端无本地记录数据时,才交给服务端解析
184
- resolveOnServer: (0, import_variablesParams.createViewRecordResolveOnServer)(ctx, () => (0, import_variablesParams.getViewRecordFromParent)(flowContext, ctx))
185
- });
186
- (0, import_createViewMeta.registerPopupVariable)(ctx, currentPage);
187
- const PageWithContext = (0, import__.observer)(
188
- () => {
189
- var _a2, _b2, _c2, _d;
190
- const mountedRef = import_react.default.useRef(false);
191
- const pageContent = import_react.default.useMemo(
192
- () => typeof content === "function" ? content(currentPage, ctx) : content,
193
- []
194
- );
195
- void ctx.themeToken;
196
- import_react.default.useEffect(() => {
197
- var _a3;
198
- (_a3 = config.onOpen) == null ? void 0 : _a3.call(config, currentPage, ctx);
199
- }, []);
200
- if (((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.hidden) == null ? void 0 : _b2.value) && !mountedRef.current) {
201
- return null;
202
- }
203
- mountedRef.current = true;
204
- return /* @__PURE__ */ import_react.default.createElement(
205
- import_PageComponent.PageComponent,
206
- {
207
- ref: pageRef,
208
- hidden: (_d = (_c2 = config.inputArgs) == null ? void 0 : _c2.hidden) == null ? void 0 : _d.value,
209
- ...restConfig,
210
- onClose: () => {
211
- currentPage.close(config.result);
352
+ if (isGlobalEmbedContainer) {
353
+ globalEmbedActiveRef.current = currentPage;
354
+ }
355
+ return Object.assign(promise, currentPage);
356
+ }, "openCurrentPage");
357
+ if (isGlobalEmbedContainer && globalEmbedActiveRef.current) {
358
+ const replacementToken = globalEmbedReplacementTokenRef.current += 1;
359
+ const cancelOpen = /* @__PURE__ */ __name(() => onOpenCancelled == null ? void 0 : onOpenCancelled(), "cancelOpen");
360
+ let closeResult;
361
+ try {
362
+ closeResult = closeReplacingGlobalEmbed(target, globalEmbedActiveRef.current);
363
+ } catch (error) {
364
+ cancelOpen();
365
+ throw error;
366
+ }
367
+ if (isPromiseLike(closeResult)) {
368
+ return createPendingGlobalEmbedView(
369
+ Promise.resolve(closeResult).then(
370
+ (closed) => {
371
+ if (closed === false || replacementToken !== globalEmbedReplacementTokenRef.current) {
372
+ cancelOpen();
373
+ return { page: null };
374
+ }
375
+ return { page: openCurrentPage() };
376
+ },
377
+ (error) => {
378
+ cancelOpen();
379
+ throw error;
212
380
  }
213
- },
214
- pageContent
381
+ ),
382
+ viewInputArgs,
383
+ !!config.preventClose
215
384
  );
216
- },
217
- {
218
- displayName: "PageWithContext"
219
385
  }
220
- );
221
- const key = (viewInputArgs == null ? void 0 : viewInputArgs.viewUid) || `page-${uuid}`;
222
- const page = /* @__PURE__ */ import_react.default.createElement(import_provider.FlowEngineProvider, { key, engine: scopedEngine }, /* @__PURE__ */ import_react.default.createElement(import_FlowContextProvider.FlowViewContextProvider, { context: ctx }, /* @__PURE__ */ import_react.default.createElement(PageWithContext, null)));
223
- if (target && target instanceof HTMLElement) {
224
- closeFunc = (_b = holderRef.current) == null ? void 0 : _b.patchElement(import_react_dom.default.createPortal(page, target, key));
225
- } else {
226
- closeFunc = (_c = holderRef.current) == null ? void 0 : _c.patchElement(page);
227
- }
228
- if (isGlobalEmbedContainer) {
229
- globalEmbedActiveRef.current = { destroy: currentPage.destroy };
386
+ if (closeResult === false) {
387
+ cancelOpen();
388
+ return createPendingGlobalEmbedView(Promise.resolve({ page: null }), viewInputArgs, !!config.preventClose);
389
+ }
230
390
  }
231
- return Object.assign(promise, currentPage);
391
+ return openCurrentPage();
232
392
  }, "open");
233
393
  const api = import_react.default.useMemo(() => ({ open }), []);
234
394
  return [api, /* @__PURE__ */ import_react.default.createElement(PageElementsHolder, { key: "page-holder", ref: holderRef })];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "2.1.0-beta.8",
3
+ "version": "2.2.0-alpha.1",
4
4
  "private": false,
5
5
  "description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
6
6
  "main": "lib/index.js",
@@ -8,9 +8,10 @@
8
8
  "dependencies": {
9
9
  "@formily/antd-v5": "1.x",
10
10
  "@formily/reactive": "2.x",
11
- "@nocobase/sdk": "2.1.0-beta.8",
12
- "@nocobase/shared": "2.1.0-beta.8",
11
+ "@nocobase/sdk": "2.2.0-alpha.1",
12
+ "@nocobase/shared": "2.2.0-alpha.1",
13
13
  "ahooks": "^3.7.2",
14
+ "axios": "^1.7.0",
14
15
  "dayjs": "^1.11.9",
15
16
  "dompurify": "^3.0.2",
16
17
  "lodash": "^4.x",
@@ -36,5 +37,5 @@
36
37
  ],
37
38
  "author": "NocoBase Team",
38
39
  "license": "Apache-2.0",
39
- "gitHead": "5099d561c5467292414c1e77ad6bad3730d97344"
40
+ "gitHead": "303663aba6c6eefa27e6a6435b4c0352074ec40f"
40
41
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  import React, { createContext, useContext } from 'react';
11
11
  import { FlowContext, FlowEngineContext } from './flowContext';
12
- import { FlowView } from './views/FlowView';
12
+ import { FlowView, FlowViewer } from './views/FlowView';
13
13
 
14
14
  export const FlowReactContext = createContext<FlowContext>(null);
15
15
  export const FlowViewContext = createContext<FlowContext>(null);
@@ -40,3 +40,11 @@ export function useFlowView() {
40
40
  const ctx = useFlowContext();
41
41
  return ctx.view as FlowView;
42
42
  }
43
+
44
+ /**
45
+ * Access the `FlowViewer` that opens new drawers / modals / pages (`viewer.drawer({...})`, `viewer.modal({...})`, etc.). This is the counterpart to `useFlowView()`: `useFlowView()` returns the *current* mounted view (use it to close yourself, render Header/Footer slots, etc.), while `useFlowViewer()` returns the surface that lets you open a *new* view from inside any flow-context subtree.
46
+ */
47
+ export function useFlowViewer() {
48
+ const ctx = useFlowContext();
49
+ return ctx.viewer as FlowViewer;
50
+ }
@@ -11,7 +11,7 @@ import { describe, it, expect, vi } from 'vitest';
11
11
  import { FlowContext } from '../flowContext';
12
12
  import { FlowEngine } from '../flowEngine';
13
13
  import type { FlowView } from '../views/FlowView';
14
- import { createPopupMeta } from '../views/createViewMeta';
14
+ import { buildPopupRuntime, createPopupMeta } from '../views/createViewMeta';
15
15
 
16
16
  describe('createPopupMeta - popup variables', () => {
17
17
  function makeCtx() {
@@ -23,6 +23,62 @@ describe('createPopupMeta - popup variables', () => {
23
23
  return { engine, ctx };
24
24
  }
25
25
 
26
+ function makeNestedPopupView(viewUid: string, filterByTk: number): FlowView {
27
+ return {
28
+ type: 'drawer',
29
+ inputArgs: {
30
+ viewUid,
31
+ filterByTk,
32
+ sourceId: 13,
33
+ },
34
+ Header: null,
35
+ Footer: null,
36
+ close: () => void 0,
37
+ update: () => void 0,
38
+ navigation: {
39
+ viewStack: [
40
+ { viewUid: 'base-page-uid' },
41
+ { viewUid: 'parent-popup-uid', filterByTk: 13, sourceId: 13 },
42
+ { viewUid: 'child-popup-uid', filterByTk: 24, sourceId: 13 },
43
+ ],
44
+ } as any,
45
+ } as any;
46
+ }
47
+
48
+ function mockNestedPopupModels(engine: FlowEngine) {
49
+ vi.spyOn(engine as any, 'getModel').mockImplementation((uid: string) => {
50
+ if (uid === 'parent-popup-uid') {
51
+ return {
52
+ getStepParams: vi.fn((_fk: string, sk: string) =>
53
+ sk === 'openView'
54
+ ? {
55
+ collectionName: 'users',
56
+ dataSourceKey: 'main',
57
+ associationName: 'users.orgs',
58
+ }
59
+ : undefined,
60
+ ),
61
+ };
62
+ }
63
+ if (uid === 'child-popup-uid') {
64
+ return {
65
+ getStepParams: vi.fn((_fk: string, sk: string) =>
66
+ sk === 'openView'
67
+ ? {
68
+ collectionName: 'orgs',
69
+ dataSourceKey: 'main',
70
+ associationName: 'users.orgs',
71
+ }
72
+ : undefined,
73
+ ),
74
+ };
75
+ }
76
+ return {
77
+ getStepParams: vi.fn(() => undefined),
78
+ };
79
+ });
80
+ }
81
+
26
82
  it('buildVariablesParams(record) uses anchor view instead of ctx.view', async () => {
27
83
  const { engine, ctx } = makeCtx();
28
84
 
@@ -108,6 +164,64 @@ describe('createPopupMeta - popup variables', () => {
108
164
  expect(vars.record.collection).not.toBe('comments');
109
165
  });
110
166
 
167
+ it('buildPopupRuntime anchors current popup even when the navigation stack already has a child popup', async () => {
168
+ const { engine, ctx } = makeCtx();
169
+ const parentView = makeNestedPopupView('parent-popup-uid', 13);
170
+ mockNestedPopupModels(engine);
171
+
172
+ const popup = await buildPopupRuntime(ctx, parentView);
173
+
174
+ expect(popup?.uid).toBe('parent-popup-uid');
175
+ expect(popup?.resource).toEqual({
176
+ dataSourceKey: 'main',
177
+ collectionName: 'users',
178
+ associationName: 'users.orgs',
179
+ filterByTk: 13,
180
+ sourceId: 13,
181
+ });
182
+ });
183
+
184
+ it('buildVariablesParams(record) keeps the parent view record when a child popup is open', async () => {
185
+ const { engine, ctx } = makeCtx();
186
+ const parentView = makeNestedPopupView('parent-popup-uid', 13);
187
+ mockNestedPopupModels(engine);
188
+
189
+ const meta = (await createPopupMeta(ctx, parentView)())!;
190
+ const vars = (await meta.buildVariablesParams!(ctx)) as any;
191
+
192
+ expect(vars.record).toEqual({
193
+ collection: 'users',
194
+ dataSourceKey: 'main',
195
+ filterByTk: 13,
196
+ associationName: 'users.orgs',
197
+ sourceId: 13,
198
+ });
199
+ });
200
+
201
+ it('buildVariablesParams(parent.record) is still relative to the child popup view', async () => {
202
+ const { engine, ctx } = makeCtx();
203
+ const childView = makeNestedPopupView('child-popup-uid', 24);
204
+ mockNestedPopupModels(engine);
205
+
206
+ const meta = (await createPopupMeta(ctx, childView)())!;
207
+ const vars = (await meta.buildVariablesParams!(ctx)) as any;
208
+
209
+ expect(vars.record).toEqual({
210
+ collection: 'orgs',
211
+ dataSourceKey: 'main',
212
+ filterByTk: 24,
213
+ associationName: 'users.orgs',
214
+ sourceId: 13,
215
+ });
216
+ expect(vars.parent.record).toEqual({
217
+ collection: 'users',
218
+ dataSourceKey: 'main',
219
+ filterByTk: 13,
220
+ sourceId: 13,
221
+ associationName: 'users.orgs',
222
+ });
223
+ });
224
+
111
225
  it('properties() provides a record factory node (lazy) with title', async () => {
112
226
  const { engine, ctx } = makeCtx();
113
227
  // 只要能通过 anchorView 推断到集合名和主键即可;集合详情在懒加载时再取