diff --git a/lib/Map.js b/lib/Map.js index 8f8265f5..0af0a6e8 100644 --- a/lib/Map.js +++ b/lib/Map.js @@ -16,6 +16,7 @@ import OLMap from 'ol/Map.js'; import propTypes from 'prop-types'; import {Component, createElement, createRef, forwardRef} from 'react'; +import {MAP} from './internal/config.js'; import {render, updateInstanceFromProps} from './internal/render.js'; const defaultDivStyle = { @@ -38,7 +39,7 @@ class Map extends Component { innerRef.current = this.map; } } - updateInstanceFromProps(this.map, mapProps); + updateInstanceFromProps(this.map, MAP, {}, mapProps); } componentDidMount() { diff --git a/lib/internal/config.js b/lib/internal/config.js index 7c754b41..936c2028 100644 --- a/lib/internal/config.js +++ b/lib/internal/config.js @@ -1,6 +1,7 @@ /** * The constants below represent the primitive element types handled by the renderer. */ +export const MAP = 'map'; export const VIEW = 'view'; export const OVERLAY = 'overlay'; export const CONTROL = 'control'; diff --git a/lib/internal/render.js b/lib/internal/render.js index 5ae5e413..a2f5eff4 100644 --- a/lib/internal/render.js +++ b/lib/internal/render.js @@ -10,6 +10,7 @@ import {CONTROL, INTERACTION, LAYER, OVERLAY, SOURCE, VIEW} from './config.js'; import { ConcurrentRoot, DefaultEventPriority, + NoEventPriority, } from 'react-reconciler/constants.js'; import { prepareControlUpdate, @@ -41,20 +42,52 @@ const knownTypes = { [SOURCE]: true, }; -export function updateInstanceFromProps(instance, props, oldProps = {}) { - for (const key in props) { +/** + * @param {Array} a1 An array. + * @param {Array} a2 An array. + * @return {boolean} All elements in the array are the same; + */ +function arrayEquals(a1, a2) { + if (!a1 || !a2) { + return false; + } + const len1 = a1.length; + const len2 = a2.length; + if (len1 !== len2) { + return false; + } + for (let i = 0; i < len1; i += 1) { + const v1 = a1[i]; + const v2 = a2[i]; + if (v1 !== v2) { + return false; + } + } + return true; +} + +export function updateInstanceFromProps(instance, type, oldProps, newProps) { + for (const key in newProps) { if (reservedProps[key]) { continue; } + const newValue = newProps[key]; + const oldValue = oldProps[key]; + if (oldValue === newValue) { + continue; + } + if (listenerRegex.test(key)) { - const listener = props[key]; - const type = key + const listener = newProps[key]; + const eventType = key .replace(listenerColonRegex, 'onChange:') .replace(listenerRegex, '$1') .toLowerCase(); - instance.on(type, listener); - if (oldProps[key]) { - instance.un(type, oldProps[key]); + instance.on(eventType, listener); + + const oldListener = oldProps[key]; + if (oldListener) { + instance.un(eventType, oldListener); if (instance.changed) { instance.changed(); } @@ -62,16 +95,20 @@ export function updateInstanceFromProps(instance, props, oldProps = {}) { continue; } + if (key === 'center' && arrayEquals(newValue, oldValue)) { + continue; + } + const setter = setterName(key); if (typeof instance[setter] === 'function') { - instance[setter](props[key]); + instance[setter](newValue); continue; } if (key === 'features' && typeof instance.addFeatures === 'function') { // TODO: there is likely a smarter way to diff features instance.clear(true); - instance.addFeatures(props[key]); + instance.addFeatures(newValue); continue; } @@ -80,12 +117,12 @@ export function updateInstanceFromProps(instance, props, oldProps = {}) { typeof instance.addInteraction === 'function' ) { instance.getInteractions().clear(); - props[key].forEach(interaction => instance.addInteraction(interaction)); + newValue.forEach(interaction => instance.addInteraction(interaction)); continue; } if (key === 'controls' && typeof instance.addControl === 'function') { instance.getControls().clear(); - props[key].forEach(control => instance.addControl(control)); + newValue.forEach(control => instance.addControl(control)); continue; } @@ -102,7 +139,7 @@ function createInstance(type, {cls: Constructor, ...props}) { } const instance = new Constructor(props.options || {}); - updateInstanceFromProps(instance, props); + updateInstanceFromProps(instance, type, {}, props); return instance; } @@ -167,8 +204,8 @@ function prepareUpdate(instance, type, oldProps, newProps) { return updater(instance, type, oldProps, newProps); } -function commitUpdate(instance, updatePayload, type, oldProps) { - updateInstanceFromProps(instance, updatePayload, oldProps); +function commitUpdate(instance, type, oldProps, newProps) { + updateInstanceFromProps(instance, type, oldProps, newProps); } function removeChildFromContainer(map, child) { @@ -247,6 +284,9 @@ function insertBefore(parent, child, beforeChild) { throw new Error(`Cannot insert child ${child} into parent ${parent}`); } +const noContext = {}; +let currentUpdatePriority = NoEventPriority; + const reconciler = ReactReconciler({ supportsMutation: true, isPrimaryRenderer: false, @@ -260,21 +300,66 @@ const reconciler = ReactReconciler({ clearContainer, removeChildFromContainer, removeChild, - insertInContainerBefore, insertBefore, + noTimeout: -1, + + getInstanceFromNode() { + return null; + }, + + shouldAttemptEagerTransition() { + return false; + }, + + requestPostPaintCallback() {}, + + maySuspendCommit() { + return false; + }, + + preloadInstance() { + return true; + }, + + startSuspendingCommit() {}, + + suspendInstance() {}, + + waitForCommitToBeReady() { + return null; + }, + + setCurrentUpdatePriority(newPriority) { + currentUpdatePriority = newPriority; + }, + + getCurrentUpdatePriority() { + return currentUpdatePriority; + }, + + resolveUpdatePriority() { + if (currentUpdatePriority) { + return currentUpdatePriority; + } + return DefaultEventPriority; + }, finalizeInitialChildren() { return false; }, - getChildHostContext() {}, + getChildHostContext() { + return noContext; + }, getPublicInstance(instance) { return instance; }, - getRootHostContext() {}, + getRootHostContext() { + return noContext; + }, getCurrentEventPriority() { return DefaultEventPriority; @@ -299,9 +384,7 @@ export function render(element, container) { let root = roots.get(container); if (!root) { const logRecoverableError = - typeof reportError === 'function' - ? reportError // eslint-disable-line no-undef - : console.error; // eslint-disable-line no-console + typeof reportError === 'function' ? reportError : console.error; // eslint-disable-line no-console root = reconciler.createContainer( container, diff --git a/lib/internal/update.js b/lib/internal/update.js index 13f25d1c..de0bab45 100644 --- a/lib/internal/update.js +++ b/lib/internal/update.js @@ -14,6 +14,7 @@ export const reservedProps = { children: true, cls: true, options: true, + ref: true, // TODO: deal with changing ref }; export function prepareViewUpdate(view, type, oldProps, newProps) { diff --git a/package-lock.json b/package-lock.json index 1718a9bb..aa7cfa05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "10.3.0", "license": "Apache-2.0", "dependencies": { - "react-reconciler": "^0.29.0" + "react-reconciler": "^0.31.0" }, "devDependencies": { "@astrojs/mdx": "^3.0.0", @@ -17,8 +17,8 @@ "@octokit/rest": "^21.0.0", "@playwright/test": "^1.25.2", "@testing-library/react": "^16.0.0", - "@types/react": "^18.0.27", - "@types/react-dom": "^18.0.10", + "@types/react": "^19.0.2", + "@types/react-dom": "^19.0.2", "@vitest/browser": "^2.0.3", "astro": "^4.0.3", "es-main": "^1.2.0", @@ -33,8 +33,8 @@ "ol": "^10.3.0", "ol-mapbox-style": "^12.3.5", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "remark-html": "^16.0.1", "remark-parse": "^11.0.0", "semapro": "^1.1.0", @@ -2842,11 +2842,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "dev": true, - "license": "MIT" - }, "node_modules/@types/rbush": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-4.0.0.tgz", @@ -2855,22 +2850,23 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz", + "integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz", + "integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==", "dev": true, - "dependencies": { - "@types/react": "*" + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" } }, "node_modules/@types/statuses": { @@ -7268,6 +7264,7 @@ }, "node_modules/js-tokens": { "version": "4.0.0", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -7522,6 +7519,7 @@ }, "node_modules/loose-envify": { "version": "1.4.0", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -10751,27 +10749,25 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "dev": true, + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-is": { @@ -10780,18 +10776,17 @@ "license": "MIT" }, "node_modules/react-reconciler": { - "version": "0.29.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", - "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz", + "integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "engines": { "node": ">=0.10.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-refresh": { @@ -11564,12 +11559,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" }, "node_modules/section-matter": { "version": "1.0.0", diff --git a/package.json b/package.json index 75d4f011..e84b4b07 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "license": "Apache-2.0", "dependencies": { - "react-reconciler": "^0.29.0" + "react-reconciler": "^0.31.0" }, "peerDependencies": { "ol": "*", @@ -35,8 +35,8 @@ "@octokit/rest": "^21.0.0", "@playwright/test": "^1.25.2", "@testing-library/react": "^16.0.0", - "@types/react": "^18.0.27", - "@types/react-dom": "^18.0.10", + "@types/react": "^19.0.2", + "@types/react-dom": "^19.0.2", "@vitest/browser": "^2.0.3", "astro": "^4.0.3", "es-main": "^1.2.0", @@ -51,8 +51,8 @@ "ol": "^10.3.0", "ol-mapbox-style": "^12.3.5", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "remark-html": "^16.0.1", "remark-parse": "^11.0.0", "semapro": "^1.1.0",