dev-docs/RFCs/v8.0/ui-module-rfc.md
Visualization applications often require some chrome around the WebGL canvas to offer better user experience. While it is possible for every app to implement their own UI components that interact with deck.gl, it is no trivial effort to implement them well.
This RFC proposes to add a new module that packages common UI components that seamlessly integrate with deck.gl. The following components have been suggested during the RFC process:
Deck.gl does not currently export any DOM-based UI components. The documentation recommends react-map-gl as a companion library in the following pattern:
import {StaticMap, MapContext, NavigationControl, FullscreenControl} from 'react-map-gl'
<DeckGL
ContextProvider={MapContext.Provider}
>
<StaticMap />
<NavigationControl />
<FullscreenControl />
</DeckGL>
There are major limitations with this approach:
Widget from @deck.gl/core. It will define certain hook methods that a UI component can implement, along the lines of:interface Widget<PropsT> {
/// Populated by core when mounted
deck?: Deck;
container?: HTMLDivElement;
constructor(props: PropsT);
/// Lifecycle hooks
onAdd: () => void;
onRemove: () => void;
setProps: (props: PropsT) => void;
/// Event hooks
onViewStateChange?: () => void;
onRedraw?: () => void;
onHover?: (info) => void;
onClick?: (info) => void;
}
Add a new module @deck.gl/widgets which exports common UI components that implement the Widget interface.
Add the following APIs to the Deck class:
/// Prop type for React / declarative usage
new Deck({
widgets: Widget[]
})
/// Imperative methods for vanilla JS usage
addUIControl(widget: Widget, opts?: {
placement?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
viewId?: string;
}): void
removeUIControl(widget: Widget): void
Users will use this new module like the following:
import {NavigationWidget, FullscreenWidget} from '@deck.gl/widgets'
new Deck({
...
uiControls: [
new NavigationWidget(),
new FullscreenWidget()
]
})
React and Mapbox users use "controls" to refer to UI components. However, we already call an input handler "controller" in the deck.gl context. I have seen users not differentiating the two in issues and discussions. I am concerned that introducing another concept called "control" is going to add to the confusion.
Edit: per feedback I'm tentatively naming these "widgets"
All components in @deck.gl/widgets should work without React. @deck.gl/react can offer thin wrappers that work with the DeckGL component:
import {NavigationWidget, FullscreenWidget} from '@deck.gl/react'
<DeckGL>
<MapView>
<NavigationControl />
<FullscreenControl />
</MapView>
</DeckGL>
A new WidgetManager will be added to Deck alongside ViewManager EffectManager etc. It will handle calling the lifecycle and event hooks of registered controls. For existing apps, where no UI controls are used, the performance impact is projected to be neglegible.
I would like to call out the expected behavior when the widgets prop shallowly changes. Following existing behavior pattern of layers, views and effects, upon setProps:
WidgetManager deeply compares the elements of widgets' old and new values. Two controls are be the "same" if they share the same constructor and the same id.onAddonRemoveThis would avoid the performance hit by constantly adding/removing controls when using React.
react-map-gl (inherited from mapbox-gl) requires a separate CSS stylesheet be included in the page's head. deck.gl traditionally uses CSS-in-JS (see getTooltip). While I like the convenience of everything-in-one-bundle approach, the new UI module will have much more complex DOM structures, and it will be harder to design an API that allows users to customize the look and feel of their UI.
Ideas:
While it does not block initial release, the UI components should strive for accessibility compliance. That includes good out-of-the-box support for keyboard navigation, screen reader, and localization.
Nebula.gl currently implements a HTMLOverlay for its UI. The library has extensive integration with deck.gl throughout the render / event cycle and is a good test for the flexibility of the Widget interface. We should inspect the code base to make sure that the Widget interface will be sufficient for its use case.