mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-24 06:33:40 +00:00
122 lines
4.3 KiB
TypeScript
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;
|