diff --git a/docs/modules/widgets/README.md b/docs/modules/widgets/README.md
index bb949e6c..137a9a29 100644
--- a/docs/modules/widgets/README.md
+++ b/docs/modules/widgets/README.md
@@ -39,6 +39,7 @@ export function App() {
```
The [Pan and Zoom widgets example](/examples/widgets/pan-and-zoom-controls) shows the controls managing an orthographic view over abstract data.
+The [React widgets example](/examples/widgets/react-widgets) demonstrates adding the same controls as JSX children via the React wrappers in `@deck.gl-community/react`.
### HTML overlays
diff --git a/examples/widgets/react-widgets/app.tsx b/examples/widgets/react-widgets/app.tsx
new file mode 100644
index 00000000..f48bd8f2
--- /dev/null
+++ b/examples/widgets/react-widgets/app.tsx
@@ -0,0 +1,94 @@
+// deck.gl-community
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import {useCallback, useMemo, useState} from 'react';
+import DeckGL from '@deck.gl/react';
+import {OrthographicView} from '@deck.gl/core';
+import {ScatterplotLayer} from '@deck.gl/layers';
+import {PanWidget, ZoomRangeWidget} from '@deck.gl-community/react';
+
+import '@deck.gl/widgets/stylesheet.css';
+
+const INITIAL_VIEW_STATE = {
+ target: [0, 0],
+ zoom: 0
+};
+
+const view = new OrthographicView({id: 'ortho'});
+
+export function App() {
+ const [viewState, setViewState] = useState(INITIAL_VIEW_STATE);
+
+ const data = useMemo(() => {
+ const points: {position: [number, number]; color: [number, number, number, number]}[] = [];
+ const size = 10;
+
+ for (let x = -size; x <= size; x++) {
+ for (let y = -size; y <= size; y++) {
+ const distance = Math.sqrt(x * x + y * y);
+ const intensity = Math.max(0, 1 - distance / size);
+ points.push({
+ position: [x * 20, y * 20],
+ color: [255 * intensity, 128 + 80 * intensity, 200, 200]
+ });
+ }
+ }
+
+ return points;
+ }, []);
+
+ const layers = useMemo(
+ () => [
+ new ScatterplotLayer({
+ id: 'points',
+ data,
+ getPosition: (d) => d.position,
+ getFillColor: (d) => d.color,
+ radiusMinPixels: 4,
+ radiusMaxPixels: 12,
+ radiusUnits: 'pixels',
+ pickable: false
+ })
+ ],
+ [data]
+ );
+
+ const handleViewStateChange = useCallback(({viewState: nextViewState}) => {
+ setViewState(nextViewState);
+ }, []);
+
+ return (
+
+
+
+
+
+
React wrapper widgets
+
+ This example renders the Pan and Zoom range widgets as JSX children using the React wrappers from{' '}
+ @deck.gl-community/react. Each widget registers itself through the shared{' '}
+ <DeckGL> context, so there is no need to pass a widgets array prop.
+
+
+
+ );
+}
diff --git a/examples/widgets/react-widgets/index.html b/examples/widgets/react-widgets/index.html
new file mode 100644
index 00000000..87586f11
--- /dev/null
+++ b/examples/widgets/react-widgets/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ React Widgets | deck.gl-community
+
+
+
+
+
+
diff --git a/examples/widgets/react-widgets/index.tsx b/examples/widgets/react-widgets/index.tsx
new file mode 100644
index 00000000..bc6d42b0
--- /dev/null
+++ b/examples/widgets/react-widgets/index.tsx
@@ -0,0 +1,20 @@
+// deck.gl-community
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+import React from 'react';
+import {createRoot} from 'react-dom/client';
+
+import {App} from './app';
+
+const container = document.getElementById('app');
+if (!container) {
+ throw new Error('Unable to find #app container');
+}
+
+const root = createRoot(container);
+root.render(
+
+
+
+);
diff --git a/examples/widgets/react-widgets/package.json b/examples/widgets/react-widgets/package.json
new file mode 100644
index 00000000..3275b70c
--- /dev/null
+++ b/examples/widgets/react-widgets/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "widgets-react-wrappers-example",
+ "version": "9.2.0",
+ "private": true,
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "start": "vite --open",
+ "start-local": "vite --config ../../vite.config.local.mjs"
+ },
+ "dependencies": {
+ "@deck.gl-community/react": "workspace:*",
+ "@deck.gl/core": "~9.2.1",
+ "@deck.gl/layers": "~9.2.1",
+ "@deck.gl/react": "~9.2.1",
+ "@deck.gl/widgets": "~9.2.1",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "vite": "7.1.1"
+ }
+}
diff --git a/examples/widgets/react-widgets/tsconfig.json b/examples/widgets/react-widgets/tsconfig.json
new file mode 100644
index 00000000..ec517aad
--- /dev/null
+++ b/examples/widgets/react-widgets/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": "./",
+ "paths": {
+ "*": ["./node_modules/*", "./*"]
+ }
+ },
+ "include": ["./**/*.ts", "./**/*.tsx"],
+ "exclude": ["dist", "node_modules"]
+}
diff --git a/modules/react/package.json b/modules/react/package.json
index e078cffb..3787a508 100644
--- a/modules/react/package.json
+++ b/modules/react/package.json
@@ -34,6 +34,8 @@
"src"
],
"dependencies": {
+ "@deck.gl-community/widgets": "^9.2.0-beta.3",
+ "@deck.gl/react": "~9.2.1",
"@types/styled-react-modal": "^1.2.5",
"boxicons": "^2.1.4",
"prop-types": "^15.8.1",
diff --git a/modules/react/src/index.ts b/modules/react/src/index.ts
index 83a4f66f..d22c5a89 100644
--- a/modules/react/src/index.ts
+++ b/modules/react/src/index.ts
@@ -12,4 +12,10 @@ export {EditorModal as Modal} from './components/modal';
export {Button} from './components/modal';
export {Icon} from './components/icon';
+// Widgets (from @deck.gl-community/widgets)
+export {PanWidget} from './widgets/pan-widget';
+export {ZoomRangeWidget} from './widgets/zoom-range-widget';
+export {HtmlOverlayWidget} from './widgets/html-overlay-widget';
+export {HtmlTooltipWidget} from './widgets/html-tooltip-widget';
+
// Overlays migrated to @deck.gl-community/widgets
diff --git a/modules/react/src/widgets/html-overlay-widget.tsx b/modules/react/src/widgets/html-overlay-widget.tsx
new file mode 100644
index 00000000..9711d445
--- /dev/null
+++ b/modules/react/src/widgets/html-overlay-widget.tsx
@@ -0,0 +1,15 @@
+// deck.gl-community
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+/* eslint-disable import/no-unresolved */
+import {HtmlOverlayWidget as _HtmlOverlayWidget} from '@deck.gl-community/widgets';
+import type {HtmlOverlayWidgetProps} from '@deck.gl-community/widgets';
+/* eslint-enable import/no-unresolved */
+import {useWidget} from '@deck.gl/react';
+
+/** React wrapper for the HtmlOverlayWidget. */
+export const HtmlOverlayWidget = (props: HtmlOverlayWidgetProps = {}) => {
+ useWidget(_HtmlOverlayWidget, props);
+ return null;
+};
diff --git a/modules/react/src/widgets/html-tooltip-widget.tsx b/modules/react/src/widgets/html-tooltip-widget.tsx
new file mode 100644
index 00000000..c3fa2a5f
--- /dev/null
+++ b/modules/react/src/widgets/html-tooltip-widget.tsx
@@ -0,0 +1,15 @@
+// deck.gl-community
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+/* eslint-disable import/no-unresolved */
+import {HtmlTooltipWidget as _HtmlTooltipWidget} from '@deck.gl-community/widgets';
+import type {HtmlTooltipWidgetProps} from '@deck.gl-community/widgets';
+/* eslint-enable import/no-unresolved */
+import {useWidget} from '@deck.gl/react';
+
+/** React wrapper for the HtmlTooltipWidget. */
+export const HtmlTooltipWidget = (props: HtmlTooltipWidgetProps = {}) => {
+ useWidget(_HtmlTooltipWidget, props);
+ return null;
+};
diff --git a/modules/react/src/widgets/pan-widget.tsx b/modules/react/src/widgets/pan-widget.tsx
new file mode 100644
index 00000000..e98e56c7
--- /dev/null
+++ b/modules/react/src/widgets/pan-widget.tsx
@@ -0,0 +1,15 @@
+// deck.gl-community
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+/* eslint-disable import/no-unresolved */
+import {PanWidget as _PanWidget} from '@deck.gl-community/widgets';
+import type {PanWidgetProps} from '@deck.gl-community/widgets';
+/* eslint-enable import/no-unresolved */
+import {useWidget} from '@deck.gl/react';
+
+/** React wrapper for the PanWidget. */
+export const PanWidget = (props: PanWidgetProps = {}) => {
+ useWidget(_PanWidget, props);
+ return null;
+};
diff --git a/modules/react/src/widgets/zoom-range-widget.tsx b/modules/react/src/widgets/zoom-range-widget.tsx
new file mode 100644
index 00000000..2b53d48c
--- /dev/null
+++ b/modules/react/src/widgets/zoom-range-widget.tsx
@@ -0,0 +1,15 @@
+// deck.gl-community
+// SPDX-License-Identifier: MIT
+// Copyright (c) vis.gl contributors
+
+/* eslint-disable import/no-unresolved */
+import {ZoomRangeWidget as _ZoomRangeWidget} from '@deck.gl-community/widgets';
+import type {ZoomRangeWidgetProps} from '@deck.gl-community/widgets';
+/* eslint-enable import/no-unresolved */
+import {useWidget} from '@deck.gl/react';
+
+/** React wrapper for the ZoomRangeWidget. */
+export const ZoomRangeWidget = (props: ZoomRangeWidgetProps = {}) => {
+ useWidget(_ZoomRangeWidget, props);
+ return null;
+};
diff --git a/modules/react/tsconfig.json b/modules/react/tsconfig.json
index 175b131b..b051e7a9 100644
--- a/modules/react/tsconfig.json
+++ b/modules/react/tsconfig.json
@@ -1,11 +1,22 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
- "exclude": ["node_modules"],
+ "exclude": ["node_modules", "../../node_modules/@deck.gl/core/src/**/*"],
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "dist",
- "noEmit": false
- }
+ "noEmit": false,
+ "baseUrl": ".",
+ "allowJs": false,
+ "checkJs": false,
+ "moduleResolution": "bundler",
+ "paths": {
+ "@deck.gl-community/widgets": ["../widgets/dist/index.d.ts"],
+ "@deck.gl-community/widgets/*": ["../widgets/dist/*"],
+ "@deck.gl/core": ["../../node_modules/@deck.gl/core/dist/index.d.ts"],
+ "@deck.gl/core/*": ["../../node_modules/@deck.gl/core/dist/*"]
+ }
+ },
+ "references": [{"path": "../widgets"}]
}
diff --git a/website/src/examples-sidebar.js b/website/src/examples-sidebar.js
index 49aaf202..e4efdec2 100644
--- a/website/src/examples-sidebar.js
+++ b/website/src/examples-sidebar.js
@@ -59,7 +59,8 @@ const sidebars = {
label: '@deck.gl-community/widgets',
items: [
"widgets/html-overlays",
- "widgets/pan-and-zoom-controls"
+ "widgets/pan-and-zoom-controls",
+ "widgets/react-widgets"
]
},
// {
diff --git a/website/src/examples/widgets/react-widgets.mdx b/website/src/examples/widgets/react-widgets.mdx
new file mode 100644
index 00000000..27e9d29c
--- /dev/null
+++ b/website/src/examples/widgets/react-widgets.mdx
@@ -0,0 +1,5 @@
+# React Widgets
+
+import Demo from './react-widgets';
+
+
diff --git a/website/src/examples/widgets/react-widgets.tsx b/website/src/examples/widgets/react-widgets.tsx
new file mode 100644
index 00000000..e9982c8b
--- /dev/null
+++ b/website/src/examples/widgets/react-widgets.tsx
@@ -0,0 +1,25 @@
+import React, {Component} from 'react';
+import BrowserOnly from '@docusaurus/BrowserOnly';
+
+import {GITHUB_TREE} from '../../constants/defaults';
+import {App} from '../../../../examples/widgets/react-widgets/app';
+
+import {makeExample} from '../../components';
+
+class ReactWidgetsDemo extends Component {
+ static title = 'React Widgets';
+
+ static code = `${GITHUB_TREE}/examples/widgets/react-widgets`;
+
+ static renderInfo(meta) {
+ return <>>;
+ }
+
+ render() {
+ const {...otherProps} = this.props;
+
+ return {() => };
+ }
+}
+
+export default makeExample(ReactWidgetsDemo, {addInfoPanel: false});
diff --git a/yarn.lock b/yarn.lock
index 39e065c8..72e4ec58 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1428,6 +1428,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "@deck.gl-community/react@workspace:modules/react"
dependencies:
+ "@deck.gl-community/widgets": "npm:^9.2.0-beta.3"
+ "@deck.gl/react": "npm:~9.2.1"
"@types/styled-react-modal": "npm:^1.2.5"
boxicons: "npm:^2.1.4"
prop-types: "npm:^15.8.1"
@@ -1444,7 +1446,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@deck.gl-community/widgets@workspace:*, @deck.gl-community/widgets@workspace:modules/widgets":
+"@deck.gl-community/widgets@npm:^9.2.0-beta.3, @deck.gl-community/widgets@workspace:*, @deck.gl-community/widgets@workspace:modules/widgets":
version: 0.0.0-use.local
resolution: "@deck.gl-community/widgets@workspace:modules/widgets"
dependencies:
@@ -20657,6 +20659,21 @@ __metadata:
languageName: unknown
linkType: soft
+"widgets-react-wrappers-example@workspace:examples/widgets/react-widgets":
+ version: 0.0.0-use.local
+ resolution: "widgets-react-wrappers-example@workspace:examples/widgets/react-widgets"
+ dependencies:
+ "@deck.gl-community/react": "workspace:*"
+ "@deck.gl/core": "npm:~9.2.1"
+ "@deck.gl/layers": "npm:~9.2.1"
+ "@deck.gl/react": "npm:~9.2.1"
+ "@deck.gl/widgets": "npm:~9.2.1"
+ react: "npm:^18.3.1"
+ react-dom: "npm:^18.3.1"
+ vite: "npm:7.1.1"
+ languageName: unknown
+ linkType: soft
+
"word-wrap@npm:^1.2.5":
version: 1.2.5
resolution: "word-wrap@npm:1.2.5"