bootstrap icon indicating copy to clipboard operation
bootstrap copied to clipboard

Bootstrap FocusTrap utility is intercepting external events and throwing Uncaught RangeError: Maximum call stack size exceeded

Open SnippetsUnlimited opened this issue 1 year ago • 1 comments

Prerequisites

Describe the issue

Bootstrap uses its custom FocusTrap utility to trap focus to a modal. Bootstrap internally uses activate and deactivate functions to trap focus within show and hide calls. The issue is that when the focus-trap/focus-trap package is used with Bootstrap modal, both Bootstrap modal and focus-trap/focus-trap package start competing to focus their own tabable elements and the following stack overflow exception is raised within Bootstrap javascript:

Uncaught RangeError: Maximum call stack size exceeded.
    at FocusTrap._handleFocusin (focustrap.js:102:1)
    at HTMLDocument.<anonymous> (focustrap.js:72:1)
    at HTMLDocument.handler (event-handler.js:98:1)
    at tryFocus (index.js:432:1)
    at HTMLDocument.checkFocusIn (index.js:716:1)
    at FocusTrap._handleFocusin (focustrap.js:102:1)
    at HTMLDocument.<anonymous> (focustrap.js:72:1)
    at HTMLDocument.handler (event-handler.js:98:1)
    at tryFocus (index.js:432:1)
    at HTMLDocument.checkFocusIn (index.js:716:1)

The issue can be reproduced by displaying a Bootstrap modal along with an HTML fragment using focus-trap/focus-trap instance activated. The culprit is the Event Handler with event named "focusin.bs.focustrap" which gets triggered when modal tabable element is not focused. Can Bootstrap FocusTrap utility be modified in a way that focustrap can be enabled or disabled at will.

The react code to reproduce the issue is below:

import { useEffect, useRef } from "react";
import * as focusTrap from 'focus-trap';
import { Modal } from "bootstrap";

function MyReactComponent() {

  useEffect(() => {

    var element = document.getElementById("modal-id");
    var modal = Modal.getOrCreateInstance(element);
    modal.show();

    var element2 = document.getElementById("trapped");
    var instance = focusTrap.createFocusTrap(element2);
    instance.activate();
    instance.unpause();
  }, []);

  return (
    <>
      <div className="modal fade" id="modal-id" data-bs-backdrop="static" data-bs-keyboard="false"
        tabIndex={-1} aria-labelledby="staticBackdropLabel" aria-hidden="true">
        <div className="modal-dialog modal-lg">
          <div className="modal-content">
            <div className="modal-header">
              <h5 className="modal-title" id="staticBackdropLabel">TEST</h5>
              <button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div className="modal-body">
              TEST
            </div>
            <div className="modal-footer">
              <button type="button" className="btn btn-secondary">TEST</button>
            </div>
          </div>
        </div>
      </div>

      <div id="trapped">
        <button type="button" className="btn btn-secondary">TRAPPED</button>
      </div>
    </>
  );
}

export default MyReactComponent;

Reduced test cases

I cannot workout codepen but code is shared above.

The fix should make activate and deactivate functions for focus trap configurable such that we can enable disable disable focus trap.

What operating system(s) are you seeing the problem on?

Windows

What browser(s) are you seeing the problem on?

Chrome

What version of Bootstrap are you using?

5.3

SnippetsUnlimited avatar Oct 01 '23 06:10 SnippetsUnlimited