@mmmmzxe/react-360-view 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 maryemmostafa24
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # @mmmmzxe/react-360-viewer
2
+
3
+ A standalone, configurable 360° image viewer for React with drag rotation, zoom, hotspots, and auto-rotate support.
4
+
5
+ ## Features
6
+
7
+ * Drag-to-rotate frame navigation
8
+ * Zoom with mouse wheel and toolbar controls
9
+ * Pan when zoomed
10
+ * Optional auto-rotate
11
+ * Interactive hotspots with tooltips
12
+ * Hotspot add mode
13
+ * TypeScript support
14
+ * Tailwind + shadcn compatible
15
+ * Headless hook support
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @mmmmzxe/react-360-viewer
21
+ ```
22
+
23
+ ### Peer Dependencies
24
+
25
+ ```bash
26
+ npm install react react-dom lucide-react
27
+ ```
28
+
29
+ ### Styles
30
+
31
+ Import the stylesheet once in your app (e.g. in your root layout or entry file):
32
+
33
+ ```tsx
34
+ import '@mmmmzxe/react-360-viewer/styles.css';
35
+ ```
36
+
37
+ Styles are scoped to `[data-viewer-360]` via CSS `@scope` and will not override your app's global theme or Tailwind classes. Requires a **client component** in Next.js App Router (`'use client'`).
38
+
39
+ Wrap the viewer in a sized container if needed:
40
+
41
+ ```tsx
42
+ <div className="w-full max-w-3xl">
43
+ <Viewer360 frames={frames} />
44
+ </div>
45
+ ```
46
+
47
+ If styles don't appear after updating, delete `.next` and restart the dev server.
48
+
49
+ ---
50
+
51
+ ## Quick Start
52
+
53
+ ```tsx
54
+ 'use client';
55
+
56
+ import '@mmmmzxe/react-360-viewer/styles.css';
57
+ import { useState } from "react";
58
+ import {
59
+ Viewer360,
60
+ type Viewer360Frame,
61
+ } from "@mmmmzxe/react-360-viewer";
62
+
63
+ const frames: Viewer360Frame[] = [
64
+ { id: "1", src: "/images/frame-01.jpg", label: "Front" },
65
+ { id: "2", src: "/images/frame-02.jpg", label: "Side" },
66
+ { id: "3", src: "/images/frame-03.jpg", label: "Rear" },
67
+ ];
68
+
69
+ export default function ProductViewer() {
70
+ const [frameIndex, setFrameIndex] = useState(0);
71
+
72
+ return (
73
+ <Viewer360
74
+ frames={frames}
75
+ currentFrameIndex={frameIndex}
76
+ onFrameChange={setFrameIndex}
77
+ />
78
+ );
79
+ }
80
+ ```
81
+
82
+ ## Hotspots
83
+
84
+ ```tsx
85
+ <Viewer360
86
+ frames={frames}
87
+ hotspots={[
88
+ {
89
+ id: "1",
90
+ frameIndex: 0,
91
+ positionX: 50,
92
+ positionY: 40,
93
+ data: {
94
+ title: "Scratch",
95
+ description: "Front bumper",
96
+ },
97
+ },
98
+ ]}
99
+ />
100
+ ```
101
+
102
+ ### Add Hotspots
103
+
104
+ ```tsx
105
+ <Viewer360
106
+ frames={frames}
107
+ hotspots={hotspots}
108
+ showHotspotModeControl
109
+ onHotspotAdd={(position) => {
110
+ console.log(position);
111
+ }}
112
+ />
113
+ ```
114
+
115
+ ## Configuration
116
+
117
+ ```tsx
118
+ <Viewer360
119
+ config={{
120
+ minZoom: 1,
121
+ maxZoom: 4,
122
+ zoomStep: 0.2,
123
+ dragSensitivity: 8,
124
+ autoRotate: true,
125
+ autoRotateIntervalMs: 150,
126
+ }}
127
+ />
128
+ ```
129
+
130
+ ## Main Props
131
+
132
+ | Prop | Type | Description |
133
+ | ---------------------- | ------------------ | ------------------------- |
134
+ | frames | Viewer360Frame[] | Required image frames |
135
+ | hotspots | Viewer360Hotspot[] | Hotspots to display |
136
+ | currentFrameIndex | number | Controlled frame |
137
+ | onFrameChange | function | Frame change callback |
138
+ | config | Viewer360Config | Viewer settings |
139
+ | showFrameIndicator | boolean | Show frame counter |
140
+ | showHotspotModeControl | boolean | Enable add hotspot button |
141
+ | onHotspotAdd | function | Add hotspot callback |
142
+
143
+ ## Development
144
+
145
+ ```bash
146
+ npm install
147
+ npm run build
148
+ npm run dev
149
+ npm run test-run
150
+ ```
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,379 @@
1
+ import * as React$1 from 'react';
2
+ import { ReactNode, MouseEvent, CSSProperties, JSX, RefObject, PointerEvent, WheelEvent } from 'react';
3
+ import { ClassValue } from 'clsx';
4
+ import * as class_variance_authority_types from 'class-variance-authority/types';
5
+ import { VariantProps } from 'class-variance-authority';
6
+
7
+ declare const defaultViewer360Config: {
8
+ minZoom: number;
9
+ maxZoom: number;
10
+ zoomStep: number;
11
+ dragSensitivity: number;
12
+ autoRotate: boolean;
13
+ autoRotateIntervalMs: number;
14
+ autoRotateDirection: "forward";
15
+ };
16
+
17
+ type Viewer360Frame = {
18
+ id: string;
19
+ src: string;
20
+ label?: string;
21
+ };
22
+ type Viewer360PanOffset = {
23
+ panX: number;
24
+ panY: number;
25
+ };
26
+ type Viewer360Config = {
27
+ minZoom?: number;
28
+ maxZoom?: number;
29
+ zoomStep?: number;
30
+ dragSensitivity?: number;
31
+ autoRotate?: boolean;
32
+ autoRotateIntervalMs?: number;
33
+ autoRotateDirection?: 'forward' | 'backward';
34
+ };
35
+ type Viewer360Hotspot<TData = unknown> = {
36
+ id: string;
37
+ frameIndex: number;
38
+ positionX: number;
39
+ positionY: number;
40
+ data?: TData;
41
+ };
42
+ type Viewer360HotspotPosition = {
43
+ frameIndex: number;
44
+ frameId: string;
45
+ positionX: number;
46
+ positionY: number;
47
+ };
48
+ type Viewer360HotspotRenderProps<TData = unknown> = {
49
+ hotspot: Viewer360Hotspot<TData>;
50
+ leftPercent: number;
51
+ topPercent: number;
52
+ };
53
+ type Viewer360OverlayRenderProps = {
54
+ currentFrameIndex: number;
55
+ frameCount: number;
56
+ frameLabel?: string;
57
+ isHotspotMode: boolean;
58
+ labels: Required<Viewer360Labels>;
59
+ frameIndicatorClassName: string;
60
+ };
61
+ type Viewer360ToolbarRenderProps = {
62
+ zoom: number;
63
+ minZoom: number;
64
+ maxZoom: number;
65
+ isResetDisabled: boolean;
66
+ isHotspotMode: boolean;
67
+ showHotspotModeControl: boolean;
68
+ showZoomControls: boolean;
69
+ showResetControl: boolean;
70
+ showDragHint: boolean;
71
+ labels: Required<Viewer360Labels>;
72
+ onZoomIn: () => void;
73
+ onZoomOut: () => void;
74
+ onResetView: () => void;
75
+ onHotspotModeChange: (active: boolean) => void;
76
+ };
77
+
78
+ type Viewer360Marker = {
79
+ id: string;
80
+ title: string;
81
+ description?: string;
82
+ };
83
+ type Viewer360MarkerPinClassNames = {
84
+ root?: string;
85
+ ping?: string;
86
+ dot?: string;
87
+ tooltip?: string;
88
+ tooltipHeader?: string;
89
+ tooltipBody?: string;
90
+ tooltipTitle?: string;
91
+ tooltipDescription?: string;
92
+ deleteButton?: string;
93
+ };
94
+ type Viewer360MarkerPinLabels = {
95
+ delete?: string;
96
+ };
97
+ type Viewer360MarkerPinRenderProps<TData = unknown> = {
98
+ marker: Viewer360Marker;
99
+ hotspot?: Viewer360Hotspot<TData>;
100
+ };
101
+ type Viewer360MarkerPinProps<TData = unknown> = {
102
+ marker: Viewer360Marker;
103
+ hotspot?: Viewer360Hotspot<TData>;
104
+ leftPercent: number;
105
+ topPercent: number;
106
+ onDelete?: (id: string) => void;
107
+ isDeletePending?: boolean;
108
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
109
+ renderTag?: (props: Viewer360MarkerPinRenderProps<TData>) => ReactNode;
110
+ classNames?: Viewer360MarkerPinClassNames;
111
+ labels?: Viewer360MarkerPinLabels;
112
+ };
113
+ type Viewer360HotspotPinOptions<TData = unknown> = {
114
+ onDelete?: (id: string) => void;
115
+ deletingMarkerId?: string | null;
116
+ getMarker?: (hotspot: Viewer360Hotspot<TData>) => Viewer360Marker;
117
+ renderTag?: (props: Viewer360MarkerPinRenderProps<TData>) => ReactNode;
118
+ classNames?: Viewer360MarkerPinClassNames;
119
+ labels?: Viewer360MarkerPinLabels;
120
+ };
121
+
122
+ type Viewer360ClassNames = {
123
+ root?: string;
124
+ viewport?: string;
125
+ canvas?: string;
126
+ overlay?: string;
127
+ loading?: string;
128
+ loadingText?: string;
129
+ frameIndicator?: string;
130
+ hotspotModeBanner?: string;
131
+ toolbar?: string;
132
+ dragHint?: string;
133
+ controls?: string;
134
+ controlButton?: string;
135
+ controlButtonActive?: string;
136
+ controlButtonDisabled?: string;
137
+ zoomDisplay?: string;
138
+ divider?: string;
139
+ };
140
+ type Viewer360Labels = {
141
+ loading?: string;
142
+ dragHint?: string;
143
+ frameIndicator?: (params: {
144
+ current: number;
145
+ total: number;
146
+ label?: string;
147
+ }) => string;
148
+ zoom?: (percent: number) => string;
149
+ hotspotModeActive?: string;
150
+ addHotspot?: string;
151
+ zoomIn?: string;
152
+ zoomOut?: string;
153
+ resetView?: string;
154
+ deleteMarker?: string;
155
+ };
156
+ type Viewer360Theme = {
157
+ '--viewer-bg'?: string;
158
+ '--viewer-border'?: string;
159
+ '--viewer-text'?: string;
160
+ '--viewer-muted'?: string;
161
+ '--viewer-accent'?: string;
162
+ '--viewer-accent-foreground'?: string;
163
+ '--viewer-control-bg'?: string;
164
+ '--viewer-control-border'?: string;
165
+ '--viewer-hotspot-banner-bg'?: string;
166
+ '--viewer-hotspot-banner-border'?: string;
167
+ '--viewer-hotspot-banner-text'?: string;
168
+ };
169
+ type Viewer360Props<TData = unknown> = {
170
+ frames: Viewer360Frame[];
171
+ currentFrameIndex?: number;
172
+ defaultFrameIndex?: number;
173
+ onFrameChange?: (index: number) => void;
174
+ config?: Viewer360Config;
175
+ className?: string;
176
+ classNames?: Viewer360ClassNames;
177
+ style?: CSSProperties;
178
+ theme?: Viewer360Theme;
179
+ labels?: Viewer360Labels;
180
+ aspectRatio?: string;
181
+ showZoomControls?: boolean;
182
+ showResetControl?: boolean;
183
+ showFrameIndicator?: boolean;
184
+ showDragHint?: boolean;
185
+ showHotspotModeControl?: boolean;
186
+ hotspotPin?: Viewer360HotspotPinOptions<TData>;
187
+ hotspots?: Viewer360Hotspot<TData>[];
188
+ renderHotspot?: (props: Viewer360HotspotRenderProps<TData>) => ReactNode;
189
+ renderLoading?: () => ReactNode;
190
+ renderFrameIndicator?: (props: Viewer360OverlayRenderProps) => ReactNode;
191
+ renderHotspotModeBanner?: (props: Pick<Viewer360OverlayRenderProps, 'labels'>) => ReactNode;
192
+ renderToolbar?: (props: Viewer360ToolbarRenderProps) => ReactNode;
193
+ onHotspotClick?: (hotspot: Viewer360Hotspot<TData>, event: MouseEvent<HTMLDivElement>) => void;
194
+ hotspotMode?: boolean;
195
+ defaultHotspotMode?: boolean;
196
+ onHotspotModeChange?: (active: boolean) => void;
197
+ onHotspotAdd?: (position: Viewer360HotspotPosition) => void;
198
+ children?: ReactNode;
199
+ };
200
+
201
+ declare const defaultViewer360Labels: Required<Viewer360Labels>;
202
+
203
+ declare const defaultViewer360MarkerPinLabels: {
204
+ readonly delete: "Remove marker";
205
+ };
206
+
207
+ declare const viewer360ClassNames: Required<Viewer360ClassNames>;
208
+ declare const viewer360MarkerPinClassNames: Required<Viewer360MarkerPinClassNames>;
209
+ /** @deprecated Use `viewer360ClassNames` */
210
+ declare const defaultViewer360ClassNames: Required<Viewer360ClassNames>;
211
+ /** @deprecated Use `viewer360MarkerPinClassNames` */
212
+ declare const defaultViewer360MarkerPinClassNames: Required<Viewer360MarkerPinClassNames>;
213
+
214
+ declare function Viewer360<TData = unknown>({ frames, currentFrameIndex: controlledFrameIndex, defaultFrameIndex, onFrameChange, config, className, classNames, style, theme, labels, aspectRatio, showZoomControls, showResetControl, showFrameIndicator, showDragHint, showHotspotModeControl, hotspotPin, hotspots, renderHotspot, renderLoading, renderFrameIndicator, renderHotspotModeBanner, renderToolbar, onHotspotClick, hotspotMode: controlledHotspotMode, defaultHotspotMode, onHotspotModeChange, onHotspotAdd, children, }: Viewer360Props<TData>): JSX.Element;
215
+
216
+ type Viewer360AddModeBannerProps = {
217
+ className?: string;
218
+ label: string;
219
+ };
220
+ declare function Viewer360AddModeBanner({ className, label }: Viewer360AddModeBannerProps): JSX.Element;
221
+
222
+ type Viewer360FrameIndicatorProps = {
223
+ className?: string;
224
+ label: string;
225
+ };
226
+ declare function Viewer360FrameIndicator({ className, label }: Viewer360FrameIndicatorProps): JSX.Element;
227
+
228
+ type Viewer360LoadingOverlayProps = {
229
+ className?: string;
230
+ textClassName?: string;
231
+ label: string;
232
+ };
233
+ declare function Viewer360LoadingOverlay({ className, textClassName, label }: Viewer360LoadingOverlayProps): JSX.Element;
234
+
235
+ declare function Viewer360MarkerPin<TData = unknown>({ marker, hotspot, leftPercent, topPercent, onDelete, isDeletePending, onClick, renderTag, classNames, labels, }: Viewer360MarkerPinProps<TData>): JSX.Element;
236
+
237
+ type Viewer360ToolbarProps = Viewer360ToolbarRenderProps;
238
+ declare function Viewer360Toolbar({ showDragHint, showHotspotModeControl, showZoomControls, showResetControl, labels, isHotspotMode, zoom, minZoom, maxZoom, isResetDisabled, onHotspotModeChange, onZoomIn, onZoomOut, onResetView, }: Viewer360ToolbarProps): JSX.Element;
239
+
240
+ type ViewerImageLayout = {
241
+ width: number;
242
+ height: number;
243
+ centerX: number;
244
+ centerY: number;
245
+ drawWidth: number;
246
+ drawHeight: number;
247
+ offsetX: number;
248
+ offsetY: number;
249
+ };
250
+ type PanOffset = {
251
+ panX: number;
252
+ panY: number;
253
+ };
254
+ type ComputeViewerImageLayoutParams = {
255
+ containerWidth: number;
256
+ containerHeight: number;
257
+ imageWidth: number;
258
+ imageHeight: number;
259
+ pan?: PanOffset;
260
+ };
261
+ declare function computeViewerImageLayout({ containerWidth, containerHeight, imageWidth, imageHeight, pan, }: ComputeViewerImageLayoutParams): ViewerImageLayout;
262
+ declare function computeHotspotScreenPosition(hotspotX: number, hotspotY: number, layout: ViewerImageLayout, zoom: number): {
263
+ leftPercent: number;
264
+ topPercent: number;
265
+ };
266
+ declare function computeHotspotPositionFromClick(clientX: number, clientY: number, containerRect: DOMRect, layout: ViewerImageLayout, zoom: number): {
267
+ positionX: number;
268
+ positionY: number;
269
+ };
270
+
271
+ type UseViewer360Params<TData> = {
272
+ frames: Viewer360Frame[];
273
+ currentFrameIndex: number;
274
+ onFrameChange: (index: number) => void;
275
+ hotspots?: Viewer360Hotspot<TData>[];
276
+ config?: Viewer360Config;
277
+ hotspotMode?: boolean;
278
+ onHotspotAdd?: (position: Viewer360HotspotPosition) => void;
279
+ };
280
+ type UseViewer360Return<TData> = {
281
+ canvasRef: RefObject<HTMLCanvasElement | null>;
282
+ containerRef: RefObject<HTMLDivElement | null>;
283
+ currentFrame: Viewer360Frame | undefined;
284
+ currentFrameHotspots: Viewer360Hotspot<TData>[];
285
+ imagesLoaded: boolean;
286
+ isHotspotMode: boolean;
287
+ isResetDisabled: boolean;
288
+ maxZoom: number;
289
+ minZoom: number;
290
+ viewerCursorClass: string;
291
+ zoom: number;
292
+ getCurrentImageLayout: () => ViewerImageLayout | null;
293
+ getHotspotScreenPosition: (hotspot: Viewer360Hotspot<TData>) => {
294
+ leftPercent: number;
295
+ topPercent: number;
296
+ };
297
+ handleCanvasClick: (event: React.MouseEvent<HTMLDivElement>) => void;
298
+ handlePointerDown: (event: PointerEvent<HTMLDivElement>) => void;
299
+ handlePointerMove: (event: PointerEvent<HTMLDivElement>) => void;
300
+ handlePointerUp: (event: PointerEvent<HTMLDivElement>) => void;
301
+ handleResetView: () => void;
302
+ handleWheel: (event: WheelEvent<HTMLDivElement>) => void;
303
+ handleZoomIn: () => void;
304
+ handleZoomOut: () => void;
305
+ };
306
+ declare function useViewer360<TData = unknown>({ frames, hotspots, currentFrameIndex, onFrameChange, config, hotspotMode: hotspotModeProp, onHotspotAdd, }: UseViewer360Params<TData>): UseViewer360Return<TData>;
307
+
308
+ type ResolvedViewer360Config = Required<Viewer360Config>;
309
+ declare function resolveViewer360Config(config?: Viewer360Config): ResolvedViewer360Config;
310
+ declare function clampZoom(zoom: number, config: ResolvedViewer360Config): number;
311
+ declare function applyWheelZoom(currentZoom: number, deltaY: number, currentPan: PanOffset, config: ResolvedViewer360Config): {
312
+ zoom: number;
313
+ pan: PanOffset;
314
+ };
315
+ declare function stepZoomIn(currentZoom: number, config: ResolvedViewer360Config): number;
316
+ declare function stepZoomOut(currentZoom: number, currentPan: PanOffset, config: ResolvedViewer360Config): {
317
+ zoom: number;
318
+ pan: PanOffset;
319
+ };
320
+ declare function isResetDisabled(zoom: number, pan: PanOffset, config: ResolvedViewer360Config): boolean;
321
+ declare function getViewerCursorClass(isHotspotMode: boolean, zoom: number, isDragging: boolean, minZoom: number): string;
322
+
323
+ declare function clampFrameIndex(index: number, frameCount: number): number;
324
+ type DragStart = {
325
+ pointerX: number;
326
+ frameIndex: number;
327
+ };
328
+ declare function computeDragFrameIndex(dragStart: DragStart, clientX: number, frameCount: number, dragSensitivity: number): number | null;
329
+
330
+ type PanStart = {
331
+ pointerX: number;
332
+ pointerY: number;
333
+ panX: number;
334
+ panY: number;
335
+ };
336
+ declare function computeViewerPanOffset(panStart: PanStart, clientX: number, clientY: number): PanOffset;
337
+
338
+ declare function hotspotToViewer360Marker<TData>(hotspot: Viewer360Hotspot<TData>): Viewer360Marker;
339
+ declare function toViewer360Hotspots<TData extends Viewer360Marker>(markers: Array<Viewer360Marker & {
340
+ frameIndex: number;
341
+ positionX: number;
342
+ positionY: number;
343
+ }>, mapData?: (marker: Viewer360Marker & {
344
+ frameIndex: number;
345
+ positionX: number;
346
+ positionY: number;
347
+ }) => TData): Viewer360Hotspot<TData>[];
348
+
349
+ type DrawFrameOnCanvasParams = {
350
+ canvas: HTMLCanvasElement;
351
+ container: HTMLDivElement;
352
+ image: HTMLImageElement;
353
+ zoom: number;
354
+ pan: PanOffset;
355
+ };
356
+ declare function drawFrameOnCanvas({ canvas, container, image, zoom, pan }: DrawFrameOnCanvasParams): void;
357
+ declare function getFramesSignature(frames: Viewer360Frame[]): string;
358
+ declare function preloadFrameImage(frame: Viewer360Frame): Promise<HTMLImageElement>;
359
+ declare function preloadViewerFrames(frames: Viewer360Frame[]): Promise<HTMLImageElement[]>;
360
+ declare function hasLoadedViewerFrame(images: HTMLImageElement[]): boolean;
361
+ declare function filterHotspotsByFrame<TData>(hotspots: Array<{
362
+ frameIndex: number;
363
+ data?: TData;
364
+ }>, frameIndex: number): Array<{
365
+ frameIndex: number;
366
+ data?: TData;
367
+ }>;
368
+
369
+ declare function cn(...inputs: ClassValue[]): string;
370
+
371
+ declare const buttonVariants: (props?: ({
372
+ variant?: "default" | "link" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
373
+ size?: "default" | "icon" | "sm" | "xs" | "lg" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
374
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
375
+ declare function Button({ className, variant, size, asChild, ...props }: React$1.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
376
+ asChild?: boolean;
377
+ }): JSX.Element;
378
+
379
+ export { Button, Viewer360, Viewer360AddModeBanner, type Viewer360ClassNames, type Viewer360Config, type Viewer360Frame, Viewer360FrameIndicator, type Viewer360Hotspot, type Viewer360HotspotPinOptions, type Viewer360HotspotPosition, type Viewer360HotspotRenderProps, type Viewer360Labels, Viewer360LoadingOverlay, type Viewer360Marker, Viewer360MarkerPin, type Viewer360MarkerPinClassNames, type Viewer360MarkerPinLabels, type Viewer360MarkerPinProps, type Viewer360MarkerPinRenderProps, type Viewer360OverlayRenderProps, type Viewer360PanOffset, type Viewer360Props, type Viewer360Theme, Viewer360Toolbar, type Viewer360ToolbarRenderProps, type ViewerImageLayout, applyWheelZoom, buttonVariants, clampFrameIndex, clampZoom, cn, computeDragFrameIndex, computeHotspotPositionFromClick, computeHotspotScreenPosition, computeViewerImageLayout, computeViewerPanOffset, defaultViewer360ClassNames, defaultViewer360Config, defaultViewer360Labels, defaultViewer360MarkerPinClassNames, defaultViewer360MarkerPinLabels, drawFrameOnCanvas, filterHotspotsByFrame, getFramesSignature, getViewerCursorClass, hasLoadedViewerFrame, hotspotToViewer360Marker, isResetDisabled, preloadFrameImage, preloadViewerFrames, resolveViewer360Config, stepZoomIn, stepZoomOut, toViewer360Hotspots, useViewer360, viewer360ClassNames, viewer360MarkerPinClassNames };