import { useCallback, useMemo } from 'react' import { getWidgetStore, useWidget, useWidgetId, useWidgetShallow, } from '../stores' import { TableUI, type TableUIProps } from './table-ui' import { DEFAULT_TABLE_PAGE_SIZE, type TableColumn, type TableRow, type TableSortState, type TableWidgetState, } from './types' import { deriveVisibleRows, resolveColumns } from './helpers' export interface TableProps extends Pick< TableUIProps, | 'pageSizeOptions' | 'selectable' | 'labels' | 'emptyContent' | 'size' | 'keyColumn' > { /** * Column definitions. Order can be overridden by ChangeColumn via the * `columnOrder` field on the extended widget state. */ columns: readonly TableColumn[] /** * When set, render only the first `visibleColumns` entries **after** * applying the user's `columnOrder` from the store. Useful for showing * a compact projection while letting consumers (e.g. `Widget.ChangeColumn`) * see the full column list. When omitted, every column renders. */ visibleColumns?: number /** Initial page size (only on first mount; afterwards lives on the store). */ initialPageSize?: number /** Selected row ids (destination-owned). */ selection?: readonly (string | number)[] onSelectionChange?: (next: readonly (string | number)[]) => void onRowClick?: (row: T) => void onRowHover?: (row: T | null) => void /** * When `true`, the bridge stops sorting and paginating locally and * renders `data` as-is — the consumer is then responsible for * refetching the slice that matches the active `page` / `pageSize` / * `sort` from the outbound callbacks below. `total` is required in * this mode (the server already knows the full row count; the * widget can't infer it from a partial page). */ remote?: boolean /** * Server-reported total row count. Required when `remote === true`; * ignored in local mode (where the bridge derives total from the * sorted result of `deriveVisibleRows`). */ total?: number /** Fires after the store-level sort write so the consumer can refetch. */ onSortChange?: (next: TableSortState) => void /** Fires after the store-level page write so the consumer can refetch. */ onPageChange?: (page: number) => void /** Fires after the store-level pageSize write so the consumer can refetch. */ onPageSizeChange?: (pageSize: number) => void } /** * Bridge component — reads the widget store's `data`, applies sort + pagination * locally, and feeds ``. Sort, page, and pageSize live on the * extended widget state ({@link TableWidgetState}). */ export function Table({ columns, visibleColumns, initialPageSize = DEFAULT_TABLE_PAGE_SIZE, selection, onSelectionChange, onRowClick, onRowHover, remote = false, total: remoteTotal, onSortChange, onPageChange, onPageSizeChange, ...uiProps }: TableProps) { const id = useWidgetId() const data = useWidget(id, (s) => s.data as readonly T[] | undefined) const ui = useWidgetShallow< { sort: TableSortState | undefined page: number pageSize: number columnOrder: readonly string[] | undefined }, TableWidgetState >(id, (s) => ({ sort: s.sort, page: s.page ?? 0, pageSize: s.pageSize ?? initialPageSize, columnOrder: s.columnOrder, })) const orderedColumns = useMemo(() => { const reordered = resolveColumns(columns, ui.columnOrder) return visibleColumns == null ? reordered : reordered.slice(0, visibleColumns) }, [columns, ui.columnOrder, visibleColumns]) // In remote mode the consumer already paginated / sorted server-side, // so pass `data` and `total` straight through. Local mode keeps the // bridge-driven sort + slice via `deriveVisibleRows`. const { rows, totalRows } = useMemo(() => { if (remote) { return { rows: data ?? [], totalRows: remoteTotal ?? 0, } } const { sorted, visible } = deriveVisibleRows(data ?? [], { sort: ui.sort, page: ui.page, pageSize: ui.pageSize, }) return { rows: visible, totalRows: sorted.length } }, [remote, data, remoteTotal, ui.sort, ui.page, ui.pageSize]) const handleSortChange = useCallback( (next: TableSortState) => { getWidgetStore(id).setState({ sort: next, page: 0, } as Partial) onSortChange?.(next) }, [id, onSortChange], ) const handlePageChange = useCallback( (page: number) => { getWidgetStore(id).setState({ page } as Partial) onPageChange?.(page) }, [id, onPageChange], ) const handlePageSizeChange = useCallback( (pageSize: number) => { getWidgetStore(id).setState({ pageSize, page: 0, } as Partial) onPageSizeChange?.(pageSize) }, [id, onPageSizeChange], ) return ( ) }