maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
225 lines (200 loc) • 8.17 kB
text/typescript
import {DOM} from '../../util/dom.ts';
import {warnOnce} from '../../util/util.ts';
import {Event, Evented} from '../../util/evented.ts';
import type {Map} from '../map.ts';
import type {IControl} from './control.ts';
/**
* The {@link FullscreenControl} options object
*/
export type FullscreenControlOptions = {
/**
* `container` is the [compatible DOM element](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen#Compatible_elements) which should be made full screen. By default, the map container element will be made full screen.
*/
container?: HTMLElement;
/**
* If `true`, the fullscreen control will always use pseudo fullscreen mode (CSS-based, expanding to browser viewport) instead of native fullscreen API.
* This can be useful for faster transitions and to allow multiple maps to be "fullscreen" simultaneously in different browser windows.
* @defaultValue false
*/
pseudo?: boolean;
};
/**
* The event class for fullscreen control events (`fullscreenstart` and `fullscreenend`).
*
* @group Event Related
*/
export class FullscreenEvent extends Event {
type: 'fullscreenstart' | 'fullscreenend';
/**
* The `FullscreenControl` object that fired the event.
*/
target: FullscreenControl;
}
/**
* `FullscreenControlEventType` - a mapping between the fullscreen control event name and the event value.
* These events are used with the {@link FullscreenControl.on} method.
*
* @group Event Related
*/
export type FullscreenControlEventType = {
/**
* Fired when fullscreen mode has started.
*/
fullscreenstart: FullscreenEvent;
/**
* Fired when fullscreen mode has ended.
*/
fullscreenend: FullscreenEvent;
};
/**
* A `FullscreenControl` control contains a button for toggling the map in and out of fullscreen mode.
* When [requestFullscreen](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen) is not supported, fullscreen is handled via CSS properties.
* The map's `cooperativeGestures` option is temporarily disabled while the map
* is in fullscreen mode, and is restored when the map exist fullscreen mode.
*
* @group Markers and Controls
* @param options - the full screen control options
*
* @example
* ```ts
* map.addControl(new FullscreenControl({container: document.querySelector('body')}));
* ```
* @see [View a fullscreen map](https://maplibre.org/maplibre-gl-js/docs/examples/view-a-fullscreen-map/)
*
* ## Events
*
* **Event** `fullscreenstart` of type {@link FullscreenEvent} will be fired when fullscreen mode has started.
*
* **Event** `fullscreenend` of type {@link FullscreenEvent} will be fired when fullscreen mode has ended.
*/
export class FullscreenControl extends Evented<FullscreenControlEventType> implements IControl {
_map: Map;
_controlContainer: HTMLElement;
_fullscreen: boolean;
_fullscreenchange: string;
_fullscreenButton: HTMLButtonElement;
_container: HTMLElement;
_prevCooperativeGesturesEnabled: boolean;
_pseudo: boolean;
/**
* @param options - the control's options
*/
constructor(options: FullscreenControlOptions = {}) {
super();
this._fullscreen = false;
this._pseudo = options.pseudo ?? false;
if (options?.container) {
if (options.container instanceof HTMLElement) {
this._container = options.container;
} else {
warnOnce('Full screen control \'container\' must be a DOM element.');
}
}
if ('onfullscreenchange' in document) {
this._fullscreenchange = 'fullscreenchange';
} else if ('onmozfullscreenchange' in document) {
this._fullscreenchange = 'mozfullscreenchange';
} else if ('onwebkitfullscreenchange' in document) {
this._fullscreenchange = 'webkitfullscreenchange';
} else if ('onmsfullscreenchange' in document) {
this._fullscreenchange = 'MSFullscreenChange';
}
}
/** {@inheritDoc IControl.onAdd} */
onAdd(map: Map): HTMLElement {
this._map = map;
this._container ||= this._map.getContainer();
this._controlContainer = DOM.create('div', 'maplibregl-ctrl maplibregl-ctrl-group');
this._setupUI();
return this._controlContainer;
}
/** {@inheritDoc IControl.onRemove} */
onRemove(): void {
this._controlContainer.remove();
this._map = null;
window.document.removeEventListener(this._fullscreenchange, this._onFullscreenChange);
}
_setupUI(): void {
const button = this._fullscreenButton = DOM.create('button', (('maplibregl-ctrl-fullscreen')), this._controlContainer);
DOM.create('span', 'maplibregl-ctrl-icon', button).setAttribute('aria-hidden', 'true');
button.type = 'button';
this._updateTitle();
this._fullscreenButton.addEventListener('click', this._onClickFullscreen);
window.document.addEventListener(this._fullscreenchange, this._onFullscreenChange);
}
_updateTitle(): void {
const title = this._getTitle();
this._fullscreenButton.setAttribute('aria-label', title);
this._fullscreenButton.title = title;
}
_getTitle(): string {
return this._map._getUIString(this._isFullscreen() ? 'FullscreenControl.Exit' : 'FullscreenControl.Enter');
}
_isFullscreen(): boolean {
return this._fullscreen;
}
_onFullscreenChange = (): void => {
// WebKit due to https://caniuse.com/mdn-api_document_fullscreenelement
let fullscreenElement =
window.document.fullscreenElement ||
(window.document as any).webkitFullscreenElement;
while (fullscreenElement?.shadowRoot?.fullscreenElement) {
fullscreenElement = fullscreenElement.shadowRoot.fullscreenElement;
}
if ((fullscreenElement === this._container) !== this._fullscreen) {
this._handleFullscreenChange();
}
};
_handleFullscreenChange(): void {
this._fullscreen = !this._fullscreen;
this._fullscreenButton.classList.toggle('maplibregl-ctrl-shrink');
this._fullscreenButton.classList.toggle('maplibregl-ctrl-fullscreen');
this._updateTitle();
if (this._fullscreen) {
this.fire(new FullscreenEvent('fullscreenstart'));
this._prevCooperativeGesturesEnabled = this._map.cooperativeGestures.isEnabled();
this._map.cooperativeGestures.disable();
} else {
this.fire(new FullscreenEvent('fullscreenend'));
if (this._prevCooperativeGesturesEnabled) {
this._map.cooperativeGestures.enable();
}
}
}
_onClickFullscreen = (): void => {
if (this._isFullscreen()) {
this._exitFullscreen();
} else {
this._requestFullscreen();
}
};
_exitFullscreen(): void {
if (this._pseudo) {
this._togglePseudoFullScreen();
} else if (window.document.exitFullscreen) {
(window.document as any).exitFullscreen();
} else if ((window.document as any).webkitCancelFullScreen) {
// due to https://caniuse.com/mdn-api_document_exitfullscreen
(window.document as any).webkitCancelFullScreen();
} else {
this._togglePseudoFullScreen();
}
}
_requestFullscreen(): void {
if (this._pseudo) {
this._togglePseudoFullScreen();
} else if (this._container.requestFullscreen) {
this._container.requestFullscreen();
} else if ((this._container as any).webkitRequestFullscreen) {
// due to https://caniuse.com/mdn-api_element_requestfullscreen
(this._container as any).webkitRequestFullscreen();
} else {
this._togglePseudoFullScreen();
}
}
_togglePseudoFullScreen(): void {
this._container.classList.toggle('maplibregl-pseudo-fullscreen');
this._handleFullscreenChange();
this._map.resize();
}
}