sagalbot / vue-select

Everything you wish the HTML <select> element could do, wrapped up into a lightweight, extensible Vue component.
https://vue-select.org
MIT License
4.65k stars 1.34k forks source link

Dynamic positioning with Floating UI #1852

Open adamreisnz opened 2 months ago

adamreisnz commented 2 months ago

Is your feature request related to a problem? Please describe. The current documentation contains a section with an example for using Popper integration for custom positioning: https://vue-select.org/guide/positioning.html#popper-js-integration

However, Popper.JS has now been replaced by Floating UI, and the API of it is vastly different from Popper.

Describe the solution you'd like It would be good to have an example in the documentation on how to accomplish custom positioning using Floating UI.

There is currently no practical way to do this, as Floating UI requires a reference to the dropdown element (the floating element). This is currently not accessible through VSelect as far as I can see.

Ideally, VSelect would simply have built in support for positioning itself either below the target element or above, depending on the scroll position, so that we didn't have to rely on an external package to accomplish this.

Describe alternatives you've considered A quick hacky workaround is to set the reference to the dropdown element in the calculatePosition function and then watch the floatingStyles for changes:

//Refs
const vSelect = ref(null)
const dropdownRef = ref(null)

//Use floating UI
const {floatingStyles} = useFloating(vSelect, dropdownRef, {
  placement,
  middleware: [
    offset(8),
    flip(),
  ],
  whileElementsMounted: autoUpdate,
})

//Calculate position
const calculatePosition = dropdown => {
  dropdownRef.value = dropdown //Remember the dropdown element
  dropdown.style.width = `${vSelect.value.$el.offsetWidth}px` //Using the width provided to `calculatePosition` doesn't work, as it's not the right size.
}

//Update floating styles
watch(floatingStyles, () => {
  if (dropdownRef.value) {
    Object.assign(dropdownRef.value.style, floatingStyles.value)
  }
})

However, this was not ideal as it means now we have to append the dropdown to the body, which means we can no longer apply conditional styles or classes as we have lose the nesting of the dropdown element inside of our wrapper component.

This problem with appendToBody is also described in: https://github.com/sagalbot/vue-select/issues/1266