{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "command",
  "title": "Command",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/command/command.component.ts",
      "content": "import {\n  AfterContentInit,\n  AfterViewInit,\n  Component,\n  DestroyRef,\n  Directive,\n  ElementRef,\n  EventEmitter,\n  Input,\n  OnChanges,\n  OnDestroy,\n  OnInit,\n  Output,\n  TemplateRef,\n  ViewChild,\n  ViewContainerRef,\n  booleanAttribute,\n  computed,\n  forwardRef,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport {\n  BodyPortal,\n  afterExitAnimation,\n  lockScroll,\n  pushDismissableLayer,\n  trapFocus,\n  uniqueId,\n} from '@/ui/popper/popper'\n\nexport type CommandFilter = (value: string, search: string, keywords?: string[]) => number\n\n// ---------------------------------------------------------------------------\n// command-score (the ranking cmdk uses): fuzzy abbreviation match, rewarding\n// contiguous runs and word starts, penalising skipped characters.\nconst SCORE_CONTINUE_MATCH = 1\nconst SCORE_SPACE_WORD_JUMP = 0.9\nconst SCORE_NON_SPACE_WORD_JUMP = 0.8\nconst SCORE_CHARACTER_JUMP = 0.17\nconst SCORE_TRANSPOSITION = 0.1\nconst PENALTY_SKIPPED = 0.999\nconst PENALTY_CASE_MISMATCH = 0.9999\nconst PENALTY_NOT_COMPLETE = 0.99\nconst IS_GAP_REGEXP = /[\\\\/_+.#\"@[({&]/\nconst COUNT_GAPS_REGEXP = /[\\\\/_+.#\"@[({&]/g\nconst IS_SPACE_REGEXP = /[\\s-]/\nconst COUNT_SPACE_REGEXP = /[\\s-]/g\n\nfunction scoreInner(\n  string: string,\n  abbreviation: string,\n  lowerString: string,\n  lowerAbbreviation: string,\n  stringIndex: number,\n  abbreviationIndex: number,\n  memo: Record<string, number>,\n): number {\n  if (abbreviationIndex === abbreviation.length) {\n    return stringIndex === string.length ? SCORE_CONTINUE_MATCH : PENALTY_NOT_COMPLETE\n  }\n  const memoKey = `${stringIndex},${abbreviationIndex}`\n  if (memo[memoKey] !== undefined) return memo[memoKey]!\n  const abbreviationChar = lowerAbbreviation.charAt(abbreviationIndex)\n  let index = lowerString.indexOf(abbreviationChar, stringIndex)\n  let highScore = 0\n  while (index >= 0) {\n    let score = scoreInner(string, abbreviation, lowerString, lowerAbbreviation, index + 1, abbreviationIndex + 1, memo)\n    if (score > highScore) {\n      if (index === stringIndex) score *= SCORE_CONTINUE_MATCH\n      else if (IS_GAP_REGEXP.test(string.charAt(index - 1))) {\n        score *= SCORE_NON_SPACE_WORD_JUMP\n        const breaks = string.slice(stringIndex, index - 1).match(COUNT_GAPS_REGEXP)\n        if (breaks && stringIndex > 0) score *= Math.pow(PENALTY_SKIPPED, breaks.length)\n      } else if (IS_SPACE_REGEXP.test(string.charAt(index - 1))) {\n        score *= SCORE_SPACE_WORD_JUMP\n        const breaks = string.slice(stringIndex, index - 1).match(COUNT_SPACE_REGEXP)\n        if (breaks && stringIndex > 0) score *= Math.pow(PENALTY_SKIPPED, breaks.length)\n      } else {\n        score *= SCORE_CHARACTER_JUMP\n        if (stringIndex > 0) score *= Math.pow(PENALTY_SKIPPED, index - stringIndex)\n      }\n      if (string.charAt(index) !== abbreviation.charAt(abbreviationIndex)) score *= PENALTY_CASE_MISMATCH\n    }\n    if (\n      (score < SCORE_TRANSPOSITION &&\n        lowerString.charAt(index - 1) === lowerAbbreviation.charAt(abbreviationIndex + 1)) ||\n      (lowerAbbreviation.charAt(abbreviationIndex + 1) === lowerAbbreviation.charAt(abbreviationIndex) &&\n        lowerString.charAt(index - 1) !== lowerAbbreviation.charAt(abbreviationIndex))\n    ) {\n      const transposed = scoreInner(\n        string,\n        abbreviation,\n        lowerString,\n        lowerAbbreviation,\n        index + 1,\n        abbreviationIndex + 2,\n        memo,\n      )\n      if (transposed * SCORE_TRANSPOSITION > score) score = transposed * SCORE_TRANSPOSITION\n    }\n    if (score > highScore) highScore = score\n    index = lowerString.indexOf(abbreviationChar, index + 1)\n  }\n  memo[memoKey] = highScore\n  return highScore\n}\n\nconst formatInput = (s: string) => s.toLowerCase().replace(COUNT_SPACE_REGEXP, ' ')\n\n/** cmdk's default filter: 0 hides the item, higher ranks it earlier. */\nexport function commandScore(value: string, search: string, keywords?: string[]): number {\n  const string = keywords && keywords.length > 0 ? `${value} ${keywords.join(' ')}` : value\n  return scoreInner(string, search, formatInput(string), formatInput(search), 0, 0, {})\n}\n\nconst GROUP_ITEMS_SELECTOR = '[cmdk-group-items]'\nconst LIST_SIZER_SELECTOR = '[cmdk-list-sizer]'\n\n/**\n * cmdk root state shared by `ui-command` and `ui-command-dialog` (the dialog renders its\n * own Command inside the portal, so projected items resolve the root through this token).\n */\n@Directive()\nexport abstract class CommandRoot {\n  /** Accessible label for the input (cmdk renders a visually hidden <label>). */\n  @Input() label = ''\n  @Input({ transform: booleanAttribute }) shouldFilter = true\n  @Input() filter?: CommandFilter\n  @Input({ transform: booleanAttribute }) loop = false\n  @Input({ transform: booleanAttribute }) vimBindings = true\n  @Input({ transform: booleanAttribute }) disablePointerSelection = false\n  /** Controlled selected (highlighted) item value. */\n  @Input()\n  set value(v: string | undefined) {\n    if (v !== undefined) this.selected.set(v)\n  }\n  get value(): string | undefined {\n    return this.selected()\n  }\n  @Output() valueChange = new EventEmitter<string>()\n\n  readonly search = signal('')\n  readonly selected = signal<string | undefined>(undefined)\n  readonly items = signal<UiCommandItemComponent[]>([])\n  readonly groups = signal<UiCommandGroupComponent[]>([])\n  readonly inputId = uniqueId('cmdk-input')\n  readonly listId = uniqueId('cmdk-list')\n  readonly labelId = uniqueId('cmdk-label')\n\n  /** Items matching the search (cmdk `filtered.count`, drives CommandEmpty). */\n  readonly visibleCount = computed(() => this.items().filter((i) => i.visible()).length)\n  readonly selectedId = computed(() => this.items().find((i) => i.valueSig() === this.selected())?.id ?? null)\n\n  /** The element carrying `cmdk-root` (host for `ui-command`, the portalled panel for the dialog). */\n  abstract rootElement(): HTMLElement | null | undefined\n\n  scoreFor(value: string, keywords: string[] | undefined): number {\n    const search = this.search()\n    if (!this.shouldFilter || !search) return 1\n    return (this.filter ?? commandScore)(value, search, keywords)\n  }\n\n  register(item: UiCommandItemComponent): void {\n    this.items.update((list) => [...list, item])\n  }\n\n  unregister(item: UiCommandItemComponent): void {\n    this.items.update((list) => list.filter((i) => i !== item))\n  }\n\n  registerGroup(group: UiCommandGroupComponent): void {\n    this.groups.update((list) => [...list, group])\n  }\n\n  unregisterGroup(group: UiCommandGroupComponent): void {\n    this.groups.update((list) => list.filter((g) => g !== group))\n  }\n\n  setSearch(value: string): void {\n    if (value === this.search()) return\n    this.search.set(value)\n    if (value) this.sort()\n    else this.restoreOrder()\n    this.selectFirst()\n  }\n\n  setSelected(value: string | undefined, scroll = false): void {\n    if (value === undefined || value === this.selected()) return\n    this.selected.set(value)\n    this.valueChange.emit(value)\n    if (scroll) this.scrollSelectedIntoView()\n  }\n\n  /** Visible, enabled items in DOM order (cmdk getValidItems). */\n  validItems(): UiCommandItemComponent[] {\n    const root = this.rootElement()\n    if (!root) return []\n    const byEl = new Map(this.items().map((i) => [i.element, i]))\n    return [...root.querySelectorAll<HTMLElement>('[cmdk-item]')]\n      .map((el) => byEl.get(el))\n      .filter((i): i is UiCommandItemComponent => !!i && i.visible() && !i.disabled)\n  }\n\n  selectFirst(): void {\n    const first = this.validItems()[0]\n    if (first) this.setSelected(first.valueSig())\n  }\n\n  private selectedItem(): UiCommandItemComponent | undefined {\n    return this.items().find((i) => i.valueSig() === this.selected() && i.visible())\n  }\n\n  /** Children of every container sort() reorders, in source order, taken before the first reorder. */\n  private sourceOrder: Map<HTMLElement, HTMLElement[]> | null = null\n\n  private snapshotOrder(root: HTMLElement): void {\n    if (this.sourceOrder) return\n    const containers = new Set<HTMLElement>()\n    const sizer = root.querySelector<HTMLElement>(LIST_SIZER_SELECTOR)\n    if (sizer) containers.add(sizer)\n    for (const el of root.querySelectorAll<HTMLElement>(GROUP_ITEMS_SELECTOR)) containers.add(el)\n    for (const g of this.groups()) if (g.element.parentElement) containers.add(g.element.parentElement)\n    this.sourceOrder = new Map([...containers].map((c) => [c, [...c.children] as HTMLElement[]]))\n  }\n\n  /**\n   * An empty search shows the list in source order again. cmdk gets this from React\n   * re-rendering; here sort() moved real DOM nodes, so put them back. Nodes added since the\n   * snapshot keep their place after the original ones.\n   */\n  private restoreOrder(): void {\n    if (!this.sourceOrder) return\n    for (const [container, children] of this.sourceOrder) {\n      const current = [...container.children] as HTMLElement[]\n      const known = children.filter((c) => c.parentElement === container)\n      const added = current.filter((c) => !children.includes(c))\n      for (const child of [...known, ...added]) container.appendChild(child)\n    }\n    this.sourceOrder = null\n  }\n\n  /** cmdk sort(): while searching, rank items inside their group and groups inside the list. */\n  private sort(): void {\n    const root = this.rootElement()\n    if (!root || !this.search() || !this.shouldFilter) return\n    this.snapshotOrder(root)\n    const sizer = root.querySelector<HTMLElement>(LIST_SIZER_SELECTOR)\n    const ranked = this.validItems().sort((a, b) => b.score() - a.score())\n    for (const item of ranked) {\n      const el = item.element\n      const groupItems = el.closest<HTMLElement>(GROUP_ITEMS_SELECTOR)\n      if (groupItems) {\n        groupItems.appendChild(el.parentElement === groupItems ? el : el.closest(`${GROUP_ITEMS_SELECTOR} > *`)!)\n      } else if (sizer) {\n        sizer.appendChild(el.parentElement === sizer ? el : (el.closest(`${LIST_SIZER_SELECTOR} > *`) ?? el))\n      }\n    }\n    const groups = this.groups()\n      .filter((g) => g.visible())\n      .map((g) => [g, Math.max(0, ...g.items().map((i) => i.score()))] as const)\n      .sort((a, b) => b[1] - a[1])\n    for (const [group] of groups) group.element.parentElement?.appendChild(group.element)\n  }\n\n  private scrollSelectedIntoView(): void {\n    const el = this.selectedItem()?.element\n    if (!el) return\n    if (el.parentElement?.firstElementChild === el) {\n      el.closest('[cmdk-group]')?.querySelector('[cmdk-group-heading]')?.scrollIntoView?.({ block: 'nearest' })\n    }\n    el.scrollIntoView?.({ block: 'nearest' })\n  }\n\n  private updateByItem(change: 1 | -1): void {\n    const items = this.validItems()\n    const index = items.findIndex((i) => i.valueSig() === this.selected())\n    let next = items[index + change]\n    if (this.loop) {\n      next =\n        index + change < 0\n          ? items[items.length - 1]\n          : index + change === items.length\n            ? items[0]\n            : items[index + change]\n    }\n    if (next) this.setSelected(next.valueSig(), true)\n  }\n\n  private updateByGroup(change: 1 | -1): void {\n    let group = this.selectedItem()?.element.closest('[cmdk-group]') ?? null\n    let next: UiCommandItemComponent | undefined\n    const valid = new Set(this.validItems().map((i) => i.element))\n    while (group && !next) {\n      group = change > 0 ? nextSibling(group, '[cmdk-group]') : previousSibling(group, '[cmdk-group]')\n      const els = group ? [...group.querySelectorAll<HTMLElement>('[cmdk-item]')].filter((el) => valid.has(el)) : []\n      next = this.items().find((i) => i.element === els[0])\n    }\n    if (next) this.setSelected(next.valueSig(), true)\n    else this.updateByItem(change)\n  }\n\n  private edge(last: boolean): void {\n    const items = this.validItems()\n    const item = last ? items[items.length - 1] : items[0]\n    if (item) this.setSelected(item.valueSig(), true)\n  }\n\n  private move(event: KeyboardEvent, change: 1 | -1): void {\n    event.preventDefault()\n    if (event.metaKey) this.edge(change > 0)\n    else if (event.altKey) this.updateByGroup(change)\n    else this.updateByItem(change)\n  }\n\n  /** cmdk root keyboard: arrows (+ Ctrl-n/j/p/k), Alt = by group, Meta / Home / End = edges, Enter selects. */\n  onKeydown(event: KeyboardEvent): void {\n    if (event.defaultPrevented) return\n    switch (event.key) {\n      case 'n':\n      case 'j':\n        if (this.vimBindings && event.ctrlKey) this.move(event, 1)\n        break\n      case 'ArrowDown':\n        this.move(event, 1)\n        break\n      case 'p':\n      case 'k':\n        if (this.vimBindings && event.ctrlKey) this.move(event, -1)\n        break\n      case 'ArrowUp':\n        this.move(event, -1)\n        break\n      case 'Home':\n        event.preventDefault()\n        this.edge(false)\n        break\n      case 'End':\n        event.preventDefault()\n        this.edge(true)\n        break\n      case 'Enter':\n        if (!event.isComposing && event.keyCode !== 229) {\n          event.preventDefault()\n          this.selectedItem()?.onSelect()\n        }\n    }\n  }\n}\n\nfunction nextSibling(el: Element, selector: string): Element | null {\n  let sibling = el.nextElementSibling\n  while (sibling) {\n    if (sibling.matches(selector)) return sibling\n    sibling = sibling.nextElementSibling\n  }\n  return null\n}\n\nfunction previousSibling(el: Element, selector: string): Element | null {\n  let sibling = el.previousElementSibling\n  while (sibling) {\n    if (sibling.matches(selector)) return sibling\n    sibling = sibling.previousElementSibling\n  }\n  return null\n}\n\n/**\n * Angular port of UIPKGE Command, behaving like the cmdk component the React version\n * wraps: typing filters and ranks items (command-score), the first match is highlighted,\n * arrow keys / Home / End / Enter drive the highlight, groups hide when empty, separators\n * hide while searching and CommandEmpty shows when nothing matches. Class strings are\n * identical to the React source.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command, [ui-command]',\n  standalone: true,\n  providers: [{ provide: CommandRoot, useExisting: forwardRef(() => UiCommandComponent) }],\n  host: {\n    tabindex: '-1',\n    'cmdk-root': '',\n    'data-uipkge': '',\n    'data-slot': 'command',\n    '[class]': 'hostClass',\n    '(keydown)': 'onKeydown($event)',\n  },\n  template: `\n    <label cmdk-label class=\"sr-only\" [attr.for]=\"inputId\" [id]=\"labelId\">{{ label }}</label>\n    <ng-content />\n  `,\n})\nexport class UiCommandComponent extends CommandRoot implements AfterContentInit {\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  @Input('class') className?: string\n\n  rootElement(): HTMLElement {\n    return this.el\n  }\n\n  ngAfterContentInit(): void {\n    if (this.selected() === undefined) this.selectFirst()\n  }\n\n  get hostClass(): string {\n    return cn(\n      'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',\n      this.className,\n    )\n  }\n}\n\nconst COMMAND_DIALOG_COMMAND_CLASS =\n  '[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-1 [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:size-5'\n\n/**\n * CommandDialog: a Radix-Dialog-style modal (body portal, overlay, focus trap + restore,\n * scroll lock, Escape / overlay dismiss, sr-only title + description) wrapping a Command.\n * Built on the popper helpers like the sheet; React composes @radix-ui/react-dialog\n * primitives directly too, not the registry Dialog component.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-dialog, [ui-command-dialog]',\n  standalone: true,\n  providers: [{ provide: CommandRoot, useExisting: forwardRef(() => UiCommandDialogComponent) }],\n  host: { class: 'hidden' },\n  template: `\n    <ng-template #panel>\n      <div\n        data-uipkge=\"\"\n        data-slot=\"command-dialog-overlay\"\n        [attr.data-state]=\"state()\"\n        class=\"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\"\n      ></div>\n      <div\n        role=\"dialog\"\n        tabindex=\"-1\"\n        [id]=\"contentId\"\n        [attr.aria-labelledby]=\"titleId\"\n        [attr.aria-describedby]=\"descriptionId\"\n        data-uipkge=\"\"\n        data-slot=\"command-dialog\"\n        [attr.data-state]=\"state()\"\n        class=\"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 overflow-hidden rounded-lg border p-0 shadow-lg duration-200\"\n      >\n        <h2 class=\"sr-only\" [id]=\"titleId\">{{ title }}</h2>\n        <p class=\"sr-only\" [id]=\"descriptionId\">{{ description }}</p>\n        <div\n          tabindex=\"-1\"\n          cmdk-root=\"\"\n          data-uipkge=\"\"\n          data-slot=\"command\"\n          [class]=\"commandClass\"\n          (keydown)=\"onKeydown($event)\"\n        >\n          <label cmdk-label class=\"sr-only\" [attr.for]=\"inputId\" [id]=\"labelId\">{{ label }}</label>\n          <ng-content />\n        </div>\n      </div>\n    </ng-template>\n  `,\n})\nexport class UiCommandDialogComponent extends CommandRoot implements OnChanges, OnDestroy {\n  private readonly portal = new BodyPortal(inject(ViewContainerRef))\n  @Input() open?: boolean\n  @Input() defaultOpen = false\n  @Input({ transform: booleanAttribute }) modal = true\n  @Input() title = 'Command Palette'\n  @Input() description = 'Search for a command to run...'\n  @Output() openChange = new EventEmitter<boolean>()\n  @ViewChild('panel', { static: true }) panelTpl!: TemplateRef<unknown>\n\n  readonly contentId = uniqueId('command-dialog')\n  readonly titleId = uniqueId('command-dialog-title')\n  readonly descriptionId = uniqueId('command-dialog-description')\n  readonly state = signal<'open' | 'closed'>('closed')\n  readonly commandClass = cn(\n    'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',\n    COMMAND_DIALOG_COMMAND_CLASS,\n  )\n  private readonly _open = signal<boolean | null>(null)\n  private commandEl?: HTMLElement\n  private panelEl?: HTMLElement\n  private restoreFocusTo: HTMLElement | null = null\n  private cleanups: (() => void)[] = []\n\n  get isOpen(): boolean {\n    if (this.open !== undefined) return this.open\n    return this._open() ?? this.defaultOpen\n  }\n\n  rootElement(): HTMLElement | undefined {\n    return this.commandEl\n  }\n\n  ngOnChanges(): void {\n    this.sync()\n  }\n\n  setOpen(value: boolean): void {\n    if (value === this.isOpen) return\n    this._open.set(value)\n    this.openChange.emit(value)\n    this.sync()\n  }\n\n  toggle(): void {\n    this.setOpen(!this.isOpen)\n  }\n\n  private sync(): void {\n    if (!this.panelTpl) return\n    if (this.isOpen && (!this.portal.attached || this.state() === 'closed')) {\n      if (this.portal.attached) this.portal.detach()\n      this.show()\n    } else if (!this.isOpen && this.portal.attached && this.state() === 'open') void this.hide()\n  }\n\n  private show(): void {\n    this.restoreFocusTo = document.activeElement as HTMLElement | null\n    this.state.set('open')\n    const host = this.portal.attach(this.panelTpl)\n    const panel = host.querySelector<HTMLElement>('[data-slot=\"command-dialog\"]')!\n    this.panelEl = panel\n    this.commandEl = panel.querySelector<HTMLElement>('[cmdk-root]')!\n    const close = () => this.setOpen(false)\n    this.cleanups.push(\n      pushDismissableLayer({\n        contains: (t) => panel.contains(t),\n        onEscape: close,\n        onPointerDownOutside: close,\n      }),\n      trapFocus(panel),\n    )\n    if (this.modal) this.cleanups.push(lockScroll())\n    // A fresh cmdk mounts on every open in React: start from the first item.\n    this.selected.set(undefined)\n    this.selectFirst()\n    queueMicrotask(() => {\n      const first = panel.querySelector<HTMLElement>(\n        '[autofocus],a[href],button:not([disabled]),input:not([disabled]),select,textarea,[tabindex]:not([tabindex=\"-1\"])',\n      )\n      ;(first ?? panel).focus({ preventScroll: true })\n    })\n  }\n\n  private async hide(): Promise<void> {\n    this.state.set('closed')\n    this.cleanups.splice(0).forEach((fn) => fn())\n    await afterExitAnimation(this.panelEl, 200)\n    if (this.isOpen) return\n    this.portal.detach()\n    this.panelEl = undefined\n    this.commandEl = undefined\n    // React unmounts the cmdk tree on close, so the query resets — and the list goes back to\n    // source order (the items are projected content, so they survive the close).\n    this.setSearch('')\n    this.restoreFocusTo?.focus?.({ preventScroll: true })\n  }\n\n  ngOnDestroy(): void {\n    this.cleanups.splice(0).forEach((fn) => fn())\n    this.portal.detach()\n  }\n}\n\n/** Search field: the host is the `cmdk-input-wrapper` row, `class` styles the <input> (as in React). */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-input, [ui-command-input]',\n  standalone: true,\n  host: {\n    'data-uipkge': '',\n    'data-slot': 'command-input-wrapper',\n    'cmdk-input-wrapper': '',\n    // Attribute (not class) binding: React sends className to the <input>, so a consumer\n    // `class` must not also land on the wrapper row.\n    '[attr.class]': '\"flex h-9 items-center gap-2 border-b px-3\"',\n  },\n  template: `\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      stroke-width=\"2\"\n      stroke-linecap=\"round\"\n      stroke-linejoin=\"round\"\n      class=\"lucide lucide-search text-muted-foreground size-4 shrink-0\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"m21 21-4.34-4.34\" />\n      <circle cx=\"11\" cy=\"11\" r=\"8\" />\n    </svg>\n    <input\n      #input\n      cmdk-input=\"\"\n      data-uipkge=\"\"\n      data-slot=\"command-input\"\n      autocomplete=\"off\"\n      autocorrect=\"off\"\n      spellcheck=\"false\"\n      aria-autocomplete=\"list\"\n      role=\"combobox\"\n      aria-expanded=\"true\"\n      type=\"text\"\n      [id]=\"root.inputId\"\n      [attr.aria-controls]=\"root.listId\"\n      [attr.aria-labelledby]=\"root.labelId\"\n      [attr.aria-activedescendant]=\"root.selectedId()\"\n      [attr.placeholder]=\"placeholder ?? null\"\n      [disabled]=\"disabled\"\n      [value]=\"root.search()\"\n      [class]=\"inputClass\"\n      (input)=\"onInput($event)\"\n    />\n  `,\n})\nexport class UiCommandInputComponent implements AfterViewInit {\n  readonly root = inject(CommandRoot)\n  @Input() placeholder?: string\n  @Input({ transform: booleanAttribute }) disabled = false\n  // Default false so inline Command embeds don't steal focus / scroll the page.\n  @Input({ transform: booleanAttribute }) autoFocus = false\n  /** Controlled search query (cmdk Input `value`). */\n  @Input()\n  set value(v: string | undefined) {\n    if (v !== undefined) this.root.setSearch(v)\n  }\n  get value(): string {\n    return this.root.search()\n  }\n  @Output() valueChange = new EventEmitter<string>()\n  @Input('class') className?: string\n  @ViewChild('input', { static: true }) inputRef!: ElementRef<HTMLInputElement>\n\n  get inputClass(): string {\n    return cn(\n      'placeholder:text-muted-foreground focus-visible:ring-ring/40 flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',\n      this.className,\n    )\n  }\n\n  ngAfterViewInit(): void {\n    if (this.autoFocus) this.inputRef.nativeElement.focus({ preventScroll: true })\n  }\n\n  onInput(event: Event): void {\n    const v = (event.target as HTMLInputElement).value\n    this.root.setSearch(v)\n    this.valueChange.emit(v)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-list, [ui-command-list]',\n  standalone: true,\n  host: {\n    'cmdk-list': '',\n    role: 'listbox',\n    tabindex: '-1',\n    '[attr.aria-label]': 'label',\n    '[id]': 'root.listId',\n    'data-uipkge': '',\n    'data-slot': 'command-list',\n    '[class]': 'hostClass',\n  },\n  template: `<div cmdk-list-sizer=\"\" #sizer><ng-content /></div>`,\n})\nexport class UiCommandListComponent implements AfterViewInit, OnDestroy {\n  readonly root = inject(CommandRoot)\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  @Input() label = 'Suggestions'\n  @Input('class') className?: string\n  @ViewChild('sizer', { static: true }) sizer!: ElementRef<HTMLElement>\n  private ro?: ResizeObserver\n\n  get hostClass(): string {\n    return cn('block', 'max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto', this.className)\n  }\n\n  /** cmdk exposes the content height as --cmdk-list-height (for height transitions). */\n  ngAfterViewInit(): void {\n    if (typeof ResizeObserver === 'undefined') return\n    this.ro = new ResizeObserver(() => {\n      this.el.style.setProperty('--cmdk-list-height', `${this.sizer.nativeElement.offsetHeight.toFixed(1)}px`)\n    })\n    this.ro.observe(this.sizer.nativeElement)\n  }\n\n  ngOnDestroy(): void {\n    this.ro?.disconnect()\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-empty, [ui-command-empty]',\n  standalone: true,\n  host: {\n    'cmdk-empty': '',\n    role: 'presentation',\n    'data-uipkge': '',\n    'data-slot': 'command-empty',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiCommandEmptyComponent {\n  private readonly root = inject(CommandRoot)\n  @Input('class') className?: string\n\n  /** cmdk renders Empty only while no item matches; hidden (not unmounted) here. */\n  get hostClass(): string {\n    return this.root.visibleCount() === 0 ? cn('block', 'py-6 text-center text-sm', this.className) : 'hidden'\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-group, [ui-command-group]',\n  standalone: true,\n  host: {\n    'cmdk-group': '',\n    role: 'presentation',\n    '[attr.data-value]': 'value ?? heading ?? null',\n    '[attr.hidden]': 'visible() ? null : \"\"',\n    'data-uipkge': '',\n    'data-slot': 'command-group',\n    '[class]': 'hostClass',\n  },\n  template: `\n    @if (heading) {\n      <div cmdk-group-heading=\"\" aria-hidden=\"true\" [id]=\"headingId\">{{ heading }}</div>\n    }\n    <div cmdk-group-items=\"\" role=\"group\" [attr.aria-labelledby]=\"heading ? headingId : null\"><ng-content /></div>\n  `,\n})\nexport class UiCommandGroupComponent {\n  private readonly root = inject(CommandRoot)\n  readonly element = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  readonly headingId = uniqueId('cmdk-group-heading')\n  readonly items = signal<UiCommandItemComponent[]>([])\n  private readonly force = signal(false)\n  @Input() heading?: string\n  @Input() value?: string\n  @Input({ transform: booleanAttribute })\n  set forceMount(v: boolean) {\n    this.force.set(v)\n  }\n  @Input('class') className?: string\n\n  readonly visible = computed(() => this.force() || this.items().some((i) => i.visible()))\n\n  constructor() {\n    this.root.registerGroup(this)\n    inject(DestroyRef).onDestroy(() => this.root.unregisterGroup(this))\n  }\n\n  get hostClass(): string {\n    return cn(\n      'block',\n      'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-separator, [ui-command-separator]',\n  standalone: true,\n  host: {\n    'cmdk-separator': '',\n    role: 'separator',\n    '[attr.hidden]': 'root.search() && !alwaysRender ? \"\" : null',\n    'data-uipkge': '',\n    'data-slot': 'command-separator',\n    '[class]': 'hostClass',\n  },\n  template: ``,\n})\nexport class UiCommandSeparatorComponent {\n  readonly root = inject(CommandRoot)\n  /** cmdk hides separators while a search is active unless alwaysRender. */\n  @Input({ transform: booleanAttribute }) alwaysRender = false\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('block', 'bg-border -mx-1 h-px', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-item, [ui-command-item]',\n  standalone: true,\n  host: {\n    'cmdk-item': '',\n    role: 'option',\n    '[id]': 'id',\n    '[attr.aria-disabled]': 'disabled',\n    '[attr.aria-selected]': 'isSelected()',\n    '[attr.data-disabled]': 'disabled',\n    '[attr.data-selected]': 'isSelected()',\n    '[attr.data-value]': 'valueSig()',\n    '[attr.hidden]': 'visible() ? null : \"\"',\n    'data-uipkge': '',\n    'data-slot': 'command-item',\n    '[class]': 'hostClass',\n    '(pointermove)': 'onPointerMove()',\n    '(click)': 'onSelect()',\n  },\n  template: `<ng-content />`,\n})\nexport class UiCommandItemComponent implements OnInit, OnChanges {\n  private readonly root = inject(CommandRoot)\n  private readonly group = inject(UiCommandGroupComponent, { optional: true })\n  readonly element = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  readonly id = uniqueId('cmdk-item')\n  readonly valueSig = signal('')\n  private readonly keywordsSig = signal<string[] | undefined>(undefined)\n  private readonly force = signal(false)\n\n  @Input() value?: string\n  @Input() keywords?: string[]\n  @Input({ transform: booleanAttribute }) disabled = false\n  @Input({ transform: booleanAttribute })\n  set forceMount(v: boolean) {\n    this.force.set(v)\n  }\n  @Input('class') className?: string\n  /** cmdk `onSelect`: click or Enter while highlighted. Emits the item value. */\n  @Output() select = new EventEmitter<string>()\n\n  readonly score = computed(() => this.root.scoreFor(this.valueSig(), this.keywordsSig()))\n  readonly visible = computed(() => this.force() || this.score() > 0)\n  readonly isSelected = computed(() => this.root.selected() === this.valueSig())\n\n  constructor() {\n    this.root.register(this)\n    this.group?.items.update((list) => [...list, this])\n    inject(DestroyRef).onDestroy(() => {\n      this.root.unregister(this)\n      this.group?.items.update((list) => list.filter((i) => i !== this))\n    })\n  }\n\n  ngOnInit(): void {\n    this.syncValue()\n  }\n\n  ngOnChanges(): void {\n    this.syncValue()\n    this.keywordsSig.set(this.keywords?.map((k) => k.trim()))\n  }\n\n  /** cmdk value: the `value` prop, else the item's text, trimmed. */\n  private syncValue(): void {\n    this.valueSig.set((this.value ?? this.element.textContent ?? '').trim())\n  }\n\n  get hostClass(): string {\n    return cn(\n      \"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n      this.className,\n    )\n  }\n\n  onPointerMove(): void {\n    if (this.disabled || this.root.disablePointerSelection) return\n    this.root.setSelected(this.valueSig())\n  }\n\n  onSelect(): void {\n    if (this.disabled) return\n    this.root.setSelected(this.valueSig())\n    this.select.emit(this.valueSig())\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-command-shortcut, [ui-command-shortcut]',\n  standalone: true,\n  host: {\n    'data-uipkge': '',\n    'data-slot': 'command-shortcut',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiCommandShortcutComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('text-muted-foreground ml-auto text-xs tracking-widest', this.className)\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/command/command.component.ts"
    },
    {
      "path": "packages/registry-angular/components/command/index.ts",
      "content": "export {\n  CommandRoot,\n  UiCommandComponent,\n  UiCommandDialogComponent,\n  UiCommandInputComponent,\n  UiCommandListComponent,\n  UiCommandEmptyComponent,\n  UiCommandGroupComponent,\n  UiCommandItemComponent,\n  UiCommandShortcutComponent,\n  UiCommandSeparatorComponent,\n  commandScore,\n  type CommandFilter,\n} from './command.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/command/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/popper.json"
  ],
  "description": "Searchable command palette à la Cmd-K — keyboard-driven menu with grouped items, icons, shortcuts, and cmdk-style fuzzy filtering + ranking. Use as a global launcher (CommandDialog: portalled modal with focus trap) or inline as a typeahead select.",
  "categories": [
    "overlay"
  ]
}