/
githubmirror
/
components
Обзор
Документация
Войти
/
githubmirror
/
components
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/cdk/overlay/overlay.ts
152 строки
5 KB
Artur
fix(cdk/overlay): guard against null document.body before popover support check (#33403)
08 июл 2026, 15:37
Не верифицирован
08 июл 2026, 15:37
67a5031
Код
Авторство
О чём код?
/** * @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.dev/license */ import {Directionality} from '../bidi'; import {DomPortalOutlet} from '../portal'; import {Location} from '@angular/common'; import { ApplicationRef, Service, Injector, NgZone, ANIMATION_MODULE_TYPE, EnvironmentInjector, inject, RendererFactory2, DOCUMENT, Renderer2, InjectionToken, } from '@angular/core'; import {_IdGenerator} from '../a11y'; import {_CdkPrivateStyleLoader} from '../private'; import {OverlayKeyboardDispatcher} from './dispatchers/overlay-keyboard-dispatcher'; import {OverlayOutsideClickDispatcher} from './dispatchers/overlay-outside-click-dispatcher'; import {OverlayConfig} from './overlay-config'; import {_CdkOverlayStyleLoader, OverlayContainer} from './overlay-container'; import {isElement, OverlayRef} from './overlay-ref'; import {OverlayPositionBuilder} from './position/overlay-position-builder'; import {ScrollStrategyOptions} from './scroll/index'; /** Object used to configure the default options for overlays. */ export interface OverlayDefaultConfig { /** Whether overlays should be rendered inside popovers by default. */ usePopover?: boolean; } /** Injection token used to configure the default options for CDK overlays. */ export const OVERLAY_DEFAULT_CONFIG = new InjectionToken<OverlayDefaultConfig>( 'OVERLAY_DEFAULT_CONFIG', ); /** * Creates an overlay. * @param injector Injector to use when resolving the overlay's dependencies. * @param config Configuration applied to the overlay. * @returns Reference to the created overlay. */ export function createOverlayRef(injector: Injector, config?: OverlayConfig): OverlayRef { // This is done in the overlay container as well, but we have it here // since it's common to mock out the overlay container in tests. injector.get(_CdkPrivateStyleLoader).load(_CdkOverlayStyleLoader); const overlayContainer = injector.get(OverlayContainer); const doc = injector.get(DOCUMENT); const idGenerator = injector.get(_IdGenerator); const appRef = injector.get(ApplicationRef); const directionality = injector.get(Directionality); const renderer = injector.get(Renderer2, null, {optional: true}) || injector.get(RendererFactory2).createRenderer(null, null); const overlayConfig = new OverlayConfig(config); const defaultUsePopover = injector.get(OVERLAY_DEFAULT_CONFIG, null, {optional: true})?.usePopover ?? true; overlayConfig.direction = overlayConfig.direction || directionality.value; // `document.body` can be null during page navigation or unload cycles per the WHATWG spec // (https://html.spec.whatwg.org/multipage/dom.html#dom-document-body), even though TypeScript // types it as non-nullable. Guard against it to avoid "Cannot use 'in' operator ... in null". if (!doc.body || !('showPopover' in doc.body)) { overlayConfig.usePopover = false; } else { overlayConfig.usePopover = config?.usePopover ?? defaultUsePopover; } const pane = doc.createElement('div'); const host = doc.createElement('div'); pane.id = idGenerator.getId('cdk-overlay-'); pane.classList.add('cdk-overlay-pane'); host.appendChild(pane); if (overlayConfig.usePopover) { host.setAttribute('popover', 'manual'); host.classList.add('cdk-overlay-popover'); } const customInsertionPoint = overlayConfig.usePopover ? overlayConfig.positionStrategy?.getPopoverInsertionPoint?.() : null; if (isElement(customInsertionPoint)) { customInsertionPoint.after(host); } else if (customInsertionPoint?.type === 'parent') { customInsertionPoint.element.appendChild(host); } else { overlayContainer.getContainerElement().appendChild(host); } return new OverlayRef( new DomPortalOutlet(pane, appRef, injector), host, pane, overlayConfig, injector.get(NgZone), injector.get(OverlayKeyboardDispatcher), doc, injector.get(Location), injector.get(OverlayOutsideClickDispatcher), config?.disableAnimations ?? injector.get(ANIMATION_MODULE_TYPE, null, {optional: true}) === 'NoopAnimations', injector.get(EnvironmentInjector), renderer, ); } /** * Service to create Overlays. Overlays are dynamically added pieces of floating UI, meant to be * used as a low-level building block for other components. Dialogs, tooltips, menus, * selects, etc. can all be built using overlays. The service should primarily be used by authors * of re-usable components rather than developers building end-user applications. * * An overlay *is* a PortalOutlet, so any kind of Portal can be loaded into one. */ @Service() export class Overlay { scrollStrategies = inject(ScrollStrategyOptions); private _positionBuilder = inject(OverlayPositionBuilder); private _injector = inject(Injector); /** * Creates an overlay. * @param config Configuration applied to the overlay. * @returns Reference to the created overlay. */ create(config?: OverlayConfig): OverlayRef { return createOverlayRef(this._injector, config); } /** * Gets a position builder that can be used, via fluent API, * to construct and configure a position strategy. * @returns An overlay position builder. */ position(): OverlayPositionBuilder { return this._positionBuilder; } }