@foblex/flow 19.1.1 → 19.1.2

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/AI.md CHANGED
@@ -142,6 +142,27 @@ export class Editor {
142
142
  - For initial or other application-driven viewport positioning, suppress the event at the canvas helper: `canvas.resetScaleAndCenter(false, false)`. The second `false` means `emitCanvasChange = false`, so managed history is untouched.
143
143
  - Connection endpoints are connector ids. Automatic cascade from node/group deletion uses the rendered connector registry; before connectors render, remove known attached connection ids explicitly in the same `state.batch(...)`.
144
144
 
145
+ ### Async reflow must stay inside an open transaction
146
+
147
+ When an application expands/collapses a node, its record changes synchronously but `withReflowOnResize()` emits the resulting `fMoveNodes` positions later, after Angular rendering and `ResizeObserver`. Do not use synchronous `state.batch(() => ...)` if both changes must be one undo item. Open the transaction before changing the record and close it only after reflow settles:
148
+
149
+ ```typescript
150
+ state.beginBatch();
151
+
152
+ try {
153
+ state.updateNode(nodeId, { isExpanded });
154
+ await new Promise<void>((resolve) => {
155
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
156
+ });
157
+ } finally {
158
+ state.endBatch();
159
+ }
160
+ ```
161
+
162
+ The state controller applies reflow's `fMoveNodes` event inside the open transaction. One `undo()` then restores both the expanded value and every moved position. Two frames fit an immediately rendered size change; for CSS transitions, lazy content, or longer asynchronous layout, close from the real completion signal. Always close in `finally`, because an unclosed batch merges unrelated future mutations into the same history entry.
163
+
164
+ If the toggle is nested inside `fDragHandle`, add `fDragBlocker` to the toggle. Otherwise its pointer down can select the node first; with `selectionInHistory: true`, that selection is a separate intentional history item even though expand and reflow share one batch.
165
+
145
166
  ## Common Silent Failures — Check These First
146
167
 
147
168
  When the flow compiles but looks wrong, verify in this order:
@@ -195,6 +216,7 @@ See [STYLING.md](./STYLING.md).
195
216
  - Human docs: https://flow.foblex.com/docs/get-started
196
217
  - Live examples with source: https://flow.foblex.com/examples/overview
197
218
  - Managed state example and contract: https://flow.foblex.com/examples/state
219
+ - Managed state + asynchronous reflow recipe: https://flow.foblex.com/examples/state
198
220
 
199
221
  ## Fallback Rule
200
222
 
@@ -8529,9 +8529,10 @@ let CreateConnectionFromConnectorPreparation = class CreateConnectionFromConnect
8529
8529
  this._startDrag(event.getPosition(), source);
8530
8530
  }
8531
8531
  _findConnector(target) {
8532
+ const connectorElement = target.closest('[fConnector]');
8532
8533
  return this._store.connectors
8533
8534
  .getAll()
8534
- .find((x) => x instanceof FConnectorDirective && x.hostElement.contains(target));
8535
+ .find((x) => x instanceof FConnectorDirective && x.hostElement === connectorElement);
8535
8536
  }
8536
8537
  _findSourcesInNode(node) {
8537
8538
  return getAllSourceConnectors(this._store).filter((x) => node.isContains(x.hostElement));
@@ -16130,9 +16131,10 @@ class FClickConnectFlow {
16130
16131
  return connector.canBeConnected ? connector : undefined;
16131
16132
  }
16132
16133
  _findUnifiedConnector(target) {
16134
+ const connectorElement = target.closest('[fConnector]');
16133
16135
  return this._store.connectors
16134
16136
  .getAll()
16135
- .find((x) => x instanceof FConnectorDirective && x.hostElement.contains(target));
16137
+ .find((x) => x instanceof FConnectorDirective && x.hostElement === connectorElement);
16136
16138
  }
16137
16139
  _resolveSourceForOutlet(outlet) {
16138
16140
  const node = this._store.nodes.getAll().find((n) => n.isContains(outlet.hostElement));