@mmmmzxe/react-360-viewer 0.1.3 → 0.1.4

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React$1 from 'react';
2
- import { ReactNode, CSSProperties, MouseEvent, JSX, RefObject, PointerEvent, WheelEvent } from 'react';
2
+ import { ReactNode, MouseEvent, CSSProperties, JSX, RefObject, PointerEvent, WheelEvent } from 'react';
3
3
  import { ClassValue } from 'clsx';
4
4
  import * as class_variance_authority_types from 'class-variance-authority/types';
5
5
  import { VariantProps } from 'class-variance-authority';
@@ -104,6 +104,7 @@ type Viewer360MarkerPinProps<TData = unknown> = {
104
104
  topPercent: number;
105
105
  onDelete?: (id: string) => void;
106
106
  isDeletePending?: boolean;
107
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
107
108
  renderTag?: (props: Viewer360MarkerPinRenderProps<TData>) => ReactNode;
108
109
  classNames?: Viewer360MarkerPinClassNames;
109
110
  labels?: Viewer360MarkerPinLabels;
@@ -230,7 +231,7 @@ type Viewer360LoadingOverlayProps = {
230
231
  };
231
232
  declare function Viewer360LoadingOverlay({ className, textClassName, label }: Viewer360LoadingOverlayProps): JSX.Element;
232
233
 
233
- declare function Viewer360MarkerPin<TData = unknown>({ marker, hotspot, leftPercent, topPercent, onDelete, isDeletePending, renderTag, classNames, labels, }: Viewer360MarkerPinProps<TData>): JSX.Element;
234
+ declare function Viewer360MarkerPin<TData = unknown>({ marker, hotspot, leftPercent, topPercent, onDelete, isDeletePending, onClick, renderTag, classNames, labels, }: Viewer360MarkerPinProps<TData>): JSX.Element;
234
235
 
235
236
  type Viewer360ToolbarProps = Viewer360ToolbarRenderProps;
236
237
  declare function Viewer360Toolbar({ showDragHint, showHotspotModeControl, showZoomControls, showResetControl, labels, isHotspotMode, zoom, minZoom, maxZoom, isResetDisabled, onHotspotModeChange, onZoomIn, onZoomOut, onResetView, }: Viewer360ToolbarProps): JSX.Element;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/feature/Viewer360.tsx
4
- import { useEffect as useEffect2, useMemo as useMemo2, useState as useState3 } from "react";
4
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
5
5
 
6
6
  // src/constants/viewer360Labels.ts
7
7
  var defaultViewer360Labels = {
@@ -577,73 +577,20 @@ function toViewer360Hotspots(markers, mapData) {
577
577
  }));
578
578
  }
579
579
 
580
- // src/components/ui/Button/index.tsx
580
+ // src/components/ui/Item/index.tsx
581
581
  import { cva as cva2 } from "class-variance-authority";
582
582
  import { Slot as Slot2 } from "radix-ui";
583
- import { jsx as jsx5 } from "react/jsx-runtime";
584
- var buttonVariants = cva2(
585
- "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
586
- {
587
- variants: {
588
- variant: {
589
- default: "bg-primary text-primary-foreground hover:bg-primary/80",
590
- outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
591
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
592
- ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
593
- destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
594
- link: "text-primary underline-offset-4 hover:underline"
595
- },
596
- size: {
597
- default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2",
598
- xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5",
599
- sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5",
600
- lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pe-3 has-data-[icon=inline-start]:ps-3",
601
- icon: "size-9",
602
- "icon-xs": "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md",
603
- "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
604
- "icon-lg": "size-10"
605
- }
606
- },
607
- defaultVariants: {
608
- variant: "default",
609
- size: "default"
610
- }
611
- }
612
- );
613
- function Button({
614
- className,
615
- variant = "default",
616
- size = "default",
617
- asChild = false,
618
- ...props
619
- }) {
620
- const Comp = asChild ? Slot2.Root : "button";
621
- return /* @__PURE__ */ jsx5(
622
- Comp,
623
- {
624
- "data-slot": "button",
625
- "data-variant": variant,
626
- "data-size": size,
627
- className: cn(buttonVariants({ variant, size, className })),
628
- ...props
629
- }
630
- );
631
- }
632
-
633
- // src/components/ui/Item/index.tsx
634
- import { cva as cva3 } from "class-variance-authority";
635
- import { Slot as Slot3 } from "radix-ui";
636
583
 
637
584
  // src/components/ui/Separator/index.tsx
638
585
  import { Separator as SeparatorPrimitive } from "radix-ui";
639
- import { jsx as jsx6 } from "react/jsx-runtime";
586
+ import { jsx as jsx5 } from "react/jsx-runtime";
640
587
  function Separator({
641
588
  className,
642
589
  orientation = "horizontal",
643
590
  decorative = true,
644
591
  ...props
645
592
  }) {
646
- return /* @__PURE__ */ jsx6(
593
+ return /* @__PURE__ */ jsx5(
647
594
  SeparatorPrimitive.Root,
648
595
  {
649
596
  "data-slot": "separator",
@@ -659,8 +606,8 @@ function Separator({
659
606
  }
660
607
 
661
608
  // src/components/ui/Item/index.tsx
662
- import { jsx as jsx7 } from "react/jsx-runtime";
663
- var itemVariants = cva3(
609
+ import { jsx as jsx6 } from "react/jsx-runtime";
610
+ var itemVariants = cva2(
664
611
  "[a]:hover:bg-muted rounded-md border text-sm w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors",
665
612
  {
666
613
  variants: {
@@ -688,8 +635,8 @@ function Item({
688
635
  asChild = false,
689
636
  ...props
690
637
  }) {
691
- const Comp = asChild ? Slot3.Root : "div";
692
- return /* @__PURE__ */ jsx7(
638
+ const Comp = asChild ? Slot2.Root : "div";
639
+ return /* @__PURE__ */ jsx6(
693
640
  Comp,
694
641
  {
695
642
  "data-slot": "item",
@@ -700,7 +647,7 @@ function Item({
700
647
  }
701
648
  );
702
649
  }
703
- var itemMediaVariants = cva3(
650
+ var itemMediaVariants = cva2(
704
651
  "gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none",
705
652
  {
706
653
  variants: {
@@ -716,7 +663,7 @@ var itemMediaVariants = cva3(
716
663
  }
717
664
  );
718
665
  function ItemContent({ className, ...props }) {
719
- return /* @__PURE__ */ jsx7(
666
+ return /* @__PURE__ */ jsx6(
720
667
  "div",
721
668
  {
722
669
  "data-slot": "item-content",
@@ -726,7 +673,7 @@ function ItemContent({ className, ...props }) {
726
673
  );
727
674
  }
728
675
  function ItemTitle({ className, ...props }) {
729
- return /* @__PURE__ */ jsx7(
676
+ return /* @__PURE__ */ jsx6(
730
677
  "div",
731
678
  {
732
679
  "data-slot": "item-title",
@@ -736,7 +683,7 @@ function ItemTitle({ className, ...props }) {
736
683
  );
737
684
  }
738
685
  function ItemDescription({ className, ...props }) {
739
- return /* @__PURE__ */ jsx7(
686
+ return /* @__PURE__ */ jsx6(
740
687
  "p",
741
688
  {
742
689
  "data-slot": "item-description",
@@ -749,11 +696,10 @@ function ItemDescription({ className, ...props }) {
749
696
  );
750
697
  }
751
698
  function ItemActions({ className, ...props }) {
752
- return /* @__PURE__ */ jsx7("div", { "data-slot": "item-actions", className: cn("gap-2 flex items-center", className), ...props });
699
+ return /* @__PURE__ */ jsx6("div", { "data-slot": "item-actions", className: cn("gap-2 flex items-center", className), ...props });
753
700
  }
754
701
 
755
702
  // src/feature/Viewer360MarkerPin.tsx
756
- import { useState as useState2 } from "react";
757
703
  import { Trash2 } from "lucide-react";
758
704
 
759
705
  // src/constants/viewer360MarkerLabels.ts
@@ -761,38 +707,61 @@ var defaultViewer360MarkerPinLabels = {
761
707
  delete: "Remove marker"
762
708
  };
763
709
 
764
- // src/components/ui/Popover/index.tsx
765
- import { Popover as PopoverPrimitive } from "radix-ui";
766
- import { jsx as jsx8 } from "react/jsx-runtime";
767
- function Popover({ ...props }) {
768
- return /* @__PURE__ */ jsx8(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
769
- }
770
- function PopoverTrigger({ ...props }) {
771
- return /* @__PURE__ */ jsx8(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
772
- }
773
- function PopoverContent({
710
+ // src/components/ui/Button/index.tsx
711
+ import { cva as cva3 } from "class-variance-authority";
712
+ import { Slot as Slot3 } from "radix-ui";
713
+ import { jsx as jsx7 } from "react/jsx-runtime";
714
+ var buttonVariants = cva3(
715
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
716
+ {
717
+ variants: {
718
+ variant: {
719
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
720
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
721
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
722
+ ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
723
+ destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
724
+ link: "text-primary underline-offset-4 hover:underline"
725
+ },
726
+ size: {
727
+ default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2",
728
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5",
729
+ sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5",
730
+ lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pe-3 has-data-[icon=inline-start]:ps-3",
731
+ icon: "size-9",
732
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md",
733
+ "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
734
+ "icon-lg": "size-10"
735
+ }
736
+ },
737
+ defaultVariants: {
738
+ variant: "default",
739
+ size: "default"
740
+ }
741
+ }
742
+ );
743
+ function Button({
774
744
  className,
775
- align = "center",
776
- sideOffset = 4,
745
+ variant = "default",
746
+ size = "default",
747
+ asChild = false,
777
748
  ...props
778
749
  }) {
779
- return /* @__PURE__ */ jsx8(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx8(
780
- PopoverPrimitive.Content,
750
+ const Comp = asChild ? Slot3.Root : "button";
751
+ return /* @__PURE__ */ jsx7(
752
+ Comp,
781
753
  {
782
- "data-slot": "popover-content",
783
- align,
784
- sideOffset,
785
- className: cn(
786
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=start]:slide-in-from-end-2 data-[side=end]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 flex flex-col gap-4 rounded-md p-4 text-sm shadow-md ring-1 duration-100 z-50 w-72 origin-(--radix-popover-content-transform-origin) outline-hidden",
787
- className
788
- ),
754
+ "data-slot": "button",
755
+ "data-variant": variant,
756
+ "data-size": size,
757
+ className: cn(buttonVariants({ variant, size, className })),
789
758
  ...props
790
759
  }
791
- ) });
760
+ );
792
761
  }
793
762
 
794
763
  // src/feature/Viewer360MarkerPin.tsx
795
- import { jsx as jsx9, jsxs } from "react/jsx-runtime";
764
+ import { jsx as jsx8, jsxs } from "react/jsx-runtime";
796
765
  function Viewer360MarkerPin({
797
766
  marker,
798
767
  hotspot,
@@ -800,23 +769,26 @@ function Viewer360MarkerPin({
800
769
  topPercent,
801
770
  onDelete,
802
771
  isDeletePending = false,
772
+ onClick,
803
773
  renderTag,
804
774
  classNames,
805
775
  labels
806
776
  }) {
807
- const [isOpen, setIsOpen] = useState2(false);
808
777
  const deleteLabel = labels?.delete ?? defaultViewer360MarkerPinLabels.delete;
809
- return /* @__PURE__ */ jsx9(Popover, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsxs(
778
+ const showTooltip = Boolean(marker.title || marker.description || onDelete || renderTag);
779
+ return /* @__PURE__ */ jsxs(
810
780
  Item,
811
781
  {
812
782
  size: "xs",
813
783
  variant: "default",
814
- className: cn(viewer360MarkerPinClassNames.root, classNames?.root, "w-auto border-transparent p-0"),
784
+ className: cn(
785
+ viewer360MarkerPinClassNames.root,
786
+ classNames?.root,
787
+ "group/marker w-auto border-transparent p-0"
788
+ ),
815
789
  style: { left: `${leftPercent}%`, top: `${topPercent}%` },
816
- onMouseEnter: () => setIsOpen(true),
817
- onMouseLeave: () => setIsOpen(false),
818
790
  children: [
819
- /* @__PURE__ */ jsx9(
791
+ /* @__PURE__ */ jsx8(
820
792
  Badge,
821
793
  {
822
794
  variant: "destructive",
@@ -824,7 +796,7 @@ function Viewer360MarkerPin({
824
796
  "aria-hidden": "true"
825
797
  }
826
798
  ),
827
- /* @__PURE__ */ jsx9(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx9(
799
+ /* @__PURE__ */ jsx8(
828
800
  Button,
829
801
  {
830
802
  type: "button",
@@ -835,16 +807,18 @@ function Viewer360MarkerPin({
835
807
  classNames?.dot,
836
808
  "size-4 min-h-4 min-w-4 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:scale-125 hover:bg-destructive"
837
809
  ),
838
- "aria-label": marker.title
810
+ "aria-label": marker.title,
811
+ onClick
839
812
  }
840
- ) }),
841
- /* @__PURE__ */ jsx9(
842
- PopoverContent,
813
+ ),
814
+ showTooltip && /* @__PURE__ */ jsx8(
815
+ "div",
843
816
  {
844
- className: cn(viewer360MarkerPinClassNames.tooltip, classNames?.tooltip, "w-64 p-0"),
845
- side: "top",
846
- align: "center",
847
- onOpenAutoFocus: (event) => event.preventDefault(),
817
+ className: cn(
818
+ viewer360MarkerPinClassNames.tooltip,
819
+ classNames?.tooltip,
820
+ "pointer-events-none opacity-0 transition-opacity duration-150 group-hover/marker:pointer-events-auto group-hover/marker:opacity-100 group-focus-within/marker:pointer-events-auto group-focus-within/marker:opacity-100"
821
+ ),
848
822
  children: /* @__PURE__ */ jsxs(
849
823
  Item,
850
824
  {
@@ -853,11 +827,11 @@ function Viewer360MarkerPin({
853
827
  className: cn(viewer360MarkerPinClassNames.tooltipHeader, classNames?.tooltipHeader, "w-full border-transparent"),
854
828
  children: [
855
829
  /* @__PURE__ */ jsxs(ItemContent, { className: cn(viewer360MarkerPinClassNames.tooltipBody, classNames?.tooltipBody), children: [
856
- /* @__PURE__ */ jsx9(ItemTitle, { className: cn(viewer360MarkerPinClassNames.tooltipTitle, classNames?.tooltipTitle), children: marker.title }),
830
+ /* @__PURE__ */ jsx8(ItemTitle, { className: cn(viewer360MarkerPinClassNames.tooltipTitle, classNames?.tooltipTitle), children: marker.title }),
857
831
  renderTag?.({ marker, hotspot }),
858
- marker.description && /* @__PURE__ */ jsx9(ItemDescription, { className: cn(viewer360MarkerPinClassNames.tooltipDescription, classNames?.tooltipDescription), children: marker.description })
832
+ marker.description && /* @__PURE__ */ jsx8(ItemDescription, { className: cn(viewer360MarkerPinClassNames.tooltipDescription, classNames?.tooltipDescription), children: marker.description })
859
833
  ] }),
860
- onDelete && /* @__PURE__ */ jsx9(ItemActions, { children: /* @__PURE__ */ jsx9(
834
+ onDelete && /* @__PURE__ */ jsx8(ItemActions, { children: /* @__PURE__ */ jsx8(
861
835
  Button,
862
836
  {
863
837
  variant: "ghost",
@@ -869,7 +843,7 @@ function Viewer360MarkerPin({
869
843
  event.stopPropagation();
870
844
  onDelete(marker.id);
871
845
  },
872
- children: /* @__PURE__ */ jsx9(Trash2, { className: "size-4" })
846
+ children: /* @__PURE__ */ jsx8(Trash2, { className: "size-4" })
873
847
  }
874
848
  ) })
875
849
  ]
@@ -879,11 +853,11 @@ function Viewer360MarkerPin({
879
853
  )
880
854
  ]
881
855
  }
882
- ) });
856
+ );
883
857
  }
884
858
 
885
859
  // src/feature/Viewer360HotspotOverlay.tsx
886
- import { jsx as jsx10 } from "react/jsx-runtime";
860
+ import { jsx as jsx9 } from "react/jsx-runtime";
887
861
  function Viewer360HotspotOverlay({
888
862
  hotspot,
889
863
  leftPercent,
@@ -893,45 +867,30 @@ function Viewer360HotspotOverlay({
893
867
  onHotspotClick
894
868
  }) {
895
869
  if (renderHotspot) {
896
- return /* @__PURE__ */ jsx10(Item, { size: "xs", variant: "default", className: "pointer-events-auto w-auto border-transparent p-0", children: renderHotspot({ hotspot, leftPercent, topPercent }) });
897
- }
898
- if (hotspotPin) {
899
- const marker = hotspotPin.getMarker?.(hotspot) ?? hotspotToViewer360Marker(hotspot);
900
- return /* @__PURE__ */ jsx10(
901
- Viewer360MarkerPin,
902
- {
903
- marker,
904
- hotspot,
905
- leftPercent,
906
- topPercent,
907
- onDelete: hotspotPin.onDelete,
908
- isDeletePending: hotspotPin.deletingMarkerId === hotspot.id,
909
- renderTag: hotspotPin.renderTag,
910
- classNames: hotspotPin.classNames,
911
- labels: hotspotPin.labels
912
- }
913
- );
870
+ return /* @__PURE__ */ jsx9(Item, { size: "xs", variant: "default", className: "pointer-events-auto w-auto border-transparent p-0", children: renderHotspot({ hotspot, leftPercent, topPercent }) });
914
871
  }
915
- return /* @__PURE__ */ jsx10(
916
- Button,
872
+ const marker = hotspotPin?.getMarker?.(hotspot) ?? hotspotToViewer360Marker(hotspot);
873
+ return /* @__PURE__ */ jsx9(
874
+ Viewer360MarkerPin,
917
875
  {
918
- type: "button",
919
- variant: "destructive",
920
- size: "icon-xs",
921
- className: cn(
922
- "pointer-events-auto absolute z-30 size-4 min-h-4 min-w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:bg-destructive"
923
- ),
924
- style: { left: `${leftPercent}%`, top: `${topPercent}%` },
925
- "aria-label": `Hotspot ${hotspot.id}`,
926
- onClick: (event) => onHotspotClick?.(hotspot, event)
876
+ marker,
877
+ hotspot,
878
+ leftPercent,
879
+ topPercent,
880
+ onDelete: hotspotPin?.onDelete,
881
+ isDeletePending: hotspotPin?.deletingMarkerId === hotspot.id,
882
+ renderTag: hotspotPin?.renderTag,
883
+ classNames: hotspotPin?.classNames,
884
+ labels: hotspotPin?.labels,
885
+ onClick: onHotspotClick ? (event) => onHotspotClick(hotspot, event) : void 0
927
886
  }
928
887
  );
929
888
  }
930
889
 
931
890
  // src/components/ui/Label/index.tsx
932
- import { jsx as jsx11 } from "react/jsx-runtime";
891
+ import { jsx as jsx10 } from "react/jsx-runtime";
933
892
  function Label({ className, ...props }) {
934
- return /* @__PURE__ */ jsx11(
893
+ return /* @__PURE__ */ jsx10(
935
894
  "label",
936
895
  {
937
896
  "data-slot": "label",
@@ -946,13 +905,13 @@ function Label({ className, ...props }) {
946
905
 
947
906
  // src/components/ui/Spinner/index.tsx
948
907
  import { Loader2Icon } from "lucide-react";
949
- import { jsx as jsx12 } from "react/jsx-runtime";
908
+ import { jsx as jsx11 } from "react/jsx-runtime";
950
909
  function Spinner({ className, ...props }) {
951
- return /* @__PURE__ */ jsx12(Loader2Icon, { role: "status", "aria-label": "Loading", className: cn("size-4 animate-spin", className), ...props });
910
+ return /* @__PURE__ */ jsx11(Loader2Icon, { role: "status", "aria-label": "Loading", className: cn("size-4 animate-spin", className), ...props });
952
911
  }
953
912
 
954
913
  // src/feature/Viewer360LoadingOverlay.tsx
955
- import { jsx as jsx13, jsxs as jsxs2 } from "react/jsx-runtime";
914
+ import { jsx as jsx12, jsxs as jsxs2 } from "react/jsx-runtime";
956
915
  function Viewer360LoadingOverlay({ className, textClassName, label }) {
957
916
  return /* @__PURE__ */ jsxs2(
958
917
  Item,
@@ -961,8 +920,8 @@ function Viewer360LoadingOverlay({ className, textClassName, label }) {
961
920
  variant: "muted",
962
921
  className: cn("pointer-events-none w-auto justify-center border-transparent bg-muted/80", className),
963
922
  children: [
964
- /* @__PURE__ */ jsx13(Spinner, { className: "size-5 text-muted-foreground" }),
965
- /* @__PURE__ */ jsx13(Label, { className: cn("font-normal text-muted-foreground", textClassName), children: label })
923
+ /* @__PURE__ */ jsx12(Spinner, { className: "size-5 text-muted-foreground" }),
924
+ /* @__PURE__ */ jsx12(Label, { className: cn("font-normal text-muted-foreground", textClassName), children: label })
966
925
  ]
967
926
  }
968
927
  );
@@ -970,7 +929,7 @@ function Viewer360LoadingOverlay({ className, textClassName, label }) {
970
929
 
971
930
  // src/feature/Viewer360Toolbar.tsx
972
931
  import { Crosshair, Minus, Plus, RotateCcw, ZoomIn } from "lucide-react";
973
- import { Fragment, jsx as jsx14, jsxs as jsxs3 } from "react/jsx-runtime";
932
+ import { Fragment, jsx as jsx13, jsxs as jsxs3 } from "react/jsx-runtime";
974
933
  function Viewer360Toolbar({
975
934
  showDragHint,
976
935
  showHotspotModeControl,
@@ -988,30 +947,30 @@ function Viewer360Toolbar({
988
947
  onResetView
989
948
  }) {
990
949
  return /* @__PURE__ */ jsxs3(CardFooter, { className: cn(viewer360ClassNames.toolbar, "gap-2 border-t px-4 py-3 pt-3"), children: [
991
- showDragHint && /* @__PURE__ */ jsx14(Label, { className: cn(viewer360ClassNames.dragHint, "font-normal text-muted-foreground"), children: labels.dragHint }),
950
+ showDragHint && /* @__PURE__ */ jsx13(Label, { className: cn(viewer360ClassNames.dragHint, "font-normal text-muted-foreground"), children: labels.dragHint }),
992
951
  /* @__PURE__ */ jsxs3("div", { className: viewer360ClassNames.controls, children: [
993
952
  showHotspotModeControl && /* @__PURE__ */ jsxs3(Fragment, { children: [
994
953
  /* @__PURE__ */ jsxs3(Button, { variant: isHotspotMode ? "default" : "outline", size: "sm", onClick: () => onHotspotModeChange(!isHotspotMode), children: [
995
- /* @__PURE__ */ jsx14(Crosshair, { className: "me-1.5 size-4" }),
954
+ /* @__PURE__ */ jsx13(Crosshair, { className: "me-1.5 size-4" }),
996
955
  labels.addHotspot
997
956
  ] }),
998
- /* @__PURE__ */ jsx14(Separator, { orientation: "vertical", className: cn(viewer360ClassNames.divider, "h-6") })
957
+ /* @__PURE__ */ jsx13(Separator, { orientation: "vertical", className: cn(viewer360ClassNames.divider, "h-6") })
999
958
  ] }),
1000
959
  showZoomControls && /* @__PURE__ */ jsxs3(Fragment, { children: [
1001
- /* @__PURE__ */ jsx14(Button, { variant: "outline", size: "icon-sm", disabled: zoom <= minZoom, "aria-label": labels.zoomOut, onClick: onZoomOut, children: /* @__PURE__ */ jsx14(Minus, { className: "size-4" }) }),
960
+ /* @__PURE__ */ jsx13(Button, { variant: "outline", size: "icon-sm", disabled: zoom <= minZoom, "aria-label": labels.zoomOut, onClick: onZoomOut, children: /* @__PURE__ */ jsx13(Minus, { className: "size-4" }) }),
1002
961
  /* @__PURE__ */ jsxs3(Badge, { variant: "outline", className: cn(viewer360ClassNames.zoomDisplay, "h-8 gap-1 px-2 py-1"), children: [
1003
- /* @__PURE__ */ jsx14(ZoomIn, { className: "size-3 text-muted-foreground" }),
962
+ /* @__PURE__ */ jsx13(ZoomIn, { className: "size-3 text-muted-foreground" }),
1004
963
  labels.zoom(Math.round(zoom * 100))
1005
964
  ] }),
1006
- /* @__PURE__ */ jsx14(Button, { variant: "outline", size: "icon-sm", disabled: zoom >= maxZoom, "aria-label": labels.zoomIn, onClick: onZoomIn, children: /* @__PURE__ */ jsx14(Plus, { className: "size-4" }) })
965
+ /* @__PURE__ */ jsx13(Button, { variant: "outline", size: "icon-sm", disabled: zoom >= maxZoom, "aria-label": labels.zoomIn, onClick: onZoomIn, children: /* @__PURE__ */ jsx13(Plus, { className: "size-4" }) })
1007
966
  ] }),
1008
- showResetControl && /* @__PURE__ */ jsx14(Button, { variant: "outline", size: "icon-sm", disabled: isResetDisabled2, "aria-label": labels.resetView, onClick: onResetView, children: /* @__PURE__ */ jsx14(RotateCcw, { className: "size-4" }) })
967
+ showResetControl && /* @__PURE__ */ jsx13(Button, { variant: "outline", size: "icon-sm", disabled: isResetDisabled2, "aria-label": labels.resetView, onClick: onResetView, children: /* @__PURE__ */ jsx13(RotateCcw, { className: "size-4" }) })
1009
968
  ] })
1010
969
  ] });
1011
970
  }
1012
971
 
1013
972
  // src/feature/Viewer360.tsx
1014
- import { jsx as jsx15, jsxs as jsxs4 } from "react/jsx-runtime";
973
+ import { jsx as jsx14, jsxs as jsxs4 } from "react/jsx-runtime";
1015
974
  function Viewer360({
1016
975
  frames,
1017
976
  currentFrameIndex: controlledFrameIndex,
@@ -1046,8 +1005,8 @@ function Viewer360({
1046
1005
  const mergedLabels = useMemo2(() => mergeViewer360Labels(labels), [labels]);
1047
1006
  const mergedClassNames = useMemo2(() => mergeViewer360ClassNames(classNames), [classNames]);
1048
1007
  const themeStyle = useMemo2(() => buildViewer360ThemeStyle(theme), [theme]);
1049
- const [internalFrameIndex, setInternalFrameIndex] = useState3(defaultFrameIndex);
1050
- const [internalHotspotMode, setInternalHotspotMode] = useState3(defaultHotspotMode);
1008
+ const [internalFrameIndex, setInternalFrameIndex] = useState2(defaultFrameIndex);
1009
+ const [internalHotspotMode, setInternalHotspotMode] = useState2(defaultHotspotMode);
1051
1010
  const currentFrameIndex = controlledFrameIndex ?? internalFrameIndex;
1052
1011
  const hotspotMode = controlledHotspotMode ?? internalHotspotMode;
1053
1012
  function handleFrameChange(index) {
@@ -1137,11 +1096,11 @@ function Viewer360({
1137
1096
  onWheel: handleWheel,
1138
1097
  onClick: handleCanvasClick,
1139
1098
  children: [
1140
- /* @__PURE__ */ jsx15("canvas", { ref: canvasRef, className: mergedClassNames.canvas }),
1099
+ /* @__PURE__ */ jsx14("canvas", { ref: canvasRef, className: mergedClassNames.canvas }),
1141
1100
  /* @__PURE__ */ jsxs4("div", { className: mergedClassNames.overlay, children: [
1142
1101
  currentFrameHotspots.map((hotspot) => {
1143
1102
  const position = getHotspotScreenPosition(hotspot);
1144
- return /* @__PURE__ */ jsx15(
1103
+ return /* @__PURE__ */ jsx14(
1145
1104
  Viewer360HotspotOverlay,
1146
1105
  {
1147
1106
  hotspot,
@@ -1156,7 +1115,7 @@ function Viewer360({
1156
1115
  }),
1157
1116
  children
1158
1117
  ] }),
1159
- !imagesLoaded && (renderLoading ? renderLoading() : /* @__PURE__ */ jsx15(
1118
+ !imagesLoaded && (renderLoading ? renderLoading() : /* @__PURE__ */ jsx14(
1160
1119
  Viewer360LoadingOverlay,
1161
1120
  {
1162
1121
  className: mergedClassNames.loading,
@@ -1164,7 +1123,7 @@ function Viewer360({
1164
1123
  label: mergedLabels.loading
1165
1124
  }
1166
1125
  )),
1167
- showFrameIndicator && frames.length > 0 && (renderFrameIndicator ? renderFrameIndicator(overlayProps) : /* @__PURE__ */ jsx15(
1126
+ showFrameIndicator && frames.length > 0 && (renderFrameIndicator ? renderFrameIndicator(overlayProps) : /* @__PURE__ */ jsx14(
1168
1127
  Viewer360FrameIndicator,
1169
1128
  {
1170
1129
  className: mergedClassNames.frameIndicator,
@@ -1175,11 +1134,11 @@ function Viewer360({
1175
1134
  })
1176
1135
  }
1177
1136
  )),
1178
- isHotspotMode && onHotspotAdd && (renderHotspotModeBanner ? renderHotspotModeBanner({ labels: mergedLabels }) : /* @__PURE__ */ jsx15(Viewer360AddModeBanner, { className: mergedClassNames.hotspotModeBanner, label: mergedLabels.hotspotModeActive }))
1137
+ isHotspotMode && onHotspotAdd && (renderHotspotModeBanner ? renderHotspotModeBanner({ labels: mergedLabels }) : /* @__PURE__ */ jsx14(Viewer360AddModeBanner, { className: mergedClassNames.hotspotModeBanner, label: mergedLabels.hotspotModeActive }))
1179
1138
  ]
1180
1139
  }
1181
1140
  ),
1182
- renderToolbar ? renderToolbar(toolbarProps) : showDefaultToolbar ? /* @__PURE__ */ jsx15(Viewer360Toolbar, { ...toolbarProps }) : null
1141
+ renderToolbar ? renderToolbar(toolbarProps) : showDefaultToolbar ? /* @__PURE__ */ jsx14(Viewer360Toolbar, { ...toolbarProps }) : null
1183
1142
  ] });
1184
1143
  }
1185
1144
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/feature/Viewer360.tsx","../src/constants/viewer360Labels.ts","../src/constants/viewer360ClassNames.ts","../src/components/utils/index.ts","../src/helpers/viewer360PropsHelpers.ts","../src/hooks/useViewer360.ts","../src/helpers/adjustViewerZoom.ts","../src/helpers/computeDragFrameIndex.ts","../src/helpers/computeViewerImageLayout.ts","../src/helpers/computeViewerPanOffset.ts","../src/helpers/viewerHelpers.ts","../src/components/ui/Card/index.tsx","../src/components/ui/Badge/index.tsx","../src/feature/Viewer360AddModeBanner.tsx","../src/feature/Viewer360FrameIndicator.tsx","../src/helpers/markerHelpers.ts","../src/components/ui/Button/index.tsx","../src/components/ui/Item/index.tsx","../src/components/ui/Separator/index.tsx","../src/feature/Viewer360MarkerPin.tsx","../src/constants/viewer360MarkerLabels.ts","../src/components/ui/Popover/index.tsx","../src/feature/Viewer360HotspotOverlay.tsx","../src/components/ui/Label/index.tsx","../src/components/ui/Spinner/index.tsx","../src/feature/Viewer360LoadingOverlay.tsx","../src/feature/Viewer360Toolbar.tsx","../src/constants/viewer360Config.ts"],"sourcesContent":["import type { JSX } from 'react';\nimport { useEffect, useMemo, useState } from 'react';\n\nimport { buildViewer360ThemeStyle, mergeViewer360ClassNames, mergeViewer360Labels } from '../helpers/viewer360PropsHelpers';\nimport { useViewer360 } from '../hooks/useViewer360';\nimport type { Viewer360OverlayRenderProps, Viewer360Props, Viewer360ToolbarRenderProps } from '../types';\nimport { Card } from '@/components/ui/Card';\nimport { cn } from '@/components/utils';\n\nimport { Viewer360AddModeBanner } from './Viewer360AddModeBanner';\nimport { Viewer360FrameIndicator } from './Viewer360FrameIndicator';\nimport { Viewer360HotspotOverlay } from './Viewer360HotspotOverlay';\nimport { Viewer360LoadingOverlay } from './Viewer360LoadingOverlay';\nimport { Viewer360Toolbar } from './Viewer360Toolbar';\n\nexport function Viewer360<TData = unknown>({\n frames,\n currentFrameIndex: controlledFrameIndex,\n defaultFrameIndex = 0,\n onFrameChange,\n config,\n className,\n classNames,\n style,\n theme,\n labels,\n aspectRatio = '16 / 10',\n showZoomControls = true,\n showResetControl = true,\n showFrameIndicator = true,\n showDragHint = true,\n showHotspotModeControl = false,\n hotspotPin,\n hotspots = [],\n renderHotspot,\n renderLoading,\n renderFrameIndicator,\n renderHotspotModeBanner,\n renderToolbar,\n onHotspotClick,\n hotspotMode: controlledHotspotMode,\n defaultHotspotMode = false,\n onHotspotModeChange,\n onHotspotAdd,\n children,\n}: Viewer360Props<TData>): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: States & Constants\n // ----------------------------------------------------------------------------------------------------\n const mergedLabels = useMemo(() => mergeViewer360Labels(labels), [labels]);\n const mergedClassNames = useMemo(() => mergeViewer360ClassNames(classNames), [classNames]);\n const themeStyle = useMemo(() => buildViewer360ThemeStyle(theme), [theme]);\n\n const [internalFrameIndex, setInternalFrameIndex] = useState(defaultFrameIndex);\n const [internalHotspotMode, setInternalHotspotMode] = useState(defaultHotspotMode);\n\n const currentFrameIndex = controlledFrameIndex ?? internalFrameIndex;\n const hotspotMode = controlledHotspotMode ?? internalHotspotMode;\n\n // ----------------------------------------------------------------------------------------------------\n // MARK: Functions\n // ----------------------------------------------------------------------------------------------------\n function handleFrameChange(index: number): void {\n if (controlledFrameIndex === undefined) {\n setInternalFrameIndex(index);\n }\n\n onFrameChange?.(index);\n }\n\n function handleHotspotModeChange(active: boolean): void {\n if (controlledHotspotMode === undefined) {\n setInternalHotspotMode(active);\n }\n\n onHotspotModeChange?.(active);\n }\n\n const {\n canvasRef,\n containerRef,\n currentFrame,\n currentFrameHotspots,\n imagesLoaded,\n isHotspotMode,\n isResetDisabled,\n maxZoom,\n minZoom,\n viewerCursorClass,\n zoom,\n getHotspotScreenPosition,\n handleCanvasClick,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleWheel,\n handleResetView,\n handleZoomIn,\n handleZoomOut,\n } = useViewer360<TData>({\n frames,\n hotspots,\n currentFrameIndex,\n onFrameChange: handleFrameChange,\n config,\n hotspotMode,\n onHotspotAdd,\n });\n\n useEffect(() => {\n if (controlledHotspotMode === undefined) return;\n if (controlledHotspotMode !== internalHotspotMode) {\n setInternalHotspotMode(controlledHotspotMode);\n }\n }, [controlledHotspotMode, internalHotspotMode]);\n\n const frameLabel = currentFrame?.label ?? frames[currentFrameIndex]?.label;\n const overlayProps: Viewer360OverlayRenderProps = {\n currentFrameIndex,\n frameCount: frames.length,\n frameLabel,\n isHotspotMode,\n labels: mergedLabels,\n };\n const toolbarProps: Viewer360ToolbarRenderProps = {\n zoom,\n minZoom,\n maxZoom,\n isResetDisabled,\n isHotspotMode,\n showHotspotModeControl,\n showZoomControls,\n showResetControl,\n showDragHint,\n labels: mergedLabels,\n onZoomIn: handleZoomIn,\n onZoomOut: handleZoomOut,\n onResetView: handleResetView,\n onHotspotModeChange: handleHotspotModeChange,\n };\n const showDefaultToolbar = showZoomControls || showResetControl || showHotspotModeControl || showDragHint;\n\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Card className={cn(mergedClassNames.root, 'gap-0 py-0 shadow-none ring-0', className)} style={{ ...themeStyle, ...style }}>\n <div\n ref={containerRef}\n className={cn(mergedClassNames.viewport, viewerCursorClass)}\n style={{ aspectRatio }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerLeave={handlePointerUp}\n onWheel={handleWheel}\n onClick={handleCanvasClick}\n >\n <canvas ref={canvasRef} className={mergedClassNames.canvas} />\n\n <div className={mergedClassNames.overlay}>\n {currentFrameHotspots.map((hotspot) => {\n const position = getHotspotScreenPosition(hotspot);\n\n return (\n <Viewer360HotspotOverlay\n key={hotspot.id}\n hotspot={hotspot}\n leftPercent={position.leftPercent}\n topPercent={position.topPercent}\n hotspotPin={hotspotPin}\n renderHotspot={renderHotspot}\n onHotspotClick={onHotspotClick}\n />\n );\n })}\n {children}\n </div>\n\n {!imagesLoaded &&\n (renderLoading ? (\n renderLoading()\n ) : (\n <Viewer360LoadingOverlay\n className={mergedClassNames.loading}\n textClassName={mergedClassNames.loadingText}\n label={mergedLabels.loading}\n />\n ))}\n\n {showFrameIndicator &&\n frames.length > 0 &&\n (renderFrameIndicator ? (\n renderFrameIndicator(overlayProps)\n ) : (\n <Viewer360FrameIndicator\n className={mergedClassNames.frameIndicator}\n label={mergedLabels.frameIndicator({\n current: currentFrameIndex + 1,\n total: frames.length,\n label: frameLabel,\n })}\n />\n ))}\n\n {isHotspotMode &&\n onHotspotAdd &&\n (renderHotspotModeBanner ? (\n renderHotspotModeBanner({ labels: mergedLabels })\n ) : (\n <Viewer360AddModeBanner className={mergedClassNames.hotspotModeBanner} label={mergedLabels.hotspotModeActive} />\n ))}\n </div>\n\n {renderToolbar ? renderToolbar(toolbarProps) : showDefaultToolbar ? <Viewer360Toolbar {...toolbarProps} /> : null}\n </Card>\n );\n}\n","import type { Viewer360Labels } from '../types/Viewer360Props';\n\nexport const defaultViewer360Labels: Required<Viewer360Labels> = {\n loading: 'Loading images…',\n dragHint: 'Drag to rotate • Scroll to zoom',\n frameIndicator: ({ current, total, label }) => (label ? `${label} · ${current} / ${total}` : `${current} / ${total}`),\n zoom: (percent) => `${percent}%`,\n hotspotModeActive: 'Click on the image to place a hotspot',\n addHotspot: 'Add hotspot',\n zoomIn: 'Zoom in',\n zoomOut: 'Zoom out',\n resetView: 'Reset view',\n deleteMarker: 'Remove marker',\n};\n","import type { Viewer360ClassNames } from '../types/Viewer360Props';\nimport type { Viewer360MarkerPinClassNames } from '../types/Viewer360Marker';\n\nexport const viewer360ClassNames: Required<Viewer360ClassNames> = {\n root: 'overflow-hidden rounded-lg border bg-card text-card-foreground',\n viewport: 'relative aspect-[16/10] w-full touch-none select-none bg-muted',\n canvas: 'absolute inset-0 size-full',\n overlay: 'pointer-events-none absolute inset-0 overflow-hidden',\n loading: 'absolute inset-0 flex items-center justify-center bg-muted/80',\n loadingText: 'text-sm text-muted-foreground',\n frameIndicator:\n 'pointer-events-none absolute bottom-4 start-4 z-20 rounded-full border bg-background px-4 py-1.5 text-xs font-medium shadow-sm',\n hotspotModeBanner:\n 'pointer-events-none absolute top-4 start-1/2 z-20 -translate-x-1/2 rounded-full border border-amber-200 bg-amber-50 px-4 py-1.5 text-xs font-medium text-amber-800 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-400',\n toolbar: 'flex flex-wrap items-center justify-between gap-2 border-t px-4 py-3',\n dragHint: 'hidden text-xs text-muted-foreground sm:block',\n controls: 'ms-auto flex items-center gap-1.5',\n controlButton: '',\n controlButtonActive: '',\n controlButtonDisabled: '',\n zoomDisplay: 'flex min-w-[3rem] items-center justify-center gap-1 rounded-md border bg-background px-2 py-1 text-xs font-medium',\n divider: 'mx-1 h-6 w-px bg-border',\n};\n\nexport const viewer360MarkerPinClassNames: Required<Viewer360MarkerPinClassNames> = {\n root: 'pointer-events-auto absolute z-30 -translate-x-1/2 -translate-y-1/2',\n ping: 'absolute inline-flex size-6 -translate-x-1/4 -translate-y-1/4 animate-ping rounded-full bg-destructive opacity-60',\n dot: 'relative flex size-4 items-center justify-center rounded-full border-2 border-background bg-destructive shadow-md transition-transform duration-200 hover:scale-125 focus:outline-none',\n tooltip: 'absolute bottom-6 left-1/2 z-40 w-64 -translate-x-1/2 rounded-lg border bg-popover p-3 text-popover-foreground shadow-md',\n tooltipHeader: 'flex items-start justify-between gap-2',\n tooltipBody: 'flex min-w-0 flex-col gap-1',\n tooltipTitle: 'text-sm font-medium',\n tooltipDescription: 'mt-2 line-clamp-3 text-xs text-muted-foreground',\n deleteButton: '',\n};\n\n/** @deprecated Use `viewer360ClassNames` */\nexport const defaultViewer360ClassNames = viewer360ClassNames;\n\n/** @deprecated Use `viewer360MarkerPinClassNames` */\nexport const defaultViewer360MarkerPinClassNames = viewer360MarkerPinClassNames;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { CSSProperties } from 'react';\n\nimport { defaultViewer360Labels } from '../constants/viewer360Labels';\nimport { viewer360ClassNames } from '../constants/viewer360ClassNames';\nimport type { Viewer360ClassNames, Viewer360Labels, Viewer360Theme } from '../types';\nimport { cn } from '@/components/utils';\n\nexport function mergeViewer360Labels(labels?: Viewer360Labels): Required<Viewer360Labels> {\n return {\n loading: labels?.loading ?? defaultViewer360Labels.loading,\n dragHint: labels?.dragHint ?? defaultViewer360Labels.dragHint,\n frameIndicator: labels?.frameIndicator ?? defaultViewer360Labels.frameIndicator,\n zoom: labels?.zoom ?? defaultViewer360Labels.zoom,\n hotspotModeActive: labels?.hotspotModeActive ?? defaultViewer360Labels.hotspotModeActive,\n addHotspot: labels?.addHotspot ?? defaultViewer360Labels.addHotspot,\n zoomIn: labels?.zoomIn ?? defaultViewer360Labels.zoomIn,\n zoomOut: labels?.zoomOut ?? defaultViewer360Labels.zoomOut,\n resetView: labels?.resetView ?? defaultViewer360Labels.resetView,\n deleteMarker: labels?.deleteMarker ?? defaultViewer360Labels.deleteMarker,\n };\n}\n\nexport function mergeViewer360ClassNames(classNames?: Viewer360ClassNames): Required<Viewer360ClassNames> {\n return {\n root: cn(viewer360ClassNames.root, classNames?.root),\n viewport: cn(viewer360ClassNames.viewport, classNames?.viewport),\n canvas: cn(viewer360ClassNames.canvas, classNames?.canvas),\n overlay: cn(viewer360ClassNames.overlay, classNames?.overlay),\n loading: cn(viewer360ClassNames.loading, classNames?.loading),\n loadingText: cn(viewer360ClassNames.loadingText, classNames?.loadingText),\n frameIndicator: cn(viewer360ClassNames.frameIndicator, classNames?.frameIndicator),\n hotspotModeBanner: cn(viewer360ClassNames.hotspotModeBanner, classNames?.hotspotModeBanner),\n toolbar: cn(viewer360ClassNames.toolbar, classNames?.toolbar),\n dragHint: cn(viewer360ClassNames.dragHint, classNames?.dragHint),\n controls: cn(viewer360ClassNames.controls, classNames?.controls),\n controlButton: cn(viewer360ClassNames.controlButton, classNames?.controlButton),\n controlButtonActive: cn(viewer360ClassNames.controlButtonActive, classNames?.controlButtonActive),\n controlButtonDisabled: cn(viewer360ClassNames.controlButtonDisabled, classNames?.controlButtonDisabled),\n zoomDisplay: cn(viewer360ClassNames.zoomDisplay, classNames?.zoomDisplay),\n divider: cn(viewer360ClassNames.divider, classNames?.divider),\n };\n}\n\nexport function buildViewer360ThemeStyle(theme?: Viewer360Theme): CSSProperties {\n return theme ? (theme as CSSProperties) : {};\n}\n","import type { PointerEvent as ReactPointerEvent, RefObject, WheelEvent as ReactWheelEvent } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\nimport {\n applyWheelZoom,\n getViewerCursorClass,\n isResetDisabled,\n resolveViewer360Config,\n stepZoomIn,\n stepZoomOut,\n} from '../helpers/adjustViewerZoom';\nimport { computeDragFrameIndex } from '../helpers/computeDragFrameIndex';\nimport {\n computeHotspotPositionFromClick,\n computeHotspotScreenPosition,\n computeViewerImageLayout,\n type ViewerImageLayout,\n} from '../helpers/computeViewerImageLayout';\nimport { computeViewerPanOffset } from '../helpers/computeViewerPanOffset';\nimport {\n drawFrameOnCanvas,\n filterHotspotsByFrame,\n getFramesSignature,\n hasLoadedViewerFrame,\n preloadViewerFrames,\n} from '../helpers/viewerHelpers';\nimport type {\n Viewer360Config,\n Viewer360Frame,\n Viewer360Hotspot,\n Viewer360HotspotPosition,\n Viewer360PanOffset,\n} from '../types';\n\ntype UseViewer360Params<TData> = {\n frames: Viewer360Frame[];\n currentFrameIndex: number;\n onFrameChange: (index: number) => void;\n hotspots?: Viewer360Hotspot<TData>[];\n config?: Viewer360Config;\n hotspotMode?: boolean;\n onHotspotAdd?: (position: Viewer360HotspotPosition) => void;\n};\n\ntype UseViewer360Return<TData> = {\n canvasRef: RefObject<HTMLCanvasElement | null>;\n containerRef: RefObject<HTMLDivElement | null>;\n currentFrame: Viewer360Frame | undefined;\n currentFrameHotspots: Viewer360Hotspot<TData>[];\n imagesLoaded: boolean;\n isHotspotMode: boolean;\n isResetDisabled: boolean;\n maxZoom: number;\n minZoom: number;\n viewerCursorClass: string;\n zoom: number;\n getCurrentImageLayout: () => ViewerImageLayout | null;\n getHotspotScreenPosition: (hotspot: Viewer360Hotspot<TData>) => { leftPercent: number; topPercent: number };\n handleCanvasClick: (event: React.MouseEvent<HTMLDivElement>) => void;\n handlePointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;\n handlePointerMove: (event: ReactPointerEvent<HTMLDivElement>) => void;\n handlePointerUp: (event: ReactPointerEvent<HTMLDivElement>) => void;\n handleResetView: () => void;\n handleWheel: (event: ReactWheelEvent<HTMLDivElement>) => void;\n handleZoomIn: () => void;\n handleZoomOut: () => void;\n};\n\nexport function useViewer360<TData = unknown>({\n frames,\n hotspots = [],\n currentFrameIndex,\n onFrameChange,\n config,\n hotspotMode: hotspotModeProp = false,\n onHotspotAdd,\n}: UseViewer360Params<TData>): UseViewer360Return<TData> {\n const resolvedConfig = useMemo(() => resolveViewer360Config(config), [config]);\n const { minZoom, maxZoom } = resolvedConfig;\n\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const imagesRef = useRef<HTMLImageElement[]>([]);\n const dragStartRef = useRef<{ pointerX: number; frameIndex: number } | null>(null);\n const panStartRef = useRef<{ pointerX: number; pointerY: number; panX: number; panY: number } | null>(null);\n\n const framesSignature = getFramesSignature(frames);\n const [loadedSignature, setLoadedSignature] = useState<string | null>(null);\n const [zoom, setZoom] = useState<number>(minZoom);\n const [pan, setPan] = useState<Viewer360PanOffset>({ panX: 0, panY: 0 });\n const [isDragging, setIsDragging] = useState(false);\n\n const isHotspotMode = hotspotModeProp;\n const imagesLoaded = loadedSignature === framesSignature;\n const currentFrameHotspots = filterHotspotsByFrame(hotspots, currentFrameIndex) as Viewer360Hotspot<TData>[];\n const currentFrame = frames[currentFrameIndex];\n const viewerCursorClass = getViewerCursorClass(isHotspotMode, zoom, isDragging, minZoom);\n const resetDisabled = isResetDisabled(zoom, pan, resolvedConfig);\n\n useEffect(() => {\n let cancelled = false;\n imagesRef.current = [];\n\n async function loadFrames(): Promise<void> {\n const loadedImages = await preloadViewerFrames(frames);\n\n if (!cancelled && hasLoadedViewerFrame(loadedImages)) {\n imagesRef.current = loadedImages;\n setLoadedSignature(framesSignature);\n }\n }\n\n void loadFrames();\n\n return (): void => {\n cancelled = true;\n };\n }, [frames, framesSignature]);\n\n useEffect(() => {\n if (!imagesLoaded) return;\n\n function renderFrame(): void {\n const canvas = canvasRef.current;\n const container = containerRef.current;\n const img = imagesRef.current[currentFrameIndex];\n\n if (!canvas || !container) return;\n\n drawFrameOnCanvas({ canvas, container, image: img, zoom, pan });\n }\n\n renderFrame();\n window.addEventListener('resize', renderFrame);\n\n return (): void => window.removeEventListener('resize', renderFrame);\n }, [imagesLoaded, currentFrameIndex, zoom, pan]);\n\n useEffect(() => {\n if (!resolvedConfig.autoRotate || frames.length <= 1 || isDragging || isHotspotMode || zoom > minZoom) {\n return;\n }\n\n const direction = resolvedConfig.autoRotateDirection === 'backward' ? -1 : 1;\n const interval = window.setInterval(() => {\n onFrameChange((currentFrameIndex + direction + frames.length) % frames.length);\n }, resolvedConfig.autoRotateIntervalMs);\n\n return (): void => window.clearInterval(interval);\n }, [\n resolvedConfig.autoRotate,\n resolvedConfig.autoRotateDirection,\n resolvedConfig.autoRotateIntervalMs,\n frames.length,\n currentFrameIndex,\n isDragging,\n isHotspotMode,\n zoom,\n minZoom,\n onFrameChange,\n ]);\n\n function getCurrentImageLayout(): ViewerImageLayout | null {\n const container = containerRef.current;\n const image = imagesRef.current[currentFrameIndex];\n\n if (!container || !image || !image.complete || !image.naturalWidth) return null;\n\n const rect = container.getBoundingClientRect();\n\n return computeViewerImageLayout({\n containerWidth: rect.width,\n containerHeight: rect.height,\n imageWidth: image.naturalWidth,\n imageHeight: image.naturalHeight,\n pan,\n });\n }\n\n function getHotspotScreenPosition(hotspot: Viewer360Hotspot<TData>): { leftPercent: number; topPercent: number } {\n const layout = getCurrentImageLayout();\n\n if (!layout) {\n return { leftPercent: hotspot.positionX, topPercent: hotspot.positionY };\n }\n\n return computeHotspotScreenPosition(hotspot.positionX, hotspot.positionY, layout, zoom);\n }\n\n function handlePointerDown(event: ReactPointerEvent<HTMLDivElement>): void {\n if (isHotspotMode) return;\n\n event.currentTarget.setPointerCapture(event.pointerId);\n\n if (zoom > minZoom) {\n panStartRef.current = { pointerX: event.clientX, pointerY: event.clientY, panX: pan.panX, panY: pan.panY };\n } else {\n dragStartRef.current = { pointerX: event.clientX, frameIndex: currentFrameIndex };\n }\n\n setIsDragging(true);\n }\n\n function handlePointerMove(event: ReactPointerEvent<HTMLDivElement>): void {\n if (!isDragging) return;\n\n if (panStartRef.current && zoom > minZoom) {\n setPan(computeViewerPanOffset(panStartRef.current, event.clientX, event.clientY));\n return;\n }\n\n if (dragStartRef.current && zoom <= minZoom) {\n const nextFrameIndex = computeDragFrameIndex(\n dragStartRef.current,\n event.clientX,\n frames.length,\n resolvedConfig.dragSensitivity\n );\n\n if (nextFrameIndex !== null) {\n onFrameChange(nextFrameIndex);\n }\n }\n }\n\n function handlePointerUp(event: ReactPointerEvent<HTMLDivElement>): void {\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n\n dragStartRef.current = null;\n panStartRef.current = null;\n setIsDragging(false);\n }\n\n function handleWheel(event: ReactWheelEvent<HTMLDivElement>): void {\n event.preventDefault();\n const { zoom: nextZoom, pan: nextPan } = applyWheelZoom(zoom, event.deltaY, pan, resolvedConfig);\n setZoom(nextZoom);\n setPan(nextPan);\n }\n\n function handleCanvasClick(event: React.MouseEvent<HTMLDivElement>): void {\n if (!isHotspotMode || isDragging || !onHotspotAdd) return;\n\n const frame = frames[currentFrameIndex];\n if (!frame) return;\n\n const layout = getCurrentImageLayout();\n if (!layout) return;\n\n const { positionX, positionY } = computeHotspotPositionFromClick(\n event.clientX,\n event.clientY,\n event.currentTarget.getBoundingClientRect(),\n layout,\n zoom\n );\n\n onHotspotAdd({\n frameIndex: currentFrameIndex,\n frameId: frame.id,\n positionX,\n positionY,\n });\n }\n\n function handleResetView(): void {\n setZoom(minZoom);\n setPan({ panX: 0, panY: 0 });\n }\n\n function handleZoomIn(): void {\n setZoom(stepZoomIn(zoom, resolvedConfig));\n }\n\n function handleZoomOut(): void {\n const { zoom: nextZoom, pan: nextPan } = stepZoomOut(zoom, pan, resolvedConfig);\n setZoom(nextZoom);\n setPan(nextPan);\n }\n\n return {\n canvasRef,\n containerRef,\n currentFrame,\n currentFrameHotspots,\n imagesLoaded,\n isHotspotMode,\n isResetDisabled: resetDisabled,\n maxZoom,\n minZoom,\n viewerCursorClass,\n zoom,\n getCurrentImageLayout,\n getHotspotScreenPosition,\n handleCanvasClick,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleResetView,\n handleWheel,\n handleZoomIn,\n handleZoomOut,\n };\n}\n","import type { Viewer360Config } from '../types';\n\nimport type { PanOffset } from './computeViewerImageLayout';\n\ntype ResolvedViewer360Config = Required<Viewer360Config>;\n\nexport function resolveViewer360Config(config?: Viewer360Config): ResolvedViewer360Config {\n return {\n minZoom: config?.minZoom ?? 1,\n maxZoom: config?.maxZoom ?? 3,\n zoomStep: config?.zoomStep ?? 0.15,\n dragSensitivity: config?.dragSensitivity ?? 8,\n autoRotate: config?.autoRotate ?? false,\n autoRotateIntervalMs: config?.autoRotateIntervalMs ?? 100,\n autoRotateDirection: config?.autoRotateDirection ?? 'forward',\n };\n}\n\nexport function clampZoom(zoom: number, config: ResolvedViewer360Config): number {\n return Math.min(config.maxZoom, Math.max(config.minZoom, zoom));\n}\n\nexport function applyWheelZoom(\n currentZoom: number,\n deltaY: number,\n currentPan: PanOffset,\n config: ResolvedViewer360Config\n): { zoom: number; pan: PanOffset } {\n const delta = deltaY > 0 ? -config.zoomStep : config.zoomStep;\n const zoom = clampZoom(currentZoom + delta, config);\n\n return {\n zoom,\n pan: zoom === config.minZoom ? { panX: 0, panY: 0 } : currentPan,\n };\n}\n\nexport function stepZoomIn(currentZoom: number, config: ResolvedViewer360Config): number {\n return clampZoom(currentZoom + config.zoomStep, config);\n}\n\nexport function stepZoomOut(\n currentZoom: number,\n currentPan: PanOffset,\n config: ResolvedViewer360Config\n): { zoom: number; pan: PanOffset } {\n const zoom = clampZoom(currentZoom - config.zoomStep, config);\n\n return {\n zoom,\n pan: zoom === config.minZoom ? { panX: 0, panY: 0 } : currentPan,\n };\n}\n\nexport function isResetDisabled(zoom: number, pan: PanOffset, config: ResolvedViewer360Config): boolean {\n return zoom === config.minZoom && pan.panX === 0 && pan.panY === 0;\n}\n\nexport function getViewerCursorClass(isHotspotMode: boolean, zoom: number, isDragging: boolean, minZoom: number): string {\n if (isHotspotMode) return 'cursor-crosshair';\n if (isDragging) return 'cursor-grabbing';\n if (zoom > minZoom) return 'cursor-grab';\n return 'cursor-ew-resize';\n}\n","export function clampFrameIndex(index: number, frameCount: number): number {\n if (frameCount === 0) return 0;\n return ((index % frameCount) + frameCount) % frameCount;\n}\n\ntype DragStart = {\n pointerX: number;\n frameIndex: number;\n};\n\nexport function computeDragFrameIndex(\n dragStart: DragStart,\n clientX: number,\n frameCount: number,\n dragSensitivity: number\n): number | null {\n const deltaX = clientX - dragStart.pointerX;\n const frameDelta = Math.round(-deltaX / dragSensitivity);\n\n if (frameDelta === 0) return null;\n\n return clampFrameIndex(dragStart.frameIndex + frameDelta, frameCount);\n}\n","export type ViewerImageLayout = {\n width: number;\n height: number;\n centerX: number;\n centerY: number;\n drawWidth: number;\n drawHeight: number;\n offsetX: number;\n offsetY: number;\n};\n\nexport type PanOffset = {\n panX: number;\n panY: number;\n};\n\ntype ComputeViewerImageLayoutParams = {\n containerWidth: number;\n containerHeight: number;\n imageWidth: number;\n imageHeight: number;\n pan?: PanOffset;\n};\n\nexport function computeViewerImageLayout({\n containerWidth,\n containerHeight,\n imageWidth,\n imageHeight,\n pan = { panX: 0, panY: 0 },\n}: ComputeViewerImageLayoutParams): ViewerImageLayout {\n const imgAspect = imageWidth / imageHeight;\n const containerAspect = containerWidth / containerHeight;\n\n let drawWidth: number;\n let drawHeight: number;\n\n if (imgAspect > containerAspect) {\n drawWidth = containerWidth;\n drawHeight = containerWidth / imgAspect;\n } else {\n drawHeight = containerHeight;\n drawWidth = containerHeight * imgAspect;\n }\n\n const offsetX = (containerWidth - drawWidth) / 2 + pan.panX;\n const offsetY = (containerHeight - drawHeight) / 2 + pan.panY;\n\n return {\n width: containerWidth,\n height: containerHeight,\n centerX: containerWidth / 2,\n centerY: containerHeight / 2,\n drawWidth,\n drawHeight,\n offsetX,\n offsetY,\n };\n}\n\nexport function computeHotspotScreenPosition(\n hotspotX: number,\n hotspotY: number,\n layout: ViewerImageLayout,\n zoom: number\n): { leftPercent: number; topPercent: number } {\n const baseOffsetX = (layout.width - layout.drawWidth) / 2;\n const baseOffsetY = (layout.height - layout.drawHeight) / 2;\n\n const containerX = (hotspotX / 100) * layout.width;\n const containerY = (hotspotY / 100) * layout.height;\n\n const imageLocalX = (containerX - baseOffsetX) / layout.drawWidth;\n const imageLocalY = (containerY - baseOffsetY) / layout.drawHeight;\n\n const imagePointX = layout.offsetX + imageLocalX * layout.drawWidth;\n const imagePointY = layout.offsetY + imageLocalY * layout.drawHeight;\n\n const screenX = layout.centerX + zoom * (imagePointX - layout.centerX);\n const screenY = layout.centerY + zoom * (imagePointY - layout.centerY);\n\n return {\n leftPercent: (screenX / layout.width) * 100,\n topPercent: (screenY / layout.height) * 100,\n };\n}\n\nexport function computeHotspotPositionFromClick(\n clientX: number,\n clientY: number,\n containerRect: DOMRect,\n layout: ViewerImageLayout,\n zoom: number\n): { positionX: number; positionY: number } {\n const clickX = clientX - containerRect.left;\n const clickY = clientY - containerRect.top;\n\n const unzoomedX = layout.centerX + (clickX - layout.centerX) / zoom;\n const unzoomedY = layout.centerY + (clickY - layout.centerY) / zoom;\n\n const baseOffsetX = (layout.width - layout.drawWidth) / 2;\n const baseOffsetY = (layout.height - layout.drawHeight) / 2;\n\n const imageLocalX = Math.min(1, Math.max(0, (unzoomedX - layout.offsetX) / layout.drawWidth));\n const imageLocalY = Math.min(1, Math.max(0, (unzoomedY - layout.offsetY) / layout.drawHeight));\n\n const storedX = baseOffsetX + imageLocalX * layout.drawWidth;\n const storedY = baseOffsetY + imageLocalY * layout.drawHeight;\n\n return {\n positionX: Math.min(100, Math.max(0, (storedX / layout.width) * 100)),\n positionY: Math.min(100, Math.max(0, (storedY / layout.height) * 100)),\n };\n}\n","import type { PanOffset } from './computeViewerImageLayout';\n\ntype PanStart = {\n pointerX: number;\n pointerY: number;\n panX: number;\n panY: number;\n};\n\nexport function computeViewerPanOffset(panStart: PanStart, clientX: number, clientY: number): PanOffset {\n const deltaX = clientX - panStart.pointerX;\n const deltaY = clientY - panStart.pointerY;\n\n return {\n panX: panStart.panX + deltaX,\n panY: panStart.panY + deltaY,\n };\n}\n","import type { Viewer360Frame } from '../types';\n\nimport { computeViewerImageLayout, type PanOffset } from './computeViewerImageLayout';\n\ntype DrawFrameOnCanvasParams = {\n canvas: HTMLCanvasElement;\n container: HTMLDivElement;\n image: HTMLImageElement;\n zoom: number;\n pan: PanOffset;\n};\n\nexport function drawFrameOnCanvas({ canvas, container, image, zoom, pan }: DrawFrameOnCanvasParams): void {\n if (!image.complete || !image.naturalWidth) return;\n\n const rect = container.getBoundingClientRect();\n const dpr = window.devicePixelRatio || 1;\n\n canvas.width = rect.width * dpr;\n canvas.height = rect.height * dpr;\n canvas.style.width = `${rect.width}px`;\n canvas.style.height = `${rect.height}px`;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, rect.width, rect.height);\n\n const layout = computeViewerImageLayout({\n containerWidth: rect.width,\n containerHeight: rect.height,\n imageWidth: image.naturalWidth,\n imageHeight: image.naturalHeight,\n pan,\n });\n\n ctx.save();\n ctx.translate(layout.centerX, layout.centerY);\n ctx.scale(zoom, zoom);\n ctx.translate(-layout.centerX, -layout.centerY);\n ctx.drawImage(image, layout.offsetX, layout.offsetY, layout.drawWidth, layout.drawHeight);\n ctx.restore();\n}\n\nexport function getFramesSignature(frames: Viewer360Frame[]): string {\n return frames.map((frame) => frame.id).join('-');\n}\n\nexport function preloadFrameImage(frame: Viewer360Frame): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = (): void => resolve(img);\n img.onerror = (): void => reject(new Error(`Failed to load frame: ${frame.src}`));\n img.src = frame.src;\n });\n}\n\nexport async function preloadViewerFrames(frames: Viewer360Frame[]): Promise<HTMLImageElement[]> {\n const results = await Promise.allSettled(frames.map(preloadFrameImage));\n\n return results.map((result) => (result.status === 'fulfilled' ? result.value : new Image()));\n}\n\nexport function hasLoadedViewerFrame(images: HTMLImageElement[]): boolean {\n return images.some((image) => image.complete && image.naturalWidth > 0);\n}\n\nexport function filterHotspotsByFrame<TData>(\n hotspots: Array<{ frameIndex: number; data?: TData }>,\n frameIndex: number\n): Array<{ frameIndex: number; data?: TData }> {\n return hotspots.filter((hotspot) => hotspot.frameIndex === frameIndex);\n}\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cn } from '@/components/utils';\n\nfunction Card({ className, size = 'default', ...props }: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }): JSX.Element {\n return (\n <div\n data-slot=\"card\"\n data-size={size}\n className={cn(\n 'ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-6 text-sm shadow-xs ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col',\n className\n )}\n {...props}\n />\n );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-header\"\n className={cn(\n 'gap-0.5 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]',\n className\n )}\n {...props}\n />\n );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-title\"\n className={cn('text-lg leading-normal font-semibold group-data-[size=sm]/card:text-sm', className)}\n {...props}\n />\n );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"card-description\" className={cn('text-muted-foreground text-xs font-medium', className)} {...props} />;\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-action\"\n className={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}\n {...props}\n />\n );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"card-content\" className={cn('px-6 group-data-[size=sm]/card:px-4', className)} {...props} />;\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-footer\"\n className={cn(\n 'rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center',\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\n\nimport { cn } from '@/components/utils';\n\nconst badgeVariants = cva(\n 'h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n destructive:\n 'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',\n outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n link: 'text-primary underline-offset-4 hover:underline',\n info: 'bg-blue-500 text-white hover:bg-blue-600',\n warning: 'bg-amber-500 text-white hover:bg-amber-600',\n success: 'bg-green-600 text-white hover:bg-green-700',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n }\n);\n\nfunction Badge({\n className,\n variant = 'default',\n asChild = false,\n ...props\n}: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & { asChild?: boolean }): JSX.Element {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const Comp = asChild ? Slot.Root : 'span';\n\n return <Comp data-slot=\"badge\" data-variant={variant} className={cn(badgeVariants({ variant }), className)} {...props} />;\n}\n\nexport { Badge, badgeVariants };\n","import type { JSX } from 'react';\n\nimport { Badge } from '@/components/ui/Badge';\nimport { cn } from '@/components/utils';\n\ntype Viewer360AddModeBannerProps = {\n className?: string;\n label: string;\n};\n\nexport function Viewer360AddModeBanner({ className, label }: Viewer360AddModeBannerProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Badge variant=\"outline\" className={cn('pointer-events-none', className)}>\n {label}\n </Badge>\n );\n}\n","import type { JSX } from 'react';\n\nimport { Badge } from '@/components/ui/Badge';\nimport { cn } from '@/components/utils';\n\ntype Viewer360FrameIndicatorProps = {\n className?: string;\n label: string;\n};\n\nexport function Viewer360FrameIndicator({ className, label }: Viewer360FrameIndicatorProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Badge variant=\"outline\" className={cn('pointer-events-none shadow-sm', className)}>\n {label}\n </Badge>\n );\n}\n","import type { Viewer360Hotspot, Viewer360Marker } from '../types';\n\nexport function hotspotToViewer360Marker<TData>(hotspot: Viewer360Hotspot<TData>): Viewer360Marker {\n const data = hotspot.data;\n\n if (data && typeof data === 'object' && 'title' in data && typeof (data as { title?: unknown }).title === 'string') {\n const marker = data as unknown as Viewer360Marker;\n\n return {\n id: marker.id ?? hotspot.id,\n title: marker.title,\n description: marker.description,\n };\n }\n\n return {\n id: hotspot.id,\n title: hotspot.id,\n };\n}\n\nexport function toViewer360Hotspots<TData extends Viewer360Marker>(\n markers: Array<Viewer360Marker & { frameIndex: number; positionX: number; positionY: number }>,\n mapData?: (marker: Viewer360Marker & { frameIndex: number; positionX: number; positionY: number }) => TData\n): Viewer360Hotspot<TData>[] {\n return markers.map((marker) => ({\n id: marker.id,\n frameIndex: marker.frameIndex,\n positionX: marker.positionX,\n positionY: marker.positionY,\n data: mapData ? mapData(marker) : (marker as unknown as TData),\n }));\n}\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\n\nimport { cn } from '@/components/utils';\n\nconst buttonVariants = cva(\n 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n outline:\n 'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',\n destructive:\n 'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default:\n 'h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2',\n xs: 'h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5',\n sm: 'h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5',\n lg: 'h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pe-3 has-data-[icon=inline-start]:ps-3',\n icon: 'size-9',\n 'icon-xs':\n 'size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md',\n 'icon-sm': 'size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md',\n 'icon-lg': 'size-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n }\n);\n\nfunction Button({\n className,\n variant = 'default',\n size = 'default',\n asChild = false,\n ...props\n}: React.ComponentProps<'button'> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }): JSX.Element {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const Comp = asChild ? Slot.Root : 'button';\n\n return (\n <Comp\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\n\nimport { Separator } from '@/components/ui/Separator';\nimport { cn } from '@/components/utils';\n\nfunction ItemGroup({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n role=\"list\"\n data-slot=\"item-group\"\n className={cn('gap-4 has-[[data-size=sm]]:gap-2.5 has-[[data-size=xs]]:gap-2 group/item-group flex w-full flex-col', className)}\n {...props}\n />\n );\n}\n\nfunction ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>): JSX.Element {\n return <Separator data-slot=\"item-separator\" orientation=\"horizontal\" className={cn('my-2', className)} {...props} />;\n}\n\nconst itemVariants = cva(\n '[a]:hover:bg-muted rounded-md border text-sm w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors',\n {\n variants: {\n variant: {\n default: 'border-transparent',\n outline: 'border-border',\n muted: 'bg-muted/50 border-transparent',\n },\n size: {\n default: 'gap-3.5 px-4 py-3.5',\n sm: 'gap-2.5 px-3 py-2.5',\n xs: 'gap-2 px-2.5 py-2 [[data-slot=dropdown-menu-content]_&]:p-0',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n }\n);\n\nfunction Item({\n className,\n variant = 'default',\n size = 'default',\n asChild = false,\n ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof itemVariants> & { asChild?: boolean }): JSX.Element {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const Comp = asChild ? Slot.Root : 'div';\n return (\n <Comp\n data-slot=\"item\"\n data-variant={variant}\n data-size={size}\n className={cn(itemVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nconst itemMediaVariants = cva(\n 'gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none',\n {\n variants: {\n variant: {\n default: 'bg-transparent',\n icon: '[&_svg]:size-4',\n image: 'size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n }\n);\n\nfunction ItemMedia({\n className,\n variant = 'default',\n ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>): JSX.Element {\n return <div data-slot=\"item-media\" data-variant={variant} className={cn(itemMediaVariants({ variant, className }))} {...props} />;\n}\n\nfunction ItemContent({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"item-content\"\n className={cn('gap-1 group-data-[size=xs]/item:gap-0 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none', className)}\n {...props}\n />\n );\n}\n\nfunction ItemTitle({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"item-title\"\n className={cn('gap-2 text-sm leading-snug font-medium underline-offset-4 line-clamp-1 flex w-fit items-center', className)}\n {...props}\n />\n );\n}\n\nfunction ItemDescription({ className, ...props }: React.ComponentProps<'p'>): JSX.Element {\n return (\n <p\n data-slot=\"item-description\"\n className={cn(\n 'text-muted-foreground text-left text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4',\n className\n )}\n {...props}\n />\n );\n}\n\nfunction ItemActions({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"item-actions\" className={cn('gap-2 flex items-center', className)} {...props} />;\n}\n\nfunction ItemHeader({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"item-header\" className={cn('gap-2 flex basis-full items-center justify-between', className)} {...props} />;\n}\n\nfunction ItemFooter({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"item-footer\" className={cn('gap-2 flex basis-full items-center justify-between', className)} {...props} />;\n}\n\nexport { Item, ItemMedia, ItemContent, ItemActions, ItemGroup, ItemSeparator, ItemTitle, ItemDescription, ItemHeader, ItemFooter };\n","'use client';\n\nimport * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { Separator as SeparatorPrimitive } from 'radix-ui';\n\nimport { cn } from '@/components/utils';\n\nfunction Separator({\n className,\n orientation = 'horizontal',\n decorative = true,\n ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>): JSX.Element {\n return (\n <SeparatorPrimitive.Root\n data-slot=\"separator\"\n decorative={decorative}\n orientation={orientation}\n className={cn(\n 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch',\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Separator };\n","import type { JSX } from 'react';\nimport { useState } from 'react';\n\nimport { Trash2 } from 'lucide-react';\n\nimport { defaultViewer360MarkerPinLabels } from '../constants/viewer360MarkerLabels';\nimport { viewer360MarkerPinClassNames } from '../constants/viewer360ClassNames';\nimport type { Viewer360MarkerPinProps } from '../types';\nimport { Badge } from '@/components/ui/Badge';\nimport { Button } from '@/components/ui/Button';\nimport {\n Item,\n ItemActions,\n ItemContent,\n ItemDescription,\n ItemTitle,\n} from '@/components/ui/Item';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover';\nimport { cn } from '@/components/utils';\n\nexport function Viewer360MarkerPin<TData = unknown>({\n marker,\n hotspot,\n leftPercent,\n topPercent,\n onDelete,\n isDeletePending = false,\n renderTag,\n classNames,\n labels,\n}: Viewer360MarkerPinProps<TData>): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: States & Constants\n // ----------------------------------------------------------------------------------------------------\n const [isOpen, setIsOpen] = useState(false);\n const deleteLabel = labels?.delete ?? defaultViewer360MarkerPinLabels.delete;\n\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Popover open={isOpen} onOpenChange={setIsOpen}>\n <Item\n size=\"xs\"\n variant=\"default\"\n className={cn(viewer360MarkerPinClassNames.root, classNames?.root, 'w-auto border-transparent p-0')}\n style={{ left: `${leftPercent}%`, top: `${topPercent}%` }}\n onMouseEnter={() => setIsOpen(true)}\n onMouseLeave={() => setIsOpen(false)}\n >\n <Badge\n variant=\"destructive\"\n className={cn(viewer360MarkerPinClassNames.ping, classNames?.ping, 'absolute size-6 border-0 bg-destructive opacity-60')}\n aria-hidden=\"true\"\n />\n\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"destructive\"\n size=\"icon-xs\"\n className={cn(\n viewer360MarkerPinClassNames.dot,\n classNames?.dot,\n 'size-4 min-h-4 min-w-4 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:scale-125 hover:bg-destructive'\n )}\n aria-label={marker.title}\n />\n </PopoverTrigger>\n\n <PopoverContent\n className={cn(viewer360MarkerPinClassNames.tooltip, classNames?.tooltip, 'w-64 p-0')}\n side=\"top\"\n align=\"center\"\n onOpenAutoFocus={(event) => event.preventDefault()}\n >\n <Item\n size=\"sm\"\n variant=\"default\"\n className={cn(viewer360MarkerPinClassNames.tooltipHeader, classNames?.tooltipHeader, 'w-full border-transparent')}\n >\n <ItemContent className={cn(viewer360MarkerPinClassNames.tooltipBody, classNames?.tooltipBody)}>\n <ItemTitle className={cn(viewer360MarkerPinClassNames.tooltipTitle, classNames?.tooltipTitle)}>\n {marker.title}\n </ItemTitle>\n {renderTag?.({ marker, hotspot })}\n {marker.description && (\n <ItemDescription className={cn(viewer360MarkerPinClassNames.tooltipDescription, classNames?.tooltipDescription)}>\n {marker.description}\n </ItemDescription>\n )}\n </ItemContent>\n {onDelete && (\n <ItemActions>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className={classNames?.deleteButton}\n disabled={isDeletePending}\n aria-label={deleteLabel}\n onClick={(event) => {\n event.stopPropagation();\n onDelete(marker.id);\n }}\n >\n <Trash2 className=\"size-4\" />\n </Button>\n </ItemActions>\n )}\n </Item>\n </PopoverContent>\n </Item>\n </Popover>\n );\n}\n","export const defaultViewer360MarkerPinLabels = {\n delete: 'Remove marker',\n} as const;\n","'use client';\n\nimport * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { Popover as PopoverPrimitive } from 'radix-ui';\n\nimport { cn } from '@/components/utils';\n\nfunction Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>): JSX.Element {\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>): JSX.Element {\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverContent({\n className,\n align = 'center',\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>): JSX.Element {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n 'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=start]:slide-in-from-end-2 data-[side=end]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 flex flex-col gap-4 rounded-md p-4 text-sm shadow-md ring-1 duration-100 z-50 w-72 origin-(--radix-popover-content-transform-origin) outline-hidden',\n className\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\nfunction PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>): JSX.Element {\n return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />;\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"popover-header\" className={cn('flex flex-col gap-1 text-sm', className)} {...props} />;\n}\n\nfunction PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>): JSX.Element {\n return <div data-slot=\"popover-title\" className={cn('font-medium', className)} {...props} />;\n}\n\nfunction PopoverDescription({ className, ...props }: React.ComponentProps<'p'>): JSX.Element {\n return <p data-slot=\"popover-description\" className={cn('text-muted-foreground', className)} {...props} />;\n}\n\nexport { Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger };\n","import type { JSX, MouseEvent, ReactNode } from 'react';\n\nimport { hotspotToViewer360Marker } from '../helpers/markerHelpers';\nimport type { Viewer360Hotspot, Viewer360HotspotPinOptions, Viewer360HotspotRenderProps } from '../types';\nimport { Button } from '@/components/ui/Button';\nimport { Item } from '@/components/ui/Item';\nimport { cn } from '@/components/utils';\n\nimport { Viewer360MarkerPin } from './Viewer360MarkerPin';\n\ntype Viewer360HotspotOverlayProps<TData = unknown> = {\n hotspot: Viewer360Hotspot<TData>;\n leftPercent: number;\n topPercent: number;\n hotspotPin?: Viewer360HotspotPinOptions<TData>;\n renderHotspot?: (props: Viewer360HotspotRenderProps<TData>) => ReactNode;\n onHotspotClick?: (hotspot: Viewer360Hotspot<TData>, event: MouseEvent<HTMLDivElement>) => void;\n};\n\nexport function Viewer360HotspotOverlay<TData = unknown>({\n hotspot,\n leftPercent,\n topPercent,\n hotspotPin,\n renderHotspot,\n onHotspotClick,\n}: Viewer360HotspotOverlayProps<TData>): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n if (renderHotspot) {\n return (\n <Item size=\"xs\" variant=\"default\" className=\"pointer-events-auto w-auto border-transparent p-0\">\n {renderHotspot({ hotspot, leftPercent, topPercent })}\n </Item>\n );\n }\n\n if (hotspotPin) {\n const marker = hotspotPin.getMarker?.(hotspot) ?? hotspotToViewer360Marker(hotspot);\n\n return (\n <Viewer360MarkerPin\n marker={marker}\n hotspot={hotspot}\n leftPercent={leftPercent}\n topPercent={topPercent}\n onDelete={hotspotPin.onDelete}\n isDeletePending={hotspotPin.deletingMarkerId === hotspot.id}\n renderTag={hotspotPin.renderTag}\n classNames={hotspotPin.classNames}\n labels={hotspotPin.labels}\n />\n );\n }\n\n return (\n <Button\n type=\"button\"\n variant=\"destructive\"\n size=\"icon-xs\"\n className={cn(\n 'pointer-events-auto absolute z-30 size-4 min-h-4 min-w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:bg-destructive'\n )}\n style={{ left: `${leftPercent}%`, top: `${topPercent}%` }}\n aria-label={`Hotspot ${hotspot.id}`}\n onClick={(event) => onHotspotClick?.(hotspot, event as unknown as MouseEvent<HTMLDivElement>)}\n />\n );\n}\n","'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/components/utils';\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>): React.ReactNode {\n return (\n <label\n data-slot=\"label\"\n className={cn(\n 'gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed',\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Label };\n","import type { JSX } from 'react';\n\nimport { Loader2Icon } from 'lucide-react';\n\nimport { cn } from '@/components/utils';\n\nfunction Spinner({ className, ...props }: React.ComponentProps<'svg'>): JSX.Element {\n return <Loader2Icon role=\"status\" aria-label=\"Loading\" className={cn('size-4 animate-spin', className)} {...props} />;\n}\n\nexport { Spinner };\n","import type { JSX } from 'react';\n\nimport { Item } from '@/components/ui/Item';\nimport { Label } from '@/components/ui/Label';\nimport { Spinner } from '@/components/ui/Spinner';\nimport { cn } from '@/components/utils';\n\ntype Viewer360LoadingOverlayProps = {\n className?: string;\n textClassName?: string;\n label: string;\n};\n\nexport function Viewer360LoadingOverlay({ className, textClassName, label }: Viewer360LoadingOverlayProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Item\n size=\"sm\"\n variant=\"muted\"\n className={cn('pointer-events-none w-auto justify-center border-transparent bg-muted/80', className)}\n >\n <Spinner className=\"size-5 text-muted-foreground\" />\n <Label className={cn('font-normal text-muted-foreground', textClassName)}>{label}</Label>\n </Item>\n );\n}\n","import type { JSX } from 'react';\n\nimport { Crosshair, Minus, Plus, RotateCcw, ZoomIn } from 'lucide-react';\n\nimport { viewer360ClassNames } from '../constants/viewer360ClassNames';\nimport type { Viewer360ToolbarRenderProps } from '../types';\nimport { Badge } from '@/components/ui/Badge';\nimport { Button } from '@/components/ui/Button';\nimport { CardFooter } from '@/components/ui/Card';\nimport { Label } from '@/components/ui/Label';\nimport { Separator } from '@/components/ui/Separator';\nimport { cn } from '@/components/utils';\n\ntype Viewer360ToolbarProps = Viewer360ToolbarRenderProps;\n\nexport function Viewer360Toolbar({\n showDragHint,\n showHotspotModeControl,\n showZoomControls,\n showResetControl,\n labels,\n isHotspotMode,\n zoom,\n minZoom,\n maxZoom,\n isResetDisabled,\n onHotspotModeChange,\n onZoomIn,\n onZoomOut,\n onResetView,\n}: Viewer360ToolbarProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <CardFooter className={cn(viewer360ClassNames.toolbar, 'gap-2 border-t px-4 py-3 pt-3')}>\n {showDragHint && (\n <Label className={cn(viewer360ClassNames.dragHint, 'font-normal text-muted-foreground')}>{labels.dragHint}</Label>\n )}\n\n <div className={viewer360ClassNames.controls}>\n {showHotspotModeControl && (\n <>\n <Button variant={isHotspotMode ? 'default' : 'outline'} size=\"sm\" onClick={() => onHotspotModeChange(!isHotspotMode)}>\n <Crosshair className=\"me-1.5 size-4\" />\n {labels.addHotspot}\n </Button>\n <Separator orientation=\"vertical\" className={cn(viewer360ClassNames.divider, 'h-6')} />\n </>\n )}\n\n {showZoomControls && (\n <>\n <Button variant=\"outline\" size=\"icon-sm\" disabled={zoom <= minZoom} aria-label={labels.zoomOut} onClick={onZoomOut}>\n <Minus className=\"size-4\" />\n </Button>\n <Badge variant=\"outline\" className={cn(viewer360ClassNames.zoomDisplay, 'h-8 gap-1 px-2 py-1')}>\n <ZoomIn className=\"size-3 text-muted-foreground\" />\n {labels.zoom(Math.round(zoom * 100))}\n </Badge>\n <Button variant=\"outline\" size=\"icon-sm\" disabled={zoom >= maxZoom} aria-label={labels.zoomIn} onClick={onZoomIn}>\n <Plus className=\"size-4\" />\n </Button>\n </>\n )}\n\n {showResetControl && (\n <Button variant=\"outline\" size=\"icon-sm\" disabled={isResetDisabled} aria-label={labels.resetView} onClick={onResetView}>\n <RotateCcw className=\"size-4\" />\n </Button>\n )}\n </div>\n </CardFooter>\n );\n}\n","export const viewer360Config = {\n minZoom: 1,\n maxZoom: 3,\n zoomStep: 0.15,\n dragSensitivity: 8,\n autoRotate: false,\n autoRotateIntervalMs: 100,\n autoRotateDirection: 'forward' as const,\n};\n\nexport const defaultViewer360Config = viewer360Config;\n"],"mappings":"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","names":["useEffect","useMemo","useState","jsx","jsx","jsx","cva","Slot","jsx","cva","Slot","cva","Slot","jsx","jsx","cva","Slot","jsx","jsx","useState","jsx","jsx","useState","jsx","jsx","jsx","jsx","jsxs","jsx","jsxs","isResetDisabled","jsx","jsxs","useMemo","useState","isResetDisabled","useEffect"]}
1
+ {"version":3,"sources":["../src/feature/Viewer360.tsx","../src/constants/viewer360Labels.ts","../src/constants/viewer360ClassNames.ts","../src/components/utils/index.ts","../src/helpers/viewer360PropsHelpers.ts","../src/hooks/useViewer360.ts","../src/helpers/adjustViewerZoom.ts","../src/helpers/computeDragFrameIndex.ts","../src/helpers/computeViewerImageLayout.ts","../src/helpers/computeViewerPanOffset.ts","../src/helpers/viewerHelpers.ts","../src/components/ui/Card/index.tsx","../src/components/ui/Badge/index.tsx","../src/feature/Viewer360AddModeBanner.tsx","../src/feature/Viewer360FrameIndicator.tsx","../src/helpers/markerHelpers.ts","../src/components/ui/Item/index.tsx","../src/components/ui/Separator/index.tsx","../src/feature/Viewer360MarkerPin.tsx","../src/constants/viewer360MarkerLabels.ts","../src/components/ui/Button/index.tsx","../src/feature/Viewer360HotspotOverlay.tsx","../src/components/ui/Label/index.tsx","../src/components/ui/Spinner/index.tsx","../src/feature/Viewer360LoadingOverlay.tsx","../src/feature/Viewer360Toolbar.tsx","../src/constants/viewer360Config.ts"],"sourcesContent":["import type { JSX } from 'react';\nimport { useEffect, useMemo, useState } from 'react';\n\nimport { buildViewer360ThemeStyle, mergeViewer360ClassNames, mergeViewer360Labels } from '../helpers/viewer360PropsHelpers';\nimport { useViewer360 } from '../hooks/useViewer360';\nimport type { Viewer360OverlayRenderProps, Viewer360Props, Viewer360ToolbarRenderProps } from '../types';\nimport { Card } from '@/components/ui/Card';\nimport { cn } from '@/components/utils';\n\nimport { Viewer360AddModeBanner } from './Viewer360AddModeBanner';\nimport { Viewer360FrameIndicator } from './Viewer360FrameIndicator';\nimport { Viewer360HotspotOverlay } from './Viewer360HotspotOverlay';\nimport { Viewer360LoadingOverlay } from './Viewer360LoadingOverlay';\nimport { Viewer360Toolbar } from './Viewer360Toolbar';\n\nexport function Viewer360<TData = unknown>({\n frames,\n currentFrameIndex: controlledFrameIndex,\n defaultFrameIndex = 0,\n onFrameChange,\n config,\n className,\n classNames,\n style,\n theme,\n labels,\n aspectRatio = '16 / 10',\n showZoomControls = true,\n showResetControl = true,\n showFrameIndicator = true,\n showDragHint = true,\n showHotspotModeControl = false,\n hotspotPin,\n hotspots = [],\n renderHotspot,\n renderLoading,\n renderFrameIndicator,\n renderHotspotModeBanner,\n renderToolbar,\n onHotspotClick,\n hotspotMode: controlledHotspotMode,\n defaultHotspotMode = false,\n onHotspotModeChange,\n onHotspotAdd,\n children,\n}: Viewer360Props<TData>): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: States & Constants\n // ----------------------------------------------------------------------------------------------------\n const mergedLabels = useMemo(() => mergeViewer360Labels(labels), [labels]);\n const mergedClassNames = useMemo(() => mergeViewer360ClassNames(classNames), [classNames]);\n const themeStyle = useMemo(() => buildViewer360ThemeStyle(theme), [theme]);\n\n const [internalFrameIndex, setInternalFrameIndex] = useState(defaultFrameIndex);\n const [internalHotspotMode, setInternalHotspotMode] = useState(defaultHotspotMode);\n\n const currentFrameIndex = controlledFrameIndex ?? internalFrameIndex;\n const hotspotMode = controlledHotspotMode ?? internalHotspotMode;\n\n // ----------------------------------------------------------------------------------------------------\n // MARK: Functions\n // ----------------------------------------------------------------------------------------------------\n function handleFrameChange(index: number): void {\n if (controlledFrameIndex === undefined) {\n setInternalFrameIndex(index);\n }\n\n onFrameChange?.(index);\n }\n\n function handleHotspotModeChange(active: boolean): void {\n if (controlledHotspotMode === undefined) {\n setInternalHotspotMode(active);\n }\n\n onHotspotModeChange?.(active);\n }\n\n const {\n canvasRef,\n containerRef,\n currentFrame,\n currentFrameHotspots,\n imagesLoaded,\n isHotspotMode,\n isResetDisabled,\n maxZoom,\n minZoom,\n viewerCursorClass,\n zoom,\n getHotspotScreenPosition,\n handleCanvasClick,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleWheel,\n handleResetView,\n handleZoomIn,\n handleZoomOut,\n } = useViewer360<TData>({\n frames,\n hotspots,\n currentFrameIndex,\n onFrameChange: handleFrameChange,\n config,\n hotspotMode,\n onHotspotAdd,\n });\n\n useEffect(() => {\n if (controlledHotspotMode === undefined) return;\n if (controlledHotspotMode !== internalHotspotMode) {\n setInternalHotspotMode(controlledHotspotMode);\n }\n }, [controlledHotspotMode, internalHotspotMode]);\n\n const frameLabel = currentFrame?.label ?? frames[currentFrameIndex]?.label;\n const overlayProps: Viewer360OverlayRenderProps = {\n currentFrameIndex,\n frameCount: frames.length,\n frameLabel,\n isHotspotMode,\n labels: mergedLabels,\n };\n const toolbarProps: Viewer360ToolbarRenderProps = {\n zoom,\n minZoom,\n maxZoom,\n isResetDisabled,\n isHotspotMode,\n showHotspotModeControl,\n showZoomControls,\n showResetControl,\n showDragHint,\n labels: mergedLabels,\n onZoomIn: handleZoomIn,\n onZoomOut: handleZoomOut,\n onResetView: handleResetView,\n onHotspotModeChange: handleHotspotModeChange,\n };\n const showDefaultToolbar = showZoomControls || showResetControl || showHotspotModeControl || showDragHint;\n\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Card className={cn(mergedClassNames.root, 'gap-0 py-0 shadow-none ring-0', className)} style={{ ...themeStyle, ...style }}>\n <div\n ref={containerRef}\n className={cn(mergedClassNames.viewport, viewerCursorClass)}\n style={{ aspectRatio }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerLeave={handlePointerUp}\n onWheel={handleWheel}\n onClick={handleCanvasClick}\n >\n <canvas ref={canvasRef} className={mergedClassNames.canvas} />\n\n <div className={mergedClassNames.overlay}>\n {currentFrameHotspots.map((hotspot) => {\n const position = getHotspotScreenPosition(hotspot);\n\n return (\n <Viewer360HotspotOverlay\n key={hotspot.id}\n hotspot={hotspot}\n leftPercent={position.leftPercent}\n topPercent={position.topPercent}\n hotspotPin={hotspotPin}\n renderHotspot={renderHotspot}\n onHotspotClick={onHotspotClick}\n />\n );\n })}\n {children}\n </div>\n\n {!imagesLoaded &&\n (renderLoading ? (\n renderLoading()\n ) : (\n <Viewer360LoadingOverlay\n className={mergedClassNames.loading}\n textClassName={mergedClassNames.loadingText}\n label={mergedLabels.loading}\n />\n ))}\n\n {showFrameIndicator &&\n frames.length > 0 &&\n (renderFrameIndicator ? (\n renderFrameIndicator(overlayProps)\n ) : (\n <Viewer360FrameIndicator\n className={mergedClassNames.frameIndicator}\n label={mergedLabels.frameIndicator({\n current: currentFrameIndex + 1,\n total: frames.length,\n label: frameLabel,\n })}\n />\n ))}\n\n {isHotspotMode &&\n onHotspotAdd &&\n (renderHotspotModeBanner ? (\n renderHotspotModeBanner({ labels: mergedLabels })\n ) : (\n <Viewer360AddModeBanner className={mergedClassNames.hotspotModeBanner} label={mergedLabels.hotspotModeActive} />\n ))}\n </div>\n\n {renderToolbar ? renderToolbar(toolbarProps) : showDefaultToolbar ? <Viewer360Toolbar {...toolbarProps} /> : null}\n </Card>\n );\n}\n","import type { Viewer360Labels } from '../types/Viewer360Props';\n\nexport const defaultViewer360Labels: Required<Viewer360Labels> = {\n loading: 'Loading images…',\n dragHint: 'Drag to rotate • Scroll to zoom',\n frameIndicator: ({ current, total, label }) => (label ? `${label} · ${current} / ${total}` : `${current} / ${total}`),\n zoom: (percent) => `${percent}%`,\n hotspotModeActive: 'Click on the image to place a hotspot',\n addHotspot: 'Add hotspot',\n zoomIn: 'Zoom in',\n zoomOut: 'Zoom out',\n resetView: 'Reset view',\n deleteMarker: 'Remove marker',\n};\n","import type { Viewer360ClassNames } from '../types/Viewer360Props';\nimport type { Viewer360MarkerPinClassNames } from '../types/Viewer360Marker';\n\nexport const viewer360ClassNames: Required<Viewer360ClassNames> = {\n root: 'overflow-hidden rounded-lg border bg-card text-card-foreground',\n viewport: 'relative aspect-[16/10] w-full touch-none select-none bg-muted',\n canvas: 'absolute inset-0 size-full',\n overlay: 'pointer-events-none absolute inset-0 overflow-hidden',\n loading: 'absolute inset-0 flex items-center justify-center bg-muted/80',\n loadingText: 'text-sm text-muted-foreground',\n frameIndicator:\n 'pointer-events-none absolute bottom-4 start-4 z-20 rounded-full border bg-background px-4 py-1.5 text-xs font-medium shadow-sm',\n hotspotModeBanner:\n 'pointer-events-none absolute top-4 start-1/2 z-20 -translate-x-1/2 rounded-full border border-amber-200 bg-amber-50 px-4 py-1.5 text-xs font-medium text-amber-800 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-400',\n toolbar: 'flex flex-wrap items-center justify-between gap-2 border-t px-4 py-3',\n dragHint: 'hidden text-xs text-muted-foreground sm:block',\n controls: 'ms-auto flex items-center gap-1.5',\n controlButton: '',\n controlButtonActive: '',\n controlButtonDisabled: '',\n zoomDisplay: 'flex min-w-[3rem] items-center justify-center gap-1 rounded-md border bg-background px-2 py-1 text-xs font-medium',\n divider: 'mx-1 h-6 w-px bg-border',\n};\n\nexport const viewer360MarkerPinClassNames: Required<Viewer360MarkerPinClassNames> = {\n root: 'pointer-events-auto absolute z-30 -translate-x-1/2 -translate-y-1/2',\n ping: 'absolute inline-flex size-6 -translate-x-1/4 -translate-y-1/4 animate-ping rounded-full bg-destructive opacity-60',\n dot: 'relative flex size-4 items-center justify-center rounded-full border-2 border-background bg-destructive shadow-md transition-transform duration-200 hover:scale-125 focus:outline-none',\n tooltip:\n 'absolute bottom-6 left-1/2 z-40 w-64 -translate-x-1/2 rounded-lg border bg-popover p-3 text-popover-foreground shadow-md',\n tooltipHeader: 'flex items-start justify-between gap-2',\n tooltipBody: 'flex min-w-0 flex-col gap-1',\n tooltipTitle: 'text-sm font-medium',\n tooltipDescription: 'mt-2 line-clamp-3 text-xs text-muted-foreground',\n deleteButton: '',\n};\n\n/** @deprecated Use `viewer360ClassNames` */\nexport const defaultViewer360ClassNames = viewer360ClassNames;\n\n/** @deprecated Use `viewer360MarkerPinClassNames` */\nexport const defaultViewer360MarkerPinClassNames = viewer360MarkerPinClassNames;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import type { CSSProperties } from 'react';\n\nimport { defaultViewer360Labels } from '../constants/viewer360Labels';\nimport { viewer360ClassNames } from '../constants/viewer360ClassNames';\nimport type { Viewer360ClassNames, Viewer360Labels, Viewer360Theme } from '../types';\nimport { cn } from '@/components/utils';\n\nexport function mergeViewer360Labels(labels?: Viewer360Labels): Required<Viewer360Labels> {\n return {\n loading: labels?.loading ?? defaultViewer360Labels.loading,\n dragHint: labels?.dragHint ?? defaultViewer360Labels.dragHint,\n frameIndicator: labels?.frameIndicator ?? defaultViewer360Labels.frameIndicator,\n zoom: labels?.zoom ?? defaultViewer360Labels.zoom,\n hotspotModeActive: labels?.hotspotModeActive ?? defaultViewer360Labels.hotspotModeActive,\n addHotspot: labels?.addHotspot ?? defaultViewer360Labels.addHotspot,\n zoomIn: labels?.zoomIn ?? defaultViewer360Labels.zoomIn,\n zoomOut: labels?.zoomOut ?? defaultViewer360Labels.zoomOut,\n resetView: labels?.resetView ?? defaultViewer360Labels.resetView,\n deleteMarker: labels?.deleteMarker ?? defaultViewer360Labels.deleteMarker,\n };\n}\n\nexport function mergeViewer360ClassNames(classNames?: Viewer360ClassNames): Required<Viewer360ClassNames> {\n return {\n root: cn(viewer360ClassNames.root, classNames?.root),\n viewport: cn(viewer360ClassNames.viewport, classNames?.viewport),\n canvas: cn(viewer360ClassNames.canvas, classNames?.canvas),\n overlay: cn(viewer360ClassNames.overlay, classNames?.overlay),\n loading: cn(viewer360ClassNames.loading, classNames?.loading),\n loadingText: cn(viewer360ClassNames.loadingText, classNames?.loadingText),\n frameIndicator: cn(viewer360ClassNames.frameIndicator, classNames?.frameIndicator),\n hotspotModeBanner: cn(viewer360ClassNames.hotspotModeBanner, classNames?.hotspotModeBanner),\n toolbar: cn(viewer360ClassNames.toolbar, classNames?.toolbar),\n dragHint: cn(viewer360ClassNames.dragHint, classNames?.dragHint),\n controls: cn(viewer360ClassNames.controls, classNames?.controls),\n controlButton: cn(viewer360ClassNames.controlButton, classNames?.controlButton),\n controlButtonActive: cn(viewer360ClassNames.controlButtonActive, classNames?.controlButtonActive),\n controlButtonDisabled: cn(viewer360ClassNames.controlButtonDisabled, classNames?.controlButtonDisabled),\n zoomDisplay: cn(viewer360ClassNames.zoomDisplay, classNames?.zoomDisplay),\n divider: cn(viewer360ClassNames.divider, classNames?.divider),\n };\n}\n\nexport function buildViewer360ThemeStyle(theme?: Viewer360Theme): CSSProperties {\n return theme ? (theme as CSSProperties) : {};\n}\n","import type { PointerEvent as ReactPointerEvent, RefObject, WheelEvent as ReactWheelEvent } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\nimport {\n applyWheelZoom,\n getViewerCursorClass,\n isResetDisabled,\n resolveViewer360Config,\n stepZoomIn,\n stepZoomOut,\n} from '../helpers/adjustViewerZoom';\nimport { computeDragFrameIndex } from '../helpers/computeDragFrameIndex';\nimport {\n computeHotspotPositionFromClick,\n computeHotspotScreenPosition,\n computeViewerImageLayout,\n type ViewerImageLayout,\n} from '../helpers/computeViewerImageLayout';\nimport { computeViewerPanOffset } from '../helpers/computeViewerPanOffset';\nimport {\n drawFrameOnCanvas,\n filterHotspotsByFrame,\n getFramesSignature,\n hasLoadedViewerFrame,\n preloadViewerFrames,\n} from '../helpers/viewerHelpers';\nimport type {\n Viewer360Config,\n Viewer360Frame,\n Viewer360Hotspot,\n Viewer360HotspotPosition,\n Viewer360PanOffset,\n} from '../types';\n\ntype UseViewer360Params<TData> = {\n frames: Viewer360Frame[];\n currentFrameIndex: number;\n onFrameChange: (index: number) => void;\n hotspots?: Viewer360Hotspot<TData>[];\n config?: Viewer360Config;\n hotspotMode?: boolean;\n onHotspotAdd?: (position: Viewer360HotspotPosition) => void;\n};\n\ntype UseViewer360Return<TData> = {\n canvasRef: RefObject<HTMLCanvasElement | null>;\n containerRef: RefObject<HTMLDivElement | null>;\n currentFrame: Viewer360Frame | undefined;\n currentFrameHotspots: Viewer360Hotspot<TData>[];\n imagesLoaded: boolean;\n isHotspotMode: boolean;\n isResetDisabled: boolean;\n maxZoom: number;\n minZoom: number;\n viewerCursorClass: string;\n zoom: number;\n getCurrentImageLayout: () => ViewerImageLayout | null;\n getHotspotScreenPosition: (hotspot: Viewer360Hotspot<TData>) => { leftPercent: number; topPercent: number };\n handleCanvasClick: (event: React.MouseEvent<HTMLDivElement>) => void;\n handlePointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;\n handlePointerMove: (event: ReactPointerEvent<HTMLDivElement>) => void;\n handlePointerUp: (event: ReactPointerEvent<HTMLDivElement>) => void;\n handleResetView: () => void;\n handleWheel: (event: ReactWheelEvent<HTMLDivElement>) => void;\n handleZoomIn: () => void;\n handleZoomOut: () => void;\n};\n\nexport function useViewer360<TData = unknown>({\n frames,\n hotspots = [],\n currentFrameIndex,\n onFrameChange,\n config,\n hotspotMode: hotspotModeProp = false,\n onHotspotAdd,\n}: UseViewer360Params<TData>): UseViewer360Return<TData> {\n const resolvedConfig = useMemo(() => resolveViewer360Config(config), [config]);\n const { minZoom, maxZoom } = resolvedConfig;\n\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const imagesRef = useRef<HTMLImageElement[]>([]);\n const dragStartRef = useRef<{ pointerX: number; frameIndex: number } | null>(null);\n const panStartRef = useRef<{ pointerX: number; pointerY: number; panX: number; panY: number } | null>(null);\n\n const framesSignature = getFramesSignature(frames);\n const [loadedSignature, setLoadedSignature] = useState<string | null>(null);\n const [zoom, setZoom] = useState<number>(minZoom);\n const [pan, setPan] = useState<Viewer360PanOffset>({ panX: 0, panY: 0 });\n const [isDragging, setIsDragging] = useState(false);\n\n const isHotspotMode = hotspotModeProp;\n const imagesLoaded = loadedSignature === framesSignature;\n const currentFrameHotspots = filterHotspotsByFrame(hotspots, currentFrameIndex) as Viewer360Hotspot<TData>[];\n const currentFrame = frames[currentFrameIndex];\n const viewerCursorClass = getViewerCursorClass(isHotspotMode, zoom, isDragging, minZoom);\n const resetDisabled = isResetDisabled(zoom, pan, resolvedConfig);\n\n useEffect(() => {\n let cancelled = false;\n imagesRef.current = [];\n\n async function loadFrames(): Promise<void> {\n const loadedImages = await preloadViewerFrames(frames);\n\n if (!cancelled && hasLoadedViewerFrame(loadedImages)) {\n imagesRef.current = loadedImages;\n setLoadedSignature(framesSignature);\n }\n }\n\n void loadFrames();\n\n return (): void => {\n cancelled = true;\n };\n }, [frames, framesSignature]);\n\n useEffect(() => {\n if (!imagesLoaded) return;\n\n function renderFrame(): void {\n const canvas = canvasRef.current;\n const container = containerRef.current;\n const img = imagesRef.current[currentFrameIndex];\n\n if (!canvas || !container) return;\n\n drawFrameOnCanvas({ canvas, container, image: img, zoom, pan });\n }\n\n renderFrame();\n window.addEventListener('resize', renderFrame);\n\n return (): void => window.removeEventListener('resize', renderFrame);\n }, [imagesLoaded, currentFrameIndex, zoom, pan]);\n\n useEffect(() => {\n if (!resolvedConfig.autoRotate || frames.length <= 1 || isDragging || isHotspotMode || zoom > minZoom) {\n return;\n }\n\n const direction = resolvedConfig.autoRotateDirection === 'backward' ? -1 : 1;\n const interval = window.setInterval(() => {\n onFrameChange((currentFrameIndex + direction + frames.length) % frames.length);\n }, resolvedConfig.autoRotateIntervalMs);\n\n return (): void => window.clearInterval(interval);\n }, [\n resolvedConfig.autoRotate,\n resolvedConfig.autoRotateDirection,\n resolvedConfig.autoRotateIntervalMs,\n frames.length,\n currentFrameIndex,\n isDragging,\n isHotspotMode,\n zoom,\n minZoom,\n onFrameChange,\n ]);\n\n function getCurrentImageLayout(): ViewerImageLayout | null {\n const container = containerRef.current;\n const image = imagesRef.current[currentFrameIndex];\n\n if (!container || !image || !image.complete || !image.naturalWidth) return null;\n\n const rect = container.getBoundingClientRect();\n\n return computeViewerImageLayout({\n containerWidth: rect.width,\n containerHeight: rect.height,\n imageWidth: image.naturalWidth,\n imageHeight: image.naturalHeight,\n pan,\n });\n }\n\n function getHotspotScreenPosition(hotspot: Viewer360Hotspot<TData>): { leftPercent: number; topPercent: number } {\n const layout = getCurrentImageLayout();\n\n if (!layout) {\n return { leftPercent: hotspot.positionX, topPercent: hotspot.positionY };\n }\n\n return computeHotspotScreenPosition(hotspot.positionX, hotspot.positionY, layout, zoom);\n }\n\n function handlePointerDown(event: ReactPointerEvent<HTMLDivElement>): void {\n if (isHotspotMode) return;\n\n event.currentTarget.setPointerCapture(event.pointerId);\n\n if (zoom > minZoom) {\n panStartRef.current = { pointerX: event.clientX, pointerY: event.clientY, panX: pan.panX, panY: pan.panY };\n } else {\n dragStartRef.current = { pointerX: event.clientX, frameIndex: currentFrameIndex };\n }\n\n setIsDragging(true);\n }\n\n function handlePointerMove(event: ReactPointerEvent<HTMLDivElement>): void {\n if (!isDragging) return;\n\n if (panStartRef.current && zoom > minZoom) {\n setPan(computeViewerPanOffset(panStartRef.current, event.clientX, event.clientY));\n return;\n }\n\n if (dragStartRef.current && zoom <= minZoom) {\n const nextFrameIndex = computeDragFrameIndex(\n dragStartRef.current,\n event.clientX,\n frames.length,\n resolvedConfig.dragSensitivity\n );\n\n if (nextFrameIndex !== null) {\n onFrameChange(nextFrameIndex);\n }\n }\n }\n\n function handlePointerUp(event: ReactPointerEvent<HTMLDivElement>): void {\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n\n dragStartRef.current = null;\n panStartRef.current = null;\n setIsDragging(false);\n }\n\n function handleWheel(event: ReactWheelEvent<HTMLDivElement>): void {\n event.preventDefault();\n const { zoom: nextZoom, pan: nextPan } = applyWheelZoom(zoom, event.deltaY, pan, resolvedConfig);\n setZoom(nextZoom);\n setPan(nextPan);\n }\n\n function handleCanvasClick(event: React.MouseEvent<HTMLDivElement>): void {\n if (!isHotspotMode || isDragging || !onHotspotAdd) return;\n\n const frame = frames[currentFrameIndex];\n if (!frame) return;\n\n const layout = getCurrentImageLayout();\n if (!layout) return;\n\n const { positionX, positionY } = computeHotspotPositionFromClick(\n event.clientX,\n event.clientY,\n event.currentTarget.getBoundingClientRect(),\n layout,\n zoom\n );\n\n onHotspotAdd({\n frameIndex: currentFrameIndex,\n frameId: frame.id,\n positionX,\n positionY,\n });\n }\n\n function handleResetView(): void {\n setZoom(minZoom);\n setPan({ panX: 0, panY: 0 });\n }\n\n function handleZoomIn(): void {\n setZoom(stepZoomIn(zoom, resolvedConfig));\n }\n\n function handleZoomOut(): void {\n const { zoom: nextZoom, pan: nextPan } = stepZoomOut(zoom, pan, resolvedConfig);\n setZoom(nextZoom);\n setPan(nextPan);\n }\n\n return {\n canvasRef,\n containerRef,\n currentFrame,\n currentFrameHotspots,\n imagesLoaded,\n isHotspotMode,\n isResetDisabled: resetDisabled,\n maxZoom,\n minZoom,\n viewerCursorClass,\n zoom,\n getCurrentImageLayout,\n getHotspotScreenPosition,\n handleCanvasClick,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleResetView,\n handleWheel,\n handleZoomIn,\n handleZoomOut,\n };\n}\n","import type { Viewer360Config } from '../types';\n\nimport type { PanOffset } from './computeViewerImageLayout';\n\ntype ResolvedViewer360Config = Required<Viewer360Config>;\n\nexport function resolveViewer360Config(config?: Viewer360Config): ResolvedViewer360Config {\n return {\n minZoom: config?.minZoom ?? 1,\n maxZoom: config?.maxZoom ?? 3,\n zoomStep: config?.zoomStep ?? 0.15,\n dragSensitivity: config?.dragSensitivity ?? 8,\n autoRotate: config?.autoRotate ?? false,\n autoRotateIntervalMs: config?.autoRotateIntervalMs ?? 100,\n autoRotateDirection: config?.autoRotateDirection ?? 'forward',\n };\n}\n\nexport function clampZoom(zoom: number, config: ResolvedViewer360Config): number {\n return Math.min(config.maxZoom, Math.max(config.minZoom, zoom));\n}\n\nexport function applyWheelZoom(\n currentZoom: number,\n deltaY: number,\n currentPan: PanOffset,\n config: ResolvedViewer360Config\n): { zoom: number; pan: PanOffset } {\n const delta = deltaY > 0 ? -config.zoomStep : config.zoomStep;\n const zoom = clampZoom(currentZoom + delta, config);\n\n return {\n zoom,\n pan: zoom === config.minZoom ? { panX: 0, panY: 0 } : currentPan,\n };\n}\n\nexport function stepZoomIn(currentZoom: number, config: ResolvedViewer360Config): number {\n return clampZoom(currentZoom + config.zoomStep, config);\n}\n\nexport function stepZoomOut(\n currentZoom: number,\n currentPan: PanOffset,\n config: ResolvedViewer360Config\n): { zoom: number; pan: PanOffset } {\n const zoom = clampZoom(currentZoom - config.zoomStep, config);\n\n return {\n zoom,\n pan: zoom === config.minZoom ? { panX: 0, panY: 0 } : currentPan,\n };\n}\n\nexport function isResetDisabled(zoom: number, pan: PanOffset, config: ResolvedViewer360Config): boolean {\n return zoom === config.minZoom && pan.panX === 0 && pan.panY === 0;\n}\n\nexport function getViewerCursorClass(isHotspotMode: boolean, zoom: number, isDragging: boolean, minZoom: number): string {\n if (isHotspotMode) return 'cursor-crosshair';\n if (isDragging) return 'cursor-grabbing';\n if (zoom > minZoom) return 'cursor-grab';\n return 'cursor-ew-resize';\n}\n","export function clampFrameIndex(index: number, frameCount: number): number {\n if (frameCount === 0) return 0;\n return ((index % frameCount) + frameCount) % frameCount;\n}\n\ntype DragStart = {\n pointerX: number;\n frameIndex: number;\n};\n\nexport function computeDragFrameIndex(\n dragStart: DragStart,\n clientX: number,\n frameCount: number,\n dragSensitivity: number\n): number | null {\n const deltaX = clientX - dragStart.pointerX;\n const frameDelta = Math.round(-deltaX / dragSensitivity);\n\n if (frameDelta === 0) return null;\n\n return clampFrameIndex(dragStart.frameIndex + frameDelta, frameCount);\n}\n","export type ViewerImageLayout = {\n width: number;\n height: number;\n centerX: number;\n centerY: number;\n drawWidth: number;\n drawHeight: number;\n offsetX: number;\n offsetY: number;\n};\n\nexport type PanOffset = {\n panX: number;\n panY: number;\n};\n\ntype ComputeViewerImageLayoutParams = {\n containerWidth: number;\n containerHeight: number;\n imageWidth: number;\n imageHeight: number;\n pan?: PanOffset;\n};\n\nexport function computeViewerImageLayout({\n containerWidth,\n containerHeight,\n imageWidth,\n imageHeight,\n pan = { panX: 0, panY: 0 },\n}: ComputeViewerImageLayoutParams): ViewerImageLayout {\n const imgAspect = imageWidth / imageHeight;\n const containerAspect = containerWidth / containerHeight;\n\n let drawWidth: number;\n let drawHeight: number;\n\n if (imgAspect > containerAspect) {\n drawWidth = containerWidth;\n drawHeight = containerWidth / imgAspect;\n } else {\n drawHeight = containerHeight;\n drawWidth = containerHeight * imgAspect;\n }\n\n const offsetX = (containerWidth - drawWidth) / 2 + pan.panX;\n const offsetY = (containerHeight - drawHeight) / 2 + pan.panY;\n\n return {\n width: containerWidth,\n height: containerHeight,\n centerX: containerWidth / 2,\n centerY: containerHeight / 2,\n drawWidth,\n drawHeight,\n offsetX,\n offsetY,\n };\n}\n\nexport function computeHotspotScreenPosition(\n hotspotX: number,\n hotspotY: number,\n layout: ViewerImageLayout,\n zoom: number\n): { leftPercent: number; topPercent: number } {\n const baseOffsetX = (layout.width - layout.drawWidth) / 2;\n const baseOffsetY = (layout.height - layout.drawHeight) / 2;\n\n const containerX = (hotspotX / 100) * layout.width;\n const containerY = (hotspotY / 100) * layout.height;\n\n const imageLocalX = (containerX - baseOffsetX) / layout.drawWidth;\n const imageLocalY = (containerY - baseOffsetY) / layout.drawHeight;\n\n const imagePointX = layout.offsetX + imageLocalX * layout.drawWidth;\n const imagePointY = layout.offsetY + imageLocalY * layout.drawHeight;\n\n const screenX = layout.centerX + zoom * (imagePointX - layout.centerX);\n const screenY = layout.centerY + zoom * (imagePointY - layout.centerY);\n\n return {\n leftPercent: (screenX / layout.width) * 100,\n topPercent: (screenY / layout.height) * 100,\n };\n}\n\nexport function computeHotspotPositionFromClick(\n clientX: number,\n clientY: number,\n containerRect: DOMRect,\n layout: ViewerImageLayout,\n zoom: number\n): { positionX: number; positionY: number } {\n const clickX = clientX - containerRect.left;\n const clickY = clientY - containerRect.top;\n\n const unzoomedX = layout.centerX + (clickX - layout.centerX) / zoom;\n const unzoomedY = layout.centerY + (clickY - layout.centerY) / zoom;\n\n const baseOffsetX = (layout.width - layout.drawWidth) / 2;\n const baseOffsetY = (layout.height - layout.drawHeight) / 2;\n\n const imageLocalX = Math.min(1, Math.max(0, (unzoomedX - layout.offsetX) / layout.drawWidth));\n const imageLocalY = Math.min(1, Math.max(0, (unzoomedY - layout.offsetY) / layout.drawHeight));\n\n const storedX = baseOffsetX + imageLocalX * layout.drawWidth;\n const storedY = baseOffsetY + imageLocalY * layout.drawHeight;\n\n return {\n positionX: Math.min(100, Math.max(0, (storedX / layout.width) * 100)),\n positionY: Math.min(100, Math.max(0, (storedY / layout.height) * 100)),\n };\n}\n","import type { PanOffset } from './computeViewerImageLayout';\n\ntype PanStart = {\n pointerX: number;\n pointerY: number;\n panX: number;\n panY: number;\n};\n\nexport function computeViewerPanOffset(panStart: PanStart, clientX: number, clientY: number): PanOffset {\n const deltaX = clientX - panStart.pointerX;\n const deltaY = clientY - panStart.pointerY;\n\n return {\n panX: panStart.panX + deltaX,\n panY: panStart.panY + deltaY,\n };\n}\n","import type { Viewer360Frame } from '../types';\n\nimport { computeViewerImageLayout, type PanOffset } from './computeViewerImageLayout';\n\ntype DrawFrameOnCanvasParams = {\n canvas: HTMLCanvasElement;\n container: HTMLDivElement;\n image: HTMLImageElement;\n zoom: number;\n pan: PanOffset;\n};\n\nexport function drawFrameOnCanvas({ canvas, container, image, zoom, pan }: DrawFrameOnCanvasParams): void {\n if (!image.complete || !image.naturalWidth) return;\n\n const rect = container.getBoundingClientRect();\n const dpr = window.devicePixelRatio || 1;\n\n canvas.width = rect.width * dpr;\n canvas.height = rect.height * dpr;\n canvas.style.width = `${rect.width}px`;\n canvas.style.height = `${rect.height}px`;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, rect.width, rect.height);\n\n const layout = computeViewerImageLayout({\n containerWidth: rect.width,\n containerHeight: rect.height,\n imageWidth: image.naturalWidth,\n imageHeight: image.naturalHeight,\n pan,\n });\n\n ctx.save();\n ctx.translate(layout.centerX, layout.centerY);\n ctx.scale(zoom, zoom);\n ctx.translate(-layout.centerX, -layout.centerY);\n ctx.drawImage(image, layout.offsetX, layout.offsetY, layout.drawWidth, layout.drawHeight);\n ctx.restore();\n}\n\nexport function getFramesSignature(frames: Viewer360Frame[]): string {\n return frames.map((frame) => frame.id).join('-');\n}\n\nexport function preloadFrameImage(frame: Viewer360Frame): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = (): void => resolve(img);\n img.onerror = (): void => reject(new Error(`Failed to load frame: ${frame.src}`));\n img.src = frame.src;\n });\n}\n\nexport async function preloadViewerFrames(frames: Viewer360Frame[]): Promise<HTMLImageElement[]> {\n const results = await Promise.allSettled(frames.map(preloadFrameImage));\n\n return results.map((result) => (result.status === 'fulfilled' ? result.value : new Image()));\n}\n\nexport function hasLoadedViewerFrame(images: HTMLImageElement[]): boolean {\n return images.some((image) => image.complete && image.naturalWidth > 0);\n}\n\nexport function filterHotspotsByFrame<TData>(\n hotspots: Array<{ frameIndex: number; data?: TData }>,\n frameIndex: number\n): Array<{ frameIndex: number; data?: TData }> {\n return hotspots.filter((hotspot) => hotspot.frameIndex === frameIndex);\n}\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cn } from '@/components/utils';\n\nfunction Card({ className, size = 'default', ...props }: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }): JSX.Element {\n return (\n <div\n data-slot=\"card\"\n data-size={size}\n className={cn(\n 'ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-6 text-sm shadow-xs ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col',\n className\n )}\n {...props}\n />\n );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-header\"\n className={cn(\n 'gap-0.5 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]',\n className\n )}\n {...props}\n />\n );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-title\"\n className={cn('text-lg leading-normal font-semibold group-data-[size=sm]/card:text-sm', className)}\n {...props}\n />\n );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"card-description\" className={cn('text-muted-foreground text-xs font-medium', className)} {...props} />;\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-action\"\n className={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}\n {...props}\n />\n );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"card-content\" className={cn('px-6 group-data-[size=sm]/card:px-4', className)} {...props} />;\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"card-footer\"\n className={cn(\n 'rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center',\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\n\nimport { cn } from '@/components/utils';\n\nconst badgeVariants = cva(\n 'h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n destructive:\n 'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',\n outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n link: 'text-primary underline-offset-4 hover:underline',\n info: 'bg-blue-500 text-white hover:bg-blue-600',\n warning: 'bg-amber-500 text-white hover:bg-amber-600',\n success: 'bg-green-600 text-white hover:bg-green-700',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n }\n);\n\nfunction Badge({\n className,\n variant = 'default',\n asChild = false,\n ...props\n}: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & { asChild?: boolean }): JSX.Element {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const Comp = asChild ? Slot.Root : 'span';\n\n return <Comp data-slot=\"badge\" data-variant={variant} className={cn(badgeVariants({ variant }), className)} {...props} />;\n}\n\nexport { Badge, badgeVariants };\n","import type { JSX } from 'react';\n\nimport { Badge } from '@/components/ui/Badge';\nimport { cn } from '@/components/utils';\n\ntype Viewer360AddModeBannerProps = {\n className?: string;\n label: string;\n};\n\nexport function Viewer360AddModeBanner({ className, label }: Viewer360AddModeBannerProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Badge variant=\"outline\" className={cn('pointer-events-none', className)}>\n {label}\n </Badge>\n );\n}\n","import type { JSX } from 'react';\n\nimport { Badge } from '@/components/ui/Badge';\nimport { cn } from '@/components/utils';\n\ntype Viewer360FrameIndicatorProps = {\n className?: string;\n label: string;\n};\n\nexport function Viewer360FrameIndicator({ className, label }: Viewer360FrameIndicatorProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Badge variant=\"outline\" className={cn('pointer-events-none shadow-sm', className)}>\n {label}\n </Badge>\n );\n}\n","import type { Viewer360Hotspot, Viewer360Marker } from '../types';\n\nexport function hotspotToViewer360Marker<TData>(hotspot: Viewer360Hotspot<TData>): Viewer360Marker {\n const data = hotspot.data;\n\n if (data && typeof data === 'object' && 'title' in data && typeof (data as { title?: unknown }).title === 'string') {\n const marker = data as unknown as Viewer360Marker;\n\n return {\n id: marker.id ?? hotspot.id,\n title: marker.title,\n description: marker.description,\n };\n }\n\n return {\n id: hotspot.id,\n title: hotspot.id,\n };\n}\n\nexport function toViewer360Hotspots<TData extends Viewer360Marker>(\n markers: Array<Viewer360Marker & { frameIndex: number; positionX: number; positionY: number }>,\n mapData?: (marker: Viewer360Marker & { frameIndex: number; positionX: number; positionY: number }) => TData\n): Viewer360Hotspot<TData>[] {\n return markers.map((marker) => ({\n id: marker.id,\n frameIndex: marker.frameIndex,\n positionX: marker.positionX,\n positionY: marker.positionY,\n data: mapData ? mapData(marker) : (marker as unknown as TData),\n }));\n}\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\n\nimport { Separator } from '@/components/ui/Separator';\nimport { cn } from '@/components/utils';\n\nfunction ItemGroup({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n role=\"list\"\n data-slot=\"item-group\"\n className={cn('gap-4 has-[[data-size=sm]]:gap-2.5 has-[[data-size=xs]]:gap-2 group/item-group flex w-full flex-col', className)}\n {...props}\n />\n );\n}\n\nfunction ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>): JSX.Element {\n return <Separator data-slot=\"item-separator\" orientation=\"horizontal\" className={cn('my-2', className)} {...props} />;\n}\n\nconst itemVariants = cva(\n '[a]:hover:bg-muted rounded-md border text-sm w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors',\n {\n variants: {\n variant: {\n default: 'border-transparent',\n outline: 'border-border',\n muted: 'bg-muted/50 border-transparent',\n },\n size: {\n default: 'gap-3.5 px-4 py-3.5',\n sm: 'gap-2.5 px-3 py-2.5',\n xs: 'gap-2 px-2.5 py-2 [[data-slot=dropdown-menu-content]_&]:p-0',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n }\n);\n\nfunction Item({\n className,\n variant = 'default',\n size = 'default',\n asChild = false,\n ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof itemVariants> & { asChild?: boolean }): JSX.Element {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const Comp = asChild ? Slot.Root : 'div';\n return (\n <Comp\n data-slot=\"item\"\n data-variant={variant}\n data-size={size}\n className={cn(itemVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nconst itemMediaVariants = cva(\n 'gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none',\n {\n variants: {\n variant: {\n default: 'bg-transparent',\n icon: '[&_svg]:size-4',\n image: 'size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n }\n);\n\nfunction ItemMedia({\n className,\n variant = 'default',\n ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>): JSX.Element {\n return <div data-slot=\"item-media\" data-variant={variant} className={cn(itemMediaVariants({ variant, className }))} {...props} />;\n}\n\nfunction ItemContent({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"item-content\"\n className={cn('gap-1 group-data-[size=xs]/item:gap-0 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none', className)}\n {...props}\n />\n );\n}\n\nfunction ItemTitle({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return (\n <div\n data-slot=\"item-title\"\n className={cn('gap-2 text-sm leading-snug font-medium underline-offset-4 line-clamp-1 flex w-fit items-center', className)}\n {...props}\n />\n );\n}\n\nfunction ItemDescription({ className, ...props }: React.ComponentProps<'p'>): JSX.Element {\n return (\n <p\n data-slot=\"item-description\"\n className={cn(\n 'text-muted-foreground text-left text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4',\n className\n )}\n {...props}\n />\n );\n}\n\nfunction ItemActions({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"item-actions\" className={cn('gap-2 flex items-center', className)} {...props} />;\n}\n\nfunction ItemHeader({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"item-header\" className={cn('gap-2 flex basis-full items-center justify-between', className)} {...props} />;\n}\n\nfunction ItemFooter({ className, ...props }: React.ComponentProps<'div'>): JSX.Element {\n return <div data-slot=\"item-footer\" className={cn('gap-2 flex basis-full items-center justify-between', className)} {...props} />;\n}\n\nexport { Item, ItemMedia, ItemContent, ItemActions, ItemGroup, ItemSeparator, ItemTitle, ItemDescription, ItemHeader, ItemFooter };\n","'use client';\n\nimport * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { Separator as SeparatorPrimitive } from 'radix-ui';\n\nimport { cn } from '@/components/utils';\n\nfunction Separator({\n className,\n orientation = 'horizontal',\n decorative = true,\n ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>): JSX.Element {\n return (\n <SeparatorPrimitive.Root\n data-slot=\"separator\"\n decorative={decorative}\n orientation={orientation}\n className={cn(\n 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch',\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Separator };\n","import type { JSX, MouseEvent } from 'react';\n\nimport { Trash2 } from 'lucide-react';\n\nimport { defaultViewer360MarkerPinLabels } from '../constants/viewer360MarkerLabels';\nimport { viewer360MarkerPinClassNames } from '../constants/viewer360ClassNames';\nimport type { Viewer360MarkerPinProps } from '../types';\nimport { Badge } from '@/components/ui/Badge';\nimport { Button } from '@/components/ui/Button';\nimport {\n Item,\n ItemActions,\n ItemContent,\n ItemDescription,\n ItemTitle,\n} from '@/components/ui/Item';\nimport { cn } from '@/components/utils';\n\nexport function Viewer360MarkerPin<TData = unknown>({\n marker,\n hotspot,\n leftPercent,\n topPercent,\n onDelete,\n isDeletePending = false,\n onClick,\n renderTag,\n classNames,\n labels,\n}: Viewer360MarkerPinProps<TData>): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: States & Constants\n // ----------------------------------------------------------------------------------------------------\n const deleteLabel = labels?.delete ?? defaultViewer360MarkerPinLabels.delete;\n const showTooltip = Boolean(marker.title || marker.description || onDelete || renderTag);\n\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Item\n size=\"xs\"\n variant=\"default\"\n className={cn(\n viewer360MarkerPinClassNames.root,\n classNames?.root,\n 'group/marker w-auto border-transparent p-0'\n )}\n style={{ left: `${leftPercent}%`, top: `${topPercent}%` }}\n >\n <Badge\n variant=\"destructive\"\n className={cn(viewer360MarkerPinClassNames.ping, classNames?.ping, 'absolute size-6 border-0 bg-destructive opacity-60')}\n aria-hidden=\"true\"\n />\n\n <Button\n type=\"button\"\n variant=\"destructive\"\n size=\"icon-xs\"\n className={cn(\n viewer360MarkerPinClassNames.dot,\n classNames?.dot,\n 'size-4 min-h-4 min-w-4 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:scale-125 hover:bg-destructive'\n )}\n aria-label={marker.title}\n onClick={onClick}\n />\n\n {showTooltip && (\n <div\n className={cn(\n viewer360MarkerPinClassNames.tooltip,\n classNames?.tooltip,\n 'pointer-events-none opacity-0 transition-opacity duration-150 group-hover/marker:pointer-events-auto group-hover/marker:opacity-100 group-focus-within/marker:pointer-events-auto group-focus-within/marker:opacity-100'\n )}\n >\n <Item\n size=\"sm\"\n variant=\"default\"\n className={cn(viewer360MarkerPinClassNames.tooltipHeader, classNames?.tooltipHeader, 'w-full border-transparent')}\n >\n <ItemContent className={cn(viewer360MarkerPinClassNames.tooltipBody, classNames?.tooltipBody)}>\n <ItemTitle className={cn(viewer360MarkerPinClassNames.tooltipTitle, classNames?.tooltipTitle)}>\n {marker.title}\n </ItemTitle>\n {renderTag?.({ marker, hotspot })}\n {marker.description && (\n <ItemDescription className={cn(viewer360MarkerPinClassNames.tooltipDescription, classNames?.tooltipDescription)}>\n {marker.description}\n </ItemDescription>\n )}\n </ItemContent>\n {onDelete && (\n <ItemActions>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className={classNames?.deleteButton}\n disabled={isDeletePending}\n aria-label={deleteLabel}\n onClick={(event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n onDelete(marker.id);\n }}\n >\n <Trash2 className=\"size-4\" />\n </Button>\n </ItemActions>\n )}\n </Item>\n </div>\n )}\n </Item>\n );\n}\n","export const defaultViewer360MarkerPinLabels = {\n delete: 'Remove marker',\n} as const;\n","import * as React from 'react';\nimport type { JSX } from 'react';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\n\nimport { cn } from '@/components/utils';\n\nconst buttonVariants = cva(\n 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n outline:\n 'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',\n destructive:\n 'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default:\n 'h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2',\n xs: 'h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5',\n sm: 'h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5',\n lg: 'h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pe-3 has-data-[icon=inline-start]:ps-3',\n icon: 'size-9',\n 'icon-xs':\n 'size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md',\n 'icon-sm': 'size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md',\n 'icon-lg': 'size-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n }\n);\n\nfunction Button({\n className,\n variant = 'default',\n size = 'default',\n asChild = false,\n ...props\n}: React.ComponentProps<'button'> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }): JSX.Element {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const Comp = asChild ? Slot.Root : 'button';\n\n return (\n <Comp\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n","import type { JSX, MouseEvent, ReactNode } from 'react';\n\nimport { hotspotToViewer360Marker } from '../helpers/markerHelpers';\nimport type { Viewer360Hotspot, Viewer360HotspotPinOptions, Viewer360HotspotRenderProps } from '../types';\nimport { Item } from '@/components/ui/Item';\n\nimport { Viewer360MarkerPin } from './Viewer360MarkerPin';\n\ntype Viewer360HotspotOverlayProps<TData = unknown> = {\n hotspot: Viewer360Hotspot<TData>;\n leftPercent: number;\n topPercent: number;\n hotspotPin?: Viewer360HotspotPinOptions<TData>;\n renderHotspot?: (props: Viewer360HotspotRenderProps<TData>) => ReactNode;\n onHotspotClick?: (hotspot: Viewer360Hotspot<TData>, event: MouseEvent<HTMLDivElement>) => void;\n};\n\nexport function Viewer360HotspotOverlay<TData = unknown>({\n hotspot,\n leftPercent,\n topPercent,\n hotspotPin,\n renderHotspot,\n onHotspotClick,\n}: Viewer360HotspotOverlayProps<TData>): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n if (renderHotspot) {\n return (\n <Item size=\"xs\" variant=\"default\" className=\"pointer-events-auto w-auto border-transparent p-0\">\n {renderHotspot({ hotspot, leftPercent, topPercent })}\n </Item>\n );\n }\n\n const marker = hotspotPin?.getMarker?.(hotspot) ?? hotspotToViewer360Marker(hotspot);\n\n return (\n <Viewer360MarkerPin\n marker={marker}\n hotspot={hotspot}\n leftPercent={leftPercent}\n topPercent={topPercent}\n onDelete={hotspotPin?.onDelete}\n isDeletePending={hotspotPin?.deletingMarkerId === hotspot.id}\n renderTag={hotspotPin?.renderTag}\n classNames={hotspotPin?.classNames}\n labels={hotspotPin?.labels}\n onClick={\n onHotspotClick\n ? (event) => onHotspotClick(hotspot, event as unknown as MouseEvent<HTMLDivElement>)\n : undefined\n }\n />\n );\n}\n","'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/components/utils';\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>): React.ReactNode {\n return (\n <label\n data-slot=\"label\"\n className={cn(\n 'gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed',\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Label };\n","import type { JSX } from 'react';\n\nimport { Loader2Icon } from 'lucide-react';\n\nimport { cn } from '@/components/utils';\n\nfunction Spinner({ className, ...props }: React.ComponentProps<'svg'>): JSX.Element {\n return <Loader2Icon role=\"status\" aria-label=\"Loading\" className={cn('size-4 animate-spin', className)} {...props} />;\n}\n\nexport { Spinner };\n","import type { JSX } from 'react';\n\nimport { Item } from '@/components/ui/Item';\nimport { Label } from '@/components/ui/Label';\nimport { Spinner } from '@/components/ui/Spinner';\nimport { cn } from '@/components/utils';\n\ntype Viewer360LoadingOverlayProps = {\n className?: string;\n textClassName?: string;\n label: string;\n};\n\nexport function Viewer360LoadingOverlay({ className, textClassName, label }: Viewer360LoadingOverlayProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <Item\n size=\"sm\"\n variant=\"muted\"\n className={cn('pointer-events-none w-auto justify-center border-transparent bg-muted/80', className)}\n >\n <Spinner className=\"size-5 text-muted-foreground\" />\n <Label className={cn('font-normal text-muted-foreground', textClassName)}>{label}</Label>\n </Item>\n );\n}\n","import type { JSX } from 'react';\n\nimport { Crosshair, Minus, Plus, RotateCcw, ZoomIn } from 'lucide-react';\n\nimport { viewer360ClassNames } from '../constants/viewer360ClassNames';\nimport type { Viewer360ToolbarRenderProps } from '../types';\nimport { Badge } from '@/components/ui/Badge';\nimport { Button } from '@/components/ui/Button';\nimport { CardFooter } from '@/components/ui/Card';\nimport { Label } from '@/components/ui/Label';\nimport { Separator } from '@/components/ui/Separator';\nimport { cn } from '@/components/utils';\n\ntype Viewer360ToolbarProps = Viewer360ToolbarRenderProps;\n\nexport function Viewer360Toolbar({\n showDragHint,\n showHotspotModeControl,\n showZoomControls,\n showResetControl,\n labels,\n isHotspotMode,\n zoom,\n minZoom,\n maxZoom,\n isResetDisabled,\n onHotspotModeChange,\n onZoomIn,\n onZoomOut,\n onResetView,\n}: Viewer360ToolbarProps): JSX.Element {\n // ----------------------------------------------------------------------------------------------------\n // MARK: Main Component UI\n // ----------------------------------------------------------------------------------------------------\n return (\n <CardFooter className={cn(viewer360ClassNames.toolbar, 'gap-2 border-t px-4 py-3 pt-3')}>\n {showDragHint && (\n <Label className={cn(viewer360ClassNames.dragHint, 'font-normal text-muted-foreground')}>{labels.dragHint}</Label>\n )}\n\n <div className={viewer360ClassNames.controls}>\n {showHotspotModeControl && (\n <>\n <Button variant={isHotspotMode ? 'default' : 'outline'} size=\"sm\" onClick={() => onHotspotModeChange(!isHotspotMode)}>\n <Crosshair className=\"me-1.5 size-4\" />\n {labels.addHotspot}\n </Button>\n <Separator orientation=\"vertical\" className={cn(viewer360ClassNames.divider, 'h-6')} />\n </>\n )}\n\n {showZoomControls && (\n <>\n <Button variant=\"outline\" size=\"icon-sm\" disabled={zoom <= minZoom} aria-label={labels.zoomOut} onClick={onZoomOut}>\n <Minus className=\"size-4\" />\n </Button>\n <Badge variant=\"outline\" className={cn(viewer360ClassNames.zoomDisplay, 'h-8 gap-1 px-2 py-1')}>\n <ZoomIn className=\"size-3 text-muted-foreground\" />\n {labels.zoom(Math.round(zoom * 100))}\n </Badge>\n <Button variant=\"outline\" size=\"icon-sm\" disabled={zoom >= maxZoom} aria-label={labels.zoomIn} onClick={onZoomIn}>\n <Plus className=\"size-4\" />\n </Button>\n </>\n )}\n\n {showResetControl && (\n <Button variant=\"outline\" size=\"icon-sm\" disabled={isResetDisabled} aria-label={labels.resetView} onClick={onResetView}>\n <RotateCcw className=\"size-4\" />\n </Button>\n )}\n </div>\n </CardFooter>\n );\n}\n","export const viewer360Config = {\n minZoom: 1,\n maxZoom: 3,\n zoomStep: 0.15,\n dragSensitivity: 8,\n autoRotate: false,\n autoRotateIntervalMs: 100,\n autoRotateDirection: 'forward' as const,\n};\n\nexport const defaultViewer360Config = viewer360Config;\n"],"mappings":"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","names":["useEffect","useMemo","useState","jsx","jsx","jsx","cva","Slot","jsx","jsx","cva","Slot","jsx","jsx","cva","Slot","jsx","cva","Slot","jsx","jsx","jsx","jsx","jsx","jsxs","jsx","jsxs","isResetDisabled","jsx","jsxs","useMemo","useState","isResetDisabled","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmmzxe/react-360-viewer",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A standalone, configurable 360° image viewer for React with drag rotation, zoom, hotspots, and auto-rotate support.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -26,7 +26,8 @@ export const viewer360MarkerPinClassNames: Required<Viewer360MarkerPinClassNames
26
26
  root: 'pointer-events-auto absolute z-30 -translate-x-1/2 -translate-y-1/2',
27
27
  ping: 'absolute inline-flex size-6 -translate-x-1/4 -translate-y-1/4 animate-ping rounded-full bg-destructive opacity-60',
28
28
  dot: 'relative flex size-4 items-center justify-center rounded-full border-2 border-background bg-destructive shadow-md transition-transform duration-200 hover:scale-125 focus:outline-none',
29
- tooltip: 'absolute bottom-6 left-1/2 z-40 w-64 -translate-x-1/2 rounded-lg border bg-popover p-3 text-popover-foreground shadow-md',
29
+ tooltip:
30
+ 'absolute bottom-6 left-1/2 z-40 w-64 -translate-x-1/2 rounded-lg border bg-popover p-3 text-popover-foreground shadow-md',
30
31
  tooltipHeader: 'flex items-start justify-between gap-2',
31
32
  tooltipBody: 'flex min-w-0 flex-col gap-1',
32
33
  tooltipTitle: 'text-sm font-medium',
@@ -2,9 +2,7 @@ import type { JSX, MouseEvent, ReactNode } from 'react';
2
2
 
3
3
  import { hotspotToViewer360Marker } from '../helpers/markerHelpers';
4
4
  import type { Viewer360Hotspot, Viewer360HotspotPinOptions, Viewer360HotspotRenderProps } from '../types';
5
- import { Button } from '@/components/ui/Button';
6
5
  import { Item } from '@/components/ui/Item';
7
- import { cn } from '@/components/utils';
8
6
 
9
7
  import { Viewer360MarkerPin } from './Viewer360MarkerPin';
10
8
 
@@ -36,35 +34,24 @@ export function Viewer360HotspotOverlay<TData = unknown>({
36
34
  );
37
35
  }
38
36
 
39
- if (hotspotPin) {
40
- const marker = hotspotPin.getMarker?.(hotspot) ?? hotspotToViewer360Marker(hotspot);
41
-
42
- return (
43
- <Viewer360MarkerPin
44
- marker={marker}
45
- hotspot={hotspot}
46
- leftPercent={leftPercent}
47
- topPercent={topPercent}
48
- onDelete={hotspotPin.onDelete}
49
- isDeletePending={hotspotPin.deletingMarkerId === hotspot.id}
50
- renderTag={hotspotPin.renderTag}
51
- classNames={hotspotPin.classNames}
52
- labels={hotspotPin.labels}
53
- />
54
- );
55
- }
37
+ const marker = hotspotPin?.getMarker?.(hotspot) ?? hotspotToViewer360Marker(hotspot);
56
38
 
57
39
  return (
58
- <Button
59
- type="button"
60
- variant="destructive"
61
- size="icon-xs"
62
- className={cn(
63
- 'pointer-events-auto absolute z-30 size-4 min-h-4 min-w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:bg-destructive'
64
- )}
65
- style={{ left: `${leftPercent}%`, top: `${topPercent}%` }}
66
- aria-label={`Hotspot ${hotspot.id}`}
67
- onClick={(event) => onHotspotClick?.(hotspot, event as unknown as MouseEvent<HTMLDivElement>)}
40
+ <Viewer360MarkerPin
41
+ marker={marker}
42
+ hotspot={hotspot}
43
+ leftPercent={leftPercent}
44
+ topPercent={topPercent}
45
+ onDelete={hotspotPin?.onDelete}
46
+ isDeletePending={hotspotPin?.deletingMarkerId === hotspot.id}
47
+ renderTag={hotspotPin?.renderTag}
48
+ classNames={hotspotPin?.classNames}
49
+ labels={hotspotPin?.labels}
50
+ onClick={
51
+ onHotspotClick
52
+ ? (event) => onHotspotClick(hotspot, event as unknown as MouseEvent<HTMLDivElement>)
53
+ : undefined
54
+ }
68
55
  />
69
56
  );
70
57
  }
@@ -1,5 +1,4 @@
1
- import type { JSX } from 'react';
2
- import { useState } from 'react';
1
+ import type { JSX, MouseEvent } from 'react';
3
2
 
4
3
  import { Trash2 } from 'lucide-react';
5
4
 
@@ -15,7 +14,6 @@ import {
15
14
  ItemDescription,
16
15
  ItemTitle,
17
16
  } from '@/components/ui/Item';
18
- import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover';
19
17
  import { cn } from '@/components/utils';
20
18
 
21
19
  export function Viewer360MarkerPin<TData = unknown>({
@@ -25,6 +23,7 @@ export function Viewer360MarkerPin<TData = unknown>({
25
23
  topPercent,
26
24
  onDelete,
27
25
  isDeletePending = false,
26
+ onClick,
28
27
  renderTag,
29
28
  classNames,
30
29
  labels,
@@ -32,47 +31,49 @@ export function Viewer360MarkerPin<TData = unknown>({
32
31
  // ----------------------------------------------------------------------------------------------------
33
32
  // MARK: States & Constants
34
33
  // ----------------------------------------------------------------------------------------------------
35
- const [isOpen, setIsOpen] = useState(false);
36
34
  const deleteLabel = labels?.delete ?? defaultViewer360MarkerPinLabels.delete;
35
+ const showTooltip = Boolean(marker.title || marker.description || onDelete || renderTag);
37
36
 
38
37
  // ----------------------------------------------------------------------------------------------------
39
38
  // MARK: Main Component UI
40
39
  // ----------------------------------------------------------------------------------------------------
41
40
  return (
42
- <Popover open={isOpen} onOpenChange={setIsOpen}>
43
- <Item
44
- size="xs"
45
- variant="default"
46
- className={cn(viewer360MarkerPinClassNames.root, classNames?.root, 'w-auto border-transparent p-0')}
47
- style={{ left: `${leftPercent}%`, top: `${topPercent}%` }}
48
- onMouseEnter={() => setIsOpen(true)}
49
- onMouseLeave={() => setIsOpen(false)}
50
- >
51
- <Badge
52
- variant="destructive"
53
- className={cn(viewer360MarkerPinClassNames.ping, classNames?.ping, 'absolute size-6 border-0 bg-destructive opacity-60')}
54
- aria-hidden="true"
55
- />
41
+ <Item
42
+ size="xs"
43
+ variant="default"
44
+ className={cn(
45
+ viewer360MarkerPinClassNames.root,
46
+ classNames?.root,
47
+ 'group/marker w-auto border-transparent p-0'
48
+ )}
49
+ style={{ left: `${leftPercent}%`, top: `${topPercent}%` }}
50
+ >
51
+ <Badge
52
+ variant="destructive"
53
+ className={cn(viewer360MarkerPinClassNames.ping, classNames?.ping, 'absolute size-6 border-0 bg-destructive opacity-60')}
54
+ aria-hidden="true"
55
+ />
56
56
 
57
- <PopoverTrigger asChild>
58
- <Button
59
- type="button"
60
- variant="destructive"
61
- size="icon-xs"
62
- className={cn(
63
- viewer360MarkerPinClassNames.dot,
64
- classNames?.dot,
65
- 'size-4 min-h-4 min-w-4 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:scale-125 hover:bg-destructive'
66
- )}
67
- aria-label={marker.title}
68
- />
69
- </PopoverTrigger>
57
+ <Button
58
+ type="button"
59
+ variant="destructive"
60
+ size="icon-xs"
61
+ className={cn(
62
+ viewer360MarkerPinClassNames.dot,
63
+ classNames?.dot,
64
+ 'size-4 min-h-4 min-w-4 rounded-full border-2 border-background bg-destructive p-0 shadow-md hover:scale-125 hover:bg-destructive'
65
+ )}
66
+ aria-label={marker.title}
67
+ onClick={onClick}
68
+ />
70
69
 
71
- <PopoverContent
72
- className={cn(viewer360MarkerPinClassNames.tooltip, classNames?.tooltip, 'w-64 p-0')}
73
- side="top"
74
- align="center"
75
- onOpenAutoFocus={(event) => event.preventDefault()}
70
+ {showTooltip && (
71
+ <div
72
+ className={cn(
73
+ viewer360MarkerPinClassNames.tooltip,
74
+ classNames?.tooltip,
75
+ 'pointer-events-none opacity-0 transition-opacity duration-150 group-hover/marker:pointer-events-auto group-hover/marker:opacity-100 group-focus-within/marker:pointer-events-auto group-focus-within/marker:opacity-100'
76
+ )}
76
77
  >
77
78
  <Item
78
79
  size="sm"
@@ -98,7 +99,7 @@ export function Viewer360MarkerPin<TData = unknown>({
98
99
  className={classNames?.deleteButton}
99
100
  disabled={isDeletePending}
100
101
  aria-label={deleteLabel}
101
- onClick={(event) => {
102
+ onClick={(event: MouseEvent<HTMLButtonElement>) => {
102
103
  event.stopPropagation();
103
104
  onDelete(marker.id);
104
105
  }}
@@ -108,8 +109,8 @@ export function Viewer360MarkerPin<TData = unknown>({
108
109
  </ItemActions>
109
110
  )}
110
111
  </Item>
111
- </PopoverContent>
112
- </Item>
113
- </Popover>
112
+ </div>
113
+ )}
114
+ </Item>
114
115
  );
115
116
  }
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from 'react';
1
+ import type { MouseEvent, ReactNode } from 'react';
2
2
 
3
3
  import type { Viewer360Hotspot } from './Viewer360Hotspot';
4
4
 
@@ -36,6 +36,7 @@ export type Viewer360MarkerPinProps<TData = unknown> = {
36
36
  topPercent: number;
37
37
  onDelete?: (id: string) => void;
38
38
  isDeletePending?: boolean;
39
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
39
40
  renderTag?: (props: Viewer360MarkerPinRenderProps<TData>) => ReactNode;
40
41
  classNames?: Viewer360MarkerPinClassNames;
41
42
  labels?: Viewer360MarkerPinLabels;