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.
- package/README.md +81 -3
- package/dist/js-cloudimage-360-view.min.js +12 -7
- package/dist/react/ci360-NRnvVDl6.js +70 -0
- package/dist/react/ci360-NRnvVDl6.js.map +1 -0
- package/dist/react/{ci360-BFobl9uu.mjs → ci360-VASGkLhF.mjs} +1049 -1012
- package/dist/react/ci360-VASGkLhF.mjs.map +1 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.ts +9 -0
- package/dist/react/index.js +69 -66
- package/dist/react/index.js.map +1 -1
- package/dist/react/style.css +1 -1
- package/package.json +1 -1
- package/src/react/types.d.ts +9 -0
- package/src/types/ci360.d.ts +2 -1
- package/dist/react/ci360-B2r9u7br.js +0 -65
- package/dist/react/ci360-B2r9u7br.js.map +0 -1
- package/dist/react/ci360-BFobl9uu.mjs.map +0 -1
package/dist/react/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
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
|
package/dist/react/index.cjs.map
CHANGED
|
@@ -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":"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,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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/react/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { useState as
|
|
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
|
|
5
|
-
const [C, f] =
|
|
6
|
-
|
|
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-
|
|
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 =
|
|
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
|
|
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:
|
|
83
|
-
autoplayBehavior:
|
|
84
|
-
playOnce:
|
|
85
|
-
speed:
|
|
86
|
-
autoplayReverse:
|
|
87
|
-
dragSpeed:
|
|
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:
|
|
127
|
+
aspectRatio: ce,
|
|
127
128
|
// Event callbacks
|
|
128
|
-
onReady:
|
|
129
|
-
onLoad:
|
|
130
|
-
onSpin:
|
|
131
|
-
onAutoplayStart:
|
|
132
|
-
onAutoplayStop:
|
|
133
|
-
onFullscreenOpen:
|
|
134
|
-
onFullscreenClose:
|
|
135
|
-
onZoomIn:
|
|
136
|
-
onZoomOut:
|
|
137
|
-
onDragStart:
|
|
138
|
-
onDragEnd:
|
|
139
|
-
onHotspotOpen:
|
|
140
|
-
onHotspotClose:
|
|
141
|
-
onProductClick:
|
|
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
|
-
...
|
|
145
|
-
} = d, Ze = I(null),
|
|
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:
|
|
165
|
-
autoplayBehavior:
|
|
166
|
-
playOnce:
|
|
167
|
-
speed:
|
|
168
|
-
autoplayReverse:
|
|
169
|
-
dragSpeed:
|
|
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:
|
|
210
|
+
aspectRatio: ce,
|
|
209
211
|
// Event callbacks
|
|
210
|
-
onReady:
|
|
211
|
-
onLoad:
|
|
212
|
-
onSpin:
|
|
213
|
-
onAutoplayStart:
|
|
214
|
-
onAutoplayStop:
|
|
215
|
-
onFullscreenOpen:
|
|
216
|
-
onFullscreenClose:
|
|
217
|
-
onZoomIn:
|
|
218
|
-
onZoomOut:
|
|
219
|
-
onDragStart:
|
|
220
|
-
onDragEnd:
|
|
221
|
-
onHotspotOpen:
|
|
222
|
-
onHotspotClose:
|
|
223
|
-
onProductClick:
|
|
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
|
-
//
|
|
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 } =
|
|
309
|
-
return
|
|
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__ */
|
|
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
|
-
...
|
|
369
|
+
...ke
|
|
367
370
|
}
|
|
368
371
|
);
|
|
369
|
-
},
|
|
370
|
-
|
|
372
|
+
}, He = Fe(De);
|
|
373
|
+
He.displayName = "CI360Viewer";
|
|
371
374
|
export {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
375
|
+
He as CI360Viewer,
|
|
376
|
+
He as CI360ViewerDefault,
|
|
377
|
+
Be as useCI360,
|
|
378
|
+
Be as useCI360Default
|
|
376
379
|
};
|
|
377
380
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -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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"}
|
package/dist/react/style.css
CHANGED
|
@@ -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
package/src/react/types.d.ts
CHANGED
|
@@ -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;
|
package/src/types/ci360.d.ts
CHANGED
|
@@ -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?:
|
|
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"
|