shadcn-svelte icon indicating copy to clipboard operation
shadcn-svelte copied to clipboard

Command onValueChange event fires every time the cursor is moved over an command item

Open xFrigginx opened this issue 7 months ago • 4 comments

Describe the bug

When implementing the Combobox example and adding onValueChange handler to the Command.Root, the event fires every time the cursor is moved over an command item, not when an item is actually selected. I would expect the event only to fire when the selection is actually changed.

Reproduction

See: https://stackblitz.com/edit/github-wuyshrsb?file=src%2Froutes%2F%2Bpage.svelte

<script lang="ts">
 import ChevronsUpDown from "@lucide/svelte/icons/chevrons-up-down";
 import { tick } from "svelte";
 import * as Command from "$lib/components/ui/command/index.js";
 import * as Popover from "$lib/components/ui/popover/index.js";
 import { Button } from "$lib/components/ui/button/index.js";
 import { cn } from "$lib/utils.js";
 
 const frameworks = [
  {
   value: "sveltekit",
   label: "SvelteKit",
  },
  {
   value: "next.js",
   label: "Next.js",
  },
  {
   value: "nuxt.js",
   label: "Nuxt.js",
  },
  {
   value: "remix",
   label: "Remix",
  },
  {
   value: "astro",
   label: "Astro",
  },
 ];
 
 let open = $state(false);
 let value = $state("");
 let selected=$state("");
 let triggerRef = $state<HTMLButtonElement>(null!);
 
 const selectedValue = $derived(
  frameworks.find((f) => f.value === value)?.label
 );
 
 // We want to refocus the trigger button when the user selects
 // an item from the list so users can continue navigating the
 // rest of the form with the keyboard.
 function closeAndFocusTrigger() {
  open = false;
  tick().then(() => {
   triggerRef.focus();
  });
 }

 function valueChange(value:string){
   console.log(value);
   selected=value;
 }
</script>
 
<Popover.Root bind:open>
 <Popover.Trigger bind:ref={triggerRef}>
  {#snippet child({ props })}
   <Button
    variant="outline"
    class="w-[200px] justify-between"
    {...props}
    role="combobox"
    aria-expanded={open}
   >
    {selectedValue || "Select a framework..."}
    <ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
   </Button>
  {/snippet}
 </Popover.Trigger>
 <Popover.Content class="w-[200px] p-0">
  <Command.Root onValueChange={valueChange}>
   <Command.Input placeholder="Search framework..." />
   <Command.List>
    <Command.Empty>No framework found.</Command.Empty>
    <Command.Group>
     {#each frameworks as framework}
      <Command.Item
       value={framework.value}
       onSelect={() => {
        value = framework.value;
        closeAndFocusTrigger();
       }}
      >
       <Check
        class={cn(
         "mr-2 size-4",
         value !== framework.value && "text-transparent"
        )}
       />
       {framework.label}
      </Command.Item>
     {/each}
    </Command.Group>
   </Command.List>
  </Command.Root>
 </Popover.Content>
</Popover.Root>
{selected}

Logs


System Info

System:
    OS: Windows 11 10.0.26100
    CPU: (16) x64 AMD Ryzen 7 PRO 4750U with Radeon Graphics
    Memory: 14.22 GB / 31.23 GB
  Binaries:
    Node: 20.15.1 - C:\nvm4w\nodejs\node.EXE
    npm: 10.8.2 - C:\nvm4w\nodejs\npm.CMD
    pnpm: 10.6.3 - ~\AppData\Local\pnpm\pnpm.CMD
  Browsers:
    Edge: Chromium (131.0.2903.112)
    Internet Explorer: 11.0.26100.1882
  npmPackages:
    bits-ui: ^1.3.19 => 1.3.19
    svelte: latest => 5.27.0
    tailwindcss: ^3.4.17 => 3.4.17

Severity

annoyance

xFrigginx avatar Apr 22 '25 12:04 xFrigginx

I'm not sure if this is the intended behavior in bits-ui or not.

But I normally don't use onValueChange on the Root element. Normally I would use onSelect on each individual item and handle it there.

ieedan avatar Apr 22 '25 12:04 ieedan

From the api reference

  1. Change Handler To perform additional logic on state changes, use the onValueChange prop. This approach is useful when you need to execute side effects when the value changes.
<script lang="ts">
	import { Command } from "bits-ui";
</script>
 
<Command.Root
	onValueChange={(value) => {
		// do something with the new value
		console.log(value);
	}}
>
	<!-- ... -->
</Command.Root>

Reading this i got the impression that binding to the Root is encouraged.

xFrigginx avatar Apr 22 '25 16:04 xFrigginx

Yeah again I am not sure if it's the intended behavior but it I would encourage anyone to use onSelect on each item because I haven't really ever needed to use onValueChange.

Also bits-ui is at 1.0.0 so no need to go to next.bits-ui.com.

ieedan avatar Apr 22 '25 16:04 ieedan

Fair enough. next.bits-ui.com is where the href on the api reference button is pointing to on https://next.shadcn-svelte.com/docs/components/command

Image

xFrigginx avatar Apr 23 '25 14:04 xFrigginx

Fixed in bits-ui@latest.

huntabyte avatar Apr 30 '25 01:04 huntabyte