{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command",
  "title": "Command",
  "description": "Ink UI Command component.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "utils",
    "theme",
    "input-group",
    "scroll-area"
  ],
  "files": [
    {
      "path": "registry/default/ui/command.tsx",
      "content": "import * as React from \"react\";\nimport { Autocomplete as BaseAutocomplete } from \"@base-ui/react/autocomplete\";\nimport { cn } from \"@/lib/utils\";\nimport { InputGroupInput, InputGroupTextarea } from \"./input-group\";\nimport { ScrollArea } from \"./scroll-area\";\n\ninterface CommandTriggerMatch {\n  query: string;\n  start: number;\n  end: number;\n}\n\ntype CommandTriggerMatcher = (value: string, caret: number) => CommandTriggerMatch | null;\n\ninterface CreateCommandTriggerOptions {\n  position?: \"start\" | \"anywhere\";\n}\n\nconst DEFAULT_COMMAND_TRIGGERS: readonly string[] = [\"/\", \"、\"];\n\nfunction isGroupedItems(\n  items: readonly unknown[],\n): items is readonly { items: readonly unknown[] }[] {\n  const first = items[0];\n  return (\n    typeof first === \"object\" && first !== null && \"items\" in first && Array.isArray(first.items)\n  );\n}\n\nfunction createCommandTrigger(\n  trigger: string | readonly string[],\n  { position = \"start\" }: CreateCommandTriggerOptions = {},\n): CommandTriggerMatcher {\n  const triggers = typeof trigger === \"string\" ? [trigger] : trigger;\n\n  return (value, caret) => {\n    if (position === \"start\") {\n      const prefix = triggers.find((candidate) => value.startsWith(candidate));\n      if (prefix === undefined) return null;\n      const query = value.slice(prefix.length);\n      if (/\\s/.test(query)) return null;\n      return { end: value.length, query, start: 0 };\n    }\n\n    const before = value.slice(0, caret);\n    let start = -1;\n    let matched = \"\";\n    for (const candidate of triggers) {\n      const index = before.lastIndexOf(candidate);\n      if (index > start) {\n        start = index;\n        matched = candidate;\n      }\n    }\n    if (start === -1) return null;\n    if (start > 0 && !/\\s/.test(before[start - 1] ?? \"\")) return null;\n    const query = before.slice(start + matched.length);\n    if (/\\s/.test(query)) return null;\n    return { end: caret, query, start };\n  };\n}\n\ntype CommandConfirmKey = \"enter\" | \"tab\" | \"both\";\n\ninterface CommandContextValue {\n  open: boolean;\n  query: string;\n  confirmKey: CommandConfirmKey;\n  inputRef: React.RefObject<HTMLInputElement | null>;\n  highlightedRef: React.RefObject<unknown>;\n  getAnchor: () => Element | null;\n  stageSelection: (itemValue: unknown) => void;\n}\n\nconst CommandContext = React.createContext<CommandContextValue | null>(null);\n\nfunction useCommandContext() {\n  const context = React.useContext(CommandContext);\n  if (!context) {\n    throw new Error(\"Command components must be used within <Command>.\");\n  }\n  return context;\n}\n\nfunction useCommand() {\n  const { open, query } = useCommandContext();\n  return { open, query: open ? query : \"\" };\n}\n\ntype CommandChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails;\n\ninterface CommandSelectDetails {\n  match: CommandTriggerMatch | null;\n  query: string;\n  value: string;\n}\n\ntype CommandFilter<ItemValue> = (\n  itemValue: ItemValue,\n  query: string,\n  itemToStringValue?: (itemValue: ItemValue) => string,\n) => boolean;\n\ninterface CommandProps<ItemValue> extends Omit<\n  BaseAutocomplete.Root.Props<ItemValue>,\n  | \"mode\"\n  | \"inline\"\n  | \"open\"\n  | \"defaultOpen\"\n  | \"openOnInputClick\"\n  | \"filter\"\n  | \"value\"\n  | \"defaultValue\"\n  | \"onValueChange\"\n  | \"filteredItems\"\n  | \"limit\"\n> {\n  trigger?: string | readonly string[] | CommandTriggerMatcher;\n  filter?: CommandFilter<ItemValue>;\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string, eventDetails: CommandChangeEventDetails) => void;\n  onSelect?: (itemValue: ItemValue, details: CommandSelectDetails) => void;\n  confirmKey?: CommandConfirmKey;\n}\n\nfunction Command<Items extends readonly { items: readonly any[] }[]>(\n  props: Omit<CommandProps<Items[number][\"items\"][number]>, \"items\"> & { items: Items },\n): React.JSX.Element;\nfunction Command<ItemValue>(\n  props: Omit<CommandProps<ItemValue>, \"items\"> & { items?: readonly ItemValue[] | undefined },\n): React.JSX.Element;\nfunction Command({\n  trigger = DEFAULT_COMMAND_TRIGGERS,\n  filter,\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n  onOpenChange,\n  onSelect,\n  onItemHighlighted,\n  confirmKey = \"enter\",\n  autoHighlight = \"always\",\n  keepHighlight = true,\n  itemToStringValue,\n  ...props\n}: CommandProps<any>) {\n  const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue ?? \"\");\n  const isControlled = valueProp !== undefined;\n  const value = isControlled ? valueProp : uncontrolledValue;\n\n  const [open, setOpen] = React.useState(false);\n  const [match, setMatch] = React.useState<CommandTriggerMatch | null>(null);\n\n  const inputRef = React.useRef<HTMLInputElement | null>(null);\n  const highlightedRef = React.useRef<unknown>(undefined);\n  const pendingSelectionRef = React.useRef<{ itemValue: unknown } | null>(null);\n\n  const triggers =\n    typeof trigger === \"function\" ? null : typeof trigger === \"string\" ? [trigger] : trigger;\n  const matcher = React.useMemo(\n    () => (typeof trigger === \"function\" ? trigger : createCommandTrigger(trigger)),\n    [trigger],\n  );\n  const collator = BaseAutocomplete.useFilter({ locale: props.locale });\n  const query = match?.query ?? \"\";\n\n  const filteredItems = React.useMemo(() => {\n    const { items } = props;\n    if (!items || query === \"\") return items;\n    const matches = (itemValue: unknown) =>\n      (filter ?? collator.contains)(itemValue, query, itemToStringValue);\n    return isGroupedItems(items)\n      ? items\n          .map((group) => ({ ...group, items: group.items.filter(matches) }))\n          .filter((group) => group.items.length > 0)\n      : items.filter(matches);\n  }, [props.items, filter, collator, query, itemToStringValue]);\n\n  const commitValue = (next: string, eventDetails: CommandChangeEventDetails) => {\n    if (!isControlled) setUncontrolledValue(next);\n    onValueChange?.(next, eventDetails);\n  };\n\n  const commitOpen = (next: boolean, eventDetails: CommandChangeEventDetails) => {\n    if (next === open) return;\n    setOpen(next);\n    onOpenChange?.(next, eventDetails);\n  };\n\n  const getCaret = (fallback: string) => inputRef.current?.selectionStart ?? fallback.length;\n\n  const completeSelection = (eventDetails: CommandChangeEventDetails) => {\n    const pending = pendingSelectionRef.current;\n    if (!pending) return;\n    pendingSelectionRef.current = null;\n\n    const nextValue = match ? value.slice(0, match.start) + value.slice(match.end) : value;\n    commitValue(nextValue, eventDetails);\n    commitOpen(false, eventDetails);\n    onSelect?.(pending.itemValue, { match, query: match?.query ?? \"\", value: nextValue });\n  };\n\n  const handleValueChange = (next: string, eventDetails: CommandChangeEventDetails) => {\n    const { reason } = eventDetails;\n    if (reason === \"item-press\") {\n      completeSelection(eventDetails);\n      return;\n    }\n\n    commitValue(next, eventDetails);\n\n    const nextMatch = matcher(next, getCaret(next));\n    if (nextMatch) {\n      setMatch(nextMatch);\n      commitOpen(true, eventDetails);\n    } else {\n      commitOpen(false, eventDetails);\n    }\n  };\n\n  const handleOpenChange = (next: boolean, eventDetails: CommandChangeEventDetails) => {\n    const { reason } = eventDetails;\n    if (!next) {\n      commitOpen(false, eventDetails);\n      return;\n    }\n\n    if (reason === \"input-change\" || reason === \"input-press\") return;\n\n    const insert = triggers?.[0];\n    if (insert && value === \"\") {\n      commitValue(insert, eventDetails);\n      setMatch({ end: insert.length, query: \"\", start: 0 });\n    } else {\n      setMatch(matcher(value, getCaret(value)));\n    }\n    commitOpen(true, eventDetails);\n  };\n\n  const contextValue = React.useMemo<CommandContextValue>(\n    () => ({\n      getAnchor: () =>\n        inputRef.current?.closest(\"[data-slot=input-group]\") ?? inputRef.current ?? null,\n      confirmKey,\n      highlightedRef,\n      inputRef,\n      open,\n      query,\n      stageSelection: (itemValue) => {\n        pendingSelectionRef.current = { itemValue };\n      },\n    }),\n    [confirmKey, open, query],\n  );\n\n  return (\n    <CommandContext.Provider value={contextValue}>\n      <BaseAutocomplete.Root\n        {...props}\n        autoHighlight={autoHighlight}\n        filteredItems={filteredItems}\n        itemToStringValue={itemToStringValue}\n        keepHighlight={keepHighlight}\n        mode=\"list\"\n        onItemHighlighted={(highlightedValue, eventDetails) => {\n          highlightedRef.current = highlightedValue;\n          onItemHighlighted?.(highlightedValue, eventDetails);\n        }}\n        onOpenChange={handleOpenChange}\n        onValueChange={handleValueChange}\n        open={open}\n        openOnInputClick={false}\n        value={value}\n      />\n    </CommandContext.Provider>\n  );\n}\n\nconst PASSTHROUGH_KEYS_WHEN_CLOSED = new Set([\"ArrowUp\", \"ArrowDown\", \"Home\", \"End\", \"Escape\"]);\n\ninterface CommandInputProps extends BaseAutocomplete.Input.Props {\n  multiline?: boolean;\n}\n\nfunction CommandInput({ multiline = false, onKeyDown, ref, render, ...props }: CommandInputProps) {\n  const { open, confirmKey, highlightedRef, inputRef } = useCommandContext();\n\n  return (\n    <BaseAutocomplete.Input\n      ref={(element: HTMLInputElement | null) => {\n        inputRef.current = element;\n        if (typeof ref === \"function\") return ref(element);\n        if (ref) ref.current = element;\n      }}\n      onKeyDown={(event) => {\n        if (!open) {\n          if (PASSTHROUGH_KEYS_WHEN_CLOSED.has(event.key)) event.preventBaseUIHandler();\n          onKeyDown?.(event);\n          return;\n        }\n\n        if (event.key === \"Home\" || event.key === \"End\") event.preventBaseUIHandler();\n\n        const hasModifier = event.shiftKey || event.metaKey || event.ctrlKey || event.altKey;\n        const canConfirm = highlightedRef.current !== undefined && !hasModifier;\n        const isEnter = event.key === \"Enter\" && confirmKey !== \"tab\";\n        const isTab = event.key === \"Tab\" && confirmKey !== \"enter\";\n\n        if (event.key === \"Enter\" && !isEnter) event.preventBaseUIHandler();\n\n        if (canConfirm && isTab) {\n          // Base UI only confirms on Enter, so click the highlighted item ourselves.\n          event.preventDefault();\n          const activeId = event.currentTarget.getAttribute(\"aria-activedescendant\");\n          if (activeId) document.getElementById(activeId)?.click();\n          return;\n        }\n\n        if (!(canConfirm && isEnter)) onKeyDown?.(event);\n      }}\n      render={render ?? (multiline ? <InputGroupTextarea /> : <InputGroupInput />)}\n      {...props}\n    />\n  );\n}\n\nfunction CommandTrigger({ className, ...props }: BaseAutocomplete.Trigger.Props) {\n  return (\n    <BaseAutocomplete.Trigger\n      className={cn(\"rounded-sm\", className)}\n      data-slot=\"command-trigger\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandContent({\n  className,\n  positionerProps,\n  children,\n  ...props\n}: BaseAutocomplete.Popup.Props & {\n  positionerProps?: BaseAutocomplete.Positioner.Props;\n}) {\n  const { getAnchor } = useCommandContext();\n\n  return (\n    <BaseAutocomplete.Portal>\n      <BaseAutocomplete.Positioner\n        align=\"start\"\n        anchor={getAnchor}\n        side=\"top\"\n        sideOffset={8}\n        {...positionerProps}\n        className={cn(\n          \"z-50 w-(--anchor-width) outline-none select-none\",\n          positionerProps?.className,\n        )}\n      >\n        <BaseAutocomplete.Popup\n          className={cn(\n            \"group flex max-h-[min(var(--available-height),20rem)] origin-bottom flex-col overflow-hidden rounded-xl bg-popover text-popover-foreground shadow-lg outline outline-border transition-[transform,scale,opacity] data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 data-[side=bottom]:origin-top dark:shadow-none\",\n            className,\n          )}\n          data-slot=\"command-popup\"\n          {...props}\n        >\n          <ScrollArea\n            className=\"flex min-h-0 flex-1 flex-col\"\n            viewportProps={{ className: \"min-h-0 flex-1 p-1.5\" }}\n            scrollBarProps={{ className: \"my-2\" }}\n          >\n            {children}\n          </ScrollArea>\n        </BaseAutocomplete.Popup>\n      </BaseAutocomplete.Positioner>\n    </BaseAutocomplete.Portal>\n  );\n}\n\nfunction CommandList({ className, children, ...props }: BaseAutocomplete.List.Props) {\n  return (\n    <BaseAutocomplete.List\n      className={cn(\"flex flex-col\", className)}\n      data-slot=\"command-list\"\n      {...props}\n    >\n      {children}\n    </BaseAutocomplete.List>\n  );\n}\n\nfunction CommandItem({ className, children, onClick, ...props }: BaseAutocomplete.Item.Props) {\n  const { stageSelection } = useCommandContext();\n\n  return (\n    <BaseAutocomplete.Item\n      className={cn(\n        \"group flex cursor-default items-center gap-2 rounded-lg p-2 text-sm leading-4 outline-none select-none data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 data-highlighted:bg-primary data-highlighted:text-primary-foreground pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem]\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:[&_svg:not([class*='text-'])]:text-primary-foreground\",\n        className,\n      )}\n      data-slot=\"command-item\"\n      onClick={(event) => {\n        onClick?.(event);\n        stageSelection(props.value);\n      }}\n      {...props}\n    >\n      {children}\n    </BaseAutocomplete.Item>\n  );\n}\n\nfunction CommandItemLabel({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      className={cn(\"truncate font-medium\", className)}\n      data-slot=\"command-item-label\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandItemDescription({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      className={cn(\n        \"truncate text-muted-foreground group-data-highlighted:text-primary-foreground/70\",\n        className,\n      )}\n      data-slot=\"command-item-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      className={cn(\n        \"ml-auto flex shrink-0 items-center gap-1 text-xs text-muted-foreground group-data-highlighted:text-primary-foreground/70\",\n        className,\n      )}\n      data-slot=\"command-shortcut\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandEmpty({ className, children, ...props }: BaseAutocomplete.Empty.Props) {\n  return (\n    <BaseAutocomplete.Empty\n      className={cn(\"px-2.5 py-2 text-sm text-muted-foreground empty:m-0 empty:p-0\", className)}\n      data-slot=\"command-empty\"\n      {...props}\n    >\n      {children}\n    </BaseAutocomplete.Empty>\n  );\n}\n\nfunction CommandStatus({ className, children, ...props }: BaseAutocomplete.Status.Props) {\n  return (\n    <BaseAutocomplete.Status\n      className={cn(\"px-2.5 py-2 text-sm text-muted-foreground empty:m-0 empty:p-0\", className)}\n      data-slot=\"command-status\"\n      {...props}\n    >\n      {children}\n    </BaseAutocomplete.Status>\n  );\n}\n\nfunction CommandSeparator({ className, ...props }: BaseAutocomplete.Separator.Props) {\n  return (\n    <BaseAutocomplete.Separator\n      className={cn(\"-mx-1.5 my-1.5 h-px bg-border\", className)}\n      data-slot=\"command-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroup({ className, ...props }: BaseAutocomplete.Group.Props) {\n  return (\n    <BaseAutocomplete.Group\n      className={cn(\"flex flex-col\", className)}\n      data-slot=\"command-group\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroupLabel({ className, ...props }: BaseAutocomplete.GroupLabel.Props) {\n  return (\n    <BaseAutocomplete.GroupLabel\n      className={cn(\"px-2 py-1.5 text-xs font-medium text-muted-foreground select-none\", className)}\n      data-slot=\"command-group-label\"\n      {...props}\n    />\n  );\n}\n\nconst CommandCollection = BaseAutocomplete.Collection;\nconst CommandRow = BaseAutocomplete.Row;\n\nexport {\n  Command,\n  CommandInput,\n  CommandTrigger,\n  CommandContent,\n  CommandList,\n  CommandItem,\n  CommandItemLabel,\n  CommandItemDescription,\n  CommandShortcut,\n  CommandEmpty,\n  CommandStatus,\n  CommandSeparator,\n  CommandGroup,\n  CommandGroupLabel,\n  CommandCollection,\n  CommandRow,\n  createCommandTrigger,\n  useCommand,\n};\nexport type {\n  CommandConfirmKey,\n  CommandProps,\n  CommandInputProps,\n  CommandFilter,\n  CommandSelectDetails,\n  CommandTriggerMatch,\n  CommandTriggerMatcher,\n  CreateCommandTriggerOptions,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/command.tsx"
    }
  ],
  "type": "registry:ui"
}