diff --git a/.changeset/hot-planes-roll.md b/.changeset/hot-planes-roll.md new file mode 100644 index 00000000..e3f19527 --- /dev/null +++ b/.changeset/hot-planes-roll.md @@ -0,0 +1,5 @@ +--- +'focus-trap-react': major +--- + +Dropping `propTypes` no longer supported by React 19 (going forward, use TypeScript for prop typings, and if necessary, a runtime library to validate props); Increasing minimum supported React version up to >=18 diff --git a/.eslintrc.js b/.eslintrc.js index fd6dd01b..513e48ed 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -105,6 +105,10 @@ module.exports = { 'jest/no-focused-tests': 'error', 'jest/no-identical-title': 'error', 'jest/valid-title': 'error', + + //// from react plugin + + 'react/prop-types': 'off', // React 19 no longer supports propTypes }, settings: { // eslint-plugin-react settings: a version needs to be specified, diff --git a/README.md b/README.md index 2c2b784f..0f4d927a 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,21 @@ npm install focus-trap-react ### React dependency -React `>= 16.3.0` +React `>= 18.0.0` -## Browser Support +> Note that while React 18.x still supported `propTypes` and `defaultProps`, they had long-since been deprecated, and are completely dropped in React 19. + +Therefore, this library no longer assigns these properties to the `` element for runtime validation and initialization. The same techniques you would now use in React 19 are backward-compatible with React 18: -As old and as broad as _reasonably_ possible, excluding browsers that are out of support or have nearly no user base. +- Use TypeScript for static prop type validation +- Use a runtime validation library such as [RTV.js](https://rtvjs.stefcameron.com/), [JSON Schema](https://json-schema.org/), or [yup](https://github.com/jquense/yup) for runtime prop validation to replace `prop-types`) + +## Browser Support Focused on desktop browsers, particularly Chrome, Edge, FireFox, Safari, and Opera. +Gated by what React 18 [supports](https://legacy.reactjs.org/docs/javascript-environment-requirements.html). + Focus-trap-react is not officially tested on any mobile browsers or devices. > ⚠️ Microsoft [no longer supports](https://blogs.windows.com/windowsexperience/2022/06/15/internet-explorer-11-has-retired-and-is-officially-out-of-support-what-you-need-to-know/) any version of IE, so IE is no longer supported by this library. diff --git a/index.d.ts b/index.d.ts index 92740328..6afe9355 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,6 @@ import { Options as FocusTrapOptions } from 'focus-trap'; import * as React from 'react'; -export = FocusTrap; - declare namespace FocusTrap { export interface Props extends React.AllHTMLAttributes { children?: React.ReactNode; @@ -13,4 +11,4 @@ declare namespace FocusTrap { } } -declare class FocusTrap extends React.Component { } +export declare class FocusTrap extends React.Component { } diff --git a/package-lock.json b/package-lock.json index 475ba608..9ba452dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,17 +43,15 @@ "jest-watch-typeahead": "^2.2.2", "onchange": "^7.1.0", "prettier": "^3.3.3", - "prop-types": "^15.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^18.3.0", + "react-dom": "^18.3.0", "regenerator-runtime": "^0.14.1", "start-server-and-test": "^2.0.8", "typescript": "^5.6.2" }, "peerDependencies": { - "prop-types": "^15.8.1", - "react": ">=16.3.0", - "react-dom": ">=16.3.0" + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -14186,6 +14184,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -14198,6 +14197,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -14730,6 +14730,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } diff --git a/package.json b/package.json index b8db4e5c..8262598c 100644 --- a/package.json +++ b/package.json @@ -87,9 +87,8 @@ "jest-watch-typeahead": "^2.2.2", "onchange": "^7.1.0", "prettier": "^3.3.3", - "prop-types": "^15.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^18.3.0", + "react-dom": "^18.3.0", "regenerator-runtime": "^0.14.1", "start-server-and-test": "^2.0.8", "typescript": "^5.6.2" @@ -99,8 +98,7 @@ "tabbable": "^6.2.0" }, "peerDependencies": { - "prop-types": "^15.8.1", - "react": ">=16.3.0", - "react-dom": ">=16.3.0" + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } } diff --git a/src/focus-trap-react.js b/src/focus-trap-react.js index c11345ce..8668ef4c 100644 --- a/src/focus-trap-react.js +++ b/src/focus-trap-react.js @@ -1,8 +1,10 @@ const React = require('react'); -const PropTypes = require('prop-types'); const { createFocusTrap } = require('focus-trap'); const { isFocusable } = require('tabbable'); +/** + * @type {import('../index.d.ts').FocusTrap} + */ class FocusTrap extends React.Component { constructor(props) { super(props); @@ -412,74 +414,6 @@ class FocusTrap extends React.Component { } } -// support server-side rendering where `Element` will not be defined -const ElementType = typeof Element === 'undefined' ? Function : Element; - -FocusTrap.propTypes = { - active: PropTypes.bool, - paused: PropTypes.bool, - focusTrapOptions: PropTypes.shape({ - document: PropTypes.object, - onActivate: PropTypes.func, - onPostActivate: PropTypes.func, - checkCanFocusTrap: PropTypes.func, - onPause: PropTypes.func, - onPostPause: PropTypes.func, - onUnpause: PropTypes.func, - onPostUnpause: PropTypes.func, - onDeactivate: PropTypes.func, - onPostDeactivate: PropTypes.func, - checkCanReturnFocus: PropTypes.func, - initialFocus: PropTypes.oneOfType([ - PropTypes.instanceOf(ElementType), - PropTypes.string, - PropTypes.bool, - PropTypes.func, - ]), - fallbackFocus: PropTypes.oneOfType([ - PropTypes.instanceOf(ElementType), - PropTypes.string, - // NOTE: does not support `false` as value (or return value from function) - PropTypes.func, - ]), - escapeDeactivates: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), - clickOutsideDeactivates: PropTypes.oneOfType([ - PropTypes.bool, - PropTypes.func, - ]), - returnFocusOnDeactivate: PropTypes.bool, - setReturnFocus: PropTypes.oneOfType([ - PropTypes.instanceOf(ElementType), - PropTypes.string, - PropTypes.bool, - PropTypes.func, - ]), - allowOutsideClick: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), - preventScroll: PropTypes.bool, - tabbableOptions: PropTypes.shape({ - displayCheck: PropTypes.oneOf([ - 'full', - 'legacy-full', - 'non-zero-area', - 'none', - ]), - getShadowRoot: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), - }), - trapStack: PropTypes.array, - isKeyForward: PropTypes.func, - isKeyBackward: PropTypes.func, - }), - containerElements: PropTypes.arrayOf(PropTypes.instanceOf(ElementType)), // DOM element ONLY - children: PropTypes.oneOfType([ - PropTypes.element, // React element - PropTypes.instanceOf(ElementType), // DOM element - ]), - - // NOTE: _createFocusTrap is internal, for testing purposes only, so we don't - // specify it here. It's expected to be set to the function returned from - // require('focus-trap'), or one with a compatible interface. -}; - FocusTrap.defaultProps = { active: true, paused: false,