From 113997b1b6c02685ee3e8f33e101f6154ff5404a Mon Sep 17 00:00:00 2001 From: Yuan Gao Date: Tue, 5 Jun 2018 13:22:08 -0700 Subject: [PATCH 1/2] fix(select): Improve the a11y of select component by making overlay always exist in the DOM --- src/cdk/overlay/overlay-directives.spec.ts | 14 ++++ src/cdk/overlay/overlay-directives.ts | 21 ++++-- src/cdk/overlay/overlay-ref.ts | 8 +- src/lib/select/select.html | 11 ++- src/lib/select/select.spec.ts | 86 ++++++++++++++++------ src/lib/select/select.ts | 51 ++++++++++--- 6 files changed, 146 insertions(+), 45 deletions(-) diff --git a/src/cdk/overlay/overlay-directives.spec.ts b/src/cdk/overlay/overlay-directives.spec.ts index 875a0a16ecc2..855946b32c07 100644 --- a/src/cdk/overlay/overlay-directives.spec.ts +++ b/src/cdk/overlay/overlay-directives.spec.ts @@ -120,6 +120,18 @@ describe('Overlay directives', () => { 'Expected overlay to have been detached.'); }); + it('should not close when pressing escape when changed the setting of "disableClose"', () => { + fixture.componentInstance.isOpen = true; + fixture.componentInstance.disableClose = true; + fixture.detectChanges(); + + dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent!.trim()).not.toBe('', + 'Expected overlay to have not been detached.'); + }); + it('should not depend on the order in which the `origin` and `open` are set', async(() => { fixture.destroy(); @@ -479,6 +491,7 @@ describe('Overlay directives', () => { [cdkConnectedOverlayFlexibleDimensions]="flexibleDimensions" [cdkConnectedOverlayGrowAfterOpen]="growAfterOpen" [cdkConnectedOverlayPush]="push" + [cdkConnectedOverlayDisableClose]="disableClose" cdkConnectedOverlayBackdropClass="mat-test-class" (backdropClick)="backdropClickHandler($event)" [cdkConnectedOverlayOffsetX]="offsetX" @@ -511,6 +524,7 @@ class ConnectedOverlayDirectiveTest { flexibleDimensions: boolean; growAfterOpen: boolean; push: boolean; + disableClose: boolean = false; backdropClickHandler = jasmine.createSpy('backdropClick handler'); positionChangeHandler = jasmine.createSpy('positionChange handler'); keydownHandler = jasmine.createSpy('keydown handler'); diff --git a/src/cdk/overlay/overlay-directives.ts b/src/cdk/overlay/overlay-directives.ts index da50ed72485c..6d4630a6eb9c 100644 --- a/src/cdk/overlay/overlay-directives.ts +++ b/src/cdk/overlay/overlay-directives.ts @@ -114,6 +114,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { private _offsetX: number; private _offsetY: number; private _position: FlexibleConnectedPositionStrategy; + private _disableClose = false; + private _open = false; /** Origin for the connected overlay. */ @Input('cdkConnectedOverlayOrigin') origin: CdkOverlayOrigin; @@ -165,8 +167,15 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { @Input('cdkConnectedOverlayScrollStrategy') scrollStrategy: ScrollStrategy = this._scrollStrategy(); + /** Whether the overlay closes when the user pressed the Escape key. */ + @Input('cdkConnectedOverlayDisableClose') + get disableClose() { return this._disableClose; } + set disableClose(value: any) { this._disableClose = coerceBooleanProperty(value); } + /** Whether the overlay is open. */ - @Input('cdkConnectedOverlayOpen') open: boolean = false; + @Input('cdkConnectedOverlayOpen') + get open() { return this._open; } + set open(value: any) { this._open = coerceBooleanProperty(value); } /** Whether or not the overlay should attach a backdrop. */ @Input('cdkConnectedOverlayHasBackdrop') @@ -340,7 +349,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { this._overlayRef!.keydownEvents().subscribe((event: KeyboardEvent) => { this.overlayKeydown.next(event); - if (event.keyCode === ESCAPE) { + if (!this.disableClose && event.keyCode === ESCAPE) { this._detachOverlay(); } }); @@ -359,11 +368,9 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { this.attach.emit(); } - if (this.hasBackdrop) { - this._backdropSubscription = this._overlayRef.backdropClick().subscribe(event => { - this.backdropClick.emit(event); - }); - } + this._backdropSubscription = this._overlayRef.backdropClicks().subscribe(event => { + this.backdropClick.emit(event); + }); } /** Detaches the overlay and unsubscribes to backdrop clicks if backdrop exists */ diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index f55a36abcfb7..c23df3de8262 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -124,7 +124,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference { this._togglePointerEvents(true); if (this._config.hasBackdrop) { - this._attachBackdrop(); + this.attachBackdrop(); } if (this._config.panelClass) { @@ -213,6 +213,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference { return this._portalOutlet.hasAttached(); } + backdropClicks() { + return this._backdropClick; + } + /** Gets an observable that emits when the backdrop has been clicked. */ backdropClick(): Observable { return this._backdropClick.asObservable(); @@ -293,7 +297,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference { } /** Attaches a backdrop for this overlay. */ - private _attachBackdrop() { + attachBackdrop() { const showingClass = 'cdk-overlay-backdrop-showing'; this._backdropElement = this._document.createElement('div'); diff --git a/src/lib/select/select.html b/src/lib/select/select.html index f22f93087785..fb73d7a4d8f6 100644 --- a/src/lib/select/select.html +++ b/src/lib/select/select.html @@ -18,32 +18,31 @@ -
-
diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index fe29c1e208e5..611f22a6a7f9 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -333,7 +333,6 @@ describe('MatSelect', () => { it('should open a single-selection select using ALT + DOWN_ARROW', fakeAsync(() => { const {control: formControl, select: selectInstance} = fixture.componentInstance; - expect(selectInstance.panelOpen).toBe(false, 'Expected select to be closed.'); expect(formControl.value).toBeFalsy('Expected no initial value.'); @@ -435,6 +434,10 @@ describe('MatSelect', () => { expect(instance.select.panelOpen).toBe(true, 'Expected panel to be open.'); expect(instance.control.value).toBe(initialValue, 'Expected value to stay the same.'); expect(event.defaultPrevented).toBe(true, 'Expected default to be prevented.'); + + const options = + overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + expect(options.length).toEqual(8, 'Expect select has 8 options'); })); it('should open the panel when pressing a horizontal arrow key on closed multiple select', @@ -790,6 +793,10 @@ describe('MatSelect', () => { overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; })); + it('should have correct number of options', fakeAsync(() => { + expect(options.length).toEqual(8, 'Expect select has 8 options'); + })); + it('should set the role of mat-option to option', fakeAsync(() => { expect(options[0].getAttribute('role')).toEqual('option'); expect(options[1].getAttribute('role')).toEqual('option'); @@ -904,7 +911,6 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent).toEqual(''); expect(fixture.componentInstance.select.panelOpen).toBe(false); })); @@ -920,12 +926,13 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent).toEqual(''); expect(fixture.componentInstance.select.panelOpen).toBe(false); })); it('should set the width of the overlay based on the trigger', fakeAsync(() => { trigger.style.width = '200px'; + fixture.detectChanges(); + flush(); trigger.click(); fixture.detectChanges(); @@ -1084,6 +1091,7 @@ describe('MatSelect', () => { it('should be able to render options inside groups with an ng-container', fakeAsync(() => { fixture.destroy(); + flush(); const groupFixture = TestBed.createComponent(SelectWithGroupsAndNgContainer); groupFixture.detectChanges(); @@ -1343,6 +1351,7 @@ describe('MatSelect', () => { it('should not select options inside a disabled group', fakeAsync(() => { fixture.destroy(); + flush(); const groupFixture = TestBed.createComponent(SelectWithGroups); groupFixture.detectChanges(); @@ -1384,6 +1393,8 @@ describe('MatSelect', () => { it('should handle accessing `optionSelectionChanges` before the options are initialized', fakeAsync(() => { fixture.destroy(); + flush(); + fixture = TestBed.createComponent(BasicSelect); let spy = jasmine.createSpy('option selection spy'); @@ -1639,8 +1650,6 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - expect(overlayContainerElement.textContent) - .toEqual('', `Expected select panel to stay closed.`); expect(fixture.componentInstance.select.panelOpen) .toBe(false, `Expected select panelOpen property to stay false.`); @@ -1765,6 +1774,7 @@ describe('MatSelect', () => { it('should skip option group labels', fakeAsync(() => { fixture.destroy(); + flush(); const groupFixture = TestBed.createComponent(SelectWithGroups); @@ -1908,8 +1918,6 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - expect(overlayContainerElement.textContent) - .toEqual('', `Expected select panel to stay closed.`); expect(fixture.componentInstance.select.panelOpen) .toBe(false, `Expected select panelOpen property to stay false.`); @@ -1937,9 +1945,11 @@ describe('MatSelect', () => { it('should handle nesting in an ngIf', fakeAsync(() => { const fixture = TestBed.createComponent(NgIfSelect); fixture.detectChanges(); + flush(); fixture.componentInstance.isShowing = true; fixture.detectChanges(); + flush(); const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement; trigger.style.width = '300px'; @@ -2001,13 +2011,14 @@ describe('MatSelect', () => { options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + // selects[0] has 2 options, so selects[1]'s option starts at options[2] expect(selects[1].nativeElement.getAttribute('aria-owns')) - .toContain(options[0].id, `Expected aria-owns to contain IDs of its child options.`); + .toContain(options[2].id, `Expected aria-owns to contain IDs of its child options.`); expect(selects[1].nativeElement.getAttribute('aria-owns')) - .toContain(options[1].id, `Expected aria-owns to contain IDs of its child options.`); + .toContain(options[3].id, `Expected aria-owns to contain IDs of its child options.`); })); - it('should remove aria-owns when the options are not visible', fakeAsync(() => { + it('should have aria-owns when the options are not visible', fakeAsync(() => { const select = fixture.debugElement.query(By.css('mat-select')); expect(select.nativeElement.hasAttribute('aria-owns')) @@ -2020,7 +2031,7 @@ describe('MatSelect', () => { flush(); expect(select.nativeElement.hasAttribute('aria-owns')) - .toBe(false, 'Expected select not to have aria-owns when closed.'); + .toBe(true, 'Expected select to have aria-owns when closed.'); })); it('should set the option id properly', fakeAsync(() => { @@ -2044,7 +2055,7 @@ describe('MatSelect', () => { overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; expect(options[0].id) .toContain('mat-option', `Expected option ID to have the correct prefix.`); - expect(options[0].id).not.toEqual(firstOptionID, `Expected option IDs to be unique.`); + expect(options[0].id).toEqual(firstOptionID, `Expected option Id stays the same`); expect(options[0].id).not.toEqual(options[1].id, `Expected option IDs to be unique.`); })); }); @@ -2089,6 +2100,7 @@ describe('MatSelect', () => { it ('should default to global floating label type', fakeAsync(() => { fixture.destroy(); + flush(); TestBed.resetTestingModule(); TestBed.configureTestingModule({ @@ -2147,7 +2159,7 @@ describe('MatSelect', () => { describe('change events', () => { beforeEach(async(() => configureMatSelectTestingModule([SelectWithPlainTabindex]))); - it('should complete the stateChanges stream on destroy', () => { + it('should complete the stateChanges stream on destroy', fakeAsync(() => { const fixture = TestBed.createComponent(SelectWithPlainTabindex); fixture.detectChanges(); @@ -2158,9 +2170,10 @@ describe('MatSelect', () => { const subscription = select.stateChanges.subscribe(undefined, undefined, spy); fixture.destroy(); + flush(); expect(spy).toHaveBeenCalled(); subscription.unsubscribe(); - }); + })); }); describe('when initially hidden', () => { @@ -2401,6 +2414,7 @@ describe('MatSelect', () => { }; fixture.destroy(); + flush(); TestBed.resetTestingModule().configureTestingModule({ imports: [MatSelectModule, ReactiveFormsModule, FormsModule, NoopAnimationsModule], @@ -3028,6 +3042,7 @@ describe('MatSelect', () => { } fixture.destroy(); + flush(); let groupFixture = TestBed.createComponent(SelectWithGroups); groupFixture.detectChanges(); @@ -3191,10 +3206,12 @@ describe('MatSelect', () => { // Push the select to a position with not enough space on the bottom to open formField.style.bottom = '56px'; fixture.detectChanges(); + flush(); // Select an option that cannot be scrolled any farther upward fixture.componentInstance.control.setValue('coke-0'); fixture.detectChanges(); + flush(); trigger.click(); fixture.detectChanges(); @@ -3223,6 +3240,8 @@ describe('MatSelect', () => { fakeAsync(() => { // Push the select to a position with not enough space on the top to open formField.style.top = '85px'; + fixture.detectChanges(); + flush(); // Select an option that cannot be scrolled any farther downward fixture.componentInstance.control.setValue('sushi-7'); @@ -3258,6 +3277,9 @@ describe('MatSelect', () => { it('should stay within the viewport when overflowing on the left in ltr', fakeAsync(() => { formField.style.left = '-100px'; + fixture.detectChanges(); + flush(); + trigger.click(); fixture.detectChanges(); flush(); @@ -3271,6 +3293,9 @@ describe('MatSelect', () => { it('should stay within the viewport when overflowing on the left in rtl', fakeAsync(() => { dir.value = 'rtl'; formField.style.left = '-100px'; + fixture.detectChanges(); + flush(); + trigger.click(); fixture.detectChanges(); flush(); @@ -3283,6 +3308,9 @@ describe('MatSelect', () => { it('should stay within the viewport when overflowing on the right in ltr', fakeAsync(() => { formField.style.right = '-100px'; + fixture.detectChanges(); + flush(); + trigger.click(); fixture.detectChanges(); flush(); @@ -3449,6 +3477,8 @@ describe('MatSelect', () => { // Scroll the select into view setScrollTop(1400); + fixture.detectChanges(); + flush(); // In the iOS simulator (BrowserStack & SauceLabs), adding the content to the // body causes karma's iframe for the test to stretch to fit that content once we attempt to @@ -3459,12 +3489,13 @@ describe('MatSelect', () => { return; } + const triggerBottom = trigger.getBoundingClientRect().bottom; + trigger.click(); fixture.detectChanges(); flush(); const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; - const triggerBottom = trigger.getBoundingClientRect().bottom; const overlayBottom = overlayPane.getBoundingClientRect().bottom; const difference = Math.floor(overlayBottom) - Math.floor(triggerBottom); @@ -3512,6 +3543,9 @@ describe('MatSelect', () => { beforeEach(fakeAsync(() => { formField.style.position = 'fixed'; formField.style.left = '30px'; + + fixture.detectChanges(); + flush(); })); it('should align the trigger and the selected option on the x-axis in ltr', fakeAsync(() => { @@ -3560,6 +3594,9 @@ describe('MatSelect', () => { formField.style.position = 'fixed'; formField.style.left = '60px'; + + multiFixture.detectChanges(); + flush(); })); it('should adjust for the checkbox in ltr', fakeAsync(() => { @@ -3568,8 +3605,8 @@ describe('MatSelect', () => { flush(); const triggerLeft = trigger.getBoundingClientRect().left; - const firstOptionLeft = - document.querySelector('.cdk-overlay-pane mat-option')!.getBoundingClientRect().left; + const firstOptionLeft = document.querySelector('.cdk-overlay-pane mat-option.mat-active')! + .getBoundingClientRect().left; // 44px accounts for the checkbox size, margin and the panel's padding. expect(Math.floor(firstOptionLeft)) @@ -3579,13 +3616,16 @@ describe('MatSelect', () => { it('should adjust for the checkbox in rtl', fakeAsync(() => { dir.value = 'rtl'; + multiFixture.detectChanges(); + flush(); + trigger.click(); multiFixture.detectChanges(); flush(); const triggerRight = trigger.getBoundingClientRect().right; - const firstOptionRight = - document.querySelector('.cdk-overlay-pane mat-option')!.getBoundingClientRect().right; + const firstOptionRight = document.querySelector('.cdk-overlay-pane mat-option.mat-active')! + .getBoundingClientRect().right; // 44px accounts for the checkbox size, margin and the panel's padding. expect(Math.floor(firstOptionRight)) @@ -3630,6 +3670,7 @@ describe('MatSelect', () => { dir.value = 'rtl'; groupFixture.componentInstance.control.setValue('oddish-1'); groupFixture.detectChanges(); + flush(); trigger.click(); groupFixture.detectChanges(); @@ -3662,9 +3703,11 @@ describe('MatSelect', () => { expect(Math.floor(selectedOptionLeft)).toEqual(Math.floor(triggerLeft - 16)); })); - it('should align the first option to the trigger, if nothing is selected', fakeAsync(() => { + it('should align the first option to the trigger, if nothing is selected', fakeAsync(() => { // Push down the form field so there is space for the item to completely align. formField.style.top = '100px'; + groupFixture.detectChanges(); + flush(); const menuItemHeight = 48; const triggerFontSize = 16; @@ -3677,7 +3720,8 @@ describe('MatSelect', () => { const triggerTop = trigger.getBoundingClientRect().top; - const option = overlayContainerElement.querySelector('.cdk-overlay-pane mat-option'); + const option = overlayContainerElement.querySelector + ('.cdk-overlay-pane mat-option.mat-active'); const optionTop = option ? option.getBoundingClientRect().top : 0; // There appears to be a small rounding error on IE, so we verify that the value is close, diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index cd11b499f569..7411a5a3360f 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -14,6 +14,7 @@ import { DOWN_ARROW, END, ENTER, + ESCAPE, HOME, LEFT_ARROW, RIGHT_ARROW, @@ -195,7 +196,7 @@ export class MatSelectTrigger {} '[attr.aria-required]': 'required.toString()', '[attr.aria-disabled]': 'disabled.toString()', '[attr.aria-invalid]': 'errorState', - '[attr.aria-owns]': 'panelOpen ? _optionIds : null', + '[attr.aria-owns]': '_optionIds', '[attr.aria-multiselectable]': 'multiple', '[attr.aria-describedby]': '_ariaDescribedby || null', '[attr.aria-activedescendant]': '_getAriaActiveDescendant()', @@ -560,25 +561,32 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, // Note: The computed font-size will be a string pixel value (e.g. "16px"). // `parseInt` ignores the trailing 'px' and converts this to a number. this._triggerFontSize = parseInt(getComputedStyle(this.trigger.nativeElement)['font-size']); + if (this._triggerFontSize && this.overlayDir.overlayRef && + this.overlayDir.overlayRef.overlayElement) { + this.overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`; + } this._panelOpen = true; this._keyManager.withHorizontalOrientation(null); this._calculateOverlayPosition(); this._highlightCorrectOption(); - this._changeDetectorRef.markForCheck(); - // Set the font size on the panel element once it exists. - this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => { - if (this._triggerFontSize && this.overlayDir.overlayRef && - this.overlayDir.overlayRef.overlayElement) { - this.overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`; - } - }); + this.overlayDir.overlayRef.updateSize({minWidth: `${this._triggerRect.width}px`}); + if (this._dir) { + this.overlayDir.overlayRef.setDirection(this._dir); + } + this.overlayDir.overlayRef.updatePosition(); + + this._changeDetectorRef.detectChanges(); + this._calculateOverlayOffsetX(); + this.panel.nativeElement.scrollTop = this._scrollTop; + this._showBackdrop(); } /** Closes the overlay panel and focuses the host element. */ close(): void { if (this._panelOpen) { + this._hideBackdrop(); this._panelOpen = false; this._keyManager.withHorizontalOrientation(this._isRtl() ? 'rtl' : 'ltr'); this._changeDetectorRef.markForCheck(); @@ -662,6 +670,12 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, return this._selectionModel.selected[0].viewValue; } + /** The animation transform for overlay panel. */ + get _transformPanel() { + return this.panelOpen ? (this.multiple ? 'showing-multiple' : 'showing') : 'void'; + } + + /** Whether the element is in RTL mode. */ _isRtl(): boolean { return this._dir ? this._dir.value === 'rtl' : false; @@ -710,6 +724,9 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, event.preventDefault(); const hasDeselectedOptions = this.options.some(option => !option.selected); this.options.forEach(option => hasDeselectedOptions ? option.select() : option.deselect()); + } else if (keyCode === ESCAPE) { + event.preventDefault(); + this.close(); } else { const previouslyFocusedIndex = manager.activeItemIndex; @@ -773,6 +790,22 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, return !this._selectionModel || this._selectionModel.isEmpty(); } + /** Show the backdrop of the overlay. */ + private _showBackdrop() { + if (this.overlayDir.overlayRef.backdropElement) { + this.overlayDir.overlayRef.backdropElement.style.display = 'block'; + } else { + this.overlayDir.overlayRef.attachBackdrop(); + } + } + + /** Hide the backdrop of the overlay. */ + private _hideBackdrop() { + if (this.overlayDir.overlayRef.backdropElement) { + this.overlayDir.overlayRef.backdropElement.style.display = 'none'; + } + } + private _initializeSelection(): void { // Defer setting the value in order to avoid the "Expression // has changed after it was checked" errors from Angular. From b5ee8318818c1e87f8c2066fda1739d5aa2e88be Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Thu, 12 Jul 2018 15:32:30 -0700 Subject: [PATCH 2/2] Add hidden property to cdkOverlay directive --- package-lock.json | 334 +++++++++++++++----------- src/cdk/overlay/overlay-directives.ts | 59 ++++- src/lib/select/select.html | 2 +- 3 files changed, 246 insertions(+), 149 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85c073011a09..8d02ddae2397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,14 @@ "chokidar": "^2.0.3", "rxjs": "^6.0.0", "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "@angular-devkit/schematics": { @@ -1125,9 +1133,9 @@ } }, "@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.0.tgz", + "integrity": "sha512-sWj7AMiG0fYmta6ug1ublLjtj/tqn+CnCZeo7yswR1ykxel0FOWFGdWviTcGSNAMmtLbycDqbg6w98VPFKJmbw==", "dev": true }, "@types/request": { @@ -2669,6 +2677,14 @@ "dev": true, "requires": { "source-map": "0.5.x" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "cli-boxes": { @@ -2819,6 +2835,14 @@ "semver-dsl": "^1.0.1", "source-map": "^0.5.7", "sprintf-js": "^1.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "collapse-white-space": { @@ -4363,6 +4387,23 @@ "stringmap": "^0.2.2", "typescript": "~2.7.1", "urlencode": "^1.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + } } }, "di": { @@ -4451,9 +4492,9 @@ } }, "domino": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/domino/-/domino-2.0.2.tgz", - "integrity": "sha512-vzykUakUw5s1p0RrN/vI2sShYo3pLRy/z7PM1PuOIZIlMOJ0XfOnrckGE5f4MxIQVe5XcrH7yG9mR+l77mgLVA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/domino/-/domino-2.0.3.tgz", + "integrity": "sha512-QkW2THVtKJw9FmV6awFQbcpaJPIqQtF+F1PMO5EXIdULVit9IaU3w+ZQgBjrR6hSHgP97TKyo/tcFqkgwfYenA==", "dev": true }, "domutils": { @@ -4573,9 +4614,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.51", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.51.tgz", - "integrity": "sha1-akK0nar38ipbN7mR2vlJ8029ubU=", + "version": "1.3.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz", + "integrity": "sha1-0tnxJwuko7lnuDHEDvcftNmrXOA=", "dev": true }, "empower": { @@ -4836,13 +4877,6 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true } } }, @@ -5403,9 +5437,9 @@ "dev": true }, "filing-cabinet": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-1.14.2.tgz", - "integrity": "sha512-je+ZSQiyJ7SfcK6PTHUkeq06CtaeYrcgwjXNJ6Mb+m+1aGa/8wOxi11iSXZukKPCCn3Bb9RBULqSaHEFoEFVvg==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-1.14.3.tgz", + "integrity": "sha512-UIPZII8rPTUDRmLNCXbu/326xK13k3OK2CW0aA7gI1POSYdwCeBEi4FwD6i3NUN0ZS3dFFkJDYRYyv9+HkPI/w==", "dev": true, "requires": { "app-module-path": "^2.2.0", @@ -5417,7 +5451,7 @@ "module-lookup-amd": "^5.0.1", "resolve": "^1.5.0", "resolve-dependency-path": "^1.0.2", - "sass-lookup": "^1.1.0", + "sass-lookup": "^2.0.0", "stylus-lookup": "^2.0.0", "typescript": "^2.4.2" }, @@ -6808,9 +6842,9 @@ } }, "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, "get-own-enumerable-property-symbols": { @@ -9619,12 +9653,6 @@ "uglify-js": "3.4.x" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "uglify-js": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.4.tgz", @@ -10643,9 +10671,9 @@ } }, "js-base64": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz", - "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.6.tgz", + "integrity": "sha512-O9SR2NVICx6rCqh1qsU91QZ5IoNa+2T1ROJ0OQlfvATKGmnjsAvg3r0E5ufPZ4a95jdKTPXhFWiE/sOZ7a5Rtg==", "dev": true }, "js-tokens": { @@ -10950,12 +10978,6 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -11018,6 +11040,12 @@ "get-stdin": "^4.0.1", "meow": "^3.3.0" } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -11584,9 +11612,9 @@ } }, "log4js": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.10.0.tgz", - "integrity": "sha512-NnhN9PjFF9zhxinAjlmDYvkqqrIW+yA3LLJAoTJ3fs6d1zru86OqQHfsxiUcc1kRq3z+faGR4DeyXUfiNbVxKQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.11.0.tgz", + "integrity": "sha512-z1XdwyGFg8/WGkOyF6DPJjivCWNLKrklGdViywdYnSKOvgtEBo2UyEMZS5sD2mZrQlU3TvO8wDWLc8mzE1ncBQ==", "dev": true, "requires": { "amqplib": "^0.5.2", @@ -12472,9 +12500,9 @@ "dev": true }, "mimic-response": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", - "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, "minimatch": { @@ -13695,6 +13723,17 @@ "requires": { "ms": "2.0.0" } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", + "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + } } } }, @@ -14108,6 +14147,14 @@ "js-base64": "^2.1.9", "source-map": "^0.5.6", "supports-color": "^3.2.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "postcss-html": { @@ -14194,12 +14241,6 @@ "supports-color": "^5.4.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -14263,12 +14304,6 @@ "supports-color": "^5.4.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -14342,12 +14377,6 @@ "supports-color": "^5.4.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -14405,12 +14434,6 @@ "supports-color": "^5.4.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -14800,12 +14823,33 @@ "integrity": "sha512-5ViC9dwf1VIAtrOFTvOuN04lJgw28eKjuy0Vg2Bd/fSlxKP2feCSkIw04ZgOENL2ywdWrtbkthp1XVLEjJmouw==", "dev": true }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, "adm-zip": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", "dev": true }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, "webdriver-manager": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.0.tgz", @@ -14839,9 +14883,9 @@ } }, "proxy-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.0.0.tgz", - "integrity": "sha512-g6n6vnk8fRf705ShN+FEXFG/SDJaW++lSs0d9KaJh4uBWW/wi7en4Cpo5VYQW3SZzAE121lhB/KLQrbURoubZw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.0.1.tgz", + "integrity": "sha512-mAZexaz9ZxQhYPWfAjzlrloEjW+JHiBFryE4AJXFDTnaXfmH/FKqC1swTRKuEPbHWz02flQNXFOyDUF7zfEG6A==", "dev": true, "optional": true, "requires": { @@ -14852,7 +14896,7 @@ "lru-cache": "^4.1.2", "pac-proxy-agent": "^2.0.1", "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^3.0.0" + "socks-proxy-agent": "^4.0.1" }, "dependencies": { "debug": { @@ -15834,24 +15878,12 @@ } }, "sass-lookup": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-1.1.0.tgz", - "integrity": "sha1-2kSiG+6llV8U7/24G97idRttFeI=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-2.0.0.tgz", + "integrity": "sha512-DZEg7g605XNZX3rxQMkndPmlSzaGR3ld33Rvx3XPTxP8hXBPErmCTrL2CPItzjCJqvjgt9kXhxQrzkbdJZToaA==", "dev": true, "requires": { - "commander": "~2.8.1", - "is-relative-path": "~1.0.0" - }, - "dependencies": { - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - } + "commander": "^2.16.0" } }, "sauce-connect-launcher": { @@ -16309,7 +16341,8 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", - "dev": true + "dev": true, + "optional": true }, "smtp-connection": { "version": "2.12.0", @@ -16369,6 +16402,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -16526,19 +16565,41 @@ "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", "dev": true, + "optional": true, "requires": { "ip": "^1.1.4", "smart-buffer": "^1.0.13" } }, "socks-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", - "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", + "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", "dev": true, + "optional": true, "requires": { - "agent-base": "^4.1.0", - "socks": "^1.1.10" + "agent-base": "~4.2.0", + "socks": "~2.2.0" + }, + "dependencies": { + "smart-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", + "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==", + "dev": true, + "optional": true + }, + "socks": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz", + "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==", + "dev": true, + "optional": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" + } + } } }, "sorcery": { @@ -16564,10 +16625,9 @@ } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.2", @@ -16583,12 +16643,12 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", "requires": { - "source-map": "^0.5.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "source-map-url": { @@ -17307,12 +17367,6 @@ "strip-indent": "^2.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -17428,12 +17482,6 @@ "supports-color": "^5.4.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -18213,6 +18261,21 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -18279,22 +18342,6 @@ "mkdirp": "^0.5.1", "source-map": "^0.6.0", "source-map-support": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", - "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } } }, "tslib": { @@ -18466,6 +18513,12 @@ "wordwrap": "0.0.2" } }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -19039,13 +19092,10 @@ } }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "user-home": { "version": "2.0.0", @@ -19323,6 +19373,14 @@ "dev": true, "requires": { "source-map": "^0.5.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "vlq": { diff --git a/src/cdk/overlay/overlay-directives.ts b/src/cdk/overlay/overlay-directives.ts index 6d4630a6eb9c..037f48739346 100644 --- a/src/cdk/overlay/overlay-directives.ts +++ b/src/cdk/overlay/overlay-directives.ts @@ -100,22 +100,24 @@ export class CdkOverlayOrigin { */ @Directive({ selector: '[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]', - exportAs: 'cdkConnectedOverlay' + exportAs: 'cdkConnectedOverlay', }) export class CdkConnectedOverlay implements OnDestroy, OnChanges { + private readonly _templatePortal: TemplatePortal; private _overlayRef: OverlayRef; - private _templatePortal: TemplatePortal; private _hasBackdrop = false; private _lockPosition = false; private _growAfterOpen = false; private _flexibleDimensions = false; private _push = false; private _backdropSubscription = Subscription.EMPTY; + private _keydownSubscription: Subscription | null = null; private _offsetX: number; private _offsetY: number; private _position: FlexibleConnectedPositionStrategy; private _disableClose = false; private _open = false; + private _hidden = false; /** Origin for the connected overlay. */ @Input('cdkConnectedOverlayOrigin') origin: CdkOverlayOrigin; @@ -176,6 +178,15 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { @Input('cdkConnectedOverlayOpen') get open() { return this._open; } set open(value: any) { this._open = coerceBooleanProperty(value); } + + /** Whether the overlay is hidden, but still in the DOM. */ + @Input('cdkConnectedOverlayHidden') + get hidden() { return this._hidden; } + set hidden(value: any) { + this._hidden = coerceBooleanProperty(value); + this._updateKeydownSubscription(); + this._updateOverlayElementVisibility(); + } /** Whether or not the overlay should attach a backdrop. */ @Input('cdkConnectedOverlayHasBackdrop') @@ -345,14 +356,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { private _attachOverlay() { if (!this._overlayRef) { this._createOverlay(); - - this._overlayRef!.keydownEvents().subscribe((event: KeyboardEvent) => { - this.overlayKeydown.next(event); - - if (!this.disableClose && event.keyCode === ESCAPE) { - this._detachOverlay(); - } - }); + this._updateKeydownSubscription(); + this._updateOverlayElementVisibility(); } else { // Update the overlay size, in case the directive's inputs have changed this._overlayRef.updateSize({ @@ -373,6 +378,37 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { }); } + /** + * Sets up keydown handlers if the overlay is attached and visible, or removes keydown + * handlers if the overlay is detached or hidden. + */ + private _updateKeydownSubscription() { + if (!this._hidden && this._overlayRef && !this._keydownSubscription) { + this._keydownSubscription = this._overlayRef!.keydownEvents().subscribe(event => { + this.overlayKeydown.next(event); + + if (!this.disableClose && event.keyCode === ESCAPE) { + this._detachOverlay(); + } + }); + } else if (this._keydownSubscription) { + this._keydownSubscription.unsubscribe(); + this._keydownSubscription = null; + } + } + + /** + * Updates the visibility of the overlay element by adding or removing the `cdk-visually-hidden` + * css class based on the `hidden` property of this directive. + */ + private _updateOverlayElementVisibility() { + if (this._overlayRef && this._overlayRef.hasAttached() && this._overlayRef.overlayElement) { + const classList = this._overlayRef.overlayElement.classList; + const hiddenClass = 'cdk-visually-hidden'; + this._hidden ? classList.add(hiddenClass) : classList.remove(hiddenClass); + } + } + /** Detaches the overlay and unsubscribes to backdrop clicks if backdrop exists */ private _detachOverlay() { if (this._overlayRef) { @@ -390,6 +426,9 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { } this._backdropSubscription.unsubscribe(); + if (this._keydownSubscription) { + this._keydownSubscription.unsubscribe(); + } } } diff --git a/src/lib/select/select.html b/src/lib/select/select.html index fb73d7a4d8f6..e476faf6988b 100644 --- a/src/lib/select/select.html +++ b/src/lib/select/select.html @@ -26,13 +26,13 @@ [cdkConnectedOverlayPositions]="_positions" [cdkConnectedOverlayMinWidth]="_triggerRect?.width" [cdkConnectedOverlayOffsetY]="_offsetY" + [cdkConnectedOverlayHidden]="!panelOpen" (backdropClick)="close()" (attach)="_onAttached()" (detach)="close()">