@ericchen1990/pano-view 3.6.3 → 3.6.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.mjs CHANGED
@@ -3981,7 +3981,10 @@ function MouseControls({
3981
3981
  const element = gl.domElement;
3982
3982
  const drag = createPointerDrag(camera, element, () => controlsRef.current, "mouse");
3983
3983
  const allowedButtons = resolveButtons(buttons);
3984
+ const pending = /* @__PURE__ */ new Set();
3985
+ let disposed = false;
3984
3986
  const clearPointers = () => {
3987
+ pending.clear();
3985
3988
  for (const pointerId of pointersRef.current.keys()) {
3986
3989
  if (element.hasPointerCapture?.(pointerId)) {
3987
3990
  element.releasePointerCapture?.(pointerId);
@@ -4007,6 +4010,25 @@ function MouseControls({
4007
4010
  { cancelled }
4008
4011
  );
4009
4012
  };
4013
+ const beginPointer = (event) => {
4014
+ if (disposed || controlsRef.current?.isInteractionLocked()) {
4015
+ return;
4016
+ }
4017
+ if (pointersRef.current.has(event.pointerId)) {
4018
+ return;
4019
+ }
4020
+ pointersRef.current.set(event.pointerId, {
4021
+ x: event.clientX,
4022
+ y: event.clientY,
4023
+ at: performance.now()
4024
+ });
4025
+ try {
4026
+ element.setPointerCapture?.(event.pointerId);
4027
+ } catch {
4028
+ }
4029
+ syncActive();
4030
+ drag.start({ x: event.clientX, y: event.clientY }, pointerAt(event));
4031
+ };
4010
4032
  const onPointerDown = (event) => {
4011
4033
  if (!isMouseLike(event)) {
4012
4034
  return;
@@ -4018,14 +4040,14 @@ function MouseControls({
4018
4040
  return;
4019
4041
  }
4020
4042
  event.preventDefault();
4021
- pointersRef.current.set(event.pointerId, {
4022
- x: event.clientX,
4023
- y: event.clientY,
4024
- at: performance.now()
4043
+ const pointerId = event.pointerId;
4044
+ pending.add(pointerId);
4045
+ queueMicrotask(() => {
4046
+ if (!pending.delete(pointerId)) {
4047
+ return;
4048
+ }
4049
+ beginPointer(event);
4025
4050
  });
4026
- element.setPointerCapture?.(event.pointerId);
4027
- syncActive();
4028
- drag.start({ x: event.clientX, y: event.clientY }, pointerAt(event));
4029
4051
  };
4030
4052
  const onPointerMove = (event) => {
4031
4053
  if (!isMouseLike(event)) {
@@ -4049,6 +4071,7 @@ function MouseControls({
4049
4071
  drag.move(next, previous, speed, optionsRef.current.invert, pointerAt(event));
4050
4072
  };
4051
4073
  const releasePointer = (event) => {
4074
+ pending.delete(event.pointerId);
4052
4075
  if (!pointersRef.current.has(event.pointerId)) {
4053
4076
  return;
4054
4077
  }
@@ -4090,6 +4113,7 @@ function MouseControls({
4090
4113
  element.addEventListener("lostpointercapture", releasePointer);
4091
4114
  element.addEventListener("wheel", onWheel, { passive: false });
4092
4115
  return () => {
4116
+ disposed = true;
4093
4117
  validateDragRef.current = void 0;
4094
4118
  element.removeEventListener("pointerdown", onPointerDown);
4095
4119
  element.removeEventListener("pointermove", onPointerMove);
@@ -5607,6 +5631,12 @@ function vector3ToPanoPosition(vector) {
5607
5631
  )
5608
5632
  });
5609
5633
  }
5634
+ function translateHotspotPosition(origin, from, to) {
5635
+ return normalizePanoPosition({
5636
+ yaw: origin.yaw + normalizePanoYaw(to.yaw - from.yaw),
5637
+ pitch: origin.pitch + (to.pitch - from.pitch)
5638
+ });
5639
+ }
5610
5640
 
5611
5641
  // src/panorama-radius.ts
5612
5642
  var DEFAULT_PANORAMA_RADIUS = 1e3;
@@ -5750,7 +5780,10 @@ function TouchControls({
5750
5780
  useEffect11(() => {
5751
5781
  const element = gl.domElement;
5752
5782
  const drag = createPointerDrag(camera, element, () => controlsRef.current, "touch");
5783
+ const pending = /* @__PURE__ */ new Set();
5784
+ let disposed = false;
5753
5785
  const clearPointers = () => {
5786
+ pending.clear();
5754
5787
  for (const pointerId of pointersRef.current.keys()) {
5755
5788
  if (element.hasPointerCapture?.(pointerId)) {
5756
5789
  element.releasePointerCapture?.(pointerId);
@@ -5776,20 +5809,22 @@ function TouchControls({
5776
5809
  { cancelled }
5777
5810
  );
5778
5811
  };
5779
- const onPointerDown = (event) => {
5780
- if (event.pointerType !== "touch") {
5812
+ const beginPointer = (event) => {
5813
+ if (disposed || controlsRef.current?.isInteractionLocked()) {
5781
5814
  return;
5782
5815
  }
5783
- if (controlsRef.current?.isInteractionLocked()) {
5816
+ if (pointersRef.current.has(event.pointerId)) {
5784
5817
  return;
5785
5818
  }
5786
- event.preventDefault();
5787
5819
  pointersRef.current.set(event.pointerId, {
5788
5820
  x: event.clientX,
5789
5821
  y: event.clientY,
5790
5822
  at: performance.now()
5791
5823
  });
5792
- element.setPointerCapture?.(event.pointerId);
5824
+ try {
5825
+ element.setPointerCapture?.(event.pointerId);
5826
+ } catch {
5827
+ }
5793
5828
  syncActive();
5794
5829
  drag.start({ x: event.clientX, y: event.clientY }, pointerAt2(event));
5795
5830
  if (pointersRef.current.size >= 2) {
@@ -5798,6 +5833,23 @@ function TouchControls({
5798
5833
  pinchDistanceRef.current = pointerDistance(first, second);
5799
5834
  }
5800
5835
  };
5836
+ const onPointerDown = (event) => {
5837
+ if (event.pointerType !== "touch") {
5838
+ return;
5839
+ }
5840
+ if (controlsRef.current?.isInteractionLocked()) {
5841
+ return;
5842
+ }
5843
+ event.preventDefault();
5844
+ const pointerId = event.pointerId;
5845
+ pending.add(pointerId);
5846
+ queueMicrotask(() => {
5847
+ if (!pending.delete(pointerId)) {
5848
+ return;
5849
+ }
5850
+ beginPointer(event);
5851
+ });
5852
+ };
5801
5853
  const onPointerMove = (event) => {
5802
5854
  if (event.pointerType !== "touch") {
5803
5855
  return;
@@ -5841,6 +5893,7 @@ function TouchControls({
5841
5893
  drag.move(next, previous, speed, optionsRef.current.invert, pointerAt2(event));
5842
5894
  };
5843
5895
  const releasePointer = (event) => {
5896
+ pending.delete(event.pointerId);
5844
5897
  if (!pointersRef.current.has(event.pointerId)) {
5845
5898
  return;
5846
5899
  }
@@ -5864,6 +5917,7 @@ function TouchControls({
5864
5917
  element.addEventListener("pointercancel", releasePointer);
5865
5918
  element.addEventListener("lostpointercapture", releasePointer);
5866
5919
  return () => {
5920
+ disposed = true;
5867
5921
  validateDragRef.current = void 0;
5868
5922
  element.removeEventListener("pointerdown", onPointerDown);
5869
5923
  element.removeEventListener("pointermove", onPointerMove);
@@ -11505,13 +11559,103 @@ import {
11505
11559
  EdgesGeometry,
11506
11560
  MathUtils as MathUtils8,
11507
11561
  Matrix4 as Matrix44,
11562
+ PerspectiveCamera as PerspectiveCamera8,
11508
11563
  Plane,
11509
11564
  PlaneGeometry as PlaneGeometry4,
11510
11565
  Quaternion as Quaternion3,
11511
11566
  Ray,
11567
+ Raycaster as Raycaster2,
11568
+ Vector2 as Vector24,
11512
11569
  Vector3 as Vector316
11513
11570
  } from "three";
11514
11571
 
11572
+ // src/hotspot/drag-session.ts
11573
+ var CLICK_SUPPRESS_MS = 1e3;
11574
+ function trackPointerDrag(source, element, pointerId, onMove, onEnd) {
11575
+ let stopped = false;
11576
+ const releaseCapture = () => {
11577
+ if (!element.hasPointerCapture?.(pointerId)) {
11578
+ return;
11579
+ }
11580
+ try {
11581
+ element.releasePointerCapture?.(pointerId);
11582
+ } catch {
11583
+ }
11584
+ };
11585
+ const stop = () => {
11586
+ if (stopped) {
11587
+ return;
11588
+ }
11589
+ stopped = true;
11590
+ source.removeEventListener("pointermove", onPointerMove, true);
11591
+ source.removeEventListener("pointerup", onPointerUp, true);
11592
+ source.removeEventListener("pointercancel", onPointerCancel, true);
11593
+ element.removeEventListener("lostpointercapture", onLostCapture);
11594
+ releaseCapture();
11595
+ };
11596
+ const finish = (event) => {
11597
+ if (stopped || event.pointerId !== pointerId) {
11598
+ return;
11599
+ }
11600
+ stop();
11601
+ onEnd(event);
11602
+ };
11603
+ const onPointerMove = (event) => {
11604
+ if (stopped || event.pointerId !== pointerId) {
11605
+ return;
11606
+ }
11607
+ try {
11608
+ event.preventDefault?.();
11609
+ } catch {
11610
+ }
11611
+ onMove(event);
11612
+ };
11613
+ const onPointerUp = (event) => {
11614
+ finish(event);
11615
+ };
11616
+ const onPointerCancel = (event) => {
11617
+ finish(event);
11618
+ };
11619
+ const onLostCapture = (event) => {
11620
+ if ((event.buttons ?? 0) > 0) {
11621
+ return;
11622
+ }
11623
+ finish(event);
11624
+ };
11625
+ try {
11626
+ element.setPointerCapture?.(pointerId);
11627
+ } catch {
11628
+ }
11629
+ source.addEventListener("pointermove", onPointerMove, true);
11630
+ source.addEventListener("pointerup", onPointerUp, true);
11631
+ source.addEventListener("pointercancel", onPointerCancel, true);
11632
+ element.addEventListener("lostpointercapture", onLostCapture);
11633
+ return stop;
11634
+ }
11635
+ function suppressFollowingClick(target) {
11636
+ let cleaned = false;
11637
+ const cleanup = () => {
11638
+ if (cleaned) {
11639
+ return;
11640
+ }
11641
+ cleaned = true;
11642
+ target.removeEventListener("click", swallowClick, true);
11643
+ target.removeEventListener("pointerdown", cancel, true);
11644
+ };
11645
+ const swallowClick = (event) => {
11646
+ event.preventDefault();
11647
+ event.stopPropagation();
11648
+ event.stopImmediatePropagation();
11649
+ cleanup();
11650
+ };
11651
+ const cancel = () => {
11652
+ cleanup();
11653
+ };
11654
+ target.addEventListener("click", swallowClick, true);
11655
+ target.addEventListener("pointerdown", cancel, true);
11656
+ setTimeout(cleanup, CLICK_SUPPRESS_MS);
11657
+ }
11658
+
11515
11659
  // src/hotspot/hotspot-tooltip.tsx
11516
11660
  import { useEffect as useEffect23, useMemo as useMemo18, useState as useState12 } from "react";
11517
11661
 
@@ -11780,6 +11924,21 @@ function fixedScaleFactor(cameraFov, referenceFov) {
11780
11924
  const safeReferenceFov = MathUtils6.clamp(referenceFov, 1, 179);
11781
11925
  return Math.tan(MathUtils6.degToRad(cameraFov) / 2) / Math.tan(MathUtils6.degToRad(safeReferenceFov) / 2);
11782
11926
  }
11927
+ function selectionFrameScale(worldScaleX, worldScaleY, cameraSpaceDepth, fovDegrees, viewportHeight, outsetPixels) {
11928
+ const pixels = Number.isFinite(outsetPixels) && outsetPixels > 0 ? outsetPixels : 0;
11929
+ const depth = Number.isFinite(cameraSpaceDepth) ? Math.max(cameraSpaceDepth, 1e-4) : 1e-4;
11930
+ const height = Number.isFinite(viewportHeight) ? Math.max(viewportHeight, 1) : 1;
11931
+ const fov = Number.isFinite(fovDegrees) ? MathUtils6.clamp(fovDegrees, 1, 179) : 75;
11932
+ const pixelsPerWorldUnit = height / (2 * Math.tan(MathUtils6.degToRad(fov) * 0.5) * depth);
11933
+ const axis = (worldScale) => {
11934
+ const pixelsPerLocalUnit = pixelsPerWorldUnit * Math.abs(worldScale);
11935
+ if (pixelsPerLocalUnit < 1e-4) {
11936
+ return 1;
11937
+ }
11938
+ return 1 + 2 * pixels / pixelsPerLocalUnit;
11939
+ };
11940
+ return [axis(worldScaleX), axis(worldScaleY)];
11941
+ }
11783
11942
 
11784
11943
  // src/hotspot/hotspot-stroke-line.tsx
11785
11944
  import { Line } from "@react-three/drei";
@@ -11866,6 +12025,11 @@ var DEFAULT_REFERENCE_FOV = 75;
11866
12025
  var MIN_ANGULAR_SIZE = 0.01;
11867
12026
  var MAX_ANGULAR_SIZE = 179;
11868
12027
  var DRAG_EPSILON_DEGREES = 1e-3;
12028
+ var SELECTION_BORDER_WIDTH = 3;
12029
+ var SELECTION_BORDER_GAP = 3;
12030
+ var SELECTION_BORDER_OUTSET = SELECTION_BORDER_GAP + SELECTION_BORDER_WIDTH / 2;
12031
+ var SELECTION_CAMERA_POSITION = new Vector316();
12032
+ var SELECTION_WORLD_SCALE = new Vector316();
11869
12033
  var SELECTION_BORDER_POINTS = [
11870
12034
  [-0.5, -0.5, 1e-3],
11871
12035
  [0.5, -0.5, 1e-3],
@@ -11953,6 +12117,34 @@ function angularDistance(a, b) {
11953
12117
  const bVector = panoPositionToVector3(b);
11954
12118
  return MathUtils8.radToDeg(aVector.angleTo(bVector));
11955
12119
  }
12120
+ var DRAG_POINTER = new Vector24();
12121
+ var DRAG_RAYCASTER = new Raycaster2();
12122
+ function aimDragRaycaster(event, element, camera) {
12123
+ const rect = element.getBoundingClientRect();
12124
+ if (!(rect.width > 0) || !(rect.height > 0)) {
12125
+ return false;
12126
+ }
12127
+ DRAG_POINTER.set(
12128
+ (event.clientX - rect.left) / rect.width * 2 - 1,
12129
+ -((event.clientY - rect.top) / rect.height) * 2 + 1
12130
+ );
12131
+ camera.updateMatrixWorld();
12132
+ DRAG_RAYCASTER.setFromCamera(DRAG_POINTER, camera);
12133
+ return true;
12134
+ }
12135
+ function pointerToPanoPosition(event, element, camera) {
12136
+ if (!aimDragRaycaster(event, element, camera)) {
12137
+ return null;
12138
+ }
12139
+ return vector3ToPanoPosition(DRAG_RAYCASTER.ray.direction);
12140
+ }
12141
+ function pointerHitsHotspot(event, element, camera, group) {
12142
+ if (!group || !aimDragRaycaster(event, element, camera)) {
12143
+ return false;
12144
+ }
12145
+ group.updateMatrixWorld(true);
12146
+ return DRAG_RAYCASTER.intersectObject(group, true).length > 0;
12147
+ }
11956
12148
  function isTooltipOpen(trigger, hovered, focused, pinned) {
11957
12149
  switch (trigger) {
11958
12150
  case "always":
@@ -12148,6 +12340,24 @@ function updateTooltipAnchor(anchor, group, placement, useAngularScale, camera,
12148
12340
  }
12149
12341
  setTooltipAnchorOnLocalBox(anchor, placement, -0.5, 0.5, -0.5, 0.5);
12150
12342
  }
12343
+ function placeSelectionFrame(frame, group, camera, viewportHeight) {
12344
+ if (!frame) {
12345
+ return;
12346
+ }
12347
+ group.getWorldScale(SELECTION_WORLD_SCALE);
12348
+ SELECTION_CAMERA_POSITION.setFromMatrixPosition(group.matrixWorld).applyMatrix4(
12349
+ camera.matrixWorldInverse
12350
+ );
12351
+ const [scaleX, scaleY] = selectionFrameScale(
12352
+ SELECTION_WORLD_SCALE.x,
12353
+ SELECTION_WORLD_SCALE.y,
12354
+ -SELECTION_CAMERA_POSITION.z,
12355
+ camera instanceof PerspectiveCamera8 ? camera.fov : DEFAULT_REFERENCE_FOV,
12356
+ viewportHeight,
12357
+ SELECTION_BORDER_OUTSET
12358
+ );
12359
+ frame.scale.set(scaleX, scaleY, 1);
12360
+ }
12151
12361
  function HotspotAnchor({
12152
12362
  id,
12153
12363
  position,
@@ -12187,10 +12397,13 @@ function HotspotAnchor({
12187
12397
  const hotspotTargetRegistry = useContext21(HotspotTargetRegistryContext);
12188
12398
  const eventBus = useContext21(PanoEventBusContext);
12189
12399
  const cursorApi = usePanoCursor();
12190
- const { camera, size } = useThree19();
12400
+ const { camera, gl, size } = useThree19();
12191
12401
  const groupRef = useRef25(null);
12402
+ const selectionFrameRef = useRef25(null);
12192
12403
  const tooltipAnchorRef = useRef25(null);
12193
12404
  const dragStateRef = useRef25(null);
12405
+ const updateDragRef = useRef25(() => void 0);
12406
+ const endDragRef = useRef25(() => void 0);
12194
12407
  const suppressNextClickRef = useRef25(false);
12195
12408
  const [hovered, setHovered] = useState13(false);
12196
12409
  const [dragging, setDragging] = useState13(false);
@@ -12260,7 +12473,9 @@ function HotspotAnchor({
12260
12473
  const cursorClaimId = hotspotCursorClaimId(id);
12261
12474
  useEffect24(
12262
12475
  () => () => {
12263
- dragStateRef.current?.releaseInteractionLock();
12476
+ const dragState = dragStateRef.current;
12477
+ dragState?.stopTracking();
12478
+ dragState?.releaseInteractionLock();
12264
12479
  dragStateRef.current = null;
12265
12480
  },
12266
12481
  []
@@ -12317,6 +12532,7 @@ function HotspotAnchor({
12317
12532
  if (!dragState) {
12318
12533
  return;
12319
12534
  }
12535
+ dragState.stopTracking();
12320
12536
  dragState.releaseInteractionLock();
12321
12537
  dragStateRef.current = null;
12322
12538
  setDragging(false);
@@ -12339,6 +12555,7 @@ function HotspotAnchor({
12339
12555
  worldHeight,
12340
12556
  resolvedDistance
12341
12557
  );
12558
+ placeSelectionFrame(selectionFrameRef.current, group, camera, size.height);
12342
12559
  if (tooltipOpen && tooltipAnchorRef.current) {
12343
12560
  updateTooltipAnchor(
12344
12561
  tooltipAnchorRef.current,
@@ -12357,6 +12574,7 @@ function HotspotAnchor({
12357
12574
  rotation,
12358
12575
  scaleMode,
12359
12576
  size,
12577
+ selected,
12360
12578
  surfaceQuaternion3,
12361
12579
  tooltipOpen,
12362
12580
  tooltipPlacement,
@@ -12382,6 +12600,12 @@ function HotspotAnchor({
12382
12600
  worldHeight,
12383
12601
  resolvedDistance
12384
12602
  );
12603
+ placeSelectionFrame(
12604
+ selectionFrameRef.current,
12605
+ group,
12606
+ state.camera,
12607
+ state.size.height
12608
+ );
12385
12609
  if (tooltipOpen && tooltipAnchorRef.current) {
12386
12610
  updateTooltipAnchor(
12387
12611
  tooltipAnchorRef.current,
@@ -12397,31 +12621,78 @@ function HotspotAnchor({
12397
12621
  event.stopPropagation();
12398
12622
  event.nativeEvent.stopImmediatePropagation?.();
12399
12623
  };
12400
- const releasePointer = (event, emitDragEnd) => {
12624
+ const dragPosition = (dragState, event) => {
12625
+ const pointerPosition = pointerToPanoPosition(event, gl.domElement, camera);
12626
+ if (!pointerPosition) {
12627
+ return dragState.lastPosition;
12628
+ }
12629
+ return translateHotspotPosition(
12630
+ dragState.startPosition,
12631
+ dragState.pointerOrigin,
12632
+ pointerPosition
12633
+ );
12634
+ };
12635
+ updateDragRef.current = (nativeEvent) => {
12401
12636
  const dragState = dragStateRef.current;
12402
- if (!dragState || dragState.pointerId !== event.pointerId) {
12637
+ if (!dragState || dragState.pointerId !== nativeEvent.pointerId) {
12403
12638
  return;
12404
12639
  }
12405
- stopPointerEvent(event);
12406
- const target = event.target;
12407
- if (target.hasPointerCapture?.(event.pointerId)) {
12408
- target.releasePointerCapture?.(event.pointerId);
12409
- }
12410
- const nextPosition = vector3ToPanoPosition(event.ray.direction);
12411
- if (emitDragEnd) {
12412
- onDragEnd?.({
12413
- ...makeInteractionEvent(id, nextPosition, event),
12414
- source: "pointer",
12415
- startPosition: dragState.startPosition
12416
- });
12640
+ const nextPosition = dragPosition(dragState, nativeEvent);
12641
+ dragState.lastPosition = nextPosition;
12642
+ dragState.moved || (dragState.moved = angularDistance(dragState.startPosition, nextPosition) > DRAG_EPSILON_DEGREES);
12643
+ onPositionChange?.({
12644
+ id,
12645
+ position: nextPosition,
12646
+ source: "pointer",
12647
+ nativeEvent,
12648
+ startPosition: dragState.startPosition
12649
+ });
12650
+ };
12651
+ endDragRef.current = (nativeEvent, emitDragEnd) => {
12652
+ const dragState = dragStateRef.current;
12653
+ if (!dragState || dragState.pointerId !== nativeEvent.pointerId) {
12654
+ return;
12417
12655
  }
12418
- suppressNextClickRef.current = dragState.moved;
12656
+ const nextPosition = dragPosition(dragState, nativeEvent);
12657
+ dragState.lastPosition = nextPosition;
12658
+ dragState.moved || (dragState.moved = angularDistance(dragState.startPosition, nextPosition) > DRAG_EPSILON_DEGREES);
12659
+ const moved = dragState.moved;
12660
+ const startPosition = dragState.startPosition;
12661
+ dragState.stopTracking();
12419
12662
  dragState.releaseInteractionLock();
12420
12663
  dragStateRef.current = null;
12664
+ suppressNextClickRef.current = moved;
12421
12665
  setDragging(false);
12422
- if (emitDragEnd) {
12423
- setHovered(true);
12666
+ if (!emitDragEnd) {
12667
+ return;
12424
12668
  }
12669
+ const overHotspot = pointerHitsHotspot(
12670
+ nativeEvent,
12671
+ gl.domElement,
12672
+ camera,
12673
+ groupRef.current
12674
+ );
12675
+ eventHovered.current = overHotspot;
12676
+ setHovered(overHotspot);
12677
+ if (moved && typeof window !== "undefined") {
12678
+ suppressFollowingClick(window);
12679
+ }
12680
+ if (moved) {
12681
+ onPositionChange?.({
12682
+ id,
12683
+ position: nextPosition,
12684
+ source: "pointer",
12685
+ nativeEvent,
12686
+ startPosition
12687
+ });
12688
+ }
12689
+ onDragEnd?.({
12690
+ id,
12691
+ position: nextPosition,
12692
+ source: "pointer",
12693
+ nativeEvent,
12694
+ startPosition
12695
+ });
12425
12696
  };
12426
12697
  return /* @__PURE__ */ jsxs15(
12427
12698
  "group",
@@ -12441,23 +12712,42 @@ function HotspotAnchor({
12441
12712
  }
12442
12713
  suppressNextClickRef.current = false;
12443
12714
  } : void 0,
12444
- onLostPointerCapture: acceptsPointer ? (event) => releasePointer(event, false) : void 0,
12445
- onPointerCancel: acceptsPointer ? (event) => releasePointer(event, false) : void 0,
12715
+ onLostPointerCapture: acceptsPointer ? (event) => {
12716
+ endDragRef.current(event.nativeEvent, true);
12717
+ } : void 0,
12718
+ onPointerCancel: acceptsPointer ? (event) => {
12719
+ endDragRef.current(event.nativeEvent, true);
12720
+ } : void 0,
12446
12721
  onPointerDown: acceptsPointer ? (event) => {
12447
- stopPointerEvent(event);
12722
+ const native = event.nativeEvent;
12448
12723
  if (!draggable || dragStateRef.current) {
12724
+ stopPointerEvent(event);
12449
12725
  return;
12450
12726
  }
12727
+ const pointerId = native.pointerId;
12728
+ stopPointerEvent(event);
12451
12729
  const releaseInteractionLock = controlsRef?.current?.acquireInteractionLock() ?? (() => void 0);
12452
- const target = event.target;
12453
- target.setPointerCapture?.(event.pointerId);
12730
+ const pointerOrigin = pointerToPanoPosition(native, gl.domElement, camera) ?? vector3ToPanoPosition(event.ray.direction);
12454
12731
  const startPosition = { ...normalizedPosition };
12732
+ const tracksOutsideMesh = typeof window !== "undefined";
12733
+ const stopTracking = tracksOutsideMesh ? trackPointerDrag(
12734
+ window,
12735
+ gl.domElement,
12736
+ pointerId,
12737
+ (pointerEvent) => updateDragRef.current(pointerEvent),
12738
+ (pointerEvent) => endDragRef.current(pointerEvent, true)
12739
+ ) : () => void 0;
12455
12740
  dragStateRef.current = {
12456
- pointerId: event.pointerId,
12741
+ pointerId,
12457
12742
  startPosition,
12743
+ pointerOrigin,
12744
+ lastPosition: startPosition,
12458
12745
  releaseInteractionLock,
12459
- moved: false
12746
+ moved: false,
12747
+ tracksOutsideMesh,
12748
+ stopTracking
12460
12749
  };
12750
+ eventHovered.current = false;
12461
12751
  setHovered(false);
12462
12752
  setDragging(true);
12463
12753
  onDragStart?.({
@@ -12468,17 +12758,13 @@ function HotspotAnchor({
12468
12758
  } : void 0,
12469
12759
  onPointerMove: acceptsPointer ? (event) => {
12470
12760
  const dragState = dragStateRef.current;
12471
- if (!dragState || dragState.pointerId !== event.pointerId) {
12761
+ if (!dragState || dragState.pointerId !== event.nativeEvent.pointerId) {
12472
12762
  return;
12473
12763
  }
12474
12764
  stopPointerEvent(event);
12475
- const nextPosition = vector3ToPanoPosition(event.ray.direction);
12476
- dragState.moved || (dragState.moved = angularDistance(dragState.startPosition, nextPosition) > DRAG_EPSILON_DEGREES);
12477
- onPositionChange?.({
12478
- ...makeInteractionEvent(id, nextPosition, event),
12479
- source: "pointer",
12480
- startPosition: dragState.startPosition
12481
- });
12765
+ if (!dragState.tracksOutsideMesh) {
12766
+ updateDragRef.current(event.nativeEvent);
12767
+ }
12482
12768
  } : void 0,
12483
12769
  onPointerOut: acceptsPointer ? (event) => {
12484
12770
  if (!dragStateRef.current) {
@@ -12507,7 +12793,10 @@ function HotspotAnchor({
12507
12793
  );
12508
12794
  }
12509
12795
  } : void 0,
12510
- onPointerUp: acceptsPointer ? (event) => releasePointer(event, true) : void 0,
12796
+ onPointerUp: acceptsPointer ? (event) => {
12797
+ stopPointerEvent(event);
12798
+ endDragRef.current(event.nativeEvent, true);
12799
+ } : void 0,
12511
12800
  children: [
12512
12801
  focused ? focusContent ?? /* @__PURE__ */ jsx26(
12513
12802
  "lineSegments",
@@ -12520,16 +12809,16 @@ function HotspotAnchor({
12520
12809
  }
12521
12810
  ) : null,
12522
12811
  children,
12523
- selected && useAngularScale ? /* @__PURE__ */ jsx26(
12812
+ selected && useAngularScale ? /* @__PURE__ */ jsx26("group", { ref: selectionFrameRef, children: /* @__PURE__ */ jsx26(
12524
12813
  HotspotStrokeLine,
12525
12814
  {
12526
12815
  points: SELECTION_BORDER_POINTS,
12527
12816
  color: "#ffb300",
12528
- lineWidth: 3,
12817
+ lineWidth: SELECTION_BORDER_WIDTH,
12529
12818
  opacity: 1,
12530
12819
  renderOrder: renderOrder + 1
12531
12820
  }
12532
- ) : null,
12821
+ ) }) : null,
12533
12822
  tooltipOpen && tooltipContent ? /* @__PURE__ */ jsx26("group", { ref: tooltipAnchorRef, children: /* @__PURE__ */ jsx26(
12534
12823
  HotspotTooltip,
12535
12824
  {
@@ -12549,11 +12838,11 @@ import {
12549
12838
  DoubleSide as DoubleSide3,
12550
12839
  Mesh as Mesh4,
12551
12840
  MeshBasicMaterial,
12552
- Vector2 as Vector24
12841
+ Vector2 as Vector25
12553
12842
  } from "three";
12554
12843
  import { jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
12555
12844
  var HOTSPOT_ALPHA_CUTOFF = 0.05;
12556
- var SAMPLE_UV = new Vector24();
12845
+ var SAMPLE_UV = new Vector25();
12557
12846
  var RAYCAST_HITS = [];
12558
12847
  var IMAGE_DATA_CACHE = /* @__PURE__ */ new WeakMap();
12559
12848
  function clampOpacity(opacity) {
@@ -13816,7 +14105,7 @@ import {
13816
14105
  ClampToEdgeWrapping as ClampToEdgeWrapping8,
13817
14106
  LinearFilter as LinearFilter10,
13818
14107
  MathUtils as MathUtils10,
13819
- PerspectiveCamera as PerspectiveCamera8,
14108
+ PerspectiveCamera as PerspectiveCamera9,
13820
14109
  SRGBColorSpace as SRGBColorSpace9
13821
14110
  } from "three";
13822
14111
  import { jsx as jsx33 } from "react/jsx-runtime";
@@ -14070,11 +14359,15 @@ function createTextTexture(text, width, height, style, rasterLong) {
14070
14359
  if (!context) {
14071
14360
  throw new Error("Canvas 2D is unavailable for this text hotspot.");
14072
14361
  }
14073
- drawTextHotspot(context, canvasWidth, canvasHeight, text, {
14074
- ...style,
14075
- fontSize: resolvePositive(style.fontSize, DEFAULT_FONT_SIZE) * scale,
14076
- strokeWidth: Number.isFinite(style.strokeWidth) ? style.strokeWidth * scale : style.strokeWidth
14077
- });
14362
+ context.setTransform(
14363
+ canvasWidth / layout.canvasWidth,
14364
+ 0,
14365
+ 0,
14366
+ canvasHeight / layout.canvasHeight,
14367
+ 0,
14368
+ 0
14369
+ );
14370
+ drawTextHotspot(context, layout.canvasWidth, layout.canvasHeight, text, style);
14078
14371
  const texture = new CanvasTexture3(canvas);
14079
14372
  texture.colorSpace = SRGBColorSpace9;
14080
14373
  texture.wrapS = ClampToEdgeWrapping8;
@@ -14092,7 +14385,7 @@ function useRasterLongSide(width, height, scale, scaleMode, referenceFov) {
14092
14385
  const measure = () => {
14093
14386
  const pixelRatio = getPixelRatio();
14094
14387
  const viewportHeight = size.height * (Number.isFinite(pixelRatio) && pixelRatio > 0 ? pixelRatio : 1);
14095
- const cameraFov = camera instanceof PerspectiveCamera8 ? camera.fov : DEFAULT_REFERENCE_FOV2;
14388
+ const cameraFov = camera instanceof PerspectiveCamera9 ? camera.fov : DEFAULT_REFERENCE_FOV2;
14096
14389
  const screenLong = Math.max(
14097
14390
  projectedDevicePixels(
14098
14391
  width * resolveScale2(scale),
@@ -14134,13 +14427,7 @@ function useTextTexture(text, width, height, style, scale, scaleMode, referenceF
14134
14427
  let nextTexture = null;
14135
14428
  const run = async () => {
14136
14429
  try {
14137
- const layout = textureSize(width, height);
14138
- const layoutLong = Math.max(layout.canvasWidth, layout.canvasHeight);
14139
- const drawScale = rasterLong / layoutLong;
14140
- const { font } = resolveTextFont({
14141
- ...style,
14142
- fontSize: resolvePositive(style.fontSize, DEFAULT_FONT_SIZE) * drawScale
14143
- });
14430
+ const { font } = resolveTextFont(style);
14144
14431
  try {
14145
14432
  await document.fonts?.load(font);
14146
14433
  } catch {
@@ -14369,7 +14656,7 @@ import {
14369
14656
  Matrix4 as Matrix45,
14370
14657
  Quaternion as Quaternion4,
14371
14658
  ShapeUtils,
14372
- Vector2 as Vector25,
14659
+ Vector2 as Vector26,
14373
14660
  Vector3 as Vector318
14374
14661
  } from "three";
14375
14662
  import { jsx as jsx35, jsxs as jsxs18 } from "react/jsx-runtime";
@@ -14407,7 +14694,7 @@ function intersects(a, b, c, d) {
14407
14694
  }
14408
14695
  function hasSelfIntersection(vertices) {
14409
14696
  const points = vertices.map(
14410
- (vertex) => new Vector25(vertex.unwrappedYaw, vertex.pitch)
14697
+ (vertex) => new Vector26(vertex.unwrappedYaw, vertex.pitch)
14411
14698
  );
14412
14699
  for (let index = 0; index < points.length; index += 1) {
14413
14700
  const nextIndex = (index + 1) % points.length;
@@ -14556,7 +14843,7 @@ function addSubdividedTriangle(positions, indices, a, b, c, worldToLocal, steps)
14556
14843
  }
14557
14844
  function makeFillGeometry(vertices, center) {
14558
14845
  const unwrapped = unwrapPolygonVertices(vertices);
14559
- const contour = unwrapped.map((vertex) => new Vector25(vertex.unwrappedYaw, vertex.pitch));
14846
+ const contour = unwrapped.map((vertex) => new Vector26(vertex.unwrappedYaw, vertex.pitch));
14560
14847
  const triangles = ShapeUtils.triangulateShape(contour, []);
14561
14848
  const positions = [];
14562
14849
  const indices = [];
@@ -16054,12 +16341,12 @@ import { useFrame as useFrame21, useThree as useThree26 } from "@react-three/fib
16054
16341
  import { useRef as useRef36 } from "react";
16055
16342
  import {
16056
16343
  DoubleSide as DoubleSide6,
16057
- Raycaster as Raycaster3,
16058
- Vector2 as Vector26,
16344
+ Raycaster as Raycaster4,
16345
+ Vector2 as Vector27,
16059
16346
  Vector3 as Vector321
16060
16347
  } from "three";
16061
16348
  import { jsx as jsx38, jsxs as jsxs19 } from "react/jsx-runtime";
16062
- var CENTER_NDC = new Vector26(0, 0);
16349
+ var CENTER_NDC = new Vector27(0, 0);
16063
16350
  var HUD_DISTANCE = 2;
16064
16351
  var RETICLE_SCALE = 1;
16065
16352
  var RETICLE_RENDER_ORDER = 1e5;
@@ -16145,7 +16432,7 @@ function WebVRReticle({
16145
16432
  const groupRef = useRef36(null);
16146
16433
  const ringMeshRef = useRef36(null);
16147
16434
  const directionRef = useRef36(new Vector321());
16148
- const raycasterRef = useRef36(new Raycaster3());
16435
+ const raycasterRef = useRef36(new Raycaster4());
16149
16436
  const hoveredRef = useRef36(null);
16150
16437
  const dwellRef = useRef36(0);
16151
16438
  const clickedRef = useRef36(false);
@@ -16315,12 +16602,12 @@ import { useContext as useContext26, useEffect as useEffect36, useMemo as useMem
16315
16602
  import {
16316
16603
  Mesh as Mesh6,
16317
16604
  OrthographicCamera,
16318
- PerspectiveCamera as PerspectiveCamera9,
16605
+ PerspectiveCamera as PerspectiveCamera10,
16319
16606
  PlaneGeometry as PlaneGeometry5,
16320
16607
  Scene,
16321
16608
  ShaderMaterial as ShaderMaterial3,
16322
16609
  StereoCamera as StereoCamera2,
16323
- Vector2 as Vector27,
16610
+ Vector2 as Vector28,
16324
16611
  Vector4 as Vector42,
16325
16612
  WebGLRenderTarget as WebGLRenderTarget2
16326
16613
  } from "three";
@@ -16358,7 +16645,7 @@ function createDistortionResources() {
16358
16645
  depthWrite: false,
16359
16646
  fragmentShader,
16360
16647
  uniforms: {
16361
- distortion: { value: new Vector27() },
16648
+ distortion: { value: new Vector28() },
16362
16649
  sourceTexture: { value: target.texture }
16363
16650
  },
16364
16651
  vertexShader
@@ -16406,7 +16693,7 @@ function WebVRStereoRenderer({
16406
16693
  const stereoRef = useRef37(stereo);
16407
16694
  stereoRef.current = stereo;
16408
16695
  const resources = useMemo25(createDistortionResources, []);
16409
- const drawingSizeRef = useRef37(new Vector27());
16696
+ const drawingSizeRef = useRef37(new Vector28());
16410
16697
  const viewportRef = useRef37(new Vector42());
16411
16698
  const scissorRef = useRef37(new Vector42());
16412
16699
  const originalFovRef = useRef37(null);
@@ -16426,7 +16713,7 @@ function WebVRStereoRenderer({
16426
16713
  useEffect36(() => {
16427
16714
  const originalRender = gl.render.bind(gl);
16428
16715
  gl.render = (sceneToRender, cameraToRender) => {
16429
- if (!stereoActiveRef.current || !(cameraToRender instanceof PerspectiveCamera9)) {
16716
+ if (!stereoActiveRef.current || !(cameraToRender instanceof PerspectiveCamera10)) {
16430
16717
  originalRender(sceneToRender, cameraToRender);
16431
16718
  return;
16432
16719
  }
@@ -16501,7 +16788,7 @@ function WebVRStereoRenderer({
16501
16788
  if (runtime) {
16502
16789
  runtime.stereoView.active = fallback;
16503
16790
  }
16504
- if (fallback && camera instanceof PerspectiveCamera9) {
16791
+ if (fallback && camera instanceof PerspectiveCamera10) {
16505
16792
  syncStereoCameras(
16506
16793
  stereoRef.current,
16507
16794
  camera,
@@ -16512,7 +16799,7 @@ function WebVRStereoRenderer({
16512
16799
  );
16513
16800
  return;
16514
16801
  }
16515
- if (originalFovRef.current !== null && camera instanceof PerspectiveCamera9) {
16802
+ if (originalFovRef.current !== null && camera instanceof PerspectiveCamera10) {
16516
16803
  camera.fov = originalFovRef.current;
16517
16804
  camera.updateProjectionMatrix();
16518
16805
  originalFovRef.current = null;