-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Open
Milestone
Description
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
- ThemeWidget: Add controlled
themeMode+onThemeModeChange - FullscreenWidget: Add controlled
fullscreen+onFullscreenChange - TimelineWidget: Add controlled
time,playing+onPlayingChange - ZoomWidget: Add
onZoomcallback - CompassWidget: Add
onCompassResetcallback - GimbalWidget: Add
onGimbalResetcallback - GeocoderWidget: Add
onGeocodecallback - ResetViewWidget: Add
onResetcallback - ViewSelectorWidget: Add controlled
viewMode - SplitterWidget: Add controlled
split - StatsWidget: Add controlled
collapsed+onCollapsedChange - LoadingWidget: Add
onLoadingChangenotification - Update documentation
- Add examples showing controlled usage
- Update React widget wrappers to pass through new props
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels