|
|
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 calculateDistance = useCallback((font1, font2) => { |
|
|
if (!font1 || !font2 || !font1.x || !font1.y || !font2.x || !font2.y) { |
|
|
return Infinity; |
|
|
} |
|
|
|
|
|
const dx = font1.x - font2.x; |
|
|
const dy = font1.y - font2.y; |
|
|
return Math.sqrt(dx * dx + dy * dy); |
|
|
}, []); |
|
|
|
|
|
|
|
|
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 (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) { |
|
|
event.preventDefault(); |
|
|
findNearestFontInDirection(event.key); |
|
|
} |
|
|
}, [selectedFont, findNearestFontInDirection]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
window.addEventListener('keydown', handleKeyDown); |
|
|
|
|
|
return () => { |
|
|
window.removeEventListener('keydown', handleKeyDown); |
|
|
}; |
|
|
}, [handleKeyDown]); |
|
|
|
|
|
|
|
|
return { |
|
|
canNavigate: selectedFont && getFilteredFonts().length > 1, |
|
|
filteredFontsCount: getFilteredFonts().length, |
|
|
selectedFontName: selectedFont?.name |
|
|
}; |
|
|
}; |
|
|
|