All files / src/util focustrap.js

95.34% Statements 41/43
94.73% Branches 18/19
90% Functions 9/10
95.12% Lines 39/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                              3x 3x 3x 3x 3x   3x 3x 3x   3x         3x                     112x 112x 112x 112x         112x       112x                 66x       66x 65x     66x 205x 66x   66x       28x 5x     23x 23x         205x   205x 2x     203x   203x 200x 3x 1x   2x         10x 6x     4x          
/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.2.3): util/focustrap.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */
 
import EventHandler from '../dom/event-handler'
import SelectorEngine from '../dom/selector-engine'
import Config from './config'
 
/**
 * Constants
 */
 
const NAME = 'focustrap'
const DATA_KEY = 'bs.focustrap'
const EVENT_KEY = `.${DATA_KEY}`
const EVENT_FOCUSIN = `focusin${EVENT_KEY}`
const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`
 
const TAB_KEY = 'Tab'
const TAB_NAV_FORWARD = 'forward'
const TAB_NAV_BACKWARD = 'backward'
 
const Default = {
  autofocus: true,
  trapElement: null // The element to trap focus inside of
}
 
const DefaultType = {
  autofocus: 'boolean',
  trapElement: 'element'
}
 
/**
 * Class definition
 */
 
class FocusTrap extends Config {
  constructor(config) {
    super()
    this._config = this._getConfig(config)
    this._isActive = false
    this._lastTabNavDirection = null
  }
 
  // Getters
  static get Default() {
    return Default
  }
 
  static get DefaultType() {
    return DefaultType
  }
 
  static get NAME() {
    return NAME
  }
 
  // Public
  activate() {
    Iif (this._isActive) {
      return
    }
 
    if (this._config.autofocus) {
      this._config.trapElement.focus()
    }
 
    EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop
    EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))
    EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))
 
    this._isActive = true
  }
 
  deactivate() {
    if (!this._isActive) {
      return
    }
 
    this._isActive = false
    EventHandler.off(document, EVENT_KEY)
  }
 
  // Private
  _handleFocusin(event) {
    const { trapElement } = this._config
 
    if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {
      return
    }
 
    const elements = SelectorEngine.focusableChildren(trapElement)
 
    if (elements.length === 0) {
      trapElement.focus()
    } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {
      elements[elements.length - 1].focus()
    } else {
      elements[0].focus()
    }
  }
 
  _handleKeydown(event) {
    if (event.key !== TAB_KEY) {
      return
    }
 
    this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD
  }
}
 
export default FocusTrap