@arbidocs/blocks 0.4.73 → 0.4.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +2 -1
  2. package/dist/agents/index.cjs +255 -254
  3. package/dist/agents/index.js +5 -4
  4. package/dist/agents/workflows/WorkflowRunner.d.ts +20 -4
  5. package/dist/agents/workflows/WorkflowStepBar.d.ts +23 -0
  6. package/dist/agents/workflows/run.d.ts +1 -0
  7. package/dist/agents/workflows/types.d.ts +2 -0
  8. package/dist/chat/index.cjs +124 -124
  9. package/dist/chat/index.js +3 -3
  10. package/dist/{chunk-3SFFTSOI.cjs → chunk-7CVF3DK2.cjs} +5 -5
  11. package/dist/{chunk-3SFFTSOI.cjs.map → chunk-7CVF3DK2.cjs.map} +1 -1
  12. package/dist/{chunk-V3L5DONY.cjs → chunk-B4SRYJLM.cjs} +31 -2
  13. package/dist/chunk-B4SRYJLM.cjs.map +1 -0
  14. package/dist/{chunk-IUR565UW.cjs → chunk-F4LUD3YC.cjs} +49 -48
  15. package/dist/chunk-F4LUD3YC.cjs.map +1 -0
  16. package/dist/{chunk-WOYUXVVQ.cjs → chunk-HITAN7JF.cjs} +4 -4
  17. package/dist/{chunk-WOYUXVVQ.cjs.map → chunk-HITAN7JF.cjs.map} +1 -1
  18. package/dist/{chunk-3VQYJBOI.js → chunk-HOXKR26S.js} +31 -4
  19. package/dist/chunk-HOXKR26S.js.map +1 -0
  20. package/dist/{chunk-KJ3QEKOJ.cjs → chunk-IPGKTUF7.cjs} +7 -33
  21. package/dist/chunk-IPGKTUF7.cjs.map +1 -0
  22. package/dist/{chunk-IA7RTPT3.cjs → chunk-IXALYQT7.cjs} +2 -200
  23. package/dist/chunk-IXALYQT7.cjs.map +1 -0
  24. package/dist/{chunk-QDNXEB3T.js → chunk-LT2UORRG.js} +7 -6
  25. package/dist/chunk-LT2UORRG.js.map +1 -0
  26. package/dist/{chunk-ORA2XE7U.js → chunk-MK6NJQGF.js} +3 -3
  27. package/dist/{chunk-ORA2XE7U.js.map → chunk-MK6NJQGF.js.map} +1 -1
  28. package/dist/chunk-NUNKSZKU.js +203 -0
  29. package/dist/chunk-NUNKSZKU.js.map +1 -0
  30. package/dist/{chunk-6OL5S7MJ.cjs → chunk-NYBIKDWI.cjs} +5 -2
  31. package/dist/chunk-NYBIKDWI.cjs.map +1 -0
  32. package/dist/{chunk-GTNUQBXI.cjs → chunk-P2RBE3FD.cjs} +254 -102
  33. package/dist/chunk-P2RBE3FD.cjs.map +1 -0
  34. package/dist/{chunk-D46ZAPEE.js → chunk-R3PDNQYT.js} +5 -2
  35. package/dist/chunk-R3PDNQYT.js.map +1 -0
  36. package/dist/{chunk-G5DP27DU.js → chunk-UIWLDSTF.js} +3 -29
  37. package/dist/chunk-UIWLDSTF.js.map +1 -0
  38. package/dist/chunk-V4ISCZJ7.cjs +207 -0
  39. package/dist/chunk-V4ISCZJ7.cjs.map +1 -0
  40. package/dist/{chunk-VRPRAU3J.js → chunk-WJEKRSPH.js} +3 -3
  41. package/dist/{chunk-VRPRAU3J.js.map → chunk-WJEKRSPH.js.map} +1 -1
  42. package/dist/{chunk-2P3T23C7.js → chunk-YWYVIQ7T.js} +256 -104
  43. package/dist/chunk-YWYVIQ7T.js.map +1 -0
  44. package/dist/{chunk-BUHKUKKB.js → chunk-ZPLG7QYF.js} +4 -199
  45. package/dist/chunk-ZPLG7QYF.js.map +1 -0
  46. package/dist/connect/index.cjs +96 -37
  47. package/dist/connect/index.cjs.map +1 -1
  48. package/dist/connect/index.js +81 -22
  49. package/dist/connect/index.js.map +1 -1
  50. package/dist/connection/ConnectedWorkflowRunner.d.ts +5 -1
  51. package/dist/connection/useWorkflowThread.d.ts +4 -0
  52. package/dist/databases/DatabaseColumnDialog.d.ts +43 -0
  53. package/dist/databases/DatabaseList.d.ts +28 -0
  54. package/dist/databases/DatabaseTable.d.ts +38 -0
  55. package/dist/databases/DatabaseToolbar.d.ts +38 -0
  56. package/dist/databases/NewDatabaseDialog.d.ts +25 -0
  57. package/dist/databases/SelectCellEditor.d.ts +6 -0
  58. package/dist/databases/SelectChip.d.ts +4 -0
  59. package/dist/databases/column-types.d.ts +58 -0
  60. package/dist/databases/databaseColumns.d.ts +28 -0
  61. package/dist/databases/index.cjs +1081 -0
  62. package/dist/databases/index.cjs.map +1 -0
  63. package/dist/databases/index.d.ts +13 -0
  64. package/dist/databases/index.js +1055 -0
  65. package/dist/databases/index.js.map +1 -0
  66. package/dist/facts/index.cjs +7 -6
  67. package/dist/facts/index.cjs.map +1 -1
  68. package/dist/facts/index.js +3 -2
  69. package/dist/facts/index.js.map +1 -1
  70. package/dist/files/index.cjs +14 -13
  71. package/dist/files/index.cjs.map +1 -1
  72. package/dist/files/index.js +4 -2
  73. package/dist/files/index.js.map +1 -1
  74. package/dist/index.cjs +632 -631
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.js +12 -11
  77. package/dist/index.js.map +1 -1
  78. package/dist/settings/index.cjs +1 -1
  79. package/dist/settings/index.js +1 -1
  80. package/dist/shell/ActionCluster.d.ts +3 -1
  81. package/dist/shell/index.cjs +45 -45
  82. package/dist/shell/index.js +1 -1
  83. package/dist/studio/index.cjs +4 -4
  84. package/dist/studio/index.js +3 -3
  85. package/dist/workflow-canvas/StepInspector.d.ts +11 -0
  86. package/dist/workflow-canvas/WorkflowCanvas.d.ts +6 -2
  87. package/dist/workflow-canvas/index.cjs +202 -91
  88. package/dist/workflow-canvas/index.cjs.map +1 -1
  89. package/dist/workflow-canvas/index.js +170 -59
  90. package/dist/workflow-canvas/index.js.map +1 -1
  91. package/dist/workflow-canvas/viewport.d.ts +47 -0
  92. package/package.json +9 -4
  93. package/dist/chunk-2P3T23C7.js.map +0 -1
  94. package/dist/chunk-3VQYJBOI.js.map +0 -1
  95. package/dist/chunk-6OL5S7MJ.cjs.map +0 -1
  96. package/dist/chunk-BUHKUKKB.js.map +0 -1
  97. package/dist/chunk-D46ZAPEE.js.map +0 -1
  98. package/dist/chunk-G5DP27DU.js.map +0 -1
  99. package/dist/chunk-GTNUQBXI.cjs.map +0 -1
  100. package/dist/chunk-IA7RTPT3.cjs.map +0 -1
  101. package/dist/chunk-IUR565UW.cjs.map +0 -1
  102. package/dist/chunk-KJ3QEKOJ.cjs.map +0 -1
  103. package/dist/chunk-QDNXEB3T.js.map +0 -1
  104. package/dist/chunk-V3L5DONY.cjs.map +0 -1
@@ -1,19 +1,20 @@
1
1
  "use client";
2
2
  import { useConnection } from '../chunk-OYMWV45N.js';
3
- import { describeRRule, RecurrenceBuilder, AgentSelect, useWorkflowPreview, workflowWhen, isoToLocalInput, localInputToIso } from '../chunk-QDNXEB3T.js';
3
+ import { describeRRule, RecurrenceBuilder, AgentSelect, useWorkflowPreview, workflowWhen, isoToLocalInput, localInputToIso } from '../chunk-LT2UORRG.js';
4
4
  import '../chunk-CKB64Z4R.js';
5
5
  import '../chunk-P6GHJSLR.js';
6
- import { workflowSteps, dependsOn, stepLevels, stepDependencies, validateWorkflowGraph, WorkflowRunner, fromPlanSteps, layoutFromPlanSteps, skillToWorkflow, orderSteps, toPlanSteps } from '../chunk-2P3T23C7.js';
6
+ import { workflowSteps, dependsOn, stepLevels, stepDependencies, runFields, validateWorkflowGraph, WorkflowRunner, fromPlanSteps, layoutFromPlanSteps, skillToWorkflow, orderSteps, toPlanSteps } from '../chunk-YWYVIQ7T.js';
7
7
  import '../chunk-4WWM3GQ5.js';
8
8
  import '../chunk-RFXJYIDV.js';
9
9
  import '../chunk-DPLRHEJZ.js';
10
10
  import '../chunk-EVUEIBNU.js';
11
- import '../chunk-BUHKUKKB.js';
11
+ import '../chunk-NUNKSZKU.js';
12
12
  import '../chunk-YU2KUMGE.js';
13
+ import '../chunk-ZPLG7QYF.js';
13
14
  import '../chunk-DDF5GPIJ.js';
14
15
  import '../chunk-XD2M6LIG.js';
15
16
  import '../chunk-S7PSMMRG.js';
16
- import '../chunk-3VQYJBOI.js';
17
+ import '../chunk-HOXKR26S.js';
17
18
  import '../chunk-AA3PT6KR.js';
18
19
  import '../chunk-SLSBJRWW.js';
19
20
  import '../chunk-NPLYHR2E.js';
@@ -25,9 +26,9 @@ import '../chunk-VUNV25KB.js';
25
26
  import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
26
27
  import { useWorkflows, useSkills, useAgents, useConversations, useCreateWorkflow, useUpdateWorkflow, useRunWorkflow } from '@arbidocs/react';
27
28
  import { Input, Textarea, Checkbox, Button, cn, Label, Popover, PopoverTrigger, PopoverContent } from '@arbidocs/react/ui';
28
- import { X, Trash2, Plus, ClipboardCheck, Bot, Upload, FileInput, PencilRuler, Eye, LayoutGrid, Settings2, Play, Loader2, Save, Zap, GitBranch, Check, AlertTriangle, MinusCircle } from 'lucide-react';
29
+ import { X, Trash2, Plus, Maximize, ClipboardCheck, Bot, Upload, FileInput, PencilRuler, Eye, LayoutGrid, Settings2, Play, Loader2, Save, Zap, GitBranch, Check, AlertTriangle, MinusCircle } from 'lucide-react';
29
30
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
30
- import { MarkerType, applyNodeChanges, ReactFlow, Background, BackgroundVariant, Controls, MiniMap, Handle, Position } from '@xyflow/react';
31
+ import { MarkerType, applyNodeChanges, ReactFlow, Background, BackgroundVariant, Controls, ControlButton, MiniMap, Handle, Position } from '@xyflow/react';
31
32
 
32
33
  // src/workflow-canvas/edit.ts
33
34
  function newSteppedWorkflow(id, title) {
@@ -71,7 +72,7 @@ function blankStep(kind, id, title) {
71
72
  case "upload":
72
73
  return { id, kind, title };
73
74
  case "agent":
74
- return { id, kind, title, run: { kind: "prompt", promptTemplate: "" } };
75
+ return { id, kind, title };
75
76
  case "review":
76
77
  return { id, kind, title, instructions: "" };
77
78
  }
@@ -176,10 +177,13 @@ var DEFAULT_STEP_INSPECTOR_LABELS = {
176
177
  fieldType: "Type",
177
178
  required: "Required",
178
179
  runs: "Runs",
180
+ chooseRun: "Choose a skill or write an instruction\u2026",
179
181
  ownPrompt: "Its own instruction",
180
182
  prompt: "Instruction",
181
183
  promptHint: "Use {{field}} for an input and {{steps.<id>}} for an earlier step\u2019s answer.",
182
184
  inputFor: "Input",
185
+ inputHint: "Type a value, or use a field: {fields}",
186
+ inputHintNoFields: "Type a value, or add a form field to fill it from.",
183
187
  thread: "Where it runs",
184
188
  threadMain: "In the run\u2019s thread (sees the conversation so far)",
185
189
  threadBranch: "On a branch (delivers only its result to the thread)",
@@ -327,7 +331,17 @@ function StepInspector({
327
331
  }
328
332
  ) })
329
333
  ] }),
330
- step.kind === "agent" && /* @__PURE__ */ jsx(AgentSettings, { step, workflows, patch, l, testId }),
334
+ step.kind === "agent" && /* @__PURE__ */ jsx(
335
+ AgentSettings,
336
+ {
337
+ step,
338
+ workflows,
339
+ fields: runFields(workflow).map((f) => f.name),
340
+ patch,
341
+ l,
342
+ testId
343
+ }
344
+ ),
331
345
  step.kind === "review" && /* @__PURE__ */ jsx(Field, { label: l.instructions, hint: l.instructionsHint, children: /* @__PURE__ */ jsx(
332
346
  Textarea,
333
347
  {
@@ -440,42 +454,77 @@ function FormFields({
440
454
  )
441
455
  ] }) });
442
456
  }
457
+ var OWN_PROMPT = "__prompt";
458
+ function prefillSkillInputs(target, fields, current = {}) {
459
+ const out = {};
460
+ for (const f of target?.inputs ?? []) {
461
+ const typed = current[f.name];
462
+ if (typed) out[f.name] = typed;
463
+ else if (fields.includes(f.name)) out[f.name] = `{{${f.name}}}`;
464
+ }
465
+ return out;
466
+ }
443
467
  function AgentSettings({
444
468
  step,
445
469
  workflows,
470
+ fields,
446
471
  patch,
447
472
  l,
448
473
  testId
449
474
  }) {
450
475
  const target = step.workflow ? workflows.find((w) => w.id === step.workflow) : void 0;
476
+ const value = step.workflow ?? (step.run ? OWN_PROMPT : "");
477
+ const codes = fields.map((f) => `{{${f}}}`).join(", ");
451
478
  return /* @__PURE__ */ jsxs(Fragment, { children: [
452
479
  /* @__PURE__ */ jsx(Field, { label: l.runs, children: /* @__PURE__ */ jsxs(
453
480
  "select",
454
481
  {
455
482
  className: selectClass,
456
- value: step.workflow ?? "",
457
- onChange: (e) => e.target.value ? patch({ workflow: e.target.value, run: void 0, with: step.with ?? {} }) : patch({
458
- workflow: void 0,
459
- with: void 0,
460
- run: { kind: "prompt", promptTemplate: "" }
461
- }),
483
+ value,
484
+ onChange: (e) => {
485
+ const id = e.target.value;
486
+ if (id === OWN_PROMPT)
487
+ patch({
488
+ workflow: void 0,
489
+ with: void 0,
490
+ run: { kind: "prompt", promptTemplate: "" }
491
+ });
492
+ else if (id)
493
+ patch({
494
+ workflow: id,
495
+ run: void 0,
496
+ with: prefillSkillInputs(
497
+ workflows.find((w) => w.id === id),
498
+ fields,
499
+ step.with
500
+ )
501
+ });
502
+ },
462
503
  "data-testid": `${testId}-runs`,
463
504
  children: [
464
- /* @__PURE__ */ jsx("option", { value: "", children: l.ownPrompt }),
505
+ !value && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: l.chooseRun }),
506
+ /* @__PURE__ */ jsx("option", { value: OWN_PROMPT, children: l.ownPrompt }),
465
507
  workflows.map((w) => /* @__PURE__ */ jsx("option", { value: w.id, children: w.title }, w.id)),
466
508
  step.workflow && !target && /* @__PURE__ */ jsx("option", { value: step.workflow, children: step.workflow })
467
509
  ]
468
510
  }
469
511
  ) }),
470
- step.workflow ? (target?.inputs ?? []).map((f) => /* @__PURE__ */ jsx(Field, { label: `${l.inputFor}: ${f.label}`, children: /* @__PURE__ */ jsx(
471
- Input,
512
+ step.workflow ? (target?.inputs ?? []).map((f) => /* @__PURE__ */ jsx(
513
+ Field,
472
514
  {
473
- value: step.with?.[f.name] ?? "",
474
- placeholder: `{{${f.name}}}`,
475
- onChange: (e) => patch({ with: { ...step.with, [f.name]: e.target.value } }),
476
- "data-testid": `${testId}-with-${f.name}`
477
- }
478
- ) }, f.name)) : step.run?.kind === "prompt" ? /* @__PURE__ */ jsx(Field, { label: l.prompt, hint: l.promptHint, children: /* @__PURE__ */ jsx(
515
+ label: `${l.inputFor}: ${f.label}`,
516
+ hint: step.with?.[f.name] ? void 0 : codes ? l.inputHint.replace("{fields}", codes) : l.inputHintNoFields,
517
+ children: /* @__PURE__ */ jsx(
518
+ Input,
519
+ {
520
+ value: step.with?.[f.name] ?? "",
521
+ onChange: (e) => patch({ with: { ...step.with, [f.name]: e.target.value } }),
522
+ "data-testid": `${testId}-with-${f.name}`
523
+ }
524
+ )
525
+ },
526
+ f.name
527
+ )) : step.run?.kind === "prompt" ? /* @__PURE__ */ jsx(Field, { label: l.prompt, hint: l.promptHint, children: /* @__PURE__ */ jsx(
479
528
  Textarea,
480
529
  {
481
530
  value: step.run.promptTemplate,
@@ -656,6 +705,45 @@ function TriggerInspector({
656
705
  ] })
657
706
  ] });
658
707
  }
708
+
709
+ // src/workflow-canvas/viewport.ts
710
+ var READABLE_ZOOM = 0.7;
711
+ var DEFAULT_W = 256;
712
+ var DEFAULT_H = 80;
713
+ var MARGIN = 48;
714
+ var sizeOf = (n) => ({
715
+ w: n.measured?.width ?? DEFAULT_W,
716
+ h: n.measured?.height ?? DEFAULT_H
717
+ });
718
+ function readableViewport(nodes, fittedZoom, frame, {
719
+ anchor,
720
+ centre = false,
721
+ reserveRight = 0
722
+ } = {}) {
723
+ if (fittedZoom >= READABLE_ZOOM || !frame.height || nodes.length === 0) return null;
724
+ const z = READABLE_ZOOM;
725
+ const at = nodes.find((n) => n.id === anchor) ?? nodes.reduce((a, b) => b.position.x < a.position.x ? b : a);
726
+ const { w, h } = sizeOf(at);
727
+ const y = frame.height / 2 - (at.position.y + h / 2) * z;
728
+ if (centre && at.id === anchor) {
729
+ const visible = Math.max(0, frame.width - reserveRight);
730
+ return { x: visible / 2 - (at.position.x + w / 2) * z, y, zoom: z };
731
+ }
732
+ return { x: MARGIN - at.position.x * z, y, zoom: z };
733
+ }
734
+ function revealCenter(node, view, frame, reserveRight = 0) {
735
+ const { w, h } = sizeOf(node);
736
+ const { x, y, zoom } = view;
737
+ const left = node.position.x * zoom + x;
738
+ const top = node.position.y * zoom + y;
739
+ const right = frame.width - reserveRight;
740
+ if (left >= 16 && top >= 16 && left + w * zoom <= right - 16 && top + h * zoom <= frame.height - 16)
741
+ return null;
742
+ return {
743
+ x: node.position.x + w / 2 + reserveRight / 2 / zoom,
744
+ y: node.position.y + h / 2
745
+ };
746
+ }
659
747
  var DEFAULT_WORKFLOW_CANVAS_LABELS = {
660
748
  form: "Form",
661
749
  upload: "Upload",
@@ -670,12 +758,13 @@ var DEFAULT_WORKFLOW_CANVAS_LABELS = {
670
758
  },
671
759
  addNext: "Add the next step",
672
760
  runs: "Runs \u201C{name}\u201D",
673
- instruction: "No instruction yet",
761
+ chooseRun: "Choose a skill or write an instruction",
674
762
  fields: "{count} fields",
675
763
  noFields: "No fields yet",
676
764
  approvedBy: "A person approves",
677
765
  emptyTitle: "Start your workflow",
678
- emptyHint: "Add the first step: what should happen when it starts?"
766
+ emptyHint: "Add the first step: what should happen when it starts?",
767
+ fitView: "Fit view"
679
768
  };
680
769
  var TRIGGER_NODE = "__trigger";
681
770
  var STEP_KIND_ICONS = {
@@ -800,7 +889,6 @@ function TriggerNode({ data }) {
800
889
  );
801
890
  }
802
891
  var NODE_TYPES = { step: StepNode, trigger: TriggerNode };
803
- var READABLE_ZOOM = 0.7;
804
892
  var firstWords = (text, n = 14) => {
805
893
  const words = text.trim().split(/\s+/);
806
894
  return words.length > n ? `${words.slice(0, n).join(" ")}\u2026` : text.trim();
@@ -811,7 +899,8 @@ function summaryOf(step, l, workflowTitle) {
811
899
  case "agent":
812
900
  if (step.workflow)
813
901
  return l.runs.replace("{name}", workflowTitle?.(step.workflow) ?? step.workflow);
814
- return step.run?.kind === "prompt" && step.run.promptTemplate.trim() ? firstWords(step.run.promptTemplate) : l.instruction;
902
+ if (step.run?.kind === "operation") return void 0;
903
+ return step.run?.kind === "prompt" && step.run.promptTemplate.trim() ? firstWords(step.run.promptTemplate) : l.chooseRun;
815
904
  case "form":
816
905
  return step.inputs.length ? step.inputs.length === 1 ? step.inputs[0].label : l.fields.replace("{count}", String(step.inputs.length)) : l.noFields;
817
906
  case "upload":
@@ -825,6 +914,7 @@ function WorkflowCanvas({
825
914
  onChange,
826
915
  selected = null,
827
916
  onSelect,
917
+ focus,
828
918
  onAddAfter,
829
919
  phases,
830
920
  workflowTitle,
@@ -916,46 +1006,57 @@ function WorkflowCanvas({
916
1006
  );
917
1007
  const frame = useRef(null);
918
1008
  const flowRef = useRef(null);
919
- const onInit = useCallback(
920
- async (flow) => {
921
- flowRef.current = flow;
1009
+ const hasTrigger = !!trigger;
1010
+ const selectedRef = useRef(selected);
1011
+ selectedRef.current = selected;
1012
+ const fit = useCallback(
1013
+ async (flow, anchorOnSelection) => {
922
1014
  await flow.fitView({
923
1015
  padding: { top: "48px", bottom: "48px", left: "48px", right: `${reserveRight + 48}px` },
924
1016
  maxZoom: 1
925
1017
  });
926
- const height = frame.current?.clientHeight;
927
- const shown = flow.getNodes();
928
- if (flow.getZoom() >= READABLE_ZOOM || !height || !shown.length) return;
929
- const left = Math.min(...shown.map((n) => n.position.x));
930
- const ys = shown.map((n) => n.position.y + (n.measured?.height ?? 0) / 2);
931
- const middle = (Math.min(...ys) + Math.max(...ys)) / 2;
932
- flow.setViewport({
933
- x: 48 - left * READABLE_ZOOM,
934
- y: height / 2 - middle * READABLE_ZOOM,
935
- zoom: READABLE_ZOOM
936
- });
1018
+ const box = frame.current;
1019
+ const picked = anchorOnSelection ? selectedRef.current : null;
1020
+ const view = readableViewport(
1021
+ flow.getNodes(),
1022
+ flow.getZoom(),
1023
+ { width: box?.clientWidth ?? 0, height: box?.clientHeight ?? 0 },
1024
+ {
1025
+ anchor: picked ?? (hasTrigger ? TRIGGER_NODE : null),
1026
+ centre: !!picked,
1027
+ reserveRight
1028
+ }
1029
+ );
1030
+ if (view) await flow.setViewport(view);
1031
+ },
1032
+ [reserveRight, hasTrigger]
1033
+ );
1034
+ const onInit = useCallback(
1035
+ async (flow) => {
1036
+ flowRef.current = flow;
1037
+ await fit(flow, false);
937
1038
  },
938
- [reserveRight]
1039
+ [fit]
939
1040
  );
1041
+ const target = focus ?? selected;
1042
+ const revealed = useRef(null);
940
1043
  useEffect(() => {
941
1044
  const flow = flowRef.current;
942
1045
  const box = frame.current;
943
- const node = selected ? flow?.getNode(selected) : void 0;
944
- if (!flow || !box || !node) return;
945
- const { x, y, zoom } = flow.getViewport();
946
- const w = (node.measured?.width ?? 256) * zoom;
947
- const h = (node.measured?.height ?? 80) * zoom;
948
- const left = node.position.x * zoom + x;
949
- const top = node.position.y * zoom + y;
950
- const right = box.clientWidth - reserveRight;
951
- if (left >= 16 && top >= 16 && left + w <= right - 16 && top + h <= box.clientHeight - 16)
952
- return;
953
- void flow.setCenter(
954
- node.position.x + (node.measured?.width ?? 256) / 2 + reserveRight / 2 / zoom,
955
- node.position.y + (node.measured?.height ?? 80) / 2,
956
- { zoom, duration: 250 }
1046
+ const node = target ? flow?.getNode(target) : void 0;
1047
+ const key = `${target}:${reserveRight}`;
1048
+ if (!target) revealed.current = null;
1049
+ if (!flow || !box || !node || revealed.current === key) return;
1050
+ revealed.current = key;
1051
+ const view = flow.getViewport();
1052
+ const centre = revealCenter(
1053
+ node,
1054
+ view,
1055
+ { width: box.clientWidth, height: box.clientHeight },
1056
+ reserveRight
957
1057
  );
958
- }, [selected, nodes, reserveRight]);
1058
+ if (centre) void flow.setCenter(centre.x, centre.y, { zoom: view.zoom, duration: 250 });
1059
+ }, [target, nodes, reserveRight]);
959
1060
  const onDelete = useCallback(
960
1061
  ({ nodes: gone, edges: cut }) => {
961
1062
  if (!onChange) return;
@@ -997,12 +1098,21 @@ function WorkflowCanvas({
997
1098
  elementsSelectable: true,
998
1099
  deleteKeyCode: editable ? ["Backspace", "Delete"] : null,
999
1100
  onInit,
1000
- fitViewOptions: { padding: 0.2, maxZoom: 1 },
1001
1101
  minZoom: 0.3,
1002
1102
  proOptions: { hideAttribution: true },
1003
1103
  children: [
1004
1104
  /* @__PURE__ */ jsx(Background, { variant: BackgroundVariant.Dots, gap: 18, size: 1.5 }),
1005
- /* @__PURE__ */ jsx(Controls, { showInteractive: false, position: "bottom-left" }),
1105
+ /* @__PURE__ */ jsx(Controls, { showInteractive: false, showFitView: false, position: "bottom-left", children: /* @__PURE__ */ jsx(
1106
+ ControlButton,
1107
+ {
1108
+ className: "react-flow__controls-fitview",
1109
+ onClick: () => flowRef.current && void fit(flowRef.current, true),
1110
+ title: l.fitView,
1111
+ "aria-label": l.fitView,
1112
+ "data-testid": `${testId}-fit`,
1113
+ children: /* @__PURE__ */ jsx(Maximize, {})
1114
+ }
1115
+ ) }),
1006
1116
  steps.length > 3 && /* @__PURE__ */ jsx(
1007
1117
  MiniMap,
1008
1118
  {
@@ -1224,6 +1334,7 @@ function WorkflowBuilder({
1224
1334
  onChange: setWorkflow,
1225
1335
  selected,
1226
1336
  onSelect: setSelected,
1337
+ focus: selected ?? (panelOpen ? TRIGGER_NODE : null),
1227
1338
  onAddAfter: addAfter,
1228
1339
  trigger: { title: l.start, summary },
1229
1340
  workflowTitle: (id) => titles.get(id),