diff --git a/.changeset/orange-bananas-laugh.md b/.changeset/orange-bananas-laugh.md new file mode 100644 index 00000000..c83b19c1 --- /dev/null +++ b/.changeset/orange-bananas-laugh.md @@ -0,0 +1,5 @@ +--- +'focus-trap-react': patch +--- + +Fix deprecation warning in React 19 when accessing ref the pre-v19 way diff --git a/demo/js/demo-defaults.js b/demo/js/demo-defaults.js index 876dfabf..bbc1ca16 100644 --- a/demo/js/demo-defaults.js +++ b/demo/js/demo-defaults.js @@ -14,6 +14,8 @@ class DemoDefaults extends React.Component { this.mountTrap = this.mountTrap.bind(this); this.unmountTrap = this.unmountTrap.bind(this); + + this.containerEl = null; } mountTrap() { @@ -24,6 +26,13 @@ class DemoDefaults extends React.Component { this.setState({ activeTrap: false }); } + // purposely using a ref here to test new React 19 "ref as a prop" behavior + // with fallback to React 18 deprecated behavior + handleCallbackRef(el) { + this.containerEl = el; + this.containerEl?.classList.add('is-active'); + } + render() { const trap = this.state.activeTrap && ( -
+
this.handleCallbackRef(el)} + >

Here is a focus trap with some{' '} focusable parts. diff --git a/package-lock.json b/package-lock.json index b8f027d4..8abff13d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,8 +26,8 @@ "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.5.2", "@types/jquery": "^3.5.32", - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "all-contributors-cli": "^6.26.1", "babel-jest": "^29.7.0", "babelify": "^10.0.0", @@ -45,8 +45,8 @@ "jest-watch-typeahead": "^2.2.2", "onchange": "^7.1.0", "prettier": "^3.4.2", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "regenerator-runtime": "^0.14.1", "start-server-and-test": "^2.0.9", "typescript": "^5.7.2" @@ -3838,31 +3838,24 @@ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", "dev": true }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true - }, "node_modules/@types/react": { - "version": "18.3.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.14.tgz", - "integrity": "sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg==", + "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.2", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.2.tgz", - "integrity": "sha512-Fqp+rcvem9wEnGr3RY8dYNvSQ8PoLqjZ9HLgaPUOjJJD120uDyOxOjc/39M4Kddp9JQCxpGQbnhVQF0C0ncYVg==", + "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, "license": "MIT", - "dependencies": { - "@types/react": "^18" + "peerDependencies": { + "@types/react": "^19.0.0" } }, "node_modules/@types/semver": { @@ -14113,30 +14106,26 @@ } }, "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==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "dev": true, "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "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": { @@ -14658,14 +14647,11 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", diff --git a/package.json b/package.json index 9b40d22c..ae9507b9 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.5.2", "@types/jquery": "^3.5.32", - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "all-contributors-cli": "^6.26.1", "babel-jest": "^29.7.0", "babelify": "^10.0.0", @@ -89,8 +89,8 @@ "jest-watch-typeahead": "^2.2.2", "onchange": "^7.1.0", "prettier": "^3.4.2", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "regenerator-runtime": "^0.14.1", "start-server-and-test": "^2.0.9", "typescript": "^5.7.2" diff --git a/src/focus-trap-react.js b/src/focus-trap-react.js index 12da5192..18ddf70d 100644 --- a/src/focus-trap-react.js +++ b/src/focus-trap-react.js @@ -2,6 +2,12 @@ const React = require('react'); const { createFocusTrap } = require('focus-trap'); const { isFocusable } = require('tabbable'); +/** + * The major version of React currently running. + * @type {number} + */ +const reactVerMajor = parseInt(/^(\d+)\./.exec(React.version)?.[1] ?? 0, 10); + /** * @type {import('../index.d.ts').FocusTrap} */ @@ -391,10 +397,21 @@ class FocusTrap extends React.Component { const { containerElements } = this.props; if (child) { - if (typeof child.ref === 'function') { - child.ref(element); - } else if (child.ref) { - child.ref.current = element; + // React 19 moved the `ref` to an official prop + if (reactVerMajor >= 19) { + if (typeof child.props.ref === 'function') { + child.props.ref(element); + } else if (child.props.ref) { + child.props.ref.current = element; + } + } else { + // older versions of React had the `ref` separate from props (still works in R19 + // but results in a deprecation warning in Dev builds) + if (typeof child.ref === 'function') { + child.ref(element); + } else if (child.ref) { + child.ref.current = element; + } } }