diff --git a/modules/react/package.json b/modules/react/package.json index 4cd6fe5df67..cf2061fe5af 100644 --- a/modules/react/package.json +++ b/modules/react/package.json @@ -39,9 +39,9 @@ "react-dom": ">=16.3.0" }, "devDependencies": { - "@types/react": "^18.2.0", + "@types/react": "^19.2.0", "@types/react-dom": "^19.2.3", - "react": "^18.2.0", + "react": "^19.2.0", "react-dom": "^19.2.5" }, "gitHead": "13ace64fc2cee08c133afc882fc307253489a4e4" diff --git a/modules/react/src/utils/evaluate-children.ts b/modules/react/src/utils/evaluate-children.ts index 0179092cc8a..4d0dafdef57 100644 --- a/modules/react/src/utils/evaluate-children.ts +++ b/modules/react/src/utils/evaluate-children.ts @@ -38,7 +38,7 @@ export function isComponent(child: React.ReactNode): child is React.ReactElement } function isReactMap(child: React.ReactElement): boolean { - return child.props?.mapStyle; + return (child.props as any)?.mapStyle; } function needsDeckGLViewProps(child: React.ReactElement): boolean { diff --git a/modules/react/src/utils/extract-jsx-layers.ts b/modules/react/src/utils/extract-jsx-layers.ts index 581f297b71d..2ce574998b1 100644 --- a/modules/react/src/utils/extract-jsx-layers.ts +++ b/modules/react/src/utils/extract-jsx-layers.ts @@ -52,7 +52,7 @@ function wrapInView(node: React.ReactNode | DeckGLRenderCallback): React.ReactNo } if (isComponent(node)) { if (node.type === React.Fragment) { - return wrapInView(node.props.children); + return wrapInView((node.props as any).children); } if (inheritsFrom(node.type, View)) { return node; @@ -92,7 +92,11 @@ export default function extractJSXLayers({ } // empty id => default view - if (inheritsFrom(ElementType, View) && ElementType !== View && reactElement.props.id) { + if ( + inheritsFrom(ElementType, View) && + ElementType !== View && + (reactElement.props as any).id + ) { // @ts-ignore Cannot instantiate an abstract class (View) const view = new ElementType(reactElement.props); jsxViews[view.id] = view; diff --git a/modules/react/src/utils/position-children-under-views.ts b/modules/react/src/utils/position-children-under-views.ts index 818a3524301..e75353c2ae6 100644 --- a/modules/react/src/utils/position-children-under-views.ts +++ b/modules/react/src/utils/position-children-under-views.ts @@ -46,8 +46,8 @@ export default function positionChildrenUnderViews({ let viewChildren = child; if (isComponent(child) && inheritsFrom(child.type, View)) { - viewId = child.props.id || defaultViewId; - viewChildren = child.props.children; + viewId = (child.props as any).id || defaultViewId; + viewChildren = (child.props as any).children; } const viewport = viewManager.getViewport(viewId) as Viewport; diff --git a/modules/widgets/src/stats-widget.tsx b/modules/widgets/src/stats-widget.tsx index 70063019974..fe395b24883 100644 --- a/modules/widgets/src/stats-widget.tsx +++ b/modules/widgets/src/stats-widget.tsx @@ -4,7 +4,7 @@ import {Widget, type WidgetPlacement, type WidgetProps} from '@deck.gl/core'; import {luma} from '@luma.gl/core'; -import {render} from 'preact'; +import {render, type JSX} from 'preact'; import {useEffect, useState} from 'preact/hooks'; import type {Stats, Stat} from '@probe.gl/stats'; import {IconButton} from './lib/components/icon-button'; diff --git a/package.json b/package.json index 0bc8fdc9235..3e664516a5e 100644 --- a/package.json +++ b/package.json @@ -73,15 +73,7 @@ "vite-bundle-analyzer": "^1.3.6" }, "resolutions": { - "vite": "^7.3.1", - "@luma.gl/core": "^9.3.2", - "@luma.gl/effects": "^9.3.2", - "@luma.gl/engine": "^9.3.2", - "@luma.gl/gltf": "^9.3.2", - "@luma.gl/shadertools": "^9.3.2", - "@luma.gl/test-utils": "^9.3.2", - "@luma.gl/webgl": "^9.3.2", - "@luma.gl/webgpu": "^9.3.2" + "vite": "^7.3.1" }, "pre-commit": [ "test-fast", diff --git a/test/modules/react/deckgl.spec.ts b/test/modules/react/deckgl.spec.ts index 4b9f3fd89c6..4340ac5469d 100644 --- a/test/modules/react/deckgl.spec.ts +++ b/test/modules/react/deckgl.spec.ts @@ -4,15 +4,19 @@ /* eslint-disable no-unused-vars */ import {test, expect} from 'vitest'; -import {createElement, createRef} from 'react'; +import {createElement, createRef, act} from 'react'; import {createRoot} from 'react-dom/client'; -import {act} from 'react-dom/test-utils'; -import {DeckGL, Layer, Widget} from 'deck.gl'; +import {Layer, Widget} from '@deck.gl/core'; +import DeckGL, {type DeckGLRef} from '@deck.gl/react'; import {type WidgetProps, type WidgetPlacement} from '@deck.gl/core'; import {device, gl} from '@deck.gl/test-utils/vitest'; +// Required by React 19 +// @ts-expect-error undefined global flag +self.IS_REACT_ACT_ENVIRONMENT = true; + const TEST_VIEW_STATE = { latitude: 37.7515, longitude: -122.4269, @@ -26,12 +30,12 @@ const TEST_VIEW_STATE = { const getDeckProps = () => (globalThis.__JSDOM__ ? {gl} : {device}); test('DeckGL#mount/unmount', async () => { - const ref = createRef(); + const ref = createRef(); const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); - const loadPromise = new Promise((resolve, reject) => { + const loadPromise = new Promise(resolve => { act(() => { root.render( createElement(DeckGL, { @@ -40,34 +44,28 @@ test('DeckGL#mount/unmount', async () => { width: 100, height: 100, ...getDeckProps(), - onLoad: () => { - try { - const {deck} = ref.current; - expect(deck, 'DeckGL is initialized').toBeTruthy(); - const viewport = deck.getViewports()[0]; - expect(viewport && viewport.longitude, 'View state is set').toBe( - TEST_VIEW_STATE.longitude - ); - - act(() => { - root.render(null); - }); - - expect(deck.animationLoop, 'Deck is finalized').toBeFalsy(); - - container.remove(); - resolve(); - } catch (error) { - reject(error); - } - } + onLoad: () => resolve() }) ); }); }); + await loadPromise; expect(ref.current, 'DeckGL overlay is rendered.').toBeTruthy(); - await loadPromise; + + const {deck} = ref.current!; + expect(deck).toBeTruthy(); + expect(deck, 'DeckGL is initialized').toBeTruthy(); + const viewport = deck!.getViewports()[0]; + expect(viewport && viewport.longitude, 'View state is set').toBe(TEST_VIEW_STATE.longitude); + + act(() => { + root.render(null); + }); + + expect(deck!.animationLoop, 'Deck is finalized').toBeFalsy(); + + container.remove(); }); test('DeckGL#render', async () => { @@ -75,34 +73,32 @@ test('DeckGL#render', async () => { document.body.append(container); const root = createRoot(container); - await new Promise((resolve, reject) => { + const renderPromise = new Promise(resolve => { act(() => { root.render( createElement( DeckGL, { - viewState: TEST_VIEW_STATE, + initialViewState: TEST_VIEW_STATE, width: 100, height: 100, ...getDeckProps(), - onAfterRender: () => { - try { - const child = container.querySelector('.child'); - expect(child, 'Child is rendered').toBeTruthy(); - - root.render(null); - container.remove(); - resolve(); - } catch (error) { - reject(error); - } - } + onAfterRender: () => resolve() }, - [createElement('div', {key: 0, className: 'child'}, 'Child')] + createElement('div', {className: 'child'}, 'Child') ) ); }); }); + await renderPromise; + + const child = container.querySelector('.child'); + expect(child, 'Child is rendered').toBeTruthy(); + + act(() => { + root.render(null); + }); + container.remove(); }); class TestLayer extends Layer { @@ -117,22 +113,18 @@ class TestWidget extends Widget { placement: WidgetPlacement = 'top-left'; className = 'deck-test-widget'; - constructor(props: WidgetProps = {}) { - super(props, Widget.defaultProps); - } - onRenderHTML(rootElement: HTMLElement): void {} } const WIDGETS = [new TestWidget({id: 'A'})]; test('DeckGL#props omitted are reset', async () => { - const ref = createRef(); + const ref = createRef(); const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); - const renderPromise = new Promise((resolve, reject) => { + let renderPromise = new Promise(resolve => { // Initialize widgets and layers on first render. act(() => { root.render( @@ -144,54 +136,46 @@ test('DeckGL#props omitted are reset', async () => { ...getDeckProps(), layers: LAYERS, widgets: WIDGETS, - onLoad: () => { - try { - const {deck} = ref.current; - expect(deck, 'DeckGL is initialized').toBeTruthy(); - const {widgets, layers} = deck.props; - expect(widgets && Array.isArray(widgets) && widgets.length, 'Widgets is set').toBe(1); - expect(layers && Array.isArray(layers) && layers.length, 'Layers is set').toBe(1); - - act(() => { - // Render deck a second time without setting widget or layer props. - root.render( - createElement(DeckGL, { - ref, - ...getDeckProps(), - onAfterRender: () => { - try { - const {deck} = ref.current; - const {widgets, layers} = deck.props; - expect( - widgets && Array.isArray(widgets) && widgets.length, - 'Widgets is reset to an empty array' - ).toBe(0); - expect( - layers && Array.isArray(layers) && layers.length, - 'Layers is reset to an empty array' - ).toBe(0); - - root.render(null); - container.remove(); - resolve(); - } catch (error) { - reject(error); - } - } - }) - ); - }); - } catch (error) { - reject(error); - } - } + onLoad: () => resolve() }) ); }); }); + await renderPromise; - act(() => { - expect(ref.current, 'DeckGL overlay is rendered.').toBeTruthy(); + const {deck} = ref.current!; + expect(deck, 'DeckGL is initialized').toBeTruthy(); + let {widgets, layers} = deck!.props; + expect(widgets && Array.isArray(widgets) && widgets.length, 'Widgets is set').toBe(1); + expect(layers && Array.isArray(layers) && layers.length, 'Layers is set').toBe(1); + + renderPromise = new Promise(resolve => { + act(() => { + // Render deck a second time without setting widget or layer props. + root.render( + createElement(DeckGL, { + ref, + ...getDeckProps(), + onAfterRender: () => resolve() + }) + ); + }); }); await renderPromise; + + widgets = deck!.props.widgets; + layers = deck!.props.layers; + expect( + widgets && Array.isArray(widgets) && widgets.length, + 'Widgets is reset to an empty array' + ).toBe(0); + expect( + layers && Array.isArray(layers) && layers.length, + 'Layers is reset to an empty array' + ).toBe(0); + + act(() => { + root.render(null); + }); + container.remove(); }); diff --git a/yarn.lock b/yarn.lock index 73ed40f0afa..2bee3f8c07a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2794,23 +2794,17 @@ resolved "https://registry.yarnpkg.com/@types/proj4/-/proj4-2.5.5.tgz#044d53782dc75f20335577ca3af2643962a56980" integrity sha512-y4tHUVVoMEOm2nxRLQ2/ET8upj/pBmoutGxFw2LZJTQWPgWXI+cbxVEUFFmIzr/bpFR83hGDOTSXX6HBeObvZA== -"@types/prop-types@*": - version "15.7.12" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" - integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== - "@types/react-dom@^19.2.3": version "19.2.3" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c" integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== -"@types/react@^18.2.0": - version "18.3.4" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.4.tgz#dfdd534a1d081307144c00e325c06e00312c93a3" - integrity sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw== +"@types/react@^19.2.0": + version "19.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad" + integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" + csstype "^3.2.2" "@types/sizzle@*": version "2.3.8" @@ -4447,10 +4441,10 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== cwise-compiler@^1.0.0, cwise-compiler@^1.1.2: version "1.1.3" @@ -7432,7 +7426,7 @@ long@^5.2.1: resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -9089,12 +9083,10 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react@^18.2.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" +react@^19.2.0: + version "19.2.5" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.5.tgz#c888ab8b8ef33e2597fae8bdb2d77edbdb42858b" + integrity sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA== read-cmd-shim@4.0.0, read-cmd-shim@^4.0.0: version "4.0.0" @@ -9891,16 +9883,7 @@ streamx@^2.21.0: fast-fifo "^1.3.2" text-decoder "^1.1.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10006,7 +9989,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -10020,13 +10003,6 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -10987,7 +10963,7 @@ wordwrapjs@^5.1.0: resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-5.1.1.tgz#bfd1eb426f0f7eec73b7df32cf7df1f618bfb3a9" integrity sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -11005,15 +10981,6 @@ wrap-ansi@^6.0.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"