Skip to content

Commit 166d5e7

Browse files
committed
Update v6 to Floating UI
1 parent 4fecde4 commit 166d5e7

File tree

20 files changed

+379
-512
lines changed

20 files changed

+379
-512
lines changed

build/generate-sri.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ const files = [
4242
configPropertyName: 'js_bundle_hash'
4343
},
4444
{
45-
file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
46-
configPropertyName: 'popper_hash'
45+
file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.min.js',
46+
configPropertyName: 'floating_ui_hash'
4747
}
4848
]
4949

build/rollup.config.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const BUNDLE = process.env.BUNDLE === 'true'
1212
const ESM = process.env.ESM === 'true'
1313

1414
let destinationFile = `bootstrap${ESM ? '.esm' : ''}`
15-
const external = ['@popperjs/core']
15+
const external = ['@floating-ui/dom']
1616
const plugins = [
1717
babel({
1818
// Only transpile our source code
@@ -22,14 +22,14 @@ const plugins = [
2222
})
2323
]
2424
const globals = {
25-
'@popperjs/core': 'Popper'
25+
'@floating-ui/dom': 'FloatingUIDOM'
2626
}
2727

2828
if (BUNDLE) {
2929
destinationFile += '.bundle'
30-
// Remove last entry in external array to bundle Popper
30+
// Remove last entry in external array to bundle FloatingUI
3131
external.pop()
32-
delete globals['@popperjs/core']
32+
delete globals['@floating-ui/dom']
3333
plugins.push(
3434
replace({
3535
'process.env.NODE_ENV': '"production"',

config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ cdn:
4242
js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
4343
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
4444
js_bundle_hash: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
45-
popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
46-
popper_hash: "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
47-
popper_esm: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
45+
floating_ui: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.12/dist/floating-ui.dom.umd.min.js"
46+
floating_ui_hash: "sha384-Os8n9bzoYJ/ESbGD7cW0VOTLk0hO++SO+Y4swXBE2dHrxiZkjADEr5ZGOcc9CorD"
47+
floating_ui_esm: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.12/dist/floating-ui.dom.min.js"
4848

4949
anchors:
5050
min: 2

js/src/dropdown.js

Lines changed: 35 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@
55
* --------------------------------------------------------------------------
66
*/
77

8-
import * as Popper from '@popperjs/core'
8+
import { inline, offset, shift } from '@floating-ui/dom'
99
import BaseComponent from './base-component.js'
1010
import EventHandler from './dom/event-handler.js'
11-
import Manipulator from './dom/manipulator.js'
1211
import SelectorEngine from './dom/selector-engine.js'
1312
import {
1413
execute,
15-
getElement,
1614
getNextActiveElement,
1715
isDisabled,
18-
isElement,
19-
isRTL,
2016
isVisible,
2117
noop
2218
} from './util/index.js'
19+
import FloatingUi from './util/floating-ui.js'
2320

2421
/**
2522
* Constants
@@ -58,30 +55,28 @@ const SELECTOR_NAVBAR = '.navbar'
5855
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
5956
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
6057

61-
const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'
62-
const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'
63-
const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'
64-
const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'
65-
const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'
66-
const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'
6758
const PLACEMENT_TOPCENTER = 'top'
59+
const PLACEMENT_TOPEND = 'top-end'
60+
const PLACEMENT_TOP = 'top-start'
6861
const PLACEMENT_BOTTOMCENTER = 'bottom'
62+
const PLACEMENT_BOTTOMEND = 'bottom-end'
63+
const PLACEMENT_BOTTOM = 'bottom-start'
64+
const PLACEMENT_RIGHT = 'right-start'
65+
const PLACEMENT_LEFT = 'left-start'
6966

7067
const Default = {
7168
autoClose: true,
72-
boundary: 'clippingParents',
7369
display: 'dynamic',
74-
offset: [0, 2],
75-
popperConfig: null,
70+
offset: 10,
71+
positionConfig: null,
7672
reference: 'toggle'
7773
}
7874

7975
const DefaultType = {
8076
autoClose: '(boolean|string)',
81-
boundary: '(string|element)',
8277
display: 'string',
83-
offset: '(array|string|function)',
84-
popperConfig: '(null|object|function)',
78+
offset: '(number|array|string|function)',
79+
positionConfig: '(null|object|function)',
8580
reference: '(string|element|object)'
8681
}
8782

@@ -93,13 +88,12 @@ class Dropdown extends BaseComponent {
9388
constructor(element, config) {
9489
super(element, config)
9590

96-
this._popper = null
9791
this._parent = this._element.parentNode // dropdown wrapper
9892
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
9993
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||
10094
SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||
10195
SelectorEngine.findOne(SELECTOR_MENU, this._parent)
102-
this._inNavbar = this._detectNavbar()
96+
this._positionHelper = new FloatingUi(this._element)
10397
}
10498

10599
// Getters
@@ -135,7 +129,7 @@ class Dropdown extends BaseComponent {
135129
return
136130
}
137131

138-
this._createPopper()
132+
this.update()
139133

140134
// If this is a touch-enabled device we add extra
141135
// empty mouseover listeners to the body's immediate children;
@@ -167,19 +161,9 @@ class Dropdown extends BaseComponent {
167161
this._completeHide(relatedTarget)
168162
}
169163

170-
dispose() {
171-
if (this._popper) {
172-
this._popper.destroy()
173-
}
174-
175-
super.dispose()
176-
}
177-
178164
update() {
179-
this._inNavbar = this._detectNavbar()
180-
if (this._popper) {
181-
this._popper.update()
182-
}
165+
const reference = this._positionHelper.getReferenceElement(this._config.reference, this._parent, NAME)
166+
this._positionHelper.calculate(reference, this._menu, this._getFloatingUiConfig())
183167
}
184168

185169
// Private
@@ -197,47 +181,28 @@ class Dropdown extends BaseComponent {
197181
}
198182
}
199183

200-
if (this._popper) {
201-
this._popper.destroy()
202-
}
203-
204184
this._menu.classList.remove(CLASS_NAME_SHOW)
205185
this._element.classList.remove(CLASS_NAME_SHOW)
206186
this._element.setAttribute('aria-expanded', 'false')
207-
Manipulator.removeDataAttribute(this._menu, 'popper')
187+
this._positionHelper.stop()
208188
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
209189
}
210190

211-
_getConfig(config) {
212-
config = super._getConfig(config)
213-
214-
if (typeof config.reference === 'object' && !isElement(config.reference) &&
215-
typeof config.reference.getBoundingClientRect !== 'function'
216-
) {
217-
// Popper virtual elements require a getBoundingClientRect method
218-
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
191+
_getFloatingUiConfig() {
192+
const defaultBsConfig = {
193+
placement: this._getPlacement(),
194+
middleware: [offset(this._positionHelper.parseOffset(this._config.offset)), shift()]
219195
}
220196

221-
return config
222-
}
223-
224-
_createPopper() {
225-
if (typeof Popper === 'undefined') {
226-
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)')
197+
// Disable positioning if we have a static display or Dropdown is in Navbar
198+
if (this._detectNavbar() || this._config.display === 'static') {
199+
defaultBsConfig.middleware.push(inline())
227200
}
228201

229-
let referenceElement = this._element
230-
231-
if (this._config.reference === 'parent') {
232-
referenceElement = this._parent
233-
} else if (isElement(this._config.reference)) {
234-
referenceElement = getElement(this._config.reference)
235-
} else if (typeof this._config.reference === 'object') {
236-
referenceElement = this._config.reference
202+
return {
203+
...defaultBsConfig,
204+
...execute(this._config.positionConfig, [undefined, defaultBsConfig])
237205
}
238-
239-
const popperConfig = this._getPopperConfig()
240-
this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)
241206
}
242207

243208
_isShown() {
@@ -247,20 +212,15 @@ class Dropdown extends BaseComponent {
247212
_getPlacement() {
248213
const parentDropdown = this._parent
249214

250-
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
251-
return PLACEMENT_RIGHT
215+
const matches = {
216+
[CLASS_NAME_DROPEND]: PLACEMENT_RIGHT,
217+
[CLASS_NAME_DROPSTART]: PLACEMENT_LEFT,
218+
[CLASS_NAME_DROPUP_CENTER]: PLACEMENT_TOPCENTER,
219+
[CLASS_NAME_DROPDOWN_CENTER]: PLACEMENT_BOTTOMCENTER
252220
}
253-
254-
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
255-
return PLACEMENT_LEFT
256-
}
257-
258-
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
259-
return PLACEMENT_TOPCENTER
260-
}
261-
262-
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
263-
return PLACEMENT_BOTTOMCENTER
221+
const match = Object.keys(matches).find(keyClass => parentDropdown.classList.contains(keyClass))
222+
if (match) {
223+
return matches[match]
264224
}
265225

266226
// We need to trim the value because custom properties can also include spaces
@@ -277,52 +237,6 @@ class Dropdown extends BaseComponent {
277237
return this._element.closest(SELECTOR_NAVBAR) !== null
278238
}
279239

280-
_getOffset() {
281-
const { offset } = this._config
282-
283-
if (typeof offset === 'string') {
284-
return offset.split(',').map(value => Number.parseInt(value, 10))
285-
}
286-
287-
if (typeof offset === 'function') {
288-
return popperData => offset(popperData, this._element)
289-
}
290-
291-
return offset
292-
}
293-
294-
_getPopperConfig() {
295-
const defaultBsPopperConfig = {
296-
placement: this._getPlacement(),
297-
modifiers: [{
298-
name: 'preventOverflow',
299-
options: {
300-
boundary: this._config.boundary
301-
}
302-
},
303-
{
304-
name: 'offset',
305-
options: {
306-
offset: this._getOffset()
307-
}
308-
}]
309-
}
310-
311-
// Disable Popper if we have a static display or Dropdown is in Navbar
312-
if (this._inNavbar || this._config.display === 'static') {
313-
Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove
314-
defaultBsPopperConfig.modifiers = [{
315-
name: 'applyStyles',
316-
enabled: false
317-
}]
318-
}
319-
320-
return {
321-
...defaultBsPopperConfig,
322-
...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
323-
}
324-
}
325-
326240
_selectMenuItem({ key, target }) {
327241
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))
328242

js/src/popover.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ const Default = {
2222
offset: [0, 8],
2323
placement: 'right',
2424
template: '<div class="popover" role="tooltip">' +
25-
'<div class="popover-arrow"></div>' +
25+
'<div class="popover-inner">' +
2626
'<h3 class="popover-header"></h3>' +
2727
'<div class="popover-body"></div>' +
28+
'</div>' +
2829
'</div>',
2930
trigger: 'click'
3031
}

0 commit comments

Comments
 (0)