Skip to content

Commit f205299

Browse files
committed
feat(overlay): add the ability to set a panelClass based on the current connected position
Adds a property on the `ConnectedPosition` to allow the consumer to set a class on the overlay pane, depending on which position is currently active.
1 parent c5cfede commit f205299

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ export class ConnectionPositionPair {
4040
constructor(
4141
origin: OriginConnectionPosition,
4242
overlay: OverlayConnectionPosition,
43+
/** Offset along the X axis. */
4344
public offsetX?: number,
44-
public offsetY?: number) {
45+
/** Offset along the Y axis. */
46+
public offsetY?: number,
47+
/** Class(es) to be applied to the panel while this position is active. */
48+
public panelClass?: string | string[]) {
4549

4650
this.originX = origin.originX;
4751
this.originY = origin.originY;

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

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,110 @@ describe('FlexibleConnectedPositionStrategy', () => {
17991799
});
18001800
});
18011801

1802+
describe('panel classes', () => {
1803+
let originElement: HTMLElement;
1804+
let positionStrategy: FlexibleConnectedPositionStrategy;
1805+
1806+
beforeEach(() => {
1807+
originElement = createPositionedBlockElement();
1808+
document.body.appendChild(originElement);
1809+
positionStrategy = overlay.position()
1810+
.flexibleConnectedTo(originElement)
1811+
.withFlexibleDimensions(false)
1812+
.withPush(false);
1813+
});
1814+
1815+
afterEach(() => {
1816+
document.body.removeChild(originElement);
1817+
});
1818+
1819+
it('should be able to apply a class based on the position', () => {
1820+
positionStrategy.withPositions([{
1821+
originX: 'start',
1822+
originY: 'bottom',
1823+
overlayX: 'start',
1824+
overlayY: 'top',
1825+
panelClass: 'is-below'
1826+
}]);
1827+
1828+
attachOverlay({positionStrategy});
1829+
1830+
expect(overlayRef.overlayElement.classList).toContain('is-below');
1831+
});
1832+
1833+
it('should be able to apply multiple classes based on the position', () => {
1834+
positionStrategy.withPositions([{
1835+
originX: 'start',
1836+
originY: 'bottom',
1837+
overlayX: 'start',
1838+
overlayY: 'top',
1839+
panelClass: ['is-below', 'is-under']
1840+
}]);
1841+
1842+
attachOverlay({positionStrategy});
1843+
1844+
expect(overlayRef.overlayElement.classList).toContain('is-below');
1845+
expect(overlayRef.overlayElement.classList).toContain('is-under');
1846+
});
1847+
1848+
it('should remove the panel class when detaching', () => {
1849+
positionStrategy.withPositions([{
1850+
originX: 'start',
1851+
originY: 'bottom',
1852+
overlayX: 'start',
1853+
overlayY: 'top',
1854+
panelClass: 'is-below'
1855+
}]);
1856+
1857+
attachOverlay({positionStrategy});
1858+
expect(overlayRef.overlayElement.classList).toContain('is-below');
1859+
1860+
overlayRef.detach();
1861+
expect(overlayRef.overlayElement.classList).not.toContain('is-below');
1862+
});
1863+
1864+
it('should clear the previous classes when the position changes', () => {
1865+
originElement.style.top = '200px';
1866+
originElement.style.right = '25px';
1867+
1868+
positionStrategy.withPositions([
1869+
{
1870+
originX: 'end',
1871+
originY: 'center',
1872+
overlayX: 'start',
1873+
overlayY: 'center',
1874+
panelClass: ['is-center', 'is-in-the-middle']
1875+
},
1876+
{
1877+
originX: 'start',
1878+
originY: 'bottom',
1879+
overlayX: 'end',
1880+
overlayY: 'top',
1881+
panelClass: 'is-below'
1882+
}
1883+
]);
1884+
1885+
attachOverlay({positionStrategy});
1886+
1887+
const overlayClassList = overlayRef.overlayElement.classList;
1888+
1889+
expect(overlayClassList).not.toContain('is-center');
1890+
expect(overlayClassList).not.toContain('is-in-the-middle');
1891+
expect(overlayClassList).toContain('is-below');
1892+
1893+
// Move the element so another position is applied.
1894+
originElement.style.top = '200px';
1895+
originElement.style.left = '200px';
1896+
1897+
overlayRef.updatePosition();
1898+
1899+
expect(overlayClassList).toContain('is-center');
1900+
expect(overlayClassList).toContain('is-in-the-middle');
1901+
expect(overlayClassList).not.toContain('is-below');
1902+
});
1903+
1904+
});
1905+
18021906
});
18031907

18041908
/** Creates an absolutely positioned, display: block element with a default size. */

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import {Observable, Subscription, Subject} from 'rxjs';
2020
import {OverlayReference} from '../overlay-reference';
2121
import {isElementScrolledOutsideView, isElementClippedByScrolling} from './scroll-clip';
22-
import {coerceCssPixelValue} from '@angular/cdk/coercion';
22+
import {coerceCssPixelValue, coerceArray} from '@angular/cdk/coercion';
2323
import {Platform} from '@angular/cdk/platform';
2424
import {OverlayContainer} from '../overlay-container';
2525

@@ -112,6 +112,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
112112
/** Amount of subscribers to the `positionChanges` stream. */
113113
private _positionChangeSubscriptions = 0;
114114

115+
/** Keeps track of the CSS classes that the position strategy has applied on the overlay panel. */
116+
private _appliedPanelClasses: string[] = [];
117+
115118
/** Observable sequence of position changes. */
116119
positionChanges: Observable<ConnectedOverlayPositionChange> = Observable.create(observer => {
117120
const subscription = this._positionChanges.subscribe(observer);
@@ -184,6 +187,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
184187
return;
185188
}
186189

190+
this._clearPanelClasses();
187191
this._resetOverlayElementStyles();
188192
this._resetBoundingBoxStyles();
189193

@@ -282,6 +286,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
282286
}
283287

284288
detach() {
289+
this._clearPanelClasses();
285290
this._resizeSubscription.unsubscribe();
286291
}
287292

@@ -590,6 +595,10 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
590595
this._setOverlayElementStyles(originPoint, position);
591596
this._setBoundingBoxStyles(originPoint, position);
592597

598+
if (position.panelClass) {
599+
this._addPanelClasses(position.panelClass);
600+
}
601+
593602
// Save the last connected position in case the position needs to be re-calculated.
594603
this._lastPosition = position;
595604

@@ -991,6 +1000,26 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
9911000
validateVerticalPosition('overlayY', pair.overlayY);
9921001
});
9931002
}
1003+
1004+
/** Adds a single CSS class or an array of classes on the overlay panel. */
1005+
private _addPanelClasses(cssClasses: string | string[]) {
1006+
if (this._pane) {
1007+
coerceArray(cssClasses).forEach(cssClass => {
1008+
if (this._appliedPanelClasses.indexOf(cssClass) === -1) {
1009+
this._appliedPanelClasses.push(cssClass);
1010+
this._pane.classList.add(cssClass);
1011+
}
1012+
});
1013+
}
1014+
}
1015+
1016+
/** Clears the classes that the position strategy has applied from the overlay panel. */
1017+
private _clearPanelClasses() {
1018+
if (this._pane) {
1019+
this._appliedPanelClasses.forEach(cssClass => this._pane.classList.remove(cssClass));
1020+
this._appliedPanelClasses = [];
1021+
}
1022+
}
9941023
}
9951024

9961025
/** A simple (x, y) coordinate. */
@@ -1052,6 +1081,7 @@ export interface ConnectedPosition {
10521081
weight?: number;
10531082
offsetX?: number;
10541083
offsetY?: number;
1084+
panelClass?: string | string[];
10551085
}
10561086

10571087
/** Shallow-extends a stylesheet object with another stylesheet object. */

0 commit comments

Comments
 (0)