// Copyright (c) Meta Platforms, Inc. and affiliates.
'use client';
/**
* @file useTableRowIndex.tsx
* @input React, StyleX, Table types + the rendered data array
* @output Exports useTableRowIndex hook + config type
* @position Row-index plugin; consumed by Table via plugins prop
*
* SYNC: When modified, update these files to stay in sync:
* - /packages/core/src/Table/index.ts (exports)
*/
import { useMemo, type ReactNode } from 'react';
import * as stylex from "../../../style-runtime.js";
import "../../../theme/tokens.stylex.js";
import { colorVars, typeScaleVars } from "../../../theme/tokens.stylex.js";
import type { TableColumn, TablePlugin } from "../../types.js";
/**
* Configuration for {@link useTableRowIndex}.
*
* Astryx's `renderCell` receives only the row item (not its position), so the
* plugin needs the rendered `data` array — the same array passed to
* `
` — to derive each row's 1-based ordinal. Pass `getRowKey` when
* items don't have a stable identity by reference (e.g. new objects each
* render); it must return a **unique** string per row (duplicate keys collapse
* to a single ordinal).
*/
export interface UseTableRowIndexConfig> {
/** The data array currently rendered by the table (post sort/filter/page). */
data: T[];
/**
* Optional key extractor returning a **unique** string per row. When
* provided, index lookup is keyed by the returned string (stable across new
* object identities); otherwise items are matched by reference identity.
* For a stable plugin identity across renders, memoize this with
* `useCallback`.
*/
getRowKey?: (item: T) => string;
/** Header label for the index column. @default '#' */
label?: ReactNode;
/** First index value. @default 1 */
startFrom?: number;
}
const INDEX_COLUMN_WIDTH = {
type: 'pixel' as const,
value: 48
};
/**
* Returns a {@link TablePlugin} that prepends a right-aligned, monospaced
* row-number column. Numbering follows the order of the rendered `data` array,
* so it reflects the current sort / filter / pagination view.
*
* @example
* ```
* const rowIndex = useTableRowIndex({data});
* ;
* ```
*/
export function useTableRowIndex>(config: UseTableRowIndexConfig): TablePlugin {
const {
data,
getRowKey,
label = '#',
startFrom = 1
} = config;
// Single item → ordinal lookup, built in one pass and rebuilt only when the
// data array or key extractor changes. Keyed by getRowKey when provided
// (stable across new object identities), otherwise by object reference.
const lookup = useMemo(() => {
const map = new Map();
for (let i = 0; i < data.length; i++) {
const item = data[i];
map.set(getRowKey ? getRowKey(item) : item, i);
}
return (item: T): number | undefined => map.get(getRowKey ? getRowKey(item) : item);
}, [data, getRowKey]);
// The plugin object depends only on the stable lookup and the presentational
// props — not on data directly — so it stays as stable as its inputs allow.
return useMemo((): TablePlugin => ({
transformColumns(columns) {
const indexColumn: TableColumn = {
key: '__rowIndex',
header: label,
width: INDEX_COLUMN_WIDTH,
align: 'end',
resizable: false,
renderCell: (item: T) => {
const idx = lookup(item);
if (idx == null) {
return null;
}
return {idx + startFrom};
}
};
return [indexColumn, ...columns];
}
}), [label, startFrom, lookup]);
}