stremio-web/src/services/GamepadNavigation/useContentGamepadNavigation.tsx
2025-04-01 16:29:45 +03:00

122 lines
4.3 KiB
TypeScript

// Copyright (C) 2017-2025 Smart code 203358507
import { useEffect } from 'react';
import { useGamepad } from '../GamepadContext';
const useContentGamepadNavigation = (
sectionRef: React.RefObject<HTMLDivElement>,
gamepadHandlerId: string
) => {
const gamepad = useGamepad();
useEffect(() => {
const handleGamepadNavigation = (
direction: 'left' | 'right' | 'up' | 'down'
) => {
const elements = Array.from(
sectionRef.current?.querySelectorAll<HTMLDivElement>('[tabindex="0"]') || []
);
if (elements.length === 0) return;
const activeElement = sectionRef.current?.querySelector<HTMLDivElement>(':focus');
if (!activeElement) {
elements[0].focus();
return;
}
let closestElement: HTMLDivElement | null = null;
const currentRect = activeElement.getBoundingClientRect();
let closestDistance = Infinity;
elements.forEach((el) => {
if (el === activeElement) return;
const rect = el.getBoundingClientRect();
let distance = Infinity;
switch (direction) {
case 'left':
if (
rect.right <= currentRect.left &&
(rect.top === currentRect.top ||
(rect.top < currentRect.top && rect.bottom > currentRect.top)
)
) {
distance = currentRect.left - rect.right;
}
break;
case 'right':
if (
currentRect.right <= rect.left &&
(rect.top === currentRect.top ||
(rect.top < currentRect.top && rect.bottom > currentRect.top)
)
) {
distance = rect.left - currentRect.right;
}
break;
case 'up':
if (
rect.bottom <= currentRect.top &&
(rect.left === currentRect.left ||
(rect.left < currentRect.left && rect.right > currentRect.left)
)
) {
distance = currentRect.top - rect.bottom;
}
break;
case 'down':
if (
rect.top >= currentRect.bottom &&
(rect.left === currentRect.left ||
(rect.left < currentRect.left && rect.right > currentRect.left)
)
) {
distance = rect.top - currentRect.bottom;
}
break;
}
if (distance < closestDistance) {
closestDistance = distance;
closestElement = el;
}
});
if (closestElement) {
closestElement.focus();
}
};
const onSelect = () => {
const elements = Array.from(
sectionRef.current?.querySelectorAll<HTMLDivElement>('[tabindex="0"]') || []
);
if (elements.length === 0) return;
const activeElement = sectionRef.current?.querySelector<HTMLDivElement>(':focus');
if (!activeElement) {
elements[0].focus();
return;
}
const isActiveSelectElement = [activeElement.classList].some((className) => /^select-input/.test(className.toString()));
if (!isActiveSelectElement) {
activeElement?.click();
}
};
gamepad?.on('analog', gamepadHandlerId, handleGamepadNavigation);
gamepad?.on('buttonA', gamepadHandlerId, onSelect);
return () => {
gamepad?.off('analog', gamepadHandlerId);
gamepad?.off('buttonA', gamepadHandlerId);
};
}, [gamepad, gamepadHandlerId, sectionRef]);
};
export default useContentGamepadNavigation;