| import { useEffect, useCallback } from 'react'; |
|
|
| |
| |
| |
| |
| export const useArrowNavigation = ( |
| selectedFont, |
| fonts, |
| filter, |
| searchTerm, |
| onFontSelect |
| ) => { |
| |
| const getFilteredFonts = useCallback(() => { |
| if (!fonts || fonts.length === 0) return []; |
| |
| return fonts.filter(font => { |
| |
| const familyMatch = filter === 'all' || font.family === filter; |
| |
| |
| const searchMatch = !searchTerm || |
| font.name.toLowerCase().includes(searchTerm.toLowerCase()) || |
| font.family.toLowerCase().includes(searchTerm.toLowerCase()); |
| |
| return familyMatch && searchMatch; |
| }); |
| }, [fonts, filter, searchTerm]); |
|
|
| |
|
|
| |
| const findNearestFontInDirection = useCallback((direction) => { |
| if (!selectedFont || !onFontSelect) return; |
| |
| const filteredFonts = getFilteredFonts(); |
| if (filteredFonts.length <= 1) return; |
| |
| |
| const currentFont = filteredFonts.find(font => font.name === selectedFont.name); |
| if (!currentFont) return; |
| |
| let bestFont = null; |
| let bestDistance = Infinity; |
| |
| |
| filteredFonts.forEach(font => { |
| if (font.name === selectedFont.name) return; |
| |
| const dx = font.x - currentFont.x; |
| const dy = font.y - currentFont.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| |
| if (distance === 0) return; |
| |
| let isInDirection = false; |
| |
| |
| switch (direction) { |
| case 'ArrowUp': |
| |
| isInDirection = dy > 0 && Math.abs(dx) <= Math.abs(dy) * 2; |
| break; |
| case 'ArrowDown': |
| |
| isInDirection = dy < 0 && Math.abs(dx) <= Math.abs(dy) * 2; |
| break; |
| case 'ArrowLeft': |
| |
| isInDirection = dx < 0 && Math.abs(dy) <= Math.abs(dx) * 2; |
| break; |
| case 'ArrowRight': |
| |
| isInDirection = dx > 0 && Math.abs(dy) <= Math.abs(dx) * 2; |
| break; |
| default: |
| return; |
| } |
| |
| |
| if (isInDirection && distance < bestDistance) { |
| bestDistance = distance; |
| bestFont = font; |
| } |
| }); |
| |
| |
| if (bestFont) { |
| onFontSelect(bestFont); |
| } |
| }, [selectedFont, getFilteredFonts, onFontSelect]); |
|
|
| |
| const handleKeyDown = useCallback((event) => { |
| |
| if (!selectedFont) return; |
| |
| |
| const activeElement = document.activeElement; |
| if (activeElement && ( |
| activeElement.tagName === 'INPUT' || |
| activeElement.tagName === 'TEXTAREA' || |
| activeElement.contentEditable === 'true' |
| )) { |
| return; |
| } |
| |
| |
| if (event.key === 'Escape') { |
| event.preventDefault(); |
| onFontSelect(null); |
| return; |
| } |
|
|
| |
| if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) { |
| event.preventDefault(); |
| findNearestFontInDirection(event.key); |
| } |
| }, [selectedFont, findNearestFontInDirection, onFontSelect]); |
|
|
| |
| useEffect(() => { |
| window.addEventListener('keydown', handleKeyDown); |
| |
| return () => { |
| window.removeEventListener('keydown', handleKeyDown); |
| }; |
| }, [handleKeyDown]); |
|
|
| |
| return { |
| canNavigate: selectedFont && getFilteredFonts().length > 1, |
| filteredFontsCount: getFilteredFonts().length, |
| selectedFontName: selectedFont?.name |
| }; |
| }; |
|
|