Giter Club home page Giter Club logo

focus-trap-react's Introduction

focus-trap-react CI Codecov license

All Contributors

A React component that traps focus.

This component is a light wrapper around focus-trap, tailored to your React-specific needs.

You might want it for, say, building an accessible modal?

What it does

Check out the demo.

Please read the focus-trap documentation to understand what a focus trap is, what happens when a focus trap is activated, and what happens when one is deactivated.

This module simply provides a React component that creates and manages a focus trap.

  • The focus trap automatically activates when mounted (by default, though this can be changed).
  • The focus trap automatically deactivates when unmounted.
  • The focus trap can be activated and deactivated, paused and unpaused via props.

Installation

npm install focus-trap-react

dist/focus-trap-react.js is the Babel-compiled file that you'll use.

React dependency

React >= 16.3.0

Browser Support

As old and as broad as reasonably possible, excluding browsers that are out of support or have nearly no user base.

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

Focus-trap-react is not officially tested on any mobile browsers or devices.

โš ๏ธ Microsoft no longer supports any version of IE, so IE is no longer supported by this library.

๐Ÿ’ฌ Focus-trap-react relies on focus-trap so its browser support is at least what focus-trap supports.

๐Ÿ’ฌ Keep in mind that performance optimization and old browser support are often at odds, so tabbable may not always be able to use the most optimal (typically modern) APIs in all cases.

Usage

You wrap any element that you want to act as a focus trap with the <FocusTrap> component. <FocusTrap> expects exactly one child element which can be any HTML element or other React component that contains focusable elements. It cannot be a Fragment because <FocusTrap> needs to be able to get a reference to the underlying HTML element, and Fragments do not have any representation in the DOM.

For example:

<FocusTrap>
  <div id="modal-dialog" className="modal" >
    <button>Ok</button>
    <button>Cancel</button>
  </div>
</FocusTrap>
<FocusTrap>
  <ModalDialog okButtonText="Ok" cancelButtonText="Cancel" />
</FocusTrap>

You can read further code examples in demo/ (it's very simple), and see how it works.

Here's one more simple example:

const React = require('react');
const ReactDOM = require('react-dom'); // React 16-17
const { createRoot } = require('react-dom/client'); // React 18
const FocusTrap = require('focus-trap-react');

class Demo extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      activeTrap: false
    };

    this.mountTrap = this.mountTrap.bind(this);
    this.unmountTrap = this.unmountTrap.bind(this);
  }

  mountTrap = () => {
    this.setState({ activeTrap: true });
  };

  unmountTrap = () => {
    this.setState({ activeTrap: false });
  };

  render() {
    const trap = this.state.activeTrap
      ? <FocusTrap
          focusTrapOptions={{
            onDeactivate: this.unmountTrap
          }}
        >
          <div className="trap">
            <p>
              Here is a focus trap
              {' '}
              <a href="#">with</a>
              {' '}
              <a href="#">some</a>
              {' '}
              <a href="#">focusable</a>
              {' '}
              parts.
            </p>
            <p>
              <button onClick={this.unmountTrap}>
                deactivate trap
              </button>
            </p>
          </div>
        </FocusTrap>
      : false;

    return (
      <div>
        <p>
          <button onClick={this.mountTrap}>
            activate trap
          </button>
        </p>
        {trap}
      </div>
    );
  }
}

ReactDOM.render(<Demo />, document.getElementById('root')); // React 16-17
createRoot(document.getElementById('root')).render(<Demo />); // React 18

โ—๏ธโ—๏ธ React 18 Strict Mode โ—๏ธโ—๏ธ

React 18 introduced new behavior in Strict Mode whereby it mimics a possible future behavior where React might optimize an app's performance by unmounting certain components that aren't in use and later remounting them with previous, reused state when the user needs them again. What constitutes "not in use" and "needs them again" is as yet undefined.

Remounted with reused state is the key difference between what is otherwise expected about unmounted components.

v9.0.2 adds support for this new Strict Mode behavior: The trap attempts to detect that it has been remounted with previous state: If the active prop's value is true, and an internal focus trap instance already exists, the focus trap is re-activated on remount in order to reconcile stated expectations.

๐Ÿšจ In Strict Mode (and so in dev builds only, since this behavior of Strict Mode only affects dev builds), the trap will be deactivated as soon as it is mounted, and then reactivated again, almost immediately, because React will immediately unmount and remount the trap as soon as it's rendered.

Therefore, avoid using options like onActivate, onPostActivate, onDeactivate, or onPostDeactivate to affect component state.

Explanation and sample anti-pattern to avoid

See this discussion for an example sandbox (issue description) where onDeactivate was used to trigger the close of a dialog when the trap was deactivated (e.g. to react to the user clicking outside the trap with focusTrapOptions.clickOutsideDeactivates=true).

The result can be that (depending on how you render the trap) in Strict Mode, the dialog never appears because it gets closed as soon as the trap renders, since the trap is deactivated as soon as it's unmounted, and so the onDeactivate handler is called, thus hiding the dialog...

This is intentional: If the trap gets unmounted, it has no idea if it's being unmounted for good or if it's going to be remounted at some future point in time. It also has no idea of knowing how long it will be until it's remounted again. So it must be deactivated as though it's going away for good in order to prevent unintentional behavior and memory leaks (from orphaned document event listeners).

Props

children

โš ๏ธ The <FocusTrap> component requires a single child, and this child must forward refs onto the element which will ultimately be considered the trap's container. Since React does not provide for a way to forward refs to class-based components, this means the child must be a functional component that uses the React.forwardRef() API.

If you must use a class-based component as the trap's container, then you will need to get your own ref to it upon render, and use the containerElements prop (initially set to an empty array []) in order to provide the ref's element to it once updated by React (hint: use a callback ref).

๐Ÿ’ฌ The child is ignored (but still rendered) if the containerElements prop is used to imperatively provide trap container elements.

Example:

const React = require('react');
const { createRoot } = require('react-dom/client');
const propTypes = require('prop-types');
const FocusTrap = require('../../dist/focus-trap-react');

const container = document.getElementById('demo-function-child');

const TrapChild = React.forwardRef(function ({ onDeactivate }, ref) {
  return (
    <div ref={ref}>
      <p>
        Here is a focus trap <a href="#">with</a> <a href="#">some</a>{' '}
        <a href="#">focusable</a> parts.
      </p>
      <p>
        <button
          onClick={onDeactivate}
          aria-describedby="class-child-heading"
        >
          deactivate trap
        </button>
      </p>
    </div>
  );
});

TrapChild.displayName = 'TrapChild';
TrapChild.propTypes = {
  onDeactivate: propTypes.func,
};

class DemoFunctionChild extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      activeTrap: false,
    };

    this.mountTrap = this.mountTrap.bind(this);
    this.unmountTrap = this.unmountTrap.bind(this);
  }

  mountTrap() {
    this.setState({ activeTrap: true });
  }

  unmountTrap() {
    this.setState({ activeTrap: false });
  }

  render() {
    const trap = this.state.activeTrap && (
      <FocusTrap
        focusTrapOptions={{
          onDeactivate: this.unmountTrap,
        }}
      >
        <TrapChild />
      </FocusTrap>
    );

    return (
      <div>
        <p>
          <button onClick={this.mountTrap} aria-describedby="function-child-heading">
            activate trap
          </button>
        </p>
        {trap}
      </div>
    );
  }
}

const root = createRoot(container);
root.render(<DemoFunctionChild />);

focusTrapOptions

Type: Object, optional

Pass any of the options available in focus-trap's createOptions.

โ—๏ธ This prop is only read once on the first render. It's never looked at again. This is particularly important if you use state-dependent memoized React Hooks (e.g. const onActivate = useCallback(() => {...}, [something])) for any of the focus-trap callbacks like onActivate(), onDeactivate(), clickOutsideDeactivates(), etc.

If you need state-dependent callbacks, you have two options: (1) Use a React component class (as in the examples in this README) with bound member handlers, or (2) use a React Ref like useRef({ myState: 1 }) in your callbacks and manually manage your state.

See #947 for more details.

โš ๏ธ See notes about testing in JSDom (e.g. using Jest) if that's what you currently use.

active

Type: Boolean, optional

By default, the FocusTrap activates when it mounts. So you activate and deactivate it via mounting and unmounting. If, however, you want to keep the FocusTrap mounted while still toggling its activation state, you can do that with this prop.

See demo/demo-special-element.js.

paused

Type: Boolean, optional

If you would like to pause or unpause the focus trap (see focus-trap's documentation), toggle this prop.

containerElements

Type: Array of HTMLElement, optional

If specified, these elements will be used as the boundaries for the focus-trap, instead of the child. These get passed as arguments to focus-trap's updateContainerElements() method.

๐Ÿ’ฌ Note that when you use containerElements, the need for a child is eliminated as the child is always ignored (though still rendered) when the prop is specified, even if this prop is [] (an empty array).

Also note that if the refs you're putting into the array, like containerElements={[ref1.current, ref2.current]}, aren't resolved yet, resulting in [null, null] for example, the trap will not get created. The array must contain at least one valid HTMLElement in order for the trap to get created/updated.

If containerElements is subsequently updated (i.e. after the trap has been created) to an empty array (or an array of falsy values like [null, null]), the trap will still be active, but the TAB key will do nothing because the trap will not contain any tabbable groups of nodes. At this point, the trap can either be deactivated manually or by unmounting, or an updated set of elements can be given to containerElements to resume use of the TAB key.

Using containerElements does require the use of React refs which, by nature, will require at least one state update in order to get the resolved elements into the prop, resulting in at least one additional render. In the normal case, this is likely more than acceptable, but if you really want to optimize things, then you could consider using focus-trap directly (see Trap2.js).

Help

Testing in JSDom

โš ๏ธ JSDom is not officially supported. Your mileage may vary, and tests may break from one release to the next (even a patch or minor release).

This topic is just here to help with what we know may affect your tests.

In general, a focus trap is best tested in a full browser environment such as Cypress, Playwright, or Nightwatch where a full DOM is available.

Sometimes, that's not entirely desirable, and depending on what you're testing, you may be able to get away with using JSDom (e.g. via Jest), but you'll have to configure your traps using the focusTrapOptions.tabbableOptions.displayCheck: 'none' option.

See Testing focus-trap in JSDom for more details.

Contributing

See CONTRIBUTING.

Contributors

In alphabetical order:

Alex Dawson
Alex Dawson

๐Ÿ“–
Benjamin Koltes
Benjamin Koltes

๐Ÿ›
Benjamin Tan
Benjamin Tan

๐Ÿ“–
Clint Goodman
Clint Goodman

๐Ÿ’ป ๐Ÿ“– ๐Ÿ’ก โš ๏ธ
Daniel
Daniel

๐Ÿšง โš ๏ธ
Daniel Tonon
Daniel Tonon

๐Ÿ“– ๐Ÿ’ป โš ๏ธ
David Clark
David Clark

๐Ÿ’ป ๐Ÿ› ๐Ÿš‡ โš ๏ธ ๐Ÿ“– ๐Ÿšง
Dependabot
Dependabot

๐Ÿšง
Johannes Ewald
Johannes Ewald

๐Ÿ’ป
Jonathan Suzuki
Jonathan Suzuki

๐Ÿ›
Kasper Garnรฆs
Kasper Garnรฆs

๐Ÿ›
Kathleen McMahon
Kathleen McMahon

๐Ÿšง
LoganDark
LoganDark

๐Ÿ›
Marais Rossouw
Marais Rossouw

๐Ÿš‡
Michael
Michael

๐Ÿ’ก
Misha Moroshko
Misha Moroshko

๐Ÿ›
Nate Liu
Nate Liu

โš ๏ธ
Rivaldo Junior
Rivaldo Junior

๐Ÿšง
Scott Rippey
Scott Rippey

๐Ÿ’ป ๐Ÿ›
Sean McPherson
Sean McPherson

๐Ÿ’ป
Shane Moore
Shane Moore

๐Ÿ“ฆ
Slapbox
Slapbox

๐Ÿ“– ๐Ÿ›
Stefan Cameron
Stefan Cameron

๐Ÿ’ป ๐Ÿ› ๐Ÿš‡ โš ๏ธ ๐Ÿ“– ๐Ÿšง
Tyler Hawkins
Tyler Hawkins

๐Ÿ“– ๐Ÿ’ก โš ๏ธ ๐Ÿ”ง
Wandrille Verlut
Wandrille Verlut

๐Ÿ’ป โš ๏ธ
krikienoid
krikienoid

๐Ÿ›
robert-westenberger
robert-westenberger

๐Ÿ“– ๐Ÿ›
syntactic-salt
syntactic-salt

๐Ÿ›

focus-trap-react's People

Contributors

allcontributors[bot] avatar almerosteyn avatar ayc0 avatar bnhovde avatar bnjmnt4n avatar cerinman avatar cgood92 avatar colmbrady avatar dan503 avatar davidtheclark avatar dependabot[bot] avatar disintegrator avatar dsifford avatar dsil avatar github-actions[bot] avatar iernie avatar jhnns avatar liunate avatar maraisr avatar resource11 avatar rivajunior avatar scottrippey avatar seanmcp avatar shiawuen avatar smoores-dev avatar stefcameron avatar taddei avatar teddriggs avatar thawkin3 avatar wandroll avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

focus-trap-react's Issues

Seeking co-maintainers!

I've been shifting my focus away from UI development, so don't plan on addressing new issues myself. If you use this library and want to see development continue, you can make that happen by becoming a co-maintainer โ€”ย with permissions to triage issues, merge PRs, cut releases, etc.

Please comment below if you're interested!

Another possibility is for a dedicated owner to fork this code and create a new package. I'd be happy to link to that library from the README of this one.

Button that toggles focus trap when clickOutsideDeactivates: true does not work

I have a button that toggles the activation of a focus trap. However I want the the FocusTrap to go away when I click anywhere outside of it, including the button (which is outside the focustrap). The problem is if I click the button the onDeactivate function is called which kills the focus trap, but then the onClick function for the button then thinks the focus trap is dead so it turns it back on.

How do I prevent the onClick handler for the button from firing when clickOutsideDeactivates: true calls the the onDeactivate function?? The button lies outside the focus trap.

Using this in a React 15.5 application causes deprecation warnings

With the release of React 15.5 the use of CreateClass instead of ES6 classes and referencing PropTypes from React itself are marked as deprecation warnings and this will be removed in React 16.

This package is referenced in react-aria-modal where I have also submitted a PR but without updating this package as well the fixes there does not remove the React warnings.

I have opened a PR on this for your consideration #11

Use Jest for testing

The existing tests could be replaced with Jest tests. Although we'd lose the browser variety we get with zuul, I think it's well worth the simplification.

<FocusTrap /> fails to render when doing SSR

Hi @davidtheclark, thanks for the awesome project!

Something we noticed is that when using <FocusTrap /> in an SSR environment, we get the following stack trace:

   ReferenceError: Element is not defined
     
     at Object.<anonymous> (node_modules/tabbable/index.js:14:15)
     at Object.<anonymous> (node_modules/focus-trap/index.js:1:105)
     at Object.<anonymous> (node_modules/y-modal/node_modules/focus-trap-react/dist/focus-trap-react.js:12:23)

This is because of this line https://github.com/davidtheclark/tabbable/blob/e8ecf52617fe97066361356070ae42364e089c8b/index.js#L14 - Element only exists on window, and not in a node environment

Clearly this component only really makes sense in a browser environment, and the answer here is the detect when we're in SSR and stub out the component as necessary.

I'm wondering if we could do this automagically behind the scenes tho, so this is transparent to application developers? Maybe we could just render a dumb <div> (or whatever element was specified) if we're in a SSR environment.

Would you be open to such a PR? Thanks!

Add React 16 Support

focus-trap-react provides support for react v0.14.x || v^15.0.0. React 16 is expected to be released soon and can currently be tested with react 16.0.0-rc (more information can be found on facebook/react#10294).

focus-trap-react is a dependency for a few of the packages in the terra-core monorepo, which will need to be React 16 compatible. From my initial assessment of our repository, it seems focus-trap behaves as expected with react v16.0.0-rc, but confirmed testing is needed.

clickInsideReactivates or equiv?

Entirely possible this is already possible and I just haven't seen it in the docs, but is there a way to reactivate a trap upon clicking inside the form?

Change focus after focus-trap unmounts requires setTimeout

Im using focus-trap in a modal component. When the modal is closed I need to set the focus on a specific element (using a ref). I have this working in a setTimeout but it doesn't work without. Is this expected or is this a code smell?

Here is my code. The modal is closed and focus is set based on a URL change provided by React Router.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.buttonRef = React.createRef();
  }

  componentDidUpdate(prevProps) {
    if (this.props.url === '' && prevProps.url = "old-url") {
      console.log('target element: ', this.buttonRef.current.firstChild)

      // This doenst work if not in a setTimeout
      // this.buttonRef.current.firstChild.focus();
      setTimeout(() => {
        this.buttonRef.current.firstChild.focus();
      }, 1);
    }
  }

Use React Testing Library for unit tests

Hi all,

How would you feel about using Kent Dodds' React Testing Library package to unit test this project? That's probably the most commonly used testing library at this point and is more of an "industry standard" than using the test utils directly like what's currently being done.

I'm happy to submit an MR adding this soon! I just wanted to check if there was some good reason that this is not currently being used.

Thanks!

Add className to focus-trap div

Hi,

We are using focus-trap-react in our Modal component.

I noticed that focus-trap-react is wrapping the childern inside a div. Unfortunately, this div is causing a conflict in our css and it would be helpful if we could pass a specific class that will align the modal correctly.

Is there a way to pass that as a prop? Or to solve this conflict?

Thanks.

Option `allowOutsideClick` should accept a boolean

Description

The prop type for allowOutsideClick is func:

allowOutsideClick: PropTypes.func,

This is fine (and mimics the focus-trap options), but the name implies that it should also accept a boolean. "allows" is one of those words โ€“ like "has", "is", and "will" โ€“ that indicates to the user that the value is either true or false.

Expected behavior

Passing true to the allowOutsideClick works as expected.

Actual behavior

A run-time warning is logged and the option is not active.

Possible solution

In this loop:

for (const optionName in specifiedFocusTrapOptions) {
if (
!Object.prototype.hasOwnProperty.call(
specifiedFocusTrapOptions,
optionName
)
) {
continue;
}
if (optionName === 'returnFocusOnDeactivate') {
continue;
}
tailoredFocusTrapOptions[optionName] =
specifiedFocusTrapOptions[optionName];
}

We could add a type check like:

if (optionName === 'allowOutsideClick' && typeof specifiedFocusTrapOptions[optionName] === 'boolean') {
    tailoredFocusTrapOptions[optionName] = () => specifiedFocusTrapOptions[optionName]
}

Then update the prop types at the bottom to be:

allowOutsideClick: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.bool
]),

Conclusion

This is a small patch that would make this little feature a little more usable. I'd be happy to contribute and make this addition, but yield to the maintainers for further discussion.

New containerElements option doesn't lend itself to React

My scenario: I want to limit focus to the child of FocusTrap, and a set of CSS selectors. Here's what my code has to look like:

export const MyFocusTrap = props => {
  const ref = React.useRef();
  const [dummy, setDummy] = React.useState();

  React.useEffect(() => {
    setDummy(Math.random());
  }, []);

  // `array` is my own array utils library.
  const elts = array.compact(array.flatten([
    props.containerCss.map(s => Array.from(document.querySelectorAll(s))),
    ref.current
  ]));

  return (
    <FocusTrapReact containerElements={elts}>
      <div ref={ref}>{props.children}</div>
    </FocusTrapReact>
  );
};

The issues I see are:

  1. In order to include the child, I have to use a ref. I don't want that div to exist at all.
  2. Because the ref is necessary, I have to use dummy state to force the component to render twice on mount. Otherwise, the ref wouldn't be added to the elements list. This is the biggest issue, because it causes a potentially significant performance hit.
  3. containerElements must be HTMLElements, not selectors, so I have to do a lot of extra work to get them into the right format.

clickOutsideDeactivates option does not work on iOS devices

The clickOutsideDeactivates option appears to work fine in the regular focus-trap demo page (not focus-trap-react), on all browsers and devices I've tested.

However, when using the focus-trap-react demo page on my iPad, when I tap the "activate trap" button on the "special element" demo, tapping outside the trap does not deactivate it.

This problem seems to be unique to the react version of the library, and unique to iOS. Testing on Android and on desktop browsers, it works fine. And the normal library's demo of clickOutsideDeactivates seems to work fine on iOS.

Error: `initialFocus` refers to no known node in Enzyme

I have consumed this component and it works fine. while writing test cases i m getting this error:

Error: initialFocus refers to no known node

I actually passed a selector to initialFocus prop. I tried passing a well mounted node which already exists (tested by querying), but i m still getting the same error. Any idea why is this happening?

"No focusable element" error thrown if children are hidden.

I have a hidden dialog box wrapped in a FocusTrap. I make the dialog box visible at the same time that I activate the FocusTrap. This causes the error:

index.js:163 Uncaught Error: You can't have a focus-trap without at least one focusable element.

It seems that as the FocusTrap activates, it's upset that there isn't already a non-hidden focusable element, even though I'm trying to create one simultaneously. If I add a fake always-visible focusable element it stops complaining. But I don't want that in production.
My setup is basically this:

<FocusTrap active={showIt}>
	<input style={{visibility: "visible"}} tabIndex={0}/>     // FAKE focusable element makes it work.
	<div style={{visibility: showIt ? "visible" : "hidden"}}>
		<input style={{visibility: "inherit"}} tabIndex={0}/>
	</div>
</FocusTrap>

I don't think I can use fallbackFocus since none of the contained elements are focusable while they're hidden. Is there a way just to suppress this error from being thrown?
Thanks!

Option: `setReturnFocus` is not being respected on component unmount

setReturnFocus seems to be ignored in the focusTrapOptions; went looking for solutions in the original focus-trap repository, then realized the deactivation logic is being hijacked for the react variant. It doesn't look like it was taken into account during implementation.

Going to use the more generic onDeactivate as a workaround for the time being; would love to see an update to README though, in case anyone else stumbles into this issue as well.

focus-trap version bump

Hope not to be a nuisance, but can we get a new release of this that includes the latest version of focus-trap (2.0.2)?

IE11 focus is not trapped when SVG is first focusable element

Inside of FocusTrap, I've set an svg to be the initial focusable element. However, this doesn't trap the focus, which means that the elements in the background are tabbable. This is only in IE11 (wish I didn't have to work with IE11 trust me).

For brevity, here's a basic composition. The "dialog" div is appended to the body tag.
<FocusTrap><div id="dialog"><svg focusable tabIndex="0"> ... </svg></div></FocusTrap>

By doing this, I can verify in the DOM in IE11 that the svg does have the tabIndex="0" and focusable="true". But the focus is not trapped and I cannot reach the svg by tabbing.

I can work around this by putting the initial focus on the "dialog" div instead. This traps the focus correctly and the svg is in the tab flow. Curious to know if anyone has insight as to why focus isn't trapped if an svg is the initial focusable element.

`this.node.current` is null.

I have a code like this in react:

constructor(props) {
    super(props);
    this.node = React.createRef();
}

And then in the return call, I have this:

<FocusTrap>
    <ul
        ref={this.node}
    >
        {children}
    </ul>
</FocusTrap>

If I go ahead and fetch this.node.current, I get null.
However, if I do it without the <FocusTrap> everything works fine.

Any idea why <FocusTrap> would be messing with the node?

If however I do this, then it fetches the this.node.current:

<FocusTrap>
    <div>
        <ul
            ref={this.node}
        >
            {children}
        </ul>
    </div>
</FocusTrap>

I do not want to give that extra containing class, as it is messing up the layout.

Object doesn't support property or method 'focus'

Hi there,

I am running into an issue on IE11 in which the user "closes" the FocusTrap component and I run into this exception:
Object doesn't support property or method on 'focus'

Looking around the code, I notice I do not pass in focusTrapOptions.returnFocusOnDeactivate which is checked here in componentWillUnmount().

Since that prop is undefined it actually fails the !== false comparison and the component attempts to call previouslyFocusedElement.focus.

I figure I could:

a. Just pass the prop in as false.
b. Change the check to be a little less strict on false.
c. Provide a default value of false on the prop.

Any thoughts or completely different suggestions?

Thanks for your work on this!

Demo page appears to be broken

Looking at the demo page at http://davidtheclark.github.io/focus-trap-react/demo/, I notice a few issues:

  • In the "first focus, no escape" demo, the "activate trap" button does nothing. The button disappears, and nothing appears.
  • the "special element" and "demo autofocus" demos do not appear to render at all.

Notably, when I build and run the demo page locally, everything works fine.

Focus trap not disabled when clicking outside the container

I would like the focus-trap to be disabled when I click outside of the container.
I'm using react, so I would like to refrain from doing something like body.click() etc.

The focus trap is sitting within an <Overlay> element from react bootstrap, and that closes when you click outside of it, but with focus-trap that functionality no longer seems to work.

Trap doesn't work if the children are rendered into a React portal

Test case - https://codesandbox.io/s/9y9lmmko84

A stated use case for React's portals it to avoid overflow issues with things like modals & tooltips. In Elastic UI we use quite a few portals, and have a case where our focused-trapped modal (in a portal) tries to open a focus-trapped context menu (in another portal), but the context menu items won't receive mouse clicks because the on-outside-click detection doesn't understand the portal.

FWIW I don't think this is solvable by focus-trap-react without underlying changes to react-dom, but I wanted to start the conversation here. Since outside click detection is often implemented by asking if the parent DOM element contains the even target, I think ReactDOM should expose a similar contains method that continues through portals, just as event bubbling does.

Any similar change would require focus-trap to allow a configuration parameter to specify a contains function, which could retain the current logic as the default for backwards compatibility.

Please let me know your thoughts on this (if the would-be required changes here and focus-trap make sense, etc), and I'm happy to open the related issue on the React github.

Last example on demo website is not working on Firefox

Steps to reproduce:

  1. Open Firefox (I used version 82)
  2. Go to http://focus-trap.github.io/focus-trap-react/demo/
  3. Open the last example
  4. Press tab multiple times, notice that Another focusable thing is never get focused

Expected:
Another focusable thing should be focus-able

Actual:
This issue found on Firefox 82 (haven't tested on previous versions), but working as expected on Chrome.
There are probably other examples in the demo website which are also not working correctly, I just highlighted the last example.

Problems resetting trigger element focus for always-rendered elements

Heeeeeey, first off, thanks for creating this standalone component! It's been useful for me in creating some modal dialog components.

Unfortunately, my component's original transitions get broken when I wrap the dialog with the FocusTrap. I use a ref to pass to another component which provides callbacks when transitions start/end, but it seems that this component overrides that ref with another instead of looking for one that is existing.

This is the conditional for the render:

      {ariaModal && open
        ? (<FocusTrap focusTrapOptions={focusTrapOptions}>{renderDialog()}</FocusTrap>)
        : renderDialog()
      }

This is the behavior without the focus trap:
modal_dialog_no_focus_trap

And this is the behavior with the focus trap:
modal_dialog_with_focus_trap

It would seem to me that it might be trivial to update code that attaches the ref to look for an existing ref, but I've never done that before so it's hard to say. If there is any workaround or anything, I'd love to know! It's also possible that something else is going on as I don't understand why the transitions themselves are broken given that they are specified 100% in the css and only their events are tracked based on the ref. Any insight here is welcome!

EDIT: (renamed issue after discovering root issue) Now that I'm thinking about it, the different conditional renders might be causing the css transform transitions to fail since there is new parent markup. That markup probably causes a brand new render on the dialog whose initial state is open and therefore transitions from nowhere. The only reason I did it that way is that always rendering the focus trap means that the initial mount is the only time the trap grabs the trigger element (so it can be refocused on dialog close). I originally tried to get around this by using state to grab the document.activeElement when the dialog is set to open and then passing that to the trap component via a setReturnFocus function, but it didn't seem to get honored on subsequent re-renders when toggling the trap's active state.

It would seem this library doesn't take into account elements which always exist in a view and are either hidden via display/visibility properties or via positioning properties that shove elements off screen until their "open" state is triggered. Please help! ๐Ÿ˜“

returnFocusOnDeactivate does not work when using active prop

Ive noticed that when you are using the active prop, there is code which ignores the returnFocusOnDeactivate option. Is this intentional?

I would of thought that the componentDidUpdate function should be this:

componentDidUpdate(prevProps) {
    if (prevProps.active && !this.props.active) {
      const { focusTrapOptions } = this.props;
      const returnFocus = focusTrapOptions.returnFocusOnDeactivate || false;
      this.focusTrap.deactivate({ returnFocus });
    } else if (!prevProps.active && this.props.active) {
      this.focusTrap.activate();
    }

    if (prevProps.paused && !this.props.paused) {
      this.focusTrap.unpause();
    } else if (!prevProps.paused && this.props.paused) {
      this.focusTrap.pause();
    }
  }

that way - when the focus trap is made inactive, then we still get the returnFocus behaviour.

Im happy to P.R this if I am correct in understanding the intended behaviour.

Cheers.

Updated to react 16 and getting Error

I just went into my package.json and updated to react 16 and focus trap is giving me this error:

Error: You can't have a focus-trap without at least one focusable element

"focus-trap-react": "^3.0.4",
"react": "^16.0.0",
"react-dom": "^16.0.0",

Not really sure what happened exactly. Looking into it but wanted to let you know.

autoFocus and initialFocus

You get autoFocus for free. My understanding is that whenever a focusable element with autoFocus is added by react, it automatically gets focused.

autoFocus doesn't work right now with focus-trap-react.

  • If initialFocus is unspecified, it ignores any autoFocus elements and focuses the first element.
  • If you pass a selector that matches no elements, focus-trap-react throws an error and breaks the component.
  • If you pass a valid selector, then you're not taking advantage of the convenience and simplicity of autoFocus in the first place

Bump focus-trap-react to use focus-trap 4.0.2

Id like to bump focus-trap-react to use 4.0.2 of focus-trap. We tested bumping to 4.0.2 in the current version of focus-trap-react to test if it fixes a nested focus trap issue we were having and it looks good.

Ill prepare a P.R for your review.

Types in index.d.ts not resolved

When using FocusTrap component in a typescript project I get the following error:

error TS2605: JSX element type 'FocusTrap' is not a constructor function for JSX elements.
  Type 'FocusTrap' is missing the following properties from type 'ElementClass': render, context, setState, forceUpdate, and 3 more.
Component.tsx(63,7): error TS2607: JSX element class does not support attributes because it does not have a 'props' property.

ReferenceError: document is not defined

When I use react server side render by razzle, I encounter this:

ReferenceError: document is not defined

 at focusTrap (/dat/react-dat-shopping-list/node_modules/focus-trap/index.js:39:13)
    at processChild (/dat/react-dat-shopping-list/node_modules/react-dom/cjs/react-dom-server.node.development.js:3090:14)
    at resolve (/dat/react-dat-shopping-list/node_modules/react-dom/cjs/react-dom-server.node.development.js:3013:5)

My code is normal:

  //...
  render() {
    let { show, text, className } = this.props;

    return (
      <FocusTrap>
        <div
          onClick={this.closeAlert}
          className={className + (show ? " show" : "")}
        >
          <div className="alertContent">
            <div>{text}</div>
            <Button label="OK" onClick={this.closeAlert}></Button>
          </div>
        </div>
      </FocusTrap>
    );
   //...

Code base needs to be modernized to drop findDOMNode and string refs

The following lint issues had to be disabled in order to maintain long backward compatibility to React 16.0:

TL/DR: Using callback refs should clear all these up.

Sooner or later, however, these issues will have to be rectified in order for the code to be compatible with the next major release of React, at which point, we'll have to drop support for < React 16.3 which is when React.createRef() and callback refs were introduced (AFAICT).

New prop types do not work well with server-side rendering

While building a Gatsby project that uses focus-trap-react, I'm getting this build failure:

Building static HTML failed

See our docs page for more info on this error: https://gatsby.dev/debug-html


  160 |     onActivate: PropTypes.func,
  161 |     onDeactivate: PropTypes.func,
> 162 |     initialFocus: PropTypes.oneOfType([PropTypes.instanceOf(Element), PropTypes.string, PropTypes.func]),
      | ^
  163 |     fallbackFocus: PropTypes.oneOfType([PropTypes.instanceOf(Element), PropTypes.string, PropTypes.func]),
  164 |     escapeDeactivates: PropTypes.bool,
  165 |     clickOutsideDeactivates: PropTypes.bool,


  WebpackError: ReferenceError: Element is not defined

  - focus-trap-react.js:162 Object.../../node_modules/focus-trap-react/dist/focus-trap-react.js
    [dds]/[focus-trap-react]/dist/focus-trap-react.js:162:1

This is because Gatsby uses server-side rendering when doing its production build. See the following two similar issues:

Proposed fix is to add this definition before defining the prop types:

const ElementType = typeof Element === "undefined" ? Function : Element;

and then change each occurrence of PropTypes.instanceOf(Element) to PropTypes.instanceOf(ElementType), which is essentially the same as what was done in remix-run/react-router#6610, however what they did there results in nothing being an instance of function() {} because it's a new, unique function. By using Function (which is native to JavaScript), the result is that anything that's a function is an instance of it. That basically renders the type check moot, but I think it's better than having it fail and maybe output some type of warning that you can't fix anyway.

Applying the fix above fixes the issue with Gatsby, and the validation continues to work fine in the browser.

Focus trap should not be forced to 'unpause' when another trap is disabled

Found an issue where one trap is incorrectly forced to unpause when another trap is disabled/destroyed.

Here is simple example:

Steps to reproduce:
Click on 'btn 2' to remove second trap.

Result:
Trap 1 (with child 'btn 1') becomes active preventing 'btn 3' from being focused or clicked.

Expectation:
Trap 1 (with child 'btn 1') should remain paused due <FocusTrap paused={true}>.
'btn 3' should be focusable and clickable.

Possible to use with contentEditable?

Iโ€™ve build a tool that allows you to try out a bunch of different webfonts in the browser. You can change the font family or style; set the size, leading, tracking, and margins; and toggle on and off any of its stylistic sets or open type features.

The editor itself is a <div /> with contentEditable turned on and role set to textbox. This is the element I need to trap focus on conditionally, but Iโ€™m seeing the error that says:

Error: You can't have a focus-trap without at least one focusable element

Is there any way to use focus-trap-react with a contentEditable element?

E2E testing on major browsers

Run E2E testing at each push and pull-request on major browsers:

Cannot read property 'ownerDocument' of null

I'm getting this error thrown by the tabbable library which focus-trap-react depends on by way of focus-trap. At first I thought this might be a jest/enzyme problem but I'm also seeing it in non-test code.

TypeError: Cannot read property 'ownerDocument' of null
    at tabbable (index.js:21)
    at updateTabbableNodes (index.js:267)
    at Object.activate (index.js:72)
    at FocusTrap.componentDidMount (focus-trap-react.js:55)

I also noticed that the line that's throwing this error is an older version than your most recent update of tabbable...perhaps a version update would help? I haven't tested that yet, but I would love any input you have.

I've used this library before and had really great results with it, so thanks!

Strange bug

<FocusTrap active={"0" === this.state.active }>
  <button>Hello</button>
</FocusTrap>

<FocusTrap active={"1" === this.state.active }>
   <button>Hello2</button>
</FocusTrap>

Iโ€™m using the active flag to change focus between two FocusTraps.

active == "0" works fine
active == "1" works fine, the first FocusTrap loses focus
active == "0" again, works fine, the first FocusTrap gains focus
active == "1" the focus disappears!

I looked at the source code of focus-trap and can't figure it out.

Thanks.

Error: Uncaught [Error: Your focus-trap needs to have at least one focusable element]

The new 7.0.0/7.0.1 upgrade breaks down jest/enzyme tests with the error:
Error: Uncaught [Error: Your focus-trap needs to have at least one focusable element].

The above error occurred in the component:
in FocusTrap (created by WrapperComponent)
in WrapperComponent

Seems that focus trap cannot locate any child element.
I have added the isolated test file as well as package.json - everything is up to date there.

package.txt
focustest.txt

Listener memory leak

I've just spent a while hunting down a little memory leak that we traced to this library. The event handlers on inputs that are left focussed when we unmounted a route in our app were being held and causing a leak. Have you come across this before? Is there anyway to ensure these are released from within focus-trap-react?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.