@next-box/link-extra 0.0.19 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/production.analysis.txt +26 -26
- package/dist/types/cjs/Link.d.cts +2 -2
- package/dist/types/cjs/Link.d.cts.map +1 -1
- package/dist/types/cjs/LinkProvider.d.cts +3 -3
- package/dist/types/cjs/LinkProvider.d.cts.map +1 -1
- package/dist/types/cjs/helpers.d.cts +1 -1
- package/dist/types/cjs/helpers.d.cts.map +1 -1
- package/dist/types/cjs/types.d.cts +7 -4
- package/dist/types/cjs/types.d.cts.map +1 -1
- package/dist/types/esm/Link.d.ts +2 -2
- package/dist/types/esm/Link.d.ts.map +1 -1
- package/dist/types/esm/LinkProvider.d.ts +3 -3
- package/dist/types/esm/LinkProvider.d.ts.map +1 -1
- package/dist/types/esm/helpers.d.ts +1 -1
- package/dist/types/esm/helpers.d.ts.map +1 -1
- package/dist/types/esm/types.d.ts +7 -4
- package/dist/types/esm/types.d.ts.map +1 -1
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +15 -14
- package/src/Link.tsx +11 -8
- package/src/LinkProvider.tsx +38 -46
- package/src/helpers.ts +7 -9
- package/src/types.ts +10 -4
- package/src/useLinkExtra.ts +2 -2
package/src/Link.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AnchorHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
-
import { addCacheBusterQueryParam,
|
|
1
|
+
import { type AnchorHTMLAttributes, type JSX, type ReactNode } from 'react';
|
|
2
|
+
import { addCacheBusterQueryParam, shouldUrlAndLocationMatch } from '#helpers.ts';
|
|
3
3
|
import { useLinkExtra } from './useLinkExtra.ts';
|
|
4
4
|
|
|
5
5
|
export type LinkProps<T extends object> = {
|
|
@@ -19,24 +19,27 @@ export const Link = <T extends object>({
|
|
|
19
19
|
prefetch = false,
|
|
20
20
|
scroll = false,
|
|
21
21
|
...otherProps
|
|
22
|
-
}: LinkProps<T>) => {
|
|
23
|
-
|
|
22
|
+
}: LinkProps<T>): JSX.Element => {
|
|
23
|
+
// This and the two other disables below are false positives
|
|
24
|
+
// eslint-disable-next-line @eslint-react/static-components
|
|
25
|
+
const { NextLink, OwnLink, onLinkClicked } = useLinkExtra();
|
|
24
26
|
|
|
25
27
|
if (disableRouterCache) {
|
|
26
28
|
href = addCacheBusterQueryParam(href);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
return (
|
|
30
|
-
//
|
|
32
|
+
// This needs to remain: https://github.com/vercel/next.js/discussions/76329
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated, @eslint-react/static-components
|
|
31
34
|
<NextLink href={href} legacyBehavior passHref prefetch={prefetch} scroll={scroll}>
|
|
32
|
-
<OwnLink
|
|
35
|
+
<OwnLink // eslint-disable-line @eslint-react/static-components
|
|
33
36
|
onClick={event => {
|
|
34
37
|
if (onClick) {
|
|
35
38
|
onClick(event);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
if (!
|
|
39
|
-
|
|
41
|
+
if (!shouldUrlAndLocationMatch(href) && !event.defaultPrevented) {
|
|
42
|
+
onLinkClicked();
|
|
40
43
|
}
|
|
41
44
|
}}
|
|
42
45
|
{...otherProps}
|
package/src/LinkProvider.tsx
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import Link from 'next/link.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import type Link from 'next/link.js';
|
|
2
|
+
import {
|
|
3
|
+
type ComponentType,
|
|
4
|
+
type FC,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
createContext,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { type LinkContextData, type NextLinkComponent, type OwnLinkProps } from '#types.js';
|
|
4
13
|
|
|
5
|
-
//
|
|
6
|
-
// eslint-disable-next-line
|
|
7
|
-
const
|
|
8
|
-
// This is a noop
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
10
|
-
const noop = () => {};
|
|
11
|
-
|
|
12
|
-
export const LinkContext = createContext<LinkContextData>({
|
|
13
|
-
NextLink: Link,
|
|
14
|
-
OwnLink: NoopComponent,
|
|
15
|
-
setLinkClicked: noop,
|
|
16
|
-
});
|
|
14
|
+
// Context requires an initial value, but this is set in the provider so casting
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
16
|
+
export const LinkContext = createContext<LinkContextData>({} as LinkContextData);
|
|
17
17
|
|
|
18
18
|
export type LinkProviderProps = {
|
|
19
19
|
LoadingComponent: ComponentType;
|
|
@@ -24,55 +24,47 @@ export type LinkProviderProps = {
|
|
|
24
24
|
pathname: string;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export const LinkProvider = ({
|
|
27
|
+
export const LinkProvider: FC<LinkProviderProps> = ({
|
|
30
28
|
LoadingComponent,
|
|
31
29
|
NextLink,
|
|
32
30
|
OwnLink,
|
|
33
31
|
children,
|
|
34
32
|
loadingTimeout = 500,
|
|
35
33
|
pathname,
|
|
36
|
-
}
|
|
37
|
-
const
|
|
34
|
+
}) => {
|
|
35
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
38
36
|
const [showLoading, setShowLoading] = useState<boolean>(false);
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
setShowLoading(true);
|
|
44
|
-
}, loadingTimeout);
|
|
38
|
+
const onLinkClicked = useCallback(() => {
|
|
39
|
+
if (timeoutRef.current) {
|
|
40
|
+
clearTimeout(timeoutRef.current);
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
52
|
-
}, [linkClicked]);
|
|
43
|
+
timeoutRef.current = setTimeout(() => {
|
|
44
|
+
setShowLoading(true);
|
|
45
|
+
}, loadingTimeout);
|
|
46
|
+
}, [loadingTimeout]);
|
|
53
47
|
|
|
54
48
|
useEffect(() => {
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
setShowLoading(false);
|
|
58
|
-
globalThis.scroll(0, 0);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (timeoutId) {
|
|
62
|
-
clearTimeout(timeoutId);
|
|
49
|
+
if (timeoutRef.current) {
|
|
50
|
+
clearTimeout(timeoutRef.current);
|
|
63
51
|
}
|
|
64
52
|
|
|
65
|
-
//
|
|
66
|
-
// eslint-disable-next-line react-hooks/
|
|
53
|
+
// This is setting state based on pathname change
|
|
54
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect, @eslint-react/set-state-in-effect
|
|
55
|
+
setShowLoading(false);
|
|
56
|
+
scroll(0, 0);
|
|
67
57
|
}, [pathname]);
|
|
68
58
|
|
|
69
59
|
return (
|
|
70
|
-
<LinkContext
|
|
60
|
+
<LinkContext
|
|
71
61
|
value={{
|
|
72
|
-
|
|
62
|
+
// Casting due to issues marrying up prop type and context type
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
64
|
+
NextLink: NextLink as unknown as NextLinkComponent,
|
|
73
65
|
OwnLink,
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
onLinkClicked: () => {
|
|
67
|
+
onLinkClicked();
|
|
76
68
|
},
|
|
77
69
|
}}
|
|
78
70
|
>
|
|
@@ -80,6 +72,6 @@ export const LinkProvider = ({
|
|
|
80
72
|
{children}
|
|
81
73
|
{showLoading ? <LoadingComponent /> : undefined}
|
|
82
74
|
</>
|
|
83
|
-
</LinkContext
|
|
75
|
+
</LinkContext>
|
|
84
76
|
);
|
|
85
77
|
};
|
package/src/helpers.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const shouldUrlAndLocationMatch = (url: string): boolean => {
|
|
4
4
|
// It is possible for this to be undefined
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
6
6
|
if (!globalThis.location) {
|
|
7
7
|
return false;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const parsedUrl = URL.parse(url,
|
|
10
|
+
const parsedUrl = URL.parse(url, location.origin);
|
|
11
11
|
|
|
12
12
|
if (!parsedUrl) {
|
|
13
13
|
return false;
|
|
@@ -20,15 +20,13 @@ export const doUrlAndLocationMatch = (url: string) => {
|
|
|
20
20
|
urlSearchParams.delete('y');
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const locationSearchParams = new URLSearchParams(
|
|
23
|
+
const locationSearchParams = new URLSearchParams(location.search);
|
|
24
24
|
|
|
25
25
|
if (locationSearchParams.has('y')) {
|
|
26
26
|
locationSearchParams.delete('y');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
return (
|
|
30
|
-
`${pathname}?${urlSearchParams.toString()}` === `${globalThis.location.pathname}?${locationSearchParams.toString()}`
|
|
31
|
-
);
|
|
29
|
+
return `${pathname}?${urlSearchParams.toString()}` === `${location.pathname}?${locationSearchParams.toString()}`;
|
|
32
30
|
};
|
|
33
31
|
|
|
34
32
|
export const addCacheBusterQueryParam = (url: string): string => {
|
|
@@ -38,13 +36,13 @@ export const addCacheBusterQueryParam = (url: string): string => {
|
|
|
38
36
|
return url;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
const parsedUrl = URL.parse(url,
|
|
39
|
+
const parsedUrl = URL.parse(url, location.origin);
|
|
42
40
|
|
|
43
41
|
if (!parsedUrl) {
|
|
44
42
|
return url;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
const locationSearchParams = new URLSearchParams(
|
|
45
|
+
const locationSearchParams = new URLSearchParams(location.search);
|
|
48
46
|
const locationCacheBusterSearchParam = locationSearchParams.get('y');
|
|
49
47
|
|
|
50
48
|
if (locationCacheBusterSearchParam) {
|
|
@@ -54,7 +52,7 @@ export const addCacheBusterQueryParam = (url: string): string => {
|
|
|
54
52
|
const { pathname, search } = parsedUrl;
|
|
55
53
|
const searchParams = new URLSearchParams(search);
|
|
56
54
|
|
|
57
|
-
if (`${pathname}?${search}` === `${
|
|
55
|
+
if (`${pathname}?${search}` === `${location.pathname}?${locationSearchParams.toString()}`) {
|
|
58
56
|
searchParams.set('y', locationCacheBusterSearchParam ?? nanoid());
|
|
59
57
|
} else {
|
|
60
58
|
searchParams.set('y', nanoid());
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type AnchorHTMLAttributes, type ComponentType } from 'react';
|
|
1
|
+
import { type LinkProps } from 'next/link.js';
|
|
2
|
+
import { type AnchorHTMLAttributes, type ComponentType, type ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
// This needs to be as generic as possible
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
6
|
export type OwnLinkProps = Record<string, any> & AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
7
7
|
|
|
8
|
+
export type NextLinkComponent = ComponentType<
|
|
9
|
+
LinkProps & {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
>;
|
|
13
|
+
|
|
8
14
|
export type LinkContextData = {
|
|
9
|
-
NextLink:
|
|
15
|
+
NextLink: NextLinkComponent;
|
|
10
16
|
OwnLink: ComponentType<AnchorHTMLAttributes<HTMLAnchorElement>>;
|
|
11
|
-
|
|
17
|
+
onLinkClicked: () => void;
|
|
12
18
|
};
|
package/src/useLinkExtra.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { use } from 'react';
|
|
2
2
|
import { type LinkContextData } from '#types.ts';
|
|
3
3
|
import { LinkContext } from './LinkProvider.tsx';
|
|
4
4
|
|
|
5
5
|
export const useLinkExtra = (): LinkContextData => {
|
|
6
|
-
return
|
|
6
|
+
return use(LinkContext);
|
|
7
7
|
};
|