Update to popper.js v2.x

This commit is contained in:
Johann-S 2020-06-19 11:17:01 +03:00 committed by XhmikosR
parent 5f89ea3a0f
commit adfdf7160b
26 changed files with 145 additions and 297 deletions

View File

@ -14,6 +14,7 @@ const rollup = require('rollup')
const { babel } = require('@rollup/plugin-babel') const { babel } = require('@rollup/plugin-babel')
const banner = require('./banner.js') const banner = require('./banner.js')
const rootPath = path.resolve(__dirname, '../js/dist/')
const plugins = [ const plugins = [
babel({ babel({
// Only transpile our source code // Only transpile our source code
@ -39,7 +40,6 @@ const bsPlugins = {
Toast: path.resolve(__dirname, '../js/src/toast.js'), Toast: path.resolve(__dirname, '../js/src/toast.js'),
Tooltip: path.resolve(__dirname, '../js/src/tooltip.js') Tooltip: path.resolve(__dirname, '../js/src/tooltip.js')
} }
const rootPath = path.resolve(__dirname, '../js/dist/')
const defaultPluginConfig = { const defaultPluginConfig = {
external: [ external: [
@ -87,9 +87,9 @@ const getConfigByPluginKey = pluginKey => {
if (pluginKey === 'Dropdown' || pluginKey === 'Tooltip') { if (pluginKey === 'Dropdown' || pluginKey === 'Tooltip') {
const config = Object.assign(defaultPluginConfig) const config = Object.assign(defaultPluginConfig)
config.external.push(bsPlugins.Manipulator, 'popper.js') config.external.push(bsPlugins.Manipulator, '@popperjs/core')
config.globals[bsPlugins.Manipulator] = 'Manipulator' config.globals[bsPlugins.Manipulator] = 'Manipulator'
config.globals['popper.js'] = 'Popper' config.globals['@popperjs/core'] = 'Popper'
return config return config
} }

View File

@ -42,7 +42,7 @@ const files = [
configPropertyName: 'js_bundle_hash' configPropertyName: 'js_bundle_hash'
}, },
{ {
file: 'node_modules/popper.js/dist/umd/popper.min.js', file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
configPropertyName: 'popper_hash' configPropertyName: 'popper_hash'
} }
] ]

View File

@ -3,13 +3,14 @@
const path = require('path') const path = require('path')
const { babel } = require('@rollup/plugin-babel') const { babel } = require('@rollup/plugin-babel')
const { nodeResolve } = require('@rollup/plugin-node-resolve') const { nodeResolve } = require('@rollup/plugin-node-resolve')
const replace = require('@rollup/plugin-replace')
const banner = require('./banner.js') const banner = require('./banner.js')
const BUNDLE = process.env.BUNDLE === 'true' const BUNDLE = process.env.BUNDLE === 'true'
const ESM = process.env.ESM === 'true' const ESM = process.env.ESM === 'true'
let fileDest = `bootstrap${ESM ? '.esm' : ''}` let fileDest = `bootstrap${ESM ? '.esm' : ''}`
const external = ['popper.js'] const external = ['@popperjs/core']
const plugins = [ const plugins = [
babel({ babel({
// Only transpile our source code // Only transpile our source code
@ -19,15 +20,15 @@ const plugins = [
}) })
] ]
const globals = { const globals = {
'popper.js': 'Popper' '@popperjs/core': 'Popper'
} }
if (BUNDLE) { if (BUNDLE) {
fileDest += '.bundle' fileDest += '.bundle'
// Remove last entry in external array to bundle Popper // Remove last entry in external array to bundle Popper
external.pop() external.pop()
delete globals['popper.js'] delete globals['@popperjs/core']
plugins.push(nodeResolve()) plugins.push(replace({ 'process.env.NODE_ENV': '"production"' }), nodeResolve())
} }
const rollupConfig = { const rollupConfig = {

View File

@ -75,5 +75,5 @@ params:
js_hash: "sha384-supZtwqjyYg6XvvTCi4/w6J6Hm6IKqXaaeoyGhIhonCdkSvA70sSucW7OqXIo4lZ" js_hash: "sha384-supZtwqjyYg6XvvTCi4/w6J6Hm6IKqXaaeoyGhIhonCdkSvA70sSucW7OqXIo4lZ"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-alpha3/dist/js/bootstrap.bundle.min.js" js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-alpha3/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-G/J8d6sz9bTod37AsZzNtTwT77J24FKjJEO1YsU2vW7iPcmYP3/tznu+LcK824t9" js_bundle_hash: "sha384-G/J8d6sz9bTod37AsZzNtTwT77J24FKjJEO1YsU2vW7iPcmYP3/tznu+LcK824t9"
popper: "https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.4.2/dist/umd/popper.min.js"
popper_hash: "sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" popper_hash: "sha384-a46n7BtEJaPKKs2SeVxZzwKkapYzBUr8c7DyCLEpkRrs4LE03nlh53ZSOPgkJB7U"

View File

@ -5,6 +5,8 @@
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import * as Popper from '@popperjs/core'
import { import {
getjQuery, getjQuery,
onDOMContentLoaded, onDOMContentLoaded,
@ -18,7 +20,6 @@ import {
import Data from './dom/data' import Data from './dom/data'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator' import Manipulator from './dom/manipulator'
import Popper from 'popper.js'
import SelectorEngine from './dom/selector-engine' import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component' import BaseComponent from './base-component'
@ -58,7 +59,6 @@ const CLASS_NAME_DROPEND = 'dropend'
const CLASS_NAME_DROPSTART = 'dropstart' const CLASS_NAME_DROPSTART = 'dropstart'
const CLASS_NAME_MENUEND = 'dropdown-menu-end' const CLASS_NAME_MENUEND = 'dropdown-menu-end'
const CLASS_NAME_NAVBAR = 'navbar' const CLASS_NAME_NAVBAR = 'navbar'
const CLASS_NAME_POSITION_STATIC = 'position-static'
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]'
const SELECTOR_FORM_CHILD = '.dropdown form' const SELECTOR_FORM_CHILD = '.dropdown form'
@ -76,7 +76,7 @@ const PLACEMENT_LEFT = isRTL ? 'right-start' : 'left-start'
const Default = { const Default = {
offset: 0, offset: 0,
flip: true, flip: true,
boundary: 'scrollParent', boundary: 'clippingParents',
reference: 'toggle', reference: 'toggle',
display: 'dynamic', display: 'dynamic',
popperConfig: null popperConfig: null
@ -176,14 +176,7 @@ class Dropdown extends BaseComponent {
} }
} }
// If boundary is not `scrollParent`, then set position to `static` this._popper = Popper.createPopper(referenceElement, this._menu, this._getPopperConfig())
// to allow the menu to "escape" the scroll parent's boundaries
// https://github.com/twbs/bootstrap/issues/24251
if (this._config.boundary !== 'scrollParent') {
parent.classList.add(CLASS_NAME_POSITION_STATIC)
}
this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig())
} }
// If this is a touch-enabled device we add extra // If this is a touch-enabled device we add extra
@ -233,6 +226,7 @@ class Dropdown extends BaseComponent {
super.dispose() super.dispose()
EventHandler.off(this._element, EVENT_KEY) EventHandler.off(this._element, EVENT_KEY)
this._menu = null this._menu = null
if (this._popper) { if (this._popper) {
this._popper.destroy() this._popper.destroy()
this._popper = null this._popper = null
@ -242,7 +236,7 @@ class Dropdown extends BaseComponent {
update() { update() {
this._inNavbar = this._detectNavbar() this._inNavbar = this._detectNavbar()
if (this._popper) { if (this._popper) {
this._popper.scheduleUpdate() this._popper.update()
} }
} }
@ -296,44 +290,24 @@ class Dropdown extends BaseComponent {
return Boolean(this._element.closest(`.${CLASS_NAME_NAVBAR}`)) return Boolean(this._element.closest(`.${CLASS_NAME_NAVBAR}`))
} }
_getOffset() {
const offset = {}
if (typeof this._config.offset === 'function') {
offset.fn = data => {
data.offsets = {
...data.offsets,
...(this._config.offset(data.offsets, this._element) || {})
}
return data
}
} else {
offset.offset = this._config.offset
}
return offset
}
_getPopperConfig() { _getPopperConfig() {
const popperConfig = { const popperConfig = {
placement: this._getPlacement(), placement: this._getPlacement(),
modifiers: { modifiers: [{
offset: this._getOffset(), name: 'preventOverflow',
flip: { options: {
enabled: this._config.flip altBoundary: this._config.flip,
}, rootBoundary: this._config.boundary
preventOverflow: {
boundariesElement: this._config.boundary
} }
} }]
} }
// Disable Popper if we have a static display // Disable Popper if we have a static display
if (this._config.display === 'static') { if (this._config.display === 'static') {
popperConfig.modifiers.applyStyle = { popperConfig.modifiers = [{
name: 'applyStyles',
enabled: false enabled: false
} }]
} }
return { return {

View File

@ -5,6 +5,8 @@
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import * as Popper from '@popperjs/core'
import { import {
getjQuery, getjQuery,
onDOMContentLoaded, onDOMContentLoaded,
@ -25,7 +27,6 @@ import {
import Data from './dom/data' import Data from './dom/data'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator' import Manipulator from './dom/manipulator'
import Popper from 'popper.js'
import SelectorEngine from './dom/selector-engine' import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component' import BaseComponent from './base-component'
@ -51,9 +52,8 @@ const DefaultType = {
html: 'boolean', html: 'boolean',
selector: '(string|boolean)', selector: '(string|boolean)',
placement: '(string|function)', placement: '(string|function)',
offset: '(number|string|function)',
container: '(string|element|boolean)', container: '(string|element|boolean)',
fallbackPlacement: '(string|array)', fallbackPlacements: '(null|array)',
boundary: '(string|element)', boundary: '(string|element)',
customClass: '(string|function)', customClass: '(string|function)',
sanitize: 'boolean', sanitize: 'boolean',
@ -82,10 +82,9 @@ const Default = {
html: false, html: false,
selector: false, selector: false,
placement: 'top', placement: 'top',
offset: 0,
container: false, container: false,
fallbackPlacement: 'flip', fallbackPlacements: null,
boundary: 'scrollParent', boundary: 'clippingParents',
customClass: '', customClass: '',
sanitize: true, sanitize: true,
sanitizeFn: null, sanitizeFn: null,
@ -287,7 +286,7 @@ class Tooltip extends BaseComponent {
EventHandler.trigger(this._element, this.constructor.Event.INSERTED) EventHandler.trigger(this._element, this.constructor.Event.INSERTED)
this._popper = new Popper(this._element, tip, this._getPopperConfig(attachment)) this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
tip.classList.add(CLASS_NAME_SHOW) tip.classList.add(CLASS_NAME_SHOW)
@ -307,13 +306,9 @@ class Tooltip extends BaseComponent {
} }
const complete = () => { const complete = () => {
if (this.config.animation) {
this._fixTransition()
}
const prevHoverState = this._hoverState const prevHoverState = this._hoverState
this._hoverState = null
this._hoverState = null
EventHandler.trigger(this._element, this.constructor.Event.SHOWN) EventHandler.trigger(this._element, this.constructor.Event.SHOWN)
if (prevHoverState === HOVER_STATE_OUT) { if (prevHoverState === HOVER_STATE_OUT) {
@ -345,7 +340,11 @@ class Tooltip extends BaseComponent {
this._cleanTipClass() this._cleanTipClass()
this._element.removeAttribute('aria-describedby') this._element.removeAttribute('aria-describedby')
EventHandler.trigger(this._element, this.constructor.Event.HIDDEN) EventHandler.trigger(this._element, this.constructor.Event.HIDDEN)
this._popper.destroy()
if (this._popper) {
this._popper.destroy()
this._popper = null
}
} }
const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE) const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE)
@ -380,7 +379,7 @@ class Tooltip extends BaseComponent {
update() { update() {
if (this._popper !== null) { if (this._popper !== null) {
this._popper.scheduleUpdate() this._popper.update()
} }
} }
@ -469,26 +468,45 @@ class Tooltip extends BaseComponent {
// Private // Private
_getPopperConfig(attachment) { _getPopperConfig(attachment) {
const flipModifier = {
name: 'flip',
options: {
altBoundary: true
}
}
if (this.config.fallbackPlacements) {
flipModifier.options.fallbackPlacements = this.config.fallbackPlacements
}
const defaultBsConfig = { const defaultBsConfig = {
placement: attachment, placement: attachment,
modifiers: { modifiers: [
offset: this._getOffset(), flipModifier,
flip: { {
behavior: this.config.fallbackPlacement name: 'preventOverflow',
options: {
rootBoundary: this.config.boundary
}
}, },
arrow: { {
element: `.${this.constructor.NAME}-arrow` name: 'arrow',
options: {
element: `.${this.constructor.NAME}-arrow`
}
}, },
preventOverflow: { {
boundariesElement: this.config.boundary name: 'onChange',
enabled: true,
phase: 'afterWrite',
fn: data => this._handlePopperPlacementChange(data)
} }
}, ],
onCreate: data => { onFirstUpdate: data => {
if (data.originalPlacement !== data.placement) { if (data.options.placement !== data.placement) {
this._handlePopperPlacementChange(data) this._handlePopperPlacementChange(data)
} }
}, }
onUpdate: data => this._handlePopperPlacementChange(data)
} }
return { return {
@ -501,25 +519,6 @@ class Tooltip extends BaseComponent {
this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`) this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`)
} }
_getOffset() {
const offset = {}
if (typeof this.config.offset === 'function') {
offset.fn = data => {
data.offsets = {
...data.offsets,
...(this.config.offset(data.offsets, this._element) || {})
}
return data
}
} else {
offset.offset = this.config.offset
}
return offset
}
_getContainer() { _getContainer() {
if (this.config.container === false) { if (this.config.container === false) {
return document.body return document.body
@ -743,23 +742,15 @@ class Tooltip extends BaseComponent {
} }
_handlePopperPlacementChange(popperData) { _handlePopperPlacementChange(popperData) {
this.tip = popperData.instance.popper const { state } = popperData
this._cleanTipClass()
this._addAttachmentClass(this._getAttachment(popperData.placement))
}
_fixTransition() { if (!state) {
const tip = this.getTipElement()
const initConfigAnimation = this.config.animation
if (tip.getAttribute('x-placement') !== null) {
return return
} }
tip.classList.remove(CLASS_NAME_FADE) this.tip = state.elements.popper
this.config.animation = false this._cleanTipClass()
this.hide() this._addAttachmentClass(this._getAttachment(state.placement))
this.show()
this.config.animation = initConfigAnimation
} }
// Static // Static

View File

@ -1,5 +1,5 @@
import 'popper.js'
import Tooltip from '../../dist/tooltip' import Tooltip from '../../dist/tooltip'
import '../../dist/carousel'
window.addEventListener('load', () => { window.addEventListener('load', () => {
[].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]')) [].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]'))

View File

@ -1,4 +1,3 @@
import 'popper.js'
import { Tooltip } from '../../../dist/js/bootstrap.esm.js' import { Tooltip } from '../../../dist/js/bootstrap.esm.js'
window.addEventListener('load', () => { window.addEventListener('load', () => {

View File

@ -2,6 +2,7 @@
const { babel } = require('@rollup/plugin-babel') const { babel } = require('@rollup/plugin-babel')
const { nodeResolve } = require('@rollup/plugin-node-resolve') const { nodeResolve } = require('@rollup/plugin-node-resolve')
const replace = require('@rollup/plugin-replace')
module.exports = { module.exports = {
input: 'js/tests/integration/bundle.js', input: 'js/tests/integration/bundle.js',
@ -10,6 +11,9 @@ module.exports = {
format: 'iife' format: 'iife'
}, },
plugins: [ plugins: [
replace({
'process.env.NODE_ENV': '"production"'
}),
nodeResolve(), nodeResolve(),
babel({ babel({
exclude: 'node_modules/**', exclude: 'node_modules/**',

View File

@ -5,6 +5,7 @@ const ip = require('ip')
const { babel } = require('@rollup/plugin-babel') const { babel } = require('@rollup/plugin-babel')
const istanbul = require('rollup-plugin-istanbul') const istanbul = require('rollup-plugin-istanbul')
const { nodeResolve } = require('@rollup/plugin-node-resolve') const { nodeResolve } = require('@rollup/plugin-node-resolve')
const replace = require('@rollup/plugin-replace')
const { const {
browsers, browsers,
@ -74,6 +75,9 @@ const conf = {
}, },
rollupPreprocessor: { rollupPreprocessor: {
plugins: [ plugins: [
replace({
'process.env.NODE_ENV': '"dev"'
}),
istanbul({ istanbul({
exclude: [ exclude: [
'node_modules/**', 'node_modules/**',

View File

@ -1,5 +1,3 @@
import Popper from 'popper.js'
import Dropdown from '../../src/dropdown' import Dropdown from '../../src/dropdown'
import EventHandler from '../../src/dom/event-handler' import EventHandler from '../../src/dom/event-handler'
@ -36,50 +34,6 @@ describe('Dropdown', () => {
}) })
describe('constructor', () => { describe('constructor', () => {
it('should create offset modifier correctly when offset option is a function', () => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Secondary link</a>',
' </div>',
'</div>'
].join('')
const getOffset = offsets => offsets
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown, {
offset: getOffset
})
const offset = dropdown._getOffset()
expect(offset.offset).toBeUndefined()
expect(typeof offset.fn).toEqual('function')
})
it('should create offset modifier correctly when offset option is not a function', () => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Secondary link</a>',
' </div>',
'</div>'
].join('')
const myOffset = 7
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown, {
offset: myOffset
})
const offset = dropdown._getOffset()
expect(offset.offset).toEqual(myOffset)
expect(offset.fn).toBeUndefined()
})
it('should add a listener on trigger which do not have data-bs-toggle="dropdown"', () => { it('should add a listener on trigger which do not have data-bs-toggle="dropdown"', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div class="dropdown">', '<div class="dropdown">',
@ -860,14 +814,11 @@ describe('Dropdown', () => {
expect(dropdown._menu).toBeDefined() expect(dropdown._menu).toBeDefined()
expect(dropdown._element).toBeDefined() expect(dropdown._element).toBeDefined()
spyOn(Popper.prototype, 'destroy')
dropdown.dispose() dropdown.dispose()
expect(dropdown._popper).toBeNull() expect(dropdown._popper).toBeNull()
expect(dropdown._menu).toBeNull() expect(dropdown._menu).toBeNull()
expect(dropdown._element).toBeNull() expect(dropdown._element).toBeNull()
expect(Popper.prototype.destroy).toHaveBeenCalled()
}) })
}) })
@ -889,12 +840,12 @@ describe('Dropdown', () => {
expect(dropdown._popper).toBeDefined() expect(dropdown._popper).toBeDefined()
spyOn(dropdown._popper, 'scheduleUpdate') spyOn(dropdown._popper, 'update')
spyOn(dropdown, '_detectNavbar') spyOn(dropdown, '_detectNavbar')
dropdown.update() dropdown.update()
expect(dropdown._popper.scheduleUpdate).toHaveBeenCalled() expect(dropdown._popper.update).toHaveBeenCalled()
expect(dropdown._detectNavbar).toHaveBeenCalled() expect(dropdown._detectNavbar).toHaveBeenCalled()
}) })
@ -921,48 +872,6 @@ describe('Dropdown', () => {
}) })
describe('data-api', () => { describe('data-api', () => {
it('should not add class position-static to dropdown if boundary not set', done => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Secondary link</a>',
' </div>',
'</div>'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownEl = fixtureEl.querySelector('.dropdown')
dropdownEl.addEventListener('shown.bs.dropdown', () => {
expect(dropdownEl.classList.contains('position-static')).toEqual(false)
done()
})
btnDropdown.click()
})
it('should add class position-static to dropdown if boundary not scrollParent', done => {
fixtureEl.innerHTML = [
'<div class="dropdown">',
' <button class="btn dropdown-toggle" data-bs-toggle="dropdown" data-bs-boundary="viewport">Dropdown</button>',
' <div class="dropdown-menu">',
' <a class="dropdown-item" href="#">Secondary link</a>',
' </div>',
'</div>'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownEl = fixtureEl.querySelector('.dropdown')
dropdownEl.addEventListener('shown.bs.dropdown', () => {
expect(dropdownEl.classList.contains('position-static')).toEqual(true)
done()
})
btnDropdown.click()
})
it('should show and hide a dropdown', done => { it('should show and hide a dropdown', done => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div class="dropdown">', '<div class="dropdown">',

View File

@ -483,24 +483,6 @@ describe('Tooltip', () => {
tooltip.show() tooltip.show()
}) })
it('should show a tooltip with offset as a function', done => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
const spy = jasmine.createSpy('offset').and.returnValue({})
const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, {
offset: spy
})
tooltipEl.addEventListener('shown.bs.tooltip', () => {
expect(document.querySelector('.tooltip')).toBeDefined()
expect(spy).toHaveBeenCalled()
done()
})
tooltip.show()
})
it('should show a tooltip without the animation', done => { it('should show a tooltip without the animation', done => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
@ -789,18 +771,18 @@ describe('Tooltip', () => {
}) })
describe('update', () => { describe('update', () => {
it('should call popper schedule update', done => { it('should call popper update', done => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
tooltipEl.addEventListener('shown.bs.tooltip', () => { tooltipEl.addEventListener('shown.bs.tooltip', () => {
spyOn(tooltip._popper, 'scheduleUpdate') spyOn(tooltip._popper, 'update')
tooltip.update() tooltip.update()
expect(tooltip._popper.scheduleUpdate).toHaveBeenCalled() expect(tooltip._popper.update).toHaveBeenCalled()
done() done()
}) })

View File

@ -78,7 +78,9 @@
<li><a class="dropdown-item" href="#">Something else here</a></li> <li><a class="dropdown-item" href="#">Something else here</a></li>
</ul> </ul>
</div> </div>
</div>
<div class="col-sm-12 mt-4">
<div class="btn-group dropup"> <div class="btn-group dropup">
<button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropup</button> <button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropup</button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@ -170,16 +172,6 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-3 mt-4">
<div class="btn-group dropdown">
<button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" data-bs-offset="10,20">Dropdown offset</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</div>
</div>
<div class="col-sm-3 mt-4"> <div class="col-sm-3 mt-4">
<div class="btn-group dropdown"> <div class="btn-group dropdown">
<button type="button" class="btn btn-secondary">Dropdown reference</button> <button type="button" class="btn btn-secondary">Dropdown reference</button>
@ -206,10 +198,9 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script> <script src="../../../node_modules/@popperjs/core/dist/umd/popper.min.js"></script>
<script src="../../dist/dom/event-handler.js"></script> <script src="../../dist/dom/event-handler.js"></script>
<script src="../../dist/dom/selector-engine.js"></script> <script src="../../dist/dom/selector-engine.js"></script>
<script src="../../dist/dom/data.js"></script> <script src="../../dist/dom/data.js"></script>

View File

@ -31,7 +31,7 @@
</button> </button>
</div> </div>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script> <script src="../../../node_modules/@popperjs/core/dist/umd/popper.min.js"></script>
<script src="../../dist/dom/event-handler.js"></script> <script src="../../dist/dom/event-handler.js"></script>
<script src="../../dist/dom/selector-engine.js"></script> <script src="../../dist/dom/selector-engine.js"></script>
<script src="../../dist/dom/manipulator.js"></script> <script src="../../dist/dom/manipulator.js"></script>

View File

@ -68,7 +68,7 @@
<div id="customContainer"></div> <div id="customContainer"></div>
</div> </div>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script> <script src="../../../node_modules/@popperjs/core/dist/umd/popper.min.js"></script>
<script src="../../dist/dom/selector-engine.js"></script> <script src="../../dist/dom/selector-engine.js"></script>
<script src="../../dist/dom/event-handler.js"></script> <script src="../../dist/dom/event-handler.js"></script>
<script src="../../dist/dom/manipulator.js"></script> <script src="../../dist/dom/manipulator.js"></script>

View File

@ -17,7 +17,7 @@
<copyright>Copyright 2017-2020</copyright> <copyright>Copyright 2017-2020</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<dependencies> <dependencies>
<dependency id="popper.js" version="[1.16.1,2)" /> <dependency id="@popperjs/core" version="[2.4.2,3)" />
</dependencies> </dependencies>
<tags>css mobile-first responsive front-end framework web</tags> <tags>css mobile-first responsive front-end framework web</tags>
<contentFiles> <contentFiles>

View File

@ -17,7 +17,7 @@
<copyright>Copyright 2017-2020</copyright> <copyright>Copyright 2017-2020</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<dependencies> <dependencies>
<dependency id="popper.js" version="[1.16.1,2)" /> <dependency id="@popperjs/core" version="[2.4.2,3)" />
</dependencies> </dependencies>
<tags>css sass mobile-first responsive front-end framework web</tags> <tags>css sass mobile-first responsive front-end framework web</tags>
<contentFiles> <contentFiles>

22
package-lock.json generated
View File

@ -1157,6 +1157,12 @@
"fastq": "^1.6.0" "fastq": "^1.6.0"
} }
}, },
"@popperjs/core": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.2.tgz",
"integrity": "sha512-JlGTGRYHC2QK+DDbePyXdBdooxFq2+noLfWpRqJtkxcb/oYWzOF0kcbfvvbWrwevCC1l6hLUg1wHYT+ona5BWQ==",
"dev": true
},
"@rollup/plugin-babel": { "@rollup/plugin-babel": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.2.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.2.2.tgz",
@ -1204,6 +1210,16 @@
"resolve": "^1.19.0" "resolve": "^1.19.0"
} }
}, },
"@rollup/plugin-replace": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz",
"integrity": "sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
"magic-string": "^0.25.7"
}
},
"@rollup/pluginutils": { "@rollup/pluginutils": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
@ -8228,12 +8244,6 @@
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
"dev": true "dev": true
}, },
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"dev": true
},
"posix-character-classes": { "posix-character-classes": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",

View File

@ -95,15 +95,17 @@
}, },
"dependencies": {}, "dependencies": {},
"peerDependencies": { "peerDependencies": {
"popper.js": "^1.16.1" "@popperjs/core": "^2.4.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.12.8", "@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9", "@babel/core": "^7.12.9",
"@babel/preset-env": "^7.12.7", "@babel/preset-env": "^7.12.7",
"@popperjs/core": "^2.4.2",
"@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-commonjs": "^17.0.0", "@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0", "@rollup/plugin-node-resolve": "^11.0.0",
"@rollup/plugin-replace": "^2.3.4",
"autoprefixer": "^10.0.4", "autoprefixer": "^10.0.4",
"bundlewatch": "^0.3.1", "bundlewatch": "^0.3.1",
"clean-css-cli": "^4.3.0", "clean-css-cli": "^4.3.0",
@ -131,7 +133,6 @@
"lockfile-lint": "^4.3.7", "lockfile-lint": "^4.3.7",
"nodemon": "^2.0.6", "nodemon": "^2.0.6",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"popper.js": "^1.16.1",
"postcss": "^8.1.10", "postcss": "^8.1.10",
"postcss-cli": "^8.3.0", "postcss-cli": "^8.3.0",
"rollup": "^2.34.0", "rollup": "^2.34.0",
@ -161,13 +162,13 @@
"shim": { "shim": {
"js/bootstrap": { "js/bootstrap": {
"deps": [ "deps": [
"popper.js" "@popperjs/core"
] ]
} }
}, },
"dependencies": {}, "dependencies": {},
"peerDependencies": { "peerDependencies": {
"popper.js": "^1.16.1" "@popperjs/core": "^2.4.2"
} }
} }
} }

View File

@ -104,10 +104,10 @@
// When Popper is enabled, reset the basic dropdown position // When Popper is enabled, reset the basic dropdown position
// stylelint-disable-next-line no-duplicate-selectors // stylelint-disable-next-line no-duplicate-selectors
.dropdown-menu { .dropdown-menu {
&[x-placement^="top"], &[data-popper-placement^="top"],
&[x-placement^="right"], &[data-popper-placement^="right"],
&[x-placement^="bottom"], &[data-popper-placement^="bottom"],
&[x-placement^="left"] { &[data-popper-placement^="left"] {
right: auto; right: auto;
bottom: auto; bottom: auto;
left: auto; left: auto;

View File

@ -134,16 +134,16 @@
} }
.bs-popover-auto { .bs-popover-auto {
&[x-placement^="top"] { &[data-popper-placement^="top"] {
@extend .bs-popover-top; @extend .bs-popover-top;
} }
&[x-placement^="right"] { &[data-popper-placement^="right"] {
@extend .bs-popover-end; @extend .bs-popover-end;
} }
&[x-placement^="bottom"] { &[data-popper-placement^="bottom"] {
@extend .bs-popover-bottom; @extend .bs-popover-bottom;
} }
&[x-placement^="left"] { &[data-popper-placement^="left"] {
@extend .bs-popover-start; @extend .bs-popover-start;
} }
} }

View File

@ -36,7 +36,7 @@
bottom: 0; bottom: 0;
&::before { &::before {
top: 0; top: -1px;
border-width: $tooltip-arrow-height ($tooltip-arrow-width / 2) 0; border-width: $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;
border-top-color: $tooltip-arrow-color; border-top-color: $tooltip-arrow-color;
} }
@ -52,7 +52,7 @@
height: $tooltip-arrow-width; height: $tooltip-arrow-width;
&::before { &::before {
right: 0 #{"/* rtl:ignore */"}; right: -1px #{"/* rtl:ignore */"};
border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0 #{"/* rtl:ignore */"}; border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0 #{"/* rtl:ignore */"};
border-right-color: $tooltip-arrow-color #{"/* rtl:ignore */"}; border-right-color: $tooltip-arrow-color #{"/* rtl:ignore */"};
} }
@ -66,7 +66,7 @@
top: 0; top: 0;
&::before { &::before {
bottom: 0; bottom: -1px;
border-width: 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height; border-width: 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;
border-bottom-color: $tooltip-arrow-color; border-bottom-color: $tooltip-arrow-color;
} }
@ -82,7 +82,7 @@
height: $tooltip-arrow-width; height: $tooltip-arrow-width;
&::before { &::before {
left: 0 #{"/* rtl:ignore */"}; left: -1px #{"/* rtl:ignore */"};
border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height #{"/* rtl:ignore */"}; border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height #{"/* rtl:ignore */"};
border-left-color: $tooltip-arrow-color #{"/* rtl:ignore */"}; border-left-color: $tooltip-arrow-color #{"/* rtl:ignore */"};
} }
@ -90,16 +90,16 @@
} }
.bs-tooltip-auto { .bs-tooltip-auto {
&[x-placement^="top"] { &[data-popper-placement^="top"] {
@extend .bs-tooltip-top; @extend .bs-tooltip-top;
} }
&[x-placement^="right"] { &[data-popper-placement^="right"] {
@extend .bs-tooltip-end; @extend .bs-tooltip-end;
} }
&[x-placement^="bottom"] { &[data-popper-placement^="bottom"] {
@extend .bs-tooltip-bottom; @extend .bs-tooltip-bottom;
} }
&[x-placement^="left"] { &[data-popper-placement^="left"] {
@extend .bs-tooltip-start; @extend .bs-tooltip-start;
} }
} }

View File

@ -872,33 +872,23 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr>
<td><code>offset</code></td>
<td>number | string | function</td>
<td><code>0</code></td>
<td>
<p>Offset of the dropdown relative to its target.</p>
<p>When a function is used to determine the offset, it is called with an object containing the offset data as its first argument. The function must return an object with the same structure. The triggering element DOM node is passed as the second argument.</p>
<p>For more information refer to Popper's <a href="https://popper.js.org/docs/v1/#modifiers..offset.offset">offset docs</a>.</p>
</td>
</tr>
<tr> <tr>
<td><code>flip</code></td> <td><code>flip</code></td>
<td>boolean</td> <td>boolean</td>
<td><code>true</code></td> <td><code>true</code></td>
<td>Allow Dropdown to flip in case of an overlapping on the reference element. For more information refer to Popper's <a href="https://popper.js.org/docs/v1/#modifiers..flip.enabled">flip docs</a>.</td> <td>Allow Dropdown to flip in case of an overlapping on the reference element. For more information refer to Popper's <a href="https://popper.js.org/docs/v2/modifiers/flip/">flip docs</a>.</td>
</tr> </tr>
<tr> <tr>
<td><code>boundary</code></td> <td><code>boundary</code></td>
<td>string | element</td> <td>string | element</td>
<td><code>'scrollParent'</code></td> <td><code>'scrollParent'</code></td>
<td>Overflow constraint boundary of the dropdown menu. Accepts the values of <code>'viewport'</code>, <code>'window'</code>, <code>'scrollParent'</code>, or an HTMLElement reference (JavaScript only). For more information refer to Popper's <a href="https://popper.js.org/docs/v1/#modifiers..preventOverflow.boundariesElement">preventOverflow docs</a>.</td> <td>Overflow constraint boundary of the dropdown menu. By default it's <code>'clippingParents'</code> and can accept an HTMLElement reference (JavaScript only). For more information refer to Popper's <a href="https://popper.js.org/docs/v2/utils/detect-overflow/#boundary">preventOverflow docs</a>.</td>
</tr> </tr>
<tr> <tr>
<td><code>reference</code></td> <td><code>reference</code></td>
<td>string | element</td> <td>string | element</td>
<td><code>'toggle'</code></td> <td><code>'toggle'</code></td>
<td>Reference element of the dropdown menu. Accepts the values of <code>'toggle'</code>, <code>'parent'</code>, or an HTMLElement reference. For more information refer to Popper's <a href="https://popper.js.org/docs/v1/#referenceObject">referenceObject docs</a>.</td> <td>Reference element of the dropdown menu. Accepts the values of <code>'toggle'</code>, <code>'parent'</code>, or an HTMLElement reference. For more information refer to Popper's <a href="https://popper.js.org/docs/v2/constructors/#createpopper">constructor docs</a>.</td>
</tr> </tr>
<tr> <tr>
<td><code>display</code></td> <td><code>display</code></td>

View File

@ -248,28 +248,18 @@ Note that for security reasons the `sanitize`, `sanitizeFn`, and `allowList` opt
<p><code>'hover'</code> on its own will result in tooltips that cannot be triggered via the keyboard, and should only be used if alternative methods for conveying the same information for keyboard users is present.</p> <p><code>'hover'</code> on its own will result in tooltips that cannot be triggered via the keyboard, and should only be used if alternative methods for conveying the same information for keyboard users is present.</p>
</td> </td>
</tr> </tr>
<tr>
<td><code>offset</code></td>
<td>number | string | function</td>
<td><code>0</code></td>
<td>
<p>Offset of the tooltip relative to its target.</p>
<p>When a function is used to determine the offset, it is called with an object containing the offset data as its first argument. The function must return an object with the same structure. The triggering element DOM node is passed as the second argument.</p>
<p>For more information refer to Popper's <a href="https://popper.js.org/docs/v1/#modifiers..offset.offset">offset docs</a>.</p>
</td>
</tr>
<tr> <tr>
<td><code>fallbackPlacement</code></td> <td><code>fallbackPlacement</code></td>
<td>string | array</td> <td>null | array</td>
<td><code>'flip'</code></td> <td><code>null</code></td>
<td>Allow to specify which position Popper will use on fallback. For more information refer to <td>Allow to specify which position Popper will use on fallback. For more information refer to
Popper's <a href="https://popper.js.org/docs/v1/#modifiers..flip.behavior">behavior docs</a></td> Popper's <a href="https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements">behavior docs</a></td>
</tr> </tr>
<tr> <tr>
<td><code>boundary</code></td> <td><code>boundary</code></td>
<td>string | element</td> <td>string | element</td>
<td><code>'scrollParent'</code></td> <td><code>'clippingParents'</code></td>
<td>Overflow constraint boundary of the tooltip. Accepts the values of <code>'viewport'</code>, <code>'window'</code>, <code>'scrollParent'</code>, or an HTMLElement reference (JavaScript only). For more information refer to Popper's <a href="https://popper.js.org/docs/v1/#modifiers..preventOverflow.boundariesElement">preventOverflow docs</a>.</td> <td>Overflow constraint boundary of the tooltip. By default it's <code>'clippingParents'</code> and can accept an HTMLElement reference (JavaScript only). For more information refer to Popper's <a href="https://popper.js.org/docs/v2/utils/detect-overflow/#boundary">preventOverflow docs</a>.</td>
</tr> </tr>
<tr> <tr>
<td><code>customClass</code></td> <td><code>customClass</code></td>

View File

@ -27,7 +27,7 @@ import Alert from 'bootstrap/js/dist/alert';
``` ```
Bootstrap depends on [Popper](https://popper.js.org/), which is specified in the `peerDependencies` property. Bootstrap depends on [Popper](https://popper.js.org/), which is specified in the `peerDependencies` property.
This means that you will have to make sure to add it to your `package.json` using `npm install popper.js`. This means that you will have to make sure to add it to your `package.json` using `npm install @popperjs/core`.
## Importing Styles ## Importing Styles

View File

@ -283,6 +283,8 @@ Changes to our source and compiled JavaScript files.
- Dropped jQuery dependency and rewrote plugins to be in regular JavaScript. - Dropped jQuery dependency and rewrote plugins to be in regular JavaScript.
- Removed underscore from public static methods like `_getInstance()``getInstance()`. - Removed underscore from public static methods like `_getInstance()``getInstance()`.
- Moved from Popper v1.x to Popper v2.x
- Removed `offset` option from our Tooltip/Popover and Dropdown plugin, this can still be achieve using `popperConfig` parameter.
### Color system ### Color system