From babd9de95be9f9b8ddf029dc1aea53ea18cdd914 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 3 Aug 2021 22:50:08 +0200 Subject: [PATCH] refactor(cdk/overlay): remove deprecated connected position strategy The `ConnectedPositionStrategy` has been deprecated since at least 7.0.0 and is just a wrapper around `FlexibleConnectedPositionStrategy`. These changes remove it since it still adds a fair bit of code that has to be maintained. BREAKING CHANGE: * `ConnectedPositionStrategy` has been removed. Use `FlexibleConnectedPositionStrategy` instead. * `OverlayPositionBuilder.connectedTo` has been removed. Use `OverlayPositionBuilder.flexibleConnectedTo` instead. --- .../connected-position-strategy.spec.ts | 819 ------------------ .../position/connected-position-strategy.ts | 206 ----- .../position/overlay-position-builder.ts | 23 +- src/cdk/overlay/public-api.ts | 1 - tools/public_api_guard/cdk/overlay.md | 24 - 5 files changed, 1 insertion(+), 1072 deletions(-) delete mode 100644 src/cdk/overlay/position/connected-position-strategy.spec.ts delete mode 100644 src/cdk/overlay/position/connected-position-strategy.ts diff --git a/src/cdk/overlay/position/connected-position-strategy.spec.ts b/src/cdk/overlay/position/connected-position-strategy.spec.ts deleted file mode 100644 index 0f9a23bd09bf..000000000000 --- a/src/cdk/overlay/position/connected-position-strategy.spec.ts +++ /dev/null @@ -1,819 +0,0 @@ -import {ComponentPortal, PortalModule} from '@angular/cdk/portal'; -import {CdkScrollable, ScrollingModule} from '@angular/cdk/scrolling'; -import {MockNgZone} from '../../testing/private'; -import {Component, ElementRef, NgModule, NgZone} from '@angular/core'; -import {inject, TestBed} from '@angular/core/testing'; -import {Subscription} from 'rxjs'; -import { - ConnectedOverlayPositionChange, - ConnectedPositionStrategy, - ConnectionPositionPair, - Overlay, - OverlayContainer, - OverlayModule, - OverlayRef, -} from '../index'; - - -// Default width and height of the overlay and origin panels throughout these tests. -const DEFAULT_HEIGHT = 30; -const DEFAULT_WIDTH = 60; - -// For all tests, we assume the browser window is 1024x786 (outerWidth x outerHeight). -// The karma config has been set to this for local tests, and it is the default size -// for tests on CI (both SauceLabs and Browserstack). - -describe('ConnectedPositionStrategy', () => { - let overlay: Overlay; - let overlayContainer: OverlayContainer; - let zone: MockNgZone; - let overlayRef: OverlayRef; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ScrollingModule, OverlayModule, OverlayTestModule], - providers: [{provide: NgZone, useFactory: () => zone = new MockNgZone()}] - }); - - inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => { - overlay = o; - overlayContainer = oc; - })(); - }); - - afterEach(() => { - overlayContainer.ngOnDestroy(); - - if (overlayRef) { - overlayRef.dispose(); - } - }); - - function attachOverlay(positionStrategy: ConnectedPositionStrategy) { - overlayRef = overlay.create({positionStrategy}); - overlayRef.attach(new ComponentPortal(TestOverlay)); - zone.simulateZoneExit(); - } - - describe('with origin on document body', () => { - const ORIGIN_HEIGHT = DEFAULT_HEIGHT; - const ORIGIN_WIDTH = DEFAULT_WIDTH; - const OVERLAY_HEIGHT = DEFAULT_HEIGHT; - const OVERLAY_WIDTH = DEFAULT_WIDTH; - - let originElement: HTMLElement; - let positionStrategy: ConnectedPositionStrategy; - let fakeElementRef: ElementRef; - - let originRect: ClientRect | null; - let originCenterX: number | null; - let originCenterY: number | null; - - beforeEach(() => { - // The origin and overlay elements need to be in the document body in order to have geometry. - originElement = createPositionedBlockElement(); - document.body.appendChild(originElement); - fakeElementRef = new ElementRef(originElement); - }); - - afterEach(() => { - document.body.removeChild(originElement); - - // Reset the origin geometry after each test so we don't accidently keep state between tests. - originRect = null; - originCenterX = null; - originCenterY = null; - }); - - describe('when not near viewport edge, not scrolled', () => { - // Place the original element close to the center of the window. - // (1024 / 2, 768 / 2). It's not exact, since outerWidth/Height includes browser - // chrome, but it doesn't really matter for these tests. - const ORIGIN_LEFT = 500; - const ORIGIN_TOP = 350; - - beforeEach(() => { - originElement.style.left = `${ORIGIN_LEFT}px`; - originElement.style.top = `${ORIGIN_TOP}px`; - - originRect = originElement.getBoundingClientRect(); - originCenterX = originRect.left + (ORIGIN_WIDTH / 2); - originCenterY = originRect.top + (ORIGIN_HEIGHT / 2); - }); - - // Preconditions are set, now just run the full set of simple position tests. - runSimplePositionTests(); - }); - - describe('when scrolled', () => { - // Place the original element decently far outside the unscrolled document (1024x768). - const ORIGIN_LEFT = 2500; - const ORIGIN_TOP = 2500; - - // Create a very large element that will make the page scrollable. - let veryLargeElement: HTMLElement = document.createElement('div'); - veryLargeElement.style.width = '4000px'; - veryLargeElement.style.height = '4000px'; - - beforeEach(() => { - // Scroll the page such that the origin element is roughly in the - // center of the visible viewport (2500 - 1024/2, 2500 - 768/2). - document.body.appendChild(veryLargeElement); - document.body.scrollTop = 2100; - document.body.scrollLeft = 2100; - - originElement.style.top = `${ORIGIN_TOP}px`; - originElement.style.left = `${ORIGIN_LEFT}px`; - - originRect = originElement.getBoundingClientRect(); - originCenterX = originRect.left + (ORIGIN_WIDTH / 2); - originCenterY = originRect.top + (ORIGIN_HEIGHT / 2); - }); - - afterEach(() => { - document.body.removeChild(veryLargeElement); - document.body.scrollTop = 0; - document.body.scrollLeft = 0; - }); - - // Preconditions are set, now just run the full set of simple position tests. - runSimplePositionTests(); - }); - - describe('when near viewport edge', () => { - it('should reposition the overlay if it would go off the top of the screen', () => { - // We can use the real ViewportRuler in this test since we know that zero is - // always the top of the viewport. - - originElement.style.top = '5px'; - originElement.style.left = '200px'; - originRect = originElement.getBoundingClientRect(); - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'top'}, - {overlayX: 'end', overlayY: 'bottom'}) - .withFallbackPosition( - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); - }); - - it('should reposition the overlay if it would go off the left of the screen', () => { - // We can use the real ViewportRuler in this test since we know that zero is - // always the left edge of the viewport. - - originElement.style.top = '200px'; - originElement.style.left = '5px'; - originRect = originElement.getBoundingClientRect(); - originCenterY = originRect.top + (ORIGIN_HEIGHT / 2); - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'end', overlayY: 'top'}) - .withFallbackPosition( - {originX: 'end', originY: 'center'}, - {overlayX: 'start', overlayY: 'center'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY - (OVERLAY_HEIGHT / 2))); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.right)); - }); - - it('should reposition the overlay if it would go off the bottom of the screen', () => { - originElement.style.bottom = '25px'; - originElement.style.left = '200px'; - originRect = originElement.getBoundingClientRect(); - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}) - .withFallbackPosition( - {originX: 'end', originY: 'top'}, - {overlayX: 'end', overlayY: 'bottom'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top)); - expect(Math.floor(overlayRect.right)).toBe(Math.floor(originRect.right)); - }); - - it('should reposition the overlay if it would go off the right of the screen', () => { - originElement.style.top = '200px'; - originElement.style.right = '25px'; - originRect = originElement.getBoundingClientRect(); - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'center'}, - {overlayX: 'start', overlayY: 'center'}) - .withFallbackPosition( - {originX: 'start', originY: 'bottom'}, - {overlayX: 'end', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); - expect(Math.floor(overlayRect.right)).toBe(Math.floor(originRect.left)); - }); - - it('should recalculate and set the last position with recalculateLastPosition()', () => { - // Push the trigger down so the overlay doesn't have room to open on the bottom. - originElement.style.bottom = '25px'; - originRect = originElement.getBoundingClientRect(); - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}) - .withFallbackPosition( - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'bottom'}); - - // This should apply the fallback position, as the original position won't fit. - attachOverlay(positionStrategy); - - // Now make the overlay small enough to fit in the first preferred position. - overlayRef.overlayElement.style.height = '15px'; - - // This should only re-align in the last position, even though the first would fit. - positionStrategy.recalculateLastPosition(); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top), - 'Expected overlay to be re-aligned to the trigger in the previous position.'); - }); - - it('should default to the initial position, if no positions fit in the viewport', () => { - // Make the origin element taller than the viewport. - originElement.style.height = '1000px'; - originElement.style.top = '0'; - originRect = originElement.getBoundingClientRect(); - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'bottom'}); - - attachOverlay(positionStrategy); - positionStrategy.recalculateLastPosition(); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - - expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top), - 'Expected overlay to be re-aligned to the trigger in the initial position.'); - }); - - it('should position a panel properly when rtl', () => { - // must make the overlay longer than the origin to properly test attachment - originRect = originElement.getBoundingClientRect(); - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}) - .withDirection('rtl'); - - attachOverlay(positionStrategy); - overlayRef.overlayElement.style.width = `500px`; - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); - expect(Math.floor(overlayRect.right)).toBe(Math.floor(originRect.right)); - }); - - it('should position a panel with the x offset provided', () => { - originRect = originElement.getBoundingClientRect(); - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'top'}); - - positionStrategy.withOffsetX(10); - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left + 10)); - }); - - it('should position a panel with the y offset provided', () => { - originRect = originElement.getBoundingClientRect(); - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'top'}); - - positionStrategy.withOffsetY(50); - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top + 50)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); - }); - - it('should allow for the fallback positions to specify their own offsets', () => { - originElement.style.bottom = '0'; - originElement.style.left = '50%'; - originElement.style.position = 'fixed'; - originRect = originElement.getBoundingClientRect(); - positionStrategy = overlay.position() - .connectedTo( - fakeElementRef, - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'top'}) - .withFallbackPosition( - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'bottom'}, - -100, -100); - - positionStrategy.withOffsetY(50).withOffsetY(50); - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.bottom)).toBe(Math.floor(originRect.top - 100)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left - 100)); - }); - - }); - - it('should emit onPositionChange event when position changes', () => { - originElement.style.top = '200px'; - originElement.style.right = '25px'; - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'center'}, - {overlayX: 'start', overlayY: 'center'}) - .withFallbackPosition( - {originX: 'start', originY: 'bottom'}, - {overlayX: 'end', overlayY: 'top'}); - - const positionChangeHandler = jasmine.createSpy('positionChangeHandler'); - const subscription = positionStrategy.onPositionChange.subscribe(positionChangeHandler); - - attachOverlay(positionStrategy); - - const latestCall = positionChangeHandler.calls.mostRecent(); - - expect(positionChangeHandler).toHaveBeenCalled(); - expect(latestCall.args[0] instanceof ConnectedOverlayPositionChange) - .toBe(true, `Expected strategy to emit an instance of ConnectedOverlayPositionChange.`); - - // If the strategy is re-applied and the initial position would now fit, - // the position change event should be emitted again. - originElement.style.top = '200px'; - originElement.style.left = '200px'; - - positionStrategy.apply(); - - expect(positionChangeHandler).toHaveBeenCalledTimes(2); - - subscription.unsubscribe(); - }); - - it('should emit the onPositionChange event even if none of the positions fit', () => { - originElement.style.bottom = '25px'; - originElement.style.right = '25px'; - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}) - .withFallbackPosition( - {originX: 'start', originY: 'bottom'}, - {overlayX: 'end', overlayY: 'top'}); - - const positionChangeHandler = jasmine.createSpy('positionChangeHandler'); - const subscription = positionStrategy.onPositionChange.subscribe(positionChangeHandler); - - attachOverlay(positionStrategy); - - expect(positionChangeHandler).toHaveBeenCalled(); - - subscription.unsubscribe(); - }); - - it('should complete the onPositionChange stream on dispose', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}); - - const completeHandler = jasmine.createSpy('complete handler'); - - positionStrategy.onPositionChange.subscribe({complete: completeHandler}); - attachOverlay(positionStrategy); - positionStrategy.dispose(); - - expect(completeHandler).toHaveBeenCalled(); - }); - - it('should pick the fallback position that shows the largest area of the element', () => { - originElement.style.top = '200px'; - originElement.style.right = '25px'; - originRect = originElement.getBoundingClientRect(); - - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'center'}, - {overlayX: 'start', overlayY: 'center'}) - .withFallbackPosition( - {originX: 'end', originY: 'top'}, - {overlayX: 'start', overlayY: 'bottom'}) - .withFallbackPosition( - {originX: 'end', originY: 'top'}, - {overlayX: 'end', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); - }); - - it('should re-use the preferred position when re-applying while locked in', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'center'}, - {overlayX: 'start', overlayY: 'center'}) - .withLockedPosition(true) - .withFallbackPosition( - {originX: 'start', originY: 'bottom'}, - {overlayX: 'end', overlayY: 'top'}); - - const recalcSpy = spyOn(positionStrategy._positionStrategy, 'reapplyLastPosition'); - - attachOverlay(positionStrategy); - - expect(recalcSpy).not.toHaveBeenCalled(); - - positionStrategy.apply(); - - expect(recalcSpy).toHaveBeenCalled(); - }); - - /** - * Run all tests for connecting the overlay to the origin such that first preferred - * position does not go off-screen. We do this because there are several cases where we - * want to run the exact same tests with different preconditions (e.g., not scroll, scrolled, - * different element sized, etc.). - */ - function runSimplePositionTests() { - it('should position a panel below, left-aligned', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.left)); - }); - - it('should position to the right, center aligned vertically', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'center'}, - {overlayX: 'start', overlayY: 'center'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY! - (OVERLAY_HEIGHT / 2))); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.right)); - }); - - it('should position to the left, below', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'end', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); - expect(Math.round(overlayRect.right)).toBe(Math.round(originRect!.left)); - }); - - it('should position above, right aligned', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'top'}, - {overlayX: 'end', overlayY: 'bottom'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.round(overlayRect.bottom)).toBe(Math.round(originRect!.top)); - expect(Math.round(overlayRect.right)).toBe(Math.round(originRect!.right)); - }); - - it('should position below, centered', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'center', originY: 'bottom'}, - {overlayX: 'center', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originCenterX! - (OVERLAY_WIDTH / 2))); - }); - - it('should center the overlay on the origin', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'center', originY: 'center'}, - {overlayX: 'center', overlayY: 'center'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.top)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.left)); - }); - - it('should allow for the positions to be updated after init', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - let overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.left)); - - positionStrategy.withPositions([new ConnectionPositionPair( - {originX: 'start', originY: 'bottom'}, - {overlayX: 'end', overlayY: 'top'} - )]); - - positionStrategy.apply(); - - overlayRect = overlayRef.overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); - expect(Math.floor(overlayRect.right)).toBe(Math.floor(originRect!.left)); - }); - - } - }); - - describe('onPositionChange with scrollable view properties', () => { - let scrollable: HTMLDivElement; - let positionChangeHandler: jasmine.Spy; - let onPositionChangeSubscription: Subscription; - let positionChange: ConnectedOverlayPositionChange; - let fakeElementRef: ElementRef; - let positionStrategy: ConnectedPositionStrategy; - - beforeEach(() => { - // Set up the origin - let originElement = createBlockElement(); - originElement.style.margin = '0 1000px 1000px 0'; // Added so that the container scrolls - - // Create a scrollable container and put the origin inside - scrollable = createOverflowContainerElement(); - document.body.appendChild(scrollable); - scrollable.appendChild(originElement); - - // Create a strategy with knowledge of the scrollable container - fakeElementRef = new ElementRef(originElement); - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'top'}); - - positionStrategy.withScrollableContainers([ - new CdkScrollable(new ElementRef(scrollable), null!, null!)]); - positionChangeHandler = jasmine.createSpy('positionChangeHandler'); - onPositionChangeSubscription = - positionStrategy.onPositionChange.subscribe(positionChangeHandler); - attachOverlay(positionStrategy); - }); - - afterEach(() => { - onPositionChangeSubscription.unsubscribe(); - document.body.removeChild(scrollable); - }); - - it('should not have origin or overlay clipped or out of view without scroll', () => { - expect(positionChangeHandler).toHaveBeenCalled(); - positionChange = positionChangeHandler.calls.mostRecent().args[0]; - expect(positionChange.scrollableViewProperties).toEqual({ - isOriginClipped: false, - isOriginOutsideView: false, - isOverlayClipped: false, - isOverlayOutsideView: false - }); - }); - - it('should evaluate if origin is clipped if scrolled slightly down', () => { - scrollable.scrollTop = 10; // Clip the origin by 10 pixels - positionStrategy.apply(); - - expect(positionChangeHandler).toHaveBeenCalled(); - positionChange = positionChangeHandler.calls.mostRecent().args[0]; - expect(positionChange.scrollableViewProperties).toEqual({ - isOriginClipped: true, - isOriginOutsideView: false, - isOverlayClipped: false, - isOverlayOutsideView: false - }); - }); - - it('should evaluate if origin is out of view and overlay is clipped if scrolled enough', () => { - scrollable.scrollTop = 31; // Origin is 30 pixels, move out of view and clip the overlay 1px - positionStrategy.apply(); - - expect(positionChangeHandler).toHaveBeenCalled(); - positionChange = positionChangeHandler.calls.mostRecent().args[0]; - expect(positionChange.scrollableViewProperties).toEqual({ - isOriginClipped: true, - isOriginOutsideView: true, - isOverlayClipped: true, - isOverlayOutsideView: false - }); - }); - - it('should evaluate the overlay and origin are both out of the view', () => { - scrollable.scrollTop = 61; // Scroll by overlay height + origin height + 1px buffer - positionStrategy.apply(); - - expect(positionChangeHandler).toHaveBeenCalled(); - positionChange = positionChangeHandler.calls.mostRecent().args[0]; - expect(positionChange.scrollableViewProperties).toEqual({ - isOriginClipped: true, - isOriginOutsideView: true, - isOverlayClipped: true, - isOverlayOutsideView: true - }); - }); - }); - - describe('positioning properties', () => { - let originElement: HTMLElement; - let positionStrategy: ConnectedPositionStrategy; - let fakeElementRef: ElementRef; - - beforeEach(() => { - // The origin and overlay elements need to be in the document body in order to have geometry. - originElement = createPositionedBlockElement(); - document.body.appendChild(originElement); - fakeElementRef = new ElementRef(originElement); - }); - - afterEach(() => { - document.body.removeChild(originElement); - }); - - describe('in ltr', () => { - it('should use `left` when positioning an element at the start', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'top'}); - - - attachOverlay(positionStrategy); - - expect(overlayRef.overlayElement.style.left).toBeTruthy(); - expect(overlayRef.overlayElement.style.right).toBeFalsy(); - }); - - it('should use `right` when positioning an element at the end', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'top'}, - {overlayX: 'end', overlayY: 'top'}); - - attachOverlay(positionStrategy); - - expect(overlayRef.overlayElement.style.right).toBeTruthy(); - expect(overlayRef.overlayElement.style.left).toBeFalsy(); - }); - - }); - - describe('in rtl', () => { - it('should use `right` when positioning an element at the start', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'top'} - ) - .withDirection('rtl'); - - attachOverlay(positionStrategy); - - expect(overlayRef.overlayElement.style.right).toBeTruthy(); - expect(overlayRef.overlayElement.style.left).toBeFalsy(); - }); - - it('should use `left` when positioning an element at the end', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'end', originY: 'top'}, - {overlayX: 'end', overlayY: 'top'} - ).withDirection('rtl'); - - attachOverlay(positionStrategy); - - expect(overlayRef.overlayElement.style.left).toBeTruthy(); - expect(overlayRef.overlayElement.style.right).toBeFalsy(); - }); - }); - - describe('vertical', () => { - it('should use `top` when positioning at element along the top', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'top'}, - {overlayX: 'start', overlayY: 'top'} - ); - - attachOverlay(positionStrategy); - - expect(overlayRef.overlayElement.style.top).toBeTruthy(); - expect(overlayRef.overlayElement.style.bottom).toBeFalsy(); - }); - - it('should use `bottom` when positioning at element along the bottom', () => { - positionStrategy = overlay.position().connectedTo( - fakeElementRef, - {originX: 'start', originY: 'bottom'}, - {overlayX: 'start', overlayY: 'bottom'} - ); - - attachOverlay(positionStrategy); - - expect(overlayRef.overlayElement.style.bottom).toBeTruthy(); - expect(overlayRef.overlayElement.style.top).toBeFalsy(); - }); - }); - - }); - -}); - -/** Creates an absolutely positioned, display: block element with a default size. */ -function createPositionedBlockElement() { - let element = createBlockElement(); - element.style.position = 'absolute'; - return element; -} - -/** Creates a block element with a default size. */ -function createBlockElement() { - let element = document.createElement('div'); - element.style.width = `${DEFAULT_WIDTH}px`; - element.style.height = `${DEFAULT_HEIGHT}px`; - element.style.backgroundColor = 'rebeccapurple'; - element.style.zIndex = '100'; - return element; -} - -/** Creates an overflow container with a set height and width with margin. */ -function createOverflowContainerElement() { - let element = document.createElement('div'); - element.style.position = 'relative'; - element.style.overflow = 'auto'; - element.style.height = '300px'; - element.style.width = '300px'; - element.style.margin = '100px'; - return element; -} - - -@Component({ - template: `
` -}) -class TestOverlay { } - - -@NgModule({ - imports: [OverlayModule, PortalModule], - exports: [TestOverlay], - declarations: [TestOverlay], - entryComponents: [TestOverlay], -}) -class OverlayTestModule { } diff --git a/src/cdk/overlay/position/connected-position-strategy.ts b/src/cdk/overlay/position/connected-position-strategy.ts deleted file mode 100644 index 551f5671d286..000000000000 --- a/src/cdk/overlay/position/connected-position-strategy.ts +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Direction} from '@angular/cdk/bidi'; -import {Platform} from '@angular/cdk/platform'; -import {CdkScrollable, ViewportRuler} from '@angular/cdk/scrolling'; -import {ElementRef} from '@angular/core'; -import {Observable} from 'rxjs'; - -import {OverlayContainer} from '../overlay-container'; -import {OverlayReference} from '../overlay-reference'; - -import { - ConnectedOverlayPositionChange, - ConnectionPositionPair, - OriginConnectionPosition, - OverlayConnectionPosition, -} from './connected-position'; -import {FlexibleConnectedPositionStrategy} from './flexible-connected-position-strategy'; -import {PositionStrategy} from './position-strategy'; - -/** - * A strategy for positioning overlays. Using this strategy, an overlay is given an - * implicit position relative to some origin element. The relative position is defined in terms of - * a point on the origin element that is connected to a point on the overlay element. For example, - * a basic dropdown is connecting the bottom-left corner of the origin to the top-left corner - * of the overlay. - * @deprecated Use `FlexibleConnectedPositionStrategy` instead. - * @breaking-change 8.0.0 - */ -export class ConnectedPositionStrategy implements PositionStrategy { - /** - * Reference to the underlying position strategy to which all the API calls are proxied. - * @docs-private - */ - _positionStrategy: FlexibleConnectedPositionStrategy; - - /** The overlay to which this strategy is attached. */ - private _overlayRef: OverlayReference; - - private _direction: Direction | null; - - /** Ordered list of preferred positions, from most to least desirable. */ - _preferredPositions: ConnectionPositionPair[] = []; - - /** Emits an event when the connection point changes. */ - readonly onPositionChange: Observable; - - constructor( - originPos: OriginConnectionPosition, overlayPos: OverlayConnectionPosition, - connectedTo: ElementRef, viewportRuler: ViewportRuler, document: Document, - platform: Platform, overlayContainer: OverlayContainer) { - // Since the `ConnectedPositionStrategy` is deprecated and we don't want to maintain - // the extra logic, we create an instance of the positioning strategy that has some - // defaults that make it behave as the old position strategy and to which we'll - // proxy all of the API calls. - this._positionStrategy = new FlexibleConnectedPositionStrategy( - connectedTo, viewportRuler, document, platform, overlayContainer) - .withFlexibleDimensions(false) - .withPush(false) - .withViewportMargin(0); - - this.withFallbackPosition(originPos, overlayPos); - this.onPositionChange = this._positionStrategy.positionChanges; - } - - /** Ordered list of preferred positions, from most to least desirable. */ - get positions(): ConnectionPositionPair[] { - return this._preferredPositions; - } - - /** Attach this position strategy to an overlay. */ - attach(overlayRef: OverlayReference): void { - this._overlayRef = overlayRef; - this._positionStrategy.attach(overlayRef); - - if (this._direction) { - overlayRef.setDirection(this._direction); - this._direction = null; - } - } - - /** Disposes all resources used by the position strategy. */ - dispose() { - this._positionStrategy.dispose(); - } - - /** @docs-private */ - detach() { - this._positionStrategy.detach(); - } - - /** - * Updates the position of the overlay element, using whichever preferred position relative - * to the origin fits on-screen. - * @docs-private - */ - apply(): void { - this._positionStrategy.apply(); - } - - /** - * Re-positions the overlay element with the trigger in its last calculated position, - * even if a position higher in the "preferred positions" list would now fit. This - * allows one to re-align the panel without changing the orientation of the panel. - */ - recalculateLastPosition(): void { - this._positionStrategy.reapplyLastPosition(); - } - - /** - * Sets the list of Scrollable containers that host the origin element so that - * on reposition we can evaluate if it or the overlay has been clipped or outside view. Every - * Scrollable must be an ancestor element of the strategy's origin element. - */ - withScrollableContainers(scrollables: CdkScrollable[]) { - this._positionStrategy.withScrollableContainers(scrollables); - } - - /** - * Adds a new preferred fallback position. - * @param originPos - * @param overlayPos - */ - withFallbackPosition( - originPos: OriginConnectionPosition, - overlayPos: OverlayConnectionPosition, - offsetX?: number, - offsetY?: number): this { - - const position = new ConnectionPositionPair(originPos, overlayPos, offsetX, offsetY); - this._preferredPositions.push(position); - this._positionStrategy.withPositions(this._preferredPositions); - return this; - } - - /** - * Sets the layout direction so the overlay's position can be adjusted to match. - * @param dir New layout direction. - */ - withDirection(dir: 'ltr' | 'rtl'): this { - // Since the direction might be declared before the strategy is attached, - // we save the value in a temporary property and we'll transfer it to the - // overlay ref on attachment. - if (this._overlayRef) { - this._overlayRef.setDirection(dir); - } else { - this._direction = dir; - } - - return this; - } - - /** - * Sets an offset for the overlay's connection point on the x-axis - * @param offset New offset in the X axis. - */ - withOffsetX(offset: number): this { - this._positionStrategy.withDefaultOffsetX(offset); - return this; - } - - /** - * Sets an offset for the overlay's connection point on the y-axis - * @param offset New offset in the Y axis. - */ - withOffsetY(offset: number): this { - this._positionStrategy.withDefaultOffsetY(offset); - return this; - } - - /** - * Sets whether the overlay's position should be locked in after it is positioned - * initially. When an overlay is locked in, it won't attempt to reposition itself - * when the position is re-applied (e.g. when the user scrolls away). - * @param isLocked Whether the overlay should locked in. - */ - withLockedPosition(isLocked: boolean): this { - this._positionStrategy.withLockedPosition(isLocked); - return this; - } - - /** - * Overwrites the current set of positions with an array of new ones. - * @param positions Position pairs to be set on the strategy. - */ - withPositions(positions: ConnectionPositionPair[]): this { - this._preferredPositions = positions.slice(); - this._positionStrategy.withPositions(this._preferredPositions); - return this; - } - - /** - * Sets the origin element, relative to which to position the overlay. - * @param origin Reference to the new origin element. - */ - setOrigin(origin: ElementRef): this { - this._positionStrategy.setOrigin(origin); - return this; - } -} diff --git a/src/cdk/overlay/position/overlay-position-builder.ts b/src/cdk/overlay/position/overlay-position-builder.ts index 032cf9335be5..9bda044e1f83 100644 --- a/src/cdk/overlay/position/overlay-position-builder.ts +++ b/src/cdk/overlay/position/overlay-position-builder.ts @@ -9,12 +9,8 @@ import {Platform} from '@angular/cdk/platform'; import {ViewportRuler} from '@angular/cdk/scrolling'; import {DOCUMENT} from '@angular/common'; -import {ElementRef, Inject, Injectable} from '@angular/core'; - +import {Inject, Injectable} from '@angular/core'; import {OverlayContainer} from '../overlay-container'; - -import {OriginConnectionPosition, OverlayConnectionPosition} from './connected-position'; -import {ConnectedPositionStrategy} from './connected-position-strategy'; import { FlexibleConnectedPositionStrategy, FlexibleConnectedPositionStrategyOrigin, @@ -36,23 +32,6 @@ export class OverlayPositionBuilder { return new GlobalPositionStrategy(); } - /** - * Creates a relative position strategy. - * @param elementRef - * @param originPos - * @param overlayPos - * @deprecated Use `flexibleConnectedTo` instead. - * @breaking-change 8.0.0 - */ - connectedTo( - elementRef: ElementRef, - originPos: OriginConnectionPosition, - overlayPos: OverlayConnectionPosition): ConnectedPositionStrategy { - return new ConnectedPositionStrategy( - originPos, overlayPos, elementRef, this._viewportRuler, this._document, this._platform, - this._overlayContainer); - } - /** * Creates a flexible position strategy. * @param origin Origin relative to which to position the overlay. diff --git a/src/cdk/overlay/public-api.ts b/src/cdk/overlay/public-api.ts index 707e9bca4905..9eb7a15a7bb6 100644 --- a/src/cdk/overlay/public-api.ts +++ b/src/cdk/overlay/public-api.ts @@ -23,7 +23,6 @@ export {OverlayPositionBuilder} from './position/overlay-position-builder'; // Export pre-defined position strategies and interface to build custom ones. export {PositionStrategy} from './position/position-strategy'; export {GlobalPositionStrategy} from './position/global-position-strategy'; -export {ConnectedPositionStrategy} from './position/connected-position-strategy'; export { ConnectedPosition, FlexibleConnectedPositionStrategy, diff --git a/tools/public_api_guard/cdk/overlay.md b/tools/public_api_guard/cdk/overlay.md index 178974f3a588..0832a015225f 100644 --- a/tools/public_api_guard/cdk/overlay.md +++ b/tools/public_api_guard/cdk/overlay.md @@ -171,28 +171,6 @@ export interface ConnectedPosition { weight?: number; } -// @public @deprecated -export class ConnectedPositionStrategy implements PositionStrategy { - constructor(originPos: OriginConnectionPosition, overlayPos: OverlayConnectionPosition, connectedTo: ElementRef, viewportRuler: ViewportRuler, document: Document, platform: Platform, overlayContainer: OverlayContainer); - apply(): void; - attach(overlayRef: OverlayReference): void; - detach(): void; - dispose(): void; - readonly onPositionChange: Observable; - get positions(): ConnectionPositionPair[]; - _positionStrategy: FlexibleConnectedPositionStrategy; - _preferredPositions: ConnectionPositionPair[]; - recalculateLastPosition(): void; - setOrigin(origin: ElementRef): this; - withDirection(dir: 'ltr' | 'rtl'): this; - withFallbackPosition(originPos: OriginConnectionPosition, overlayPos: OverlayConnectionPosition, offsetX?: number, offsetY?: number): this; - withLockedPosition(isLocked: boolean): this; - withOffsetX(offset: number): this; - withOffsetY(offset: number): this; - withPositions(positions: ConnectionPositionPair[]): this; - withScrollableContainers(scrollables: CdkScrollable[]): void; -} - // @public export class ConnectionPositionPair { constructor(origin: OriginConnectionPosition, overlay: OverlayConnectionPosition, @@ -382,8 +360,6 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher { // @public export class OverlayPositionBuilder { constructor(_viewportRuler: ViewportRuler, _document: any, _platform: Platform, _overlayContainer: OverlayContainer); - // @deprecated - connectedTo(elementRef: ElementRef, originPos: OriginConnectionPosition, overlayPos: OverlayConnectionPosition): ConnectedPositionStrategy; flexibleConnectedTo(origin: FlexibleConnectedPositionStrategyOrigin): FlexibleConnectedPositionStrategy; global(): GlobalPositionStrategy; // (undocumented)