Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/joint-core/src/dia/Paper.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ export const Paper = View.extend({
this.cloneOptions();
this.render();
this._setDimensions();
this._startObservingElementSize();
this.startListening();

// Mouse wheel events buffer
Expand Down Expand Up @@ -2253,6 +2254,7 @@ export const Paper = View.extend({

onRemove: function() {

this._stopObservingElementSize();
this.freeze();
this._updates.disabled = true;
//clean up all DOM elements/views to prevent memory leaks
Expand Down Expand Up @@ -2281,6 +2283,10 @@ export const Paper = View.extend({
this._setDimensions();
const computedSize = this.getComputedSize();
this.trigger('resize', computedSize.width, computedSize.height, data);
// Re-evaluate observer attachment in case the user toggled between
// explicit-size and CSS-relative modes.
this._stopObservingElementSize();
this._startObservingElementSize();
},

_setDimensions: function() {
Expand All @@ -2295,6 +2301,47 @@ export const Paper = View.extend({
});
},

_elementResizeObserver: null,
_lastObservedSize: null,

_startObservingElementSize: function() {
if (this._elementResizeObserver) return;
if (typeof ResizeObserver === 'undefined') return;
const { width, height } = this.options;
// Explicit-size mode: `setDimensions()` is the sole source of 'resize'.
if (isNumber(width) && isNumber(height)) return;

const size = this.getComputedSize();
this._lastObservedSize = { width: size.width, height: size.height };

this._elementResizeObserver = new ResizeObserver(() => {
if (!this.el || !this._elementResizeObserver) return;
// Re-read via getComputedSize() rather than the ResizeObserverEntry
// payload. `clientWidth/clientHeight` (the non-numeric branch of
// getComputedSize) is the padding box minus scrollbar — none of
// `contentBoxSize` / `borderBoxSize` / `contentRect` match exactly
// once the host has padding or border. Re-reading keeps the size
// we emit identical to every other `getComputedSize()` caller; the
// cost is one DOM read in a post-layout callback.
const next = this.getComputedSize();
const prev = this._lastObservedSize;
if (prev && prev.width === next.width && prev.height === next.height) return;
this._lastObservedSize = { width: next.width, height: next.height };
// The host already has its CSS-driven size; do not call
// `_setDimensions()` here — writing px values back would clobber CSS.
this.trigger('resize', next.width, next.height, { source: 'observer' });
});
this._elementResizeObserver.observe(this.el);
},

_stopObservingElementSize: function() {
if (this._elementResizeObserver) {
this._elementResizeObserver.disconnect();
this._elementResizeObserver = null;
}
this._lastObservedSize = null;
},

// Expand/shrink the paper to fit the content.
// Alternatively signature function(opt)
fitToContent: function(gridWidth, gridHeight, padding, opt) {
Expand Down
73 changes: 73 additions & 0 deletions packages/joint-core/test/jointjs/paper.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,79 @@ QUnit.module('paper', function(hooks) {
resizeCbSpy.resetHistory();
});

QUnit.module('autoResizePaper', function() {

// Waits for the ResizeObserver to deliver its async callback.
// Two rAFs cover the spec's "delivered before the next paint" plus
// browser implementation slack.
function afterResizeObserverDelivery(callback) {
requestAnimationFrame(function() {
requestAnimationFrame(callback);
});
}

QUnit.test('fires resize when host CSS size changes', function(assert) {
var done = assert.async();
var paper = this.paper;
// '100%' / '100%' makes the paper fill the container while
// keeping options non-numeric (i.e. observer-eligible).
$container.css({ width: '200px', height: '100px' });
paper.setDimensions('100%', '100%');
var spy = sinon.spy();
paper.on('resize', spy);
afterResizeObserverDelivery(function() {
spy.resetHistory();
$container.css({ width: '300px', height: '150px' });
afterResizeObserverDelivery(function() {
assert.ok(spy.called, 'resize fires for host size change');
var args = spy.lastCall.args;
assert.equal(args[0], 300, 'width reports host clientWidth');
assert.equal(args[1], 150, 'height reports host clientHeight');
assert.deepEqual(args[2], { source: 'observer' }, 'data carries observer source');
done();
});
});
});

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();
});
});

QUnit.test('disconnects on paper.remove()', function(assert) {
var paperEl = document.createElement('div');
$container.append(paperEl);
var paper = new joint.dia.Paper({
el: paperEl,
model: new joint.dia.Graph,
width: '100%',
height: '100%'
});
assert.ok(paper._elementResizeObserver, 'observer attached on init');
var disconnectSpy = sinon.spy(paper._elementResizeObserver, 'disconnect');
paper.remove();
assert.ok(disconnectSpy.calledOnce, 'disconnect called exactly once on remove');
assert.notOk(paper._elementResizeObserver, 'observer reference cleared');
});

QUnit.test('toggling between fixed and CSS-relative dimensions attaches/detaches observer', function(assert) {
var paper = this.paper;
assert.notOk(paper._elementResizeObserver, 'no observer with default numeric dims');
paper.setDimensions('100%', '100%');
assert.ok(paper._elementResizeObserver, 'observer attached after switching to CSS-relative');
paper.setDimensions(100, 100);
assert.notOk(paper._elementResizeObserver, 'observer detached when both dims numeric again');
});
});

});

QUnit.test('paper.addCell() number of sort()', function(assert) {
Expand Down
Loading