tv模式左右焦点锁定,默认聚焦首页导航
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { ReactNode } from 'react';
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
|
||||
import TVVirtualRemote from './TVVirtualRemote';
|
||||
|
||||
@@ -38,6 +38,19 @@ export default function TVLayout({
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
if (!showNav || pathname !== '/tv') return;
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
const active = document.activeElement;
|
||||
if (active instanceof HTMLElement && active !== document.body) return;
|
||||
|
||||
document
|
||||
.querySelector<HTMLElement>('[data-tv-home-nav="true"]')
|
||||
?.focus({ preventScroll: true });
|
||||
});
|
||||
}, [pathname, showNav]);
|
||||
|
||||
return (
|
||||
<main className='min-h-screen overflow-x-hidden bg-black text-slate-50'>
|
||||
<div className='fixed inset-0 pointer-events-none bg-[radial-gradient(circle_at_20%_0%,rgba(225,29,72,0.22),transparent_34%),radial-gradient(circle_at_90%_10%,rgba(79,70,229,0.24),transparent_30%),linear-gradient(180deg,#05050b_0%,#000_55%)]' />
|
||||
@@ -54,6 +67,7 @@ export default function TVLayout({
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
data-tv-home-nav={item.href === '/tv' ? 'true' : undefined}
|
||||
className={`group flex shrink-0 cursor-pointer items-center gap-2 rounded-2xl px-5 py-3 text-xl font-semibold outline-none transition duration-200 tv-focusable ${
|
||||
active
|
||||
? 'bg-rose-600 text-white shadow-lg shadow-rose-950/40'
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function TVRow({ section }: { section: TVSection }) {
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex gap-5 overflow-x-auto px-5 py-6 [scrollbar-width:none]'>
|
||||
<div data-tv-focus-row='horizontal' className='flex gap-5 overflow-x-auto px-5 py-6 [scrollbar-width:none]'>
|
||||
{section.items.map((item) => <TVCard key={`${section.title}-${item.id}`} item={item} />)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -159,6 +159,41 @@ function focusNearestTopNavigationElement(active: HTMLElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function moveTopNavigationFocus(active: HTMLElement, direction: 'left' | 'right') {
|
||||
const topNavigationElements = getTopNavigationElements();
|
||||
const currentIndex = topNavigationElements.indexOf(active);
|
||||
if (currentIndex === -1) return false;
|
||||
|
||||
const nextIndex = direction === 'right'
|
||||
? Math.min(currentIndex + 1, topNavigationElements.length - 1)
|
||||
: Math.max(currentIndex - 1, 0);
|
||||
|
||||
if (nextIndex === currentIndex) return true;
|
||||
|
||||
focusElement(topNavigationElements[nextIndex]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function moveHorizontalRowFocus(active: HTMLElement, direction: 'left' | 'right') {
|
||||
const row = active.closest<HTMLElement>('[data-tv-focus-row="horizontal"]');
|
||||
if (!row) return false;
|
||||
|
||||
const rowElements = Array.from(row.querySelectorAll<HTMLElement>(focusableSelector))
|
||||
.filter((element) => !element.closest('[data-tv-no-focus="true"]'))
|
||||
.filter(isVisible);
|
||||
const currentIndex = rowElements.indexOf(active);
|
||||
if (currentIndex === -1) return false;
|
||||
|
||||
const nextIndex = direction === 'right'
|
||||
? Math.min(currentIndex + 1, rowElements.length - 1)
|
||||
: Math.max(currentIndex - 1, 0);
|
||||
|
||||
if (nextIndex === currentIndex) return true;
|
||||
|
||||
focusElement(rowElements[nextIndex]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function focusElement(element: HTMLElement) {
|
||||
element.focus({ preventScroll: true });
|
||||
|
||||
@@ -202,6 +237,14 @@ function moveSpatialFocus(direction: 'up' | 'down' | 'left' | 'right', lastFocus
|
||||
return;
|
||||
}
|
||||
|
||||
if ((direction === 'left' || direction === 'right') && isTopNavigationElement(active)) {
|
||||
if (moveTopNavigationFocus(active, direction)) return;
|
||||
}
|
||||
|
||||
if (direction === 'left' || direction === 'right') {
|
||||
if (moveHorizontalRowFocus(active, direction)) return;
|
||||
}
|
||||
|
||||
if (direction === 'down' && isTopNavigationElement(active)) {
|
||||
const firstContentElement = getContentFocusableElements()[0];
|
||||
if (firstContentElement) {
|
||||
|
||||
Reference in New Issue
Block a user