Skip to content

Commit 97b7446

Browse files
committed
fix(overlay): centered flexible positioning not working in some browsers
Currently using centering with the `FlexibleConnectedPositionStrategy` in flexible mode doesn't work in browsers that haven't implemented [the new absolute position behavior inside a flex container](https://developers.google.com/web/updates/2016/06/absolute-positioned-children) (e.g. iOS Safari), which means that the position will be thrown off completely. These changes rework the position strategy to use `position: static` and `justify-content`/`align-items`, rather than `position: absolute`, to push the overlay pane to the proper edge of the bounding box in flexible mode. As a result, support for having flexible dimensions in only one direction has been removed.
1 parent edb57f9 commit 97b7446

File tree

11 files changed

+157
-166
lines changed

11 files changed

+157
-166
lines changed

src/cdk/overlay/_overlay.scss

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ $backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
5151

5252
// A single overlay pane.
5353
.cdk-overlay-pane {
54+
// Note: it's important for this one to start off `absolute`,
55+
// in order for us to be able to measure it correctly.
5456
position: absolute;
55-
5657
pointer-events: auto;
5758
box-sizing: border-box;
5859
z-index: $cdk-z-index-overlay;
@@ -113,8 +114,10 @@ $backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
113114
// When *not* centering, a top/left/bottom/right will be set which overrides the normal
114115
// flex layout.
115116
display: flex;
116-
justify-content: center;
117-
align-items: center;
117+
118+
// We use the `column` direction here to avoid some flexbox issues in Edge
119+
// when using the "grow after open" options.
120+
flex-direction: column;
118121

119122
// Add some dimensions so the element has an `innerText` which some people depend on in tests.
120123
min-width: 1px;

src/cdk/overlay/overlay-directives.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
289289
// the same way as the old ConnectedPositionStrategy and to avoid breaking changes.
290290
// TODO(crisbeto): make these on by default and add inputs for them
291291
// next time we do breaking changes.
292-
.withFlexibleHeight(false)
293-
.withFlexibleWidth(false)
292+
.withFlexibleDimensions(false)
294293
.withPush(false)
295294
.withGrowAfterOpen(false)
296295
.withLockedPosition(this.lockPosition);

src/cdk/overlay/position/connected-position-strategy.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
7070
// proxy all of the API calls.
7171
this._positionStrategy =
7272
new FlexibleConnectedPositionStrategy(connectedTo, viewportRuler, document)
73-
.withFlexibleHeight(false)
74-
.withFlexibleWidth(false)
73+
.withFlexibleDimensions(false)
7574
.withPush(false)
7675
.withViewportMargin(0);
7776

src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts

Lines changed: 78 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@ describe('FlexibleConnectedPositionStrategy', () => {
120120
document.body.appendChild(originElement);
121121
positionStrategy = overlay.position()
122122
.flexibleConnectedTo(new ElementRef(originElement))
123-
.withFlexibleHeight(false)
124-
.withFlexibleWidth(false)
123+
.withFlexibleDimensions(false)
125124
.withPush(false);
126125
});
127126

@@ -865,8 +864,7 @@ describe('FlexibleConnectedPositionStrategy', () => {
865864
document.body.appendChild(originElement);
866865
positionStrategy = overlay.position()
867866
.flexibleConnectedTo(new ElementRef(originElement))
868-
.withFlexibleHeight(false)
869-
.withFlexibleWidth(false)
867+
.withFlexibleDimensions(false)
870868
.withPush();
871869
});
872870

@@ -1003,8 +1001,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
10031001

10041002
it('should align the overlay to `flex-start` when the content is flowing to the right', () => {
10051003
positionStrategy
1006-
.withFlexibleWidth()
1007-
.withFlexibleHeight()
1004+
.withFlexibleDimensions()
1005+
.withPush(false)
10081006
.withPositions([{
10091007
overlayY: 'top',
10101008
overlayX: 'start',
@@ -1014,13 +1012,13 @@ describe('FlexibleConnectedPositionStrategy', () => {
10141012

10151013
attachOverlay({positionStrategy});
10161014

1017-
expect(overlayRef.overlayElement.style.justifyContent).toBe('flex-start');
1015+
expect(overlayRef.hostElement.style.alignItems).toBe('flex-start');
10181016
});
10191017

10201018
it('should align the overlay to `flex-end` when the content is flowing to the left', () => {
10211019
positionStrategy
1022-
.withFlexibleWidth()
1023-
.withFlexibleHeight()
1020+
.withFlexibleDimensions()
1021+
.withPush(false)
10241022
.withPositions([{
10251023
overlayY: 'top',
10261024
overlayX: 'end',
@@ -1030,13 +1028,13 @@ describe('FlexibleConnectedPositionStrategy', () => {
10301028

10311029
attachOverlay({positionStrategy});
10321030

1033-
expect(overlayRef.overlayElement.style.justifyContent).toBe('flex-end');
1031+
expect(overlayRef.hostElement.style.alignItems).toBe('flex-end');
10341032
});
10351033

10361034
it('should align the overlay to `center` when the content is centered', () => {
10371035
positionStrategy
1038-
.withFlexibleWidth()
1039-
.withFlexibleHeight()
1036+
.withFlexibleDimensions()
1037+
.withPush(false)
10401038
.withPositions([{
10411039
overlayY: 'top',
10421040
overlayX: 'center',
@@ -1046,48 +1044,48 @@ describe('FlexibleConnectedPositionStrategy', () => {
10461044

10471045
attachOverlay({positionStrategy});
10481046

1049-
expect(overlayRef.overlayElement.style.justifyContent).toBe('center');
1047+
expect(overlayRef.hostElement.style.alignItems).toBe('center');
10501048
});
10511049

1052-
// TODO(crisbeto): investigate failure in older Safari
1053-
// it('should support offsets when centering', () => {
1054-
// originElement.style.top = '200px';
1055-
// originElement.style.left = '200px';
1056-
1057-
// positionStrategy
1058-
// .withFlexibleWidth()
1059-
// .withFlexibleHeight()
1060-
// .withPositions([{
1061-
// overlayY: 'center',
1062-
// overlayX: 'center',
1063-
// originY: 'center',
1064-
// originX: 'center',
1065-
// offsetY: 20,
1066-
// offsetX: -15
1067-
// }]);
1068-
1069-
// attachOverlay({positionStrategy});
1070-
1071-
// const originRect = originElement.getBoundingClientRect();
1072-
// const originCenterY = originRect.top + (ORIGIN_HEIGHT / 2);
1073-
// const originCenterX = originRect.left + (ORIGIN_WIDTH / 2);
1074-
1075-
// const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
1076-
// const overlayCenterY = overlayRect.top + (OVERLAY_HEIGHT / 2);
1077-
// const overlayCenterX = overlayRect.left + (OVERLAY_WIDTH / 2);
1078-
1079-
// expect(overlayRef.overlayElement.style.transform)
1080-
// .toBe('translateX(-15px) translateY(20px)');
1081-
// expect(Math.floor(overlayCenterY)).toBe(Math.floor(originCenterY) + 20);
1082-
// expect(Math.floor(overlayCenterX)).toBe(Math.floor(originCenterX) - 15);
1083-
// });
1050+
it('should support offsets when centering', () => {
1051+
originElement.style.top = '200px';
1052+
originElement.style.left = '200px';
1053+
1054+
positionStrategy
1055+
.withFlexibleDimensions()
1056+
.withPush(false)
1057+
.withPositions([{
1058+
overlayY: 'center',
1059+
overlayX: 'center',
1060+
originY: 'center',
1061+
originX: 'center',
1062+
offsetY: 20,
1063+
offsetX: -15
1064+
}]);
1065+
1066+
attachOverlay({positionStrategy});
1067+
1068+
const originRect = originElement.getBoundingClientRect();
1069+
const originCenterX = originRect.left + (DEFAULT_WIDTH / 2);
1070+
const originCenterY = originRect.top + (DEFAULT_HEIGHT / 2);
1071+
1072+
const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
1073+
const overlayCenterY = overlayRect.top + (OVERLAY_HEIGHT / 2);
1074+
const overlayCenterX = overlayRect.left + (OVERLAY_WIDTH / 2);
1075+
1076+
expect(overlayRef.overlayElement.style.transform)
1077+
.toBe('translateX(-15px) translateY(20px)');
1078+
expect(Math.floor(overlayCenterY)).toBe(Math.floor(originCenterY) + 20);
1079+
expect(Math.floor(overlayCenterX)).toBe(Math.floor(originCenterX) - 15);
1080+
});
10841081

10851082
it('should become scrollable when it hits the viewport edge with a flexible height', () => {
10861083
originElement.style.left = '200px';
10871084
originElement.style.bottom = `${OVERLAY_HEIGHT - 10}px`;
10881085

10891086
positionStrategy
1090-
.withFlexibleHeight()
1087+
.withFlexibleDimensions()
1088+
.withPush(false)
10911089
.withPositions([{
10921090
overlayY: 'top',
10931091
overlayX: 'start',
@@ -1107,7 +1105,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
11071105
originElement.style.right = '-20px';
11081106

11091107
positionStrategy
1110-
.withFlexibleWidth()
1108+
.withFlexibleDimensions()
1109+
.withPush(false)
11111110
.withPositions([{
11121111
overlayY: 'top',
11131112
overlayX: 'start',
@@ -1127,7 +1126,7 @@ describe('FlexibleConnectedPositionStrategy', () => {
11271126
originElement.style.bottom = `${OVERLAY_HEIGHT - 10}px`;
11281127

11291128
positionStrategy
1130-
.withFlexibleHeight()
1129+
.withFlexibleDimensions()
11311130
.withPositions([{
11321131
overlayY: 'top',
11331132
overlayX: 'start',
@@ -1149,7 +1148,7 @@ describe('FlexibleConnectedPositionStrategy', () => {
11491148
originElement.style.right = '-20px';
11501149

11511150
positionStrategy
1152-
.withFlexibleWidth()
1151+
.withFlexibleDimensions()
11531152
.withPositions([{
11541153
overlayY: 'top',
11551154
overlayX: 'start',
@@ -1171,7 +1170,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
11711170
originElement.style.right = '25px';
11721171

11731172
positionStrategy
1174-
.withFlexibleWidth()
1173+
.withFlexibleDimensions()
1174+
.withPush(false)
11751175
.withPositions([
11761176
{
11771177
originX: 'end',
@@ -1202,7 +1202,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
12021202
originElement.style.bottom = `${OVERLAY_HEIGHT - 10}px`;
12031203

12041204
positionStrategy
1205-
.withFlexibleHeight()
1205+
.withFlexibleDimensions()
1206+
.withPush(false)
12061207
.withGrowAfterOpen()
12071208
.withPositions([{
12081209
overlayY: 'top',
@@ -1239,7 +1240,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
12391240
window.scroll(0, 50);
12401241

12411242
positionStrategy
1242-
.withFlexibleHeight()
1243+
.withFlexibleDimensions()
1244+
.withPush(false)
12431245
.withPositions([{
12441246
overlayY: 'bottom',
12451247
overlayX: 'start',
@@ -1257,14 +1259,15 @@ describe('FlexibleConnectedPositionStrategy', () => {
12571259
window.scroll(0, 0);
12581260
document.body.removeChild(veryLargeElement);
12591261
});
1262+
12601263
it('should set the proper styles when the `bottom` value is exactly zero', () => {
12611264
originElement.style.position = 'fixed';
12621265
originElement.style.bottom = '0';
12631266
originElement.style.left = '200px';
12641267

12651268
positionStrategy
1266-
.withFlexibleWidth()
1267-
.withFlexibleHeight()
1269+
.withFlexibleDimensions()
1270+
.withPush(false)
12681271
.withPositions([{
12691272
overlayY: 'bottom',
12701273
overlayX: 'start',
@@ -1289,8 +1292,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
12891292
originElement.style.left = '200px';
12901293

12911294
positionStrategy
1292-
.withFlexibleWidth()
1293-
.withFlexibleHeight()
1295+
.withFlexibleDimensions()
1296+
.withPush(false)
12941297
.withPositions([{
12951298
overlayY: 'top',
12961299
overlayX: 'start',
@@ -1315,8 +1318,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
13151318
originElement.style.top = '200px';
13161319

13171320
positionStrategy
1318-
.withFlexibleWidth()
1319-
.withFlexibleHeight()
1321+
.withFlexibleDimensions()
1322+
.withPush(false)
13201323
.withPositions([{
13211324
overlayY: 'top',
13221325
overlayX: 'start',
@@ -1341,8 +1344,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
13411344
originElement.style.top = '200px';
13421345

13431346
positionStrategy
1344-
.withFlexibleWidth()
1345-
.withFlexibleHeight()
1347+
.withFlexibleDimensions()
1348+
.withPush(false)
13461349
.withPositions([{
13471350
overlayY: 'top',
13481351
overlayX: 'end',
@@ -1368,7 +1371,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
13681371
originElement.style.right = '200px';
13691372

13701373
positionStrategy
1371-
.withFlexibleHeight()
1374+
.withFlexibleDimensions()
1375+
.withPush(false)
13721376
.withViewportMargin(viewportMargin)
13731377
.withPositions([
13741378
{
@@ -1408,6 +1412,7 @@ describe('FlexibleConnectedPositionStrategy', () => {
14081412
// Create a strategy with knowledge of the scrollable container
14091413
const strategy = overlay.position()
14101414
.flexibleConnectedTo(new ElementRef(originElement))
1415+
.withPush(false)
14111416
.withPositions([{
14121417
originX: 'start',
14131418
originY: 'bottom',
@@ -1503,8 +1508,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
15031508

15041509
attachOverlay({positionStrategy});
15051510

1506-
expect(overlayRef.overlayElement.style.left).toBeTruthy();
1507-
expect(overlayRef.overlayElement.style.right).toBeFalsy();
1511+
expect(overlayRef.hostElement.style.left).toBeTruthy();
1512+
expect(overlayRef.hostElement.style.right).toBeFalsy();
15081513
});
15091514

15101515
it('should use `right` when positioning an element at the end', () => {
@@ -1517,8 +1522,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
15171522

15181523
attachOverlay({positionStrategy});
15191524

1520-
expect(overlayRef.overlayElement.style.right).toBeTruthy();
1521-
expect(overlayRef.overlayElement.style.left).toBeFalsy();
1525+
expect(overlayRef.hostElement.style.right).toBeTruthy();
1526+
expect(overlayRef.hostElement.style.left).toBeFalsy();
15221527
});
15231528

15241529
});
@@ -1537,8 +1542,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
15371542
direction: 'rtl'
15381543
});
15391544

1540-
expect(overlayRef.overlayElement.style.right).toBeTruthy();
1541-
expect(overlayRef.overlayElement.style.left).toBeFalsy();
1545+
expect(overlayRef.hostElement.style.right).toBeTruthy();
1546+
expect(overlayRef.hostElement.style.left).toBeFalsy();
15421547
});
15431548

15441549
it('should use `left` when positioning an element at the end', () => {
@@ -1551,8 +1556,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
15511556

15521557
attachOverlay({positionStrategy, direction: 'rtl'});
15531558

1554-
expect(overlayRef.overlayElement.style.left).toBeTruthy();
1555-
expect(overlayRef.overlayElement.style.right).toBeFalsy();
1559+
expect(overlayRef.hostElement.style.left).toBeTruthy();
1560+
expect(overlayRef.hostElement.style.right).toBeFalsy();
15561561
});
15571562
});
15581563

@@ -1567,8 +1572,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
15671572

15681573
attachOverlay({positionStrategy});
15691574

1570-
expect(overlayRef.overlayElement.style.top).toBeTruthy();
1571-
expect(overlayRef.overlayElement.style.bottom).toBeFalsy();
1575+
expect(overlayRef.hostElement.style.top).toBeTruthy();
1576+
expect(overlayRef.hostElement.style.bottom).toBeFalsy();
15721577
});
15731578

15741579
it('should use `bottom` when positioning at element along the bottom', () => {
@@ -1581,8 +1586,8 @@ describe('FlexibleConnectedPositionStrategy', () => {
15811586

15821587
attachOverlay({positionStrategy});
15831588

1584-
expect(overlayRef.overlayElement.style.bottom).toBeTruthy();
1585-
expect(overlayRef.overlayElement.style.top).toBeFalsy();
1589+
expect(overlayRef.hostElement.style.bottom).toBeTruthy();
1590+
expect(overlayRef.hostElement.style.top).toBeFalsy();
15861591
});
15871592
});
15881593

0 commit comments

Comments
 (0)