diff --git a/src/cdk/coercion/css-pixel-value.spec.ts b/src/cdk/coercion/css-pixel-value.spec.ts new file mode 100644 index 000000000000..a3706921547d --- /dev/null +++ b/src/cdk/coercion/css-pixel-value.spec.ts @@ -0,0 +1,19 @@ +import {coerceCssPixelValue} from './css-pixel-value'; + +describe('coerceCssPixelValue', () => { + it('should add pixel units to a number value', () => { + expect(coerceCssPixelValue(1337)).toBe('1337px'); + }); + + it('should ignore string values', () => { + expect(coerceCssPixelValue('1337rem')).toBe('1337rem'); + }); + + it('should return an empty string for null', () => { + expect(coerceCssPixelValue(null)).toBe(''); + }); + + it('should return an empty string for undefined', () => { + expect(coerceCssPixelValue(undefined)).toBe(''); + }); +}); diff --git a/src/cdk/coercion/css-pixel-value.ts b/src/cdk/coercion/css-pixel-value.ts new file mode 100644 index 000000000000..195110d3e230 --- /dev/null +++ b/src/cdk/coercion/css-pixel-value.ts @@ -0,0 +1,16 @@ +/** + * @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 + */ + +/** Coerces a value to a CSS pixel value. */ +export function coerceCssPixelValue(value: any): string { + if (value == null) { + return ''; + } + + return typeof value === 'string' ? value : `${value}px`; +} diff --git a/src/cdk/coercion/public-api.ts b/src/cdk/coercion/public-api.ts index aa2fd160b1ab..6563d2bfab05 100644 --- a/src/cdk/coercion/public-api.ts +++ b/src/cdk/coercion/public-api.ts @@ -9,3 +9,4 @@ export * from './boolean-property'; export * from './number-property'; export * from './array'; +export * from './css-pixel-value'; diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index cd8ff411d415..83eb45f9c665 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -13,6 +13,7 @@ import {Observable, Subject} from 'rxjs'; import {take} from 'rxjs/operators'; import {OverlayKeyboardDispatcher} from './keyboard/overlay-keyboard-dispatcher'; import {OverlayConfig} from './overlay-config'; +import {coerceCssPixelValue} from '@angular/cdk/coercion'; /** An object where all of its properties cannot be written. */ @@ -254,27 +255,27 @@ export class OverlayRef implements PortalOutlet { /** Updates the size of the overlay element based on the overlay config. */ private _updateElementSize() { if (this._config.width || this._config.width === 0) { - this._pane.style.width = formatCssUnit(this._config.width); + this._pane.style.width = coerceCssPixelValue(this._config.width); } if (this._config.height || this._config.height === 0) { - this._pane.style.height = formatCssUnit(this._config.height); + this._pane.style.height = coerceCssPixelValue(this._config.height); } if (this._config.minWidth || this._config.minWidth === 0) { - this._pane.style.minWidth = formatCssUnit(this._config.minWidth); + this._pane.style.minWidth = coerceCssPixelValue(this._config.minWidth); } if (this._config.minHeight || this._config.minHeight === 0) { - this._pane.style.minHeight = formatCssUnit(this._config.minHeight); + this._pane.style.minHeight = coerceCssPixelValue(this._config.minHeight); } if (this._config.maxWidth || this._config.maxWidth === 0) { - this._pane.style.maxWidth = formatCssUnit(this._config.maxWidth); + this._pane.style.maxWidth = coerceCssPixelValue(this._config.maxWidth); } if (this._config.maxHeight || this._config.maxHeight === 0) { - this._pane.style.maxHeight = formatCssUnit(this._config.maxHeight); + this._pane.style.maxHeight = coerceCssPixelValue(this._config.maxHeight); } } @@ -369,10 +370,6 @@ export class OverlayRef implements PortalOutlet { } } -function formatCssUnit(value: number | string) { - return typeof value === 'string' ? value as string : `${value}px`; -} - /** Size properties for an overlay. */ export interface OverlaySizeConfig { diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index 4792318a89d4..76ced6f3762b 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -17,6 +17,7 @@ import { import {Observable, Subscription, Subject} from 'rxjs'; import {OverlayRef} from '../overlay-ref'; import {isElementScrolledOutsideView, isElementClippedByScrolling} from './scroll-clip'; +import {coerceCssPixelValue} from '@angular/cdk/coercion'; // TODO: refactor clipping detection into a separate thing (part of scrolling module) @@ -665,9 +666,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { styles.bottom = ''; styles.height = '100%'; } else { - styles.height = `${boundingBoxRect.height}px`; - styles.top = boundingBoxRect.top != null ? `${boundingBoxRect.top}px` : ''; - styles.bottom = boundingBoxRect.bottom != null ? `${boundingBoxRect.bottom}px` : ''; + styles.top = coerceCssPixelValue(boundingBoxRect.top); + styles.bottom = coerceCssPixelValue(boundingBoxRect.bottom); + styles.height = coerceCssPixelValue(boundingBoxRect.height); } if (!this._hasFlexibleWidth || this._isPushed) { @@ -675,19 +676,19 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { styles.right = ''; styles.width = '100%'; } else { - styles.width = `${boundingBoxRect.width}px`; - styles.left = boundingBoxRect.left != null ? `${boundingBoxRect.left}px` : ''; - styles.right = boundingBoxRect.right != null ? `${boundingBoxRect.right}px` : ''; + styles.left = coerceCssPixelValue(boundingBoxRect.left); + styles.right = coerceCssPixelValue(boundingBoxRect.right); + styles.width = coerceCssPixelValue(boundingBoxRect.width); } const maxHeight = this._overlayRef.getConfig().maxHeight; if (maxHeight && this._hasFlexibleHeight) { - styles.maxHeight = formatCssUnit(maxHeight); + styles.maxHeight = coerceCssPixelValue(maxHeight); } const maxWidth = this._overlayRef.getConfig().maxWidth; if (maxWidth && this._hasFlexibleWidth) { - styles.maxWidth = formatCssUnit(maxWidth); + styles.maxWidth = coerceCssPixelValue(maxWidth); } this._lastBoundingBoxSize = boundingBoxRect; @@ -800,7 +801,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { const documentHeight = this._document.documentElement.clientHeight; styles.bottom = `${documentHeight - (overlayPoint.y + this._overlayRect.height)}px`; } else { - styles.top = `${overlayPoint.y}px`; + styles.top = coerceCssPixelValue(overlayPoint.y); } return styles; @@ -835,7 +836,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { const documentWidth = this._document.documentElement.clientWidth; styles.right = `${documentWidth - (overlayPoint.x + this._overlayRect.width)}px`; } else { - styles.left = `${overlayPoint.x}px`; + styles.left = coerceCssPixelValue(overlayPoint.x); } return styles; @@ -971,11 +972,6 @@ export interface ConnectedPosition { offsetY?: number; } -// TODO: move to common place -function formatCssUnit(value: number | string) { - return typeof value === 'string' ? value as string : `${value}px`; -} - /** Shallow-extends a stylesheet object with another stylesheet object. */ function extendStyles(dest: CSSStyleDeclaration, source: CSSStyleDeclaration): CSSStyleDeclaration { for (let key in source) { diff --git a/src/cdk/overlay/scroll/block-scroll-strategy.ts b/src/cdk/overlay/scroll/block-scroll-strategy.ts index 00cd0642e404..5ad36ea2a3e1 100644 --- a/src/cdk/overlay/scroll/block-scroll-strategy.ts +++ b/src/cdk/overlay/scroll/block-scroll-strategy.ts @@ -8,6 +8,7 @@ import {ScrollStrategy} from './scroll-strategy'; import {ViewportRuler} from '@angular/cdk/scrolling'; +import {coerceCssPixelValue} from '@angular/cdk/coercion'; /** * Strategy that will prevent the user from scrolling while the overlay is visible. @@ -38,8 +39,8 @@ export class BlockScrollStrategy implements ScrollStrategy { // Note: we're using the `html` node, instead of the `body`, because the `body` may // have the user agent margin, whereas the `html` is guaranteed not to have one. - root.style.left = `${-this._previousScrollPosition.left}px`; - root.style.top = `${-this._previousScrollPosition.top}px`; + root.style.left = coerceCssPixelValue(-this._previousScrollPosition.left); + root.style.top = coerceCssPixelValue(-this._previousScrollPosition.top); root.classList.add('cdk-global-scrollblock'); this._isEnabled = true; }