mirror of https://github.com/twbs/bootstrap.git
Update v6 to Floating UI
This commit is contained in:
parent
4fecde40b8
commit
166d5e7a13
|
|
@ -42,8 +42,8 @@ const files = [
|
||||||
configPropertyName: 'js_bundle_hash'
|
configPropertyName: 'js_bundle_hash'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
|
file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.min.js',
|
||||||
configPropertyName: 'popper_hash'
|
configPropertyName: 'floating_ui_hash'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const BUNDLE = process.env.BUNDLE === 'true'
|
||||||
const ESM = process.env.ESM === 'true'
|
const ESM = process.env.ESM === 'true'
|
||||||
|
|
||||||
let destinationFile = `bootstrap${ESM ? '.esm' : ''}`
|
let destinationFile = `bootstrap${ESM ? '.esm' : ''}`
|
||||||
const external = ['@popperjs/core']
|
const external = ['@floating-ui/dom']
|
||||||
const plugins = [
|
const plugins = [
|
||||||
babel({
|
babel({
|
||||||
// Only transpile our source code
|
// Only transpile our source code
|
||||||
|
|
@ -22,14 +22,14 @@ const plugins = [
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
const globals = {
|
const globals = {
|
||||||
'@popperjs/core': 'Popper'
|
'@floating-ui/dom': 'FloatingUIDOM'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BUNDLE) {
|
if (BUNDLE) {
|
||||||
destinationFile += '.bundle'
|
destinationFile += '.bundle'
|
||||||
// Remove last entry in external array to bundle Popper
|
// Remove last entry in external array to bundle FloatingUI
|
||||||
external.pop()
|
external.pop()
|
||||||
delete globals['@popperjs/core']
|
delete globals['@floating-ui/dom']
|
||||||
plugins.push(
|
plugins.push(
|
||||||
replace({
|
replace({
|
||||||
'process.env.NODE_ENV': '"production"',
|
'process.env.NODE_ENV': '"production"',
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,9 @@ cdn:
|
||||||
js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
|
js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
|
||||||
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
||||||
js_bundle_hash: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
js_bundle_hash: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
||||||
popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
|
floating_ui: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.12/dist/floating-ui.dom.umd.min.js"
|
||||||
popper_hash: "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
|
floating_ui_hash: "sha384-Os8n9bzoYJ/ESbGD7cW0VOTLk0hO++SO+Y4swXBE2dHrxiZkjADEr5ZGOcc9CorD"
|
||||||
popper_esm: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
|
floating_ui_esm: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.12/dist/floating-ui.dom.min.js"
|
||||||
|
|
||||||
anchors:
|
anchors:
|
||||||
min: 2
|
min: 2
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,18 @@
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Popper from '@popperjs/core'
|
import { inline, offset, shift } from '@floating-ui/dom'
|
||||||
import BaseComponent from './base-component.js'
|
import BaseComponent from './base-component.js'
|
||||||
import EventHandler from './dom/event-handler.js'
|
import EventHandler from './dom/event-handler.js'
|
||||||
import Manipulator from './dom/manipulator.js'
|
|
||||||
import SelectorEngine from './dom/selector-engine.js'
|
import SelectorEngine from './dom/selector-engine.js'
|
||||||
import {
|
import {
|
||||||
execute,
|
execute,
|
||||||
getElement,
|
|
||||||
getNextActiveElement,
|
getNextActiveElement,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isElement,
|
|
||||||
isRTL,
|
|
||||||
isVisible,
|
isVisible,
|
||||||
noop
|
noop
|
||||||
} from './util/index.js'
|
} from './util/index.js'
|
||||||
|
import FloatingUi from './util/floating-ui.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants
|
* Constants
|
||||||
|
|
@ -58,30 +55,28 @@ const SELECTOR_NAVBAR = '.navbar'
|
||||||
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
|
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
|
||||||
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
|
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_TOPCENTER = 'top'
|
||||||
|
const PLACEMENT_TOPEND = 'top-end'
|
||||||
|
const PLACEMENT_TOP = 'top-start'
|
||||||
const PLACEMENT_BOTTOMCENTER = 'bottom'
|
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 = {
|
const Default = {
|
||||||
autoClose: true,
|
autoClose: true,
|
||||||
boundary: 'clippingParents',
|
|
||||||
display: 'dynamic',
|
display: 'dynamic',
|
||||||
offset: [0, 2],
|
offset: 10,
|
||||||
popperConfig: null,
|
positionConfig: null,
|
||||||
reference: 'toggle'
|
reference: 'toggle'
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultType = {
|
const DefaultType = {
|
||||||
autoClose: '(boolean|string)',
|
autoClose: '(boolean|string)',
|
||||||
boundary: '(string|element)',
|
|
||||||
display: 'string',
|
display: 'string',
|
||||||
offset: '(array|string|function)',
|
offset: '(number|array|string|function)',
|
||||||
popperConfig: '(null|object|function)',
|
positionConfig: '(null|object|function)',
|
||||||
reference: '(string|element|object)'
|
reference: '(string|element|object)'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,13 +88,12 @@ class Dropdown extends BaseComponent {
|
||||||
constructor(element, config) {
|
constructor(element, config) {
|
||||||
super(element, config)
|
super(element, config)
|
||||||
|
|
||||||
this._popper = null
|
|
||||||
this._parent = this._element.parentNode // dropdown wrapper
|
this._parent = this._element.parentNode // dropdown wrapper
|
||||||
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
||||||
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||
|
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||
|
||||||
SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||
|
SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||
|
||||||
SelectorEngine.findOne(SELECTOR_MENU, this._parent)
|
SelectorEngine.findOne(SELECTOR_MENU, this._parent)
|
||||||
this._inNavbar = this._detectNavbar()
|
this._positionHelper = new FloatingUi(this._element)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
|
@ -135,7 +129,7 @@ class Dropdown extends BaseComponent {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._createPopper()
|
this.update()
|
||||||
|
|
||||||
// If this is a touch-enabled device we add extra
|
// If this is a touch-enabled device we add extra
|
||||||
// empty mouseover listeners to the body's immediate children;
|
// empty mouseover listeners to the body's immediate children;
|
||||||
|
|
@ -167,19 +161,9 @@ class Dropdown extends BaseComponent {
|
||||||
this._completeHide(relatedTarget)
|
this._completeHide(relatedTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
|
||||||
if (this._popper) {
|
|
||||||
this._popper.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
super.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this._inNavbar = this._detectNavbar()
|
const reference = this._positionHelper.getReferenceElement(this._config.reference, this._parent, NAME)
|
||||||
if (this._popper) {
|
this._positionHelper.calculate(reference, this._menu, this._getFloatingUiConfig())
|
||||||
this._popper.update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
@ -197,47 +181,28 @@ class Dropdown extends BaseComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._popper) {
|
|
||||||
this._popper.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
this._menu.classList.remove(CLASS_NAME_SHOW)
|
this._menu.classList.remove(CLASS_NAME_SHOW)
|
||||||
this._element.classList.remove(CLASS_NAME_SHOW)
|
this._element.classList.remove(CLASS_NAME_SHOW)
|
||||||
this._element.setAttribute('aria-expanded', 'false')
|
this._element.setAttribute('aria-expanded', 'false')
|
||||||
Manipulator.removeDataAttribute(this._menu, 'popper')
|
this._positionHelper.stop()
|
||||||
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
|
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
_getConfig(config) {
|
_getFloatingUiConfig() {
|
||||||
config = super._getConfig(config)
|
const defaultBsConfig = {
|
||||||
|
placement: this._getPlacement(),
|
||||||
if (typeof config.reference === 'object' && !isElement(config.reference) &&
|
middleware: [offset(this._positionHelper.parseOffset(this._config.offset)), shift()]
|
||||||
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
|
// Disable positioning if we have a static display or Dropdown is in Navbar
|
||||||
|
if (this._detectNavbar() || this._config.display === 'static') {
|
||||||
|
defaultBsConfig.middleware.push(inline())
|
||||||
}
|
}
|
||||||
|
|
||||||
_createPopper() {
|
return {
|
||||||
if (typeof Popper === 'undefined') {
|
...defaultBsConfig,
|
||||||
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)')
|
...execute(this._config.positionConfig, [undefined, defaultBsConfig])
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
const popperConfig = this._getPopperConfig()
|
|
||||||
this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_isShown() {
|
_isShown() {
|
||||||
|
|
@ -247,20 +212,15 @@ class Dropdown extends BaseComponent {
|
||||||
_getPlacement() {
|
_getPlacement() {
|
||||||
const parentDropdown = this._parent
|
const parentDropdown = this._parent
|
||||||
|
|
||||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
|
const matches = {
|
||||||
return PLACEMENT_RIGHT
|
[CLASS_NAME_DROPEND]: PLACEMENT_RIGHT,
|
||||||
|
[CLASS_NAME_DROPSTART]: PLACEMENT_LEFT,
|
||||||
|
[CLASS_NAME_DROPUP_CENTER]: PLACEMENT_TOPCENTER,
|
||||||
|
[CLASS_NAME_DROPDOWN_CENTER]: PLACEMENT_BOTTOMCENTER
|
||||||
}
|
}
|
||||||
|
const match = Object.keys(matches).find(keyClass => parentDropdown.classList.contains(keyClass))
|
||||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
|
if (match) {
|
||||||
return PLACEMENT_LEFT
|
return matches[match]
|
||||||
}
|
|
||||||
|
|
||||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
|
|
||||||
return PLACEMENT_TOPCENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
|
|
||||||
return PLACEMENT_BOTTOMCENTER
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to trim the value because custom properties can also include spaces
|
// We need to trim the value because custom properties can also include spaces
|
||||||
|
|
@ -277,52 +237,6 @@ class Dropdown extends BaseComponent {
|
||||||
return this._element.closest(SELECTOR_NAVBAR) !== null
|
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,
|
|
||||||
...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectMenuItem({ key, target }) {
|
_selectMenuItem({ key, target }) {
|
||||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))
|
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ const Default = {
|
||||||
offset: [0, 8],
|
offset: [0, 8],
|
||||||
placement: 'right',
|
placement: 'right',
|
||||||
template: '<div class="popover" role="tooltip">' +
|
template: '<div class="popover" role="tooltip">' +
|
||||||
'<div class="popover-arrow"></div>' +
|
'<div class="popover-inner">' +
|
||||||
'<h3 class="popover-header"></h3>' +
|
'<h3 class="popover-header"></h3>' +
|
||||||
'<div class="popover-body"></div>' +
|
'<div class="popover-body"></div>' +
|
||||||
|
'</div>' +
|
||||||
'</div>',
|
'</div>',
|
||||||
trigger: 'click'
|
trigger: 'click'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,18 @@
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Popper from '@popperjs/core'
|
import {
|
||||||
|
flip, hide, offset, shift
|
||||||
|
} from '@floating-ui/dom'
|
||||||
import BaseComponent from './base-component.js'
|
import BaseComponent from './base-component.js'
|
||||||
import EventHandler from './dom/event-handler.js'
|
import EventHandler from './dom/event-handler.js'
|
||||||
import Manipulator from './dom/manipulator.js'
|
|
||||||
import {
|
import {
|
||||||
execute, findShadowRoot, getElement, getUID, isRTL, noop
|
execute, findShadowRoot, getElement, getUID, isVisible, noop
|
||||||
} from './util/index.js'
|
} from './util/index.js'
|
||||||
|
import Manipulator from './dom/manipulator.js'
|
||||||
import { DefaultAllowlist } from './util/sanitizer.js'
|
import { DefaultAllowlist } from './util/sanitizer.js'
|
||||||
import TemplateFactory from './util/template-factory.js'
|
import TemplateFactory from './util/template-factory.js'
|
||||||
|
import FloatingUi from './util/floating-ui.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants
|
* Constants
|
||||||
|
|
@ -50,28 +53,26 @@ const EVENT_MOUSELEAVE = 'mouseleave'
|
||||||
const AttachmentMap = {
|
const AttachmentMap = {
|
||||||
AUTO: 'auto',
|
AUTO: 'auto',
|
||||||
TOP: 'top',
|
TOP: 'top',
|
||||||
RIGHT: isRTL() ? 'left' : 'right',
|
RIGHT: 'right',
|
||||||
BOTTOM: 'bottom',
|
BOTTOM: 'bottom',
|
||||||
LEFT: isRTL() ? 'right' : 'left'
|
LEFT: 'left'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Default = {
|
const Default = {
|
||||||
allowList: DefaultAllowlist,
|
allowList: DefaultAllowlist,
|
||||||
animation: true,
|
animation: true,
|
||||||
boundary: 'clippingParents',
|
|
||||||
container: false,
|
container: false,
|
||||||
customClass: '',
|
customClass: '',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
fallbackPlacements: ['top', 'right', 'bottom', 'left'],
|
fallbackPlacements: ['top', 'right', 'bottom', 'left'],
|
||||||
html: false,
|
html: false,
|
||||||
offset: [0, 6],
|
offset: 0,
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
popperConfig: null,
|
positionConfig: null,
|
||||||
sanitize: true,
|
sanitize: true,
|
||||||
sanitizeFn: null,
|
sanitizeFn: null,
|
||||||
selector: false,
|
selector: false,
|
||||||
template: '<div class="tooltip" role="tooltip">' +
|
template: '<div class="tooltip" role="tooltip">' +
|
||||||
'<div class="tooltip-arrow"></div>' +
|
|
||||||
'<div class="tooltip-inner"></div>' +
|
'<div class="tooltip-inner"></div>' +
|
||||||
'</div>',
|
'</div>',
|
||||||
title: '',
|
title: '',
|
||||||
|
|
@ -81,15 +82,14 @@ const Default = {
|
||||||
const DefaultType = {
|
const DefaultType = {
|
||||||
allowList: 'object',
|
allowList: 'object',
|
||||||
animation: 'boolean',
|
animation: 'boolean',
|
||||||
boundary: '(string|element)',
|
|
||||||
container: '(string|element|boolean)',
|
container: '(string|element|boolean)',
|
||||||
customClass: '(string|function)',
|
customClass: '(string|function)',
|
||||||
delay: '(number|object)',
|
delay: '(number|object)',
|
||||||
fallbackPlacements: 'array',
|
fallbackPlacements: 'array',
|
||||||
html: 'boolean',
|
html: 'boolean',
|
||||||
offset: '(array|string|function)',
|
offset: '(number|array|string|function)',
|
||||||
placement: '(string|function)',
|
placement: '(string|function)',
|
||||||
popperConfig: '(null|object|function)',
|
positionConfig: '(null|object|function)',
|
||||||
sanitize: 'boolean',
|
sanitize: 'boolean',
|
||||||
sanitizeFn: '(null|function)',
|
sanitizeFn: '(null|function)',
|
||||||
selector: '(string|boolean)',
|
selector: '(string|boolean)',
|
||||||
|
|
@ -104,10 +104,6 @@ const DefaultType = {
|
||||||
|
|
||||||
class Tooltip extends BaseComponent {
|
class Tooltip extends BaseComponent {
|
||||||
constructor(element, config) {
|
constructor(element, config) {
|
||||||
if (typeof Popper === 'undefined') {
|
|
||||||
throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)')
|
|
||||||
}
|
|
||||||
|
|
||||||
super(element, config)
|
super(element, config)
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
@ -115,7 +111,7 @@ class Tooltip extends BaseComponent {
|
||||||
this._timeout = 0
|
this._timeout = 0
|
||||||
this._isHovered = null
|
this._isHovered = null
|
||||||
this._activeTrigger = {}
|
this._activeTrigger = {}
|
||||||
this._popper = null
|
this._positionHelper = new FloatingUi(this._element)
|
||||||
this._templateFactory = null
|
this._templateFactory = null
|
||||||
this._newContent = null
|
this._newContent = null
|
||||||
|
|
||||||
|
|
@ -182,7 +178,7 @@ class Tooltip extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
if (this._element.style.display === 'none') {
|
if (!isVisible(this._element)) {
|
||||||
throw new Error('Please use show on visible elements')
|
throw new Error('Please use show on visible elements')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,22 +195,14 @@ class Tooltip extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: v6 remove this or make it optional
|
// TODO: v6 remove this or make it optional
|
||||||
this._disposePopper()
|
if (this.tip) {
|
||||||
|
this.tip.remove()
|
||||||
const tip = this._getTipElement()
|
this.tip = null
|
||||||
|
|
||||||
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._popper = this._createPopper(tip)
|
this.update()
|
||||||
|
|
||||||
tip.classList.add(CLASS_NAME_SHOW)
|
this.tip.classList.add(CLASS_NAME_SHOW)
|
||||||
|
|
||||||
// If this is a touch-enabled device we add extra
|
// If this is a touch-enabled device we add extra
|
||||||
// empty mouseover listeners to the body's immediate children;
|
// empty mouseover listeners to the body's immediate children;
|
||||||
|
|
@ -271,7 +259,8 @@ class Tooltip extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._isHovered) {
|
if (!this._isHovered) {
|
||||||
this._disposePopper()
|
this._positionHelper.stop()
|
||||||
|
this.tip.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
this._element.removeAttribute('aria-describedby')
|
this._element.removeAttribute('aria-describedby')
|
||||||
|
|
@ -282,9 +271,7 @@ class Tooltip extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
if (this._popper) {
|
this._positionHelper.calculate(this._element, this._getTipElement(), this._getFloatingUiConfig(), { position: 'fixed' })
|
||||||
this._popper.update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protected
|
// Protected
|
||||||
|
|
@ -295,6 +282,14 @@ class Tooltip extends BaseComponent {
|
||||||
_getTipElement() {
|
_getTipElement() {
|
||||||
if (!this.tip) {
|
if (!this.tip) {
|
||||||
this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())
|
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
|
return this.tip
|
||||||
|
|
@ -309,8 +304,6 @@ class Tooltip extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
|
tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
|
||||||
// TODO: v6 the following can be achieved with CSS only
|
|
||||||
tip.classList.add(`bs-${this.constructor.NAME}-auto`)
|
|
||||||
|
|
||||||
const tipId = getUID(this.constructor.NAME).toString()
|
const tipId = getUID(this.constructor.NAME).toString()
|
||||||
|
|
||||||
|
|
@ -326,7 +319,7 @@ class Tooltip extends BaseComponent {
|
||||||
setContent(content) {
|
setContent(content) {
|
||||||
this._newContent = content
|
this._newContent = content
|
||||||
if (this._isShown()) {
|
if (this._isShown()) {
|
||||||
this._disposePopper()
|
this._positionHelper.stop()
|
||||||
this.show()
|
this.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -370,77 +363,33 @@ class Tooltip extends BaseComponent {
|
||||||
return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)
|
return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)
|
||||||
}
|
}
|
||||||
|
|
||||||
_createPopper(tip) {
|
_getFloatingUiConfig() {
|
||||||
const placement = execute(this._config.placement, [this, tip, this._element])
|
const defaultBsConfig = {
|
||||||
const attachment = AttachmentMap[placement.toUpperCase()]
|
strategy: 'fixed',
|
||||||
return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
|
placement: this._getPlacement(),
|
||||||
}
|
middleware: [
|
||||||
|
offset(this._positionHelper.parseOffset(this._config.offset)),
|
||||||
_getOffset() {
|
flip({ fallbackPlacements: this._config.fallbackPlacements }),
|
||||||
const { offset } = this._config
|
shift(),
|
||||||
|
hide()
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolvePossibleFunction(arg) {
|
|
||||||
return execute(arg, [this._element, this._element])
|
|
||||||
}
|
|
||||||
|
|
||||||
_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 {
|
return {
|
||||||
...defaultBsPopperConfig,
|
...defaultBsConfig,
|
||||||
...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
|
...execute(this._config.positionConfig, [undefined, defaultBsConfig])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getPlacement() {
|
||||||
|
const placement = execute(this._config.placement, [this, this.tip, this._element])
|
||||||
|
return AttachmentMap[placement.toUpperCase()]
|
||||||
|
}
|
||||||
|
|
||||||
|
_resolvePossibleFunction(arg) {
|
||||||
|
return execute(arg, [this._element, this._element])
|
||||||
|
}
|
||||||
|
|
||||||
_setListeners() {
|
_setListeners() {
|
||||||
const triggers = this._config.trigger.split(' ')
|
const triggers = this._config.trigger.split(' ')
|
||||||
|
|
||||||
|
|
@ -593,17 +542,5 @@ class Tooltip extends BaseComponent {
|
||||||
// `Object.fromEntries(keysWithDifferentValues)`
|
// `Object.fromEntries(keysWithDifferentValues)`
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposePopper() {
|
|
||||||
if (this._popper) {
|
|
||||||
this._popper.destroy()
|
|
||||||
this._popper = null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.tip) {
|
|
||||||
this.tip.remove()
|
|
||||||
this.tip = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
export default Tooltip
|
export default Tooltip
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Bootstrap floating-ui.js
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { autoUpdate, computePosition } from '@floating-ui/dom'
|
||||||
|
import Manipulator from '../dom/manipulator.js'
|
||||||
|
import { getElement, isElement } from './index.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class definition
|
||||||
|
*/
|
||||||
|
class FloatingUi {
|
||||||
|
constructor(element) {
|
||||||
|
if (typeof computePosition === 'undefined') {
|
||||||
|
throw new TypeError('Bootstrap\'s tooltips and dropdowns require Floating UI (https://floating-ui.com/)')
|
||||||
|
}
|
||||||
|
|
||||||
|
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`
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
return popperData => value(popperData, this._element)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const values = value.split(',')
|
||||||
|
value = [
|
||||||
|
Number.parseInt(values[0], 10),
|
||||||
|
Number.parseInt(values[1] || 0, 10)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return {
|
||||||
|
mainAxis: value[0],
|
||||||
|
crossAxis: value[1] || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FloatingUi
|
||||||
|
|
@ -18,6 +18,9 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.0.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/markdown-remark": "^6.3.6",
|
"@astrojs/markdown-remark": "^6.3.6",
|
||||||
|
|
@ -28,7 +31,7 @@
|
||||||
"@babel/core": "^7.28.4",
|
"@babel/core": "^7.28.4",
|
||||||
"@babel/preset-env": "^7.28.3",
|
"@babel/preset-env": "^7.28.3",
|
||||||
"@docsearch/js": "^3.9.0",
|
"@docsearch/js": "^3.9.0",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@floating-ui/dom": "^1.0.0",
|
||||||
"@rollup/plugin-babel": "^6.0.4",
|
"@rollup/plugin-babel": "^6.0.4",
|
||||||
"@rollup/plugin-commonjs": "^28.0.6",
|
"@rollup/plugin-commonjs": "^28.0.6",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
|
|
@ -94,7 +97,7 @@
|
||||||
"zod": "^4.1.9"
|
"zod": "^4.1.9"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@popperjs/core": "^2.11.8"
|
"@floating-ui/dom": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adobe/css-tools": {
|
"node_modules/@adobe/css-tools": {
|
||||||
|
|
@ -2971,6 +2974,31 @@
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
|
||||||
|
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.2.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
|
||||||
|
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.7.3",
|
||||||
|
"@floating-ui/utils": "^0.2.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.2.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
|
||||||
|
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||||
|
|
@ -4099,17 +4127,6 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@popperjs/core": {
|
|
||||||
"version": "2.11.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
|
||||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/popperjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/plugin-babel": {
|
"node_modules/@rollup/plugin-babel": {
|
||||||
"version": "6.0.4",
|
"version": "6.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
|
||||||
|
|
|
||||||
11
package.json
11
package.json
|
|
@ -105,7 +105,10 @@
|
||||||
"astro-preview": "astro preview --root site --port 9001"
|
"astro-preview": "astro preview --root site --port 9001"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@popperjs/core": "^2.11.8"
|
"@floating-ui/dom": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
|
|
@ -117,7 +120,7 @@
|
||||||
"@babel/core": "^7.28.4",
|
"@babel/core": "^7.28.4",
|
||||||
"@babel/preset-env": "^7.28.3",
|
"@babel/preset-env": "^7.28.3",
|
||||||
"@docsearch/js": "^3.9.0",
|
"@docsearch/js": "^3.9.0",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@floating-ui/dom": "^1.0.0",
|
||||||
"@rollup/plugin-babel": "^6.0.4",
|
"@rollup/plugin-babel": "^6.0.4",
|
||||||
"@rollup/plugin-commonjs": "^28.0.6",
|
"@rollup/plugin-commonjs": "^28.0.6",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
|
|
@ -198,13 +201,13 @@
|
||||||
"shim": {
|
"shim": {
|
||||||
"js/bootstrap": {
|
"js/bootstrap": {
|
||||||
"deps": [
|
"deps": [
|
||||||
"@popperjs/core"
|
"@floating-ui/dom"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@popperjs/core": "^2.11.8"
|
"@floating-ui/dom": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,6 @@ $dropdown-dark-header-color: $gray-500 !default;
|
||||||
@include border-radius(var(--#{$prefix}dropdown-border-radius));
|
@include border-radius(var(--#{$prefix}dropdown-border-radius));
|
||||||
@include box-shadow(var(--#{$prefix}dropdown-box-shadow));
|
@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 {
|
@if $dropdown-padding-y == 0 {
|
||||||
> .dropdown-item:first-child,
|
> .dropdown-item:first-child,
|
||||||
|
|
@ -144,7 +139,7 @@ $dropdown-dark-header-color: $gray-500 !default;
|
||||||
|
|
||||||
// scss-docs-start responsive-breakpoints
|
// scss-docs-start responsive-breakpoints
|
||||||
// We deliberately hardcode the `bs-` prefix because we check
|
// 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) {
|
@each $breakpoint in map.keys($grid-breakpoints) {
|
||||||
@include media-breakpoint-up($breakpoint) {
|
@include media-breakpoint-up($breakpoint) {
|
||||||
|
|
@ -152,20 +147,10 @@ $dropdown-dark-header-color: $gray-500 !default;
|
||||||
|
|
||||||
.dropdown-menu#{$infix}-start {
|
.dropdown-menu#{$infix}-start {
|
||||||
--bs-position: start;
|
--bs-position: start;
|
||||||
|
|
||||||
&[data-bs-popper] {
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu#{$infix}-end {
|
.dropdown-menu#{$infix}-end {
|
||||||
--bs-position: end;
|
--bs-position: end;
|
||||||
|
|
||||||
&[data-bs-popper] {
|
|
||||||
right: 0;
|
|
||||||
left: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,26 +159,12 @@ $dropdown-dark-header-color: $gray-500 !default;
|
||||||
// Allow for dropdowns to go bottom up (aka, dropup-menu)
|
// Allow for dropdowns to go bottom up (aka, dropup-menu)
|
||||||
// Just add .dropup after the standard .dropdown class and you're set.
|
// Just add .dropup after the standard .dropdown class and you're set.
|
||||||
.dropup {
|
.dropup {
|
||||||
.dropdown-menu[data-bs-popper] {
|
|
||||||
top: auto;
|
|
||||||
bottom: 100%;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: var(--#{$prefix}dropdown-spacer);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
@include caret(up);
|
@include caret(up);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropend {
|
.dropend {
|
||||||
.dropdown-menu[data-bs-popper] {
|
|
||||||
top: 0;
|
|
||||||
right: auto;
|
|
||||||
left: 100%;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-left: var(--#{$prefix}dropdown-spacer);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
@include caret(end);
|
@include caret(end);
|
||||||
|
|
@ -204,14 +175,6 @@ $dropdown-dark-header-color: $gray-500 !default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropstart {
|
.dropstart {
|
||||||
.dropdown-menu[data-bs-popper] {
|
|
||||||
top: 0;
|
|
||||||
right: 100%;
|
|
||||||
left: auto;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-right: var(--#{$prefix}dropdown-spacer);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
@include caret(start);
|
@include caret(start);
|
||||||
&::before {
|
&::before {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,9 @@ $popover-arrow-height: .5rem !default;
|
||||||
--#{$prefix}popover-arrow-border: var(--#{$prefix}popover-border-color);
|
--#{$prefix}popover-arrow-border: var(--#{$prefix}popover-border-color);
|
||||||
// scss-docs-end popover-css-vars
|
// scss-docs-end popover-css-vars
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
z-index: var(--#{$prefix}popover-zindex);
|
z-index: var(--#{$prefix}popover-zindex);
|
||||||
display: block;
|
display: block;
|
||||||
max-width: var(--#{$prefix}popover-max-width);
|
max-width: var(--#{$prefix}popover-max-width);
|
||||||
|
|
@ -63,16 +66,6 @@ $popover-arrow-height: .5rem !default;
|
||||||
@include font-size(var(--#{$prefix}popover-font-size));
|
@include font-size(var(--#{$prefix}popover-font-size));
|
||||||
// Allow breaking very long words so they don't overflow the popover's bounds
|
// Allow breaking very long words so they don't overflow the popover's bounds
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
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,
|
&::before,
|
||||||
&::after {
|
&::after {
|
||||||
|
|
@ -82,16 +75,23 @@ $popover-arrow-height: .5rem !default;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-popover-top {
|
.popover-inner {
|
||||||
> .popover-arrow {
|
margin: var(--#{$prefix}popover-arrow-height);
|
||||||
bottom: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width));
|
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[data-bs-placement="top"] {
|
||||||
&::before,
|
&::before,
|
||||||
&::after {
|
&::after {
|
||||||
|
left: 50%;
|
||||||
border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0;
|
border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,40 +105,11 @@ $popover-arrow-height: .5rem !default;
|
||||||
border-top-color: var(--#{$prefix}popover-bg);
|
border-top-color: var(--#{$prefix}popover-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* rtl:begin:ignore */
|
|
||||||
.bs-popover-end {
|
|
||||||
> .popover-arrow {
|
|
||||||
left: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width));
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rtl:end:ignore */
|
|
||||||
|
|
||||||
.bs-popover-bottom {
|
|
||||||
> .popover-arrow {
|
|
||||||
top: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width));
|
|
||||||
|
|
||||||
|
.popover[data-bs-placement="bottom"] {
|
||||||
&::before,
|
&::before,
|
||||||
&::after {
|
&::after {
|
||||||
|
left: 50%;
|
||||||
border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height);
|
border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,29 +124,35 @@ $popover-arrow-height: .5rem !default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(-.5 * var(--#{$prefix}popover-arrow-width));
|
|
||||||
content: "";
|
|
||||||
border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rtl:begin:ignore */
|
/* rtl:begin:ignore */
|
||||||
.bs-popover-start {
|
|
||||||
> .popover-arrow {
|
|
||||||
right: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width));
|
|
||||||
width: var(--#{$prefix}popover-arrow-height);
|
|
||||||
height: var(--#{$prefix}popover-arrow-width);
|
|
||||||
|
|
||||||
|
.popover[data-bs-placement="right"] {
|
||||||
&::before,
|
&::before,
|
||||||
&::after {
|
&::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;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::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="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);
|
border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height);
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
|
@ -188,23 +165,20 @@ $popover-arrow-height: .5rem !default;
|
||||||
border-left-color: var(--#{$prefix}popover-bg);
|
border-left-color: var(--#{$prefix}popover-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* rtl:end:ignore */
|
/* rtl:end:ignore */
|
||||||
|
|
||||||
.bs-popover-auto {
|
// This will remove the popover-header's border just below the arrow
|
||||||
&[data-popper-placement^="top"] {
|
.popover-header::before {
|
||||||
@extend .bs-popover-top;
|
position: absolute;
|
||||||
}
|
top: 0;
|
||||||
&[data-popper-placement^="right"] {
|
left: 50%;
|
||||||
@extend .bs-popover-end;
|
display: block;
|
||||||
}
|
width: var(--#{$prefix}popover-arrow-width);
|
||||||
&[data-popper-placement^="bottom"] {
|
margin-left: calc(var(--#{$prefix}popover-arrow-width) * -.5);
|
||||||
@extend .bs-popover-bottom;
|
content: "";
|
||||||
}
|
border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg);
|
||||||
&[data-popper-placement^="left"] {
|
|
||||||
@extend .bs-popover-start;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offset the popover to account for the popover arrow
|
// Offset the popover to account for the popover arrow
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ $tooltip-border-radius: var(--#{$prefix}border-radius) !default;
|
||||||
$tooltip-opacity: .9 !default;
|
$tooltip-opacity: .9 !default;
|
||||||
$tooltip-padding-y: $spacer * .25 !default;
|
$tooltip-padding-y: $spacer * .25 !default;
|
||||||
$tooltip-padding-x: $spacer * .5 !default;
|
$tooltip-padding-x: $spacer * .5 !default;
|
||||||
$tooltip-margin: null !default; // TODO: remove this in v6
|
|
||||||
|
|
||||||
$tooltip-arrow-width: .8rem !default;
|
$tooltip-arrow-width: .8rem !default;
|
||||||
$tooltip-arrow-height: .4rem !default;
|
$tooltip-arrow-height: .4rem !default;
|
||||||
|
|
@ -38,7 +37,6 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default;
|
||||||
--#{$prefix}tooltip-max-width: #{$tooltip-max-width};
|
--#{$prefix}tooltip-max-width: #{$tooltip-max-width};
|
||||||
--#{$prefix}tooltip-padding-x: #{$tooltip-padding-x};
|
--#{$prefix}tooltip-padding-x: #{$tooltip-padding-x};
|
||||||
--#{$prefix}tooltip-padding-y: #{$tooltip-padding-y};
|
--#{$prefix}tooltip-padding-y: #{$tooltip-padding-y};
|
||||||
--#{$prefix}tooltip-margin: #{$tooltip-margin};
|
|
||||||
@include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size);
|
@include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size);
|
||||||
--#{$prefix}tooltip-color: #{$tooltip-color};
|
--#{$prefix}tooltip-color: #{$tooltip-color};
|
||||||
--#{$prefix}tooltip-bg: #{$tooltip-bg};
|
--#{$prefix}tooltip-bg: #{$tooltip-bg};
|
||||||
|
|
@ -48,10 +46,12 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default;
|
||||||
--#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height};
|
--#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height};
|
||||||
// scss-docs-end tooltip-css-vars
|
// scss-docs-end tooltip-css-vars
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
z-index: var(--#{$prefix}tooltip-zindex);
|
z-index: var(--#{$prefix}tooltip-zindex);
|
||||||
display: block;
|
display: block;
|
||||||
margin: var(--#{$prefix}tooltip-margin);
|
padding: var(--#{$prefix}tooltip-arrow-height);
|
||||||
@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.
|
// 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.
|
// So reset our font and text properties to avoid inheriting weird values.
|
||||||
@include reset-text();
|
@include reset-text();
|
||||||
|
|
@ -62,83 +62,43 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default;
|
||||||
|
|
||||||
&.show { opacity: var(--#{$prefix}tooltip-opacity); }
|
&.show { opacity: var(--#{$prefix}tooltip-opacity); }
|
||||||
|
|
||||||
.tooltip-arrow {
|
|
||||||
display: block;
|
|
||||||
width: var(--#{$prefix}tooltip-arrow-width);
|
|
||||||
height: var(--#{$prefix}tooltip-arrow-height);
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-tooltip-top .tooltip-arrow {
|
.tooltip[data-bs-placement="top"]::before {
|
||||||
bottom: calc(-1 * var(--#{$prefix}tooltip-arrow-height));
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
&::before {
|
|
||||||
top: -1px;
|
|
||||||
border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0;
|
border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0;
|
||||||
border-top-color: var(--#{$prefix}tooltip-bg);
|
border-top-color: var(--#{$prefix}tooltip-bg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* rtl:begin:ignore */
|
.tooltip[data-bs-placement="bottom"]::before {
|
||||||
.bs-tooltip-end .tooltip-arrow {
|
top: 0;
|
||||||
left: calc(-1 * var(--#{$prefix}tooltip-arrow-height));
|
left: 50%;
|
||||||
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;
|
|
||||||
border-right-color: var(--#{$prefix}tooltip-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rtl:end:ignore */
|
|
||||||
|
|
||||||
.bs-tooltip-bottom .tooltip-arrow {
|
|
||||||
top: calc(-1 * var(--#{$prefix}tooltip-arrow-height));
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
bottom: -1px;
|
|
||||||
border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height);
|
border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height);
|
||||||
border-bottom-color: var(--#{$prefix}tooltip-bg);
|
border-bottom-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;
|
||||||
|
border-right-color: var(--#{$prefix}tooltip-bg);
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rtl:begin:ignore */
|
.tooltip[data-bs-placement="left"]::before {
|
||||||
.bs-tooltip-start .tooltip-arrow {
|
top: 50%;
|
||||||
right: calc(-1 * var(--#{$prefix}tooltip-arrow-height));
|
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);
|
border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height);
|
||||||
border-left-color: var(--#{$prefix}tooltip-bg);
|
border-left-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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper for the tooltip content
|
// Wrapper for the tooltip content
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ toc: true
|
||||||
|
|
||||||
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/blog/bootstrap-explained-dropdowns/).
|
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/blog/bootstrap-explained-dropdowns/).
|
||||||
|
|
||||||
Dropdowns are built on a third party library, [Popper](https://popper.js.org/docs/v2/), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js]([[config: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, [Floating UI](https://floating-ui.com/), which provides dynamic positioning and viewport detection. Be sure to include [floating-ui.dom.umd.min.js]([[config:cdn.floating_ui]]) before Bootstrap's JavaScript or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains Floating UI. Floating UI isn't used to position dropdowns in navbars though as dynamic positioning isn't required.
|
||||||
|
|
||||||
## Accessibility
|
## Accessibility
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ toc: true
|
||||||
|
|
||||||
Things to know when using the popover plugin:
|
Things to know when using the popover plugin:
|
||||||
|
|
||||||
- Popovers rely on the third party library [Popper](https://popper.js.org/docs/v2/) for positioning. You must include [popper.min.js]([[config:cdn.popper]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.
|
- Popovers rely on the third party library [Floating UI](https://floating-ui.com/) for positioning. You must include [floating-ui.dom.umd.min.js]([[config:cdn.floating_ui]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Floating UI.
|
||||||
- Popovers require the [popover plugin]([[docsref:/components/popovers]]) as a dependency.
|
- Popovers require the [popover plugin]([[docsref:/components/popovers]]) as a dependency.
|
||||||
- Popovers are opt-in for performance reasons, so **you must initialize them yourself**.
|
- Popovers are opt-in for performance reasons, so **you must initialize them yourself**.
|
||||||
- Zero-length `title` and `content` values will never show a popover.
|
- Zero-length `title` and `content` values will never show a popover.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ toc: true
|
||||||
|
|
||||||
Things to know when using the tooltip plugin:
|
Things to know when using the tooltip plugin:
|
||||||
|
|
||||||
- Tooltips rely on the third party library [Popper](https://popper.js.org/docs/v2/) for positioning. You must include [popper.min.js]([[config:cdn.popper]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper.
|
- Tooltips rely on the third party library [Floating UI](https://floating-ui.com/) for positioning. You must include [floating-ui.dom.umd.min.js]([[config:cdn.floating_ui]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Floating UI.
|
||||||
- Tooltips are opt-in for performance reasons, so **you must initialize them yourself**.
|
- Tooltips are opt-in for performance reasons, so **you must initialize them yourself**.
|
||||||
- Tooltips with zero-length titles are never displayed.
|
- 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).
|
- Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc).
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Download ready-to-use compiled code for **Bootstrap v[[config:current_version]]*
|
||||||
- Compiled and minified CSS bundles (see [CSS files comparison]([[docsref:/getting-started/contents#css-files]]))
|
- Compiled and minified CSS bundles (see [CSS files comparison]([[docsref:/getting-started/contents#css-files]]))
|
||||||
- Compiled and minified JavaScript plugins (see [JS files comparison]([[docsref:/getting-started/contents#js-files]]))
|
- Compiled and minified JavaScript plugins (see [JS files comparison]([[docsref:/getting-started/contents#js-files]]))
|
||||||
|
|
||||||
This doesn’t include documentation, source files, or any optional JavaScript dependencies like Popper.
|
This doesn't include documentation, source files, or any optional JavaScript dependencies like Floating UI.
|
||||||
|
|
||||||
<a href="[[config:download.dist]]" class="btn btn-bd-primary">Download</a>
|
<a href="[[config:download.dist]]" class="btn btn-bd-primary">Download</a>
|
||||||
|
|
||||||
|
|
@ -41,10 +41,10 @@ Skip the download with [jsDelivr](https://www.jsdelivr.com/) to deliver cached v
|
||||||
<script src="[[config:cdn.js_bundle]]" integrity="[[config:cdn.js_bundle_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.js_bundle]]" integrity="[[config:cdn.js_bundle_hash]]" crossorigin="anonymous"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
If you’re using our compiled JavaScript and prefer to include Popper separately, add Popper before our JS, via a CDN preferably.
|
If you're using our compiled JavaScript and prefer to include Floating UI separately, add Floating UI before our JS, via a CDN preferably.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="[[config:cdn.popper]]" integrity="[[config:cdn.popper_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.floating_ui]]" integrity="[[config:cdn.floating_ui_hash]]" crossorigin="anonymous"></script>
|
||||||
<script src="[[config:cdn.js]]" integrity="[[config:cdn.js_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.js]]" integrity="[[config:cdn.js_hash]]" crossorigin="anonymous"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ Get started by including Bootstrap’s production-ready CSS and JavaScript via C
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Include Bootstrap’s CSS and JS.** Place the `<link>` tag in the `<head>` for our CSS, and the `<script>` tag for our JavaScript bundle (including Popper for positioning dropdowns, popovers, and tooltips) before the closing `</body>`. Learn more about our [CDN links](#cdn-links).
|
2. **Include Bootstrap's CSS and JS.** Place the `<link>` tag in the `<head>` for our CSS, and the `<script>` tag for our JavaScript bundle (including Floating UI for positioning dropdowns, popovers, and tooltips) before the closing `</body>`. Learn more about our [CDN links](#cdn-links).
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
|
@ -48,10 +48,10 @@ Get started by including Bootstrap’s production-ready CSS and JavaScript via C
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also include [Popper](https://popper.js.org/docs/v2/) and our JS separately. If you don’t plan to use dropdowns, popovers, or tooltips, save some kilobytes by not including Popper.
|
You can also include [Floating UI](https://floating-ui.com/) and our JS separately. If you don't plan to use dropdowns, popovers, or tooltips, save some kilobytes by not including Floating UI.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="[[config:cdn.popper]]" integrity="[[config:cdn.popper_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.floating_ui]]" integrity="[[config:cdn.floating_ui_hash]]" crossorigin="anonymous"></script>
|
||||||
<script src="[[config:cdn.js]]" integrity="[[config:cdn.js_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.js]]" integrity="[[config:cdn.js_hash]]" crossorigin="anonymous"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,19 +41,19 @@ We provide a version of Bootstrap built as `ESM` (`bootstrap.esm.js` and `bootst
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Compared to JS bundlers, using ESM in the browser requires you to use the full path and filename instead of the module name. [Read more about JS modules in the browser.](https://v8.dev/features/modules#specifiers) That’s why we use `'bootstrap.esm.min.js'` instead of `'bootstrap'` above. However, this is further complicated by our Popper dependency, which imports Popper into our JavaScript like so:
|
Compared to JS bundlers, using ESM in the browser requires you to use the full path and filename instead of the module name. [Read more about JS modules in the browser.](https://v8.dev/features/modules#specifiers) That’s why we use `'bootstrap.esm.min.js'` instead of `'bootstrap'` above. However, this is further complicated by our Floating UI dependency, which imports Floating UI into our JavaScript like so:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import * as Popper from "@popperjs/core"
|
import { computePosition } from "@floating-ui/dom"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you try this as-is, you’ll see an error in the console like the following:
|
If you try this as-is, you'll see an error in the console like the following:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Uncaught TypeError: Failed to resolve module specifier "@popperjs/core". Relative references must start with either "/", "./", or "../".
|
Uncaught TypeError: Failed to resolve module specifier "@floating-ui/dom". Relative references must start with either "/", "./", or "../".
|
||||||
```
|
```
|
||||||
|
|
||||||
To fix this, you can use an `importmap` to resolve the arbitrary module names to complete paths. If your [targeted browsers](https://caniuse.com/?search=importmap) do not support `importmap`, you’ll need to use the [es-module-shims](https://github.com/guybedford/es-module-shims) project. Here’s how it works for Bootstrap and Popper:
|
To fix this, you can use an `importmap` to resolve the arbitrary module names to complete paths. If your [targeted browsers](https://caniuse.com/?search=importmap) do not support `importmap`, you'll need to use the [es-module-shims](https://github.com/guybedford/es-module-shims) project. Here's how it works for Bootstrap and Floating UI:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
|
@ -72,7 +72,7 @@ To fix this, you can use an `importmap` to resolve the arbitrary module names to
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"@popperjs/core": "[[config:cdn.popper_esm]]",
|
"@floating-ui/dom": "[[config:cdn.floating_ui_esm]]",
|
||||||
"bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@[[config:current_version]]/dist/js/bootstrap.esm.min.js"
|
"bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@[[config:current_version]]/dist/js/bootstrap.esm.min.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,12 @@ You can see the above requirements reflected in this modified RTL starter templa
|
||||||
|
|
||||||
<!-- Optional JavaScript; choose one of the two! -->
|
<!-- Optional JavaScript; choose one of the two! -->
|
||||||
|
|
||||||
<!-- Option 1: Bootstrap Bundle with Popper -->
|
<!-- Option 1: Bootstrap Bundle with Floating UI -->
|
||||||
<script src="[[config:cdn.js_bundle]]" integrity="[[config:cdn.js_bundle_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.js_bundle]]" integrity="[[config:cdn.js_bundle_hash]]" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Option 2: Separate Popper and Bootstrap JS -->
|
<!-- Option 2: Separate Floating UI and Bootstrap JS -->
|
||||||
<!--
|
<!--
|
||||||
<script src="[[config:cdn.popper]]" integrity="[[config:cdn.popper_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.floating_ui]]" integrity="[[config:cdn.floating_ui_hash]]" crossorigin="anonymous"></script>
|
||||||
<script src="[[config:cdn.js]]" integrity="[[config:cdn.js_hash]]" crossorigin="anonymous"></script>
|
<script src="[[config:cdn.js]]" integrity="[[config:cdn.js_hash]]" crossorigin="anonymous"></script>
|
||||||
-->
|
-->
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ const configSchema = z.object({
|
||||||
js_hash: z.string(),
|
js_hash: z.string(),
|
||||||
js_bundle: z.string().url(),
|
js_bundle: z.string().url(),
|
||||||
js_bundle_hash: z.string(),
|
js_bundle_hash: z.string(),
|
||||||
popper: z.string().url(),
|
floating_ui: z.string().url(),
|
||||||
popper_esm: z.string().url(),
|
floating_ui_esm: z.string().url(),
|
||||||
popper_hash: z.string()
|
floating_ui_hash: z.string()
|
||||||
}),
|
}),
|
||||||
current_version: zVersionSemver,
|
current_version: zVersionSemver,
|
||||||
current_ruby_version: zVersionSemver,
|
current_ruby_version: zVersionSemver,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue