Summary
Add support for controlled components pattern and state change callbacks across deck.gl widgets. This enables:
- Parent applications to control widget state via props
- Notification when widget state changes
- Better integration with state management libraries (Redux, Zustand, etc.)
- Consistent API patterns across all widgets
Motivation
Currently, most deck.gl widgets manage their own internal state without providing ways for parent applications to:
- Control state externally - For example, syncing theme mode with an application's theme system
- Be notified of state changes - For example, knowing when a user toggles fullscreen or changes zoom
Widget Inventory
| Widget |
Internal State |
Has Callbacks |
Proposed Changes |
| ZoomWidget |
viewports |
- |
onZoom |
| CompassWidget |
viewports |
- |
onCompassReset |
| GimbalWidget |
viewports |
- |
onGimbalReset |
| GeocoderWidget |
addressText, viewports |
- |
onGeocode |
| ResetViewWidget |
- |
- |
onReset |
| ThemeWidget |
themeMode |
- |
themeMode, onThemeModeChange |
| FullscreenWidget |
fullscreen |
- |
fullscreen, onFullscreenChange |
| TimelineWidget |
currentTime, playing |
onTimeChange |
time, playing, onPlayingChange |
| ViewSelectorWidget |
viewMode |
onViewModeChange |
viewMode |
| StatsWidget |
collapsed |
- |
collapsed, onCollapsedChange |
| SplitterWidget |
split (preact state) |
onChange, onDragStart, onDragEnd |
split |
| LoadingWidget |
loading (derived) |
- |
onLoadingChange |
| ScreenshotWidget |
- |
onCapture |
- |
| InfoWidget |
position, visible, text |
onClick, getTooltip |
- |
| ContextMenuWidget |
visible, position, menuItems |
onMenuItemSelected, getMenuItems |
- |
| ScaleWidget |
derived from viewport |
- |
- |
| FpsWidget |
derived from metrics |
- |
- |
| TooltipWidget |
isVisible |
- |
- (managed by Deck's getTooltip) |
Proposed Solution
Controlled/Uncontrolled Pattern
Follow React's controlled component pattern:
// Uncontrolled (current behavior) - widget manages its own state
<ThemeWidget initialThemeMode="dark" />
// Controlled - parent manages state via props
const [themeMode, setThemeMode] = useState('dark');
<ThemeWidget
themeMode={themeMode}
onThemeModeChange={setThemeMode}
/>
When the controlled prop is provided (!== undefined), the widget:
- Uses the prop value as the source of truth
- Calls the callback on user interaction instead of updating internal state
- Parent is responsible for updating the prop
Implementation Pattern
_handleClick() {
const nextMode = this.getThemeMode() === 'dark' ? 'light' : 'dark';
// Always call callback if provided
this.props.onThemeModeChange?.(nextMode);
// Only update internal state if uncontrolled
if (this.props.themeMode === undefined) {
this._setThemeMode(nextMode);
}
}
getThemeMode(): 'light' | 'dark' {
// Use controlled prop if provided, otherwise internal state
return this.props.themeMode ?? this.themeMode;
}
Detailed Changes
View State Widgets - Add Callbacks
These widgets modify the view and should notify when changes occur:
ZoomWidget
onZoom?: (params: {
viewId: string;
delta: number; // +1 or -1
zoom: number; // new zoom level
}) => void;
CompassWidget
onCompassReset?: (params: {
viewId: string;
bearing: number;
pitch: number;
}) => void;
GimbalWidget
onGimbalReset?: (params: {
viewId: string;
rotationOrbit: number;
rotationX: number;
}) => void;
GeocoderWidget
onGeocode?: (params: {
viewId: string;
coordinates: {longitude: number; latitude: number; zoom?: number};
}) => void;
ResetViewWidget
onReset?: (params: {
viewId: string;
viewState: ViewState;
}) => void;
Toggle Widgets - Add Controlled Mode
ThemeWidget
/** Controlled theme mode */
themeMode?: 'light' | 'dark';
/** Called when user clicks toggle */
onThemeModeChange?: (mode: 'light' | 'dark') => void;
FullscreenWidget
/** Controlled fullscreen state */
fullscreen?: boolean;
/** Called when fullscreen state changes */
onFullscreenChange?: (fullscreen: boolean) => void;
TimelineWidget (already has onTimeChange)
/** Controlled time value */
time?: number;
/** Controlled playing state */
playing?: boolean;
/** Called when play/pause is toggled */
onPlayingChange?: (playing: boolean) => void;
ViewSelectorWidget (already has onViewModeChange)
/** Controlled view mode */
viewMode?: ViewMode;
StatsWidget
/** Controlled collapsed state */
collapsed?: boolean;
/** Called when collapsed state changes */
onCollapsedChange?: (collapsed: boolean) => void;
SplitterWidget (already has onChange)
/** Controlled split position (0-1) */
split?: number;
Notification-Only Callback
LoadingWidget
/** Called when loading state changes */
onLoadingChange?: (loading: boolean) => void;
Implementation Plan
Phase 1: High-Value Controlled Mode
- ThemeWidget - controlled
themeMode + onThemeModeChange
- FullscreenWidget - controlled
fullscreen + onFullscreenChange
- TimelineWidget - controlled
time, playing + onPlayingChange
Phase 2: View State Callbacks
- ZoomWidget -
onZoom
- CompassWidget -
onCompassReset
- GimbalWidget -
onGimbalReset
- GeocoderWidget -
onGeocode
- ResetViewWidget -
onReset
Phase 3: Remaining Widgets
- ViewSelectorWidget - controlled
viewMode
- SplitterWidget - controlled
split
- StatsWidget - controlled
collapsed + onCollapsedChange
- LoadingWidget -
onLoadingChange
Breaking Changes
None. All changes are additive:
- New optional props for controlled mode
- New optional callback props
- Existing uncontrolled behavior preserved as default
Checklist
Summary
Add support for controlled components pattern and state change callbacks across deck.gl widgets. This enables:
Motivation
Currently, most deck.gl widgets manage their own internal state without providing ways for parent applications to:
Widget Inventory
viewportsonZoomviewportsonCompassResetviewportsonGimbalResetaddressText,viewportsonGeocodeonResetthemeModethemeMode,onThemeModeChangefullscreenfullscreen,onFullscreenChangecurrentTime,playingonTimeChangetime,playing,onPlayingChangeviewModeonViewModeChangeviewModecollapsedcollapsed,onCollapsedChangesplit(preact state)onChange,onDragStart,onDragEndsplitloading(derived)onLoadingChangeonCaptureposition,visible,textonClick,getTooltipvisible,position,menuItemsonMenuItemSelected,getMenuItemsisVisiblegetTooltip)Proposed Solution
Controlled/Uncontrolled Pattern
Follow React's controlled component pattern:
When the controlled prop is provided (
!== undefined), the widget:Implementation Pattern
Detailed Changes
View State Widgets - Add Callbacks
These widgets modify the view and should notify when changes occur:
ZoomWidget
CompassWidget
GimbalWidget
GeocoderWidget
ResetViewWidget
Toggle Widgets - Add Controlled Mode
ThemeWidget
FullscreenWidget
TimelineWidget (already has
onTimeChange)ViewSelectorWidget (already has
onViewModeChange)StatsWidget
SplitterWidget (already has
onChange)Notification-Only Callback
LoadingWidget
Implementation Plan
Phase 1: High-Value Controlled Mode
themeMode+onThemeModeChangefullscreen+onFullscreenChangetime,playing+onPlayingChangePhase 2: View State Callbacks
onZoomonCompassResetonGimbalResetonGeocodeonResetPhase 3: Remaining Widgets
viewModesplitcollapsed+onCollapsedChangeonLoadingChangeBreaking Changes
None. All changes are additive:
Checklist
themeMode+onThemeModeChangefullscreen+onFullscreenChangetime,playing+onPlayingChangeonZoomcallbackonCompassResetcallbackonGimbalResetcallbackonGeocodecallbackonResetcallbackviewModesplitcollapsed+onCollapsedChangeonLoadingChangenotification