Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[performance] Virtualise CommandItem when trying to display large lists? #80

Open
NathanAdhitya opened this issue Jun 5, 2024 · 2 comments

Comments

@NathanAdhitya
Copy link

NathanAdhitya commented Jun 5, 2024

As title says.

I'm personally trying to make it working by integrating TanStack Virtual.
It's not going very well. Extremely janky, but cut initial mount time (around 600 elements, Combobox element from shadcn-svelte) from 2-3s after clicking the Popover.Trigger to perceivably instant. I haven't gotten filtering to work yet.

Has anyone gotten this issue, if so, what solutions helped?

@huntabyte
Copy link
Owner

This is going to be improved in the Svelte 5 version for sure 😃

For example, here's a list of 2,000 items running on Svelte 5 version (coming soon):

CleanShot.2024-09-20.at.21.58.40.mp4

Something to note about this project that differs from a traditional combobox is that it's actually sorting/ranking the dom elements whereas the combobox simply filters.

@gustavomorinaga
Copy link

gustavomorinaga commented Sep 25, 2024

@NathanAdhitya I had the same issue. For this, I created an component that wraps the virtua lib (because of shadcn-svelte folder convention, but you can call directly if you want).

src/lib/ui/virtual-list/virtual-list.svelte

<script lang="ts" context="module">
  import { VList } from 'virtua/svelte';
  import type { ComponentEvents, ComponentProps } from 'svelte';
</script>

<script lang="ts">
  type T = $$Generic;
  type $$Props = ComponentProps<VList<T>>;
  type $$Events = ComponentEvents<VList<T>>;
  type $$Slots = { default: { item: T; index: number } };

  export let data: $$Props['data'] = [];
</script>

{#key data.length}
  <VList {data} {...$$restProps} let:item let:index>
    <slot {item} {index} />
  </VList>
{/key}

Then, I use it together with the Command component (in my case, I needed to use a derived store for filtered items to display the correct info and debounce the search input):

<script>
  import * as Command from '$lib/components/ui/command';
  import { VirtualList } from '$lib/components/ui/virtual-list';
</script>

<Command.Root shouldFilter={false} class="size-auto rounded-none bg-transparent">
  <Command.Input
    placeholder="Search item..."
    value={$searchTerm}
    on:input={debounceSearch.call}
    class="h-10 py-2"
  />
  <Command.List class="h-[20svh] [&>div]:contents">
    <Command.Empty class="py-4">No results</Command.Empty>
    {#if $filtered.length}
      <Command.Group alwaysRender class="h-full p-0 [&>div]:h-full">
        <VirtualList
          data={$filtered}
          getKey={(item) => item.name}
          let:item={option}
          class="overflow-y-auto p-2"
        >
          <Command.Item value={option.name} class="shrink-0 gap-3 !bg-transparent">
            <Checkbox
              id={option.name}
              aria-labelledby={option.name}
              bind:checked={$options[option.name].checked}
              on:click={debounceFilter.call}
            />
            <Label id={option.name} for={option.name} class="w-full">
              {option.name}
            </Label>
          </Command.Item>
        </VirtualList>
      </Command.Group>
    {/if}
  </Command.List>
</Command.Root>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants