@nocobase/flow-engine 2.2.0-beta.8 → 2.2.0

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 (83) hide show
  1. package/lib/acl/Acl.d.ts +2 -1
  2. package/lib/acl/Acl.js +28 -0
  3. package/lib/components/FlowContextSelector.js +62 -13
  4. package/lib/components/FormItem.js +11 -7
  5. package/lib/components/MobilePopup.js +39 -10
  6. package/lib/components/MobilePopup.style.js +11 -1
  7. package/lib/components/subModel/LazyDropdown.js +62 -33
  8. package/lib/components/variables/VariableHybridInput.d.ts +9 -0
  9. package/lib/components/variables/VariableHybridInput.js +146 -17
  10. package/lib/components/variables/VariableInput.js +19 -7
  11. package/lib/components/variables/VariableTag.js +48 -36
  12. package/lib/components/variables/types.d.ts +21 -0
  13. package/lib/flowContext.d.ts +12 -1
  14. package/lib/flowContext.js +57 -12
  15. package/lib/flowEngine.js +6 -0
  16. package/lib/flowI18n.js +3 -3
  17. package/lib/locale/en-US.json +2 -0
  18. package/lib/locale/index.d.ts +4 -0
  19. package/lib/locale/zh-CN.json +2 -0
  20. package/lib/resources/flowResource.js +1 -0
  21. package/lib/types.d.ts +3 -1
  22. package/lib/types.js +1 -0
  23. package/lib/utils/associationObjectVariable.d.ts +10 -0
  24. package/lib/utils/associationObjectVariable.js +10 -7
  25. package/lib/utils/dateVariable.d.ts +22 -0
  26. package/lib/utils/dateVariable.js +123 -16
  27. package/lib/utils/dirtyAwareApiClient.d.ts +1 -0
  28. package/lib/utils/dirtyAwareApiClient.js +280 -13
  29. package/lib/utils/index.d.ts +3 -3
  30. package/lib/utils/index.js +8 -0
  31. package/lib/utils/loadedPageCache.d.ts +1 -0
  32. package/lib/utils/loadedPageCache.js +6 -0
  33. package/lib/utils/params-resolvers.d.ts +3 -0
  34. package/lib/utils/params-resolvers.js +10 -0
  35. package/lib/utils/variablesParams.js +5 -0
  36. package/lib/views/createViewMeta.d.ts +1 -0
  37. package/lib/views/createViewMeta.js +53 -22
  38. package/package.json +4 -4
  39. package/src/__tests__/createViewMeta.popup.test.ts +84 -1
  40. package/src/__tests__/flowContext.test.ts +31 -0
  41. package/src/__tests__/flowI18n.test.ts +11 -0
  42. package/src/__tests__/objectVariable.test.ts +6 -1
  43. package/src/__tests__/runjsFormSubmit.test.ts +138 -0
  44. package/src/__tests__/viewScopedFlowEngine.test.ts +72 -6
  45. package/src/acl/Acl.tsx +36 -1
  46. package/src/acl/__tests__/Acl.test.tsx +70 -0
  47. package/src/components/FlowContextSelector.tsx +73 -12
  48. package/src/components/FormItem.tsx +12 -7
  49. package/src/components/MobilePopup.style.ts +12 -1
  50. package/src/components/MobilePopup.tsx +42 -10
  51. package/src/components/__tests__/FormItem.test.tsx +17 -2
  52. package/src/components/__tests__/MobilePopup.test.tsx +150 -0
  53. package/src/components/subModel/LazyDropdown.tsx +71 -38
  54. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +85 -2
  55. package/src/components/subModel/__tests__/LazyDropdown.test.tsx +202 -0
  56. package/src/components/variables/VariableHybridInput.tsx +185 -14
  57. package/src/components/variables/VariableInput.tsx +32 -7
  58. package/src/components/variables/VariableTag.tsx +51 -37
  59. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +95 -3
  60. package/src/components/variables/__tests__/VariableHybridInput.test.tsx +212 -0
  61. package/src/components/variables/__tests__/VariableInput.test.tsx +202 -6
  62. package/src/components/variables/__tests__/VariableTag.test.tsx +80 -0
  63. package/src/components/variables/types.ts +21 -0
  64. package/src/flowContext.ts +83 -9
  65. package/src/flowEngine.ts +6 -0
  66. package/src/flowI18n.ts +8 -3
  67. package/src/locale/__tests__/index.test.ts +21 -0
  68. package/src/locale/en-US.json +2 -0
  69. package/src/locale/zh-CN.json +2 -0
  70. package/src/resources/__tests__/flowResource.test.ts +3 -0
  71. package/src/resources/flowResource.ts +1 -0
  72. package/src/types.ts +2 -0
  73. package/src/utils/__tests__/dateVariable.test.ts +57 -4
  74. package/src/utils/__tests__/dirtyAwareApiClient.test.ts +321 -0
  75. package/src/utils/__tests__/variablesParams.test.ts +28 -1
  76. package/src/utils/associationObjectVariable.ts +9 -6
  77. package/src/utils/dateVariable.ts +145 -18
  78. package/src/utils/dirtyAwareApiClient.ts +348 -13
  79. package/src/utils/index.ts +17 -2
  80. package/src/utils/loadedPageCache.ts +7 -0
  81. package/src/utils/params-resolvers.ts +12 -0
  82. package/src/utils/variablesParams.ts +10 -0
  83. package/src/views/createViewMeta.ts +52 -18
@@ -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) => {
216
- const { addonBefore, className, converters, disabled, metaTree, onChange, placeholder, style } = props;
259
+ var _a;
260
+ const { addonBefore, className, converters, disabled, metaTree, onChange, placeholder, readOnly, 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]
229
304
  );
230
- const [html, setHtml] = (0, import_react.useState)(() => renderHTML(value, labelMap, variableRegExp));
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]
315
+ );
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);
@@ -304,12 +390,13 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
304
390
  );
305
391
  const handleInput = (0, import_react.useCallback)(
306
392
  ({ currentTarget }) => {
393
+ if (readOnly) return;
307
394
  if (isComposing) return;
308
395
  setChanged(true);
309
396
  setRange(getCurrentRange(currentTarget));
310
397
  emitChange(currentTarget);
311
398
  },
312
- [emitChange, isComposing]
399
+ [emitChange, isComposing, readOnly]
313
400
  );
314
401
  const handleBlur = (0, import_react.useCallback)(({ currentTarget }) => {
315
402
  setRange(getCurrentRange(currentTarget));
@@ -322,6 +409,7 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
322
409
  const handlePaste = (0, import_react.useCallback)(
323
410
  (event) => {
324
411
  event.preventDefault();
412
+ if (readOnly) return;
325
413
  const text = event.clipboardData.getData("text/plain").replace(/\n/g, " ");
326
414
  if (!text) return;
327
415
  setChanged(true);
@@ -329,17 +417,18 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
329
417
  setRange(getCurrentRange(event.currentTarget));
330
418
  emitChange(event.currentTarget);
331
419
  },
332
- [emitChange]
420
+ [emitChange, readOnly]
333
421
  );
334
422
  const handleCompositionStart = (0, import_react.useCallback)(() => setIsComposing(true), []);
335
423
  const handleCompositionEnd = (0, import_react.useCallback)(
336
424
  ({ currentTarget }) => {
337
425
  setIsComposing(false);
426
+ if (readOnly) return;
338
427
  setChanged(true);
339
428
  setRange(getCurrentRange(currentTarget));
340
429
  emitChange(currentTarget);
341
430
  },
342
- [emitChange]
431
+ [emitChange, readOnly]
343
432
  );
344
433
  const wrapperClassName = (0, import_react.useMemo)(
345
434
  () => import_css.css`
@@ -457,6 +546,42 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
457
546
  border-color: ${token.colorBorder};
458
547
  }
459
548
  }
549
+
550
+ &.is-readonly {
551
+ cursor: default;
552
+
553
+ &:hover {
554
+ border-color: ${token.colorBorder};
555
+ }
556
+ }
557
+
558
+ &.is-error {
559
+ border-color: ${token.colorError};
560
+
561
+ &:hover {
562
+ border-color: ${token.colorErrorBorderHover};
563
+ }
564
+
565
+ &:focus,
566
+ &:focus-visible {
567
+ border-color: ${token.colorError};
568
+ box-shadow: 0 0 0 ${token.controlOutlineWidth}px ${token.colorErrorOutline};
569
+ }
570
+ }
571
+
572
+ &.is-warning {
573
+ border-color: ${token.colorWarning};
574
+
575
+ &:hover {
576
+ border-color: ${token.colorWarningBorderHover};
577
+ }
578
+
579
+ &:focus,
580
+ &:focus-visible {
581
+ border-color: ${token.colorWarning};
582
+ box-shadow: 0 0 0 ${token.controlOutlineWidth}px ${token.colorWarningOutline};
583
+ }
584
+ }
460
585
  `;
461
586
  }, [token, addonBefore]);
462
587
  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,9 +591,13 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
466
591
  role: "textbox",
467
592
  "aria-label": "textbox",
468
593
  className: (0, import_css.cx)(editorClassName, {
469
- "is-disabled": disabled
594
+ "is-disabled": disabled,
595
+ "is-readonly": readOnly,
596
+ "is-error": effectiveStatus === "error",
597
+ "is-warning": effectiveStatus === "warning"
470
598
  }),
471
- contentEditable: !disabled,
599
+ contentEditable: !disabled && !readOnly,
600
+ "aria-readonly": readOnly,
472
601
  "data-placeholder": placeholder,
473
602
  onInput: handleInput,
474
603
  onBlur: handleBlur,
@@ -483,10 +612,10 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
483
612
  {
484
613
  metaTree,
485
614
  disabled,
486
- parseValueToPath: (converters == null ? void 0 : converters.parseValueToPath) ?? import_utils.parseValueToPath,
615
+ parseValueToPath,
487
616
  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);
617
+ var _a2;
618
+ return ((_a2 = converters == null ? void 0 : converters.formatPathToValue) == null ? void 0 : _a2.call(converters, item)) || (0, import_utils.formatPathToValue)(item);
490
619
  },
491
620
  onChange: handleSelectorChange
492
621
  }
@@ -132,16 +132,18 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
132
132
  },
133
133
  [onChange]
134
134
  );
135
+ const resolvedPath = (0, import_react.useMemo)(() => {
136
+ return resolvePathFromValue == null ? void 0 : resolvePathFromValue(innerValue);
137
+ }, [innerValue, resolvePathFromValue]);
135
138
  const resolvedMetaTreeNode = (0, import_react.useMemo)(() => {
136
139
  if (currentMetaTreeNode) return currentMetaTreeNode;
137
140
  if (Array.isArray(resolvedMetaTree)) {
138
- const path = resolvePathFromValue == null ? void 0 : resolvePathFromValue(innerValue);
139
- if (path) {
140
- return findMetaTreeNodeByPath(resolvedMetaTree, path);
141
+ if (resolvedPath) {
142
+ return findMetaTreeNodeByPath(resolvedMetaTree, resolvedPath);
141
143
  }
142
144
  }
143
145
  return null;
144
- }, [currentMetaTreeNode, innerValue, resolvedMetaTree, resolvePathFromValue]);
146
+ }, [currentMetaTreeNode, resolvedMetaTree, resolvedPath]);
145
147
  (0, import_react.useEffect)(() => {
146
148
  const restoreFromValue = /* @__PURE__ */ __name(async () => {
147
149
  if (!Array.isArray(resolvedMetaTree) || value == null) return;
@@ -188,13 +190,17 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
188
190
  }
189
191
  }, "restoreFromValue");
190
192
  restoreFromValue();
191
- }, [resolvedMetaTree, innerValue, resolvePathFromValue, currentMetaTreeNode]);
193
+ }, [resolvedMetaTree, innerValue, resolvePathFromValue, currentMetaTreeNode, value]);
192
194
  const ValueComponent = (0, import_react.useMemo)(() => {
193
195
  const Component = renderInputComponent == null ? void 0 : renderInputComponent(resolvedMetaTreeNode);
194
196
  const CustomComponent = resolvedMetaTreeNode == null ? void 0 : resolvedMetaTreeNode.render;
195
- const finalComponent = (0, import_utils.isVariableValue)(innerValue) ? import_VariableTag.VariableTag : Component || CustomComponent || import_antd.Input;
197
+ const shouldRenderVariableTag = (0, import_utils.isVariableValue)(innerValue) || Array.isArray(resolvedPath) && resolvedPath.length > 0 && !Component && !CustomComponent;
198
+ const finalComponent = shouldRenderVariableTag ? import_VariableTag.VariableTag : Component || CustomComponent || import_antd.Input;
196
199
  return finalComponent;
197
- }, [renderInputComponent, resolvedMetaTreeNode, innerValue]);
200
+ }, [renderInputComponent, resolvedMetaTreeNode, innerValue, resolvedPath]);
201
+ const isVariableActive = (0, import_react.useMemo)(() => {
202
+ return (0, import_utils.isVariableValue)(innerValue) || Array.isArray(resolvedPath) && resolvedPath.length > 0 && !(renderInputComponent == null ? void 0 : renderInputComponent(resolvedMetaTreeNode)) && !(resolvedMetaTreeNode == null ? void 0 : resolvedMetaTreeNode.render);
203
+ }, [innerValue, renderInputComponent, resolvedMetaTreeNode, resolvedPath]);
198
204
  (0, import_react.useEffect)(() => {
199
205
  if (!resolvedMetaTreeNode) return;
200
206
  if (!Array.isArray(resolvedMetaTree) || innerValue == null) return;
@@ -278,8 +284,11 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
278
284
  return {
279
285
  ...baseProps,
280
286
  onClear: handleClear,
287
+ disabled,
288
+ allowCustomTagInput: false,
281
289
  metaTreeNode: resolvedMetaTreeNode,
282
290
  metaTree,
291
+ resolvedPath,
283
292
  style: stableProps.style
284
293
  };
285
294
  }
@@ -300,6 +309,7 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
300
309
  disabled,
301
310
  handleClear,
302
311
  resolvedMetaTreeNode,
312
+ resolvedPath,
303
313
  metaTree,
304
314
  ValueComponent,
305
315
  stableProps
@@ -325,6 +335,8 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
325
335
  {
326
336
  metaTree: resolvedMetaTree,
327
337
  value: innerValue,
338
+ active: isVariableActive,
339
+ disabled,
328
340
  onChange: handleVariableSelect,
329
341
  parseValueToPath: resolvePathFromValue,
330
342
  formatPathToValue: resolveValueFromPath,
@@ -47,35 +47,37 @@ var import_utils = require("./utils");
47
47
  var import_useResolvedMetaTree = require("./useResolvedMetaTree");
48
48
  var import_ahooks = require("ahooks");
49
49
  var import_FlowContextProvider = require("../../FlowContextProvider");
50
+ const VARIABLE_PARSING_FAILED_TEXT = "Variable parsing failed";
50
51
  const VariableTagComponent = /* @__PURE__ */ __name(({
51
52
  value,
52
53
  onClear,
54
+ disabled = false,
55
+ allowCustomTagInput = true,
53
56
  className,
54
57
  style,
55
58
  metaTreeNode,
56
- metaTree
59
+ metaTree,
60
+ resolvedPath
57
61
  }) => {
58
62
  const { resolvedMetaTree } = (0, import_useResolvedMetaTree.useResolvedMetaTree)(metaTree);
59
63
  const ctx = (0, import_FlowContextProvider.useFlowContext)();
60
- const { data: displayedValue } = (0, import_ahooks.useRequest)(
64
+ const { data: displayState } = (0, import_ahooks.useRequest)(
61
65
  async () => {
62
66
  const resolveLabelFromPath = /* @__PURE__ */ __name(async (rawPath2) => {
63
- if (!rawPath2) return null;
64
- if (!Array.isArray(rawPath2)) return null;
65
- if (!Array.isArray(resolvedMetaTree)) return null;
67
+ if (!rawPath2) return { label: null, resolved: false, attempted: false };
68
+ if (!Array.isArray(rawPath2)) return { label: null, resolved: false, attempted: false };
69
+ if (!Array.isArray(resolvedMetaTree)) return { label: null, resolved: false, attempted: false };
66
70
  const topNames = new Set((resolvedMetaTree || []).map((n) => String(n == null ? void 0 : n.name)));
67
71
  const path = !topNames.has(String(rawPath2[0])) ? rawPath2.slice(1) : rawPath2;
68
- if (!path.length) return "";
72
+ if (!path.length) return { label: "", resolved: true, attempted: true };
69
73
  let nodes = resolvedMetaTree;
70
74
  const titleChain = [];
71
- let matchedCount = 0;
72
75
  for (let i = 0; i < path.length; i++) {
73
- if (!nodes) break;
76
+ if (!nodes) return { label: null, resolved: false, attempted: true };
74
77
  const seg = String(path[i]);
75
78
  const node = nodes.find((n) => String(n == null ? void 0 : n.name) === seg);
76
- if (!node) break;
79
+ if (!node) return { label: null, resolved: false, attempted: true };
77
80
  titleChain.push(String(node.title ?? node.name ?? seg));
78
- matchedCount = i + 1;
79
81
  if (i < path.length - 1) {
80
82
  if (Array.isArray(node.children)) {
81
83
  nodes = node.children;
@@ -92,29 +94,37 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
92
94
  }
93
95
  }
94
96
  }
95
- if (matchedCount === 0) return null;
96
- let label2 = titleChain.map(ctx.t).join("/");
97
- if (matchedCount < path.length) {
98
- const tail = path.slice(matchedCount).join("/");
99
- label2 = tail ? `${label2}/${tail}` : label2;
100
- }
101
- return label2;
97
+ return { label: titleChain.map(ctx.t).join("/"), resolved: true, attempted: true };
102
98
  }, "resolveLabelFromPath");
103
99
  if (metaTreeNode == null ? void 0 : metaTreeNode.parentTitles) {
104
- return [...metaTreeNode.parentTitles, metaTreeNode.title].map(ctx.t).join("/");
100
+ return {
101
+ text: [...metaTreeNode.parentTitles, metaTreeNode.title].map(ctx.t).join("/"),
102
+ invalid: false
103
+ };
105
104
  }
106
105
  if (metaTreeNode) {
107
- const rawPath2 = (0, import_utils.parseValueToPath)(value) || metaTreeNode.paths;
108
- const label2 = await resolveLabelFromPath(rawPath2);
109
- return label2 ?? ctx.t(metaTreeNode.title) ?? "";
106
+ const rawPath2 = resolvedPath || (0, import_utils.parseValueToPath)(value) || metaTreeNode.paths;
107
+ const result2 = await resolveLabelFromPath(rawPath2);
108
+ if (result2.resolved && result2.label != null) {
109
+ return { text: result2.label, invalid: false };
110
+ }
111
+ if (result2.attempted) {
112
+ return { text: VARIABLE_PARSING_FAILED_TEXT, invalid: true, rawValue: String(value ?? "") };
113
+ }
114
+ return { text: ctx.t(metaTreeNode.title) ?? "", invalid: false };
115
+ }
116
+ if (!value) return { text: String(value), invalid: false };
117
+ const rawPath = resolvedPath || (0, import_utils.parseValueToPath)(value);
118
+ const result = await resolveLabelFromPath(rawPath);
119
+ if (result.resolved && result.label != null) {
120
+ return { text: result.label, invalid: false };
121
+ }
122
+ if (result.attempted) {
123
+ return { text: VARIABLE_PARSING_FAILED_TEXT, invalid: true, rawValue: String(value ?? "") };
110
124
  }
111
- if (!value) return String(value);
112
- const rawPath = (0, import_utils.parseValueToPath)(value);
113
- const label = await resolveLabelFromPath(rawPath);
114
- if (label != null) return label;
115
- return Array.isArray(rawPath) ? rawPath.join("/") : String(value);
125
+ return { text: Array.isArray(rawPath) ? rawPath.join("/") : String(value), invalid: false };
116
126
  },
117
- { refreshDeps: [resolvedMetaTree, value, metaTreeNode] }
127
+ { refreshDeps: [resolvedMetaTree, resolvedPath, value, metaTreeNode] }
118
128
  );
119
129
  const { token } = import_antd.theme.useToken();
120
130
  const shrinkableOverflowItem = import_css.css`
@@ -129,12 +139,12 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
129
139
  }
130
140
  `;
131
141
  const customTagRender = /* @__PURE__ */ __name((props) => {
132
- const { label } = props;
133
- const fullText = typeof label === "string" ? label : String(label);
134
- return /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: fullText, placement: "top", getPopupContainer: () => document.body }, /* @__PURE__ */ import_react.default.createElement(
142
+ const fullText = (displayState == null ? void 0 : displayState.text) || (typeof props.label === "string" ? props.label : String(props.label));
143
+ const tooltipText = (displayState == null ? void 0 : displayState.invalid) ? displayState.rawValue || fullText : fullText;
144
+ return /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: tooltipText, placement: "top", getPopupContainer: () => document.body }, /* @__PURE__ */ import_react.default.createElement(
135
145
  import_antd.Tag,
136
146
  {
137
- color: "blue",
147
+ color: (displayState == null ? void 0 : displayState.invalid) ? "error" : "blue",
138
148
  style: {
139
149
  margin: `0 ${token.marginXXS || token.marginXS}px`,
140
150
  borderRadius: token.borderRadiusSM,
@@ -175,12 +185,14 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
175
185
  flex: "1 1 auto",
176
186
  ...style
177
187
  },
178
- value: displayedValue ? [displayedValue] : [],
179
- mode: "tags",
188
+ value: (displayState == null ? void 0 : displayState.text) ? [displayState.text] : [],
189
+ mode: allowCustomTagInput ? "tags" : "multiple",
180
190
  open: false,
181
- allowClear: !!onClear,
182
- onClear,
183
- disabled: !onClear,
191
+ showSearch: allowCustomTagInput,
192
+ searchValue: allowCustomTagInput ? void 0 : "",
193
+ allowClear: !disabled && !!onClear,
194
+ onClear: disabled ? void 0 : onClear,
195
+ disabled: disabled || !onClear,
184
196
  variant: "outlined",
185
197
  suffixIcon: null,
186
198
  tagRender: customTagRender,
@@ -13,12 +13,30 @@ export interface FlowContextSelectorProps extends Omit<CascaderProps<ContextSele
13
13
  value?: string;
14
14
  onChange?: (value: string, metaTreeNode?: MetaTreeNode) => void;
15
15
  children?: CascaderProps<ContextSelectorItem>['children'];
16
+ /**
17
+ * Controls whether the default `x` trigger button is rendered as active
18
+ * (`type="primary"`). When omitted, the selector falls back to its parsed
19
+ * `value` path (`true` iff a valid variable path is currently selected).
20
+ *
21
+ * Use this when callers intentionally feed synthetic paths such as
22
+ * `['constant']` / `['null']` into the cascader to keep menu state aligned,
23
+ * but only want real variable references to show the blue active button.
24
+ */
25
+ active?: boolean;
16
26
  metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
17
27
  parseValueToPath?: (value: string) => string[] | undefined;
18
28
  formatPathToValue?: (item: MetaTreeNode) => string;
19
29
  open?: boolean;
20
30
  onlyLeafSelectable?: boolean;
21
31
  ignoreFieldNames?: string[];
32
+ /**
33
+ * Footer rendered at the bottom of the dropdown. Defaults to a muted
34
+ * "Double click to choose entire object" hint when non-leaf selection is
35
+ * allowed (`onlyLeafSelectable` is false) — since double-clicking a non-leaf
36
+ * node selects the whole object. Pass an explicit node to override, or `null`
37
+ * to hide it.
38
+ */
39
+ dropdownFooter?: React.ReactNode;
22
40
  }
23
41
  export interface ContextSelectorItem {
24
42
  label: React.ReactNode;
@@ -68,7 +86,10 @@ export interface VariableInputProps {
68
86
  }
69
87
  export interface VariableTagProps {
70
88
  value?: string;
89
+ resolvedPath?: Array<string | number>;
71
90
  onClear?: () => void;
91
+ disabled?: boolean;
92
+ allowCustomTagInput?: boolean;
72
93
  className?: string;
73
94
  style?: React.CSSProperties;
74
95
  metaTreeNode?: MetaTreeNode | null;
@@ -24,9 +24,13 @@ import { FlowResource, FlowSQLRepository } from './resources';
24
24
  import type { ActionDefinition, EventDefinition, ResourceType } from './types';
25
25
  import { JSONValue } from './utils/params-resolvers';
26
26
  import type { RecordRef } from './utils/serverContextParams';
27
+ import { PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS } from './utils/dirtyAwareApiClient';
27
28
  import { FlowView, FlowViewer } from './views/FlowView';
28
29
  import { type RunJSVersion } from './runjs-context/registry';
29
30
  type Getter<T = any> = (ctx: FlowContext) => T | Promise<T>;
31
+ export type ResolveJsonTemplateOptions = {
32
+ contractModelUid?: string | number | null;
33
+ };
30
34
  export type FlowContextDocRef = string | {
31
35
  url: string;
32
36
  title?: string;
@@ -77,6 +81,7 @@ export interface MetaTreeNode {
77
81
  hidden?: boolean | (() => boolean);
78
82
  disabled?: boolean | (() => boolean);
79
83
  disabledReason?: string | (() => string | undefined);
84
+ selectable?: boolean;
80
85
  children?: MetaTreeNode[] | (() => Promise<MetaTreeNode[]>);
81
86
  }
82
87
  export interface PropertyMeta {
@@ -343,7 +348,7 @@ declare class BaseFlowEngineContext extends FlowContext {
343
348
  * @deprecated use `resolveJsonTemplate` instead
344
349
  */
345
350
  renderJson: (template: JSONValue) => Promise<any>;
346
- resolveJsonTemplate: (template: JSONValue) => Promise<any>;
351
+ resolveJsonTemplate: (template: JSONValue, options?: ResolveJsonTemplateOptions) => Promise<any>;
347
352
  getVar: (path: string) => Promise<any>;
348
353
  request: (options: RequestOptions) => Promise<any>;
349
354
  runjs: (code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<any>;
@@ -469,6 +474,12 @@ export declare function createRunJSDeprecationProxy(ctx: any, options?: {
469
474
  doc?: RunJSDocMeta;
470
475
  }): any;
471
476
  export declare class FlowRunJSContext extends FlowContext {
477
+ [PREPARE_CONTEXT_RESOURCE_ACTION_PARAMS](action: {
478
+ actionName: string;
479
+ dataSourceKey?: string;
480
+ resourceName: string;
481
+ resourceOf?: unknown;
482
+ }, params: Record<string, unknown> | undefined): Record<string, unknown>;
472
483
  constructor(delegate: FlowContext);
473
484
  exit(): void;
474
485
  exitAll(): void;