Dejvino's Curriculum Vitae
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

70 lines
2.5 KiB

  1. import { useState, useRef, useEffect, useCallback } from "react";
  2. const focusUrlParamName = 'focus'
  3. const focusChangedEventName = "focus-changed"
  4. const focusedElementClassName = 'focused-element'
  5. function triggerElementFocused(elementKey?: string) {
  6. const url = new URL(window.location.href);
  7. if (elementKey) {
  8. url.searchParams.set(focusUrlParamName, elementKey)
  9. } else {
  10. url.searchParams.delete(focusUrlParamName)
  11. }
  12. const focusChangeEvent = new Event(focusChangedEventName)
  13. history.pushState({}, "", url);
  14. window.dispatchEvent(focusChangeEvent)
  15. }
  16. export function useFocusedElement<ElementType extends HTMLElement>(elementKey: string) {
  17. const [isFocusedElement, setFocusedElement] = useState(false)
  18. const focusedClass = isFocusedElement ? focusedElementClassName : ''
  19. const elementRef = useRef<ElementType>(null)
  20. const focusElement = useCallback(() => {
  21. triggerElementFocused(isFocusedElement ? undefined : elementKey)
  22. }, [isFocusedElement, elementKey])
  23. useEffect(() => {
  24. function updateFocusedState() {
  25. const params = new URLSearchParams(window.location.search)
  26. const focusedElement = params.get(focusUrlParamName)
  27. const focused: boolean = focusedElement && focusedElement == elementKey || false
  28. setFocusedElement(focused)
  29. }
  30. updateFocusedState()
  31. addEventListener(focusChangedEventName, updateFocusedState)
  32. return () => {
  33. removeEventListener(focusChangedEventName, updateFocusedState)
  34. }
  35. }, [elementKey, setFocusedElement, focusElement])
  36. isFocusedElement && elementRef.current?.scrollIntoView()
  37. return {isFocusedElement, focusedClass, elementRef, focusElement}
  38. }
  39. export function useAutoFocus<ElementType extends HTMLElement>(elementKey: string) {
  40. const {elementRef, focusedClass, focusElement} = useFocusedElement<ElementType>(elementKey);
  41. useEffect(() => {
  42. let cleanup = () => {}
  43. if (elementRef.current) {
  44. const elem = elementRef.current
  45. elem.onclick = (evt) => {
  46. evt.stopPropagation()
  47. focusElement()
  48. }
  49. const classNameBackup = elem.className
  50. elem.className += ' focusable ' + focusedClass
  51. cleanup = () => {
  52. elem.className = classNameBackup
  53. }
  54. }
  55. return cleanup
  56. }, [elementRef, focusedClass, focusElement])
  57. return elementRef
  58. }