@mmmmzxe/react-360-viewer 0.1.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.
Files changed (44) hide show
  1. package/README.md +214 -0
  2. package/dist/index.d.ts +377 -0
  3. package/dist/index.js +1237 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +74 -0
  6. package/src/components/ui/Badge/index.tsx +44 -0
  7. package/src/components/ui/Button/index.tsx +68 -0
  8. package/src/components/ui/Card/index.tsx +74 -0
  9. package/src/components/ui/Item/index.tsx +136 -0
  10. package/src/components/ui/Label/index.tsx +20 -0
  11. package/src/components/ui/Popover/index.tsx +56 -0
  12. package/src/components/ui/Separator/index.tsx +30 -0
  13. package/src/components/ui/Spinner/index.tsx +11 -0
  14. package/src/components/utils/index.ts +6 -0
  15. package/src/constants/viewer360ClassNames.ts +41 -0
  16. package/src/constants/viewer360Config.ts +11 -0
  17. package/src/constants/viewer360Labels.ts +14 -0
  18. package/src/constants/viewer360MarkerLabels.ts +3 -0
  19. package/src/feature/Viewer360.test.tsx +47 -0
  20. package/src/feature/Viewer360.tsx +218 -0
  21. package/src/feature/Viewer360AddModeBanner.tsx +20 -0
  22. package/src/feature/Viewer360FrameIndicator.tsx +20 -0
  23. package/src/feature/Viewer360HotspotOverlay.tsx +70 -0
  24. package/src/feature/Viewer360LoadingOverlay.tsx +28 -0
  25. package/src/feature/Viewer360MarkerPin.tsx +115 -0
  26. package/src/feature/Viewer360Toolbar.tsx +75 -0
  27. package/src/helpers/adjustViewerZoom.test.ts +29 -0
  28. package/src/helpers/adjustViewerZoom.ts +64 -0
  29. package/src/helpers/computeDragFrameIndex.test.ts +20 -0
  30. package/src/helpers/computeDragFrameIndex.ts +23 -0
  31. package/src/helpers/computeViewerImageLayout.test.ts +48 -0
  32. package/src/helpers/computeViewerImageLayout.ts +114 -0
  33. package/src/helpers/computeViewerPanOffset.ts +18 -0
  34. package/src/helpers/markerHelpers.test.ts +38 -0
  35. package/src/helpers/markerHelpers.ts +33 -0
  36. package/src/helpers/viewer360PropsHelpers.ts +46 -0
  37. package/src/helpers/viewerHelpers.ts +74 -0
  38. package/src/hooks/useViewer360.ts +306 -0
  39. package/src/index.ts +68 -0
  40. package/src/types/Viewer360Hotspot.ts +66 -0
  41. package/src/types/Viewer360Marker.ts +51 -0
  42. package/src/types/Viewer360Props.ts +108 -0
  43. package/src/types/index.ts +30 -0
  44. package/src/utils/index.ts +6 -0
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # @mmmmzxe/react-360-viewer
2
+
3
+ A standalone, configurable 360° image viewer for React. Drag to rotate through frames, scroll or use controls to zoom, optionally auto-rotate, and place interactive hotspots on any frame.
4
+
5
+ ## Features
6
+
7
+ - Drag-to-rotate frame navigation
8
+ - Zoom via scroll wheel or toolbar controls
9
+ - Pan when zoomed in
10
+ - Optional auto-rotate
11
+ - Hotspot support with built-in marker pins
12
+ - shadcn-style UI with Tailwind CSS design tokens
13
+ - TypeScript-first API
14
+ - Peer dependencies: React, lucide-react, and a Tailwind setup in the host app
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @mmmmzxe/react-360-viewer
20
+ # or
21
+ bun add @mmmmzxe/react-360-viewer
22
+ ```
23
+
24
+ Peer dependencies: `react`, `react-dom` (v18+), and `lucide-react`.
25
+
26
+ ### Tailwind setup
27
+
28
+ The package uses Tailwind utility classes with shadcn design tokens (`bg-card`, `border-border`, `text-muted-foreground`, etc.). Add the package source to your Tailwind CSS entry so those classes are generated:
29
+
30
+ ```css
31
+ @source "../node_modules/@mmmmzxe/react-360-viewer/src/**/*.{ts,tsx}";
32
+ ```
33
+
34
+ Your host app should also define the standard shadcn CSS variables (`--background`, `--foreground`, `--primary`, `--border`, `--muted-foreground`, and related tokens) in your global stylesheet.
35
+
36
+ ## Quick start
37
+
38
+ ```tsx
39
+ import { useState } from 'react';
40
+ import { Viewer360, type Viewer360Frame } from '@mmmmzxe/react-360-viewer';
41
+
42
+ const frames: Viewer360Frame[] = [
43
+ { id: '1', src: '/images/frame-01.jpg', label: 'Front' },
44
+ { id: '2', src: '/images/frame-02.jpg', label: 'Front-right' },
45
+ // ...
46
+ ];
47
+
48
+ export function ProductViewer() {
49
+ const [frameIndex, setFrameIndex] = useState(0);
50
+
51
+ return (
52
+ <Viewer360
53
+ frames={frames}
54
+ currentFrameIndex={frameIndex}
55
+ onFrameChange={setFrameIndex}
56
+ config={{ dragSensitivity: 8, autoRotate: false }}
57
+ />
58
+ );
59
+ }
60
+ ```
61
+
62
+ ## Hotspots & markers
63
+
64
+ Hotspots are percentage-based positions (`positionX`, `positionY` from 0–100) tied to a specific frame.
65
+
66
+ ### Built-in marker pins
67
+
68
+ Use `hotspotPin` for tooltip pins with optional delete — no custom UI required:
69
+
70
+ ```tsx
71
+ import { Viewer360, toViewer360Hotspots } from '@mmmmzxe/react-360-viewer';
72
+
73
+ const hotspots = toViewer360Hotspots([
74
+ { id: '1', frameIndex: 0, positionX: 42, positionY: 58, title: 'Scratch', description: 'Front bumper' },
75
+ ]);
76
+
77
+ <Viewer360
78
+ frames={frames}
79
+ hotspots={hotspots}
80
+ showHotspotModeControl
81
+ hotspotPin={{
82
+ onDelete: (id) => removeMarker(id),
83
+ deletingMarkerId: pendingDeleteId,
84
+ }}
85
+ onHotspotAdd={(position) => saveMarker(position)}
86
+ />
87
+ ```
88
+
89
+ Use `renderTag` inside `hotspotPin` for badges (e.g. damage type). Use `renderHotspot` only when you need fully custom marker UI.
90
+
91
+ ### Custom hotspot UI
92
+
93
+ ```tsx
94
+ import { Viewer360, type Viewer360Hotspot } from '@mmmmzxe/react-360-viewer';
95
+
96
+ type DamageHotspot = { title: string; severity: 'low' | 'high' };
97
+
98
+ const hotspots: Viewer360Hotspot<DamageHotspot>[] = [
99
+ { id: 'a', frameIndex: 0, positionX: 42, positionY: 58, data: { title: 'Scratch', severity: 'low' } },
100
+ ];
101
+
102
+ <Viewer360
103
+ frames={frames}
104
+ hotspots={hotspots}
105
+ showHotspotModeControl
106
+ onHotspotAdd={(position) => {
107
+ // position: { frameIndex, frameId, positionX, positionY }
108
+ console.log('Place hotspot at', position);
109
+ }}
110
+ renderHotspot={({ hotspot, leftPercent, topPercent }) => (
111
+ <button
112
+ style={{ position: 'absolute', left: `${leftPercent}%`, top: `${topPercent}%` }}
113
+ onClick={() => alert(hotspot.data?.title)}
114
+ />
115
+ )}
116
+ />
117
+ ```
118
+
119
+ When hotspot add mode is active, clicking the viewport calls `onHotspotAdd` with normalized coordinates that stay aligned while zooming or panning.
120
+
121
+ ## Configuration
122
+
123
+ | Prop | Type | Default | Description |
124
+ |------|------|---------|-------------|
125
+ | `frames` | `Viewer360Frame[]` | required | Image sources |
126
+ | `config.minZoom` | `number` | `1` | Minimum zoom level |
127
+ | `config.maxZoom` | `number` | `3` | Maximum zoom level |
128
+ | `config.zoomStep` | `number` | `0.15` | Zoom increment |
129
+ | `config.dragSensitivity` | `number` | `8` | Pixels per frame while dragging |
130
+ | `config.autoRotate` | `boolean` | `false` | Enable automatic rotation |
131
+ | `config.autoRotateIntervalMs` | `number` | `100` | Delay between auto-rotate steps |
132
+ | `config.autoRotateDirection` | `'forward' \| 'backward'` | `'forward'` | Auto-rotate direction |
133
+ | `showZoomControls` | `boolean` | `true` | Toolbar zoom buttons |
134
+ | `showResetControl` | `boolean` | `true` | Reset zoom/pan button |
135
+ | `showFrameIndicator` | `boolean` | `true` | Frame counter badge |
136
+ | `showDragHint` | `boolean` | `true` | Helper text in toolbar |
137
+ | `aspectRatio` | `string` | `'16 / 10'` | CSS aspect-ratio value |
138
+ | `labels` | `Viewer360Labels` | English defaults | Localized strings |
139
+ | `classNames` | `Viewer360ClassNames` | BEM classes | Override CSS hooks |
140
+ | `theme` | `Viewer360Theme` | light palette | CSS variable overrides |
141
+ | `hotspotPin` | `Viewer360HotspotPinOptions` | — | Built-in marker pin with tooltip/delete |
142
+ | `renderHotspot` | `function` | — | Fully custom hotspot UI |
143
+ | `renderToolbar` | `function` | — | App design-system toolbar |
144
+ | `renderLoading` | `function` | — | Custom loading overlay |
145
+ | `renderFrameIndicator` | `function` | — | Custom frame badge |
146
+ | `renderHotspotModeBanner` | `function` | — | Custom add-mode banner |
147
+
148
+ ## Headless usage
149
+
150
+ Use `useViewer360` when you need full control over markup:
151
+
152
+ ```tsx
153
+ import { useViewer360 } from '@mmmmzxe/react-360-viewer';
154
+
155
+ const viewer = useViewer360({
156
+ frames,
157
+ currentFrameIndex,
158
+ onFrameChange: setFrameIndex,
159
+ });
160
+ ```
161
+
162
+ The hook exposes canvas refs, interaction handlers, zoom state, and hotspot positioning helpers.
163
+
164
+ ## Theming
165
+
166
+ Override CSS variables on the root element or pass `theme`:
167
+
168
+ ```tsx
169
+ <Viewer360
170
+ theme={{
171
+ '--viewer-bg': '#111827',
172
+ '--viewer-text': '#f9fafb',
173
+ '--viewer-accent': '#3b82f6',
174
+ }}
175
+ />
176
+ ```
177
+
178
+ See the `theme` prop on `Viewer360` for supported CSS variable overrides.
179
+
180
+ ## Package structure
181
+
182
+ ```
183
+ src/
184
+ components/
185
+ ui/ Badge, Button, Card, Item, Label, Popover, Separator, Spinner
186
+ utils/ cn helper
187
+ feature/ Viewer360 and sub-components
188
+ constants/
189
+ helpers/
190
+ hooks/
191
+ types/
192
+ ```
193
+
194
+ The published package includes `dist/` (ESM + type declarations) and `src/` (for Tailwind `@source` scanning).
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ npm install
200
+ npm run test-run
201
+ npm run build
202
+ ```
203
+
204
+ ## Publishing
205
+
206
+ ```bash
207
+ npm publish
208
+ ```
209
+
210
+ `prepublishOnly` runs the build automatically. The package ships ESM + type declarations from `dist/`.
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,377 @@
1
+ import * as React$1 from 'react';
2
+ import { ReactNode, CSSProperties, MouseEvent, 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
+ };
60
+ type Viewer360ToolbarRenderProps = {
61
+ zoom: number;
62
+ minZoom: number;
63
+ maxZoom: number;
64
+ isResetDisabled: boolean;
65
+ isHotspotMode: boolean;
66
+ showHotspotModeControl: boolean;
67
+ showZoomControls: boolean;
68
+ showResetControl: boolean;
69
+ showDragHint: boolean;
70
+ labels: Required<Viewer360Labels>;
71
+ onZoomIn: () => void;
72
+ onZoomOut: () => void;
73
+ onResetView: () => void;
74
+ onHotspotModeChange: (active: boolean) => void;
75
+ };
76
+
77
+ type Viewer360Marker = {
78
+ id: string;
79
+ title: string;
80
+ description?: string;
81
+ };
82
+ type Viewer360MarkerPinClassNames = {
83
+ root?: string;
84
+ ping?: string;
85
+ dot?: string;
86
+ tooltip?: string;
87
+ tooltipHeader?: string;
88
+ tooltipBody?: string;
89
+ tooltipTitle?: string;
90
+ tooltipDescription?: string;
91
+ deleteButton?: string;
92
+ };
93
+ type Viewer360MarkerPinLabels = {
94
+ delete?: string;
95
+ };
96
+ type Viewer360MarkerPinRenderProps<TData = unknown> = {
97
+ marker: Viewer360Marker;
98
+ hotspot?: Viewer360Hotspot<TData>;
99
+ };
100
+ type Viewer360MarkerPinProps<TData = unknown> = {
101
+ marker: Viewer360Marker;
102
+ hotspot?: Viewer360Hotspot<TData>;
103
+ leftPercent: number;
104
+ topPercent: number;
105
+ onDelete?: (id: string) => void;
106
+ isDeletePending?: boolean;
107
+ renderTag?: (props: Viewer360MarkerPinRenderProps<TData>) => ReactNode;
108
+ classNames?: Viewer360MarkerPinClassNames;
109
+ labels?: Viewer360MarkerPinLabels;
110
+ };
111
+ type Viewer360HotspotPinOptions<TData = unknown> = {
112
+ onDelete?: (id: string) => void;
113
+ deletingMarkerId?: string | null;
114
+ getMarker?: (hotspot: Viewer360Hotspot<TData>) => Viewer360Marker;
115
+ renderTag?: (props: Viewer360MarkerPinRenderProps<TData>) => ReactNode;
116
+ classNames?: Viewer360MarkerPinClassNames;
117
+ labels?: Viewer360MarkerPinLabels;
118
+ };
119
+
120
+ type Viewer360ClassNames = {
121
+ root?: string;
122
+ viewport?: string;
123
+ canvas?: string;
124
+ overlay?: string;
125
+ loading?: string;
126
+ loadingText?: string;
127
+ frameIndicator?: string;
128
+ hotspotModeBanner?: string;
129
+ toolbar?: string;
130
+ dragHint?: string;
131
+ controls?: string;
132
+ controlButton?: string;
133
+ controlButtonActive?: string;
134
+ controlButtonDisabled?: string;
135
+ zoomDisplay?: string;
136
+ divider?: string;
137
+ };
138
+ type Viewer360Labels = {
139
+ loading?: string;
140
+ dragHint?: string;
141
+ frameIndicator?: (params: {
142
+ current: number;
143
+ total: number;
144
+ label?: string;
145
+ }) => string;
146
+ zoom?: (percent: number) => string;
147
+ hotspotModeActive?: string;
148
+ addHotspot?: string;
149
+ zoomIn?: string;
150
+ zoomOut?: string;
151
+ resetView?: string;
152
+ deleteMarker?: string;
153
+ };
154
+ type Viewer360Theme = {
155
+ '--viewer-bg'?: string;
156
+ '--viewer-border'?: string;
157
+ '--viewer-text'?: string;
158
+ '--viewer-muted'?: string;
159
+ '--viewer-accent'?: string;
160
+ '--viewer-accent-foreground'?: string;
161
+ '--viewer-control-bg'?: string;
162
+ '--viewer-control-border'?: string;
163
+ '--viewer-hotspot-banner-bg'?: string;
164
+ '--viewer-hotspot-banner-border'?: string;
165
+ '--viewer-hotspot-banner-text'?: string;
166
+ };
167
+ type Viewer360Props<TData = unknown> = {
168
+ frames: Viewer360Frame[];
169
+ currentFrameIndex?: number;
170
+ defaultFrameIndex?: number;
171
+ onFrameChange?: (index: number) => void;
172
+ config?: Viewer360Config;
173
+ className?: string;
174
+ classNames?: Viewer360ClassNames;
175
+ style?: CSSProperties;
176
+ theme?: Viewer360Theme;
177
+ labels?: Viewer360Labels;
178
+ aspectRatio?: string;
179
+ showZoomControls?: boolean;
180
+ showResetControl?: boolean;
181
+ showFrameIndicator?: boolean;
182
+ showDragHint?: boolean;
183
+ showHotspotModeControl?: boolean;
184
+ hotspotPin?: Viewer360HotspotPinOptions<TData>;
185
+ hotspots?: Viewer360Hotspot<TData>[];
186
+ renderHotspot?: (props: Viewer360HotspotRenderProps<TData>) => ReactNode;
187
+ renderLoading?: () => ReactNode;
188
+ renderFrameIndicator?: (props: Viewer360OverlayRenderProps) => ReactNode;
189
+ renderHotspotModeBanner?: (props: Pick<Viewer360OverlayRenderProps, 'labels'>) => ReactNode;
190
+ renderToolbar?: (props: Viewer360ToolbarRenderProps) => ReactNode;
191
+ onHotspotClick?: (hotspot: Viewer360Hotspot<TData>, event: MouseEvent<HTMLDivElement>) => void;
192
+ hotspotMode?: boolean;
193
+ defaultHotspotMode?: boolean;
194
+ onHotspotModeChange?: (active: boolean) => void;
195
+ onHotspotAdd?: (position: Viewer360HotspotPosition) => void;
196
+ children?: ReactNode;
197
+ };
198
+
199
+ declare const defaultViewer360Labels: Required<Viewer360Labels>;
200
+
201
+ declare const defaultViewer360MarkerPinLabels: {
202
+ readonly delete: "Remove marker";
203
+ };
204
+
205
+ declare const viewer360ClassNames: Required<Viewer360ClassNames>;
206
+ declare const viewer360MarkerPinClassNames: Required<Viewer360MarkerPinClassNames>;
207
+ /** @deprecated Use `viewer360ClassNames` */
208
+ declare const defaultViewer360ClassNames: Required<Viewer360ClassNames>;
209
+ /** @deprecated Use `viewer360MarkerPinClassNames` */
210
+ declare const defaultViewer360MarkerPinClassNames: Required<Viewer360MarkerPinClassNames>;
211
+
212
+ 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;
213
+
214
+ type Viewer360AddModeBannerProps = {
215
+ className?: string;
216
+ label: string;
217
+ };
218
+ declare function Viewer360AddModeBanner({ className, label }: Viewer360AddModeBannerProps): JSX.Element;
219
+
220
+ type Viewer360FrameIndicatorProps = {
221
+ className?: string;
222
+ label: string;
223
+ };
224
+ declare function Viewer360FrameIndicator({ className, label }: Viewer360FrameIndicatorProps): JSX.Element;
225
+
226
+ type Viewer360LoadingOverlayProps = {
227
+ className?: string;
228
+ textClassName?: string;
229
+ label: string;
230
+ };
231
+ declare function Viewer360LoadingOverlay({ className, textClassName, label }: Viewer360LoadingOverlayProps): JSX.Element;
232
+
233
+ declare function Viewer360MarkerPin<TData = unknown>({ marker, hotspot, leftPercent, topPercent, onDelete, isDeletePending, renderTag, classNames, labels, }: Viewer360MarkerPinProps<TData>): JSX.Element;
234
+
235
+ type Viewer360ToolbarProps = Viewer360ToolbarRenderProps;
236
+ declare function Viewer360Toolbar({ showDragHint, showHotspotModeControl, showZoomControls, showResetControl, labels, isHotspotMode, zoom, minZoom, maxZoom, isResetDisabled, onHotspotModeChange, onZoomIn, onZoomOut, onResetView, }: Viewer360ToolbarProps): JSX.Element;
237
+
238
+ type ViewerImageLayout = {
239
+ width: number;
240
+ height: number;
241
+ centerX: number;
242
+ centerY: number;
243
+ drawWidth: number;
244
+ drawHeight: number;
245
+ offsetX: number;
246
+ offsetY: number;
247
+ };
248
+ type PanOffset = {
249
+ panX: number;
250
+ panY: number;
251
+ };
252
+ type ComputeViewerImageLayoutParams = {
253
+ containerWidth: number;
254
+ containerHeight: number;
255
+ imageWidth: number;
256
+ imageHeight: number;
257
+ pan?: PanOffset;
258
+ };
259
+ declare function computeViewerImageLayout({ containerWidth, containerHeight, imageWidth, imageHeight, pan, }: ComputeViewerImageLayoutParams): ViewerImageLayout;
260
+ declare function computeHotspotScreenPosition(hotspotX: number, hotspotY: number, layout: ViewerImageLayout, zoom: number): {
261
+ leftPercent: number;
262
+ topPercent: number;
263
+ };
264
+ declare function computeHotspotPositionFromClick(clientX: number, clientY: number, containerRect: DOMRect, layout: ViewerImageLayout, zoom: number): {
265
+ positionX: number;
266
+ positionY: number;
267
+ };
268
+
269
+ type UseViewer360Params<TData> = {
270
+ frames: Viewer360Frame[];
271
+ currentFrameIndex: number;
272
+ onFrameChange: (index: number) => void;
273
+ hotspots?: Viewer360Hotspot<TData>[];
274
+ config?: Viewer360Config;
275
+ hotspotMode?: boolean;
276
+ onHotspotAdd?: (position: Viewer360HotspotPosition) => void;
277
+ };
278
+ type UseViewer360Return<TData> = {
279
+ canvasRef: RefObject<HTMLCanvasElement | null>;
280
+ containerRef: RefObject<HTMLDivElement | null>;
281
+ currentFrame: Viewer360Frame | undefined;
282
+ currentFrameHotspots: Viewer360Hotspot<TData>[];
283
+ imagesLoaded: boolean;
284
+ isHotspotMode: boolean;
285
+ isResetDisabled: boolean;
286
+ maxZoom: number;
287
+ minZoom: number;
288
+ viewerCursorClass: string;
289
+ zoom: number;
290
+ getCurrentImageLayout: () => ViewerImageLayout | null;
291
+ getHotspotScreenPosition: (hotspot: Viewer360Hotspot<TData>) => {
292
+ leftPercent: number;
293
+ topPercent: number;
294
+ };
295
+ handleCanvasClick: (event: React.MouseEvent<HTMLDivElement>) => void;
296
+ handlePointerDown: (event: PointerEvent<HTMLDivElement>) => void;
297
+ handlePointerMove: (event: PointerEvent<HTMLDivElement>) => void;
298
+ handlePointerUp: (event: PointerEvent<HTMLDivElement>) => void;
299
+ handleResetView: () => void;
300
+ handleWheel: (event: WheelEvent<HTMLDivElement>) => void;
301
+ handleZoomIn: () => void;
302
+ handleZoomOut: () => void;
303
+ };
304
+ declare function useViewer360<TData = unknown>({ frames, hotspots, currentFrameIndex, onFrameChange, config, hotspotMode: hotspotModeProp, onHotspotAdd, }: UseViewer360Params<TData>): UseViewer360Return<TData>;
305
+
306
+ type ResolvedViewer360Config = Required<Viewer360Config>;
307
+ declare function resolveViewer360Config(config?: Viewer360Config): ResolvedViewer360Config;
308
+ declare function clampZoom(zoom: number, config: ResolvedViewer360Config): number;
309
+ declare function applyWheelZoom(currentZoom: number, deltaY: number, currentPan: PanOffset, config: ResolvedViewer360Config): {
310
+ zoom: number;
311
+ pan: PanOffset;
312
+ };
313
+ declare function stepZoomIn(currentZoom: number, config: ResolvedViewer360Config): number;
314
+ declare function stepZoomOut(currentZoom: number, currentPan: PanOffset, config: ResolvedViewer360Config): {
315
+ zoom: number;
316
+ pan: PanOffset;
317
+ };
318
+ declare function isResetDisabled(zoom: number, pan: PanOffset, config: ResolvedViewer360Config): boolean;
319
+ declare function getViewerCursorClass(isHotspotMode: boolean, zoom: number, isDragging: boolean, minZoom: number): string;
320
+
321
+ declare function clampFrameIndex(index: number, frameCount: number): number;
322
+ type DragStart = {
323
+ pointerX: number;
324
+ frameIndex: number;
325
+ };
326
+ declare function computeDragFrameIndex(dragStart: DragStart, clientX: number, frameCount: number, dragSensitivity: number): number | null;
327
+
328
+ type PanStart = {
329
+ pointerX: number;
330
+ pointerY: number;
331
+ panX: number;
332
+ panY: number;
333
+ };
334
+ declare function computeViewerPanOffset(panStart: PanStart, clientX: number, clientY: number): PanOffset;
335
+
336
+ declare function hotspotToViewer360Marker<TData>(hotspot: Viewer360Hotspot<TData>): Viewer360Marker;
337
+ declare function toViewer360Hotspots<TData extends Viewer360Marker>(markers: Array<Viewer360Marker & {
338
+ frameIndex: number;
339
+ positionX: number;
340
+ positionY: number;
341
+ }>, mapData?: (marker: Viewer360Marker & {
342
+ frameIndex: number;
343
+ positionX: number;
344
+ positionY: number;
345
+ }) => TData): Viewer360Hotspot<TData>[];
346
+
347
+ type DrawFrameOnCanvasParams = {
348
+ canvas: HTMLCanvasElement;
349
+ container: HTMLDivElement;
350
+ image: HTMLImageElement;
351
+ zoom: number;
352
+ pan: PanOffset;
353
+ };
354
+ declare function drawFrameOnCanvas({ canvas, container, image, zoom, pan }: DrawFrameOnCanvasParams): void;
355
+ declare function getFramesSignature(frames: Viewer360Frame[]): string;
356
+ declare function preloadFrameImage(frame: Viewer360Frame): Promise<HTMLImageElement>;
357
+ declare function preloadViewerFrames(frames: Viewer360Frame[]): Promise<HTMLImageElement[]>;
358
+ declare function hasLoadedViewerFrame(images: HTMLImageElement[]): boolean;
359
+ declare function filterHotspotsByFrame<TData>(hotspots: Array<{
360
+ frameIndex: number;
361
+ data?: TData;
362
+ }>, frameIndex: number): Array<{
363
+ frameIndex: number;
364
+ data?: TData;
365
+ }>;
366
+
367
+ declare function cn(...inputs: ClassValue[]): string;
368
+
369
+ declare const buttonVariants: (props?: ({
370
+ variant?: "default" | "link" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
371
+ size?: "default" | "icon" | "sm" | "xs" | "lg" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
372
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
373
+ declare function Button({ className, variant, size, asChild, ...props }: React$1.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
374
+ asChild?: boolean;
375
+ }): JSX.Element;
376
+
377
+ 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 };