configPropertyName: 'js_bundle_hash'
},
{
- file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
- configPropertyName: 'popper_hash'
+ file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.js',
+ configPropertyName: 'floating_ui_hash'
}
]
const ESM = process.env.ESM === 'true'
let fileDestination = `bootstrap${ESM ? '.esm' : ''}`
-const external = ['@popperjs/core']
+const external = ['@floating-ui/dom']
const plugins = [
babel({
// Only transpile our source code
})
]
const globals = {
- '@popperjs/core': 'Popper'
+ '@floating-ui/dom': 'floatingUi'
}
if (BUNDLE) {
fileDestination += '.bundle'
// Remove last entry in external array to bundle Popper
external.pop()
- delete globals['@popperjs/core']
+ delete globals['@floating-ui/dom']
plugins.push(
replace({
'process.env.NODE_ENV': '"production"',
js_hash: "sha384-ODmDIVzN+pFdexxHEHFBQH3/9/vQ9uori45z4JjnFsRydbmQbmL5t1tQ0culUzyK"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa"
- popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.5/dist/umd/popper.min.js"
- popper_hash: "sha384-Xe+8cL9oJa6tN/veChSP7q+mnSPaj5Bcu9mPX5F5xIGE0DVittaqT5lorf0EI7Vk"
+ floating_ui: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@0.5.4/dist/floating-ui.dom.umd.min.js"
+ floating_ui_hash: "sha384-Os8n9bzoYJ/ESbGD7cW0VOTLk0hO++SO+Y4swXBE2dHrxiZkjADEr5ZGOcc9CorD"
anchors:
min: 2
* --------------------------------------------------------------------------
*/
-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
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'
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'
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)'
}
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
return
}
- this._createPopper()
+ this.update()
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
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
}
}
- 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() {
_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
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))
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
}
offset: [0, 8],
placement: 'right',
template: '<div class="popover" role="tooltip">' +
- '<div class="popover-arrow"></div>' +
+ '<div class="popover-inner">' +
'<h3 class="popover-header"></h3>' +
'<div class="popover-body"></div>' +
+ '</div>' +
'</div>',
trigger: 'click'
}
* --------------------------------------------------------------------------
*/
-import * as Popper from '@popperjs/core'
-import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'
+import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isVisible, noop } from './util/index'
import { DefaultAllowlist } from './util/sanitizer'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import BaseComponent from './base-component'
import TemplateFactory from './util/template-factory'
+import FloatingUi from './util/floating-ui'
+import { flip, hide, offset, shift } from '@floating-ui/dom'
/**
* Constants
const AttachmentMap = {
AUTO: 'auto',
TOP: 'top',
- RIGHT: isRTL() ? 'left' : 'right',
+ RIGHT: 'right',
BOTTOM: 'bottom',
- LEFT: isRTL() ? 'right' : 'left'
+ LEFT: 'left'
}
const Default = {
delay: 0,
fallbackPlacements: ['top', 'right', 'bottom', 'left'],
html: false,
- offset: [0, 0],
+ offset: 0,
placement: 'top',
- popperConfig: null,
+ positionConfig: null,
sanitize: true,
sanitizeFn: null,
selector: false,
template: '<div class="tooltip" role="tooltip">' +
- '<div class="tooltip-arrow"></div>' +
- '<div class="tooltip-inner"></div>' +
- '</div>',
+ '<div class="tooltip-inner"></div>' +
+ '</div>',
title: '',
trigger: 'hover focus'
}
delay: '(number|object)',
fallbackPlacements: 'array',
html: 'boolean',
- offset: '(array|string|function)',
+ offset: '(number|array|string|function)',
placement: '(string|function)',
- popperConfig: '(null|object|function)',
+ positionConfig: '(null|object|function)',
sanitize: 'boolean',
sanitizeFn: '(null|function)',
selector: '(string|boolean)',
class Tooltip extends BaseComponent {
constructor(element, config) {
- if (typeof Popper === 'undefined') {
- throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)')
- }
-
super(element, config)
// Private
this._timeout = 0
this._isHovered = false
this._activeTrigger = {}
- this._popper = null
+ this._positionHelper = new FloatingUi(this._element)
this._templateFactory = null
this._newContent = null
}
show() {
- if (this._element.style.display === 'none') {
+ if (!isVisible(this._element)) {
throw new Error('Please use show on visible elements')
}
this.tip = null
}
- const tip = this._getTipElement()
-
- this._element.setAttribute('aria-describedby', tip.getAttribute('id'))
-
- const { container } = this._config
-
- if (!this._element.ownerDocument.documentElement.contains(this.tip)) {
- container.append(tip)
- EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))
- }
+ this.update()
- if (this._popper) {
- this._popper.update()
- } else {
- this._popper = this._createPopper(tip)
- }
-
- tip.classList.add(CLASS_NAME_SHOW)
+ this.tip.classList.add(CLASS_NAME_SHOW)
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
}
if (!this._isHovered) {
+ this._positionHelper.stop()
tip.remove()
}
this._element.removeAttribute('aria-describedby')
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))
-
- this._disposePopper()
}
this._queueCallback(complete, this.tip, this._isAnimated())
}
- update() {
- if (this._popper) {
- this._popper.update()
- }
- }
-
// Protected
_isWithContent() {
return Boolean(this._getTitle())
_getTipElement() {
if (!this.tip) {
this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())
+ this._element.setAttribute('aria-describedby', this.tip.getAttribute('id'))
+ }
+
+ const { container } = this._config
+
+ if (!this._element.ownerDocument.documentElement.contains(this.tip)) {
+ container.append(this.tip)
+ EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))
}
return this.tip
}
tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
- // todo: on v6 the following can be achieved with CSS only
- tip.classList.add(`bs-${this.constructor.NAME}-auto`)
const tipId = getUID(this.constructor.NAME).toString()
return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)
}
- _createPopper(tip) {
- const placement = typeof this._config.placement === 'function' ?
- this._config.placement.call(this, tip, this._element) :
- this._config.placement
- const attachment = AttachmentMap[placement.toUpperCase()]
- return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
- }
-
- _getOffset() {
- const { offset } = this._config
-
- if (typeof offset === 'string') {
- return offset.split(',').map(value => Number.parseInt(value, 10))
+ update() {
+ this._positionHelper.calculate(this._element, this._getTipElement(), this._getFloatingUiConfig(), { position: 'fixed' })
+ }
+
+ _getFloatingUiConfig() {
+ const defaultBsConfig = {
+ strategy: 'fixed',
+ placement: this._getPlacement(),
+ middleware: [
+ offset(this._positionHelper.parseOffset(this._config.offset)),
+ flip({ fallbackPlacements: this._config.fallbackPlacements }),
+ shift(),
+ hide()
+ ]
}
- if (typeof offset === 'function') {
- return popperData => offset(popperData, this._element)
+ return {
+ ...defaultBsConfig,
+ ...(typeof this._config.positionConfig === 'function' ? this._config.positionConfig(defaultBsConfig) : this._config.positionConfig)
}
+ }
- return offset
+ _getPlacement() {
+ const placement = typeof this._config.placement === 'function' ?
+ this._config.placement.call(this, this.tip, this._element) :
+ this._config.placement
+ return AttachmentMap[placement.toUpperCase()]
}
_resolvePossibleFunction(arg) {
return typeof arg === 'function' ? arg.call(this._element) : arg
}
- _getPopperConfig(attachment) {
- const defaultBsPopperConfig = {
- placement: attachment,
- modifiers: [
- {
- name: 'flip',
- options: {
- fallbackPlacements: this._config.fallbackPlacements
- }
- },
- {
- name: 'offset',
- options: {
- offset: this._getOffset()
- }
- },
- {
- name: 'preventOverflow',
- options: {
- boundary: this._config.boundary
- }
- },
- {
- name: 'arrow',
- options: {
- element: `.${this.constructor.NAME}-arrow`
- }
- },
- {
- name: 'preSetPlacement',
- enabled: true,
- phase: 'beforeMain',
- fn: data => {
- // Pre-set Popper's placement attribute in order to read the arrow sizes properly.
- // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement
- this._getTipElement().setAttribute('data-popper-placement', data.state.placement)
- }
- }
- ]
- }
-
- return {
- ...defaultBsPopperConfig,
- ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
- }
- }
-
_setListeners() {
const triggers = this._config.trigger.split(' ')
return config
}
- _disposePopper() {
- if (this._popper) {
- this._popper.destroy()
- this._popper = null
- }
- }
-
// Static
static jQueryInterface(config) {
return this.each(function () {
--- /dev/null
+// import {computePosition, flip, shift} from '@floating-ui/dom'
+//
+//
+import { autoUpdate, computePosition } from '@floating-ui/dom'
+import { getElement, isElement } from './index'
+import Manipulator from '../dom/manipulator'
+
+class FloatingUi {
+ constructor(element) {
+ if (typeof computePosition === 'undefined') {
+ throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)')
+ }
+
+ this._element = element
+ this._cleanup = null
+ }
+
+ calculate(reference, floatingEl, config, extraCss = {}) {
+ this._cleanup = autoUpdate(reference, floatingEl, () => {
+ computePosition(reference, floatingEl, config)
+ .then(({ x, y, placement, middlewareData }) => {
+ const positionCss = {
+ left: `${x}px`,
+ top: `${y}px`
+ }
+ console.log(middlewareData) // eslint-disable-line no-console
+ if (middlewareData.hide) {
+ const { referenceHidden } = middlewareData.hide
+
+ Object.assign(floatingEl.style, {
+ visibility: referenceHidden ? 'hidden' : 'visible'
+ })
+ }
+
+ Object.assign(floatingEl.style, { ...positionCss, ...extraCss })
+ Manipulator.setDataAttribute(floatingEl, 'placement', placement)
+ })
+ })
+ }
+
+ stop() {
+ if (this._cleanup) {
+ this._cleanup()
+ }
+ }
+
+ getReferenceElement(reference, parent, PluginName) {
+ if (reference === 'parent') {
+ return parent
+ }
+
+ if (isElement(reference)) {
+ return getElement(reference)
+ }
+
+ if (typeof reference === 'object') {
+ if (typeof reference.getBoundingClientRect !== 'function') {
+ throw new TypeError(`${PluginName.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
+ }
+
+ return reference
+ }
+
+ return this._element
+ }
+
+ parseOffset(value) {
+ console.log(value) // eslint-disable-line no-console
+ if (typeof value === 'function') {
+ return popperData => value(popperData, this._element)
+ }
+
+ if (typeof value === 'string') {
+ console.log('offset', value) // eslint-disable-line no-console
+ value = [
+ Number.parseInt(value.split(',')[0], 10),
+ Number.parseInt(value.split(',')[1] || 0, 10)
+ ]
+ }
+
+ if (Array.isArray(value)) {
+ return {
+ mainAxis: value[0],
+ alignmentAxis: value[1]
+ }
+ }
+
+ return value
+ }
+}
+
+export default FloatingUi
}
],
"license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^0.5.4"
+ },
"devDependencies": {
"@babel/cli": "^7.18.10",
"@babel/core": "^7.18.10",
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
+ "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
+ "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
+ "dependencies": {
+ "@floating-ui/core": "^0.7.3"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
}
}
},
+ "@floating-ui/core": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
+ "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
+ },
+ "@floating-ui/dom": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
+ "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
+ "requires": {
+ "@floating-ui/core": "^0.7.3"
+ }
+ },
"@humanwhocodes/config-array": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
"watch-js-docs": "nodemon --watch site/assets/js/ --ext js --exec \"npm run js-lint\""
},
"peerDependencies": {
- "@popperjs/core": "^2.11.5"
+ "@floating-ui/dom": "^0.5.4"
},
"devDependencies": {
"@babel/cli": "^7.18.10",
"@babel/core": "^7.18.10",
"@babel/preset-env": "^7.18.10",
- "@popperjs/core": "^2.11.5",
+ "@floating-ui/dom": "^0.5.4",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-commonjs": "^22.0.1",
"@rollup/plugin-node-resolve": "^13.3.0",
},
"dependencies": {},
"peerDependencies": {
- "@popperjs/core": "^2.11.5"
+ "@floating-ui/dom": "^0.5.4"
}
}
}
@include border-radius(var(--#{$prefix}dropdown-border-radius));
@include box-shadow(var(--#{$prefix}dropdown-box-shadow));
- &[data-bs-popper] {
- top: 100%;
- left: 0;
- margin-top: var(--#{$prefix}dropdown-spacer);
- }
-
@if $dropdown-padding-y == 0 {
> .dropdown-item:first-child,
> li:first-child .dropdown-item {
// scss-docs-start responsive-breakpoints
// We deliberately hardcode the `bs-` prefix because we check
-// this custom property in JS to determine Popper's positioning
+// this custom property in JS to determine positioning
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
.dropdown-menu#{$infix}-start {
--bs-position: start;
-
- &[data-bs-popper] {
- right: auto;
- left: 0;
- }
}
.dropdown-menu#{$infix}-end {
--bs-position: end;
-
- &[data-bs-popper] {
- right: 0;
- left: auto;
- }
}
}
}
// Allow for dropdowns to go bottom up (aka, dropup-menu)
// Just add .dropup after the standard .dropdown class and you're set.
.dropup {
- .dropdown-menu[data-bs-popper] {
- top: auto;
- bottom: 100%;
- margin-top: 0;
- margin-bottom: var(--#{$prefix}dropdown-spacer);
- }
-
.dropdown-toggle {
@include caret(up);
}
}
.dropend {
- .dropdown-menu[data-bs-popper] {
- top: 0;
- right: auto;
- left: 100%;
- margin-top: 0;
- margin-left: var(--#{$prefix}dropdown-spacer);
- }
.dropdown-toggle {
@include caret(end);
}
.dropstart {
- .dropdown-menu[data-bs-popper] {
- top: 0;
- right: 100%;
- left: auto;
- margin-top: 0;
- margin-right: var(--#{$prefix}dropdown-spacer);
- }
-
.dropdown-toggle {
@include caret(start);
&::before {
--#{$prefix}popover-arrow-border: var(--#{$prefix}popover-border-color);
// scss-docs-end popover-css-vars
+ position: absolute;
+ top: 0;
+ left: 0;
z-index: var(--#{$prefix}popover-zindex);
display: block;
max-width: var(--#{$prefix}popover-max-width);
@include font-size(var(--#{$prefix}popover-font-size));
// Allow breaking very long words so they don't overflow the popover's bounds
word-wrap: break-word;
+
+ &::before,
+ &::after {
+ position: absolute;
+ display: block;
+ content: "";
+ border-color: transparent;
+ border-style: solid;
+ border-width: 0;
+ transform: translateX(-50%);
+ }
+}
+
+.popover-inner {
+ margin: var(--#{$prefix}popover-arrow-height);
background-color: var(--#{$prefix}popover-bg);
background-clip: padding-box;
border: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color);
@include border-radius(var(--#{$prefix}popover-border-radius));
@include box-shadow(var(--#{$prefix}popover-box-shadow));
-
- .popover-arrow {
- display: block;
- width: var(--#{$prefix}popover-arrow-width);
- height: var(--#{$prefix}popover-arrow-height);
-
- &::before,
- &::after {
- position: absolute;
- display: block;
- content: "";
- border-color: transparent;
- border-style: solid;
- border-width: 0;
- }
- }
}
-.bs-popover-top {
- > .popover-arrow {
- bottom: calc((var(--#{$prefix}popover-arrow-height) * -1) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
-
- &::before,
- &::after {
- border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
- }
+.popover[data-bs-placement="top"] {
+ &::before,
+ &::after {
+ left: 50%;
+ border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ }
- &::before {
- bottom: 0;
- border-top-color: var(--#{$prefix}popover-arrow-border);
- }
+ &::before {
+ bottom: 0;
+ border-top-color: var(--#{$prefix}popover-arrow-border);
+ }
- &::after {
- bottom: var(--#{$prefix}popover-border-width);
- border-top-color: var(--#{$prefix}popover-bg);
- }
+ &::after {
+ bottom: var(--#{$prefix}popover-border-width);
+ border-top-color: var(--#{$prefix}popover-bg);
}
}
-/* rtl:begin:ignore */
-.bs-popover-end {
- > .popover-arrow {
- left: calc((var(--#{$prefix}popover-arrow-height) * -1) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
- width: var(--#{$prefix}popover-arrow-height);
- height: var(--#{$prefix}popover-arrow-width);
-
- &::before,
- &::after {
- border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
- }
-
- &::before {
- left: 0;
- border-right-color: var(--#{$prefix}popover-arrow-border);
- }
-
- &::after {
- left: var(--#{$prefix}popover-border-width);
- border-right-color: var(--#{$prefix}popover-bg);
- }
+.popover[data-bs-placement="bottom"] {
+ &::before,
+ &::after {
+ left: 50%;
+ border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list
}
-}
-/* rtl:end:ignore */
+ &::before {
+ top: 0;
+ border-bottom-color: var(--#{$prefix}popover-arrow-border);
+ }
-.bs-popover-bottom {
- > .popover-arrow {
- top: calc((var(--#{$prefix}popover-arrow-height) * -1) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
+ &::after {
+ top: var(--#{$prefix}popover-border-width);
+ border-bottom-color: var(--#{$prefix}popover-bg);
+ }
+}
- &::before,
- &::after {
- border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list
- }
- &::before {
- top: 0;
- border-bottom-color: var(--#{$prefix}popover-arrow-border);
- }
+/* rtl:begin:ignore */
- &::after {
- top: var(--#{$prefix}popover-border-width);
- border-bottom-color: var(--#{$prefix}popover-bg);
- }
+.popover[data-bs-placement="right"] {
+ &::before,
+ &::after {
+ top: 50%;
+ border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ transform: translateY(-50%);
}
- // This will remove the popover-header's border just below the arrow
- .popover-header::before {
- position: absolute;
- top: 0;
- left: 50%;
- display: block;
- width: var(--#{$prefix}popover-arrow-width);
- margin-left: calc(var(--#{$prefix}popover-arrow-width) * -.5); // stylelint-disable-line function-disallowed-list
- content: "";
- border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg);
+ &::before {
+ left: 0;
+ border-right-color: var(--#{$prefix}popover-arrow-border);
}
-}
-/* rtl:begin:ignore */
-.bs-popover-start {
- > .popover-arrow {
- right: calc((var(--#{$prefix}popover-arrow-height) * -1) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
- width: var(--#{$prefix}popover-arrow-height);
- height: var(--#{$prefix}popover-arrow-width);
-
- &::before,
- &::after {
- border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list
- }
-
- &::before {
- right: 0;
- border-left-color: var(--#{$prefix}popover-arrow-border);
- }
-
- &::after {
- right: var(--#{$prefix}popover-border-width);
- border-left-color: var(--#{$prefix}popover-bg);
- }
+ &::after {
+ left: var(--#{$prefix}popover-border-width);
+ border-right-color: var(--#{$prefix}popover-bg);
}
}
-/* rtl:end:ignore */
-.bs-popover-auto {
- &[data-popper-placement^="top"] {
- @extend .bs-popover-top;
+.popover[data-bs-placement="left"] {
+ &::before,
+ &::after {
+ top: 50%;
+ border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list
+ transform: translateY(-50%);
}
- &[data-popper-placement^="right"] {
- @extend .bs-popover-end;
- }
- &[data-popper-placement^="bottom"] {
- @extend .bs-popover-bottom;
+
+ &::before {
+ right: 0;
+ border-left-color: var(--#{$prefix}popover-arrow-border);
}
- &[data-popper-placement^="left"] {
- @extend .bs-popover-start;
+
+ &::after {
+ right: var(--#{$prefix}popover-border-width);
+ border-left-color: var(--#{$prefix}popover-bg);
}
}
+
+/* rtl:end:ignore */
+
+// This will remove the popover-header's border just below the arrow
+.popover-header::before {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ display: block;
+ width: var(--#{$prefix}popover-arrow-width);
+ margin-left: calc(var(--#{$prefix}popover-arrow-width) * -.5); // stylelint-disable-line function-disallowed-list
+ content: "";
+ border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg);
+}
+
+
// Offset the popover to account for the popover arrow
.popover-header {
padding: var(--#{$prefix}popover-header-padding-y) var(--#{$prefix}popover-header-padding-x);
--#{$prefix}tooltip-max-width: #{$tooltip-max-width};
--#{$prefix}tooltip-padding-x: #{$tooltip-padding-x};
--#{$prefix}tooltip-padding-y: #{$tooltip-padding-y};
- --#{$prefix}tooltip-margin: #{$tooltip-margin};
@include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size);
--#{$prefix}tooltip-color: #{$tooltip-color};
--#{$prefix}tooltip-bg: #{$tooltip-bg};
--#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height};
// scss-docs-end tooltip-css-vars
+ position: absolute;
+ top: 0;
+ left: 0;
z-index: var(--#{$prefix}tooltip-zindex);
display: block;
padding: var(--#{$prefix}tooltip-arrow-height);
- margin: var(--#{$prefix}tooltip-margin);
- @include deprecate("`$tooltip-margin`", "v5", "v5.x", true);
// Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.
// So reset our font and text properties to avoid inheriting weird values.
@include reset-text();
&.show { opacity: var(--#{$prefix}tooltip-opacity); }
- .tooltip-arrow {
- display: block;
- width: var(--#{$prefix}tooltip-arrow-width);
- height: var(--#{$prefix}tooltip-arrow-height);
-
- &::before {
- position: absolute;
- content: "";
- border-color: transparent;
- border-style: solid;
- }
- }
-}
-
-.bs-tooltip-top .tooltip-arrow {
- bottom: 0;
-
&::before {
- top: -1px;
- border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
- border-top-color: var(--#{$prefix}tooltip-bg);
+ position: absolute;
+ content: "";
+ border-color: transparent;
+ border-style: solid;
+ transform: translateX(-50%);
}
-}
-
-/* rtl:begin:ignore */
-.bs-tooltip-end .tooltip-arrow {
- left: 0;
- width: var(--#{$prefix}tooltip-arrow-height);
- height: var(--#{$prefix}tooltip-arrow-width);
- &::before {
- right: -1px;
- border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
- border-right-color: var(--#{$prefix}tooltip-bg);
- }
}
-/* rtl:end:ignore */
+.tooltip[data-bs-placement="top"]::before {
+ bottom: 0;
+ left: 50%;
+ border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ border-top-color: var(--#{$prefix}tooltip-bg);
+}
-.bs-tooltip-bottom .tooltip-arrow {
+.tooltip[data-bs-placement="bottom"]::before {
top: 0;
-
- &::before {
- bottom: -1px;
- border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list
- border-bottom-color: var(--#{$prefix}tooltip-bg);
- }
+ left: 50%;
+ border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list
+ border-bottom-color: var(--#{$prefix}tooltip-bg);
}
-/* rtl:begin:ignore */
-.bs-tooltip-start .tooltip-arrow {
- right: 0;
- width: var(--#{$prefix}tooltip-arrow-height);
- height: var(--#{$prefix}tooltip-arrow-width);
-
- &::before {
- left: -1px;
- border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list
- border-left-color: var(--#{$prefix}tooltip-bg);
- }
+.tooltip[data-bs-placement="right"]::before {
+ top: 50%;
+ left: 0;
+ border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ border-right-color: var(--#{$prefix}tooltip-bg);
+ transform: translateY(-50%);
}
-/* rtl:end:ignore */
-
-.bs-tooltip-auto {
- &[data-popper-placement^="top"] {
- @extend .bs-tooltip-top;
- }
- &[data-popper-placement^="right"] {
- @extend .bs-tooltip-end;
- }
- &[data-popper-placement^="bottom"] {
- @extend .bs-tooltip-bottom;
- }
- &[data-popper-placement^="left"] {
- @extend .bs-tooltip-start;
- }
+.tooltip[data-bs-placement="left"]::before {
+ top: 50%;
+ right: 0;
+ border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list
+ border-left-color: var(--#{$prefix}tooltip-bg);
+ transform: translateY(-50%);
}
// Wrapper for the tooltip content
$tooltip-opacity: .9 !default;
$tooltip-padding-y: $spacer * .25 !default;
$tooltip-padding-x: $spacer * .5 !default;
-$tooltip-margin: null !default; // TODO: remove this in v6
$tooltip-arrow-width: .8rem !default;
$tooltip-arrow-height: .4rem !default;
// Instantiate all tooltips in a docs or StackBlitz page
document.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach(tooltip => {
- new bootstrap.Tooltip(tooltip)
+ new bootstrap.Tooltip(tooltip, { trigger: 'click' })
})
// --------
Dropdowns are toggleable, contextual overlays for displaying lists of links and more. They're made interactive with the included Bootstrap dropdown JavaScript plugin. They're toggled by clicking, not by hovering; this is [an intentional design decision](https://markdotto.com/2012/02/27/bootstrap-explained-dropdowns/).
-Dropdowns are built on a third party library, [Popper](https://popper.js.org/), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js]({{< param "cdn.popper" >}}) before Bootstrap's JavaScript or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains Popper. Popper isn't used to position dropdowns in navbars though as dynamic positioning isn't required.
+Dropdowns are built on a third party library, [Popper](https://popper.js.org/), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js]({{< param "cdn.floating_ui" >}}) before Bootstrap's JavaScript or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains Popper. Popper isn't used to position dropdowns in navbars though as dynamic positioning isn't required.
## Accessibility
Things to know when using the popover plugin:
-- Popovers rely on the third party library [Popper](https://popper.js.org/) for positioning. You must include [popper.min.js]({{< param "cdn.popper" >}}) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.
+- Popovers rely on the third party library [Popper](https://popper.js.org/) for positioning. You must include [popper.min.js]({{< param "cdn.floating_ui" >}}) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.
- Popovers require the [popover plugin]({{< docsref "/components/popovers" >}}) as a dependency.
- Popovers are opt-in for performance reasons, so **you must initialize them yourself**.
- Zero-length `title` and `content` values will never show a popover.
Things to know when using the tooltip plugin:
-- Tooltips rely on the third party library [Popper](https://popper.js.org/) for positioning. You must include [popper.min.js]({{< param "cdn.popper" >}}) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.
+- Tooltips rely on the third party library [Popper](https://popper.js.org/) for positioning. You must include [popper.min.js]({{< param "cdn.floating_ui" >}}) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.
- Tooltips are opt-in for performance reasons, so **you must initialize them yourself**.
- Tooltips with zero-length titles are never displayed.
- Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc).
If you're using our compiled JavaScript and prefer to include Popper separately, add Popper before our JS, via a CDN preferably.
```html
-<script src="{{< param "cdn.popper" >}}" integrity="{{< param "cdn.popper_hash" >}}" crossorigin="anonymous"></script>
+<script src="{{< param "cdn.floating_ui" >}}" integrity="{{< param "cdn.floating_ui_hash" >}}" crossorigin="anonymous"></script>
<script src="{{< param "cdn.js" >}}" integrity="{{< param "cdn.js_hash" >}}" crossorigin="anonymous"></script>
```
You can also include [Popper](https://popper.js.org/) and our JS separately. If you don't plan to use dropdowns, popovers, or tooltips, save some kilobytes by not including Popper.
```html
- <script src="{{< param "cdn.popper" >}}" integrity="{{< param "cdn.popper_hash" >}}" crossorigin="anonymous"></script>
+ <script src="{{< param "cdn.floating_ui" >}}" integrity="{{< param "cdn.floating_ui_hash" >}}" crossorigin="anonymous"></script>
<script src="{{< param "cdn.js" >}}" integrity="{{< param "cdn.js_hash" >}}" crossorigin="anonymous"></script>
```
<script type="importmap">
{
"imports": {
- "@popperjs/core": "{{< param "cdn.popper" >}}",
+ "@popperjs/core": "{{< param "cdn.floating_ui" >}}",
"bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@{{< param "current_version" >}}/dist/js/bootstrap.esm.min.js"
}
}
<!-- Option 2: Separate Popper and Bootstrap JS -->
<!--
- <script src="{{< param "cdn.popper" >}}" integrity="{{< param "cdn.popper_hash" >}}" crossorigin="anonymous"></script>
+ <script src="{{< param "cdn.floating_ui" >}}" integrity="{{< param "cdn.floating_ui_hash" >}}" crossorigin="anonymous"></script>
<script src="{{< param "cdn.js" >}}" integrity="{{< param "cdn.js_hash" >}}" crossorigin="anonymous"></script>
-->
</body>