@adatechnology/conversations-ui 0.1.0-rc.40 → 0.1.0-rc.41

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.
@@ -1869,6 +1869,9 @@ var CHAIN_FRAME_PADDING = 36;
1869
1869
  var QUICK_ADD_COLUMN_GAP = 320;
1870
1870
  var FLOW_KEY_PATTERN = /^[a-z0-9_]{2,40}$/;
1871
1871
  var EDGE_COLOR_LINEAR = "#94a3b8";
1872
+ var FOCUS_MAX_ZOOM = 1;
1873
+ var FOCUS_PADDING = 0.2;
1874
+ var FOCUS_DURATION_MS = 400;
1872
1875
  var EDGE_COLOR_BRANCH = "#8b5cf6";
1873
1876
  var EDGE_COLOR_FALLBACK = "#cbd5e1";
1874
1877
  var EDGE_COLOR_LIVE = "#3b82f6";
@@ -1937,6 +1940,7 @@ function FlowsWorkspace({
1937
1940
  const [rfNodes, setRfNodes] = useState6([]);
1938
1941
  const [flowInstance, setFlowInstance] = useState6(null);
1939
1942
  const [pendingFocusNodeId, setPendingFocusNodeId] = useState6(null);
1943
+ const [pendingFocusFlowKey, setPendingFocusFlowKey] = useState6(null);
1940
1944
  const reloadGraphs = useCallback(async () => {
1941
1945
  try {
1942
1946
  const loaded = await api.getGraphs();
@@ -2030,6 +2034,7 @@ function FlowsWorkspace({
2030
2034
  if (others.some(isFlowDirty) && !window.confirm(labels.workspace.unsavedChangesConfirm)) return;
2031
2035
  setOpenFlowKeys(graphs ? mergedFlowKeysFrom(key, graphs) : [key]);
2032
2036
  if (editingRef && editingRef.flowKey !== key) setEditingRef(null);
2037
+ setPendingFocusFlowKey(key);
2033
2038
  },
2034
2039
  [openFlowKeys, isFlowDirty, editingRef, graphs, labels]
2035
2040
  );
@@ -2222,6 +2227,18 @@ function FlowsWorkspace({
2222
2227
  flowInstance.setCenter(target.position.x + NODE_CARD_WIDTH / 2, target.position.y, { zoom: 1, duration: 400 });
2223
2228
  setPendingFocusNodeId(null);
2224
2229
  }, [pendingFocusNodeId, flowInstance, rfNodes]);
2230
+ useEffect3(() => {
2231
+ if (!pendingFocusFlowKey || !flowInstance) return;
2232
+ const flowNodes = rfNodes.filter((node) => parseNamespacedId(node.id).flowKey === pendingFocusFlowKey);
2233
+ if (flowNodes.length === 0) return;
2234
+ void flowInstance.fitView({
2235
+ nodes: flowNodes.map((node) => ({ id: node.id })),
2236
+ maxZoom: FOCUS_MAX_ZOOM,
2237
+ padding: FOCUS_PADDING,
2238
+ duration: FOCUS_DURATION_MS
2239
+ });
2240
+ setPendingFocusFlowKey(null);
2241
+ }, [pendingFocusFlowKey, flowInstance, rfNodes]);
2225
2242
  const onNodesChange = useCallback((changes) => {
2226
2243
  setRfNodes((current) => applyNodeChanges(changes, current));
2227
2244
  }, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adatechnology/conversations-ui",
3
- "version": "0.1.0-rc.40",
3
+ "version": "0.1.0-rc.41",
4
4
  "description": "WhatsApp conversation UI components — parametrizável por endpoint, tema e feature flags",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -98,6 +98,11 @@ const QUICK_ADD_COLUMN_GAP = 320
98
98
  const FLOW_KEY_PATTERN = /^[a-z0-9_]{2,40}$/
99
99
 
100
100
  const EDGE_COLOR_LINEAR = '#94a3b8'
101
+
102
+ /** Enquadramento ao focar um fluxo pela aba. */
103
+ const FOCUS_MAX_ZOOM = 1
104
+ const FOCUS_PADDING = 0.2
105
+ const FOCUS_DURATION_MS = 400
101
106
  const EDGE_COLOR_BRANCH = '#8b5cf6'
102
107
  const EDGE_COLOR_FALLBACK = '#cbd5e1'
103
108
  const EDGE_COLOR_LIVE = '#3b82f6'
@@ -262,6 +267,7 @@ export function FlowsWorkspace({
262
267
  const [rfNodes, setRfNodes] = useState<Node[]>([])
263
268
  const [flowInstance, setFlowInstance] = useState<ReactFlowInstance | null>(null)
264
269
  const [pendingFocusNodeId, setPendingFocusNodeId] = useState<string | null>(null)
270
+ const [pendingFocusFlowKey, setPendingFocusFlowKey] = useState<string | null>(null)
265
271
 
266
272
  const reloadGraphs = useCallback(async () => {
267
273
  try {
@@ -379,6 +385,10 @@ export function FlowsWorkspace({
379
385
  if (others.some(isFlowDirty) && !window.confirm(labels.workspace.unsavedChangesConfirm)) return
380
386
  setOpenFlowKeys(graphs ? mergedFlowKeysFrom(key, graphs) : [key])
381
387
  if (editingRef && editingRef.flowKey !== key) setEditingRef(null)
388
+ // Trocar o fluxo primário não movia a câmera: a aba acendia, o canvas continuava onde
389
+ // estava e o fluxo escolhido ficava fora da tela — clicar em "Consórcio" parecia não fazer
390
+ // nada. O enquadramento espera os cards existirem (ver o efeito abaixo).
391
+ setPendingFocusFlowKey(key)
382
392
  },
383
393
  [openFlowKeys, isFlowDirty, editingRef, graphs, labels],
384
394
  )
@@ -637,6 +647,22 @@ export function FlowsWorkspace({
637
647
  setPendingFocusNodeId(null)
638
648
  }, [pendingFocusNodeId, flowInstance, rfNodes])
639
649
 
650
+ // Enquadra o fluxo recém-focado. `fitView` restrito aos cards DELE, e não o `fitView` geral:
651
+ // o canvas mostra o fecho transitivo inteiro, e enquadrar tudo devolveria a mesma visão de
652
+ // sempre. `maxZoom` evita que um fluxo de dois nós encha a tela.
653
+ useEffect(() => {
654
+ if (!pendingFocusFlowKey || !flowInstance) return
655
+ const flowNodes = rfNodes.filter((node) => parseNamespacedId(node.id).flowKey === pendingFocusFlowKey)
656
+ if (flowNodes.length === 0) return
657
+ void flowInstance.fitView({
658
+ nodes: flowNodes.map((node) => ({ id: node.id })),
659
+ maxZoom: FOCUS_MAX_ZOOM,
660
+ padding: FOCUS_PADDING,
661
+ duration: FOCUS_DURATION_MS,
662
+ })
663
+ setPendingFocusFlowKey(null)
664
+ }, [pendingFocusFlowKey, flowInstance, rfNodes])
665
+
640
666
  const onNodesChange = useCallback((changes: NodeChange[]) => {
641
667
  setRfNodes((current) => applyNodeChanges(changes, current))
642
668
  }, [])
@@ -93,3 +93,34 @@ describe('contrato de customização', () => {
93
93
  }
94
94
  })
95
95
  })
96
+
97
+ describe('focar um fluxo leva a câmera até ele', () => {
98
+ /**
99
+ * `focusFlow` trocava o fluxo primário e remesclava o canvas, mas nunca movia a viewport: a aba
100
+ * acendia, a tela continuava onde estava e o fluxo escolhido ficava fora do campo de visão — em
101
+ * "Consórcio", parado no início com o fluxo lá embaixo, clicar parecia não fazer nada.
102
+ *
103
+ * Teste de fonte, como os demais deste arquivo: não prova o enquadramento na tela, prova que o
104
+ * clique pede o enquadramento e que ele é restrito ao fluxo clicado.
105
+ */
106
+ it('focar um fluxo agenda o enquadramento dele', async () => {
107
+ const content = await Bun.file(WORKSPACE_SOURCE).text()
108
+
109
+ expect(content).toContain('setPendingFocusFlowKey(key)')
110
+ })
111
+
112
+ it('enquadra apenas os cards do fluxo focado, não o canvas inteiro', async () => {
113
+ // O canvas mostra o fecho transitivo inteiro. Um `fitView()` sem `nodes` devolveria a mesma
114
+ // visão de sempre — que é exatamente o defeito relatado.
115
+ const content = await Bun.file(WORKSPACE_SOURCE).text()
116
+
117
+ expect(content).toMatch(/fitView\(\{[\s\S]*?nodes: flowNodes\.map/)
118
+ expect(content).toMatch(/flowKey === pendingFocusFlowKey/)
119
+ })
120
+
121
+ it('limita o zoom, para um fluxo de dois nós não encher a tela', async () => {
122
+ const content = await Bun.file(WORKSPACE_SOURCE).text()
123
+
124
+ expect(content).toContain('maxZoom: FOCUS_MAX_ZOOM')
125
+ })
126
+ })