@foblex/flow 19.1.1 → 19.1.3
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 +23 -0
- package/fesm2022/foblex-flow.mjs +28 -10
- package/fesm2022/foblex-flow.mjs.map +1 -1
- package/index.d.ts +6 -3
- package/package.json +1 -1
package/AI.md
CHANGED
|
@@ -139,9 +139,31 @@ export class Editor {
|
|
|
139
139
|
- Rotation, connection waypoint editing, and user resize are not captured by managed state in v1.
|
|
140
140
|
- `state.changes()` increments once when a standalone mutation or outer batch settles. A drag can emit selection at start and move/drop at end while remaining one history step and one `changes()` increment.
|
|
141
141
|
- Use `state.snapshot()` for persistence; `load()` replaces data and resets history.
|
|
142
|
+
- `canvasTransformDebounce` defaults to `350ms`. It waits for wheel/pinch zoom events to settle, then records the current canvas transform as one change and one undo item. Setting it to `0` disables batching: every emitted `fCanvasChange` is applied immediately, increments `state.changes()`, and normally creates a separate undo item. Keep the canvas `[debounceTime]` unset when managed state owns viewport history; do not stack canvas and State debounce layers.
|
|
142
143
|
- 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
144
|
- 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
145
|
|
|
146
|
+
### Async reflow must stay inside an open transaction
|
|
147
|
+
|
|
148
|
+
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:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
state.beginBatch();
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
state.updateNode(nodeId, { isExpanded });
|
|
155
|
+
await new Promise<void>((resolve) => {
|
|
156
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
157
|
+
});
|
|
158
|
+
} finally {
|
|
159
|
+
state.endBatch();
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
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.
|
|
166
|
+
|
|
145
167
|
## Common Silent Failures — Check These First
|
|
146
168
|
|
|
147
169
|
When the flow compiles but looks wrong, verify in this order:
|
|
@@ -195,6 +217,7 @@ See [STYLING.md](./STYLING.md).
|
|
|
195
217
|
- Human docs: https://flow.foblex.com/docs/get-started
|
|
196
218
|
- Live examples with source: https://flow.foblex.com/examples/overview
|
|
197
219
|
- Managed state example and contract: https://flow.foblex.com/examples/state
|
|
220
|
+
- Managed state + asynchronous reflow recipe: https://flow.foblex.com/examples/state
|
|
198
221
|
|
|
199
222
|
## Fallback Rule
|
|
200
223
|
|
package/fesm2022/foblex-flow.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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));
|
|
@@ -22490,7 +22492,7 @@ function mergeFlowStateConfig(config) {
|
|
|
22490
22492
|
historyLimit: 50,
|
|
22491
22493
|
selectionInHistory: true,
|
|
22492
22494
|
canvasTransformInHistory: true,
|
|
22493
|
-
canvasTransformDebounce:
|
|
22495
|
+
canvasTransformDebounce: 350,
|
|
22494
22496
|
dropToGroup: false,
|
|
22495
22497
|
...config,
|
|
22496
22498
|
};
|
|
@@ -23306,18 +23308,34 @@ class FFlowStateController {
|
|
|
23306
23308
|
if (this._transformDebounceTimer !== null) {
|
|
23307
23309
|
clearTimeout(this._transformDebounceTimer);
|
|
23308
23310
|
}
|
|
23311
|
+
this._scheduleCanvasChangeFlush(debounce);
|
|
23312
|
+
}
|
|
23313
|
+
_flushCanvasChange() {
|
|
23314
|
+
const pending = this._pendingTransform;
|
|
23315
|
+
if (!pending) {
|
|
23316
|
+
return;
|
|
23317
|
+
}
|
|
23318
|
+
const current = this._readCanvasTransform();
|
|
23319
|
+
const debounce = this._config?.canvasTransformDebounce ?? 0;
|
|
23320
|
+
if (debounce > 0 && current && !this._isSameCanvasTransform(pending, current)) {
|
|
23321
|
+
this._pendingTransform = current;
|
|
23322
|
+
this._scheduleCanvasChangeFlush(debounce);
|
|
23323
|
+
return;
|
|
23324
|
+
}
|
|
23325
|
+
this._pendingTransform = null;
|
|
23326
|
+
const settled = debounce > 0 ? (current ?? pending) : pending;
|
|
23327
|
+
this._dispatch(() => this._state.applyTransform(settled));
|
|
23328
|
+
}
|
|
23329
|
+
_scheduleCanvasChangeFlush(debounce) {
|
|
23309
23330
|
this._transformDebounceTimer = setTimeout(() => {
|
|
23310
23331
|
this._transformDebounceTimer = null;
|
|
23311
23332
|
this._flushCanvasChange();
|
|
23312
23333
|
}, debounce);
|
|
23313
23334
|
}
|
|
23314
|
-
|
|
23315
|
-
|
|
23316
|
-
|
|
23317
|
-
|
|
23318
|
-
return;
|
|
23319
|
-
}
|
|
23320
|
-
this._dispatch(() => this._state.applyTransform(transform));
|
|
23335
|
+
_isSameCanvasTransform(first, second) {
|
|
23336
|
+
return (first.position.x === second.position.x &&
|
|
23337
|
+
first.position.y === second.position.y &&
|
|
23338
|
+
first.scale === second.scale);
|
|
23321
23339
|
}
|
|
23322
23340
|
/**
|
|
23323
23341
|
* Subscribes to every node's and group's `sizeChange` (a per-directive
|