summaryrefslogtreecommitdiff
path: root/web/js/selection.js
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-01 15:57:13 +0300
committerPaul Buetow <paul@buetow.org>2026-05-01 15:57:13 +0300
commit65ee2e4d7f2be8036457e3cdca0e26e09eb56e0b (patch)
treecbd8143db9e81baed51239a628dfedae2dbf375f /web/js/selection.js
parent56666ca87f11e72521dcda00de72438e13899b95 (diff)
add readme and so on
Diffstat (limited to 'web/js/selection.js')
-rw-r--r--web/js/selection.js70
1 files changed, 70 insertions, 0 deletions
diff --git a/web/js/selection.js b/web/js/selection.js
index 00b91b8..c83fd81 100644
--- a/web/js/selection.js
+++ b/web/js/selection.js
@@ -14,6 +14,21 @@ function refreshCards() {
return Array.from(document.querySelectorAll('.media-card, .media-row'));
}
+// Compute grid geometry from the rendered cards.
+function gridGeometry(cards) {
+ if (!cards.length) return { cols: 0, rowMap: [] };
+ const first = cards[0].getBoundingClientRect();
+ let colCount = 0;
+ for (let i = 0; i < cards.length; i++) {
+ const r = cards[i].getBoundingClientRect();
+ if (Math.round(r.top) !== Math.round(first.top)) break;
+ colCount++;
+ }
+ // rowMap[i] = row index of cards[i]
+ const rowMap = cards.map((_, i) => Math.floor(i / colCount));
+ return { cols: colCount || 1, rowMap };
+}
+
export function select(index) {
const cards = refreshCards();
if (!cards.length) { selectedIndex = -1; return; }
@@ -39,5 +54,60 @@ export function next(step = 1) {
}
export function prev(step = 1) { next(-step); }
+
+// Grid-aware keyboard navigation
+export function navLeft() {
+ const cards = refreshCards();
+ if (!cards.length) return;
+ const { cols } = gridGeometry(cards);
+ if (cols === 0) return;
+ const row = Math.floor(selectedIndex / cols);
+ const col = selectedIndex % cols;
+ if (col > 0) {
+ select(selectedIndex - 1);
+ } else if (row > 0) {
+ // wrap to end of previous row
+ select(row * cols - 1);
+ }
+}
+
+export function navRight() {
+ const cards = refreshCards();
+ if (!cards.length) return;
+ const { cols } = gridGeometry(cards);
+ if (cols === 0) return;
+ const row = Math.floor(selectedIndex / cols);
+ const col = selectedIndex % cols;
+ const lastInRow = Math.min((row + 1) * cols, cards.length) - 1;
+ if (col < (lastInRow - row * cols)) {
+ select(selectedIndex + 1);
+ } else if (selectedIndex < cards.length - 1) {
+ // wrap to start of next row
+ select(Math.min((row + 1) * cols, cards.length - 1));
+ }
+}
+
+export function navUp() {
+ const cards = refreshCards();
+ if (!cards.length) return;
+ const { cols } = gridGeometry(cards);
+ if (cols === 0) return;
+ const idxAbove = selectedIndex - cols;
+ if (idxAbove >= 0) {
+ select(idxAbove);
+ }
+}
+
+export function navDown() {
+ const cards = refreshCards();
+ if (!cards.length) return;
+ const { cols } = gridGeometry(cards);
+ if (cols === 0) return;
+ const idxBelow = selectedIndex + cols;
+ if (idxBelow < cards.length) {
+ select(idxBelow);
+ }
+}
+
export function currentIndex() { return selectedIndex; }
export function currentElement() { return refreshCards()[selectedIndex] ?? null; }