@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.
@@ -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;