Architecture

Directory structure, component tree, and data flow.

Directory Structure

src/lib/
├── models/          command.model.ts           — Command, SearchResult, CommandGroup, Config
├── tokens/          command-palette.tokens.ts  — InjectionToken + defaults
├── search/          fuzzy-search.engine.ts     — Ranked fuzzy search engine
├── keyboard/        shortcut.parser.ts         — parseShortcut / matchesAnyShortcut
├── services/        command-palette.service.ts — Signals-only state hub
├── providers/       command-palette.provider.ts — provideCommandPalette()
└── components/
    ├── palette-item/     — Single command row (role="option")
    ├── palette-group/    — Category section (role="group")
    ├── palette-results/  — Scrollable listbox (role="listbox")
    ├── palette-input/    — Combobox search input
    └── command-palette/  — Host component (af-command-palette)

Component Tree

CommandPaletteComponent  (af-command-palette)
  — Listens: (document:keydown) for shortcuts + Escape
  — CDK BlockScrollStrategy (scroll locked while open)
  — Owns: activeIndex signal, scrolling
  @if (service.isOpen())
  └── div.af-cp-root
      ├── div.af-cp-backdrop  (click → close)
      └── div.af-cp-dialog  (role="dialog", cdkTrapFocus)
          ├── PaletteInputComponent    (role="combobox")
          ├── PaletteResultsComponent  (role="listbox")
          │   └── PaletteGroupComponent × N  (role="group")
          │       └── PaletteItemComponent × N  (role="option")
          └── div.af-cp-footer  (keyboard hints)

Data Flow

register(commands)
    ↓
CommandPaletteService._commands (signal Map)
    ↓
results = computed(fuzzySearch(_commands, query))
    ↓
groups = computed(groupBy(results, 'category'))
    ↓
Template renders groups → items