{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "entity-picker",
  "title": "Entity Picker",
  "author": "ilinxa",
  "description": "Searchable picker for typed entities — single or multi select, kind badges, chip cluster with removal, and custom render slots.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "command",
    "popover"
  ],
  "files": [
    {
      "path": "src/registry/components/forms/entity-picker/entity-picker.tsx",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useId,\n  useImperativeHandle,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type KeyboardEvent,\n  type Ref,\n} from \"react\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandInput,\n  CommandList,\n} from \"@/components/ui/command\";\nimport type {\n  EntityLike,\n  EntityPickerHandle,\n  EntityPickerProps,\n  KindMeta,\n  MultiPickerProps,\n  PickerMode,\n  SinglePickerProps,\n} from \"./types\";\nimport { defaultMatch } from \"./lib/default-match\";\nimport {\n  multiSelectionEqual,\n  singleSelectionEqual,\n} from \"./lib/selection-equality\";\nimport { useItemsById } from \"./hooks/use-items-by-id\";\nimport { useOpenState } from \"./hooks/use-open-state\";\nimport { TriggerButton } from \"./parts/trigger-button\";\nimport { ResultRow } from \"./parts/result-row\";\nimport { DefaultEmptyState } from \"./parts/default-empty-state\";\n\nfunction EntityPickerImpl<T extends EntityLike>(props: EntityPickerProps<T>) {\n  const {\n    items,\n    match,\n    placeholder = \"Search…\",\n    open: controlledOpen,\n    onOpenChange,\n    kinds,\n    showKindBadges: showKindBadgesProp,\n    renderItem,\n    renderTrigger,\n    renderEmpty,\n    disabled = false,\n    triggerLabel,\n    id,\n    className,\n    ref,\n  } = props;\n\n  const mode: PickerMode = props.mode === \"multi\" ? \"multi\" : \"single\";\n  const value = props.value as T | T[] | null;\n\n  const reactId = useId();\n  const listboxId = `${reactId}-listbox`;\n\n  const { open, setOpen: rawSetOpen } = useOpenState({\n    controlled: controlledOpen,\n    onOpenChange,\n  });\n\n  const [query, setQuery] = useState(\"\");\n\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (!next) setQuery(\"\");\n      rawSetOpen(next);\n    },\n    [rawSetOpen],\n  );\n\n  const itemsById = useItemsById(items);\n\n  const showKindBadges =\n    showKindBadgesProp !== undefined\n      ? showKindBadgesProp\n      : items.some((i) => !!i.kind);\n\n  // Track the trigger element via state (not a ref) so the setter is React's\n  // own setState — passes the React Compiler-aware lint rule that flags\n  // ref-accessor functions passed to user-rendered functions during render.\n  // The ref-fn is stable across renders (React guarantees setState identity).\n  const [triggerNode, setTriggerNode] = useState<HTMLElement | null>(null);\n\n  // F-cross-13 consumer-trigger path: when a press lands while the popover is\n  // open, Radix/Base UI's outside-press dismiss closes it BEFORE the click\n  // event — the wrapper's click handler must not immediately re-open. The\n  // capture-phase pointerdown snapshots the pre-dismiss open state.\n  const openRef = useRef(open);\n  // Layout effect: the capture-phase pointerdown guard reads this ref, and a\n  // press can land between commit and a passive effect's flush.\n  useLayoutEffect(() => {\n    openRef.current = open;\n  });\n  const pointerDownWhileOpenRef = useRef(false);\n\n  const handleSelect = useCallback(\n    (item: T) => {\n      if (mode === \"multi\") {\n        const multiProps = props as MultiPickerProps<T>;\n        const current = multiProps.value;\n        const isSelected = current.some((v) => v.id === item.id);\n        const next = isSelected\n          ? current.filter((v) => v.id !== item.id)\n          : [...current, item];\n        if (!multiSelectionEqual(current, next)) {\n          multiProps.onChange(next);\n        }\n      } else {\n        const singleProps = props as SinglePickerProps<T>;\n        const current = singleProps.value;\n        const next = item;\n        if (!singleSelectionEqual(current, next)) {\n          singleProps.onChange(next);\n        }\n        setOpen(false);\n      }\n    },\n    [mode, props, setOpen],\n  );\n\n  const handleRemoveChip = useCallback(\n    (idToRemove: string) => {\n      if (mode !== \"multi\") return;\n      const multiProps = props as MultiPickerProps<T>;\n      const next = multiProps.value.filter((v) => v.id !== idToRemove);\n      if (!multiSelectionEqual(multiProps.value, next)) {\n        multiProps.onChange(next);\n      }\n    },\n    [mode, props],\n  );\n\n  const handleClear = useCallback(() => {\n    if (mode === \"multi\") {\n      const multiProps = props as MultiPickerProps<T>;\n      if (multiProps.value.length > 0) multiProps.onChange([]);\n    } else {\n      const singleProps = props as SinglePickerProps<T>;\n      if (singleProps.value !== null) singleProps.onChange(null);\n    }\n  }, [mode, props]);\n\n  const focusTrigger = useCallback(() => {\n    if (!triggerNode) return;\n    triggerNode.focus();\n  }, [triggerNode]);\n\n  useImperativeHandle(\n    ref as Ref<EntityPickerHandle>,\n    (): EntityPickerHandle => ({\n      focus: focusTrigger,\n      open: () => setOpen(true),\n      close: () => setOpen(false),\n      clear: handleClear,\n    }),\n    [focusTrigger, setOpen, handleClear],\n  );\n\n  const handleTriggerKeyDown = useCallback(\n    (e: KeyboardEvent<HTMLButtonElement>) => {\n      if (disabled) return;\n      if (e.key === \"Enter\" || e.key === \" \") {\n        e.preventDefault();\n        setOpen(true);\n        return;\n      }\n      if (e.key === \"ArrowDown\") {\n        e.preventDefault();\n        setOpen(true);\n      }\n    },\n    [disabled, setOpen],\n  );\n\n  const handleSearchKeyDown = useCallback(\n    (e: KeyboardEvent<HTMLInputElement>) => {\n      if (\n        e.key === \"Backspace\" &&\n        query.length === 0 &&\n        mode === \"multi\"\n      ) {\n        const multiProps = props as MultiPickerProps<T>;\n        if (multiProps.value.length > 0) {\n          e.preventDefault();\n          const last = multiProps.value[multiProps.value.length - 1];\n          handleRemoveChip(last.id);\n        }\n      }\n    },\n    [query, mode, props, handleRemoveChip],\n  );\n\n  const filterFn = useCallback(\n    (entityId: string, search: string): number => {\n      const item = itemsById.get(entityId);\n      if (!item) return 0;\n      try {\n        const result = match\n          ? match(item, search)\n          : defaultMatch(item, search);\n        return result ? 1 : 0;\n      } catch (err) {\n        if (process.env.NODE_ENV !== \"production\") {\n          console.error(\n            `[entity-picker] match threw for item \"${item.id}\":`,\n            err,\n          );\n        }\n        return 0;\n      }\n    },\n    [itemsById, match],\n  );\n\n  // F-cross-13: no `asChild` on either path. Default path — TriggerButton\n  // renders the PopoverTrigger <button> internally. Consumer path — the node\n  // can no longer BE the trigger (Slot semantics need asChild), so a hidden\n  // full-size PopoverTrigger anchors the popover to the node's box while the\n  // wrapper span owns open-on-click; the consumer node keeps its own\n  // interactivity, hover styles, and the documented `triggerRef` contract.\n  const renderTriggerNode = renderTrigger ? (\n    <span\n      className=\"relative inline-flex max-w-full\"\n      onPointerDownCapture={() => {\n        pointerDownWhileOpenRef.current = openRef.current;\n      }}\n      onClick={(e) => {\n        const wasOpen = pointerDownWhileOpenRef.current;\n        pointerDownWhileOpenRef.current = false;\n        if (disabled) return;\n        // Keyboard \"clicks\" (detail 0) have no preceding pointerdown, so no\n        // outside-press dismiss ran — toggle both ways here (Enter closes).\n        if (e.detail === 0) {\n          setOpen(!openRef.current);\n          return;\n        }\n        if (wasOpen) return;\n        setOpen(true);\n      }}\n    >\n      <PopoverTrigger\n        aria-hidden=\"true\"\n        tabIndex={-1}\n        // Close-autofocus lands here (it's the backend's registered trigger);\n        // bounce it to the consumer's real trigger element.\n        onFocus={() => triggerNode?.focus()}\n        className=\"pointer-events-none absolute inset-0\"\n      />\n      {renderTrigger({\n        value,\n        open,\n        triggerRef: setTriggerNode,\n      })}\n    </span>\n  ) : (\n    <TriggerButton<T>\n      ref={setTriggerNode}\n      id={id}\n      mode={mode}\n      value={value}\n      open={open}\n      disabled={disabled}\n      triggerLabel={triggerLabel}\n      kinds={kinds}\n      showKindBadges={showKindBadges}\n      listboxId={listboxId}\n      className={className}\n      onKeyDown={handleTriggerKeyDown}\n      onRemoveChip={handleRemoveChip}\n    />\n  );\n\n  const valueMap = (() => {\n    const out = new Set<string>();\n    if (mode === \"multi\") {\n      const m = props as MultiPickerProps<T>;\n      for (const v of m.value) out.add(v.id);\n    } else {\n      const s = props as SinglePickerProps<T>;\n      if (s.value) out.add(s.value.id);\n    }\n    return out;\n  })();\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      {renderTriggerNode}\n      <PopoverContent\n        align=\"start\"\n        sideOffset={4}\n        // Radix exposes --radix-popover-trigger-width, Base UI --anchor-width\n        // (F-cross-13) — cover both; min-w-60 is the last-resort floor.\n        className=\"w-(--radix-popover-trigger-width,var(--anchor-width)) min-w-60 p-0\"\n      >\n        <Command filter={filterFn} loop>\n          <CommandInput\n            placeholder={placeholder}\n            value={query}\n            onValueChange={setQuery}\n            onKeyDown={handleSearchKeyDown}\n            aria-label={triggerLabel ?? \"Search entities\"}\n          />\n          <CommandList id={listboxId} aria-multiselectable={mode === \"multi\"}>\n            <CommandEmpty>\n              {renderEmpty ? (\n                renderEmpty({ query, itemCount: items.length })\n              ) : (\n                <DefaultEmptyState query={query} itemCount={items.length} />\n              )}\n            </CommandEmpty>\n            {items.map((item) => {\n              const selected = valueMap.has(item.id);\n              const kindMeta: KindMeta | undefined =\n                item.kind && kinds ? kinds[item.kind] : undefined;\n              const customNode = renderItem\n                ? renderItem(item, { selected, query })\n                : undefined;\n              return (\n                <ResultRow<T>\n                  key={item.id}\n                  item={item}\n                  selected={selected}\n                  kindMeta={kindMeta}\n                  showKindBadges={showKindBadges}\n                  onSelect={() => handleSelect(item)}\n                  customRender={customNode}\n                />\n              );\n            })}\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n}\n\nexport function EntityPicker<T extends EntityLike>(\n  props: SinglePickerProps<T>,\n): React.JSX.Element;\nexport function EntityPicker<T extends EntityLike>(\n  props: MultiPickerProps<T>,\n): React.JSX.Element;\nexport function EntityPicker<T extends EntityLike>(\n  props: EntityPickerProps<T>,\n): React.JSX.Element {\n  return EntityPickerImpl(props);\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/entity-picker.tsx"
    },
    {
      "path": "src/registry/components/forms/entity-picker/index.ts",
      "content": "export { EntityPicker } from \"./entity-picker\";\nexport type {\n  CommonPickerProps,\n  EntityLike,\n  EntityPickerHandle,\n  EntityPickerProps,\n  KindMeta,\n  MultiPickerProps,\n  PickerMode,\n  RenderEmptyContext,\n  RenderItemContext,\n  RenderTriggerContext,\n  SinglePickerProps,\n} from \"./types\";\n",
      "type": "registry:component",
      "target": "components/entity-picker/index.ts"
    },
    {
      "path": "src/registry/components/forms/entity-picker/types.ts",
      "content": "import type { ReactNode, Ref } from \"react\";\n\nexport interface EntityLike {\n  id: string;\n  label: string;\n  kind?: string;\n}\n\nexport interface KindMeta {\n  label: string;\n  color?: string;\n}\n\nexport type PickerMode = \"single\" | \"multi\";\n\nexport interface RenderItemContext {\n  selected: boolean;\n  query: string;\n}\n\nexport interface RenderTriggerContext<T extends EntityLike> {\n  value: T | T[] | null;\n  open: boolean;\n  triggerRef: (node: HTMLElement | null) => void;\n}\n\nexport interface RenderEmptyContext {\n  query: string;\n  itemCount: number;\n}\n\nexport interface CommonPickerProps<T extends EntityLike> {\n  items: ReadonlyArray<T>;\n\n  match?: (item: T, query: string) => boolean;\n  placeholder?: string;\n\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n\n  kinds?: Record<string, KindMeta>;\n  showKindBadges?: boolean;\n\n  renderItem?: (item: T, ctx: RenderItemContext) => ReactNode;\n  renderTrigger?: (ctx: RenderTriggerContext<T>) => ReactNode;\n  renderEmpty?: (ctx: RenderEmptyContext) => ReactNode;\n\n  disabled?: boolean;\n  triggerLabel?: string;\n  id?: string;\n  className?: string;\n\n  ref?: Ref<EntityPickerHandle>;\n}\n\nexport interface SinglePickerProps<T extends EntityLike>\n  extends CommonPickerProps<T> {\n  mode?: \"single\";\n  value: T | null;\n  onChange: (value: T | null) => void;\n}\n\nexport interface MultiPickerProps<T extends EntityLike>\n  extends CommonPickerProps<T> {\n  mode: \"multi\";\n  value: T[];\n  onChange: (value: T[]) => void;\n}\n\nexport type EntityPickerProps<T extends EntityLike> =\n  | SinglePickerProps<T>\n  | MultiPickerProps<T>;\n\nexport interface EntityPickerHandle {\n  focus(): void;\n  open(): void;\n  close(): void;\n  clear(): void;\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/types.ts"
    },
    {
      "path": "src/registry/components/forms/entity-picker/hooks/use-items-by-id.ts",
      "content": "\"use client\";\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport type { EntityLike } from \"../types\";\n\nexport function useItemsById<T extends EntityLike>(\n  items: ReadonlyArray<T>,\n): Map<string, T> {\n  const map = useMemo(() => {\n    const out = new Map<string, T>();\n    for (const item of items) {\n      if (out.has(item.id)) {\n        if (process.env.NODE_ENV !== \"production\") {\n          console.error(\n            `[entity-picker] duplicate id \"${item.id}\" in items; later occurrences are ignored.`,\n          );\n        }\n        continue;\n      }\n      out.set(item.id, item);\n    }\n    return out;\n  }, [items]);\n\n  const stabilityRef = useRef({ last: items, count: 0, warned: false });\n  useEffect(() => {\n    const tracker = stabilityRef.current;\n    if (tracker.last === items) {\n      tracker.count = 0;\n      return;\n    }\n    tracker.last = items;\n    tracker.count++;\n    if (\n      tracker.count > 5 &&\n      !tracker.warned &&\n      process.env.NODE_ENV !== \"production\"\n    ) {\n      tracker.warned = true;\n      console.warn(\n        \"[entity-picker] `items` prop is changing every render. \" +\n          \"Wrap with `useMemo` or hoist to module scope to avoid re-derivation.\",\n      );\n    }\n  }, [items]);\n\n  return map;\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/hooks/use-items-by-id.ts"
    },
    {
      "path": "src/registry/components/forms/entity-picker/hooks/use-open-state.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\ninterface UseOpenStateArgs {\n  controlled: boolean | undefined;\n  onOpenChange: ((open: boolean) => void) | undefined;\n}\n\ninterface UseOpenStateResult {\n  open: boolean;\n  setOpen: (next: boolean) => void;\n}\n\nexport function useOpenState({\n  controlled,\n  onOpenChange,\n}: UseOpenStateArgs): UseOpenStateResult {\n  const isControlled = controlled !== undefined;\n  const [internalOpen, setInternalOpen] = useState(false);\n\n  const onOpenChangeRef = useRef(onOpenChange);\n  useEffect(() => {\n    onOpenChangeRef.current = onOpenChange;\n  });\n\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (!isControlled) setInternalOpen(next);\n      onOpenChangeRef.current?.(next);\n    },\n    [isControlled],\n  );\n\n  const open = isControlled ? (controlled as boolean) : internalOpen;\n  return { open, setOpen };\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/hooks/use-open-state.ts"
    },
    {
      "path": "src/registry/components/forms/entity-picker/lib/default-match.ts",
      "content": "import type { EntityLike } from \"../types\";\n\nexport function defaultMatch<T extends EntityLike>(\n  item: T,\n  query: string,\n): boolean {\n  if (query.length === 0) return true;\n  return item.label.toLowerCase().includes(query.toLowerCase());\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/lib/default-match.ts"
    },
    {
      "path": "src/registry/components/forms/entity-picker/lib/normalize-value.ts",
      "content": "import type { EntityLike } from \"../types\";\n\nexport function valueToArray<T extends EntityLike>(\n  value: T | T[] | null,\n): T[] {\n  if (value === null || value === undefined) return [];\n  if (Array.isArray(value)) return value;\n  return [value];\n}\n\nexport function valueIds<T extends EntityLike>(\n  value: T | T[] | null,\n): Set<string> {\n  const out = new Set<string>();\n  for (const item of valueToArray(value)) out.add(item.id);\n  return out;\n}\n\nexport function hasId<T extends EntityLike>(\n  value: T | T[] | null,\n  id: string,\n): boolean {\n  if (value === null || value === undefined) return false;\n  if (Array.isArray(value)) return value.some((v) => v.id === id);\n  return value.id === id;\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/lib/normalize-value.ts"
    },
    {
      "path": "src/registry/components/forms/entity-picker/lib/selection-equality.ts",
      "content": "import type { EntityLike } from \"../types\";\n\nexport function singleSelectionEqual<T extends EntityLike>(\n  a: T | null,\n  b: T | null,\n): boolean {\n  if (a === null && b === null) return true;\n  if (a === null || b === null) return false;\n  return a.id === b.id;\n}\n\nexport function multiSelectionEqual<T extends EntityLike>(\n  a: ReadonlyArray<T>,\n  b: ReadonlyArray<T>,\n): boolean {\n  if (a.length !== b.length) return false;\n  if (a.length === 0) return true;\n  const seen = new Set<string>();\n  for (const item of a) seen.add(item.id);\n  for (const item of b) {\n    if (!seen.has(item.id)) return false;\n  }\n  return true;\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/lib/selection-equality.ts"
    },
    {
      "path": "src/registry/components/forms/entity-picker/parts/chip-cluster.tsx",
      "content": "\"use client\";\n\nimport type { EntityLike, KindMeta } from \"../types\";\nimport { Chip } from \"./chip\";\n\ninterface ChipClusterProps<T extends EntityLike> {\n  value: T[];\n  kinds: Record<string, KindMeta> | undefined;\n  showKindBadges: boolean;\n  onRemove: (id: string) => void;\n  disabled?: boolean;\n}\n\nexport function ChipCluster<T extends EntityLike>({\n  value,\n  kinds,\n  showKindBadges,\n  onRemove,\n  disabled,\n}: ChipClusterProps<T>) {\n  if (value.length === 0) return null;\n  // F-cross-13 shape: paints above the field's trigger-button overlay\n  // (`relative`) but stays click-transparent (`pointer-events-none`) so field\n  // clicks fall through; only each chip's remove <button> re-enables pointers.\n  return (\n    <div className=\"pointer-events-none relative flex flex-1 flex-wrap items-center gap-1\">\n      {value.map((entity) => (\n        <Chip\n          key={entity.id}\n          entity={entity}\n          kindMeta={entity.kind ? kinds?.[entity.kind] : undefined}\n          showKind={showKindBadges}\n          onRemove={() => onRemove(entity.id)}\n          disabled={disabled}\n        />\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/parts/chip-cluster.tsx"
    },
    {
      "path": "src/registry/components/forms/entity-picker/parts/chip.tsx",
      "content": "\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn } from \"@/lib/utils\";\nimport type { EntityLike, KindMeta } from \"../types\";\n\ninterface ChipProps<T extends EntityLike> {\n  entity: T;\n  kindMeta: KindMeta | undefined;\n  showKind: boolean;\n  onRemove: () => void;\n  disabled?: boolean;\n}\n\nexport function Chip<T extends EntityLike>({\n  entity,\n  kindMeta,\n  showKind,\n  onRemove,\n  disabled,\n}: ChipProps<T>) {\n  return (\n    <Badge\n      variant=\"secondary\"\n      className=\"gap-1 pr-0.5 text-xs\"\n    >\n      {showKind && entity.kind ? (\n        <span\n          className={cn(\n            \"font-mono text-[10px] uppercase opacity-70\",\n            kindMeta?.color ? \"text-[var(--kind-color)]\" : undefined,\n          )}\n          style={\n            kindMeta?.color\n              ? ({ \"--kind-color\": kindMeta.color } as React.CSSProperties)\n              : undefined\n          }\n        >\n          {kindMeta?.label ?? entity.kind}\n        </span>\n      ) : null}\n      <span className=\"truncate\">{entity.label}</span>\n      <button\n        type=\"button\"\n        aria-label={`Remove ${entity.label}`}\n        disabled={disabled}\n        onClick={(e) => {\n          e.stopPropagation();\n          onRemove();\n        }}\n        onKeyDown={(e) => {\n          // Stop Enter/Space from bubbling to the parent trigger which would toggle the popover.\n          if (e.key === \"Enter\" || e.key === \" \") e.stopPropagation();\n        }}\n        className=\"pointer-events-auto ml-0.5 rounded-sm p-0.5 hover:bg-foreground/10 disabled:opacity-50\"\n      >\n        <X aria-hidden=\"true\" className=\"size-3\" />\n      </button>\n    </Badge>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/parts/chip.tsx"
    },
    {
      "path": "src/registry/components/forms/entity-picker/parts/default-empty-state.tsx",
      "content": "interface DefaultEmptyStateProps {\n  query: string;\n  itemCount: number;\n}\n\nexport function DefaultEmptyState({\n  itemCount,\n}: DefaultEmptyStateProps) {\n  if (itemCount === 0) {\n    return (\n      <p role=\"status\" className=\"px-2 text-center text-xs text-muted-foreground\">\n        Nothing to pick from\n      </p>\n    );\n  }\n  return (\n    <p role=\"status\" className=\"px-2 text-center text-xs text-muted-foreground\">\n      No results\n    </p>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/parts/default-empty-state.tsx"
    },
    {
      "path": "src/registry/components/forms/entity-picker/parts/kind-badge.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn } from \"@/lib/utils\";\nimport type { KindMeta } from \"../types\";\n\ninterface KindBadgeProps {\n  kindKey: string;\n  meta: KindMeta | undefined;\n  className?: string;\n}\n\nexport function KindBadge({ kindKey, meta, className }: KindBadgeProps) {\n  const display = meta?.label ?? kindKey;\n  const style = meta?.color\n    ? ({ \"--kind-color\": meta.color } as React.CSSProperties)\n    : undefined;\n  return (\n    <Badge\n      variant=\"secondary\"\n      className={cn(\n        \"font-mono text-[10px] uppercase tracking-[0.12em]\",\n        meta?.color\n          ? \"bg-[var(--kind-color)]/15 text-[var(--kind-color)]\"\n          : \"text-muted-foreground\",\n        className,\n      )}\n      style={style}\n    >\n      {display}\n    </Badge>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/parts/kind-badge.tsx"
    },
    {
      "path": "src/registry/components/forms/entity-picker/parts/result-row.tsx",
      "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { CommandItem } from \"@/components/ui/command\";\nimport type { EntityLike, KindMeta } from \"../types\";\nimport { KindBadge } from \"./kind-badge\";\n\ninterface ResultRowProps<T extends EntityLike> {\n  item: T;\n  selected: boolean;\n  kindMeta: KindMeta | undefined;\n  showKindBadges: boolean;\n  onSelect: () => void;\n  customRender?: ReactNode;\n}\n\nexport function ResultRow<T extends EntityLike>({\n  item,\n  selected,\n  kindMeta,\n  showKindBadges,\n  onSelect,\n  customRender,\n}: ResultRowProps<T>) {\n  return (\n    <CommandItem\n      value={item.id}\n      onSelect={onSelect}\n      data-checked={selected ? \"true\" : undefined}\n      aria-selected={selected || undefined}\n      className=\"flex items-center gap-2\"\n    >\n      {customRender ?? (\n        <>\n          {showKindBadges && item.kind ? (\n            <KindBadge kindKey={item.kind} meta={kindMeta} />\n          ) : null}\n          <span className=\"truncate\">{item.label}</span>\n        </>\n      )}\n    </CommandItem>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/entity-picker/parts/result-row.tsx"
    },
    {
      "path": "src/registry/components/forms/entity-picker/parts/trigger-button.tsx",
      "content": "\"use client\";\n\nimport { forwardRef, useId, type KeyboardEventHandler } from \"react\";\nimport { ChevronDown } from \"lucide-react\";\nimport { PopoverTrigger } from \"@/components/ui/popover\";\nimport type { EntityLike, KindMeta, PickerMode } from \"../types\";\nimport { ChipCluster } from \"./chip-cluster\";\nimport { KindBadge } from \"./kind-badge\";\nimport { cn } from \"@/lib/utils\";\n\ninterface TriggerButtonProps<T extends EntityLike> {\n  id?: string;\n  mode: PickerMode;\n  value: T | T[] | null;\n  open: boolean;\n  disabled?: boolean;\n  triggerLabel?: string;\n  kinds?: Record<string, KindMeta>;\n  showKindBadges: boolean;\n  listboxId: string;\n  ariaLabelledBy?: string;\n  className?: string;\n  onKeyDown: KeyboardEventHandler<HTMLButtonElement>;\n  onRemoveChip: (id: string) => void;\n}\n\nfunction TriggerButtonInner<T extends EntityLike>(\n  props: TriggerButtonProps<T>,\n  ref: React.Ref<HTMLButtonElement>,\n) {\n  const {\n    id,\n    mode,\n    value,\n    open,\n    disabled,\n    triggerLabel,\n    kinds,\n    showKindBadges,\n    listboxId,\n    ariaLabelledBy,\n    className,\n    onKeyDown,\n    onRemoveChip,\n  } = props;\n\n  const contentId = useId();\n  const placeholder = triggerLabel ?? \"Select…\";\n  const isMulti = mode === \"multi\";\n  const multiValue = Array.isArray(value) ? value : [];\n  const singleValue = !Array.isArray(value) ? value : null;\n  const empty = isMulti ? multiValue.length === 0 : singleValue === null;\n  const singleKindMeta =\n    singleValue?.kind && kinds ? kinds[singleValue.kind] : undefined;\n\n  return (\n    <div\n      id={id}\n      className={cn(\n        \"group relative flex min-h-9 w-full cursor-default items-center gap-1.5 rounded-lg border border-input bg-transparent px-2 py-1 text-sm transition-colors\",\n        // parity with the old focusable field div: border shifts with the ring\n        \"has-focus-visible:border-ring\",\n        \"data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50\",\n        \"dark:bg-input/30\",\n        className,\n      )}\n      data-disabled={disabled || undefined}\n      data-state={open ? \"open\" : \"closed\"}\n    >\n      {/* F-cross-13: no `asChild` — the Popover trigger is a real <button>\n          (both backends) rendered as a full-field overlay. It can't WRAP the\n          chips (button-in-button is illegal), so the chip layer paints above\n          it (see ChipCluster) and every other click falls through to here. */}\n      <PopoverTrigger\n        ref={ref}\n        type=\"button\"\n        disabled={disabled}\n        aria-haspopup=\"listbox\"\n        aria-expanded={open}\n        aria-controls={listboxId}\n        aria-label={!ariaLabelledBy && triggerLabel ? triggerLabel : undefined}\n        aria-labelledby={ariaLabelledBy ?? (triggerLabel ? undefined : contentId)}\n        onKeyDown={onKeyDown}\n        className=\"absolute inset-0 rounded-lg outline-none focus-visible:ring-3 focus-visible:ring-ring/50\"\n      />\n      <span id={contentId} className=\"flex min-w-0 flex-1 items-center gap-1.5\">\n        {isMulti ? (\n          multiValue.length === 0 ? (\n            <span className=\"flex-1 truncate text-muted-foreground\">\n              {placeholder}\n            </span>\n          ) : (\n            <ChipCluster\n              value={multiValue}\n              kinds={kinds}\n              showKindBadges={showKindBadges}\n              onRemove={onRemoveChip}\n              disabled={disabled}\n            />\n          )\n        ) : empty ? (\n          <span className=\"flex-1 truncate text-muted-foreground\">\n            {placeholder}\n          </span>\n        ) : (\n          <span className=\"flex flex-1 items-center gap-2 truncate\">\n            {showKindBadges && singleValue?.kind ? (\n              <KindBadge\n                kindKey={singleValue.kind}\n                meta={singleKindMeta}\n              />\n            ) : null}\n            <span className=\"truncate text-foreground\">\n              {singleValue?.label}\n            </span>\n          </span>\n        )}\n      </span>\n      <ChevronDown\n        aria-hidden=\"true\"\n        className=\"ml-auto size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180\"\n      />\n    </div>\n  );\n}\n\n// forwardRef over a generic component — the cast preserves the generic call\n// signature. The ref lands on the internal PopoverTrigger <button> (the field's\n// single focusable element); the F-cross-13 sweep removed the asChild-Slot\n// composition this file previously relied on.\nexport const TriggerButton = forwardRef(TriggerButtonInner) as <\n  T extends EntityLike,\n>(\n  props: TriggerButtonProps<T> & { ref?: React.Ref<HTMLButtonElement> },\n) => React.ReactElement;\n",
      "type": "registry:component",
      "target": "components/entity-picker/parts/trigger-button.tsx"
    }
  ],
  "categories": [
    "forms",
    "graph-system"
  ],
  "type": "registry:block"
}