js-cloudimage-360-view 4.9.3 → 4.9.5

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.
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ke=require("react/jsx-runtime"),n=require("react");let R=null;function I(f,t){const[v,y]=n.useState(!1),a=n.useRef(null),l=n.useRef(null),m=n.useId();n.useEffect(()=>{if(typeof window>"u"||!f.current||t.autoInit===!1)return;let u=!0;const s=f.current;return(async()=>{try{if(R||(R=(await Promise.resolve().then(()=>require("./ci360-B2r9u7br.js"))).default),!s||!u)return;s.id||(s.id=`ci360-${m.replace(/:/g,"")}`);const i={...t,onReady:c=>{var p;u&&(y(!0),(p=t.onReady)==null||p.call(t,c))}};l.current=new R,a.current=l.current.init(s,i)}catch(i){console.error("Failed to initialize CI360 viewer:",i)}})(),()=>{if(u=!1,a.current){try{a.current.destroy()}catch{}a.current=null}l.current=null,y(!1)}},[t.folder,t.filenameX,t.filenameY,t.filenameGrid,t.imageListX,t.imageListY,t.imageListGrid,t.amountX,t.amountY,t.hotspots,t.theme,t.aspectRatio,t.cropAspectRatio,t.cropGravity,m]);const C=n.useCallback(()=>a.current,[]);return{viewer:a.current,isReady:v,getViewer:C}}const Xe=(f,t)=>{const{id:v,className:y,style:a,folder:l,apiVersion:m,filenameX:C,filenameY:u,filenameGrid:s,imageListX:w,imageListY:i,imageListGrid:c,indexZeroBase:p,amountX:z,amountY:g,draggable:S,swipeable:V,keys:L,keysReverse:T,autoplay:O,autoplayBehavior:P,playOnce:Z,speed:b,autoplayReverse:A,dragSpeed:k,dragReverse:X,stopAtEdges:Y,stopAtEdgesX:E,stopAtEdgesY:F,inertia:G,fullscreen:q,magnifier:x,pointerZoom:B,pinchZoom:D,zoomMax:H,zoomStep:M,zoomControls:j,zoomControlsPosition:N,scrollHint:$,bottomCircle:J,bottomCircleOffset:K,initialIconShown:Q,hide360Logo:U,logoSrc:W,imageInfo:_,hints:ee,theme:oe,markerTheme:te,brandColor:re,ciToken:ne,ciFilters:ae,ciTransformation:se,cropAspectRatio:ie,cropGravity:le,lazyload:ue,hotspots:me,hotspotTrigger:ce,hotspotTimelineOnClick:pe,aspectRatio:de,onReady:fe,onLoad:ye,onSpin:Ce,onAutoplayStart:ve,onAutoplayStop:we,onFullscreenOpen:Re,onFullscreenClose:Ie,onZoomIn:he,onZoomOut:ze,onDragStart:ge,onDragEnd:Se,onHotspotOpen:Ve,onHotspotClose:Le,onProductClick:Te,onNavigate:Oe,onError:Pe,...be}=f,Ze=n.useRef(null),Ae=n.useMemo(()=>({folder:l,apiVersion:m,filenameX:C,filenameY:u,filenameGrid:s,imageListX:w,imageListY:i,imageListGrid:c,indexZeroBase:p,amountX:z,amountY:g,draggable:S,swipeable:V,keys:L,keysReverse:T,autoplay:O,autoplayBehavior:P,playOnce:Z,speed:b,autoplayReverse:A,dragSpeed:k,dragReverse:X,stopAtEdges:Y,stopAtEdgesX:E,stopAtEdgesY:F,inertia:G,fullscreen:q,magnifier:x,pointerZoom:B,pinchZoom:D,zoomMax:H,zoomStep:M,zoomControls:j,zoomControlsPosition:N,scrollHint:$,bottomCircle:J,bottomCircleOffset:K,initialIconShown:Q,hide360Logo:U,logoSrc:W,imageInfo:_,hints:ee,theme:oe,markerTheme:te,brandColor:re,ciToken:ne,ciFilters:ae,ciTransformation:se,cropAspectRatio:ie,cropGravity:le,lazyload:ue,hotspots:me,hotspotTrigger:ce,hotspotTimelineOnClick:pe,aspectRatio:de,onReady:fe,onLoad:ye,onSpin:Ce,onAutoplayStart:ve,onAutoplayStop:we,onFullscreenOpen:Re,onFullscreenClose:Ie,onZoomIn:he,onZoomOut:ze,onDragStart:ge,onDragEnd:Se,onHotspotOpen:Ve,onHotspotClose:Le,onProductClick:Te,onNavigate:Oe,onError:Pe}),[l,m,C,u,s,w,i,c,p,z,g,S,V,L,T,O,P,Z,b,A,k,X,Y,E,F,G,q,x,B,D,H,M,j,N,$,J,K,Q,U,W,_,ee,oe,te,re,ne,ae,se,ie,le,ue,me,ce,pe,de,fe,ye,Ce,ve,we,Re,Ie,he,ze,ge,Se,Ve,Le,Te,Oe,Pe]),{getViewer:r}=I(Ze,Ae);return n.useImperativeHandle(t,()=>({moveLeft:(e=1)=>{var o;return(o=r())==null?void 0:o.moveLeft(!1,e)},moveRight:(e=1)=>{var o;return(o=r())==null?void 0:o.moveRight(!1,e)},moveTop:(e=1)=>{var o;return(o=r())==null?void 0:o.moveTop(!1,e)},moveBottom:(e=1)=>{var o;return(o=r())==null?void 0:o.moveBottom(!1,e)},play:()=>{var e;return(e=r())==null?void 0:e.play()},stop:()=>{var e;return(e=r())==null?void 0:e.stopAutoplay()},zoomIn:()=>{var e,o;return(o=(e=r())==null?void 0:e.zoomPan)==null?void 0:o.zoomIn()},zoomOut:()=>{var e,o;return(o=(e=r())==null?void 0:e.zoomPan)==null?void 0:o.zoomOut()},resetZoom:()=>{var e,o;return(o=(e=r())==null?void 0:e.zoomPan)==null?void 0:o.resetZoom()},setZoom:e=>{var o,d;return(d=(o=r())==null?void 0:o.zoomPan)==null?void 0:d.setZoom(e)},goToFrame:(e,o)=>{var d;return(d=r())==null?void 0:d.animateToFrame(e,o)},getViewer:()=>r()}),[r]),ke.jsx("div",{ref:Ze,id:v,className:y,style:a,...be})},h=n.forwardRef(Xe);h.displayName="CI360Viewer";exports.CI360Viewer=h;exports.CI360ViewerDefault=h;exports.useCI360=I;exports.useCI360Default=I;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Xe=require("react/jsx-runtime"),n=require("react");let R=null;function I(f,t){const[v,y]=n.useState(!1),a=n.useRef(null),l=n.useRef(null),m=n.useId();n.useEffect(()=>{if(typeof window>"u"||!f.current||t.autoInit===!1)return;let u=!0;const s=f.current;return(async()=>{try{if(R||(R=(await Promise.resolve().then(()=>require("./ci360-NRnvVDl6.js"))).default),!s||!u)return;s.id||(s.id=`ci360-${m.replace(/:/g,"")}`);const i={...t,onReady:c=>{var p;u&&(y(!0),(p=t.onReady)==null||p.call(t,c))}};l.current=new R,a.current=l.current.init(s,i)}catch(i){console.error("Failed to initialize CI360 viewer:",i)}})(),()=>{if(u=!1,a.current){try{a.current.destroy()}catch{}a.current=null}l.current=null,y(!1)}},[t.folder,t.filenameX,t.filenameY,t.filenameGrid,t.imageListX,t.imageListY,t.imageListGrid,t.amountX,t.amountY,t.hotspots,t.theme,t.aspectRatio,t.cropAspectRatio,t.cropGravity,m]);const C=n.useCallback(()=>a.current,[]);return{viewer:a.current,isReady:v,getViewer:C}}const Ye=(f,t)=>{const{id:v,className:y,style:a,folder:l,apiVersion:m,filenameX:C,filenameY:u,filenameGrid:s,imageListX:w,imageListY:i,imageListGrid:c,indexZeroBase:p,amountX:z,amountY:g,draggable:S,swipeable:V,keys:L,keysReverse:O,autoplay:T,autoplayBehavior:P,playOnce:Z,speed:b,autoplayReverse:k,dragSpeed:A,dragReverse:X,stopAtEdges:Y,stopAtEdgesX:E,stopAtEdgesY:F,inertia:G,fullscreen:q,magnifier:x,pointerZoom:B,pinchZoom:D,zoomMax:H,zoomStep:M,zoomControls:j,zoomControlsPosition:N,scrollHint:$,bottomCircle:J,bottomCircleOffset:K,initialIconShown:Q,hide360Logo:U,logoSrc:W,imageInfo:_,hints:ee,theme:oe,markerTheme:te,brandColor:re,ciToken:ne,ciFilters:ae,ciTransformation:se,cropAspectRatio:ie,cropGravity:le,lazyload:ue,hotspots:me,hotspotTrigger:ce,hotspotTimelineOnClick:pe,initOnClick:de,aspectRatio:fe,onReady:ye,onLoad:Ce,onSpin:ve,onAutoplayStart:we,onAutoplayStop:Re,onFullscreenOpen:Ie,onFullscreenClose:he,onZoomIn:ze,onZoomOut:ge,onDragStart:Se,onDragEnd:Ve,onHotspotOpen:Le,onHotspotClose:Oe,onProductClick:Te,onNavigate:Pe,onError:Ze,...ke}=f,be=n.useRef(null),Ae=n.useMemo(()=>({folder:l,apiVersion:m,filenameX:C,filenameY:u,filenameGrid:s,imageListX:w,imageListY:i,imageListGrid:c,indexZeroBase:p,amountX:z,amountY:g,draggable:S,swipeable:V,keys:L,keysReverse:O,autoplay:T,autoplayBehavior:P,playOnce:Z,speed:b,autoplayReverse:k,dragSpeed:A,dragReverse:X,stopAtEdges:Y,stopAtEdgesX:E,stopAtEdgesY:F,inertia:G,fullscreen:q,magnifier:x,pointerZoom:B,pinchZoom:D,zoomMax:H,zoomStep:M,zoomControls:j,zoomControlsPosition:N,scrollHint:$,bottomCircle:J,bottomCircleOffset:K,initialIconShown:Q,hide360Logo:U,logoSrc:W,imageInfo:_,hints:ee,theme:oe,markerTheme:te,brandColor:re,ciToken:ne,ciFilters:ae,ciTransformation:se,cropAspectRatio:ie,cropGravity:le,lazyload:ue,hotspots:me,hotspotTrigger:ce,hotspotTimelineOnClick:pe,initOnClick:de,aspectRatio:fe,onReady:ye,onLoad:Ce,onSpin:ve,onAutoplayStart:we,onAutoplayStop:Re,onFullscreenOpen:Ie,onFullscreenClose:he,onZoomIn:ze,onZoomOut:ge,onDragStart:Se,onDragEnd:Ve,onHotspotOpen:Le,onHotspotClose:Oe,onProductClick:Te,onNavigate:Pe,onError:Ze}),[l,m,C,u,s,w,i,c,p,z,g,S,V,L,O,T,P,Z,b,k,A,X,Y,E,F,G,q,x,B,D,H,M,j,N,$,J,K,Q,U,W,_,ee,oe,te,re,ne,ae,se,ie,le,ue,me,ce,pe,de,fe,ye,Ce,ve,we,Re,Ie,he,ze,ge,Se,Ve,Le,Oe,Te,Pe,Ze]),{getViewer:r}=I(be,Ae);return n.useImperativeHandle(t,()=>({moveLeft:(e=1)=>{var o;return(o=r())==null?void 0:o.moveLeft(!1,e)},moveRight:(e=1)=>{var o;return(o=r())==null?void 0:o.moveRight(!1,e)},moveTop:(e=1)=>{var o;return(o=r())==null?void 0:o.moveTop(!1,e)},moveBottom:(e=1)=>{var o;return(o=r())==null?void 0:o.moveBottom(!1,e)},play:()=>{var e;return(e=r())==null?void 0:e.play()},stop:()=>{var e;return(e=r())==null?void 0:e.stopAutoplay()},zoomIn:()=>{var e,o;return(o=(e=r())==null?void 0:e.zoomPan)==null?void 0:o.zoomIn()},zoomOut:()=>{var e,o;return(o=(e=r())==null?void 0:e.zoomPan)==null?void 0:o.zoomOut()},resetZoom:()=>{var e,o;return(o=(e=r())==null?void 0:e.zoomPan)==null?void 0:o.resetZoom()},setZoom:e=>{var o,d;return(d=(o=r())==null?void 0:o.zoomPan)==null?void 0:d.setZoom(e)},goToFrame:(e,o)=>{var d;return(d=r())==null?void 0:d.animateToFrame(e,o)},getViewer:()=>r()}),[r]),Xe.jsx("div",{ref:be,id:v,className:y,style:a,...ke})},h=n.forwardRef(Ye);h.displayName="CI360Viewer";exports.CI360Viewer=h;exports.CI360ViewerDefault=h;exports.useCI360=I;exports.useCI360Default=I;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/react/useCI360.ts","../../src/react/CI360Viewer.tsx"],"sourcesContent":["import { useEffect, useRef, useState, useId, useCallback, type RefObject } from 'react';\nimport type {\n CI360Config,\n CI360ViewerInstance,\n UseCI360Return,\n UseCI360Options,\n} from './types';\n\n// Import CI360 class dynamically to avoid SSR issues\nlet CI360Class: any = null;\n\n/**\n * Custom hook for integrating CI360 viewer with React\n *\n * @param containerRef - React ref to the container element\n * @param config - CI360 configuration options\n * @returns Object containing viewer instance and ready state\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { viewer, isReady } = useCI360(containerRef, {\n * folder: 'https://example.com/images/',\n * filenameX: 'image-{index}.jpg',\n * amountX: 36,\n * });\n *\n * return <div ref={containerRef} />;\n * }\n * ```\n */\nexport function useCI360(\n containerRef: RefObject<HTMLDivElement | null>,\n config: UseCI360Options\n): UseCI360Return {\n const [isReady, setIsReady] = useState(false);\n const viewerRef = useRef<CI360ViewerInstance | null>(null);\n const ci360Ref = useRef<any>(null);\n const uniqueId = useId();\n\n // Initialize viewer\n useEffect(() => {\n // SSR guard\n if (typeof window === 'undefined') return;\n if (!containerRef.current) return;\n if (config.autoInit === false) return;\n\n let isMounted = true;\n const container = containerRef.current;\n\n const initViewer = async () => {\n try {\n // Dynamically import CI360 to avoid SSR issues\n if (!CI360Class) {\n const module = await import('../ci360');\n CI360Class = module.default;\n }\n\n if (!container || !isMounted) return;\n\n // Set a unique ID on the container if not present\n if (!container.id) {\n container.id = `ci360-${uniqueId.replace(/:/g, '')}`;\n }\n\n // Wrap user callbacks to update React state\n const wrappedConfig: CI360Config = {\n ...config,\n onReady: (data) => {\n if (isMounted) {\n setIsReady(true);\n config.onReady?.(data);\n }\n },\n };\n\n // Create CI360 instance and initialize viewer\n ci360Ref.current = new CI360Class();\n viewerRef.current = ci360Ref.current.init(container, wrappedConfig);\n } catch (error) {\n console.error('Failed to initialize CI360 viewer:', error);\n }\n };\n\n initViewer();\n\n // Cleanup on unmount or when dependencies change\n return () => {\n isMounted = false;\n if (viewerRef.current) {\n try {\n viewerRef.current.destroy();\n } catch (e) {\n // Ignore errors during cleanup - element may already be detached\n }\n viewerRef.current = null;\n }\n ci360Ref.current = null;\n setIsReady(false);\n };\n }, [\n config.folder,\n config.filenameX,\n config.filenameY,\n config.filenameGrid,\n config.imageListX,\n config.imageListY,\n config.imageListGrid,\n config.amountX,\n config.amountY,\n config.hotspots,\n config.theme,\n config.aspectRatio,\n config.cropAspectRatio,\n config.cropGravity,\n uniqueId,\n ]);\n\n // Stable getter to always return current viewer (avoids stale closures)\n const getViewer = useCallback(() => viewerRef.current, []);\n\n return {\n viewer: viewerRef.current,\n isReady,\n getViewer,\n };\n}\n\nexport default useCI360;\n","import {\n useRef,\n useImperativeHandle,\n forwardRef,\n useMemo,\n type ForwardRefRenderFunction,\n} from 'react';\nimport { useCI360 } from './useCI360';\nimport type {\n CI360ViewerProps,\n CI360ViewerRef,\n CI360Config,\n} from './types';\n\n/**\n * CI360Viewer React Component\n *\n * A declarative React wrapper for the CI360 360-degree image viewer.\n *\n * @example\n * ```tsx\n * import { CI360Viewer } from 'js-cloudimage-360-view/react';\n * import 'js-cloudimage-360-view/css';\n *\n * function ProductView() {\n * return (\n * <CI360Viewer\n * folder=\"https://example.com/images/\"\n * filenameX=\"product-{index}.jpg\"\n * amountX={36}\n * autoplay\n * fullscreen\n * />\n * );\n * }\n * ```\n *\n * @example With ref for imperative control\n * ```tsx\n * import { useRef } from 'react';\n * import { CI360Viewer, CI360ViewerRef } from 'js-cloudimage-360-view/react';\n *\n * function ProductView() {\n * const viewerRef = useRef<CI360ViewerRef>(null);\n *\n * return (\n * <>\n * <CI360Viewer\n * ref={viewerRef}\n * folder=\"https://example.com/images/\"\n * filenameX=\"{index}.jpg\"\n * amountX={36}\n * onSpin={(e) => console.log(`Frame: ${e.activeImageX}`)}\n * />\n * <button onClick={() => viewerRef.current?.play()}>Play</button>\n * <button onClick={() => viewerRef.current?.stop()}>Stop</button>\n * </>\n * );\n * }\n * ```\n */\nconst CI360ViewerComponent: ForwardRefRenderFunction<\n CI360ViewerRef,\n CI360ViewerProps\n> = (props, ref) => {\n const {\n // Container props\n id,\n className,\n style,\n\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n\n ...restProps\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Memoize config to prevent unnecessary re-initializations\n const config = useMemo<CI360Config>(\n () => ({\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n }),\n [\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n ]\n );\n\n const { getViewer } = useCI360(containerRef, config);\n\n // Expose imperative methods via ref\n // Use getViewer() inside methods to always get the current viewer instance,\n // avoiding stale closure issues when the viewer initializes after first render\n useImperativeHandle(\n ref,\n () => ({\n moveLeft: (steps = 1) => getViewer()?.moveLeft(false, steps),\n moveRight: (steps = 1) => getViewer()?.moveRight(false, steps),\n moveTop: (steps = 1) => getViewer()?.moveTop(false, steps),\n moveBottom: (steps = 1) => getViewer()?.moveBottom(false, steps),\n play: () => getViewer()?.play(),\n stop: () => getViewer()?.stopAutoplay(),\n zoomIn: () => getViewer()?.zoomPan?.zoomIn(),\n zoomOut: () => getViewer()?.zoomPan?.zoomOut(),\n resetZoom: () => getViewer()?.zoomPan?.resetZoom(),\n setZoom: (level: number) => getViewer()?.zoomPan?.setZoom(level),\n goToFrame: (frame: number, hotspotId?: string) =>\n getViewer()?.animateToFrame(frame, hotspotId),\n getViewer: () => getViewer(),\n }),\n [getViewer]\n );\n\n return (\n <div\n ref={containerRef}\n id={id}\n className={className}\n style={style}\n {...restProps}\n />\n );\n};\n\nexport const CI360Viewer = forwardRef(CI360ViewerComponent);\nCI360Viewer.displayName = 'CI360Viewer';\n\nexport default CI360Viewer;\n"],"names":["CI360Class","useCI360","containerRef","config","isReady","setIsReady","useState","viewerRef","useRef","ci360Ref","uniqueId","useId","useEffect","isMounted","container","wrappedConfig","data","_a","error","getViewer","useCallback","CI360ViewerComponent","props","ref","id","className","style","folder","apiVersion","filenameX","filenameY","filenameGrid","imageListX","imageListY","imageListGrid","indexZeroBase","amountX","amountY","draggable","swipeable","keys","keysReverse","autoplay","autoplayBehavior","playOnce","speed","autoplayReverse","dragSpeed","dragReverse","stopAtEdges","stopAtEdgesX","stopAtEdgesY","inertia","fullscreen","magnifier","pointerZoom","pinchZoom","zoomMax","zoomStep","zoomControls","zoomControlsPosition","scrollHint","bottomCircle","bottomCircleOffset","initialIconShown","hide360Logo","logoSrc","imageInfo","hints","theme","markerTheme","brandColor","ciToken","ciFilters","ciTransformation","cropAspectRatio","cropGravity","lazyload","hotspots","hotspotTrigger","hotspotTimelineOnClick","aspectRatio","onReady","onLoad","onSpin","onAutoplayStart","onAutoplayStop","onFullscreenOpen","onFullscreenClose","onZoomIn","onZoomOut","onDragStart","onDragEnd","onHotspotOpen","onHotspotClose","onProductClick","onNavigate","onError","restProps","useMemo","useImperativeHandle","steps","_b","level","frame","hotspotId","jsx","CI360Viewer","forwardRef"],"mappings":"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,EACF,aAAAC,EACA,aAAAC,EACE,QAAAC,EAGA,WAAAC,EACA,UAAAC,EACA,YAAAC,EACA,UAAAC,EACA,QAAAC,EACA,SAAAC,EACA,aAAAC,EACA,qBAAAC,EACA,WAAAC,EACA,aAAAC,EACA,mBAAAC,EACA,iBAAAC,EACA,YAAAC,EACA,QAAAC,EACA,UAAAC,EACA,MAAAC,GACA,MAAAC,GACA,YAAAC,GACA,WAAAC,GAGA,QAAAC,GACA,UAAAC,GACA,iBAAAC,GACA,gBAAAC,GACA,YAAAC,GAGA,SAAAC,GAGA,SAAAC,GACA,eAAAC,GACA,uBAAAC,GAGA,YAAAC,GAGA,QAAAC,GACA,OAAAC,GACA,OAAAC,GACA,gBAAAC,GACA,eAAAC,GACA,iBAAAC,GACA,kBAAAC,GACA,SAAAC,GACA,UAAAC,GACA,YAAAC,GACA,UAAAC,GACA,cAAAC,GACA,eAAAC,GACA,eAAAC,GACA,WAAAC,GACA,QAAAC,EAAA,GAEF,CAEEtE,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EAGAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACFC,EACAC,EACEC,EAGAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,GACAC,GACAC,GACAC,GAGAC,GACAC,GACAC,GACAC,GACAC,GAGAC,GAGAC,GACAC,GACAC,GAGAC,GAGAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,EAAA,CACF,EAGI,CAAE,UAAA9E,CAAA,EAAclB,EAASC,GAAcC,EAAM,EAKnDiG,OAAAA,EAAAA,oBACE7E,EACA,KAAO,CACL,SAAU,CAAC8E,EAAQ,IAAA,OAAM,OAAApF,EAAAE,MAAA,YAAAF,EAAa,SAAS,GAAOoF,IACtD,UAAW,CAACA,EAAQ,IAAA,OAAM,OAAApF,EAAAE,MAAA,YAAAF,EAAa,UAAU,GAAOoF,IACxD,QAAS,CAACA,EAAQ,IAAA,OAAM,OAAApF,EAAAE,MAAA,YAAAF,EAAa,QAAQ,GAAOoF,IACpD,WAAY,CAACA,EAAQ,IAAA,OAAM,OAAApF,EAAAE,MAAA,YAAAF,EAAa,WAAW,GAAOoF,IAC1D,KAAM,IAAA,OAAM,OAAApF,EAAAE,EAAA,IAAA,YAAAF,EAAa,QACzB,KAAM,IAAA,OAAM,OAAAA,EAAAE,EAAA,IAAA,YAAAF,EAAa,gBACzB,OAAQ,IAAA,SAAM,OAAAqF,GAAArF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAqF,EAAsB,UACpC,QAAS,IAAA,SAAM,OAAAA,GAAArF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAqF,EAAsB,WACrC,UAAW,IAAA,SAAM,OAAAA,GAAArF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAqF,EAAsB,aACvC,QAAUC,GAAA,SAAkB,OAAAD,GAAArF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAqF,EAAsB,QAAQC,IAC1D,UAAW,CAACC,EAAeC,IAAA,OACzB,OAAAxF,EAAAE,MAAA,YAAAF,EAAa,eAAeuF,EAAOC,IACrC,UAAW,IAAMtF,EAAA,CAAU,GAE7B,CAACA,CAAS,CAAA,EAIVuF,GAAAA,IAAC,MAAA,CACC,IAAKxG,GACL,GAAAsB,EACA,UAAAC,EACA,MAAAC,EACC,GAAGwE,EAAA,CAAA,CAGV,EAEaS,EAAcC,EAAAA,WAAWvF,EAAoB,EAC1DsF,EAAY,YAAc"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/react/useCI360.ts","../../src/react/CI360Viewer.tsx"],"sourcesContent":["import { useEffect, useRef, useState, useId, useCallback, type RefObject } from 'react';\nimport type {\n CI360Config,\n CI360ViewerInstance,\n UseCI360Return,\n UseCI360Options,\n} from './types';\n\n// Import CI360 class dynamically to avoid SSR issues\nlet CI360Class: any = null;\n\n/**\n * Custom hook for integrating CI360 viewer with React\n *\n * @param containerRef - React ref to the container element\n * @param config - CI360 configuration options\n * @returns Object containing viewer instance and ready state\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { viewer, isReady } = useCI360(containerRef, {\n * folder: 'https://example.com/images/',\n * filenameX: 'image-{index}.jpg',\n * amountX: 36,\n * });\n *\n * return <div ref={containerRef} />;\n * }\n * ```\n */\nexport function useCI360(\n containerRef: RefObject<HTMLDivElement | null>,\n config: UseCI360Options\n): UseCI360Return {\n const [isReady, setIsReady] = useState(false);\n const viewerRef = useRef<CI360ViewerInstance | null>(null);\n const ci360Ref = useRef<any>(null);\n const uniqueId = useId();\n\n // Initialize viewer\n useEffect(() => {\n // SSR guard\n if (typeof window === 'undefined') return;\n if (!containerRef.current) return;\n if (config.autoInit === false) return;\n\n let isMounted = true;\n const container = containerRef.current;\n\n const initViewer = async () => {\n try {\n // Dynamically import CI360 to avoid SSR issues\n if (!CI360Class) {\n const module = await import('../ci360');\n CI360Class = module.default;\n }\n\n if (!container || !isMounted) return;\n\n // Set a unique ID on the container if not present\n if (!container.id) {\n container.id = `ci360-${uniqueId.replace(/:/g, '')}`;\n }\n\n // Wrap user callbacks to update React state\n const wrappedConfig: CI360Config = {\n ...config,\n onReady: (data) => {\n if (isMounted) {\n setIsReady(true);\n config.onReady?.(data);\n }\n },\n };\n\n // Create CI360 instance and initialize viewer\n ci360Ref.current = new CI360Class();\n viewerRef.current = ci360Ref.current.init(container, wrappedConfig);\n } catch (error) {\n console.error('Failed to initialize CI360 viewer:', error);\n }\n };\n\n initViewer();\n\n // Cleanup on unmount or when dependencies change\n return () => {\n isMounted = false;\n if (viewerRef.current) {\n try {\n viewerRef.current.destroy();\n } catch (e) {\n // Ignore errors during cleanup - element may already be detached\n }\n viewerRef.current = null;\n }\n ci360Ref.current = null;\n setIsReady(false);\n };\n }, [\n config.folder,\n config.filenameX,\n config.filenameY,\n config.filenameGrid,\n config.imageListX,\n config.imageListY,\n config.imageListGrid,\n config.amountX,\n config.amountY,\n config.hotspots,\n config.theme,\n config.aspectRatio,\n config.cropAspectRatio,\n config.cropGravity,\n uniqueId,\n ]);\n\n // Stable getter to always return current viewer (avoids stale closures)\n const getViewer = useCallback(() => viewerRef.current, []);\n\n return {\n viewer: viewerRef.current,\n isReady,\n getViewer,\n };\n}\n\nexport default useCI360;\n","import {\n useRef,\n useImperativeHandle,\n forwardRef,\n useMemo,\n type ForwardRefRenderFunction,\n} from 'react';\nimport { useCI360 } from './useCI360';\nimport type {\n CI360ViewerProps,\n CI360ViewerRef,\n CI360Config,\n} from './types';\n\n/**\n * CI360Viewer React Component\n *\n * A declarative React wrapper for the CI360 360-degree image viewer.\n *\n * @example\n * ```tsx\n * import { CI360Viewer } from 'js-cloudimage-360-view/react';\n * import 'js-cloudimage-360-view/css';\n *\n * function ProductView() {\n * return (\n * <CI360Viewer\n * folder=\"https://example.com/images/\"\n * filenameX=\"product-{index}.jpg\"\n * amountX={36}\n * autoplay\n * fullscreen\n * />\n * );\n * }\n * ```\n *\n * @example With ref for imperative control\n * ```tsx\n * import { useRef } from 'react';\n * import { CI360Viewer, CI360ViewerRef } from 'js-cloudimage-360-view/react';\n *\n * function ProductView() {\n * const viewerRef = useRef<CI360ViewerRef>(null);\n *\n * return (\n * <>\n * <CI360Viewer\n * ref={viewerRef}\n * folder=\"https://example.com/images/\"\n * filenameX=\"{index}.jpg\"\n * amountX={36}\n * onSpin={(e) => console.log(`Frame: ${e.activeImageX}`)}\n * />\n * <button onClick={() => viewerRef.current?.play()}>Play</button>\n * <button onClick={() => viewerRef.current?.stop()}>Stop</button>\n * </>\n * );\n * }\n * ```\n */\nconst CI360ViewerComponent: ForwardRefRenderFunction<\n CI360ViewerRef,\n CI360ViewerProps\n> = (props, ref) => {\n const {\n // Container props\n id,\n className,\n style,\n\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n initOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n\n ...restProps\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Memoize config to prevent unnecessary re-initializations\n const config = useMemo<CI360Config>(\n () => ({\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n initOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n }),\n [\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n initOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n ]\n );\n\n const { getViewer } = useCI360(containerRef, config);\n\n // Expose imperative methods via ref\n // Use getViewer() inside methods to always get the current viewer instance,\n // avoiding stale closure issues when the viewer initializes after first render\n useImperativeHandle(\n ref,\n () => ({\n moveLeft: (steps = 1) => getViewer()?.moveLeft(false, steps),\n moveRight: (steps = 1) => getViewer()?.moveRight(false, steps),\n moveTop: (steps = 1) => getViewer()?.moveTop(false, steps),\n moveBottom: (steps = 1) => getViewer()?.moveBottom(false, steps),\n play: () => getViewer()?.play(),\n stop: () => getViewer()?.stopAutoplay(),\n zoomIn: () => getViewer()?.zoomPan?.zoomIn(),\n zoomOut: () => getViewer()?.zoomPan?.zoomOut(),\n resetZoom: () => getViewer()?.zoomPan?.resetZoom(),\n setZoom: (level: number) => getViewer()?.zoomPan?.setZoom(level),\n goToFrame: (frame: number, hotspotId?: string) =>\n getViewer()?.animateToFrame(frame, hotspotId),\n getViewer: () => getViewer(),\n }),\n [getViewer]\n );\n\n return (\n <div\n ref={containerRef}\n id={id}\n className={className}\n style={style}\n {...restProps}\n />\n );\n};\n\nexport const CI360Viewer = forwardRef(CI360ViewerComponent);\nCI360Viewer.displayName = 'CI360Viewer';\n\nexport default CI360Viewer;\n"],"names":["CI360Class","useCI360","containerRef","config","isReady","setIsReady","useState","viewerRef","useRef","ci360Ref","uniqueId","useId","useEffect","isMounted","container","wrappedConfig","data","_a","error","getViewer","useCallback","CI360ViewerComponent","props","ref","id","className","style","folder","apiVersion","filenameX","filenameY","filenameGrid","imageListX","imageListY","imageListGrid","indexZeroBase","amountX","amountY","draggable","swipeable","keys","keysReverse","autoplay","autoplayBehavior","playOnce","speed","autoplayReverse","dragSpeed","dragReverse","stopAtEdges","stopAtEdgesX","stopAtEdgesY","inertia","fullscreen","magnifier","pointerZoom","pinchZoom","zoomMax","zoomStep","zoomControls","zoomControlsPosition","scrollHint","bottomCircle","bottomCircleOffset","initialIconShown","hide360Logo","logoSrc","imageInfo","hints","theme","markerTheme","brandColor","ciToken","ciFilters","ciTransformation","cropAspectRatio","cropGravity","lazyload","hotspots","hotspotTrigger","hotspotTimelineOnClick","initOnClick","aspectRatio","onReady","onLoad","onSpin","onAutoplayStart","onAutoplayStop","onFullscreenOpen","onFullscreenClose","onZoomIn","onZoomOut","onDragStart","onDragEnd","onHotspotOpen","onHotspotClose","onProductClick","onNavigate","onError","restProps","useMemo","useImperativeHandle","steps","_b","level","frame","hotspotId","jsx","CI360Viewer","forwardRef"],"mappings":"yIASA,IAAIA,EAAkB,KAuBf,SAASC,EACdC,EACAC,EACgB,CAChB,KAAM,CAACC,EAASC,CAAU,EAAIC,EAAAA,SAAS,EAAK,EACtCC,EAAYC,EAAAA,OAAmC,IAAI,EACnDC,EAAWD,EAAAA,OAAY,IAAI,EAC3BE,EAAWC,EAAAA,MAAA,EAGjBC,EAAAA,UAAU,IAAM,CAId,GAFI,OAAO,OAAW,KAClB,CAACV,EAAa,SACdC,EAAO,WAAa,GAAO,OAE/B,IAAIU,EAAY,GAChB,MAAMC,EAAYZ,EAAa,QAoC/B,OAlCmB,SAAY,CAC7B,GAAI,CAOF,GALKF,IAEHA,GADe,MAAM,QAAA,QAAA,EAAA,KAAA,IAAA,QAAO,qBAAU,CAAA,GAClB,SAGlB,CAACc,GAAa,CAACD,EAAW,OAGzBC,EAAU,KACbA,EAAU,GAAK,SAASJ,EAAS,QAAQ,KAAM,EAAE,CAAC,IAIpD,MAAMK,EAA6B,CACjC,GAAGZ,EACH,QAAUa,GAAS,OACbH,IACFR,EAAW,EAAI,GACfY,EAAAd,EAAO,UAAP,MAAAc,EAAA,KAAAd,EAAiBa,GAErB,CAAA,EAIFP,EAAS,QAAU,IAAIT,EACvBO,EAAU,QAAUE,EAAS,QAAQ,KAAKK,EAAWC,CAAa,CACpE,OAASG,EAAO,CACd,QAAQ,MAAM,qCAAsCA,CAAK,CAC3D,CACF,GAEA,EAGO,IAAM,CAEX,GADAL,EAAY,GACRN,EAAU,QAAS,CACrB,GAAI,CACFA,EAAU,QAAQ,QAAA,CACpB,MAAY,CAEZ,CACAA,EAAU,QAAU,IACtB,CACAE,EAAS,QAAU,KACnBJ,EAAW,EAAK,CAClB,CACF,EAAG,CACDF,EAAO,OACPA,EAAO,UACPA,EAAO,UACPA,EAAO,aACPA,EAAO,WACPA,EAAO,WACPA,EAAO,cACPA,EAAO,QACPA,EAAO,QACPA,EAAO,SACPA,EAAO,MACPA,EAAO,YACPA,EAAO,gBACPA,EAAO,YACPO,CAAA,CACD,EAGD,MAAMS,EAAYC,EAAAA,YAAY,IAAMb,EAAU,QAAS,CAAA,CAAE,EAEzD,MAAO,CACL,OAAQA,EAAU,QAClB,QAAAH,EACA,UAAAe,CAAA,CAEJ,CClEA,MAAME,GAGF,CAACC,EAAOC,IAAQ,CAClB,KAAM,CAEJ,GAAAC,EACA,UAAAC,EACA,MAAAC,EAGA,OAAAC,EACA,WAAAC,EACA,UAAAC,EACA,UAAAC,EACA,aAAAC,EACA,WAAAC,EACA,WAAAC,EACA,cAAAC,EACA,cAAAC,EACA,QAAAC,EACA,QAAAC,EAGA,UAAAC,EACA,UAAAC,EACA,KAAAC,EACA,YAAAC,EACA,SAAAC,EACA,iBAAAC,EACA,SAAAC,EACA,MAAAC,EACA,gBAAAC,EACA,UAAAC,EACA,YAAAC,EACA,YAAAC,EACA,aAAAC,EACA,aAAAC,EACA,QAAAC,EAGA,WAAAC,EACA,UAAAC,EACA,YAAAC,EACA,UAAAC,EACA,QAAAC,EACA,SAAAC,EACA,aAAAC,EACA,qBAAAC,EACA,WAAAC,EACA,aAAAC,EACA,mBAAAC,EACA,iBAAAC,EACA,YAAAC,EACA,QAAAC,EACA,UAAAC,EACA,MAAAC,GACA,MAAAC,GACA,YAAAC,GACA,WAAAC,GAGA,QAAAC,GACA,UAAAC,GACA,iBAAAC,GACA,gBAAAC,GACA,YAAAC,GAGA,SAAAC,GAGA,SAAAC,GACA,eAAAC,GACA,uBAAAC,GACA,YAAAC,GAGA,YAAAC,GAGA,QAAAC,GACA,OAAAC,GACA,OAAAC,GACA,gBAAAC,GACA,eAAAC,GACA,iBAAAC,GACA,kBAAAC,GACA,SAAAC,GACA,UAAAC,GACA,YAAAC,GACA,UAAAC,GACA,cAAAC,GACA,eAAAC,GACA,eAAAC,GACA,WAAAC,GACA,QAAAC,GAEA,GAAGC,EAAA,EACD7E,EAEEpB,GAAeM,EAAAA,OAAuB,IAAI,EAG1CL,GAASiG,EAAAA,QACb,KAAO,CAEL,OAAAzE,EACA,WAAAC,EACA,UAAAC,EACA,UAAAC,EACA,aAAAC,EACA,WAAAC,EACA,WAAAC,EACA,cAAAC,EACA,cAAAC,EACA,QAAAC,EACA,QAAAC,EAGA,UAAAC,EACA,UAAAC,EACA,KAAAC,EACA,YAAAC,EACA,SAAAC,EACA,iBAAAC,EACA,SAAAC,EACA,MAAAC,EACA,gBAAAC,EACA,UAAAC,EACA,YAAAC,EACA,YAAAC,EACF,aAAAC,EACA,aAAAC,EACE,QAAAC,EAGA,WAAAC,EACA,UAAAC,EACA,YAAAC,EACA,UAAAC,EACA,QAAAC,EACA,SAAAC,EACA,aAAAC,EACA,qBAAAC,EACA,WAAAC,EACA,aAAAC,EACA,mBAAAC,EACA,iBAAAC,EACA,YAAAC,EACA,QAAAC,EACA,UAAAC,EACA,MAAAC,GACA,MAAAC,GACA,YAAAC,GACA,WAAAC,GAGA,QAAAC,GACA,UAAAC,GACA,iBAAAC,GACA,gBAAAC,GACA,YAAAC,GAGA,SAAAC,GAGA,SAAAC,GACA,eAAAC,GACA,uBAAAC,GACA,YAAAC,GAGA,YAAAC,GAGA,QAAAC,GACA,OAAAC,GACA,OAAAC,GACA,gBAAAC,GACA,eAAAC,GACA,iBAAAC,GACA,kBAAAC,GACA,SAAAC,GACA,UAAAC,GACA,YAAAC,GACA,UAAAC,GACA,cAAAC,GACA,eAAAC,GACA,eAAAC,GACA,WAAAC,GACA,QAAAC,EAAA,GAEF,CAEEvE,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EAGAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACFC,EACAC,EACEC,EAGAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,GACAC,GACAC,GACAC,GAGAC,GACAC,GACAC,GACAC,GACAC,GAGAC,GAGAC,GACAC,GACAC,GACAC,GAGAC,GAGAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,GACAC,EAAA,CACF,EAGI,CAAE,UAAA/E,CAAA,EAAclB,EAASC,GAAcC,EAAM,EAKnDkG,OAAAA,EAAAA,oBACE9E,EACA,KAAO,CACL,SAAU,CAAC+E,EAAQ,IAAA,OAAM,OAAArF,EAAAE,MAAA,YAAAF,EAAa,SAAS,GAAOqF,IACtD,UAAW,CAACA,EAAQ,IAAA,OAAM,OAAArF,EAAAE,MAAA,YAAAF,EAAa,UAAU,GAAOqF,IACxD,QAAS,CAACA,EAAQ,IAAA,OAAM,OAAArF,EAAAE,MAAA,YAAAF,EAAa,QAAQ,GAAOqF,IACpD,WAAY,CAACA,EAAQ,IAAA,OAAM,OAAArF,EAAAE,MAAA,YAAAF,EAAa,WAAW,GAAOqF,IAC1D,KAAM,IAAA,OAAM,OAAArF,EAAAE,EAAA,IAAA,YAAAF,EAAa,QACzB,KAAM,IAAA,OAAM,OAAAA,EAAAE,EAAA,IAAA,YAAAF,EAAa,gBACzB,OAAQ,IAAA,SAAM,OAAAsF,GAAAtF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAsF,EAAsB,UACpC,QAAS,IAAA,SAAM,OAAAA,GAAAtF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAsF,EAAsB,WACrC,UAAW,IAAA,SAAM,OAAAA,GAAAtF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAsF,EAAsB,aACvC,QAAUC,GAAA,SAAkB,OAAAD,GAAAtF,EAAAE,MAAA,YAAAF,EAAa,UAAb,YAAAsF,EAAsB,QAAQC,IAC1D,UAAW,CAACC,EAAeC,IAAA,OACzB,OAAAzF,EAAAE,MAAA,YAAAF,EAAa,eAAewF,EAAOC,IACrC,UAAW,IAAMvF,EAAA,CAAU,GAE7B,CAACA,CAAS,CAAA,EAIVwF,GAAAA,IAAC,MAAA,CACC,IAAKzG,GACL,GAAAsB,EACA,UAAAC,EACA,MAAAC,EACC,GAAGyE,EAAA,CAAA,CAGV,EAEaS,EAAcC,EAAAA,WAAWxF,EAAoB,EAC1DuF,EAAY,YAAc"}
@@ -76,8 +76,16 @@ export interface Hotspot {
76
76
  content?: string;
77
77
  data?: PopoverData;
78
78
  className?: string;
79
+ /** If true, the popover stays open until explicitly closed. */
80
+ keepOpen?: boolean;
81
+ /** Custom click handler for the hotspot. */
82
+ onClick?: (event: MouseEvent, popperInstance: any, hotspotId: string) => void;
83
+ /** Per-hotspot marker theme override. */
84
+ markerTheme?: MarkerTheme;
79
85
  /** Scene ID to navigate to when this hotspot is clicked. */
80
86
  navigateTo?: string;
87
+ /** Rotation angle (degrees) for the navigation arrow icon. Only applies when navigateTo is set. */
88
+ arrowDirection?: number;
81
89
  }
82
90
 
83
91
  /**
@@ -195,6 +203,7 @@ export interface CI360Config {
195
203
  hotspots?: Hotspot[] | null;
196
204
  hotspotTrigger?: HotspotTrigger;
197
205
  hotspotTimelineOnClick?: boolean;
206
+ initOnClick?: boolean;
198
207
  onReady?: (data: BaseEventData) => void;
199
208
  onLoad?: (data: LoadEventData) => void;
200
209
  onSpin?: (data: SpinEventData) => void;
@@ -1,15 +1,15 @@
1
- import { jsx as ke } from "react/jsx-runtime";
2
- import { useState as Pe, useRef as I, useId as Xe, useEffect as Ye, useCallback as be, forwardRef as Ee, useMemo as Fe, useImperativeHandle as Ge } from "react";
1
+ import { jsx as Pe } from "react/jsx-runtime";
2
+ import { useState as Xe, useRef as I, useId as Ye, useEffect as be, useCallback as Ee, forwardRef as Fe, useMemo as Ge, useImperativeHandle as xe } from "react";
3
3
  let w = null;
4
- function xe(d, t) {
5
- const [C, f] = Pe(!1), a = I(null), i = I(null), m = Xe();
6
- Ye(() => {
4
+ function Be(d, t) {
5
+ const [C, f] = Xe(!1), a = I(null), i = I(null), m = Ye();
6
+ be(() => {
7
7
  if (typeof window > "u" || !d.current || t.autoInit === !1) return;
8
8
  let l = !0;
9
9
  const n = d.current;
10
10
  return (async () => {
11
11
  try {
12
- if (w || (w = (await import("./ci360-BFobl9uu.mjs")).default), !n || !l) return;
12
+ if (w || (w = (await import("./ci360-VASGkLhF.mjs")).default), !n || !l) return;
13
13
  n.id || (n.id = `ci360-${m.replace(/:/g, "")}`);
14
14
  const s = {
15
15
  ...t,
@@ -49,14 +49,14 @@ function xe(d, t) {
49
49
  t.cropGravity,
50
50
  m
51
51
  ]);
52
- const y = be(() => a.current, []);
52
+ const y = Ee(() => a.current, []);
53
53
  return {
54
54
  viewer: a.current,
55
55
  isReady: C,
56
56
  getViewer: y
57
57
  };
58
58
  }
59
- const Be = (d, t) => {
59
+ const De = (d, t) => {
60
60
  const {
61
61
  // Container props
62
62
  id: C,
@@ -79,12 +79,12 @@ const Be = (d, t) => {
79
79
  swipeable: g,
80
80
  keys: L,
81
81
  keysReverse: V,
82
- autoplay: S,
83
- autoplayBehavior: T,
84
- playOnce: Z,
85
- speed: A,
86
- autoplayReverse: O,
87
- dragSpeed: k,
82
+ autoplay: O,
83
+ autoplayBehavior: S,
84
+ playOnce: T,
85
+ speed: Z,
86
+ autoplayReverse: k,
87
+ dragSpeed: A,
88
88
  dragReverse: P,
89
89
  stopAtEdges: X,
90
90
  stopAtEdgesX: Y,
@@ -122,27 +122,28 @@ const Be = (d, t) => {
122
122
  hotspots: le,
123
123
  hotspotTrigger: me,
124
124
  hotspotTimelineOnClick: ue,
125
+ initOnClick: pe,
125
126
  // Container
126
- aspectRatio: pe,
127
+ aspectRatio: ce,
127
128
  // Event callbacks
128
- onReady: ce,
129
- onLoad: de,
130
- onSpin: fe,
131
- onAutoplayStart: ye,
132
- onAutoplayStop: Ce,
133
- onFullscreenOpen: ve,
134
- onFullscreenClose: we,
135
- onZoomIn: Ie,
136
- onZoomOut: Re,
137
- onDragStart: he,
138
- onDragEnd: ze,
139
- onHotspotOpen: ge,
140
- onHotspotClose: Le,
141
- onProductClick: Ve,
129
+ onReady: de,
130
+ onLoad: fe,
131
+ onSpin: ye,
132
+ onAutoplayStart: Ce,
133
+ onAutoplayStop: ve,
134
+ onFullscreenOpen: we,
135
+ onFullscreenClose: Ie,
136
+ onZoomIn: Re,
137
+ onZoomOut: he,
138
+ onDragStart: ze,
139
+ onDragEnd: ge,
140
+ onHotspotOpen: Le,
141
+ onHotspotClose: Ve,
142
+ onProductClick: Oe,
142
143
  onNavigate: Se,
143
144
  onError: Te,
144
- ...Ae
145
- } = d, Ze = I(null), Oe = Fe(
145
+ ...ke
146
+ } = d, Ze = I(null), Ae = Ge(
146
147
  () => ({
147
148
  // Image source
148
149
  folder: i,
@@ -161,12 +162,12 @@ const Be = (d, t) => {
161
162
  swipeable: g,
162
163
  keys: L,
163
164
  keysReverse: V,
164
- autoplay: S,
165
- autoplayBehavior: T,
166
- playOnce: Z,
167
- speed: A,
168
- autoplayReverse: O,
169
- dragSpeed: k,
165
+ autoplay: O,
166
+ autoplayBehavior: S,
167
+ playOnce: T,
168
+ speed: Z,
169
+ autoplayReverse: k,
170
+ dragSpeed: A,
170
171
  dragReverse: P,
171
172
  stopAtEdges: X,
172
173
  stopAtEdgesX: Y,
@@ -204,23 +205,24 @@ const Be = (d, t) => {
204
205
  hotspots: le,
205
206
  hotspotTrigger: me,
206
207
  hotspotTimelineOnClick: ue,
208
+ initOnClick: pe,
207
209
  // Container
208
- aspectRatio: pe,
210
+ aspectRatio: ce,
209
211
  // Event callbacks
210
- onReady: ce,
211
- onLoad: de,
212
- onSpin: fe,
213
- onAutoplayStart: ye,
214
- onAutoplayStop: Ce,
215
- onFullscreenOpen: ve,
216
- onFullscreenClose: we,
217
- onZoomIn: Ie,
218
- onZoomOut: Re,
219
- onDragStart: he,
220
- onDragEnd: ze,
221
- onHotspotOpen: ge,
222
- onHotspotClose: Le,
223
- onProductClick: Ve,
212
+ onReady: de,
213
+ onLoad: fe,
214
+ onSpin: ye,
215
+ onAutoplayStart: Ce,
216
+ onAutoplayStop: ve,
217
+ onFullscreenOpen: we,
218
+ onFullscreenClose: Ie,
219
+ onZoomIn: Re,
220
+ onZoomOut: he,
221
+ onDragStart: ze,
222
+ onDragEnd: ge,
223
+ onHotspotOpen: Le,
224
+ onHotspotClose: Ve,
225
+ onProductClick: Oe,
224
226
  onNavigate: Se,
225
227
  onError: Te
226
228
  }),
@@ -242,12 +244,12 @@ const Be = (d, t) => {
242
244
  g,
243
245
  L,
244
246
  V,
247
+ O,
245
248
  S,
246
249
  T,
247
250
  Z,
248
- A,
249
- O,
250
251
  k,
252
+ A,
251
253
  P,
252
254
  X,
253
255
  Y,
@@ -285,10 +287,10 @@ const Be = (d, t) => {
285
287
  le,
286
288
  me,
287
289
  ue,
288
- // Container
289
290
  pe,
290
- // Event callbacks
291
+ // Container
291
292
  ce,
293
+ // Event callbacks
292
294
  de,
293
295
  fe,
294
296
  ye,
@@ -302,11 +304,12 @@ const Be = (d, t) => {
302
304
  ge,
303
305
  Le,
304
306
  Ve,
307
+ Oe,
305
308
  Se,
306
309
  Te
307
310
  ]
308
- ), { getViewer: r } = xe(Ze, Oe);
309
- return Ge(
311
+ ), { getViewer: r } = Be(Ze, Ae);
312
+ return xe(
310
313
  t,
311
314
  () => ({
312
315
  moveLeft: (e = 1) => {
@@ -356,22 +359,22 @@ const Be = (d, t) => {
356
359
  getViewer: () => r()
357
360
  }),
358
361
  [r]
359
- ), /* @__PURE__ */ ke(
362
+ ), /* @__PURE__ */ Pe(
360
363
  "div",
361
364
  {
362
365
  ref: Ze,
363
366
  id: C,
364
367
  className: f,
365
368
  style: a,
366
- ...Ae
369
+ ...ke
367
370
  }
368
371
  );
369
- }, De = Ee(Be);
370
- De.displayName = "CI360Viewer";
372
+ }, He = Fe(De);
373
+ He.displayName = "CI360Viewer";
371
374
  export {
372
- De as CI360Viewer,
373
- De as CI360ViewerDefault,
374
- xe as useCI360,
375
- xe as useCI360Default
375
+ He as CI360Viewer,
376
+ He as CI360ViewerDefault,
377
+ Be as useCI360,
378
+ Be as useCI360Default
376
379
  };
377
380
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/react/useCI360.ts","../../src/react/CI360Viewer.tsx"],"sourcesContent":["import { useEffect, useRef, useState, useId, useCallback, type RefObject } from 'react';\nimport type {\n CI360Config,\n CI360ViewerInstance,\n UseCI360Return,\n UseCI360Options,\n} from './types';\n\n// Import CI360 class dynamically to avoid SSR issues\nlet CI360Class: any = null;\n\n/**\n * Custom hook for integrating CI360 viewer with React\n *\n * @param containerRef - React ref to the container element\n * @param config - CI360 configuration options\n * @returns Object containing viewer instance and ready state\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { viewer, isReady } = useCI360(containerRef, {\n * folder: 'https://example.com/images/',\n * filenameX: 'image-{index}.jpg',\n * amountX: 36,\n * });\n *\n * return <div ref={containerRef} />;\n * }\n * ```\n */\nexport function useCI360(\n containerRef: RefObject<HTMLDivElement | null>,\n config: UseCI360Options\n): UseCI360Return {\n const [isReady, setIsReady] = useState(false);\n const viewerRef = useRef<CI360ViewerInstance | null>(null);\n const ci360Ref = useRef<any>(null);\n const uniqueId = useId();\n\n // Initialize viewer\n useEffect(() => {\n // SSR guard\n if (typeof window === 'undefined') return;\n if (!containerRef.current) return;\n if (config.autoInit === false) return;\n\n let isMounted = true;\n const container = containerRef.current;\n\n const initViewer = async () => {\n try {\n // Dynamically import CI360 to avoid SSR issues\n if (!CI360Class) {\n const module = await import('../ci360');\n CI360Class = module.default;\n }\n\n if (!container || !isMounted) return;\n\n // Set a unique ID on the container if not present\n if (!container.id) {\n container.id = `ci360-${uniqueId.replace(/:/g, '')}`;\n }\n\n // Wrap user callbacks to update React state\n const wrappedConfig: CI360Config = {\n ...config,\n onReady: (data) => {\n if (isMounted) {\n setIsReady(true);\n config.onReady?.(data);\n }\n },\n };\n\n // Create CI360 instance and initialize viewer\n ci360Ref.current = new CI360Class();\n viewerRef.current = ci360Ref.current.init(container, wrappedConfig);\n } catch (error) {\n console.error('Failed to initialize CI360 viewer:', error);\n }\n };\n\n initViewer();\n\n // Cleanup on unmount or when dependencies change\n return () => {\n isMounted = false;\n if (viewerRef.current) {\n try {\n viewerRef.current.destroy();\n } catch (e) {\n // Ignore errors during cleanup - element may already be detached\n }\n viewerRef.current = null;\n }\n ci360Ref.current = null;\n setIsReady(false);\n };\n }, [\n config.folder,\n config.filenameX,\n config.filenameY,\n config.filenameGrid,\n config.imageListX,\n config.imageListY,\n config.imageListGrid,\n config.amountX,\n config.amountY,\n config.hotspots,\n config.theme,\n config.aspectRatio,\n config.cropAspectRatio,\n config.cropGravity,\n uniqueId,\n ]);\n\n // Stable getter to always return current viewer (avoids stale closures)\n const getViewer = useCallback(() => viewerRef.current, []);\n\n return {\n viewer: viewerRef.current,\n isReady,\n getViewer,\n };\n}\n\nexport default useCI360;\n","import {\n useRef,\n useImperativeHandle,\n forwardRef,\n useMemo,\n type ForwardRefRenderFunction,\n} from 'react';\nimport { useCI360 } from './useCI360';\nimport type {\n CI360ViewerProps,\n CI360ViewerRef,\n CI360Config,\n} from './types';\n\n/**\n * CI360Viewer React Component\n *\n * A declarative React wrapper for the CI360 360-degree image viewer.\n *\n * @example\n * ```tsx\n * import { CI360Viewer } from 'js-cloudimage-360-view/react';\n * import 'js-cloudimage-360-view/css';\n *\n * function ProductView() {\n * return (\n * <CI360Viewer\n * folder=\"https://example.com/images/\"\n * filenameX=\"product-{index}.jpg\"\n * amountX={36}\n * autoplay\n * fullscreen\n * />\n * );\n * }\n * ```\n *\n * @example With ref for imperative control\n * ```tsx\n * import { useRef } from 'react';\n * import { CI360Viewer, CI360ViewerRef } from 'js-cloudimage-360-view/react';\n *\n * function ProductView() {\n * const viewerRef = useRef<CI360ViewerRef>(null);\n *\n * return (\n * <>\n * <CI360Viewer\n * ref={viewerRef}\n * folder=\"https://example.com/images/\"\n * filenameX=\"{index}.jpg\"\n * amountX={36}\n * onSpin={(e) => console.log(`Frame: ${e.activeImageX}`)}\n * />\n * <button onClick={() => viewerRef.current?.play()}>Play</button>\n * <button onClick={() => viewerRef.current?.stop()}>Stop</button>\n * </>\n * );\n * }\n * ```\n */\nconst CI360ViewerComponent: ForwardRefRenderFunction<\n CI360ViewerRef,\n CI360ViewerProps\n> = (props, ref) => {\n const {\n // Container props\n id,\n className,\n style,\n\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n\n ...restProps\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Memoize config to prevent unnecessary re-initializations\n const config = useMemo<CI360Config>(\n () => ({\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n }),\n [\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n ]\n );\n\n const { getViewer } = useCI360(containerRef, config);\n\n // Expose imperative methods via ref\n // Use getViewer() inside methods to always get the current viewer instance,\n // avoiding stale closure issues when the viewer initializes after first render\n useImperativeHandle(\n ref,\n () => ({\n moveLeft: (steps = 1) => getViewer()?.moveLeft(false, steps),\n moveRight: (steps = 1) => getViewer()?.moveRight(false, steps),\n moveTop: (steps = 1) => getViewer()?.moveTop(false, steps),\n moveBottom: (steps = 1) => getViewer()?.moveBottom(false, steps),\n play: () => getViewer()?.play(),\n stop: () => getViewer()?.stopAutoplay(),\n zoomIn: () => getViewer()?.zoomPan?.zoomIn(),\n zoomOut: () => getViewer()?.zoomPan?.zoomOut(),\n resetZoom: () => getViewer()?.zoomPan?.resetZoom(),\n setZoom: (level: number) => getViewer()?.zoomPan?.setZoom(level),\n goToFrame: (frame: number, hotspotId?: string) =>\n getViewer()?.animateToFrame(frame, hotspotId),\n getViewer: () => getViewer(),\n }),\n [getViewer]\n );\n\n return (\n <div\n ref={containerRef}\n id={id}\n className={className}\n style={style}\n {...restProps}\n />\n );\n};\n\nexport const CI360Viewer = forwardRef(CI360ViewerComponent);\nCI360Viewer.displayName = 'CI360Viewer';\n\nexport default CI360Viewer;\n"],"names":["CI360Class","useCI360","containerRef","config","isReady","setIsReady","useState","viewerRef","useRef","ci360Ref","uniqueId","useId","useEffect","isMounted","container","wrappedConfig","data","_a","error","getViewer","useCallback","CI360ViewerComponent","props","ref","id","className","style","folder","apiVersion","filenameX","filenameY","filenameGrid","imageListX","imageListY","imageListGrid","indexZeroBase","amountX","amountY","draggable","swipeable","keys","keysReverse","autoplay","autoplayBehavior","playOnce","speed","autoplayReverse","dragSpeed","dragReverse","stopAtEdges","stopAtEdgesX","stopAtEdgesY","inertia","fullscreen","magnifier","pointerZoom","pinchZoom","zoomMax","zoomStep","zoomControls","zoomControlsPosition","scrollHint","bottomCircle","bottomCircleOffset","initialIconShown","hide360Logo","logoSrc","imageInfo","hints","theme","markerTheme","brandColor","ciToken","ciFilters","ciTransformation","cropAspectRatio","cropGravity","lazyload","hotspots","hotspotTrigger","hotspotTimelineOnClick","aspectRatio","onReady","onLoad","onSpin","onAutoplayStart","onAutoplayStop","onFullscreenOpen","onFullscreenClose","onZoomIn","onZoomOut","onDragStart","onDragEnd","onHotspotOpen","onHotspotClose","onProductClick","onNavigate","onError","restProps","useMemo","useImperativeHandle","steps","_b","level","frame","hotspotId","jsx","CI360Viewer","forwardRef"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/react/useCI360.ts","../../src/react/CI360Viewer.tsx"],"sourcesContent":["import { useEffect, useRef, useState, useId, useCallback, type RefObject } from 'react';\nimport type {\n CI360Config,\n CI360ViewerInstance,\n UseCI360Return,\n UseCI360Options,\n} from './types';\n\n// Import CI360 class dynamically to avoid SSR issues\nlet CI360Class: any = null;\n\n/**\n * Custom hook for integrating CI360 viewer with React\n *\n * @param containerRef - React ref to the container element\n * @param config - CI360 configuration options\n * @returns Object containing viewer instance and ready state\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { viewer, isReady } = useCI360(containerRef, {\n * folder: 'https://example.com/images/',\n * filenameX: 'image-{index}.jpg',\n * amountX: 36,\n * });\n *\n * return <div ref={containerRef} />;\n * }\n * ```\n */\nexport function useCI360(\n containerRef: RefObject<HTMLDivElement | null>,\n config: UseCI360Options\n): UseCI360Return {\n const [isReady, setIsReady] = useState(false);\n const viewerRef = useRef<CI360ViewerInstance | null>(null);\n const ci360Ref = useRef<any>(null);\n const uniqueId = useId();\n\n // Initialize viewer\n useEffect(() => {\n // SSR guard\n if (typeof window === 'undefined') return;\n if (!containerRef.current) return;\n if (config.autoInit === false) return;\n\n let isMounted = true;\n const container = containerRef.current;\n\n const initViewer = async () => {\n try {\n // Dynamically import CI360 to avoid SSR issues\n if (!CI360Class) {\n const module = await import('../ci360');\n CI360Class = module.default;\n }\n\n if (!container || !isMounted) return;\n\n // Set a unique ID on the container if not present\n if (!container.id) {\n container.id = `ci360-${uniqueId.replace(/:/g, '')}`;\n }\n\n // Wrap user callbacks to update React state\n const wrappedConfig: CI360Config = {\n ...config,\n onReady: (data) => {\n if (isMounted) {\n setIsReady(true);\n config.onReady?.(data);\n }\n },\n };\n\n // Create CI360 instance and initialize viewer\n ci360Ref.current = new CI360Class();\n viewerRef.current = ci360Ref.current.init(container, wrappedConfig);\n } catch (error) {\n console.error('Failed to initialize CI360 viewer:', error);\n }\n };\n\n initViewer();\n\n // Cleanup on unmount or when dependencies change\n return () => {\n isMounted = false;\n if (viewerRef.current) {\n try {\n viewerRef.current.destroy();\n } catch (e) {\n // Ignore errors during cleanup - element may already be detached\n }\n viewerRef.current = null;\n }\n ci360Ref.current = null;\n setIsReady(false);\n };\n }, [\n config.folder,\n config.filenameX,\n config.filenameY,\n config.filenameGrid,\n config.imageListX,\n config.imageListY,\n config.imageListGrid,\n config.amountX,\n config.amountY,\n config.hotspots,\n config.theme,\n config.aspectRatio,\n config.cropAspectRatio,\n config.cropGravity,\n uniqueId,\n ]);\n\n // Stable getter to always return current viewer (avoids stale closures)\n const getViewer = useCallback(() => viewerRef.current, []);\n\n return {\n viewer: viewerRef.current,\n isReady,\n getViewer,\n };\n}\n\nexport default useCI360;\n","import {\n useRef,\n useImperativeHandle,\n forwardRef,\n useMemo,\n type ForwardRefRenderFunction,\n} from 'react';\nimport { useCI360 } from './useCI360';\nimport type {\n CI360ViewerProps,\n CI360ViewerRef,\n CI360Config,\n} from './types';\n\n/**\n * CI360Viewer React Component\n *\n * A declarative React wrapper for the CI360 360-degree image viewer.\n *\n * @example\n * ```tsx\n * import { CI360Viewer } from 'js-cloudimage-360-view/react';\n * import 'js-cloudimage-360-view/css';\n *\n * function ProductView() {\n * return (\n * <CI360Viewer\n * folder=\"https://example.com/images/\"\n * filenameX=\"product-{index}.jpg\"\n * amountX={36}\n * autoplay\n * fullscreen\n * />\n * );\n * }\n * ```\n *\n * @example With ref for imperative control\n * ```tsx\n * import { useRef } from 'react';\n * import { CI360Viewer, CI360ViewerRef } from 'js-cloudimage-360-view/react';\n *\n * function ProductView() {\n * const viewerRef = useRef<CI360ViewerRef>(null);\n *\n * return (\n * <>\n * <CI360Viewer\n * ref={viewerRef}\n * folder=\"https://example.com/images/\"\n * filenameX=\"{index}.jpg\"\n * amountX={36}\n * onSpin={(e) => console.log(`Frame: ${e.activeImageX}`)}\n * />\n * <button onClick={() => viewerRef.current?.play()}>Play</button>\n * <button onClick={() => viewerRef.current?.stop()}>Stop</button>\n * </>\n * );\n * }\n * ```\n */\nconst CI360ViewerComponent: ForwardRefRenderFunction<\n CI360ViewerRef,\n CI360ViewerProps\n> = (props, ref) => {\n const {\n // Container props\n id,\n className,\n style,\n\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n initOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n\n ...restProps\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Memoize config to prevent unnecessary re-initializations\n const config = useMemo<CI360Config>(\n () => ({\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n initOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n }),\n [\n // Image source\n folder,\n apiVersion,\n filenameX,\n filenameY,\n filenameGrid,\n imageListX,\n imageListY,\n imageListGrid,\n indexZeroBase,\n amountX,\n amountY,\n\n // Behavior\n draggable,\n swipeable,\n keys,\n keysReverse,\n autoplay,\n autoplayBehavior,\n playOnce,\n speed,\n autoplayReverse,\n dragSpeed,\n dragReverse,\n stopAtEdges,\n stopAtEdgesX,\n stopAtEdgesY,\n inertia,\n\n // UI Features\n fullscreen,\n magnifier,\n pointerZoom,\n pinchZoom,\n zoomMax,\n zoomStep,\n zoomControls,\n zoomControlsPosition,\n scrollHint,\n bottomCircle,\n bottomCircleOffset,\n initialIconShown,\n hide360Logo,\n logoSrc,\n imageInfo,\n hints,\n theme,\n markerTheme,\n brandColor,\n\n // Cloudimage CDN\n ciToken,\n ciFilters,\n ciTransformation,\n cropAspectRatio,\n cropGravity,\n\n // Loading\n lazyload,\n\n // Hotspots\n hotspots,\n hotspotTrigger,\n hotspotTimelineOnClick,\n initOnClick,\n\n // Container\n aspectRatio,\n\n // Event callbacks\n onReady,\n onLoad,\n onSpin,\n onAutoplayStart,\n onAutoplayStop,\n onFullscreenOpen,\n onFullscreenClose,\n onZoomIn,\n onZoomOut,\n onDragStart,\n onDragEnd,\n onHotspotOpen,\n onHotspotClose,\n onProductClick,\n onNavigate,\n onError,\n ]\n );\n\n const { getViewer } = useCI360(containerRef, config);\n\n // Expose imperative methods via ref\n // Use getViewer() inside methods to always get the current viewer instance,\n // avoiding stale closure issues when the viewer initializes after first render\n useImperativeHandle(\n ref,\n () => ({\n moveLeft: (steps = 1) => getViewer()?.moveLeft(false, steps),\n moveRight: (steps = 1) => getViewer()?.moveRight(false, steps),\n moveTop: (steps = 1) => getViewer()?.moveTop(false, steps),\n moveBottom: (steps = 1) => getViewer()?.moveBottom(false, steps),\n play: () => getViewer()?.play(),\n stop: () => getViewer()?.stopAutoplay(),\n zoomIn: () => getViewer()?.zoomPan?.zoomIn(),\n zoomOut: () => getViewer()?.zoomPan?.zoomOut(),\n resetZoom: () => getViewer()?.zoomPan?.resetZoom(),\n setZoom: (level: number) => getViewer()?.zoomPan?.setZoom(level),\n goToFrame: (frame: number, hotspotId?: string) =>\n getViewer()?.animateToFrame(frame, hotspotId),\n getViewer: () => getViewer(),\n }),\n [getViewer]\n );\n\n return (\n <div\n ref={containerRef}\n id={id}\n className={className}\n style={style}\n {...restProps}\n />\n );\n};\n\nexport const CI360Viewer = forwardRef(CI360ViewerComponent);\nCI360Viewer.displayName = 'CI360Viewer';\n\nexport default CI360Viewer;\n"],"names":["CI360Class","useCI360","containerRef","config","isReady","setIsReady","useState","viewerRef","useRef","ci360Ref","uniqueId","useId","useEffect","isMounted","container","wrappedConfig","data","_a","error","getViewer","useCallback","CI360ViewerComponent","props","ref","id","className","style","folder","apiVersion","filenameX","filenameY","filenameGrid","imageListX","imageListY","imageListGrid","indexZeroBase","amountX","amountY","draggable","swipeable","keys","keysReverse","autoplay","autoplayBehavior","playOnce","speed","autoplayReverse","dragSpeed","dragReverse","stopAtEdges","stopAtEdgesX","stopAtEdgesY","inertia","fullscreen","magnifier","pointerZoom","pinchZoom","zoomMax","zoomStep","zoomControls","zoomControlsPosition","scrollHint","bottomCircle","bottomCircleOffset","initialIconShown","hide360Logo","logoSrc","imageInfo","hints","theme","markerTheme","brandColor","ciToken","ciFilters","ciTransformation","cropAspectRatio","cropGravity","lazyload","hotspots","hotspotTrigger","hotspotTimelineOnClick","initOnClick","aspectRatio","onReady","onLoad","onSpin","onAutoplayStart","onAutoplayStop","onFullscreenOpen","onFullscreenClose","onZoomIn","onZoomOut","onDragStart","onDragEnd","onHotspotOpen","onHotspotClose","onProductClick","onNavigate","onError","restProps","useMemo","useImperativeHandle","steps","_b","level","frame","hotspotId","jsx","CI360Viewer","forwardRef"],"mappings":"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"}
@@ -1 +1 @@
1
- :root{--ci360-button-bg: rgba(255, 255, 255, .9);--ci360-button-bg-hover: rgba(255, 255, 255, 1);--ci360-button-size: 40px;--ci360-button-border-radius: 6px;--ci360-button-shadow: 0 2px 8px rgba(0, 0, 0, .15);--ci360-icon-color: #505050;--ci360-icon-color-hover: #333333;--ci360-icon-size: 20px;--ci360-initial-icon-bg: rgba(255, 255, 255, .15);--ci360-initial-icon-color: #ffffff;--ci360-initial-icon-size: 100px;--ci360-initial-icon-shadow: 0 2px 16px rgba(0, 0, 0, .08);--ci360-initial-icon-border-radius: 16px;--ci360-spinner-color: rgba(0, 0, 0, .2);--ci360-spinner-accent: #505050;--ci360-spinner-size: 30px;--ci360-fullscreen-bg: #ffffff;--ci360-zoom-controls-border-radius: 8px;--ci360-scroll-hint-bg: rgba(0, 0, 0, .7);--ci360-scroll-hint-color: #ffffff;--ci360-scroll-hint-border-radius: 20px;--ci360-focus-color: #0066cc;--ci360-hotspot-marker-size: 24px;--ci360-hotspot-marker-bg: rgba(255, 255, 255, .8);--ci360-hotspot-marker-color: #1a1a1a;--ci360-hotspot-marker-border-width: 2px;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .4);--ci360-hotspot-marker-border: var(--ci360-hotspot-marker-border-width) solid var(--ci360-hotspot-marker-border-color);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .15);--ci360-hotspot-pulse-color: rgba(0, 0, 0, .15);--ci360-hotspot-pulse-size: 40px;--ci360-hotspot-pulse-duration: 1.8s;--ci360-hotspot-brand-color: #00aaff;--ci360-popper-bg: #ffffff;--ci360-popper-color: #1a1a1a;--ci360-popper-border: 1px solid rgba(0, 0, 0, .1);--ci360-popper-border-radius: 12px;--ci360-popper-shadow: 0 8px 32px rgba(0, 0, 0, .12);--ci360-popper-padding: 16px;--ci360-popper-max-width: 320px;--ci360-popper-font-size: 14px;--ci360-popper-line-height: 1.5;--ci360-popper-title-font-size: 16px;--ci360-popper-title-font-weight: 600;--ci360-popper-title-color: #1a1a1a;--ci360-popper-price-color: #2d8c3c;--ci360-popper-price-font-size: 18px;--ci360-popper-price-font-weight: 700;--ci360-popper-original-price-color: #999999;--ci360-popper-description-color: #666666;--ci360-popper-cta-bg: #0058a3;--ci360-popper-cta-color: #fff;--ci360-popper-cta-border-radius: 8px;--ci360-popper-cta-padding: 8px 16px;--ci360-hints-bg: rgba(255, 255, 255, .9);--ci360-hints-color: #333333;--ci360-hints-font-size: 13px;--ci360-hints-border-radius: 20px;--ci360-hints-shadow: 0 2px 12px rgba(0, 0, 0, .1);--ci360-circle-color: #888888;--ci360-timeline-height: 6px;--ci360-timeline-track-bg: rgba(255, 255, 255, .4);--ci360-timeline-dot-size: 18px;--ci360-timeline-dot-color: var(--ci360-hotspot-marker-bg);--ci360-timeline-dot-active-color: var(--ci360-hotspot-brand-color);--ci360-timeline-dot-border: 2px solid #fff;--ci360-timeline-indicator-size: 12px;--ci360-timeline-indicator-color: var(--ci360-hotspot-brand-color);--ci360-timeline-tooltip-bg: rgba(255, 255, 255, .95);--ci360-timeline-tooltip-color: #333333}.ci360-theme-dark{--ci360-button-bg: rgba(40, 40, 45, .9);--ci360-button-bg-hover: rgba(55, 55, 60, .95);--ci360-button-shadow: 0 2px 8px rgba(0, 0, 0, .4);--ci360-icon-color: #e0e0e0;--ci360-icon-color-hover: #ffffff;--ci360-spinner-color: rgba(255, 255, 255, .2);--ci360-spinner-accent: #e0e0e0;--ci360-fullscreen-bg: #1a1a1f;--ci360-zoom-controls-bg: rgba(40, 40, 45, .9);--ci360-zoom-controls-bg-hover: rgba(55, 55, 60, .95);--ci360-zoom-controls-color: #e0e0e0;--ci360-zoom-controls-color-hover: #ffffff;--ci360-zoom-controls-shadow: 0 2px 8px rgba(0, 0, 0, .4);--ci360-scroll-hint-bg: rgba(255, 255, 255, .15);--ci360-scroll-hint-color: #e0e0e0;--ci360-popper-bg: #1a1a1a;--ci360-popper-color: #f0f0f0;--ci360-popper-border: 1px solid rgba(255, 255, 255, .1);--ci360-popper-shadow: 0 8px 32px rgba(0, 0, 0, .4);--ci360-popper-title-color: #f0f0f0;--ci360-popper-description-color: #aaaaaa;--ci360-popper-original-price-color: #777777;--ci360-hints-bg: rgba(40, 40, 45, .9);--ci360-hints-color: #e0e0e0;--ci360-hints-shadow: 0 2px 12px rgba(0, 0, 0, .3);--ci360-circle-color: #b0b0b0;--ci360-timeline-track-bg: rgba(255, 255, 255, .4);--ci360-timeline-dot-border: 2px solid rgba(255, 255, 255, .9);--ci360-timeline-indicator-color: var(--ci360-hotspot-brand-color);--ci360-timeline-tooltip-bg: rgba(40, 40, 45, .95);--ci360-timeline-tooltip-color: #e0e0e0;--ci360-loader-bg: rgba(40, 40, 45, .9);--ci360-loader-color: #e0e0e0;--ci360-loader-shadow: 0 4px 20px rgba(0, 0, 0, .4);--ci360-hotspot-marker-bg: rgba(0, 0, 0, .6);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .6);--ci360-hotspot-pulse-color: rgba(255, 255, 255, .15);--ci360-timeline-dot-color: var(--ci360-hotspot-marker-bg)}.ci360-hotspot-marker-inverted{--ci360-hotspot-marker-bg: rgba(0, 0, 0, .6);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .8);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .3);--ci360-hotspot-pulse-color: rgba(0, 0, 0, .2)}.ci360-theme-dark.ci360-hotspot-marker-inverted{--ci360-hotspot-marker-bg: rgba(255, 255, 255, .8);--ci360-hotspot-marker-color: #1a1a1a;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .4);--ci360-hotspot-pulse-color: rgba(255, 255, 255, .2)}.ci360-hotspot-marker-brand{--ci360-hotspot-marker-bg: var(--ci360-hotspot-brand-color);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .9);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .25);--ci360-hotspot-pulse-color: var(--ci360-hotspot-brand-color)}@keyframes rotation{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.cloudimage-360{width:100%;position:relative}.cloudimage-360-inner-box{width:100%;position:relative;overflow:hidden}.cloudimage-360-icons-container{position:absolute;display:flex;top:15px;right:10px;height:100%;flex-direction:column;align-items:center;z-index:100;gap:8px}.cloudimage-360-button{width:var(--ci360-button-size);height:var(--ci360-button-size);cursor:pointer;transition:transform .15s ease-out,background-color .15s ease-out,box-shadow .15s ease-out;display:flex;align-items:center;justify-content:center;border-radius:var(--ci360-button-border-radius);border:none;background-color:var(--ci360-button-bg);box-shadow:var(--ci360-button-shadow);padding:6px;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-button:hover{transform:scale(1.05);background-color:var(--ci360-button-bg-hover)}.cloudimage-360-button:focus{outline:none}.cloudimage-360-button:focus-visible{outline:2px solid var(--ci360-focus-color);outline-offset:2px}.cloudimage-360-button svg{width:var(--ci360-icon-size);height:var(--ci360-icon-size);stroke:var(--ci360-icon-color);transition:stroke .15s ease-out}.cloudimage-360-button:hover svg{stroke:var(--ci360-icon-color-hover)}.cloudimage-initial-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:var(--ci360-initial-icon-size);aspect-ratio:150 / 87;border-radius:var(--ci360-initial-icon-border-radius);background-color:var(--ci360-initial-icon-bg);box-shadow:var(--ci360-initial-icon-shadow);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:all .3s ease;color:var(--ci360-initial-icon-color);display:flex;align-items:center;justify-content:center;z-index:21;-webkit-user-select:none;-moz-user-select:none;user-select:none;padding:12px 16px}.cloudimage-initial-icon svg{width:100%;height:auto;color:var(--ci360-initial-icon-color);fill:var(--ci360-initial-icon-color)}.cloudimage-initial-icon:hover{transform:translate(-50%,-50%) scale(1.08)}.cloudimage-360-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:var(--ci360-loader-size, 70px);height:var(--ci360-loader-size, 70px);border-radius:50%;background-color:var(--ci360-loader-bg, rgba(255, 255, 255, .9));box-shadow:var(--ci360-loader-shadow, 0 4px 20px rgba(0, 0, 0, .15));transition:all .3s ease;color:var(--ci360-loader-color, #505050);display:flex;align-items:center;justify-content:center;z-index:2;-webkit-user-select:none;-moz-user-select:none;user-select:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);font-family:system-ui,-apple-system,sans-serif;font-size:14px;font-weight:600}.cloudimage-loading-spinner{width:var(--ci360-spinner-size);height:var(--ci360-spinner-size);transform:translate(-50%,-50%);border:3px solid var(--ci360-spinner-color);position:absolute;top:15px;left:15px;border-bottom-color:var(--ci360-spinner-accent);border-radius:50%;display:inline-block;box-sizing:border-box;opacity:0;animation:rotation .7s linear infinite}.cloudimage-360-view-360-circle{position:absolute;left:0;bottom:0;width:100%;height:auto;margin:auto;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:.2s all;z-index:2;color:var(--ci360-circle-color)}.cloudimage-360-view-360-circle svg{display:block;width:100%;height:auto}.cloudimage-360--fullscreen{width:100%;height:100%;background-color:var(--ci360-fullscreen-bg)}.cloudimage-360--fullscreen .cloudimage-360-inner-box{height:100%;display:flex;justify-content:center;align-items:center}.cloudimage-360--fullscreen canvas{width:auto!important;max-width:100%;max-height:100%}.cloudimage-360-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.cloudimage-360-hints-overlay{position:absolute;bottom:0;left:0;width:100%;display:flex;align-items:flex-end;justify-content:center;padding-bottom:16px;z-index:50;pointer-events:none;opacity:0;transition:opacity .3s ease}.cloudimage-360-hints-overlay.visible{opacity:1}.cloudimage-360-hints-overlay.hiding{opacity:0}.cloudimage-360-hints-container{display:flex;flex-direction:row;gap:20px;padding:10px 20px;background:var(--ci360-hints-bg);border-radius:var(--ci360-hints-border-radius);box-shadow:var(--ci360-hints-shadow);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-hint-item{display:flex;align-items:center;gap:8px;color:var(--ci360-hints-color);font-size:var(--ci360-hints-font-size);font-family:system-ui,-apple-system,sans-serif;white-space:nowrap}.cloudimage-360-hint-item svg{width:16px;height:16px;flex-shrink:0;stroke:var(--ci360-hints-color)}.cloudimage-360-hint-item span{opacity:.9}@media (max-width: 480px){.cloudimage-360-hints-container{flex-direction:column;gap:8px;padding:12px 16px;border-radius:12px}.cloudimage-360-hint-item{font-size:12px}.cloudimage-360-hint-item svg{width:14px;height:14px}.cloudimage-360-initial-icon{width:calc(var(--ci360-initial-icon-size) * .8);height:calc(var(--ci360-initial-icon-size) * .8)}}.cloudimage-360-inner-box.has-hotspot-timeline .cloudimage-360-hints-overlay{padding-bottom:56px}.cloudimage-360-zoom-controls{position:absolute;display:flex;flex-direction:row;gap:4px;z-index:100;opacity:0;pointer-events:none;transition:opacity .2s ease;background:var(--ci360-zoom-controls-bg, var(--ci360-button-bg));border-radius:var(--ci360-zoom-controls-border-radius);box-shadow:var(--ci360-zoom-controls-shadow, var(--ci360-button-shadow));padding:4px;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-zoom-controls.visible{opacity:1;pointer-events:auto}.cloudimage-360-zoom-controls[data-position=bottom-left]{bottom:12px;left:12px}.cloudimage-360-zoom-controls[data-position=bottom-right]{bottom:12px;right:12px}.cloudimage-360-zoom-controls[data-position=top-left]{top:12px;left:12px}.cloudimage-360-zoom-controls[data-position=top-right]{top:12px;right:12px}.cloudimage-360-zoom-controls[data-position=top-center]{top:12px;left:50%;transform:translate(-50%)}.cloudimage-360-zoom-controls[data-position=bottom-center]{bottom:12px;left:50%;transform:translate(-50%)}.cloudimage-360-zoom-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border:none;background:transparent;border-radius:calc(var(--ci360-zoom-controls-border-radius) - 2px);cursor:pointer;padding:0;color:var(--ci360-zoom-controls-color, var(--ci360-icon-color));transition:background-color .15s ease,color .15s ease}.cloudimage-360-zoom-btn:hover:not(:disabled){background:var(--ci360-zoom-controls-bg-hover, var(--ci360-button-bg-hover));color:var(--ci360-zoom-controls-color-hover, var(--ci360-icon-color-hover))}.cloudimage-360-zoom-btn:disabled{opacity:.3;cursor:default}.cloudimage-360-zoom-btn:focus{outline:none}.cloudimage-360-zoom-btn:focus-visible{outline:2px solid var(--ci360-focus-color);outline-offset:-2px}.cloudimage-360-zoom-btn svg{width:18px;height:18px;stroke:currentColor}.cloudimage-360-zoom-separator{width:1px;align-self:stretch;margin:6px 0;background:#80808033}.cloudimage-360-scroll-hint{position:absolute;bottom:50%;left:50%;transform:translate(-50%,50%);background:var(--ci360-scroll-hint-bg);color:var(--ci360-scroll-hint-color);padding:8px 16px;border-radius:var(--ci360-scroll-hint-border-radius);font-size:13px;font-family:system-ui,-apple-system,sans-serif;font-weight:500;white-space:nowrap;pointer-events:none;z-index:200;opacity:0;transition:opacity .3s ease;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-scroll-hint.visible{opacity:1}.cloudimage-360-hotspot-timeline{position:absolute;bottom:0;left:0;right:0;width:100%;padding:20px;opacity:0;transition:opacity .3s ease;pointer-events:none;z-index:30;background:linear-gradient(to top,rgba(0,0,0,.45) 0%,rgba(0,0,0,.2) 60%,transparent 100%)}.cloudimage-360-hotspot-timeline.visible{opacity:1;pointer-events:auto}.cloudimage-360-hotspot-timeline-track{position:relative;width:100%;max-width:500px;margin:0 auto;height:var(--ci360-timeline-height);background:var(--ci360-timeline-track-bg);border-radius:calc(var(--ci360-timeline-height) / 2)}.cloudimage-360-hotspot-timeline-indicator{position:absolute;top:50%;left:0;width:var(--ci360-timeline-indicator-size);height:var(--ci360-timeline-indicator-size);background:var(--ci360-timeline-indicator-color);border-radius:50%;transform:translate(-50%,-50%);transition:left .1s ease-out;pointer-events:none;box-shadow:0 2px 4px #0003}.cloudimage-360-hotspot-timeline-dot{position:absolute;top:50%;width:var(--ci360-timeline-dot-size);height:var(--ci360-timeline-dot-size);background:var(--ci360-timeline-dot-color);border:var(--ci360-timeline-dot-border);border-radius:50%;transform:translate(-50%,-50%);cursor:pointer;transition:transform .15s ease,background .2s ease,box-shadow .2s ease;box-shadow:0 1px 4px #00000026;padding:0;font:inherit;outline:none}.cloudimage-360-hotspot-timeline-dot:hover{transform:translate(-50%,-50%) scale(1.15);box-shadow:0 2px 8px #0003}.cloudimage-360-hotspot-timeline-dot.active{background:var(--ci360-timeline-dot-color);box-shadow:0 1px 4px #00000026}.cloudimage-360-hotspot-timeline-dot:focus-visible{outline:2px solid var(--ci360-focus-color);outline-offset:2px}.cloudimage-360-hotspot-timeline-tooltip{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translate(-50%);background:var(--ci360-timeline-tooltip-bg);color:var(--ci360-timeline-tooltip-color);padding:6px 12px;border-radius:6px;font-size:13px;font-weight:500;white-space:nowrap;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease;pointer-events:none;box-shadow:0 2px 8px #00000026;z-index:10}.cloudimage-360-hotspot-timeline-tooltip:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:6px solid transparent;border-top-color:var(--ci360-timeline-tooltip-bg)}.cloudimage-360-hotspot-timeline-tooltip.visible{opacity:1;visibility:visible}@media (max-width: 480px){.cloudimage-360-hotspot-timeline{padding:14px 16px}}.cloudimage-360--fullscreen .cloudimage-360-hotspot-timeline{bottom:0}.cloudimage-360--fullscreen .cloudimage-360-hints-overlay{padding-bottom:32px}.cloudimage-360--fullscreen .cloudimage-360-zoom-controls[data-position^=bottom]{bottom:38px}.cloudimage-360--fullscreen .cloudimage-360-inner-box.has-hotspot-timeline .cloudimage-360-hints-overlay{padding-bottom:72px}.cloudimage-360--fullscreen .cloudimage-360-inner-box.has-hotspot-timeline .cloudimage-360-zoom-controls[data-position^=bottom]{bottom:72px}.cloudimage-360-hotspot-container{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:20}.cloudimage-360-hotspot{display:inline-block;position:absolute;top:0;right:0;bottom:0;left:0;transform:translate(-50%,-50%);background:var(--ci360-hotspot-marker-bg);border:var(--ci360-hotspot-marker-border);border-radius:50%;height:var(--ci360-hotspot-marker-size);width:var(--ci360-hotspot-marker-size);box-shadow:var(--ci360-hotspot-marker-shadow);opacity:0;transition:transform .2s ease,box-shadow .2s ease;padding:0;cursor:pointer;font:inherit;outline:none}.cloudimage-360-hotspot:focus-visible{outline:2px solid var(--ci360-focus-color, #0066cc);outline-offset:2px}.cloudimage-360-hotspot--pulse{overflow:visible}.cloudimage-360-hotspot--pulse:before{content:"";position:absolute;top:50%;left:50%;width:var(--ci360-hotspot-pulse-size);height:var(--ci360-hotspot-pulse-size);border-radius:50%;background:var(--ci360-hotspot-pulse-color);transform:translate(-50%,-50%) scale(1);animation:ci360-pulse-ring var(--ci360-hotspot-pulse-duration) ease-out infinite;pointer-events:none}@keyframes ci360-pulse-ring{0%{transform:translate(-50%,-50%) scale(1);opacity:1}to{transform:translate(-50%,-50%) scale(1.8);opacity:0}}.cloudimage-360-hotspot--pulse{animation:ci360-breathe 2.4s ease-in-out infinite}@keyframes ci360-breathe{0%,to{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-50%,-50%) scale(1.15)}}.cloudimage-360-hotspot--pulse:hover,.cloudimage-360-hotspot--pulse:focus-visible{animation:none}.cloudimage-360-hotspot--dot-label{animation:none}.cloudimage-360-hotspot-label{position:absolute;left:calc(50% + var(--ci360-hotspot-marker-size) / 2 + 4px);top:50%;transform:translateY(-50%);padding:3px 10px;font-size:11px;font-weight:600;white-space:nowrap;color:var(--ci360-hotspot-marker-color);background:var(--ci360-hotspot-marker-bg);border:var(--ci360-hotspot-marker-border);border-radius:100px;box-shadow:var(--ci360-hotspot-marker-shadow);pointer-events:none}.cloudimage-360-hotspot--navigate{display:inline-flex;align-items:center;justify-content:center;background:var(--ci360-hotspot-navigate-bg, #6366f1);border-color:var(--ci360-hotspot-navigate-border-color, rgba(255, 255, 255, .9))}.ci360-navigate-icon{width:14px;height:14px;color:#fff;pointer-events:none}.cloudimage-360-hotspot--navigate.cloudimage-360-hotspot--pulse{animation:none}.cloudimage-360-hotspot--navigate:hover,.cloudimage-360-hotspot--navigate:focus-visible{transform:translate(-50%,-50%) scale(1.15);box-shadow:0 0 0 3px #6366f166}.cloudimage-360-hotspot--dot-label:hover{transform:translate(-50%,-50%);box-shadow:0 4px 12px #0006}.cloudimage-360-hotspot:hover{transform:translate(-50%,-50%) scale(1.2);box-shadow:0 4px 12px #0006}@media (prefers-reduced-motion: reduce){.cloudimage-360-hotspot--pulse{animation:none}.cloudimage-360-hotspot--pulse:before{animation:none}}.cloudimage-360-popper{background:var(--ci360-popper-bg);color:var(--ci360-popper-color);border:var(--ci360-popper-border);padding:var(--ci360-popper-padding);border-radius:var(--ci360-popper-border-radius);box-shadow:var(--ci360-popper-shadow);font-family:system-ui,-apple-system,sans-serif;font-size:var(--ci360-popper-font-size);line-height:var(--ci360-popper-line-height);max-width:var(--ci360-popper-max-width);z-index:9999;text-align:left;transition:opacity .2s ease;opacity:0}.cloudimage-360-popper[data-show]{opacity:1}.ci360-popper-image-wrapper{aspect-ratio:16 / 9;overflow:hidden;border-radius:calc(var(--ci360-popper-border-radius) - 4px) calc(var(--ci360-popper-border-radius) - 4px) 0 0;margin:calc(var(--ci360-popper-padding) * -1);margin-bottom:12px;width:calc(100% + var(--ci360-popper-padding) * 2)}.ci360-popper-image{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.ci360-popper-body{line-height:1.5;text-align:left}.ci360-popper-title{margin:0 0 4px;font-size:var(--ci360-popper-title-font-size);font-weight:var(--ci360-popper-title-font-weight);color:var(--ci360-popper-title-color);line-height:1.3}.ci360-popper-price-row{display:inline-flex;align-items:baseline;gap:6px;margin-bottom:8px;flex-wrap:wrap}.ci360-popper-price{font-size:var(--ci360-popper-price-font-size);font-weight:var(--ci360-popper-price-font-weight);color:var(--ci360-popper-price-color)}.ci360-popper-price-row .ci360-popper-price{margin-bottom:0}.ci360-popper-original-price{font-size:calc(var(--ci360-popper-price-font-size) - 2px);font-weight:500;color:var(--ci360-popper-original-price-color);text-decoration:line-through}.ci360-popper-description{margin:0 0 12px;color:var(--ci360-popper-description-color)}.ci360-popper-cta{display:inline-block;padding:var(--ci360-popper-cta-padding);background:var(--ci360-popper-cta-bg);color:var(--ci360-popper-cta-color);border-radius:var(--ci360-popper-cta-border-radius);text-decoration:none;font-weight:600;font-size:14px;transition:opacity .2s ease}.ci360-popper-cta:hover{opacity:.9}button.ci360-popper-cta{cursor:pointer;border:none;font:inherit;width:100%}.ci360-popper-cta:focus-visible{outline:3px solid var(--ci360-focus-color, #0066cc);outline-offset:2px}
1
+ :root{--ci360-button-bg: rgba(255, 255, 255, .9);--ci360-button-bg-hover: rgba(255, 255, 255, 1);--ci360-button-size: 40px;--ci360-button-border-radius: 6px;--ci360-button-shadow: 0 2px 8px rgba(0, 0, 0, .15);--ci360-icon-color: #505050;--ci360-icon-color-hover: #333333;--ci360-icon-size: 20px;--ci360-initial-icon-bg: rgba(255, 255, 255, .15);--ci360-initial-icon-color: #ffffff;--ci360-initial-icon-size: 100px;--ci360-initial-icon-shadow: 0 2px 16px rgba(0, 0, 0, .08);--ci360-initial-icon-border-radius: 16px;--ci360-spinner-color: rgba(0, 0, 0, .2);--ci360-spinner-accent: #505050;--ci360-spinner-size: 30px;--ci360-fullscreen-bg: #ffffff;--ci360-zoom-controls-border-radius: 8px;--ci360-scroll-hint-bg: rgba(0, 0, 0, .7);--ci360-scroll-hint-color: #ffffff;--ci360-scroll-hint-border-radius: 20px;--ci360-focus-color: #0066cc;--ci360-hotspot-marker-size: 24px;--ci360-hotspot-marker-bg: rgba(255, 255, 255, .8);--ci360-hotspot-marker-color: #1a1a1a;--ci360-hotspot-marker-border-width: 2px;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .4);--ci360-hotspot-marker-border: var(--ci360-hotspot-marker-border-width) solid var(--ci360-hotspot-marker-border-color);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .15);--ci360-hotspot-pulse-color: rgba(0, 0, 0, .15);--ci360-hotspot-pulse-size: 40px;--ci360-hotspot-pulse-duration: 1.8s;--ci360-hotspot-brand-color: #00aaff;--ci360-popper-bg: #ffffff;--ci360-popper-color: #1a1a1a;--ci360-popper-border: 1px solid rgba(0, 0, 0, .1);--ci360-popper-border-radius: 12px;--ci360-popper-shadow: 0 8px 32px rgba(0, 0, 0, .12);--ci360-popper-padding: 16px;--ci360-popper-max-width: 320px;--ci360-popper-font-size: 14px;--ci360-popper-line-height: 1.5;--ci360-popper-title-font-size: 16px;--ci360-popper-title-font-weight: 600;--ci360-popper-title-color: #1a1a1a;--ci360-popper-price-color: #2d8c3c;--ci360-popper-price-font-size: 18px;--ci360-popper-price-font-weight: 700;--ci360-popper-original-price-color: #999999;--ci360-popper-description-color: #666666;--ci360-popper-cta-bg: #0058a3;--ci360-popper-cta-color: #fff;--ci360-popper-cta-border-radius: 8px;--ci360-popper-cta-padding: 8px 16px;--ci360-hints-bg: rgba(255, 255, 255, .9);--ci360-hints-color: #333333;--ci360-hints-font-size: 13px;--ci360-hints-border-radius: 20px;--ci360-hints-shadow: 0 2px 12px rgba(0, 0, 0, .1);--ci360-circle-color: #888888;--ci360-timeline-height: 6px;--ci360-timeline-track-bg: rgba(255, 255, 255, .4);--ci360-timeline-dot-size: 18px;--ci360-timeline-dot-color: var(--ci360-hotspot-marker-bg);--ci360-timeline-dot-active-color: var(--ci360-hotspot-brand-color);--ci360-timeline-dot-border: 2px solid #fff;--ci360-timeline-indicator-size: 12px;--ci360-timeline-indicator-color: var(--ci360-hotspot-brand-color);--ci360-timeline-tooltip-bg: rgba(255, 255, 255, .95);--ci360-timeline-tooltip-color: #333333}.ci360-theme-dark{--ci360-button-bg: rgba(40, 40, 45, .9);--ci360-button-bg-hover: rgba(55, 55, 60, .95);--ci360-button-shadow: 0 2px 8px rgba(0, 0, 0, .4);--ci360-icon-color: #e0e0e0;--ci360-icon-color-hover: #ffffff;--ci360-spinner-color: rgba(255, 255, 255, .2);--ci360-spinner-accent: #e0e0e0;--ci360-fullscreen-bg: #1a1a1f;--ci360-zoom-controls-bg: rgba(40, 40, 45, .9);--ci360-zoom-controls-bg-hover: rgba(55, 55, 60, .95);--ci360-zoom-controls-color: #e0e0e0;--ci360-zoom-controls-color-hover: #ffffff;--ci360-zoom-controls-shadow: 0 2px 8px rgba(0, 0, 0, .4);--ci360-scroll-hint-bg: rgba(255, 255, 255, .15);--ci360-scroll-hint-color: #e0e0e0;--ci360-popper-bg: #1a1a1a;--ci360-popper-color: #f0f0f0;--ci360-popper-border: 1px solid rgba(255, 255, 255, .1);--ci360-popper-shadow: 0 8px 32px rgba(0, 0, 0, .4);--ci360-popper-title-color: #f0f0f0;--ci360-popper-description-color: #aaaaaa;--ci360-popper-original-price-color: #777777;--ci360-hints-bg: rgba(40, 40, 45, .9);--ci360-hints-color: #e0e0e0;--ci360-hints-shadow: 0 2px 12px rgba(0, 0, 0, .3);--ci360-circle-color: #b0b0b0;--ci360-timeline-track-bg: rgba(255, 255, 255, .4);--ci360-timeline-dot-border: 2px solid rgba(255, 255, 255, .9);--ci360-timeline-indicator-color: var(--ci360-hotspot-brand-color);--ci360-timeline-tooltip-bg: rgba(40, 40, 45, .95);--ci360-timeline-tooltip-color: #e0e0e0;--ci360-loader-bg: rgba(40, 40, 45, .9);--ci360-loader-color: #e0e0e0;--ci360-loader-shadow: 0 4px 20px rgba(0, 0, 0, .4);--ci360-hotspot-marker-bg: rgba(0, 0, 0, .6);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .6);--ci360-hotspot-pulse-color: rgba(255, 255, 255, .15);--ci360-timeline-dot-color: var(--ci360-hotspot-marker-bg)}.ci360-hotspot-marker-inverted{--ci360-hotspot-marker-bg: rgba(0, 0, 0, .6);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .8);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .3);--ci360-hotspot-pulse-color: rgba(0, 0, 0, .2);--ci360-hotspot-navigate-bg: rgba(0, 0, 0, .6);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .8)}.ci360-theme-dark.ci360-hotspot-marker-inverted{--ci360-hotspot-marker-bg: rgba(255, 255, 255, .8);--ci360-hotspot-marker-color: #1a1a1a;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .4);--ci360-hotspot-pulse-color: rgba(255, 255, 255, .2);--ci360-hotspot-navigate-bg: rgba(255, 255, 255, .8);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .4)}.ci360-hotspot-marker-brand{--ci360-hotspot-marker-bg: var(--ci360-hotspot-brand-color);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .9);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .25);--ci360-hotspot-pulse-color: rgba(255, 255, 255, .5);--ci360-hotspot-navigate-bg: var(--ci360-hotspot-brand-color);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .9)}.ci360-theme-dark.ci360-hotspot-marker-brand{--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .6);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .4);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .6)}.ci360-hotspot-marker--theme-inverted{--ci360-hotspot-marker-bg: rgba(0, 0, 0, .6);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .8);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .3);--ci360-hotspot-pulse-color: rgba(0, 0, 0, .2);--ci360-hotspot-navigate-bg: rgba(0, 0, 0, .6);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .8)}.ci360-theme-dark .ci360-hotspot-marker--theme-inverted{--ci360-hotspot-marker-bg: rgba(255, 255, 255, .8);--ci360-hotspot-marker-color: #1a1a1a;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .4);--ci360-hotspot-pulse-color: rgba(255, 255, 255, .2);--ci360-hotspot-navigate-bg: rgba(255, 255, 255, .8);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .4)}.ci360-hotspot-marker--theme-brand{--ci360-hotspot-marker-bg: var(--ci360-hotspot-brand-color, #00aaff);--ci360-hotspot-marker-color: #ffffff;--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .9);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .25);--ci360-hotspot-pulse-color: rgba(255, 255, 255, .5);--ci360-hotspot-navigate-bg: var(--ci360-hotspot-brand-color, #00aaff);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .9)}.ci360-theme-dark .ci360-hotspot-marker--theme-brand{--ci360-hotspot-marker-border-color: rgba(255, 255, 255, .6);--ci360-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .4);--ci360-hotspot-navigate-border-color: rgba(255, 255, 255, .6)}@keyframes rotation{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.cloudimage-360{width:100%;position:relative}.cloudimage-360-inner-box{width:100%;position:relative;overflow:hidden}.cloudimage-360-icons-container{position:absolute;display:flex;top:15px;right:10px;height:100%;flex-direction:column;align-items:center;z-index:100;gap:8px}.cloudimage-360-button{width:var(--ci360-button-size);height:var(--ci360-button-size);cursor:pointer;transition:transform .15s ease-out,background-color .15s ease-out,box-shadow .15s ease-out;display:flex;align-items:center;justify-content:center;border-radius:var(--ci360-button-border-radius);border:none;background-color:var(--ci360-button-bg);box-shadow:var(--ci360-button-shadow);padding:6px;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-button:hover{transform:scale(1.05);background-color:var(--ci360-button-bg-hover)}.cloudimage-360-button:focus{outline:none}.cloudimage-360-button:focus-visible{outline:2px solid var(--ci360-focus-color);outline-offset:2px}.cloudimage-360-button svg{width:var(--ci360-icon-size);height:var(--ci360-icon-size);stroke:var(--ci360-icon-color);transition:stroke .15s ease-out}.cloudimage-360-button:hover svg{stroke:var(--ci360-icon-color-hover)}.cloudimage-initial-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:var(--ci360-initial-icon-size);aspect-ratio:150 / 87;border-radius:var(--ci360-initial-icon-border-radius);background-color:var(--ci360-initial-icon-bg);box-shadow:var(--ci360-initial-icon-shadow);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:all .3s ease;color:var(--ci360-initial-icon-color);display:flex;align-items:center;justify-content:center;z-index:21;-webkit-user-select:none;-moz-user-select:none;user-select:none;padding:12px 16px}.cloudimage-initial-icon svg{width:100%;height:auto;color:var(--ci360-initial-icon-color);fill:var(--ci360-initial-icon-color)}.cloudimage-initial-icon:hover{transform:translate(-50%,-50%) scale(1.08)}.cloudimage-360-click-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:22;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;background:#0000004d;cursor:pointer;transition:background .3s ease}.cloudimage-360-click-overlay:hover{background:#00000073}.cloudimage-360-click-overlay-icon{position:static;transform:none}.cloudimage-360-click-overlay:hover .cloudimage-360-click-overlay-icon{transform:scale(1.08)}.cloudimage-360-click-overlay-label{color:#fff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-weight:500;text-shadow:0 1px 3px rgba(0,0,0,.4);-webkit-user-select:none;-moz-user-select:none;user-select:none}.cloudimage-360-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:var(--ci360-loader-size, 70px);height:var(--ci360-loader-size, 70px);border-radius:50%;background-color:var(--ci360-loader-bg, rgba(255, 255, 255, .9));box-shadow:var(--ci360-loader-shadow, 0 4px 20px rgba(0, 0, 0, .15));transition:all .3s ease;color:var(--ci360-loader-color, #505050);display:flex;align-items:center;justify-content:center;z-index:2;-webkit-user-select:none;-moz-user-select:none;user-select:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);font-family:system-ui,-apple-system,sans-serif;font-size:14px;font-weight:600}.cloudimage-loading-spinner{width:var(--ci360-spinner-size);height:var(--ci360-spinner-size);transform:translate(-50%,-50%);border:3px solid var(--ci360-spinner-color);position:absolute;top:15px;left:15px;border-bottom-color:var(--ci360-spinner-accent);border-radius:50%;display:inline-block;box-sizing:border-box;opacity:0;animation:rotation .7s linear infinite}.cloudimage-360-view-360-circle{position:absolute;left:0;bottom:0;width:100%;height:auto;margin:auto;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:.2s all;z-index:2;color:var(--ci360-circle-color)}.cloudimage-360-view-360-circle svg{display:block;width:100%;height:auto}.cloudimage-360--fullscreen{width:100%;height:100%;background-color:var(--ci360-fullscreen-bg)}.cloudimage-360--fullscreen .cloudimage-360-inner-box{height:100%;display:flex;justify-content:center;align-items:center}.cloudimage-360--fullscreen canvas{width:auto!important;max-width:100%;max-height:100%}.cloudimage-360-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.cloudimage-360-hints-overlay{position:absolute;bottom:0;left:0;width:100%;display:flex;align-items:flex-end;justify-content:center;padding-bottom:16px;z-index:50;pointer-events:none;opacity:0;transition:opacity .3s ease}.cloudimage-360-hints-overlay.visible{opacity:1}.cloudimage-360-hints-overlay.hiding{opacity:0}.cloudimage-360-hints-container{display:flex;flex-direction:row;gap:20px;padding:10px 20px;background:var(--ci360-hints-bg);border-radius:var(--ci360-hints-border-radius);box-shadow:var(--ci360-hints-shadow);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-hint-item{display:flex;align-items:center;gap:8px;color:var(--ci360-hints-color);font-size:var(--ci360-hints-font-size);font-family:system-ui,-apple-system,sans-serif;white-space:nowrap}.cloudimage-360-hint-item svg{width:16px;height:16px;flex-shrink:0;stroke:var(--ci360-hints-color)}.cloudimage-360-hint-item span{opacity:.9}@media (max-width: 480px){.cloudimage-360-hints-container{flex-direction:column;gap:8px;padding:12px 16px;border-radius:12px}.cloudimage-360-hint-item{font-size:12px}.cloudimage-360-hint-item svg{width:14px;height:14px}.cloudimage-360-initial-icon{width:calc(var(--ci360-initial-icon-size) * .8);height:calc(var(--ci360-initial-icon-size) * .8)}}.cloudimage-360-inner-box.has-hotspot-timeline .cloudimage-360-hints-overlay{padding-bottom:56px}.cloudimage-360-zoom-controls{position:absolute;display:flex;flex-direction:row;gap:4px;z-index:100;opacity:0;pointer-events:none;transition:opacity .2s ease;background:var(--ci360-zoom-controls-bg, var(--ci360-button-bg));border-radius:var(--ci360-zoom-controls-border-radius);box-shadow:var(--ci360-zoom-controls-shadow, var(--ci360-button-shadow));padding:4px;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-zoom-controls.visible{opacity:1;pointer-events:auto}.cloudimage-360-zoom-controls[data-position=bottom-left]{bottom:12px;left:12px}.cloudimage-360-zoom-controls[data-position=bottom-right]{bottom:12px;right:12px}.cloudimage-360-zoom-controls[data-position=top-left]{top:12px;left:12px}.cloudimage-360-zoom-controls[data-position=top-right]{top:12px;right:12px}.cloudimage-360-zoom-controls[data-position=top-center]{top:12px;left:50%;transform:translate(-50%)}.cloudimage-360-zoom-controls[data-position=bottom-center]{bottom:12px;left:50%;transform:translate(-50%)}.cloudimage-360-zoom-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border:none;background:transparent;border-radius:calc(var(--ci360-zoom-controls-border-radius) - 2px);cursor:pointer;padding:0;color:var(--ci360-zoom-controls-color, var(--ci360-icon-color));transition:background-color .15s ease,color .15s ease}.cloudimage-360-zoom-btn:hover:not(:disabled){background:var(--ci360-zoom-controls-bg-hover, var(--ci360-button-bg-hover));color:var(--ci360-zoom-controls-color-hover, var(--ci360-icon-color-hover))}.cloudimage-360-zoom-btn:disabled{opacity:.3;cursor:default}.cloudimage-360-zoom-btn:focus{outline:none}.cloudimage-360-zoom-btn:focus-visible{outline:2px solid var(--ci360-focus-color);outline-offset:-2px}.cloudimage-360-zoom-btn svg{width:18px;height:18px;stroke:currentColor}.cloudimage-360-zoom-separator{width:1px;align-self:stretch;margin:6px 0;background:#80808033}.cloudimage-360-scroll-hint{position:absolute;bottom:50%;left:50%;transform:translate(-50%,50%);background:var(--ci360-scroll-hint-bg);color:var(--ci360-scroll-hint-color);padding:8px 16px;border-radius:var(--ci360-scroll-hint-border-radius);font-size:13px;font-family:system-ui,-apple-system,sans-serif;font-weight:500;white-space:nowrap;pointer-events:none;z-index:200;opacity:0;transition:opacity .3s ease;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cloudimage-360-scroll-hint.visible{opacity:1}.cloudimage-360-hotspot-timeline{position:absolute;bottom:0;left:0;right:0;width:100%;padding:20px;opacity:0;transition:opacity .3s ease;pointer-events:none;z-index:30;background:linear-gradient(to top,rgba(0,0,0,.45) 0%,rgba(0,0,0,.2) 60%,transparent 100%)}.cloudimage-360-hotspot-timeline.visible{opacity:1;pointer-events:auto}.cloudimage-360-hotspot-timeline-track{position:relative;width:100%;max-width:500px;margin:0 auto;height:var(--ci360-timeline-height);background:var(--ci360-timeline-track-bg);border-radius:calc(var(--ci360-timeline-height) / 2)}.cloudimage-360-hotspot-timeline-indicator{position:absolute;top:50%;left:0;width:var(--ci360-timeline-indicator-size);height:var(--ci360-timeline-indicator-size);background:var(--ci360-timeline-indicator-color);border-radius:50%;transform:translate(-50%,-50%);transition:left .1s ease-out;pointer-events:none;box-shadow:0 2px 4px #0003}.cloudimage-360-hotspot-timeline-dot{position:absolute;top:50%;width:var(--ci360-timeline-dot-size);height:var(--ci360-timeline-dot-size);background:var(--ci360-timeline-dot-color);border:var(--ci360-timeline-dot-border);border-radius:50%;transform:translate(-50%,-50%);cursor:pointer;transition:transform .15s ease,background .2s ease,box-shadow .2s ease;box-shadow:0 1px 4px #00000026;padding:0;font:inherit;outline:none}.cloudimage-360-hotspot-timeline-dot:hover{transform:translate(-50%,-50%) scale(1.15);box-shadow:0 2px 8px #0003}.cloudimage-360-hotspot-timeline-dot.active{background:var(--ci360-timeline-dot-color);box-shadow:0 1px 4px #00000026}.cloudimage-360-hotspot-timeline-dot:focus-visible{outline:2px solid var(--ci360-focus-color);outline-offset:2px}.cloudimage-360-hotspot-timeline-tooltip{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translate(-50%);background:var(--ci360-timeline-tooltip-bg);color:var(--ci360-timeline-tooltip-color);padding:6px 12px;border-radius:6px;font-size:13px;font-weight:500;white-space:nowrap;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease;pointer-events:none;box-shadow:0 2px 8px #00000026;z-index:10}.cloudimage-360-hotspot-timeline-tooltip:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:6px solid transparent;border-top-color:var(--ci360-timeline-tooltip-bg)}.cloudimage-360-hotspot-timeline-tooltip.visible{opacity:1;visibility:visible}@media (max-width: 480px){.cloudimage-360-hotspot-timeline{padding:14px 16px}}.cloudimage-360--fullscreen .cloudimage-360-hotspot-timeline{bottom:0}.cloudimage-360--fullscreen .cloudimage-360-hints-overlay{padding-bottom:32px}.cloudimage-360--fullscreen .cloudimage-360-zoom-controls[data-position^=bottom]{bottom:38px}.cloudimage-360--fullscreen .cloudimage-360-inner-box.has-hotspot-timeline .cloudimage-360-hints-overlay{padding-bottom:72px}.cloudimage-360--fullscreen .cloudimage-360-inner-box.has-hotspot-timeline .cloudimage-360-zoom-controls[data-position^=bottom]{bottom:72px}.cloudimage-360-hotspot-container{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:20}.cloudimage-360-hotspot{display:inline-block;position:absolute;top:0;right:0;bottom:0;left:0;transform:translate(-50%,-50%);background:var(--ci360-hotspot-marker-bg);border:var(--ci360-hotspot-marker-border);border-radius:50%;height:var(--ci360-hotspot-marker-size);width:var(--ci360-hotspot-marker-size);box-shadow:var(--ci360-hotspot-marker-shadow);opacity:0;transition:transform .2s ease,box-shadow .2s ease;padding:0;cursor:pointer;font:inherit;outline:none}.cloudimage-360-hotspot:not(.cloudimage-360-hotspot--navigate):after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:8px;height:8px;border-radius:50%;background:var(--ci360-hotspot-marker-color);opacity:.45;pointer-events:none}.cloudimage-360-hotspot:focus-visible{outline:2px solid var(--ci360-focus-color, #0066cc);outline-offset:2px}.cloudimage-360-hotspot--pulse{overflow:visible}.cloudimage-360-hotspot--pulse:before{content:"";position:absolute;top:50%;left:50%;width:var(--ci360-hotspot-pulse-size);height:var(--ci360-hotspot-pulse-size);border-radius:50%;background:var(--ci360-hotspot-pulse-color);transform:translate(-50%,-50%) scale(1);animation:ci360-pulse-ring var(--ci360-hotspot-pulse-duration) ease-out infinite;pointer-events:none}@keyframes ci360-pulse-ring{0%{transform:translate(-50%,-50%) scale(1);opacity:1}to{transform:translate(-50%,-50%) scale(1.8);opacity:0}}.cloudimage-360-hotspot--pulse{animation:ci360-breathe 2.4s ease-in-out infinite}@keyframes ci360-breathe{0%,to{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-50%,-50%) scale(1.15)}}.cloudimage-360-hotspot--pulse:hover,.cloudimage-360-hotspot--pulse:focus-visible{animation:none}.cloudimage-360-hotspot--dot-label{animation:none}.cloudimage-360-hotspot-label{position:absolute;left:calc(50% + var(--ci360-hotspot-marker-size) / 2 + 4px);top:50%;transform:translateY(-50%);padding:3px 10px;font-size:11px;font-weight:600;white-space:nowrap;color:var(--ci360-hotspot-marker-color);background:var(--ci360-hotspot-marker-bg);border:var(--ci360-hotspot-marker-border);border-radius:100px;box-shadow:var(--ci360-hotspot-marker-shadow);pointer-events:none}.cloudimage-360-hotspot--navigate{display:inline-flex;align-items:center;justify-content:center;background:var(--ci360-hotspot-navigate-bg, #6366f1);border-color:var(--ci360-hotspot-navigate-border-color, rgba(255, 255, 255, .9))}.ci360-navigate-icon{width:14px;height:14px;color:#fff;pointer-events:none}.cloudimage-360-hotspot--navigate.cloudimage-360-hotspot--pulse{animation:none}.cloudimage-360-hotspot--navigate:hover,.cloudimage-360-hotspot--navigate:focus-visible{transform:translate(-50%,-50%) scale(1.15);box-shadow:0 0 0 3px #6366f166}.cloudimage-360-hotspot--dot-label:hover{transform:translate(-50%,-50%);box-shadow:0 4px 12px #0006}.cloudimage-360-hotspot:hover{transform:translate(-50%,-50%) scale(1.2);box-shadow:0 4px 12px #0006}@media (prefers-reduced-motion: reduce){.cloudimage-360-hotspot--pulse{animation:none}.cloudimage-360-hotspot--pulse:before{animation:none}}.cloudimage-360-popper{background:var(--ci360-popper-bg);color:var(--ci360-popper-color);border:var(--ci360-popper-border);padding:var(--ci360-popper-padding);border-radius:var(--ci360-popper-border-radius);box-shadow:var(--ci360-popper-shadow);font-family:system-ui,-apple-system,sans-serif;font-size:var(--ci360-popper-font-size);line-height:var(--ci360-popper-line-height);max-width:var(--ci360-popper-max-width);z-index:9999;text-align:left;transition:opacity .2s ease;opacity:0}.cloudimage-360-popper[data-show]{opacity:1}.ci360-popper-image-wrapper{aspect-ratio:16 / 9;overflow:hidden;border-radius:calc(var(--ci360-popper-border-radius) - 4px) calc(var(--ci360-popper-border-radius) - 4px) 0 0;margin:calc(var(--ci360-popper-padding) * -1);margin-bottom:12px;width:calc(100% + var(--ci360-popper-padding) * 2)}.ci360-popper-image{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.ci360-popper-body{line-height:1.5;text-align:left}.ci360-popper-title{margin:0 0 4px;font-size:var(--ci360-popper-title-font-size);font-weight:var(--ci360-popper-title-font-weight);color:var(--ci360-popper-title-color);line-height:1.3}.ci360-popper-price-row{display:inline-flex;align-items:baseline;gap:6px;margin-bottom:8px;flex-wrap:wrap}.ci360-popper-price{font-size:var(--ci360-popper-price-font-size);font-weight:var(--ci360-popper-price-font-weight);color:var(--ci360-popper-price-color)}.ci360-popper-price-row .ci360-popper-price{margin-bottom:0}.ci360-popper-original-price{font-size:calc(var(--ci360-popper-price-font-size) - 2px);font-weight:500;color:var(--ci360-popper-original-price-color);text-decoration:line-through}.ci360-popper-description{margin:0 0 12px;color:var(--ci360-popper-description-color)}.ci360-popper-cta{display:inline-block;padding:var(--ci360-popper-cta-padding);background:var(--ci360-popper-cta-bg);color:var(--ci360-popper-cta-color);border-radius:var(--ci360-popper-cta-border-radius);text-decoration:none;font-weight:600;font-size:14px;transition:opacity .2s ease}.ci360-popper-cta:hover{opacity:.9}button.ci360-popper-cta{cursor:pointer;border:none;font:inherit;width:100%}.ci360-popper-cta:focus-visible{outline:3px solid var(--ci360-focus-color, #0066cc);outline-offset:2px}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-cloudimage-360-view",
3
- "version": "4.9.3",
3
+ "version": "4.9.5",
4
4
  "main": "dist/js-cloudimage-360-view.min.js",
5
5
  "types": "src/types/ci360.d.ts",
6
6
  "files": [
@@ -76,8 +76,16 @@ export interface Hotspot {
76
76
  content?: string;
77
77
  data?: PopoverData;
78
78
  className?: string;
79
+ /** If true, the popover stays open until explicitly closed. */
80
+ keepOpen?: boolean;
81
+ /** Custom click handler for the hotspot. */
82
+ onClick?: (event: MouseEvent, popperInstance: any, hotspotId: string) => void;
83
+ /** Per-hotspot marker theme override. */
84
+ markerTheme?: MarkerTheme;
79
85
  /** Scene ID to navigate to when this hotspot is clicked. */
80
86
  navigateTo?: string;
87
+ /** Rotation angle (degrees) for the navigation arrow icon. Only applies when navigateTo is set. */
88
+ arrowDirection?: number;
81
89
  }
82
90
 
83
91
  /**
@@ -195,6 +203,7 @@ export interface CI360Config {
195
203
  hotspots?: Hotspot[] | null;
196
204
  hotspotTrigger?: HotspotTrigger;
197
205
  hotspotTimelineOnClick?: boolean;
206
+ initOnClick?: boolean;
198
207
  onReady?: (data: BaseEventData) => void;
199
208
  onLoad?: (data: LoadEventData) => void;
200
209
  onSpin?: (data: SpinEventData) => void;
@@ -47,8 +47,9 @@ declare module CI360 {
47
47
  theme?: 'light' | 'dark' | null; // Default: null
48
48
  markerTheme?: 'default' | 'inverted' | 'brand' | null; // Default: null
49
49
  brandColor?: string | null; // Default: null
50
- hotspots?: any[] | null; // Default: null
50
+ hotspots?: Array<{ markerTheme?: 'default' | 'inverted' | 'brand'; [key: string]: any }> | null; // Default: null
51
51
  hotspotTrigger?: 'hover' | 'click'; // Default: 'hover'
52
+ initOnClick?: boolean; // Default: false — if true, images load only after the user clicks on the viewer
52
53
  hotspotTimelineOnClick?: boolean; // Default: true
53
54
  aspectRatio?: string | null; // Default: null — CSS container aspect-ratio e.g. "16 / 9"
54
55
  cropAspectRatio?: string | null; // Default: null — CDN crop ratio e.g. "16:9"