@nocobase/flow-engine 2.2.0-alpha.8 → 2.2.0-alpha.9

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.
@@ -194,12 +194,12 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
194
194
  const ValueComponent = (0, import_react.useMemo)(() => {
195
195
  const Component = renderInputComponent == null ? void 0 : renderInputComponent(resolvedMetaTreeNode);
196
196
  const CustomComponent = resolvedMetaTreeNode == null ? void 0 : resolvedMetaTreeNode.render;
197
- const shouldRenderVariableTag = (0, import_utils.isVariableValue)(innerValue) || Boolean(resolvedMetaTreeNode) && Array.isArray(resolvedPath) && resolvedPath.length > 0 && !Component && !CustomComponent;
197
+ const shouldRenderVariableTag = (0, import_utils.isVariableValue)(innerValue) || Array.isArray(resolvedPath) && resolvedPath.length > 0 && !Component && !CustomComponent;
198
198
  const finalComponent = shouldRenderVariableTag ? import_VariableTag.VariableTag : Component || CustomComponent || import_antd.Input;
199
199
  return finalComponent;
200
200
  }, [renderInputComponent, resolvedMetaTreeNode, innerValue, resolvedPath]);
201
201
  const isVariableActive = (0, import_react.useMemo)(() => {
202
- return (0, import_utils.isVariableValue)(innerValue) || Boolean(resolvedMetaTreeNode) && Array.isArray(resolvedPath) && resolvedPath.length > 0 && !(renderInputComponent == null ? void 0 : renderInputComponent(resolvedMetaTreeNode)) && !(resolvedMetaTreeNode == null ? void 0 : resolvedMetaTreeNode.render);
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
203
  }, [innerValue, renderInputComponent, resolvedMetaTreeNode, resolvedPath]);
204
204
  (0, import_react.useEffect)(() => {
205
205
  if (!resolvedMetaTreeNode) return;
@@ -288,6 +288,7 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
288
288
  allowCustomTagInput: false,
289
289
  metaTreeNode: resolvedMetaTreeNode,
290
290
  metaTree,
291
+ resolvedPath,
291
292
  style: stableProps.style
292
293
  };
293
294
  }
@@ -308,6 +309,7 @@ const VariableInputComponent = /* @__PURE__ */ __name(({
308
309
  disabled,
309
310
  handleClear,
310
311
  resolvedMetaTreeNode,
312
+ resolvedPath,
311
313
  metaTree,
312
314
  ValueComponent,
313
315
  stableProps
@@ -56,7 +56,8 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
56
56
  className,
57
57
  style,
58
58
  metaTreeNode,
59
- metaTree
59
+ metaTree,
60
+ resolvedPath
60
61
  }) => {
61
62
  const { resolvedMetaTree } = (0, import_useResolvedMetaTree.useResolvedMetaTree)(metaTree);
62
63
  const ctx = (0, import_FlowContextProvider.useFlowContext)();
@@ -102,7 +103,7 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
102
103
  };
103
104
  }
104
105
  if (metaTreeNode) {
105
- const rawPath2 = (0, import_utils.parseValueToPath)(value) || metaTreeNode.paths;
106
+ const rawPath2 = resolvedPath || (0, import_utils.parseValueToPath)(value) || metaTreeNode.paths;
106
107
  const result2 = await resolveLabelFromPath(rawPath2);
107
108
  if (result2.resolved && result2.label != null) {
108
109
  return { text: result2.label, invalid: false };
@@ -113,7 +114,7 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
113
114
  return { text: ctx.t(metaTreeNode.title) ?? "", invalid: false };
114
115
  }
115
116
  if (!value) return { text: String(value), invalid: false };
116
- const rawPath = (0, import_utils.parseValueToPath)(value);
117
+ const rawPath = resolvedPath || (0, import_utils.parseValueToPath)(value);
117
118
  const result = await resolveLabelFromPath(rawPath);
118
119
  if (result.resolved && result.label != null) {
119
120
  return { text: result.label, invalid: false };
@@ -123,7 +124,7 @@ const VariableTagComponent = /* @__PURE__ */ __name(({
123
124
  }
124
125
  return { text: Array.isArray(rawPath) ? rawPath.join("/") : String(value), invalid: false };
125
126
  },
126
- { refreshDeps: [resolvedMetaTree, value, metaTreeNode] }
127
+ { refreshDeps: [resolvedMetaTree, resolvedPath, value, metaTreeNode] }
127
128
  );
128
129
  const { token } = import_antd.theme.useToken();
129
130
  const shrinkableOverflowItem = import_css.css`
@@ -86,6 +86,7 @@ export interface VariableInputProps {
86
86
  }
87
87
  export interface VariableTagProps {
88
88
  value?: string;
89
+ resolvedPath?: Array<string | number>;
89
90
  onClear?: () => void;
90
91
  disabled?: boolean;
91
92
  allowCustomTagInput?: boolean;
package/lib/types.d.ts CHANGED
@@ -122,7 +122,9 @@ export declare enum ActionScene {
122
122
  /** 动态事件流可用 */
123
123
  DYNAMIC_EVENT_FLOW = 6,
124
124
  /** 菜单项联动规则可用 */
125
- MENU_LINKAGE_RULES = 7
125
+ MENU_LINKAGE_RULES = 7,
126
+ /** 标签页联动规则可用 */
127
+ TAB_LINKAGE_RULES = 8
126
128
  }
127
129
  /**
128
130
  * Defines a reusable action with generic model type support.
package/lib/types.js CHANGED
@@ -37,6 +37,7 @@ var ActionScene = /* @__PURE__ */ ((ActionScene2) => {
37
37
  ActionScene2[ActionScene2["ACTION_LINKAGE_RULES"] = 5] = "ACTION_LINKAGE_RULES";
38
38
  ActionScene2[ActionScene2["DYNAMIC_EVENT_FLOW"] = 6] = "DYNAMIC_EVENT_FLOW";
39
39
  ActionScene2[ActionScene2["MENU_LINKAGE_RULES"] = 7] = "MENU_LINKAGE_RULES";
40
+ ActionScene2[ActionScene2["TAB_LINKAGE_RULES"] = 8] = "TAB_LINKAGE_RULES";
40
41
  return ActionScene2;
41
42
  })(ActionScene || {});
42
43
  // Annotate the CommonJS export names for ESM import in node:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "2.2.0-alpha.8",
3
+ "version": "2.2.0-alpha.9",
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.2.0-alpha.8",
12
- "@nocobase/shared": "2.2.0-alpha.8",
11
+ "@nocobase/sdk": "2.2.0-alpha.9",
12
+ "@nocobase/shared": "2.2.0-alpha.9",
13
13
  "ahooks": "^3.7.2",
14
14
  "axios": "^1.7.0",
15
15
  "dayjs": "^1.11.9",
@@ -37,5 +37,5 @@
37
37
  ],
38
38
  "author": "NocoBase Team",
39
39
  "license": "Apache-2.0",
40
- "gitHead": "810c81e5963966bda7ec03b6453a3a80fe60e027"
40
+ "gitHead": "4949bf5ebcd92d77a3a0a718ed578c270e4bd570"
41
41
  }
@@ -201,16 +201,11 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
201
201
  const Component = renderInputComponent?.(resolvedMetaTreeNode);
202
202
  const CustomComponent = resolvedMetaTreeNode?.render;
203
203
  // Some domains (workflow) persist variables as `{{$context...}}` rather than the core `{{ ctx... }}` form.
204
- // Those values are not recognized by `isVariableValue`, but if the active converters can resolve them back to a
205
- // real meta-tree path and that path maps to a plain variable node (not Constant / Null / RunJS), they should still
206
- // render as the labelled pill instead of falling back to the raw input text.
204
+ // When a custom converter recognizes such a value as a variable path, render VariableTag even if the path no longer
205
+ // exists in the current meta tree. VariableTag will then show either the resolved label or the parsing-failed state.
207
206
  const shouldRenderVariableTag =
208
207
  isVariableValue(innerValue) ||
209
- (Boolean(resolvedMetaTreeNode) &&
210
- Array.isArray(resolvedPath) &&
211
- resolvedPath.length > 0 &&
212
- !Component &&
213
- !CustomComponent);
208
+ (Array.isArray(resolvedPath) && resolvedPath.length > 0 && !Component && !CustomComponent);
214
209
  const finalComponent = shouldRenderVariableTag ? VariableTag : Component || CustomComponent || Input;
215
210
  return finalComponent;
216
211
  }, [renderInputComponent, resolvedMetaTreeNode, innerValue, resolvedPath]);
@@ -218,8 +213,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
218
213
  const isVariableActive = useMemo(() => {
219
214
  return (
220
215
  isVariableValue(innerValue) ||
221
- (Boolean(resolvedMetaTreeNode) &&
222
- Array.isArray(resolvedPath) &&
216
+ (Array.isArray(resolvedPath) &&
223
217
  resolvedPath.length > 0 &&
224
218
  !renderInputComponent?.(resolvedMetaTreeNode) &&
225
219
  !resolvedMetaTreeNode?.render)
@@ -332,6 +326,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
332
326
  allowCustomTagInput: false,
333
327
  metaTreeNode: resolvedMetaTreeNode,
334
328
  metaTree,
329
+ resolvedPath,
335
330
  style: stableProps.style,
336
331
  };
337
332
  }
@@ -354,6 +349,7 @@ const VariableInputComponent: React.FC<VariableInputProps> = ({
354
349
  disabled,
355
350
  handleClear,
356
351
  resolvedMetaTreeNode,
352
+ resolvedPath,
357
353
  metaTree,
358
354
  ValueComponent,
359
355
  stableProps,
@@ -28,6 +28,7 @@ const VariableTagComponent: React.FC<VariableTagProps> = ({
28
28
  style,
29
29
  metaTreeNode,
30
30
  metaTree,
31
+ resolvedPath,
31
32
  }) => {
32
33
  const { resolvedMetaTree } = useResolvedMetaTree(metaTree);
33
34
  const ctx = useFlowContext();
@@ -87,7 +88,7 @@ const VariableTagComponent: React.FC<VariableTagProps> = ({
87
88
 
88
89
  // 2) metaTreeNode 存在但缺少 parentTitles:尝试根据 value/metaTreeNode.paths 从 metaTree 还原完整路径
89
90
  if (metaTreeNode) {
90
- const rawPath = parseValueToPath(value) || metaTreeNode.paths;
91
+ const rawPath = resolvedPath || parseValueToPath(value) || metaTreeNode.paths;
91
92
  const result = await resolveLabelFromPath(rawPath as any);
92
93
  if (result.resolved && result.label != null) {
93
94
  return { text: result.label, invalid: false };
@@ -100,7 +101,7 @@ const VariableTagComponent: React.FC<VariableTagProps> = ({
100
101
 
101
102
  // 3) 无 metaTreeNode:从 value 还原路径并拼接标题链;若找不到任何前缀则回退原始路径字符串
102
103
  if (!value) return { text: String(value), invalid: false };
103
- const rawPath = parseValueToPath(value);
104
+ const rawPath = resolvedPath || parseValueToPath(value);
104
105
  const result = await resolveLabelFromPath(rawPath as any);
105
106
  if (result.resolved && result.label != null) {
106
107
  return { text: result.label, invalid: false };
@@ -110,7 +111,7 @@ const VariableTagComponent: React.FC<VariableTagProps> = ({
110
111
  }
111
112
  return { text: Array.isArray(rawPath) ? rawPath.join('/') : String(value), invalid: false };
112
113
  },
113
- { refreshDeps: [resolvedMetaTree, value, metaTreeNode] },
114
+ { refreshDeps: [resolvedMetaTree, resolvedPath, value, metaTreeNode] },
114
115
  );
115
116
 
116
117
  const { token } = theme.useToken();
@@ -155,6 +155,52 @@ describe('VariableInput', () => {
155
155
  expect(selectorButton.className).toContain('ant-btn-primary');
156
156
  });
157
157
 
158
+ it('renders a parsing failure tag when a custom variable path is missing from the meta tree', async () => {
159
+ const flowContext = createTestFlowContext();
160
+ const workflowMetaTree = [
161
+ {
162
+ name: '$context',
163
+ title: 'Trigger variables',
164
+ type: 'object',
165
+ paths: ['$context'],
166
+ children: [
167
+ {
168
+ name: 'data',
169
+ title: 'Trigger data',
170
+ type: 'object',
171
+ paths: ['$context', 'data'],
172
+ },
173
+ ],
174
+ },
175
+ ];
176
+
177
+ render(
178
+ <TestFlowContextWrapper context={flowContext}>
179
+ <VariableInput
180
+ value="{{$context.data.id}}"
181
+ metaTree={workflowMetaTree}
182
+ converters={{
183
+ resolvePathFromValue: (currentValue) =>
184
+ currentValue === '{{$context.data.id}}' ? ['$context', 'data', 'id'] : undefined,
185
+ resolveValueFromPath: () => undefined,
186
+ }}
187
+ />
188
+ </TestFlowContextWrapper>,
189
+ );
190
+
191
+ await waitFor(
192
+ () => {
193
+ const variableTag = screen.getByText('Variable parsing failed');
194
+ expect(variableTag).toBeInTheDocument();
195
+ expect(variableTag.closest('.ant-tag')).toHaveClass('ant-tag-error');
196
+ },
197
+ { timeout: 3000 },
198
+ );
199
+
200
+ expect(screen.queryByDisplayValue('{{$context.data.id}}')).not.toBeInTheDocument();
201
+ expect(screen.getByRole('button').className).toContain('ant-btn-primary');
202
+ });
203
+
158
204
  it('disables custom tag input when rendering a selected variable', async () => {
159
205
  const flowContext = createTestFlowContext();
160
206
  const { container } = render(
@@ -94,6 +94,7 @@ export interface VariableInputProps {
94
94
 
95
95
  export interface VariableTagProps {
96
96
  value?: string;
97
+ resolvedPath?: Array<string | number>;
97
98
  onClear?: () => void;
98
99
  disabled?: boolean;
99
100
  allowCustomTagInput?: boolean;
package/src/types.ts CHANGED
@@ -147,6 +147,8 @@ export enum ActionScene {
147
147
  DYNAMIC_EVENT_FLOW,
148
148
  /** 菜单项联动规则可用 */
149
149
  MENU_LINKAGE_RULES,
150
+ /** 标签页联动规则可用 */
151
+ TAB_LINKAGE_RULES,
150
152
  }
151
153
 
152
154
  /**