diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b21ff0..87ff7ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to the Reactodia will be documented in this document. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +#### 🐛 Fixed +- Fix incorrect canvas viewport position when `zoomToFit()` or similar operation is called immediately after element position changes; ## [0.31.0] - 2025-11-15 #### 🚀 New Features diff --git a/examples/stressTest.tsx b/examples/stressTest.tsx index 9701e02..ff5bf4f 100644 --- a/examples/stressTest.tsx +++ b/examples/stressTest.tsx @@ -52,7 +52,6 @@ function StressTestExample() { const canvas = view.findAnyCanvas(); if (canvas) { - canvas.renderingState.syncUpdate(); await canvas.zoomToFit(); } } diff --git a/package-lock.json b/package-lock.json index 1662ace..d63208f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@reactodia/workspace", - "version": "0.31.0", + "version": "0.31.1-dev", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@reactodia/workspace", - "version": "0.31.0", + "version": "0.31.1-dev", "license": "LGPL-2.1-or-later", "dependencies": { "@reactodia/hashmap": "^0.2.1", diff --git a/package.json b/package.json index 3388253..2a9c4f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@reactodia/workspace", - "version": "0.31.0", + "version": "0.31.1-dev", "description": "Reactodia Workspace -- library for visual interaction with graphs in a form of a diagram.", "repository": { "type": "git", diff --git a/src/diagram/paperArea.tsx b/src/diagram/paperArea.tsx index 4d83df6..85f6ca4 100644 --- a/src/diagram/paperArea.tsx +++ b/src/diagram/paperArea.tsx @@ -689,34 +689,40 @@ export class PaperArea extends React.Component implements } centerTo(paperPosition?: Vector, options: CenterToOptions = {}): Promise { + this.renderingState.updateLayersUpTo(RenderingLayer.PaperArea); const {width, height} = this.state.transform; const paperCenter = paperPosition || {x: width / 2, y: height / 2}; + return this.centerToPosition(paperCenter, options); + } + + centerContent(options: ViewportOptions = {}): Promise { + this.renderingState.updateLayersUpTo(RenderingLayer.PaperArea); + const bbox = this.getContentFittingBox(); + return this.centerTo({ + x: bbox.x + bbox.width / 2, + y: bbox.y + bbox.height / 2, + }, options); + } + + private centerToPosition(paperPosition: Vector, options: CenterToOptions) { if (typeof options.scale === 'number') { const {min, max} = this.zoomOptions; let scale = options.scale; scale = Math.max(scale, min); scale = Math.min(scale, max); const viewportState: Partial = { - center: paperCenter, + center: paperPosition, scale: {x: scale, y: scale}, }; return this.setViewportState(viewportState, options); } else { const viewportState: Partial = { - center: paperCenter, + center: paperPosition, }; return this.setViewportState(viewportState, options); } } - centerContent(options: ViewportOptions = {}): Promise { - const bbox = this.getContentFittingBox(); - return this.centerTo({ - x: bbox.x + bbox.width / 2, - y: bbox.y + bbox.height / 2, - }, options); - } - getScale() { return this.state.transform.scale; } @@ -766,11 +772,12 @@ export class PaperArea extends React.Component implements zoomToFit(options: ViewportOptions = {}): Promise { const {model, renderingState} = this.props; - const {elements} = model; + const {elements, links} = model; if (elements.length === 0) { return this.centerTo(); } - const bbox = getContentFittingBox(elements, [], renderingState); + this.renderingState.updateLayersUpTo(RenderingLayer.PaperArea); + const bbox = getContentFittingBox(elements, links, renderingState); return this.zoomToFitRect(bbox, options); } diff --git a/src/diagram/renderingState.ts b/src/diagram/renderingState.ts index 9d33acd..b8b02a7 100644 --- a/src/diagram/renderingState.ts +++ b/src/diagram/renderingState.ts @@ -92,14 +92,14 @@ export enum RenderingLayer { * Layer to measure rendered elements to get their sizes. */ ElementSize, - /** - * Layer to adjust scrollable area for the underlying canvas. - */ - PaperArea, /** * Layer to route links (compute link geometry). */ LinkRoutes, + /** + * Layer to adjust scrollable area for the underlying canvas. + */ + PaperArea, /** * Layer to render link templates. */ @@ -278,7 +278,7 @@ export class MutableRenderingState implements RenderingState { syncUpdate() { this.layerUpdater.dispose(); - this.runLayerUpdate(); + this.updateLayersUpTo(LAST_LAYER); } scheduleOnLayerUpdate(layer: RenderingLayer, callback: () => void): void { @@ -293,9 +293,11 @@ export class MutableRenderingState implements RenderingState { } } - private runLayerUpdate = () => { + private runLayerUpdate = () => this.updateLayersUpTo(LAST_LAYER); + + updateLayersUpTo(lastLayer: RenderingLayer): void { const toRun = new Set<() => void>(); - for (let layer = FIRST_LAYER; layer <= LAST_LAYER; layer++) { + for (let layer = FIRST_LAYER; layer <= lastLayer; layer++) { const callbackSet = this.scheduledByLayer.get(layer); if (callbackSet && callbackSet.size > 0) { for (const callback of callbackSet) {