Skip to content

Commit

Permalink
MVP support for React 19 (#1350)
Browse files Browse the repository at this point in the history
focus-trap-react was already "ready" for React 19. All that was necessary
was to drop `prop-types`, which has been removed as a dependency.

It's also time to move forward and stop supporting ancient versions of
React. Therefore, the minimum supported version of React will now be
18.0.0, and I intend for that remain one version behind the latest
version going forward, provided the gap between the two is reasonably
surmountable.
  • Loading branch information
stefcameron authored Dec 7, 2024
1 parent bdac34a commit 4a37dae
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 121 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-planes-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'focus-trap-react': major
---

Dropping `propTypes` and `defaultProps` no longer supported by React 19 and long deprecated in React 18 (going forward, use TypeScript for prop typings, and if necessary, a runtime library to validate props); Increasing minimum supported React version up to >=18; Bumping `focus-trap` dependency to v7.6.2
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ dist/

cypress/videos
cypress/screenshots
cypress/downloads
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,23 @@ 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 `<FocusTrap>` element for runtime validation and initialization. The same techniques you would now use in React 19 are backward-compatible with React 18:

- 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`)

As old and as broad as _reasonably_ possible, excluding browsers that are out of support or have nearly no user base.
> This library aims to support one major version of React _behind_ the current major version, since React major releases are typically years apart -- to the extent that the feature drift is not too great and remains reasonably surmountable.
## Browser Support

Focused on desktop browsers, particularly Chrome, Edge, FireFox, Safari, and Opera.

Gated by what React [supports](https://legacy.reactjs.org/docs/javascript-environment-requirements.html) in the version [currently](#react-dependency) supported.

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.
Expand Down
4 changes: 1 addition & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -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<any> {
children?: React.ReactNode;
Expand All @@ -13,4 +11,4 @@ declare namespace FocusTrap {
}
}

declare class FocusTrap extends React.Component<FocusTrap.Props> { }
export declare class FocusTrap extends React.Component<FocusTrap.Props> { }
74 changes: 33 additions & 41 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@
"@testing-library/cypress": "^10.0.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@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",
"all-contributors-cli": "^6.26.1",
"babel-jest": "^29.7.0",
"babelify": "^10.0.0",
Expand All @@ -87,7 +89,6 @@
"jest-watch-typeahead": "^2.2.2",
"onchange": "^7.1.0",
"prettier": "^3.4.1",
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"regenerator-runtime": "^0.14.1",
Expand All @@ -99,8 +100,9 @@
"tabbable": "^6.2.0"
},
"peerDependencies": {
"prop-types": "^15.8.1",
"react": ">=16.3.0",
"react-dom": ">=16.3.0"
"@types/react": "^18.0.0 || ^19.0.0",
"@types/react-dom": "^18.0.0 || ^19.0.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
}
}
76 changes: 7 additions & 69 deletions src/focus-trap-react.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -412,74 +414,10 @@ 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.
};

// NOTE: While React 19 REMOVED support for `propTypes`, support for `defaultProps`
// __for class components ONLY__ remains: "Class components will continue to support
// defaultProps since there is no ES6 alternative."
// @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-proptypes-and-defaultprops
FocusTrap.defaultProps = {
active: true,
paused: false,
Expand Down

0 comments on commit 4a37dae

Please sign in to comment.