@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.
- package/lib/flowContext.js +2 -9
- package/lib/flowSettings.js +12 -10
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -0
- package/lib/runjs-context/contexts/base.js +10 -4
- package/lib/runjsLibs.d.ts +15 -0
- package/lib/runjsLibs.js +223 -0
- package/lib/utils/runjsTemplateCompat.js +353 -2
- package/lib/views/index.d.ts +1 -1
- package/lib/views/index.js +4 -0
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +33 -4
- package/package.json +4 -4
- package/src/__tests__/flowSettings.open.test.tsx +69 -15
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/flowContext.ts +3 -12
- package/src/flowSettings.ts +12 -11
- package/src/index.ts +2 -0
- package/src/runjs-context/contexts/base.ts +9 -2
- package/src/runjsLibs.ts +218 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +33 -0
- package/src/utils/runjsTemplateCompat.ts +395 -2
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/index.tsx +1 -1
- package/src/views/usePage.tsx +38 -4
|
@@ -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
|
-
|
|
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 {
|
package/lib/views/index.d.ts
CHANGED
|
@@ -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';
|
package/lib/views/index.js
CHANGED
|
@@ -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,
|
package/lib/views/usePage.d.ts
CHANGED
|
@@ -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";
|
package/lib/views/usePage.js
CHANGED
|
@@ -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 {
|
|
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:
|
|
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 = (
|
|
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.
|
|
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.
|
|
12
|
-
"@nocobase/shared": "2.0.0-beta.
|
|
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": "
|
|
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 =
|
|
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
|
-
|
|
1098
|
-
if (selector ===
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1183
|
-
if (selector ===
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1323
|
+
querySelectorSpy.mockRestore();
|
|
1270
1324
|
});
|
|
1271
1325
|
|
|
1272
1326
|
it('handles error in function-based step uiMode gracefully', async () => {
|