Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(overlay): add the ability to set a panelClass based on the current connected position #12631

Merged
merged 1 commit into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/cdk/overlay/overlay-directives.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ describe('Overlay directives', () => {
// TODO(jelbourn) figure out why, when compiling with bazel, these offsets are required.
offsetX: 0,
offsetY: 0,
panelClass: 'custom-class'
}];

fixture.componentInstance.isOpen = true;
Expand All @@ -348,7 +349,8 @@ describe('Overlay directives', () => {
overlayX: 'start',
overlayY: 'top',
offsetX: 20,
offsetY: 10
offsetY: 10,
panelClass: 'custom-class'
}];

fixture.componentInstance.isOpen = true;
Expand Down
6 changes: 5 additions & 1 deletion src/cdk/overlay/position/connected-position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ export class ConnectionPositionPair {
constructor(
origin: OriginConnectionPosition,
overlay: OverlayConnectionPosition,
/** Offset along the X axis. */
public offsetX?: number,
public offsetY?: number) {
/** Offset along the Y axis. */
public offsetY?: number,
/** Class(es) to be applied to the panel while this position is active. */
public panelClass?: string | string[]) {

this.originX = origin.originX;
this.originY = origin.originY;
Expand Down
143 changes: 143 additions & 0 deletions src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1799,6 +1799,149 @@ describe('FlexibleConnectedPositionStrategy', () => {
});
});

describe('panel classes', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test for not removing an existing css class on the element?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

let originElement: HTMLElement;
let positionStrategy: FlexibleConnectedPositionStrategy;

beforeEach(() => {
originElement = createPositionedBlockElement();
document.body.appendChild(originElement);
positionStrategy = overlay.position()
.flexibleConnectedTo(originElement)
.withFlexibleDimensions(false)
.withPush(false);
});

afterEach(() => {
document.body.removeChild(originElement);
});

it('should be able to apply a class based on the position', () => {
positionStrategy.withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
panelClass: 'is-below'
}]);

attachOverlay({positionStrategy});

expect(overlayRef.overlayElement.classList).toContain('is-below');
});

it('should be able to apply multiple classes based on the position', () => {
positionStrategy.withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
panelClass: ['is-below', 'is-under']
}]);

attachOverlay({positionStrategy});

expect(overlayRef.overlayElement.classList).toContain('is-below');
expect(overlayRef.overlayElement.classList).toContain('is-under');
});

it('should remove the panel class when detaching', () => {
positionStrategy.withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
panelClass: 'is-below'
}]);

attachOverlay({positionStrategy});
expect(overlayRef.overlayElement.classList).toContain('is-below');

overlayRef.detach();
expect(overlayRef.overlayElement.classList).not.toContain('is-below');
});

it('should clear the previous classes when the position changes', () => {
originElement.style.top = '200px';
originElement.style.right = '25px';

positionStrategy.withPositions([
{
originX: 'end',
originY: 'center',
overlayX: 'start',
overlayY: 'center',
panelClass: ['is-center', 'is-in-the-middle']
},
{
originX: 'start',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top',
panelClass: 'is-below'
}
]);

attachOverlay({positionStrategy});

const overlayClassList = overlayRef.overlayElement.classList;

expect(overlayClassList).not.toContain('is-center');
expect(overlayClassList).not.toContain('is-in-the-middle');
expect(overlayClassList).toContain('is-below');

// Move the element so another position is applied.
originElement.style.top = '200px';
originElement.style.left = '200px';

overlayRef.updatePosition();

expect(overlayClassList).toContain('is-center');
expect(overlayClassList).toContain('is-in-the-middle');
expect(overlayClassList).not.toContain('is-below');
});

it('should not clear the existing `panelClass` from the `OverlayRef`', () => {
originElement.style.top = '200px';
originElement.style.right = '25px';

positionStrategy.withPositions([
{
originX: 'end',
originY: 'center',
overlayX: 'start',
overlayY: 'center',
panelClass: ['is-center', 'is-in-the-middle']
},
{
originX: 'start',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top',
panelClass: 'is-below'
}
]);

attachOverlay({
panelClass: 'custom-panel-class',
positionStrategy
});

const overlayClassList = overlayRef.overlayElement.classList;

expect(overlayClassList).toContain('custom-panel-class');

// Move the element so another position is applied.
originElement.style.top = '200px';
originElement.style.left = '200px';

overlayRef.updatePosition();

expect(overlayClassList).toContain('custom-panel-class');
});

});

});

/** Creates an absolutely positioned, display: block element with a default size. */
Expand Down
32 changes: 31 additions & 1 deletion src/cdk/overlay/position/flexible-connected-position-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import {Observable, Subscription, Subject} from 'rxjs';
import {OverlayReference} from '../overlay-reference';
import {isElementScrolledOutsideView, isElementClippedByScrolling} from './scroll-clip';
import {coerceCssPixelValue} from '@angular/cdk/coercion';
import {coerceCssPixelValue, coerceArray} from '@angular/cdk/coercion';
import {Platform} from '@angular/cdk/platform';
import {OverlayContainer} from '../overlay-container';

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

/** Keeps track of the CSS classes that the position strategy has applied on the overlay panel. */
private _appliedPanelClasses: string[] = [];

/** Observable sequence of position changes. */
positionChanges: Observable<ConnectedOverlayPositionChange> = Observable.create(observer => {
const subscription = this._positionChanges.subscribe(observer);
Expand Down Expand Up @@ -184,6 +187,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
return;
}

this._clearPanelClasses();
this._resetOverlayElementStyles();
this._resetBoundingBoxStyles();

Expand Down Expand Up @@ -282,6 +286,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
}

detach() {
this._clearPanelClasses();
this._resizeSubscription.unsubscribe();
}

Expand Down Expand Up @@ -590,6 +595,10 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
this._setOverlayElementStyles(originPoint, position);
this._setBoundingBoxStyles(originPoint, position);

if (position.panelClass) {
this._addPanelClasses(position.panelClass);
}

// Save the last connected position in case the position needs to be re-calculated.
this._lastPosition = position;

Expand Down Expand Up @@ -991,6 +1000,26 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy {
validateVerticalPosition('overlayY', pair.overlayY);
});
}

/** Adds a single CSS class or an array of classes on the overlay panel. */
private _addPanelClasses(cssClasses: string | string[]) {
if (this._pane) {
coerceArray(cssClasses).forEach(cssClass => {
if (this._appliedPanelClasses.indexOf(cssClass) === -1) {
this._appliedPanelClasses.push(cssClass);
this._pane.classList.add(cssClass);
}
});
}
}

/** Clears the classes that the position strategy has applied from the overlay panel. */
private _clearPanelClasses() {
if (this._pane) {
this._appliedPanelClasses.forEach(cssClass => this._pane.classList.remove(cssClass));
this._appliedPanelClasses = [];
}
}
}

/** A simple (x, y) coordinate. */
Expand Down Expand Up @@ -1052,6 +1081,7 @@ export interface ConnectedPosition {
weight?: number;
offsetX?: number;
offsetY?: number;
panelClass?: string | string[];
}

/** Shallow-extends a stylesheet object with another stylesheet object. */
Expand Down