-
Notifications
You must be signed in to change notification settings - Fork 168
/
link.ts
105 lines (100 loc) · 3.36 KB
/
link.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import React, { Children, cloneElement, CSSProperties, isValidElement, MouseEvent, PropsWithChildren, useCallback, useEffect, useMemo, useRef } from 'https://esm.sh/react'
import { redirect } from './app.ts'
import { useRouter } from './hooks.ts'
import util from './util.ts'
interface LinkProps {
to: string
replace?: boolean
prefetch?: boolean
className?: string
style?: CSSProperties
}
export default function Link({
to,
replace = false,
prefetch: prefetchImmediately = false,
className,
style,
children
}: PropsWithChildren<LinkProps>) {
const { pathname: currentPath, query: currentQuery } = useRouter()
const currentHref = useMemo(() => {
return [currentPath, Object.entries(currentQuery).map(([key, value]) => {
if (util.isArray(value)) {
return value.map(v => `${key}=${v}`).join('&')
}
return `${key}=${value}`
}).join('&')].filter(Boolean).join('?')
}, [currentPath, currentQuery])
const href = useMemo(() => {
if (util.isHttpUrl(to)) {
return to
}
let [pathname, search] = util.splitBy(to, '?')
if (pathname.startsWith('/')) {
pathname = util.cleanPath(pathname)
} else {
pathname = util.cleanPath(currentPath + '/' + pathname)
}
return [pathname, search].filter(Boolean).join('?')
}, [currentPath, to])
const prefetchStatus = useRef('')
const prefetch = useCallback(() => {
if (prefetchStatus.current != href && !util.isHttpUrl(href) && href !== currentHref) {
prefetchStatus.current = href
// prefetchPage(href)
}
}, [href, currentHref])
const onClick = useCallback((e: MouseEvent) => {
e.preventDefault()
if (href !== currentHref) {
redirect(href, replace)
}
}, [href, currentHref, replace])
useEffect(() => {
if (prefetchImmediately) {
prefetch()
}
}, [prefetchImmediately, prefetch])
if (Children.count(children) === 1) {
const child = Children.toArray(children)[0]
if (isValidElement(child) && child.type === 'a') {
const { props } = child
return cloneElement(child, {
...props,
className: [className, props.className].filter(util.isNEString).join(' ') || undefined,
style: Object.assign({}, style, props.style),
href,
'aria-current': props['aria-current'] || 'page',
onClick: (e: MouseEvent) => {
if (util.isFunction(props.onClick)) {
props.onClick(e)
}
if (!e.defaultPrevented) {
onClick(e)
}
},
onMouseEnter: (e: MouseEvent) => {
if (util.isFunction(props.onMouseEnter)) {
props.onMouseEnter(e)
}
if (!e.defaultPrevented) {
prefetch()
}
}
})
}
}
return React.createElement(
'a',
{
className,
style,
href,
onClick,
onMouseEnter: prefetch,
'aria-current': 'page'
},
children
)
}