Skip to content

feat(widgets): Add controlled mode and state callbacks#9973

Open
chrisgervang wants to merge 5 commits intomasterfrom
chr/widget-state-callbacks
Open

feat(widgets): Add controlled mode and state callbacks#9973
chrisgervang wants to merge 5 commits intomasterfrom
chr/widget-state-callbacks

Conversation

@chrisgervang
Copy link
Collaborator

@chrisgervang chrisgervang commented Jan 30, 2026

Summary

Implements controlled/uncontrolled component pattern for 12 deck.gl widgets, enabling applications to drive widget state from external state management. Adds state props, getter methods, and event callbacks.

Change List

  • Theme/Display: ThemeWidget (themeMode), FullscreenWidget (fullscreen), StatsWidget (collapsed)
  • Timeline: TimelineWidget (time, playing)
  • Navigation: ZoomWidget (onZoom), CompassWidget (onCompassReset), GimbalWidget (onGimbalReset)
  • Geospatial: GeocoderWidget (onGeocode), ResetViewWidget (onReset)
  • Views: ViewSelectorWidget (viewMode), SplitterWidget (split)
  • Notifications: LoadingWidget (onLoadingChange)
  • Documentation: Widget overview with controlled/uncontrolled mode explanation

Closes #9964


Note

High Risk
Touches state/interaction logic across many widgets (fullscreen, theming, timeline playback, view selection/splitting, navigation), so regressions in UI behavior and state sync are plausible. The StatsWidget refactor appears to leave a this.collapsed assignment despite removing the field, which may be a TypeScript/build break if not corrected.

Overview
Widgets can now run in controlled mode: several widgets accept new state props (e.g. themeMode, fullscreen, collapsed, time/playing, viewMode, split) and expose getters so apps can drive widget UI from external state.

Adds new event hooks for user-driven changes such as onFullscreenChange, onThemeModeChange, onCollapsedChange, onPlayingChange, and action callbacks like onZoom, onCompassReset, onGimbalReset, onGeocode, onReset, and onLoadingChange.

Docs are updated across widget API pages plus an overview.md section explaining controlled vs uncontrolled patterns.

Written by Cursor Bugbot for commit 66d4315. This will update automatically on new commits. Configure here.

Implement controlled/uncontrolled component pattern for 12 widgets (ThemeWidget, FullscreenWidget, TimelineWidget, ZoomWidget, CompassWidget, GimbalWidget, GeocoderWidget, ResetViewWidget, ViewSelectorWidget, SplitterWidget, StatsWidget, LoadingWidget). Add state props, getter methods, and callbacks. Update widget overview documentation with controlled/uncontrolled mode explanation.

Closes #9964

Co-Authored-By: Claude (global.anthropic.claude-haiku-4-5-20251001-v1:0) <noreply@anthropic.com>
@chrisgervang chrisgervang requested a review from ibgreen January 30, 2026 18:27
@coveralls
Copy link

coveralls commented Jan 30, 2026

Coverage Status

coverage: 91.091%. remained the same
when pulling 7e952dd on chr/widget-state-callbacks
into 1510545 on master.

@chrisgervang chrisgervang mentioned this pull request Jan 30, 2026
62 tasks
@chrisgervang chrisgervang added this to the v9.3 milestone Feb 2, 2026
Resolve conflict in stats-widget.tsx: keep _collapsed naming for controlled
mode internal state, use _getStats() method from master.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove redundant _playing assignments in timeline-widget start()/stop()
- Remove redundant updateHTML() calls after super.setProps() in multiple widgets
- Fix IconMenu to support controlled mode via selectedItem prop

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolve conflicts in:
- docs/api-reference/widgets/stats-widget.md: Merged documentation formats, added controlled mode props
- modules/widgets/src/reset-view-widget.tsx: Combined multi-view loop with onReset callback
- modules/widgets/src/stats-widget.tsx: Used dropdown button with corrected variable name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

*/
getCollapsed(): boolean {
return this.props.collapsed ?? this._collapsed;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructor sets wrong property name for collapsed state

High Severity

The internal state property was renamed from collapsed to _collapsed (line 85), but the constructor assignment on line 100 still writes to this.collapsed instead of this._collapsed. This creates a stray dynamic property on the instance that nothing reads, while _collapsed retains its default value of true. As a result, getCollapsed() always returns true in uncontrolled mode, completely breaking the defaultIsExpanded prop — the widget will always start collapsed regardless of the setting.

Additional Locations (1)

Fix in Cursor Fix in Web

} else if (!props.playing && this._playing) {
this._stopTimer();
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial controlled playing: true never starts the timer

Medium Severity

When a TimelineWidget is created with playing: true in controlled mode, the playback timer never starts. In setProps, the change detection compares props.playing against prevPlaying captured from this.props — but since the constructor already merged props via super(props), both values are true, so the condition props.playing !== prevPlaying is always false on initial setup. Additionally, onAdd unconditionally resets _playing to false. The UI correctly shows "Pause" (via getPlaying()), but time never advances because _startTimer() is never called.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mapbox): Add Controlled Mode and State Callbacks to Widgets

2 participants