diff --git a/.changeset/steady-idle-returns.md b/.changeset/steady-idle-returns.md new file mode 100644 index 0000000000..aa84ec154b --- /dev/null +++ b/.changeset/steady-idle-returns.md @@ -0,0 +1,5 @@ +--- +"@joint/router-avoid": patch +--- + +MainThreadProvider - fire `processed` (and so the RouterService `idle` event) after every incremental change, not only after a full sync, matching the Worker provider's behaviour diff --git a/packages/joint-router-avoid/src/providers/MainThreadProvider.mts b/packages/joint-router-avoid/src/providers/MainThreadProvider.mts index 8c7c7db6ea..fddd852ac2 100644 --- a/packages/joint-router-avoid/src/providers/MainThreadProvider.mts +++ b/packages/joint-router-avoid/src/providers/MainThreadProvider.mts @@ -62,6 +62,18 @@ export class MainThreadProvider extends Provider { return this.avoidInstance; } + /** + * Runs a routing pass over the pending avoid changes and announces its + * completion. Every processing path must go through here - `processed` + * drives the `RouterService`'s `idle` event, and the Worker provider + * fires it after every batch, so the incremental main-thread paths + * (not just {@link sync}) have to fire it too for parity. + */ + protected processTransaction(): void { + this.avoidRouter.processTransaction(); + this.trigger('processed'); + } + /** * Creates or updates the avoid shape for a JointJS element. * @@ -81,7 +93,7 @@ export class MainThreadProvider extends Provider { // Only update the position and size of the shape. avoidRouter.moveShape(existingShapeRef, shapeRect); if (process) { - avoidRouter.processTransaction(); + this.processTransaction(); } return; } @@ -103,7 +115,7 @@ export class MainThreadProvider extends Provider { }); if (process) { - avoidRouter.processTransaction(); + this.processTransaction(); } } @@ -121,7 +133,9 @@ export class MainThreadProvider extends Provider { connector.sourceId === undefined || connector.sourcePinId === undefined || connector.targetId === undefined || connector.targetPinId === undefined ) { - this.deleteConnector(connector.id); + // Forward `process`: inside a batched sync() the deletion must not + // process (and announce) a transaction of its own mid-batch. + this.deleteConnector(connector.id, process); return; } @@ -146,7 +160,7 @@ export class MainThreadProvider extends Provider { if (existingConnRef) { // It was already created, we just updated the endpoints. if (process) { - this.avoidRouter.processTransaction(); + this.processTransaction(); } return; } @@ -161,7 +175,7 @@ export class MainThreadProvider extends Provider { connRef.setCallback(this.onAvoidConnectorChanged, connRef); if (process) { - this.avoidRouter.processTransaction(); + this.processTransaction(); } return; @@ -180,7 +194,7 @@ export class MainThreadProvider extends Provider { delete this.shapeRefs[shapeId]; if (process) { - this.avoidRouter.processTransaction(); + this.processTransaction(); } } @@ -199,7 +213,7 @@ export class MainThreadProvider extends Provider { delete this.linksByPointer[connRef.g]; if (process) { - this.avoidRouter.processTransaction(); + this.processTransaction(); } } @@ -244,8 +258,7 @@ export class MainThreadProvider extends Provider { shapes.forEach((shape) => this.setShape(shape, false)); connectors.forEach((connector) => this.setConnector(connector, false)); - this.avoidRouter.processTransaction(); - this.trigger('processed'); + this.processTransaction(); } /** diff --git a/packages/joint-router-avoid/test/index.js b/packages/joint-router-avoid/test/index.js index d01c82d785..faf6ee3cec 100644 --- a/packages/joint-router-avoid/test/index.js +++ b/packages/joint-router-avoid/test/index.js @@ -654,3 +654,28 @@ QUnit.module('destroy()', () => { assert.deepEqual(cancelledLinks, []); }); }); + +QUnit.module('idle after incremental changes (main thread)', () => { + // `MainThreadProvider` routes synchronously, so `idle` (driven by the + // provider's `processed`) must fire during the originating graph change, + // not only after `start()`'s initial sync / `routeAll()`. + QUnit.test('an element move fires idle', async assert => { + const { routerService, source } = await initRouterWithLink({ x: 0, y: 0 }, { x: 300, y: 0 }); + + let idleCount = 0; + routerService.on('idle', () => idleCount++); + + source.position(50, 50); + assert.equal(idleCount, 1, 'idle fired for the element move'); + }); + + QUnit.test('a link removal fires idle', async assert => { + const { routerService, link } = await initRouterWithLink({ x: 0, y: 0 }, { x: 300, y: 0 }); + + let idleCount = 0; + routerService.on('idle', () => idleCount++); + + link.remove(); + assert.equal(idleCount, 1, 'idle fired for the link removal'); + }); +});