@nocobase/flow-engine 2.1.11 → 2.2.0-alpha.2

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 (67) hide show
  1. package/lib/JSRunner.d.ts +1 -0
  2. package/lib/JSRunner.js +110 -20
  3. package/lib/components/FlowContextSelector.js +24 -3
  4. package/lib/components/variables/VariableHybridInput.d.ts +8 -0
  5. package/lib/components/variables/VariableHybridInput.js +128 -12
  6. package/lib/components/variables/types.d.ts +8 -0
  7. package/lib/flowContext.d.ts +1 -1
  8. package/lib/flowContext.js +33 -7
  9. package/lib/flowI18n.js +3 -3
  10. package/lib/locale/en-US.json +1 -0
  11. package/lib/locale/index.d.ts +2 -0
  12. package/lib/locale/zh-CN.json +1 -0
  13. package/lib/resources/apiResource.js +2 -1
  14. package/lib/resources/baseRecordResource.js +6 -17
  15. package/lib/resources/multiRecordResource.js +13 -3
  16. package/lib/resources/singleRecordResource.js +7 -2
  17. package/lib/runjs-context/helpers.js +12 -5
  18. package/lib/utils/dataSourceDirty.d.ts +20 -0
  19. package/lib/utils/dataSourceDirty.js +139 -0
  20. package/lib/utils/dirtyAwareApiClient.d.ts +11 -0
  21. package/lib/utils/dirtyAwareApiClient.js +378 -0
  22. package/lib/utils/index.d.ts +1 -1
  23. package/lib/utils/index.js +11 -11
  24. package/lib/utils/openViewRouteState.d.ts +28 -0
  25. package/lib/utils/openViewRouteState.js +125 -0
  26. package/lib/utils/parsePathnameToViewParams.d.ts +3 -0
  27. package/lib/utils/parsePathnameToViewParams.js +18 -1
  28. package/lib/utils/resolveRunJSObjectValues.js +3 -2
  29. package/lib/utils/runjsModuleLoader.js +0 -30
  30. package/lib/views/ViewNavigation.js +5 -0
  31. package/package.json +4 -4
  32. package/src/JSRunner.ts +112 -25
  33. package/src/__tests__/JSRunner.test.ts +4 -5
  34. package/src/__tests__/flowContext.test.ts +88 -0
  35. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +51 -0
  36. package/src/__tests__/flowI18n.test.ts +11 -0
  37. package/src/__tests__/runjsRuntimeFeatures.test.ts +15 -2
  38. package/src/components/FlowContextSelector.tsx +33 -2
  39. package/src/components/variables/VariableHybridInput.tsx +166 -9
  40. package/src/components/variables/__tests__/VariableHybridInput.test.tsx +178 -0
  41. package/src/components/variables/types.ts +8 -0
  42. package/src/flowContext.ts +43 -8
  43. package/src/flowI18n.ts +8 -3
  44. package/src/locale/en-US.json +1 -0
  45. package/src/locale/zh-CN.json +1 -0
  46. package/src/resources/apiResource.ts +2 -1
  47. package/src/resources/baseRecordResource.ts +6 -23
  48. package/src/resources/multiRecordResource.ts +13 -3
  49. package/src/resources/singleRecordResource.ts +6 -1
  50. package/src/runjs-context/helpers.ts +12 -6
  51. package/src/utils/__tests__/dirtyAwareApiClient.test.ts +392 -0
  52. package/src/utils/__tests__/openViewRouteState.test.ts +40 -0
  53. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +36 -0
  54. package/src/utils/dataSourceDirty.ts +126 -0
  55. package/src/utils/dirtyAwareApiClient.ts +430 -0
  56. package/src/utils/index.ts +10 -9
  57. package/src/utils/openViewRouteState.ts +107 -0
  58. package/src/utils/parsePathnameToViewParams.ts +23 -1
  59. package/src/utils/resolveRunJSObjectValues.ts +5 -2
  60. package/src/utils/runjsModuleLoader.ts +0 -32
  61. package/src/views/ViewNavigation.ts +6 -1
  62. package/src/views/__tests__/ViewNavigation.test.ts +15 -0
  63. package/lib/utils/safeGlobals.d.ts +0 -28
  64. package/lib/utils/safeGlobals.js +0 -367
  65. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +0 -38
  66. package/src/utils/__tests__/safeGlobals.test.ts +0 -106
  67. package/src/utils/safeGlobals.ts +0 -406
package/lib/JSRunner.d.ts CHANGED
@@ -27,6 +27,7 @@ export interface JSRunnerOptions {
27
27
  * 3. Fallback keeps v1-compatible behavior (enabled).
28
28
  */
29
29
  export declare function shouldPreprocessRunJSTemplates(options?: Pick<JSRunnerOptions, 'preprocessTemplates' | 'version'>): boolean;
30
+ export declare const RUNJS_ALLOWED_BARE_GLOBAL_NAMES: readonly ["ctx", "console", "window", "document", "navigator", "setTimeout", "clearTimeout", "setInterval", "clearInterval", "Array", "ArrayBuffer", "BigInt", "BigInt64Array", "BigUint64Array", "Boolean", "DataView", "Date", "Error", "EvalError", "FinalizationRegistry", "Float32Array", "Float64Array", "Int8Array", "Int16Array", "Int32Array", "Map", "Math", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "SyntaxError", "TypeError", "URIError", "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array", "WeakMap", "WeakRef", "WeakSet", "JSON", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", "isFinite", "isNaN", "parseFloat", "parseInt", "undefined", "NaN", "Infinity", "fetch", "localStorage", "sessionStorage", "XMLHttpRequest", "WebSocket", "Worker", "SharedWorker", "ServiceWorker", "BroadcastChannel", "EventSource", "indexedDB", "caches", "Function", "eval", "globalThis", "Intl", "Blob", "URL", "location"];
30
31
  export declare class JSRunner {
31
32
  private globals;
32
33
  private timeoutMs;
package/lib/JSRunner.js CHANGED
@@ -28,6 +28,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
28
28
  var JSRunner_exports = {};
29
29
  __export(JSRunner_exports, {
30
30
  JSRunner: () => JSRunner,
31
+ RUNJS_ALLOWED_BARE_GLOBAL_NAMES: () => RUNJS_ALLOWED_BARE_GLOBAL_NAMES,
31
32
  shouldPreprocessRunJSTemplates: () => shouldPreprocessRunJSTemplates
32
33
  });
33
34
  module.exports = __toCommonJS(JSRunner_exports);
@@ -40,6 +41,113 @@ function shouldPreprocessRunJSTemplates(options) {
40
41
  return (options == null ? void 0 : options.version) !== "v2";
41
42
  }
42
43
  __name(shouldPreprocessRunJSTemplates, "shouldPreprocessRunJSTemplates");
44
+ const RUNJS_BROWSER_GLOBAL_NAMES = [
45
+ "fetch",
46
+ "localStorage",
47
+ "sessionStorage",
48
+ "XMLHttpRequest",
49
+ "WebSocket",
50
+ "Worker",
51
+ "SharedWorker",
52
+ "ServiceWorker",
53
+ "BroadcastChannel",
54
+ "EventSource",
55
+ "indexedDB",
56
+ "caches",
57
+ "Function",
58
+ "eval",
59
+ "globalThis",
60
+ "Intl",
61
+ "Blob",
62
+ "URL",
63
+ "location"
64
+ ];
65
+ const RUNJS_ALLOWED_BARE_GLOBAL_NAMES = [
66
+ "ctx",
67
+ "console",
68
+ "window",
69
+ "document",
70
+ "navigator",
71
+ "setTimeout",
72
+ "clearTimeout",
73
+ "setInterval",
74
+ "clearInterval",
75
+ "Array",
76
+ "ArrayBuffer",
77
+ "BigInt",
78
+ "BigInt64Array",
79
+ "BigUint64Array",
80
+ "Boolean",
81
+ "DataView",
82
+ "Date",
83
+ "Error",
84
+ "EvalError",
85
+ "FinalizationRegistry",
86
+ "Float32Array",
87
+ "Float64Array",
88
+ "Int8Array",
89
+ "Int16Array",
90
+ "Int32Array",
91
+ "Map",
92
+ "Math",
93
+ "Number",
94
+ "Object",
95
+ "Promise",
96
+ "Proxy",
97
+ "RangeError",
98
+ "ReferenceError",
99
+ "Reflect",
100
+ "RegExp",
101
+ "Set",
102
+ "String",
103
+ "Symbol",
104
+ "SyntaxError",
105
+ "TypeError",
106
+ "URIError",
107
+ "Uint8Array",
108
+ "Uint8ClampedArray",
109
+ "Uint16Array",
110
+ "Uint32Array",
111
+ "WeakMap",
112
+ "WeakRef",
113
+ "WeakSet",
114
+ "JSON",
115
+ "decodeURI",
116
+ "decodeURIComponent",
117
+ "encodeURI",
118
+ "encodeURIComponent",
119
+ "isFinite",
120
+ "isNaN",
121
+ "parseFloat",
122
+ "parseInt",
123
+ "undefined",
124
+ "NaN",
125
+ "Infinity",
126
+ ...RUNJS_BROWSER_GLOBAL_NAMES
127
+ ];
128
+ function collectRunJSBrowserGlobals(providedGlobals = {}) {
129
+ const windowGlobal = providedGlobals.window;
130
+ if (!windowGlobal || typeof windowGlobal !== "object") {
131
+ return {};
132
+ }
133
+ const windowRecord = windowGlobal;
134
+ const globals = {};
135
+ RUNJS_BROWSER_GLOBAL_NAMES.forEach((name) => {
136
+ if (Object.prototype.hasOwnProperty.call(providedGlobals, name)) {
137
+ return;
138
+ }
139
+ try {
140
+ const value = windowRecord[name];
141
+ if (typeof value === "undefined") {
142
+ return;
143
+ }
144
+ globals[name] = name === "fetch" && typeof value === "function" ? value.bind(windowGlobal) : value;
145
+ } catch {
146
+ }
147
+ });
148
+ return globals;
149
+ }
150
+ __name(collectRunJSBrowserGlobals, "collectRunJSBrowserGlobals");
43
151
  const BARE_CTX_TEMPLATE_RE = /(^|[=(:,[\s)])(\{\{\s*(ctx(?:\.|\[|\?\.)[^}]*)\s*\}\})/m;
44
152
  function extractDeprecatedCtxTemplateUsage(code) {
45
153
  const src = String(code || "");
@@ -78,7 +186,6 @@ const _JSRunner = class _JSRunner {
78
186
  globals;
79
187
  timeoutMs;
80
188
  constructor(options = {}) {
81
- var _a, _b;
82
189
  const bindWindowFn = /* @__PURE__ */ __name((key) => {
83
190
  if (typeof window !== "undefined" && typeof window[key] === "function") {
84
191
  return window[key].bind(window);
@@ -87,25 +194,7 @@ const _JSRunner = class _JSRunner {
87
194
  return typeof fn === "function" ? fn.bind(globalThis) : fn;
88
195
  }, "bindWindowFn");
89
196
  const providedGlobals = options.globals || {};
90
- const liftedGlobals = {};
91
- if (!Object.prototype.hasOwnProperty.call(providedGlobals, "Blob")) {
92
- try {
93
- const blobCtor = (_a = providedGlobals.window) == null ? void 0 : _a.Blob;
94
- if (typeof blobCtor !== "undefined") {
95
- liftedGlobals.Blob = blobCtor;
96
- }
97
- } catch {
98
- }
99
- }
100
- if (!Object.prototype.hasOwnProperty.call(providedGlobals, "URL")) {
101
- try {
102
- const urlCtor = (_b = providedGlobals.window) == null ? void 0 : _b.URL;
103
- if (typeof urlCtor !== "undefined") {
104
- liftedGlobals.URL = urlCtor;
105
- }
106
- } catch {
107
- }
108
- }
197
+ const liftedGlobals = collectRunJSBrowserGlobals(providedGlobals);
109
198
  this.globals = {
110
199
  console,
111
200
  setTimeout: bindWindowFn("setTimeout"),
@@ -169,5 +258,6 @@ let JSRunner = _JSRunner;
169
258
  // Annotate the CommonJS export names for ESM import in node:
170
259
  0 && (module.exports = {
171
260
  JSRunner,
261
+ RUNJS_ALLOWED_BARE_GLOBAL_NAMES,
172
262
  shouldPreprocessRunJSTemplates
173
263
  });
@@ -97,6 +97,7 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
97
97
  open,
98
98
  onlyLeafSelectable = false,
99
99
  ignoreFieldNames,
100
+ dropdownFooter,
100
101
  ...cascaderProps
101
102
  }) => {
102
103
  const { token } = import_antd.theme.useToken();
@@ -289,12 +290,32 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
289
290
  },
290
291
  [cascaderOnDropdownVisibleChange, open]
291
292
  );
293
+ const footerNode = (0, import_react.useMemo)(() => {
294
+ if (dropdownFooter !== void 0) {
295
+ return dropdownFooter;
296
+ }
297
+ if (onlyLeafSelectable) {
298
+ return null;
299
+ }
300
+ return /* @__PURE__ */ import_react.default.createElement(
301
+ "div",
302
+ {
303
+ className: import_css.css`
304
+ padding: 6px 12px;
305
+ color: ${token.colorTextDescription};
306
+ border-top: 1px solid ${token.colorSplit};
307
+ font-size: ${token.fontSizeSM}px;
308
+ `
309
+ },
310
+ flowCtx.t("Double click to choose entire object")
311
+ );
312
+ }, [dropdownFooter, onlyLeafSelectable, token, flowCtx]);
292
313
  const renderDropdown = (0, import_react.useCallback)(
293
314
  (menu) => {
294
315
  const cascaderMenuNode = cascaderDropdownRender ? cascaderDropdownRender(menu) : menu;
295
316
  const cascaderMenu = import_react.default.isValidElement(cascaderMenuNode) ? cascaderMenuNode : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, cascaderMenuNode);
296
317
  if (!isSearchEnabled || children === null) {
297
- return cascaderMenu;
318
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, cascaderMenu, footerNode);
298
319
  }
299
320
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: cascaderSearchInputClassName }, /* @__PURE__ */ import_react.default.createElement(
300
321
  import_antd.Input,
@@ -306,9 +327,9 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
306
327
  onChange: (e) => setSearchText(e.target.value),
307
328
  onKeyDown: (e) => e.stopPropagation()
308
329
  }
309
- )), cascaderMenu);
330
+ )), cascaderMenu, footerNode);
310
331
  },
311
- [cascaderDropdownRender, cascaderSearchInputClassName, children, flowCtx, isSearchEnabled, searchText]
332
+ [cascaderDropdownRender, cascaderSearchInputClassName, children, flowCtx, isSearchEnabled, searchText, footerNode]
312
333
  );
313
334
  const inlinePlaceholder = typeof restCascaderProps.placeholder === "string" ? restCascaderProps.placeholder : flowCtx.t("Search");
314
335
  const hasSelectedPath = Array.isArray(effectivePath) && effectivePath.length > 0;
@@ -23,5 +23,13 @@ export interface VariableHybridInputProps {
23
23
  converters?: VariableHybridInputConverters;
24
24
  style?: React.CSSProperties;
25
25
  className?: string;
26
+ /**
27
+ * Validation status — turns the input border red (`error`) or amber
28
+ * (`warning`). Usually omitted: when rendered inside an antd `Form.Item`, the
29
+ * status is read automatically from `FormItemInputContext`, so dropping this
30
+ * into a `Form.Item` with failing rules colours the border with no extra
31
+ * wiring. An explicit prop wins over the inherited form status.
32
+ */
33
+ status?: 'error' | 'warning';
26
34
  }
27
35
  export declare const VariableHybridInput: React.NamedExoticComponent<VariableHybridInputProps>;
@@ -42,6 +42,7 @@ __export(VariableHybridInput_exports, {
42
42
  module.exports = __toCommonJS(VariableHybridInput_exports);
43
43
  var import_css = require("@emotion/css");
44
44
  var import_antd = require("antd");
45
+ var import_context = require("antd/es/form/context");
45
46
  var import_react = __toESM(require("react"));
46
47
  var import_FlowContextProvider = require("../../FlowContextProvider");
47
48
  var import_FlowContextSelector = require("../FlowContextSelector");
@@ -83,14 +84,28 @@ function normalizeVariableKey(value) {
83
84
  return value.replace(/^\{\{\s*/, "").replace(/\s*\}\}$/, "").trim();
84
85
  }
85
86
  __name(normalizeVariableKey, "normalizeVariableKey");
86
- function renderHTML(value, labelMap, regExp) {
87
+ function renderHTML(value, regExp, resolveLabel) {
87
88
  const re = new RegExp(regExp.source, regExp.flags.includes("g") ? regExp.flags : `${regExp.flags}g`);
88
89
  return escapeHtml(value || "").replace(re, (matched) => {
89
- const label = labelMap.get(normalizeVariableKey(matched)) || matched;
90
+ const label = resolveLabel(matched) || matched;
90
91
  return createTagHTML(matched, label);
91
92
  });
92
93
  }
93
94
  __name(renderHTML, "renderHTML");
95
+ function resolveTitlesByPath(roots, path, ctxT) {
96
+ if (!roots || !path || !path.length) return void 0;
97
+ const titles = [];
98
+ let nodes = roots;
99
+ for (const segment of path) {
100
+ if (!nodes) return void 0;
101
+ const matched = nodes.find((node) => node.name === segment);
102
+ if (!matched) return void 0;
103
+ titles.push(reactNodeToPlainText(matched.title || matched.name));
104
+ nodes = Array.isArray(matched.children) ? matched.children : void 0;
105
+ }
106
+ return titles.map(ctxT).join("/");
107
+ }
108
+ __name(resolveTitlesByPath, "resolveTitlesByPath");
94
109
  function buildLabelMap(nodes, ctxT, converters) {
95
110
  const map = /* @__PURE__ */ new Map();
96
111
  function walk(items = [], parentTitles = []) {
@@ -112,6 +127,34 @@ function buildLabelMap(nodes, ctxT, converters) {
112
127
  return map;
113
128
  }
114
129
  __name(buildLabelMap, "buildLabelMap");
130
+ function collectReferencePaths(value, regExp, parseValueToPath) {
131
+ const re = new RegExp(regExp.source, regExp.flags.includes("g") ? regExp.flags : `${regExp.flags}g`);
132
+ const paths = [];
133
+ for (const matched of value.match(re) ?? []) {
134
+ const path = parseValueToPath(matched);
135
+ if (path && path.length) {
136
+ paths.push(path);
137
+ }
138
+ }
139
+ return paths;
140
+ }
141
+ __name(collectReferencePaths, "collectReferencePaths");
142
+ async function preloadReferencePath(path, roots) {
143
+ let nodes = roots;
144
+ let didLoad = false;
145
+ for (const segment of path) {
146
+ if (!nodes) break;
147
+ const matched = nodes.find((node) => node.name === segment);
148
+ if (!matched) break;
149
+ if (typeof matched.children === "function") {
150
+ matched.children = await (0, import_utils.loadMetaTreeChildren)(matched);
151
+ didLoad = true;
152
+ }
153
+ nodes = Array.isArray(matched.children) ? matched.children : void 0;
154
+ }
155
+ return didLoad;
156
+ }
157
+ __name(preloadReferencePath, "preloadReferencePath");
115
158
  function pasteHTML(container, html, indexes) {
116
159
  var _a;
117
160
  const selection = (_a = window.getSelection) == null ? void 0 : _a.call(window);
@@ -213,21 +256,64 @@ function getCurrentRange(element) {
213
256
  }
214
257
  __name(getCurrentRange, "getCurrentRange");
215
258
  const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
259
+ var _a;
216
260
  const { addonBefore, className, converters, disabled, metaTree, onChange, placeholder, style } = props;
217
261
  const { token } = import_antd.theme.useToken();
218
262
  const ctx = (0, import_FlowContextProvider.useFlowContext)();
219
263
  const { resolvedMetaTree } = (0, import_useResolvedMetaTree.useResolvedMetaTree)(metaTree);
264
+ const formItemStatus = (_a = (0, import_react.useContext)(import_context.FormItemInputContext)) == null ? void 0 : _a.status;
265
+ const effectiveStatus = props.status ?? formItemStatus;
220
266
  const inputRef = (0, import_react.useRef)(null);
221
267
  const [isComposing, setIsComposing] = (0, import_react.useState)(false);
222
268
  const [changed, setChanged] = (0, import_react.useState)(false);
223
269
  const [range, setRange] = (0, import_react.useState)([-1, 0, -1, 0]);
224
270
  const value = typeof props.value === "string" ? props.value : props.value == null ? "" : String(props.value);
225
271
  const variableRegExp = (converters == null ? void 0 : converters.variableRegExp) ?? DEFAULT_VARIABLE_REGEXP;
272
+ const parseValueToPath = (converters == null ? void 0 : converters.parseValueToPath) ?? import_utils.parseValueToPath;
273
+ const [loadedFlag, setLoadedFlag] = (0, import_react.useState)(0);
274
+ (0, import_react.useEffect)(() => {
275
+ if (!value || !Array.isArray(resolvedMetaTree) || !resolvedMetaTree.length) {
276
+ return;
277
+ }
278
+ const paths = collectReferencePaths(value, variableRegExp, parseValueToPath);
279
+ if (!paths.length) {
280
+ return;
281
+ }
282
+ let cancelled = false;
283
+ const run = /* @__PURE__ */ __name(async () => {
284
+ let didLoad = false;
285
+ for (const path of paths) {
286
+ const loaded = await preloadReferencePath(path, resolvedMetaTree);
287
+ if (cancelled) return;
288
+ didLoad = didLoad || loaded;
289
+ }
290
+ if (didLoad && !cancelled) {
291
+ setLoadedFlag((prev) => prev + 1);
292
+ }
293
+ }, "run");
294
+ run();
295
+ return () => {
296
+ cancelled = true;
297
+ };
298
+ }, [value, resolvedMetaTree, variableRegExp, parseValueToPath]);
226
299
  const labelMap = (0, import_react.useMemo)(
227
300
  () => buildLabelMap(resolvedMetaTree, ctx.t, converters),
228
- [resolvedMetaTree, ctx, converters]
301
+ // `loadedFlag` is read so the map recomputes after a lazy level resolves.
302
+ // eslint-disable-next-line react-hooks/exhaustive-deps
303
+ [resolvedMetaTree, ctx, converters, loadedFlag]
304
+ );
305
+ const resolveLabel = (0, import_react.useCallback)(
306
+ (matched) => {
307
+ const mapped = labelMap.get(normalizeVariableKey(matched));
308
+ if (mapped) return mapped;
309
+ const path = parseValueToPath(matched);
310
+ return resolveTitlesByPath(resolvedMetaTree, path, ctx.t);
311
+ },
312
+ // `loadedFlag` is read so a resolved lazy level re-creates this callback and re-renders the tags. `ctx` carries the translation fn.
313
+ // eslint-disable-next-line react-hooks/exhaustive-deps
314
+ [labelMap, parseValueToPath, resolvedMetaTree, ctx, loadedFlag]
229
315
  );
230
- const [html, setHtml] = (0, import_react.useState)(() => renderHTML(value, labelMap, variableRegExp));
316
+ const [html, setHtml] = (0, import_react.useState)(() => renderHTML(value, variableRegExp, resolveLabel));
231
317
  const emitChange = (0, import_react.useCallback)(
232
318
  (target) => {
233
319
  onChange == null ? void 0 : onChange(getDomValue(target).trim());
@@ -235,20 +321,20 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
235
321
  [onChange]
236
322
  );
237
323
  (0, import_react.useEffect)(() => {
238
- setHtml(renderHTML(value, labelMap, variableRegExp));
324
+ setHtml(renderHTML(value, variableRegExp, resolveLabel));
239
325
  if (!changed) {
240
326
  setRange([-1, 0, -1, 0]);
241
327
  }
242
- }, [value, labelMap]);
328
+ }, [value, resolveLabel]);
243
329
  (0, import_react.useEffect)(() => {
244
- var _a;
330
+ var _a2;
245
331
  const element = inputRef.current;
246
332
  if (!element) return;
247
333
  if (document.activeElement !== element) return;
248
334
  const nextRange = new Range();
249
335
  if (changed) {
250
336
  if (range.join() === "-1,0,-1,0") return;
251
- const selection = (_a = window.getSelection) == null ? void 0 : _a.call(window);
337
+ const selection = (_a2 = window.getSelection) == null ? void 0 : _a2.call(window);
252
338
  if (!selection) return;
253
339
  try {
254
340
  const children = Array.from(element.childNodes);
@@ -457,6 +543,34 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
457
543
  border-color: ${token.colorBorder};
458
544
  }
459
545
  }
546
+
547
+ &.is-error {
548
+ border-color: ${token.colorError};
549
+
550
+ &:hover {
551
+ border-color: ${token.colorErrorBorderHover};
552
+ }
553
+
554
+ &:focus,
555
+ &:focus-visible {
556
+ border-color: ${token.colorError};
557
+ box-shadow: 0 0 0 ${token.controlOutlineWidth}px ${token.colorErrorOutline};
558
+ }
559
+ }
560
+
561
+ &.is-warning {
562
+ border-color: ${token.colorWarning};
563
+
564
+ &:hover {
565
+ border-color: ${token.colorWarningBorderHover};
566
+ }
567
+
568
+ &:focus,
569
+ &:focus-visible {
570
+ border-color: ${token.colorWarning};
571
+ box-shadow: 0 0 0 ${token.controlOutlineWidth}px ${token.colorWarningOutline};
572
+ }
573
+ }
460
574
  `;
461
575
  }, [token, addonBefore]);
462
576
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_antd.Space.Compact, { className: (0, import_css.cx)("nb-variable-hybrid-input", wrapperClassName, className), style }, addonBefore != null && /* @__PURE__ */ import_react.default.createElement("span", { className: addonClassName }, addonBefore), /* @__PURE__ */ import_react.default.createElement(
@@ -466,7 +580,9 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
466
580
  role: "textbox",
467
581
  "aria-label": "textbox",
468
582
  className: (0, import_css.cx)(editorClassName, {
469
- "is-disabled": disabled
583
+ "is-disabled": disabled,
584
+ "is-error": effectiveStatus === "error",
585
+ "is-warning": effectiveStatus === "warning"
470
586
  }),
471
587
  contentEditable: !disabled,
472
588
  "data-placeholder": placeholder,
@@ -483,10 +599,10 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
483
599
  {
484
600
  metaTree,
485
601
  disabled,
486
- parseValueToPath: (converters == null ? void 0 : converters.parseValueToPath) ?? import_utils.parseValueToPath,
602
+ parseValueToPath,
487
603
  formatPathToValue: (item) => {
488
- var _a;
489
- return ((_a = converters == null ? void 0 : converters.formatPathToValue) == null ? void 0 : _a.call(converters, item)) || (0, import_utils.formatPathToValue)(item);
604
+ var _a2;
605
+ return ((_a2 = converters == null ? void 0 : converters.formatPathToValue) == null ? void 0 : _a2.call(converters, item)) || (0, import_utils.formatPathToValue)(item);
490
606
  },
491
607
  onChange: handleSelectorChange
492
608
  }
@@ -19,6 +19,14 @@ export interface FlowContextSelectorProps extends Omit<CascaderProps<ContextSele
19
19
  open?: boolean;
20
20
  onlyLeafSelectable?: boolean;
21
21
  ignoreFieldNames?: string[];
22
+ /**
23
+ * Footer rendered at the bottom of the dropdown. Defaults to a muted
24
+ * "Double click to choose entire object" hint when non-leaf selection is
25
+ * allowed (`onlyLeafSelectable` is false) — since double-clicking a non-leaf
26
+ * node selects the whole object. Pass an explicit node to override, or `null`
27
+ * to hide it.
28
+ */
29
+ dropdownFooter?: React.ReactNode;
22
30
  }
23
31
  export interface ContextSelectorItem {
24
32
  label: React.ReactNode;
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { ISchema } from '@formily/json-schema';
10
- import { APIClient, RequestOptions } from '@nocobase/sdk';
10
+ import type { APIClient, RequestOptions } from '@nocobase/sdk';
11
11
  import type { Router } from '@remix-run/router';
12
12
  import { MessageInstance } from 'antd/es/message/interface';
13
13
  import type { HookAPI } from 'antd/es/modal/useModal';
@@ -75,6 +75,7 @@ var import_utils = require("./utils");
75
75
  var import_exceptions = require("./utils/exceptions");
76
76
  var import_params_resolvers = require("./utils/params-resolvers");
77
77
  var import_serverContextParams = require("./utils/serverContextParams");
78
+ var import_dirtyAwareApiClient = require("./utils/dirtyAwareApiClient");
78
79
  var import_variablesParams = require("./utils/variablesParams");
79
80
  var import_registry = require("./runjs-context/registry");
80
81
  var import_createEphemeralContext = require("./utils/createEphemeralContext");
@@ -1899,15 +1900,16 @@ const _FlowContext = class _FlowContext {
1899
1900
  const options = this._props[key];
1900
1901
  if (!options) return void 0;
1901
1902
  if ("value" in options) {
1902
- return options.value;
1903
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(options.value, currentContext) : options.value;
1903
1904
  }
1904
1905
  if (options.get) {
1905
1906
  if (options.cache === false) {
1906
- return options.get(currentContext);
1907
+ const value = options.get(currentContext);
1908
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(value, currentContext) : value;
1907
1909
  }
1908
1910
  const cacheKey = options.observable ? "_observableCache" : "_cache";
1909
1911
  if (key in this[cacheKey]) {
1910
- return this[cacheKey][key];
1912
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(this[cacheKey][key], currentContext) : this[cacheKey][key];
1911
1913
  }
1912
1914
  if (this._pending[key]) return this._pending[key];
1913
1915
  const result = options.get(this.createProxy());
@@ -1917,7 +1919,7 @@ const _FlowContext = class _FlowContext {
1917
1919
  (v) => {
1918
1920
  this[cacheKey][key] = v;
1919
1921
  delete this._pending[key];
1920
- return v;
1922
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(v, currentContext) : v;
1921
1923
  },
1922
1924
  (err) => {
1923
1925
  delete this._pending[key];
@@ -1927,7 +1929,7 @@ const _FlowContext = class _FlowContext {
1927
1929
  return this._pending[key];
1928
1930
  }
1929
1931
  this[cacheKey][key] = result;
1930
- return result;
1932
+ return key === "api" ? (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(result, currentContext) : result;
1931
1933
  }
1932
1934
  return void 0;
1933
1935
  }
@@ -2251,7 +2253,7 @@ const _BaseFlowEngineContext = class _BaseFlowEngineContext extends FlowContext
2251
2253
  this.defineMethod("getModel", (modelName, searchInPreviousEngines) => {
2252
2254
  return this.engine.getModel(modelName, searchInPreviousEngines);
2253
2255
  });
2254
- this.defineMethod("request", (options) => {
2256
+ this.defineMethod("request", function(options) {
2255
2257
  const app = this.app;
2256
2258
  if (typeof (options == null ? void 0 : options.url) === "string" && shouldBypassApiClient(options.url, app)) {
2257
2259
  return import_axios.default.request(options);
@@ -2718,7 +2720,17 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
2718
2720
  doc = {};
2719
2721
  }
2720
2722
  const deprecatedCtx = createRunJSDeprecationProxy(runCtx, { doc });
2721
- const globals = { ctx: deprecatedCtx, ...(options == null ? void 0 : options.globals) || {} };
2723
+ const browserGlobals = {};
2724
+ if (typeof window !== "undefined") {
2725
+ browserGlobals.window = window;
2726
+ if (typeof navigator !== "undefined") {
2727
+ browserGlobals.navigator = navigator;
2728
+ }
2729
+ }
2730
+ if (typeof document !== "undefined") {
2731
+ browserGlobals.document = document;
2732
+ }
2733
+ const globals = { ctx: deprecatedCtx, ...browserGlobals, ...(options == null ? void 0 : options.globals) || {} };
2722
2734
  const { timeoutMs } = options || {};
2723
2735
  return new import_JSRunner.JSRunner({ globals, timeoutMs });
2724
2736
  });
@@ -3500,6 +3512,20 @@ const _FlowRunJSContext = class _FlowRunJSContext extends FlowContext {
3500
3512
  ReactDOMShim.__nbRunjsInternalShim = true;
3501
3513
  this.defineProperty("ReactDOM", { value: ReactDOMShim });
3502
3514
  (0, import_runjsLibs.setupRunJSLibs)(this);
3515
+ this.defineMethod("openView", async function(uid, options) {
3516
+ const delegateOpenView = delegate.openView;
3517
+ if (typeof delegateOpenView !== "function") {
3518
+ throw new Error("ctx.openView is not available in current context.");
3519
+ }
3520
+ const routeState = (0, import_utils.createOpenViewRouteState)(options);
3521
+ if (!routeState) {
3522
+ return delegateOpenView(uid, options);
3523
+ }
3524
+ return delegateOpenView(uid, {
3525
+ ...options || {},
3526
+ [import_utils.RUNJS_OPEN_VIEW_ROUTE_STATE]: routeState
3527
+ });
3528
+ });
3503
3529
  this.defineMethod(
3504
3530
  "render",
3505
3531
  function(vnode, container) {
package/lib/flowI18n.js CHANGED
@@ -81,7 +81,7 @@ const _FlowI18n = class _FlowI18n {
81
81
  * @private
82
82
  */
83
83
  isTemplate(str) {
84
- return /\{\{\s*t\s*\(\s*["'`].*?["'`]\s*(?:,\s*.*?)?\s*\)\s*\}\}/g.test(str);
84
+ return /\{\{\s*t\s*\(\s*(["'`])(?:\\.|(?!\1).)*?\1\s*(?:,\s*.*?)?\s*\)\s*\}\}/.test(str);
85
85
  }
86
86
  /**
87
87
  * 编译模板字符串
@@ -89,8 +89,8 @@ const _FlowI18n = class _FlowI18n {
89
89
  */
90
90
  compileTemplate(template) {
91
91
  return template.replace(
92
- /\{\{\s*t\s*\(\s*["'`](.*?)["'`]\s*(?:,\s*((?:[^{}]|\{[^}]*\})*?))?\s*\)\s*\}\}/g,
93
- (match, key, optionsStr) => {
92
+ /\{\{\s*t\s*\(\s*(["'`])((?:\\.|(?!\1).)*?)\1\s*(?:,\s*((?:[^{}]|\{[^}]*\})*?))?\s*\)\s*\}\}/g,
93
+ (match, _quote, key, optionsStr) => {
94
94
  try {
95
95
  let templateOptions = {};
96
96
  if (optionsStr) {
@@ -56,6 +56,7 @@
56
56
  "Replace current block with template?": "Replace current block with template?",
57
57
  "Replaced with template block": "Replaced with template block",
58
58
  "Render failed": "Render failed",
59
+ "Response record": "Response record",
59
60
  "Step configuration": "Step configuration",
60
61
  "Step parameter configuration": "Step parameter configuration",
61
62
  "Step with key {{stepKey}} not found": "Step with key {{stepKey}} not found",
@@ -65,6 +65,7 @@ export declare const locales: {
65
65
  "Replace current block with template?": string;
66
66
  "Replaced with template block": string;
67
67
  "Render failed": string;
68
+ "Response record": string;
68
69
  "Step configuration": string;
69
70
  "Step parameter configuration": string;
70
71
  "Step with key {{stepKey}} not found": string;
@@ -150,6 +151,7 @@ export declare const locales: {
150
151
  "Other blocks": string;
151
152
  "Previous step": string;
152
153
  "Render failed": string;
154
+ "Response record": string;
153
155
  "Step configuration": string;
154
156
  "Step parameter configuration": string;
155
157
  "Step with key {{stepKey}} not found": string;
@@ -60,6 +60,7 @@
60
60
  "Other blocks": "其他区块",
61
61
  "Previous step": "上一步",
62
62
  "Render failed": "渲染失败",
63
+ "Response record": "响应结果记录",
63
64
  "Step configuration": "步骤配置",
64
65
  "Step parameter configuration": "步骤参数配置",
65
66
  "Step with key {{stepKey}} not found": "未找到key为 {{stepKey}} 的步骤",
@@ -30,6 +30,7 @@ __export(apiResource_exports, {
30
30
  APIResource: () => APIResource
31
31
  });
32
32
  module.exports = __toCommonJS(apiResource_exports);
33
+ var import_dirtyAwareApiClient = require("../utils/dirtyAwareApiClient");
33
34
  var import_flowResource = require("./flowResource");
34
35
  const _APIResource = class _APIResource extends import_flowResource.FlowResource {
35
36
  // 请求配置
@@ -49,7 +50,7 @@ const _APIResource = class _APIResource extends import_flowResource.FlowResource
49
50
  }
50
51
  }
51
52
  setAPIClient(api) {
52
- this.api = api;
53
+ this.api = (0, import_dirtyAwareApiClient.getDirtyAwareApiClient)(api, this.context);
53
54
  return this;
54
55
  }
55
56
  getURL() {