使用 Vue 中的 useVirtualList 来渲染 HeadlessUI 的 Listbox 组件

Use useVirtualList from VueUse to render HeadlessUI's Listbox component

我想使用 VueUse's virtual scroller composable to render the HeadlessUI's listbox 内容。

这是我试过的:

<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>

这是可组合的虚拟滚动条:

const { list, containerProps, wrapperProps } = useVirtualList(icons, { itemHeight: 40 })

问题是,当我尝试打开列表框时,出现此错误:

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)

我也收到这个警告:

[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">​&ZeroWidthSpace;​</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'.

看起来它正试图将自己绑定到本机 DOM 元素以外的东西。在 VueUse 站点的示例中,他们使用 v-bind 指令显示原生 DOM 元素。但是,您的代码在非本机 DOM 元素上使用 v-bind,您在 VNode(ListBox 组件)上使用它。

即使查看源代码,您也可以看到 containerProps 的绑定需要 HTMLElement.

const containerRef: Ref = ref<HTMLElement | null>()

我以前没有使用过 HeadlessUI,但查看 ListBoxOptions 组件的源代码似乎它不会呈现传递到其默认插槽中的元素之外的任何元素;也就是你的 <div v-bind="wrapperProps">。您的 类 列表甚至可以在任何东西上渲染吗?如果没有示例,很难说出您的代码 运行。

我建议再创建一个 div,嵌套在 ListBoxOptions 内,包裹 <div v-bind="wrapperProps"> 元素。在这个新的 div 上,将 v-bind="containerProps"ListBoxOptions 移动到它上面;见下文。

<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>

我认为这可能会解决您的问题。