@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.
- package/lib/acl/Acl.d.ts +2 -1
- package/lib/acl/Acl.js +28 -0
- package/lib/components/FlowContextSelector.js +62 -13
- package/lib/components/FormItem.js +11 -7
- package/lib/components/MobilePopup.js +39 -10
- package/lib/components/MobilePopup.style.js +11 -1
- package/lib/components/subModel/LazyDropdown.js +62 -33
- package/lib/components/variables/VariableHybridInput.d.ts +9 -0
- package/lib/components/variables/VariableHybridInput.js +146 -17
- package/lib/components/variables/VariableInput.js +19 -7
- package/lib/components/variables/VariableTag.js +48 -36
- package/lib/components/variables/types.d.ts +21 -0
- package/lib/flowContext.d.ts +12 -1
- package/lib/flowContext.js +57 -12
- package/lib/flowEngine.js +6 -0
- package/lib/flowI18n.js +3 -3
- package/lib/locale/en-US.json +2 -0
- package/lib/locale/index.d.ts +4 -0
- package/lib/locale/zh-CN.json +2 -0
- package/lib/resources/flowResource.js +1 -0
- package/lib/types.d.ts +3 -1
- package/lib/types.js +1 -0
- package/lib/utils/associationObjectVariable.d.ts +10 -0
- package/lib/utils/associationObjectVariable.js +10 -7
- package/lib/utils/dateVariable.d.ts +22 -0
- package/lib/utils/dateVariable.js +123 -16
- package/lib/utils/dirtyAwareApiClient.d.ts +1 -0
- package/lib/utils/dirtyAwareApiClient.js +280 -13
- package/lib/utils/index.d.ts +3 -3
- package/lib/utils/index.js +8 -0
- package/lib/utils/loadedPageCache.d.ts +1 -0
- package/lib/utils/loadedPageCache.js +6 -0
- package/lib/utils/params-resolvers.d.ts +3 -0
- package/lib/utils/params-resolvers.js +10 -0
- package/lib/utils/variablesParams.js +5 -0
- package/lib/views/createViewMeta.d.ts +1 -0
- package/lib/views/createViewMeta.js +53 -22
- package/package.json +4 -4
- package/src/__tests__/createViewMeta.popup.test.ts +84 -1
- package/src/__tests__/flowContext.test.ts +31 -0
- package/src/__tests__/flowI18n.test.ts +11 -0
- package/src/__tests__/objectVariable.test.ts +6 -1
- package/src/__tests__/runjsFormSubmit.test.ts +138 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +72 -6
- package/src/acl/Acl.tsx +36 -1
- package/src/acl/__tests__/Acl.test.tsx +70 -0
- package/src/components/FlowContextSelector.tsx +73 -12
- package/src/components/FormItem.tsx +12 -7
- package/src/components/MobilePopup.style.ts +12 -1
- package/src/components/MobilePopup.tsx +42 -10
- package/src/components/__tests__/FormItem.test.tsx +17 -2
- package/src/components/__tests__/MobilePopup.test.tsx +150 -0
- package/src/components/subModel/LazyDropdown.tsx +71 -38
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +85 -2
- package/src/components/subModel/__tests__/LazyDropdown.test.tsx +202 -0
- package/src/components/variables/VariableHybridInput.tsx +185 -14
- package/src/components/variables/VariableInput.tsx +32 -7
- package/src/components/variables/VariableTag.tsx +51 -37
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +95 -3
- package/src/components/variables/__tests__/VariableHybridInput.test.tsx +212 -0
- package/src/components/variables/__tests__/VariableInput.test.tsx +202 -6
- package/src/components/variables/__tests__/VariableTag.test.tsx +80 -0
- package/src/components/variables/types.ts +21 -0
- package/src/flowContext.ts +83 -9
- package/src/flowEngine.ts +6 -0
- package/src/flowI18n.ts +8 -3
- package/src/locale/__tests__/index.test.ts +21 -0
- package/src/locale/en-US.json +2 -0
- package/src/locale/zh-CN.json +2 -0
- package/src/resources/__tests__/flowResource.test.ts +3 -0
- package/src/resources/flowResource.ts +1 -0
- package/src/types.ts +2 -0
- package/src/utils/__tests__/dateVariable.test.ts +57 -4
- package/src/utils/__tests__/dirtyAwareApiClient.test.ts +321 -0
- package/src/utils/__tests__/variablesParams.test.ts +28 -1
- package/src/utils/associationObjectVariable.ts +9 -6
- package/src/utils/dateVariable.ts +145 -18
- package/src/utils/dirtyAwareApiClient.ts +348 -13
- package/src/utils/index.ts +17 -2
- package/src/utils/loadedPageCache.ts +7 -0
- package/src/utils/params-resolvers.ts +12 -0
- package/src/utils/variablesParams.ts +10 -0
- 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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
324
|
+
setHtml(renderHTML(value, variableRegExp, resolveLabel));
|
|
239
325
|
if (!changed) {
|
|
240
326
|
setRange([-1, 0, -1, 0]);
|
|
241
327
|
}
|
|
242
|
-
}, [value,
|
|
328
|
+
}, [value, resolveLabel]);
|
|
243
329
|
(0, import_react.useEffect)(() => {
|
|
244
|
-
var
|
|
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 = (
|
|
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
|
|
615
|
+
parseValueToPath,
|
|
487
616
|
formatPathToValue: (item) => {
|
|
488
|
-
var
|
|
489
|
-
return ((
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
return findMetaTreeNodeByPath(resolvedMetaTree, path);
|
|
141
|
+
if (resolvedPath) {
|
|
142
|
+
return findMetaTreeNodeByPath(resolvedMetaTree, resolvedPath);
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
return null;
|
|
144
|
-
}, [currentMetaTreeNode,
|
|
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
|
|
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:
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
133
|
-
const
|
|
134
|
-
return /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title:
|
|
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:
|
|
179
|
-
mode: "tags",
|
|
188
|
+
value: (displayState == null ? void 0 : displayState.text) ? [displayState.text] : [],
|
|
189
|
+
mode: allowCustomTagInput ? "tags" : "multiple",
|
|
180
190
|
open: false,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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;
|
package/lib/flowContext.d.ts
CHANGED
|
@@ -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;
|