-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathindex.ts
141 lines (118 loc) · 3.74 KB
/
index.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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* Part of this code is taken from @chakra-ui/react package ❤️
*/
import type {ImgHTMLAttributes, SyntheticEvent} from "react";
import {useRef, useState, useEffect, MutableRefObject} from "react";
import {useIsHydrated} from "@nextui-org/react-utils";
import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect";
type NativeImageProps = ImgHTMLAttributes<HTMLImageElement>;
export interface UseImageProps {
/**
* The image `src` attribute
*/
src?: string;
/**
* The image `srcset` attribute
*/
srcSet?: string;
/**
* The image `sizes` attribute
*/
sizes?: string;
/**
* A callback for when the image `src` has been loaded
*/
onLoad?: NativeImageProps["onLoad"];
/**
* A callback for when there was an error loading the image `src`
*/
onError?: NativeImageProps["onError"];
/**
* If `true`, opt out of the `fallbackSrc` logic and use as `img`
*/
ignoreFallback?: boolean;
/**
* The key used to set the crossOrigin on the HTMLImageElement into which the image will be loaded.
* This tells the browser to request cross-origin access when trying to download the image data.
*/
crossOrigin?: NativeImageProps["crossOrigin"];
loading?: NativeImageProps["loading"];
}
type Status = "loading" | "failed" | "pending" | "loaded";
export type FallbackStrategy = "onError" | "beforeLoadOrError";
type ImageEvent = SyntheticEvent<HTMLImageElement, Event>;
/**
* React hook that loads an image in the browser,
* and lets us know the `status` so we can show image
* fallback if it is still `pending`
*
* @returns the status of the image loading progress
*
* @example
*
* ```jsx
* function App(){
* const status = useImage({ src: "image.png" })
* return status === "loaded" ? <img src="image.png" /> : <Placeholder />
* }
* ```
*/
export function useImage(props: UseImageProps = {}) {
const {onLoad, onError, ignoreFallback} = props;
const isHydrated = useIsHydrated();
const imageRef = useRef<HTMLImageElement | null>(isHydrated ? new Image() : null);
const [status, setStatus] = useState<Status>("pending");
useEffect(() => {
if (!imageRef.current) return;
imageRef.current.onload = (event) => {
flush();
setStatus("loaded");
onLoad?.(event as unknown as ImageEvent);
};
imageRef.current.onerror = (error) => {
flush();
setStatus("failed");
onError?.(error as any);
};
}, [imageRef.current]);
const flush = () => {
if (imageRef.current) {
imageRef.current.onload = null;
imageRef.current.onerror = null;
imageRef.current = null;
}
};
useSafeLayoutEffect(() => {
if (isHydrated) {
setStatus(setImageAndGetInitialStatus(props, imageRef));
}
}, [isHydrated]);
/**
* If user opts out of the fallback/placeholder
* logic, let's just return 'loaded'
*/
return ignoreFallback ? "loaded" : status;
}
function setImageAndGetInitialStatus(
props: UseImageProps,
imageRef: MutableRefObject<HTMLImageElement | null | undefined>,
): Status {
const {loading, src, srcSet, crossOrigin, sizes, ignoreFallback} = props;
if (!src) return "pending";
if (ignoreFallback) return "loaded";
const img = new Image();
img.src = src;
if (crossOrigin) img.crossOrigin = crossOrigin;
if (srcSet) img.srcset = srcSet;
if (sizes) img.sizes = sizes;
if (loading) img.loading = loading;
imageRef.current = img;
if (img.complete && img.naturalWidth) {
return "loaded";
}
return "loading";
}
export const shouldShowFallbackImage = (status: Status, fallbackStrategy: FallbackStrategy) =>
(status !== "loaded" && fallbackStrategy === "beforeLoadOrError") ||
(status === "failed" && fallbackStrategy === "onError");
export type UseImageReturn = ReturnType<typeof useImage>;