From 9b601b73656d3abe5524c5a880072478251190e7 Mon Sep 17 00:00:00 2001 From: Steve Brush Date: Thu, 29 Mar 2018 14:56:10 -0400 Subject: [PATCH] Contrib > Added resizing functionality to flyout (#1539) (#1566) --- src/demos/flyout/flyout-demo.component.ts | 5 +- src/modules/flyout/flyout.component.html | 13 +++- src/modules/flyout/flyout.component.scss | 31 ++++++-- src/modules/flyout/flyout.component.spec.ts | 82 ++++++++++++++++++++- src/modules/flyout/flyout.component.ts | 41 +++++++++++ src/modules/flyout/types/flyout-config.ts | 3 + 6 files changed, 165 insertions(+), 10 deletions(-) diff --git a/src/demos/flyout/flyout-demo.component.ts b/src/demos/flyout/flyout-demo.component.ts index 2e7e8cd9d..4fc7ac73e 100644 --- a/src/demos/flyout/flyout-demo.component.ts +++ b/src/demos/flyout/flyout-demo.component.ts @@ -39,7 +39,10 @@ export class SkyFlyoutDemoComponent { }], ariaDescribedBy: 'my-describedby-id', ariaLabelledBy: 'my-labelledby-id', - ariaRole: 'modal' + ariaRole: 'modal', + defaultWidth: 500, + maxWidth: 1000, + minWidth: 200 }); this.flyout.closed.subscribe(() => { diff --git a/src/modules/flyout/flyout.component.html b/src/modules/flyout/flyout.component.html index 22bf86042..888e9a525 100644 --- a/src/modules/flyout/flyout.component.html +++ b/src/modules/flyout/flyout.component.html @@ -6,7 +6,18 @@ [attr.aria-labelledby]="config?.ariaLabelledBy" [ngClass]="{ 'sky-flyout-hidden': !isOpen && !isOpening }" (@flyoutState.done)="animationDone($event)" - [@flyoutState]="getAnimationState()"> + [@flyoutState]="getAnimationState()" + [style.width.px]="flyoutWidth"> + + +
diff --git a/src/modules/flyout/flyout.component.scss b/src/modules/flyout/flyout.component.scss index 569a7662d..79bfa8162 100644 --- a/src/modules/flyout/flyout.component.scss +++ b/src/modules/flyout/flyout.component.scss @@ -10,16 +10,14 @@ background-color: #fff; border-left: 6px solid $sky-highlight-color-info; z-index: $sky-flyout-z-index; - overflow: auto; - width: 100%; &:focus { outline: none; } - @media (min-width: $sky-screen-sm-min) { - width: 50%; - min-width: 320px; + @media (max-width: $sky-screen-sm-min) { + min-width: 100%; + max-width: 100%; } &.sky-flyout-hidden { @@ -27,6 +25,24 @@ } } +.sky-flyout-resize-handle { + height: 100%; + width: 14px; + position: absolute; + left: -10px; + cursor: ew-resize; + padding: 0; + border: 0; + background: transparent; + display: block; + top: 0; + bottom: 0; + + @media (max-width: $sky-screen-sm-min) { + cursor: initial; + } +} + .sky-flyout-header { @include sky-border(dark, bottom); width: 100%; @@ -48,3 +64,8 @@ padding-right: 50px; } } + +.sky-flyout-content { + overflow-y: auto; + height: calc(100% - 50px); +} diff --git a/src/modules/flyout/flyout.component.spec.ts b/src/modules/flyout/flyout.component.spec.ts index 1ff909310..9cd8f0a45 100644 --- a/src/modules/flyout/flyout.component.spec.ts +++ b/src/modules/flyout/flyout.component.spec.ts @@ -54,10 +54,21 @@ describe('Flyout component', () => { tick(); } + function makeEvent(eventType: string, evtObj: any) { + let evt = document.createEvent('MouseEvents'); + evt.initMouseEvent(eventType, false, false, window, 0, 0, 0, evtObj.clientX, + 0, false, false, false, false, 0, undefined); + document.dispatchEvent(evt); + } + function getFlyoutElement(): HTMLElement { - return document.querySelector('.sky-flyout') as HTMLElement; + return document.querySelector('.sky-flyout') as HTMLElement; } + function getFlyoutHandleElement(): HTMLElement { + return document.querySelector('.sky-flyout-resize-handle') as HTMLElement; + } + function getFlyoutHeaderElement(): HTMLElement { return document.querySelector('.sky-flyout-header') as HTMLElement; } @@ -154,16 +165,18 @@ describe('Flyout component', () => { expect(flyoutContentElement).toHaveText('Sally'); })); - it('should accept configuration options for aria-labelledBy, aria-describedby and role', + it('should accept configuration options for aria-labelledBy, aria-describedby, role, and width', fakeAsync(() => { const expectedLabel = 'customlabelledby'; const expectedDescribed = 'customdescribedby'; const expectedRole = 'customrole'; + const expectedDefault = 500; openFlyout({ ariaLabelledBy: expectedLabel, ariaDescribedBy: expectedDescribed, - ariaRole: expectedRole + ariaRole: expectedRole, + defaultWidth: expectedDefault }); const flyoutElement = getFlyoutElement(); @@ -174,6 +187,8 @@ describe('Flyout component', () => { .toBe(expectedDescribed); expect(flyoutElement.getAttribute('role')) .toBe(expectedRole); + expect(flyoutElement.style.width) + .toBe(expectedDefault + 'px'); }) ); @@ -193,4 +208,65 @@ describe('Flyout component', () => { expect(headerElement.classList.contains('sky-flyout-help-shim')).toBeTruthy(); }) ); + + it('should resize when handle is dragged', fakeAsync(() => { + openFlyout({}); + const flyoutElement = getFlyoutElement(); + const handleElement = getFlyoutHandleElement(); + + expect(flyoutElement.style.width).toBe('500px'); + + let evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('mousedown', false, false, window, 0, 0, 0, 1000, + 0, false, false, false, false, 0, undefined); + + handleElement.dispatchEvent(evt); + makeEvent('mousemove', { clientX: 1100 }); + fixture.detectChanges(); + expect(flyoutElement.style.width).toBe('400px'); + makeEvent('mousemove', { clientX: 1000 }); + fixture.detectChanges(); + expect(flyoutElement.style.width).toBe('500px'); + makeEvent('mouseup', {}); + }) + ); + + it('should respect minimum and maximum when resizing', fakeAsync(() => { + openFlyout({ maxWidth: 1000, minWidth: 200}); + const flyoutElement = getFlyoutElement(); + const handleElement = getFlyoutHandleElement(); + + expect(flyoutElement.style.width).toBe('500px'); + let evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('mousedown', false, false, window, 0, 0, 0, 1000, + 0, false, false, false, false, 0, undefined); + handleElement.dispatchEvent(evt); + makeEvent('mousemove', { clientX: 500 }); + fixture.detectChanges(); + expect(flyoutElement.style.width).toBe('1000px'); + makeEvent('mousemove', { clientX: 200 }); + fixture.detectChanges(); + expect(flyoutElement.style.width).toBe('1000px'); + makeEvent('mousemove', { clientX: 1300 }); + fixture.detectChanges(); + expect(flyoutElement.style.width).toBe('200px'); + makeEvent('mousemove', { clientX: 1400 }); + fixture.detectChanges(); + expect(flyoutElement.style.width).toBe('200px'); + makeEvent('mouseup', {}); + }) + ); + + it('should not resize when handle is not clicked', + fakeAsync(() => { + + openFlyout({}); + const flyoutElement = getFlyoutElement(); + + expect(flyoutElement.style.width).toBe('500px'); + makeEvent('mousemove', { clientX: 1100 }); + fixture.detectChanges(); + expect(flyoutElement.style.width).toBe('500px'); + }) + ); }); diff --git a/src/modules/flyout/flyout.component.ts b/src/modules/flyout/flyout.component.ts index d852b5784..828ce552b 100644 --- a/src/modules/flyout/flyout.component.ts +++ b/src/modules/flyout/flyout.component.ts @@ -4,6 +4,7 @@ import { ChangeDetectorRef, ComponentFactoryResolver, ElementRef, + HostListener, Injector, OnDestroy, OnInit, @@ -60,6 +61,10 @@ export class SkyFlyoutComponent implements OnDestroy, OnInit { public isOpen = false; public isOpening = false; + public flyoutWidth = 0; + public isDragging = false; + private xCoord = 0; + public get messageStream(): Subject { return this._messageStream; } @@ -113,6 +118,9 @@ export class SkyFlyoutComponent implements OnDestroy, OnInit { } this.config = Object.assign({ providers: [] }, config); + this.config.defaultWidth = this.config.defaultWidth || 500; + this.config.minWidth = this.config.minWidth || 320; + this.config.maxWidth = this.config.maxWidth || this.config.defaultWidth; const factory = this.resolver.resolveComponentFactory(component); const providers = ReflectiveInjector.resolve(this.config.providers); @@ -126,6 +134,8 @@ export class SkyFlyoutComponent implements OnDestroy, OnInit { type: SkyFlyoutMessageType.Open }); + this.flyoutWidth = this.config.defaultWidth; + return this.flyoutInstance; } @@ -145,6 +155,37 @@ export class SkyFlyoutComponent implements OnDestroy, OnInit { } } + public onMouseDown(event: MouseEvent) { + this.isDragging = true; + this.xCoord = event.clientX; + event.preventDefault(); + event.stopPropagation(); + } + + @HostListener('document:mousemove', ['$event']) + public onMouseMove(event: MouseEvent) { + if (!this.isDragging) { + return; + } + + const offsetX = event.clientX - this.xCoord; + let width = this.flyoutWidth; + + width -= offsetX; + + if (width < this.config.minWidth || width > this.config.maxWidth) { + return; + } + + this.flyoutWidth = width; + this.xCoord = event.clientX; + } + + @HostListener('document:mouseup', ['$event']) + public onHandleRelease(event: MouseEvent) { + this.isDragging = false; + } + private open() { if (!this.isOpen) { this.isOpen = false; diff --git a/src/modules/flyout/types/flyout-config.ts b/src/modules/flyout/types/flyout-config.ts index 2c3967a59..9e3c48862 100644 --- a/src/modules/flyout/types/flyout-config.ts +++ b/src/modules/flyout/types/flyout-config.ts @@ -3,4 +3,7 @@ export interface SkyFlyoutConfig { ariaDescribedBy?: string; ariaLabelledBy?: string; ariaRole?: string; + defaultWidth?: number; + minWidth?: number; + maxWidth?: number; }