You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP

diagram-js

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

diagram-js - npm Package Compare versions

Comparing version

to
11.0.0-aplha.0

@@ -74,3 +74,3 @@ import {

const parent = document.createElement('div');
parent.setAttribute('class', 'djs-container');
parent.setAttribute('class', 'djs-container djs-parent');

@@ -77,0 +77,0 @@ assignStyle(parent, {

import {
assign,
render,
html
} from '../../ui';
import {
domify,
remove as domRemove,
attr as domAttr
} from 'min-dom';
import {
forEach,
isFunction,
isDefined,
omit,
size
isDefined
} from 'min-dash';
import {
assignStyle,
delegate as domDelegate,
domify as domify,
classes as domClasses,
attr as domAttr,
query as domQuery,
remove as domRemove
} from 'min-dom';
import PopupMenuComponent from './PopupMenuComponent';
import {
escapeCSS
} from '../../util/EscapeUtil';

@@ -49,2 +47,5 @@ var DATA_REF = 'data-id';

export default function PopupMenu(config, eventBus, canvas) {
this._eventBus = eventBus;
this._canvas = canvas;
this._current = {};

@@ -60,6 +61,16 @@ var scale = isDefined(config && config.scale) ? config.scale : {

this._eventBus = eventBus;
this._canvas = canvas;
this._providers = {};
this._current = {};
eventBus.on('diagram.destroy', () => {
this._destroy();
});
eventBus.on('element.changed', event => {
const element = this.isOpen() && this._current.element;
if (event.element === element) {
this._render();
}
});
}

@@ -73,61 +84,39 @@

/**
* Registers a popup menu provider
*
* @param {string} id
* @param {number} [priority=1000]
* @param {Object} provider
*
* @example
* const popupMenuProvider = {
* getPopupMenuEntries: function(element) {
* return {
* 'entry-1': {
* label: 'My Entry',
* action: function() { alert("I have been clicked!"); }
* }
* }
* }
* };
*
* popupMenu.registerProvider('myMenuID', popupMenuProvider);
*/
PopupMenu.prototype.registerProvider = function(id, priority, provider) {
if (!provider) {
provider = priority;
priority = DEFAULT_PRIORITY;
}
PopupMenu.prototype._render = function() {
this._eventBus.on('popupMenu.getProviders.' + id, priority, function(event) {
event.providers.push(provider);
});
};
const {
position: _position,
className,
entries,
headerEntries,
options
} = this._current;
/**
* Determine if the popup menu has entries.
*
* @return {boolean} true if empty
*/
PopupMenu.prototype.isEmpty = function(element, providerId) {
if (!element) {
throw new Error('element parameter is missing');
}
const entriesArray = [
...Object.entries(entries).map(
([ key, value ]) => ({ id: key, ...value })
)
];
if (!providerId) {
throw new Error('providerId parameter is missing');
}
const position = _position && (
(container) => this._ensureVisible(container, _position)
);
var providers = this._getProviders(providerId);
const scale = this._updateScale(this._current.container);
if (!providers) {
return true;
}
const onClose = result => this.close(result);
var entries = this._getEntries(element, providers),
headerEntries = this._getHeaderEntries(element, providers);
var hasEntries = size(entries) > 0,
hasHeaderEntries = headerEntries && size(headerEntries) > 0;
return !hasEntries && !hasHeaderEntries;
render(html`
<${PopupMenuComponent}
onClose=${ onClose }
position=${ position }
className=${ className }
entries=${ entriesArray }
headerEntries=${ headerEntries }
scale=${ scale }
...${{ ...options }}
/>
`,
this._current.container
);
};

@@ -145,6 +134,3 @@

*/
PopupMenu.prototype.open = function(element, id, position) {
var providers = this._getProviders(id);
PopupMenu.prototype.open = function(element, providerId, position, options) {
if (!element) {

@@ -154,4 +140,4 @@ throw new Error('Element is missing');

if (!providers || !providers.length) {
throw new Error('No registered providers for: ' + id);
if (!providerId) {
throw new Error('No registered providers for: ' + providerId);
}

@@ -166,41 +152,48 @@

}
const {
entries,
headerEntries
} = this._getContext(element, providerId);
this._current = {
position,
className: providerId,
element,
entries,
headerEntries,
container: this._createContainer({ provider: providerId }),
options
};
this._emit('open');
var current = this._current = {
className: id,
element: element,
position: position
};
this._bindAutoClose();
var entries = this._getEntries(element, providers),
headerEntries = this._getHeaderEntries(element, providers);
this._render();
current.entries = assign({}, entries, headerEntries);
current.container = this._createContainer(id);
};
if (size(headerEntries)) {
current.container.appendChild(
this._createEntries(headerEntries, 'djs-popup-header')
);
}
if (size(entries)) {
current.container.appendChild(
this._createEntries(entries, 'djs-popup-body')
);
PopupMenu.prototype._getContext = function(element, provider) {
const providers = this._getProviders(provider);
if (!providers || !providers.length) {
throw new Error('No registered providers for: ' + provider);
}
var canvas = this._canvas,
parent = canvas.getContainer();
const entries = this._getEntries(element, providers);
const headerEntries = this._getHeaderEntries(element, providers);
this._attachContainer(current.container, parent, position.cursor);
this._bindAutoClose();
return {
entries,
headerEntries,
empty: !(
Object.keys(entries).length ||
Object.keys(headerEntries).length
)
};
};
/**
* Removes the popup menu and unbinds the event handlers.
*/
PopupMenu.prototype.close = function() {

@@ -214,15 +207,35 @@

this._unbindAutoClose();
domRemove(this._current.container);
this.reset();
this._current.container = null;
};
PopupMenu.prototype.reset = function() {
render(null, this._current.container);
};
PopupMenu.prototype._emit = function(event, payload) {
this._eventBus.fire(`popupMenu.${ event }`, payload);
};
PopupMenu.prototype._createContainer = function(config) {
let parent = config && config.parent || 'body';
if (typeof parent === 'string') {
parent = document.querySelector(parent);
}
const container = domify(`<div class="djs-popup-parent djs-parent" data-popup=${config.provider}></div>`);
parent.appendChild(container);
return container;
};
/**
* Determine if an open popup menu exist.
*
* @return {boolean} true if open
* Set up listener to close popup automatically on certain events.
*/
PopupMenu.prototype.isOpen = function() {
return !!this._current.container;
PopupMenu.prototype._bindAutoClose = function() {
this._eventBus.once(CLOSE_EVENTS, this.close, this);
};

@@ -232,23 +245,138 @@

/**
* Trigger an action associated with an entry.
* Remove the auto-closing listener.
*/
PopupMenu.prototype._unbindAutoClose = function() {
this._eventBus.off(CLOSE_EVENTS, this.close, this);
};
PopupMenu.prototype._destroy = function() {
this._current.container && domRemove(this._current.container);
};
/**
* Updates popup style.transform with respect to the config and zoom level.
*
* @param {Object} event
*
* @return the result of the action callback, if any
* @param {Object} container
*/
PopupMenu.prototype.trigger = function(event) {
PopupMenu.prototype._updateScale = function(container) {
var zoom = this._canvas.zoom();
// silence other actions
event.preventDefault();
var scaleConfig = this._config.scale,
minScale,
maxScale,
scale = zoom;
var element = event.delegateTarget || event.target,
entryId = domAttr(element, DATA_REF);
if (scaleConfig !== true) {
var entry = this._getEntry(entryId);
if (scaleConfig === false) {
minScale = 1;
maxScale = 1;
} else {
minScale = scaleConfig.min;
maxScale = scaleConfig.max;
}
if (entry.action) {
return entry.action.call(null, event, entry);
if (isDefined(minScale) && zoom < minScale) {
scale = minScale;
}
if (isDefined(maxScale) && zoom > maxScale) {
scale = maxScale;
}
}
return scale;
};
PopupMenu.prototype._ensureVisible = function(container, position) {
var documentBounds = document.documentElement.getBoundingClientRect();
var containerBounds = container.getBoundingClientRect();
var overAxis = {},
left = position.x,
top = position.y;
if (position.x + containerBounds.width > documentBounds.width) {
overAxis.x = true;
}
if (position.y + containerBounds.height > documentBounds.height) {
overAxis.y = true;
}
if (overAxis.x && overAxis.y) {
left = position.x - containerBounds.width;
top = position.y - containerBounds.height;
} else if (overAxis.x) {
left = position.x - containerBounds.width;
top = position.y;
} else if (overAxis.y && position.y < containerBounds.height) {
left = position.x;
top = 10;
} else if (overAxis.y) {
left = position.x;
top = position.y - containerBounds.height;
}
return {
x: left,
y: top
};
};
PopupMenu.prototype.isEmpty = function(element, providerId) {
if (!element) {
throw new Error('element parameter is missing');
}
if (!providerId) {
throw new Error('providerId parameter is missing');
}
const providers = this._getProviders(providerId);
if (!providers || !providers.length) {
return true;
}
return this._getContext(element, providerId).empty;
};
/**
* Registers a popup menu provider
*
* @param {string} id
* @param {number} [priority=1000]
* @param {Object} provider
*
* @example
* const popupMenuProvider = {
* getPopupMenuEntries: function(element) {
* return {
* 'entry-1': {
* label: 'My Entry',
* action: function() { alert("I have been clicked!"); }
* }
* }
* }
* };
*
* popupMenu.registerProvider('myMenuID', popupMenuProvider);
*/
PopupMenu.prototype.registerProvider = function(id, priority, provider) {
if (!provider) {
provider = priority;
priority = DEFAULT_PRIORITY;
}
this._eventBus.on('popupMenu.getProviders.' + id, priority, function(event) {
event.providers.push(provider);
});
};
PopupMenu.prototype._getProviders = function(id) {

@@ -342,46 +470,10 @@

/**
* Gets an entry instance (either entry or headerEntry) by id.
*
* @param {string} entryId
*
* @return {Object} entry instance
*/
PopupMenu.prototype._getEntry = function(entryId) {
var entry = this._current.entries[entryId];
if (!entry) {
throw new Error('entry not found');
}
return entry;
};
PopupMenu.prototype._emit = function(eventName) {
this._eventBus.fire('popupMenu.' + eventName);
};
/**
* Creates the popup menu container.
* Determine if an open popup menu exist.
*
* @return {Object} a DOM container
* @return {boolean} true if open
*/
PopupMenu.prototype._createContainer = function(id) {
var container = domify('<div class="djs-popup">'),
position = this._current.position,
className = this._current.className;
assignStyle(container, {
position: 'absolute',
left: position.x + 'px',
top: position.y + 'px',
visibility: 'hidden'
});
domClasses(container).add(className);
domAttr(container, 'data-popup', id);
return container;
PopupMenu.prototype.isOpen = function() {
return !!this._current.container;
};

@@ -391,228 +483,40 @@

/**
* Attaches the container to the DOM.
* Trigger an action associated with an entry.
*
* @param {Object} container
* @param {Object} parent
*/
PopupMenu.prototype._attachContainer = function(container, parent, cursor) {
var self = this;
// Event handler
domDelegate.bind(container, '.entry' ,'click', function(event) {
self.trigger(event);
});
this._updateScale(container);
// Attach to DOM
parent.appendChild(container);
if (cursor) {
this._assureIsInbounds(container, cursor);
}
// display after position adjustment to avoid flickering
assignStyle(container, { visibility: 'visible' });
};
/**
* Updates popup style.transform with respect to the config and zoom level.
* @param {Object} event
*
* @method _updateScale
*
* @param {Object} container
* @return the result of the action callback, if any
*/
PopupMenu.prototype._updateScale = function(container) {
var zoom = this._canvas.zoom();
PopupMenu.prototype.trigger = function(event) {
var scaleConfig = this._config.scale,
minScale,
maxScale,
scale = zoom;
// silence other actions
event.preventDefault();
if (scaleConfig !== true) {
var element = event.delegateTarget || event.target,
entryId = domAttr(element, DATA_REF);
if (scaleConfig === false) {
minScale = 1;
maxScale = 1;
} else {
minScale = scaleConfig.min;
maxScale = scaleConfig.max;
}
var entry = this._getEntry(entryId);
if (isDefined(minScale) && zoom < minScale) {
scale = minScale;
}
if (isDefined(maxScale) && zoom > maxScale) {
scale = maxScale;
}
if (entry.action) {
return entry.action.call(null, event, entry);
}
setTransform(container, 'scale(' + scale + ')');
};
/**
* Make sure that the menu is always fully shown
* Gets an entry instance (either entry or headerEntry) by id.
*
* @method function
* @param {string} entryId
*
* @param {Object} container
* @param {Position} cursor {x, y}
* @return {Object} entry instance
*/
PopupMenu.prototype._assureIsInbounds = function(container, cursor) {
var canvas = this._canvas,
clientRect = canvas._container.getBoundingClientRect();
PopupMenu.prototype._getEntry = function(entryId) {
var containerX = container.offsetLeft,
containerY = container.offsetTop,
containerWidth = container.scrollWidth,
containerHeight = container.scrollHeight,
overAxis = {},
left, top;
var entry = this._current.entries[entryId];
var cursorPosition = {
x: cursor.x - clientRect.left,
y: cursor.y - clientRect.top
};
if (containerX + containerWidth > clientRect.width) {
overAxis.x = true;
if (!entry) {
throw new Error('entry not found');
}
if (containerY + containerHeight > clientRect.height) {
overAxis.y = true;
}
if (overAxis.x && overAxis.y) {
left = cursorPosition.x - containerWidth + 'px';
top = cursorPosition.y - containerHeight + 'px';
} else if (overAxis.x) {
left = cursorPosition.x - containerWidth + 'px';
top = cursorPosition.y + 'px';
} else if (overAxis.y && cursorPosition.y < containerHeight) {
left = cursorPosition.x + 'px';
top = 10 + 'px';
} else if (overAxis.y) {
left = cursorPosition.x + 'px';
top = cursorPosition.y - containerHeight + 'px';
}
assignStyle(container, { left: left, top: top }, { 'zIndex': 1000 });
};
/**
* Creates a list of entries and returns them as a DOM container.
*
* @param {Array<Object>} entries an array of entry objects
* @param {string} className the class name of the entry container
*
* @return {Object} a DOM container
*/
PopupMenu.prototype._createEntries = function(entries, className) {
var entriesContainer = domify('<div>'),
self = this;
domClasses(entriesContainer).add(className);
forEach(entries, function(entry, id) {
var entryContainer = self._createEntry(entry, id),
grouping = entry.group || 'default',
groupContainer = domQuery('[data-group=' + escapeCSS(grouping) + ']', entriesContainer);
if (!groupContainer) {
groupContainer = domify('<div class="group"></div>');
domAttr(groupContainer, 'data-group', grouping);
entriesContainer.appendChild(groupContainer);
}
groupContainer.appendChild(entryContainer);
});
return entriesContainer;
};
/**
* Creates a single entry and returns it as a DOM container.
*
* @param {Object} entry
*
* @return {Object} a DOM container
*/
PopupMenu.prototype._createEntry = function(entry, id) {
var entryContainer = domify('<div>'),
entryClasses = domClasses(entryContainer);
entryClasses.add('entry');
if (entry.className) {
entry.className.split(' ').forEach(function(className) {
entryClasses.add(className);
});
}
domAttr(entryContainer, DATA_REF, id);
if (entry.label) {
var label = domify('<span>');
label.textContent = entry.label;
entryContainer.appendChild(label);
}
if (entry.imageUrl) {
var image = domify('<img>');
domAttr(image, 'src', entry.imageUrl);
entryContainer.appendChild(image);
}
if (entry.active === true) {
entryClasses.add('active');
}
if (entry.disabled === true) {
entryClasses.add('disabled');
}
if (entry.title) {
entryContainer.title = entry.title;
}
return entryContainer;
};
/**
* Set up listener to close popup automatically on certain events.
*/
PopupMenu.prototype._bindAutoClose = function() {
this._eventBus.once(CLOSE_EVENTS, this.close, this);
};
/**
* Remove the auto-closing listener.
*/
PopupMenu.prototype._unbindAutoClose = function() {
this._eventBus.off(CLOSE_EVENTS, this.close, this);
};
// helpers /////////////////////////////
function setTransform(element, transform) {
element.style['transform-origin'] = 'top left';
[ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
element.style[prefix + 'transform'] = transform;
});
}
return entry;
};

@@ -17,2 +17,29 @@ /**

/**
* This method should implement the creation or update of a map of entry objects.
*
* @param {djs.model.Base} element
*
* The following example shows how to replace any entries returned
* by previous providers with one entry which alerts the id of the current selected
* element, when clicking on the entry.
*
* @example
* PopupMenuProvider.getPopupMenuEntries = function(element) {
* return function(entries) {
* return {
* alert: {
* label: 'Alert element ID',
* className: 'alert',
* action: function () {
* alert(element.id);
* }
* }
* };
* };
*/
PopupMenuProvider.getPopupMenuEntries = function(element) {};
/**
* @deprecated
* This method should implement the creation of a list of entry objects.

@@ -37,3 +64,3 @@ *

* return entries;
*}
* };
*/

@@ -76,2 +103,2 @@ PopupMenuProvider.getEntries = function(element) {};

*/
PopupMenuProvider.register = function() {};
PopupMenuProvider.register = function() {};
{
"name": "diagram-js",
"version": "10.0.0",
"version": "11.0.0-aplha.0",
"description": "A toolbox for displaying and modifying diagrams on the web",

@@ -45,6 +45,6 @@ "main": "index.js",

"devDependencies": {
"@babel/core": "^7.18.10",
"babel-loader": "^8.2.5",
"@babel/core": "^7.20.2",
"babel-loader": "^9.1.0",
"babel-plugin-istanbul": "^6.1.1",
"chai": "^4.2.0",
"chai": "^4.3.6",
"eslint": "^8.24.0",

@@ -62,7 +62,7 @@ "eslint-plugin-bpmn-io": "^0.16.0",

"karma-webpack": "^5.0.0",
"mocha": "^9.2.1",
"mocha": "^10.1.0",
"mocha-test-container-support": "^0.2.0",
"npm-run-all": "^4.1.2",
"puppeteer": "^16.1.1",
"sinon": "^7.5.0",
"puppeteer": "^19.0.0",
"sinon": "^9.2.4",
"sinon-chai": "^3.7.0",

@@ -72,2 +72,4 @@ "webpack": "^5.74.0"

"dependencies": {
"@bpmn-io/diagram-js-ui": "^0.2.0",
"clsx": "^1.2.1",
"css.escape": "^1.5.1",

@@ -74,0 +76,0 @@ "didi": "^9.0.0",

Sorry, the diff of this file is not supported yet