{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "toggle-group",
  "title": "Toggle Group",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/toggle-group/ToggleGroup.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  type ToggleGroupKeydownEvent = Parameters<NonNullable<HTMLAttributes<HTMLDivElement>['onkeydown']>>[0]\n\n  export interface ToggleGroupProps extends HTMLAttributes<HTMLDivElement> {\n    variant?: 'default' | 'outline'\n    size?: 'default' | 'sm' | 'lg'\n    spacing?: number\n    type?: 'single' | 'multiple'\n    /** Controlled value: a string for single-select, an array for multi-select. */\n    value?: string | string[]\n    disabled?: boolean\n    loop?: boolean\n    orientation?: 'horizontal' | 'vertical'\n    rovingFocus?: boolean\n    /** Sliding selection indicator for single-select (default true). Multi-select keeps item chrome. */\n    animated?: boolean\n    /** Called with the new value after every selection change. */\n    onValueChange?: (value: string | string[]) => void\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { tick } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { ToggleGroupContextState, setToggleGroupContext } from './context.svelte'\n\n  let {\n    class: className,\n    variant,\n    size,\n    spacing = 0,\n    type,\n    value = $bindable(),\n    disabled = false,\n    loop = true,\n    orientation = 'horizontal',\n    rovingFocus = true,\n    dir,\n    animated = true,\n    onValueChange,\n    children,\n    ref = $bindable(null),\n    onkeydown,\n    ...restProps\n  }: ToggleGroupProps = $props()\n\n  const ctx = new ToggleGroupContextState()\n  ctx.select = (itemValue: string) => {\n    if (disabled) return\n    if (type === 'multiple') {\n      const current = Array.isArray(value) ? value : []\n      const next = current.includes(itemValue) ? current.filter((v) => v !== itemValue) : [...current, itemValue]\n      value = next\n      onValueChange?.(next)\n    } else {\n      const next = value === itemValue ? '' : itemValue\n      value = next\n      onValueChange?.(next)\n    }\n  }\n  setToggleGroupContext(ctx)\n\n  $effect(() => {\n    ctx.variant = variant\n    ctx.size = size\n    ctx.spacing = spacing\n    ctx.type = type\n    ctx.value = value\n  })\n\n  // Sliding pill only for single-select. Multi-select paints per-item surfaces.\n  const indicatorActive = $derived(animated !== false && type !== 'multiple')\n\n  let rootEl: HTMLDivElement | null = $state(null)\n  let indicatorStyle = $state<Record<string, string>>({ opacity: '0' })\n\n  function motionSafeTransition(firstPosition: boolean) {\n    if (typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n      return 'none'\n    }\n    return firstPosition\n      ? 'none'\n      : 'transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms cubic-bezier(0.22, 1, 0.36, 1), height 220ms cubic-bezier(0.22, 1, 0.36, 1), border-radius 220ms cubic-bezier(0.22, 1, 0.36, 1)'\n  }\n\n  function updateIndicator(firstPosition: boolean) {\n    const root = rootEl\n    if (!root) return false\n    const active = root.querySelector<HTMLElement>('[data-slot=\"toggle-group-item\"][data-state=\"on\"]')\n    if (!active) {\n      indicatorStyle = { opacity: '0' }\n      return false\n    }\n    const listRect = root.getBoundingClientRect()\n    const activeRect = active.getBoundingClientRect()\n    const left = activeRect.left - listRect.left + root.scrollLeft\n    const top = activeRect.top - listRect.top + root.scrollTop\n    indicatorStyle = {\n      width: `${activeRect.width}px`,\n      height: `${activeRect.height}px`,\n      transform: `translate3d(${left}px, ${top}px, 0)`,\n      borderRadius: getComputedStyle(active).borderRadius,\n      opacity: '1',\n      transition: motionSafeTransition(firstPosition),\n    }\n    return true\n  }\n\n  $effect(() => {\n    const active = indicatorActive\n    // Re-seat the pill when layout inputs change.\n    void spacing\n    void size\n    void variant\n    void orientation\n    void rootEl\n    if (!active || !rootEl) {\n      indicatorStyle = { opacity: '0' }\n      return\n    }\n    const root = rootEl\n    let firstPosition = true\n    const ro = new ResizeObserver(() => {\n      firstPosition = !updateIndicator(firstPosition) ? firstPosition : false\n    })\n    ro.observe(root)\n    root.querySelectorAll('[data-slot=\"toggle-group-item\"]').forEach((el) => ro.observe(el))\n    const mo = new MutationObserver((mutations) => {\n      for (const m of mutations) {\n        if (m.type === 'childList') {\n          root.querySelectorAll('[data-slot=\"toggle-group-item\"]').forEach((el) => ro.observe(el))\n        }\n      }\n      tick().then(() => {\n        firstPosition = !updateIndicator(firstPosition) ? firstPosition : false\n      })\n    })\n    mo.observe(root, {\n      attributes: true,\n      attributeFilter: ['data-state'],\n      subtree: true,\n      childList: true,\n    })\n    firstPosition = !updateIndicator(firstPosition) ? firstPosition : false\n    return () => {\n      ro.disconnect()\n      mo.disconnect()\n    }\n  })\n\n  /** Arrow-key navigation between items (focus moves; selection stays on click/space). */\n  function handleKeydown(event: ToggleGroupKeydownEvent) {\n    onkeydown?.(event)\n    if (!rovingFocus || !rootEl || event.defaultPrevented) return\n    const items = Array.from(\n      rootEl.querySelectorAll<HTMLElement>('[data-slot=\"toggle-group-item\"]:not(:disabled)'),\n    )\n    if (items.length < 2) return\n    const current = items.indexOf(document.activeElement as HTMLElement)\n    if (current === -1) return\n    const horizontal = orientation !== 'vertical'\n    let next = -1\n    if ((horizontal && event.key === 'ArrowRight') || (!horizontal && event.key === 'ArrowDown')) next = current + 1\n    else if ((horizontal && event.key === 'ArrowLeft') || (!horizontal && event.key === 'ArrowUp')) next = current - 1\n    else if (event.key === 'Home') next = 0\n    else if (event.key === 'End') next = items.length - 1\n    else return\n    event.preventDefault()\n    if (next < 0) next = loop ? items.length - 1 : 0\n    if (next >= items.length) next = loop ? 0 : items.length - 1\n    items[next]?.focus()\n  }\n</script>\n\n<div\n  bind:this={rootEl}\n  role=\"group\"\n  data-uipkge=\"\"\n  data-slot=\"toggle-group\"\n  data-size={size}\n  data-variant={variant}\n  data-spacing={spacing}\n  data-animated={indicatorActive ? 'true' : 'false'}\n  data-orientation={orientation}\n  data-disabled={disabled ? '' : undefined}\n  style:--gap={spacing}\n  dir={dir}\n  class={cn(\n    'group/toggle-group relative flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs',\n    className,\n  )}\n  onkeydown={handleKeydown}\n  {...restProps}\n>\n  {#if indicatorActive}\n    <span\n      data-slot=\"toggle-group-indicator\"\n      aria-hidden=\"true\"\n      class=\"bg-accent pointer-events-none absolute top-0 left-0 z-0 shadow-xs will-change-transform\"\n      style={Object.entries(indicatorStyle)\n        .map(([k, v]) => `${k}: ${v}`)\n        .join('; ')}\n    ></span>\n  {/if}\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/toggle-group/ToggleGroup.svelte"
    },
    {
      "path": "packages/registry-svelte/components/toggle-group/ToggleGroupItem.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  type ToggleGroupItemClickEvent = Parameters<NonNullable<HTMLButtonAttributes['onclick']>>[0]\n\n  export interface ToggleGroupItemProps extends HTMLButtonAttributes {\n    variant?: 'default' | 'outline'\n    size?: 'default' | 'sm' | 'lg'\n    value: string\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { toggleVariants } from '$lib/components/ui/toggle/toggle.variants'\n  import { getToggleGroupContext } from './context.svelte'\n\n  let {\n    class: className,\n    variant,\n    size,\n    value: itemValue,\n    disabled = false,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: ToggleGroupItemProps = $props()\n\n  const ctx = getToggleGroupContext()\n\n  const effectiveVariant = $derived(ctx?.variant ?? variant)\n  const effectiveSize = $derived(ctx?.size ?? size)\n  const effectiveSpacing = $derived(ctx?.spacing)\n  const selected = $derived(ctx?.isSelected(itemValue) ?? false)\n\n  function handleClick(event: ToggleGroupItemClickEvent) {\n    onclick?.(event)\n    if (disabled || event.defaultPrevented) return\n    ctx?.select(itemValue)\n  }\n</script>\n\n<button\n  bind:this={ref}\n  type=\"button\"\n  data-uipkge=\"\"\n  data-slot=\"toggle-group-item\"\n  data-variant={effectiveVariant}\n  data-size={effectiveSize}\n  data-spacing={effectiveSpacing}\n  data-state={selected ? 'on' : 'off'}\n  aria-pressed={selected}\n  {disabled}\n  class={cn(\n    toggleVariants({\n      variant: effectiveVariant,\n      size: effectiveSize,\n    }),\n    // z-10 keeps label/icons above the sliding indicator. When the parent\n    // has data-animated=true (single-select), on-state surface lives on the\n    // indicator — suppress item bg so the pill can slide cleanly.\n    'relative z-10 w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10',\n    'group-data-[animated=true]/toggle-group:data-[state=on]:bg-transparent group-data-[animated=true]/toggle-group:data-[state=on]:hover:bg-transparent',\n    // first/last-of-type (not first/last-child): sliding indicator is a sibling span\n    // and must not steal end-cap rounding or the outline left border.\n    'data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first-of-type:rounded-l-md data-[spacing=0]:last-of-type:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first-of-type:border-l',\n    className,\n  )}\n  onclick={handleClick}\n  {...restProps}\n>\n  {@render children?.()}\n</button>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/toggle-group/ToggleGroupItem.svelte"
    },
    {
      "path": "packages/registry-svelte/components/toggle-group/context.svelte.ts",
      "content": "import { getContext, setContext } from 'svelte'\n\nexport type ToggleGroupType = 'single' | 'multiple'\n\nexport class ToggleGroupContextState {\n  variant = $state<'default' | 'outline' | undefined>(undefined)\n  size = $state<'default' | 'sm' | 'lg' | undefined>(undefined)\n  spacing = $state(0)\n  /** Mirrored selection for item chrome. Synced from the group's `value`. */\n  value = $state<string | string[] | undefined>(undefined)\n  type = $state<ToggleGroupType | undefined>(undefined)\n  /** Assigned by the group during init; items call it on click. */\n  select: (itemValue: string) => void = () => {}\n\n  isSelected(itemValue: string): boolean {\n    if (this.type === 'multiple') {\n      return Array.isArray(this.value) ? this.value.includes(itemValue) : false\n    }\n    return this.value === itemValue\n  }\n}\n\nconst TOGGLE_GROUP_KEY = Symbol('ToggleGroupContext')\n\nexport function setToggleGroupContext(ctx: ToggleGroupContextState) {\n  setContext(TOGGLE_GROUP_KEY, ctx)\n}\n\nexport function getToggleGroupContext(): ToggleGroupContextState | undefined {\n  return getContext<ToggleGroupContextState | undefined>(TOGGLE_GROUP_KEY)\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/toggle-group/context.svelte.ts"
    },
    {
      "path": "packages/registry-svelte/components/toggle-group/index.ts",
      "content": "export { default as ToggleGroup, type ToggleGroupProps } from './ToggleGroup.svelte'\nexport { default as ToggleGroupItem, type ToggleGroupItemProps } from './ToggleGroupItem.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/toggle-group/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/toggle.json"
  ],
  "description": "Group of `Toggle` buttons that act as a single-select or multi-select control. Use for view-mode pickers (grid/list), text-format toolbars, and any \"pick one of N\" button bar.",
  "categories": [
    "action"
  ]
}