Skip to content

Commit

Permalink
feat(dia.ToolView): add visibility option callback (#2745)
Browse files Browse the repository at this point in the history
  • Loading branch information
kumilingus authored Aug 27, 2024
1 parent dd0db32 commit 3706574
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 31 deletions.
33 changes: 29 additions & 4 deletions packages/joint-core/src/dia/ToolView.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const ToolView = mvc.View.extend({
className: 'tool',
svgElement: true,
_visible: true,
_visibleExplicit: true,

init: function() {
var name = this.name;
Expand All @@ -30,16 +31,40 @@ export const ToolView = mvc.View.extend({
return this.name;
},

// Evaluate the visibility of the tool and update the `display` CSS property
updateVisibility: function() {
const isVisible = this.computeVisibility();
this.el.style.display = isVisible ? '' : 'none';
this._visible = isVisible;
},

// Evaluate the visibility of the tool. The method returns `true` if the tool
// should be visible in the DOM.
computeVisibility() {
if (!this.isExplicitlyVisible()) return false;
const { visibility } = this.options;
if (typeof visibility !== 'function') return true;
return !!visibility.call(this, this.relatedView, this);
},

show: function() {
this.el.style.display = '';
this._visible = true;
this._visibleExplicit = true;
this.updateVisibility();
},

hide: function() {
this.el.style.display = 'none';
this._visible = false;
this._visibleExplicit = false;
this.updateVisibility();
},

// The method returns `false` if the `hide()` method was called on the tool.
isExplicitlyVisible: function() {
return !!this._visibleExplicit;
},

// The method returns `false` if the tool is not visible (it has `display: none`).
// This can happen if the `hide()` method was called or the tool is not visible
// because of the `visibility` option was evaluated to `false`.
isVisible: function() {
return !!this._visible;
},
Expand Down
11 changes: 8 additions & 3 deletions packages/joint-core/src/dia/ToolsView.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ export const ToolsView = mvc.View.extend({
var isRendered = this.isRendered;
for (var i = 0, n = tools.length; i < n; i++) {
var tool = tools[i];
tool.updateVisibility();
if (!tool.isVisible()) continue;
if (!isRendered) {
// First update executes render()
tool.render();
} else if (opt.tool !== tool.cid && tool.isVisible()) {
} else if (opt.tool !== tool.cid) {
tool.update();
}
}
Expand Down Expand Up @@ -87,9 +89,12 @@ export const ToolsView = mvc.View.extend({
if (!tools) return this;
for (var i = 0, n = tools.length; i < n; i++) {
var tool = tools[i];
if (tool !== blurredTool && !tool.isVisible()) {
if (tool !== blurredTool && !tool.isExplicitlyVisible()) {
tool.show();
tool.update();
// Check if the tool is conditionally visible too
if (tool.isVisible()) {
tool.update();
}
}
}
return this;
Expand Down
118 changes: 118 additions & 0 deletions packages/joint-core/test/jointjs/dia/linkTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,124 @@ QUnit.module('linkTools', function(hooks) {
});
});

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

QUnit.test('isVisible()', function(assert) {
const remove = new joint.linkTools.Remove();
assert.ok(remove.isVisible());
remove.hide();
assert.notOk(remove.isVisible());
remove.show();
assert.ok(remove.isVisible());
});

QUnit.test('updateVisibility()', function(assert) {
const remove = new joint.linkTools.Remove();
const toolsView = new joint.dia.ToolsView({ tools: [remove] });
linkView.addTools(toolsView);
assert.notEqual(getComputedStyle(remove.el).display, 'none');
remove.hide();
assert.equal(getComputedStyle(remove.el).display, 'none');
remove.show();
assert.notEqual(getComputedStyle(remove.el).display, 'none');
});

QUnit.module('option: visibility', function(assert) {

QUnit.test('is visible or hidden', function(assert) {
let isVisible = true;
const visibilitySpy = sinon.spy(() => isVisible);
const removeButton = new joint.linkTools.Remove({
visibility: visibilitySpy
});
const otherButton = new joint.linkTools.Button();
const toolsView = new joint.dia.ToolsView({
tools: [
removeButton,
otherButton
]
});
linkView.addTools(toolsView);

// Initial state.
assert.notEqual(getComputedStyle(removeButton.el).display, 'none');
assert.ok(removeButton.isVisible());
assert.equal(visibilitySpy.callCount, 1);
assert.ok(visibilitySpy.calledWithExactly(linkView, removeButton));
assert.ok(visibilitySpy.calledOn(removeButton));

// Visibility function should be called on update.
isVisible = false;
toolsView.update();
assert.equal(getComputedStyle(removeButton.el).display, 'none');
assert.notOk(removeButton.isVisible());
assert.ok(removeButton.isExplicitlyVisible());
assert.equal(visibilitySpy.callCount, 2);
assert.ok(visibilitySpy.calledWithExactly(linkView, removeButton));
assert.ok(visibilitySpy.calledOn(removeButton));

// Other button should not be affected by the visibility function.
assert.notEqual(getComputedStyle(otherButton.el).display, 'none');
assert.ok(otherButton.isVisible());

// Focus & blur on other button should not change the visibility of
// the remove button.
toolsView.focusTool(otherButton);
assert.equal(getComputedStyle(removeButton.el).display, 'none');
assert.notOk(removeButton.isVisible());
assert.notOk(removeButton.isExplicitlyVisible());
toolsView.blurTool(otherButton);
assert.equal(getComputedStyle(removeButton.el).display, 'none');
assert.notOk(removeButton.isVisible());
assert.ok(removeButton.isExplicitlyVisible());

isVisible = true;
toolsView.update();
toolsView.focusTool(otherButton);
assert.equal(getComputedStyle(removeButton.el).display, 'none');
assert.notOk(removeButton.isVisible());
assert.notOk(removeButton.isExplicitlyVisible());
toolsView.blurTool(otherButton);
assert.notEqual(getComputedStyle(removeButton.el).display, 'none');
assert.ok(removeButton.isVisible());
assert.ok(removeButton.isExplicitlyVisible());
});

QUnit.test('it\'s not updated when hidden', function(assert) {
const button1 = new joint.linkTools.Button({
visibility: () => false
});
const button2 = new joint.linkTools.Button({
visibility: () => true
});
const button1UpdateSpy = sinon.spy(button1, 'update');
const button2UpdateSpy = sinon.spy(button2, 'update');
const toolsView = new joint.dia.ToolsView({
tools: [button1, button2]
});
linkView.addTools(toolsView);
assert.equal(button1.update.callCount, 0);
assert.equal(button2.update.callCount, 1);
toolsView.update();
assert.equal(button1.update.callCount, 0);
assert.equal(button2.update.callCount, 2);
button1.show();
button2.hide();
toolsView.update();
assert.equal(button1.update.callCount, 0);
assert.equal(button2.update.callCount, 2);
toolsView.focusTool(null); // hide all
assert.equal(button1.update.callCount, 0);
assert.equal(button2.update.callCount, 2);
toolsView.blurTool(null); // show all
assert.equal(button1.update.callCount, 0);
assert.equal(button2.update.callCount, 3);
button1UpdateSpy.restore();
button2UpdateSpy.restore();
});
});
});

QUnit.module('TargetAnchor', function() {
[{
resetAnchor: true,
Expand Down
8 changes: 8 additions & 0 deletions packages/joint-core/test/ts/toolsView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ const toolsView = new dia.ToolsView({

toolsView.configure({ component: false });

new linkTools.Button({
visibility: (view) => view.getConnectionLength() > 100,
});

new elementTools.Button({
visibility: (view) => view.model.size().width > 100,
});

interface RadiusControlOptions extends elementTools.Control.Options {
testOption?: number;
}
Expand Down
Loading

0 comments on commit 3706574

Please sign in to comment.