diff --git a/src/uix/soma/components/table/table-core.svelte.test.ts b/src/uix/soma/components/table/table-core.svelte.test.ts index ad6659f08..98828642a 100644 --- a/src/uix/soma/components/table/table-core.svelte.test.ts +++ b/src/uix/soma/components/table/table-core.svelte.test.ts @@ -148,6 +148,32 @@ describe('createTable', () => { } }); + it('normalizes invalid pagination state', () => { + const { table, cleanup } = withTable({ + data: () => people, + columns, + pagination: { enabled: true }, + initialPagination: { pageIndex: -4, pageSize: 0 } + }); + + try { + expect(table.pagination).toEqual({ pageIndex: 0, pageSize: 1 }); + expect(table.pageCount).toBe(4); + expect(table.rows.map((row) => row.original.name)).toEqual(['Ada']); + + table.setPageSize(2.8); + flushSync(); + expect(table.pagination).toEqual({ pageIndex: 0, pageSize: 2 }); + expect(table.pageCount).toBe(2); + + table.setPageIndex(Number.POSITIVE_INFINITY); + flushSync(); + expect(table.pagination.pageIndex).toBe(0); + } finally { + cleanup(); + } + }); + it('passes the source row index to accessor functions after sorting and pagination', () => { const indexedColumns: ColumnDef[] = [ { @@ -190,6 +216,11 @@ describe('createTable', () => { expect(table.headers.map((header) => header.id)).toEqual(['name', 'status', 'score', 'age']); expect(table.getPinnedOffset('name')).toBe(0); + table.pinColumn('status', 'left'); + flushSync(); + expect(table.headers.map((header) => header.id)).toEqual(['name', 'status', 'score', 'age']); + expect(table.getIsColumnPinned('status')).toBe(false); + table.pinColumn('score', 'left'); flushSync(); expect(table.headers.map((header) => header.id)).toEqual(['name', 'score', 'status', 'age']); diff --git a/src/uix/soma/components/table/table-core.svelte.ts b/src/uix/soma/components/table/table-core.svelte.ts index f9e37005b..bb556f34d 100644 --- a/src/uix/soma/components/table/table-core.svelte.ts +++ b/src/uix/soma/components/table/table-core.svelte.ts @@ -220,13 +220,24 @@ function applyGlobalFilter( // ── Pagination logic ───────────────────────────────────────────────────────── +function normalizePageSize(size: number): number { + if (!Number.isFinite(size)) return 1; + return Math.max(1, Math.floor(size)); +} + +function normalizePageIndex(index: number): number { + if (!Number.isFinite(index)) return 0; + return Math.max(0, Math.floor(index)); +} + function applyPagination( rows: IndexedRow[], pagination: PaginationState | undefined ): IndexedRow[] { if (!pagination) return rows; - const start = pagination.pageIndex * pagination.pageSize; - return rows.slice(start, start + pagination.pageSize); + const pageSize = normalizePageSize(pagination.pageSize); + const start = normalizePageIndex(pagination.pageIndex) * pageSize; + return rows.slice(start, start + pageSize); } // ── Table row/cell models ──────────────────────────────────────────────────── @@ -429,12 +440,12 @@ export function createTable(config: TableConfig): TableInstance(config.initialGlobalFilter ?? ''); let columnVisibility = $state(config.initialColumnVisibility ?? {}); let selection = $state(config.initialSelection ?? {}); - let pagination = $state( - config.initialPagination ?? { - pageIndex: 0, - pageSize: config.pagination?.pageSize ?? 10 - } - ); + let pagination = $state({ + pageIndex: normalizePageIndex(config.initialPagination?.pageIndex ?? 0), + pageSize: normalizePageSize( + config.initialPagination?.pageSize ?? config.pagination?.pageSize ?? 10 + ) + }); let columnPinning = $state(config.initialColumnPinning ?? {}); let columnSizing = $state(config.initialColumnSizing ?? {}); let rowDetailOpen = $state(config.initialRowDetailOpen ?? {}); @@ -460,7 +471,7 @@ export function createTable(config: TableConfig): TableInstance { if (!config.pagination?.enabled) return 1; - return Math.max(1, Math.ceil(totalRowCount / pagination.pageSize)); + return Math.max(1, Math.ceil(totalRowCount / normalizePageSize(pagination.pageSize))); }); const paginatedRows = $derived.by(() => { @@ -712,13 +723,13 @@ export function createTable(config: TableConfig): TableInstance pagination.pageIndex < pageCount - 1); function setPageIndex(index: number) { - const clamped = Math.max(0, Math.min(index, pageCount - 1)); + const clamped = Math.max(0, Math.min(normalizePageIndex(index), pageCount - 1)); pagination = { ...pagination, pageIndex: clamped }; config.onPaginationChange?.(pagination); } function setPageSize(size: number) { - pagination = { pageIndex: 0, pageSize: size }; + pagination = { pageIndex: 0, pageSize: normalizePageSize(size) }; config.onPaginationChange?.(pagination); } @@ -739,7 +750,7 @@ export function createTable(config: TableConfig): TableInstance c.id === columnId); - if (!col || col.def.enablePinning === false) return; + if (!col || col.def.enablePinning !== true) return; const left = (columnPinning.left ?? []).filter((id) => id !== columnId); const right = (columnPinning.right ?? []).filter((id) => id !== columnId); if (position === 'left') {