@burdenoff/fluidgrids-fe-sdk 2026.918.1 → 2026.922.1
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/dist/{WorkflowBuilder-S2PXF5RZ.js → WorkflowBuilder-RG4QXBZN.js} +4 -4
- package/dist/{WorkflowBuilder-S2PXF5RZ.js.map → WorkflowBuilder-RG4QXBZN.js.map} +1 -1
- package/dist/{chunk-ZUKIBJVG.js → chunk-EFLYJTQI.js} +3 -3
- package/dist/{chunk-ZUKIBJVG.js.map → chunk-EFLYJTQI.js.map} +1 -1
- package/dist/{chunk-UVG4SYBS.js → chunk-GCUAXFMW.js} +3 -3
- package/dist/{chunk-UVG4SYBS.js.map → chunk-GCUAXFMW.js.map} +1 -1
- package/dist/{chunk-TVZ5UUGI.js → chunk-ND4ACNAW.js} +3 -3
- package/dist/{chunk-TVZ5UUGI.js.map → chunk-ND4ACNAW.js.map} +1 -1
- package/dist/{chunk-UVBJY5FB.js → chunk-XASNLRFQ.js} +545 -37
- package/dist/chunk-XASNLRFQ.js.map +1 -0
- package/dist/index.js +7 -7
- package/dist/templates.js +2 -2
- package/dist/workflow-builder.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-UVBJY5FB.js.map +0 -1
|
@@ -224,6 +224,245 @@ function stripSubmitActions(card) {
|
|
|
224
224
|
}
|
|
225
225
|
return result;
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
// src/builder/components/lib/expression-preview.ts
|
|
229
|
+
var PURE_EXPRESSION_REGEX = /^\{\{(.*)\}\}$/s;
|
|
230
|
+
var PURE_LEGACY_REGEX = /^\$\{(.*)\}$/s;
|
|
231
|
+
function containsExpressionSyntax(str) {
|
|
232
|
+
return typeof str === "string" && str.includes("{{");
|
|
233
|
+
}
|
|
234
|
+
function findEmbeddedExpressions(str) {
|
|
235
|
+
const matches = [];
|
|
236
|
+
let searchFrom = 0;
|
|
237
|
+
while (searchFrom < str.length) {
|
|
238
|
+
const start = str.indexOf("{{", searchFrom);
|
|
239
|
+
if (start === -1) break;
|
|
240
|
+
const end = str.indexOf("}}", start + 2);
|
|
241
|
+
if (end === -1) break;
|
|
242
|
+
matches.push({
|
|
243
|
+
full: str.slice(start, end + 2),
|
|
244
|
+
expression: str.slice(start + 2, end).trim()
|
|
245
|
+
});
|
|
246
|
+
searchFrom = end + 2;
|
|
247
|
+
}
|
|
248
|
+
return matches;
|
|
249
|
+
}
|
|
250
|
+
function toItems(value) {
|
|
251
|
+
if (value === void 0 || value === null) return [];
|
|
252
|
+
if (Array.isArray(value)) return value.map((entry) => ({ json: entry }));
|
|
253
|
+
return [{ json: value }];
|
|
254
|
+
}
|
|
255
|
+
var SAFE_GLOBALS = {
|
|
256
|
+
parseInt,
|
|
257
|
+
parseFloat,
|
|
258
|
+
isNaN,
|
|
259
|
+
isFinite,
|
|
260
|
+
Number,
|
|
261
|
+
String,
|
|
262
|
+
Boolean,
|
|
263
|
+
JSON,
|
|
264
|
+
Date,
|
|
265
|
+
Math,
|
|
266
|
+
Array,
|
|
267
|
+
Object,
|
|
268
|
+
console: {
|
|
269
|
+
log: () => void 0,
|
|
270
|
+
error: () => void 0,
|
|
271
|
+
warn: () => void 0
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
function buildExpressionSandbox(scope) {
|
|
275
|
+
const inputItems = toItems(scope.input);
|
|
276
|
+
const currentItem = inputItems[0] ?? { json: {} };
|
|
277
|
+
const itemsForNode = (nodeName) => toItems(scope.nodeOutputs[nodeName]);
|
|
278
|
+
const dollarNode = (nodeName) => {
|
|
279
|
+
const items = itemsForNode(nodeName);
|
|
280
|
+
const at = items[0] ?? { json: {} };
|
|
281
|
+
return {
|
|
282
|
+
item: at.json,
|
|
283
|
+
all: () => items,
|
|
284
|
+
first: () => items[0]?.json ?? {},
|
|
285
|
+
last: () => items[items.length - 1]?.json ?? {}
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
const nodeObject = {};
|
|
289
|
+
for (const nodeName of Object.keys(scope.nodeOutputs)) {
|
|
290
|
+
nodeObject[nodeName] = dollarNode(nodeName);
|
|
291
|
+
}
|
|
292
|
+
const now = /* @__PURE__ */ new Date();
|
|
293
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
294
|
+
return {
|
|
295
|
+
...SAFE_GLOBALS,
|
|
296
|
+
$json: currentItem.json,
|
|
297
|
+
$node: nodeObject,
|
|
298
|
+
$: dollarNode,
|
|
299
|
+
$items: (nodeName) => nodeName ? itemsForNode(nodeName) : inputItems,
|
|
300
|
+
$input: {
|
|
301
|
+
item: currentItem.json,
|
|
302
|
+
all: () => inputItems,
|
|
303
|
+
first: () => inputItems[0]?.json ?? {},
|
|
304
|
+
last: () => inputItems[inputItems.length - 1]?.json ?? {}
|
|
305
|
+
},
|
|
306
|
+
$workflow: {
|
|
307
|
+
id: scope.workflowKey,
|
|
308
|
+
name: scope.workflowKey,
|
|
309
|
+
version: scope.version
|
|
310
|
+
},
|
|
311
|
+
$execution: { id: scope.runId },
|
|
312
|
+
// Stubbed pending BOFF-7517 (Variables model) — mirrors the backend stub.
|
|
313
|
+
$vars: {},
|
|
314
|
+
// Never real env vars in the browser preview.
|
|
315
|
+
$env: {},
|
|
316
|
+
$now: now,
|
|
317
|
+
$today: today
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function evaluateBare(expression, sandbox) {
|
|
321
|
+
const paramNames = Object.keys(sandbox);
|
|
322
|
+
const paramValues = Object.values(sandbox);
|
|
323
|
+
const fn = new Function(
|
|
324
|
+
...paramNames,
|
|
325
|
+
`"use strict"; return (${expression});`
|
|
326
|
+
);
|
|
327
|
+
return fn(...paramValues);
|
|
328
|
+
}
|
|
329
|
+
function stringifyForInterpolation(value) {
|
|
330
|
+
if (value === null || value === void 0) return "";
|
|
331
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
332
|
+
return String(value);
|
|
333
|
+
}
|
|
334
|
+
function previewNewSyntax(str, scope) {
|
|
335
|
+
const sandbox = buildExpressionSandbox(scope);
|
|
336
|
+
const pure = PURE_EXPRESSION_REGEX.exec(str);
|
|
337
|
+
try {
|
|
338
|
+
if (pure) {
|
|
339
|
+
const expression = pure[1]?.trim();
|
|
340
|
+
if (!expression) {
|
|
341
|
+
return { isTemplate: true, error: "Empty expression" };
|
|
342
|
+
}
|
|
343
|
+
return { isTemplate: true, value: evaluateBare(expression, sandbox) };
|
|
344
|
+
}
|
|
345
|
+
let result = str;
|
|
346
|
+
for (const match of findEmbeddedExpressions(str)) {
|
|
347
|
+
if (!match.expression) continue;
|
|
348
|
+
const value = evaluateBare(match.expression, sandbox);
|
|
349
|
+
result = result.replace(match.full, stringifyForInterpolation(value));
|
|
350
|
+
}
|
|
351
|
+
return { isTemplate: true, value: result };
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return {
|
|
354
|
+
isTemplate: true,
|
|
355
|
+
error: error instanceof Error ? error.message : String(error)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function previewLegacySyntax(str, scope) {
|
|
360
|
+
const sandbox = {
|
|
361
|
+
...SAFE_GLOBALS,
|
|
362
|
+
output: scope.input,
|
|
363
|
+
TriggerInput: {},
|
|
364
|
+
WorkflowState: Object.fromEntries(
|
|
365
|
+
Object.entries(scope.nodeOutputs).map(([nodeId, output]) => [
|
|
366
|
+
nodeId,
|
|
367
|
+
{ output, status: "COMPLETED" }
|
|
368
|
+
])
|
|
369
|
+
),
|
|
370
|
+
RunContext: {
|
|
371
|
+
runId: scope.runId ?? "preview",
|
|
372
|
+
workflowKey: scope.workflowKey ?? "preview",
|
|
373
|
+
version: scope.version ?? "1.0.0"
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const pure = PURE_LEGACY_REGEX.exec(str);
|
|
377
|
+
try {
|
|
378
|
+
if (pure) {
|
|
379
|
+
const expression = pure[1]?.trim();
|
|
380
|
+
if (!expression) return { isTemplate: true, error: "Empty expression" };
|
|
381
|
+
return { isTemplate: true, value: evaluateBare(expression, sandbox) };
|
|
382
|
+
}
|
|
383
|
+
if (!str.includes("${")) {
|
|
384
|
+
return { isTemplate: false };
|
|
385
|
+
}
|
|
386
|
+
let result = str;
|
|
387
|
+
const templateRegex = /\$\{([^}]+)\}/g;
|
|
388
|
+
for (const match of str.matchAll(templateRegex)) {
|
|
389
|
+
const expression = match[1]?.trim();
|
|
390
|
+
if (!expression) continue;
|
|
391
|
+
const value = evaluateBare(expression, sandbox);
|
|
392
|
+
result = result.replace(match[0], stringifyForInterpolation(value));
|
|
393
|
+
}
|
|
394
|
+
return { isTemplate: true, value: result };
|
|
395
|
+
} catch (error) {
|
|
396
|
+
return {
|
|
397
|
+
isTemplate: true,
|
|
398
|
+
error: error instanceof Error ? error.message : String(error)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function evaluateExpressionPreview(str, scope) {
|
|
403
|
+
if (typeof str !== "string" || str.trim() === "") {
|
|
404
|
+
return { isTemplate: false };
|
|
405
|
+
}
|
|
406
|
+
if (containsExpressionSyntax(str)) {
|
|
407
|
+
return previewNewSyntax(str, scope);
|
|
408
|
+
}
|
|
409
|
+
return previewLegacySyntax(str, scope);
|
|
410
|
+
}
|
|
411
|
+
var EXPRESSION_BUILTINS = [
|
|
412
|
+
{ insert: "$json", label: "$json", description: "Current item's data" },
|
|
413
|
+
{
|
|
414
|
+
insert: '$node["NodeName"]',
|
|
415
|
+
label: '$node["NodeName"]',
|
|
416
|
+
description: "Another node's current item"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
insert: '$("NodeName")',
|
|
420
|
+
label: '$("NodeName")',
|
|
421
|
+
description: "Lineage helper: .item / .all() / .first() / .last()"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
insert: "$items()",
|
|
425
|
+
label: "$items()",
|
|
426
|
+
description: "This node's input as a list of items"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
insert: "$input",
|
|
430
|
+
label: "$input",
|
|
431
|
+
description: "Input helper: .item / .all() / .first() / .last()"
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
insert: "$workflow",
|
|
435
|
+
label: "$workflow",
|
|
436
|
+
description: "Workflow id / name / version"
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
insert: "$execution",
|
|
440
|
+
label: "$execution",
|
|
441
|
+
description: "Current execution id"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
insert: "$vars",
|
|
445
|
+
label: "$vars",
|
|
446
|
+
description: "Workflow variables (coming soon)"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
insert: "$env",
|
|
450
|
+
label: "$env",
|
|
451
|
+
description: "Allowlisted environment values"
|
|
452
|
+
},
|
|
453
|
+
{ insert: "$now", label: "$now", description: "Current date/time" },
|
|
454
|
+
{ insert: "$today", label: "$today", description: "Start of today" }
|
|
455
|
+
];
|
|
456
|
+
var AUTOCOMPLETE_LOOKBACK = 40;
|
|
457
|
+
function openExpressionPrefix(value, cursor) {
|
|
458
|
+
const windowStart = Math.max(0, cursor - AUTOCOMPLETE_LOOKBACK);
|
|
459
|
+
const before = value.slice(windowStart, cursor);
|
|
460
|
+
const openIndex = before.lastIndexOf("{{");
|
|
461
|
+
if (openIndex === -1) return null;
|
|
462
|
+
const between = before.slice(openIndex + 2);
|
|
463
|
+
if (between.includes("}}")) return null;
|
|
464
|
+
return between.trimStart();
|
|
465
|
+
}
|
|
227
466
|
function ExpressionEditor({
|
|
228
467
|
value,
|
|
229
468
|
onChange,
|
|
@@ -231,14 +470,18 @@ function ExpressionEditor({
|
|
|
231
470
|
className,
|
|
232
471
|
rows = 4,
|
|
233
472
|
label,
|
|
234
|
-
helpText
|
|
473
|
+
helpText,
|
|
474
|
+
nodeId
|
|
235
475
|
}) {
|
|
236
476
|
const tr = useTr();
|
|
237
477
|
const editorRef = useRef(null);
|
|
238
478
|
const [isFocused, setIsFocused] = useState(false);
|
|
479
|
+
const [suggestionsOpen, setSuggestionsOpen] = useState(false);
|
|
239
480
|
const handleChange = useCallback(
|
|
240
481
|
(e) => {
|
|
241
482
|
onChange(e.target.value);
|
|
483
|
+
const cursor = e.target.selectionStart;
|
|
484
|
+
setSuggestionsOpen(openExpressionPrefix(e.target.value, cursor) !== null);
|
|
242
485
|
},
|
|
243
486
|
[onChange]
|
|
244
487
|
);
|
|
@@ -260,6 +503,83 @@ function ExpressionEditor({
|
|
|
260
503
|
},
|
|
261
504
|
[value, onChange]
|
|
262
505
|
);
|
|
506
|
+
const applySuggestion = useCallback(
|
|
507
|
+
(insert) => {
|
|
508
|
+
const textarea = editorRef.current;
|
|
509
|
+
if (!textarea) {
|
|
510
|
+
insertTemplate(`{{ ${insert} }}`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const cursor = textarea.selectionStart;
|
|
514
|
+
const windowStart = Math.max(0, cursor - AUTOCOMPLETE_LOOKBACK);
|
|
515
|
+
const before = value.slice(windowStart, cursor);
|
|
516
|
+
const openIndex = before.lastIndexOf("{{");
|
|
517
|
+
if (openIndex === -1) {
|
|
518
|
+
insertTemplate(`{{ ${insert} }}`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const replaceStart = windowStart + openIndex;
|
|
522
|
+
const newValue = value.slice(0, replaceStart) + `{{ ${insert} }}` + value.slice(cursor);
|
|
523
|
+
onChange(newValue);
|
|
524
|
+
setSuggestionsOpen(false);
|
|
525
|
+
const cursorAfter = replaceStart + `{{ ${insert} }}`.length;
|
|
526
|
+
setTimeout(() => {
|
|
527
|
+
textarea.focus();
|
|
528
|
+
textarea.setSelectionRange(cursorAfter, cursorAfter);
|
|
529
|
+
}, 0);
|
|
530
|
+
},
|
|
531
|
+
[value, onChange, insertTemplate]
|
|
532
|
+
);
|
|
533
|
+
const cursorPos = editorRef.current?.selectionStart ?? value.length;
|
|
534
|
+
const filterText = suggestionsOpen ? openExpressionPrefix(value, cursorPos) ?? "" : "";
|
|
535
|
+
const suggestions = useMemo(() => {
|
|
536
|
+
if (!suggestionsOpen) return [];
|
|
537
|
+
const needle = filterText.toLowerCase();
|
|
538
|
+
return EXPRESSION_BUILTINS.filter(
|
|
539
|
+
(b) => b.label.toLowerCase().startsWith(needle || "$")
|
|
540
|
+
).slice(0, 8);
|
|
541
|
+
}, [suggestionsOpen, filterText]);
|
|
542
|
+
const logs = useFlowStore((s) => s.execution.logs);
|
|
543
|
+
const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
|
|
544
|
+
const workflowVersion = useFlowStore((s) => s.workflow.version);
|
|
545
|
+
const previewScope = useMemo(() => {
|
|
546
|
+
const nodeOutputs = {};
|
|
547
|
+
let input = {};
|
|
548
|
+
let runId;
|
|
549
|
+
for (const log of logs) {
|
|
550
|
+
if (log.status !== "completed" || !log.output) continue;
|
|
551
|
+
nodeOutputs[log.nodeId] = log.output;
|
|
552
|
+
if (log.nodeId === nodeId) {
|
|
553
|
+
input = log.inputArgs ?? {};
|
|
554
|
+
runId = log.runId;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return { input, nodeOutputs, workflowKey, version: workflowVersion, runId };
|
|
558
|
+
}, [logs, nodeId, workflowKey, workflowVersion]);
|
|
559
|
+
const hasPreviewData = Object.keys(previewScope.input).length > 0;
|
|
560
|
+
const [preview, setPreview] = useState(null);
|
|
561
|
+
useEffect(() => {
|
|
562
|
+
if (!nodeId || !value.trim()) {
|
|
563
|
+
setPreview(null);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const timer = setTimeout(() => {
|
|
567
|
+
setPreview(evaluateExpressionPreview(value, previewScope));
|
|
568
|
+
}, 200);
|
|
569
|
+
return () => clearTimeout(timer);
|
|
570
|
+
}, [value, previewScope, nodeId]);
|
|
571
|
+
const previewText = useMemo(() => {
|
|
572
|
+
if (!preview) return null;
|
|
573
|
+
if (!preview.isTemplate) return null;
|
|
574
|
+
if (preview.error) return null;
|
|
575
|
+
if (preview.value === void 0) return "undefined";
|
|
576
|
+
if (typeof preview.value === "string") return preview.value;
|
|
577
|
+
try {
|
|
578
|
+
return JSON.stringify(preview.value);
|
|
579
|
+
} catch {
|
|
580
|
+
return String(preview.value);
|
|
581
|
+
}
|
|
582
|
+
}, [preview]);
|
|
263
583
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
|
|
264
584
|
label && /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-foreground", children: label }),
|
|
265
585
|
/* @__PURE__ */ jsxs(
|
|
@@ -270,7 +590,7 @@ function ExpressionEditor({
|
|
|
270
590
|
isFocused && "ring-2 ring-primary ring-offset-1"
|
|
271
591
|
),
|
|
272
592
|
children: [
|
|
273
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5 border-b bg-muted/30", children: [
|
|
593
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 px-2 py-1.5 border-b bg-muted/30", children: [
|
|
274
594
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mr-2", children: tr("fluidgrids.builder.expressionEditor.insertLabel", "Insert:") }),
|
|
275
595
|
/* @__PURE__ */ jsx(
|
|
276
596
|
"button",
|
|
@@ -298,29 +618,97 @@ function ExpressionEditor({
|
|
|
298
618
|
className: "text-xs px-2 py-0.5 rounded bg-primary/10 text-primary hover:bg-primary/20 transition-colors",
|
|
299
619
|
children: "${WorkflowState}"
|
|
300
620
|
}
|
|
621
|
+
),
|
|
622
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
|
|
623
|
+
/* @__PURE__ */ jsx(
|
|
624
|
+
"button",
|
|
625
|
+
{
|
|
626
|
+
type: "button",
|
|
627
|
+
onClick: () => insertTemplate("{{ $json. }}"),
|
|
628
|
+
className: "text-xs px-2 py-0.5 rounded bg-secondary/40 text-secondary-foreground hover:bg-secondary/60 transition-colors",
|
|
629
|
+
children: "{{ $json }}"
|
|
630
|
+
}
|
|
301
631
|
)
|
|
302
632
|
] }),
|
|
303
|
-
/* @__PURE__ */
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
633
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
634
|
+
/* @__PURE__ */ jsx(
|
|
635
|
+
"textarea",
|
|
636
|
+
{
|
|
637
|
+
ref: editorRef,
|
|
638
|
+
value,
|
|
639
|
+
onChange: handleChange,
|
|
640
|
+
onKeyUp: (e) => {
|
|
641
|
+
const target = e.currentTarget;
|
|
642
|
+
setSuggestionsOpen(
|
|
643
|
+
openExpressionPrefix(target.value, target.selectionStart) !== null
|
|
644
|
+
);
|
|
645
|
+
},
|
|
646
|
+
onFocus: () => setIsFocused(true),
|
|
647
|
+
onBlur: () => {
|
|
648
|
+
setIsFocused(false);
|
|
649
|
+
setTimeout(() => setSuggestionsOpen(false), 150);
|
|
650
|
+
},
|
|
651
|
+
placeholder: placeholder ?? tr(
|
|
652
|
+
"fluidgrids.builder.expressionEditor.defaultPlaceholder",
|
|
653
|
+
"Enter expression..."
|
|
654
|
+
),
|
|
655
|
+
rows,
|
|
656
|
+
spellCheck: false,
|
|
657
|
+
className: cn(
|
|
658
|
+
"w-full p-3 font-mono text-sm bg-background resize-none",
|
|
659
|
+
"focus:outline-none",
|
|
660
|
+
"placeholder:text-muted-foreground/50"
|
|
661
|
+
)
|
|
662
|
+
}
|
|
663
|
+
),
|
|
664
|
+
suggestionsOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
|
|
665
|
+
"div",
|
|
666
|
+
{
|
|
667
|
+
className: "absolute z-20 left-2 right-2 top-full mt-1 rounded-md border bg-popover shadow-md overflow-hidden",
|
|
668
|
+
role: "listbox",
|
|
669
|
+
"aria-label": tr(
|
|
670
|
+
"fluidgrids.builder.expressionEditor.autocompleteLabel",
|
|
671
|
+
"Expression variable suggestions"
|
|
672
|
+
),
|
|
673
|
+
children: suggestions.map((s) => /* @__PURE__ */ jsxs(
|
|
674
|
+
"button",
|
|
675
|
+
{
|
|
676
|
+
type: "button",
|
|
677
|
+
role: "option",
|
|
678
|
+
"aria-selected": false,
|
|
679
|
+
onMouseDown: (e) => {
|
|
680
|
+
e.preventDefault();
|
|
681
|
+
applySuggestion(s.insert);
|
|
682
|
+
},
|
|
683
|
+
className: "flex w-full items-baseline gap-2 px-3 py-1.5 text-left hover:bg-accent",
|
|
684
|
+
children: [
|
|
685
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-primary", children: s.label }),
|
|
686
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground truncate", children: s.description })
|
|
687
|
+
]
|
|
688
|
+
},
|
|
689
|
+
s.insert
|
|
690
|
+
))
|
|
691
|
+
}
|
|
692
|
+
)
|
|
693
|
+
] }),
|
|
694
|
+
nodeId && value.trim() && /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/20 px-3 py-1.5 text-xs", children: preview?.error ? /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
|
|
695
|
+
tr(
|
|
696
|
+
"fluidgrids.builder.expressionEditor.previewError",
|
|
697
|
+
"Preview error:"
|
|
698
|
+
),
|
|
699
|
+
" ",
|
|
700
|
+
preview.error
|
|
701
|
+
] }) : previewText !== null ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
702
|
+
tr(
|
|
703
|
+
"fluidgrids.builder.expressionEditor.previewLabel",
|
|
704
|
+
"Preview:"
|
|
705
|
+
),
|
|
706
|
+
" ",
|
|
707
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-foreground", children: previewText })
|
|
708
|
+
] }) : !hasPreviewData ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 italic", children: tr(
|
|
709
|
+
"fluidgrids.builder.expressionEditor.previewNoData",
|
|
710
|
+
'Run "Execute step" on this node to preview real values.'
|
|
711
|
+
) }) : null })
|
|
324
712
|
]
|
|
325
713
|
}
|
|
326
714
|
),
|
|
@@ -13819,7 +14207,7 @@ var DEFAULT_CONFIG = {
|
|
|
13819
14207
|
maxTemplateLength: 1e4,
|
|
13820
14208
|
debug: false
|
|
13821
14209
|
};
|
|
13822
|
-
var
|
|
14210
|
+
var SAFE_GLOBALS2 = {
|
|
13823
14211
|
// Type conversion
|
|
13824
14212
|
parseInt,
|
|
13825
14213
|
parseFloat,
|
|
@@ -13866,7 +14254,7 @@ function parseTemplate(template) {
|
|
|
13866
14254
|
}
|
|
13867
14255
|
function buildSandbox(scope) {
|
|
13868
14256
|
return {
|
|
13869
|
-
...
|
|
14257
|
+
...SAFE_GLOBALS2,
|
|
13870
14258
|
output: scope.output,
|
|
13871
14259
|
TriggerInput: scope.TriggerInput,
|
|
13872
14260
|
WorkflowState: scope.WorkflowState,
|
|
@@ -18780,11 +19168,59 @@ function useInsertStickyNote() {
|
|
|
18780
19168
|
return sticky;
|
|
18781
19169
|
}, [addNodes, getNodes, screenToFlowPosition, updateNode]);
|
|
18782
19170
|
}
|
|
19171
|
+
|
|
19172
|
+
// src/builder/components/flow-builder/components/controls/canvas-controls.drag.ts
|
|
19173
|
+
function clampDragOffset(baseLeft, baseTop, width, height, bounds, margin, rawOffset) {
|
|
19174
|
+
const nextLeft = baseLeft + rawOffset.x;
|
|
19175
|
+
const nextTop = baseTop + rawOffset.y;
|
|
19176
|
+
const maxX = bounds.right - margin - width;
|
|
19177
|
+
const maxY = bounds.bottom - margin - height;
|
|
19178
|
+
const clampedLeft = Math.min(Math.max(nextLeft, bounds.left + margin), maxX);
|
|
19179
|
+
const clampedTop = Math.min(Math.max(nextTop, bounds.top + margin), maxY);
|
|
19180
|
+
return {
|
|
19181
|
+
x: clampedLeft - baseLeft,
|
|
19182
|
+
y: clampedTop - baseTop
|
|
19183
|
+
};
|
|
19184
|
+
}
|
|
19185
|
+
var MINIMAP_WIDTH = 200;
|
|
19186
|
+
var MINIMAP_HEIGHT = 150;
|
|
19187
|
+
var MINIMAP_GAP = 4;
|
|
19188
|
+
function computeMinimapRect(panelRect, bounds, margin) {
|
|
19189
|
+
let left = panelRect.left;
|
|
19190
|
+
let top = panelRect.top - MINIMAP_HEIGHT - MINIMAP_GAP;
|
|
19191
|
+
if (top < bounds.top + margin) {
|
|
19192
|
+
top = panelRect.bottom + MINIMAP_GAP;
|
|
19193
|
+
}
|
|
19194
|
+
const maxLeft = bounds.right - margin - MINIMAP_WIDTH;
|
|
19195
|
+
left = Math.min(Math.max(left, bounds.left + margin), maxLeft);
|
|
19196
|
+
const maxTop = bounds.bottom - margin - MINIMAP_HEIGHT;
|
|
19197
|
+
top = Math.min(Math.max(top, bounds.top + margin), maxTop);
|
|
19198
|
+
return { left, top };
|
|
19199
|
+
}
|
|
18783
19200
|
var ZOOM_DURATION = 500;
|
|
18784
19201
|
var NODE_WIDTH = 220;
|
|
18785
19202
|
var NODE_HEIGHT = 80;
|
|
18786
19203
|
var H_GAP = 80;
|
|
18787
19204
|
var V_GAP = 48;
|
|
19205
|
+
var CANVAS_MARGIN = 12;
|
|
19206
|
+
function readCanvasBounds(el) {
|
|
19207
|
+
if (!el) return null;
|
|
19208
|
+
const cs = getComputedStyle(el);
|
|
19209
|
+
const num = (value, fallback) => {
|
|
19210
|
+
const n = parseFloat(value);
|
|
19211
|
+
return Number.isFinite(n) ? n : fallback;
|
|
19212
|
+
};
|
|
19213
|
+
return {
|
|
19214
|
+
left: num(cs.getPropertyValue("--canvas-left"), 0),
|
|
19215
|
+
right: num(cs.getPropertyValue("--canvas-right"), window.innerWidth),
|
|
19216
|
+
top: num(cs.getPropertyValue("--canvas-top"), 0),
|
|
19217
|
+
bottom: num(cs.getPropertyValue("--canvas-bottom"), window.innerHeight)
|
|
19218
|
+
};
|
|
19219
|
+
}
|
|
19220
|
+
function isInteractiveTarget(target) {
|
|
19221
|
+
if (!(target instanceof Element)) return false;
|
|
19222
|
+
return target.closest("button, input, a, [data-no-drag]") !== null;
|
|
19223
|
+
}
|
|
18788
19224
|
function zoomSelector(s) {
|
|
18789
19225
|
return {
|
|
18790
19226
|
zoom: s.transform[2],
|
|
@@ -18821,6 +19257,10 @@ function CanvasControls() {
|
|
|
18821
19257
|
const isReadOnly = searchParams.get("readonly") === "true";
|
|
18822
19258
|
const [showMinimap, setShowMinimap] = useState(false);
|
|
18823
19259
|
const [edgesHidden, setEdgesHidden] = useState(false);
|
|
19260
|
+
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
|
19261
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
19262
|
+
const panelRef = useRef(null);
|
|
19263
|
+
const dragState = useRef(null);
|
|
18824
19264
|
const { zoom, minZoomReached, maxZoomReached } = useStore(
|
|
18825
19265
|
zoomSelector,
|
|
18826
19266
|
shallow
|
|
@@ -18900,6 +19340,59 @@ function CanvasControls() {
|
|
|
18900
19340
|
);
|
|
18901
19341
|
setTimeout(() => fitView({ duration: ZOOM_DURATION, padding: 0.15 }), 50);
|
|
18902
19342
|
}, [getNodes, getEdges, setNodes, fitView]);
|
|
19343
|
+
const handlePointerDown = useCallback(
|
|
19344
|
+
(e) => {
|
|
19345
|
+
if (isInteractiveTarget(e.target)) return;
|
|
19346
|
+
const panel = panelRef.current;
|
|
19347
|
+
if (!panel) return;
|
|
19348
|
+
panel.setPointerCapture(e.pointerId);
|
|
19349
|
+
const rect = panel.getBoundingClientRect();
|
|
19350
|
+
dragState.current = {
|
|
19351
|
+
startX: e.clientX,
|
|
19352
|
+
startY: e.clientY,
|
|
19353
|
+
startOffsetX: dragOffset.x,
|
|
19354
|
+
startOffsetY: dragOffset.y,
|
|
19355
|
+
baseLeft: rect.left - dragOffset.x,
|
|
19356
|
+
baseTop: rect.top - dragOffset.y,
|
|
19357
|
+
width: rect.width,
|
|
19358
|
+
height: rect.height
|
|
19359
|
+
};
|
|
19360
|
+
setIsDragging(true);
|
|
19361
|
+
},
|
|
19362
|
+
[dragOffset]
|
|
19363
|
+
);
|
|
19364
|
+
const handlePointerMove = useCallback((e) => {
|
|
19365
|
+
const ds = dragState.current;
|
|
19366
|
+
if (!ds) return;
|
|
19367
|
+
const bounds = readCanvasBounds(panelRef.current);
|
|
19368
|
+
if (!bounds) return;
|
|
19369
|
+
const rawOffset = {
|
|
19370
|
+
x: ds.startOffsetX + (e.clientX - ds.startX),
|
|
19371
|
+
y: ds.startOffsetY + (e.clientY - ds.startY)
|
|
19372
|
+
};
|
|
19373
|
+
setDragOffset(
|
|
19374
|
+
clampDragOffset(
|
|
19375
|
+
ds.baseLeft,
|
|
19376
|
+
ds.baseTop,
|
|
19377
|
+
ds.width,
|
|
19378
|
+
ds.height,
|
|
19379
|
+
bounds,
|
|
19380
|
+
CANVAS_MARGIN,
|
|
19381
|
+
rawOffset
|
|
19382
|
+
)
|
|
19383
|
+
);
|
|
19384
|
+
}, []);
|
|
19385
|
+
const handlePointerUp = useCallback((e) => {
|
|
19386
|
+
if (!dragState.current) return;
|
|
19387
|
+
if (panelRef.current?.hasPointerCapture(e.pointerId)) {
|
|
19388
|
+
panelRef.current.releasePointerCapture(e.pointerId);
|
|
19389
|
+
}
|
|
19390
|
+
dragState.current = null;
|
|
19391
|
+
setIsDragging(false);
|
|
19392
|
+
}, []);
|
|
19393
|
+
const panelRect = panelRef.current?.getBoundingClientRect();
|
|
19394
|
+
const canvasBounds = readCanvasBounds(panelRef.current);
|
|
19395
|
+
const minimapRect = panelRect && canvasBounds ? computeMinimapRect(panelRect, canvasBounds, CANVAS_MARGIN) : null;
|
|
18903
19396
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18904
19397
|
showMinimap && /* @__PURE__ */ jsx(
|
|
18905
19398
|
MiniMap,
|
|
@@ -18907,9 +19400,9 @@ function CanvasControls() {
|
|
|
18907
19400
|
position: "bottom-left",
|
|
18908
19401
|
style: {
|
|
18909
19402
|
position: "fixed",
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
19403
|
+
left: minimapRect ? minimapRect.left : void 0,
|
|
19404
|
+
bottom: minimapRect ? window.innerHeight - minimapRect.top - MINIMAP_HEIGHT : 52,
|
|
19405
|
+
margin: 0,
|
|
18913
19406
|
backgroundColor: "var(--color-card)"
|
|
18914
19407
|
},
|
|
18915
19408
|
className: "!rounded-lg !border !border-card-foreground/10 !shadow-lg",
|
|
@@ -18923,8 +19416,20 @@ function CanvasControls() {
|
|
|
18923
19416
|
/* @__PURE__ */ jsxs(
|
|
18924
19417
|
"div",
|
|
18925
19418
|
{
|
|
18926
|
-
|
|
18927
|
-
|
|
19419
|
+
ref: panelRef,
|
|
19420
|
+
onPointerDown: handlePointerDown,
|
|
19421
|
+
onPointerMove: handlePointerMove,
|
|
19422
|
+
onPointerUp: handlePointerUp,
|
|
19423
|
+
onPointerCancel: handlePointerUp,
|
|
19424
|
+
className: [
|
|
19425
|
+
"fixed bottom-3 z-50 flex items-center gap-1 rounded-xl border",
|
|
19426
|
+
"border-card-foreground/10 bg-card px-2 py-1.5 shadow-lg touch-none",
|
|
19427
|
+
isDragging ? "cursor-grabbing" : "cursor-grab"
|
|
19428
|
+
].join(" "),
|
|
19429
|
+
style: {
|
|
19430
|
+
left: "calc(var(--canvas-left, 64px) + 12px)",
|
|
19431
|
+
transform: `translate3d(${dragOffset.x}px, ${dragOffset.y}px, 0)`
|
|
19432
|
+
},
|
|
18928
19433
|
children: [
|
|
18929
19434
|
/* @__PURE__ */ jsx(
|
|
18930
19435
|
ControlBtn,
|
|
@@ -23152,13 +23657,16 @@ var FlowBuilder = ({ readOnly = false }) => {
|
|
|
23152
23657
|
{
|
|
23153
23658
|
ref: (el) => {
|
|
23154
23659
|
if (!el) return;
|
|
23155
|
-
const update = () =>
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23660
|
+
const update = () => {
|
|
23661
|
+
const rect = el.getBoundingClientRect();
|
|
23662
|
+
el.style.setProperty("--canvas-left", `${rect.left}px`);
|
|
23663
|
+
el.style.setProperty("--canvas-right", `${rect.right}px`);
|
|
23664
|
+
el.style.setProperty("--canvas-top", `${rect.top}px`);
|
|
23665
|
+
el.style.setProperty("--canvas-bottom", `${rect.bottom}px`);
|
|
23666
|
+
};
|
|
23159
23667
|
update();
|
|
23160
23668
|
const ro = new ResizeObserver(update);
|
|
23161
|
-
ro.observe(
|
|
23669
|
+
ro.observe(el);
|
|
23162
23670
|
el._ro?.disconnect();
|
|
23163
23671
|
el._ro = ro;
|
|
23164
23672
|
},
|
|
@@ -23316,5 +23824,5 @@ var FlowBuilderPage = ({
|
|
|
23316
23824
|
};
|
|
23317
23825
|
|
|
23318
23826
|
export { Card2 as Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ConnectionFormDialog, ConnectionSelector, EntityTagPicker, FLUIDGRIDS_ENTITY_TYPES, FlowBuilderPage, FluidGridsProvider, ProviderPicker, SchemaConnectionForm, ScopePicker, TemplateCard, buildNodeCheck, buildRequiredNodeTypes, listCustomBlocks, mergeStickyNotes, refetchStoreNodes, registerCustomBlock, resolveUiSpec, stripSubmitActions, tagLabel, useAvailableTags, useConnection, useConnectionDialog, useConnections, useConnectionsByProvider, useCreateConnection, useCreateTag, useDraftEntityTags, useEntityTags, useFluidGridsContext, useImportWorkflowTemplate, useInitiateOAuth, useInstalledNodes, useNodeExecution, useProviderByKey, useProviderCatalog, useProviders, useReplaceEntityTags, useSearchTags, useTagEntity, useUntagEntity, useWorkflowTemplates };
|
|
23319
|
-
//# sourceMappingURL=chunk-
|
|
23320
|
-
//# sourceMappingURL=chunk-
|
|
23827
|
+
//# sourceMappingURL=chunk-XASNLRFQ.js.map
|
|
23828
|
+
//# sourceMappingURL=chunk-XASNLRFQ.js.map
|