I want to use VueUse's virtual scroller composable to render the HeadlessUI's listbox content.
This is what I tried:
<Listbox v-model="selectedIcon">
<div class="relative mt-1">
<ListboxButton class="bg-white rounded-lg cursor-default shadow-md text-left w-full py-2 pr-10 pl-3 relative sm:text-sm focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-white focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-offset-orange-300 focus-visible:ring-offset-2">
<span class="block truncate">test</span>
<span class="flex pr-2 inset-y-0 right-0 absolute items-center pointer-events-none">
<SelectorIcon class="h-5 text-gray-400 w-5" aria-hidden="true" />
</span>
</ListboxButton>
<ListboxOptions v-bind="containerProps" class="bg-white rounded-md shadow-lg ring-black mt-1 text-base w-full max-h-60 h-full py-1 ring-1 ring-opacity-5 absolute overflow-auto sm:text-sm focus:outline-none">
<div v-bind="wrapperProps">
<ListboxOption v-slot="{ active, selected }" v-for="icon in list" :key="icon.data.name" :value="icon">
<li>
<span>{{ icon.data.name }}</span>
</li>
</ListboxOption>
</div>
</ListboxOptions>
</div>
</Listbox>
And this is the virtual scroller composable:
const { list, containerProps, wrapperProps } = useVirtualList(icons, { itemHeight: 40 })
The problem is that when I try to open the listbox, I get this error:
Uncaught (in promise) TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'.
at watch.immediate (index.mjs:1322)
at callWithErrorHandling (runtime-core.esm-bundler.js:6737)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:6746)
at Array.job (runtime-core.esm-bundler.js:7154)
at flushPostFlushCbs (runtime-core.esm-bundler.js:6938)
at flushJobs (runtime-core.esm-bundler.js:6983)
at flushJobs (runtime-core.esm-bundler.js:6991)
I also get this warning:
[Vue warn]: Unhandled error during execution of watcher callback
at <ApplicationIconSelector key=0 >
at <Anonymous as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" ... >
at <ForcePortalRoot force=false >
at <PortalGroup target= <div class="fixed z-10 inset-0 overflow-y-auto" id="headlessui-dialog-4" role="dialog" aria-modal="true" aria-labelledby="headlessui-dialog-title-8">…</div><div class="flex min-h-screen text-center px-4 pt-4 pb-20 items-end justify-center sm:p-0 sm:block"><div id="headlessui-dialog-overlay-6" aria-hidden="true" class="bg-gray-975 bg-opacity-85 inset-0 transition-opacity fixed"></div><!-- This element is to trick the browser into centering the modal contents. --><span class="hidden sm:h-screen sm:inline-block sm:align-middle" aria-hidden="true">​</span><div class="rounded-lg shadow-xl text-left transform transition-all text-gray-850 inline-block align-bottom sm:max-w-lg sm:my-8 sm:w-full sm:align-middle dark:text-gray-200"><div class="rounded-t-lg bg-gray-25 px-4 pt-5 pb-4 sm:p-6 sm:pb-4 dark:bg-gray-925">…</div><div class="bg-gray-75 py-3 px-3 sm:flex sm:flex-row-reverse dark:bg-gray-900">…</div>flex<button class="flex-y-center justify-center p-2 px-3 font-medium text-sm transition duration-75 select-none cursor-pointer focus:outline-none rounded-md bg-gray-75 text-gray-850 hover:bg-gray-100 active:bg-gray-175 dark:text-gray-250 dark:bg-gray-875 dark:hover:bg-gray-850 dark:active:bg-gray-825 w-full">…</button>flex</div></div></div></div> >
at <Portal>
at <ForcePortalRoot force=true >
at <Dialog as="div" class="fixed z-10 inset-0 overflow-y-auto" ref="el" ... >
at <Anonymous onBeforeEnter=fn<onBeforeEnter> onAfterEnter=fn<onAfterEnter> onBeforeLeave=fn<onBeforeLeave> ... >
at <Anonymous as="template" show=true data=null >
at <AddEditApplication modelValue=true onUpdate:modelValue=fn data=null >
at <Index onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {__v_skip: true} > >
at <RouterView>
at <App>
Uncaught (in promise) TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'.
Looks like it is attempting to bind itself to something other than a native DOM element. In the example on the VueUse site they show native DOM elements with the v-bind
directive. However, your code uses v-bind
on non-native DOM elements, you have it on a VNode (the ListBox component).
Even looking at the source code you can see that the binding of containerProps
expects an HTMLElement
.
const containerRef: Ref = ref<HTMLElement | null>()
I've not used HeadlessUI before, but looking at the source code for the ListBoxOptions
component it appears that it does not render any elements outside of what is passed into its default slot; aka your <div v-bind="wrapperProps">
. Is your list of classes even rendering on anything? Hard to tell how your code is running without an example.
I recommend creating another div
, nested inside of ListBoxOptions
, that wraps the <div v-bind="wrapperProps">
element. On this new div
move the v-bind="containerProps"
from the ListBoxOptions
onto it; see below.
<ListboxOptions>
<div v-bind="containerProps" class="bg-white rounded-md shadow-lg ring-black mt-1 text-base w-full max-h-60 h-full py-1 ring-1 ring-opacity-5 absolute overflow-auto sm:text-sm focus:outline-none">
<div v-bind="wrapperProps">
<ListboxOption v-slot="{ active, selected }" v-for="icon in list" :key="icon.data.name" :value="icon">
<li>
<span>{{ icon.data.name }}</span>
</li>
</ListboxOption>
</div>
</div>
</ListboxOptions>
I think that may fix your issue.