UNPKG

dragee-widgets

Version:

Widgets Based on Dragee Library

1,407 lines (1,387 loc) 263 kB
var DrageeWidgets = (function (exports) { 'use strict'; function setStyle(element, style) { style = style || {}; let cssText = ''; for (const key in style) { if (style.hasOwnProperty(key)) { cssText += key + ': ' + style[key] + '; '; } } element.style.cssText = cssText; } function appendFirstChild(element, node) { if (element.firstChild) { element.insertBefore(node, element.firstChild); } else { element.appendChild(node); } } function createCanvas(area, rectagle) { const canvas = document.createElement('canvas'); if (window.getComputedStyle(area).position === 'static') { area.style.position = 'relative'; } canvas.setAttribute('width', rectagle.size.x + 'px'); canvas.setAttribute('height', rectagle.size.y + 'px'); setStyle(canvas, { position: 'absolute', left: rectagle.position.y + 'px', top: rectagle.position.y + 'px', width: rectagle.size.x + 'px', height: rectagle.size.y + 'px' }); appendFirstChild(area, canvas); return canvas; } /** Class representing a point. */ class Point { /** * Create a point. * @param {number} x - The x value. * @param {number} y - The y value. */ constructor(x, y) { this.x = x; this.y = y; } add(p) { return new Point(this.x + p.x, this.y + p.y); } sub(p) { return new Point(this.x - p.x, this.y - p.y); } mult(k) { return new Point(this.x * k, this.y * k); } negative() { return new Point(-this.x, -this.y); } compare(p) { return this.x === p.x && this.y === p.y; } clone() { return new Point(this.x, this.y); } toString() { return `{x=${this.x},y=${this.y}`; } static elementOffset(element, parent) { parent = parent || element.parentNode; const elementRect = element.getBoundingClientRect(); const parentRect = parent.getBoundingClientRect(); return new Point(elementRect.left - parentRect.left, elementRect.top - parentRect.top); } static elementSize(element) { const elementRect = element.getBoundingClientRect(); return new Point(elementRect.width, elementRect.height); } } class Rectangle { constructor(position, size) { this.position = position; this.size = size; } getP1() { return this.position; } getP2() { return new Point(this.position.x + this.size.x, this.position.y); } getP3() { return this.position.add(this.size); } getP4() { return new Point(this.position.x, this.position.y + this.size.y); } getCenter() { return this.position.add(this.size.mult(0.5)); } or(rect) { const position = new Point(Math.min(this.position.x, rect.position.x), Math.min(this.position.y, rect.position.y)); const size = new Point(Math.max(this.position.x + this.size.x, rect.position.x + rect.size.x), Math.max(this.position.y + this.size.y, rect.position.y + rect.size.y)).sub(position); return new Rectangle(position, size); } and(rect) { const position = new Point(Math.max(this.position.x, rect.position.x), Math.max(this.position.y, rect.position.y)); const size = new Point(Math.min(this.position.x + this.size.x, rect.position.x + rect.size.x), Math.min(this.position.y + this.size.y, rect.position.y + rect.size.y)).sub(position); if (size.x <= 0 || size.y <= 0) { return null; } return new Rectangle(position, size); } includePoint(p) { return !(this.position.x > p.x || this.position.x + this.size.x < p.x || this.position.y > p.y || this.position.y + this.size.y < p.y); } includeRectangle(rectangle) { return this.includePoint(rectangle.position) && this.includePoint(rectangle.getP3()); } moveToBound(rect, axis) { let selAxis, crossRectangle; if (axis) { selAxis = axis; } else { crossRectangle = this.and(rect); if (!crossRectangle) { return rect; } selAxis = crossRectangle.size.x > crossRectangle.size.y ? 'y' : 'x'; } const thisCenter = this.getCenter(); const rectCenter = rect.getCenter(); const sign = thisCenter[selAxis] > rectCenter[selAxis] ? -1 : 1; const offset = sign > 0 ? this.position[selAxis] + this.size[selAxis] - rect.position[selAxis] : this.position[selAxis] - (rect.position[selAxis] + rect.size[selAxis]); rect.position[selAxis] = rect.position[selAxis] + offset; return rect; } getSquare() { return this.size.x * this.size.y; } styleApply(el) { el = el || document.querySelector('ind'); el.style.left = this.position.x + 'px'; el.style.top = this.position.y + 'px'; el.style.width = this.size.x + 'px'; el.style.height = this.size.y + 'px'; } growth(size) { this.size = this.size.add(size); this.position = this.position.add(size.mult(-0.5)); } getMinSide() { return Math.min(this.size.x, this.size.y); } static fromElement(element) { let parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : element.parentNode; let isConsiderTranslate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; const position = Point.elementOffset(element, parent, isConsiderTranslate); const size = Point.elementSize(element); return new Rectangle(position, size); } } function getDefaultContainer(element) { let container = element.parentNode; while (container.parentNode && window.getComputedStyle(container)['position'] === 'static' && container.tagName !== 'BODY') { container = container.parentNode; } return container; } let EventEmitter$1 = class EventEmitter { constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; this.events = {}; if (options && options.on) { for (const [eventName, fn] of Object.entries(options.on)) { this.on(eventName, fn); } } } emit(eventName) { this.interrupted = false; const args = [].slice.call(arguments, 1); if (!this.events[eventName]) return; for (const func of this.events[eventName]) { func(...args); if (this.interrupted) { return; } } } interrupt() { this.interrupted = true; } on(eventName, fn) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(fn); } prependOn(eventName, fn) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].unshift(fn); } unsubscribe(eventName, fn) { if (this.events[eventName]) { const index = this.events[eventName].indexOf(fn); this.events[eventName].splice(index, 1); } } resetEmitter() { this.events = {}; } resetOn(eventName) { this.events[eventName] = []; } }; function getDistance(p1, p2) { const dx = p1.x - p2.x, dy = p1.y - p2.y; return Math.sqrt(dx * dx + dy * dy); } function transformedSpaceDistanceFactory(options) { return (p1, p2) => { return Math.sqrt(Math.pow(options.x * Math.abs(p1.x - p2.x), 2) + Math.pow(options.y * Math.abs(p1.y - p2.y), 2)); }; } function indexOfNearestPoint(arr, val, radius) { let getDistanceFunc = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : getDistance; let size, index = 0, i, temp; if (arr.length === 0) { return -1; } size = getDistanceFunc(arr[0], val); for (i = 0; i < arr.length; i++) { temp = getDistanceFunc(arr[i], val); if (temp < size) { size = temp; index = i; } } if (radius >= 0 && size > radius) { return -1; } return index; } //Return crossing point of two lines function directCrossing(L1P1, L1P2, L2P1, L2P2) { let temp, k1, k2, b1, b2, x, y; if (L2P1.x === L2P2.x) { temp = L2P1; L2P1 = L1P1; L1P1 = temp; temp = L2P2; L2P2 = L1P2; L1P2 = temp; } if (L1P1.x === L1P2.x) { k2 = (L2P2.y - L2P1.y) / (L2P2.x - L2P1.x); b2 = (L2P2.x * L2P1.y - L2P1.x * L2P2.y) / (L2P2.x - L2P1.x); x = L1P1.x; y = x * k2 + b2; return new Point(x, y); } else { k1 = (L1P2.y - L1P1.y) / (L1P2.x - L1P1.x); b1 = (L1P2.x * L1P1.y - L1P1.x * L1P2.y) / (L1P2.x - L1P1.x); k2 = (L2P2.y - L2P1.y) / (L2P2.x - L2P1.x); b2 = (L2P2.x * L2P1.y - L2P1.x * L2P2.y) / (L2P2.x - L2P1.x); x = (b1 - b2) / (k2 - k1); y = x * k1 + b1; return new Point(x, y); } } function boundToLine(A, B, P) { const AP = new Point(P.x - A.x, P.y - A.y), AB = new Point(B.x - A.x, B.y - A.y), ab2 = AB.x * AB.x + AB.y * AB.y, ap_ab = AP.x * AB.x + AP.y * AB.y, t = ap_ab / ab2; return new Point(A.x + AB.x * t, A.y + AB.y * t); } function getPointOnLineByLenght(LP1, LP2, lenght) { const dx = LP2.x - LP1.x; const dy = LP2.y - LP1.y; const percent = lenght / getDistance(LP1, LP2); return new Point(LP1.x + percent * dx, LP1.y + percent * dy); } function addPointToBoundPoints(boundpoints, point, isRight) { const result = boundpoints.filter(bPoint => { return bPoint.y > point.y || (bPoint.x > point.x); }); for (let i = 0; i < result.length; i++) { if (point.y < result[i].y) { result.splice(i, 0, point); return result; } } result.push(point); return result; } function getAngleDiff(alpha, beta) { const minAngle = Math.min(alpha, beta); const maxAngle = Math.max(alpha, beta); return Math.min(maxAngle - minAngle, minAngle + Math.PI * 2 - maxAngle); } function getAngle$1(p1, p2) { const diff = p2.sub(p1); return normalizeAngle$1(Math.atan2(diff.y, diff.x)); } function boundAngle(min, max, val) { let dmin, dmax; if (min < max && val > min && val < max) { return val; } else if (max < min && (val < max || val > min)) { return val; } else { dmin = getAngleDiff(min, val); dmax = getAngleDiff(max, val); if (dmin < dmax) { return min; } else { return max; } } } function normalizeAngle$1(val) { while (val < 0) { val += 2 * Math.PI; } while (val > 2 * Math.PI) { val -= 2 * Math.PI; } return val; } function getPointFromRadialSystem$1(angle, length, center) { center = center || new Point(0, 0); return center.add(new Point(length * Math.cos(angle), length * Math.sin(angle))); } class Bound { constructor() {} bound(point, _size) { return point; } refresh() {} static bounding() { const instance = new this(...arguments); return instance.bound.bind(instance); } } class BoundToRectangle extends Bound { constructor(rectangle) { super(); this.rectangle = rectangle; } bound(point, size) { const calcPoint = point.clone(); const rectP2 = this.rectangle.getP3(); if (this.rectangle.position.x > calcPoint.x) { calcPoint.x = this.rectangle.position.x; } if (this.rectangle.position.y > calcPoint.y) { calcPoint.y = this.rectangle.position.y; } if (rectP2.x < calcPoint.x + size.x) { calcPoint.x = rectP2.x - size.x; } if (rectP2.y < calcPoint.y + size.y) { calcPoint.y = rectP2.y - size.y; } return calcPoint; } } class BoundToElement extends BoundToRectangle { constructor(element, container) { super(Rectangle.fromElement(element, container)); this.element = element; this.container = container; } refresh() { this.rectangle = Rectangle.fromElement(this.element, this.container); } } class BoundToLine extends Bound { constructor(startPoint, endPoint) { super(); this.startPoint = startPoint; this.endPoint = endPoint; const alpha = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x); const beta = alpha + Math.PI / 2; this.someK = 10; this.cosBeta = Math.cos(beta); this.sinBeta = Math.sin(beta); } bound(point, size) { const point2 = new Point(point.x + this.someK * this.cosBeta, point.y + this.someK * this.sinBeta); const newEndPoint = getPointOnLineByLenght(this.endPoint, this.startPoint, size.x); const pointCrossing = directCrossing(this.startPoint, this.endPoint, point, point2); return boundToLine(this.startPoint, newEndPoint, pointCrossing); } } class BoundToCircle extends Bound { constructor(center, radius) { super(); this.center = center; this.radius = radius; } bound(point, _size) { return getPointOnLineByLenght(this.center, point, this.radius); } } class BoundToArc extends BoundToCircle { constructor(center, radius, startAngle, endAngle) { super(center, radius); this._startAngle = startAngle; this._endAngle = endAngle; } startAngle() { return typeof this._startAngle === 'function' ? this._startAngle() : this._startAngle; } endAngle() { return typeof this._endAngle === 'function' ? this._endAngle() : this._endAngle; } bound(point, _size) { let angle = getAngle$1(this.center, point); angle = normalizeAngle$1(angle); angle = boundAngle(this.startAngle(), this.endAngle(), angle); return getPointFromRadialSystem$1(angle, this.radius, this.center); } } function removeItem (array, val) { for (let i = 0; i < array.length; i++) { if (array[i] === val) { array.splice(i, 1); i--; } } return array; } function range$1(start, stop, step) { const result = []; if (typeof stop === 'undefined') { stop = start; start = 0; } if (typeof step === 'undefined') { step = 1; } if (step > 0 && start >= stop || step < 0 && start <= stop) { return []; } for (let i = start; step > 0 ? i < stop : i > stop; i += step) { result.push(i); } return result; } class BasicStrategy { constructor(rectangle) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; this.rectangle = rectangle; this.options = options; } get boundRect() { return typeof this.rectangle === 'function' ? this.rectangle() : this.rectangle; } } class FloatLeftStrategy extends BasicStrategy { constructor(rectangle) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; super(rectangle, options); this.options = Object.assign({ removable: true }, options); this.radius = options.radius || 80; this.paddingTopLeft = options.paddingTopLeft || new Point(0, 0); this.paddingBottomRight = options.paddingBottomRight || new Point(0, 0); this.yGapBetweenDraggables = options.yGapBetweenDraggables || 0; this.getDistance = options.getDistance || getDistance; this.getPosition = options.getPosition || (draggable => draggable.position); } positioning(rectangleList, _indexesOfNews) { const boundRect = this.boundRect; const rectP2 = boundRect.getP2(); let boundaryPoints = [boundRect.position]; rectangleList.forEach((rect, rectIndex) => { let position, isValid = false; for (let i = 0; i < boundaryPoints.length; i++) { position = new Point(boundaryPoints[i].x + this.paddingTopLeft.x, i > 0 ? boundaryPoints[i - 1].y + this.yGapBetweenDraggables : boundRect.position.y + this.paddingTopLeft.y); isValid = position.x + rect.size.x < rectP2.x; if (isValid) { break; } } if (!isValid) { position = new Point(boundRect.position.x + this.paddingTopLeft.x, boundaryPoints[boundaryPoints.length - 1].y + (rectIndex > 0 ? this.yGapBetweenDraggables : this.paddingTopLeft.y)); } rect.position = position; if (this.options.removable && rect.getP3().y > boundRect.getP3().y) { rect.removable = true; } boundaryPoints = addPointToBoundPoints(boundaryPoints, rect.getP3().add(this.paddingBottomRight)); }); return rectangleList; } sorting(odlDraggablesList, newDraggables, indexOfNews) { const newList = odlDraggablesList.concat(); const listOldPosition = odlDraggablesList.map(draggable => draggable.getPosition()); newDraggables.forEach(newDraggable => { let index = indexOfNearestPoint(listOldPosition, this.getPosition(newDraggable), this.radius, this.getDistance); if (index === -1) { index = newList.length; } else { index = newList.indexOf(odlDraggablesList[index]); } newList.splice(index, 0, newDraggable); }); newDraggables.forEach(newDraggable => { indexOfNews.push(newList.indexOf(newDraggable)); }); return newList; } } const addToDefaultScope$1 = function (target) { defaultScope.addTarget(target); }; class Target extends EventEmitter$1 { constructor(element, draggables) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; super(options); const target = this; this.options = Object.assign({ timeEnd: 200, timeExcange: 400 }, options); this.positioningStrategy = options.strategy || new FloatLeftStrategy(this.getRectangle.bind(this), { radius: 80, getDistance: transformedSpaceDistanceFactory({ x: 1, y: 4 }), removable: true }); this.element = element; draggables.forEach(draggable => draggable.targets.push(target)); this.draggables = draggables; Target.emitter.emit('target:create', this); this.startBounding(); this.init(); } startBounding() { this.bound = this.options.bound || BoundToElement.bounding(this.element); } positioning(draggables, indexesOfNew) { return this.positioningStrategy.positioning(draggables, indexesOfNew); } sorting(oldDraggables, newDraggables, indexOfNews) { return this.positioningStrategy.sorting(oldDraggables, newDraggables, indexOfNews); } init() { let rectangles, indexesOfNew; this.innerDraggables = this.draggables.filter(draggable => { let element = draggable.element.parentNode; while (element) { if (element === this.element) { return true; } element = element.parentNode; } return false; }); if (this.innerDraggables.length) { indexesOfNew = range$1(this.innerDraggables.length); rectangles = this.positioning(this.innerDraggables.map(draggable => { return draggable.getRectangle(); }), indexesOfNew); this.setPosition(rectangles, indexesOfNew); this.innerDraggables.forEach(draggable => this.emit('target:add', draggable)); } } getRectangle() { return Rectangle.fromElement(this.element, this.container, true); } catchDraggable(draggable) { if (this.options.catchDraggable) { return this.options.catchDraggable(this, draggable); } else { const targetRectangle = this.getRectangle(); const draggableSquare = draggable.getRectangle().getSquare(); return draggableSquare < targetRectangle.getSquare() && targetRectangle.includePoint(draggable.getCenter()); } } getPosition() { return this.getRectangle().position; } getSize() { return this.getRectangle().size; } destroy() { scopes.forEach(scope => removeItem(scope.targets, this)); } refresh() { const rectangles = this.positioning(this.innerDraggables.map(draggable => { return draggable.getRectangle(); }), []); this.setPosition(rectangles, [], 0); } onEnd(draggable) { const newDraggablesIndex = []; if (this.getRectangle().includePoint(draggable.getCenter())) { draggable.position = this.bound(draggable.position, draggable.getSize()); } else { return false; } this.emit('target:beforeAdd', draggable); this.innerDraggables = this.sorting(this.innerDraggables, [draggable], newDraggablesIndex); const rectangles = this.positioning(this.innerDraggables.map(draggable => { return draggable.getRectangle(); }), newDraggablesIndex); this.setPosition(rectangles, newDraggablesIndex); if (this.innerDraggables.indexOf(draggable) !== -1) { this.addRemoveOnMove(draggable); } return true; } setPosition(rectangles, indexesOfNew, time) { this.innerDraggables.slice(0).forEach((draggable, i) => { const rect = rectangles[i], timeEnd = time || time === 0 ? time : indexesOfNew.indexOf(i) !== -1 ? this.options.timeEnd : this.options.timeExcange; if (rect.removable) { draggable.move(draggable.initialPosition, timeEnd, true, true); removeItem(this.innerDraggables, draggable); this.emit('target:remove', draggable); } else { draggable.move(rect.position, timeEnd, true, true); } }); } add(draggable, time) { const newDraggablesIndex = this.innerDraggables.length; this.emit('target:beforeAdd', draggable); this.pushInnerDraggable(draggable); const rectangles = this.positioning(this.innerDraggables.map(draggable => { return draggable.getRectangle(); }), newDraggablesIndex, draggable); this.setPosition(rectangles, [newDraggablesIndex], time || 0); if (this.innerDraggables.indexOf(draggable) !== -1) { this.addRemoveOnMove(draggable); } } pushInnerDraggable(draggable) { if (this.innerDraggables.indexOf(draggable) === -1) { this.innerDraggables.push(draggable); } } addRemoveOnMove(draggable) { draggable.on('drag:move', this.removeHandler = () => { this.remove(draggable); }); this.emit('target:add', draggable); } remove(draggable) { draggable.unsubscribe('drag:move', this.removeHandler); const index = this.innerDraggables.indexOf(draggable); if (index === -1) { return; } this.innerDraggables.splice(index, 1); const rectangles = this.positioning(this.innerDraggables.map(draggable => { return draggable.getRectangle(); }), []); this.setPosition(rectangles, []); this.emit('target:remove', draggable); } reset() { this.innerDraggables.forEach(draggable => { draggable.move(draggable.initialPosition, 0, true, true); this.emit('target:remove', draggable); }); this.innerDraggables = []; } getSortedDraggables() { this.innerDraggables.slice(); } get container() { return this._container = this._container || this.options.container || this.options.parent || getDefaultContainer(this.element); } } Target.emitter = new EventEmitter$1(); Target.emitter.on('target:create', addToDefaultScope$1); const scopes = []; class Scope extends EventEmitter$1 { constructor(draggables, targets) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; super(options); scopes.forEach(scope => { if (draggables) { draggables.forEach(draggable => { removeItem(scope.draggables, draggable); }); } if (targets) { targets.forEach(target => { removeItem(scope.targets, target); }); } }); this.draggables = draggables || []; this.targets = targets || []; scopes.push(this); this.options = { timeEnd: options.timeEnd || 400 }; this.init(); } init() { this.draggables.forEach(draggable => { draggable.dragEndAction = () => this.onEnd(draggable); }); } addDraggable(draggable) { this.draggables.push(draggable); draggable.dragEndAction = () => this.onEnd(draggable); } addTarget(target) { this.targets.push(target); } onEnd(draggable) { const shotTargets = this.targets.filter(target => { return target.draggables.indexOf(draggable) !== -1; }).filter(target => { return target.catchDraggable(draggable); }).sort((a, b) => { return a.getRectangle().getSquare() - b.getRectangle().getSquare(); }); if (shotTargets.length) { shotTargets[0].onEnd(draggable); } else if (draggable.targets.length) { draggable.pinPosition(draggable.initialPosition, this.options.timeEnd); } this.emit('scope:change'); } reset() { this.targets.forEach(target => target.reset()); } refresh() { this.draggables.forEach(draggable => draggable.refresh()); this.targets.forEach(target => target.refresh()); } get positions() { return this.targets.map(target => { return target.innerDraggables.map(draggable => this.draggables.indexOf(draggable)); }); } set positions(positions) { const message = 'wrong array length'; if (positions.length === this.targets.length) { this.targets.forEach(target => target.reset()); positions.forEach((targetIndexes, i) => { targetIndexes.forEach(index => { this.targets[i].add(this.draggables[index]); }); }); } else { throw message; } } } const defaultScope = new Scope(); function throttle(func, wait) { let lastTime = 0; return function executedFunction() { const context = this; const args = arguments; const now = Date.now(); if (now - lastTime >= wait) { func.apply(context, args); lastTime = now; } }; } function getParentsChain(childElement, rootElement) { const chain = []; let element = childElement; while (element.parentNode && element !== rootElement) { chain.unshift(element.parentNode); element = element.parentNode; } return chain; } const throttledDragOver = (callback, duration) => { const throttledCallback = throttle(event => callback(event), duration); return event => { event.preventDefault(); throttledCallback(event); }; }; const passiveFalse = { passive: false }; const isTouch = navigator.maxTouchPoints > 0; const mouseEvents = { start: 'mousedown', move: 'mousemove', end: 'mouseup' }; const touchEvents = { start: 'touchstart', move: 'touchmove', end: 'touchend' }; const draggables = []; const transformProperty = 'transform'; const transitionProperty = 'transition'; function getTouchByID(element, touchId) { for (let i = 0; i < element.changedTouches.length; i++) { if (element.changedTouches[i].identifier === touchId) { return element.changedTouches[i]; } } return false; } function preventDoubleInit(draggable) { const message = "for this element Dragee.Draggable is already exist, don't create it twice "; if (draggables.some(existing => draggable.element === existing.element)) { throw message; } draggables.push(draggable); } function addToDefaultScope(draggable) { defaultScope.addDraggable(draggable); } function copyStyles(source, destination) { const cs = window.getComputedStyle(source); for (let i = 0; i < cs.length; i++) { const key = cs[i]; if (key.indexOf('transition') < 0 && key.indexOf('transform') < 0) { destination.style[key] = cs[key]; } } for (let i = 0; i < source.children.length; i++) { copyStyles(source.children[i], destination.children[i]); } } class Draggable extends EventEmitter$1 { constructor(element) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; super(options); this.targets = []; this.options = options; this.element = element; preventDoubleInit(this); Draggable.emitter.emit('draggable:create', this); this._enable = true; this.startBounding(); this.startPositioning(); this.startListening(); } startBounding() { if (this.options.bound) { this.bounding = { bound: this.options.bound }; } else { this.bounding = new BoundToElement(this.container, this.container); } } startPositioning() { this._setDefaultTransition(); this.offset = Point.elementOffset(this.element, this.container); this.pinnedPosition = this.offset; this.position = this.offset; this.initialPosition = this.options.position || this.offset; this.pinPosition(this.initialPosition); if (this.bounding.refresh) { this.bounding.refresh(); } } startListening() { this._dragStart = event => this.dragStart(event); this._dragMove = event => this.dragMove(event); this._dragEnd = event => this.dragEnd(event); this._nativeDragStart = event => this.nativeDragStart(event); this._nativeDragOver = throttledDragOver(event => this.nativeDragOver(event), this.dragOverThrottleDuration); this._nativeDragEnd = event => this.nativeDragEnd(event); this._nativeDrop = event => this.nativeDrop(event); this._scroll = event => this.onScroll(event); this.handler.addEventListener(touchEvents.start, this._dragStart, passiveFalse); this.handler.addEventListener(mouseEvents.start, this._dragStart, passiveFalse); } getSize() { return Point.elementSize(this.element); } getPosition() { this.position = this.offset.add(this._transformPosition || new Point(0, 0)); return this.position; } getCenter() { return this.position.add(this.getSize().mult(0.5)); } _setDefaultTransition() { if (!this.element.style[transitionProperty]) { this.element.style[transitionProperty] = window.getComputedStyle(this.element)[transitionProperty]; } } _setTransition(time) { let transition = this.element.style[transitionProperty]; const transitionCss = `transform ${time}ms`; if (!/transform\s?\d*m?s?/.test(transition)) { if (transition) { transition += `, ${transitionCss}`; } else { transition = transitionCss; } } else { transition = transition.replace(/transform\s?\d*m?s?/g, transitionCss); } if (this.element.style[transitionProperty] !== transition) { this.element.style[transitionProperty] = transition; } } _setTranslate(point) { this._transformPosition = point; const translateCss = `translate3d(${point.x}px, ${point.y}px, 0px)`; let transform = this.element.style[transformProperty]; if (this.shouldRemoveZeroTranslate && point.x === 0 && point.y === 0) { transform = transform.replace(/translate3d\([^)]+\)/, ''); } else if (!/translate3d\([^)]+\)/.test(transform)) { if (transform) { transform += ' '; } transform += translateCss; } else { transform = transform.replace(/translate3d\([^)]+\)/, translateCss); } if (this.element.style[transformProperty] !== transform) { this.element.style[transformProperty] = transform; } } move(point) { let time = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; let isSilent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; point = point.clone(); this.position = point; this._setTransition(time); this._setTranslate(point.sub(this.offset)); if (!isSilent) { this.emit('drag:move'); } } pinPosition(point) { let time = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; let silent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; this.pinnedPosition = point.clone(); this.move(this.pinnedPosition, time, silent); } resetPositionToInitial() { this.pinPosition(this.initialPosition); } refreshPosition() { this.setPosition(this.getPosition()); } setPosition(point) { point = point.clone(); this.position = point; this._setTransition(0); this._setTranslate(point.sub(this.offset)); } determineDirection(point) { this._previousDirectionPosition ||= this._startPosition; this.leftDirection = this._previousDirectionPosition.x < point.x; this.rightDirection = this._previousDirectionPosition.x > point.x; this.upDirection = this._previousDirectionPosition.y > point.y; this.downDirection = this._previousDirectionPosition.y < point.y; this._previousDirectionPosition = point; } seemsScrolling() { return +new Date() - this._startTouchTimestamp < this.touchDraggingThreshold; } shouldUseNativeDragAndDrop() { if (this.isTouchEvent) { return this.nativeDragAndDrop && this.emulateNativeDragAndDropOnTouch; } else { return this.nativeDragAndDrop; } } dragStart(event) { if (!this._enable) { return; } if (this.stopPropagationOnDragStart) { event.stopPropagation(); } this.isTouchEvent = isTouch && event instanceof window.TouchEvent; this.touchPoint = this._startTouchPoint = new Point(this.isTouchEvent ? event.changedTouches[0].pageX : event.clientX, this.isTouchEvent ? event.changedTouches[0].pageY : event.clientY); this._startPosition = this.getPosition(); if (this.isTouchEvent) { this._touchId = event.changedTouches[0].identifier; this._startTouchTimestamp = +new Date(); } this._startWindowScrollPoint = this.windowScrollPoint; this._startScrollElementsOffset = this.scrollElementsOffset; if (event.target instanceof window.HTMLInputElement || event.target instanceof window.HTMLInputElement) { event.target.focus(); } if (this.shouldUseNativeDragAndDrop()) { if (this.isTouchEvent && this.emulateNativeDragAndDropOnTouch) { this._startParentsScrollOffset = this.parentsScrollOffset; const emulateOnFirstMove = event => { if (this.seemsScrolling()) { this.cancelDragging(); } else { this.emulateNativeDragAndDrop(event); } cancelEmulation(); }; const cancelEmulation = () => { document.removeEventListener(touchEvents.move, emulateOnFirstMove); document.removeEventListener(touchEvents.end, cancelEmulation); }; document.addEventListener(touchEvents.move, emulateOnFirstMove, passiveFalse); document.addEventListener(touchEvents.end, cancelEmulation, passiveFalse); } else { this.element.addEventListener('dragstart', this._nativeDragStart); this.element.draggable = true; document.addEventListener(mouseEvents.end, this._nativeDragEnd, passiveFalse); } } else { document.addEventListener(touchEvents.move, this._dragMove, passiveFalse); document.addEventListener(mouseEvents.move, this._dragMove, passiveFalse); document.addEventListener(touchEvents.end, this._dragEnd, passiveFalse); document.addEventListener(mouseEvents.end, this._dragEnd, passiveFalse); } window.addEventListener('scroll', this._scroll); this.scrollElements.forEach(p => p.addEventListener('scroll', this._scroll)); this.emit('drag:start'); } dragMove(event) { let touch; this.isDragging = true; this.isTouchEvent = isTouch && event instanceof window.TouchEvent; if (this.isTouchEvent) { touch = getTouchByID(event, this._touchId); if (!touch) { return; } if (this.seemsScrolling()) { this.cancelDragging(); return; } } event.stopPropagation(); event.preventDefault(); this.touchPoint = new Point(this.isTouchEvent ? touch.pageX : event.clientX, this.isTouchEvent ? touch.pageY : event.clientY); let point = this._startPosition.add(this.touchPoint.sub(this._startTouchPoint)).add(this.windowScrollPoint.sub(this._startWindowScrollPoint)).add(this.scrollElementsOffset.sub(this._startScrollElementsOffset)); point = this.bounding.bound(point, this.getSize()); this.determineDirection(point); this.move(point); this.element.classList.add('dragee-active'); } dragEnd(event) { this.isTouchEvent = isTouch && event instanceof window.TouchEvent; if (this.isTouchEvent && !getTouchByID(event, this._touchId)) { return; } if (this.isDragging) { event.stopPropagation(); event.preventDefault(); } this.dragEndAction(); this.emit('drag:end'); this.cancelDragging(); setTimeout(() => this.element.classList.remove('dragee-active')); } onScroll(_event) { let point = this._startPosition.add(this.touchPoint.sub(this._startTouchPoint)).add(this.windowScrollPoint.sub(this._startWindowScrollPoint)).add(this.scrollElementsOffset.sub(this._startScrollElementsOffset)); point = this.bounding.bound(point, this.getSize()); if (!this.nativeDragAndDrop) { this.determineDirection(point); this.move(point); } } nativeDragStart(event) { event.stopPropagation(); event.dataTransfer.setData('text', 'FireFox fix'); event.dataTransfer.effectAllowed = 'move'; document.addEventListener('dragover', this._nativeDragOver); document.addEventListener('dragend', this._nativeDragEnd); document.addEventListener('drop', this._nativeDrop); } nativeDragOver(event) { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; this.element.classList.add('dragee-placeholder'); if (event.clientX === 0 && event.clientY === 0) { return; } this.touchPoint = new Point(event.clientX, event.clientY); let point = this._startPosition.add(this.touchPoint.sub(this._startTouchPoint)).add(this.windowScrollPoint.sub(this._startWindowScrollPoint)).add(this.scrollElementsOffset.sub(this._startScrollElementsOffset)); point = this.bounding.bound(point, this.getSize()); this.determineDirection(point); this.position = point; this.emit('drag:move'); } nativeDragEnd(_event) { this.element.classList.remove('dragee-placeholder'); this.dragEndAction(); this.emit('drag:end'); document.removeEventListener('dragover', this._nativeDragOver); document.removeEventListener('dragend', this._nativeDragEnd); document.removeEventListener(mouseEvents.end, this._nativeDragEnd); document.removeEventListener('drop', this._nativeDrop); window.removeEventListener('scroll', this._scroll); this.scrollElements.forEach(p => p.removeEventListener('scroll', this._scroll)); this.isDragging = false; this.element.removeAttribute('draggable'); this.element.removeEventListener('dragstart', this._nativeDragStart); this.element.classList.remove('dragee-active'); } nativeDrop(event) { event.stopPropagation(); event.preventDefault(); } cancelDragging() { document.removeEventListener(touchEvents.move, this._dragMove); document.removeEventListener(mouseEvents.move, this._dragMove); document.removeEventListener(touchEvents.end, this._dragEnd); document.removeEventListener(mouseEvents.end, this._dragEnd); document.removeEventListener(mouseEvents.end, this._nativeDragEnd); window.removeEventListener('scroll', this._scroll); this.scrollElements.forEach(p => p.removeEventListener('scroll', this._scroll)); this.isDragging = false; this._previousDirectionPosition = null; this.element.removeAttribute('draggable'); this.element.removeEventListener('dragstart', this._nativeDragStart); } copyStyles(source, destination) { if (this.options.copyStyles) { this.options.copyStyles(source, destination); } else { copyStyles(source, destination); } } emulateNativeDragAndDrop(event) { const containerRect = this.container.getBoundingClientRect(); const clonedElement = this.element.cloneNode(true); clonedElement.style[transformProperty] = ''; this.copyStyles(this.element, clonedElement); clonedElement.classList.add('dragee-native-emulation'); clonedElement.style.position = 'absolute'; document.body.appendChild(clonedElement); this.element.classList.add('dragee-placeholder'); const emulationDraggable = new Draggable(clonedElement, { container: document.body, touchDraggingThreshold: 0, bound(point) { return point; }, on: { 'drag:move': () => { const containerRectPoint = new Point(containerRect.left, containerRect.top); this.position = emulationDraggable.position.sub(containerRectPoint).sub(this._startWindowScrollPoint).add(this._startParentsScrollOffset); this.determineDirection(this.position); this.emit('drag:move'); }, 'drag:end': () => { emulationDraggable.destroy(); document.body.removeChild(clonedElement); this.element.classList.remove('dragee-placeholder'); this.element.classList.remove('dragee-active'); this.emit('drag:end'); this.dragEndAction(); this.cancelDragging(); } } }); const containerRectPoint = new Point(containerRect.left, containerRect.top); emulationDraggable._startWindowScrollPoint = this._startWindowScrollPoint; emulationDraggable.move(this.pinnedPosition.add(containerRectPoint).add(this.windowScrollPoint).sub(this.parentsScrollOffset)); emulationDraggable.dragStart(event); event.preventDefault(); } dragEndAction() { this.pinPosition(this.position); } getRectangle() { return new Rectangle(this.position, this.getSize()); } refresh() { if (this.bounding.refresh) { this.bounding.refresh(); } } destroy() { this.handler.removeEventListener(touchEvents.start, this._dragStart); this.handler.removeEventListener(mouseEvents.start, this._dragStart); this.element.removeEventListener('dragstart', this._nativeDragStart); document.removeEventListener(touchEvents.move, this._dragMove); document.removeEventListener(mouseEvents.move, this._dragMove); document.removeEventListener(touchEvents.end, this._dragEnd); document.removeEventListener(mouseEvents.end, this._dragEnd); document.removeEventListener('dragover', this._nativeDragOver); document.removeEventListener('dragend', this._nativeDragEnd); document.removeEventListener(mouseEvents.end, this._nativeDragEnd); document.removeEventListener('drop', this._nativeDrop); this.resetEmitter(); const index = draggables.indexOf(this); if (index > -1) { draggables.splice(index, 1); } } get container() { return this._container = this._container || this.options.container || this.options.parent || getDefaultContainer(this.element); } get handler() { if (!this._handler) { if (typeof this.options.handler === 'string') { this._handler = this.element.querySelector(this.options.handler) || this.element; } else { this._handler = this.options.handler || this.element; } } return this._handler; } get stopPropagationOnDragStart() { return this.options.stopPropagationOnDragStart || false; } get nativeDragAndDrop() { return this.options.nativeDragAndDrop || false; } get emulateNativeDragAndDropOnTouch() { return this.options.emulateNativeDragAndDropOnTouch || false; } get shouldRemoveZeroTranslate() { return this.options.shouldRemoveZeroTranslate || false; } get touchDraggingThreshold() { return this.options.touchDraggingThreshold || 0; } get dragOverThrottleDuration() { return this.options.dragOverThrottleDuration || 16; } get windowScrollPoint() { return new Point(window.scrollX, window.scrollY); } get scrollRootContainer() { return this.options.scrollRootContainer || this.container; } get scrollElements() { return this._cachedScrollElements ? this._cachedScrollElements : this._cachedScrollElements = getParentsChain(this.element, this.scrollRootContainer); } get scrollElementsOffset() { return new Point(this.scrollElements.reduce((sum, p) => sum + p.scrollLeft, 0), this.scrollElements.reduce((sum, p) => sum + p.scrollTop, 0)); } get parents() { return this._cachedParents ? this._cachedParents : this._cachedParents = getParentsChain(this.element, this.container); } get parentsScrollOffset() { return new Point(this.parents.reduce((sum, p) => sum + p.scrollLeft, 0), this.parents.reduce((sum, p) => sum + p.scrollTop, 0)); } get enable() { return this._enable; } set enable(enable) { if (enable) { this.element.classList.remove('dragee-disable'); } else { this.element.classList.add('dragee-disable'); } this._enable = enable; } } Draggable.emitter = new EventEmitter$1(); Draggable.emitter.on('draggable:create', addToDefaultScope); function getAngle(p1, p2) { const diff = p2.sub(p1); return normalizeAngle(Math.atan2(diff.y, diff.x)); } function toRadian(angle) { return angle % 360 * Math.PI / 180; } function normalizeAngle(val) { while (val < 0) { val += 2 * Math.PI; } while (val > 2 * Math.PI) { val -= 2 * Math.PI; } return val; } function getPointFromRadialSystem(angle, length, center) { center = center || new Point(0, 0); return center.add(new Point(length * Math.cos(angle), length * Math.sin(angle))); } class Spider { constructor(area, elements) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; const areaRectangle = Rectangle.fromElement(area, area); this.options = Object.assign({ angle: 0, dAngle: 2 * Math.PI / elements.length, center: areaRectangle.getCenter(), startRadius: 50, endRadius: areaRectangle.getMinSide() / 2, lineWidth: 2, strokeStyle: '#ff5577', fillStyle: 'rgba(150,255,50,0.8)' }, options); this.area = area; this.areaRectangle = areaRectangle; this.init(elements); } init(elements) { this.canvas = createCanvas(this.area, this.areaRectangle); this.context = this.canvas.getContext('2d'); this.draggables = elements.map((element, i) => { const angle = this.options.angle + i * this.options.dAngle; const halfSize = Point.elementSize(element).mult(0.5); const start = getPointFromRadialSystem(angle, this.options.startRadius, this.options.center).sub(halfSize); const end = getPointFromRadialSystem(angle, this.options.endRadius, this.options.center).sub(halfSize); return new Draggable(element, { container: this.area, bound: BoundToLine.bounding(start, end), position: start, on: { 'drag:move': () => this.draw() } }); }); this.isInit = true; this.draw(); } draw() { if (!this.isInit) { return; } this.context.clearRect(0, 0, this.areaRectangle.size.x, this.areaRectangle.size.y); this.context.beginPath(); let point = this.draggables[0].getCenter(); this.context.moveTo(point.x, point.y); for (let i = 0; i < this.draggables.length; i++) { point = this.draggables[i].getCenter(); this.context.lineTo(point.x, point.y); } this.context.closePath(); this.context.lineWidth = this.options.lineWidth; this.context.strokeStyle = this.options.strokeStyle; this.context.stroke(); this.context.fillStyle = this.options.fillStyle; this.context.fill(); } } class EventEmitter { constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; this.events = {}; if (options && options.on) { for (const [eventName, fn] of Object.entries(options.on)) { this.on(eventName, fn); } } } emit(eventName) { this.interrupted = false; const args = [].slice.call(arguments, 1); if (!this.events[eventName]) return; for (const func of this.events[eventName]) { func.apply(...args); if (this.interrupted) { return; } } } interrupt() { this.interrupted = true; } on(eventName, fn) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(fn); } prependOn(eventName, fn) { if (!this.events[eventName]) { this.event