fix(tv): Alphabet-Sidebar per D-Pad/Fernbedienung navigierbar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bce5460fcf
commit
78dd9ebe03
1 changed files with 68 additions and 4 deletions
|
|
@ -36,9 +36,9 @@ class FocusManager {
|
|||
el => el.classList.remove("input-editing"));
|
||||
}
|
||||
if (e.target && e.target.hasAttribute && e.target.hasAttribute("data-focusable")) {
|
||||
if (!e.target.closest("#tv-nav")) {
|
||||
// Nur echte Content-Elemente merken (nicht Filter/Controls)
|
||||
if (e.target.closest(".tv-grid, .tv-list-compact, .tv-detail-list, .tv-folder-view, .tv-row, .tv-episode-list, .tv-episode-grid, .tv-tabs, .tv-detail-actions, .tv-alpha-sidebar, .tv-view-switch, .tv-filter-bar, .tv-season-actions, .profiles-grid")) {
|
||||
if (!e.target.closest("#tv-nav") && !e.target.closest(".tv-alpha-sidebar")) {
|
||||
// Nur echte Content-Elemente merken (nicht Nav/Sidebar)
|
||||
if (e.target.closest(".tv-grid, .tv-list-compact, .tv-detail-list, .tv-folder-view, .tv-row, .tv-episode-list, .tv-episode-grid, .tv-tabs, .tv-detail-actions, .tv-view-switch, .tv-filter-bar, .tv-season-actions, .profiles-grid")) {
|
||||
this._lastContentFocus = e.target;
|
||||
}
|
||||
}
|
||||
|
|
@ -229,6 +229,70 @@ class FocusManager {
|
|||
}
|
||||
}
|
||||
|
||||
// ===== Alphabet-Sidebar Navigation =====
|
||||
const inSidebar = active.closest(".tv-alpha-sidebar");
|
||||
|
||||
if (inSidebar) {
|
||||
if (direction === "ArrowLeft") {
|
||||
// Zurueck zum Content
|
||||
if (this._lastContentFocus && document.contains(this._lastContentFocus)) {
|
||||
this._lastContentFocus.focus();
|
||||
} else {
|
||||
const firstCard = document.querySelector(".tv-grid [data-focusable], .tv-card[data-focusable]");
|
||||
if (firstCard) firstCard.focus();
|
||||
}
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (direction === "ArrowUp" || direction === "ArrowDown") {
|
||||
// Sequentiell durch Buchstaben
|
||||
const letters = Array.from(document.querySelectorAll(".tv-alpha-letter[data-focusable]"))
|
||||
.filter(el => el.offsetHeight > 0);
|
||||
const idx = letters.indexOf(active);
|
||||
const next = direction === "ArrowDown" ? idx + 1 : idx - 1;
|
||||
if (next >= 0 && next < letters.length) {
|
||||
letters[next].focus();
|
||||
letters[next].scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
}
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ArrowRight am rechten Grid-Rand -> Sidebar
|
||||
if (!inNav && !inSidebar && direction === "ArrowRight") {
|
||||
const sidebar = document.getElementById("alpha-sidebar");
|
||||
if (sidebar && sidebar.offsetHeight > 0) {
|
||||
// Pruefen ob es noch ein Element rechts im Content gibt
|
||||
const contentEls = focusables.filter(el => !el.closest("#tv-nav") && !el.closest(".tv-alpha-sidebar"));
|
||||
const currentRect_r = active.getBoundingClientRect();
|
||||
const rightNeighbor = contentEls.some(el => {
|
||||
const r = el.getBoundingClientRect();
|
||||
return r.left > currentRect_r.right + 5 && Math.abs(r.top - currentRect_r.top) < 100;
|
||||
});
|
||||
if (!rightNeighbor) {
|
||||
// Kein Content rechts -> zur Sidebar springen
|
||||
const sidebarLetters = Array.from(sidebar.querySelectorAll("[data-focusable]"))
|
||||
.filter(el => el.offsetHeight > 0);
|
||||
if (sidebarLetters.length > 0) {
|
||||
// Naechstgelegenen Buchstaben vertikal finden
|
||||
const cy_r = currentRect_r.top + currentRect_r.height / 2;
|
||||
let best = sidebarLetters[0];
|
||||
let bestDist_r = Infinity;
|
||||
sidebarLetters.forEach(l => {
|
||||
const lr = l.getBoundingClientRect();
|
||||
const d = Math.abs(lr.top + lr.height / 2 - cy_r);
|
||||
if (d < bestDist_r) { bestDist_r = d; best = l; }
|
||||
});
|
||||
this._lastContentFocus = active;
|
||||
best.focus();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Naechstes Element in Richtung finden (Nearest-Neighbor)
|
||||
const currentRect = active.getBoundingClientRect();
|
||||
const cx = currentRect.left + currentRect.width / 2;
|
||||
|
|
@ -240,7 +304,7 @@ class FocusManager {
|
|||
// Nur Elemente im gleichen Bereich (Nav oder Content) bevorzugen
|
||||
const searchEls = inNav
|
||||
? focusables.filter(el => el.closest("#tv-nav"))
|
||||
: focusables.filter(el => !el.closest("#tv-nav"));
|
||||
: focusables.filter(el => !el.closest("#tv-nav") && !el.closest(".tv-alpha-sidebar"));
|
||||
|
||||
for (const el of searchEls) {
|
||||
if (el === active) continue;
|
||||
|
|
|
|||
Loading…
Reference in a new issue