Skip to content

fix(joint-core): emit 'resize' when host element CSS size changes#3317

Merged
Geliogabalus merged 4 commits into
clientIO:devfrom
kumilingus:paper-auto-resize-observer
May 19, 2026
Merged

fix(joint-core): emit 'resize' when host element CSS size changes#3317
Geliogabalus merged 4 commits into
clientIO:devfrom
kumilingus:paper-auto-resize-observer

Conversation

@kumilingus

Copy link
Copy Markdown
Contributor

Summary

dia.Paper now attaches a ResizeObserver to its host <div> and re-emits the existing 'resize' event when the host's computed size changes. Downstream subscribers — GridLayerView, PaperScroller, rulers, the React host — automatically follow CSS-relative sizing (width: null / '100%' / flex / container queries) without manual setDimensions() calls.

The observer self-skips when both options.width and options.height are numeric, so the existing explicit-size contract is preserved. Auto-emitted events carry { source: 'observer' } as the data argument so listeners doing expensive work can short-circuit on it.

Motivation

Paper#getComputedSize() already falls back to el.clientWidth / el.clientHeight when options.width/height are non-numeric, so CSS-relative sizing works — but JointJS never learns when the host actually changes size. The 'resize' event only fired from explicit setDimensions() calls, leaving GridLayerView and other size-aware consumers stale on flex resizes, window resizes, sidebar toggles, container queries, etc.

@joint/react makes this especially visible: it already forces width: undefined / height: undefined on every Paper, so every React paper is in CSS-relative mode. This change automatically lights up grid / scroller / rulers across the entire React surface with no other code changes required.

Flow

┌─────────────────────────────┐
│ host <div> CSS size changes │
└─────────────┬───────────────┘
              ▼
   ┌────────────────────────┐        ┌──────────────────────────────┐
   │  ResizeObserver        │──────▶ │ Paper observer callback      │
   │  (owned by Paper)      │        │ • getComputedSize()          │
   └────────────────────────┘        │ • dedup vs _lastObservedSize │
                                     │ • trigger('resize', w, h,    │
                                     │         { source:'observer'})│
                                     └─────────────┬────────────────┘
                                                   ▼
                              GridLayerView.updateGrid /
                              PaperScroller / rulers /
                              user `paper.on('resize', …)`

setDimensions() keeps its own direct trigger('resize', …) for the explicit-API path; both code paths feed _lastObservedSize, so observer + setDimensions never double-fire.

Changes

  • packages/joint-core/src/dia/Paper.mjs
    • New private methods _startObservingElementSize / _stopObservingElementSize and dedup state _lastObservedSize.
    • Wired into init(), onRemove(), and setDimensions() (re-evaluates observer attachment when toggling between explicit and CSS-relative modes).
  • packages/joint-core/test/jointjs/paper.js — five new QUnit tests covering fire / skip / teardown / toggle paths.

Test plan

  • yarn workspace @joint/core test — full QUnit + Mocha suite green.
  • yarn workspace @joint/core run test-ts — TS pass.
  • Live: in any story with CSS-relative sizing (e.g. @joint/react storybook → Examples / Dynamic Interactivity), add drawGrid={{ name: 'dot' }} and confirm the grid follows window resize and sidebar toggles.
  • Regression: open a demo using numeric width/height. Confirm no extra 'resize' events fire on window resize.
  • Cleanup: mount / unmount under React StrictMode and confirm ResizeObserver#disconnect is called exactly once per unmount.

claude and others added 2 commits May 15, 2026 18:39
`dia.Paper` now attaches a ResizeObserver to its host `<div>` and re-emits
the existing `'resize'` event when the host's computed size changes. This
makes downstream subscribers — `GridLayerView`, `PaperScroller`, rulers,
the React host — track CSS-relative sizing (`width: null`/`'100%'`/flex/
container queries) without manual `setDimensions()` calls. The observer
self-skips when both `options.width` and `options.height` are numeric,
preserving the explicit-size contract. New opt-out: `autoResizePaper: false`.

Auto-emitted events carry `{ source: 'observer' }` as `data` so listeners
can distinguish them from explicit `setDimensions()` emissions.

https://claude.ai/code/session_013FyjBbiuXq4gr3ZRkT4Uvz

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances dia.Paper to automatically re-emit the existing 'resize' event when the host element’s CSS-driven size changes by attaching a ResizeObserver (while preserving the explicit numeric width/height behavior).

Changes:

  • Add ResizeObserver lifecycle management to dia.Paper and emit 'resize' with { source: 'observer' } when the host element’s computed size changes.
  • Ensure observer behavior is coordinated with setDimensions() (dedup/avoid double resize emissions).
  • Add QUnit coverage for observer attach/detach, opt-out, and toggle scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/joint-core/src/dia/Paper.mjs Adds ResizeObserver-based resize re-emission and lifecycle hooks (init, onRemove, setDimensions).
packages/joint-core/test/jointjs/paper.js Adds QUnit tests validating observer-driven resize behavior and teardown/toggle paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/joint-core/src/dia/Paper.mjs
Comment thread packages/joint-core/src/dia/Paper.mjs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

packages/joint-core/test/jointjs/paper.js:142

  • This test doesn’t actually verify that the observer is skipped in numeric-dimensions mode: even if a ResizeObserver were attached, changing $container size wouldn’t change the paper element’s px size, so no callback/event would fire. To make the test assert the intended behavior, also check that paper._elementResizeObserver is not created (or spy on ResizeObserver/observe() calls).
            QUnit.test('numeric dimensions skip the observer', function(assert) {
                var done = assert.async();
                var paper = this.paper;
                paper.setDimensions(200, 100);
                var spy = sinon.spy();
                paper.on('resize', spy);
                $container.css({ width: '500px', height: '500px' });
                afterResizeObserverDelivery(function() {
                    assert.ok(spy.notCalled, 'host CSS resize ignored in explicit-size mode');
                    done();
                });

Comment thread packages/joint-core/test/jointjs/paper.js
Comment thread packages/joint-core/src/dia/Paper.mjs
@kumilingus kumilingus changed the title feat(joint-core): auto-emit 'resize' when host element CSS size changes fix(joint-core): emit 'resize' when host element CSS size changes May 15, 2026
@Geliogabalus Geliogabalus merged commit 6cca6db into clientIO:dev May 19, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants