UNPKG

@deck.gl/core

Version:

deck.gl core library

157 lines (133 loc) 5.23 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import type Deck from './deck'; import type Viewport from '../viewports/viewport'; import type {PickingInfo} from './picking/pick-info'; import type {MjolnirPointerEvent, MjolnirGestureEvent} from 'mjolnir.js'; import type Layer from './layer'; import type {WidgetManager, WidgetPlacement} from './widget-manager'; import type {ViewOrViews} from './view-manager'; import {deepEqual} from '../utils/deep-equal'; import {applyStyles, removeStyles} from '../utils/apply-styles'; export type WidgetProps = { id?: string; /** CSS inline style overrides. */ style?: Partial<CSSStyleDeclaration>; /** Additional CSS class. */ className?: string; }; export abstract class Widget< PropsT extends WidgetProps = WidgetProps, ViewsT extends ViewOrViews = null > { static defaultProps: Required<WidgetProps> = { id: 'widget', style: {}, className: '' }; /** Unique identifier of the widget. */ id: string; /** Widget props, with defaults applied */ props: Required<PropsT>; /** * The view id that this widget is being attached to. Default `null`. * If assigned, this widget will only respond to events occurred inside the specific view that matches this id. */ viewId?: string | null = null; /** Widget positioning within the view. Default 'top-left'. */ abstract placement: WidgetPlacement; /** Class name for this widget */ abstract className: string; // Populated by core when mounted widgetManager?: WidgetManager; deck?: Deck<ViewsT>; rootElement?: HTMLDivElement | null; constructor(props: PropsT) { this.props = { // @ts-expect-error `defaultProps` may not exist on constructor ...(this.constructor.defaultProps as Required<PropsT>), ...props }; // @ts-expect-error TODO(ib) - why is id considered optional even though we use Required<> this.id = this.props.id; } /** Called to update widget options */ setProps(props: Partial<PropsT>): void { const oldProps = this.props; const el = this.rootElement; // Update className and style if (el && oldProps.className !== props.className) { if (oldProps.className) el.classList.remove(oldProps.className); if (props.className) el.classList.add(props.className); } // Update style if (el && !deepEqual(oldProps.style, props.style, 1)) { removeStyles(el, oldProps.style); applyStyles(el, props.style); } Object.assign(this.props, props); // Update the HTML to match the new props this.updateHTML(); } /** Update the HTML to reflect latest props and state */ updateHTML(): void { if (this.rootElement) { this.onRenderHTML(this.rootElement); } } // @note empty method calls have an overhead in V8 but it is very low, ~1ns /** * Common utility to create the root DOM element for this widget * Configures the top-level styles and adds basic class names for theming * @returns an UI element that should be appended to the Deck container */ protected onCreateRootElement(): HTMLDivElement { const CLASS_NAMES = [ // Add class names for theming 'deck-widget', this.className, // plus any app-supplied class name this.props.className ]; const element = document.createElement('div'); CLASS_NAMES.filter((cls): cls is string => typeof cls === 'string' && cls.length > 0).forEach( className => element.classList.add(className) ); applyStyles(element, this.props.style); return element; } // WIDGET LIFECYCLE /** Called to render HTML into the root element */ abstract onRenderHTML(rootElement: HTMLElement): void; /** Internal API called by Deck when the widget is first added to a Deck instance */ _onAdd(params: {deck: Deck<any>; viewId: string | null}): HTMLDivElement { return this.onAdd(params) ?? this.onCreateRootElement(); } /** Overridable by subclass - called when the widget is first added to a Deck instance * @returns an optional UI element that should be appended to the Deck container */ onAdd(params: { /** The Deck instance that the widget is attached to */ deck: Deck<any>; /** The id of the view that the widget is attached to */ viewId: string | null; }): HTMLDivElement | void {} /** Called when the widget is removed */ onRemove(): void {} // deck integration - Event hooks /** Called when the containing view is changed */ onViewportChange(viewport: Viewport): void {} /** Called when the containing view is redrawn */ onRedraw(params: {viewports: Viewport[]; layers: Layer[]}): void {} /** Called when a hover event occurs */ onHover(info: PickingInfo, event: MjolnirPointerEvent): void {} /** Called when a click event occurs */ onClick(info: PickingInfo, event: MjolnirGestureEvent): void {} /** Called when a drag event occurs */ onDrag(info: PickingInfo, event: MjolnirGestureEvent): void {} /** Called when a dragstart event occurs */ onDragStart(info: PickingInfo, event: MjolnirGestureEvent): void {} /** Called when a dragend event occurs */ onDragEnd(info: PickingInfo, event: MjolnirGestureEvent): void {} }