55 * --------------------------------------------------------------------------
66 */
77
8- import * as Popper from '@popperjs/core '
8+ import { inline , offset , shift } from '@floating-ui/dom '
99import BaseComponent from './base-component.js'
1010import EventHandler from './dom/event-handler.js'
11- import Manipulator from './dom/manipulator.js'
1211import SelectorEngine from './dom/selector-engine.js'
1312import {
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'
5855const SELECTOR_NAVBAR_NAV = '.navbar-nav'
5956const 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'
6758const PLACEMENT_TOPCENTER = 'top'
59+ const PLACEMENT_TOPEND = 'top-end'
60+ const PLACEMENT_TOP = 'top-start'
6861const 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
7067const 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
7975const 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
0 commit comments