@adatechnology/conversations-ui 0.1.0-rc.29 → 0.1.0-rc.30

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.
@@ -1347,61 +1347,60 @@ function isGraphDirty(working, published) {
1347
1347
  return JSON.stringify(working) !== JSON.stringify(published);
1348
1348
  }
1349
1349
 
1350
- // src/flows/FlowsWorkspace.tsx
1351
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1352
- var RF_NODE_TYPES = {
1353
- ...flowNodeTypes,
1354
- ...flowPortalNodeTypes,
1355
- ...flowGroupHeaderNodeTypes,
1356
- ...flowGroupFrameNodeTypes
1357
- };
1358
- var CHAIN_FRAME_PADDING = 36;
1350
+ // src/flows/flowCanvasModel.ts
1359
1351
  var COLUMN_GAP = 360;
1360
1352
  var ROW_GAP = 40;
1361
- var FLOW_KEY_PATTERN = /^[a-z0-9_]{2,40}$/;
1362
- var EDGE_COLOR_LINEAR = "#94a3b8";
1363
- var EDGE_COLOR_BRANCH = "#8b5cf6";
1364
- var EDGE_COLOR_FALLBACK = "#cbd5e1";
1365
- var EDGE_COLOR_LIVE = "#3b82f6";
1366
- var EDGE_COLOR_CROSS_FLOW = "#06b6d4";
1367
- var BACKGROUND_COLOR_LIGHT2 = "#cbd5e1";
1368
- var BACKGROUND_COLOR_DARK2 = "#334155";
1369
- function portalNodeId(sourceId, target) {
1370
- return `__portal__${sourceId}__${target}`;
1353
+ var CHAIN_LABEL_ROOM = 24;
1354
+ function portalNodeId(sourceNodeId, target) {
1355
+ return `__portal__${sourceNodeId}__${target}`;
1371
1356
  }
1372
- function autoMergeAll(rootKey, graphsSource) {
1373
- return mergedFlowKeysFrom(rootKey, graphsSource).map((key) => ({ key, offset: { x: 0, y: 0 } }));
1357
+ function chainFrameNodeId(actionNodeId) {
1358
+ return `__chain__${actionNodeId}`;
1374
1359
  }
1375
- function computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) {
1376
- const openKeys = new Set(openFlows.map((flow) => flow.key));
1377
- const nodeByNsId = /* @__PURE__ */ new Map();
1378
- for (const { key } of openFlows) {
1379
- const graph = workingGraphs[key];
1380
- if (!graph) continue;
1381
- for (const node of Object.values(graph.nodes)) nodeByNsId.set(namespaceNodeId(key, node.id), node);
1360
+ var GROUP_HEADER_NODE_ID = "__group_header__";
1361
+ function countLiveByNode(params) {
1362
+ const { flowKey, rootFlowKey, positions } = params;
1363
+ const isRoot = flowKey === rootFlowKey;
1364
+ const counts = {};
1365
+ for (const position of positions ?? []) {
1366
+ if (position.flow !== flowKey && !isRoot) continue;
1367
+ const nodeId = isRoot ? position.menuNodeId : position.nodeId;
1368
+ if (!nodeId) continue;
1369
+ counts[nodeId] = (counts[nodeId] ?? 0) + 1;
1370
+ }
1371
+ return counts;
1372
+ }
1373
+ function computeMergedLayout(params) {
1374
+ const { openKeys, graphs, primaryFlowKey } = params;
1375
+ const open = new Set(openKeys);
1376
+ const nodeById = /* @__PURE__ */ new Map();
1377
+ for (const key of openKeys) {
1378
+ for (const node of Object.values(graphs[key]?.nodes ?? {})) {
1379
+ nodeById.set(namespaceNodeId(key, node.id), node);
1380
+ }
1382
1381
  }
1383
1382
  function forwardEdges(flowKey, node) {
1384
1383
  const result = [];
1385
1384
  for (const { target } of targetsOf(node)) {
1386
1385
  if (isCrossFlowTarget(target)) {
1387
- const targetFlowKey = crossFlowKey(target);
1388
- const targetGraph = openKeys.has(targetFlowKey) ? workingGraphs[targetFlowKey] : void 0;
1389
- if (targetGraph) result.push(namespaceNodeId(targetFlowKey, targetGraph.startNodeId));
1390
- } else if (workingGraphs[flowKey]?.nodes[target]) {
1386
+ const targetKey = crossFlowKey(target);
1387
+ const targetGraph = open.has(targetKey) ? graphs[targetKey] : void 0;
1388
+ if (targetGraph) result.push(namespaceNodeId(targetKey, targetGraph.startNodeId));
1389
+ } else if (graphs[flowKey]?.nodes[target]) {
1391
1390
  result.push(namespaceNodeId(flowKey, target));
1392
1391
  }
1393
1392
  }
1394
1393
  return result;
1395
1394
  }
1396
1395
  const rank = /* @__PURE__ */ new Map();
1397
- const primaryGraph = workingGraphs[primaryFlowKey];
1396
+ const primaryGraph = graphs[primaryFlowKey];
1398
1397
  const rootId = primaryGraph ? namespaceNodeId(primaryFlowKey, primaryGraph.startNodeId) : void 0;
1399
- if (rootId && nodeByNsId.has(rootId)) {
1398
+ if (rootId && nodeById.has(rootId)) {
1400
1399
  rank.set(rootId, 0);
1401
1400
  const queue = [rootId];
1402
1401
  while (queue.length > 0) {
1403
1402
  const id = queue.shift();
1404
- const node = nodeByNsId.get(id);
1403
+ const node = nodeById.get(id);
1405
1404
  if (!node) continue;
1406
1405
  for (const nextId of forwardEdges(parseNamespacedId(id).flowKey, node)) {
1407
1406
  if (!rank.has(nextId)) {
@@ -1412,100 +1411,111 @@ function computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) {
1412
1411
  }
1413
1412
  }
1414
1413
  const strayRank = Math.max(0, ...rank.values()) + 1;
1415
- for (const nsId of nodeByNsId.keys()) {
1416
- if (!rank.has(nsId)) rank.set(nsId, strayRank);
1414
+ for (const id of nodeById.keys()) {
1415
+ if (!rank.has(id)) rank.set(id, strayRank);
1417
1416
  }
1418
1417
  const layers = /* @__PURE__ */ new Map();
1419
- for (const [nsId, nodeRank] of rank) {
1420
- if (!layers.has(nodeRank)) layers.set(nodeRank, []);
1421
- layers.get(nodeRank).push(nsId);
1418
+ for (const [id, value] of rank) {
1419
+ const layer = layers.get(value);
1420
+ if (layer) layer.push(id);
1421
+ else layers.set(value, [id]);
1422
1422
  }
1423
1423
  const positions = /* @__PURE__ */ new Map();
1424
- for (const nodeRank of [...layers.keys()].sort((a, b) => a - b)) {
1424
+ for (const value of [...layers.keys()].sort((a, b) => a - b)) {
1425
1425
  let cursorY = 0;
1426
- for (const nsId of layers.get(nodeRank)) {
1427
- positions.set(nsId, { x: nodeRank * COLUMN_GAP, y: cursorY });
1428
- cursorY += estimateNodeHeight(nodeByNsId.get(nsId)) + ROW_GAP;
1426
+ for (const id of layers.get(value)) {
1427
+ positions.set(id, { x: value * COLUMN_GAP, y: cursorY });
1428
+ cursorY += estimateNodeHeight(nodeById.get(id)) + ROW_GAP;
1429
1429
  }
1430
1430
  }
1431
1431
  return positions;
1432
1432
  }
1433
- function computeLiveCounts(flowKey, rootFlowKey, livePositions) {
1434
- const counts = {};
1435
- for (const position of livePositions ?? []) {
1436
- if (position.flow !== flowKey && flowKey !== rootFlowKey) continue;
1437
- const nodeId = flowKey === rootFlowKey ? position.menuNodeId : position.nodeId;
1438
- if (!nodeId) continue;
1439
- counts[nodeId] = (counts[nodeId] ?? 0) + 1;
1440
- }
1441
- return counts;
1442
- }
1443
- function buildAllEdges(openFlows, workingGraphs, rootFlowKey, livePositions) {
1444
- const openKeys = new Set(openFlows.map((flow) => flow.key));
1433
+ function buildFlowEdges(params) {
1434
+ const { openKeys, graphs, rootFlowKey, livePositions } = params;
1435
+ const open = new Set(openKeys);
1445
1436
  const edges = [];
1446
- for (const { key: flowKey } of openFlows) {
1447
- const graph = workingGraphs[flowKey];
1437
+ for (const flowKey of openKeys) {
1438
+ const graph = graphs[flowKey];
1448
1439
  if (!graph) continue;
1449
- const liveCounts = computeLiveCounts(flowKey, rootFlowKey, livePositions);
1450
- for (const [id, node] of Object.entries(graph.nodes)) {
1451
- const isLive = (liveCounts[id] ?? 0) > 0;
1440
+ const liveCounts = countLiveByNode({ flowKey, rootFlowKey, positions: livePositions });
1441
+ for (const [nodeId, node] of Object.entries(graph.nodes)) {
1442
+ const live = (liveCounts[nodeId] ?? 0) > 0;
1452
1443
  for (const { target, optionId, isDefault } of targetsOf(node)) {
1453
1444
  if (!target) continue;
1454
1445
  const crossFlow = isCrossFlowTarget(target);
1455
1446
  let targetFlowKey = flowKey;
1456
- let rawTargetId = target;
1447
+ let targetNodeId = target;
1457
1448
  if (crossFlow) {
1458
1449
  const wantedKey = crossFlowKey(target);
1459
- const targetGraph = openKeys.has(wantedKey) ? workingGraphs[wantedKey] : void 0;
1450
+ const targetGraph = open.has(wantedKey) ? graphs[wantedKey] : void 0;
1460
1451
  if (targetGraph) {
1461
1452
  targetFlowKey = wantedKey;
1462
- rawTargetId = targetGraph.startNodeId;
1453
+ targetNodeId = targetGraph.startNodeId;
1463
1454
  } else {
1464
- rawTargetId = portalNodeId(id, target);
1455
+ targetNodeId = portalNodeId(nodeId, target);
1465
1456
  }
1466
1457
  }
1467
- const source = namespaceNodeId(flowKey, id);
1468
- const edgeTarget = namespaceNodeId(targetFlowKey, rawTargetId);
1469
- if (optionId === void 0 && !isDefault) {
1470
- const color = crossFlow ? EDGE_COLOR_CROSS_FLOW : isLive ? EDGE_COLOR_LIVE : EDGE_COLOR_LINEAR;
1471
- edges.push({
1472
- id: `${source}->${edgeTarget}`,
1473
- source,
1474
- target: edgeTarget,
1475
- type: "bezier",
1476
- animated: isLive,
1477
- style: crossFlow ? { stroke: color, strokeWidth: 1.5, strokeDasharray: "3 3" } : { stroke: color, strokeWidth: isLive ? 2.5 : 1.5 },
1478
- markerEnd: { type: MarkerType2.ArrowClosed, color, width: 18, height: 18 }
1479
- });
1480
- } else if (isDefault) {
1481
- const color = crossFlow ? EDGE_COLOR_CROSS_FLOW : EDGE_COLOR_FALLBACK;
1458
+ const source = namespaceNodeId(flowKey, nodeId);
1459
+ const edgeTarget = namespaceNodeId(targetFlowKey, targetNodeId);
1460
+ if (isDefault) {
1482
1461
  edges.push({
1483
1462
  id: `${source}->${edgeTarget}-default`,
1484
1463
  source,
1485
- sourceHandle: "__default",
1486
- target: edgeTarget,
1487
- type: "bezier",
1488
- style: { stroke: color, strokeWidth: 1.5, strokeDasharray: "5 4" },
1489
- markerEnd: { type: MarkerType2.ArrowClosed, color, width: 16, height: 16 }
1490
- });
1491
- } else {
1492
- const color = crossFlow ? EDGE_COLOR_CROSS_FLOW : isLive ? EDGE_COLOR_LIVE : EDGE_COLOR_BRANCH;
1493
- edges.push({
1494
- id: `${source}->${edgeTarget}-${optionId}`,
1495
- source,
1496
- sourceHandle: optionId,
1497
1464
  target: edgeTarget,
1498
- type: "bezier",
1499
- animated: isLive,
1500
- style: crossFlow ? { stroke: color, strokeWidth: 1.75, strokeDasharray: "3 3" } : { stroke: color, strokeWidth: isLive ? 2.5 : 1.75 },
1501
- markerEnd: { type: MarkerType2.ArrowClosed, color, width: 18, height: 18 }
1465
+ sourceHandle: "__default",
1466
+ kind: "fallback",
1467
+ crossFlow,
1468
+ // Fallback não anima: é o caminho que ninguém escolheu, e piscar sugeria que a conversa
1469
+ // estava passando por ali.
1470
+ live: false
1502
1471
  });
1472
+ continue;
1473
+ }
1474
+ if (optionId === void 0) {
1475
+ edges.push({ id: `${source}->${edgeTarget}`, source, target: edgeTarget, kind: "linear", crossFlow, live });
1476
+ continue;
1503
1477
  }
1478
+ edges.push({
1479
+ id: `${source}->${edgeTarget}-${optionId}`,
1480
+ source,
1481
+ target: edgeTarget,
1482
+ sourceHandle: optionId,
1483
+ kind: "branch",
1484
+ crossFlow,
1485
+ live
1486
+ });
1504
1487
  }
1505
1488
  }
1506
1489
  }
1507
1490
  return edges;
1508
1491
  }
1492
+ function detachedNodeIds(graph) {
1493
+ const connected = /* @__PURE__ */ new Set();
1494
+ for (const node of Object.values(graph.nodes)) {
1495
+ for (const { target } of targetsOf(node)) {
1496
+ if (!isCrossFlowTarget(target)) connected.add(target);
1497
+ }
1498
+ }
1499
+ const detached = /* @__PURE__ */ new Set();
1500
+ for (const nodeId of Object.keys(graph.nodes)) {
1501
+ if (nodeId !== graph.startNodeId && !connected.has(nodeId)) detached.add(nodeId);
1502
+ }
1503
+ return detached;
1504
+ }
1505
+ function chainFrameBounds(params) {
1506
+ const { nodeIds, graph, positionOf, padding } = params;
1507
+ const positions = nodeIds.map(positionOf);
1508
+ const minX = Math.min(...positions.map((each) => each.x));
1509
+ const maxX = Math.max(...positions.map((each) => each.x)) + NODE_CARD_WIDTH;
1510
+ const minY = Math.min(...positions.map((each) => each.y));
1511
+ const maxY = Math.max(...nodeIds.map((id, index) => positions[index].y + estimateNodeHeight(graph.nodes[id])));
1512
+ return {
1513
+ x: minX - padding,
1514
+ y: minY - padding - CHAIN_LABEL_ROOM,
1515
+ width: maxX - minX + padding * 2,
1516
+ height: maxY - minY + padding * 2 + CHAIN_LABEL_ROOM
1517
+ };
1518
+ }
1509
1519
  function newNodeFromSpec(spec, existingIds) {
1510
1520
  if (spec.kind === "question") {
1511
1521
  const id2 = slugifyNodeId("nova_pergunta", existingIds);
@@ -1532,6 +1542,44 @@ function newNodeFromSpec(spec, existingIds) {
1532
1542
  const id = slugifyNodeId("nova_acao", existingIds);
1533
1543
  return { id, type: "action", actionKind: spec.actionKind };
1534
1544
  }
1545
+
1546
+ // src/flows/FlowsWorkspace.tsx
1547
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1548
+ var RF_NODE_TYPES = {
1549
+ ...flowNodeTypes,
1550
+ ...flowPortalNodeTypes,
1551
+ ...flowGroupHeaderNodeTypes,
1552
+ ...flowGroupFrameNodeTypes
1553
+ };
1554
+ var CHAIN_FRAME_PADDING = 36;
1555
+ var FLOW_KEY_PATTERN = /^[a-z0-9_]{2,40}$/;
1556
+ var EDGE_COLOR_LINEAR = "#94a3b8";
1557
+ var EDGE_COLOR_BRANCH = "#8b5cf6";
1558
+ var EDGE_COLOR_FALLBACK = "#cbd5e1";
1559
+ var EDGE_COLOR_LIVE = "#3b82f6";
1560
+ var EDGE_COLOR_CROSS_FLOW = "#06b6d4";
1561
+ var BACKGROUND_COLOR_LIGHT2 = "#cbd5e1";
1562
+ var BACKGROUND_COLOR_DARK2 = "#334155";
1563
+ function styleEdge(spec) {
1564
+ const color = spec.crossFlow ? EDGE_COLOR_CROSS_FLOW : spec.kind === "fallback" ? EDGE_COLOR_FALLBACK : spec.live ? EDGE_COLOR_LIVE : spec.kind === "branch" ? EDGE_COLOR_BRANCH : EDGE_COLOR_LINEAR;
1565
+ const baseWidth = spec.kind === "branch" ? 1.75 : 1.5;
1566
+ const dash = spec.crossFlow ? "3 3" : spec.kind === "fallback" ? "5 4" : void 0;
1567
+ const markerSize = spec.kind === "fallback" ? 16 : 18;
1568
+ return {
1569
+ id: spec.id,
1570
+ source: spec.source,
1571
+ target: spec.target,
1572
+ ...spec.sourceHandle === void 0 ? {} : { sourceHandle: spec.sourceHandle },
1573
+ type: "bezier",
1574
+ animated: spec.live,
1575
+ style: {
1576
+ stroke: color,
1577
+ strokeWidth: spec.live && !spec.crossFlow ? 2.5 : baseWidth,
1578
+ ...dash === void 0 ? {} : { strokeDasharray: dash }
1579
+ },
1580
+ markerEnd: { type: MarkerType2.ArrowClosed, color, width: markerSize, height: markerSize }
1581
+ };
1582
+ }
1535
1583
  function extractErrorMessage(error) {
1536
1584
  if (error instanceof Error) return error.message;
1537
1585
  return void 0;
@@ -1554,7 +1602,7 @@ function FlowsWorkspace({
1554
1602
  const [loadState, setLoadState] = useState3("loading");
1555
1603
  const [livePositions, setLivePositions] = useState3(void 0);
1556
1604
  const [viewMode, setViewMode] = useState3("detail");
1557
- const [openFlows, setOpenFlows] = useState3([{ key: rootFlowKey, offset: { x: 0, y: 0 } }]);
1605
+ const [openFlowKeys, setOpenFlowKeys] = useState3([rootFlowKey]);
1558
1606
  const [hasAutoMerged, setHasAutoMerged] = useState3(false);
1559
1607
  const [workingGraphs, setWorkingGraphs] = useState3({});
1560
1608
  const [editingRef, setEditingRef] = useState3(null);
@@ -1599,11 +1647,11 @@ function FlowsWorkspace({
1599
1647
  clearInterval(timer);
1600
1648
  };
1601
1649
  }, [api, livePollIntervalMs]);
1602
- const primaryFlowKey = openFlows[0]?.key ?? rootFlowKey;
1650
+ const primaryFlowKey = openFlowKeys[0] ?? rootFlowKey;
1603
1651
  const primaryGraph = workingGraphs[primaryFlowKey];
1604
1652
  useEffect2(() => {
1605
1653
  if (!graphs || hasAutoMerged) return;
1606
- setOpenFlows(autoMergeAll(rootFlowKey, graphs));
1654
+ setOpenFlowKeys(mergedFlowKeysFrom(rootFlowKey, graphs));
1607
1655
  setHasAutoMerged(true);
1608
1656
  }, [graphs, hasAutoMerged, rootFlowKey]);
1609
1657
  useEffect2(() => {
@@ -1611,7 +1659,7 @@ function FlowsWorkspace({
1611
1659
  setWorkingGraphs((prev) => {
1612
1660
  let changed = false;
1613
1661
  const next = { ...prev };
1614
- for (const { key } of openFlows) {
1662
+ for (const key of openFlowKeys) {
1615
1663
  if (!next[key] && graphs[key]) {
1616
1664
  next[key] = graphs[key];
1617
1665
  changed = true;
@@ -1619,7 +1667,7 @@ function FlowsWorkspace({
1619
1667
  }
1620
1668
  return changed ? next : prev;
1621
1669
  });
1622
- }, [graphs, openFlows]);
1670
+ }, [graphs, openFlowKeys]);
1623
1671
  const isFlowDirty = useCallback(
1624
1672
  (key) => {
1625
1673
  const working = workingGraphs[key];
@@ -1631,12 +1679,12 @@ function FlowsWorkspace({
1631
1679
  );
1632
1680
  const issuesByFlow = useMemo2(() => {
1633
1681
  const map = {};
1634
- for (const { key } of openFlows) {
1682
+ for (const key of openFlowKeys) {
1635
1683
  const graph = workingGraphs[key];
1636
1684
  if (graph) map[key] = validateGraph(graph, labels.validation);
1637
1685
  }
1638
1686
  return map;
1639
- }, [openFlows, workingGraphs, labels]);
1687
+ }, [openFlowKeys, workingGraphs, labels]);
1640
1688
  const errorCount = Object.values(issuesByFlow).reduce(
1641
1689
  (sum, list) => sum + list.filter((issue) => issue.severity === "error").length,
1642
1690
  0
@@ -1645,7 +1693,7 @@ function FlowsWorkspace({
1645
1693
  (sum, list) => sum + list.filter((issue) => issue.severity === "warning").length,
1646
1694
  0
1647
1695
  );
1648
- const dirtyKeys = openFlows.map((flow) => flow.key).filter(isFlowDirty);
1696
+ const dirtyKeys = openFlowKeys.filter(isFlowDirty);
1649
1697
  const isDirty = dirtyKeys.length > 0;
1650
1698
  const updateFlow = useCallback((flowKey, updater) => {
1651
1699
  setWorkingGraphs((prev) => {
@@ -1656,17 +1704,17 @@ function FlowsWorkspace({
1656
1704
  }, []);
1657
1705
  const focusFlow = useCallback(
1658
1706
  (key) => {
1659
- const others = openFlows.filter((flow) => flow.key !== key);
1660
- if (others.some((flow) => isFlowDirty(flow.key)) && !window.confirm(labels.workspace.unsavedChangesConfirm)) return;
1661
- setOpenFlows(graphs ? autoMergeAll(key, graphs) : [{ key, offset: { x: 0, y: 0 } }]);
1707
+ const others = openFlowKeys.filter((each) => each !== key);
1708
+ if (others.some(isFlowDirty) && !window.confirm(labels.workspace.unsavedChangesConfirm)) return;
1709
+ setOpenFlowKeys(graphs ? mergedFlowKeysFrom(key, graphs) : [key]);
1662
1710
  if (editingRef && editingRef.flowKey !== key) setEditingRef(null);
1663
1711
  },
1664
- [openFlows, isFlowDirty, editingRef, graphs, labels]
1712
+ [openFlowKeys, isFlowDirty, editingRef, graphs, labels]
1665
1713
  );
1666
1714
  const closeFlow = useCallback(
1667
1715
  (key) => {
1668
1716
  if (isFlowDirty(key) && !window.confirm(labels.workspace.unsavedChangesConfirm)) return;
1669
- setOpenFlows((prev) => prev.filter((flow) => flow.key !== key));
1717
+ setOpenFlowKeys((prev) => prev.filter((each) => each !== key));
1670
1718
  setWorkingGraphs((prev) => {
1671
1719
  const { [key]: _removed, ...rest } = prev;
1672
1720
  return rest;
@@ -1675,50 +1723,30 @@ function FlowsWorkspace({
1675
1723
  },
1676
1724
  [isFlowDirty, editingRef, labels]
1677
1725
  );
1678
- const mergeFlow = useCallback(
1679
- (targetFlowKey, source) => {
1680
- setOpenFlows((prev) => {
1681
- if (prev.some((flow) => flow.key === targetFlowKey)) return prev;
1682
- const sourceOpen = prev.find((flow) => flow.key === source.flowKey);
1683
- const sourceGraph = workingGraphs[source.flowKey];
1684
- const sourceLocalPosition = sourceGraph?.nodes[source.nodeId]?.position ?? (sourceGraph ? computeAutoLayout(sourceGraph)[source.nodeId] : void 0) ?? { x: 0, y: 0 };
1685
- const baseOffset = sourceOpen?.offset ?? { x: 0, y: 0 };
1686
- return [
1687
- ...prev,
1688
- {
1689
- key: targetFlowKey,
1690
- offset: {
1691
- x: baseOffset.x + sourceLocalPosition.x + 400,
1692
- y: baseOffset.y + sourceLocalPosition.y
1693
- }
1694
- }
1695
- ];
1696
- });
1697
- },
1698
- [workingGraphs]
1699
- );
1700
- const isMerged = openFlows.length > 1;
1726
+ const mergeFlow = useCallback((targetFlowKey) => {
1727
+ setOpenFlowKeys((prev) => prev.includes(targetFlowKey) ? prev : [...prev, targetFlowKey]);
1728
+ }, []);
1729
+ const isMerged = openFlowKeys.length > 1;
1701
1730
  const mergedPositions = useMemo2(
1702
- () => isMerged ? computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) : null,
1703
- [isMerged, openFlows, workingGraphs, primaryFlowKey]
1731
+ () => isMerged ? computeMergedLayout({ openKeys: openFlowKeys, graphs: workingGraphs, primaryFlowKey }) : null,
1732
+ [isMerged, openFlowKeys, workingGraphs, primaryFlowKey]
1704
1733
  );
1705
1734
  const renderedPositionsRef = useRef2(/* @__PURE__ */ new Map());
1706
1735
  const derivedNodes = useMemo2(() => {
1707
1736
  const allNodes = [];
1708
- for (const { key: flowKey, offset } of openFlows) {
1737
+ for (const flowKey of openFlowKeys) {
1709
1738
  let resolvePosition2 = function(nodeId) {
1710
1739
  if (mergedPositions) {
1711
1740
  const nsId = namespaceNodeId(flowKey, nodeId);
1712
1741
  return renderedPositionsRef.current.get(nsId) ?? mergedPositions.get(nsId) ?? { x: 0, y: 0 };
1713
1742
  }
1714
- const local = graph.nodes[nodeId]?.position ?? fallbackPositions[nodeId] ?? { x: 0, y: 0 };
1715
- return { x: local.x + offset.x, y: local.y + offset.y };
1743
+ return graph.nodes[nodeId]?.position ?? fallbackPositions[nodeId] ?? { x: 0, y: 0 };
1716
1744
  };
1717
1745
  var resolvePosition = resolvePosition2;
1718
1746
  const graph = workingGraphs[flowKey];
1719
1747
  if (!graph) continue;
1720
1748
  const fallbackPositions = mergedPositions ? {} : computeAutoLayout(graph);
1721
- const liveCounts = computeLiveCounts(flowKey, rootFlowKey, livePositions);
1749
+ const liveCounts = countLiveByNode({ flowKey, rootFlowKey, positions: livePositions });
1722
1750
  const flowIssues = issuesByFlow[flowKey] ?? [];
1723
1751
  const isPrimary = flowKey === primaryFlowKey;
1724
1752
  const connectedTargets = /* @__PURE__ */ new Set();
@@ -1748,7 +1776,7 @@ function FlowsWorkspace({
1748
1776
  const crossFlowTargets = [...new Set(targetsOf(node).map((edge) => edge.target).filter(isCrossFlowTarget))];
1749
1777
  crossFlowTargets.forEach((target, index) => {
1750
1778
  const targetFlowKey = crossFlowKey(target);
1751
- if (openFlows.some((flow) => flow.key === targetFlowKey)) return;
1779
+ if (openFlowKeys.includes(targetFlowKey)) return;
1752
1780
  allNodes.push({
1753
1781
  id: namespaceNodeId(flowKey, portalNodeId(node.id, target)),
1754
1782
  type: "flowPortal",
@@ -1757,7 +1785,7 @@ function FlowsWorkspace({
1757
1785
  position: { x: position.x + 320, y: position.y + index * 70 },
1758
1786
  data: {
1759
1787
  label: graphs?.[targetFlowKey]?.label ?? targetFlowKey,
1760
- onNavigate: () => mergeFlow(targetFlowKey, { flowKey, nodeId: node.id })
1788
+ onNavigate: () => mergeFlow(targetFlowKey)
1761
1789
  }
1762
1790
  });
1763
1791
  });
@@ -1803,7 +1831,7 @@ function FlowsWorkspace({
1803
1831
  }
1804
1832
  return allNodes;
1805
1833
  }, [
1806
- openFlows,
1834
+ openFlowKeys,
1807
1835
  workingGraphs,
1808
1836
  mergedPositions,
1809
1837
  livePositions,
@@ -1818,8 +1846,8 @@ function FlowsWorkspace({
1818
1846
  labels
1819
1847
  ]);
1820
1848
  const edges = useMemo2(
1821
- () => buildAllEdges(openFlows, workingGraphs, rootFlowKey, livePositions),
1822
- [openFlows, workingGraphs, rootFlowKey, livePositions]
1849
+ () => buildFlowEdges({ openKeys: openFlowKeys, graphs: workingGraphs, rootFlowKey, livePositions }).map(styleEdge),
1850
+ [openFlowKeys, workingGraphs, rootFlowKey, livePositions]
1823
1851
  );
1824
1852
  useEffect2(() => {
1825
1853
  setRfNodes(derivedNodes);
@@ -1841,16 +1869,13 @@ function FlowsWorkspace({
1841
1869
  (_event, node) => {
1842
1870
  renderedPositionsRef.current.set(node.id, node.position);
1843
1871
  const { flowKey, nodeId } = parseNamespacedId(node.id);
1844
- const openFlow = openFlows.find((flow) => flow.key === flowKey);
1845
- if (!openFlow) return;
1846
- const drawnWithOffset = openFlows.length === 1;
1847
- const localPosition = drawnWithOffset ? { x: node.position.x - openFlow.offset.x, y: node.position.y - openFlow.offset.y } : node.position;
1872
+ if (!openFlowKeys.includes(flowKey)) return;
1848
1873
  updateFlow(
1849
1874
  flowKey,
1850
- (graph) => graph.nodes[nodeId] ? { ...graph, nodes: { ...graph.nodes, [nodeId]: { ...graph.nodes[nodeId], position: localPosition } } } : graph
1875
+ (graph) => graph.nodes[nodeId] ? { ...graph, nodes: { ...graph.nodes, [nodeId]: { ...graph.nodes[nodeId], position: node.position } } } : graph
1851
1876
  );
1852
1877
  },
1853
- [openFlows, updateFlow]
1878
+ [openFlowKeys, updateFlow]
1854
1879
  );
1855
1880
  const onConnect = useCallback(
1856
1881
  (connection) => {
@@ -1890,17 +1915,14 @@ function FlowsWorkspace({
1890
1915
  if (!primaryGraph) return;
1891
1916
  renderedPositionsRef.current.clear();
1892
1917
  if (isMerged) {
1893
- const positions2 = computeMergedLayout(openFlows, workingGraphs, primaryFlowKey);
1894
- for (const { key, offset } of openFlows) {
1918
+ const positions2 = computeMergedLayout({ openKeys: openFlowKeys, graphs: workingGraphs, primaryFlowKey });
1919
+ for (const key of openFlowKeys) {
1895
1920
  updateFlow(key, (graph) => ({
1896
1921
  ...graph,
1897
1922
  nodes: Object.fromEntries(
1898
1923
  Object.entries(graph.nodes).map(([id, node]) => {
1899
1924
  const position = positions2.get(namespaceNodeId(key, id));
1900
- return [
1901
- id,
1902
- position ? { ...node, position: { x: position.x - offset.x, y: position.y - offset.y } } : node
1903
- ];
1925
+ return [id, position ? { ...node, position } : node];
1904
1926
  })
1905
1927
  )
1906
1928
  }));
@@ -1955,7 +1977,7 @@ function FlowsWorkspace({
1955
1977
  ...newFlow.showInMenu ? { menuOptionLabel: newFlow.menuOptionLabel || newFlow.label } : {}
1956
1978
  });
1957
1979
  await reloadGraphs();
1958
- setOpenFlows([{ key: newFlow.key, offset: { x: 0, y: 0 } }]);
1980
+ setOpenFlowKeys([newFlow.key]);
1959
1981
  setShowCreateDialog(false);
1960
1982
  setNewFlow({ key: "", label: "", showInMenu: false, menuOptionLabel: "" });
1961
1983
  setFlowMutationState({ pending: false });
@@ -1970,7 +1992,7 @@ function FlowsWorkspace({
1970
1992
  try {
1971
1993
  await deleteFlow(primaryGraph.key);
1972
1994
  const reloaded = await reloadGraphs();
1973
- setOpenFlows(reloaded ? autoMergeAll(rootFlowKey, reloaded) : [{ key: rootFlowKey, offset: { x: 0, y: 0 } }]);
1995
+ setOpenFlowKeys(reloaded ? mergedFlowKeysFrom(rootFlowKey, reloaded) : [rootFlowKey]);
1974
1996
  setShowDeleteDialog(false);
1975
1997
  setFlowMutationState({ pending: false });
1976
1998
  } catch (error) {
@@ -2318,13 +2340,20 @@ export {
2318
2340
  FlowPortalNode,
2319
2341
  FlowWhatsAppPreview,
2320
2342
  FlowsWorkspace,
2343
+ GROUP_HEADER_NODE_ID,
2321
2344
  NODE_CARD_WIDTH,
2322
2345
  WHATSAPP_LIMITS,
2323
2346
  applyConnection,
2347
+ buildFlowEdges,
2348
+ chainFrameBounds,
2349
+ chainFrameNodeId,
2324
2350
  computeAutoLayout,
2325
2351
  computeFlowMapLayout,
2352
+ computeMergedLayout,
2353
+ countLiveByNode,
2326
2354
  crossFlowKey,
2327
2355
  crossFlowTargetsOf,
2356
+ detachedNodeIds,
2328
2357
  estimateNodeHeight,
2329
2358
  findCollectionChains,
2330
2359
  flowGroupFrameNodeTypes,
@@ -2337,8 +2366,10 @@ export {
2337
2366
  mergeFlowEditorLabels,
2338
2367
  mergedFlowKeysFrom,
2339
2368
  namespaceNodeId,
2369
+ newNodeFromSpec,
2340
2370
  nodeLabel,
2341
2371
  parseNamespacedId,
2372
+ portalNodeId,
2342
2373
  removeNodeAndCleanRefs,
2343
2374
  rendersAsButtons,
2344
2375
  resolveConnection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adatechnology/conversations-ui",
3
- "version": "0.1.0-rc.29",
3
+ "version": "0.1.0-rc.30",
4
4
  "description": "WhatsApp conversation UI components — parametrizável por endpoint, tema e feature flags",
5
5
  "publishConfig": {
6
6
  "access": "public"