From e40464718386b2e894c51c9b9d365edabfd32340 Mon Sep 17 00:00:00 2001 From: Badradine Boulahia Date: Sun, 5 Mar 2017 10:01:08 +0100 Subject: [PATCH 1/4] feat(elementAbsolutePosition): using offsetPosition when true --- .gitignore | 2 ++ demo/demo.component.ts | 35 +++++++++++++++++++-------------- package.json | 1 + src/resizable.directive.ts | 40 ++++++++++++++++++++++++++++---------- test/resizable.spec.ts | 3 ++- 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 370ca30..9dc6fdb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .DS_Store node_modules coverage + +*.iml diff --git a/demo/demo.component.ts b/demo/demo.component.ts index c8e6c21..13a7ca0 100644 --- a/demo/demo.component.ts +++ b/demo/demo.component.ts @@ -4,8 +4,11 @@ import {ResizeEvent} from './../src'; @Component({ selector: 'demo-app', styles: [` + .container { + transform: scale3d(0.7, 0.7, 0.7); + } .rectangle { - position: relative; + position: absolute; top: 200px; display: flex; align-items: center; @@ -26,20 +29,22 @@ import {ResizeEvent} from './../src'; template: `

Drag and pull the edges of the rectangle

-
- +
+
+ +
` diff --git a/package.json b/package.json index 1e59d3a..c109678 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "module": "./dist/esm/src/index.js", "typings": "./dist/esm/src/index.d.ts", "scripts": { + "demo": "webpack-dev-server --open", "start": "concurrently --raw \"webpack-dev-server --open\" \"npm run test:watch\"", "build:demo": "webpack -p", "build:umd": "webpack --config webpack.config.umd.ts", diff --git a/src/resizable.directive.ts b/src/resizable.directive.ts index 820a597..8bb91f0 100644 --- a/src/resizable.directive.ts +++ b/src/resizable.directive.ts @@ -36,14 +36,10 @@ function isNumberCloseTo(value1: number, value2: number, precision: number = 3): return diff < precision; } -function getNewBoundingRectangle(startingRect: BoundingRectangle, edges: Edges, mouseX: number, mouseY: number): BoundingRectangle { +function getNewBoundingRectangle(element: ElementRef, edges: Edges, mouseX: number, mouseY: number, + elementToResizeFixed: boolean): BoundingRectangle { - const newBoundingRect: BoundingRectangle = { - top: startingRect.top, - bottom: startingRect.bottom, - left: startingRect.left, - right: startingRect.right - }; + const newBoundingRect: BoundingRectangle = getElementRect(element, elementToResizeFixed); if (edges.top) { newBoundingRect.top += mouseY; @@ -64,6 +60,25 @@ function getNewBoundingRectangle(startingRect: BoundingRectangle, edges: Edges, } +function getElementRect(element: ElementRef, elementToResizeFixed: boolean): BoundingRectangle { + if (elementToResizeFixed) { + return { + top: element.nativeElement.offsetTop, + bottom: element.nativeElement.offsetHeight + element.nativeElement.offsetTop, + left: element.nativeElement.offsetLeft, + right: element.nativeElement.offsetWidth + element.nativeElement.offsetLeft + }; + } else { + const boundingRect: BoundingRectangle = element.nativeElement.getBoundingClientRect(); + return { + top: boundingRect.top, + bottom: boundingRect.bottom, + left: boundingRect.left, + right: boundingRect.right + }; + } +} + function isWithinBoundingY({mouseY, rect}: {mouseY: number, rect: ClientRect}): boolean { return mouseY >= rect.top && mouseY <= rect.bottom; } @@ -212,6 +227,11 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { */ @Input() resizeCursorPrecision: number = 3; + /** + * If the element have already a position fixed set to true. + */ + @Input() elementToResizeFixed: boolean = false; + /** * Called when the mouse is pressed and a resize event is about to begin. `$event` is a `ResizeEvent` object. */ @@ -359,7 +379,7 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { }).filter(() => !!currentResize); mousedrag.map(({mouseX, mouseY}) => { - return getNewBoundingRectangle(currentResize.startingRect, currentResize.edges, mouseX, mouseY); + return getNewBoundingRectangle(this.elm, currentResize.edges, mouseX, mouseY, this.elementToResizeFixed); }).filter((newBoundingRect: BoundingRectangle) => { return newBoundingRect.height > 0 && newBoundingRect.width > 0; }).filter((newBoundingRect: BoundingRectangle) => { @@ -408,7 +428,7 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { if (currentResize) { removeGhostElement(); } - const startingRect: BoundingRectangle = this.elm.nativeElement.getBoundingClientRect(); + const startingRect: BoundingRectangle = getElementRect(this.elm, this.elementToResizeFixed); currentResize = { edges, startingRect, @@ -429,7 +449,7 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { this.zone.run(() => { this.resizeStart.emit({ edges: getEdgesDiff({edges, initialRectangle: startingRect, newRectangle: startingRect}), - rectangle: getNewBoundingRectangle(startingRect, {}, 0, 0) + rectangle: getNewBoundingRectangle(this.elm, {}, 0, 0, this.elementToResizeFixed) }); }); }); diff --git a/test/resizable.spec.ts b/test/resizable.spec.ts index 4ac0af3..021b643 100644 --- a/test/resizable.spec.ts +++ b/test/resizable.spec.ts @@ -29,6 +29,7 @@ describe('resizable directive', () => { [resizeSnapGrid]="resizeSnapGrid" [resizeCursors]="resizeCursors" [resizeCursorPrecision]="resizeCursorPrecision" + [elementToResizeFixed]="elementToResizeFixed" (resizeStart)="resizeStart($event)" (resizing)="resizing($event)" (resizeEnd)="resizeEnd($event)"> @@ -48,7 +49,7 @@ describe('resizable directive', () => { public resizeSnapGrid: Object = {}; public resizeCursors: Object = {}; public resizeCursorPrecision: number; - + public elementToResizeFixed: boolean = false; } const triggerDomEvent: Function = (eventType: string, target: HTMLElement | Element, eventData: Object = {}) => { From 75103514e13a6055236ac78f80f9ccbba635bece Mon Sep 17 00:00:00 2001 From: Badradine Boulahia Date: Mon, 6 Mar 2017 10:33:17 +0100 Subject: [PATCH 2/4] feat(elementAbsolutePosition): using offsetPosition when true --- demo/demo.component.ts | 2 +- src/resizable.directive.ts | 30 +++++++++++++++++++----------- test/resizable.spec.ts | 4 ++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/demo/demo.component.ts b/demo/demo.component.ts index 13a7ca0..e654f4a 100644 --- a/demo/demo.component.ts +++ b/demo/demo.component.ts @@ -37,7 +37,7 @@ import {ResizeEvent} from './../src'; [validateResize]="validate" [resizeEdges]="{bottom: true, right: true, top: true, left: true}" [enableGhostResize]="true" - [elementToResizeFixed]="true" + [enableAbsolutePositioning]="true" (resizeEnd)="onResizeEnd($event)"> !!currentResize); mousedrag.map(({mouseX, mouseY}) => { - return getNewBoundingRectangle(this.elm, currentResize.edges, mouseX, mouseY, this.elementToResizeFixed); + return getNewBoundingRectangle(currentResize.startingRect, currentResize.edges, mouseX, mouseY); }).filter((newBoundingRect: BoundingRectangle) => { return newBoundingRect.height > 0 && newBoundingRect.width > 0; }).filter((newBoundingRect: BoundingRectangle) => { @@ -428,7 +432,7 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { if (currentResize) { removeGhostElement(); } - const startingRect: BoundingRectangle = getElementRect(this.elm, this.elementToResizeFixed); + const startingRect: BoundingRectangle = getElementRect(this.elm, this.enableAbsolutePositioning); currentResize = { edges, startingRect, @@ -439,7 +443,11 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { const resizeCursors: ResizeCursors = Object.assign({}, DEFAULT_RESIZE_CURSORS, this.resizeCursors); this.elm.nativeElement.parentElement.appendChild(currentResize.clonedNode); this.renderer.setElementStyle(this.elm.nativeElement, 'visibility', 'hidden'); - this.renderer.setElementStyle(currentResize.clonedNode, 'position', 'fixed'); + if (this.enableAbsolutePositioning) { + this.renderer.setElementStyle(currentResize.clonedNode, 'position', 'absolute'); + } else { + this.renderer.setElementStyle(currentResize.clonedNode, 'position', 'fixed'); + } this.renderer.setElementStyle(currentResize.clonedNode, 'left', `${currentResize.startingRect.left}px`); this.renderer.setElementStyle(currentResize.clonedNode, 'top', `${currentResize.startingRect.top}px`); this.renderer.setElementStyle(currentResize.clonedNode, 'height', `${currentResize.startingRect.height}px`); @@ -449,7 +457,7 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { this.zone.run(() => { this.resizeStart.emit({ edges: getEdgesDiff({edges, initialRectangle: startingRect, newRectangle: startingRect}), - rectangle: getNewBoundingRectangle(this.elm, {}, 0, 0, this.elementToResizeFixed) + rectangle: getNewBoundingRectangle(startingRect, {}, 0, 0) }); }); }); diff --git a/test/resizable.spec.ts b/test/resizable.spec.ts index 021b643..cd7c663 100644 --- a/test/resizable.spec.ts +++ b/test/resizable.spec.ts @@ -29,7 +29,7 @@ describe('resizable directive', () => { [resizeSnapGrid]="resizeSnapGrid" [resizeCursors]="resizeCursors" [resizeCursorPrecision]="resizeCursorPrecision" - [elementToResizeFixed]="elementToResizeFixed" + [enableAbsolutePositioning]="enableAbsolutePositioning" (resizeStart)="resizeStart($event)" (resizing)="resizing($event)" (resizeEnd)="resizeEnd($event)"> @@ -49,7 +49,7 @@ describe('resizable directive', () => { public resizeSnapGrid: Object = {}; public resizeCursors: Object = {}; public resizeCursorPrecision: number; - public elementToResizeFixed: boolean = false; + public enableAbsolutePositioning: boolean = false; } const triggerDomEvent: Function = (eventType: string, target: HTMLElement | Element, eventData: Object = {}) => { From 76bde3dbf64bb1ac0d6f92577c39eda8d1d8b75c Mon Sep 17 00:00:00 2001 From: Badradine Boulahia Date: Tue, 7 Mar 2017 19:19:31 +0100 Subject: [PATCH 3/4] feat(elementAbsolutePosition): using offsetPosition when true --- demo/demo.component.ts | 35 +++++++++++++++-------------------- package.json | 1 - 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/demo/demo.component.ts b/demo/demo.component.ts index e654f4a..c8e6c21 100644 --- a/demo/demo.component.ts +++ b/demo/demo.component.ts @@ -4,11 +4,8 @@ import {ResizeEvent} from './../src'; @Component({ selector: 'demo-app', styles: [` - .container { - transform: scale3d(0.7, 0.7, 0.7); - } .rectangle { - position: absolute; + position: relative; top: 200px; display: flex; align-items: center; @@ -29,22 +26,20 @@ import {ResizeEvent} from './../src'; template: `

Drag and pull the edges of the rectangle

-
-
- -
+
+
` diff --git a/package.json b/package.json index c109678..1e59d3a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "module": "./dist/esm/src/index.js", "typings": "./dist/esm/src/index.d.ts", "scripts": { - "demo": "webpack-dev-server --open", "start": "concurrently --raw \"webpack-dev-server --open\" \"npm run test:watch\"", "build:demo": "webpack -p", "build:umd": "webpack --config webpack.config.umd.ts", From 3a0423bb8e46caa8e966a141924c56201a01132f Mon Sep 17 00:00:00 2001 From: Badradine Boulahia Date: Mon, 13 Mar 2017 14:37:47 +0100 Subject: [PATCH 4/4] feat(elementAbsolutePosition): add unit-test --- src/resizable.directive.ts | 16 ++-- test/resizable.spec.ts | 160 ++++++++++++++++++++++++++++++++++++- 2 files changed, 162 insertions(+), 14 deletions(-) diff --git a/src/resizable.directive.ts b/src/resizable.directive.ts index 6b7ee21..9cd2d87 100644 --- a/src/resizable.directive.ts +++ b/src/resizable.directive.ts @@ -64,8 +64,8 @@ function getNewBoundingRectangle(startingRect: BoundingRectangle, edges: Edges, } -function getElementRect(element: ElementRef, enableAbsolutePositioning: boolean): BoundingRectangle { - if (enableAbsolutePositioning) { +function getElementRect(element: ElementRef, ghostElementPositioning: string): BoundingRectangle { + if (ghostElementPositioning === 'absolute') { return { top: element.nativeElement.offsetTop, bottom: element.nativeElement.offsetHeight + element.nativeElement.offsetTop, @@ -232,9 +232,9 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { @Input() resizeCursorPrecision: number = 3; /** - * If we want to position cloned node with absolute position + * Define the positioning of the ghost element (can be fixed or absolute) */ - @Input() enableAbsolutePositioning: boolean = false; + @Input() ghostElementPositioning: 'fixed' | 'absolute' = 'fixed'; /** * Called when the mouse is pressed and a resize event is about to begin. `$event` is a `ResizeEvent` object. @@ -432,7 +432,7 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { if (currentResize) { removeGhostElement(); } - const startingRect: BoundingRectangle = getElementRect(this.elm, this.enableAbsolutePositioning); + const startingRect: BoundingRectangle = getElementRect(this.elm, this.ghostElementPositioning); currentResize = { edges, startingRect, @@ -443,11 +443,7 @@ export class Resizable implements OnInit, OnDestroy, AfterViewInit { const resizeCursors: ResizeCursors = Object.assign({}, DEFAULT_RESIZE_CURSORS, this.resizeCursors); this.elm.nativeElement.parentElement.appendChild(currentResize.clonedNode); this.renderer.setElementStyle(this.elm.nativeElement, 'visibility', 'hidden'); - if (this.enableAbsolutePositioning) { - this.renderer.setElementStyle(currentResize.clonedNode, 'position', 'absolute'); - } else { - this.renderer.setElementStyle(currentResize.clonedNode, 'position', 'fixed'); - } + this.renderer.setElementStyle(currentResize.clonedNode, 'position', this.ghostElementPositioning); this.renderer.setElementStyle(currentResize.clonedNode, 'left', `${currentResize.startingRect.left}px`); this.renderer.setElementStyle(currentResize.clonedNode, 'top', `${currentResize.startingRect.top}px`); this.renderer.setElementStyle(currentResize.clonedNode, 'height', `${currentResize.startingRect.height}px`); diff --git a/test/resizable.spec.ts b/test/resizable.spec.ts index cd7c663..85e80ad 100644 --- a/test/resizable.spec.ts +++ b/test/resizable.spec.ts @@ -29,7 +29,7 @@ describe('resizable directive', () => { [resizeSnapGrid]="resizeSnapGrid" [resizeCursors]="resizeCursors" [resizeCursorPrecision]="resizeCursorPrecision" - [enableAbsolutePositioning]="enableAbsolutePositioning" + [ghostElementPositioning]="ghostElementPositioning" (resizeStart)="resizeStart($event)" (resizing)="resizing($event)" (resizeEnd)="resizeEnd($event)"> @@ -49,7 +49,7 @@ describe('resizable directive', () => { public resizeSnapGrid: Object = {}; public resizeCursors: Object = {}; public resizeCursorPrecision: number; - public enableAbsolutePositioning: boolean = false; + public ghostElementPositioning: 'fixed' | 'absolute' = 'fixed'; } const triggerDomEvent: Function = (eventType: string, target: HTMLElement | Element, eventData: Object = {}) => { @@ -66,9 +66,12 @@ describe('resizable directive', () => { let component: ComponentFixture, createComponent: Function; beforeEach(() => { document.body.style.margin = '0px'; - createComponent = (template?: string) => { + createComponent = (template?: string, styles?: Array) => { if (template) { - TestBed.overrideComponent(TestCmp, {set: {template}}); + TestBed.overrideComponent(TestCmp, {set: {template: template}}); + } + if (styles) { + TestBed.overrideComponent(TestCmp, {set: {styles: styles}}); } const fixture: ComponentFixture = TestBed.createComponent(TestCmp); fixture.detectChanges(); @@ -1078,4 +1081,153 @@ describe('resizable directive', () => { }); + describe('absolute positioning', () => { + let domEvents: Array; + beforeEach(() => { + domEvents = []; + }); + + it('should have the same top/left/height when resize from the right', () => { + domEvents.push({ + name: 'mousedown', + data: { + clientX: 600, + clientY: 405 + } + }); + domEvents.push({ + name: 'mousemove', + data: { + clientX: 620, + clientY: 405 + }, + style: { + top: '200px', + left: '100px', + height: '150px' + } + }); + }); + + it('should have the same top/height when resize from the left', () => { + domEvents.push({ + name: 'mousedown', + data: { + clientX: 300, + clientY: 405 + } + }); + domEvents.push({ + name: 'mousemove', + data: { + clientX: 280, + clientY: 405 + }, + style: { + top: '200px', + height: '150px' + } + }); + }); + + it('should have the same left/width when resize from the top', () => { + domEvents.push({ + name: 'mousedown', + data: { + clientX: 400, + clientY: 400 + } + }); + domEvents.push({ + name: 'mousemove', + data: { + clientX: 400, + clientY: 280 + }, + style: { + left: '100px', + width: '300px' + } + }); + }); + + it('should have the same top/left/width when resize from the bottom', () => { + domEvents.push({ + name: 'mousedown', + data: { + clientX: 400, + clientY: 550 + } + }); + domEvents.push({ + name: 'mousemove', + data: { + clientX: 400, + clientY: 570 + }, + style: { + top: '200px', + left: '100px', + width: '300px', + } + }); + }); + + afterEach(() => { + const template: string = ` +
+
+
+
+ `; + const styles: Array = [` + .container { + -webkit-transform: scale3d(1, 1, 1); + position: relative; + top: 200px; + left: 200px; + } + .rectangle { + position: absolute; + top: 200px; + left: 100px; + width: 300px; + height: 150px; + } + `]; + + const fixture: ComponentFixture = createComponent(template, styles); + fixture.componentInstance.ghostElementPositioning = 'absolute'; + fixture.detectChanges(); + + const elm: HTMLElement = fixture.componentInstance.resizable.elm.nativeElement; + domEvents.forEach(event => { + triggerDomEvent(event.name, elm, event.data); + + const clonedNode: Element = elm.parentElement.children[1]; + if (event.name !== 'mouseup') { + expect(clonedNode['style'].position).to.equal('absolute'); + } + if (event.style) { + Object.keys(event.style).forEach(styleKey => { + expect(clonedNode['style'][styleKey]).to.equal(event.style[styleKey]); + }); + } + }); + }); + }); + });