Skip to content

feat(mapbox): Add widget support to MapboxOverlay via IControl adapter#9963

Open
chrisgervang wants to merge 30 commits intomasterfrom
chr/mapbox-widget-support
Open

feat(mapbox): Add widget support to MapboxOverlay via IControl adapter#9963
chrisgervang wants to merge 30 commits intomasterfrom
chr/mapbox-widget-support

Conversation

@chrisgervang
Copy link
Copy Markdown
Collaborator

@chrisgervang chrisgervang commented Jan 25, 2026

Closes #9962

Background

Enable deck widgets to coexist with native Mapbox/MapLibre controls without DOM overlap by rendering widgets with viewId: 'mapbox' into the map's control container system via the new _container prop.

Screenshot 2026-04-06 at 5 27 21 PM

Change List

  • Add DeckWidgetControl class that wraps deck widgets as Mapbox IControls
  • Process widgets with viewId: 'mapbox' in MapboxOverlay, automatically wrapping them as IControls
  • Set widget.props._container to Mapbox-positioned element so WidgetManager appends there
  • Widgets stack correctly with native controls in the same DOM container, preventing overlap

Note

Medium Risk
Moderate risk because it changes MapboxOverlay lifecycle/setProps behavior to dynamically add/remove map controls and mutate widget _container targets, which could affect widget rendering or control cleanup across mapbox/maplibre versions.

Overview
Adds support for rendering deck.gl widgets inside the Mapbox/MapLibre control container when a widget sets viewId: 'mapbox', so widgets can be positioned alongside native controls without DOM overlap.

MapboxOverlay now detects viewId: 'mapbox' widgets, wraps them in a new DeckWidgetControl (an IControl adapter that sets widget.props._container), manages control reuse/removal across setProps, and cleans everything up on onRemove (works in both overlaid and interleaved modes). Styling is adjusted to remove widget margins when embedded in map controls, docs/whats-new are updated, and examples/tests are expanded (including a new widget-browser test app and updated mapbox mocks).

Reviewed by Cursor Bugbot for commit 3ef91cd. Bugbot is set up for automated code reviews on this repo. Configure here.

@coveralls
Copy link
Copy Markdown

coveralls commented Jan 25, 2026

Coverage Status

coverage: 81.638% (+0.05%) from 81.588% — chr/mapbox-widget-support into master

@chrisgervang chrisgervang mentioned this pull request Jan 25, 2026
62 tasks
@chrisgervang chrisgervang added this to the v9.3 milestone Feb 2, 2026
@chrisgervang chrisgervang force-pushed the chr/mapbox-widget-support branch from 40d85b9 to 9426c8c Compare February 4, 2026 23:24
@chrisgervang chrisgervang force-pushed the chr/mapbox-widget-support branch from 79355a6 to 287b467 Compare February 27, 2026 19:08
chrisgervang and others added 7 commits March 9, 2026 11:05
Enable deck widgets to coexist with native Mapbox/MapLibre controls without
DOM overlap by rendering widgets with viewId: 'mapbox' into the map's control
container system.

- Add DeckWidgetControl class that wraps deck widgets as IControls
- Process widgets with viewId: 'mapbox' in MapboxOverlay
- Set widget._container to Mapbox-positioned element via IControl
- Widgets stack correctly with native controls in same container

Closes #9962

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add TestWidget class for testing widget integration
- Test regular widgets render in deck container
- Test widgets with viewId: 'mapbox' are wrapped as IControl
- Test mixed widgets (regular + mapbox)
- Test setProps updates widget controls
- Test interleaved mode widget support
- Update mock map to support control positions and hasControl

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Overlaid mode tests create their own Deck instances which can cause GL
context corruption when sharing contexts across tests. Using an isolated
WebGLDevice for these tests prevents hangs in the test suite.

Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com>
…d rootElement

When setProps was called with widgets, _processWidgets would destroy and
recreate all DeckWidgetControls. This removed the container from the DOM,
orphaning the widget's rootElement since WidgetManager wouldn't re-attach
it (same widget id means no change detected).

Now matches widgets by id (like WidgetManager) and only recreates controls
when the widget is new or placement changes. For existing widgets with same
id and placement, the container is preserved and copied to the new widget
instance to support React patterns where widgets are recreated on render.

Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com>
Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com>
Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com>
When _processWidgets reuses a DeckWidgetControl for a new widget instance
with the same id and placement, the control's internal _widget reference
must be updated. Otherwise the control references the old widget, causing
incorrect behavior in onRemove() and potential memory leaks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@chrisgervang chrisgervang force-pushed the chr/mapbox-widget-support branch from 287b467 to e793c90 Compare March 9, 2026 18:06
@ibgreen ibgreen mentioned this pull request Mar 22, 2026
34 tasks
Resolve merge conflict in mapbox-overlay.spec.ts:
- Keep isolated overlaidTestDevice for overlaid mode tests (our branch)
- Add device import from @deck.gl/test-utils/vitest and webglTest helper (master)
- Keep TestWidget class and widget tests (our branch)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
chrisgervang and others added 8 commits April 1, 2026 08:17
The widget tests used tape-style assertions (t.ok, t.is, t.notOk, t.end)
but the file uses vitest's test() which doesn't pass a t argument.
Converted all widget tests to use vitest's expect() style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Placement change recreates control
- setWidget updates control widget reference
- onRemove clears widget _container
- getDefaultPosition maps placement correctly (incl. fill fallback)
- Null widgets in array are filtered

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Demonstrates deck.gl widgets (viewId: 'mapbox') coexisting with
native NavigationControl in the same map control container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…upport

Resolved conflict in test/modules/mapbox/mapbox-overlay.spec.ts:
- Imports: combined Widget/WidgetPlacement imports (ours) with
  getDeckInstance/MapboxLayerGroup imports (master)
- Tests: kept both our widget support tests and master's new
  MapboxLayerGroup tests (independent test suites appended together)
const map = this._map;
if (!map) return;

const mapboxWidgets = widgets?.filter(w => w && w.viewId === 'mapbox') ?? [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hardcoded string instead of imported constant MAPBOX_VIEW_ID

Low Severity

_processWidgets uses the hardcoded string 'mapbox' to filter widgets, but the constant MAPBOX_VIEW_ID is already imported in this same file (line 13) and used for the same conceptual purpose at line 348 in _getViews. Using the literal instead of the constant creates an inconsistency — if MAPBOX_VIEW_ID were ever updated, this filter would silently stop matching.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b1babd7. Configure here.

// Update the control's widget reference to the new instance
existingControl.setWidget(widget);
newControls.push(existingControl);
existingControlsById.delete(widget.id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Widget control references diverge from WidgetManager's resolved widgets

Low Severity

When reusing a control for a new widget instance with the same id, setWidget(widget) updates the control to reference the new widget instance. However, WidgetManager._setWidgets independently reuses the old widget instance (calling oldWidget.setProps(widget.props) and keeping oldWidget). This means DeckWidgetControl._widget and the widget actually managed by WidgetManager are different objects. Consequently, DeckWidgetControl.onRemove clears _container on the wrong (unmanaged) widget instance, leaving the managed widget's _container stale and pointing to a removed DOM element.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b1babd7. Configure here.

chrisgervang and others added 2 commits April 6, 2026 17:06
…ration

- Add "Using Widgets" section to MapboxOverlay docs explaining
  viewId: 'mapbox' positioning, default deck overlay positioning,
  and known widget limitations (view controls, canvas capture)
- Add "Basemap Integration" section to what's-new collecting 9.3
  improvements: grouped rendering, auto-injected mapbox view,
  widget support, and bug fixes
- Remove stale comment from test file

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

@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 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit afec60a. Configure here.

chrisgervang and others added 10 commits April 6, 2026 21:25
When widgets are placed in a basemap control container (via viewId: 'mapbox'),
the map already provides spacing between controls. Remove the widget's own
margin to prevent double-spacing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…narios

Combines coverage from widgets-9.2, widgets-infovis, widgets-multi-view-9.2,
and controlled-widgets into a single React app with four tabs:
- MapView: full widget showcase with controlled state/callbacks
- InfoVis: OrbitView + OrthographicView with GimbalWidget, ScrollbarWidget
- Multi-View: nested SplitterWidget with 3-way MapView layout
- Basemap: MapLibre + MapboxOverlay with viewId: 'mapbox' widget positioning

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…upport

Resolved conflict in docs/whats-new.md by keeping our Layers, Basemap
Integration, and Views sections above the Widgets section from master.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed duplicate Layers/Views sections that were introduced during merge
conflict resolution. Merged basemap integration content (with PR links) into
the existing @deck.gl/mapbox section, renamed to "Basemap Integration".
Dropped standalone Views section (content covered by Improved 3D Support).

Section order is now: Widgets > Layers > Basemap Integration > Improved 3D Support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…upport

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split "Basemap Integration" into @deck.gl/mapbox and @deck.gl/google-maps
sections. Reword "ensuring consistent behavior" to sound less robotic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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 widget support to MapboxOverlay via IControl adapter

3 participants