<commit_before>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.0.0-beta3): dropdown.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import * as Popper from '@popperjs/core'

import {
  defineJQueryPlugin,
  getElementFromSelector,
  isDisabled,
  isElement,
  isVisible,
  isRTL,
  noop,
  typeCheckConfig
} from './util/index'
import Data from './dom/data'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component'

/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */

const NAME = 'dropdown'
const DATA_KEY = 'bs.dropdown'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'

const ESCAPE_KEY = 'Escape'
const SPACE_KEY = 'Space'
const TAB_KEY = 'Tab'
const ARROW_UP_KEY = 'ArrowUp'
const ARROW_DOWN_KEY = 'ArrowDown'
const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button

const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`)

const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}`
const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_CLICK = `click${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_DROPUP = 'dropup'
const CLASS_NAME_DROPEND = 'dropend'
const CLASS_NAME_DROPSTART = 'dropstart'
const CLASS_NAME_NAVBAR = 'navbar'

const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]'
const SELECTOR_MENU = '.dropdown-menu'
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'

const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'
const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'
const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'
const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'
const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'
const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'

const Default = {
  offset: [0, 2],
  boundary: 'clippingParents',
  reference: 'toggle',
  display: 'dynamic',
  popperConfig: null
}

const DefaultType = {
  offset: '(array|string|function)',
  boundary: '(string|element)',
  reference: '(string|element|object)',
  display: 'string',
  popperConfig: '(null|object|function)'
}

/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

class Dropdown extends BaseComponent {
  constructor(element, config) {
    super(element)

    this._popper = null
    this._config = this._getConfig(config)
    this._menu = this._getMenuElement()
    this._inNavbar = this._detectNavbar()

    this._addEventListeners()
  }

  // Getters

  static get Default() {
    return Default
  }

  static get DefaultType() {
    return DefaultType
  }

  static get DATA_KEY() {
    return DATA_KEY
  }

  // Public

  toggle() {
    if (isDisabled(this._element)) {
      return
    }

    const isActive = this._element.classList.contains(CLASS_NAME_SHOW)

    Dropdown.clearMenus()

    if (isActive) {
      return
    }

    this.show()
  }

  show() {
    if (isDisabled(this._element) || this._menu.classList.contains(CLASS_NAME_SHOW)) {
      return
    }

    const parent = Dropdown.getParentFromElement(this._element)
    const relatedTarget = {
      relatedTarget: this._element
    }

    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)

    if (showEvent.defaultPrevented) {
      return
    }

    // Totally disable Popper for Dropdowns in Navbar
    if (this._inNavbar) {
      Manipulator.setDataAttribute(this._menu, 'popper', 'none')
    } else {
      if (typeof Popper === 'undefined') {
        throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)')
      }

      let referenceElement = this._element

      if (this._config.reference === 'parent') {
        referenceElement = parent
      } else if (isElement(this._config.reference)) {
        referenceElement = this._config.reference

        // Check if it's jQuery element
        if (typeof this._config.reference.jquery !== 'undefined') {
          referenceElement = this._config.reference[0]
        }
      } else if (typeof this._config.reference === 'object') {
        referenceElement = this._config.reference
      }

      const popperConfig = this._getPopperConfig()
      const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false)

      this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)

      if (isDisplayStatic) {
        Manipulator.setDataAttribute(this._menu, 'popper', 'static')
      }
    }

    // If this is a touch-enabled device we add extra
    // empty mouseover listeners to the body's immediate children;
    // only needed because of broken event delegation on iOS
    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
    if ('ontouchstart' in document.documentElement &&
      !parent.closest(SELECTOR_NAVBAR_NAV)) {
      [].concat(...document.body.children)
        .forEach(elem => EventHandler.on(elem, 'mouseover', noop))
    }

    this._element.focus()
    this._element.setAttribute('aria-expanded', true)

    this._menu.classList.toggle(CLASS_NAME_SHOW)
    this._element.classList.toggle(CLASS_NAME_SHOW)
    EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)
  }

  hide() {
    if (isDisabled(this._element) || !this._menu.classList.contains(CLASS_NAME_SHOW)) {
      return
    }

    const relatedTarget = {
      relatedTarget: this._element
    }

    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)

    if (hideEvent.defaultPrevented) {
      return
    }

    // If this is a touch-enabled device we remove the extra
    // empty mouseover listeners we added for iOS support
    if ('ontouchstart' in document.documentElement) {
      [].concat(...document.body.children)
        .forEach(elem => EventHandler.off(elem, 'mouseover', noop))
    }

    if (this._popper) {
      this._popper.destroy()
    }

    this._menu.classList.toggle(CLASS_NAME_SHOW)
    this._element.classList.toggle(CLASS_NAME_SHOW)
    this._element.setAttribute('aria-expanded', 'false')
    Manipulator.removeDataAttribute(this._menu, 'popper')
    EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
  }

  dispose() {
    this._menu = null

    if (this._popper) {
      this._popper.destroy()
      this._popper = null
    }

    super.dispose()
  }

  update() {
    this._inNavbar = this._detectNavbar()
    if (this._popper) {
      this._popper.update()
    }
  }

  // Private

  _addEventListeners() {
    EventHandler.on(this._element, EVENT_CLICK, event => {
      event.preventDefault()
      this.toggle()
    })
  }

  _getConfig(config) {
    config = {
      ...this.constructor.Default,
      ...Manipulator.getDataAttributes(this._element),
      ...config
    }

    typeCheckConfig(NAME, config, this.constructor.DefaultType)

    if (typeof config.reference === 'object' && !isElement(config.reference) &&
      typeof config.reference.getBoundingClientRect !== 'function'
    ) {
      // Popper virtual elements require a getBoundingClientRect method
      throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
    }

    return config
  }

  _getMenuElement() {
    return SelectorEngine.next(this._element, SELECTOR_MENU)[0]
  }

  _getPlacement() {
    const parentDropdown = this._element.parentNode

    if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
      return PLACEMENT_RIGHT
    }

    if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
      return PLACEMENT_LEFT
    }

    // We need to trim the value because custom properties can also include spaces
    const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'

    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
      return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP
    }

    return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM
  }

  _detectNavbar() {
    return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null
  }

  _getOffset() {
    const { offset } = this._config

    if (typeof offset === 'string') {
      return offset.split(',').map(val => Number.parseInt(val, 10))
    }

    if (typeof offset === 'function') {
      return popperData => offset(popperData, this._element)
    }

    return offset
  }

  _getPopperConfig() {
    const defaultBsPopperConfig = {
      placement: this._getPlacement(),
      modifiers: [{
        name: 'preventOverflow',
        options: {
          boundary: this._config.boundary
        }
      },
      {
        name: 'offset',
        options: {
          offset: this._getOffset()
        }
      }]
    }

    // Disable Popper if we have a static display
    if (this._config.display === 'static') {
      defaultBsPopperConfig.modifiers = [{
        name: 'applyStyles',
        enabled: false
      }]
    }

    return {
      ...defaultBsPopperConfig,
      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
    }
  }

  _selectMenuItem(event) {
    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible)

    if (!items.length) {
      return
    }

    let index = items.indexOf(event.target)

    // Up
    if (event.key === ARROW_UP_KEY && index > 0) {
      index--
    }

    // Down
    if (event.key === ARROW_DOWN_KEY && index < items.length - 1) {
      index++
    }

    // index is -1 if the first keydown is an ArrowUp
    index = index === -1 ? 0 : index

    items[index].focus()
  }

  // Static

  static dropdownInterface(element, config) {
    let data = Data.get(element, DATA_KEY)
    const _config = typeof config === 'object' ? config : null

    if (!data) {
      data = new Dropdown(element, _config)
    }

    if (typeof config === 'string') {
      if (typeof data[config] === 'undefined') {
        throw new TypeError(`No method named "${config}"`)
      }

      data[config]()
    }
  }

  static jQueryInterface(config) {
    return this.each(function () {
      Dropdown.dropdownInterface(this, config)
    })
  }

  static clearMenus(event) {
    if (event) {
      if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {
        return
      }

      if (/input|select|textarea|form/i.test(event.target.tagName)) {
        return
      }
    }

    const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE)

    for (let i = 0, len = toggles.length; i < len; i++) {
      const context = Data.get(toggles[i], DATA_KEY)
      const relatedTarget = {
        relatedTarget: toggles[i]
      }

      if (event && event.type === 'click') {
        relatedTarget.clickEvent = event
      }

      if (!context) {
        continue
      }

      const dropdownMenu = context._menu
      if (!toggles[i].classList.contains(CLASS_NAME_SHOW)) {
        continue
      }

      if (event) {
        // Don't close the menu if the clicked element or one of its parents is the dropdown button
        if ([context._element].some(element => event.composedPath().includes(element))) {
          continue
        }

        // Tab navigation through the dropdown menu shouldn't close the menu
        if (event.type === 'keyup' && event.key === TAB_KEY && dropdownMenu.contains(event.target)) {
          continue
        }
      }

      const hideEvent = EventHandler.trigger(toggles[i], EVENT_HIDE, relatedTarget)
      if (hideEvent.defaultPrevented) {
        continue
      }

      // If this is a touch-enabled device we remove the extra
      // empty mouseover listeners we added for iOS support
      if ('ontouchstart' in document.documentElement) {
        [].concat(...document.body.children)
          .forEach(elem => EventHandler.off(elem, 'mouseover', noop))
      }

      if (context._popper) {
        context._popper.destroy()
      }

      dropdownMenu.classList.remove(CLASS_NAME_SHOW)
      toggles[i].classList.remove(CLASS_NAME_SHOW)
      toggles[i].setAttribute('aria-expanded', 'false')
      Manipulator.removeDataAttribute(dropdownMenu, 'popper')
      EventHandler.trigger(toggles[i], EVENT_HIDDEN, relatedTarget)
    }
  }

  static getParentFromElement(element) {
    return getElementFromSelector(element) || element.parentNode
  }

  static dataApiKeydownHandler(event) {
    // If not input/textarea:
    //  - And not a key in REGEXP_KEYDOWN => not a dropdown command
    // If input/textarea:
    //  - If space key => not a dropdown command
    //  - If key is other than escape
    //    - If key is not up or down => not a dropdown command
    //    - If trigger inside the menu => not a dropdown command
    if (/input|textarea/i.test(event.target.tagName) ?
      event.key === SPACE_KEY || (event.key !== ESCAPE_KEY &&
      ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) ||
        event.target.closest(SELECTOR_MENU))) :
      !REGEXP_KEYDOWN.test(event.key)) {
      return
    }

    const isActive = this.classList.contains(CLASS_NAME_SHOW)

    if (!isActive && event.key === ESCAPE_KEY) {
      return
    }

    event.preventDefault()
    event.stopPropagation()

    if (isDisabled(this)) {
      return
    }

    const getToggleButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]

    if (event.key === ESCAPE_KEY) {
      getToggleButton().focus()
      Dropdown.clearMenus()
      return
    }

    if (!isActive && (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY)) {
      getToggleButton().click()
      return
    }

    if (!isActive || event.key === SPACE_KEY) {
      Dropdown.clearMenus()
      return
    }

    Dropdown.getInstance(getToggleButton())._selectMenuItem(event)
  }
}

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */

EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  event.preventDefault()
  Dropdown.dropdownInterface(this)
})

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 * add .Dropdown to jQuery only if jQuery is present
 */

defineJQueryPlugin(NAME, Dropdown)

export default Dropdown
<commit_msg>Refactor dropdown's hide functionality<commit_after>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.0.0-beta3): dropdown.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import * as Popper from '@popperjs/core'

import {
  defineJQueryPlugin,
  getElementFromSelector,
  isDisabled,
  isElement,
  isVisible,
  isRTL,
  noop,
  typeCheckConfig
} from './util/index'
import Data from './dom/data'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component'

/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */

const NAME = 'dropdown'
const DATA_KEY = 'bs.dropdown'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'

const ESCAPE_KEY = 'Escape'
const SPACE_KEY = 'Space'
const TAB_KEY = 'Tab'
const ARROW_UP_KEY = 'ArrowUp'
const ARROW_DOWN_KEY = 'ArrowDown'
const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button

const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`)

const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}`
const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_CLICK = `click${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_DROPUP = 'dropup'
const CLASS_NAME_DROPEND = 'dropend'
const CLASS_NAME_DROPSTART = 'dropstart'
const CLASS_NAME_NAVBAR = 'navbar'

const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]'
const SELECTOR_MENU = '.dropdown-menu'
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'

const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'
const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'
const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'
const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'
const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'
const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'

const Default = {
  offset: [0, 2],
  boundary: 'clippingParents',
  reference: 'toggle',
  display: 'dynamic',
  popperConfig: null
}

const DefaultType = {
  offset: '(array|string|function)',
  boundary: '(string|element)',
  reference: '(string|element|object)',
  display: 'string',
  popperConfig: '(null|object|function)'
}

/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

class Dropdown extends BaseComponent {
  constructor(element, config) {
    super(element)

    this._popper = null
    this._config = this._getConfig(config)
    this._menu = this._getMenuElement()
    this._inNavbar = this._detectNavbar()

    this._addEventListeners()
  }

  // Getters

  static get Default() {
    return Default
  }

  static get DefaultType() {
    return DefaultType
  }

  static get DATA_KEY() {
    return DATA_KEY
  }

  // Public

  toggle() {
    if (isDisabled(this._element)) {
      return
    }

    const isActive = this._element.classList.contains(CLASS_NAME_SHOW)

    Dropdown.clearMenus()

    if (isActive) {
      return
    }

    this.show()
  }

  show() {
    if (isDisabled(this._element) || this._menu.classList.contains(CLASS_NAME_SHOW)) {
      return
    }

    const parent = Dropdown.getParentFromElement(this._element)
    const relatedTarget = {
      relatedTarget: this._element
    }

    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)

    if (showEvent.defaultPrevented) {
      return
    }

    // Totally disable Popper for Dropdowns in Navbar
    if (this._inNavbar) {
      Manipulator.setDataAttribute(this._menu, 'popper', 'none')
    } else {
      if (typeof Popper === 'undefined') {
        throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)')
      }

      let referenceElement = this._element

      if (this._config.reference === 'parent') {
        referenceElement = parent
      } else if (isElement(this._config.reference)) {
        referenceElement = this._config.reference

        // Check if it's jQuery element
        if (typeof this._config.reference.jquery !== 'undefined') {
          referenceElement = this._config.reference[0]
        }
      } else if (typeof this._config.reference === 'object') {
        referenceElement = this._config.reference
      }

      const popperConfig = this._getPopperConfig()
      const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false)

      this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)

      if (isDisplayStatic) {
        Manipulator.setDataAttribute(this._menu, 'popper', 'static')
      }
    }

    // If this is a touch-enabled device we add extra
    // empty mouseover listeners to the body's immediate children;
    // only needed because of broken event delegation on iOS
    // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
    if ('ontouchstart' in document.documentElement &&
      !parent.closest(SELECTOR_NAVBAR_NAV)) {
      [].concat(...document.body.children)
        .forEach(elem => EventHandler.on(elem, 'mouseover', noop))
    }

    this._element.focus()
    this._element.setAttribute('aria-expanded', true)

    this._menu.classList.toggle(CLASS_NAME_SHOW)
    this._element.classList.toggle(CLASS_NAME_SHOW)
    EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)
  }

  hide() {
    if (isDisabled(this._element) || !this._menu.classList.contains(CLASS_NAME_SHOW)) {
      return
    }

    const relatedTarget = {
      relatedTarget: this._element
    }

    this._completeHide(relatedTarget)
  }

  dispose() {
    this._menu = null

    if (this._popper) {
      this._popper.destroy()
      this._popper = null
    }

    super.dispose()
  }

  update() {
    this._inNavbar = this._detectNavbar()
    if (this._popper) {
      this._popper.update()
    }
  }

  // Private

  _addEventListeners() {
    EventHandler.on(this._element, EVENT_CLICK, event => {
      event.preventDefault()
      this.toggle()
    })
  }

  _completeHide(relatedTarget) {
    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)
    if (hideEvent.defaultPrevented) {
      return
    }

    // If this is a touch-enabled device we remove the extra
    // empty mouseover listeners we added for iOS support
    if ('ontouchstart' in document.documentElement) {
      [].concat(...document.body.children)
        .forEach(elem => EventHandler.off(elem, 'mouseover', noop))
    }

    if (this._popper) {
      this._popper.destroy()
    }

    this._menu.classList.toggle(CLASS_NAME_SHOW)
    this._element.classList.toggle(CLASS_NAME_SHOW)
    this._element.setAttribute('aria-expanded', 'false')
    Manipulator.removeDataAttribute(this._menu, 'popper')
    EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
  }

  _getConfig(config) {
    config = {
      ...this.constructor.Default,
      ...Manipulator.getDataAttributes(this._element),
      ...config
    }

    typeCheckConfig(NAME, config, this.constructor.DefaultType)

    if (typeof config.reference === 'object' && !isElement(config.reference) &&
      typeof config.reference.getBoundingClientRect !== 'function'
    ) {
      // Popper virtual elements require a getBoundingClientRect method
      throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
    }

    return config
  }

  _getMenuElement() {
    return SelectorEngine.next(this._element, SELECTOR_MENU)[0]
  }

  _getPlacement() {
    const parentDropdown = this._element.parentNode

    if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
      return PLACEMENT_RIGHT
    }

    if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
      return PLACEMENT_LEFT
    }

    // We need to trim the value because custom properties can also include spaces
    const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'

    if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
      return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP
    }

    return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM
  }

  _detectNavbar() {
    return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null
  }

  _getOffset() {
    const { offset } = this._config

    if (typeof offset === 'string') {
      return offset.split(',').map(val => Number.parseInt(val, 10))
    }

    if (typeof offset === 'function') {
      return popperData => offset(popperData, this._element)
    }

    return offset
  }

  _getPopperConfig() {
    const defaultBsPopperConfig = {
      placement: this._getPlacement(),
      modifiers: [{
        name: 'preventOverflow',
        options: {
          boundary: this._config.boundary
        }
      },
      {
        name: 'offset',
        options: {
          offset: this._getOffset()
        }
      }]
    }

    // Disable Popper if we have a static display
    if (this._config.display === 'static') {
      defaultBsPopperConfig.modifiers = [{
        name: 'applyStyles',
        enabled: false
      }]
    }

    return {
      ...defaultBsPopperConfig,
      ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
    }
  }

  _selectMenuItem(event) {
    const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible)

    if (!items.length) {
      return
    }

    let index = items.indexOf(event.target)

    // Up
    if (event.key === ARROW_UP_KEY && index > 0) {
      index--
    }

    // Down
    if (event.key === ARROW_DOWN_KEY && index < items.length - 1) {
      index++
    }

    // index is -1 if the first keydown is an ArrowUp
    index = index === -1 ? 0 : index

    items[index].focus()
  }

  // Static

  static dropdownInterface(element, config) {
    let data = Data.get(element, DATA_KEY)
    const _config = typeof config === 'object' ? config : null

    if (!data) {
      data = new Dropdown(element, _config)
    }

    if (typeof config === 'string') {
      if (typeof data[config] === 'undefined') {
        throw new TypeError(`No method named "${config}"`)
      }

      data[config]()
    }
  }

  static jQueryInterface(config) {
    return this.each(function () {
      Dropdown.dropdownInterface(this, config)
    })
  }

  static clearMenus(event) {
    if (event) {
      if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {
        return
      }

      if (/input|select|textarea|form/i.test(event.target.tagName)) {
        return
      }
    }

    const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE)

    for (let i = 0, len = toggles.length; i < len; i++) {
      const context = Data.get(toggles[i], DATA_KEY)
      if (!context) {
        continue
      }

      const dropdownMenu = context._menu
      if (!toggles[i].classList.contains(CLASS_NAME_SHOW)) {
        continue
      }

      const relatedTarget = {
        relatedTarget: toggles[i]
      }

      if (event) {
        // Don't close the menu if the clicked element or one of its parents is the dropdown button
        if ([context._element].some(element => event.composedPath().includes(element))) {
          continue
        }

        // Tab navigation through the dropdown menu shouldn't close the menu
        if (event.type === 'keyup' && event.key === TAB_KEY && dropdownMenu.contains(event.target)) {
          continue
        }

        if (event.type === 'click') {
          relatedTarget.clickEvent = event
        }
      }

      context._completeHide(relatedTarget)
    }
  }

  static getParentFromElement(element) {
    return getElementFromSelector(element) || element.parentNode
  }

  static dataApiKeydownHandler(event) {
    // If not input/textarea:
    //  - And not a key in REGEXP_KEYDOWN => not a dropdown command
    // If input/textarea:
    //  - If space key => not a dropdown command
    //  - If key is other than escape
    //    - If key is not up or down => not a dropdown command
    //    - If trigger inside the menu => not a dropdown command
    if (/input|textarea/i.test(event.target.tagName) ?
      event.key === SPACE_KEY || (event.key !== ESCAPE_KEY &&
      ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) ||
        event.target.closest(SELECTOR_MENU))) :
      !REGEXP_KEYDOWN.test(event.key)) {
      return
    }

    const isActive = this.classList.contains(CLASS_NAME_SHOW)

    if (!isActive && event.key === ESCAPE_KEY) {
      return
    }

    event.preventDefault()
    event.stopPropagation()

    if (isDisabled(this)) {
      return
    }

    const getToggleButton = () => this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]

    if (event.key === ESCAPE_KEY) {
      getToggleButton().focus()
      Dropdown.clearMenus()
      return
    }

    if (!isActive && (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY)) {
      getToggleButton().click()
      return
    }

    if (!isActive || event.key === SPACE_KEY) {
      Dropdown.clearMenus()
      return
    }

    Dropdown.getInstance(getToggleButton())._selectMenuItem(event)
  }
}

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */

EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)
EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  event.preventDefault()
  Dropdown.dropdownInterface(this)
})

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 * add .Dropdown to jQuery only if jQuery is present
 */

defineJQueryPlugin(NAME, Dropdown)

export default Dropdown
<|endoftext|>


<commit_before>/* global Popper */

import Util from './util'

/**
 * --------------------------------------------------------------------------
 * Bootstrap (v4.0.0-alpha.6): dropdown.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

const Dropdown = (($) => {

  /**
   * Check for Popper dependency
   * Popper - https://popper.js.org
   */
  if (typeof Popper === 'undefined') {
    throw new Error('Bootstrap dropdown require Popper.js (https://popper.js.org)')
  }

  /**
   * ------------------------------------------------------------------------
   * Constants
   * ------------------------------------------------------------------------
   */

  const NAME                     = 'dropdown'
  const VERSION                  = '4.0.0-alpha.6'
  const DATA_KEY                 = 'bs.dropdown'
  const EVENT_KEY                = `.${DATA_KEY}`
  const DATA_API_KEY             = '.data-api'
  const JQUERY_NO_CONFLICT       = $.fn[NAME]
  const ESCAPE_KEYCODE           = 27 // KeyboardEvent.which value for Escape (Esc) key
  const SPACE_KEYCODE            = 32 // KeyboardEvent.which value for space key
  const TAB_KEYCODE              = 9 // KeyboardEvent.which value for tab key
  const ARROW_UP_KEYCODE         = 38 // KeyboardEvent.which value for up arrow key
  const ARROW_DOWN_KEYCODE       = 40 // KeyboardEvent.which value for down arrow key
  const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
  const REGEXP_KEYDOWN           = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)

  const Event = {
    HIDE             : `hide${EVENT_KEY}`,
    HIDDEN           : `hidden${EVENT_KEY}`,
    SHOW             : `show${EVENT_KEY}`,
    SHOWN            : `shown${EVENT_KEY}`,
    CLICK            : `click${EVENT_KEY}`,
    CLICK_DATA_API   : `click${EVENT_KEY}${DATA_API_KEY}`,
    KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`,
    KEYUP_DATA_API   : `keyup${EVENT_KEY}${DATA_API_KEY}`
  }

  const ClassName = {
    DISABLED : 'disabled',
    SHOW     : 'show'
  }

  const Selector = {
    DATA_TOGGLE   : '[data-toggle="dropdown"]',
    FORM_CHILD    : '.dropdown form',
    MENU          : '.dropdown-menu',
    NAVBAR_NAV    : '.navbar-nav',
    VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled)'
  }

  const AttachmentMap = {
    TOP    : 'top-start',
    BOTTOM : 'bottom-start'
  }

  const Default = {
    placement   : AttachmentMap.BOTTOM,
    offset      : 0,
    flip        : true
  }

  const DefaultType = {
    placement   : 'string',
    offset      : '(number|string)',
    flip        : 'boolean'
  }

  /**
   * ------------------------------------------------------------------------
   * Class Definition
   * ------------------------------------------------------------------------
   */

  class Dropdown {

    constructor(element, config) {
      this._element = element
      this._popper  = null
      this._config = this._getConfig(config)
      this._menu = this._getMenuElement()

      this._addEventListeners()
    }

    // getters

    static get VERSION() {
      return VERSION
    }

    static get Default() {
      return Default
    }

    static get DefaultType() {
      return DefaultType
    }

    // public

    toggle() {
      let context = $(this).data(DATA_KEY)
      if (!context) {
        context = new Dropdown(this)
        $(this).data(DATA_KEY, context)
      }

      if (context.disabled || $(this).hasClass(ClassName.DISABLED)) {
        return false
      }

      const parent   = Dropdown._getParentFromElement(this)
      const isActive = $(context._menu).hasClass(ClassName.SHOW)

      Dropdown._clearMenus()

      if (isActive) {
        return false
      }

      const relatedTarget = {
        relatedTarget : this
      }
      const showEvent = $.Event(Event.SHOW, relatedTarget)

      $(parent).trigger(showEvent)

      if (showEvent.isDefaultPrevented()) {
        return false
      }

      // Handle dropup
      const dropdownPlacement = $(this).parent().hasClass('dropup') ? AttachmentMap.TOP : context._config.placement
      this._popper = new Popper(this, context._menu, {
        placement : dropdownPlacement,
        modifiers : {
          offset : {
            offset : context._config.offset
          },
          flip : {
            enabled : context._config.flip,
            behavior : [AttachmentMap.TOP, AttachmentMap.BOTTOM]
          }
        }
      })

      // if this is a touch-enabled device we add extra
      // empty mouseover listeners to the body's immediate children;
      // only needed because of broken event delegation on iOS
      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
      if ('ontouchstart' in document.documentElement &&
         !$(parent).closest(Selector.NAVBAR_NAV).length) {
        $('body').children().on('mouseover', null, $.noop)
      }

      this.focus()
      this.setAttribute('aria-expanded', true)

      $(context._menu).toggleClass(ClassName.SHOW)
      $(parent)
        .toggleClass(ClassName.SHOW)
        .trigger($.Event(Event.SHOWN, relatedTarget))

      return false
    }

    dispose() {
      $.removeData(this._element, DATA_KEY)
      $(this._element).off(EVENT_KEY)
      this._element = null
      this._menu = null
      if (this._popper !== null) {
        this._popper.destroy()
      }
      this._popper = null
    }

    update() {
      if (this._popper !== null) {
        this._popper.scheduleUpdate()
      }
    }

    // private

    _addEventListeners() {
      $(this._element).on(Event.CLICK, this.toggle)
    }

    _getConfig(config) {
      const elementData = $(this._element).data()
      if (elementData.placement !== undefined) {
        elementData.placement = AttachmentMap[elementData.placement.toUpperCase()]
      }

      config = $.extend(
        {},
        this.constructor.Default,
        $(this._element).data(),
        config
      )

      Util.typeCheckConfig(
        NAME,
        config,
        this.constructor.DefaultType
      )

      return config
    }

    _getMenuElement() {
      if (!this._menu) {
        const parent = Dropdown._getParentFromElement(this._element)
        this._menu = $(parent).find(Selector.MENU)[0]
      }
      return this._menu
    }

    // static

    static _jQueryInterface(config) {
      return this.each(function () {
        let data = $(this).data(DATA_KEY)
        const _config = typeof config === 'object' ? config : null

        if (!data) {
          data = new Dropdown(this, _config)
          $(this).data(DATA_KEY, data)
        }

        if (typeof config === 'string') {
          if (data[config] === undefined) {
            throw new Error(`No method named "${config}"`)
          }
          data[config].call(this)
        }
      })
    }

    static _clearMenus(event) {
      if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||
        event.type === 'keyup' && event.which !== TAB_KEYCODE)) {
        return
      }

      const toggles = $.makeArray($(Selector.DATA_TOGGLE))
      for (let i = 0; i < toggles.length; i++) {
        const parent        = Dropdown._getParentFromElement(toggles[i])
        const context         = $(toggles[i]).data(DATA_KEY)
        const relatedTarget = {
          relatedTarget : toggles[i]
        }

        if (!context) {
          continue
        }

        const dropdownMenu = context._menu
        if (!$(parent).hasClass(ClassName.SHOW)) {
          continue
        }

        if (event && (event.type === 'click' &&
            /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE)
            && $.contains(parent, event.target)) {
          continue
        }

        const hideEvent = $.Event(Event.HIDE, relatedTarget)
        $(parent).trigger(hideEvent)
        if (hideEvent.isDefaultPrevented()) {
          continue
        }

        // if this is a touch-enabled device we remove the extra
        // empty mouseover listeners we added for iOS support
        if ('ontouchstart' in document.documentElement) {
          $('body').children().off('mouseover', null, $.noop)
        }

        toggles[i].setAttribute('aria-expanded', 'false')

        $(dropdownMenu).removeClass(ClassName.SHOW)
        $(parent)
          .removeClass(ClassName.SHOW)
          .trigger($.Event(Event.HIDDEN, relatedTarget))
      }
    }

    static _getParentFromElement(element) {
      let parent
      const selector = Util.getSelectorFromElement(element)

      if (selector) {
        parent = $(selector)[0]
      }

      return parent || element.parentNode
    }

    static _dataApiKeydownHandler(event) {
      if (!REGEXP_KEYDOWN.test(event.which) || /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE ||
         /input|textarea/i.test(event.target.tagName)) {
        return
      }

      event.preventDefault()
      event.stopPropagation()

      if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
        return
      }

      const parent   = Dropdown._getParentFromElement(this)
      const isActive = $(parent).hasClass(ClassName.SHOW)

      if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) ||
           isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {

        if (event.which === ESCAPE_KEYCODE) {
          const toggle = $(parent).find(Selector.DATA_TOGGLE)[0]
          $(toggle).trigger('focus')
        }

        $(this).trigger('click')
        return
      }

      const items = $(parent).find(Selector.VISIBLE_ITEMS).get()

      if (!items.length) {
        return
      }

      let index = items.indexOf(event.target)

      if (event.which === ARROW_UP_KEYCODE && index > 0) { // up
        index--
      }

      if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // down
        index++
      }

      if (index < 0) {
        index = 0
      }

      items[index].focus()
    }

  }

  /**
   * ------------------------------------------------------------------------
   * Data Api implementation
   * ------------------------------------------------------------------------
   */

  $(document)
    .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE,  Dropdown._dataApiKeydownHandler)
    .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler)
    .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus)
    .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle)
    .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
      e.stopPropagation()
    })

  /**
   * ------------------------------------------------------------------------
   * jQuery
   * ------------------------------------------------------------------------
   */

  $.fn[NAME]             = Dropdown._jQueryInterface
  $.fn[NAME].Constructor = Dropdown
  $.fn[NAME].noConflict  = function () {
    $.fn[NAME] = JQUERY_NO_CONFLICT
    return Dropdown._jQueryInterface
  }

  return Dropdown

})(jQuery)

export default Dropdown
<commit_msg>Use _jQueryInterface for Dropdown to call toggle method<commit_after>/* global Popper */

import Util from './util'

/**
 * --------------------------------------------------------------------------
 * Bootstrap (v4.0.0-alpha.6): dropdown.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

const Dropdown = (($) => {

  /**
   * Check for Popper dependency
   * Popper - https://popper.js.org
   */
  if (typeof Popper === 'undefined') {
    throw new Error('Bootstrap dropdown require Popper.js (https://popper.js.org)')
  }

  /**
   * ------------------------------------------------------------------------
   * Constants
   * ------------------------------------------------------------------------
   */

  const NAME                     = 'dropdown'
  const VERSION                  = '4.0.0-alpha.6'
  const DATA_KEY                 = 'bs.dropdown'
  const EVENT_KEY                = `.${DATA_KEY}`
  const DATA_API_KEY             = '.data-api'
  const JQUERY_NO_CONFLICT       = $.fn[NAME]
  const ESCAPE_KEYCODE           = 27 // KeyboardEvent.which value for Escape (Esc) key
  const SPACE_KEYCODE            = 32 // KeyboardEvent.which value for space key
  const TAB_KEYCODE              = 9 // KeyboardEvent.which value for tab key
  const ARROW_UP_KEYCODE         = 38 // KeyboardEvent.which value for up arrow key
  const ARROW_DOWN_KEYCODE       = 40 // KeyboardEvent.which value for down arrow key
  const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
  const REGEXP_KEYDOWN           = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)

  const Event = {
    HIDE             : `hide${EVENT_KEY}`,
    HIDDEN           : `hidden${EVENT_KEY}`,
    SHOW             : `show${EVENT_KEY}`,
    SHOWN            : `shown${EVENT_KEY}`,
    CLICK            : `click${EVENT_KEY}`,
    CLICK_DATA_API   : `click${EVENT_KEY}${DATA_API_KEY}`,
    KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`,
    KEYUP_DATA_API   : `keyup${EVENT_KEY}${DATA_API_KEY}`
  }

  const ClassName = {
    DISABLED : 'disabled',
    SHOW     : 'show'
  }

  const Selector = {
    DATA_TOGGLE   : '[data-toggle="dropdown"]',
    FORM_CHILD    : '.dropdown form',
    MENU          : '.dropdown-menu',
    NAVBAR_NAV    : '.navbar-nav',
    VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled)'
  }

  const AttachmentMap = {
    TOP    : 'top-start',
    BOTTOM : 'bottom-start'
  }

  const Default = {
    placement   : AttachmentMap.BOTTOM,
    offset      : 0,
    flip        : true
  }

  const DefaultType = {
    placement   : 'string',
    offset      : '(number|string)',
    flip        : 'boolean'
  }

  /**
   * ------------------------------------------------------------------------
   * Class Definition
   * ------------------------------------------------------------------------
   */

  class Dropdown {

    constructor(element, config) {
      this._element = element
      this._popper  = null
      this._config = this._getConfig(config)
      this._menu = this._getMenuElement()

      this._addEventListeners()
    }

    // getters

    static get VERSION() {
      return VERSION
    }

    static get Default() {
      return Default
    }

    static get DefaultType() {
      return DefaultType
    }

    // public

    toggle() {
      if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED)) {
        return
      }

      const parent   = Dropdown._getParentFromElement(this._element)
      const isActive = $(this._menu).hasClass(ClassName.SHOW)

      Dropdown._clearMenus()

      if (isActive) {
        return
      }

      const relatedTarget = {
        relatedTarget : this._element
      }
      const showEvent = $.Event(Event.SHOW, relatedTarget)

      $(parent).trigger(showEvent)

      if (showEvent.isDefaultPrevented()) {
        return
      }

      // Handle dropup
      const dropdownPlacement = $(this._element).parent().hasClass('dropup') ? AttachmentMap.TOP : this._config.placement
      this._popper = new Popper(this._element, this._menu, {
        placement : dropdownPlacement,
        modifiers : {
          offset : {
            offset : this._config.offset
          },
          flip : {
            enabled : this._config.flip
          }
        }
      })

      // if this is a touch-enabled device we add extra
      // empty mouseover listeners to the body's immediate children;
      // only needed because of broken event delegation on iOS
      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
      if ('ontouchstart' in document.documentElement &&
         !$(parent).closest(Selector.NAVBAR_NAV).length) {
        $('body').children().on('mouseover', null, $.noop)
      }

      this._element.focus()
      this._element.setAttribute('aria-expanded', true)

      $(this._menu).toggleClass(ClassName.SHOW)
      $(parent)
        .toggleClass(ClassName.SHOW)
        .trigger($.Event(Event.SHOWN, relatedTarget))
    }

    dispose() {
      $.removeData(this._element, DATA_KEY)
      $(this._element).off(EVENT_KEY)
      this._element = null
      this._menu = null
      if (this._popper !== null) {
        this._popper.destroy()
      }
      this._popper = null
    }

    update() {
      if (this._popper !== null) {
        this._popper.scheduleUpdate()
      }
    }

    // private

    _addEventListeners() {
      $(this._element).on(Event.CLICK, (event) => {
        event.preventDefault()
        event.stopPropagation()
        this.toggle()
      })
    }

    _getConfig(config) {
      const elementData = $(this._element).data()
      if (elementData.placement !== undefined) {
        elementData.placement = AttachmentMap[elementData.placement.toUpperCase()]
      }

      config = $.extend(
        {},
        this.constructor.Default,
        $(this._element).data(),
        config
      )

      Util.typeCheckConfig(
        NAME,
        config,
        this.constructor.DefaultType
      )

      return config
    }

    _getMenuElement() {
      if (!this._menu) {
        const parent = Dropdown._getParentFromElement(this._element)
        this._menu = $(parent).find(Selector.MENU)[0]
      }
      return this._menu
    }

    // static

    static _jQueryInterface(config) {
      return this.each(function () {
        let data = $(this).data(DATA_KEY)
        const _config = typeof config === 'object' ? config : null

        if (!data) {
          data = new Dropdown(this, _config)
          $(this).data(DATA_KEY, data)
        }

        if (typeof config === 'string') {
          if (data[config] === undefined) {
            throw new Error(`No method named "${config}"`)
          }
          data[config]()
        }
      })
    }

    static _clearMenus(event) {
      if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||
        event.type === 'keyup' && event.which !== TAB_KEYCODE)) {
        return
      }

      const toggles = $.makeArray($(Selector.DATA_TOGGLE))
      for (let i = 0; i < toggles.length; i++) {
        const parent        = Dropdown._getParentFromElement(toggles[i])
        const context       = $(toggles[i]).data(DATA_KEY)
        const relatedTarget = {
          relatedTarget : toggles[i]
        }

        if (!context) {
          continue
        }

        const dropdownMenu = context._menu
        if (!$(parent).hasClass(ClassName.SHOW)) {
          continue
        }

        if (event && (event.type === 'click' &&
            /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE)
            && $.contains(parent, event.target)) {
          continue
        }

        const hideEvent = $.Event(Event.HIDE, relatedTarget)
        $(parent).trigger(hideEvent)
        if (hideEvent.isDefaultPrevented()) {
          continue
        }

        // if this is a touch-enabled device we remove the extra
        // empty mouseover listeners we added for iOS support
        if ('ontouchstart' in document.documentElement) {
          $('body').children().off('mouseover', null, $.noop)
        }

        toggles[i].setAttribute('aria-expanded', 'false')

        $(dropdownMenu).removeClass(ClassName.SHOW)
        $(parent)
          .removeClass(ClassName.SHOW)
          .trigger($.Event(Event.HIDDEN, relatedTarget))
      }
    }

    static _getParentFromElement(element) {
      let parent
      const selector = Util.getSelectorFromElement(element)

      if (selector) {
        parent = $(selector)[0]
      }

      return parent || element.parentNode
    }

    static _dataApiKeydownHandler(event) {
      if (!REGEXP_KEYDOWN.test(event.which) || /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE ||
         /input|textarea/i.test(event.target.tagName)) {
        return
      }

      event.preventDefault()
      event.stopPropagation()

      if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
        return
      }

      const parent   = Dropdown._getParentFromElement(this)
      const isActive = $(parent).hasClass(ClassName.SHOW)

      if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) ||
           isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {

        if (event.which === ESCAPE_KEYCODE) {
          const toggle = $(parent).find(Selector.DATA_TOGGLE)[0]
          $(toggle).trigger('focus')
        }

        $(this).trigger('click')
        return
      }

      const items = $(parent).find(Selector.VISIBLE_ITEMS).get()

      if (!items.length) {
        return
      }

      let index = items.indexOf(event.target)

      if (event.which === ARROW_UP_KEYCODE && index > 0) { // up
        index--
      }

      if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // down
        index++
      }

      if (index < 0) {
        index = 0
      }

      items[index].focus()
    }

  }

  /**
   * ------------------------------------------------------------------------
   * Data Api implementation
   * ------------------------------------------------------------------------
   */

  $(document)
    .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE,  Dropdown._dataApiKeydownHandler)
    .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler)
    .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus)
    .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
      event.preventDefault()
      event.stopPropagation()
      Dropdown._jQueryInterface.call($(this), 'toggle')
    })
    .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
      e.stopPropagation()
    })

  /**
   * ------------------------------------------------------------------------
   * jQuery
   * ------------------------------------------------------------------------
   */

  $.fn[NAME]             = Dropdown._jQueryInterface
  $.fn[NAME].Constructor = Dropdown
  $.fn[NAME].noConflict  = function () {
    $.fn[NAME] = JQUERY_NO_CONFLICT
    return Dropdown._jQueryInterface
  }

  return Dropdown

})(jQuery)

export default Dropdown
<|endoftext|>


<commit_before>if (typeof dc === 'undefined')
    dc = {};
/* @param parent: the ID of the DOM element where the gauge will be hooked up into
 @param chartGroup: the group to which the gauge belongs to (determines when refreshing is done)
 */
dc.fgraph = function(parent) {
    var _fgraph = {}, // main object
            _parentID = parent, // keep track of the parent of an object
            _margins = {
                top: 0,
                right: 0,
                bottom: 0,
                left: 0
            },
    _nodeData, // current data used by node
            _edgeData, // current data used by edge
            _fCharge = -120, // charge parameter
            _nodeColorType = "cont", // is the coloring measure discrete? "cont" or "disc"
            _nodeColorScaleD = d3.scale.category20(), // colors to be used for nodes when nodeAccessor is discret
            _nodeMinColor = "blue",
            _nodeMaxColor = "red",
            _nodeColorScaleC = d3.scale.linear() // same for continous
            .range([_nodeMinColor, _nodeMaxColor])
            .interpolate(d3.interpolateHcl),
            _edgeColors = d3.scale.category20(), // colors to be used for edges
            _nodeLabelAccessor = function(d) {
                return "";
            },
            _nodeColorAccessor = function(d) {
                return 1;
            }, // node color accessor
            _edgeColorAccessor = function(d) {
                return 1;
            },
            _minNodeSize = 10,
            _maxNodeSize = 30,
            _nodeSizeAccessor = function(d) {
                return _minNodeSize;
            },
            _nodeSizeScale = d3.scale.linear()
            .domain([_minNodeSize, _minNodeSize])
            .range([_minNodeSize, _maxNodeSize]),
            _edgeWidthAccessor = function(d) { // edge color accessor
                return 2;
            }, // controlls the width of the link
            _weightAccessor = function(d) {
                return 2;
            }, // controlls the weight of the node
            _edgeWidthScale = d3.scale.linear().range([1, 10]),
            _linkDistance = 30, // link distance for force
            _linkStrength,
            _force, // force layout
            _svg, // svg element
            _link, // links
            _node, // nodes
            _transitionDuration = 750,
            _graph = {}, // data to be displayed
            _width = 960,
            _height = 600,
            _displayNames = true, // should we display names
            _dblclickHandler = function(d) { // handler for what happens under a dbouleclick
                // do nothing
            }
    _terminator; // not used, just to terminate list

    // partial redraw of node size
    function changeNodeSize() {
        // recompute the normalization
        _nodeSizeScale.domain(
                d3.extent(_nodeData,
                        function(d) {
                            return _nodeSizeAccessor(d.data);
                        }));
        _node.selectAll("circle")
                .attr("r", function(d) {
                    return _nodeSizeScale(_nodeSizeAccessor(d.data));
                });
    }

    // partial redraw for node colors
    function changeNodeColor() {
        if (_nodeColorType === "cont") {
            _nodeColorScaleC.domain(
                    d3.extent(_nodeData,
                            function(d) {
                                return _nodeColorAccessor(d.data);
                            }))
            _node.selectAll("circle")
                    .style("fill", function(d) {
                        return _nodeColorScaleC(_nodeColorAccessor(d.data));
                    });
        } else {
            _node.selectAll("circle")
                    .style("fill", function(d) {
                        return _nodeColorScaleD(_nodeColorAccessor(d.data));
                    });

        }
    }

    // we can initialize the links/nodes only when we have data
    function changeData(graph) {

        if (!_force)
            _force = d3.layout.force()
                    .charge(_fCharge)
                    .linkDistance(_linkDistance)
                    .size([_width, _height]);

        function zoom() {
            _svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        }

        if (!_svg) {
            _svg = d3.select(_parentID)
                    .append("svg")
                    .attr("width", _width)
                    .attr("height", _height);

            _svg.append("rect")
                    .attr("width", _width)
                    .attr("height", _height)
                    .style("fill", "none")
                    .style("pointer-events", "all")
                    .call(d3.behavior.zoom().scaleExtent([.0625, 8]).on("zoom", zoom))

            _svg = _svg.append("g")
                    ;
        }

        // wrap the info in graph so that force layout is happy
        _nodeData = graph.nodes.map(function(d, i) {
            d.index = i;
            return {
                data: d,
                weight: _weightAccessor(d)
            };
        });
        //console.log(_nodeData);
        _edgeData = graph.edges.map(function(d, i) {
            d.index = i;
            return {
                //  source: d.source,
                //  target: d.target,
                source: _nodeData[d.source.index],
                target: _nodeData[d.target.index],
                data: d.data,
                //value: d.value
            };
        });
        //console.log(_edgeData);

        _force
                .nodes(_nodeData)
                .links(_edgeData)
                .start();

        _link = _svg.selectAll(".link")
                .data(_edgeData)
                .enter().append("line")
                .attr("class", "link")
                .style("stroke", function(d) {
                    return _edgeColors(d.data);
                })
                .style("stroke-width", function(d) {
                    return _edgeWidthAccessor(d.data);
                });

        _node = _svg.selectAll(".node")
                .data(_nodeData)
                .enter().append("g")
                .attr("class", "node")
                .call(_force.drag)
                ;

        _node.append("circle")
                .attr("class", "fgraph-circle")
                .on("mousedown", function(d) {
                    d.fixed = !d.fixed;
                    d3.select(this).classed('sticky', d.fixed);
                })
                .on("dblclick", function(d){ _dblclickHandler(d); });
        changeNodeSize();
        changeNodeColor();

        _node.append("title")
                .text(function(d) {
                    return d.name;
                });

        _node.append("text")
                .attr("class", "fgraph-text")
                .text(function(d) {
                    return _nodeLabelAccessor(d);
                });

        _force.on("tick", function() {
            _link.attr("x1", function(d) {
                return d.source.x;
            })
                    .attr("y1", function(d) {
                        return d.source.y;
                    })
                    .attr("x2", function(d) {
                        return d.target.x;
                    })
                    .attr("y2", function(d) {
                        return d.target.y;
                    });

            _node.attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            });
        });
    }

    _fgraph.init = function(parent, data, width, height) {
        _parentID = parent;
        _fgraph.graphView(data)
                .resize(width, height);
        return _fgraph;
    }
    _fgraph.destroy = function()
    {
        _svg.remove();
    }
    _fgraph.doRender = function() {
        // delete old content if present
        // d3.select(_parentID).select("svg").remove();

        _fgraph.doRedraw();

        return _fgraph;
    }

    _fgraph.updateEgdeStyle = function(style, fn) {
        _link.style(style, fn);
    }
    _fgraph.updateNodeStyle = function(style, fn) {
        _node.selectAll("circle").style(style, fn);
    }
    _fgraph.updateNodeAttr = function(style, fn) {
        _node.selectAll("circle").attr(style, fn);
    }

    _fgraph.doRedraw = function() {
        //need to include jquery for following line to work
        var parent = $(_parentID); //$(_parentID+"-inner").parent();
        _width = parent.width() - _margins.left - _margins.right;
        _height = parent.height() - _margins.top - _margins.bottom;
    }

    // should we display names for nodes?
    _fgraph.displayNames = function(_) {
        if (!arguments.length)
            return _displayNames;
        _displayNames = _;

        if (_displayNames)
            _node.append("text")
                    .text(function(d) {
                        return d.name;
                    });
        else {
            _node.selectAll("text").remove();
        }

        // _fgraph.doRedraw();
        return _fgraph;
    }

    // Change/get distance parametter of force
    _fgraph.distance = function(_) {
        if (!arguments.length)
            return _linkDistance;
        _linkDistance = _;
        _force.linkDistance(_linkDistance).start();
        return _fgraph;
    }

    // Change/get strength parameter of force
    _fgraph.strength = function(_) {
        if (!arguments.length)
            return _linkStrength;
        _linkStrength = _;
        _force.linkStrength(_linkStrength).start();
        return _fgraph;
    }

    // Change/get charge for graph
    _fgraph.charge = function(_) {
        if (!arguments.length)
            return _fCharge;
        _fCharge = _;
        _force.charge(_fCharge).start();
        return _fgraph;
    }

    // Change/get inner colors
    _fgraph.nodeColors = function(_) {
        if (!arguments.length)
            return _nodeColors;
        _nodeColors = _;
        _fgraph.updateNodeStyle("fill", _);
        return _fgraph;
    }

    _fgraph.edgeColors = function(_) {
        if (!arguments.length)
            return _edgeColors;
        _edgeColors = _;

        _fgraph.updateEgdeStyle("stroke", _);
        return _fgraph;
    }

    _fgraph.nodeColorAccessor = function(_) {
        if (!arguments.length)
            return _nodeColorAccessor;
        _nodeColorAccessor = _;
        return _fgraph;
    };

    _fgraph.edgeColorAccessor = function(_) {
        if (!arguments.length)
            return _edgeColorAccessor;
        _edgeColorAccessor = _;
        return _fgraph;
    };

    _fgraph.weightAccessor = function(_) {
        if (!arguments.length)
            return _weightAccessor;
        // TODO, enforce the changes
        _weightAccessor = _;
        return _fgraph;
    };

    // Change/get data
    _fgraph.graphView = function(_) {
        if (!arguments.length)
            return _graph;
        _graph = _;
        changeData(_graph);
        return _fgraph;
    };

    _fgraph.resize = function(width, height) {
        _width = width;
        _height = height;

        if (_svg)
            d3.select(_parentID)
                    .select("svg")
                    .attr("width", _width)
                    .attr("height", _height)
                    .select("rect")
                    .attr("width", _width)
                    .attr("height", _height)
                    ;

        if (_force)
            _force.size([width, height]).start();
        return _fgraph;
    };

    _fgraph.minEdgeWidth = function(_) {
        var range = _edgeWidthScale.range();
        if (!arguments.length)
            return range[0];

        range[0] = _;
        _edgeWidthScale.range(range);

        return _fgraph;
    };

    _fgraph.maxEdgeWidth = function(_) {
        var range = _edgeWidthScale.range();
        if (!arguments.length)
            return range[1];

        range[1] = _;
        _edgeWidthScale.range(range);

        return _fgraph;
    };

    _fgraph.nodeSizeAcessor = function(_) {
        _nodeSizeAccessor = (typeof _ === 'function') ? _ : function(d) {
            return _minNodeSize;
        };
        changeNodeSize();
    };

    _fgraph.minNodeSize = function(_) {
        _minNodeSize = (typeof _ === 'number') ? _ : 5;
        _nodeSizeScale.range([_minNodeSize, _maxNodeSize]);
        changeNodeSize();
    };

    _fgraph.maxNodeSize = function(_) {
        _maxNodeSize = (typeof _ === 'number') ? _ : 15;
        _nodeSizeScale.range([_minNodeSize, _maxNodeSize]);
        changeNodeSize();
    };

    _fgraph.nodeLabelAcessor = function(_) {
        _nodeLabelAccessor = (typeof _ === 'function') ? _ : function(d) {
            return "";
        };
        _node.selectAll("text")
                .text(function(d) {
                    return _nodeLabelAccessor(d.data);
                });
    };

    _fgraph.nodeColorAcessor = function(_) {
        _nodeColorAccessor = (typeof _ === 'function') ? _ : function(d) {
            return 1;
        };
        // if function and type defined is continous
        _nodeColorType = (typeof _ === 'function' && _.returnType === "number") ? "cont" : "disc";
        changeNodeColor();
        return _fgraph;
    };

    _fgraph.minNodeColor = function(_) {
        _minNodeColor = (typeof _ === 'string') ? _ : "blue";
        _nodeColorScaleC.range([_minNodeColor, _maxNodeColor]);
        changeNodeColor();
        return _fgraph;
    };

    _fgraph.maxNodeColor = function(_) {
        _maxNodeColor = (typeof _ === 'string') ? _ : "red";
        _nodeColorScaleC.range([_minNodeColor, _maxNodeColor]);
        changeNodeColor();
        return _fgraph;
    };

    _fgraph.dblclickHandler = function(_) {
        _dblclickHandler = (typeof _ === 'function') ? _ :
                function(d){};

        return _fgraph;
    }

    return _fgraph;
};
<commit_msg>Forgot a ,<commit_after>if (typeof dc === 'undefined')
    dc = {};
/* @param parent: the ID of the DOM element where the gauge will be hooked up into
 @param chartGroup: the group to which the gauge belongs to (determines when refreshing is done)
 */
dc.fgraph = function(parent) {
    var _fgraph = {}, // main object
            _parentID = parent, // keep track of the parent of an object
            _margins = {
                top: 0,
                right: 0,
                bottom: 0,
                left: 0
            },
    _nodeData, // current data used by node
            _edgeData, // current data used by edge
            _fCharge = -120, // charge parameter
            _nodeColorType = "cont", // is the coloring measure discrete? "cont" or "disc"
            _nodeColorScaleD = d3.scale.category20(), // colors to be used for nodes when nodeAccessor is discret
            _nodeMinColor = "blue",
            _nodeMaxColor = "red",
            _nodeColorScaleC = d3.scale.linear() // same for continous
            .range([_nodeMinColor, _nodeMaxColor])
            .interpolate(d3.interpolateHcl),
            _edgeColors = d3.scale.category20(), // colors to be used for edges
            _nodeLabelAccessor = function(d) {
                return "";
            },
            _nodeColorAccessor = function(d) {
                return 1;
            }, // node color accessor
            _edgeColorAccessor = function(d) {
                return 1;
            },
            _minNodeSize = 10,
            _maxNodeSize = 30,
            _nodeSizeAccessor = function(d) {
                return _minNodeSize;
            },
            _nodeSizeScale = d3.scale.linear()
            .domain([_minNodeSize, _minNodeSize])
            .range([_minNodeSize, _maxNodeSize]),
            _edgeWidthAccessor = function(d) { // edge color accessor
                return 2;
            }, // controlls the width of the link
            _weightAccessor = function(d) {
                return 2;
            }, // controlls the weight of the node
            _edgeWidthScale = d3.scale.linear().range([1, 10]),
            _linkDistance = 30, // link distance for force
            _linkStrength,
            _force, // force layout
            _svg, // svg element
            _link, // links
            _node, // nodes
            _transitionDuration = 750,
            _graph = {}, // data to be displayed
            _width = 960,
            _height = 600,
            _displayNames = true, // should we display names
            _dblclickHandler = function(d) { // handler for what happens under a dbouleclick
                // do nothing
            },
    _terminator; // not used, just to terminate list

    // partial redraw of node size
    function changeNodeSize() {
        // recompute the normalization
        _nodeSizeScale.domain(
                d3.extent(_nodeData,
                        function(d) {
                            return _nodeSizeAccessor(d.data);
                        }));
        _node.selectAll("circle")
                .attr("r", function(d) {
                    return _nodeSizeScale(_nodeSizeAccessor(d.data));
                });
    }

    // partial redraw for node colors
    function changeNodeColor() {
        if (_nodeColorType === "cont") {
            _nodeColorScaleC.domain(
                    d3.extent(_nodeData,
                            function(d) {
                                return _nodeColorAccessor(d.data);
                            }))
            _node.selectAll("circle")
                    .style("fill", function(d) {
                        return _nodeColorScaleC(_nodeColorAccessor(d.data));
                    });
        } else {
            _node.selectAll("circle")
                    .style("fill", function(d) {
                        return _nodeColorScaleD(_nodeColorAccessor(d.data));
                    });

        }
    }

    // we can initialize the links/nodes only when we have data
    function changeData(graph) {

        if (!_force)
            _force = d3.layout.force()
                    .charge(_fCharge)
                    .linkDistance(_linkDistance)
                    .size([_width, _height]);

        function zoom() {
            _svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        }

        if (!_svg) {
            _svg = d3.select(_parentID)
                    .append("svg")
                    .attr("width", _width)
                    .attr("height", _height);

            _svg.append("rect")
                    .attr("width", _width)
                    .attr("height", _height)
                    .style("fill", "none")
                    .style("pointer-events", "all")
                    .call(d3.behavior.zoom().scaleExtent([.0625, 8]).on("zoom", zoom))

            _svg = _svg.append("g")
                    ;
        }

        // wrap the info in graph so that force layout is happy
        _nodeData = graph.nodes.map(function(d, i) {
            d.index = i;
            return {
                data: d,
                weight: _weightAccessor(d)
            };
        });
        //console.log(_nodeData);
        _edgeData = graph.edges.map(function(d, i) {
            d.index = i;
            return {
                //  source: d.source,
                //  target: d.target,
                source: _nodeData[d.source.index],
                target: _nodeData[d.target.index],
                data: d.data,
                //value: d.value
            };
        });
        //console.log(_edgeData);

        _force
                .nodes(_nodeData)
                .links(_edgeData)
                .start();

        _link = _svg.selectAll(".link")
                .data(_edgeData)
                .enter().append("line")
                .attr("class", "link")
                .style("stroke", function(d) {
                    return _edgeColors(d.data);
                })
                .style("stroke-width", function(d) {
                    return _edgeWidthAccessor(d.data);
                });

        _node = _svg.selectAll(".node")
                .data(_nodeData)
                .enter().append("g")
                .attr("class", "node")
                .call(_force.drag)
                ;

        _node.append("circle")
                .attr("class", "fgraph-circle")
                .on("mousedown", function(d) {
                    d.fixed = !d.fixed;
                    d3.select(this).classed('sticky', d.fixed);
                })
                .on("dblclick", function(d){ _dblclickHandler(d); });
        changeNodeSize();
        changeNodeColor();

        _node.append("title")
                .text(function(d) {
                    return d.name;
                });

        _node.append("text")
                .attr("class", "fgraph-text")
                .text(function(d) {
                    return _nodeLabelAccessor(d);
                });

        _force.on("tick", function() {
            _link.attr("x1", function(d) {
                return d.source.x;
            })
                    .attr("y1", function(d) {
                        return d.source.y;
                    })
                    .attr("x2", function(d) {
                        return d.target.x;
                    })
                    .attr("y2", function(d) {
                        return d.target.y;
                    });

            _node.attr("transform", function(d) {
                return "translate(" + d.x + "," + d.y + ")";
            });
        });
    }

    _fgraph.init = function(parent, data, width, height) {
        _parentID = parent;
        _fgraph.graphView(data)
                .resize(width, height);
        return _fgraph;
    }
    _fgraph.destroy = function()
    {
        _svg.remove();
    }
    _fgraph.doRender = function() {
        // delete old content if present
        // d3.select(_parentID).select("svg").remove();

        _fgraph.doRedraw();

        return _fgraph;
    }

    _fgraph.updateEgdeStyle = function(style, fn) {
        _link.style(style, fn);
    }
    _fgraph.updateNodeStyle = function(style, fn) {
        _node.selectAll("circle").style(style, fn);
    }
    _fgraph.updateNodeAttr = function(style, fn) {
        _node.selectAll("circle").attr(style, fn);
    }

    _fgraph.doRedraw = function() {
        //need to include jquery for following line to work
        var parent = $(_parentID); //$(_parentID+"-inner").parent();
        _width = parent.width() - _margins.left - _margins.right;
        _height = parent.height() - _margins.top - _margins.bottom;
    }

    // should we display names for nodes?
    _fgraph.displayNames = function(_) {
        if (!arguments.length)
            return _displayNames;
        _displayNames = _;

        if (_displayNames)
            _node.append("text")
                    .text(function(d) {
                        return d.name;
                    });
        else {
            _node.selectAll("text").remove();
        }

        // _fgraph.doRedraw();
        return _fgraph;
    }

    // Change/get distance parametter of force
    _fgraph.distance = function(_) {
        if (!arguments.length)
            return _linkDistance;
        _linkDistance = _;
        _force.linkDistance(_linkDistance).start();
        return _fgraph;
    }

    // Change/get strength parameter of force
    _fgraph.strength = function(_) {
        if (!arguments.length)
            return _linkStrength;
        _linkStrength = _;
        _force.linkStrength(_linkStrength).start();
        return _fgraph;
    }

    // Change/get charge for graph
    _fgraph.charge = function(_) {
        if (!arguments.length)
            return _fCharge;
        _fCharge = _;
        _force.charge(_fCharge).start();
        return _fgraph;
    }

    // Change/get inner colors
    _fgraph.nodeColors = function(_) {
        if (!arguments.length)
            return _nodeColors;
        _nodeColors = _;
        _fgraph.updateNodeStyle("fill", _);
        return _fgraph;
    }

    _fgraph.edgeColors = function(_) {
        if (!arguments.length)
            return _edgeColors;
        _edgeColors = _;

        _fgraph.updateEgdeStyle("stroke", _);
        return _fgraph;
    }

    _fgraph.nodeColorAccessor = function(_) {
        if (!arguments.length)
            return _nodeColorAccessor;
        _nodeColorAccessor = _;
        return _fgraph;
    };

    _fgraph.edgeColorAccessor = function(_) {
        if (!arguments.length)
            return _edgeColorAccessor;
        _edgeColorAccessor = _;
        return _fgraph;
    };

    _fgraph.weightAccessor = function(_) {
        if (!arguments.length)
            return _weightAccessor;
        // TODO, enforce the changes
        _weightAccessor = _;
        return _fgraph;
    };

    // Change/get data
    _fgraph.graphView = function(_) {
        if (!arguments.length)
            return _graph;
        _graph = _;
        changeData(_graph);
        return _fgraph;
    };

    _fgraph.resize = function(width, height) {
        _width = width;
        _height = height;

        if (_svg)
            d3.select(_parentID)
                    .select("svg")
                    .attr("width", _width)
                    .attr("height", _height)
                    .select("rect")
                    .attr("width", _width)
                    .attr("height", _height)
                    ;

        if (_force)
            _force.size([width, height]).start();
        return _fgraph;
    };

    _fgraph.minEdgeWidth = function(_) {
        var range = _edgeWidthScale.range();
        if (!arguments.length)
            return range[0];

        range[0] = _;
        _edgeWidthScale.range(range);

        return _fgraph;
    };

    _fgraph.maxEdgeWidth = function(_) {
        var range = _edgeWidthScale.range();
        if (!arguments.length)
            return range[1];

        range[1] = _;
        _edgeWidthScale.range(range);

        return _fgraph;
    };

    _fgraph.nodeSizeAcessor = function(_) {
        _nodeSizeAccessor = (typeof _ === 'function') ? _ : function(d) {
            return _minNodeSize;
        };
        changeNodeSize();
    };

    _fgraph.minNodeSize = function(_) {
        _minNodeSize = (typeof _ === 'number') ? _ : 5;
        _nodeSizeScale.range([_minNodeSize, _maxNodeSize]);
        changeNodeSize();
    };

    _fgraph.maxNodeSize = function(_) {
        _maxNodeSize = (typeof _ === 'number') ? _ : 15;
        _nodeSizeScale.range([_minNodeSize, _maxNodeSize]);
        changeNodeSize();
    };

    _fgraph.nodeLabelAcessor = function(_) {
        _nodeLabelAccessor = (typeof _ === 'function') ? _ : function(d) {
            return "";
        };
        _node.selectAll("text")
                .text(function(d) {
                    return _nodeLabelAccessor(d.data);
                });
    };

    _fgraph.nodeColorAcessor = function(_) {
        _nodeColorAccessor = (typeof _ === 'function') ? _ : function(d) {
            return 1;
        };
        // if function and type defined is continous
        _nodeColorType = (typeof _ === 'function' && _.returnType === "number") ? "cont" : "disc";
        changeNodeColor();
        return _fgraph;
    };

    _fgraph.minNodeColor = function(_) {
        _minNodeColor = (typeof _ === 'string') ? _ : "blue";
        _nodeColorScaleC.range([_minNodeColor, _maxNodeColor]);
        changeNodeColor();
        return _fgraph;
    };

    _fgraph.maxNodeColor = function(_) {
        _maxNodeColor = (typeof _ === 'string') ? _ : "red";
        _nodeColorScaleC.range([_minNodeColor, _maxNodeColor]);
        changeNodeColor();
        return _fgraph;
    };

    _fgraph.dblclickHandler = function(_) {
        _dblclickHandler = (typeof _ === 'function') ? _ :
                function(d){};

        return _fgraph;
    }

    return _fgraph;
};
<|endoftext|>


<commit_before>/* event delegation algorithm */
function delegate(events, getVTree) {
    for (var event in events) {
	document.body.addEventListener(events[event][0], function(e) {
            delegateEvent ( e
                          , getVTree()
                          , buildTargetToBody(document.body, e.target)
                          , []
                          );
	     }, events[event][1]);
    }
}

/* Accumulate parent stack as well for propagation */
function delegateEvent (event, obj, stack, parentStack) {

    /* base case, not found */
    if (!stack.length) return;

    /* stack not length 1, recurse */
    else if (stack.length > 1) {
      if (obj.domRef === stack[0]) parentStack.unshift(obj);
	for (var o = 0; o < obj.children.length; o++) {
          if (obj.children[o].type === "vtext") continue;
          delegateEvent ( event
                        , obj.children[o]
                        , stack.slice(1)
                        , parentStack
			                  );
       }
    }

    /* stack.length == 1 */
    else {
	if (obj.domRef === stack[0]) {
          if (obj.events[event.type]) {
	      var eventObj = obj.events[event.type],
		  options = eventObj.options;
              if (options.preventDefault) event.preventDefault();
          eventObj.runEvent(event);
	    if (!options.stopPropagation)
	     propogateWhileAble (parentStack, event);
          }
	}
    }
}

function buildTargetToBody (body, target) {
    var stack = [];
    while (body !== target) {
      stack.unshift (target);
      target = target.parentNode;
    }
    return stack;
}

function propogateWhileAble (parentStack, event) {
  for (var i = 0; i < parentStack.length; i++) {
    if (parentStack[i].events[event.type]) {
      var eventObj = parentStack[i].events[event.type],
          options = eventObj.options;
        if (options.preventDefault) event.preventDefault();
        eventObj.runEvent(event);
  	if (options.stopPropagation) break;
    }
  }
}

/* Convert event to JSON at a specific location in the DOM tree*/
function objectToJSON (at, obj) {
    for (var i in at) obj = obj[at[i]];
    var newObj = {};
    for (var i in obj)
	if (typeof obj[i] == "string" || typeof obj[i] == "number" || typeof obj[i] == "boolean")
	    newObj[i] = obj[i];
    return (newObj);
}
<commit_msg>Propagate to parent event handlers when no event present<commit_after>/* event delegation algorithm */
function delegate(events, getVTree) {
    for (var event in events) {
	document.body.addEventListener(events[event][0], function(e) {
            delegateEvent ( e
                          , getVTree()
                          , buildTargetToBody(document.body, e.target)
                          , []
                          );
	     }, events[event][1]);
    }
}

/* Accumulate parent stack as well for propagation */
function delegateEvent (event, obj, stack, parentStack) {

    /* base case, not found */
    if (!stack.length) return;

    /* stack not length 1, recurse */
    else if (stack.length > 1) {
      if (obj.domRef === stack[0]) parentStack.unshift(obj);
	for (var o = 0; o < obj.children.length; o++) {
          if (obj.children[o].type === "vtext") continue;
          delegateEvent ( event
                        , obj.children[o]
                        , stack.slice(1)
                        , parentStack
			                  );
       }
    }

    /* stack.length == 1 */
    else {
	if (obj.domRef === stack[0]) {
	    var eventObj = obj.events[event.type];
	    if (eventObj) {
		var options = eventObj.options;
		if (options.preventDefault)
		    event.preventDefault();
		eventObj.runEvent(event);
		if (!options.stopPropagation)
		    propogateWhileAble (parentStack, event);
	    } else {
		 /* still propagate to parent handlers even if event not defined */
 		 propogateWhileAble (parentStack, event);
	      }
	}
    }
}

function buildTargetToBody (body, target) {
    var stack = [];
    while (body !== target) {
      stack.unshift (target);
      target = target.parentNode;
    }
    return stack;
}

function propogateWhileAble (parentStack, event) {
  for (var i = 0; i < parentStack.length; i++) {
    if (parentStack[i].events[event.type]) {
      var eventObj = parentStack[i].events[event.type],
          options = eventObj.options;
        if (options.preventDefault) event.preventDefault();
        eventObj.runEvent(event);
  	if (options.stopPropagation) break;
    }
  }
}

/* Convert event to JSON at a specific location in the DOM tree*/
function objectToJSON (at, obj) {
    for (var i in at) obj = obj[at[i]];
    var newObj = {};
    for (var i in obj)
	if (typeof obj[i] == "string" || typeof obj[i] == "number" || typeof obj[i] == "boolean")
	    newObj[i] = obj[i];
    return (newObj);
}
<|endoftext|>