focus-trap-react 8.11.3 → 9.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 4d8e041: Fix an issue when running in strict mode which has React immediately unmount/remount the trap, causing it to deactivate and then have to reactivate (per existing component state) on the remount. [#720](https://github.com/focus-trap/focus-trap-react/issues/720)
8
+
9
+ ## 9.0.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 2d6cd9b: Add explicit dependency on tabbable since the source directly requires it.
14
+
15
+ ## 9.0.0
16
+
17
+ ### Major Changes
18
+
19
+ - 4a77d87: Stop using the infamous `findDOMNode()` on provided `containerElements`.
20
+ - There seems to have been no good reason for this as this prop, if specified, is already required to be an array of HTMLElement references, which means these nodes have already been rendered (if they were once React elements). There appears to have been no remaining need for this API.
21
+ - Furthermore, the minimum supported version of React is now 16.3 as it technically has been for a while now since that is the version that introduced callback refs, which we've been using for quite some time now (so this bump will hopefully not cause any ripples).
22
+
3
23
  ## 8.11.3
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # focus-trap-react [![CI](https://github.com/focus-trap/focus-trap-react/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/focus-trap/focus-trap-react/actions?query=workflow:CI+branch:master) [![Codecov](https://img.shields.io/codecov/c/github/focus-trap/focus-trap-react)](https://codecov.io/gh/focus-trap/focus-trap-react) [![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE)
2
2
 
3
3
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
4
- [![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors)
4
+ [![All Contributors](https://img.shields.io/badge/all_contributors-24-orange.svg?style=flat-square)](#contributors)
5
5
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
6
6
 
7
7
  A React component that traps focus.
@@ -214,19 +214,21 @@ In alphabetical order:
214
214
  <td align="center"><a href="http://kathleenmcmahon.dev/"><img src="https://avatars1.githubusercontent.com/u/11621935?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kathleen McMahon</b></sub></a><br /><a href="#maintenance-resource11" title="Maintenance">🚧</a></td>
215
215
  <td align="center"><a href="https://github.com/LoganDark"><img src="https://avatars.githubusercontent.com/u/4723091?v=4?s=100" width="100px;" alt=""/><br /><sub><b>LoganDark</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3ALoganDark" title="Bug reports">🐛</a></td>
216
216
  <td align="center"><a href="https://marais.io/"><img src="https://avatars2.githubusercontent.com/u/599459?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marais Rossouw</b></sub></a><br /><a href="#infra-maraisr" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
217
+ <td align="center"><a href="https://www.moroshko.me"><img src="https://avatars.githubusercontent.com/u/259753?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Misha Moroshko</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Amoroshko" title="Bug reports">🐛</a></td>
217
218
  <td align="center"><a href="https://github.com/liunate"><img src="https://avatars2.githubusercontent.com/u/38996291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nate Liu</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=liunate" title="Tests">⚠️</a></td>
218
- <td align="center"><a href="https://www.linkedin.com/in/rivajunior/"><img src="https://avatars1.githubusercontent.com/u/11370172?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rivaldo Junior</b></sub></a><br /><a href="#maintenance-rivajunior" title="Maintenance">🚧</a></td>
219
219
  </tr>
220
220
  <tr>
221
+ <td align="center"><a href="https://www.linkedin.com/in/rivajunior/"><img src="https://avatars1.githubusercontent.com/u/11370172?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rivaldo Junior</b></sub></a><br /><a href="#maintenance-rivajunior" title="Maintenance">🚧</a></td>
221
222
  <td align="center"><a href="https://scottrippey.github.io/"><img src="https://avatars3.githubusercontent.com/u/430608?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Rippey</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=scottrippey" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Ascottrippey" title="Bug reports">🐛</a></td>
222
223
  <td align="center"><a href="https://seanmcp.com/"><img src="https://avatars1.githubusercontent.com/u/6360367?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sean McPherson</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=SeanMcP" title="Code">💻</a></td>
223
- <td align="center"><a href="https://recollectr.io"><img src="https://avatars2.githubusercontent.com/u/6835891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Slapbox</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=Slapbox" title="Documentation">📖</a></td>
224
+ <td align="center"><a href="http://smoores.dev"><img src="https://avatars.githubusercontent.com/u/5354254?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shane Moore</b></sub></a><br /><a href="#platform-SMores" title="Packaging/porting to new platform">📦</a></td>
225
+ <td align="center"><a href="https://recollectr.io"><img src="https://avatars2.githubusercontent.com/u/6835891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Slapbox</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=Slapbox" title="Documentation">📖</a> <a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3ASlapbox" title="Bug reports">🐛</a></td>
224
226
  <td align="center"><a href="https://stefancameron.com/"><img src="https://avatars3.githubusercontent.com/u/2855350?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Cameron</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=stefcameron" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Astefcameron" title="Bug reports">🐛</a> <a href="#infra-stefcameron" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=stefcameron" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=stefcameron" title="Documentation">📖</a> <a href="#maintenance-stefcameron" title="Maintenance">🚧</a></td>
225
227
  <td align="center"><a href="http://tylerhawkins.info/201R/"><img src="https://avatars0.githubusercontent.com/u/13806458?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tyler Hawkins</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=thawkin3" title="Documentation">📖</a> <a href="#example-thawkin3" title="Examples">💡</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=thawkin3" title="Tests">⚠️</a> <a href="#tool-thawkin3" title="Tools">🔧</a></td>
226
- <td align="center"><a href="https://github.com/wandroll"><img src="https://avatars.githubusercontent.com/u/4492317?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Wandrille Verlut</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=wandroll" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=wandroll" title="Tests">⚠️</a></td>
227
- <td align="center"><a href="https://github.com/krikienoid"><img src="https://avatars3.githubusercontent.com/u/8528227?v=4?s=100" width="100px;" alt=""/><br /><sub><b>krikienoid</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Akrikienoid" title="Bug reports">🐛</a></td>
228
228
  </tr>
229
229
  <tr>
230
+ <td align="center"><a href="https://github.com/wandroll"><img src="https://avatars.githubusercontent.com/u/4492317?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Wandrille Verlut</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/commits?author=wandroll" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap-react/commits?author=wandroll" title="Tests">⚠️</a></td>
231
+ <td align="center"><a href="https://github.com/krikienoid"><img src="https://avatars3.githubusercontent.com/u/8528227?v=4?s=100" width="100px;" alt=""/><br /><sub><b>krikienoid</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Akrikienoid" title="Bug reports">🐛</a></td>
230
232
  <td align="center"><a href="https://github.com/syntactic-salt"><img src="https://avatars.githubusercontent.com/u/9385662?v=4?s=100" width="100px;" alt=""/><br /><sub><b>syntactic-salt</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap-react/issues?q=author%3Asyntactic-salt" title="Bug reports">🐛</a></td>
231
233
  </tr>
232
234
  </table>
@@ -26,20 +26,13 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
26
26
 
27
27
  var React = require('react');
28
28
 
29
- var ReactDOM = require('react-dom');
30
-
31
29
  var PropTypes = require('prop-types');
32
30
 
33
31
  var _require = require('focus-trap'),
34
32
  createFocusTrap = _require.createFocusTrap;
35
33
 
36
34
  var _require2 = require('tabbable'),
37
- isFocusable = _require2.isFocusable; // TODO: These issues are related to older React features which we'll likely need
38
- // to fix in order to move the code forward to the next major version of React.
39
- // @see https://github.com/davidtheclark/focus-trap-react/issues/77
40
-
41
- /* eslint-disable react/no-find-dom-node */
42
-
35
+ isFocusable = _require2.isFocusable;
43
36
 
44
37
  var FocusTrap = /*#__PURE__*/function (_React$Component) {
45
38
  _inherits(FocusTrap, _React$Component);
@@ -306,16 +299,33 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
306
299
  }, {
307
300
  key: "setupFocusTrap",
308
301
  value: function setupFocusTrap() {
309
- if (!this.focusTrap) {
310
- var focusTrapElementDOMNodes = this.focusTrapElements.map( // NOTE: `findDOMNode()` does not support CSS selectors; it'll just return
311
- // a new text node with the text wrapped in it instead of treating the
312
- // string as a selector and resolving it to a node in the DOM
313
- ReactDOM.findDOMNode);
314
- var nodesExist = focusTrapElementDOMNodes.some(Boolean);
302
+ if (this.focusTrap) {
303
+ // trap already exists: it's possible we're in StrictMode and we're being remounted,
304
+ // in which case, we will have deactivated the trap when we got unmounted (remember,
305
+ // StrictMode, in development, purposely unmounts and remounts components after
306
+ // mounting them the first time to make sure they have reusable state,
307
+ // @see https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state) so now
308
+ // we need to restore the state of the trap according to our component state
309
+ // NOTE: Strict mode __violates__ assumptions about the `componentWillUnmount()` API
310
+ // which clearly states -- even for React 18 -- that, "Once a component instance is
311
+ // unmounted, __it will never be mounted again.__" (emphasis ours). So when we get
312
+ // unmounted, we assume we're gone forever and we deactivate the trap. But then
313
+ // we get remounted and we're supposed to restore state. But if you had paused,
314
+ // we've now deactivated (we don't know we're amount to get remounted again)
315
+ // which means we need to reactivate and then pause. Otherwise, do nothing.
316
+ if (this.props.active && !this.focusTrap.active) {
317
+ this.focusTrap.activate();
318
+
319
+ if (this.props.paused) {
320
+ this.focusTrap.pause();
321
+ }
322
+ }
323
+ } else {
324
+ var nodesExist = this.focusTrapElements.some(Boolean);
315
325
 
316
326
  if (nodesExist) {
317
327
  // eslint-disable-next-line react/prop-types -- _createFocusTrap is an internal prop
318
- this.focusTrap = this.props._createFocusTrap(focusTrapElementDOMNodes, this.internalOptions);
328
+ this.focusTrap = this.props._createFocusTrap(this.focusTrapElements, this.internalOptions);
319
329
 
320
330
  if (this.props.active) {
321
331
  this.focusTrap.activate();
@@ -404,7 +414,7 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
404
414
  throw new Error('A focus-trap cannot use a Fragment as its child container. Try replacing it with a <div> element.');
405
415
  }
406
416
 
407
- var composedRefCallback = function composedRefCallback(element) {
417
+ var callbackRef = function callbackRef(element) {
408
418
  var containerElements = _this3.props.containerElements;
409
419
 
410
420
  if (child) {
@@ -419,7 +429,7 @@ var FocusTrap = /*#__PURE__*/function (_React$Component) {
419
429
  };
420
430
 
421
431
  var childWithRef = React.cloneElement(child, {
422
- ref: composedRefCallback
432
+ ref: callbackRef
423
433
  });
424
434
  return childWithRef;
425
435
  }
@@ -459,6 +469,7 @@ FocusTrap.propTypes = {
459
469
  })
460
470
  }),
461
471
  containerElements: PropTypes.arrayOf(PropTypes.instanceOf(ElementType)),
472
+ // DOM element ONLY
462
473
  children: PropTypes.oneOfType([PropTypes.element, // React element
463
474
  PropTypes.instanceOf(ElementType) // DOM element
464
475
  ]) // NOTE: _createFocusTrap is internal, for testing purposes only, so we don't
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "focus-trap-react",
3
- "version": "8.11.3",
3
+ "version": "9.0.2",
4
4
  "description": "A React component that traps focus.",
5
5
  "main": "dist/focus-trap-react.js",
6
6
  "types": "index.d.ts",
@@ -14,8 +14,8 @@
14
14
  "index.d.ts"
15
15
  ],
16
16
  "scripts": {
17
- "demo-bundle": "browserify demo/js -t babelify --extension=.jsx -o demo/demo-bundle.js",
18
- "start": "yarn build && budo demo/js/index.js:demo-bundle.js --dir demo --live -- -t babelify --extension=.jsx",
17
+ "demo-bundle": "NODE_ENV=production browserify demo/js -t babelify --extension=.jsx -o demo/demo-bundle.js",
18
+ "start": "yarn build && NODE_ENV=development budo demo/js/index.js:demo-bundle.js --dir demo --live -- -t babelify --extension=.jsx",
19
19
  "lint": "eslint \"*.js\" \"src/**/*.js\" \"test/**/*.js\" \"demo/**/*.js\" \"cypress/**/*.js\"",
20
20
  "format": "prettier --write \"{*,src/**/*,test/**/*,demo/js/**/*,.github/workflows/*,cypress/**/*}.+(js|yml)\"",
21
21
  "format:check": "prettier --check \"{*,src/**/*,test/**/*,demo/js/**/*,.github/workflows/*,cypress/**/*}.+(js|yml)\"",
@@ -24,10 +24,10 @@
24
24
  "test:types": "tsc index.d.ts",
25
25
  "test:unit": "jest",
26
26
  "test:coverage": "jest --coverage",
27
- "test:cypress": "start-server-and-test start 9966 'cypress open'",
28
- "test:cypress:ci": "start-server-and-test start 9966 'cypress run --browser $CYPRESS_BROWSER --headless'",
29
- "test:chrome": "CYPRESS_BROWSER=chrome yarn test:cypress:ci",
30
- "test": "yarn format:check && yarn lint && yarn test:unit && yarn test:types && CYPRESS_BROWSER=chrome yarn test:cypress:ci",
27
+ "test:e2e": "ELECTRON_ENABLE_LOGGING=1 start-server-and-test start 9966 'cypress run --browser $CYPRESS_BROWSER --headless'",
28
+ "test:e2e:chrome": "CYPRESS_BROWSER=chrome yarn test:e2e",
29
+ "test:e2e:dev": "ELECTRON_ENABLE_LOGGING=1 start-server-and-test start 9966 'cypress open --browser --e2e'",
30
+ "test": "yarn format:check && yarn lint && yarn test:unit && yarn test:types && yarn test:e2e:chrome",
31
31
  "prepare": "yarn build",
32
32
  "prepublishOnly": "yarn test && yarn build",
33
33
  "release": "yarn build && changeset publish"
@@ -58,12 +58,12 @@
58
58
  "homepage": "https://github.com/focus-trap/focus-trap-react#readme",
59
59
  "devDependencies": {
60
60
  "@babel/cli": "^7.17.10",
61
- "@babel/core": "^7.18.2",
61
+ "@babel/core": "^7.18.5",
62
62
  "@babel/eslint-parser": "^7.18.2",
63
63
  "@babel/plugin-proposal-class-properties": "^7.17.12",
64
64
  "@babel/preset-env": "^7.18.2",
65
65
  "@babel/preset-react": "^7.17.12",
66
- "@changesets/cli": "^2.22.0",
66
+ "@changesets/cli": "^2.23.0",
67
67
  "@testing-library/cypress": "^8.0.3",
68
68
  "@testing-library/dom": "^8.13.0",
69
69
  "@testing-library/jest-dom": "^5.16.4",
@@ -75,7 +75,7 @@
75
75
  "babelify": "^10.0.0",
76
76
  "browserify": "^17.0.0",
77
77
  "budo": "^11.7.0",
78
- "cypress": "^9.7.0",
78
+ "cypress": "^10.1.0",
79
79
  "cypress-plugin-tab": "^1.0.5",
80
80
  "eslint": "^8.17.0",
81
81
  "eslint-config-prettier": "^8.5.0",
@@ -86,20 +86,21 @@
86
86
  "jest-environment-jsdom": "^28.1.1",
87
87
  "jest-watch-typeahead": "^1.1.0",
88
88
  "onchange": "^7.1.0",
89
- "prettier": "^2.6.2",
89
+ "prettier": "^2.7.0",
90
90
  "prop-types": "^15.8.1",
91
- "react": "^18.1.0",
92
- "react-dom": "^18.1.0",
91
+ "react": "^18.2.0",
92
+ "react-dom": "^18.2.0",
93
93
  "regenerator-runtime": "^0.13.9",
94
94
  "start-server-and-test": "^1.14.0",
95
95
  "typescript": "^4.7.3"
96
96
  },
97
97
  "dependencies": {
98
- "focus-trap": "^6.9.4"
98
+ "focus-trap": "^6.9.4",
99
+ "tabbable": "^5.3.3"
99
100
  },
100
101
  "peerDependencies": {
101
102
  "prop-types": "^15.8.1",
102
- "react": ">=16.0.0",
103
- "react-dom": ">=16.0.0"
103
+ "react": ">=16.3.0",
104
+ "react-dom": ">=16.3.0"
104
105
  }
105
106
  }
@@ -1,14 +1,8 @@
1
1
  const React = require('react');
2
- const ReactDOM = require('react-dom');
3
2
  const PropTypes = require('prop-types');
4
3
  const { createFocusTrap } = require('focus-trap');
5
4
  const { isFocusable } = require('tabbable');
6
5
 
7
- // TODO: These issues are related to older React features which we'll likely need
8
- // to fix in order to move the code forward to the next major version of React.
9
- // @see https://github.com/davidtheclark/focus-trap-react/issues/77
10
- /* eslint-disable react/no-find-dom-node */
11
-
12
6
  class FocusTrap extends React.Component {
13
7
  constructor(props) {
14
8
  super(props);
@@ -276,19 +270,32 @@ class FocusTrap extends React.Component {
276
270
  }
277
271
 
278
272
  setupFocusTrap() {
279
- if (!this.focusTrap) {
280
- const focusTrapElementDOMNodes = this.focusTrapElements.map(
281
- // NOTE: `findDOMNode()` does not support CSS selectors; it'll just return
282
- // a new text node with the text wrapped in it instead of treating the
283
- // string as a selector and resolving it to a node in the DOM
284
- ReactDOM.findDOMNode
285
- );
286
-
287
- const nodesExist = focusTrapElementDOMNodes.some(Boolean);
273
+ if (this.focusTrap) {
274
+ // trap already exists: it's possible we're in StrictMode and we're being remounted,
275
+ // in which case, we will have deactivated the trap when we got unmounted (remember,
276
+ // StrictMode, in development, purposely unmounts and remounts components after
277
+ // mounting them the first time to make sure they have reusable state,
278
+ // @see https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state) so now
279
+ // we need to restore the state of the trap according to our component state
280
+ // NOTE: Strict mode __violates__ assumptions about the `componentWillUnmount()` API
281
+ // which clearly states -- even for React 18 -- that, "Once a component instance is
282
+ // unmounted, __it will never be mounted again.__" (emphasis ours). So when we get
283
+ // unmounted, we assume we're gone forever and we deactivate the trap. But then
284
+ // we get remounted and we're supposed to restore state. But if you had paused,
285
+ // we've now deactivated (we don't know we're amount to get remounted again)
286
+ // which means we need to reactivate and then pause. Otherwise, do nothing.
287
+ if (this.props.active && !this.focusTrap.active) {
288
+ this.focusTrap.activate();
289
+ if (this.props.paused) {
290
+ this.focusTrap.pause();
291
+ }
292
+ }
293
+ } else {
294
+ const nodesExist = this.focusTrapElements.some(Boolean);
288
295
  if (nodesExist) {
289
296
  // eslint-disable-next-line react/prop-types -- _createFocusTrap is an internal prop
290
297
  this.focusTrap = this.props._createFocusTrap(
291
- focusTrapElementDOMNodes,
298
+ this.focusTrapElements,
292
299
  this.internalOptions
293
300
  );
294
301
 
@@ -378,7 +385,7 @@ class FocusTrap extends React.Component {
378
385
  );
379
386
  }
380
387
 
381
- const composedRefCallback = (element) => {
388
+ const callbackRef = (element) => {
382
389
  const { containerElements } = this.props;
383
390
 
384
391
  if (child) {
@@ -395,7 +402,7 @@ class FocusTrap extends React.Component {
395
402
  };
396
403
 
397
404
  const childWithRef = React.cloneElement(child, {
398
- ref: composedRefCallback,
405
+ ref: callbackRef,
399
406
  });
400
407
 
401
408
  return childWithRef;
@@ -450,7 +457,7 @@ FocusTrap.propTypes = {
450
457
  getShadowRoot: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
451
458
  }),
452
459
  }),
453
- containerElements: PropTypes.arrayOf(PropTypes.instanceOf(ElementType)),
460
+ containerElements: PropTypes.arrayOf(PropTypes.instanceOf(ElementType)), // DOM element ONLY
454
461
  children: PropTypes.oneOfType([
455
462
  PropTypes.element, // React element
456
463
  PropTypes.instanceOf(ElementType), // DOM element