Skip to content

Commit

Permalink
feat(context-pad): position absolute intead of relative to element
Browse files Browse the repository at this point in the history
BREAKING CHANGES:

* context pad is not an overlay anymore
* `getPad` does not return an overlay anymore
* scaling through configuration is not supported anymore
  • Loading branch information
philippfromme committed Apr 24, 2024
1 parent f11518b commit 501a026
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 69 deletions.
7 changes: 2 additions & 5 deletions assets/diagram-js.css
Original file line number Diff line number Diff line change
Expand Up @@ -473,16 +473,13 @@ marker.djs-dragger tspan {
/**
* context-pad
*/
.djs-overlay-context-pad {
width: 72px;
z-index: 100;
}

.djs-context-pad {
position: absolute;
display: none;
pointer-events: none;
line-height: 1;
width: 72px;
z-index: 100;
}

.djs-context-pad .entry {
Expand Down
211 changes: 147 additions & 64 deletions lib/features/context-pad/ContextPad.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {
assign,
every,
forEach,
isArray,
isDefined,
isFunction,
some
} from 'min-dash';
Expand All @@ -17,8 +15,6 @@ import {
domify as domify
} from 'min-dom';

import { getBBox } from '../../util/Elements';

import {
escapeCSS
} from '../../util/EscapeUtil';
Expand All @@ -30,22 +26,14 @@ import { isConnection } from '../../util/ModelUtil';
* @typedef {import('../../model/Types').Element} Element
*
* @typedef {import('../../util/Types').Rect} Rect
* @typedef {import('../../util/Types').RectTRBL} RectTRBL
*
* @typedef {import('../../core/Canvas').default} Canvas
* @typedef {import('../../core/EventBus').default} EventBus
* @typedef {import('../overlays/Overlays').default} Overlays
*
* @typedef {import('../overlays/Overlays').Overlay} Overlay
*
* @typedef {import('./ContextPadProvider').default} ContextPadProvider
* @typedef {import('./ContextPadProvider').ContextPadEntries} ContextPadEntries
*
* @typedef { {
* scale?: {
* min?: number;
* max?: number;
* };
* } } ContextPadConfig
*/

/**
Expand All @@ -57,32 +45,20 @@ import { isConnection } from '../../util/ModelUtil';
var entrySelector = '.entry';

var DEFAULT_PRIORITY = 1000;
var CONTEXT_PAD_PADDING = 12;
var CONTEXT_PAD_MARGIN = 12;
var HOVER_DELAY = 300;

/**
* A context pad that displays element specific, contextual actions next
* to a diagram element.
*
* @param {Canvas} canvas
* @param {ContextPadConfig} config
* @param {EventBus} eventBus
* @param {Overlays} overlays
*/
export default function ContextPad(canvas, config, eventBus, overlays) {
export default function ContextPad(canvas, eventBus) {

this._canvas = canvas;
this._eventBus = eventBus;
this._overlays = overlays;

var scale = isDefined(config && config.scale) ? config.scale : {
min: 1,
max: 1
};

this._overlaysConfig = {
scale: scale
};

this._current = null;

Expand All @@ -91,9 +67,7 @@ export default function ContextPad(canvas, config, eventBus, overlays) {

ContextPad.$inject = [
'canvas',
'config.contextPad',
'eventBus',
'overlays'
'eventBus'
];


Expand Down Expand Up @@ -142,6 +116,22 @@ ContextPad.prototype._init = function() {
self.open(currentTarget, true);
}
});

this._eventBus.on('canvas.viewbox.changed', () => {
if (this.isOpen()) {
this._updatePositionAndVisibility();
}
});

this._container = this._createContainer();
};

ContextPad.prototype._createContainer = function() {
const container = domify('<div class="djs-context-pad-parent"></div>');

this._canvas.getContainer().appendChild(container);

return container;
};

/**
Expand Down Expand Up @@ -329,8 +319,7 @@ ContextPad.prototype._getProviders = function() {
*/
ContextPad.prototype._updateAndOpen = function(target) {
var entries = this.getEntries(target),
pad = this.getPad(target),
html = pad.html,
{ html } = this.getPad(target),
image;

forEach(entries, function(entry, id) {
Expand Down Expand Up @@ -371,18 +360,20 @@ ContextPad.prototype._updateAndOpen = function(target) {
domClasses(html).add('open');

this._current = {
target: target,
entries: entries,
pad: pad
entries,
html,
target,
};

this._updatePositionAndVisibility();

this._eventBus.fire('contextPad.open', { current: this._current });
};

/**
* @param {ContextPadTarget} target
*
* @return {Overlay}
* @return { { html: HTMLElement } }
*/
ContextPad.prototype.getPad = function(target) {
if (this.isOpen()) {
Expand All @@ -391,16 +382,8 @@ ContextPad.prototype.getPad = function(target) {

var self = this;

var overlays = this._overlays;

var html = domify('<div class="djs-context-pad"></div>');

var position = this._getPosition(target);

var overlaysConfig = assign({
html: html
}, this._overlaysConfig, position);

domDelegate.bind(html, entrySelector, 'click', function(event) {
self.trigger('click', event);
});
Expand All @@ -422,18 +405,17 @@ ContextPad.prototype.getPad = function(target) {
event.stopPropagation();
});

var activeRootElement = this._canvas.getRootElement();

this._overlayId = overlays.add(activeRootElement, 'context-pad', overlaysConfig);

var pad = overlays.get(this._overlayId);
this._container.appendChild(html);

this._eventBus.fire('contextPad.create', {
target: target,
pad: pad
pad: html
});

return pad;
// TODO(philippfromme): { html } returned for legacy reasons; in the future
// this method should return html directly
// cf. https://github.com/search?q=org%3Abpmn-io%20%22getPad(%22&type=code
return { html };
};


Expand All @@ -447,9 +429,7 @@ ContextPad.prototype.close = function() {

clearTimeout(this._timeout);

this._overlays.remove(this._overlayId);

this._overlayId = null;
this._container.innerHTML = '';

this._eventBus.fire('contextPad.close', { current: this._current });

Expand Down Expand Up @@ -503,12 +483,27 @@ ContextPad.prototype.isOpen = function(target) {
* @return {boolean}
*/
ContextPad.prototype.isShown = function() {
return this.isOpen() && this._overlays.isShown();
return this.isOpen() && !domClasses(this._current.html).has('hidden');
};

ContextPad.prototype.show = function() {
if (!this.isOpen()) {
return;
}

domClasses(this._current.html).add('open');
};

ContextPad.prototype.hide = function() {
if (!this.isOpen()) {
return;
}

domClasses(this._current.html).remove('open');
};

/**
* Get contex pad position.
* Get context pad position.
*
* If target is a connection, the context pad will be placed according to the
* connection's last waypoint.
Expand All @@ -518,23 +513,111 @@ ContextPad.prototype.isShown = function() {
*
* @param {ContextPadTarget} target
*
* @return {{ position: { left: number; top: number; }}}
* @return {RectTRBL & { x: number, y: number }}
*/
ContextPad.prototype._getPosition = function(target) {
var container = this._canvas.getContainer();

target = isConnection(target) ? getLastWaypoint(target) : target;
var containerBounds = container.getBoundingClientRect();

var elements = isArray(target) ? target : [ target ];
var bBox = getBBox(elements);
var targetBounds = this._getTargetBounds(target);

if (!isArray(target) && isConnection(target)) {
const viewbox = this._canvas.viewbox();

const lastWaypoint = getLastWaypoint(target);

const x = lastWaypoint.x * viewbox.scale - viewbox.x * viewbox.scale,
y = lastWaypoint.y * viewbox.scale - viewbox.y * viewbox.scale;

return {
left: x + CONTEXT_PAD_MARGIN,
top: y
};
}

var left = targetBounds.right - containerBounds.left + CONTEXT_PAD_MARGIN;
var top = targetBounds.top - containerBounds.top;

return {
position: {
left: bBox.x + bBox.width + CONTEXT_PAD_PADDING,
top: bBox.y - CONTEXT_PAD_PADDING / 2
}
left,
top
};
};

/**
* Update context pad position and visibility.
*/
ContextPad.prototype._updatePositionAndVisibility = function() {
if (!this.isOpen()) {
return;
}

var html = this._current.html;

var position = this._getPosition(this._current.target);

if (!position) {
this.hide();

return;
} else {
this.show();
}

if ('x' in position && 'y' in position) {
html.style.left = position.x + 'px';
html.style.top = position.y + 'px';
} else {
[
'top',
'right',
'bottom',
'left'
].forEach(function(key) {
if (key in position) {
html.style[ key ] = position[ key ] + 'px';
}
});
}
};

/**
* Get bounding client rect of target element(s).
*
* @param {ContextPadTarget} target
*
* @returns {Rect & RectTRBL}
*/
ContextPad.prototype._getTargetBounds = function(target) {
var elements = isArray(target) ? target : [ target ];

var elementsGfx = elements.map((element) => {
return this._canvas.getGraphics(element);
});

return elementsGfx.reduce((bounds, elementGfx) => {
const elementBounds = elementGfx.getBoundingClientRect();

bounds.top = Math.min(bounds.top, elementBounds.top);
bounds.right = Math.max(bounds.right, elementBounds.right);
bounds.bottom = Math.max(bounds.bottom, elementBounds.bottom);
bounds.left = Math.min(bounds.left, elementBounds.left);

bounds.x = bounds.left;
bounds.y = bounds.top;

bounds.width = bounds.right - bounds.left;
bounds.height = bounds.bottom - bounds.top;

return bounds;
}, {
top: Infinity,
right: -Infinity,
bottom: -Infinity,
left: Infinity
});
};

// helpers //////////

Expand Down

0 comments on commit 501a026

Please sign in to comment.