|
- import { useState, useRef, useEffect, useCallback } from "react";
-
- const focusUrlParamName = 'focus'
- const focusChangedEventName = "focus-changed"
- const focusedElementClassName = 'focused-element'
-
- function triggerElementFocused(elementKey?: string) {
- const url = new URL(window.location.href);
- if (elementKey) {
- url.searchParams.set(focusUrlParamName, elementKey)
- } else {
- url.searchParams.delete(focusUrlParamName)
- }
- const focusChangeEvent = new Event(focusChangedEventName)
- history.pushState({}, "", url);
- window.dispatchEvent(focusChangeEvent)
- }
-
- export function useFocusedElement<ElementType extends HTMLElement>(elementKey: string) {
- const [isFocusedElement, setFocusedElement] = useState(false)
- const focusedClass = isFocusedElement ? focusedElementClassName : ''
- const elementRef = useRef<ElementType>(null)
-
- const focusElement = useCallback(() => {
- triggerElementFocused(isFocusedElement ? undefined : elementKey)
- }, [isFocusedElement, elementKey])
-
- useEffect(() => {
- function updateFocusedState() {
- const params = new URLSearchParams(window.location.search)
- const focusedElement = params.get(focusUrlParamName)
- const focused: boolean = focusedElement && focusedElement == elementKey || false
- setFocusedElement(focused)
- }
-
- updateFocusedState()
- addEventListener(focusChangedEventName, updateFocusedState)
- return () => {
- removeEventListener(focusChangedEventName, updateFocusedState)
- }
- }, [elementKey, setFocusedElement, focusElement])
-
- isFocusedElement && elementRef.current?.scrollIntoView()
-
- return {isFocusedElement, focusedClass, elementRef, focusElement}
- }
-
- 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()
- }
- const classNameBackup = elem.className
- elem.className += ' focusable ' + focusedClass
- cleanup = () => {
- elem.className = classNameBackup
- }
- }
- return cleanup
- }, [elementRef, focusedClass, focusElement])
-
- return elementRef
- }
|