huntabyte / cmdk-sv

cmdk, but for Svelte ✨
https://cmdk-sv.com
MIT License
470 stars 19 forks source link

[question] How to change the command items on the fly? #36

Open fredguth opened 10 months ago

fredguth commented 10 months ago

This seems more a svelte problem than a cmdk-sv problem, but I am scratching my head once again.

Minimum working example: https://github.com/fredguth/cmd-sk-mwe

In the mwe, my list of options is too big and if I try to render them without filtering it takes too long (not with this json, a bigger one). So, I imagined I could just filter the options on the fly keeping the maximum number of options in a reasonable amout (56 options, for example).

The filteredOptions is changing (as you can see in the console.log), but the Command.List does not change.

<script lang="ts">
    import { Command } from 'cmdk-sv';
    import unidades from './unidades.json';

    // let options = unidades.map((item) => {
    //  item.DS_UNIDADE;
    // });
    let options = unidades.map((item) => {
        return item.DS_UNIDADE;
    });
    function filterByQuery(items: string[], query: string): string[] {
        return query
            ? items.filter((item) => item?.toUpperCase().includes(query.toUpperCase())).slice(0, 6)
            : items?.slice(0, 6);
    }
    let open = true;
    function runCommand(cmd: () => void) {
        open = false;
        cmd();
    }
    let q = '';
    let selectedOption = '';
    $: filteredOptions = filterByQuery(options, q);
    $: console.log(filteredOptions);
</script>

<Command.Root label="Command Menu">
    <Command.Input bind:value={q} />
    <Command.List>
        <Command.Empty>No results found.</Command.Empty>
        {#each filteredOptions as option}
            <Command.Item
                value={option}
                onSelect={() => {
                    runCommand(() => {
                        selectedOption = option;
                    });
                }}>{option}</Command.Item
            >
        {/each}
    </Command.List>
</Command.Root>
fredguth commented 10 months ago

There is something really strange with the Command.Input.

The code bellow only works with common Input. and breakes with Command.Input

<script lang="ts">
  import * as Command from "$lib/components/ui/command";
  import { Input } from "$lib/components/ui/input";
  import { filterByQuery, runCommand } from "$lib/utils";
  import pdms from "$lib/assets/pdm.json";

  let open = true;
  let query = "";
  let selected = "";

  $: filtered = filterByQuery(pdms, query)?.slice(0, 10);
  $: console.log(filtered);
  $: open = query.length > 0;
</script>

<Command.Root>
  <Input
    bind:value={query}
    placeholder="Query..."
    class = "w-72"
  />
  {#if open}
    <Command.List class="z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none">
      <Command.Empty> Não encontrado. </Command.Empty>
      {#each filtered as pdm}
        <Command.Item
          value={pdm}
          onSelect={() =>
            runCommand(() => {
              selected = pdm;
              open = false;
              query = "";
            })}
        >
          {pdm}
        </Command.Item>
      {/each}
    </Command.List>
  {/if}
</Command.Root>
{#if selected} <h1>{selected}</h1>{/if}
SeanMallard commented 10 months ago

Hey @fredguth, did you ever find a solution to this problem? I'm trying to solve this very thing

fredguth commented 9 months ago

Not exactly. I eventually created a component that was visually equal to command, but then I figured that combobox component worked and was better suited for my usecase.

jonolo6 commented 7 months ago

I think if you want to do custom filtering you need to turn if off explicitly first (see docs):

<Command.Root shouldFilter={false}>
   <Command.List>
      ... each statement using the filtered items here...