@nocobase/flow-engine 2.0.0-beta.13 → 2.0.0-beta.14

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.
@@ -34,10 +34,12 @@ module.exports = __toCommonJS(runjsTemplateCompat_exports);
34
34
  var import_jsxTransform = require("./jsxTransform");
35
35
  const RESOLVE_JSON_TEMPLATE_CALL = "ctx.resolveJsonTemplate";
36
36
  const CTX_TEMPLATE_MARKER_RE = /\{\{\s*ctx(?:\.|\[|\?\.)/;
37
+ const CTX_LIBS_MARKER_RE = /\bctx(?:\?\.|\.)libs\b/;
37
38
  const STRINGIFY_HELPER_BASE_NAME = "__runjs_templateValueToString";
38
39
  const BARE_PLACEHOLDER_VAR_RE = /\b__runjs_ctx_tpl_\d+\b/;
39
40
  const STRINGIFY_HELPER_RE = /\b__runjs_templateValueToString(?:_\d+)?\b/;
40
41
  const PREPROCESSED_MARKER_RE = /\b__runjs_ctx_tpl_\d+\b|\b__runjs_templateValueToString(?:_\d+)?\b/;
42
+ const ENSURE_LIBS_MARKER_RE = /\b__runjs_ensure_libs\b/;
41
43
  const PREPARE_RUNJS_CODE_CACHE_LIMIT = 256;
42
44
  const PREPARE_RUNJS_CODE_CACHE = {
43
45
  withTemplates: /* @__PURE__ */ new Map(),
@@ -186,6 +188,354 @@ function skipWhitespaceBackward(code, start) {
186
188
  return i;
187
189
  }
188
190
  __name(skipWhitespaceBackward, "skipWhitespaceBackward");
191
+ function skipSpaceAndCommentsForward(code, start) {
192
+ let i = start;
193
+ for (; ; ) {
194
+ i = skipWhitespaceForward(code, i);
195
+ const ch = code[i];
196
+ const next = code[i + 1];
197
+ if (ch === "/" && next === "/") {
198
+ i = readLineComment(code, i);
199
+ continue;
200
+ }
201
+ if (ch === "/" && next === "*") {
202
+ i = readBlockComment(code, i);
203
+ continue;
204
+ }
205
+ return i;
206
+ }
207
+ }
208
+ __name(skipSpaceAndCommentsForward, "skipSpaceAndCommentsForward");
209
+ function isIdentStartChar(ch) {
210
+ return !!ch && /[A-Za-z_$]/.test(ch);
211
+ }
212
+ __name(isIdentStartChar, "isIdentStartChar");
213
+ function readIdentifier(code, start) {
214
+ const first = code[start];
215
+ if (!isIdentStartChar(first)) return null;
216
+ let i = start + 1;
217
+ while (i < code.length && isIdentChar(code[i])) i += 1;
218
+ return { name: code.slice(start, i), end: i };
219
+ }
220
+ __name(readIdentifier, "readIdentifier");
221
+ function readSimpleStringLiteralValue(code, start, quote) {
222
+ if (code[start] !== quote) return null;
223
+ let i = start + 1;
224
+ let value = "";
225
+ while (i < code.length) {
226
+ const ch = code[i];
227
+ if (ch === "\\") {
228
+ const n = code[i + 1];
229
+ if (typeof n === "undefined") break;
230
+ value += n;
231
+ i += 2;
232
+ continue;
233
+ }
234
+ if (ch === quote) {
235
+ return { value, end: i + 1 };
236
+ }
237
+ value += ch;
238
+ i += 1;
239
+ }
240
+ return null;
241
+ }
242
+ __name(readSimpleStringLiteralValue, "readSimpleStringLiteralValue");
243
+ function readsCtxLibsBase(code, index) {
244
+ if (!code.startsWith("ctx", index)) return null;
245
+ const before = index > 0 ? code[index - 1] : "";
246
+ const after = code[index + 3] || "";
247
+ if (isIdentChar(before) || isIdentChar(after)) return null;
248
+ const tail = code.slice(index + 3);
249
+ if (tail.startsWith(".libs")) return index + 3 + 5;
250
+ if (tail.startsWith("?.libs")) return index + 3 + 6;
251
+ return null;
252
+ }
253
+ __name(readsCtxLibsBase, "readsCtxLibsBase");
254
+ function tryReadCtxLibAccess(code, index) {
255
+ const baseEnd = readsCtxLibsBase(code, index);
256
+ if (baseEnd === null) return null;
257
+ let i = skipSpaceAndCommentsForward(code, baseEnd);
258
+ const ch = code[i];
259
+ const next = code[i + 1];
260
+ const next2 = code[i + 2];
261
+ if (ch === "?" && next === "." && next2 === "[") {
262
+ i = skipSpaceAndCommentsForward(code, i + 3);
263
+ const q = code[i];
264
+ if (q === "'" || q === '"') {
265
+ const parsed = readSimpleStringLiteralValue(code, i, q);
266
+ if (!parsed) return { end: i + 1 };
267
+ let j = skipSpaceAndCommentsForward(code, parsed.end);
268
+ if (code[j] === "]") j += 1;
269
+ return { end: j, key: parsed.value };
270
+ }
271
+ return { end: i };
272
+ }
273
+ if (ch === "[") {
274
+ i = skipSpaceAndCommentsForward(code, i + 1);
275
+ const q = code[i];
276
+ if (q === "'" || q === '"') {
277
+ const parsed = readSimpleStringLiteralValue(code, i, q);
278
+ if (!parsed) return { end: i + 1 };
279
+ let j = skipSpaceAndCommentsForward(code, parsed.end);
280
+ if (code[j] === "]") j += 1;
281
+ return { end: j, key: parsed.value };
282
+ }
283
+ return { end: i };
284
+ }
285
+ if (ch === "?" && next === ".") {
286
+ i = skipSpaceAndCommentsForward(code, i + 2);
287
+ const ident = readIdentifier(code, i);
288
+ if (!ident) return { end: i };
289
+ return { end: ident.end, key: ident.name };
290
+ }
291
+ if (ch === ".") {
292
+ i = skipSpaceAndCommentsForward(code, i + 1);
293
+ const ident = readIdentifier(code, i);
294
+ if (!ident) return { end: i };
295
+ return { end: ident.end, key: ident.name };
296
+ }
297
+ return { end: baseEnd };
298
+ }
299
+ __name(tryReadCtxLibAccess, "tryReadCtxLibAccess");
300
+ function parseDestructuredKeysFromObjectPattern(pattern) {
301
+ const out = [];
302
+ const src = String(pattern ?? "");
303
+ let itemStart = 0;
304
+ let braceDepth = 0;
305
+ let bracketDepth = 0;
306
+ let parenDepth = 0;
307
+ const pushItem = /* @__PURE__ */ __name((raw) => {
308
+ const s = String(raw ?? "").trim();
309
+ if (!s) return;
310
+ if (s.startsWith("...")) return;
311
+ let keyPart = s;
312
+ let i2 = 0;
313
+ let b = 0;
314
+ let br = 0;
315
+ let p = 0;
316
+ while (i2 < s.length) {
317
+ const ch = s[i2];
318
+ const next = s[i2 + 1];
319
+ if (ch === "/" && next === "/") {
320
+ break;
321
+ }
322
+ if (ch === "/" && next === "*") {
323
+ const end = s.indexOf("*/", i2 + 2);
324
+ i2 = end === -1 ? s.length : end + 2;
325
+ continue;
326
+ }
327
+ if (ch === "'" || ch === '"') {
328
+ let j = i2 + 1;
329
+ while (j < s.length) {
330
+ const c = s[j];
331
+ if (c === "\\") {
332
+ j += 2;
333
+ continue;
334
+ }
335
+ j += 1;
336
+ if (c === ch) break;
337
+ }
338
+ i2 = j;
339
+ continue;
340
+ }
341
+ if (ch === "`") {
342
+ let j = i2 + 1;
343
+ while (j < s.length) {
344
+ const c = s[j];
345
+ if (c === "\\") {
346
+ j += 2;
347
+ continue;
348
+ }
349
+ j += 1;
350
+ if (c === "`") break;
351
+ }
352
+ i2 = j;
353
+ continue;
354
+ }
355
+ if (ch === "{") b += 1;
356
+ else if (ch === "}") b -= 1;
357
+ else if (ch === "[") br += 1;
358
+ else if (ch === "]") br -= 1;
359
+ else if (ch === "(") p += 1;
360
+ else if (ch === ")") p -= 1;
361
+ if (b === 0 && br === 0 && p === 0) {
362
+ if (ch === ":") {
363
+ keyPart = s.slice(0, i2).trim();
364
+ break;
365
+ }
366
+ if (ch === "=") {
367
+ keyPart = s.slice(0, i2).trim();
368
+ break;
369
+ }
370
+ }
371
+ i2 += 1;
372
+ }
373
+ if (!keyPart) return;
374
+ if (keyPart.startsWith("'") || keyPart.startsWith('"')) {
375
+ const q = keyPart[0];
376
+ const parsed = readSimpleStringLiteralValue(keyPart, 0, q);
377
+ if (parsed) out.push(parsed.value);
378
+ return;
379
+ }
380
+ const m = keyPart.match(/^[A-Za-z_$][A-Za-z0-9_$]*/);
381
+ if (m) out.push(m[0]);
382
+ }, "pushItem");
383
+ let i = 0;
384
+ while (i < src.length) {
385
+ const ch = src[i];
386
+ const next = src[i + 1];
387
+ if (ch === "/" && next === "/") {
388
+ break;
389
+ }
390
+ if (ch === "/" && next === "*") {
391
+ const end = src.indexOf("*/", i + 2);
392
+ i = end === -1 ? src.length : end + 2;
393
+ continue;
394
+ }
395
+ if (ch === "'" || ch === '"') {
396
+ i = readQuotedString(src, i, ch);
397
+ continue;
398
+ }
399
+ if (ch === "`") {
400
+ i = readTemplateLiteral(src, i);
401
+ continue;
402
+ }
403
+ if (ch === "{") braceDepth += 1;
404
+ else if (ch === "}") braceDepth -= 1;
405
+ else if (ch === "[") bracketDepth += 1;
406
+ else if (ch === "]") bracketDepth -= 1;
407
+ else if (ch === "(") parenDepth += 1;
408
+ else if (ch === ")") parenDepth -= 1;
409
+ if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0 && ch === ",") {
410
+ pushItem(src.slice(itemStart, i));
411
+ itemStart = i + 1;
412
+ }
413
+ i += 1;
414
+ }
415
+ pushItem(src.slice(itemStart));
416
+ return out;
417
+ }
418
+ __name(parseDestructuredKeysFromObjectPattern, "parseDestructuredKeysFromObjectPattern");
419
+ function extractUsedCtxLibKeys(code) {
420
+ if (!CTX_LIBS_MARKER_RE.test(code)) return [];
421
+ const out = /* @__PURE__ */ new Set();
422
+ const scanTemplateExpression = /* @__PURE__ */ __name((start) => {
423
+ let i2 = start;
424
+ let braceDepth = 1;
425
+ while (i2 < code.length && braceDepth > 0) {
426
+ const ch = code[i2];
427
+ const next = code[i2 + 1];
428
+ if (ch === "/" && next === "/") {
429
+ i2 = readLineComment(code, i2);
430
+ continue;
431
+ }
432
+ if (ch === "/" && next === "*") {
433
+ i2 = readBlockComment(code, i2);
434
+ continue;
435
+ }
436
+ if (ch === "'" || ch === '"') {
437
+ i2 = readQuotedString(code, i2, ch);
438
+ continue;
439
+ }
440
+ if (ch === "`") {
441
+ i2 = scanTemplateLiteral(i2 + 1);
442
+ continue;
443
+ }
444
+ const access = tryReadCtxLibAccess(code, i2);
445
+ if (access) {
446
+ if (access.key) out.add(access.key);
447
+ i2 = access.end;
448
+ continue;
449
+ }
450
+ if (ch === "{") braceDepth += 1;
451
+ else if (ch === "}") braceDepth -= 1;
452
+ i2 += 1;
453
+ }
454
+ return i2;
455
+ }, "scanTemplateExpression");
456
+ const scanTemplateLiteral = /* @__PURE__ */ __name((start) => {
457
+ let i2 = start;
458
+ while (i2 < code.length) {
459
+ const ch = code[i2];
460
+ const next = code[i2 + 1];
461
+ if (ch === "\\") {
462
+ i2 += 2;
463
+ continue;
464
+ }
465
+ if (ch === "`") return i2 + 1;
466
+ if (ch === "$" && next === "{") {
467
+ i2 = scanTemplateExpression(i2 + 2);
468
+ continue;
469
+ }
470
+ i2 += 1;
471
+ }
472
+ return i2;
473
+ }, "scanTemplateLiteral");
474
+ let i = 0;
475
+ while (i < code.length) {
476
+ const ch = code[i];
477
+ const next = code[i + 1];
478
+ if (ch === "/" && next === "/") {
479
+ i = readLineComment(code, i);
480
+ continue;
481
+ }
482
+ if (ch === "/" && next === "*") {
483
+ i = readBlockComment(code, i);
484
+ continue;
485
+ }
486
+ if (ch === "'" || ch === '"') {
487
+ i = readQuotedString(code, i, ch);
488
+ continue;
489
+ }
490
+ if (ch === "`") {
491
+ i = scanTemplateLiteral(i + 1);
492
+ continue;
493
+ }
494
+ const access = tryReadCtxLibAccess(code, i);
495
+ if (access) {
496
+ if (access.key) out.add(access.key);
497
+ i = access.end;
498
+ continue;
499
+ }
500
+ if (ch === "=" && next !== "=" && next !== ">" && next !== "<") {
501
+ const right = skipSpaceAndCommentsForward(code, i + 1);
502
+ const rhsBaseEnd = readsCtxLibsBase(code, right);
503
+ if (rhsBaseEnd !== null) {
504
+ const leftEnd = skipWhitespaceBackward(code, i - 1);
505
+ if (code[leftEnd] === "}") {
506
+ let depth = 0;
507
+ let j = leftEnd;
508
+ while (j >= 0) {
509
+ const c = code[j];
510
+ if (c === "}") depth += 1;
511
+ else if (c === "{") {
512
+ depth -= 1;
513
+ if (depth === 0) break;
514
+ }
515
+ j -= 1;
516
+ }
517
+ if (j >= 0 && code[j] === "{") {
518
+ const inner = code.slice(j + 1, leftEnd);
519
+ for (const k of parseDestructuredKeysFromObjectPattern(inner)) out.add(k);
520
+ }
521
+ }
522
+ }
523
+ }
524
+ i += 1;
525
+ }
526
+ return Array.from(out);
527
+ }
528
+ __name(extractUsedCtxLibKeys, "extractUsedCtxLibKeys");
529
+ function injectEnsureLibsPreamble(code) {
530
+ if (!CTX_LIBS_MARKER_RE.test(code)) return code;
531
+ if (ENSURE_LIBS_MARKER_RE.test(code)) return code;
532
+ const keys = extractUsedCtxLibKeys(code);
533
+ if (!keys.length) return code;
534
+ return `/* __runjs_ensure_libs */
535
+ await ctx.__ensureLibs(${JSON.stringify(keys)});
536
+ ${code}`;
537
+ }
538
+ __name(injectEnsureLibsPreamble, "injectEnsureLibsPreamble");
189
539
  function isObjectLikeKeyPosition(code, tokenStart, tokenEnd) {
190
540
  const next = skipWhitespaceForward(code, tokenEnd);
191
541
  if (code[next] !== ":") return false;
@@ -371,10 +721,11 @@ async function prepareRunJsCode(code, options = {}) {
371
721
  const cached = lruGet(cache, src);
372
722
  if (cached) return await cached;
373
723
  const task = (async () => {
374
- if (!preprocessTemplates) return await (0, import_jsxTransform.compileRunJs)(src);
724
+ if (!preprocessTemplates) return injectEnsureLibsPreamble(await (0, import_jsxTransform.compileRunJs)(src));
375
725
  const preBare = preprocessRunJsTemplates(src, { processStringLiterals: false });
376
726
  const jsxCompiled = await (0, import_jsxTransform.compileRunJs)(preBare);
377
- return preprocessRunJsTemplates(jsxCompiled, { processBarePlaceholders: false });
727
+ const out = preprocessRunJsTemplates(jsxCompiled, { processBarePlaceholders: false });
728
+ return injectEnsureLibsPreamble(out);
378
729
  })();
379
730
  lruSet(cache, src, task, PREPARE_RUNJS_CODE_CACHE_LIMIT);
380
731
  try {
@@ -8,6 +8,6 @@
8
8
  */
9
9
  export { useDialog } from './useDialog';
10
10
  export { useDrawer } from './useDrawer';
11
- export { usePage } from './usePage';
11
+ export { usePage, GLOBAL_EMBED_CONTAINER_ID, EMBED_REPLACING_DATA_KEY } from './usePage';
12
12
  export { usePopover } from './usePopover';
13
13
  export { ViewNavigation } from './ViewNavigation';
@@ -26,6 +26,8 @@ var __copyProps = (to, from, except, desc) => {
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
  var views_exports = {};
28
28
  __export(views_exports, {
29
+ EMBED_REPLACING_DATA_KEY: () => import_usePage.EMBED_REPLACING_DATA_KEY,
30
+ GLOBAL_EMBED_CONTAINER_ID: () => import_usePage.GLOBAL_EMBED_CONTAINER_ID,
29
31
  ViewNavigation: () => import_ViewNavigation.ViewNavigation,
30
32
  useDialog: () => import_useDialog.useDialog,
31
33
  useDrawer: () => import_useDrawer.useDrawer,
@@ -40,6 +42,8 @@ var import_usePopover = require("./usePopover");
40
42
  var import_ViewNavigation = require("./ViewNavigation");
41
43
  // Annotate the CommonJS export names for ESM import in node:
42
44
  0 && (module.exports = {
45
+ EMBED_REPLACING_DATA_KEY,
46
+ GLOBAL_EMBED_CONTAINER_ID,
43
47
  ViewNavigation,
44
48
  useDialog,
45
49
  useDrawer,
@@ -7,6 +7,10 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import React from 'react';
10
+ /** Global embed container element ID */
11
+ export declare const GLOBAL_EMBED_CONTAINER_ID = "nocobase-embed-container";
12
+ /** Dataset key used to signal embed replacement in progress (skip style reset on close) */
13
+ export declare const EMBED_REPLACING_DATA_KEY = "nocobaseEmbedReplacing";
10
14
  export declare function usePage(): (React.JSX.Element | {
11
15
  open: (config: any, flowContext: any) => Promise<unknown> & {
12
16
  type: "embed";
@@ -37,6 +37,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
37
37
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
38
  var usePage_exports = {};
39
39
  __export(usePage_exports, {
40
+ EMBED_REPLACING_DATA_KEY: () => EMBED_REPLACING_DATA_KEY,
41
+ GLOBAL_EMBED_CONTAINER_ID: () => GLOBAL_EMBED_CONTAINER_ID,
40
42
  usePage: () => usePage
41
43
  });
42
44
  module.exports = __toCommonJS(usePage_exports);
@@ -52,16 +54,18 @@ var import_provider = require("../provider");
52
54
  var import_ViewScopedFlowEngine = require("../ViewScopedFlowEngine");
53
55
  var import_variablesParams = require("../utils/variablesParams");
54
56
  let uuid = 0;
57
+ const GLOBAL_EMBED_CONTAINER_ID = "nocobase-embed-container";
58
+ const EMBED_REPLACING_DATA_KEY = "nocobaseEmbedReplacing";
55
59
  const PageElementsHolder = import_react.default.memo(
56
60
  import_react.default.forwardRef((props, ref) => {
57
61
  const [elements, patchElement] = (0, import_usePatchElement.default)();
58
62
  import_react.default.useImperativeHandle(ref, () => ({ patchElement }), [patchElement]);
59
- console.log("[NocoBase] Rendering PageElementsHolder with elements count:", elements.length);
60
63
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, elements);
61
64
  })
62
65
  );
63
66
  function usePage() {
64
67
  const holderRef = import_react.default.useRef(null);
68
+ const globalEmbedActiveRef = import_react.default.useRef(null);
65
69
  const open = /* @__PURE__ */ __name((config, flowContext) => {
66
70
  var _a, _b, _c;
67
71
  uuid += 1;
@@ -93,7 +97,24 @@ function usePage() {
93
97
  }, [props]);
94
98
  return null;
95
99
  }, "HeaderComponent");
96
- const { target, content, preventClose, inheritContext = true, inputArgs, ...restConfig } = config;
100
+ const {
101
+ target,
102
+ content,
103
+ preventClose,
104
+ inheritContext = true,
105
+ inputArgs: viewInputArgs = {},
106
+ ...restConfig
107
+ } = config;
108
+ const isGlobalEmbedContainer = target instanceof HTMLElement && target.id === GLOBAL_EMBED_CONTAINER_ID;
109
+ if (isGlobalEmbedContainer && globalEmbedActiveRef.current) {
110
+ try {
111
+ target.dataset[EMBED_REPLACING_DATA_KEY] = "1";
112
+ globalEmbedActiveRef.current.destroy();
113
+ } finally {
114
+ delete target.dataset[EMBED_REPLACING_DATA_KEY];
115
+ globalEmbedActiveRef.current = null;
116
+ }
117
+ }
97
118
  const ctx = new import_flowContext.FlowContext();
98
119
  const scopedEngine = (0, import_ViewScopedFlowEngine.createViewScopedEngine)(flowContext.engine);
99
120
  ctx.defineProperty("engine", { value: scopedEngine });
@@ -105,7 +126,7 @@ function usePage() {
105
126
  }
106
127
  const currentPage = {
107
128
  type: "embed",
108
- inputArgs: config.inputArgs || {},
129
+ inputArgs: viewInputArgs,
109
130
  preventClose: !!config.preventClose,
110
131
  destroy: /* @__PURE__ */ __name((result) => {
111
132
  var _a2, _b2;
@@ -113,6 +134,9 @@ function usePage() {
113
134
  resolvePromise == null ? void 0 : resolvePromise(result);
114
135
  (_b2 = pageRef.current) == null ? void 0 : _b2.destroy();
115
136
  closeFunc == null ? void 0 : closeFunc();
137
+ if (isGlobalEmbedContainer) {
138
+ globalEmbedActiveRef.current = null;
139
+ }
116
140
  scopedEngine.unlinkFromStack();
117
141
  }, "destroy"),
118
142
  update: /* @__PURE__ */ __name((newConfig) => {
@@ -185,13 +209,16 @@ function usePage() {
185
209
  displayName: "PageWithContext"
186
210
  }
187
211
  );
188
- const key = (inputArgs == null ? void 0 : inputArgs.viewUid) || `page-${uuid}`;
212
+ const key = (viewInputArgs == null ? void 0 : viewInputArgs.viewUid) || `page-${uuid}`;
189
213
  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)));
190
214
  if (target && target instanceof HTMLElement) {
191
215
  closeFunc = (_b = holderRef.current) == null ? void 0 : _b.patchElement(import_react_dom.default.createPortal(page, target, key));
192
216
  } else {
193
217
  closeFunc = (_c = holderRef.current) == null ? void 0 : _c.patchElement(page);
194
218
  }
219
+ if (isGlobalEmbedContainer) {
220
+ globalEmbedActiveRef.current = { destroy: currentPage.destroy };
221
+ }
195
222
  return Object.assign(promise, currentPage);
196
223
  }, "open");
197
224
  const api = import_react.default.useMemo(() => ({ open }), []);
@@ -200,5 +227,7 @@ function usePage() {
200
227
  __name(usePage, "usePage");
201
228
  // Annotate the CommonJS export names for ESM import in node:
202
229
  0 && (module.exports = {
230
+ EMBED_REPLACING_DATA_KEY,
231
+ GLOBAL_EMBED_CONTAINER_ID,
203
232
  usePage
204
233
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "2.0.0-beta.13",
3
+ "version": "2.0.0-beta.14",
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,8 +8,8 @@
8
8
  "dependencies": {
9
9
  "@formily/antd-v5": "1.x",
10
10
  "@formily/reactive": "2.x",
11
- "@nocobase/sdk": "2.0.0-beta.13",
12
- "@nocobase/shared": "2.0.0-beta.13",
11
+ "@nocobase/sdk": "2.0.0-beta.14",
12
+ "@nocobase/shared": "2.0.0-beta.14",
13
13
  "ahooks": "^3.7.2",
14
14
  "dayjs": "^1.11.9",
15
15
  "dompurify": "^3.0.2",
@@ -36,5 +36,5 @@
36
36
  ],
37
37
  "author": "NocoBase Team",
38
38
  "license": "AGPL-3.0",
39
- "gitHead": "3d3db6eb771d20a49559e5f2c0841057196a0c8f"
39
+ "gitHead": "e51bd3f094b93bd8bbb8fa3c6ff0f07fcca2e16d"
40
40
  }
@@ -12,6 +12,7 @@ import { screen } from '@testing-library/react';
12
12
  import { FlowSettings } from '../flowSettings';
13
13
  import { FlowModel } from '../models';
14
14
  import { FlowEngine } from '../flowEngine';
15
+ import { GLOBAL_EMBED_CONTAINER_ID } from '../views';
15
16
 
16
17
  // We will stub viewer directly on model.context in tests
17
18
 
@@ -1087,18 +1088,18 @@ describe('FlowSettings.open rendering behavior', () => {
1087
1088
 
1088
1089
  // Create mock DOM element for embed target
1089
1090
  const mockTarget = document.createElement('div');
1090
- mockTarget.id = 'nocobase-embed-container';
1091
+ mockTarget.id = GLOBAL_EMBED_CONTAINER_ID;
1091
1092
  mockTarget.style.width = 'auto';
1092
1093
  mockTarget.style.maxWidth = 'none';
1093
1094
  document.body.appendChild(mockTarget);
1094
1095
 
1095
1096
  // Mock querySelector to return our mock element
1096
- const originalQuerySelector = document.querySelector;
1097
- document.querySelector = vi.fn((selector) => {
1098
- if (selector === '#nocobase-embed-container') {
1097
+ const originalQuerySelector = document.querySelector.bind(document);
1098
+ const querySelectorSpy = vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
1099
+ if (selector === `#${GLOBAL_EMBED_CONTAINER_ID}`) {
1099
1100
  return mockTarget;
1100
1101
  }
1101
- return originalQuerySelector.call(document, selector);
1102
+ return originalQuerySelector(selector);
1102
1103
  });
1103
1104
 
1104
1105
  const M = model.constructor as any;
@@ -1164,7 +1165,61 @@ describe('FlowSettings.open rendering behavior', () => {
1164
1165
 
1165
1166
  // Cleanup
1166
1167
  document.body.removeChild(mockTarget);
1167
- document.querySelector = originalQuerySelector;
1168
+ querySelectorSpy.mockRestore();
1169
+ });
1170
+
1171
+ it('does not clear embed target DOM before opening (avoids portal unmount errors)', async () => {
1172
+ const engine = new FlowEngine();
1173
+ const flowSettings = new FlowSettings(engine);
1174
+ const model = new FlowModel({ uid: 'm-embed-no-clear', flowEngine: engine });
1175
+
1176
+ const mockTarget = document.createElement('div');
1177
+ mockTarget.id = GLOBAL_EMBED_CONTAINER_ID;
1178
+ mockTarget.innerHTML = '<div data-testid="existing">Existing</div>';
1179
+ document.body.appendChild(mockTarget);
1180
+
1181
+ const originalQuerySelector = document.querySelector.bind(document);
1182
+ const querySelectorSpy = vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
1183
+ if (selector === `#${GLOBAL_EMBED_CONTAINER_ID}`) {
1184
+ return mockTarget;
1185
+ }
1186
+ return originalQuerySelector(selector);
1187
+ });
1188
+
1189
+ const M = model.constructor as any;
1190
+ M.registerFlow({
1191
+ key: 'embedNoClearFlow',
1192
+ steps: {
1193
+ step: {
1194
+ title: 'Step',
1195
+ uiSchema: { f: { type: 'string', 'x-component': 'Input' } },
1196
+ },
1197
+ },
1198
+ });
1199
+
1200
+ const embed = vi.fn((opts: any) => {
1201
+ // The existing DOM should not be wiped out before opening the embed view.
1202
+ expect(mockTarget.querySelector('[data-testid="existing"]')).toBeTruthy();
1203
+ const dlg = { close: vi.fn(), Footer: (p: any) => null } as any;
1204
+ if (typeof opts.content === 'function') opts.content(dlg, { defineMethod: vi.fn() });
1205
+ return dlg;
1206
+ });
1207
+
1208
+ model.context.defineProperty('viewer', { value: { embed } });
1209
+ model.context.defineProperty('message', { value: { info: vi.fn(), error: vi.fn(), success: vi.fn() } });
1210
+
1211
+ await flowSettings.open({
1212
+ model,
1213
+ flowKey: 'embedNoClearFlow',
1214
+ stepKey: 'step',
1215
+ uiMode: 'embed',
1216
+ } as any);
1217
+
1218
+ expect(embed).toHaveBeenCalledTimes(1);
1219
+ expect(mockTarget.querySelector('[data-testid="existing"]')).toBeTruthy();
1220
+
1221
+ document.body.removeChild(mockTarget);
1222
+ querySelectorSpy.mockRestore();
1168
1223
  });
1169
1224
 
1170
1225
  it('uses embed uiMode with default props when target element exists', async () => {
@@ -1174,16 +1229,16 @@ describe('FlowSettings.open rendering behavior', () => {
1174
1229
 
1175
1230
  // Create mock DOM element for embed target
1176
1231
  const mockTarget = document.createElement('div');
1177
- mockTarget.id = 'nocobase-embed-container';
1232
+ mockTarget.id = GLOBAL_EMBED_CONTAINER_ID;
1178
1233
  document.body.appendChild(mockTarget);
1179
1234
 
1180
1235
  // Mock querySelector
1181
- const originalQuerySelector = document.querySelector;
1182
- document.querySelector = vi.fn((selector) => {
1183
- if (selector === '#nocobase-embed-container') {
1236
+ const originalQuerySelector = document.querySelector.bind(document);
1237
+ const querySelectorSpy = vi.spyOn(document, 'querySelector').mockImplementation((selector: string) => {
1238
+ if (selector === `#${GLOBAL_EMBED_CONTAINER_ID}`) {
1184
1239
  return mockTarget;
1185
1240
  }
1186
- return originalQuerySelector.call(document, selector);
1241
+ return originalQuerySelector(selector);
1187
1242
  });
1188
1243
 
1189
1244
  const M = model.constructor as any;
@@ -1223,7 +1278,7 @@ describe('FlowSettings.open rendering behavior', () => {
1223
1278
 
1224
1279
  // Cleanup
1225
1280
  document.body.removeChild(mockTarget);
1226
- document.querySelector = originalQuerySelector;
1281
+ querySelectorSpy.mockRestore();
1227
1282
  });
1228
1283
 
1229
1284
  it('handles embed uiMode when target element is not found', async () => {
@@ -1232,8 +1287,7 @@ describe('FlowSettings.open rendering behavior', () => {
1232
1287
  const model = new FlowModel({ uid: 'm-embed-no-target', flowEngine: engine });
1233
1288
 
1234
1289
  // Mock querySelector to return null (target not found)
1235
- const originalQuerySelector = document.querySelector;
1236
- document.querySelector = vi.fn(() => null);
1290
+ const querySelectorSpy = vi.spyOn(document, 'querySelector').mockReturnValue(null);
1237
1291
 
1238
1292
  const M = model.constructor as any;
1239
1293
  M.registerFlow({
@@ -1266,7 +1320,7 @@ describe('FlowSettings.open rendering behavior', () => {
1266
1320
  expect(embed).toHaveBeenCalledTimes(1);
1267
1321
 
1268
1322
  // Restore querySelector
1269
- document.querySelector = originalQuerySelector;
1323
+ querySelectorSpy.mockRestore();
1270
1324
  });
1271
1325
 
1272
1326
  it('handles error in function-based step uiMode gracefully', async () => {