Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions examples/widgets/html-overlays/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
HtmlOverlayWidget,
HtmlTooltipWidget
} from '@deck.gl-community/widgets';
import {h} from 'preact';
import {h, render, type JSX} from 'preact';

const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';

Expand Down Expand Up @@ -120,6 +120,21 @@ const PIN_STYLE = {
minWidth: 150
};

const createOverlayRoot = (container: HTMLElement) => {
const overlayRoot = document.createElement('div');
overlayRoot.className = 'html-overlay-root';
container.appendChild(overlayRoot);
return overlayRoot;
};

const renderOverlayContent = (
overlayRoot: unknown,
element: JSX.Element | null,
container: HTMLElement
) => {
render(element, (overlayRoot as Element | Document | ShadowRoot | null) ?? container);
};

export function App() {
const overlayItems = DESTINATIONS.map(({id, name, subtitle, coordinates}) =>
h(
Expand All @@ -142,11 +157,20 @@ export function App() {
)
);

const overlayCallbacks = useMemo(
() => ({
onCreateOverlay: createOverlayRoot,
onRenderOverlay: renderOverlayContent
}),
[]
);

const overlayWidget = new HtmlOverlayWidget({
id: 'html-destination-overlays',
overflowMargin: 128,
zIndex: 3,
items: overlayItems
items: overlayItems,
...overlayCallbacks
});

const clusterWidget = new (class extends HtmlClusterWidget<(typeof STOPOVERS)[number]> {
Expand All @@ -173,13 +197,15 @@ export function App() {
})({
id: 'html-cluster-overlays',
overflowMargin: 96,
zIndex: 4
zIndex: 4,
...overlayCallbacks
});

const tooltipWidget = new HtmlTooltipWidget({
id: 'html-overlay-tooltips',
showDelay: 120,
zIndex: 6,
...overlayCallbacks,
getTooltip: (info) => {
const stop = info.object as (typeof STOPOVERS)[number] | (typeof DESTINATIONS)[number] | null;
if (!stop) {
Expand Down
40 changes: 33 additions & 7 deletions modules/widgets/src/widgets/html-overlay-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {cloneElement, render, toChildArray, Fragment, type ComponentChildren, type VNode} from 'preact';
import {
cloneElement,
render,
toChildArray,
Fragment,
type ComponentChildren,
type VNode,
type JSX
} from 'preact';
import type {Deck, Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core';
import {Widget} from '@deck.gl/core';

Expand All @@ -15,6 +23,10 @@ export type HtmlOverlayWidgetProps = WidgetProps & {
zIndex?: number;
/** Items to render; defaults to the supplied children. */
items?: ComponentChildren;
/** Create an overlay root for custom rendering. */
onCreateOverlay?: (container: HTMLElement) => unknown;
/** Render into a previously created overlay root. */
onRenderOverlay?: (overlayRoot: unknown, element: JSX.Element | null, container: HTMLElement) => void;
};

const ROOT_STYLE: Partial<CSSStyleDeclaration> = {
Expand All @@ -41,6 +53,8 @@ export class HtmlOverlayWidget<PropsT extends HtmlOverlayWidgetProps = HtmlOverl
className = 'deck-widget-html-overlay';
deck?: Deck | null = null;
protected viewport: Viewport | null = null;
protected overlayRoot: unknown = null;
protected overlayRootInitialized = false;

constructor(props: PropsT = {} as PropsT) {
super({...HtmlOverlayWidget.defaultProps, ...props});
Expand All @@ -64,6 +78,8 @@ export class HtmlOverlayWidget<PropsT extends HtmlOverlayWidgetProps = HtmlOverl
override onRemove(): void {
this.deck = null;
this.viewport = null;
this.overlayRoot = null;
this.overlayRootInitialized = false;
}

override onViewportChange(viewport: Viewport): void {
Expand Down Expand Up @@ -134,14 +150,24 @@ export class HtmlOverlayWidget<PropsT extends HtmlOverlayWidgetProps = HtmlOverl
Object.assign(rootElement.style, ROOT_STYLE, {zIndex: `${this.props.zIndex ?? 1}`});

const viewport = this.getViewport();
if (!viewport) {
render(null, rootElement);
const element = viewport
? (() => {
const overlayItems = this.getOverlayItems(viewport);
const renderedItems = this.projectItems(overlayItems, viewport);
return <Fragment>{renderedItems}</Fragment>;
})()
: null;

const {onRenderOverlay, onCreateOverlay} = this.props;
if (onRenderOverlay) {
if (!this.overlayRootInitialized) {
this.overlayRoot = onCreateOverlay?.(rootElement) ?? null;
this.overlayRootInitialized = true;
}
onRenderOverlay(this.overlayRoot, element, rootElement);
return;
}

const overlayItems = this.getOverlayItems(viewport);
const renderedItems = this.projectItems(overlayItems, viewport);

render(<Fragment>{renderedItems}</Fragment>, rootElement);
render(element, rootElement);
}
}