@grapu-design/react-image 0.1.1
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/lib/Image-12s-O0sy0c4i.cjs +160 -0
- package/lib/Image-12s-SR1fYJF4.js +156 -0
- package/lib/index.cjs +15 -0
- package/lib/index.d.ts +1127 -0
- package/lib/index.js +11 -0
- package/package.json +48 -0
- package/src/Image.namespace.ts +8 -0
- package/src/Image.tsx +71 -0
- package/src/index.ts +19 -0
- package/src/useImage.test.tsx +48 -0
- package/src/useImage.ts +101 -0
- package/src/useImageContext.tsx +17 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
3
|
+
var reactComposeRefs = require('@radix-ui/react-compose-refs');
|
|
4
|
+
var reactUseLayoutEffect = require('@radix-ui/react-use-layout-effect');
|
|
5
|
+
var domUtils = require('@grapu-design/dom-utils');
|
|
6
|
+
var reactPrimitive = require('@grapu-design/react-primitive');
|
|
7
|
+
var react = require('react');
|
|
8
|
+
var reactUseCallbackRef = require('@radix-ui/react-use-callback-ref');
|
|
9
|
+
|
|
10
|
+
function useImage(props) {
|
|
11
|
+
const onLoadingStatusChange = reactUseCallbackRef.useCallbackRef(props.onLoadingStatusChange);
|
|
12
|
+
const [loadingStatus, setLoadingStatus] = react.useState("loading");
|
|
13
|
+
const imageRef = react.useRef(null);
|
|
14
|
+
reactUseLayoutEffect.useLayoutEffect(()=>{
|
|
15
|
+
if (imageRef.current) {
|
|
16
|
+
if (imageRef.current.complete) {
|
|
17
|
+
if (imageRef.current.naturalWidth === 0 || imageRef.current.naturalHeight === 0) {
|
|
18
|
+
setLoadingStatus("error");
|
|
19
|
+
onLoadingStatusChange?.("error");
|
|
20
|
+
} else {
|
|
21
|
+
setLoadingStatus("loaded");
|
|
22
|
+
onLoadingStatusChange?.("loaded");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, [
|
|
27
|
+
onLoadingStatusChange
|
|
28
|
+
]);
|
|
29
|
+
const isLoaded = loadingStatus === "loaded";
|
|
30
|
+
const stateProps = react.useMemo(()=>domUtils.elementProps({
|
|
31
|
+
"data-loading-state": loadingStatus
|
|
32
|
+
}), [
|
|
33
|
+
loadingStatus
|
|
34
|
+
]);
|
|
35
|
+
const setSrc = react.useCallback((src)=>{
|
|
36
|
+
if (src === undefined || src === null) {
|
|
37
|
+
setLoadingStatus("error");
|
|
38
|
+
onLoadingStatusChange?.("error");
|
|
39
|
+
} else {
|
|
40
|
+
setLoadingStatus("loading");
|
|
41
|
+
onLoadingStatusChange?.("loading");
|
|
42
|
+
}
|
|
43
|
+
}, [
|
|
44
|
+
onLoadingStatusChange
|
|
45
|
+
]);
|
|
46
|
+
const getContentProps = react.useCallback(({ src })=>{
|
|
47
|
+
return domUtils.imgProps({
|
|
48
|
+
hidden: !isLoaded,
|
|
49
|
+
"data-visible": domUtils.dataAttr(isLoaded),
|
|
50
|
+
src,
|
|
51
|
+
...stateProps
|
|
52
|
+
});
|
|
53
|
+
}, [
|
|
54
|
+
isLoaded,
|
|
55
|
+
stateProps
|
|
56
|
+
]);
|
|
57
|
+
const handleLoad = react.useCallback(()=>{
|
|
58
|
+
setLoadingStatus("loaded");
|
|
59
|
+
onLoadingStatusChange?.("loaded");
|
|
60
|
+
}, [
|
|
61
|
+
onLoadingStatusChange
|
|
62
|
+
]);
|
|
63
|
+
const handleError = react.useCallback(()=>{
|
|
64
|
+
setLoadingStatus("error");
|
|
65
|
+
onLoadingStatusChange?.("error");
|
|
66
|
+
}, [
|
|
67
|
+
onLoadingStatusChange
|
|
68
|
+
]);
|
|
69
|
+
const fallbackProps = react.useMemo(()=>domUtils.elementProps({
|
|
70
|
+
hidden: isLoaded,
|
|
71
|
+
"data-visible": domUtils.dataAttr(!isLoaded),
|
|
72
|
+
...stateProps
|
|
73
|
+
}), [
|
|
74
|
+
isLoaded,
|
|
75
|
+
stateProps
|
|
76
|
+
]);
|
|
77
|
+
return {
|
|
78
|
+
refs: {
|
|
79
|
+
image: imageRef
|
|
80
|
+
},
|
|
81
|
+
loadingStatus,
|
|
82
|
+
stateProps,
|
|
83
|
+
rootProps: stateProps,
|
|
84
|
+
setSrc,
|
|
85
|
+
getContentProps,
|
|
86
|
+
handleLoad,
|
|
87
|
+
handleError,
|
|
88
|
+
fallbackProps
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const ImageContext = /*#__PURE__*/ react.createContext(null);
|
|
93
|
+
const ImageProvider = ImageContext.Provider;
|
|
94
|
+
function useImageContext({ strict = true } = {}) {
|
|
95
|
+
const context = react.useContext(ImageContext);
|
|
96
|
+
if (!context && strict) {
|
|
97
|
+
throw new Error("useImageContext must be used within an Image");
|
|
98
|
+
}
|
|
99
|
+
return context;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ImageRoot = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
103
|
+
const { onLoadingStatusChange, ...otherProps } = props;
|
|
104
|
+
const api = useImage({
|
|
105
|
+
onLoadingStatusChange
|
|
106
|
+
});
|
|
107
|
+
return /*#__PURE__*/ jsxRuntime.jsx(ImageProvider, {
|
|
108
|
+
value: api,
|
|
109
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
110
|
+
ref: ref,
|
|
111
|
+
...domUtils.mergeProps(api.rootProps, otherProps)
|
|
112
|
+
})
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
ImageRoot.displayName = "ImageRoot";
|
|
116
|
+
const ImageContent = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
117
|
+
const { src, onLoad, onError, ...otherProps } = props;
|
|
118
|
+
const { refs, setSrc, getContentProps, handleLoad, handleError } = useImageContext();
|
|
119
|
+
reactUseLayoutEffect.useLayoutEffect(()=>{
|
|
120
|
+
setSrc(src);
|
|
121
|
+
}, [
|
|
122
|
+
src,
|
|
123
|
+
setSrc
|
|
124
|
+
]);
|
|
125
|
+
const contentProps = getContentProps({
|
|
126
|
+
src
|
|
127
|
+
});
|
|
128
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.img, {
|
|
129
|
+
ref: reactComposeRefs.composeRefs(refs.image, ref),
|
|
130
|
+
...domUtils.mergeProps(contentProps, otherProps, {
|
|
131
|
+
// if loading is lazy, we should not hide the image even if it's not loaded yet,
|
|
132
|
+
// because the browser should be able to check if it's in the viewport.
|
|
133
|
+
// TODO: it should be better than this; why doesn't useImage properly handle this case?
|
|
134
|
+
hidden: otherProps.loading === "lazy" ? false : contentProps.hidden
|
|
135
|
+
}),
|
|
136
|
+
onLoad: (e)=>{
|
|
137
|
+
handleLoad();
|
|
138
|
+
onLoad?.(e);
|
|
139
|
+
},
|
|
140
|
+
onError: (e)=>{
|
|
141
|
+
handleError();
|
|
142
|
+
onError?.(e);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
ImageContent.displayName = "ImageContent";
|
|
147
|
+
const ImageFallback = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
148
|
+
const { fallbackProps } = useImageContext();
|
|
149
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
150
|
+
ref: ref,
|
|
151
|
+
...domUtils.mergeProps(fallbackProps, props)
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
ImageFallback.displayName = "ImageFallback";
|
|
155
|
+
|
|
156
|
+
exports.ImageContent = ImageContent;
|
|
157
|
+
exports.ImageFallback = ImageFallback;
|
|
158
|
+
exports.ImageRoot = ImageRoot;
|
|
159
|
+
exports.useImage = useImage;
|
|
160
|
+
exports.useImageContext = useImageContext;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
4
|
+
import { useLayoutEffect } from '@radix-ui/react-use-layout-effect';
|
|
5
|
+
import { elementProps, imgProps, dataAttr, mergeProps } from '@grapu-design/dom-utils';
|
|
6
|
+
import { Primitive } from '@grapu-design/react-primitive';
|
|
7
|
+
import { useState, useRef, useMemo, useCallback, createContext, useContext, forwardRef } from 'react';
|
|
8
|
+
import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
|
|
9
|
+
|
|
10
|
+
function useImage(props) {
|
|
11
|
+
const onLoadingStatusChange = useCallbackRef(props.onLoadingStatusChange);
|
|
12
|
+
const [loadingStatus, setLoadingStatus] = useState("loading");
|
|
13
|
+
const imageRef = useRef(null);
|
|
14
|
+
useLayoutEffect(()=>{
|
|
15
|
+
if (imageRef.current) {
|
|
16
|
+
if (imageRef.current.complete) {
|
|
17
|
+
if (imageRef.current.naturalWidth === 0 || imageRef.current.naturalHeight === 0) {
|
|
18
|
+
setLoadingStatus("error");
|
|
19
|
+
onLoadingStatusChange?.("error");
|
|
20
|
+
} else {
|
|
21
|
+
setLoadingStatus("loaded");
|
|
22
|
+
onLoadingStatusChange?.("loaded");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, [
|
|
27
|
+
onLoadingStatusChange
|
|
28
|
+
]);
|
|
29
|
+
const isLoaded = loadingStatus === "loaded";
|
|
30
|
+
const stateProps = useMemo(()=>elementProps({
|
|
31
|
+
"data-loading-state": loadingStatus
|
|
32
|
+
}), [
|
|
33
|
+
loadingStatus
|
|
34
|
+
]);
|
|
35
|
+
const setSrc = useCallback((src)=>{
|
|
36
|
+
if (src === undefined || src === null) {
|
|
37
|
+
setLoadingStatus("error");
|
|
38
|
+
onLoadingStatusChange?.("error");
|
|
39
|
+
} else {
|
|
40
|
+
setLoadingStatus("loading");
|
|
41
|
+
onLoadingStatusChange?.("loading");
|
|
42
|
+
}
|
|
43
|
+
}, [
|
|
44
|
+
onLoadingStatusChange
|
|
45
|
+
]);
|
|
46
|
+
const getContentProps = useCallback(({ src })=>{
|
|
47
|
+
return imgProps({
|
|
48
|
+
hidden: !isLoaded,
|
|
49
|
+
"data-visible": dataAttr(isLoaded),
|
|
50
|
+
src,
|
|
51
|
+
...stateProps
|
|
52
|
+
});
|
|
53
|
+
}, [
|
|
54
|
+
isLoaded,
|
|
55
|
+
stateProps
|
|
56
|
+
]);
|
|
57
|
+
const handleLoad = useCallback(()=>{
|
|
58
|
+
setLoadingStatus("loaded");
|
|
59
|
+
onLoadingStatusChange?.("loaded");
|
|
60
|
+
}, [
|
|
61
|
+
onLoadingStatusChange
|
|
62
|
+
]);
|
|
63
|
+
const handleError = useCallback(()=>{
|
|
64
|
+
setLoadingStatus("error");
|
|
65
|
+
onLoadingStatusChange?.("error");
|
|
66
|
+
}, [
|
|
67
|
+
onLoadingStatusChange
|
|
68
|
+
]);
|
|
69
|
+
const fallbackProps = useMemo(()=>elementProps({
|
|
70
|
+
hidden: isLoaded,
|
|
71
|
+
"data-visible": dataAttr(!isLoaded),
|
|
72
|
+
...stateProps
|
|
73
|
+
}), [
|
|
74
|
+
isLoaded,
|
|
75
|
+
stateProps
|
|
76
|
+
]);
|
|
77
|
+
return {
|
|
78
|
+
refs: {
|
|
79
|
+
image: imageRef
|
|
80
|
+
},
|
|
81
|
+
loadingStatus,
|
|
82
|
+
stateProps,
|
|
83
|
+
rootProps: stateProps,
|
|
84
|
+
setSrc,
|
|
85
|
+
getContentProps,
|
|
86
|
+
handleLoad,
|
|
87
|
+
handleError,
|
|
88
|
+
fallbackProps
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const ImageContext = /*#__PURE__*/ createContext(null);
|
|
93
|
+
const ImageProvider = ImageContext.Provider;
|
|
94
|
+
function useImageContext({ strict = true } = {}) {
|
|
95
|
+
const context = useContext(ImageContext);
|
|
96
|
+
if (!context && strict) {
|
|
97
|
+
throw new Error("useImageContext must be used within an Image");
|
|
98
|
+
}
|
|
99
|
+
return context;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ImageRoot = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
103
|
+
const { onLoadingStatusChange, ...otherProps } = props;
|
|
104
|
+
const api = useImage({
|
|
105
|
+
onLoadingStatusChange
|
|
106
|
+
});
|
|
107
|
+
return /*#__PURE__*/ jsx(ImageProvider, {
|
|
108
|
+
value: api,
|
|
109
|
+
children: /*#__PURE__*/ jsx(Primitive.div, {
|
|
110
|
+
ref: ref,
|
|
111
|
+
...mergeProps(api.rootProps, otherProps)
|
|
112
|
+
})
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
ImageRoot.displayName = "ImageRoot";
|
|
116
|
+
const ImageContent = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
117
|
+
const { src, onLoad, onError, ...otherProps } = props;
|
|
118
|
+
const { refs, setSrc, getContentProps, handleLoad, handleError } = useImageContext();
|
|
119
|
+
useLayoutEffect(()=>{
|
|
120
|
+
setSrc(src);
|
|
121
|
+
}, [
|
|
122
|
+
src,
|
|
123
|
+
setSrc
|
|
124
|
+
]);
|
|
125
|
+
const contentProps = getContentProps({
|
|
126
|
+
src
|
|
127
|
+
});
|
|
128
|
+
return /*#__PURE__*/ jsx(Primitive.img, {
|
|
129
|
+
ref: composeRefs(refs.image, ref),
|
|
130
|
+
...mergeProps(contentProps, otherProps, {
|
|
131
|
+
// if loading is lazy, we should not hide the image even if it's not loaded yet,
|
|
132
|
+
// because the browser should be able to check if it's in the viewport.
|
|
133
|
+
// TODO: it should be better than this; why doesn't useImage properly handle this case?
|
|
134
|
+
hidden: otherProps.loading === "lazy" ? false : contentProps.hidden
|
|
135
|
+
}),
|
|
136
|
+
onLoad: (e)=>{
|
|
137
|
+
handleLoad();
|
|
138
|
+
onLoad?.(e);
|
|
139
|
+
},
|
|
140
|
+
onError: (e)=>{
|
|
141
|
+
handleError();
|
|
142
|
+
onError?.(e);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
ImageContent.displayName = "ImageContent";
|
|
147
|
+
const ImageFallback = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
148
|
+
const { fallbackProps } = useImageContext();
|
|
149
|
+
return /*#__PURE__*/ jsx(Primitive.div, {
|
|
150
|
+
ref: ref,
|
|
151
|
+
...mergeProps(fallbackProps, props)
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
ImageFallback.displayName = "ImageFallback";
|
|
155
|
+
|
|
156
|
+
export { ImageContent as I, ImageFallback as a, ImageRoot as b, useImageContext as c, useImage as u };
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
var Image12s = require('./Image-12s-O0sy0c4i.cjs');
|
|
2
|
+
|
|
3
|
+
var Image_namespace = {
|
|
4
|
+
__proto__: null,
|
|
5
|
+
Content: Image12s.ImageContent,
|
|
6
|
+
Fallback: Image12s.ImageFallback,
|
|
7
|
+
Root: Image12s.ImageRoot
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
exports.ImageContent = Image12s.ImageContent;
|
|
11
|
+
exports.ImageFallback = Image12s.ImageFallback;
|
|
12
|
+
exports.ImageRoot = Image12s.ImageRoot;
|
|
13
|
+
exports.useImage = Image12s.useImage;
|
|
14
|
+
exports.useImageContext = Image12s.useImageContext;
|
|
15
|
+
exports.Image = Image_namespace;
|