Skip to content

Commit a333883

Browse files
committed
refactor(platform): consolidate and expose shadow dom detection utilities
Over time we had accumulated the same utility for finding the shadow root of an element in multiple places. These changes move it into a common place under `cdk/platform`. I also decided to remove the underscore from `_supportsShadowDom` so it's officially a public API. It was already exposed under `cdk/platform` so there was nothing stopping people from using it, but the underscore looked weird. The API should be stable enough, since we've had it for more than a year in multiple components and we haven't had issues with it.
1 parent 9343d08 commit a333883

File tree

7 files changed

+31
-61
lines changed

7 files changed

+31
-61
lines changed

src/cdk/drag-drop/drop-list-ref.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {ElementRef, NgZone} from '@angular/core';
1010
import {Direction} from '@angular/cdk/bidi';
1111
import {coerceElement} from '@angular/cdk/coercion';
1212
import {ViewportRuler} from '@angular/cdk/scrolling';
13-
import {_supportsShadowDom} from '@angular/cdk/platform';
13+
import {_getShadowRoot} from '@angular/cdk/platform';
1414
import {Subject, Subscription, interval, animationFrameScheduler} from 'rxjs';
1515
import {takeUntil} from 'rxjs/operators';
1616
import {moveItemInArray} from './drag-utils';
@@ -907,7 +907,8 @@ export class DropListRef<T = any> {
907907
*/
908908
private _getShadowRoot(): DocumentOrShadowRoot {
909909
if (!this._cachedShadowRoot) {
910-
this._cachedShadowRoot = getShadowRoot(coerceElement(this.element)) || this._document;
910+
const shadowRoot = _getShadowRoot(coerceElement(this.element)) as ShadowRoot | null;
911+
this._cachedShadowRoot = shadowRoot || this._document;
911912
}
912913

913914
return this._cachedShadowRoot;
@@ -1102,16 +1103,3 @@ function getElementScrollDirections(element: HTMLElement, clientRect: ClientRect
11021103

11031104
return [verticalScrollDirection, horizontalScrollDirection];
11041105
}
1105-
1106-
/** Gets the shadow root of an element, if any. */
1107-
function getShadowRoot(element: HTMLElement): DocumentOrShadowRoot | null {
1108-
if (_supportsShadowDom()) {
1109-
const rootNode = element.getRootNode ? element.getRootNode() : null;
1110-
1111-
if (rootNode instanceof ShadowRoot) {
1112-
return rootNode;
1113-
}
1114-
}
1115-
1116-
return null;
1117-
}

src/cdk/platform/features/shadow-dom.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,16 @@ export function _supportsShadowDom(): boolean {
1717

1818
return shadowDomIsSupported;
1919
}
20+
21+
/** Gets the shadow root of an element, if supported and the element is inside the Shadow DOM. */
22+
export function _getShadowRoot(element: HTMLElement): Node | null {
23+
if (_supportsShadowDom()) {
24+
const rootNode = element.getRootNode ? element.getRootNode() : null;
25+
26+
if (rootNode instanceof ShadowRoot) {
27+
return rootNode;
28+
}
29+
}
30+
31+
return null;
32+
}

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
ScrollStrategy,
1818
ConnectedPosition,
1919
} from '@angular/cdk/overlay';
20-
import {_supportsShadowDom} from '@angular/cdk/platform';
20+
import {_getShadowRoot} from '@angular/cdk/platform';
2121
import {TemplatePortal} from '@angular/cdk/portal';
2222
import {ViewportRuler} from '@angular/cdk/scrolling';
2323
import {DOCUMENT} from '@angular/common';
@@ -230,14 +230,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn
230230
window.addEventListener('blur', this._windowBlurHandler);
231231
});
232232

233-
if (_supportsShadowDom()) {
234-
const element = this._element.nativeElement;
235-
const rootNode = element.getRootNode ? element.getRootNode() : null;
236-
237-
// We need to take the `ShadowRoot` off of `window`, because the built-in types are
238-
// incorrect. See https://github.com/Microsoft/TypeScript/issues/27929.
239-
this._isInsideShadowRoot = rootNode instanceof (window as any).ShadowRoot;
240-
}
233+
this._isInsideShadowRoot = !!_getShadowRoot(this._element.nativeElement);
241234
}
242235
}
243236

src/material/input/input.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Platform, PlatformModule, _supportsShadowDom} from '@angular/cdk/platform';
1+
import {Platform, PlatformModule, supportsShadowDom} from '@angular/cdk/platform';
22
import {
33
createFakeEvent,
44
dispatchFakeEvent,
@@ -1519,7 +1519,7 @@ describe('MatInput with appearance', () => {
15191519

15201520

15211521
it('should calculate the outline gaps inside the shadow DOM', fakeAsync(() => {
1522-
if (!_supportsShadowDom()) {
1522+
if (!supportsShadowDom()) {
15231523
return;
15241524
}
15251525

src/material/progress-spinner/progress-spinner.spec.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import {TestBed, async, inject} from '@angular/core/testing';
22
import {Component, ViewEncapsulation, ViewChild, ElementRef} from '@angular/core';
33
import {By} from '@angular/platform-browser';
4-
import {Platform} from '@angular/cdk/platform';
4+
import {Platform, _getShadowRoot, _supportsShadowDom} from '@angular/cdk/platform';
55
import {CommonModule} from '@angular/common';
6-
import {_getShadowRoot} from './progress-spinner';
76
import {
87
MatProgressSpinnerModule,
98
MatProgressSpinner,
109
MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS,
1110
} from './index';
1211

1312
describe('MatProgressSpinner', () => {
14-
const supportsShadowDom = typeof document.createElement('div').attachShadow !== 'undefined';
15-
1613
beforeEach(async(() => {
1714
TestBed.configureTestingModule({
1815
imports: [MatProgressSpinnerModule, CommonModule],
@@ -357,7 +354,7 @@ describe('MatProgressSpinner', () => {
357354

358355
it('should add the indeterminate animation style tag to the Shadow root', () => {
359356
// The test is only relevant in browsers that support Shadow DOM.
360-
if (!supportsShadowDom) {
357+
if (!_supportsShadowDom()) {
361358
return;
362359
}
363360

@@ -366,7 +363,7 @@ describe('MatProgressSpinner', () => {
366363
fixture.detectChanges();
367364

368365
const spinner = fixture.debugElement.query(By.css('mat-progress-spinner'))!.nativeElement;
369-
const shadowRoot = _getShadowRoot(spinner, document) as HTMLElement;
366+
const shadowRoot = _getShadowRoot(spinner) as HTMLElement;
370367

371368
expect(shadowRoot.querySelector('style[mat-spinner-animation="27"]')).toBeTruthy();
372369

@@ -378,7 +375,7 @@ describe('MatProgressSpinner', () => {
378375

379376
it('should not duplicate style tags inside the Shadow root', () => {
380377
// The test is only relevant in browsers that support Shadow DOM.
381-
if (!supportsShadowDom) {
378+
if (!_supportsShadowDom()) {
382379
return;
383380
}
384381

@@ -387,7 +384,7 @@ describe('MatProgressSpinner', () => {
387384
fixture.detectChanges();
388385

389386
const spinner = fixture.debugElement.query(By.css('mat-progress-spinner'))!.nativeElement;
390-
const shadowRoot = _getShadowRoot(spinner, document) as HTMLElement;
387+
const shadowRoot = _getShadowRoot(spinner) as HTMLElement;
391388

392389
expect(shadowRoot.querySelectorAll('style[mat-spinner-animation="39"]').length).toBe(1);
393390

@@ -409,7 +406,7 @@ describe('MatProgressSpinner', () => {
409406
it('should add the indeterminate animation style tag to the Shadow root if the element is ' +
410407
'inside an ngIf', () => {
411408
// The test is only relevant in browsers that support Shadow DOM.
412-
if (!supportsShadowDom) {
409+
if (!_supportsShadowDom()) {
413410
return;
414411
}
415412

@@ -418,7 +415,7 @@ describe('MatProgressSpinner', () => {
418415
fixture.detectChanges();
419416

420417
const spinner = fixture.componentInstance.spinner.nativeElement;
421-
const shadowRoot = _getShadowRoot(spinner, document) as HTMLElement;
418+
const shadowRoot = _getShadowRoot(spinner) as HTMLElement;
422419

423420
expect(shadowRoot.querySelector('style[mat-spinner-animation="27"]')).toBeTruthy();
424421

src/material/progress-spinner/progress-spinner.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {coerceNumberProperty, NumberInput} from '@angular/cdk/coercion';
10-
import {Platform} from '@angular/cdk/platform';
10+
import {Platform, _getShadowRoot} from '@angular/cdk/platform';
1111
import {DOCUMENT} from '@angular/common';
1212
import {
1313
ChangeDetectionStrategy,
@@ -218,7 +218,7 @@ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements
218218
// Note that we need to look up the root node in ngOnInit, rather than the constructor, because
219219
// Angular seems to create the element outside the shadow root and then moves it inside, if the
220220
// node is inside an `ngIf` and a ShadowDom-encapsulated component.
221-
this._styleRoot = _getShadowRoot(element, this._document) || this._document.head;
221+
this._styleRoot = _getShadowRoot(element) || this._document.head;
222222
this._attachStyleNode();
223223

224224
// On IE and Edge, we can't animate the `stroke-dashoffset`
@@ -333,26 +333,3 @@ export class MatSpinner extends MatProgressSpinner {
333333
this.mode = 'indeterminate';
334334
}
335335
}
336-
337-
338-
/** Gets the shadow root of an element, if supported and the element is inside the Shadow DOM. */
339-
export function _getShadowRoot(element: HTMLElement, _document: Document): Node | null {
340-
// TODO(crisbeto): see whether we should move this into the CDK
341-
// feature detection utilities once #15616 gets merged in.
342-
if (typeof window !== 'undefined') {
343-
const head = _document.head;
344-
345-
// Check whether the browser supports Shadow DOM.
346-
if (head && ((head as any).createShadowRoot || head.attachShadow)) {
347-
const rootNode = element.getRootNode ? element.getRootNode() : null;
348-
349-
// We need to take the `ShadowRoot` off of `window`, because the built-in types are
350-
// incorrect. See https://github.com/Microsoft/TypeScript/issues/27929.
351-
if (rootNode instanceof (window as any).ShadowRoot) {
352-
return rootNode;
353-
}
354-
}
355-
}
356-
357-
return null;
358-
}

tools/public_api_guard/cdk/platform.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export declare function _getShadowRoot(element: HTMLElement): Node | null;
2+
13
export declare function _supportsShadowDom(): boolean;
24

35
export declare function getRtlScrollAxisType(): RtlScrollAxisType;

0 commit comments

Comments
 (0)