@nocobase/flow-engine 2.2.0-alpha.3 → 2.2.0-alpha.5
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/components/FlowContextSelector.js +27 -6
- package/lib/components/variables/VariableHybridInput.d.ts +1 -0
- package/lib/components/variables/VariableHybridInput.js +18 -5
- package/lib/flowContext.js +10 -6
- package/package.json +4 -4
- package/src/__tests__/flowContext.test.ts +23 -0
- package/src/components/FlowContextSelector.tsx +29 -6
- package/src/components/variables/VariableHybridInput.tsx +19 -5
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +37 -0
- package/src/components/variables/__tests__/VariableHybridInput.test.tsx +35 -1
- package/src/flowContext.ts +4 -3
|
@@ -57,6 +57,15 @@ const cascaderPopupAutoHeightClassName = import_css.css`
|
|
|
57
57
|
max-height: 50vh;
|
|
58
58
|
}
|
|
59
59
|
`;
|
|
60
|
+
function getMetaNodeTooltip(meta) {
|
|
61
|
+
if (!meta) {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
const metaWithTooltip = meta;
|
|
65
|
+
const options = meta.options;
|
|
66
|
+
return metaWithTooltip.tooltip ?? (options == null ? void 0 : options.tooltip);
|
|
67
|
+
}
|
|
68
|
+
__name(getMetaNodeTooltip, "getMetaNodeTooltip");
|
|
60
69
|
const normalizePath = /* @__PURE__ */ __name((path) => {
|
|
61
70
|
if (!Array.isArray(path)) {
|
|
62
71
|
return void 0;
|
|
@@ -113,15 +122,27 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
|
|
|
113
122
|
const disabled = meta ? !!(typeof meta.disabled === "function" ? meta.disabled() : meta.disabled) : false;
|
|
114
123
|
const disabledReason = meta ? typeof meta.disabledReason === "function" ? meta.disabledReason() : meta.disabledReason : void 0;
|
|
115
124
|
const baseLabel = typeof o.label === "string" ? flowCtx.t(o.label) : o.label;
|
|
116
|
-
const
|
|
125
|
+
const labelText = typeof baseLabel === "string" ? baseLabel : String(o.value);
|
|
126
|
+
const tooltip = getMetaNodeTooltip(meta);
|
|
127
|
+
const tooltipTitle = disabled ? disabledReason || tooltip || flowCtx.t("This variable is not available") : tooltip;
|
|
128
|
+
const label = tooltipTitle ? /* @__PURE__ */ import_react.default.createElement("span", null, baseLabel, /* @__PURE__ */ import_react.default.createElement(
|
|
117
129
|
import_antd.Tooltip,
|
|
118
130
|
{
|
|
119
|
-
title:
|
|
120
|
-
placement: "
|
|
121
|
-
|
|
131
|
+
title: typeof tooltipTitle === "string" ? flowCtx.t(tooltipTitle) : tooltipTitle,
|
|
132
|
+
placement: "top",
|
|
133
|
+
classNames: { root: "flow-variable-tip" },
|
|
122
134
|
destroyTooltipOnHide: true
|
|
123
135
|
},
|
|
124
|
-
/* @__PURE__ */ import_react.default.createElement(
|
|
136
|
+
/* @__PURE__ */ import_react.default.createElement(
|
|
137
|
+
import_icons.QuestionCircleOutlined,
|
|
138
|
+
{
|
|
139
|
+
"aria-label": `${labelText} tooltip`,
|
|
140
|
+
style: {
|
|
141
|
+
marginLeft: token.marginXXS,
|
|
142
|
+
color: disabled ? token.colorTextDisabled : token.colorTextDescription
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
)
|
|
125
146
|
)) : baseLabel;
|
|
126
147
|
return {
|
|
127
148
|
...o,
|
|
@@ -131,7 +152,7 @@ const FlowContextSelectorComponent = /* @__PURE__ */ __name(({
|
|
|
131
152
|
};
|
|
132
153
|
});
|
|
133
154
|
},
|
|
134
|
-
[flowCtx]
|
|
155
|
+
[flowCtx, token.colorTextDescription, token.colorTextDisabled, token.marginXXS]
|
|
135
156
|
);
|
|
136
157
|
const [updateFlag, setUpdateFlag] = (0, import_react.useState)(0);
|
|
137
158
|
const [searchText, setSearchText] = (0, import_react.useState)("");
|
|
@@ -17,6 +17,7 @@ export interface VariableHybridInputProps {
|
|
|
17
17
|
value?: string;
|
|
18
18
|
onChange?: (value: string) => void;
|
|
19
19
|
disabled?: boolean;
|
|
20
|
+
readOnly?: boolean;
|
|
20
21
|
placeholder?: string;
|
|
21
22
|
addonBefore?: React.ReactNode;
|
|
22
23
|
metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
|
|
@@ -257,7 +257,7 @@ function getCurrentRange(element) {
|
|
|
257
257
|
__name(getCurrentRange, "getCurrentRange");
|
|
258
258
|
const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
|
|
259
259
|
var _a;
|
|
260
|
-
const { addonBefore, className, converters, disabled, metaTree, onChange, placeholder, style } = props;
|
|
260
|
+
const { addonBefore, className, converters, disabled, metaTree, onChange, placeholder, readOnly, style } = props;
|
|
261
261
|
const { token } = import_antd.theme.useToken();
|
|
262
262
|
const ctx = (0, import_FlowContextProvider.useFlowContext)();
|
|
263
263
|
const { resolvedMetaTree } = (0, import_useResolvedMetaTree.useResolvedMetaTree)(metaTree);
|
|
@@ -390,12 +390,13 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
|
|
|
390
390
|
);
|
|
391
391
|
const handleInput = (0, import_react.useCallback)(
|
|
392
392
|
({ currentTarget }) => {
|
|
393
|
+
if (readOnly) return;
|
|
393
394
|
if (isComposing) return;
|
|
394
395
|
setChanged(true);
|
|
395
396
|
setRange(getCurrentRange(currentTarget));
|
|
396
397
|
emitChange(currentTarget);
|
|
397
398
|
},
|
|
398
|
-
[emitChange, isComposing]
|
|
399
|
+
[emitChange, isComposing, readOnly]
|
|
399
400
|
);
|
|
400
401
|
const handleBlur = (0, import_react.useCallback)(({ currentTarget }) => {
|
|
401
402
|
setRange(getCurrentRange(currentTarget));
|
|
@@ -408,6 +409,7 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
|
|
|
408
409
|
const handlePaste = (0, import_react.useCallback)(
|
|
409
410
|
(event) => {
|
|
410
411
|
event.preventDefault();
|
|
412
|
+
if (readOnly) return;
|
|
411
413
|
const text = event.clipboardData.getData("text/plain").replace(/\n/g, " ");
|
|
412
414
|
if (!text) return;
|
|
413
415
|
setChanged(true);
|
|
@@ -415,17 +417,18 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
|
|
|
415
417
|
setRange(getCurrentRange(event.currentTarget));
|
|
416
418
|
emitChange(event.currentTarget);
|
|
417
419
|
},
|
|
418
|
-
[emitChange]
|
|
420
|
+
[emitChange, readOnly]
|
|
419
421
|
);
|
|
420
422
|
const handleCompositionStart = (0, import_react.useCallback)(() => setIsComposing(true), []);
|
|
421
423
|
const handleCompositionEnd = (0, import_react.useCallback)(
|
|
422
424
|
({ currentTarget }) => {
|
|
423
425
|
setIsComposing(false);
|
|
426
|
+
if (readOnly) return;
|
|
424
427
|
setChanged(true);
|
|
425
428
|
setRange(getCurrentRange(currentTarget));
|
|
426
429
|
emitChange(currentTarget);
|
|
427
430
|
},
|
|
428
|
-
[emitChange]
|
|
431
|
+
[emitChange, readOnly]
|
|
429
432
|
);
|
|
430
433
|
const wrapperClassName = (0, import_react.useMemo)(
|
|
431
434
|
() => import_css.css`
|
|
@@ -544,6 +547,14 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
|
|
|
544
547
|
}
|
|
545
548
|
}
|
|
546
549
|
|
|
550
|
+
&.is-readonly {
|
|
551
|
+
cursor: default;
|
|
552
|
+
|
|
553
|
+
&:hover {
|
|
554
|
+
border-color: ${token.colorBorder};
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
547
558
|
&.is-error {
|
|
548
559
|
border-color: ${token.colorError};
|
|
549
560
|
|
|
@@ -581,10 +592,12 @@ const VariableHybridInputComponent = /* @__PURE__ */ __name((props) => {
|
|
|
581
592
|
"aria-label": "textbox",
|
|
582
593
|
className: (0, import_css.cx)(editorClassName, {
|
|
583
594
|
"is-disabled": disabled,
|
|
595
|
+
"is-readonly": readOnly,
|
|
584
596
|
"is-error": effectiveStatus === "error",
|
|
585
597
|
"is-warning": effectiveStatus === "warning"
|
|
586
598
|
}),
|
|
587
|
-
contentEditable: !disabled,
|
|
599
|
+
contentEditable: !disabled && !readOnly,
|
|
600
|
+
"aria-readonly": readOnly,
|
|
588
601
|
"data-placeholder": placeholder,
|
|
589
602
|
onInput: handleInput,
|
|
590
603
|
onBlur: handleBlur,
|
package/lib/flowContext.js
CHANGED
|
@@ -2626,12 +2626,16 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
|
|
|
2626
2626
|
}, "get")
|
|
2627
2627
|
});
|
|
2628
2628
|
this.defineProperty("auth", {
|
|
2629
|
-
get: /* @__PURE__ */ __name(() =>
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2629
|
+
get: /* @__PURE__ */ __name(() => {
|
|
2630
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2631
|
+
return {
|
|
2632
|
+
roleName: (_b = (_a = this.api) == null ? void 0 : _a.auth) == null ? void 0 : _b.role,
|
|
2633
|
+
locale: (_d = (_c = this.api) == null ? void 0 : _c.auth) == null ? void 0 : _d.locale,
|
|
2634
|
+
token: (_f = (_e = this.api) == null ? void 0 : _e.auth) == null ? void 0 : _f.token,
|
|
2635
|
+
user: this.user
|
|
2636
|
+
};
|
|
2637
|
+
}, "get"),
|
|
2638
|
+
cache: false
|
|
2635
2639
|
});
|
|
2636
2640
|
this.defineProperty("date", {
|
|
2637
2641
|
get: /* @__PURE__ */ __name(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.2.0-alpha.
|
|
3
|
+
"version": "2.2.0-alpha.5",
|
|
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.
|
|
12
|
-
"@nocobase/shared": "2.2.0-alpha.
|
|
11
|
+
"@nocobase/sdk": "2.2.0-alpha.5",
|
|
12
|
+
"@nocobase/shared": "2.2.0-alpha.5",
|
|
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": "
|
|
40
|
+
"gitHead": "814ff497dd1233ca7218c9f7f2a016c2ead6516d"
|
|
41
41
|
}
|
|
@@ -1607,6 +1607,29 @@ describe('FlowEngine context', () => {
|
|
|
1607
1607
|
await expect((engine.context as any).getVar('foo.bar')).rejects.toThrow();
|
|
1608
1608
|
});
|
|
1609
1609
|
|
|
1610
|
+
it('model.context.getVar should resolve the latest ctx.auth.user after user changes', async () => {
|
|
1611
|
+
const engine = new FlowEngine();
|
|
1612
|
+
engine.context.defineProperty('api', {
|
|
1613
|
+
value: { auth: { role: 'admin', locale: 'en-US', token: 'token-1' } },
|
|
1614
|
+
});
|
|
1615
|
+
engine.context.defineProperty('user', { value: { id: 1, nickname: 'Alice' } });
|
|
1616
|
+
|
|
1617
|
+
class AuthUserModel extends FlowModel {}
|
|
1618
|
+
engine.registerModels({ AuthUserModel });
|
|
1619
|
+
const model = engine.createModel({ use: 'AuthUserModel' });
|
|
1620
|
+
|
|
1621
|
+
expect(await model.context.getVar('ctx.auth.user.id')).toBe(1);
|
|
1622
|
+
expect(await model.context.getVar('ctx.auth.token')).toBe('token-1');
|
|
1623
|
+
|
|
1624
|
+
engine.context.api.auth.role = 'member';
|
|
1625
|
+
engine.context.api.auth.token = 'token-2';
|
|
1626
|
+
engine.context.defineProperty('user', { value: { id: 2, nickname: 'Bob' } });
|
|
1627
|
+
|
|
1628
|
+
expect(await model.context.getVar('ctx.auth.user.id')).toBe(2);
|
|
1629
|
+
expect(await model.context.getVar('ctx.auth.roleName')).toBe('member');
|
|
1630
|
+
expect(await model.context.getVar('ctx.auth.token')).toBe('token-2');
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1610
1633
|
it('engine.context.runAction should resolve action from engine.getAction', async () => {
|
|
1611
1634
|
const engine = new FlowEngine();
|
|
1612
1635
|
|
|
@@ -40,6 +40,18 @@ type SelectedPathInfo = {
|
|
|
40
40
|
meta?: ContextSelectorItem['meta'];
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
type MetaNodeTooltipOptions = { tooltip?: React.ReactNode };
|
|
44
|
+
|
|
45
|
+
function getMetaNodeTooltip(meta?: ContextSelectorItem['meta']): React.ReactNode {
|
|
46
|
+
if (!meta) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const metaWithTooltip = meta as ContextSelectorItem['meta'] & MetaNodeTooltipOptions;
|
|
51
|
+
const options = meta.options as MetaNodeTooltipOptions | undefined;
|
|
52
|
+
return metaWithTooltip.tooltip ?? options?.tooltip;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
const normalizePath = (path: unknown): string[] | undefined => {
|
|
44
56
|
if (!Array.isArray(path)) {
|
|
45
57
|
return undefined;
|
|
@@ -121,17 +133,28 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
121
133
|
|
|
122
134
|
// 文本国际化:仅当 label 为字符串时进行翻译
|
|
123
135
|
const baseLabel = typeof o.label === 'string' ? flowCtx.t(o.label) : o.label;
|
|
136
|
+
const labelText = typeof baseLabel === 'string' ? baseLabel : String(o.value);
|
|
137
|
+
const tooltip = getMetaNodeTooltip(meta);
|
|
138
|
+
const tooltipTitle = disabled
|
|
139
|
+
? disabledReason || tooltip || flowCtx.t('This variable is not available')
|
|
140
|
+
: tooltip;
|
|
124
141
|
|
|
125
|
-
const label =
|
|
142
|
+
const label = tooltipTitle ? (
|
|
126
143
|
<span>
|
|
127
144
|
{baseLabel}
|
|
128
145
|
<Tooltip
|
|
129
|
-
title={
|
|
130
|
-
placement="
|
|
131
|
-
|
|
146
|
+
title={typeof tooltipTitle === 'string' ? flowCtx.t(tooltipTitle) : tooltipTitle}
|
|
147
|
+
placement="top"
|
|
148
|
+
classNames={{ root: 'flow-variable-tip' }}
|
|
132
149
|
destroyTooltipOnHide
|
|
133
150
|
>
|
|
134
|
-
<QuestionCircleOutlined
|
|
151
|
+
<QuestionCircleOutlined
|
|
152
|
+
aria-label={`${labelText} tooltip`}
|
|
153
|
+
style={{
|
|
154
|
+
marginLeft: token.marginXXS,
|
|
155
|
+
color: disabled ? token.colorTextDisabled : token.colorTextDescription,
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
135
158
|
</Tooltip>
|
|
136
159
|
</span>
|
|
137
160
|
) : (
|
|
@@ -146,7 +169,7 @@ const FlowContextSelectorComponent: React.FC<FlowContextSelectorProps> = ({
|
|
|
146
169
|
};
|
|
147
170
|
});
|
|
148
171
|
},
|
|
149
|
-
[flowCtx],
|
|
172
|
+
[flowCtx, token.colorTextDescription, token.colorTextDisabled, token.marginXXS],
|
|
150
173
|
);
|
|
151
174
|
|
|
152
175
|
// 用于强制重新渲染的状态
|
|
@@ -36,6 +36,7 @@ export interface VariableHybridInputProps {
|
|
|
36
36
|
value?: string;
|
|
37
37
|
onChange?: (value: string) => void;
|
|
38
38
|
disabled?: boolean;
|
|
39
|
+
readOnly?: boolean;
|
|
39
40
|
placeholder?: string;
|
|
40
41
|
addonBefore?: React.ReactNode;
|
|
41
42
|
metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
|
|
@@ -295,7 +296,7 @@ function getCurrentRange(element: HTMLElement): RangeIndexes {
|
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
const VariableHybridInputComponent: React.FC<VariableHybridInputProps> = (props) => {
|
|
298
|
-
const { addonBefore, className, converters, disabled, metaTree, onChange, placeholder, style } = props;
|
|
299
|
+
const { addonBefore, className, converters, disabled, metaTree, onChange, placeholder, readOnly, style } = props;
|
|
299
300
|
const { token } = theme.useToken();
|
|
300
301
|
const ctx = useFlowContext();
|
|
301
302
|
const { resolvedMetaTree } = useResolvedMetaTree(metaTree);
|
|
@@ -458,12 +459,13 @@ const VariableHybridInputComponent: React.FC<VariableHybridInputProps> = (props)
|
|
|
458
459
|
|
|
459
460
|
const handleInput = useCallback(
|
|
460
461
|
({ currentTarget }: React.FormEvent<HTMLDivElement>) => {
|
|
462
|
+
if (readOnly) return;
|
|
461
463
|
if (isComposing) return;
|
|
462
464
|
setChanged(true);
|
|
463
465
|
setRange(getCurrentRange(currentTarget));
|
|
464
466
|
emitChange(currentTarget);
|
|
465
467
|
},
|
|
466
|
-
[emitChange, isComposing],
|
|
468
|
+
[emitChange, isComposing, readOnly],
|
|
467
469
|
);
|
|
468
470
|
|
|
469
471
|
const handleBlur = useCallback(({ currentTarget }: React.FocusEvent<HTMLDivElement>) => {
|
|
@@ -479,6 +481,7 @@ const VariableHybridInputComponent: React.FC<VariableHybridInputProps> = (props)
|
|
|
479
481
|
const handlePaste = useCallback(
|
|
480
482
|
(event: React.ClipboardEvent<HTMLDivElement>) => {
|
|
481
483
|
event.preventDefault();
|
|
484
|
+
if (readOnly) return;
|
|
482
485
|
// Paste as plain text only; variable tags must be inserted via the picker.
|
|
483
486
|
const text = event.clipboardData.getData('text/plain').replace(/\n/g, ' ');
|
|
484
487
|
if (!text) return;
|
|
@@ -487,18 +490,19 @@ const VariableHybridInputComponent: React.FC<VariableHybridInputProps> = (props)
|
|
|
487
490
|
setRange(getCurrentRange(event.currentTarget));
|
|
488
491
|
emitChange(event.currentTarget);
|
|
489
492
|
},
|
|
490
|
-
[emitChange],
|
|
493
|
+
[emitChange, readOnly],
|
|
491
494
|
);
|
|
492
495
|
|
|
493
496
|
const handleCompositionStart = useCallback(() => setIsComposing(true), []);
|
|
494
497
|
const handleCompositionEnd = useCallback(
|
|
495
498
|
({ currentTarget }: React.CompositionEvent<HTMLDivElement>) => {
|
|
496
499
|
setIsComposing(false);
|
|
500
|
+
if (readOnly) return;
|
|
497
501
|
setChanged(true);
|
|
498
502
|
setRange(getCurrentRange(currentTarget));
|
|
499
503
|
emitChange(currentTarget);
|
|
500
504
|
},
|
|
501
|
-
[emitChange],
|
|
505
|
+
[emitChange, readOnly],
|
|
502
506
|
);
|
|
503
507
|
|
|
504
508
|
const wrapperClassName = useMemo(
|
|
@@ -620,6 +624,14 @@ const VariableHybridInputComponent: React.FC<VariableHybridInputProps> = (props)
|
|
|
620
624
|
}
|
|
621
625
|
}
|
|
622
626
|
|
|
627
|
+
&.is-readonly {
|
|
628
|
+
cursor: default;
|
|
629
|
+
|
|
630
|
+
&:hover {
|
|
631
|
+
border-color: ${token.colorBorder};
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
623
635
|
&.is-error {
|
|
624
636
|
border-color: ${token.colorError};
|
|
625
637
|
|
|
@@ -660,10 +672,12 @@ const VariableHybridInputComponent: React.FC<VariableHybridInputProps> = (props)
|
|
|
660
672
|
aria-label="textbox"
|
|
661
673
|
className={cx(editorClassName, {
|
|
662
674
|
'is-disabled': disabled,
|
|
675
|
+
'is-readonly': readOnly,
|
|
663
676
|
'is-error': effectiveStatus === 'error',
|
|
664
677
|
'is-warning': effectiveStatus === 'warning',
|
|
665
678
|
})}
|
|
666
|
-
contentEditable={!disabled}
|
|
679
|
+
contentEditable={!disabled && !readOnly}
|
|
680
|
+
aria-readonly={readOnly}
|
|
667
681
|
data-placeholder={placeholder}
|
|
668
682
|
onInput={handleInput}
|
|
669
683
|
onBlur={handleBlur}
|
|
@@ -284,6 +284,43 @@ describe('FlowContextSelector', () => {
|
|
|
284
284
|
});
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
+
it('shows enabled variable item tooltip above the menu item', async () => {
|
|
288
|
+
const flowContext = createTestFlowContext();
|
|
289
|
+
const metaTree: MetaTreeNode[] = [
|
|
290
|
+
{
|
|
291
|
+
name: '$system',
|
|
292
|
+
title: 'System variables',
|
|
293
|
+
type: '',
|
|
294
|
+
paths: ['$system'],
|
|
295
|
+
children: [
|
|
296
|
+
{
|
|
297
|
+
name: 'instanceId',
|
|
298
|
+
title: 'Instance ID',
|
|
299
|
+
type: '',
|
|
300
|
+
paths: ['$system', 'instanceId'],
|
|
301
|
+
options: { tooltip: 'The ID of current server instance' },
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
render(
|
|
308
|
+
<TestFlowContextWrapper context={flowContext}>
|
|
309
|
+
<FlowContextSelector metaTree={metaTree} />
|
|
310
|
+
</TestFlowContextWrapper>,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
fireEvent.click(screen.getByRole('button'));
|
|
314
|
+
fireEvent.click(await screen.findByText('System variables'));
|
|
315
|
+
|
|
316
|
+
expect(await screen.findByText('Instance ID')).toBeInTheDocument();
|
|
317
|
+
const tooltipIcon = screen.getByLabelText('Instance ID tooltip');
|
|
318
|
+
fireEvent.mouseEnter(tooltipIcon);
|
|
319
|
+
|
|
320
|
+
const tooltip = await screen.findByText('The ID of current server instance');
|
|
321
|
+
expect(tooltip.closest('.ant-tooltip')).toHaveClass('ant-tooltip-placement-top');
|
|
322
|
+
});
|
|
323
|
+
|
|
287
324
|
it('should support inline search input when children is null and keep lazy expand', async () => {
|
|
288
325
|
const flowContext = createTestFlowContext();
|
|
289
326
|
const loadOrgChildren = vi.fn(async () => [
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Plus the top-level (already-loaded) and not-in-tree (raw-token fallback) cases.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { render, waitFor } from '@testing-library/react';
|
|
17
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
18
18
|
import React from 'react';
|
|
19
19
|
import { describe, expect, it, vi } from 'vitest';
|
|
20
20
|
import { VariableHybridInput, type VariableHybridInputConverters } from '../VariableHybridInput';
|
|
@@ -175,4 +175,38 @@ describe('VariableHybridInput — saved reference labels', () => {
|
|
|
175
175
|
expect(tag?.textContent).toBe('{{$missing.field}}');
|
|
176
176
|
});
|
|
177
177
|
});
|
|
178
|
+
|
|
179
|
+
it('keeps the selector enabled when only the editor is read-only', async () => {
|
|
180
|
+
const flowContext = createTestFlowContext();
|
|
181
|
+
const onChange = vi.fn();
|
|
182
|
+
const metaTree: MetaTreeNode[] = [
|
|
183
|
+
{
|
|
184
|
+
name: '$user',
|
|
185
|
+
title: 'User',
|
|
186
|
+
type: '',
|
|
187
|
+
paths: ['$user'],
|
|
188
|
+
children: [{ name: 'name', title: 'Name', type: 'string', paths: ['$user', 'name'] }],
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
render(
|
|
193
|
+
<TestFlowContextWrapper context={flowContext}>
|
|
194
|
+
<VariableHybridInput
|
|
195
|
+
readOnly
|
|
196
|
+
value=""
|
|
197
|
+
onChange={onChange}
|
|
198
|
+
metaTree={metaTree}
|
|
199
|
+
converters={workflowConverters}
|
|
200
|
+
/>
|
|
201
|
+
</TestFlowContextWrapper>,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const editor = screen.getByRole('textbox');
|
|
205
|
+
expect(editor).toHaveAttribute('contenteditable', 'false');
|
|
206
|
+
expect(editor).toHaveAttribute('aria-readonly', 'true');
|
|
207
|
+
|
|
208
|
+
fireEvent.input(editor, { currentTarget: { textContent: 'manual' } });
|
|
209
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
210
|
+
expect(screen.getByRole('button')).not.toBeDisabled();
|
|
211
|
+
});
|
|
178
212
|
});
|
package/src/flowContext.ts
CHANGED
|
@@ -3504,11 +3504,12 @@ export class FlowEngineContext extends BaseFlowEngineContext {
|
|
|
3504
3504
|
});
|
|
3505
3505
|
this.defineProperty('auth', {
|
|
3506
3506
|
get: () => ({
|
|
3507
|
-
roleName: this.api
|
|
3508
|
-
locale: this.api
|
|
3509
|
-
token: this.api
|
|
3507
|
+
roleName: this.api?.auth?.role,
|
|
3508
|
+
locale: this.api?.auth?.locale,
|
|
3509
|
+
token: this.api?.auth?.token,
|
|
3510
3510
|
user: this.user,
|
|
3511
3511
|
}),
|
|
3512
|
+
cache: false,
|
|
3512
3513
|
});
|
|
3513
3514
|
this.defineProperty('date', {
|
|
3514
3515
|
get: () => {
|