@@ -2,10 +2,10 @@ import {PositionStrategy} from './position-strategy';
2
2
import { ElementRef } from '@angular/core' ;
3
3
import { ViewportRuler } from './viewport-ruler' ;
4
4
import {
5
- ConnectionPositionPair ,
6
- OriginConnectionPosition ,
7
- OverlayConnectionPosition ,
8
- ConnectedOverlayPositionChange
5
+ ConnectionPositionPair ,
6
+ OriginConnectionPosition ,
7
+ OverlayConnectionPosition ,
8
+ ConnectedOverlayPositionChange , ScrollableViewProperties
9
9
} from './connected-position' ;
10
10
import { Subject } from 'rxjs/Subject' ;
11
11
import { Observable } from 'rxjs/Observable' ;
@@ -35,7 +35,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
35
35
private _offsetY : number = 0 ;
36
36
37
37
/** The Scrollable containers that may cause the overlay's connectedTo element to be clipped */
38
- private scrollables : Scrollable [ ] = [ ] ;
38
+ private scrollables : Scrollable [ ] ;
39
39
40
40
/** Whether the we're dealing with an RTL context */
41
41
get _isRtl ( ) {
@@ -48,7 +48,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
48
48
/** The origin element against which the overlay will be positioned. */
49
49
private _origin : HTMLElement ;
50
50
51
- private _onPositionChange :
51
+ _onPositionChange :
52
52
Subject < ConnectedOverlayPositionChange > = new Subject < ConnectedOverlayPositionChange > ( ) ;
53
53
54
54
/** Emits an event when the connection point changes. */
@@ -102,8 +102,12 @@ export class ConnectedPositionStrategy implements PositionStrategy {
102
102
// If the overlay in the calculated position fits on-screen, put it there and we're done.
103
103
if ( overlayPoint . fitsInViewport ) {
104
104
this . _setElementPosition ( element , overlayPoint ) ;
105
- const isClipped = this . isOverlayElementClipped ( element ) ;
106
- this . _onPositionChange . next ( new ConnectedOverlayPositionChange ( pos , isClipped ) ) ;
105
+
106
+ // Notify that the position has been changed along with its change properties.
107
+ const scrollableViewProperties = this . getScrollableViewProperties ( element ) ;
108
+ const positionChange = new ConnectedOverlayPositionChange ( pos , scrollableViewProperties ) ;
109
+ this . _onPositionChange . next ( positionChange ) ;
110
+
107
111
return Promise . resolve ( null ) ;
108
112
} else if ( ! fallbackPoint || fallbackPoint . visibleArea < overlayPoint . visibleArea ) {
109
113
fallbackPoint = overlayPoint ;
@@ -243,16 +247,48 @@ export class ConnectedPositionStrategy implements PositionStrategy {
243
247
return { x, y, fitsInViewport, visibleArea} ;
244
248
}
245
249
246
- /** Whether the overlay element is clipped out of view of one of the scrollable containers. */
247
- private isOverlayElementClipped ( element : HTMLElement ) : boolean {
248
- const elementBounds = this . _getElementBounds ( element ) ;
249
- return this . scrollables . some ( ( scrollable : Scrollable ) => {
250
- const scrollingContainerBounds = this . _getElementBounds ( scrollable . getElementRef ( ) . nativeElement ) ;
250
+ /**
251
+ * Gets the view properties of the trigger and overlay, including whether they are clipped
252
+ * or completely outside the view of any of the strategy's scrollables.
253
+ */
254
+ private getScrollableViewProperties ( overlay : HTMLElement ) : ScrollableViewProperties {
255
+ const triggerBounds = this . _getElementBounds ( this . _connectedTo . nativeElement ) ;
256
+ const overlayBounds = this . _getElementBounds ( overlay ) ;
257
+ const scrollContainerBounds = this . scrollables . map ( ( scrollable : Scrollable ) => {
258
+ return this . _getElementBounds ( scrollable . getElementRef ( ) . nativeElement ) ;
259
+ } ) ;
260
+
261
+ return {
262
+ isTriggerClipped : this . isElementClipped ( triggerBounds , scrollContainerBounds ) ,
263
+ isTriggerOutsideView : this . isElementOutsideView ( triggerBounds , scrollContainerBounds ) ,
264
+ isOverlayClipped : this . isElementClipped ( overlayBounds , scrollContainerBounds ) ,
265
+ isOverlayOutsideView : this . isElementOutsideView ( overlayBounds , scrollContainerBounds ) ,
266
+ } ;
267
+ }
268
+
269
+ /** Whether the element is completely out of the view of any of the containers. */
270
+ private isElementOutsideView (
271
+ elementBounds : ElementBoundingPositions ,
272
+ containersBounds : ElementBoundingPositions [ ] ) : boolean {
273
+ return containersBounds . some ( ( containerBounds : ElementBoundingPositions ) => {
274
+ const outsideAbove = elementBounds . bottom < containerBounds . top ;
275
+ const outsideBelow = elementBounds . top > containerBounds . bottom ;
276
+ const outsideLeft = elementBounds . right < containerBounds . left ;
277
+ const outsideRight = elementBounds . left > containerBounds . right ;
278
+
279
+ return outsideAbove || outsideBelow || outsideLeft || outsideRight ;
280
+ } ) ;
281
+ }
251
282
252
- const clippedAbove = elementBounds . top < scrollingContainerBounds . top ;
253
- const clippedBelow = elementBounds . bottom > scrollingContainerBounds . bottom ;
254
- const clippedLeft = elementBounds . left < scrollingContainerBounds . left ;
255
- const clippedRight = elementBounds . right > scrollingContainerBounds . right ;
283
+ /** Whether the element is clipped by any of the containers. */
284
+ private isElementClipped (
285
+ elementBounds : ElementBoundingPositions ,
286
+ containersBounds : ElementBoundingPositions [ ] ) : boolean {
287
+ return containersBounds . some ( ( containerBounds : ElementBoundingPositions ) => {
288
+ const clippedAbove = elementBounds . top < containerBounds . top ;
289
+ const clippedBelow = elementBounds . bottom > containerBounds . bottom ;
290
+ const clippedLeft = elementBounds . left < containerBounds . left ;
291
+ const clippedRight = elementBounds . right > containerBounds . right ;
256
292
257
293
return clippedAbove || clippedBelow || clippedLeft || clippedRight ;
258
294
} ) ;
0 commit comments