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"));
|
el => el.classList.remove("input-editing"));
|
||||||
}
|
}
|
||||||
if (e.target && e.target.hasAttribute && e.target.hasAttribute("data-focusable")) {
|
if (e.target && e.target.hasAttribute && e.target.hasAttribute("data-focusable")) {
|
||||||
if (!e.target.closest("#tv-nav")) {
|
if (!e.target.closest("#tv-nav") && !e.target.closest(".tv-alpha-sidebar")) {
|
||||||
// Nur echte Content-Elemente merken (nicht Filter/Controls)
|
// 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-alpha-sidebar, .tv-view-switch, .tv-filter-bar, .tv-season-actions, .profiles-grid")) {
|
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;
|
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)
|
// Naechstes Element in Richtung finden (Nearest-Neighbor)
|
||||||
const currentRect = active.getBoundingClientRect();
|
const currentRect = active.getBoundingClientRect();
|
||||||
const cx = currentRect.left + currentRect.width / 2;
|
const cx = currentRect.left + currentRect.width / 2;
|
||||||
|
|
@ -240,7 +304,7 @@ class FocusManager {
|
||||||
// Nur Elemente im gleichen Bereich (Nav oder Content) bevorzugen
|
// Nur Elemente im gleichen Bereich (Nav oder Content) bevorzugen
|
||||||
const searchEls = inNav
|
const searchEls = inNav
|
||||||
? focusables.filter(el => el.closest("#tv-nav"))
|
? 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) {
|
for (const el of searchEls) {
|
||||||
if (el === active) continue;
|
if (el === active) continue;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue