cv/src/app/FocusedElement.tsx

70 lines
2.5 KiB
TypeScript
Raw Normal View History

2023-05-25 10:13:56 +00:00
import { useState, useRef, useEffect, useCallback } from "react";
2023-05-25 04:42:30 +00:00
const focusUrlParamName = 'focus'
const focusChangedEventName = "focus-changed"
const focusedElementClassName = 'focused-element'
2023-05-25 09:44:35 +00:00
function triggerElementFocused(elementKey?: string) {
2023-05-25 04:42:30 +00:00
const url = new URL(window.location.href);
2023-05-25 09:44:35 +00:00
if (elementKey) {
url.searchParams.set(focusUrlParamName, elementKey)
} else {
url.searchParams.delete(focusUrlParamName)
}
const focusChangeEvent = new Event(focusChangedEventName)
2023-05-25 04:42:30 +00:00
history.pushState({}, "", url);
window.dispatchEvent(focusChangeEvent)
}
2023-05-25 10:13:56 +00:00
export function useFocusedElement<ElementType extends HTMLElement>(elementKey: string) {
2023-05-25 09:44:35 +00:00
const [isFocusedElement, setFocusedElement] = useState(false)
2023-05-25 04:42:30 +00:00
const focusedClass = isFocusedElement ? focusedElementClassName : ''
2023-05-25 10:13:56 +00:00
const elementRef = useRef<ElementType>(null)
2023-05-25 04:42:30 +00:00
2023-05-25 10:13:56 +00:00
const focusElement = useCallback(() => {
2023-05-25 09:44:35 +00:00
triggerElementFocused(isFocusedElement ? undefined : elementKey)
2023-05-25 10:13:56 +00:00
}, [isFocusedElement, elementKey])
2023-05-25 09:44:35 +00:00
2023-05-25 04:42:30 +00:00
useEffect(() => {
2023-05-25 10:13:56 +00:00
function updateFocusedState() {
2023-05-25 04:42:30 +00:00
const params = new URLSearchParams(window.location.search)
const focusedElement = params.get(focusUrlParamName)
const focused: boolean = focusedElement && focusedElement == elementKey || false
setFocusedElement(focused)
}
2023-05-25 10:13:56 +00:00
updateFocusedState()
addEventListener(focusChangedEventName, updateFocusedState)
2023-05-25 04:42:30 +00:00
return () => {
2023-05-25 10:13:56 +00:00
removeEventListener(focusChangedEventName, updateFocusedState)
2023-05-25 04:42:30 +00:00
}
2023-05-25 10:13:56 +00:00
}, [elementKey, setFocusedElement, focusElement])
2023-05-25 04:42:30 +00:00
isFocusedElement && elementRef.current?.scrollIntoView()
return {isFocusedElement, focusedClass, elementRef, focusElement}
}
2023-05-25 10:13:56 +00:00
export function useAutoFocus<ElementType extends HTMLElement>(elementKey: string) {
const {elementRef, focusedClass, focusElement} = useFocusedElement<ElementType>(elementKey);
useEffect(() => {
let cleanup = () => {}
if (elementRef.current) {
const elem = elementRef.current
elem.onclick = (evt) => {
evt.stopPropagation()
focusElement()
}
2023-05-25 10:13:56 +00:00
const classNameBackup = elem.className
2023-05-25 19:01:22 +00:00
elem.className += ' focusable ' + focusedClass
2023-05-25 10:13:56 +00:00
cleanup = () => {
elem.className = classNameBackup
}
}
return cleanup
}, [elementRef, focusedClass, focusElement])
return elementRef
}