ぼざクリタグ広場 https://hub.nizika.monster
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.
 
 
 
 
 
 

109 lines
2.5 KiB

  1. import { useQueryClient } from '@tanstack/react-query'
  2. import { forwardRef, useMemo } from 'react'
  3. import { flushSync } from 'react-dom'
  4. import { createPath, useNavigate } from 'react-router-dom'
  5. import { useOverlayStore } from '@/components/RouteBlockerOverlay'
  6. import { prefetchForURL } from '@/lib/prefetchers'
  7. import { cn } from '@/lib/utils'
  8. import type { AnchorHTMLAttributes, MouseEvent, TouchEvent } from 'react'
  9. import type { To } from 'react-router-dom'
  10. type Props = AnchorHTMLAttributes<HTMLAnchorElement> & {
  11. to: To
  12. state?: Record<string, string>
  13. replace?: boolean
  14. className?: string
  15. cancelOnError?: boolean }
  16. export default forwardRef<HTMLAnchorElement, Props> (({
  17. to,
  18. replace,
  19. className,
  20. state,
  21. onMouseEnter,
  22. onTouchStart,
  23. onClick,
  24. cancelOnError = false,
  25. ...rest }, ref) => {
  26. if ('onClick' in rest)
  27. delete rest['onClick']
  28. const navigate = useNavigate ()
  29. const qc = useQueryClient ()
  30. const url = useMemo (() => {
  31. const path = (typeof to === 'string') ? to : createPath (to)
  32. return (new URL (path, location.origin)).toString ()
  33. }, [to])
  34. const setOverlay = useOverlayStore (s => s.setActive)
  35. const doPrefetch = async () => {
  36. try
  37. {
  38. await prefetchForURL (qc, url)
  39. return true
  40. }
  41. catch (e)
  42. {
  43. console.error ('データ取得エラー', e)
  44. return false
  45. }
  46. }
  47. const handleMouseEnter = async (ev: MouseEvent<HTMLAnchorElement>) => {
  48. onMouseEnter?.(ev)
  49. await doPrefetch ()
  50. }
  51. const handleTouchStart = async (ev: TouchEvent<HTMLAnchorElement>) => {
  52. onTouchStart?.(ev)
  53. await doPrefetch ()
  54. }
  55. const handleClick = async (ev: MouseEvent<HTMLAnchorElement>) => {
  56. try
  57. {
  58. onClick?.(ev)
  59. if (ev.defaultPrevented
  60. || ev.metaKey
  61. || ev.ctrlKey
  62. || ev.shiftKey
  63. || ev.altKey
  64. || (rest.target && rest.target !== '_self'))
  65. return
  66. ev.preventDefault ()
  67. flushSync (() => {
  68. setOverlay (true)
  69. })
  70. const ok = await doPrefetch ()
  71. flushSync (() => {
  72. setOverlay (false)
  73. })
  74. if (!(ok) && cancelOnError)
  75. return
  76. navigate (to, { replace, ...(state && { state }) })
  77. }
  78. catch (ex)
  79. {
  80. console.log (ex)
  81. ev.preventDefault ()
  82. }
  83. }
  84. return (
  85. <a ref={ref}
  86. href={typeof to === 'string' ? to : createPath (to)}
  87. onMouseEnter={handleMouseEnter}
  88. onTouchStart={handleTouchStart}
  89. onClick={handleClick}
  90. className={cn ('cursor-pointer', className)}
  91. {...rest}/>)
  92. })