@shopify/shop-minis-react 0.4.0 → 0.4.2

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.
Files changed (24) hide show
  1. package/dist/components/atoms/list.js +57 -65
  2. package/dist/components/atoms/list.js.map +1 -1
  3. package/dist/hooks/util/useImagePicker.js +13 -6
  4. package/dist/hooks/util/useImagePicker.js.map +1 -1
  5. package/dist/internal/utils/resizeImage.js +61 -0
  6. package/dist/internal/utils/resizeImage.js.map +1 -0
  7. package/dist/providers/ImagePickerProvider.js +123 -102
  8. package/dist/providers/ImagePickerProvider.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/components/atoms/list.tsx +3 -12
  11. package/src/hooks/util/useImagePicker.test.tsx +193 -0
  12. package/src/hooks/util/useImagePicker.ts +24 -5
  13. package/src/internal/utils/resizeImage.test.ts +314 -0
  14. package/src/internal/utils/resizeImage.ts +108 -0
  15. package/src/providers/ImagePickerProvider.test.tsx +32 -1
  16. package/src/providers/ImagePickerProvider.tsx +108 -65
  17. package/dist/components/atoms/pagination.js +0 -10
  18. package/dist/components/atoms/pagination.js.map +0 -1
  19. package/dist/components/atoms/tracking-pixel.js +0 -32
  20. package/dist/components/atoms/tracking-pixel.js.map +0 -1
  21. package/dist/shop-minis-react/node_modules/.pnpm/react-intersection-observer@9.13.1_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.js +0 -135
  22. package/dist/shop-minis-react/node_modules/.pnpm/react-intersection-observer@9.13.1_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.js.map +0 -1
  23. package/src/components/atoms/pagination.tsx +0 -19
  24. package/src/components/atoms/tracking-pixel.tsx +0 -40
@@ -0,0 +1,108 @@
1
+ import type {
2
+ ImageQuality,
3
+ CustomImageQuality,
4
+ } from '../../providers/ImagePickerProvider'
5
+
6
+ interface ResizeSettings {
7
+ size: number
8
+ compression: number
9
+ }
10
+
11
+ export interface ResizeImageParams {
12
+ file: File
13
+ quality: ImageQuality
14
+ customQuality?: CustomImageQuality
15
+ }
16
+
17
+ const QUALITY_SETTINGS: {
18
+ [key in Exclude<ImageQuality, 'original'>]: ResizeSettings
19
+ } = {
20
+ low: {
21
+ size: 1080,
22
+ compression: 0.7,
23
+ },
24
+ medium: {
25
+ size: 1600,
26
+ compression: 0.85,
27
+ },
28
+ high: {
29
+ size: 2048,
30
+ compression: 0.92,
31
+ },
32
+ } as const
33
+
34
+ export function resizeImage({
35
+ file,
36
+ quality,
37
+ customQuality,
38
+ }: ResizeImageParams): Promise<File> {
39
+ if (quality === 'original') {
40
+ return Promise.resolve(file)
41
+ }
42
+
43
+ const defaultSettings = QUALITY_SETTINGS[quality]
44
+ const settings: ResizeSettings = customQuality
45
+ ? {
46
+ size: customQuality.size ?? defaultSettings.size,
47
+ compression: customQuality.compression ?? defaultSettings.compression,
48
+ }
49
+ : defaultSettings
50
+ const maxSize = settings.size
51
+
52
+ return new Promise((resolve, reject) => {
53
+ const img = new Image()
54
+ const url = URL.createObjectURL(file)
55
+
56
+ img.onerror = () => {
57
+ URL.revokeObjectURL(url)
58
+ reject(new Error('Failed to load image'))
59
+ }
60
+
61
+ img.onload = () => {
62
+ URL.revokeObjectURL(url)
63
+
64
+ const canvas = document.createElement('canvas')
65
+ let width = img.width
66
+ let height = img.height
67
+
68
+ // Resize image dimensions maintaining aspect ratio
69
+ if (width > height) {
70
+ if (width > maxSize) {
71
+ height *= maxSize / width
72
+ width = maxSize
73
+ }
74
+ } else if (height > maxSize) {
75
+ width *= maxSize / height
76
+ height = maxSize
77
+ }
78
+
79
+ canvas.width = width
80
+ canvas.height = height
81
+
82
+ const ctx = canvas.getContext('2d')
83
+ if (!ctx) {
84
+ reject(new Error('Failed to get canvas context'))
85
+ return
86
+ }
87
+ ctx.drawImage(img, 0, 0, width, height)
88
+
89
+ canvas.toBlob(
90
+ blob => {
91
+ if (!blob) {
92
+ reject(new Error('Failed to create blob'))
93
+ return
94
+ }
95
+ const resizedFile = new File([blob], file.name, {
96
+ type: 'image/jpeg',
97
+ lastModified: Date.now(),
98
+ })
99
+ resolve(resizedFile)
100
+ },
101
+ 'image/jpeg',
102
+ settings.compression
103
+ )
104
+ }
105
+
106
+ img.src = url
107
+ })
108
+ }
@@ -34,6 +34,37 @@ describe('ImagePickerProvider', () => {
34
34
  mockRequestPermission.mockResolvedValue({granted: true})
35
35
  // Clear interaction reporting mock
36
36
  mockReportInteraction.mockClear()
37
+
38
+ // Mock URL.createObjectURL and URL.revokeObjectURL for jsdom
39
+ global.URL.createObjectURL = vi.fn(() => 'blob:mock-url')
40
+ global.URL.revokeObjectURL = vi.fn()
41
+
42
+ // Mock Image constructor for resizing
43
+ global.Image = class MockImage {
44
+ width = 1920
45
+ height = 1080
46
+ onload = null as any
47
+ onerror = null as any
48
+ src = ''
49
+
50
+ constructor() {
51
+ setTimeout(() => {
52
+ if (this.onload) {
53
+ this.onload()
54
+ }
55
+ }, 0)
56
+ }
57
+ } as any
58
+
59
+ // Mock canvas methods
60
+ HTMLCanvasElement.prototype.getContext = vi.fn(() => ({
61
+ drawImage: vi.fn(),
62
+ })) as any
63
+
64
+ HTMLCanvasElement.prototype.toBlob = vi.fn(callback => {
65
+ const blob = new Blob(['mock'], {type: 'image/jpeg'})
66
+ callback(blob)
67
+ }) as any
37
68
  })
38
69
 
39
70
  afterEach(() => {
@@ -334,7 +365,7 @@ describe('ImagePickerProvider', () => {
334
365
  <button
335
366
  type="button"
336
367
  onClick={() =>
337
- openCamera('front').catch(() => {
368
+ openCamera({cameraFacing: 'front'}).catch(() => {
338
369
  // Ignore errors from cleanup
339
370
  })
340
371
  }
@@ -9,12 +9,29 @@ import React, {
9
9
 
10
10
  import {useRequestPermissions} from '../hooks/util/useRequestPermissions'
11
11
  import {useReportInteraction} from '../internal/useReportInteraction'
12
+ import {resizeImage} from '../internal/utils/resizeImage'
12
13
 
14
+ export type ImageQuality = 'low' | 'medium' | 'high' | 'original'
13
15
  export type CameraFacing = 'front' | 'back'
16
+ export interface CustomImageQuality {
17
+ size?: number
18
+ compression?: number
19
+ }
20
+
21
+ export interface OpenCameraParams {
22
+ cameraFacing?: CameraFacing
23
+ quality?: ImageQuality
24
+ customQuality?: CustomImageQuality
25
+ }
26
+
27
+ export interface OpenGalleryParams {
28
+ quality?: ImageQuality
29
+ customQuality?: CustomImageQuality
30
+ }
14
31
 
15
32
  interface ImagePickerContextValue {
16
- openCamera: (cameraFacing?: CameraFacing) => Promise<File>
17
- openGallery: () => Promise<File>
33
+ openCamera: (params?: OpenCameraParams) => Promise<File>
34
+ openGallery: (params?: OpenGalleryParams) => Promise<File>
18
35
  }
19
36
 
20
37
  const ImagePickerContext = createContext<ImagePickerContextValue | null>(null)
@@ -44,7 +61,8 @@ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
44
61
  handler: () => void
45
62
  } | null>(null)
46
63
  const activeOperationRef = useRef<'gallery' | 'camera' | null>(null)
47
-
64
+ const qualityRef = useRef<ImageQuality>('medium')
65
+ const customQualityRef = useRef<CustomImageQuality>(undefined)
48
66
  const {requestPermission} = useRequestPermissions()
49
67
  const {reportInteraction} = useReportInteraction()
50
68
 
@@ -77,26 +95,41 @@ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
77
95
  resolveRef.current = null
78
96
  rejectRef.current = null
79
97
  activeOperationRef.current = null
98
+ qualityRef.current = 'medium'
99
+ customQualityRef.current = undefined
80
100
  }
81
101
  }, [reportInteraction])
82
102
 
83
103
  const handleFileChange = useCallback(
84
- (event: React.ChangeEvent<HTMLInputElement>) => {
85
- const file = event.target.files?.[0]
104
+ async (event: React.ChangeEvent<HTMLInputElement>) => {
105
+ const {target} = event
106
+ const file = target.files?.[0]
86
107
 
87
108
  if (file && resolveRef.current) {
88
- // Report success based on the active operation
89
- if (activeOperationRef.current === 'gallery') {
90
- reportInteraction({
91
- interactionType: 'image_picker_success',
92
- })
93
- } else if (activeOperationRef.current === 'camera') {
94
- reportInteraction({
95
- interactionType: 'camera_success',
109
+ try {
110
+ const resizedFile = await resizeImage({
111
+ file,
112
+ quality: qualityRef.current,
113
+ customQuality: customQualityRef.current,
96
114
  })
97
- }
98
115
 
99
- resolveRef.current(file)
116
+ if (activeOperationRef.current === 'gallery') {
117
+ reportInteraction({
118
+ interactionType: 'image_picker_success',
119
+ })
120
+ } else if (activeOperationRef.current === 'camera') {
121
+ reportInteraction({
122
+ interactionType: 'camera_success',
123
+ })
124
+ }
125
+
126
+ resolveRef.current(resizedFile)
127
+ } catch (error) {
128
+ console.warn('Image resize failed, using original:', error)
129
+ if (resolveRef.current) {
130
+ resolveRef.current(file)
131
+ }
132
+ }
100
133
 
101
134
  resolveRef.current = null
102
135
  rejectRef.current = null
@@ -105,80 +138,90 @@ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
105
138
  cleanupCancelHandler()
106
139
  }
107
140
 
108
- event.target.value = ''
141
+ target.value = ''
109
142
  },
110
143
  [cleanupCancelHandler, reportInteraction]
111
144
  )
112
145
 
113
- const openGallery = useCallback(() => {
114
- return new Promise<File>((resolve, reject) => {
115
- rejectPendingPromise()
116
- cleanupCancelHandler()
117
-
118
- resolveRef.current = resolve
119
- rejectRef.current = reject
120
- activeOperationRef.current = 'gallery'
121
-
122
- const input = galleryInputRef.current
146
+ const openGallery = useCallback(
147
+ ({quality = 'medium', customQuality}: OpenGalleryParams = {}) => {
148
+ return new Promise<File>((resolve, reject) => {
149
+ rejectPendingPromise()
150
+ cleanupCancelHandler()
123
151
 
124
- if (!input) {
125
- const error = new Error('Gallery input not found')
126
- reportInteraction({
127
- interactionType: 'image_picker_error',
128
- interactionValue: error.message,
129
- })
130
- reject(error)
131
- resolveRef.current = null
132
- rejectRef.current = null
133
- activeOperationRef.current = null
134
- return
135
- }
152
+ qualityRef.current = quality
153
+ customQualityRef.current = customQuality
154
+ resolveRef.current = resolve
155
+ rejectRef.current = reject
156
+ activeOperationRef.current = 'gallery'
157
+ const input = galleryInputRef.current
136
158
 
137
- const handleCancel = () => {
138
- if (rejectRef.current) {
139
- const error = new Error('User cancelled file selection')
159
+ if (!input) {
160
+ const error = new Error('Gallery input not found')
140
161
  reportInteraction({
141
162
  interactionType: 'image_picker_error',
142
163
  interactionValue: error.message,
143
164
  })
144
- rejectRef.current(error)
165
+ reject(error)
145
166
  resolveRef.current = null
146
167
  rejectRef.current = null
147
168
  activeOperationRef.current = null
169
+ return
148
170
  }
149
- cleanupCancelHandler()
150
- }
151
171
 
152
- input.addEventListener('cancel', handleCancel)
153
- activeCancelHandlerRef.current = {input, handler: handleCancel}
172
+ const handleCancel = () => {
173
+ if (rejectRef.current) {
174
+ const error = new Error('User cancelled file selection')
175
+ reportInteraction({
176
+ interactionType: 'image_picker_error',
177
+ interactionValue: error.message,
178
+ })
179
+ rejectRef.current(error)
180
+ resolveRef.current = null
181
+ rejectRef.current = null
182
+ activeOperationRef.current = null
183
+ }
184
+ cleanupCancelHandler()
185
+ }
154
186
 
155
- reportInteraction({
156
- interactionType: 'image_picker_open',
157
- })
187
+ input.addEventListener('cancel', handleCancel)
188
+ activeCancelHandlerRef.current = {input, handler: handleCancel}
158
189
 
159
- requestPermission({permission: 'CAMERA'})
160
- .then(() => {
161
- // This will show both Camera and Gallery
162
- input.click()
163
- })
164
- .catch(() => {
165
- // Show only Gallery
166
- input.click()
190
+ reportInteraction({
191
+ interactionType: 'image_picker_open',
167
192
  })
168
- })
169
- }, [
170
- rejectPendingPromise,
171
- cleanupCancelHandler,
172
- requestPermission,
173
- reportInteraction,
174
- ])
193
+
194
+ requestPermission({permission: 'CAMERA'})
195
+ .then(() => {
196
+ // This will show both Camera and Gallery
197
+ input.click()
198
+ })
199
+ .catch(() => {
200
+ // Show only Gallery
201
+ input.click()
202
+ })
203
+ })
204
+ },
205
+ [
206
+ rejectPendingPromise,
207
+ cleanupCancelHandler,
208
+ requestPermission,
209
+ reportInteraction,
210
+ ]
211
+ )
175
212
 
176
213
  const openCamera = useCallback(
177
- (cameraFacing: CameraFacing = 'back') => {
214
+ ({
215
+ cameraFacing = 'back',
216
+ quality = 'medium',
217
+ customQuality,
218
+ }: OpenCameraParams = {}) => {
178
219
  return new Promise<File>((resolve, reject) => {
179
220
  rejectPendingPromise()
180
221
  cleanupCancelHandler()
181
222
 
223
+ qualityRef.current = quality
224
+ customQualityRef.current = customQuality
182
225
  resolveRef.current = resolve
183
226
  rejectRef.current = reject
184
227
  activeOperationRef.current = 'camera'
@@ -1,10 +0,0 @@
1
- import { jsx as o } from "react/jsx-runtime";
2
- import { Skeleton as e } from "../ui/skeleton.js";
3
- import { TrackingPixel as i } from "./tracking-pixel.js";
4
- function c({ loadingComponent: r, fetchMore: n }) {
5
- return /* @__PURE__ */ o(i, { onImpression: n, children: r ?? /* @__PURE__ */ o(e, { className: "h-10 w-full p-8" }) });
6
- }
7
- export {
8
- c as Pagination
9
- };
10
- //# sourceMappingURL=pagination.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pagination.js","sources":["../../../src/components/atoms/pagination.tsx"],"sourcesContent":["import {Skeleton} from '../ui/skeleton'\n\nimport {TrackingPixel} from './tracking-pixel'\n\ninterface Props {\n loadingComponent?: React.ReactNode\n isFetchingMore?: boolean\n fetchMore: () => void\n}\n\nexport function Pagination({loadingComponent, fetchMore}: Props) {\n const loadingPlaceholder = loadingComponent ?? (\n <Skeleton className=\"h-10 w-full p-8\" />\n )\n\n return (\n <TrackingPixel onImpression={fetchMore}>{loadingPlaceholder}</TrackingPixel>\n )\n}\n"],"names":["Pagination","loadingComponent","fetchMore","jsx","TrackingPixel","Skeleton"],"mappings":";;;AAUO,SAASA,EAAW,EAAC,kBAAAC,GAAkB,WAAAC,KAAmB;AAK/D,SACG,gBAAAC,EAAAC,GAAA,EAAc,cAAcF,GAAY,UALhBD,KACxB,gBAAAE,EAAAE,GAAA,EAAS,WAAU,mBAAkB,GAIsB;AAEhE;"}
@@ -1,32 +0,0 @@
1
- import { jsx as m } from "react/jsx-runtime";
2
- import { useCallback as n } from "react";
3
- import { InView as c } from "../../shop-minis-react/node_modules/.pnpm/react-intersection-observer@9.13.1_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.js";
4
- const x = ({
5
- children: o,
6
- onImpression: r,
7
- triggerOnce: i = !1,
8
- threshold: t,
9
- className: a
10
- }) => {
11
- const e = n(
12
- (f) => {
13
- f && r?.();
14
- },
15
- [r]
16
- );
17
- return /* @__PURE__ */ m(
18
- c,
19
- {
20
- as: "div",
21
- onChange: e,
22
- triggerOnce: i,
23
- className: a,
24
- threshold: t,
25
- children: o
26
- }
27
- );
28
- };
29
- export {
30
- x as TrackingPixel
31
- };
32
- //# sourceMappingURL=tracking-pixel.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tracking-pixel.js","sources":["../../../src/components/atoms/tracking-pixel.tsx"],"sourcesContent":["import {useCallback} from 'react'\n\nimport {InView} from 'react-intersection-observer'\n\ninterface Props {\n children: React.ReactNode\n onImpression?: () => void\n triggerOnce?: boolean\n threshold?: number\n className?: string\n}\n\nexport const TrackingPixel = ({\n children,\n onImpression,\n triggerOnce = false,\n threshold,\n className,\n}: Props) => {\n const onChange = useCallback(\n (inView: boolean) => {\n if (inView) {\n onImpression?.()\n }\n },\n [onImpression]\n )\n\n return (\n <InView\n as=\"div\"\n onChange={onChange}\n triggerOnce={triggerOnce}\n className={className}\n threshold={threshold}\n >\n {children}\n </InView>\n )\n}\n"],"names":["TrackingPixel","children","onImpression","triggerOnce","threshold","className","onChange","useCallback","inView","jsx","InView"],"mappings":";;;AAYO,MAAMA,IAAgB,CAAC;AAAA,EAC5B,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,aAAAC,IAAc;AAAA,EACd,WAAAC;AAAA,EACA,WAAAC;AACF,MAAa;AACX,QAAMC,IAAWC;AAAA,IACf,CAACC,MAAoB;AACnB,MAAIA,KACaN,IAAA;AAAA,IAEnB;AAAA,IACA,CAACA,CAAY;AAAA,EACf;AAGE,SAAA,gBAAAO;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,IAAG;AAAA,MACH,UAAAJ;AAAA,MACA,aAAAH;AAAA,MACA,WAAAE;AAAA,MACA,WAAAD;AAAA,MAEC,UAAAH;AAAA,IAAA;AAAA,EACH;AAEJ;"}
@@ -1,135 +0,0 @@
1
- import * as b from "react";
2
- var V = Object.defineProperty, w = (e, t, i) => t in e ? V(e, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : e[t] = i, u = (e, t, i) => w(e, typeof t != "symbol" ? t + "" : t, i), p = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new WeakMap(), f = 0, _ = void 0;
3
- function I(e) {
4
- return e ? (l.has(e) || (f += 1, l.set(e, f.toString())), l.get(e)) : "0";
5
- }
6
- function y(e) {
7
- return Object.keys(e).sort().filter(
8
- (t) => e[t] !== void 0
9
- ).map((t) => `${t}_${t === "root" ? I(e.root) : e[t]}`).toString();
10
- }
11
- function m(e) {
12
- const t = y(e);
13
- let i = p.get(t);
14
- if (!i) {
15
- const r = /* @__PURE__ */ new Map();
16
- let d;
17
- const n = new IntersectionObserver((o) => {
18
- o.forEach((s) => {
19
- var h;
20
- const c = s.isIntersecting && d.some((a) => s.intersectionRatio >= a);
21
- e.trackVisibility && typeof s.isVisible > "u" && (s.isVisible = c), (h = r.get(s.target)) == null || h.forEach((a) => {
22
- a(c, s);
23
- });
24
- });
25
- }, e);
26
- d = n.thresholds || (Array.isArray(e.threshold) ? e.threshold : [e.threshold || 0]), i = {
27
- id: t,
28
- observer: n,
29
- elements: r
30
- }, p.set(t, i);
31
- }
32
- return i;
33
- }
34
- function M(e, t, i = {}, r = _) {
35
- if (typeof window.IntersectionObserver > "u" && r !== void 0) {
36
- const h = e.getBoundingClientRect();
37
- return t(r, {
38
- isIntersecting: r,
39
- target: e,
40
- intersectionRatio: typeof i.threshold == "number" ? i.threshold : 0,
41
- time: 0,
42
- boundingClientRect: h,
43
- intersectionRect: h,
44
- rootBounds: h
45
- }), () => {
46
- };
47
- }
48
- const { id: d, observer: n, elements: o } = m(i), s = o.get(e) || [];
49
- return o.has(e) || o.set(e, s), s.push(t), n.observe(e), function() {
50
- s.splice(s.indexOf(t), 1), s.length === 0 && (o.delete(e), n.unobserve(e)), o.size === 0 && (n.disconnect(), p.delete(d));
51
- };
52
- }
53
- function O(e) {
54
- return typeof e.children != "function";
55
- }
56
- var R = class extends b.Component {
57
- constructor(e) {
58
- super(e), u(this, "node", null), u(this, "_unobserveCb", null), u(this, "handleNode", (t) => {
59
- this.node && (this.unobserve(), !t && !this.props.triggerOnce && !this.props.skip && this.setState({ inView: !!this.props.initialInView, entry: void 0 })), this.node = t || null, this.observeNode();
60
- }), u(this, "handleChange", (t, i) => {
61
- t && this.props.triggerOnce && this.unobserve(), O(this.props) || this.setState({ inView: t, entry: i }), this.props.onChange && this.props.onChange(t, i);
62
- }), this.state = {
63
- inView: !!e.initialInView,
64
- entry: void 0
65
- };
66
- }
67
- componentDidMount() {
68
- this.unobserve(), this.observeNode();
69
- }
70
- componentDidUpdate(e) {
71
- (e.rootMargin !== this.props.rootMargin || e.root !== this.props.root || e.threshold !== this.props.threshold || e.skip !== this.props.skip || e.trackVisibility !== this.props.trackVisibility || e.delay !== this.props.delay) && (this.unobserve(), this.observeNode());
72
- }
73
- componentWillUnmount() {
74
- this.unobserve();
75
- }
76
- observeNode() {
77
- if (!this.node || this.props.skip) return;
78
- const {
79
- threshold: e,
80
- root: t,
81
- rootMargin: i,
82
- trackVisibility: r,
83
- delay: d,
84
- fallbackInView: n
85
- } = this.props;
86
- this._unobserveCb = M(
87
- this.node,
88
- this.handleChange,
89
- {
90
- threshold: e,
91
- root: t,
92
- rootMargin: i,
93
- // @ts-ignore
94
- trackVisibility: r,
95
- // @ts-ignore
96
- delay: d
97
- },
98
- n
99
- );
100
- }
101
- unobserve() {
102
- this._unobserveCb && (this._unobserveCb(), this._unobserveCb = null);
103
- }
104
- render() {
105
- const { children: e } = this.props;
106
- if (typeof e == "function") {
107
- const { inView: g, entry: C } = this.state;
108
- return e({ inView: g, entry: C, ref: this.handleNode });
109
- }
110
- const {
111
- as: t,
112
- triggerOnce: i,
113
- threshold: r,
114
- root: d,
115
- rootMargin: n,
116
- onChange: o,
117
- skip: s,
118
- trackVisibility: h,
119
- delay: c,
120
- initialInView: a,
121
- fallbackInView: N,
122
- ...v
123
- } = this.props;
124
- return b.createElement(
125
- t || "div",
126
- { ref: this.handleNode, ...v },
127
- e
128
- );
129
- }
130
- };
131
- export {
132
- R as InView,
133
- M as observe
134
- };
135
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../../../../../../../node_modules/.pnpm/react-intersection-observer@9.13.1_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.mjs"],"sourcesContent":["\"use client\";\nvar __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== \"symbol\" ? key + \"\" : key, value);\n\n// src/InView.tsx\nimport * as React from \"react\";\n\n// src/observe.ts\nvar observerMap = /* @__PURE__ */ new Map();\nvar RootIds = /* @__PURE__ */ new WeakMap();\nvar rootId = 0;\nvar unsupportedValue = void 0;\nfunction defaultFallbackInView(inView) {\n unsupportedValue = inView;\n}\nfunction getRootId(root) {\n if (!root) return \"0\";\n if (RootIds.has(root)) return RootIds.get(root);\n rootId += 1;\n RootIds.set(root, rootId.toString());\n return RootIds.get(root);\n}\nfunction optionsToId(options) {\n return Object.keys(options).sort().filter(\n (key) => options[key] !== void 0\n ).map((key) => {\n return `${key}_${key === \"root\" ? getRootId(options.root) : options[key]}`;\n }).toString();\n}\nfunction createObserver(options) {\n const id = optionsToId(options);\n let instance = observerMap.get(id);\n if (!instance) {\n const elements = /* @__PURE__ */ new Map();\n let thresholds;\n const observer = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n var _a;\n const inView = entry.isIntersecting && thresholds.some((threshold) => entry.intersectionRatio >= threshold);\n if (options.trackVisibility && typeof entry.isVisible === \"undefined\") {\n entry.isVisible = inView;\n }\n (_a = elements.get(entry.target)) == null ? void 0 : _a.forEach((callback) => {\n callback(inView, entry);\n });\n });\n }, options);\n thresholds = observer.thresholds || (Array.isArray(options.threshold) ? options.threshold : [options.threshold || 0]);\n instance = {\n id,\n observer,\n elements\n };\n observerMap.set(id, instance);\n }\n return instance;\n}\nfunction observe(element, callback, options = {}, fallbackInView = unsupportedValue) {\n if (typeof window.IntersectionObserver === \"undefined\" && fallbackInView !== void 0) {\n const bounds = element.getBoundingClientRect();\n callback(fallbackInView, {\n isIntersecting: fallbackInView,\n target: element,\n intersectionRatio: typeof options.threshold === \"number\" ? options.threshold : 0,\n time: 0,\n boundingClientRect: bounds,\n intersectionRect: bounds,\n rootBounds: bounds\n });\n return () => {\n };\n }\n const { id, observer, elements } = createObserver(options);\n const callbacks = elements.get(element) || [];\n if (!elements.has(element)) {\n elements.set(element, callbacks);\n }\n callbacks.push(callback);\n observer.observe(element);\n return function unobserve() {\n callbacks.splice(callbacks.indexOf(callback), 1);\n if (callbacks.length === 0) {\n elements.delete(element);\n observer.unobserve(element);\n }\n if (elements.size === 0) {\n observer.disconnect();\n observerMap.delete(id);\n }\n };\n}\n\n// src/InView.tsx\nfunction isPlainChildren(props) {\n return typeof props.children !== \"function\";\n}\nvar InView = class extends React.Component {\n constructor(props) {\n super(props);\n __publicField(this, \"node\", null);\n __publicField(this, \"_unobserveCb\", null);\n __publicField(this, \"handleNode\", (node) => {\n if (this.node) {\n this.unobserve();\n if (!node && !this.props.triggerOnce && !this.props.skip) {\n this.setState({ inView: !!this.props.initialInView, entry: void 0 });\n }\n }\n this.node = node ? node : null;\n this.observeNode();\n });\n __publicField(this, \"handleChange\", (inView, entry) => {\n if (inView && this.props.triggerOnce) {\n this.unobserve();\n }\n if (!isPlainChildren(this.props)) {\n this.setState({ inView, entry });\n }\n if (this.props.onChange) {\n this.props.onChange(inView, entry);\n }\n });\n this.state = {\n inView: !!props.initialInView,\n entry: void 0\n };\n }\n componentDidMount() {\n this.unobserve();\n this.observeNode();\n }\n componentDidUpdate(prevProps) {\n if (prevProps.rootMargin !== this.props.rootMargin || prevProps.root !== this.props.root || prevProps.threshold !== this.props.threshold || prevProps.skip !== this.props.skip || prevProps.trackVisibility !== this.props.trackVisibility || prevProps.delay !== this.props.delay) {\n this.unobserve();\n this.observeNode();\n }\n }\n componentWillUnmount() {\n this.unobserve();\n }\n observeNode() {\n if (!this.node || this.props.skip) return;\n const {\n threshold,\n root,\n rootMargin,\n trackVisibility,\n delay,\n fallbackInView\n } = this.props;\n this._unobserveCb = observe(\n this.node,\n this.handleChange,\n {\n threshold,\n root,\n rootMargin,\n // @ts-ignore\n trackVisibility,\n // @ts-ignore\n delay\n },\n fallbackInView\n );\n }\n unobserve() {\n if (this._unobserveCb) {\n this._unobserveCb();\n this._unobserveCb = null;\n }\n }\n render() {\n const { children } = this.props;\n if (typeof children === \"function\") {\n const { inView, entry } = this.state;\n return children({ inView, entry, ref: this.handleNode });\n }\n const {\n as,\n triggerOnce,\n threshold,\n root,\n rootMargin,\n onChange,\n skip,\n trackVisibility,\n delay,\n initialInView,\n fallbackInView,\n ...props\n } = this.props;\n return React.createElement(\n as || \"div\",\n { ref: this.handleNode, ...props },\n children\n );\n }\n};\n\n// src/useInView.tsx\nimport * as React2 from \"react\";\nfunction useInView({\n threshold,\n delay,\n trackVisibility,\n rootMargin,\n root,\n triggerOnce,\n skip,\n initialInView,\n fallbackInView,\n onChange\n} = {}) {\n var _a;\n const [ref, setRef] = React2.useState(null);\n const callback = React2.useRef();\n const [state, setState] = React2.useState({\n inView: !!initialInView,\n entry: void 0\n });\n callback.current = onChange;\n React2.useEffect(\n () => {\n if (skip || !ref) return;\n let unobserve;\n unobserve = observe(\n ref,\n (inView, entry) => {\n setState({\n inView,\n entry\n });\n if (callback.current) callback.current(inView, entry);\n if (entry.isIntersecting && triggerOnce && unobserve) {\n unobserve();\n unobserve = void 0;\n }\n },\n {\n root,\n rootMargin,\n threshold,\n // @ts-ignore\n trackVisibility,\n // @ts-ignore\n delay\n },\n fallbackInView\n );\n return () => {\n if (unobserve) {\n unobserve();\n }\n };\n },\n // We break the rule here, because we aren't including the actual `threshold` variable\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n // If the threshold is an array, convert it to a string, so it won't change between renders.\n Array.isArray(threshold) ? threshold.toString() : threshold,\n ref,\n root,\n rootMargin,\n triggerOnce,\n skip,\n trackVisibility,\n fallbackInView,\n delay\n ]\n );\n const entryTarget = (_a = state.entry) == null ? void 0 : _a.target;\n const previousEntryTarget = React2.useRef();\n if (!ref && entryTarget && !triggerOnce && !skip && previousEntryTarget.current !== entryTarget) {\n previousEntryTarget.current = entryTarget;\n setState({\n inView: !!initialInView,\n entry: void 0\n });\n }\n const result = [setRef, state.inView, state.entry];\n result.ref = result[0];\n result.inView = result[1];\n result.entry = result[2];\n return result;\n}\nexport {\n InView,\n defaultFallbackInView,\n observe,\n useInView\n};\n//# sourceMappingURL=index.mjs.map"],"names":["__defProp","__defNormalProp","obj","key","value","__publicField","observerMap","RootIds","rootId","unsupportedValue","getRootId","root","optionsToId","options","createObserver","id","instance","elements","thresholds","observer","entries","entry","_a","inView","threshold","callback","observe","element","fallbackInView","bounds","callbacks","isPlainChildren","props","InView","React","node","prevProps","rootMargin","trackVisibility","delay","children","as","triggerOnce","onChange","skip","initialInView"],"mappings":"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","x_google_ignoreList":[0]}
@@ -1,19 +0,0 @@
1
- import {Skeleton} from '../ui/skeleton'
2
-
3
- import {TrackingPixel} from './tracking-pixel'
4
-
5
- interface Props {
6
- loadingComponent?: React.ReactNode
7
- isFetchingMore?: boolean
8
- fetchMore: () => void
9
- }
10
-
11
- export function Pagination({loadingComponent, fetchMore}: Props) {
12
- const loadingPlaceholder = loadingComponent ?? (
13
- <Skeleton className="h-10 w-full p-8" />
14
- )
15
-
16
- return (
17
- <TrackingPixel onImpression={fetchMore}>{loadingPlaceholder}</TrackingPixel>
18
- )
19
- }
@@ -1,40 +0,0 @@
1
- import {useCallback} from 'react'
2
-
3
- import {InView} from 'react-intersection-observer'
4
-
5
- interface Props {
6
- children: React.ReactNode
7
- onImpression?: () => void
8
- triggerOnce?: boolean
9
- threshold?: number
10
- className?: string
11
- }
12
-
13
- export const TrackingPixel = ({
14
- children,
15
- onImpression,
16
- triggerOnce = false,
17
- threshold,
18
- className,
19
- }: Props) => {
20
- const onChange = useCallback(
21
- (inView: boolean) => {
22
- if (inView) {
23
- onImpression?.()
24
- }
25
- },
26
- [onImpression]
27
- )
28
-
29
- return (
30
- <InView
31
- as="div"
32
- onChange={onChange}
33
- triggerOnce={triggerOnce}
34
- className={className}
35
- threshold={threshold}
36
- >
37
- {children}
38
- </InView>
39
- )
40
- }