{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"table","title":"Table","description":"A virtualized shadcn-compatible table with sorting, resizing, and optional selection.","dependencies":["@tanstack/react-table","@tanstack/react-virtual","lucide-react"],"registryDependencies":["checkbox"],"files":[{"path":"components/emstein-ui/table.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport {\n  type ColumnDef,\n  type ColumnSizingState,\n  type RowSelectionState,\n  type SortingState,\n  flexRender,\n  getCoreRowModel,\n  getSortedRowModel,\n  useReactTable,\n} from \"@tanstack/react-table\"\nimport { useVirtualizer, useWindowVirtualizer } from \"@tanstack/react-virtual\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport { cn } from \"@/lib/utils\"\n\ntype Align = \"left\" | \"center\" | \"right\"\ntype SortDirection = \"asc\" | \"desc\"\ntype ColumnType =\n  | \"text\"\n  | \"longText\"\n  | \"shortText\"\n  | \"number\"\n  | \"currency\"\n  | \"date\"\n  | \"status\"\n  | \"actions\"\n\nexport type EmsteinUITableColumn<TData> = {\n  key: Extract<keyof TData, string> | string\n  label: string\n  type?: ColumnType\n  grow?: boolean | number\n  width?: number\n  minWidth?: number\n  sortable?: boolean\n  resizable?: boolean\n  align?: Align\n  render?: (row: TData) => React.ReactNode\n  footer?:\n    | React.ReactNode\n    | ((context: { data: TData[]; rows: TData[] }) => React.ReactNode)\n  className?: string\n  footerClassName?: string\n}\n\nexport type EmsteinUITableSelection = {\n  selectedRows?: string[]\n  onSelectedRowsChange?: (rows: string[]) => void\n  defaultSelectedRows?: string[]\n}\n\nexport type EmsteinUITableProps<TData> = {\n  data: TData[]\n  columns: EmsteinUITableColumn<TData>[]\n  getRowId?: (row: TData, index: number) => string\n  selection?: EmsteinUITableSelection\n  resizable?: boolean\n  sortable?: boolean\n  showUnsortedSortIcon?: boolean\n  initialSort?: { key: string; direction?: SortDirection } | false\n  scrollMode?: \"page\" | \"table\"\n  tableHeight?: number\n  rowHeight?: number\n  emptyText?: string\n  caption?: React.ReactNode\n  className?: string\n  containerClassName?: string\n  tableClassName?: string\n  headerClassName?: string\n  bodyClassName?: string\n  footerClassName?: string\n  captionClassName?: string\n}\n\nexport type TablePrimitiveProps = React.ComponentProps<\"table\"> & {\n  containerClassName?: string\n}\n\nconst alignClass: Record<Align, string> = {\n  left: \"justify-start text-left\",\n  center: \"justify-center text-center\",\n  right: \"justify-end text-right\",\n}\n\nconst columnTypeSizes: Record<\n  ColumnType,\n  { width: number; minWidth: number; grow: number }\n> = {\n  text: { width: 180, minWidth: 120, grow: 1 },\n  longText: { width: 280, minWidth: 180, grow: 2 },\n  shortText: { width: 140, minWidth: 96, grow: 0.5 },\n  number: { width: 104, minWidth: 80, grow: 0 },\n  currency: { width: 124, minWidth: 96, grow: 0 },\n  date: { width: 132, minWidth: 112, grow: 0 },\n  status: { width: 128, minWidth: 104, grow: 0 },\n  actions: { width: 72, minWidth: 56, grow: 0 },\n}\n\nfunction getColumnSizes<TData>(column: EmsteinUITableColumn<TData>) {\n  const preset = columnTypeSizes[column.type ?? \"text\"]\n\n  return {\n    width: column.width ?? preset.width,\n    minWidth: column.minWidth ?? preset.minWidth,\n  }\n}\n\nfunction getColumnGrow<TData>(column: EmsteinUITableColumn<TData>) {\n  if (typeof column.grow === \"number\") {\n    return Math.max(0, column.grow)\n  }\n\n  if (column.grow === true) {\n    return 1\n  }\n\n  if (column.grow === false) {\n    return 0\n  }\n\n  return columnTypeSizes[column.type ?? \"text\"].grow\n}\n\nfunction getValue<TData>(row: TData, key: string) {\n  return (row as Record<string, unknown>)[key]\n}\n\nfunction toRowSelection(ids: string[]): RowSelectionState {\n  return ids.reduce<RowSelectionState>((state, id) => {\n    state[id] = true\n    return state\n  }, {})\n}\n\nfunction getNextValue<TValue>(\n  updater: TValue | ((old: TValue) => TValue),\n  old: TValue\n) {\n  return typeof updater === \"function\"\n    ? (updater as (value: TValue) => TValue)(old)\n    : updater\n}\n\nexport function TableHeader({\n  className,\n  ...props\n}: React.ComponentProps<\"thead\">) {\n  return (\n    <thead\n      data-slot=\"table-header\"\n      className={cn(\"[&_tr]:border-b\", className)}\n      {...props}\n    />\n  )\n}\n\nexport function TableBody({\n  className,\n  ...props\n}: React.ComponentProps<\"tbody\">) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn(\"[&_tr:last-child]:border-0\", className)}\n      {...props}\n    />\n  )\n}\n\nexport function TableFooter({\n  className,\n  ...props\n}: React.ComponentProps<\"tfoot\">) {\n  return (\n    <tfoot\n      data-slot=\"table-footer\"\n      className={cn(\n        \"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport function TableRow({ className, ...props }: React.ComponentProps<\"tr\">) {\n  return (\n    <tr\n      data-slot=\"table-row\"\n      className={cn(\n        \"border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport function TableHead({ className, ...props }: React.ComponentProps<\"th\">) {\n  return (\n    <th\n      data-slot=\"table-head\"\n      className={cn(\n        \"h-12 px-3 text-left align-middle text-xs font-medium uppercase tracking-wider whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport function TableCell({ className, ...props }: React.ComponentProps<\"td\">) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      className={cn(\n        \"p-3 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport function TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<\"caption\">) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PrimitiveTable({\n  className,\n  containerClassName,\n  ...props\n}: TablePrimitiveProps) {\n  return (\n    <div\n      data-slot=\"table-container\"\n      className={cn(\"relative w-full overflow-x-auto\", containerClassName)}\n    >\n      <table\n        data-slot=\"table\"\n        className={cn(\"w-full caption-bottom text-sm\", className)}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction DataTable<TData>({\n  data,\n  columns,\n  getRowId,\n  selection,\n  resizable = true,\n  sortable = true,\n  showUnsortedSortIcon = false,\n  initialSort,\n  scrollMode = \"page\",\n  tableHeight = 560,\n  rowHeight = 56,\n  emptyText = \"No results.\",\n  caption,\n  className,\n  containerClassName,\n  tableClassName,\n  headerClassName,\n  bodyClassName,\n  footerClassName,\n  captionClassName,\n}: EmsteinUITableProps<TData>) {\n  const isSelectionControlled = selection?.selectedRows !== undefined\n  const [internalSelectedRows, setInternalSelectedRows] = React.useState(\n    selection?.defaultSelectedRows ?? []\n  )\n  const selectedRows = selection?.selectedRows ?? internalSelectedRows\n  const rowSelection = React.useMemo(\n    () => toRowSelection(selectedRows),\n    [selectedRows]\n  )\n  const hasSelection = selection !== undefined\n\n  const initialSorting = React.useMemo<SortingState>(() => {\n    if (!sortable || initialSort === false) {\n      return []\n    }\n\n    if (initialSort?.key) {\n      return [{ id: initialSort.key, desc: initialSort.direction === \"desc\" }]\n    }\n\n    const firstSortableColumn = columns.find((column) => column.sortable !== false)\n\n    return firstSortableColumn\n      ? [{ id: String(firstSortableColumn.key), desc: false }]\n      : []\n  }, [columns, initialSort, sortable])\n\n  const [sorting, setSorting] = React.useState<SortingState>(initialSorting)\n  const [columnSizing, setColumnSizing] = React.useState<ColumnSizingState>({})\n\n  const tableColumns = React.useMemo<ColumnDef<TData>[]>(\n    () => [\n      ...(hasSelection\n        ? [\n            {\n              id: \"_select\",\n              size: 44,\n              minSize: 44,\n              maxSize: 44,\n              enableSorting: false,\n              enableResizing: false,\n              header: ({ table }) => (\n                <Checkbox\n                  aria-label=\"Select all rows\"\n                  checked={\n                    table.getIsAllRowsSelected()\n                      ? true\n                      : table.getIsSomeRowsSelected()\n                        ? \"indeterminate\"\n                        : false\n                  }\n                  onCheckedChange={(value) =>\n                    table.toggleAllRowsSelected(Boolean(value))\n                  }\n                />\n              ),\n              cell: ({ row }) => (\n                <Checkbox\n                  aria-label=\"Select row\"\n                  checked={row.getIsSelected()}\n                  onCheckedChange={(value) => row.toggleSelected(Boolean(value))}\n                />\n              ),\n            } satisfies ColumnDef<TData>,\n          ]\n        : []),\n      ...columns.map(\n        (column): ColumnDef<TData> => {\n          const sizes = getColumnSizes(column)\n\n          return {\n            id: String(column.key),\n            accessorFn: (row) => getValue(row, String(column.key)),\n            size: sizes.width,\n            minSize: sizes.minWidth,\n            enableSorting: sortable && column.sortable !== false,\n            enableResizing: resizable && column.resizable !== false,\n            header: column.label,\n            cell: ({ row }) => {\n              const value = getValue(row.original, String(column.key))\n\n              return column.render\n                ? column.render(row.original)\n                : String(value ?? \"\")\n            },\n            meta: {\n              align: column.align ?? \"left\",\n              className: column.className,\n              grow: getColumnGrow(column),\n            },\n          }\n        }\n      ),\n    ],\n    [columns, hasSelection, resizable, sortable]\n  )\n\n  const table = useReactTable({\n    data,\n    columns: tableColumns,\n    state: {\n      sorting,\n      columnSizing,\n      rowSelection,\n    },\n    defaultColumn: {\n      minSize: 96,\n      size: 160,\n    },\n    getRowId: (row, index) => getRowId?.(row, index) ?? String(index),\n    enableRowSelection: hasSelection,\n    enableSortingRemoval: false,\n    columnResizeMode: \"onChange\",\n    onSortingChange: setSorting,\n    onColumnSizingChange: setColumnSizing,\n    onRowSelectionChange: (updater) => {\n      const next = getNextValue(updater, rowSelection)\n      const ids = Object.keys(next).filter((id) => next[id])\n\n      if (!isSelectionControlled) {\n        setInternalSelectedRows(ids)\n      }\n\n      selection?.onSelectedRowsChange?.(ids)\n    },\n    getCoreRowModel: getCoreRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n  })\n\n  const rows = table.getRowModel().rows\n  const renderedRows = React.useMemo(\n    () => rows.map((row) => row.original),\n    [rows]\n  )\n  const tableRootRef = React.useRef<HTMLDivElement>(null)\n  const tableScrollRef = React.useRef<HTMLDivElement>(null)\n  const [scrollMargin, setScrollMargin] = React.useState(0)\n  const [containerWidth, setContainerWidth] = React.useState(0)\n\n  React.useLayoutEffect(() => {\n    if (scrollMode !== \"page\" || !tableRootRef.current) {\n      return\n    }\n\n    const measure = () =>\n      setScrollMargin(\n        tableRootRef.current\n          ? tableRootRef.current.getBoundingClientRect().top + window.scrollY\n          : 0\n      )\n\n    measure()\n    window.addEventListener(\"resize\", measure)\n\n    return () => window.removeEventListener(\"resize\", measure)\n  }, [scrollMode])\n\n  React.useLayoutEffect(() => {\n    if (!tableRootRef.current) {\n      return\n    }\n\n    const root = tableRootRef.current\n    const updateWidth = () => setContainerWidth(root.clientWidth)\n    const observer = new ResizeObserver(updateWidth)\n\n    updateWidth()\n    observer.observe(root)\n\n    return () => observer.disconnect()\n  }, [])\n\n  const windowVirtualizer = useWindowVirtualizer({\n    count: rows.length,\n    estimateSize: () => rowHeight,\n    overscan: 12,\n    scrollMargin,\n    enabled: scrollMode === \"page\",\n    initialRect: { width: 1200, height: 800 },\n  })\n\n  const tableVirtualizer = useVirtualizer({\n    count: rows.length,\n    estimateSize: () => rowHeight,\n    overscan: 12,\n    getScrollElement: () => tableScrollRef.current,\n    enabled: scrollMode === \"table\",\n  })\n\n  const virtualizer =\n    scrollMode === \"page\" ? windowVirtualizer : tableVirtualizer\n  const virtualRows = virtualizer.getVirtualItems()\n  const totalSize = virtualizer.getTotalSize()\n  const leafHeaders = table.getFlatHeaders()\n  const visibleLeafHeaders = table.getVisibleLeafColumns()\n  const rawTableWidth = table.getTotalSize()\n  const tableWidth = Math.max(rawTableWidth, containerWidth)\n  const extraWidth = Math.max(0, tableWidth - rawTableWidth)\n  const growByColumn = React.useMemo(() => {\n    return Object.fromEntries(\n      leafHeaders.map((header) => {\n        const meta = header.column.columnDef.meta as\n          | { grow?: number }\n          | undefined\n\n        return [header.column.id, meta?.grow ?? 0]\n      })\n    )\n  }, [leafHeaders])\n  const totalGrow = React.useMemo(\n    () => Object.values(growByColumn).reduce((total, grow) => total + grow, 0),\n    [growByColumn]\n  )\n  const getColumnWidth = React.useCallback(\n    (columnId: string, size: number) => {\n      if (!extraWidth || !totalGrow) {\n        return size\n      }\n\n      return size + extraWidth * ((growByColumn[columnId] ?? 0) / totalGrow)\n    },\n    [extraWidth, growByColumn, totalGrow]\n  )\n  const hasFooter = columns.some((column) => column.footer !== undefined)\n\n  function resizeWithNeighbor(\n    headerIndex: number,\n    event: React.PointerEvent<HTMLButtonElement>\n  ) {\n    const header = leafHeaders[headerIndex]\n    const nextHeader = leafHeaders[headerIndex + 1]\n\n    if (!header?.column.getCanResize() || !nextHeader) {\n      return\n    }\n\n    event.preventDefault()\n    event.currentTarget.setPointerCapture(event.pointerId)\n\n    const startX = event.clientX\n    const startSize = getColumnWidth(header.column.id, header.column.getSize())\n    const nextStartSize = getColumnWidth(\n      nextHeader.column.id,\n      nextHeader.column.getSize()\n    )\n    const minSize = header.column.columnDef.minSize ?? 96\n    const nextMinSize = nextHeader.column.columnDef.minSize ?? 96\n\n    const onPointerMove = (moveEvent: PointerEvent) => {\n      const delta = moveEvent.clientX - startX\n      const clampedDelta = Math.min(\n        Math.max(delta, minSize - startSize),\n        nextStartSize - nextMinSize\n      )\n\n      setColumnSizing((current) => ({\n        ...current,\n        [header.column.id]: startSize + clampedDelta,\n        [nextHeader.column.id]: nextStartSize - clampedDelta,\n      }))\n    }\n\n    const onPointerUp = () => {\n      window.removeEventListener(\"pointermove\", onPointerMove)\n      window.removeEventListener(\"pointerup\", onPointerUp)\n    }\n\n    window.addEventListener(\"pointermove\", onPointerMove)\n    window.addEventListener(\"pointerup\", onPointerUp)\n  }\n\n  return (\n    <div\n      ref={tableRootRef}\n      className={cn(\n        \"relative w-full border\",\n        scrollMode === \"page\" && \"overflow-x-auto overflow-y-clip\",\n        scrollMode === \"table\" && \"overflow-hidden\",\n        className,\n        containerClassName\n      )}\n    >\n      <div\n        ref={tableScrollRef}\n        className={cn(\n          scrollMode === \"table\" && \"overflow-auto\",\n          scrollMode === \"page\" && \"overflow-visible\"\n        )}\n        style={scrollMode === \"table\" ? { height: tableHeight } : undefined}\n      >\n        <table\n          className={cn(\"grid w-full caption-bottom text-sm\", tableClassName)}\n          style={{ minWidth: rawTableWidth, width: tableWidth }}\n        >\n          {caption ? (\n            <TableCaption className={captionClassName}>{caption}</TableCaption>\n          ) : null}\n          <TableHeader className={cn(\"grid bg-muted\", headerClassName)}>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id} className=\"flex\">\n                {headerGroup.headers.map((header, headerIndex) => {\n                  const canSort = header.column.getCanSort()\n                  const sortState = header.column.getIsSorted()\n                  const meta = header.column.columnDef.meta as\n                    | { align?: Align }\n                    | undefined\n                  const align = meta?.align ?? \"left\"\n                  const showSortIcon =\n                    sortState || (showUnsortedSortIcon && canSort)\n                  const renderedHeader = flexRender(\n                    header.column.columnDef.header,\n                    header.getContext()\n                  )\n\n                  return (\n                    <TableHead\n                      key={header.id}\n                      className={cn(\n                        \"group relative flex items-center px-3\",\n                        canSort && \"cursor-pointer\",\n                        alignClass[align]\n                      )}\n                      style={{\n                        width: getColumnWidth(header.column.id, header.getSize()),\n                        minWidth: header.column.columnDef.minSize,\n                      }}\n                    >\n                      {canSort ? (\n                        <button\n                          type=\"button\"\n                          aria-label={`Sort ${\n                            typeof header.column.columnDef.header === \"string\"\n                              ? header.column.columnDef.header\n                              : \"column\"\n                          }`}\n                          className=\"absolute inset-0 z-0 cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\"\n                          onClick={header.column.getToggleSortingHandler()}\n                        />\n                      ) : null}\n                      {headerIndex > 0 ? (\n                        <span\n                          aria-hidden=\"true\"\n                          className=\"pointer-events-none absolute inset-y-2 left-0 z-10 w-px bg-ring opacity-0 transition-opacity group-hover:opacity-100\"\n                        />\n                      ) : null}\n                      {headerIndex < leafHeaders.length - 1 ? (\n                        <span\n                          aria-hidden=\"true\"\n                          className=\"pointer-events-none absolute inset-y-2 right-0 z-10 w-px bg-ring opacity-0 transition-opacity group-hover:opacity-100\"\n                        />\n                      ) : null}\n                      <div className=\"pointer-events-none relative z-10 inline-flex min-w-0 items-center gap-1.5 text-xs font-medium uppercase tracking-wider\">\n                        <span className=\"truncate\">\n                          {renderedHeader}\n                        </span>\n                        {showSortIcon ? (\n                          <ChevronDown\n                            data-icon=\"inline-end\"\n                            className={cn(\n                              \"transition-transform duration-200 ease-out\",\n                              sortState === \"asc\" && \"rotate-180\",\n                              !sortState && \"opacity-60\"\n                            )}\n                          />\n                        ) : null}\n                      </div>\n                      {resizable &&\n                      header.column.getCanResize() &&\n                      headerIndex < leafHeaders.length - 1 ? (\n                        <button\n                          type=\"button\"\n                          aria-label=\"Resize column\"\n                          className=\"absolute inset-y-0 right-0 z-20 w-3 cursor-col-resize outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\"\n                          onPointerDown={(event) =>\n                            resizeWithNeighbor(headerIndex, event)\n                          }\n                        />\n                      ) : null}\n                    </TableHead>\n                  )\n                })}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody\n            className={cn(\"relative grid\", bodyClassName)}\n            style={{ height: totalSize }}\n          >\n            {virtualRows.length ? (\n              virtualRows.map((virtualRow) => {\n                const row = rows[virtualRow.index]\n\n                return (\n                  <TableRow\n                    key={row.id}\n                    data-state={row.getIsSelected() && \"selected\"}\n                    className=\"absolute flex w-full\"\n                    style={{\n                      height: virtualRow.size,\n                      transform: `translateY(${\n                        scrollMode === \"page\"\n                          ? virtualRow.start - scrollMargin\n                          : virtualRow.start\n                      }px)`,\n                    }}\n                  >\n                    {row.getVisibleCells().map((cell) => {\n                      const meta = cell.column.columnDef.meta as\n                        | { align?: Align; className?: string }\n                        | undefined\n                      const align = meta?.align ?? \"left\"\n\n                      return (\n                        <TableCell\n                          key={cell.id}\n                          className={cn(\n                            \"flex items-center px-3\",\n                            alignClass[align],\n                            meta?.className\n                          )}\n                          style={{\n                            width: getColumnWidth(\n                              cell.column.id,\n                              cell.column.getSize()\n                            ),\n                            minWidth: cell.column.columnDef.minSize,\n                          }}\n                        >\n                          <span className=\"truncate\">\n                            {flexRender(\n                              cell.column.columnDef.cell,\n                              cell.getContext()\n                            )}\n                          </span>\n                        </TableCell>\n                      )\n                    })}\n                  </TableRow>\n                )\n              })\n            ) : (\n              <TableRow className=\"flex\">\n                <TableCell className=\"flex h-24 w-full items-center justify-center text-muted-foreground\">\n                  {emptyText}\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n          {hasFooter ? (\n            <TableFooter className={cn(\"grid bg-muted\", footerClassName)}>\n              <TableRow className=\"flex\">\n                {hasSelection ? (\n                  <TableCell\n                    className=\"flex items-center px-3\"\n                    style={{\n                      width: getColumnWidth(\n                        visibleLeafHeaders[0].id,\n                        visibleLeafHeaders[0].getSize()\n                      ),\n                      minWidth: visibleLeafHeaders[0].columnDef.minSize,\n                    }}\n                  />\n                ) : null}\n                {columns.map((column) => {\n                  const tableColumn = table.getColumn(String(column.key))\n\n                  if (!tableColumn) {\n                    return null\n                  }\n\n                  const align = column.align ?? \"left\"\n                  const footer =\n                    typeof column.footer === \"function\"\n                      ? column.footer({ data, rows: renderedRows })\n                      : column.footer\n\n                  return (\n                    <TableCell\n                      key={String(column.key)}\n                      className={cn(\n                        \"flex items-center px-3\",\n                        alignClass[align],\n                        column.footerClassName\n                      )}\n                      style={{\n                        width: getColumnWidth(\n                          tableColumn.id,\n                          tableColumn.getSize()\n                        ),\n                        minWidth: tableColumn.columnDef.minSize,\n                      }}\n                    >\n                      <span className=\"truncate\">{footer}</span>\n                    </TableCell>\n                  )\n                })}\n              </TableRow>\n            </TableFooter>\n          ) : null}\n        </table>\n      </div>\n    </div>\n  )\n}\n\nexport function Table<TData>(\n  props: EmsteinUITableProps<TData> | TablePrimitiveProps\n) {\n  if (\"data\" in props && \"columns\" in props) {\n    return <DataTable {...props} />\n  }\n\n  return <PrimitiveTable {...props} />\n}\n","type":"registry:component"}],"type":"registry:component"}