Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MVP support for React 19 #1350

Merged
merged 1 commit into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading