UNPKG

diagram-js

Version:

A toolbox for displaying and modifying diagrams on the web

300 lines (242 loc) 6.86 kB
import { pick, assign } from 'min-dash'; import { resizeBounds, ensureConstraints, computeChildrenBBox, getMinResizeBounds } from './ResizeUtil'; import { asTRBL, getMid, roundBounds } from '../../layout/LayoutUtil'; /** * @typedef {import('../../core/Types').ShapeLike} Shape * * @typedef {import('../../util/Types').Direction} Direction * @typedef {import('../../util/Types').Point} Point * * @typedef {import('../../core/EventBus').default} EventBus * @typedef {import('../dragging/Dragging').default} Dragging * @typedef {import('../modeling/Modeling').default} Modeling * @typedef {import('../rules/Rules').default} Rules */ var DEFAULT_MIN_WIDTH = 10; /** * A component that provides resizing of shapes on the canvas. * * The following components are part of shape resize: * * * adding resize handles, * * creating a visual during resize * * checking resize rules * * committing a change once finished * * * ## Customizing * * It's possible to customize the resizing behaviour by intercepting 'resize.start' * and providing the following parameters through the 'context': * * * minDimensions ({ width, height }): minimum shape dimensions * * * childrenBoxPadding ({ left, top, bottom, right } || number): * gap between the minimum bounding box and the container * * f.ex: * * ```javascript * eventBus.on('resize.start', 1500, function(event) { * var context = event.context, * * context.minDimensions = { width: 140, height: 120 }; * * // Passing general padding * context.childrenBoxPadding = 30; * * // Passing padding to a specific side * context.childrenBoxPadding.left = 20; * }); * ``` * * @param {EventBus} eventBus * @param {Rules} rules * @param {Modeling} modeling * @param {Dragging} dragging */ export default function Resize(eventBus, rules, modeling, dragging) { this._dragging = dragging; this._rules = rules; var self = this; /** * Handle resize move by specified delta. * * @param {Object} context * @param {Point} delta */ function handleMove(context, delta) { var shape = context.shape, direction = context.direction, resizeConstraints = context.resizeConstraints, newBounds; context.delta = delta; newBounds = resizeBounds(shape, direction, delta); // ensure constraints during resize context.newBounds = ensureConstraints(newBounds, resizeConstraints); // update + cache executable state context.canExecute = self.canResize(context); } /** * Handle resize start. * * @param {Object} context */ function handleStart(context) { var resizeConstraints = context.resizeConstraints, // evaluate minBounds for backwards compatibility minBounds = context.minBounds; if (resizeConstraints !== undefined) { return; } if (minBounds === undefined) { minBounds = self.computeMinResizeBox(context); } context.resizeConstraints = { min: asTRBL(minBounds) }; } /** * Handle resize end. * * @param {Object} context */ function handleEnd(context) { var shape = context.shape, canExecute = context.canExecute, newBounds = context.newBounds; if (canExecute) { // ensure we have actual pixel values for new bounds // (important when zoom level was > 1 during move) newBounds = roundBounds(newBounds); if (!boundsChanged(shape, newBounds)) { // no resize necessary return; } // perform the actual resize modeling.resizeShape(shape, newBounds); } } eventBus.on('resize.start', function(event) { handleStart(event.context); }); eventBus.on('resize.move', function(event) { var delta = { x: event.dx, y: event.dy }; handleMove(event.context, delta); }); eventBus.on('resize.end', function(event) { handleEnd(event.context); }); } Resize.prototype.canResize = function(context) { var rules = this._rules; var ctx = pick(context, [ 'newBounds', 'shape', 'delta', 'direction' ]); return rules.allowed('shape.resize', ctx); }; /** * Activate a resize operation. * * You may specify additional contextual information and must specify a * resize direction during activation of the resize event. * * @param {MouseEvent|TouchEvent} event * @param {Shape} shape * @param {Object|Direction} contextOrDirection */ Resize.prototype.activate = function(event, shape, contextOrDirection) { var dragging = this._dragging, context, direction; if (typeof contextOrDirection === 'string') { contextOrDirection = { direction: contextOrDirection }; } context = assign({ shape: shape }, contextOrDirection); direction = context.direction; if (!direction) { throw new Error('must provide a direction (n|w|s|e|nw|se|ne|sw)'); } dragging.init(event, getReferencePoint(shape, direction), 'resize', { autoActivate: true, cursor: getCursor(direction), data: { shape: shape, context: context } }); }; Resize.prototype.computeMinResizeBox = function(context) { var shape = context.shape, direction = context.direction, minDimensions, childrenBounds; minDimensions = context.minDimensions || { width: DEFAULT_MIN_WIDTH, height: DEFAULT_MIN_WIDTH }; // get children bounds childrenBounds = computeChildrenBBox(shape, context.childrenBoxPadding); // get correct minimum bounds from given resize direction // basically ensures that the minBounds is max(childrenBounds, minDimensions) return getMinResizeBounds(direction, shape, minDimensions, childrenBounds); }; Resize.$inject = [ 'eventBus', 'rules', 'modeling', 'dragging' ]; // helpers ////////// function boundsChanged(shape, newBounds) { return shape.x !== newBounds.x || shape.y !== newBounds.y || shape.width !== newBounds.width || shape.height !== newBounds.height; } export function getReferencePoint(shape, direction) { var mid = getMid(shape), trbl = asTRBL(shape); var referencePoint = { x: mid.x, y: mid.y }; if (direction.indexOf('n') !== -1) { referencePoint.y = trbl.top; } else if (direction.indexOf('s') !== -1) { referencePoint.y = trbl.bottom; } if (direction.indexOf('e') !== -1) { referencePoint.x = trbl.right; } else if (direction.indexOf('w') !== -1) { referencePoint.x = trbl.left; } return referencePoint; } function getCursor(direction) { var prefix = 'resize-'; if (direction === 'n' || direction === 's') { return prefix + 'ns'; } else if (direction === 'e' || direction === 'w') { return prefix + 'ew'; } else if (direction === 'nw' || direction === 'se') { return prefix + 'nwse'; } else { return prefix + 'nesw'; } }