Skip to content

Commit 32b6448

Browse files
fix: React ref methods and hotspot modal closing
- Fix React ref methods (play/stop/etc.) not working by using getViewer() callback pattern to avoid stale closure capturing null viewer - Fix hotspot modal not closing when clicking another timeline point by calling hidePopper() at start of animateToFrame() - Update CodeSandbox examples to version 4.1.3 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b82d8d4 commit 32b6448

File tree

7 files changed

+35
-18
lines changed

7 files changed

+35
-18
lines changed

codesandbox/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12-
"js-cloudimage-360-view": "4.1.2",
12+
"js-cloudimage-360-view": "4.1.3",
1313
"react": "^18.2.0",
1414
"react-dom": "^18.2.0"
1515
},

codesandbox/react/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export default function App() {
171171
<div className="app">
172172
<header>
173173
<h1>CloudImage 360 View - React</h1>
174-
<p>Interactive 360-degree product viewer - Version 4.1.2</p>
174+
<p>Interactive 360-degree product viewer - Version 4.1.3</p>
175175
</header>
176176

177177
<main>
@@ -184,7 +184,7 @@ export default function App() {
184184

185185
<footer>
186186
<a
187-
href="https://github.com/niceplayer/js-cloudimage-360-view"
187+
href="https://github.com/scaleflex/js-cloudimage-360-view"
188188
target="_blank"
189189
rel="noopener noreferrer"
190190
>

codesandbox/vanilla/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
</head>
9999
<body>
100100
<h1>CloudImage 360 View</h1>
101-
<p>Interactive 360-degree product viewer - Version 4.1.2</p>
101+
<p>Interactive 360-degree product viewer - Version 4.1.3</p>
102102

103103
<div class="examples">
104104
<!-- Example 1: Basic with Autoplay and Aspect Ratio -->
@@ -163,7 +163,7 @@ <h2>Programmatic Control</h2>
163163
</div>
164164
</div>
165165

166-
<script src="https://scaleflex.cloudimg.io/v7/plugins/js-cloudimage-360-view/4.1.2/js-cloudimage-360-view.min.js"></script>
166+
<script src="https://scaleflex.cloudimg.io/v7/plugins/js-cloudimage-360-view/4.1.3/js-cloudimage-360-view.min.js"></script>
167167
<script>
168168
// Initialize all declarative viewers
169169
const ci360 = new CI360();

src/ci360.service.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,11 @@ class CI360Viewer {
11921192
* @param {string} [hotspotId] - Optional hotspot ID to show popup for after animation
11931193
*/
11941194
animateToFrame(targetFrame, hotspotId) {
1195+
// Close any existing hotspot popup before navigating
1196+
if (this.hotspotsInstance) {
1197+
this.hotspotsInstance.hidePopper();
1198+
}
1199+
11951200
if (this.isAnimatingToFrame || targetFrame === this.activeImageX) {
11961201
// If already at the target frame, just show the hotspot if requested
11971202
if (targetFrame === this.activeImageX && hotspotId && this.hotspotsInstance && this.hotspotTimelineOnClick) {

src/react/CI360Viewer.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -287,25 +287,27 @@ const CI360ViewerComponent: ForwardRefRenderFunction<
287287
]
288288
);
289289

290-
const { viewer } = useCI360(containerRef, config);
290+
const { getViewer } = useCI360(containerRef, config);
291291

292292
// Expose imperative methods via ref
293+
// Use getViewer() inside methods to always get the current viewer instance,
294+
// avoiding stale closure issues when the viewer initializes after first render
293295
useImperativeHandle(
294296
ref,
295297
() => ({
296-
moveLeft: (steps = 1) => viewer?.moveLeft(false, steps),
297-
moveRight: (steps = 1) => viewer?.moveRight(false, steps),
298-
moveTop: (steps = 1) => viewer?.moveTop(false, steps),
299-
moveBottom: (steps = 1) => viewer?.moveBottom(false, steps),
300-
play: () => viewer?.play(),
301-
stop: () => viewer?.stopAutoplay(),
302-
zoomIn: () => viewer?.toggleZoom(),
303-
zoomOut: () => viewer?.removeZoom(),
298+
moveLeft: (steps = 1) => getViewer()?.moveLeft(false, steps),
299+
moveRight: (steps = 1) => getViewer()?.moveRight(false, steps),
300+
moveTop: (steps = 1) => getViewer()?.moveTop(false, steps),
301+
moveBottom: (steps = 1) => getViewer()?.moveBottom(false, steps),
302+
play: () => getViewer()?.play(),
303+
stop: () => getViewer()?.stopAutoplay(),
304+
zoomIn: () => getViewer()?.toggleZoom(),
305+
zoomOut: () => getViewer()?.removeZoom(),
304306
goToFrame: (frame: number, hotspotId?: string) =>
305-
viewer?.animateToFrame(frame, hotspotId),
306-
getViewer: () => viewer,
307+
getViewer()?.animateToFrame(frame, hotspotId),
308+
getViewer: () => getViewer(),
307309
}),
308-
[viewer]
310+
[getViewer]
309311
);
310312

311313
return (

src/react/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ export interface UseCI360Return {
289289
* Whether the viewer is ready
290290
*/
291291
isReady: boolean;
292+
293+
/**
294+
* Getter function to always get the current viewer instance.
295+
* Use this in callbacks to avoid stale closure issues.
296+
*/
297+
getViewer: () => CI360ViewerInstance | null;
292298
}
293299

294300
/**

src/react/useCI360.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef, useState, useId, type RefObject } from 'react';
1+
import { useEffect, useRef, useState, useId, useCallback, type RefObject } from 'react';
22
import type {
33
CI360Config,
44
CI360ViewerInstance,
@@ -111,9 +111,13 @@ export function useCI360(
111111
uniqueId,
112112
]);
113113

114+
// Stable getter to always return current viewer (avoids stale closures)
115+
const getViewer = useCallback(() => viewerRef.current, []);
116+
114117
return {
115118
viewer: viewerRef.current,
116119
isReady,
120+
getViewer,
117121
};
118122
}
119123

0 commit comments

Comments
 (0)