Skip to content

Commit 63f44c6

Browse files
committed
feat(widgets) Styled tooltips
1 parent 8265fd8 commit 63f44c6

28 files changed

Lines changed: 329 additions & 18 deletions

docs/api-reference/core/widget.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ Updates the widget. Called by the specific widget when state has changed. Calls
9191

9292
This function is implemented by the specific widget subclass to update the HTML for the widget
9393

94+
#### `onAfterRenderHTML` {#onafterrenderhtml}
95+
96+
Optional. Called after `onRenderHTML()` has updated the widget HTML.
97+
9498
#### `onAdd` {#onadd}
9599

96100
Required. Called when the widget is added to a Deck instance.

docs/api-reference/widgets/overview.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ new Deck({
133133

134134
Widgets with UI (e.g. a button or panel) can be positioned relative to the deck.gl view they are controlling, via the `viewId` and `placement` props. See [WidgetProps](../core/widget.md#widgetprops).
135135

136+
Bundled widgets render their control labels as themed tooltips instead of native browser tooltips. Custom widgets can use `updateWidgetTooltip` from `@deck.gl/widgets` in `onAfterRenderHTML()` to apply the same rendering to descendants with a `data-deck-widget-tooltip` attribute.
137+
136138
The `viewId` controls which HTML container will mount to, and the `placement` prop will position it relative to the container it is in, like so:
137139

138140
```ts

docs/whats-new.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Release date: TBD
4141

4242
A new experimental `ViewLayout` system with a helper function `buildViewsFromViewLayout()` allows advanced nested and relative view layouts to be specified using a declarative layout tree.
4343

44+
Bundled widgets now render themed tooltips for their controls.
45+
4446
## deck.gl v9.3
4547

4648
Release date: April 13, 2026

modules/core/src/lib/widget.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export abstract class Widget<
9595
updateHTML(): void {
9696
if (this.rootElement) {
9797
this.onRenderHTML(this.rootElement);
98+
this.onAfterRenderHTML(this.rootElement);
9899
}
99100
}
100101

@@ -144,6 +145,9 @@ export abstract class Widget<
144145
/** Called to render HTML into the root element */
145146
abstract onRenderHTML(rootElement: HTMLElement): void;
146147

148+
/** Overridable by subclass - called after HTML is rendered into the root element. */
149+
protected onAfterRenderHTML(rootElement: HTMLElement): void {}
150+
147151
/** Internal API called by Deck when the widget is first added to a Deck instance */
148152
_onAdd(params: {deck: Deck<any>; viewId: string | null}): HTMLDivElement {
149153
return this.onAdd(params) ?? this.onCreateRootElement();

modules/widgets/src/compass-widget.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import {Widget, FlyToInterpolator, WebMercatorViewport, _GlobeViewport} from '@deck.gl/core';
66
import type {Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core';
77
import {render} from 'preact';
8+
import {updateWidgetTooltip} from './lib/widget-tooltip';
89

910
export type CompassWidgetProps = WidgetProps & {
1011
/** Widget positioning within the view. Default 'top-left'. */
@@ -30,6 +31,8 @@ export type CompassWidgetProps = WidgetProps & {
3031
};
3132

3233
export class CompassWidget extends Widget<CompassWidgetProps> {
34+
protected override onAfterRenderHTML = updateWidgetTooltip;
35+
3336
static defaultProps: Required<CompassWidgetProps> = {
3437
...Widget.defaultProps,
3538
id: 'compass',
@@ -69,7 +72,8 @@ export class CompassWidget extends Widget<CompassWidgetProps> {
6972
this.handleCompassReset(viewport);
7073
}
7174
}}
72-
title={this.props.label}
75+
aria-label={this.props.label}
76+
data-deck-widget-tooltip={this.props.label}
7377
style={{transform: `rotateX(${rx}deg)`}}
7478
>
7579
<svg fill="none" width="100%" height="100%" viewBox="0 0 26 26">

modules/widgets/src/fullscreen-widget.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/* global document */
66
import {log, Widget, type WidgetProps, type WidgetPlacement} from '@deck.gl/core';
77
import {render} from 'preact';
8+
import {updateWidgetTooltip} from './lib/widget-tooltip';
89
import {IconButton} from './lib/components/icon-button';
910

1011
/* eslint-enable max-len */
@@ -31,6 +32,8 @@ export type FullscreenWidgetProps = WidgetProps & {
3132
};
3233

3334
export class FullscreenWidget extends Widget<FullscreenWidgetProps> {
35+
protected override onAfterRenderHTML = updateWidgetTooltip;
36+
3437
static defaultProps: Required<FullscreenWidgetProps> = {
3538
...Widget.defaultProps,
3639
id: 'fullscreen',

modules/widgets/src/gimbal-widget.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import {Widget, LinearInterpolator} from '@deck.gl/core';
66
import type {Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core';
77
import {render} from 'preact';
8+
import {updateWidgetTooltip} from './lib/widget-tooltip';
89

910
export type GimbalWidgetProps = WidgetProps & {
1011
placement?: WidgetPlacement;
@@ -31,6 +32,8 @@ export type GimbalWidgetProps = WidgetProps & {
3132
};
3233

3334
export class GimbalWidget extends Widget<GimbalWidgetProps> {
35+
protected override onAfterRenderHTML = updateWidgetTooltip;
36+
3437
static defaultProps: Required<GimbalWidgetProps> = {
3538
...Widget.defaultProps,
3639
id: 'gimbal',
@@ -71,7 +74,8 @@ export class GimbalWidget extends Widget<GimbalWidgetProps> {
7174
this.resetOrbitView(viewport);
7275
}
7376
}}
74-
title={this.props.label}
77+
aria-label={this.props.label}
78+
data-deck-widget-tooltip={this.props.label}
7579
style={{position: 'relative', width: 26, height: 26}}
7680
>
7781
{/* Outer ring */}

modules/widgets/src/icon-widget.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type {WidgetPlacement, WidgetProps} from '@deck.gl/core';
66
import {render, type JSX} from 'preact';
77
import {Widget} from '@deck.gl/core';
8+
import {updateWidgetTooltip} from './lib/widget-tooltip';
89
import {IconButton} from './lib/components/icon-button';
910

1011
export type IconWidgetProps = WidgetProps & {
@@ -26,6 +27,8 @@ export type IconWidgetProps = WidgetProps & {
2627
* A generic widget that displays a button with icon or text content.
2728
*/
2829
export class IconWidget extends Widget<IconWidgetProps> {
30+
protected override onAfterRenderHTML = updateWidgetTooltip;
31+
2932
static defaultProps: Required<IconWidgetProps> = {
3033
...Widget.defaultProps,
3134
id: 'icon',

modules/widgets/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export type {ContentBounds, ScrollbarWidgetProps, ScrollbarDecoration} from './s
6262

6363
export {LightTheme, DarkTheme, LightGlassTheme, DarkGlassTheme} from './themes';
6464
export type {DeckWidgetTheme} from './themes';
65+
export {updateWidgetTooltip} from './lib/widget-tooltip';
6566

6667
// Experimental preact components
6768
export {ButtonGroup as _ButtonGroup, type ButtonGroupProps} from './lib/components/button-group';

modules/widgets/src/lib/components/icon-button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export const IconButton = (props: IconButtonProps) => {
3232
className={`deck-widget-icon-button ${className}`}
3333
type="button"
3434
onClick={onClick}
35-
title={label}
35+
aria-label={label}
36+
data-deck-widget-tooltip={label}
3637
>
3738
{children ? children : <div className="deck-widget-icon" style={iconStyle} />}
3839
</button>

0 commit comments

Comments
 (0)