mirror of
https://github.com/tenrok/bootstrap.git
synced 2026-06-05 16:42:29 +03:00
initial
This commit is contained in:
+39
-130
@@ -5,21 +5,18 @@
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as Popper from '@popperjs/core'
|
||||
import { inline, offset, shift } from '@floating-ui/dom'
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
getElement,
|
||||
getNextActiveElement,
|
||||
isDisabled,
|
||||
isElement,
|
||||
isRTL,
|
||||
isVisible,
|
||||
noop
|
||||
} from './util/index'
|
||||
import EventHandler from './dom/event-handler'
|
||||
import Manipulator from './dom/manipulator'
|
||||
import SelectorEngine from './dom/selector-engine'
|
||||
import BaseComponent from './base-component'
|
||||
import FloatingUi from './util/floating-ui'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
@@ -28,7 +25,6 @@ import BaseComponent from './base-component'
|
||||
const NAME = 'dropdown'
|
||||
const DATA_KEY = 'bs.dropdown'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
const DATA_API_KEY = '.data-api'
|
||||
|
||||
const ESCAPE_KEY = 'Escape'
|
||||
const TAB_KEY = 'Tab'
|
||||
@@ -40,9 +36,9 @@ 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_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 EVENT_CLICK_DATA_API = `click${EVENT_KEY}`
|
||||
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}`
|
||||
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}`
|
||||
|
||||
const CLASS_NAME_SHOW = 'show'
|
||||
const CLASS_NAME_DROPUP = 'dropup'
|
||||
@@ -58,30 +54,28 @@ const SELECTOR_NAVBAR = '.navbar'
|
||||
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 PLACEMENT_TOPCENTER = 'top'
|
||||
const PLACEMENT_TOPEND = 'top-end'
|
||||
const PLACEMENT_TOP = 'top-start'
|
||||
const PLACEMENT_BOTTOMCENTER = 'bottom'
|
||||
const PLACEMENT_BOTTOMEND = 'bottom-end'
|
||||
const PLACEMENT_BOTTOM = 'bottom-start'
|
||||
const PLACEMENT_RIGHT = 'right-start'
|
||||
const PLACEMENT_LEFT = 'left-start'
|
||||
|
||||
const Default = {
|
||||
autoClose: true,
|
||||
boundary: 'clippingParents',
|
||||
display: 'dynamic',
|
||||
offset: [0, 2],
|
||||
popperConfig: null,
|
||||
offset: 10,
|
||||
positionConfig: null,
|
||||
reference: 'toggle'
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
autoClose: '(boolean|string)',
|
||||
boundary: '(string|element)',
|
||||
display: 'string',
|
||||
offset: '(array|string|function)',
|
||||
popperConfig: '(null|object|function)',
|
||||
offset: '(number|array|string|function)',
|
||||
positionConfig: '(null|object|function)',
|
||||
reference: '(string|element|object)'
|
||||
}
|
||||
|
||||
@@ -93,10 +87,9 @@ class Dropdown extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
this._popper = null
|
||||
this._parent = this._element.parentNode // dropdown wrapper
|
||||
this._menu = SelectorEngine.findOne(SELECTOR_MENU, this._parent)
|
||||
this._inNavbar = this._detectNavbar()
|
||||
this._positionHelper = new FloatingUi(this._element)
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -132,7 +125,7 @@ class Dropdown extends BaseComponent {
|
||||
return
|
||||
}
|
||||
|
||||
this._createPopper()
|
||||
this.update()
|
||||
|
||||
// If this is a touch-enabled device we add extra
|
||||
// empty mouseover listeners to the body's immediate children;
|
||||
@@ -164,19 +157,9 @@ class Dropdown extends BaseComponent {
|
||||
this._completeHide(relatedTarget)
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._popper) {
|
||||
this._popper.destroy()
|
||||
}
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
update() {
|
||||
this._inNavbar = this._detectNavbar()
|
||||
if (this._popper) {
|
||||
this._popper.update()
|
||||
}
|
||||
const reference = this._positionHelper.getReferenceElement(this._config.reference, this._parent, NAME)
|
||||
this._positionHelper.calculate(reference, this._menu, this._getFloatingUiConfig())
|
||||
}
|
||||
|
||||
// Private
|
||||
@@ -194,47 +177,28 @@ class Dropdown extends BaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._popper) {
|
||||
this._popper.destroy()
|
||||
}
|
||||
|
||||
this._menu.classList.remove(CLASS_NAME_SHOW)
|
||||
this._element.classList.remove(CLASS_NAME_SHOW)
|
||||
this._element.setAttribute('aria-expanded', 'false')
|
||||
Manipulator.removeDataAttribute(this._menu, 'popper')
|
||||
this._positionHelper.stop()
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
|
||||
}
|
||||
|
||||
_getConfig(config) {
|
||||
config = super._getConfig(config)
|
||||
|
||||
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.`)
|
||||
_getFloatingUiConfig() {
|
||||
const defaultBsConfig = {
|
||||
placement: this._getPlacement(),
|
||||
middleware: [offset(this._positionHelper.parseOffset(this._config.offset)), shift()]
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
_createPopper() {
|
||||
if (typeof Popper === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)')
|
||||
// Disable Popper if we have a static display or Dropdown is in Navbar
|
||||
if (this._detectNavbar() || this._config.display === 'static') {
|
||||
defaultBsConfig.middleware.push(inline())
|
||||
}
|
||||
|
||||
let referenceElement = this._element
|
||||
|
||||
if (this._config.reference === 'parent') {
|
||||
referenceElement = this._parent
|
||||
} else if (isElement(this._config.reference)) {
|
||||
referenceElement = getElement(this._config.reference)
|
||||
} else if (typeof this._config.reference === 'object') {
|
||||
referenceElement = this._config.reference
|
||||
return {
|
||||
...defaultBsConfig,
|
||||
...(typeof this._config.positionConfig === 'function' ? this._config.positionConfig(defaultBsConfig) : this._config.positionConfig)
|
||||
}
|
||||
|
||||
const popperConfig = this._getPopperConfig()
|
||||
this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)
|
||||
}
|
||||
|
||||
_isShown() {
|
||||
@@ -244,20 +208,15 @@ class Dropdown extends BaseComponent {
|
||||
_getPlacement() {
|
||||
const parentDropdown = this._parent
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
|
||||
return PLACEMENT_RIGHT
|
||||
const matches = {
|
||||
[CLASS_NAME_DROPEND]: PLACEMENT_RIGHT,
|
||||
[CLASS_NAME_DROPSTART]: PLACEMENT_LEFT,
|
||||
[CLASS_NAME_DROPUP_CENTER]: PLACEMENT_TOPCENTER,
|
||||
[CLASS_NAME_DROPDOWN_CENTER]: PLACEMENT_BOTTOMCENTER
|
||||
}
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
|
||||
return PLACEMENT_LEFT
|
||||
}
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
|
||||
return PLACEMENT_TOPCENTER
|
||||
}
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
|
||||
return PLACEMENT_BOTTOMCENTER
|
||||
const match = Object.keys(matches).find(keyClass => parentDropdown.classList.contains(keyClass))
|
||||
if (match) {
|
||||
return matches[match]
|
||||
}
|
||||
|
||||
// We need to trim the value because custom properties can also include spaces
|
||||
@@ -274,52 +233,6 @@ class Dropdown extends BaseComponent {
|
||||
return this._element.closest(SELECTOR_NAVBAR) !== null
|
||||
}
|
||||
|
||||
_getOffset() {
|
||||
const { offset } = this._config
|
||||
|
||||
if (typeof offset === 'string') {
|
||||
return offset.split(',').map(value => Number.parseInt(value, 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 or Dropdown is in Navbar
|
||||
if (this._inNavbar || this._config.display === 'static') {
|
||||
Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove
|
||||
defaultBsPopperConfig.modifiers = [{
|
||||
name: 'applyStyles',
|
||||
enabled: false
|
||||
}]
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultBsPopperConfig,
|
||||
...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
|
||||
}
|
||||
}
|
||||
|
||||
_selectMenuItem({ key, target }) {
|
||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))
|
||||
|
||||
@@ -364,11 +277,7 @@ class Dropdown extends BaseComponent {
|
||||
|
||||
const composedPath = event.composedPath()
|
||||
const isMenuTarget = composedPath.includes(context._menu)
|
||||
if (
|
||||
composedPath.includes(context._element) ||
|
||||
(context._config.autoClose === 'inside' && !isMenuTarget) ||
|
||||
(context._config.autoClose === 'outside' && isMenuTarget)
|
||||
) {
|
||||
if (composedPath.includes(context._element) || (context._config.autoClose === 'inside' && !isMenuTarget) || (context._config.autoClose === 'outside' && isMenuTarget)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user