{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "data-table",
  "title": "Data Table",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/data-table/data-table.component.ts",
      "content": "import { Component, EventEmitter, Input, Output, booleanAttribute, ChangeDetectionStrategy } from '@angular/core'\n/** Anything that can announce to screen readers — @angular/cdk's LiveAnnouncer fits, without depending on the CDK. */\nexport interface Announcer {\n  announce(message: string): unknown\n}\nimport { cn } from '@/lib/utils'\n\nexport interface DataTableColumn {\n  key: string\n  label: string\n  sortable?: boolean\n  align?: 'left' | 'right' | 'center'\n}\n\nexport type DataTableSortDir = 'asc' | 'desc'\n\n/**\n * Angular port of UIPKGE DataTable — sorting, global search, column\n * visibility, pagination, export-CSV and infinite-scroll hooks. Same\n * toolbar/pagination props as Vue; pure row pipeline (filter -> sort ->\n * page) is unit-testable without DOM.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-data-table, [ui-data-table]',\n  standalone: true,\n  host: {\n    '[attr.data-slot]': '\"data-table\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiDataTableComponent {\n  @Input() columns: DataTableColumn[] = []\n  @Input() data: Record<string, unknown>[] = []\n  @Input() filterColumn?: string\n  @Input() filterPlaceholder = 'Filter...'\n  @Input({ transform: booleanAttribute }) enableSearch = true\n  @Input({ transform: booleanAttribute }) enableColumnVisibility = false\n  @Input({ transform: booleanAttribute }) enablePagination = true\n  @Input({ transform: booleanAttribute }) hideToolbar = false\n  @Input({ transform: booleanAttribute }) enableExport = false\n  @Input({ transform: booleanAttribute }) infinite = false\n  @Input() pageSize = 10\n  @Input() page = 0\n  @Input() search = ''\n  @Input() sortKey?: string\n  @Input() sortDir: DataTableSortDir = 'asc'\n  @Input() hiddenColumns: string[] = []\n  @Input('class') className?: string\n  @Output() pageChange = new EventEmitter<number>()\n  @Output() sortChange = new EventEmitter<{ key: string; dir: DataTableSortDir }>()\n  @Output() searchChange = new EventEmitter<string>()\n  @Output() fetchMore = new EventEmitter<void>()\n  /** CDK a11y hook: host apps bind LiveAnnouncer to announce sort/page. */\n  announcer?: Announcer\n\n  get hostClass(): string {\n    return cn('flex flex-col gap-4', this.className)\n  }\n\n  visibleColumns(): DataTableColumn[] {\n    const hidden = new Set(this.hiddenColumns)\n    return this.columns.filter((c) => !hidden.has(c.key))\n  }\n\n  filteredData(): Record<string, unknown>[] {\n    const q = this.search.trim().toLowerCase()\n    if (!q || !this.enableSearch) return [...this.data]\n    const keys = this.filterColumn ? [this.filterColumn] : this.columns.map((c) => c.key)\n    return this.data.filter((row) =>\n      keys.some((k) =>\n        String(row[k] ?? '')\n          .toLowerCase()\n          .includes(q),\n      ),\n    )\n  }\n\n  sortedData(): Record<string, unknown>[] {\n    const rows = this.filteredData()\n    if (!this.sortKey) return rows\n    const dir = this.sortDir === 'desc' ? -1 : 1\n    const key = this.sortKey\n    return [...rows].sort((a, b) => {\n      const av = a[key]\n      const bv = b[key]\n      if (av == null && bv == null) return 0\n      if (av == null) return -1 * dir\n      if (bv == null) return 1 * dir\n      if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir\n      return String(av).localeCompare(String(bv)) * dir\n    })\n  }\n\n  pagedData(): Record<string, unknown>[] {\n    const rows = this.sortedData()\n    if (!this.enablePagination || this.infinite) return rows\n    const start = this.page * this.pageSize\n    return rows.slice(start, start + this.pageSize)\n  }\n\n  totalPages(): number {\n    if (!this.enablePagination || this.infinite) return 1\n    return Math.max(1, Math.ceil(this.sortedData().length / Math.max(1, this.pageSize)))\n  }\n\n  /**\n   * Value for `aria-sort` on a sortable column's header cell (`<th ui-table-head scope=\"col\">`),\n   * so screen readers announce the active sort. Leave it off non-sortable columns.\n   */\n  ariaSort(key: string): 'ascending' | 'descending' | 'none' {\n    if (this.sortKey !== key) return 'none'\n    return this.sortDir === 'desc' ? 'descending' : 'ascending'\n  }\n\n  setSort(key: string): void {\n    if (this.sortKey === key) {\n      this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc'\n    } else {\n      this.sortKey = key\n      this.sortDir = 'asc'\n    }\n    this.sortChange.emit({ key: this.sortKey!, dir: this.sortDir })\n    try {\n      this.announcer?.announce(`Sorted by ${key} ${this.sortDir}`)\n    } catch {\n      /* announcer optional */\n    }\n  }\n\n  gotoPage(p: number): void {\n    const next = Math.min(this.totalPages() - 1, Math.max(0, p))\n    this.pageChange.emit(next)\n  }\n\n  nextPage(): void {\n    this.gotoPage(this.page + 1)\n  }\n\n  prevPage(): void {\n    this.gotoPage(this.page - 1)\n  }\n\n  exportCsv(): string {\n    const cols = this.visibleColumns()\n    const esc = (v: unknown) => `\"${String(v ?? '').replace(/\"/g, '\"\"')}\"`\n    const head = cols.map((c) => esc(c.label)).join(',')\n    const body = this.sortedData().map((r) => cols.map((c) => esc(r[c.key])).join(','))\n    return [head, ...body].join('\\n')\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-data-table-column-header, [ui-data-table-column-header]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"data-table-column-header\"', '[attr.data-uipkge]': '\"\"', '[class]': 'hostClass' },\n  // `label` is shown unless you project your own content (e.g. label + sort icon).\n  template: `<ng-content>{{ label }}</ng-content>`,\n})\nexport class UiDataTableColumnHeaderComponent {\n  @Input() label = ''\n  @Input() align: 'left' | 'right' | 'center' = 'left'\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'inline-flex items-center gap-1 font-medium',\n      this.align === 'right' && 'justify-end',\n      this.align === 'center' && 'justify-center',\n      this.className,\n    )\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/data-table/data-table.component.ts"
    },
    {
      "path": "packages/registry-angular/components/data-table/index.ts",
      "content": "export {\n  UiDataTableComponent,\n  UiDataTableColumnHeaderComponent,\n  type DataTableColumn,\n  type DataTableSortDir,\n} from './data-table.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/data-table/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Full-feature table with sorting, filtering, column visibility, pagination, and CSV export. Pass columns + data and configure as needed.",
  "categories": [
    "data"
  ]
}