// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors /* global document */ import {log, Widget, type WidgetProps, type WidgetPlacement} from '@deck.gl/core'; import {render} from 'preact'; import {IconButton} from './lib/components/icon-button'; /* eslint-enable max-len */ export type FullscreenWidgetProps = WidgetProps & { id?: string; /** Widget positioning within the view. Default 'top-left'. */ placement?: WidgetPlacement; /** View to attach to and interact with. Required when using multiple views. */ viewId?: string | null; /** Tooltip message when out of fullscreen. */ enterLabel?: string; /** Tooltip message when fullscreen. */ exitLabel?: string; /** * A compatible DOM element which should be made full screen. By default, the map container element will be made full screen. * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen#Compatible_elements */ container?: HTMLElement; /** * Callback when fullscreen state changes (via user click or browser fullscreen events). */ onFullscreenChange?: (fullscreen: boolean) => void; }; export class FullscreenWidget extends Widget { static defaultProps: Required = { ...Widget.defaultProps, id: 'fullscreen', placement: 'top-left', viewId: null, enterLabel: 'Enter Fullscreen', exitLabel: 'Exit Fullscreen', container: undefined!, onFullscreenChange: () => {} }; className = 'deck-widget-fullscreen'; placement: WidgetPlacement = 'top-left'; fullscreen: boolean = false; constructor(props: FullscreenWidgetProps = {}) { super(props); this.setProps(this.props); } onAdd(): void { document.addEventListener('fullscreenchange', this.onFullscreenChange.bind(this)); } onRemove() { document.removeEventListener('fullscreenchange', this.onFullscreenChange.bind(this)); } onRenderHTML(rootElement: HTMLElement): void { const isFullscreen = this.getFullscreen(); render( { this.handleClick().catch(err => log.error(err)()); }} label={isFullscreen ? this.props.exitLabel : this.props.enterLabel} className={isFullscreen ? 'deck-widget-fullscreen-exit' : 'deck-widget-fullscreen-enter'} />, rootElement ); } setProps(props: Partial) { this.placement = props.placement ?? this.placement; this.viewId = props.viewId ?? this.viewId; super.setProps(props); } getContainer() { return this.props.container || this.deck?.props.parent || this.deck?.getCanvas()?.parentElement; } getFullscreen(): boolean { return this.fullscreen; } onFullscreenChange() { const fullscreen = document.fullscreenElement === this.getContainer(); if (fullscreen !== this.fullscreen) { this.fullscreen = fullscreen; this.props.onFullscreenChange?.(fullscreen); this.updateHTML(); } } async handleClick() { const isFullscreen = this.getFullscreen(); if (isFullscreen) { await this.exitFullscreen(); } else { await this.requestFullscreen(); } // Note: updateHTML is called by onFullscreenChange event handler } async requestFullscreen() { const container = this.getContainer(); if (container?.requestFullscreen) { await container.requestFullscreen({navigationUI: 'hide'}); } else { this.togglePseudoFullscreen(); } } async exitFullscreen() { if (document.exitFullscreen) { await document.exitFullscreen(); } else { this.togglePseudoFullscreen(); } } togglePseudoFullscreen() { this.getContainer()?.classList.toggle('deck-pseudo-fullscreen'); // No fullscreenchange event fires for pseudo-fullscreen, so manually update state this.fullscreen = !this.fullscreen; this.props.onFullscreenChange?.(this.fullscreen); this.updateHTML(); } }