-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs(widgets) Custom Widget Developer Guide #9304
Open
chrisgervang
wants to merge
16
commits into
master
Choose a base branch
from
chr/widget-dev-guide
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
101aaf9
docs(widgets) Widget Developer Guide
chrisgervang ebd2b50
Update docs/developer-guide/custom-widgets/README.md
chrisgervang 70248ce
Update docs/developer-guide/custom-widgets/universal-widgets.md
chrisgervang 026fd27
Update docs/developer-guide/custom-widgets/README.md
chrisgervang 41cdfb3
adding preact page and vanilla example
chrisgervang 077ddd9
onViewportChange
chrisgervang 775c7b8
misc react docs
chrisgervang e68bd41
Styling Your React Widget
chrisgervang 5291a26
TOC
chrisgervang dcb07be
type cleanup
chrisgervang 8daa0c6
Merge branch 'master' into chr/widget-dev-guide
chrisgervang e48c1c9
Adding required class members
chrisgervang 98a82a4
Add reactivity to examples
chrisgervang 5366ae2
[docs] rewrite React widget dev guide
chrisgervang f17de11
Merge branch 'master' into chr/widget-dev-guide
chrisgervang a45ba17
use type instead of interface
chrisgervang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Writing Your Own Widget | ||
|
||
## Preparations | ||
|
||
There are many ways to build a widget in deck.gl, and it’s helpful to consider which approach best suits your needs before getting started. Below are guides for commonly used approaches: | ||
|
||
* **[Implement a universal widget](./universal-widgets.md)** - A "universal widget" is a widget compatible with any deck.gl application and is UI framework agnostic. This is the best option for developing widgets intended to work across the deck.gl ecosystem. | ||
* **[Use Preact in a universal widget](./preact-widgets.md)** - Preact is a lightweight virtual DOM library commonly used to implement dynamic widget UI. It enables you to create highly interactive widgets without tightly coupling their internals to an application’s UI framework. | ||
* **[Wrap widgets in a React component](./react-widgets.md)** - If you are developing a custom Widget for a React application, you can use React to build the UI. This approach allows you to use React components and can coexist alongside other widgets. | ||
|
||
## Creating The Widget class | ||
|
||
Your widget class must implement the [Widget](../../api-reference/core/widget.md) interface. | ||
|
||
```ts | ||
import type {Widget} from '@deck.gl/core'; | ||
|
||
class AwesomeWidget implements Widget { | ||
id = 'awesome-widget'; | ||
props; | ||
constructor(props) { | ||
this.id = props.id ?? this.id; | ||
this.props = { ...props }; | ||
} | ||
onAdd() {...} | ||
onRemove() {...} | ||
} | ||
``` | ||
|
||
It's most convenient to use TypeScript, but widgets can also be implemented in JavaScript. | ||
|
||
### Defining Widget Properties | ||
|
||
The list of properties is the main API your new widget will provide to | ||
applications. So it makes sense to carefully consider what properties | ||
your widget should offer. | ||
|
||
You also need to define the default values of the widget's properties. | ||
|
||
```ts | ||
import type {WidgetPlacement} from '@deck.gl/core' | ||
|
||
type AwesomeWidgetProps = { | ||
id?: string; | ||
/** | ||
* Widget positioning within the view. Default: 'top-left'. | ||
*/ | ||
placement?: WidgetPlacement; | ||
/** | ||
* View to attach to and interact with. Required when using multiple views. Default: null | ||
*/ | ||
viewId?: string | null; | ||
... | ||
} | ||
|
||
class AwesomeWidget implements Widget<AwesomeWidgetProps> { | ||
id = 'awesome-widget'; | ||
props: AwesomeWidgetProps; | ||
placement: WidgetPlacement = 'top-left'; | ||
viewId?: string | null = null; | ||
|
||
constructor(props: AwesomeWidgetProps) { | ||
this.id = props.id ?? this.id; | ||
this.placement = props.placement ?? this.placement; | ||
this.viewId = props.viewId ?? this.viewId; | ||
|
||
this.props = { ...props } | ||
} | ||
} | ||
``` | ||
|
||
## Best Practices | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section looks a little "lost" here at the very end of the page. Maybe lead with something like this before all the examples?. Or maybe the section will grow and it will look more natural. |
||
|
||
- **Plan Your API:** Clearly define the properties and events your widget will expose so that its easy for developers to integrate into their applications. | ||
- **Handle Lifecycle Events:** Implement lifecycle methods like `onAdd`, `onRemove`, and `setProps` to manage the widget's updates effectively. | ||
- **Optimize for Performance:** Minimize unnecessary DOM re-renders and resource usage by carefully managing state updates. | ||
- **Ensure Accessibility:** Provide options for styling and interactions that respect user preferences, such as keyboard navigation and screen reader support. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# Preact Widgets | ||
|
||
Preact widgets are an easy way to add dynamic UI elements into universal deck.gl widgets using the [Preact](https://preactjs.com/) UI library. This guide will walk you through the process of building Preact-based widgets and best practices. | ||
|
||
## Why Use Preact Widgets? | ||
|
||
Preact widgets leverage the strengths of React’s component model in a lighter weight library, allowing: | ||
|
||
- **Easy Composition:** Reuse and combine components. | ||
- **Declarative UI:** Define your UI in a predictable and straightforward manner using JSX. | ||
- **Small Size:** Preact is small enough that your code is the largest part of your application. | ||
|
||
Preact widgets are suitable when you are working with any UI framework and is lightweight enough to distribute with your widget in a library. | ||
|
||
> Tip: Read more about the differences between Preact and React [here](https://preactjs.com/guide/v10/differences-to-react/). | ||
|
||
## Writing a Preact Widget | ||
|
||
### Prerequisites | ||
|
||
Ensure your project includes the `preact` package. | ||
|
||
```sh | ||
npm install preact | ||
``` | ||
|
||
When using the TypeScript compiler, add the following configuration to your `tsconfig.json` to transpile JSX to Preact-compatible JavaScript: | ||
|
||
```json | ||
{ | ||
"compilerOptions": { | ||
"jsx": "react-jsx", | ||
"jsxImportSource": "preact" | ||
} | ||
} | ||
``` | ||
|
||
> Note: Developer environments vary. Refer to the [Preact Typescript](https://preactjs.com/guide/v10/typescript) documentation for additional environments. | ||
|
||
## Example: Layer List Widget with Preact | ||
|
||
Below is a comprehensive example demonstrating a layer list widget implemented using Preact for dynamic UI rendering: | ||
|
||
```tsx | ||
import { | ||
_deepEqual as deepEqual, | ||
_applyStyles as applyStyles, | ||
_removeStyles as removeStyles | ||
} from '@deck.gl/core' | ||
import type { | ||
Deck, Viewport, Widget, WidgetPlacement, Layer | ||
} from '@deck.gl/core' | ||
import {render} from 'preact'; | ||
|
||
type LayerListWidgetProps = { | ||
id?: string; | ||
/** | ||
* Widget positioning within the view. Default: 'top-left'. | ||
*/ | ||
placement?: WidgetPlacement; | ||
/** | ||
* View to attach to and interact with. Required when using multiple views. Default: null | ||
*/ | ||
viewId?: string | null; | ||
/** | ||
* CSS inline style overrides. | ||
*/ | ||
style?: Partial<CSSStyleDeclaration>; | ||
/** | ||
* Additional CSS class. | ||
*/ | ||
className?: string; | ||
} | ||
|
||
class LayerListWidget implements Widget<LayerListWidgetProps> { | ||
id = 'layer-list-widget'; | ||
props: LayerListWidgetProps; | ||
placement: WidgetPlacement = 'top-left'; | ||
viewports: {[id: string]: Viewport} = {}; | ||
layers: Layer[] = []; | ||
deck?: Deck<any>; | ||
element?: HTMLDivElement; | ||
|
||
constructor(props: LayerListWidgetProps) { | ||
this.id = props.id ?? this.id; | ||
this.placement = props.placement ?? this.placement; | ||
this.viewId = props.viewId ?? this.viewId; | ||
|
||
this.props = { | ||
...props, | ||
style: props.style ?? {} | ||
} | ||
} | ||
|
||
onAdd({deck}: {deck: Deck<any>}): HTMLDivElement { | ||
const {style, className} = this.props; | ||
const element = document.createElement('div'); | ||
element.classList.add('deck-widget', 'deck-widget-layer-list'); | ||
if (className) element.classList.add(className); | ||
applyStyles(element, style); | ||
this.deck = deck; | ||
this.element = element; | ||
this.update(); | ||
return element; | ||
} | ||
|
||
setProps(props: Partial<LayerListWidgetProps>) { | ||
// Handle when props change here. | ||
this.placement = props.placement ?? this.placement; | ||
this.viewId = props.viewId ?? this.viewId; | ||
this.props = {...props}; | ||
this.update(); | ||
} | ||
|
||
onRedraw({layers}: {layers: Layer[]}) { | ||
this.layers = layers; | ||
this.update(); | ||
} | ||
|
||
onViewportChange(viewport) { | ||
this.viewports[viewport.id] = viewport | ||
} | ||
|
||
private update() { | ||
const element = this.element; | ||
if (!element) { | ||
return; | ||
} | ||
let layers = this.layers | ||
if (this.deck?.props.layerFilter) { | ||
const ui = ( | ||
{this.viewports.values().map(viewport => ( | ||
<div> | ||
{viewport.id} | ||
<ul> | ||
{layers.filter(layer => ( | ||
this.deck?.props.layerFilter({layer, viewport}) | ||
)).map((layer) => { | ||
<li key={layer.id}>{layer.id}</li> | ||
})} | ||
</ul> | ||
</div> | ||
))} | ||
); | ||
render(ui, element); | ||
} else { | ||
const ui = ( | ||
<ul> | ||
{this.layers.map((layer) => ( | ||
<li key={layer.id}>{layer.id}</li> | ||
))} | ||
</ul> | ||
) | ||
render(ui, element); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
This widget dynamically renders a list of layers and updates as the deck.gl state changes. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider exporting a set of base
WidgetProps
so that widget writers don't need to retype all of that?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea to encourage consistency, though it's still 100% up to the widget authors to decide how they implement this since we're only defining an interface.