{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "product-comparison-drawer",
  "title": "Product Comparison Drawer",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/product-comparison-drawer/product-comparison-drawer.component.ts",
      "content": "import {\n  Component,\n  EventEmitter,\n  Input,\n  Output,\n  booleanAttribute,\n  computed,\n  signal,\n  type OnInit,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport {\n  Check,\n  ChevronDown,\n  ChevronUp,\n  GitCompare,\n  Layers,\n  LucideAngularModule,\n  RotateCcw,\n  ShoppingCart,\n  SlidersHorizontal,\n  Star,\n  Trash2,\n  X,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiSwitchComponent } from '@/ui/switch/switch.component'\n\nexport interface ComparisonSpec {\n  price: string\n  driverSize: string\n  batteryLife: string\n  ancType: string\n  waterResistance: string\n  weight: string\n  accessories: string\n  connectivity?: string\n  fastCharging?: string\n  microphone?: string\n  warranty?: string\n}\n\nexport interface ComparisonProduct {\n  id: string\n  name: string\n  tagline: string\n  badge: string\n  badgeVariant?: 'default' | 'secondary' | 'outline'\n  price: string\n  numericPrice: number\n  rating: number\n  reviewsCount: number\n  image: string\n  inStock: boolean\n  shipping: string\n  specs: ComparisonSpec\n}\n\nexport interface SpecField {\n  key: keyof ComparisonSpec\n  label: string\n  category: 'Key Specifications' | 'Audio & Connectivity' | 'Package & Warranty'\n  description?: string\n}\n\nexport type ProductComparisonDrawerMode = 'dock' | 'expanded'\n\ninterface SpecCategory {\n  name: string\n  fields: SpecField[]\n}\n\nexport const DEFAULT_COMPARISON_PRODUCTS: ComparisonProduct[] = [\n  {\n    id: 'prod-headphones',\n    name: 'Pro Wireless Headphones',\n    tagline: 'Flagship Over-Ear Studio Sound',\n    badge: 'Best Overall',\n    badgeVariant: 'default',\n    price: '$299.00',\n    numericPrice: 299,\n    rating: 4.9,\n    reviewsCount: 1240,\n    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400&auto=format&fit=crop&q=80',\n    inStock: true,\n    shipping: 'Free Next-Day Delivery',\n    specs: {\n      price: '$299.00',\n      driverSize: '40mm Graphene',\n      batteryLife: '40 Hours',\n      ancType: 'Adaptive ANC',\n      waterResistance: 'IPX4',\n      weight: '245g',\n      accessories: 'Hard Travel Case, 3.5mm Cable, USB-C Cable, Airplane Adapter',\n      connectivity: 'Bluetooth 5.3 + Multipoint',\n      fastCharging: '10 min charge = 3 hrs playback',\n      microphone: '6 Mics with Beamforming AI',\n      warranty: '2-Year Manufacturer Warranty',\n    },\n  },\n  {\n    id: 'prod-earbuds',\n    name: 'Aero Studio Earbuds',\n    tagline: 'Compact True Wireless In-Ear',\n    badge: 'Editor Choice',\n    badgeVariant: 'secondary',\n    price: '$199.00',\n    numericPrice: 199,\n    rating: 4.7,\n    reviewsCount: 890,\n    image: 'https://images.unsplash.com/photo-1590658268037-6bf12165a8df?w=400&auto=format&fit=crop&q=80',\n    inStock: true,\n    shipping: 'Free 2-Day Delivery',\n    specs: {\n      price: '$199.00',\n      driverSize: '11mm Dynamic',\n      batteryLife: '28 Hours',\n      ancType: 'Standard ANC',\n      waterResistance: 'IPX5',\n      weight: '54g',\n      accessories: 'Wireless Charging Case, 4 Pairs Silicone Tips (XS/S/M/L), USB-C Cable',\n      connectivity: 'Bluetooth 5.3 + Multipoint',\n      fastCharging: '10 min charge = 3 hrs playback',\n      microphone: '4 Mics with Wind Noise Guard',\n      warranty: '2-Year Manufacturer Warranty',\n    },\n  },\n  {\n    id: 'prod-sport',\n    name: 'Active ANC Sport',\n    tagline: 'Rugged Ergonomic Workout Earphones',\n    badge: 'Best Value',\n    badgeVariant: 'secondary',\n    price: '$149.00',\n    numericPrice: 149,\n    rating: 4.6,\n    reviewsCount: 620,\n    image: 'https://images.unsplash.com/photo-1572536147248-ac59a8abfa4b?w=400&auto=format&fit=crop&q=80',\n    inStock: true,\n    shipping: 'Free Standard Delivery',\n    specs: {\n      price: '$149.00',\n      driverSize: '10mm Hybrid',\n      batteryLife: '24 Hours',\n      ancType: 'Passive Noise Isolation',\n      waterResistance: 'IPX7 Waterproof',\n      weight: '62g',\n      accessories: 'Compact Case, Secure Wingtips, 3 Pairs Memory Foam Tips, USB-C Cable',\n      connectivity: 'Bluetooth 5.3',\n      fastCharging: '10 min charge = 3 hrs playback',\n      microphone: '2 Mics with Environmental Noise Cancellation',\n      warranty: '2-Year Manufacturer Warranty',\n    },\n  },\n]\n\nexport const SPEC_FIELDS: SpecField[] = [\n  {\n    key: 'price',\n    label: 'Price',\n    category: 'Key Specifications',\n    description: 'Manufacturer suggested retail price',\n  },\n  {\n    key: 'driverSize',\n    label: 'Driver Size',\n    category: 'Key Specifications',\n    description: 'Acoustic transducer diameter and diaphragm material',\n  },\n  {\n    key: 'batteryLife',\n    label: 'Battery Life',\n    category: 'Key Specifications',\n    description: 'Total listening duration with active cancellation enabled',\n  },\n  {\n    key: 'ancType',\n    label: 'ANC Type',\n    category: 'Key Specifications',\n    description: 'Acoustic cancellation and transparency algorithm type',\n  },\n  {\n    key: 'waterResistance',\n    label: 'Water Resistance',\n    category: 'Key Specifications',\n    description: 'Ingress protection rating against moisture and dust',\n  },\n  {\n    key: 'weight',\n    label: 'Weight',\n    category: 'Key Specifications',\n    description: 'Total physical mass including ear cushions/enclosures',\n  },\n  {\n    key: 'accessories',\n    label: 'In-Box Accessories',\n    category: 'Package & Warranty',\n    description: 'Included bundled cables, adapters, and travel protection',\n  },\n  {\n    key: 'connectivity',\n    label: 'Connectivity',\n    category: 'Audio & Connectivity',\n    description: 'Wireless Bluetooth specification and dual-device pairing',\n  },\n  {\n    key: 'fastCharging',\n    label: 'Fast Charging',\n    category: 'Audio & Connectivity',\n    description: 'Quick-boost charging time for emergency playback',\n  },\n  {\n    key: 'microphone',\n    label: 'Microphone Array',\n    category: 'Audio & Connectivity',\n    description: 'Voice pickup sensors and beamforming algorithms',\n  },\n  {\n    key: 'warranty',\n    label: 'Warranty',\n    category: 'Package & Warranty',\n    description: 'Manufacturer warranty coverage period',\n  },\n]\n\n/** SPEC_FIELDS grouped by category, in first-seen order (React's `categories` memo). */\nfunction groupFields(fields: SpecField[]): SpecCategory[] {\n  const cats: SpecCategory[] = []\n  for (const field of fields) {\n    let group = cats.find((c) => c.name === field.category)\n    if (!group) {\n      group = { name: field.category, fields: [] }\n      cats.push(group)\n    }\n    group.fields.push(field)\n  }\n  return cats\n}\n\n/**\n * ProductComparisonDrawer -- the React block, 1:1: a catalog preview with a sticky compare dock\n * (`mode=\"dock\"`) that expands into a full specification matrix, or the matrix on its own\n * (`mode=\"expanded\"`). The matrix groups specs by category, can highlight only the rows whose\n * values differ across the selected products, and ends in a sticky buy-trigger footer.\n * Emits `addToCart`, `removeProduct`, `clearAll`, `restoreProducts` and `openChange` where React\n * calls the matching `on*` prop.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-product-comparison-drawer, [ui-product-comparison-drawer]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiSeparatorComponent, UiSwitchComponent],\n  template: `\n    <div data-slot=\"product-comparison-drawer\" [class]=\"rootClass\">\n      <!-- Background Mock Catalog Page Preview (Context for Dock) -->\n      @if (mode === 'dock') {\n        <div class=\"bg-muted/10 flex flex-col p-4 sm:p-6 lg:p-8\">\n          <div class=\"mb-6 flex flex-col justify-between gap-3 sm:flex-row sm:items-center\">\n            <div>\n              <div class=\"flex items-center gap-2\">\n                <span\n                  class=\"bg-primary/10 text-primary rounded px-2 py-0.5 text-xs font-semibold tracking-wider uppercase\"\n                >\n                  Audio Lab\n                </span>\n                <span class=\"text-muted-foreground text-xs font-medium\">Over-Ear & In-Ear Systems</span>\n              </div>\n              <h2 class=\"text-xl font-bold tracking-tight sm:text-2xl\">High-Fidelity Audio Lineup</h2>\n              <p class=\"text-muted-foreground mt-1 text-xs sm:text-sm\">\n                Select flagship models below to compare acoustic architectures, battery runtime, and diffs side by side.\n              </p>\n            </div>\n\n            <div class=\"flex items-center gap-2\">\n              @if (selectedProducts().length < catalog.length) {\n                <button ui-button variant=\"outline\" size=\"sm\" class=\"text-xs\" (click)=\"handleRestoreProducts()\">\n                  <lucide-icon [img]=\"RotateCcw\" class=\"mr-1.5 size-3.5\" />\n                  Reset Selection\n                </button>\n              }\n              <button ui-button variant=\"default\" size=\"sm\" class=\"text-xs\" (click)=\"toggleExpanded()\">\n                <lucide-icon [img]=\"SlidersHorizontal\" class=\"mr-1.5 size-3.5\" />\n                {{ isExpanded() ? 'Hide Comparison' : 'Compare (' + selectedProducts().length + ')' }}\n              </button>\n            </div>\n          </div>\n\n          <!-- Mock Catalog Cards Grid -->\n          <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n            @for (product of catalog; track product.id) {\n              <div [class]=\"catalogCardClass(product)\">\n                <div>\n                  <div class=\"bg-muted/40 relative aspect-4/3 w-full overflow-hidden rounded-md\">\n                    <img\n                      [src]=\"product.image\"\n                      [alt]=\"product.name\"\n                      class=\"size-full object-cover transition-transform duration-300 group-hover:scale-105\"\n                      loading=\"lazy\"\n                    />\n                    <div class=\"absolute top-2.5 left-2.5\">\n                      <span\n                        ui-badge\n                        [variant]=\"product.badgeVariant || 'default'\"\n                        class=\"text-xs font-medium shadow-xs\"\n                      >\n                        {{ product.badge }}\n                      </span>\n                    </div>\n                  </div>\n\n                  <div class=\"mt-3.5 space-y-1\">\n                    <div class=\"flex items-center justify-between\">\n                      <div class=\"text-warning flex items-center gap-1 text-xs\">\n                        <lucide-icon [img]=\"Star\" class=\"size-3.5 fill-current\" />\n                        <span class=\"text-foreground font-semibold tabular-nums\">{{ product.rating }}</span>\n                        <span class=\"text-muted-foreground\">({{ formatCount(product.reviewsCount) }})</span>\n                      </div>\n                      <span class=\"text-sm font-bold tabular-nums\">{{ product.price }}</span>\n                    </div>\n                    <h3 class=\"text-foreground text-sm font-semibold tracking-tight\">{{ product.name }}</h3>\n                    <p class=\"text-muted-foreground line-clamp-1 text-xs\">{{ product.tagline }}</p>\n                  </div>\n                </div>\n\n                <div class=\"mt-4 flex items-center justify-between gap-2 border-t pt-3\">\n                  <button\n                    type=\"button\"\n                    class=\"text-muted-foreground hover:text-foreground flex min-h-6 items-center gap-1.5 text-xs font-medium transition-colors\"\n                    (click)=\"toggleCompare(product)\"\n                  >\n                    <div [class]=\"compareCheckClass(product)\">\n                      @if (isSelected(product)) {\n                        <lucide-icon [img]=\"Check\" class=\"size-3 stroke-[3]\" />\n                      }\n                    </div>\n                    <span>{{ isSelected(product) ? 'Comparing' : 'Add to compare' }}</span>\n                  </button>\n\n                  <button\n                    ui-button\n                    size=\"sm\"\n                    variant=\"outline\"\n                    class=\"h-8 text-xs\"\n                    [disabled]=\"addedProductId() === product.id\"\n                    (click)=\"handleAddToCart(product)\"\n                  >\n                    @if (addedProductId() === product.id) {\n                      <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3.5\" />\n                      Added\n                    } @else {\n                      <lucide-icon [img]=\"ShoppingCart\" class=\"mr-1 size-3.5\" />\n                      Add\n                    }\n                  </button>\n                </div>\n              </div>\n            }\n          </div>\n        </div>\n      }\n\n      <!-- Floating Sticky Comparison Bar (Dock at Bottom) -->\n      @if (mode === 'dock' && !isExpanded()) {\n        <div\n          class=\"bg-background/95 supports-[backdrop-filter]:bg-background/85 sticky bottom-0 z-30 w-full border-t p-3 backdrop-blur-md sm:p-4\"\n        >\n          <div class=\"mx-auto flex max-w-6xl flex-col items-stretch justify-between gap-3 sm:flex-row sm:items-center\">\n            <!-- Selected Products List -->\n            <div class=\"flex items-center gap-2 overflow-x-auto pb-1 sm:gap-3 sm:pb-0\">\n              <div class=\"flex shrink-0 items-center gap-1.5 pr-2\">\n                <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                  <lucide-icon [img]=\"Layers\" class=\"size-4\" />\n                </div>\n                <div class=\"flex flex-col\">\n                  <span class=\"text-xs leading-tight font-semibold\">Compare Dock</span>\n                  <span class=\"text-muted-foreground text-xs tabular-nums\">\n                    {{ selectedProducts().length }} / {{ catalog.length }} selected\n                  </span>\n                </div>\n              </div>\n\n              <ui-separator orientation=\"vertical\" class=\"hidden h-8 sm:block\" />\n\n              <!-- Selected Thumbnails -->\n              @if (selectedProducts().length > 0) {\n                <div class=\"flex items-center gap-2\">\n                  @for (product of selectedProducts(); track product.id) {\n                    <div\n                      class=\"bg-card group hover:border-primary/40 relative flex shrink-0 items-center gap-2 rounded-lg border p-1.5 pr-2.5 shadow-2xs transition-colors\"\n                    >\n                      <img [src]=\"product.image\" [alt]=\"product.name\" class=\"size-9 rounded-md object-cover\" />\n                      <div class=\"flex flex-col\">\n                        <span class=\"max-w-[110px] truncate text-xs font-medium sm:max-w-[130px]\">\n                          {{ product.name }}\n                        </span>\n                        <span class=\"text-muted-foreground text-xs font-semibold tabular-nums\">\n                          {{ product.price }}\n                        </span>\n                      </div>\n                      <button\n                        type=\"button\"\n                        class=\"bg-background/80 hover:bg-destructive hover:text-destructive-foreground text-muted-foreground absolute -top-1.5 -right-1.5 flex size-4 items-center justify-center rounded-full border opacity-0 shadow-2xs transition-opacity group-hover:opacity-100 focus-visible:opacity-100\"\n                        [attr.aria-label]=\"'Remove ' + product.name + ' from comparison'\"\n                        (click)=\"$event.stopPropagation(); handleRemoveProduct(product.id)\"\n                      >\n                        <lucide-icon [img]=\"X\" class=\"size-2.5\" />\n                      </button>\n                    </div>\n                  }\n                </div>\n              } @else {\n                <div class=\"text-muted-foreground flex items-center gap-2 py-1 text-xs\">\n                  <span>No products in comparison dock.</span>\n                  <button\n                    type=\"button\"\n                    class=\"text-primary font-medium hover:underline\"\n                    (click)=\"handleRestoreProducts()\"\n                  >\n                    Restore 3 Items\n                  </button>\n                </div>\n              }\n            </div>\n\n            <!-- Action Buttons -->\n            <div class=\"flex shrink-0 items-center justify-end gap-2\">\n              @if (selectedProducts().length > 0) {\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  class=\"text-muted-foreground hover:text-foreground h-8 text-xs font-medium\"\n                  (click)=\"handleClearAll()\"\n                >\n                  Clear All\n                </button>\n              }\n\n              <button\n                ui-button\n                variant=\"default\"\n                size=\"sm\"\n                class=\"h-8 gap-1.5 text-xs font-medium shadow-xs\"\n                [disabled]=\"selectedProducts().length === 0\"\n                (click)=\"toggleExpanded()\"\n              >\n                <span>Compare {{ selectedProducts().length }} Items</span>\n                <lucide-icon [img]=\"ChevronUp\" class=\"size-3.5\" />\n              </button>\n            </div>\n          </div>\n        </div>\n      }\n\n      <!-- Expanded Full Specification Matrix Drawer / Container -->\n      @if (isExpanded() || mode === 'expanded') {\n        <div\n          class=\"bg-background motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-2 relative flex w-full flex-col border-t shadow-lg motion-safe:duration-200\"\n        >\n          <!-- Matrix Header Controls Bar -->\n          <div\n            class=\"bg-card/90 supports-[backdrop-filter]:bg-card/75 sticky top-0 z-30 border-b px-4 py-3.5 backdrop-blur-md sm:px-6\"\n          >\n            <div class=\"flex flex-col justify-between gap-3 sm:flex-row sm:items-center\">\n              <div class=\"flex items-center gap-3\">\n                <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n                  <lucide-icon [img]=\"SlidersHorizontal\" class=\"size-4\" />\n                </div>\n                <div>\n                  <div class=\"flex items-center gap-2\">\n                    <h3 class=\"text-base font-semibold tracking-tight\">Specification Matrix</h3>\n                    <span ui-badge variant=\"secondary\" class=\"text-xs font-semibold tabular-nums\">\n                      {{ selectedProducts().length }} Selected\n                    </span>\n                    @if (highlightDiffs()) {\n                      <span ui-badge variant=\"outline\" class=\"border-primary/40 bg-primary/5 text-primary text-xs\">\n                        <lucide-icon [img]=\"GitCompare\" class=\"mr-1 size-3\" />\n                        Diffs Highlighted ({{ diffCount() }})\n                      </span>\n                    }\n                  </div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Side-by-side technical breakdown across drivers, battery, acoustics, and box contents.\n                  </p>\n                </div>\n              </div>\n\n              <!-- Controls: Diff Switch & Close -->\n              <div class=\"flex items-center gap-4\">\n                <div class=\"flex items-center space-x-2\">\n                  <button\n                    ui-switch\n                    id=\"angular-diff-toggle\"\n                    size=\"sm\"\n                    [checked]=\"highlightDiffs()\"\n                    (checkedChange)=\"highlightDiffs.set($event)\"\n                  ></button>\n                  <label for=\"angular-diff-toggle\" class=\"cursor-pointer text-xs font-medium select-none\">\n                    Highlight Differences Only\n                  </label>\n                </div>\n\n                @if (mode === 'dock') {\n                  <button ui-button variant=\"outline\" size=\"sm\" class=\"h-8 gap-1 text-xs\" (click)=\"toggleExpanded()\">\n                    <span>Collapse</span>\n                    <lucide-icon [img]=\"ChevronDown\" class=\"size-3.5\" />\n                  </button>\n                }\n              </div>\n            </div>\n          </div>\n\n          <!-- Specification Matrix Content -->\n          @if (selectedProducts().length === 0) {\n            <div class=\"flex flex-col items-center justify-center p-12 text-center\">\n              <div class=\"bg-muted text-muted-foreground flex size-12 items-center justify-center rounded-full\">\n                <lucide-icon [img]=\"Trash2\" class=\"size-6\" />\n              </div>\n              <h4 class=\"mt-3 text-sm font-semibold\">No Products Selected</h4>\n              <p class=\"text-muted-foreground mt-1 max-w-sm text-xs\">\n                You have cleared all items from comparison. Restore the baseline audio products to explore differences.\n              </p>\n              <button ui-button variant=\"default\" size=\"sm\" class=\"mt-4 text-xs\" (click)=\"handleRestoreProducts()\">\n                <lucide-icon [img]=\"RotateCcw\" class=\"mr-1.5 size-3.5\" />\n                Restore 3 Items\n              </button>\n            </div>\n          } @else {\n            <div class=\"w-full overflow-x-auto\">\n              <table class=\"w-full max-w-[760px] min-w-full border-separate border-spacing-0 text-left text-xs\">\n                <!-- Sticky Product Card Header Row -->\n                <thead>\n                  <tr>\n                    <th\n                      scope=\"col\"\n                      class=\"bg-background text-muted-foreground sticky top-0 left-0 z-20 w-[200px] min-w-[180px] border-b p-4 align-bottom font-medium\"\n                    >\n                      <div class=\"space-y-1\">\n                        <span class=\"text-foreground text-xs font-semibold tracking-wider uppercase\">Attributes</span>\n                        <p class=\"text-muted-foreground text-xs font-normal\">\n                          {{ selectedProducts().length }} products compared\n                        </p>\n                      </div>\n                    </th>\n\n                    <!-- Product Columns -->\n                    @for (product of selectedProducts(); track product.id) {\n                      <th scope=\"col\" class=\"bg-background relative w-[240px] min-w-[220px] border-b p-4 align-top\">\n                        <div class=\"flex flex-col gap-3\">\n                          <!-- Remove button -->\n                          <div class=\"flex items-center justify-between gap-2\">\n                            <span ui-badge [variant]=\"product.badgeVariant || 'default'\" class=\"text-xs font-medium\">\n                              {{ product.badge }}\n                            </span>\n                            <button\n                              type=\"button\"\n                              class=\"text-muted-foreground hover:bg-muted hover:text-foreground flex size-6 items-center justify-center rounded-md transition-colors\"\n                              [attr.aria-label]=\"'Remove ' + product.name\"\n                              (click)=\"handleRemoveProduct(product.id)\"\n                            >\n                              <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n                            </button>\n                          </div>\n\n                          <!-- Product Hero Visual -->\n                          <div class=\"flex items-center gap-3\">\n                            <img\n                              [src]=\"product.image\"\n                              [alt]=\"product.name\"\n                              class=\"size-16 rounded-md border object-cover shadow-2xs\"\n                            />\n                            <div class=\"flex flex-col\">\n                              <span class=\"text-foreground text-xs leading-tight font-semibold\">\n                                {{ product.name }}\n                              </span>\n                              <div class=\"text-warning mt-1 flex items-center gap-1 text-xs\">\n                                <lucide-icon [img]=\"Star\" class=\"size-3 fill-current\" />\n                                <span class=\"text-foreground font-medium tabular-nums\">{{ product.rating }}</span>\n                              </div>\n                              <span class=\"text-foreground mt-1 text-sm font-bold tabular-nums\">\n                                {{ product.price }}\n                              </span>\n                            </div>\n                          </div>\n\n                          <!-- Quick Buy Trigger -->\n                          <button\n                            ui-button\n                            size=\"sm\"\n                            variant=\"default\"\n                            class=\"w-full gap-1.5 text-xs shadow-xs\"\n                            [disabled]=\"addedProductId() === product.id\"\n                            (click)=\"handleAddToCart(product)\"\n                          >\n                            @if (addedProductId() === product.id) {\n                              <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                              Added to Cart\n                            } @else {\n                              <lucide-icon [img]=\"ShoppingCart\" class=\"size-3.5\" />\n                              Add to Cart\n                            }\n                          </button>\n                        </div>\n                      </th>\n                    }\n                  </tr>\n                </thead>\n\n                <!-- Table Body grouped by Category -->\n                @for (category of categories; track category.name) {\n                  <tbody>\n                    <!-- Category Subheading Header Row -->\n                    <tr>\n                      <th\n                        [attr.colspan]=\"selectedProducts().length + 1\"\n                        scope=\"colgroup\"\n                        class=\"bg-muted/60 text-muted-foreground sticky left-0 z-10 border-b px-4 py-2 text-xs font-semibold tracking-wider uppercase\"\n                      >\n                        {{ category.name }}\n                      </th>\n                    </tr>\n\n                    <!-- Specification Rows -->\n                    @for (field of category.fields; track field.key) {\n                      <tr [class]=\"rowClass(field)\">\n                        <!-- Spec Label Cell (Sticky Left) -->\n                        <th\n                          scope=\"row\"\n                          class=\"bg-background group-hover/row:bg-muted/30 sticky left-0 z-10 border-b p-3.5 align-middle text-xs font-medium\"\n                        >\n                          <div class=\"flex items-center justify-between gap-1.5\">\n                            <div class=\"flex flex-col\">\n                              <span class=\"text-foreground font-medium\">{{ field.label }}</span>\n                              @if (field.description) {\n                                <span class=\"text-muted-foreground text-xs font-normal\">{{ field.description }}</span>\n                              }\n                            </div>\n                            @if (isRowDiff(field.key) && highlightDiffs()) {\n                              <span\n                                class=\"bg-primary/10 text-primary shrink-0 rounded px-1.5 py-0.5 text-xs font-semibold\"\n                              >\n                                Diff\n                              </span>\n                            }\n                          </div>\n                        </th>\n\n                        <!-- Spec Values for Each Selected Product -->\n                        @for (product of selectedProducts(); track product.id) {\n                          <td class=\"text-foreground border-b p-3.5 align-middle text-xs\">\n                            <div class=\"flex items-center gap-1.5\">\n                              <span [class]=\"valueClass(field)\">{{ specValue(product, field) }}</span>\n                            </div>\n                          </td>\n                        }\n                      </tr>\n                    }\n                  </tbody>\n                }\n\n                <!-- Sticky Bottom Buy Triggers Footer -->\n                <tfoot>\n                  <tr class=\"bg-card sticky bottom-0 z-20\">\n                    <th scope=\"row\" class=\"bg-card sticky left-0 z-20 border-t p-4 align-middle\">\n                      <div class=\"space-y-0.5\">\n                        <span class=\"text-foreground text-xs font-semibold\">Sticky Buy Trigger</span>\n                        <p class=\"text-muted-foreground text-xs\">\n                          All purchases backed by 30-day money-back guarantee.\n                        </p>\n                      </div>\n                    </th>\n\n                    @for (product of selectedProducts(); track product.id) {\n                      <td class=\"border-t p-4 align-middle\">\n                        <div class=\"flex flex-col gap-1.5\">\n                          <div class=\"flex items-center justify-between\">\n                            <span class=\"text-foreground text-xs font-bold tabular-nums\">{{ product.price }}</span>\n                            <span class=\"text-muted-foreground text-xs\">{{ product.shipping }}</span>\n                          </div>\n                          <button\n                            ui-button\n                            size=\"sm\"\n                            variant=\"default\"\n                            class=\"w-full gap-1.5 text-xs shadow-xs\"\n                            [disabled]=\"addedProductId() === product.id\"\n                            (click)=\"handleAddToCart(product)\"\n                          >\n                            @if (addedProductId() === product.id) {\n                              <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                              Added\n                            } @else {\n                              <lucide-icon [img]=\"ShoppingCart\" class=\"size-3.5\" />\n                              Buy Now\n                            }\n                          </button>\n                        </div>\n                      </td>\n                    }\n                  </tr>\n                </tfoot>\n              </table>\n            </div>\n          }\n        </div>\n      }\n    </div>\n  `,\n})\nexport class UiProductComparisonDrawerComponent implements OnInit {\n  /** Opens the matrix on first render (dock mode). */\n  @Input({ transform: booleanAttribute }) defaultOpen = false\n  /** Seeds the \"Highlight Differences Only\" switch. */\n  @Input({ transform: booleanAttribute }) defaultHighlightDiffs = false\n  /** Products in the comparison. A new array resets the selection to it (React re-seeds on a `products` change). */\n  @Input() set products(value: ComparisonProduct[] | null | undefined) {\n    this.selectedProducts.set([...(value ?? DEFAULT_COMPARISON_PRODUCTS)])\n  }\n  @Input() mode: ProductComparisonDrawerMode = 'dock'\n  @Input('class') className?: string\n\n  @Output() readonly addToCart = new EventEmitter<ComparisonProduct>()\n  @Output() readonly removeProduct = new EventEmitter<string>()\n  @Output() readonly clearAll = new EventEmitter<void>()\n  @Output() readonly restoreProducts = new EventEmitter<void>()\n  @Output() readonly openChange = new EventEmitter<boolean>()\n\n  readonly isExpanded = signal(false)\n  readonly highlightDiffs = signal(false)\n  readonly selectedProducts = signal<ComparisonProduct[]>([...DEFAULT_COMPARISON_PRODUCTS])\n  readonly addedProductId = signal<string | null>(null)\n\n  /** The catalog preview always lists the full default lineup, as in React. */\n  protected readonly catalog = DEFAULT_COMPARISON_PRODUCTS\n  protected readonly categories = groupFields(SPEC_FIELDS)\n\n  readonly diffCount = computed(() => SPEC_FIELDS.filter((f) => this.isRowDiff(f.key)).length)\n\n  protected readonly Check = Check\n  protected readonly ChevronDown = ChevronDown\n  protected readonly ChevronUp = ChevronUp\n  protected readonly GitCompare = GitCompare\n  protected readonly Layers = Layers\n  protected readonly RotateCcw = RotateCcw\n  protected readonly ShoppingCart = ShoppingCart\n  protected readonly SlidersHorizontal = SlidersHorizontal\n  protected readonly Star = Star\n  protected readonly Trash2 = Trash2\n  protected readonly X = X\n\n  ngOnInit(): void {\n    // React seeds its useState once from these props; inputs land after construction.\n    this.isExpanded.set(this.defaultOpen || this.mode === 'expanded')\n    this.highlightDiffs.set(this.defaultHighlightDiffs)\n  }\n\n  protected get rootClass(): string {\n    return cn(\n      'bg-background text-foreground relative flex w-full flex-col overflow-hidden rounded-xl border',\n      this.className,\n    )\n  }\n\n  toggleExpanded(): void {\n    const next = !this.isExpanded()\n    this.isExpanded.set(next)\n    this.openChange.emit(next)\n  }\n\n  handleRemoveProduct(id: string): void {\n    this.selectedProducts.update((prev) => prev.filter((p) => p.id !== id))\n    this.removeProduct.emit(id)\n  }\n\n  handleClearAll(): void {\n    this.selectedProducts.set([])\n    this.clearAll.emit()\n  }\n\n  handleRestoreProducts(): void {\n    this.selectedProducts.set([...DEFAULT_COMPARISON_PRODUCTS])\n    this.restoreProducts.emit()\n  }\n\n  handleAddToCart(product: ComparisonProduct): void {\n    this.addedProductId.set(product.id)\n    this.addToCart.emit(product)\n    setTimeout(() => {\n      this.addedProductId.update((current) => (current === product.id ? null : current))\n    }, 1800)\n  }\n\n  /** Catalog card checkbox: removing emits `removeProduct`, adding does not (as in React). */\n  toggleCompare(product: ComparisonProduct): void {\n    if (this.isSelected(product)) this.handleRemoveProduct(product.id)\n    else this.selectedProducts.update((prev) => [...prev, product])\n  }\n\n  isSelected(product: ComparisonProduct): boolean {\n    return this.selectedProducts().some((p) => p.id === product.id)\n  }\n\n  /** True when the spec's value differs across the currently selected products. */\n  isRowDiff(specKey: keyof ComparisonSpec): boolean {\n    const selected = this.selectedProducts()\n    if (selected.length <= 1) return false\n    const firstVal = selected[0]?.specs[specKey]\n    return selected.some((p) => p.specs[specKey] !== firstVal)\n  }\n\n  specValue(product: ComparisonProduct, field: SpecField): string {\n    return product.specs[field.key] || '—'\n  }\n\n  formatCount(value: number): string {\n    return value.toLocaleString()\n  }\n\n  protected catalogCardClass(product: ComparisonProduct): string {\n    return cn(\n      'bg-card hover:border-border group relative flex flex-col justify-between rounded-lg border p-4 shadow-xs transition-colors',\n      this.isSelected(product) && 'ring-primary/20 border-primary/40 ring-1',\n    )\n  }\n\n  protected compareCheckClass(product: ComparisonProduct): string {\n    return cn(\n      'flex size-4 items-center justify-center rounded border transition-colors',\n      this.isSelected(product)\n        ? 'border-primary bg-primary text-primary-foreground'\n        : 'border-muted-foreground/40 bg-background',\n    )\n  }\n\n  protected rowClass(field: SpecField): string {\n    const isDiff = this.isRowDiff(field.key)\n    const highlight = this.highlightDiffs()\n    return cn(\n      'group/row transition-colors',\n      isDiff && highlight ? 'bg-primary/[0.04] dark:bg-primary/[0.08]' : 'hover:bg-muted/20',\n      !isDiff && highlight ? 'opacity-40' : 'opacity-100',\n    )\n  }\n\n  protected valueClass(field: SpecField): string {\n    const isDiff = this.isRowDiff(field.key)\n    return cn({\n      'font-semibold tabular-nums': field.key === 'price' || field.key === 'weight' || field.key === 'batteryLife',\n      'text-foreground font-medium': isDiff,\n      'text-muted-foreground': !isDiff,\n    })\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/product-comparison-drawer/product-comparison-drawer.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/product-comparison-drawer/index.ts",
      "content": "export {\n  UiProductComparisonDrawerComponent,\n  DEFAULT_COMPARISON_PRODUCTS,\n  SPEC_FIELDS,\n  type ComparisonProduct,\n  type ComparisonSpec,\n  type ProductComparisonDrawerMode,\n  type SpecField,\n} from './product-comparison-drawer.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/product-comparison-drawer/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/switch.json"
  ],
  "description": "Floating multi-product specification comparison bar with attribute diff highlighting, expanded full-matrix drawer comparison, and sticky buy triggers.",
  "categories": [
    "commerce",
    "ecommerce"
  ]
}