angular2-draggable
Version:
<!-- Badges section here. --> [][npm-badge-url] [][npm-badge-url] [ • 49.7 kB
JavaScript
import { fromEvent } from 'rxjs';
import { Directive, ElementRef, Renderer2, Input, Output, HostListener, EventEmitter, NgModule } from '@angular/core';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class Position {
/**
* @param {?} x
* @param {?} y
*/
constructor(x, y) {
this.x = x;
this.y = y;
}
/**
* @param {?} e
* @param {?=} el
* @return {?}
*/
static fromEvent(e, el = null) {
/**
* Fix issue: Resize doesn't work on Windows10 IE11 (and on some windows 7 IE11)
* https://github.com/xieziyu/angular2-draggable/issues/164
* e instanceof MouseEvent check returns false on IE11
*/
if (this.isMouseEvent(e)) {
return new Position(e.clientX, e.clientY);
}
else {
if (el === null || e.changedTouches.length === 1) {
return new Position(e.changedTouches[0].clientX, e.changedTouches[0].clientY);
}
/**
* Fix issue: Multiple phone draggables at the same time
* https://github.com/xieziyu/angular2-draggable/issues/128
*/
for (let i = 0; i < e.changedTouches.length; i++) {
if (e.changedTouches[i].target === el) {
return new Position(e.changedTouches[i].clientX, e.changedTouches[i].clientY);
}
}
}
}
/**
* @param {?} e
* @return {?}
*/
static isMouseEvent(e) {
return Object.prototype.toString.apply(e).indexOf('MouseEvent') === 8;
}
/**
* @param {?} obj
* @return {?}
*/
static isIPosition(obj) {
return !!obj && ('x' in obj) && ('y' in obj);
}
/**
* @param {?} el
* @return {?}
*/
static getCurrent(el) {
/** @type {?} */
let pos = new Position(0, 0);
if (window) {
/** @type {?} */
const computed = window.getComputedStyle(el);
if (computed) {
/** @type {?} */
let x = parseInt(computed.getPropertyValue('left'), 10);
/** @type {?} */
let y = parseInt(computed.getPropertyValue('top'), 10);
pos.x = isNaN(x) ? 0 : x;
pos.y = isNaN(y) ? 0 : y;
}
return pos;
}
else {
console.error('Not Supported!');
return null;
}
}
/**
* @param {?} p
* @return {?}
*/
static copy(p) {
return new Position(0, 0).set(p);
}
/**
* @return {?}
*/
get value() {
return { x: this.x, y: this.y };
}
/**
* @template THIS
* @this {THIS}
* @param {?} p
* @return {THIS}
*/
add(p) {
(/** @type {?} */ (this)).x += p.x;
(/** @type {?} */ (this)).y += p.y;
return (/** @type {?} */ (this));
}
/**
* @template THIS
* @this {THIS}
* @param {?} p
* @return {THIS}
*/
subtract(p) {
(/** @type {?} */ (this)).x -= p.x;
(/** @type {?} */ (this)).y -= p.y;
return (/** @type {?} */ (this));
}
/**
* @param {?} n
* @return {?}
*/
multiply(n) {
this.x *= n;
this.y *= n;
}
/**
* @param {?} n
* @return {?}
*/
divide(n) {
this.x /= n;
this.y /= n;
}
/**
* @template THIS
* @this {THIS}
* @return {THIS}
*/
reset() {
(/** @type {?} */ (this)).x = 0;
(/** @type {?} */ (this)).y = 0;
return (/** @type {?} */ (this));
}
/**
* @template THIS
* @this {THIS}
* @param {?} p
* @return {THIS}
*/
set(p) {
(/** @type {?} */ (this)).x = p.x;
(/** @type {?} */ (this)).y = p.y;
return (/** @type {?} */ (this));
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class HelperBlock {
/**
* @param {?} parent
* @param {?} renderer
*/
constructor(parent, renderer) {
this.parent = parent;
this.renderer = renderer;
this._added = false;
// generate helper div
/** @type {?} */
let helper = renderer.createElement('div');
renderer.setStyle(helper, 'position', 'absolute');
renderer.setStyle(helper, 'width', '100%');
renderer.setStyle(helper, 'height', '100%');
renderer.setStyle(helper, 'background-color', 'transparent');
renderer.setStyle(helper, 'top', '0');
renderer.setStyle(helper, 'left', '0');
// done
this._helper = helper;
}
/**
* @return {?}
*/
add() {
// append div to parent
if (this.parent && !this._added) {
this.parent.appendChild(this._helper);
this._added = true;
}
}
/**
* @return {?}
*/
remove() {
if (this.parent && this._added) {
this.parent.removeChild(this._helper);
this._added = false;
}
}
/**
* @return {?}
*/
dispose() {
this._helper = null;
this._added = false;
}
/**
* @return {?}
*/
get el() {
return this._helper;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class AngularDraggableDirective {
/**
* @param {?} el
* @param {?} renderer
*/
constructor(el, renderer) {
this.el = el;
this.renderer = renderer;
this.allowDrag = true;
this.moving = false;
this.orignal = null;
this.oldTrans = new Position(0, 0);
this.tempTrans = new Position(0, 0);
this.currTrans = new Position(0, 0);
this.oldZIndex = '';
this._zIndex = '';
this.needTransform = false;
this.draggingSub = null;
/**
* Bugfix: iFrames, and context unrelated elements block all events, and are unusable
* https://github.com/xieziyu/angular2-draggable/issues/84
*/
this._helperBlock = null;
this.started = new EventEmitter();
this.stopped = new EventEmitter();
this.edge = new EventEmitter();
/**
* List of allowed out of bounds edges *
*/
this.outOfBounds = {
top: false,
right: false,
bottom: false,
left: false
};
/**
* Round the position to nearest grid
*/
this.gridSize = 1;
/**
* Whether to limit the element stay in the bounds
*/
this.inBounds = false;
/**
* Whether the element should use it's previous drag position on a new drag event.
*/
this.trackPosition = true;
/**
* Input css scale transform of element so translations are correct
*/
this.scale = 1;
/**
* Whether to prevent default event
*/
this.preventDefaultEvent = false;
/**
* Set initial position by offsets
*/
this.position = { x: 0, y: 0 };
/**
* Lock axis: 'x' or 'y'
*/
this.lockAxis = null;
/**
* Emit position offsets when moving
*/
this.movingOffset = new EventEmitter();
/**
* Emit position offsets when put back
*/
this.endOffset = new EventEmitter();
this._helperBlock = new HelperBlock(el.nativeElement, renderer);
}
/**
* Set z-index when not dragging
* @param {?} setting
* @return {?}
*/
set zIndex(setting) {
this.renderer.setStyle(this.el.nativeElement, 'z-index', setting);
this._zIndex = setting;
}
/**
* @param {?} setting
* @return {?}
*/
set ngDraggable(setting) {
if (setting !== undefined && setting !== null && setting !== '') {
this.allowDrag = !!setting;
/** @type {?} */
let element = this.getDragEl();
if (this.allowDrag) {
this.renderer.addClass(element, 'ng-draggable');
}
else {
this.putBack();
this.renderer.removeClass(element, 'ng-draggable');
}
}
}
/**
* @return {?}
*/
ngOnInit() {
if (this.allowDrag) {
/** @type {?} */
let element = this.getDragEl();
this.renderer.addClass(element, 'ng-draggable');
}
this.resetPosition();
}
/**
* @return {?}
*/
ngOnDestroy() {
this.bounds = null;
this.handle = null;
this.orignal = null;
this.oldTrans = null;
this.tempTrans = null;
this.currTrans = null;
this._helperBlock.dispose();
this._helperBlock = null;
if (this.draggingSub) {
this.draggingSub.unsubscribe();
}
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
if (changes['position'] && !changes['position'].isFirstChange()) {
/** @type {?} */
let p = changes['position'].currentValue;
if (!this.moving) {
if (Position.isIPosition(p)) {
this.oldTrans.set(p);
}
else {
this.oldTrans.reset();
}
this.transform();
}
else {
this.needTransform = true;
}
}
}
/**
* @return {?}
*/
ngAfterViewInit() {
if (this.inBounds) {
this.boundsCheck();
this.oldTrans.add(this.tempTrans);
this.tempTrans.reset();
}
}
/**
* @private
* @return {?}
*/
getDragEl() {
return this.handle ? this.handle : this.el.nativeElement;
}
/**
* @return {?}
*/
resetPosition() {
if (Position.isIPosition(this.position)) {
this.oldTrans.set(this.position);
}
else {
this.oldTrans.reset();
}
this.tempTrans.reset();
this.transform();
}
/**
* @private
* @param {?} p
* @return {?}
*/
moveTo(p) {
if (this.orignal) {
p.subtract(this.orignal);
this.tempTrans.set(p);
this.tempTrans.divide(this.scale);
this.transform();
if (this.bounds) {
this.edge.emit(this.boundsCheck());
}
this.movingOffset.emit(this.currTrans.value);
}
}
/**
* @private
* @return {?}
*/
transform() {
/** @type {?} */
let translateX = this.tempTrans.x + this.oldTrans.x;
/** @type {?} */
let translateY = this.tempTrans.y + this.oldTrans.y;
if (this.lockAxis === 'x') {
translateX = this.oldTrans.x;
this.tempTrans.x = 0;
}
else if (this.lockAxis === 'y') {
translateY = this.oldTrans.y;
this.tempTrans.y = 0;
}
// Snap to grid: by grid size
if (this.gridSize > 1) {
translateX = Math.round(translateX / this.gridSize) * this.gridSize;
translateY = Math.round(translateY / this.gridSize) * this.gridSize;
}
/** @type {?} */
let value = `translate(${Math.round(translateX)}px, ${Math.round(translateY)}px)`;
this.renderer.setStyle(this.el.nativeElement, 'transform', value);
this.renderer.setStyle(this.el.nativeElement, '-webkit-transform', value);
this.renderer.setStyle(this.el.nativeElement, '-ms-transform', value);
this.renderer.setStyle(this.el.nativeElement, '-moz-transform', value);
this.renderer.setStyle(this.el.nativeElement, '-o-transform', value);
// save current position
this.currTrans.x = translateX;
this.currTrans.y = translateY;
}
/**
* @private
* @return {?}
*/
pickUp() {
// get old z-index:
this.oldZIndex = this.el.nativeElement.style.zIndex ? this.el.nativeElement.style.zIndex : '';
if (window) {
this.oldZIndex = window.getComputedStyle(this.el.nativeElement, null).getPropertyValue('z-index');
}
if (this.zIndexMoving) {
this.renderer.setStyle(this.el.nativeElement, 'z-index', this.zIndexMoving);
}
if (!this.moving) {
this.started.emit(this.el.nativeElement);
this.moving = true;
/** @type {?} */
const element = this.getDragEl();
this.renderer.addClass(element, 'ng-dragging');
/**
* Fix performance issue:
* https://github.com/xieziyu/angular2-draggable/issues/112
*/
this.subscribeEvents();
}
}
/**
* @private
* @return {?}
*/
subscribeEvents() {
this.draggingSub = fromEvent(document, 'mousemove', { passive: false }).subscribe(event => this.onMouseMove((/** @type {?} */ (event))));
this.draggingSub.add(fromEvent(document, 'touchmove', { passive: false }).subscribe(event => this.onMouseMove((/** @type {?} */ (event)))));
this.draggingSub.add(fromEvent(document, 'mouseup', { passive: false }).subscribe(() => this.putBack()));
// checking if browser is IE or Edge - https://github.com/xieziyu/angular2-draggable/issues/153
/** @type {?} */
let isIEOrEdge = /msie\s|trident\//i.test(window.navigator.userAgent);
if (!isIEOrEdge) {
this.draggingSub.add(fromEvent(document, 'mouseleave', { passive: false }).subscribe(() => this.putBack()));
}
this.draggingSub.add(fromEvent(document, 'touchend', { passive: false }).subscribe(() => this.putBack()));
this.draggingSub.add(fromEvent(document, 'touchcancel', { passive: false }).subscribe(() => this.putBack()));
}
/**
* @private
* @return {?}
*/
unsubscribeEvents() {
this.draggingSub.unsubscribe();
this.draggingSub = null;
}
/**
* @return {?}
*/
boundsCheck() {
if (this.bounds) {
/** @type {?} */
let boundary = this.bounds.getBoundingClientRect();
/** @type {?} */
let elem = this.el.nativeElement.getBoundingClientRect();
/** @type {?} */
let result = {
'top': this.outOfBounds.top ? true : boundary.top < elem.top,
'right': this.outOfBounds.right ? true : boundary.right > elem.right,
'bottom': this.outOfBounds.bottom ? true : boundary.bottom > elem.bottom,
'left': this.outOfBounds.left ? true : boundary.left < elem.left
};
if (this.inBounds) {
if (!result.top) {
this.tempTrans.y -= (elem.top - boundary.top) / this.scale;
}
if (!result.bottom) {
this.tempTrans.y -= (elem.bottom - boundary.bottom) / this.scale;
}
if (!result.right) {
this.tempTrans.x -= (elem.right - boundary.right) / this.scale;
}
if (!result.left) {
this.tempTrans.x -= (elem.left - boundary.left) / this.scale;
}
this.transform();
}
return result;
}
}
/**
* Get current offset
* @return {?}
*/
getCurrentOffset() {
return this.currTrans.value;
}
/**
* @private
* @return {?}
*/
putBack() {
if (this._zIndex) {
this.renderer.setStyle(this.el.nativeElement, 'z-index', this._zIndex);
}
else if (this.zIndexMoving) {
if (this.oldZIndex) {
this.renderer.setStyle(this.el.nativeElement, 'z-index', this.oldZIndex);
}
else {
this.el.nativeElement.style.removeProperty('z-index');
}
}
if (this.moving) {
this.stopped.emit(this.el.nativeElement);
// Remove the helper div:
this._helperBlock.remove();
if (this.needTransform) {
if (Position.isIPosition(this.position)) {
this.oldTrans.set(this.position);
}
else {
this.oldTrans.reset();
}
this.transform();
this.needTransform = false;
}
if (this.bounds) {
this.edge.emit(this.boundsCheck());
}
this.moving = false;
this.endOffset.emit(this.currTrans.value);
if (this.trackPosition) {
this.oldTrans.add(this.tempTrans);
}
this.tempTrans.reset();
if (!this.trackPosition) {
this.transform();
}
/** @type {?} */
const element = this.getDragEl();
this.renderer.removeClass(element, 'ng-dragging');
/**
* Fix performance issue:
* https://github.com/xieziyu/angular2-draggable/issues/112
*/
this.unsubscribeEvents();
}
}
/**
* @param {?} target
* @param {?} element
* @return {?}
*/
checkHandleTarget(target, element) {
// Checks if the target is the element clicked, then checks each child element of element as well
// Ignores button clicks
// Ignore elements of type button
if (element.tagName === 'BUTTON') {
return false;
}
// If the target was found, return true (handle was found)
if (element === target) {
return true;
}
// Recursively iterate this elements children
for (let child in element.children) {
if (element.children.hasOwnProperty(child)) {
if (this.checkHandleTarget(target, element.children[child])) {
return true;
}
}
}
// Handle was not found in this lineage
// Note: return false is ignore unless it is the parent element
return false;
}
/**
* @param {?} event
* @return {?}
*/
onMouseDown(event) {
// 1. skip right click;
if (event instanceof MouseEvent && event.button === 2) {
return;
}
// 2. if handle is set, the element can only be moved by handle
/** @type {?} */
let target = event.target || event.srcElement;
if (this.handle !== undefined && !this.checkHandleTarget(target, this.handle)) {
return;
}
// 3. if allow drag is set to false, ignore the mousedown
if (this.allowDrag === false) {
return;
}
if (this.preventDefaultEvent) {
event.stopPropagation();
event.preventDefault();
}
this.orignal = Position.fromEvent(event, this.getDragEl());
this.pickUp();
}
/**
* @param {?} event
* @return {?}
*/
onMouseMove(event) {
if (this.moving && this.allowDrag) {
if (this.preventDefaultEvent) {
event.stopPropagation();
event.preventDefault();
}
// Add a transparent helper div:
this._helperBlock.add();
this.moveTo(Position.fromEvent(event, this.getDragEl()));
}
}
}
AngularDraggableDirective.decorators = [
{ type: Directive, args: [{
selector: '[ngDraggable]',
exportAs: 'ngDraggable'
},] }
];
/** @nocollapse */
AngularDraggableDirective.ctorParameters = () => [
{ type: ElementRef },
{ type: Renderer2 }
];
AngularDraggableDirective.propDecorators = {
started: [{ type: Output }],
stopped: [{ type: Output }],
edge: [{ type: Output }],
handle: [{ type: Input }],
bounds: [{ type: Input }],
outOfBounds: [{ type: Input }],
gridSize: [{ type: Input }],
zIndexMoving: [{ type: Input }],
zIndex: [{ type: Input }],
inBounds: [{ type: Input }],
trackPosition: [{ type: Input }],
scale: [{ type: Input }],
preventDefaultEvent: [{ type: Input }],
position: [{ type: Input }],
lockAxis: [{ type: Input }],
movingOffset: [{ type: Output }],
endOffset: [{ type: Output }],
ngDraggable: [{ type: Input }],
onMouseDown: [{ type: HostListener, args: ['mousedown', ['$event'],] }, { type: HostListener, args: ['touchstart', ['$event'],] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class ResizeHandle {
/**
* @param {?} parent
* @param {?} renderer
* @param {?} type
* @param {?} css
* @param {?} onMouseDown
*/
constructor(parent, renderer, type, css, onMouseDown) {
this.parent = parent;
this.renderer = renderer;
this.type = type;
this.css = css;
this.onMouseDown = onMouseDown;
// generate handle div
/** @type {?} */
let handle = renderer.createElement('div');
renderer.addClass(handle, 'ng-resizable-handle');
renderer.addClass(handle, css);
// add default diagonal for se handle
if (type === 'se') {
renderer.addClass(handle, 'ng-resizable-diagonal');
}
// append div to parent
if (this.parent) {
parent.appendChild(handle);
}
// create and register event listener
this._onResize = (event) => { onMouseDown(event, this); };
handle.addEventListener('mousedown', this._onResize, { passive: false });
handle.addEventListener('touchstart', this._onResize, { passive: false });
// done
this._handle = handle;
}
/**
* @return {?}
*/
dispose() {
this._handle.removeEventListener('mousedown', this._onResize);
this._handle.removeEventListener('touchstart', this._onResize);
if (this.parent) {
this.parent.removeChild(this._handle);
}
this._handle = null;
this._onResize = null;
}
/**
* @return {?}
*/
get el() {
return this._handle;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class Size {
/**
* @param {?} width
* @param {?} height
*/
constructor(width, height) {
this.width = width;
this.height = height;
}
/**
* @param {?} el
* @return {?}
*/
static getCurrent(el) {
/** @type {?} */
let size = new Size(0, 0);
if (window) {
/** @type {?} */
const computed = window.getComputedStyle(el);
if (computed) {
size.width = parseInt(computed.getPropertyValue('width'), 10);
size.height = parseInt(computed.getPropertyValue('height'), 10);
}
return size;
}
else {
console.error('Not Supported!');
return null;
}
}
/**
* @param {?} s
* @return {?}
*/
static copy(s) {
return new Size(0, 0).set(s);
}
/**
* @template THIS
* @this {THIS}
* @param {?} s
* @return {THIS}
*/
set(s) {
(/** @type {?} */ (this)).width = s.width;
(/** @type {?} */ (this)).height = s.height;
return (/** @type {?} */ (this));
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class AngularResizableDirective {
/**
* @param {?} el
* @param {?} renderer
*/
constructor(el, renderer) {
this.el = el;
this.renderer = renderer;
this._resizable = true;
this._handles = {};
this._handleType = [];
this._handleResizing = null;
this._direction = null;
this._directionChanged = null;
this._aspectRatio = 0;
this._containment = null;
this._origMousePos = null;
/**
* Original Size and Position
*/
this._origSize = null;
this._origPos = null;
/**
* Current Size and Position
*/
this._currSize = null;
this._currPos = null;
/**
* Initial Size and Position
*/
this._initSize = null;
this._initPos = null;
/**
* Snap to gird
*/
this._gridSize = null;
this._bounding = null;
/**
* Bugfix: iFrames, and context unrelated elements block all events, and are unusable
* https://github.com/xieziyu/angular2-draggable/issues/84
*/
this._helperBlock = null;
this.draggingSub = null;
this._adjusted = false;
/**
* Which handles can be used for resizing.
* \@example
* [rzHandles] = "'n,e,s,w,se,ne,sw,nw'"
* equals to: [rzHandles] = "'all'"
*
*
*/
this.rzHandles = 'e,s,se';
/**
* Whether the element should be constrained to a specific aspect ratio.
* Multiple types supported:
* boolean: When set to true, the element will maintain its original aspect ratio.
* number: Force the element to maintain a specific aspect ratio during resizing.
*/
this.rzAspectRatio = false;
/**
* Constrains resizing to within the bounds of the specified element or region.
* Multiple types supported:
* Selector: The resizable element will be contained to the bounding box of the first element found by the selector.
* If no element is found, no containment will be set.
* Element: The resizable element will be contained to the bounding box of this element.
* String: Possible values: "parent".
*/
this.rzContainment = null;
/**
* Snaps the resizing element to a grid, every x and y pixels.
* A number for both width and height or an array values like [ x, y ]
*/
this.rzGrid = null;
/**
* The minimum width the resizable should be allowed to resize to.
*/
this.rzMinWidth = null;
/**
* The minimum height the resizable should be allowed to resize to.
*/
this.rzMinHeight = null;
/**
* The maximum width the resizable should be allowed to resize to.
*/
this.rzMaxWidth = null;
/**
* The maximum height the resizable should be allowed to resize to.
*/
this.rzMaxHeight = null;
/**
* Whether to prevent default event
*/
this.preventDefaultEvent = true;
/**
* emitted when start resizing
*/
this.rzStart = new EventEmitter();
/**
* emitted when start resizing
*/
this.rzResizing = new EventEmitter();
/**
* emitted when stop resizing
*/
this.rzStop = new EventEmitter();
this._helperBlock = new HelperBlock(el.nativeElement, renderer);
}
/**
* Disables the resizable if set to false.
* @param {?} v
* @return {?}
*/
set ngResizable(v) {
if (v !== undefined && v !== null && v !== '') {
this._resizable = !!v;
this.updateResizable();
}
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
if (changes['rzHandles'] && !changes['rzHandles'].isFirstChange()) {
this.updateResizable();
}
if (changes['rzAspectRatio'] && !changes['rzAspectRatio'].isFirstChange()) {
this.updateAspectRatio();
}
if (changes['rzContainment'] && !changes['rzContainment'].isFirstChange()) {
this.updateContainment();
}
}
/**
* @return {?}
*/
ngOnInit() {
this.updateResizable();
}
/**
* @return {?}
*/
ngOnDestroy() {
this.removeHandles();
this._containment = null;
this._helperBlock.dispose();
this._helperBlock = null;
}
/**
* @return {?}
*/
ngAfterViewInit() {
/** @type {?} */
const elm = this.el.nativeElement;
this._initSize = Size.getCurrent(elm);
this._initPos = Position.getCurrent(elm);
this._currSize = Size.copy(this._initSize);
this._currPos = Position.copy(this._initPos);
this.updateAspectRatio();
this.updateContainment();
}
/**
* A method to reset size
* @return {?}
*/
resetSize() {
this._currSize = Size.copy(this._initSize);
this._currPos = Position.copy(this._initPos);
this.doResize();
}
/**
* A method to get current status
* @return {?}
*/
getStatus() {
if (!this._currPos || !this._currSize) {
return null;
}
return {
size: {
width: this._currSize.width,
height: this._currSize.height
},
position: {
top: this._currPos.y,
left: this._currPos.x
}
};
}
/**
* @private
* @return {?}
*/
updateResizable() {
/** @type {?} */
const element = this.el.nativeElement;
// clear handles:
this.renderer.removeClass(element, 'ng-resizable');
this.removeHandles();
// create new ones:
if (this._resizable) {
this.renderer.addClass(element, 'ng-resizable');
this.createHandles();
}
}
/**
* Use it to update aspect
* @private
* @return {?}
*/
updateAspectRatio() {
if (typeof this.rzAspectRatio === 'boolean') {
if (this.rzAspectRatio && this._currSize.height) {
this._aspectRatio = (this._currSize.width / this._currSize.height);
}
else {
this._aspectRatio = 0;
}
}
else {
/** @type {?} */
let r = Number(this.rzAspectRatio);
this._aspectRatio = isNaN(r) ? 0 : r;
}
}
/**
* Use it to update containment
* @private
* @return {?}
*/
updateContainment() {
if (!this.rzContainment) {
this._containment = null;
return;
}
if (typeof this.rzContainment === 'string') {
if (this.rzContainment === 'parent') {
this._containment = this.el.nativeElement.parentElement;
}
else {
this._containment = document.querySelector(this.rzContainment);
}
}
else {
this._containment = this.rzContainment;
}
}
/**
* Use it to create handle divs
* @private
* @return {?}
*/
createHandles() {
if (!this.rzHandles) {
return;
}
/** @type {?} */
let tmpHandleTypes;
if (typeof this.rzHandles === 'string') {
if (this.rzHandles === 'all') {
tmpHandleTypes = ['n', 'e', 's', 'w', 'ne', 'se', 'nw', 'sw'];
}
else {
tmpHandleTypes = this.rzHandles.replace(/ /g, '').toLowerCase().split(',');
}
for (let type of tmpHandleTypes) {
// default handle theme: ng-resizable-$type.
/** @type {?} */
let handle = this.createHandleByType(type, `ng-resizable-${type}`);
if (handle) {
this._handleType.push(type);
this._handles[type] = handle;
}
}
}
else {
tmpHandleTypes = Object.keys(this.rzHandles);
for (let type of tmpHandleTypes) {
// custom handle theme.
/** @type {?} */
let handle = this.createHandleByType(type, this.rzHandles[type]);
if (handle) {
this._handleType.push(type);
this._handles[type] = handle;
}
}
}
}
/**
* Use it to create a handle
* @private
* @param {?} type
* @param {?} css
* @return {?}
*/
createHandleByType(type, css) {
/** @type {?} */
const _el = this.el.nativeElement;
if (!type.match(/^(se|sw|ne|nw|n|e|s|w)$/)) {
console.error('Invalid handle type:', type);
return null;
}
return new ResizeHandle(_el, this.renderer, type, css, this.onMouseDown.bind(this));
}
/**
* @private
* @return {?}
*/
removeHandles() {
for (let type of this._handleType) {
this._handles[type].dispose();
}
this._handleType = [];
this._handles = {};
}
/**
* @param {?} event
* @param {?} handle
* @return {?}
*/
onMouseDown(event, handle) {
// skip right click;
if (event instanceof MouseEvent && event.button === 2) {
return;
}
if (this.preventDefaultEvent) {
// prevent default events
event.stopPropagation();
event.preventDefault();
}
if (!this._handleResizing) {
this._origMousePos = Position.fromEvent(event);
this.startResize(handle);
this.subscribeEvents();
}
}
/**
* @private
* @return {?}
*/
subscribeEvents() {
this.draggingSub = fromEvent(document, 'mousemove', { passive: false }).subscribe(event => this.onMouseMove((/** @type {?} */ (event))));
this.draggingSub.add(fromEvent(document, 'touchmove', { passive: false }).subscribe(event => this.onMouseMove((/** @type {?} */ (event)))));
this.draggingSub.add(fromEvent(document, 'mouseup', { passive: false }).subscribe(() => this.onMouseLeave()));
// fix for issue #164
/** @type {?} */
let isIEOrEdge = /msie\s|trident\//i.test(window.navigator.userAgent);
if (!isIEOrEdge) {
this.draggingSub.add(fromEvent(document, 'mouseleave', { passive: false }).subscribe(() => this.onMouseLeave()));
}
this.draggingSub.add(fromEvent(document, 'touchend', { passive: false }).subscribe(() => this.onMouseLeave()));
this.draggingSub.add(fromEvent(document, 'touchcancel', { passive: false }).subscribe(() => this.onMouseLeave()));
}
/**
* @private
* @return {?}
*/
unsubscribeEvents() {
this.draggingSub.unsubscribe();
this.draggingSub = null;
}
/**
* @return {?}
*/
onMouseLeave() {
if (this._handleResizing) {
this.stopResize();
this._origMousePos = null;
this.unsubscribeEvents();
}
}
/**
* @param {?} event
* @return {?}
*/
onMouseMove(event) {
if (this._handleResizing && this._resizable && this._origMousePos && this._origPos && this._origSize) {
this.resizeTo(Position.fromEvent(event));
this.onResizing();
}
}
/**
* @private
* @param {?} handle
* @return {?}
*/
startResize(handle) {
/** @type {?} */
const elm = this.el.nativeElement;
this._origSize = Size.getCurrent(elm);
this._origPos = Position.getCurrent(elm); // x: left, y: top
this._currSize = Size.copy(this._origSize);
this._currPos = Position.copy(this._origPos);
if (this._containment) {
this.getBounding();
}
this.getGridSize();
// Add a transparent helper div:
this._helperBlock.add();
this._handleResizing = handle;
this.updateDirection();
this.rzStart.emit(this.getResizingEvent());
}
/**
* @private
* @return {?}
*/
stopResize() {
// Remove the helper div:
this._helperBlock.remove();
this.rzStop.emit(this.getResizingEvent());
this._handleResizing = null;
this._direction = null;
this._origSize = null;
this._origPos = null;
if (this._containment) {
this.resetBounding();
}
}
/**
* @private
* @return {?}
*/
onResizing() {
this.rzResizing.emit(this.getResizingEvent());
}
/**
* @private
* @return {?}
*/
getResizingEvent() {
return {
host: this.el.nativeElement,
handle: this._handleResizing ? this._handleResizing.el : null,
size: {
width: this._currSize.width,
height: this._currSize.height
},
position: {
top: this._currPos.y,
left: this._currPos.x
},
direction: Object.assign({}, this._directionChanged),
};
}
/**
* @private
* @return {?}
*/
updateDirection() {
this._direction = {
n: !!this._handleResizing.type.match(/n/),
s: !!this._handleResizing.type.match(/s/),
w: !!this._handleResizing.type.match(/w/),
e: !!this._handleResizing.type.match(/e/)
};
this._directionChanged = Object.assign({}, this._direction);
// if aspect ration should be preserved:
if (this.rzAspectRatio) {
// if north then west (unless ne)
if (this._directionChanged.n && !this._directionChanged.e) {
this._directionChanged.w = true;
}
// if south then east (unless sw)
if (this._directionChanged.s && !this._directionChanged.w) {
this._directionChanged.e = true;
}
// if east then south (unless ne)
if (this._directionChanged.e && !this._directionChanged.n) {
this._directionChanged.s = true;
}
// if west then south (unless nw)
if (this._directionChanged.w && !this._directionChanged.n) {
this._directionChanged.s = true;
}
}
}
/**
* @private
* @param {?} p
* @return {?}
*/
resizeTo(p) {
p.subtract(this._origMousePos);
/** @type {?} */
const tmpX = Math.round(p.x / this._gridSize.x) * this._gridSize.x;
/** @type {?} */
const tmpY = Math.round(p.y / this._gridSize.y) * this._gridSize.y;
if (this._direction.n) {
// n, ne, nw
this._currPos.y = this._origPos.y + tmpY;
this._currSize.height = this._origSize.height - tmpY;
}
else if (this._direction.s) {
// s, se, sw
this._currSize.height = this._origSize.height + tmpY;
}
if (this._direction.e) {
// e, ne, se
this._currSize.width = this._origSize.width + tmpX;
}
else if (this._direction.w) {
// w, nw, sw
this._currSize.width = this._origSize.width - tmpX;
this._currPos.x = this._origPos.x + tmpX;
}
this.checkBounds();
this.checkSize();
this.adjustByRatio();
this.doResize();
}
/**
* @private
* @return {?}
*/
doResize() {
/** @type {?} */
const container = this.el.nativeElement;
if (!this._direction || this._direction.n || this._direction.s || this._aspectRatio) {
this.renderer.setStyle(container, 'height', this._currSize.height + 'px');
}
if (!this._direction || this._direction.w || this._direction.e || this._aspectRatio) {
this.renderer.setStyle(container, 'width', this._currSize.width + 'px');
}
this.renderer.setStyle(container, 'left', this._currPos.x + 'px');
this.renderer.setStyle(container, 'top', this._currPos.y + 'px');
}
/**
* @private
* @return {?}
*/
adjustByRatio() {
if (this._aspectRatio && !this._adjusted) {
if (this._direction.e || this._direction.w) {
/** @type {?} */
const newHeight = Math.floor(this._currSize.width / this._aspectRatio);
if (this._direction.n) {
this._currPos.y += this._currSize.height - newHeight;
}
this._currSize.height = newHeight;
}
else {
/** @type {?} */
const newWidth = Math.floor(this._aspectRatio * this._currSize.height);
if (this._direction.n) {
this._currPos.x += this._currSize.width - newWidth;
}
this._currSize.width = newWidth;
}
}
}
/**
* @private
* @return {?}
*/
checkBounds() {
if (this._containment) {
/** @type {?} */
const maxWidth = this._bounding.width - this._bounding.pr - this._bounding.deltaL - this._bounding.translateX - this._currPos.x;
/** @type {?} */
const maxHeight = this._bounding.height - this._bounding.pb - this._bounding.deltaT - this._bounding.translateY - this._currPos.y;
if (this._direction.n && (this._currPos.y + this._bounding.translateY < 0)) {
this._currPos.y = -this._bounding.translateY;
this._currSize.height = this._origSize.height + this._origPos.y + this._bounding.translateY;
}
if (this._direction.w && (this._currPos.x + this._bounding.translateX) < 0) {
this._currPos.x = -this._bounding.translateX;
this._currSize.width = this._origSize.width + this._origPos.x + this._bounding.translateX;
}
if (this._currSize.width > maxWidth) {
this._currSize.width = maxWidth;
}
if (this._currSize.height > maxHeight) {
this._currSize.height = maxHeight;
}
/**
* Fix Issue: Additional check for aspect ratio
* https://github.com/xieziyu/angular2-draggable/issues/132
*/
if (this._aspectRatio) {
this._adjusted = false;
if ((this._direction.w || this._direction.e) &&
(this._currSize.width / this._aspectRatio) >= maxHeight) {
/** @type {?} */
const newWidth = Math.floor(maxHeight * this._aspectRatio);
if (this._direction.w) {
this._currPos.x += this._currSize.width - newWidth;
}
this._currSize.width = newWidth;
this._currSize.height = maxHeight;
this._adjusted = true;
}
if ((this._direction.n || this._direction.s) &&
(this._currSize.height * this._aspectRatio) >= maxWidth) {
/** @type {?} */
const newHeight = Math.floor(maxWidth / this._aspectRatio);
if (this._direction.n) {
this._currPos.y += this._currSize.height - newHeight;
}
this._currSize.width = maxWidth;
this._currSize.height = newHeight;
this._adjusted = true;
}
}
}
}
/**
* @private
* @return {?}
*/
checkSize() {
/** @type {?} */
const minHeight = !this.rzMinHeight ? 1 : this.rzMinHeight;
/** @type {?} */
const minWidth = !this.rzMinWidth ? 1 : this.rzMinWidth;
if (this._currSize.height < minHeight) {
this._currSize.height = minHeight;
if (this._direction.n) {
this._currPos.y = this._origPos.y + (this._origSize.height - minHeight);
}
}
if (this._currSize.width < minWidth) {
this._currSize.width = minWidth;
if (this._direction.w) {
this._currPos.x = this._origPos.x + (this._origSize.width - minWidth);
}
}
if (this.rzMaxHeight && this._currSize.height > this.rzMaxHeight) {
this._currSize.height = this.rzMaxHeight;
if (this._direction.n) {
this._currPos.y = this._origPos.y + (this._origSize.height - this.rzMaxHeight);
}
}
if (this.rzMaxWidth && this._currSize.width > this.rzMaxWidth) {
this._currSize.width = this.rzMaxWidth;
if (this._direction.w) {
this._currPos.x = this._origPos.x + (this._origSize.width - this.rzMaxWidth);
}
}
}
/**
* @private
* @return {?}
*/
getBounding() {
/** @type {?} */
const el = this._containment;
/** @type {?} */
const computed = window.getComputedStyle(el);
if (computed) {
/** @type {?} */
let p = computed.getPropertyValue('position');
/** @type {?} */
const nativeEl = window.getComputedStyle(this.el.nativeElement);
/** @type {?} */
let transforms = nativeEl.getPropertyValue('transform').replace(/[^-\d,]/g, '').split(',');
this._bounding = {};
this._bounding.width = el.clientWidth;
this._bounding.height = el.clientHeight;
this._bounding.pr = parseInt(computed.getPropertyValue('padding-right'), 10);
this._bounding.pb = parseInt(computed.getPropertyValue('padding-bottom'), 10);
this._bounding.deltaL = this.el.nativeElement.offsetLeft - this._currPos.x;
this._bounding.deltaT = this.el.nativeElement.offsetTop - this._currPos.y;
if (transforms.length >= 6) {
this._bounding.translateX = parseInt(transforms[4], 10);
this._bounding.translateY = parseInt(transforms[5], 10);
}
else {
this._bounding.translateX = 0;
this._bounding.translateY = 0;
}
this._bounding.position = computed.getPropertyValue('position');
if (p === 'static') {
this.renderer.setStyle(el, 'position', 'relative');
}
}
}
/**
* @private
* @return {?}
*/
resetBounding() {
if (this._bounding && this._bounding.position === 'static') {
this.renderer.setStyle(this._containment, 'position', 'relative');
}
this._bounding = null;
}
/**
* @private
* @return {?}
*/
getGridSize() {
// set default value:
this._gridSize = { x: 1, y: 1 };
if (this.rzGrid) {
if (typeof this.rzGrid === 'number') {
this._gridSize = { x: this.rzGrid, y: this.rzGrid };
}
else if (Array.isArray(this.rzGrid)) {
this._gridSize = { x: this.rzGrid[0], y: this.rzGrid[1] };
}
}
}
}
AngularResizableDirective.decorators = [
{ type: Directive, args: [{
selector: '[ngResizable]',
exportAs: 'ngResizable'
},] }
];
/** @nocollapse */
AngularResizableDirective.ctorParameters = () => [
{ type: ElementRef },
{ type: Renderer2 }
];
AngularResizableDirective.propDecorators = {
ngResizable: [{ type: Input }],
rzHandles: [{ type: Input }],
rzAspectRatio: [{ type: Input }],
rzContainment: [{ type: Input }],
rzGrid: [{ type: Input }],
rzMinWidth: [{ type: Input }],
rzMinHeight: [{ type: Input }],
rzMaxWidth: [{ type: Input }],
rzMaxHeight: [{ type: Input }],
preventDefaultEvent: [{ type: Input }],
rzStart: [{ type: Output }],
rzResizing: [{ type: Output }],
rzStop: [{ type: Output }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class AngularDraggableModule {
}
AngularDraggableModule.decorators = [
{ type: NgModule, args: [{
imports: [],
declarations: [
AngularDraggableDirective,
AngularResizableDirective
],
exports: [
AngularDraggableDirective,
AngularResizableDirective
]
},] }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { AngularDraggableDirective, AngularResizableDirective, AngularDraggableModule, Position };
//# sourceMappingURL=angular2-draggable.js.map