@cloudparse/up-miniapps-sdk 0.2.7 → 0.5.2

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.js CHANGED
@@ -5,6 +5,11 @@ import {
5
5
  handleRouteRequest
6
6
  } from "./chunk-S4UHRBSM.js";
7
7
 
8
+ // src/components/Map/MockLocationContext.tsx
9
+ import { createContext, useContext } from "react";
10
+ var MockLocationContext = createContext(null);
11
+ var useMockLocation = () => useContext(MockLocationContext);
12
+
8
13
  // src/lib/utils.ts
9
14
  import { clsx } from "clsx";
10
15
  import { twMerge } from "tailwind-merge";
@@ -348,6 +353,8 @@ var resolveHeightToPixels = (height, containerHeight) => {
348
353
  }
349
354
  return parseFloat(height);
350
355
  };
356
+ var CONTENT_EDGE_PULL_THRESHOLD_PX = 24;
357
+ var HANDLE_BAR_HEIGHT_PX = 32;
351
358
  var MapDrawer = forwardRef3(
352
359
  ({
353
360
  menuItems,
@@ -364,15 +371,20 @@ var MapDrawer = forwardRef3(
364
371
  clampedDragOffset,
365
372
  drawerPeekHeight,
366
373
  drawerFullHeight,
374
+ drawerAutoMaxHeight,
367
375
  containerHeight,
368
376
  menuContent,
369
377
  enableTouchDrag = false,
378
+ handleVisible = true,
379
+ onAutoContentHeightChange,
370
380
  showScrollToTopButton = true,
371
381
  scrollToTopThreshold = 300,
372
382
  safeAreaInsets = { bottom: 0, left: 0, right: 0, top: 0 }
373
383
  }, ref) => {
374
384
  const scrollContainerRef = useRef(null);
375
385
  const containerRef = useRef(null);
386
+ const handleBarRef = useRef(null);
387
+ const contentAreaRef = useRef(null);
376
388
  useImperativeHandle(
377
389
  ref,
378
390
  () => scrollContainerRef.current
@@ -385,11 +397,36 @@ var MapDrawer = forwardRef3(
385
397
  onChangeState("peek");
386
398
  } else if (state === "peek") {
387
399
  onChangeState("full");
400
+ } else if (state === "auto") {
401
+ onChangeState("closed");
388
402
  } else {
389
403
  onChangeState("peek");
390
404
  }
391
405
  }
392
406
  };
407
+ const handleHandleClickRef = useRef(handleHandleClick);
408
+ handleHandleClickRef.current = handleHandleClick;
409
+ const [autoContentPx, setAutoContentPx] = useState(0);
410
+ useEffect(() => {
411
+ if (state !== "auto") return;
412
+ const scrollEl = scrollContainerRef.current;
413
+ const contentAreaEl = contentAreaRef.current;
414
+ if (!scrollEl || !contentAreaEl) return;
415
+ const handleHeight = handleVisible ? handleBarRef.current?.offsetHeight ?? 0 : 0;
416
+ const contentEl = scrollEl.firstElementChild;
417
+ const measure = () => {
418
+ const contentAreaStyle = window.getComputedStyle(contentAreaEl);
419
+ const verticalPadding = parseFloat(contentAreaStyle.paddingTop || "0") + parseFloat(contentAreaStyle.paddingBottom || "0");
420
+ const contentHeight = contentEl?.scrollHeight ?? scrollEl.scrollHeight;
421
+ const px = handleHeight + verticalPadding + contentHeight;
422
+ setAutoContentPx(px);
423
+ onAutoContentHeightChange?.(px);
424
+ };
425
+ measure();
426
+ const ro = new ResizeObserver(measure);
427
+ ro.observe(contentEl ?? scrollEl);
428
+ return () => ro.disconnect();
429
+ }, [state, handleVisible, onAutoContentHeightChange]);
393
430
  const [showScrollTop, setShowScrollTop] = useState(false);
394
431
  const handleScroll = (e) => {
395
432
  const scrollTop = e.currentTarget.scrollTop;
@@ -406,6 +443,12 @@ var MapDrawer = forwardRef3(
406
443
  const isDraggingSheet = useRef(false);
407
444
  const hasDeterminedDirection = useRef(false);
408
445
  const isTouchOnContent = useRef(false);
446
+ const isTouchOnHandle = useRef(false);
447
+ const suppressNextClickRef = useRef(false);
448
+ const contentAtTopSinceY = useRef(null);
449
+ const contentAtBottomSinceY = useRef(null);
450
+ const dragStartYRef = useRef(null);
451
+ const dragBaseHeightPxRef = useRef(0);
409
452
  const stateRef = useRef(state);
410
453
  const touchStartYRef = useRef(touchStartY);
411
454
  const currentTouchYRef = useRef(currentTouchY);
@@ -427,8 +470,12 @@ var MapDrawer = forwardRef3(
427
470
  setCurrentTouchY(clientY);
428
471
  isDraggingSheet.current = false;
429
472
  hasDeterminedDirection.current = false;
473
+ contentAtTopSinceY.current = null;
474
+ contentAtBottomSinceY.current = null;
475
+ dragStartYRef.current = null;
430
476
  const target = e.target;
431
477
  isTouchOnContent.current = scrollContainerRef.current ? scrollContainerRef.current.contains(target) : false;
478
+ isTouchOnHandle.current = handleBarRef.current ? handleBarRef.current.contains(target) : false;
432
479
  };
433
480
  const handleNativeTouchMove = (e) => {
434
481
  const startY = touchStartYRef.current;
@@ -440,39 +487,86 @@ var MapDrawer = forwardRef3(
440
487
  const scrollHeight = scrollContainerRef.current ? scrollContainerRef.current.scrollHeight : 0;
441
488
  const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
442
489
  const currentState = stateRef.current;
490
+ const containerHForHeights = containerHeight || container.parentElement?.getBoundingClientRect().height || window.innerHeight;
491
+ const fullPxNow = resolveHeightToPixels(
492
+ drawerFullHeight || "85%",
493
+ containerHForHeights
494
+ );
495
+ const peekPxNow = resolveHeightToPixels(
496
+ drawerPeekHeight || "15%",
497
+ containerHForHeights
498
+ );
499
+ const closedPxNow = (handleVisible ? HANDLE_BAR_HEIGHT_PX : 0) + (currentState === "closed" ? safeAreaInsets.bottom : 0);
500
+ const autoMaxPxNow = resolveHeightToPixels(
501
+ drawerAutoMaxHeight || "100%",
502
+ containerHForHeights
503
+ );
504
+ const autoPxNow = Math.min(autoMaxPxNow, autoContentPx);
505
+ const baselineHeightPxForState = (s) => {
506
+ if (s === "full") return fullPxNow;
507
+ if (s === "peek") return peekPxNow;
508
+ if (s === "auto") return autoPxNow;
509
+ return closedPxNow;
510
+ };
511
+ const commitDrag = () => {
512
+ isDraggingSheet.current = true;
513
+ dragStartYRef.current = currentY;
514
+ dragBaseHeightPxRef.current = baselineHeightPxForState(currentState);
515
+ };
516
+ if (scrollTop <= 0) {
517
+ if (contentAtTopSinceY.current === null) {
518
+ contentAtTopSinceY.current = currentY;
519
+ }
520
+ } else {
521
+ contentAtTopSinceY.current = null;
522
+ }
523
+ if (isAtBottom) {
524
+ if (contentAtBottomSinceY.current === null) {
525
+ contentAtBottomSinceY.current = currentY;
526
+ }
527
+ } else {
528
+ contentAtBottomSinceY.current = null;
529
+ }
530
+ const pullPastTop = contentAtTopSinceY.current !== null ? currentY - contentAtTopSinceY.current : 0;
531
+ const pullPastBottom = contentAtBottomSinceY.current !== null ? contentAtBottomSinceY.current - currentY : 0;
443
532
  if (!hasDeterminedDirection.current) {
444
533
  if (!isTouchOnContent.current) {
445
- isDraggingSheet.current = true;
534
+ if (Math.abs(deltaY) < CONTENT_EDGE_PULL_THRESHOLD_PX) {
535
+ return;
536
+ }
537
+ commitDrag();
446
538
  hasDeterminedDirection.current = true;
447
539
  } else if (currentState !== "full") {
448
- if (deltaY > 0 && isAtBottom) {
449
- isDraggingSheet.current = true;
540
+ if (deltaY > 0 && isAtBottom && pullPastBottom >= CONTENT_EDGE_PULL_THRESHOLD_PX) {
541
+ commitDrag();
450
542
  hasDeterminedDirection.current = true;
451
- } else if (deltaY < 0 && scrollTop <= 0) {
452
- isDraggingSheet.current = true;
543
+ } else if (deltaY < 0 && scrollTop <= 0 && pullPastTop >= CONTENT_EDGE_PULL_THRESHOLD_PX) {
544
+ commitDrag();
453
545
  hasDeterminedDirection.current = true;
546
+ } else if (deltaY > 0 && isAtBottom || deltaY < 0 && scrollTop <= 0) {
547
+ return;
454
548
  } else {
455
549
  isDraggingSheet.current = false;
456
550
  hasDeterminedDirection.current = true;
457
551
  }
458
552
  } else {
459
- if (deltaY < 0 && scrollTop <= 0) {
460
- isDraggingSheet.current = true;
553
+ if (deltaY < 0 && scrollTop <= 0 && pullPastTop >= CONTENT_EDGE_PULL_THRESHOLD_PX) {
554
+ commitDrag();
461
555
  hasDeterminedDirection.current = true;
556
+ } else if (deltaY < 0 && scrollTop <= 0) {
557
+ return;
462
558
  } else {
463
559
  isDraggingSheet.current = false;
464
560
  hasDeterminedDirection.current = true;
465
561
  }
466
562
  }
467
563
  } else if (!isDraggingSheet.current && isTouchOnContent.current) {
468
- if (deltaY < 0 && scrollTop <= 0) {
469
- isDraggingSheet.current = true;
470
- setTouchStartY(currentY);
564
+ if (deltaY < 0 && scrollTop <= 0 && pullPastTop >= CONTENT_EDGE_PULL_THRESHOLD_PX) {
565
+ commitDrag();
471
566
  return;
472
567
  }
473
- if (deltaY > 0 && isAtBottom && currentState !== "full") {
474
- isDraggingSheet.current = true;
475
- setTouchStartY(currentY);
568
+ if (deltaY > 0 && isAtBottom && currentState !== "full" && pullPastBottom >= CONTENT_EDGE_PULL_THRESHOLD_PX) {
569
+ commitDrag();
476
570
  return;
477
571
  }
478
572
  }
@@ -482,33 +576,25 @@ var MapDrawer = forwardRef3(
482
576
  }
483
577
  const parentContainer = container.parentElement;
484
578
  if (parentContainer) {
485
- const containerH2 = containerHeight || parentContainer.getBoundingClientRect().height;
486
- const fullPx2 = resolveHeightToPixels(
487
- drawerFullHeight || "85%",
488
- containerH2
489
- );
490
- const peekPx2 = resolveHeightToPixels(
491
- drawerPeekHeight || "15%",
492
- containerH2
493
- );
494
- const closedPx = 32 + (currentState === "closed" ? safeAreaInsets.bottom : 0);
495
- const dragOffset = currentY - startY;
579
+ const dragOffset = currentY - (dragStartYRef.current ?? currentY);
496
580
  let localClamped = dragOffset;
497
581
  if (currentState === "full") {
498
582
  localClamped = Math.max(
499
583
  0,
500
- Math.min(fullPx2 - closedPx, dragOffset)
584
+ Math.min(fullPxNow - closedPxNow, dragOffset)
501
585
  );
502
586
  } else if (currentState === "peek") {
503
587
  localClamped = Math.max(
504
- -(fullPx2 - peekPx2),
505
- Math.min(peekPx2 - closedPx, dragOffset)
588
+ -(fullPxNow - peekPxNow),
589
+ Math.min(peekPxNow - closedPxNow, dragOffset)
506
590
  );
507
591
  } else if (currentState === "closed") {
508
592
  localClamped = Math.min(
509
593
  0,
510
- Math.max(-(fullPx2 - closedPx), dragOffset)
594
+ Math.max(-(fullPxNow - closedPxNow), dragOffset)
511
595
  );
596
+ } else if (currentState === "auto") {
597
+ localClamped = Math.max(0, dragOffset);
512
598
  }
513
599
  parentContainer.style.setProperty(
514
600
  "--drag-offset",
@@ -528,6 +614,13 @@ var MapDrawer = forwardRef3(
528
614
  return;
529
615
  }
530
616
  if (!isDraggingSheet.current) {
617
+ if (isTouchOnHandle.current) {
618
+ handleHandleClickRef.current();
619
+ suppressNextClickRef.current = true;
620
+ setTimeout(() => {
621
+ suppressNextClickRef.current = false;
622
+ }, 500);
623
+ }
531
624
  setTouchStartY(null);
532
625
  setCurrentTouchY(null);
533
626
  if (parentContainer) {
@@ -536,7 +629,8 @@ var MapDrawer = forwardRef3(
536
629
  return;
537
630
  }
538
631
  const touchEndY = e.changedTouches[0].clientY;
539
- const dragDistance = Math.abs(touchEndY - startY);
632
+ const dragStartY = dragStartYRef.current ?? startY;
633
+ const dragDistance = Math.abs(touchEndY - dragStartY);
540
634
  if (dragDistance < 10) {
541
635
  setTouchStartY(null);
542
636
  setCurrentTouchY(null);
@@ -546,9 +640,6 @@ var MapDrawer = forwardRef3(
546
640
  return;
547
641
  }
548
642
  const containerH2 = containerHeight || (parentContainer ? parentContainer.getBoundingClientRect().height : window.innerHeight);
549
- const containerTop = parentContainer ? parentContainer.getBoundingClientRect().top : 0;
550
- const touchYInContainer = touchEndY - containerTop;
551
- const drawerHeightFromBottom = containerH2 - touchYInContainer;
552
643
  const fullPx2 = resolveHeightToPixels(
553
644
  drawerFullHeight || "85%",
554
645
  containerH2
@@ -558,18 +649,30 @@ var MapDrawer = forwardRef3(
558
649
  containerH2
559
650
  );
560
651
  const currentState = stateRef.current;
561
- const closedPx = 32 + (currentState === "closed" ? safeAreaInsets.bottom : 0);
562
- const distToFull = Math.abs(drawerHeightFromBottom - fullPx2);
563
- const distToPeek = Math.abs(drawerHeightFromBottom - peekPx2);
652
+ const closedPx = (handleVisible ? HANDLE_BAR_HEIGHT_PX : 0) + (currentState === "closed" ? safeAreaInsets.bottom : 0);
653
+ const dragDelta = touchEndY - dragStartY;
654
+ const drawerHeightFromBottom = dragBaseHeightPxRef.current - dragDelta;
564
655
  const distToClosed = Math.abs(drawerHeightFromBottom - closedPx);
565
- const minDist = Math.min(distToFull, distToPeek, distToClosed);
566
- let targetState = "closed";
567
- if (minDist === distToFull) {
568
- targetState = "full";
569
- } else if (minDist === distToPeek) {
570
- targetState = "peek";
656
+ let targetState;
657
+ if (currentState === "auto") {
658
+ const autoMaxPx2 = resolveHeightToPixels(
659
+ drawerAutoMaxHeight || "100%",
660
+ containerH2
661
+ );
662
+ const autoPx2 = Math.min(autoMaxPx2, autoContentPx);
663
+ const distToAuto = Math.abs(drawerHeightFromBottom - autoPx2);
664
+ targetState = distToAuto <= distToClosed ? "auto" : "closed";
571
665
  } else {
572
- targetState = "closed";
666
+ const distToFull = Math.abs(drawerHeightFromBottom - fullPx2);
667
+ const distToPeek = Math.abs(drawerHeightFromBottom - peekPx2);
668
+ const minDist = Math.min(distToFull, distToPeek, distToClosed);
669
+ if (minDist === distToFull) {
670
+ targetState = "full";
671
+ } else if (minDist === distToPeek) {
672
+ targetState = "peek";
673
+ } else {
674
+ targetState = "closed";
675
+ }
573
676
  }
574
677
  setTouchStartY(null);
575
678
  setCurrentTouchY(null);
@@ -600,17 +703,26 @@ var MapDrawer = forwardRef3(
600
703
  containerHeight,
601
704
  drawerFullHeight,
602
705
  drawerPeekHeight,
706
+ drawerAutoMaxHeight,
603
707
  safeAreaInsets,
604
708
  onChangeState,
605
709
  setTouchStartY,
606
- setCurrentTouchY
710
+ setCurrentTouchY,
711
+ autoContentPx,
712
+ handleVisible
607
713
  ]);
608
- const isBackdropActive = state === "full";
714
+ const isBackdropActive = state === "full" || state === "auto";
609
715
  const backdropStyles = isBackdropActive ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none";
610
716
  const containerH = containerHeight || window.innerHeight;
611
717
  const fullPx = resolveHeightToPixels(drawerFullHeight || "85%", containerH);
612
718
  const peekPx = resolveHeightToPixels(drawerPeekHeight || "15%", containerH);
719
+ const autoMaxPx = resolveHeightToPixels(
720
+ drawerAutoMaxHeight || "100%",
721
+ containerH
722
+ );
613
723
  const shiftPx = fullPx - peekPx;
724
+ const autoPx = Math.min(autoMaxPx, autoContentPx);
725
+ const autoShiftPx = Math.max(fullPx - autoPx, 0);
614
726
  const getBottomDrawerTransform = () => {
615
727
  if (state === "full") {
616
728
  return "translateY(var(--drag-offset, 0px))";
@@ -618,7 +730,10 @@ var MapDrawer = forwardRef3(
618
730
  if (state === "peek") {
619
731
  return `translateY(calc(${shiftPx}px + var(--drag-offset, 0px)))`;
620
732
  }
621
- const closedVisibleHeight = 32;
733
+ if (state === "auto") {
734
+ return `translateY(calc(${autoShiftPx}px + var(--drag-offset, 0px)))`;
735
+ }
736
+ const closedVisibleHeight = handleVisible ? HANDLE_BAR_HEIGHT_PX : 0;
622
737
  const closedShiftPx = fullPx - closedVisibleHeight;
623
738
  return `translateY(calc(${closedShiftPx}px + var(--drag-offset, 0px)))`;
624
739
  };
@@ -752,17 +867,34 @@ var MapDrawer = forwardRef3(
752
867
  ref: containerRef,
753
868
  style: {
754
869
  bottom: "0px",
755
- height: typeof drawerFullHeight === "number" ? `${drawerFullHeight}px` : drawerFullHeight || "85%",
870
+ // Box height must stay exactly fullPx for "closed"/"peek"/
871
+ // "full" — every other transform calc in this component
872
+ // assumes the box IS fullPx tall. Only "auto" grows it (up to
873
+ // autoMaxPx) when content needs more room than fullPx offers;
874
+ // getBottomDrawerTransform()'s auto branch is derived to match
875
+ // (shift = max(fullPx - autoPx, 0), so a taller box needs zero
876
+ // extra shift to fully reveal itself).
877
+ height: `${state === "auto" ? Math.max(fullPx, autoPx) : fullPx}px`,
756
878
  marginBottom: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px",
757
879
  transform: getBottomDrawerTransform(),
758
- transition: isDragging ? "none" : "transform 300ms ease-out, margin-bottom 250ms ease-in-out"
880
+ // `height` is animated too (not just transform/margin-bottom):
881
+ // entering/leaving "auto" with content taller than fullPx
882
+ // changes the box's actual CSS height, and without this it
883
+ // would snap instantly while transform eases in over 300ms —
884
+ // a visible pop of content before the slide animation catches
885
+ // up.
886
+ transition: isDragging ? "none" : "transform 300ms ease-out, height 300ms ease-out, margin-bottom 250ms ease-in-out"
759
887
  },
760
888
  children: [
761
- /* @__PURE__ */ jsx3(
889
+ handleVisible && /* @__PURE__ */ jsx3(
762
890
  "div",
763
891
  {
764
892
  className: "w-full h-8 flex flex-col items-center justify-center cursor-pointer hover:bg-white/5 rounded-t-2xl transition-colors border-b border-white/5",
765
- onClick: handleHandleClick,
893
+ onClick: () => {
894
+ if (suppressNextClickRef.current) return;
895
+ handleHandleClick();
896
+ },
897
+ ref: handleBarRef,
766
898
  children: /* @__PURE__ */ jsx3("div", { className: "w-12 h-1 bg-white/25 rounded-full" })
767
899
  }
768
900
  ),
@@ -770,6 +902,7 @@ var MapDrawer = forwardRef3(
770
902
  "div",
771
903
  {
772
904
  className: "flex-1 flex flex-col overflow-hidden px-4 pb-4 pt-2 transition-[margin-top] duration-250 ease-in-out relative",
905
+ ref: contentAreaRef,
773
906
  style: {
774
907
  marginTop: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px"
775
908
  },
@@ -1158,7 +1291,8 @@ var MapRightControls = ({
1158
1291
  customRecenterButton,
1159
1292
  customCompassButton,
1160
1293
  drawerPeekHeight,
1161
- insets = { bottom: 0, left: 0, right: 0, top: 0 }
1294
+ insets = { bottom: 0, left: 0, right: 0, top: 0 },
1295
+ bottomOffset = 0
1162
1296
  }) => {
1163
1297
  const aboveControls = customControls.filter(
1164
1298
  (c) => c.position === "above-recenter"
@@ -1170,7 +1304,7 @@ var MapRightControls = ({
1170
1304
  const closedHeightOffset = "32px";
1171
1305
  const activeEtaHeight = etaData ? etaBarHeight : drawerState === "closed" ? insets.bottom : 0;
1172
1306
  const peekStyle = typeof drawerPeekHeight === "number" ? `${drawerPeekHeight}px` : drawerPeekHeight || "15%";
1173
- const controlsBottom = side === "bottom" ? drawerState === "closed" ? `calc(${closedHeightOffset} + 1rem + ${activeEtaHeight}px - var(--drag-offset, 0px))` : `calc(${peekStyle} + 1rem + ${etaData ? etaBarHeight : 0}px - var(--drag-offset, 0px))` : `calc(1rem + ${insets.bottom}px)`;
1307
+ const controlsBottom = side === "bottom" ? drawerState === "closed" ? `calc(${closedHeightOffset} + 1rem + ${activeEtaHeight}px + ${bottomOffset}px - var(--drag-offset, 0px))` : `calc(${peekStyle} + 1rem + ${etaData ? etaBarHeight : 0}px + ${bottomOffset}px - var(--drag-offset, 0px))` : `calc(1rem + ${insets.bottom}px + ${bottomOffset}px)`;
1174
1308
  const isControlsVisible = side !== "bottom" || drawerState !== "full";
1175
1309
  return /* @__PURE__ */ jsxs4(
1176
1310
  "div",
@@ -1299,15 +1433,15 @@ var MapRightControls = ({
1299
1433
 
1300
1434
  // src/components/Map/MapContext.tsx
1301
1435
  import {
1302
- createContext,
1436
+ createContext as createContext2,
1303
1437
  useCallback,
1304
- useContext,
1438
+ useContext as useContext2,
1305
1439
  useMemo,
1306
1440
  useRef as useRef2,
1307
1441
  useState as useState2
1308
1442
  } from "react";
1309
1443
  import { jsx as jsx7 } from "react/jsx-runtime";
1310
- var MapControlsContext = createContext(
1444
+ var MapControlsContext = createContext2(
1311
1445
  null
1312
1446
  );
1313
1447
  var SDKMapProvider = ({
@@ -1321,6 +1455,10 @@ var SDKMapProvider = ({
1321
1455
  const setDrawerState = useCallback((val) => {
1322
1456
  setDrawerStateInternal(val);
1323
1457
  }, []);
1458
+ const [drawerHandleVisible, setDrawerHandleVisibleState] = useState2(true);
1459
+ const setDrawerHandleVisible = useCallback((val) => {
1460
+ setDrawerHandleVisibleState((prev) => prev === val ? prev : val);
1461
+ }, []);
1324
1462
  const [hasControls, setHasControlsState] = useState2(false);
1325
1463
  const setHasControls = useCallback((val) => {
1326
1464
  setHasControlsState((prev) => prev === val ? prev : val);
@@ -1347,6 +1485,7 @@ var SDKMapProvider = ({
1347
1485
  }, []);
1348
1486
  const centerCallbackRef = useRef2(null);
1349
1487
  const resetBearingCallbackRef = useRef2(null);
1488
+ const flyToCallbackRef = useRef2(null);
1350
1489
  const centerOnUser = useCallback((params) => {
1351
1490
  if (centerCallbackRef.current) {
1352
1491
  centerCallbackRef.current(params);
@@ -1357,6 +1496,14 @@ var SDKMapProvider = ({
1357
1496
  resetBearingCallbackRef.current(params);
1358
1497
  }
1359
1498
  }, []);
1499
+ const flyTo = useCallback(
1500
+ (coords, params) => {
1501
+ if (flyToCallbackRef.current) {
1502
+ flyToCallbackRef.current(coords, params);
1503
+ }
1504
+ },
1505
+ []
1506
+ );
1360
1507
  const _registerCenterCallback = useCallback(
1361
1508
  (cb) => {
1362
1509
  centerCallbackRef.current = cb;
@@ -1369,6 +1516,12 @@ var SDKMapProvider = ({
1369
1516
  },
1370
1517
  []
1371
1518
  );
1519
+ const _registerFlyToCallback = useCallback(
1520
+ (cb) => {
1521
+ flyToCallbackRef.current = cb;
1522
+ },
1523
+ []
1524
+ );
1372
1525
  const setBearing = useCallback((b) => {
1373
1526
  setBearingState(b);
1374
1527
  }, []);
@@ -1378,18 +1531,22 @@ var SDKMapProvider = ({
1378
1531
  const contextValue = useMemo(
1379
1532
  () => ({
1380
1533
  _registerCenterCallback,
1534
+ _registerFlyToCallback,
1381
1535
  _registerResetBearingCallback,
1382
1536
  bearing,
1383
1537
  centerOnUser,
1384
1538
  drawerContentRef,
1539
+ drawerHandleVisible,
1385
1540
  drawerState,
1386
1541
  etaData,
1542
+ flyTo,
1387
1543
  hasControls,
1388
1544
  insets,
1389
1545
  isFollowing,
1390
1546
  pitch,
1391
1547
  resetBearing,
1392
1548
  setBearing,
1549
+ setDrawerHandleVisible,
1393
1550
  setDrawerState,
1394
1551
  setEtaData,
1395
1552
  setHasControls,
@@ -1399,16 +1556,20 @@ var SDKMapProvider = ({
1399
1556
  }),
1400
1557
  [
1401
1558
  _registerCenterCallback,
1559
+ _registerFlyToCallback,
1402
1560
  _registerResetBearingCallback,
1403
1561
  bearing,
1404
1562
  centerOnUser,
1563
+ drawerHandleVisible,
1405
1564
  drawerState,
1406
1565
  etaData,
1566
+ flyTo,
1407
1567
  hasControls,
1408
1568
  isFollowing,
1409
1569
  pitch,
1410
1570
  resetBearing,
1411
1571
  setBearing,
1572
+ setDrawerHandleVisible,
1412
1573
  setDrawerState,
1413
1574
  setEtaData,
1414
1575
  setHasControls,
@@ -1420,7 +1581,7 @@ var SDKMapProvider = ({
1420
1581
  return /* @__PURE__ */ jsx7(MapControlsContext.Provider, { value: contextValue, children });
1421
1582
  };
1422
1583
  var useMapControls = () => {
1423
- const context = useContext(MapControlsContext);
1584
+ const context = useContext2(MapControlsContext);
1424
1585
  if (!context) {
1425
1586
  throw new Error("useMapControls must be used within a MapControlsProvider");
1426
1587
  }
@@ -1428,7 +1589,7 @@ var useMapControls = () => {
1428
1589
  };
1429
1590
 
1430
1591
  // src/components/Map/components/MapControls.tsx
1431
- import { useCallback as useCallback2, useContext as useContext2, useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
1592
+ import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
1432
1593
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1433
1594
  var resolveHeightToPixels2 = (height, containerHeight) => {
1434
1595
  if (typeof height === "number") {
@@ -1455,15 +1616,17 @@ var MapControls = ({
1455
1616
  customCompassButton,
1456
1617
  drawerPeekHeight,
1457
1618
  drawerFullHeight,
1619
+ drawerAutoMaxHeight,
1458
1620
  drawerContentRef,
1459
1621
  menuContent,
1460
1622
  children,
1461
1623
  enableTouchDrag = false,
1462
1624
  showScrollToTopButton = true,
1463
1625
  scrollToTopThreshold = 300,
1626
+ controlsBottomOffset = 0,
1464
1627
  isInternal = false
1465
1628
  }) => {
1466
- const mapContext = useContext2(MapControlsContext);
1629
+ const mapContext = useContext3(MapControlsContext);
1467
1630
  const contextRef = mapContext?.drawerContentRef;
1468
1631
  const mergedRef = useCallback2(
1469
1632
  (node) => {
@@ -1542,6 +1705,7 @@ var MapControls = ({
1542
1705
  const resolvedEtaData = etaData !== void 0 ? etaData : mapContext?.etaData;
1543
1706
  const [touchStartY, setTouchStartY] = useState3(null);
1544
1707
  const [currentTouchY, setCurrentTouchY] = useState3(null);
1708
+ const [autoContentPx, setAutoContentPx] = useState3(0);
1545
1709
  useEffect2(() => {
1546
1710
  if (typeof window === "undefined") return;
1547
1711
  const checkLayout = () => {
@@ -1563,8 +1727,14 @@ var MapControls = ({
1563
1727
  const dragOffset = isDragging ? currentTouchY - touchStartY : 0;
1564
1728
  const fullPx = resolveHeightToPixels2(drawerFullHeight || "85%", mapHeight);
1565
1729
  const peekPx = resolveHeightToPixels2(drawerPeekHeight || "15%", mapHeight);
1730
+ const autoMaxPx = resolveHeightToPixels2(
1731
+ drawerAutoMaxHeight || "100%",
1732
+ mapHeight
1733
+ );
1734
+ const autoPx = Math.min(autoMaxPx, autoContentPx);
1566
1735
  const bottomInset = mapContext?.insets.bottom || 0;
1567
- const closedPx = 32 + (drawerState === "closed" ? bottomInset : 0);
1736
+ const handleVisible = mapContext ? mapContext.drawerHandleVisible : true;
1737
+ const closedPx = (handleVisible ? 32 : 0) + (drawerState === "closed" ? bottomInset : 0);
1568
1738
  let clampedDragOffset = dragOffset;
1569
1739
  if (drawerState === "full") {
1570
1740
  clampedDragOffset = Math.max(0, Math.min(fullPx - closedPx, dragOffset));
@@ -1577,12 +1747,16 @@ var MapControls = ({
1577
1747
  );
1578
1748
  } else if (drawerState === "closed") {
1579
1749
  clampedDragOffset = Math.min(0, Math.max(-(fullPx - closedPx), dragOffset));
1750
+ } else if (drawerState === "auto") {
1751
+ clampedDragOffset = Math.max(0, dragOffset);
1580
1752
  }
1581
- let baseHeight = 32;
1753
+ let baseHeight = handleVisible ? 32 : 0;
1582
1754
  if (drawerState === "peek") {
1583
1755
  baseHeight = peekPx;
1584
1756
  } else if (drawerState === "full") {
1585
1757
  baseHeight = fullPx;
1758
+ } else if (drawerState === "auto") {
1759
+ baseHeight = autoPx;
1586
1760
  }
1587
1761
  const currentDrawerHeight = baseHeight - clampedDragOffset;
1588
1762
  const startFadeHeight = peekPx + 20;
@@ -1612,14 +1786,17 @@ var MapControls = ({
1612
1786
  clampedDragOffset,
1613
1787
  containerHeight: mapHeight,
1614
1788
  currentTouchY,
1789
+ drawerAutoMaxHeight,
1615
1790
  drawerFullHeight,
1616
1791
  drawerPeekHeight,
1617
1792
  enableTouchDrag,
1618
1793
  etaBarHeight,
1619
1794
  etaData: resolvedEtaData,
1795
+ handleVisible,
1620
1796
  isDragging,
1621
1797
  menuContent: children || menuContent,
1622
1798
  menuItems,
1799
+ onAutoContentHeightChange: setAutoContentPx,
1623
1800
  onChangeState: setDrawerState,
1624
1801
  ref: mergedRef,
1625
1802
  safeAreaInsets: mapContext?.insets,
@@ -1635,6 +1812,7 @@ var MapControls = ({
1635
1812
  /* @__PURE__ */ jsx8(
1636
1813
  MapRightControls,
1637
1814
  {
1815
+ bottomOffset: controlsBottomOffset,
1638
1816
  clampedDragOffset,
1639
1817
  customCompassButton,
1640
1818
  customControls,
@@ -1672,7 +1850,7 @@ var MapControls = ({
1672
1850
  };
1673
1851
 
1674
1852
  // src/components/Map/components/MapMenu.tsx
1675
- import { useContext as useContext3 } from "react";
1853
+ import { useContext as useContext4 } from "react";
1676
1854
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1677
1855
  var MapMenu = ({ children }) => {
1678
1856
  return /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-1.5", children });
@@ -1686,7 +1864,7 @@ var MapMenuItem = ({
1686
1864
  disabled = false,
1687
1865
  children
1688
1866
  }) => {
1689
- const context = useContext3(MapControlsContext);
1867
+ const context = useContext4(MapControlsContext);
1690
1868
  const handleClick = (e) => {
1691
1869
  if (disabled) return;
1692
1870
  if (onPress) onPress();
@@ -1903,6 +2081,7 @@ var useLiveRouting = ({
1903
2081
  const lastCalcOrigin = useRef5(null);
1904
2082
  const lastCalcDest = useRef5(null);
1905
2083
  const isRequestInProgress = useRef5(false);
2084
+ const lastResetDestKey = useRef5(null);
1906
2085
  const getDistance = useCallback4(
1907
2086
  (p1, p2) => {
1908
2087
  const R = 6371e3;
@@ -1978,6 +2157,11 @@ var useLiveRouting = ({
1978
2157
  },
1979
2158
  [profile, getDistance]
1980
2159
  );
2160
+ useEffect4(() => {
2161
+ if (initialMode === "navigation") {
2162
+ setMode("navigation");
2163
+ }
2164
+ }, [initialMode]);
1981
2165
  useEffect4(() => {
1982
2166
  if (!enabled || !destination || !calculationOrigin) {
1983
2167
  if (!enabled || !destination) {
@@ -1985,10 +2169,17 @@ var useLiveRouting = ({
1985
2169
  setTotalDistance(null);
1986
2170
  lastCalcOrigin.current = null;
1987
2171
  lastCalcDest.current = null;
2172
+ lastResetDestKey.current = null;
2173
+ setMode(initialMode);
1988
2174
  }
1989
2175
  return;
1990
2176
  }
1991
2177
  const destChanged = !lastCalcDest.current || lastCalcDest.current[0] !== destination[0] || lastCalcDest.current[1] !== destination[1];
2178
+ const destKey = `${destination[0]},${destination[1]}`;
2179
+ if (lastResetDestKey.current !== destKey) {
2180
+ lastResetDestKey.current = destKey;
2181
+ setMode(initialMode);
2182
+ }
1992
2183
  if (destChanged) {
1993
2184
  calculate(calculationOrigin, destination, true);
1994
2185
  } else if (lastCalcOrigin.current) {
@@ -2005,6 +2196,7 @@ var useLiveRouting = ({
2005
2196
  trackingLocation,
2006
2197
  destination,
2007
2198
  enabled,
2199
+ initialMode,
2008
2200
  recalculateThreshold,
2009
2201
  calculate,
2010
2202
  getDistance
@@ -2050,6 +2242,99 @@ var useRoute = (origin, destination, profile = "driving") => {
2050
2242
  return { error, isCalculating, route };
2051
2243
  };
2052
2244
 
2245
+ // src/components/Map/hooks/useJourney.ts
2246
+ import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
2247
+ var useJourney = (options = {}) => {
2248
+ const { navigation, onAbort, onModeChange } = options;
2249
+ const [progNav, setProgNav] = useState6(null);
2250
+ const [isAborted, setIsAborted] = useState6(false);
2251
+ useEffect5(() => {
2252
+ const unsubCreate = sdk.on("SDK_NAVIGATION_CREATE", (payload) => {
2253
+ setIsAborted(false);
2254
+ setProgNav(payload);
2255
+ });
2256
+ const unsubStart = sdk.on("SDK_NAVIGATION_START", () => {
2257
+ setIsAborted(false);
2258
+ setProgNav((prev) => prev ? { ...prev, mode: "navigation" } : null);
2259
+ });
2260
+ const unsubAbort = sdk.on("SDK_NAVIGATION_ABORT", () => {
2261
+ setIsAborted(true);
2262
+ setProgNav(null);
2263
+ });
2264
+ return () => {
2265
+ unsubCreate();
2266
+ unsubStart();
2267
+ unsubAbort();
2268
+ };
2269
+ }, []);
2270
+ const effectiveOptions = progNav || navigation;
2271
+ const internalNav = useLiveRouting({
2272
+ destination: effectiveOptions?.destination || null,
2273
+ enabled: !isAborted && !!effectiveOptions?.destination,
2274
+ initialMode: effectiveOptions?.mode || "preview",
2275
+ origin: effectiveOptions?.origin,
2276
+ profile: effectiveOptions?.profile || "driving",
2277
+ recalculateThreshold: effectiveOptions?.recalculateThreshold || 50
2278
+ });
2279
+ const prevDest = useRef6(null);
2280
+ const currentDest = effectiveOptions?.destination;
2281
+ useEffect5(() => {
2282
+ if (currentDest && (!prevDest.current || prevDest.current[0] !== currentDest[0] || prevDest.current[1] !== currentDest[1])) {
2283
+ setIsAborted(false);
2284
+ }
2285
+ prevDest.current = currentDest;
2286
+ }, [currentDest]);
2287
+ const onModeChangeRef = useRef6(onModeChange);
2288
+ onModeChangeRef.current = onModeChange;
2289
+ useEffect5(() => {
2290
+ onModeChangeRef.current?.(internalNav.mode);
2291
+ }, [internalNav.mode]);
2292
+ const isActive = !isAborted && !!effectiveOptions?.destination;
2293
+ const distanceRemainingMeters = isActive ? internalNav.currentDistanceMeters : null;
2294
+ const totalDistanceMeters = isActive ? internalNav.totalDistance : null;
2295
+ const routeDurationSeconds = internalNav.route?.durationSeconds ?? null;
2296
+ const remainingMeters = distanceRemainingMeters ?? 0;
2297
+ const distanceRemainingKm = remainingMeters / 1e3;
2298
+ const etaMinutes = (() => {
2299
+ if (!routeDurationSeconds || !totalDistanceMeters || !distanceRemainingMeters)
2300
+ return 0;
2301
+ const ratio = distanceRemainingMeters / totalDistanceMeters;
2302
+ return Math.ceil(routeDurationSeconds * ratio / 60);
2303
+ })();
2304
+ const progress = totalDistanceMeters && totalDistanceMeters > 0 ? Math.min(
2305
+ 100,
2306
+ Math.max(
2307
+ 0,
2308
+ (totalDistanceMeters - remainingMeters) / totalDistanceMeters * 100
2309
+ )
2310
+ ) : 0;
2311
+ const cancel = useCallback5(() => {
2312
+ setIsAborted(true);
2313
+ setProgNav(null);
2314
+ sdk.navigation.abort();
2315
+ if (onAbort) onAbort();
2316
+ }, [onAbort]);
2317
+ const start = useCallback5(() => {
2318
+ sdk.navigation.start();
2319
+ internalNav.start();
2320
+ }, [internalNav.start]);
2321
+ return {
2322
+ cancel,
2323
+ distanceRemainingKm,
2324
+ distanceRemainingMeters,
2325
+ error: isActive ? internalNav.error : null,
2326
+ etaMinutes,
2327
+ isActive,
2328
+ isCalculating: isActive ? internalNav.isCalculating : false,
2329
+ mode: isActive ? internalNav.mode : "preview",
2330
+ progress,
2331
+ recalculate: internalNav.recalculate,
2332
+ route: isActive ? internalNav.route : null,
2333
+ start,
2334
+ totalDistanceMeters
2335
+ };
2336
+ };
2337
+
2053
2338
  // src/components/Map/hooks/useTrimmedRoute.ts
2054
2339
  import { useMemo as useMemo3 } from "react";
2055
2340
  var useTrimmedRoute = (route, userLocation) => {
@@ -2127,19 +2412,81 @@ var RouteLayer = ({
2127
2412
  // src/components/Map/Map.tsx
2128
2413
  import { Map as MapLibre, MapProvider } from "@vis.gl/react-maplibre";
2129
2414
  import React4, {
2130
- useCallback as useCallback5,
2131
- useContext as useContext4,
2132
- useEffect as useEffect6,
2133
- useMemo as useMemo5,
2134
- useRef as useRef6,
2135
- useState as useState7
2415
+ useCallback as useCallback6,
2416
+ useContext as useContext5,
2417
+ useEffect as useEffect7,
2418
+ useMemo as useMemo6,
2419
+ useRef as useRef7,
2420
+ useState as useState8
2136
2421
  } from "react";
2137
2422
  import "maplibre-gl/dist/maplibre-gl.css";
2138
2423
 
2424
+ // src/components/Map/utils/geoCircle.ts
2425
+ var EARTH_RADIUS_METERS = 6371e3;
2426
+ var buildGeoCircle = (center, radiusMeters, points = 64) => {
2427
+ const [lng, lat] = center;
2428
+ const latRad = lat * Math.PI / 180;
2429
+ const coordinates = [];
2430
+ for (let i = 0; i <= points; i++) {
2431
+ const angle = i / points * 2 * Math.PI;
2432
+ const dLat = radiusMeters * Math.cos(angle) / EARTH_RADIUS_METERS * (180 / Math.PI);
2433
+ const dLng = radiusMeters * Math.sin(angle) / (EARTH_RADIUS_METERS * Math.cos(latRad)) * (180 / Math.PI);
2434
+ coordinates.push([lng + dLng, lat + dLat]);
2435
+ }
2436
+ return {
2437
+ geometry: {
2438
+ coordinates: [coordinates],
2439
+ type: "Polygon"
2440
+ },
2441
+ properties: {},
2442
+ type: "Feature"
2443
+ };
2444
+ };
2445
+
2446
+ // src/components/Map/layers/MockRadiusLayer.tsx
2447
+ import { Layer as Layer2, Source as Source2 } from "@vis.gl/react-maplibre";
2448
+ import { useMemo as useMemo4 } from "react";
2449
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
2450
+ var MockRadiusLayer = ({
2451
+ center,
2452
+ radiusMeters,
2453
+ color = "#39ff88"
2454
+ }) => {
2455
+ const data = useMemo4(
2456
+ () => buildGeoCircle(center, radiusMeters),
2457
+ [center, radiusMeters]
2458
+ );
2459
+ return /* @__PURE__ */ jsxs8(Source2, { data, id: "sdk-mock-radius-source", type: "geojson", children: [
2460
+ /* @__PURE__ */ jsx11(
2461
+ Layer2,
2462
+ {
2463
+ id: "sdk-mock-radius-fill",
2464
+ paint: {
2465
+ "fill-color": color,
2466
+ "fill-opacity": 0.12
2467
+ },
2468
+ type: "fill"
2469
+ }
2470
+ ),
2471
+ /* @__PURE__ */ jsx11(
2472
+ Layer2,
2473
+ {
2474
+ id: "sdk-mock-radius-outline",
2475
+ paint: {
2476
+ "line-color": color,
2477
+ "line-dasharray": [2, 2],
2478
+ "line-width": 2
2479
+ },
2480
+ type: "line"
2481
+ }
2482
+ )
2483
+ ] });
2484
+ };
2485
+
2139
2486
  // src/components/Map/layers/POILayers.tsx
2140
- import { Layer as Layer2, Source as Source2, useMap } from "@vis.gl/react-maplibre";
2141
- import { useEffect as useEffect5, useMemo as useMemo4, useState as useState6 } from "react";
2142
- import { jsx as jsx11 } from "react/jsx-runtime";
2487
+ import { Layer as Layer3, Source as Source3, useMap } from "@vis.gl/react-maplibre";
2488
+ import { useEffect as useEffect6, useMemo as useMemo5, useState as useState7 } from "react";
2489
+ import { jsx as jsx12 } from "react/jsx-runtime";
2143
2490
  function createEmojiIcon(emoji, size = 64) {
2144
2491
  const canvas = document.createElement("canvas");
2145
2492
  canvas.width = size;
@@ -2152,13 +2499,13 @@ function createEmojiIcon(emoji, size = 64) {
2152
2499
  return ctx.getImageData(0, 0, size, size);
2153
2500
  }
2154
2501
  var POILayers = ({ pois }) => {
2155
- const [loadedImages, setLoadedImages] = useState6(0);
2502
+ const [loadedImages, setLoadedImages] = useState7(0);
2156
2503
  const { current: map } = useMap();
2157
- const glPois = useMemo4(
2504
+ const glPois = useMemo5(
2158
2505
  () => pois.filter((p) => !p.displayMode || p.displayMode === "gl"),
2159
2506
  [pois]
2160
2507
  );
2161
- useEffect5(() => {
2508
+ useEffect6(() => {
2162
2509
  if (!map) return;
2163
2510
  const glMap = map.getMap();
2164
2511
  const defaults = [
@@ -2198,7 +2545,7 @@ var POILayers = ({ pois }) => {
2198
2545
  setLoadedImages((prev) => prev + updates);
2199
2546
  }
2200
2547
  }, [map, glPois]);
2201
- const geojson = useMemo4(
2548
+ const geojson = useMemo5(
2202
2549
  () => ({
2203
2550
  features: glPois.map((poi) => ({
2204
2551
  geometry: {
@@ -2216,8 +2563,8 @@ var POILayers = ({ pois }) => {
2216
2563
  }),
2217
2564
  [glPois]
2218
2565
  );
2219
- return /* @__PURE__ */ jsx11(Source2, { data: geojson, id: "sdk-pois", type: "geojson", children: /* @__PURE__ */ jsx11(
2220
- Layer2,
2566
+ return /* @__PURE__ */ jsx12(Source3, { data: geojson, id: "sdk-pois", type: "geojson", children: /* @__PURE__ */ jsx12(
2567
+ Layer3,
2221
2568
  {
2222
2569
  id: "sdk-poi-layer",
2223
2570
  layout: {
@@ -2253,11 +2600,11 @@ var POILayers = ({ pois }) => {
2253
2600
 
2254
2601
  // src/components/Map/layers/POIMarkers.tsx
2255
2602
  import { Marker } from "@vis.gl/react-maplibre";
2256
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2603
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2257
2604
  var POIMarkers = ({ pois, onClick }) => {
2258
2605
  const markerPois = pois.filter((p) => p.displayMode === "marker");
2259
2606
  if (markerPois.length === 0) return null;
2260
- return /* @__PURE__ */ jsx12(Fragment3, { children: markerPois.map((poi) => /* @__PURE__ */ jsx12(
2607
+ return /* @__PURE__ */ jsx13(Fragment3, { children: markerPois.map((poi) => /* @__PURE__ */ jsx13(
2261
2608
  Marker,
2262
2609
  {
2263
2610
  anchor: "bottom",
@@ -2267,7 +2614,7 @@ var POIMarkers = ({ pois, onClick }) => {
2267
2614
  e.originalEvent.stopPropagation();
2268
2615
  if (onClick) onClick(poi);
2269
2616
  },
2270
- children: poi.markerComponent ? poi.markerComponent : /* @__PURE__ */ jsxs8(
2617
+ children: poi.markerComponent ? poi.markerComponent : /* @__PURE__ */ jsxs9(
2271
2618
  "div",
2272
2619
  {
2273
2620
  style: {
@@ -2276,7 +2623,7 @@ var POIMarkers = ({ pois, onClick }) => {
2276
2623
  transform: "transition-transform duration-200 hover:scale-110"
2277
2624
  },
2278
2625
  children: [
2279
- /* @__PURE__ */ jsx12(
2626
+ /* @__PURE__ */ jsx13(
2280
2627
  "div",
2281
2628
  {
2282
2629
  style: {
@@ -2289,7 +2636,7 @@ var POIMarkers = ({ pois, onClick }) => {
2289
2636
  justifyContent: "center",
2290
2637
  width: "32px"
2291
2638
  },
2292
- children: poi.emoji ? /* @__PURE__ */ jsx12("span", { style: { fontSize: "16px" }, children: poi.emoji }) : /* @__PURE__ */ jsx12(
2639
+ children: poi.emoji ? /* @__PURE__ */ jsx13("span", { style: { fontSize: "16px" }, children: poi.emoji }) : /* @__PURE__ */ jsx13(
2293
2640
  "div",
2294
2641
  {
2295
2642
  style: {
@@ -2302,7 +2649,7 @@ var POIMarkers = ({ pois, onClick }) => {
2302
2649
  )
2303
2650
  }
2304
2651
  ),
2305
- /* @__PURE__ */ jsx12(
2652
+ /* @__PURE__ */ jsx13(
2306
2653
  "div",
2307
2654
  {
2308
2655
  style: {
@@ -2325,19 +2672,19 @@ var POIMarkers = ({ pois, onClick }) => {
2325
2672
 
2326
2673
  // src/components/Map/layers/UserLocationLayer.tsx
2327
2674
  import { Marker as Marker2 } from "@vis.gl/react-maplibre";
2328
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2675
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
2329
2676
  var UserLocationLayer = ({
2330
2677
  forcedLocation,
2331
2678
  heading
2332
2679
  }) => {
2333
2680
  if (!forcedLocation) return null;
2334
- return /* @__PURE__ */ jsx13(
2681
+ return /* @__PURE__ */ jsx14(
2335
2682
  Marker2,
2336
2683
  {
2337
2684
  anchor: "center",
2338
2685
  latitude: forcedLocation[1],
2339
2686
  longitude: forcedLocation[0],
2340
- children: /* @__PURE__ */ jsxs9(
2687
+ children: /* @__PURE__ */ jsxs10(
2341
2688
  "div",
2342
2689
  {
2343
2690
  style: {
@@ -2347,7 +2694,7 @@ var UserLocationLayer = ({
2347
2694
  position: "relative"
2348
2695
  },
2349
2696
  children: [
2350
- /* @__PURE__ */ jsx13(
2697
+ /* @__PURE__ */ jsx14(
2351
2698
  "div",
2352
2699
  {
2353
2700
  style: {
@@ -2360,7 +2707,7 @@ var UserLocationLayer = ({
2360
2707
  }
2361
2708
  }
2362
2709
  ),
2363
- heading !== void 0 && heading !== null && /* @__PURE__ */ jsx13(
2710
+ heading !== void 0 && heading !== null && /* @__PURE__ */ jsx14(
2364
2711
  "div",
2365
2712
  {
2366
2713
  style: {
@@ -2370,13 +2717,13 @@ var UserLocationLayer = ({
2370
2717
  width: "60px",
2371
2718
  zIndex: 0
2372
2719
  },
2373
- children: /* @__PURE__ */ jsxs9(
2720
+ children: /* @__PURE__ */ jsxs10(
2374
2721
  "svg",
2375
2722
  {
2376
2723
  style: { height: "100%", width: "100%" },
2377
2724
  viewBox: "0 0 100 100",
2378
2725
  children: [
2379
- /* @__PURE__ */ jsx13("defs", { children: /* @__PURE__ */ jsxs9(
2726
+ /* @__PURE__ */ jsx14("defs", { children: /* @__PURE__ */ jsxs10(
2380
2727
  "radialGradient",
2381
2728
  {
2382
2729
  cx: "50%",
@@ -2386,7 +2733,7 @@ var UserLocationLayer = ({
2386
2733
  id: "grad1",
2387
2734
  r: "50%",
2388
2735
  children: [
2389
- /* @__PURE__ */ jsx13(
2736
+ /* @__PURE__ */ jsx14(
2390
2737
  "stop",
2391
2738
  {
2392
2739
  offset: "0%",
@@ -2396,7 +2743,7 @@ var UserLocationLayer = ({
2396
2743
  }
2397
2744
  }
2398
2745
  ),
2399
- /* @__PURE__ */ jsx13(
2746
+ /* @__PURE__ */ jsx14(
2400
2747
  "stop",
2401
2748
  {
2402
2749
  offset: "100%",
@@ -2409,7 +2756,7 @@ var UserLocationLayer = ({
2409
2756
  ]
2410
2757
  }
2411
2758
  ) }),
2412
- /* @__PURE__ */ jsx13(
2759
+ /* @__PURE__ */ jsx14(
2413
2760
  "path",
2414
2761
  {
2415
2762
  d: "M 50 50 L 35 0 A 50 50 0 0 1 65 0 Z",
@@ -2421,7 +2768,7 @@ var UserLocationLayer = ({
2421
2768
  )
2422
2769
  }
2423
2770
  ),
2424
- /* @__PURE__ */ jsx13(
2771
+ /* @__PURE__ */ jsx14(
2425
2772
  "div",
2426
2773
  {
2427
2774
  style: {
@@ -2435,7 +2782,7 @@ var UserLocationLayer = ({
2435
2782
  width: "20px",
2436
2783
  zIndex: 1
2437
2784
  },
2438
- children: /* @__PURE__ */ jsx13(
2785
+ children: /* @__PURE__ */ jsx14(
2439
2786
  "div",
2440
2787
  {
2441
2788
  style: {
@@ -2449,7 +2796,7 @@ var UserLocationLayer = ({
2449
2796
  )
2450
2797
  }
2451
2798
  ),
2452
- /* @__PURE__ */ jsx13("style", { children: `@keyframes pulse { 0% { transform: scale(0.95); opacity: 0.7; } 70% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(0.95); opacity: 0; } }` })
2799
+ /* @__PURE__ */ jsx14("style", { children: `@keyframes pulse { 0% { transform: scale(0.95); opacity: 0.7; } 70% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(0.95); opacity: 0; } }` })
2453
2800
  ]
2454
2801
  }
2455
2802
  )
@@ -2458,7 +2805,7 @@ var UserLocationLayer = ({
2458
2805
  };
2459
2806
 
2460
2807
  // src/components/Map/Map.tsx
2461
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
2808
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
2462
2809
  var MapInternal = ({
2463
2810
  initialViewState = {
2464
2811
  latitude: 45.90583599233422,
@@ -2477,6 +2824,7 @@ var MapInternal = ({
2477
2824
  onLoad,
2478
2825
  showUserLocation = true,
2479
2826
  followUser: initialFollowUser = false,
2827
+ lockToUser = false,
2480
2828
  showUserLocationButton = true,
2481
2829
  renderUserLocation,
2482
2830
  pois = [],
@@ -2495,67 +2843,44 @@ var MapInternal = ({
2495
2843
  controlProps = {},
2496
2844
  enableTouchDrag = false
2497
2845
  }) => {
2498
- const mapControls = useContext4(MapControlsContext);
2499
- const mapRef = useRef6(null);
2846
+ const mapControls = useContext5(MapControlsContext);
2847
+ const mockLocation = useContext5(MockLocationContext);
2848
+ const mapRef = useRef7(null);
2500
2849
  const userLocationState = useUserLocation();
2501
- const [progNav, setProgNav] = useState7(null);
2502
- const [isAborted, setIsAborted] = useState7(false);
2503
- useEffect6(() => {
2504
- const unsubCreate = sdk.on("SDK_NAVIGATION_CREATE", (payload) => {
2505
- setIsAborted(false);
2506
- setProgNav(payload);
2507
- });
2508
- const unsubStart = sdk.on("SDK_NAVIGATION_START", () => {
2509
- setIsAborted(false);
2510
- setProgNav((prev) => prev ? { ...prev, mode: "navigation" } : null);
2511
- });
2512
- const unsubAbort = sdk.on("SDK_NAVIGATION_ABORT", () => {
2513
- setIsAborted(true);
2514
- setProgNav(null);
2515
- });
2516
- return () => {
2517
- unsubCreate();
2518
- unsubStart();
2519
- unsubAbort();
2520
- };
2521
- }, []);
2522
- const effectiveNavOptions = progNav || navigation;
2523
- const internalNav = useLiveRouting({
2524
- destination: effectiveNavOptions?.destination || null,
2525
- enabled: !!effectiveNavOptions?.destination,
2526
- initialMode: effectiveNavOptions?.mode || "preview",
2527
- origin: effectiveNavOptions?.origin,
2528
- profile: effectiveNavOptions?.profile || "driving",
2529
- recalculateThreshold: effectiveNavOptions?.recalculateThreshold || 50
2850
+ const journey = useJourney({
2851
+ navigation,
2852
+ onModeChange: navigation ? onNavigationModeChange : void 0
2530
2853
  });
2531
- useEffect6(() => {
2532
- if (onNavigationModeChange && navigation) {
2533
- onNavigationModeChange(internalNav.mode);
2534
- }
2535
- }, [internalNav.mode, onNavigationModeChange, navigation]);
2536
- const prevDest = useRef6(null);
2537
- const currentDest = effectiveNavOptions?.destination;
2538
- useEffect6(() => {
2539
- if (currentDest) {
2540
- if (!prevDest.current || prevDest.current[0] !== currentDest[0] || prevDest.current[1] !== currentDest[1]) {
2541
- setIsAborted(false);
2542
- }
2543
- }
2544
- prevDest.current = currentDest;
2545
- }, [currentDest]);
2546
- const navigationData = !isAborted ? effectiveNavOptions ? internalNav : externalNavigationData : void 0;
2547
- const route = !isAborted ? effectiveNavOptions ? internalNav.route?.geometry : externalRoute : void 0;
2548
- const showNavigationUI = !isAborted && effectiveNavOptions?.showUI !== false && (effectiveNavOptions ? !!effectiveNavOptions.destination : externalShowNavigationUI);
2854
+ const hasIntegratedNav = journey.isActive;
2855
+ const navigationData = hasIntegratedNav ? {
2856
+ currentDistanceMeters: journey.distanceRemainingMeters,
2857
+ mode: journey.mode,
2858
+ route: journey.route,
2859
+ start: journey.start,
2860
+ totalDistance: journey.totalDistanceMeters
2861
+ } : externalNavigationData;
2862
+ const route = hasIntegratedNav ? journey.route?.geometry : externalRoute;
2863
+ const showNavigationUI = hasIntegratedNav ? externalShowNavigationUI !== false && navigation?.showUI !== false : externalShowNavigationUI;
2549
2864
  const mode = navigationData?.mode || "preview";
2550
2865
  const resolvedInitialFollow = mapControls ? mapControls.isFollowing : initialFollowUser;
2551
- const isFollowingRef = useRef6(resolvedInitialFollow);
2552
- const isFlyingRef = useRef6(false);
2553
- const [localIsFollowingUI, setLocalIsFollowingUI] = useState7(
2866
+ const isFollowingRef = useRef7(resolvedInitialFollow);
2867
+ const isFlyingRef = useRef7(false);
2868
+ const flyingGenerationRef = useRef7(0);
2869
+ const lockFlying = useCallback6(() => {
2870
+ isFlyingRef.current = true;
2871
+ return ++flyingGenerationRef.current;
2872
+ }, []);
2873
+ const releaseFlyingIfCurrent = useCallback6((flightId) => {
2874
+ if (flyingGenerationRef.current === flightId) {
2875
+ isFlyingRef.current = false;
2876
+ }
2877
+ }, []);
2878
+ const [localIsFollowingUI, setLocalIsFollowingUI] = useState8(
2554
2879
  resolvedInitialFollow
2555
2880
  );
2556
2881
  const isFollowingUI = mapControls ? mapControls.isFollowing : localIsFollowingUI;
2557
2882
  const setIsFollowingCtx = mapControls?.setIsFollowing;
2558
- const setFollowMode = useCallback5(
2883
+ const setFollowMode = useCallback6(
2559
2884
  (follow) => {
2560
2885
  isFollowingRef.current = follow;
2561
2886
  setLocalIsFollowingUI(follow);
@@ -2565,7 +2890,7 @@ var MapInternal = ({
2565
2890
  },
2566
2891
  [setIsFollowingCtx]
2567
2892
  );
2568
- useEffect6(() => {
2893
+ useEffect7(() => {
2569
2894
  if (mapControls && mapControls.isFollowing !== isFollowingRef.current) {
2570
2895
  isFollowingRef.current = mapControls.isFollowing;
2571
2896
  }
@@ -2574,13 +2899,13 @@ var MapInternal = ({
2574
2899
  if (pois.length > 0 && !mergedInteractiveLayerIds.includes("sdk-poi-layer")) {
2575
2900
  mergedInteractiveLayerIds.push("sdk-poi-layer");
2576
2901
  }
2577
- const prevInitialFollow = useRef6(initialFollowUser);
2578
- useEffect6(() => {
2902
+ const prevInitialFollow = useRef7(initialFollowUser);
2903
+ useEffect7(() => {
2579
2904
  if (initialFollowUser !== prevInitialFollow.current) {
2580
2905
  setFollowMode(initialFollowUser);
2581
2906
  prevInitialFollow.current = initialFollowUser;
2582
2907
  if (initialFollowUser && userLocationState.coords && mapRef.current) {
2583
- isFlyingRef.current = true;
2908
+ const flightId = lockFlying();
2584
2909
  mapRef.current.flyTo({
2585
2910
  center: userLocationState.coords,
2586
2911
  duration: 1e3,
@@ -2588,14 +2913,20 @@ var MapInternal = ({
2588
2913
  zoom: Math.max(mapRef.current.getZoom(), 16)
2589
2914
  });
2590
2915
  setTimeout(() => {
2591
- isFlyingRef.current = false;
2916
+ releaseFlyingIfCurrent(flightId);
2592
2917
  }, 1050);
2593
2918
  }
2594
2919
  }
2595
- }, [initialFollowUser, setFollowMode, userLocationState.coords]);
2596
- const lastAutoLockedRouteRef = useRef6(null);
2597
- const lastModeRef = useRef6(null);
2598
- useEffect6(() => {
2920
+ }, [
2921
+ initialFollowUser,
2922
+ setFollowMode,
2923
+ userLocationState.coords,
2924
+ lockFlying,
2925
+ releaseFlyingIfCurrent
2926
+ ]);
2927
+ const lastAutoLockedRouteRef = useRef7(null);
2928
+ const lastModeRef = useRef7(null);
2929
+ useEffect7(() => {
2599
2930
  if (!route || !mapRef.current) {
2600
2931
  if (!route) lastAutoLockedRouteRef.current = null;
2601
2932
  return;
@@ -2603,9 +2934,11 @@ var MapInternal = ({
2603
2934
  const isNewRoute = route !== lastAutoLockedRouteRef.current;
2604
2935
  const modeChanged = lastModeRef.current !== mode;
2605
2936
  if (mode === "navigation") {
2606
- setFollowMode(true);
2937
+ if (modeChanged || isNewRoute) {
2938
+ setFollowMode(true);
2939
+ }
2607
2940
  if ((modeChanged || isNewRoute) && userLocationState.coords && mapRef.current) {
2608
- isFlyingRef.current = true;
2941
+ const flightId = lockFlying();
2609
2942
  mapRef.current.flyTo({
2610
2943
  center: userLocationState.coords,
2611
2944
  duration: 1e3,
@@ -2614,7 +2947,7 @@ var MapInternal = ({
2614
2947
  // Zoomed in on user for active guidance
2615
2948
  });
2616
2949
  setTimeout(() => {
2617
- isFlyingRef.current = false;
2950
+ releaseFlyingIfCurrent(flightId);
2618
2951
  }, 1050);
2619
2952
  }
2620
2953
  } else if (mode === "preview" && isNewRoute) {
@@ -2641,8 +2974,15 @@ var MapInternal = ({
2641
2974
  }
2642
2975
  lastAutoLockedRouteRef.current = route;
2643
2976
  lastModeRef.current = mode;
2644
- }, [route, mode, setFollowMode, userLocationState.coords]);
2645
- useEffect6(() => {
2977
+ }, [
2978
+ route,
2979
+ mode,
2980
+ setFollowMode,
2981
+ userLocationState.coords,
2982
+ lockFlying,
2983
+ releaseFlyingIfCurrent
2984
+ ]);
2985
+ useEffect7(() => {
2646
2986
  if (isFollowingRef.current && !isFlyingRef.current && userLocationState.coords && mapRef.current) {
2647
2987
  mapRef.current.easeTo({
2648
2988
  center: userLocationState.coords,
@@ -2671,15 +3011,13 @@ var MapInternal = ({
2671
3011
  100,
2672
3012
  Math.max(0, (totalDist - remainingMeters) / totalDist * 100)
2673
3013
  ) : 0;
2674
- const showEtaData = navigationData && route ? {
3014
+ const showEtaData = showNavigationUI && navigationData && route ? {
2675
3015
  distanceRemainingKm: kmRemaining,
2676
3016
  etaMins,
2677
3017
  label: mode === "navigation" ? "Walking Guidance Active" : "Route Preview",
2678
3018
  mode,
2679
3019
  onAbort: () => {
2680
- setIsAborted(true);
2681
- setProgNav(null);
2682
- sdk.navigation.abort();
3020
+ journey.cancel();
2683
3021
  if (onNavigationAbort) {
2684
3022
  onNavigationAbort();
2685
3023
  }
@@ -2688,48 +3026,19 @@ var MapInternal = ({
2688
3026
  progress: progressValue
2689
3027
  } : void 0;
2690
3028
  const setEtaDataCtx = mapControls?.setEtaData;
2691
- useEffect6(() => {
3029
+ useEffect7(() => {
2692
3030
  if (setEtaDataCtx) {
2693
3031
  setEtaDataCtx(showEtaData);
2694
3032
  }
2695
3033
  }, [showEtaData, setEtaDataCtx]);
2696
3034
  const setDrawerStateCtx = mapControls?.setDrawerState;
2697
- useEffect6(() => {
3035
+ useEffect7(() => {
2698
3036
  if (setDrawerStateCtx && propDrawerState !== void 0) {
2699
3037
  setDrawerStateCtx(propDrawerState);
2700
3038
  }
2701
3039
  }, [propDrawerState, setDrawerStateCtx]);
2702
- const defaultMenuItems = useMemo5(
2703
- () => [
2704
- {
2705
- icon: /* @__PURE__ */ jsx14(
2706
- "svg",
2707
- {
2708
- className: "w-5 h-5",
2709
- fill: "none",
2710
- stroke: "currentColor",
2711
- strokeWidth: "2",
2712
- viewBox: "0 0 24 24",
2713
- children: /* @__PURE__ */ jsx14(
2714
- "path",
2715
- {
2716
- d: "M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4",
2717
- strokeLinecap: "round",
2718
- strokeLinejoin: "round"
2719
- }
2720
- )
2721
- }
2722
- ),
2723
- id: "walking",
2724
- label: "Walking Mode",
2725
- onPress: () => {
2726
- }
2727
- }
2728
- ],
2729
- []
2730
- );
2731
- const resolvedMenuItems = menuItems || defaultMenuItems;
2732
- const { mapLibreChildren, menuContentFromChildren } = useMemo5(() => {
3040
+ const resolvedMenuItems = menuItems ?? [];
3041
+ const { mapLibreChildren, menuContentFromChildren } = useMemo6(() => {
2733
3042
  const mapLibreChildren2 = [];
2734
3043
  const extractedMenuItems = [];
2735
3044
  React4.Children.forEach(children, (child) => {
@@ -2761,10 +3070,14 @@ var MapInternal = ({
2761
3070
  mapLibreChildren2.push(child);
2762
3071
  }
2763
3072
  });
2764
- const menuContentFromChildren2 = extractedMenuItems.length > 0 ? /* @__PURE__ */ jsx14(MapMenu, { children: extractedMenuItems }) : null;
3073
+ const menuContentFromChildren2 = extractedMenuItems.length > 0 ? /* @__PURE__ */ jsx15(MapMenu, { children: extractedMenuItems }) : null;
2765
3074
  return { mapLibreChildren: mapLibreChildren2, menuContentFromChildren: menuContentFromChildren2 };
2766
3075
  }, [children]);
2767
3076
  const handleMapClick = (e) => {
3077
+ if (mockLocation?.isPlacingLocation) {
3078
+ mockLocation.placeLocation([e.lngLat.lng, e.lngLat.lat]);
3079
+ return;
3080
+ }
2768
3081
  const clickedFeatures = e.features || [];
2769
3082
  if (onPoiClick) {
2770
3083
  const poiFeature = clickedFeatures.find(
@@ -2785,26 +3098,52 @@ var MapInternal = ({
2785
3098
  }
2786
3099
  if (onClick) onClick(e);
2787
3100
  };
2788
- const handleCenterOnUser = useCallback5(
3101
+ const handleCenterOnUser = useCallback6(
2789
3102
  (payload) => {
2790
3103
  if (userLocationState.coords && mapRef.current) {
2791
3104
  setFollowMode(true);
2792
- isFlyingRef.current = true;
3105
+ const flightId = lockFlying();
2793
3106
  mapRef.current.flyTo({
2794
3107
  center: userLocationState.coords,
2795
3108
  duration: 1e3,
2796
3109
  essential: true,
2797
3110
  pitch: payload?.pitch || 0,
2798
- zoom: Math.max(mapRef.current.getZoom(), 16)
3111
+ zoom: Math.max(mapRef.current.getZoom(), 16),
3112
+ ...payload?.bearing !== void 0 ? { bearing: payload.bearing } : {}
2799
3113
  });
2800
3114
  setTimeout(() => {
2801
- isFlyingRef.current = false;
3115
+ releaseFlyingIfCurrent(flightId);
2802
3116
  }, 1050);
2803
3117
  }
2804
3118
  },
2805
- [userLocationState.coords, setFollowMode]
3119
+ [
3120
+ userLocationState.coords,
3121
+ setFollowMode,
3122
+ lockFlying,
3123
+ releaseFlyingIfCurrent
3124
+ ]
2806
3125
  );
2807
- const handleResetBearing = useCallback5((params) => {
3126
+ const handleFlyTo = useCallback6(
3127
+ (coords, params) => {
3128
+ const map = mapRef.current;
3129
+ if (!map) return;
3130
+ setFollowMode(false);
3131
+ const flightId = lockFlying();
3132
+ map.flyTo({
3133
+ center: coords,
3134
+ essential: true,
3135
+ pitch: params?.pitch ?? 0,
3136
+ zoom: params?.zoom ?? map.getZoom(),
3137
+ // Omit entirely (rather than defaulting to a fixed number) when
3138
+ // not given, so MapLibre computes its own distance-based duration.
3139
+ ...params?.duration !== void 0 ? { duration: params.duration } : {},
3140
+ ...params?.bearing !== void 0 ? { bearing: params.bearing } : {}
3141
+ });
3142
+ map.once("moveend", () => releaseFlyingIfCurrent(flightId));
3143
+ },
3144
+ [setFollowMode, lockFlying, releaseFlyingIfCurrent]
3145
+ );
3146
+ const handleResetBearing = useCallback6((params) => {
2808
3147
  if (mapRef.current) {
2809
3148
  mapRef.current.easeTo({
2810
3149
  bearing: 0,
@@ -2814,34 +3153,35 @@ var MapInternal = ({
2814
3153
  });
2815
3154
  }
2816
3155
  }, []);
2817
- useEffect6(() => {
3156
+ useEffect7(() => {
2818
3157
  if (mapControls) {
2819
3158
  mapControls._registerCenterCallback(handleCenterOnUser);
2820
3159
  mapControls._registerResetBearingCallback(handleResetBearing);
3160
+ mapControls._registerFlyToCallback(handleFlyTo);
2821
3161
  }
2822
- }, [mapControls, handleCenterOnUser, handleResetBearing]);
2823
- const handleMove = useCallback5(() => {
3162
+ }, [mapControls, handleCenterOnUser, handleResetBearing, handleFlyTo]);
3163
+ const handleMove = useCallback6(() => {
2824
3164
  if (mapRef.current && mapControls) {
2825
3165
  mapControls.setBearing(mapRef.current.getBearing());
2826
3166
  mapControls.setPitch(mapRef.current.getPitch());
2827
3167
  }
2828
3168
  }, [mapControls]);
2829
3169
  const handleUserInteraction = () => {
3170
+ if (lockToUser) return;
2830
3171
  if (isFollowingRef.current) {
2831
- console.log("[SDK:Map] Manual interaction detected. Halting tracking.");
2832
3172
  setFollowMode(false);
2833
3173
  if (mapRef.current) {
2834
3174
  mapRef.current.stop();
2835
3175
  }
2836
3176
  }
2837
3177
  };
2838
- return /* @__PURE__ */ jsxs10(
3178
+ return /* @__PURE__ */ jsxs11(
2839
3179
  "div",
2840
3180
  {
2841
- className: `w-full h-full overflow-hidden relative ${className}`,
3181
+ className: `w-full h-full overflow-hidden relative ${mockLocation?.isPlacingLocation ? "cursor-crosshair" : ""} ${className}`,
2842
3182
  style,
2843
3183
  children: [
2844
- /* @__PURE__ */ jsxs10(
3184
+ /* @__PURE__ */ jsxs11(
2845
3185
  MapLibre,
2846
3186
  {
2847
3187
  attributionControl: false,
@@ -2863,6 +3203,7 @@ var MapInternal = ({
2863
3203
  },
2864
3204
  onMouseDown: handleUserInteraction,
2865
3205
  onMove: handleMove,
3206
+ onRotateStart: handleUserInteraction,
2866
3207
  onTouchStart: handleUserInteraction,
2867
3208
  onWheel: handleUserInteraction,
2868
3209
  ref: mapRef,
@@ -2870,23 +3211,30 @@ var MapInternal = ({
2870
3211
  showUserLocation && userLocationState.coords && (renderUserLocation ? renderUserLocation({
2871
3212
  coords: userLocationState.coords,
2872
3213
  heading: userLocationState.heading
2873
- }) : /* @__PURE__ */ jsx14(
3214
+ }) : /* @__PURE__ */ jsx15(
2874
3215
  UserLocationLayer,
2875
3216
  {
2876
3217
  forcedLocation: userLocationState.coords,
2877
3218
  heading: userLocationState.heading
2878
3219
  }
2879
3220
  )),
2880
- displayRoute && /* @__PURE__ */ jsx14(RouteLayer, { geometry: displayRoute }),
2881
- pois.length > 0 && /* @__PURE__ */ jsxs10(Fragment4, { children: [
2882
- /* @__PURE__ */ jsx14(POILayers, { pois }),
2883
- /* @__PURE__ */ jsx14(POIMarkers, { onClick: onPoiClick, pois })
3221
+ mockLocation && mockLocation.radiusMeters > 0 && userLocationState.coords && /* @__PURE__ */ jsx15(
3222
+ MockRadiusLayer,
3223
+ {
3224
+ center: userLocationState.coords,
3225
+ radiusMeters: mockLocation.radiusMeters
3226
+ }
3227
+ ),
3228
+ displayRoute && /* @__PURE__ */ jsx15(RouteLayer, { geometry: displayRoute }),
3229
+ pois.length > 0 && /* @__PURE__ */ jsxs11(Fragment4, { children: [
3230
+ /* @__PURE__ */ jsx15(POILayers, { pois }),
3231
+ /* @__PURE__ */ jsx15(POIMarkers, { onClick: onPoiClick, pois })
2884
3232
  ] }),
2885
3233
  mapLibreChildren
2886
3234
  ]
2887
3235
  }
2888
3236
  ),
2889
- showControls && mapControls && !mapControls.hasControls && /* @__PURE__ */ jsx14(
3237
+ showControls && mapControls && !mapControls.hasControls && /* @__PURE__ */ jsx15(
2890
3238
  MapControls,
2891
3239
  {
2892
3240
  drawerContentRef,
@@ -2906,14 +3254,14 @@ var MapInternal = ({
2906
3254
  var SDKMapProviderWrapper = ({
2907
3255
  children
2908
3256
  }) => {
2909
- const context = useContext4(MapControlsContext);
3257
+ const context = useContext5(MapControlsContext);
2910
3258
  if (context) {
2911
- return /* @__PURE__ */ jsx14(Fragment4, { children });
3259
+ return /* @__PURE__ */ jsx15(Fragment4, { children });
2912
3260
  }
2913
- return /* @__PURE__ */ jsx14(SDKMapProvider, { children });
3261
+ return /* @__PURE__ */ jsx15(SDKMapProvider, { children });
2914
3262
  };
2915
3263
  var Map2 = (props) => {
2916
- return /* @__PURE__ */ jsx14(SDKMapProviderWrapper, { children: /* @__PURE__ */ jsx14(MapProvider, { children: /* @__PURE__ */ jsx14(MapInternal, { ...props }) }) });
3264
+ return /* @__PURE__ */ jsx15(SDKMapProviderWrapper, { children: /* @__PURE__ */ jsx15(MapProvider, { children: /* @__PURE__ */ jsx15(MapInternal, { ...props }) }) });
2917
3265
  };
2918
3266
 
2919
3267
  // src/components/MapWrapper.tsx
@@ -2930,7 +3278,7 @@ function renderMap(containerId, props = {}) {
2930
3278
  }
2931
3279
 
2932
3280
  // package.json
2933
- var version = "0.2.7";
3281
+ var version = "0.5.2";
2934
3282
 
2935
3283
  // src/sdk.ts
2936
3284
  function deepFreeze(obj) {
@@ -3133,8 +3481,11 @@ var MiniAppSDK = class {
3133
3481
  getFidelityHistory: (params) => this.bridge.send("GET_FIDELITY_HISTORY", params),
3134
3482
  /**
3135
3483
  * Triggers the native fidelity history view.
3484
+ * @param options.isDark Theme to render the native view in. Pass the
3485
+ * mini-app's current theme explicitly — the native history screen is a
3486
+ * standalone view and does not read it from `updateContext`.
3136
3487
  */
3137
- showHistory: () => this.bridge.send("SHOW_FIDELITY_HISTORY"),
3488
+ showHistory: (options) => this.bridge.send("SHOW_FIDELITY_HISTORY", options),
3138
3489
  /**
3139
3490
  * Authorize spending fidelity points (FPoints) via Two-Phase Authorization.
3140
3491
  * @param intentUuid UUID generated by your backend for the transaction.
@@ -3269,8 +3620,8 @@ var MiniAppSDK = class {
3269
3620
  var sdk = deepFreeze(new MiniAppSDK());
3270
3621
 
3271
3622
  // src/components/BridgeMocker.tsx
3272
- import { useCallback as useCallback6, useEffect as useEffect7, useRef as useRef7, useState as useState8 } from "react";
3273
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3623
+ import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo7, useRef as useRef8, useState as useState9 } from "react";
3624
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3274
3625
  var getBearing = (p1, p2) => {
3275
3626
  const lat1 = p1[1] * Math.PI / 180;
3276
3627
  const lat2 = p2[1] * Math.PI / 180;
@@ -3289,16 +3640,19 @@ var BridgeMocker = ({
3289
3640
  mockJWTToken,
3290
3641
  children
3291
3642
  }) => {
3292
- const [autoMove, setAutoMove] = useState8(isMoving);
3293
- const [location, setLocation] = useState8(initialLocation);
3294
- const [heading, setHeading] = useState8(0);
3295
- const [speedMultiplier, setSpeedMultiplier] = useState8(simulationSpeed);
3296
- const [isReady, setIsReady] = useState8(false);
3297
- const [isMinimized, setIsMinimized] = useState8(true);
3298
- const streamIntervalRef = useRef7(null);
3299
- const pathIndexRef = useRef7(0);
3300
- const activeConfigRef = useRef7(null);
3301
- const stateRef = useRef7({
3643
+ const [autoMove, setAutoMove] = useState9(isMoving);
3644
+ const [location, setLocation] = useState9(initialLocation);
3645
+ const [heading, setHeading] = useState9(0);
3646
+ const [speedMultiplier, setSpeedMultiplier] = useState9(simulationSpeed);
3647
+ const [isReady, setIsReady] = useState9(false);
3648
+ const [isMinimized, setIsMinimized] = useState9(true);
3649
+ const [radiusMeters, setRadiusMeters] = useState9(0);
3650
+ const [radiusUnit, setRadiusUnit] = useState9("m");
3651
+ const [isPlacingLocation, setIsPlacingLocation] = useState9(false);
3652
+ const streamIntervalRef = useRef8(null);
3653
+ const pathIndexRef = useRef8(0);
3654
+ const activeConfigRef = useRef8(null);
3655
+ const stateRef = useRef8({
3302
3656
  autoMove,
3303
3657
  heading,
3304
3658
  location,
@@ -3310,36 +3664,51 @@ var BridgeMocker = ({
3310
3664
  simulationPath,
3311
3665
  speedMultiplier
3312
3666
  });
3313
- const pushLocationUpdate = useCallback6(() => {
3314
- const currentState = stateRef.current;
3315
- const payload = {
3316
- accelerometer: null,
3317
- heading: {
3318
- accuracy: 3,
3319
- magneticHeading: currentState.heading,
3320
- trueHeading: currentState.heading
3321
- },
3322
- location: {
3323
- coords: {
3324
- accuracy: 10,
3325
- altitude: 0,
3326
- altitudeAccuracy: null,
3327
- heading: currentState.heading,
3328
- latitude: currentState.location[1],
3329
- longitude: currentState.location[0],
3330
- speed: currentState.autoMove ? 1.5 * currentState.speedMultiplier : 0
3667
+ const pushLocationUpdateFor = useCallback7(
3668
+ (coords, headingDeg) => {
3669
+ const currentState = stateRef.current;
3670
+ const payload = {
3671
+ accelerometer: null,
3672
+ heading: {
3673
+ accuracy: 3,
3674
+ magneticHeading: headingDeg,
3675
+ trueHeading: headingDeg
3676
+ },
3677
+ location: {
3678
+ coords: {
3679
+ accuracy: 10,
3680
+ altitude: 0,
3681
+ altitudeAccuracy: null,
3682
+ heading: headingDeg,
3683
+ latitude: coords[1],
3684
+ longitude: coords[0],
3685
+ speed: currentState.autoMove ? 1.5 * currentState.speedMultiplier : 0
3686
+ },
3687
+ timestamp: Date.now()
3331
3688
  },
3332
3689
  timestamp: Date.now()
3333
- },
3334
- timestamp: Date.now()
3335
- };
3336
- window.__MiniAppBridge?.onNativeMessage({
3337
- eventName: "LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
3338
- payload,
3339
- type: "EVENT"
3340
- });
3341
- }, []);
3342
- useEffect7(() => {
3690
+ };
3691
+ window.__MiniAppBridge?.onNativeMessage({
3692
+ eventName: "LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
3693
+ payload,
3694
+ type: "EVENT"
3695
+ });
3696
+ },
3697
+ []
3698
+ );
3699
+ const pushLocationUpdate = useCallback7(() => {
3700
+ const currentState = stateRef.current;
3701
+ pushLocationUpdateFor(currentState.location, currentState.heading);
3702
+ }, [pushLocationUpdateFor]);
3703
+ const placeLocation = useCallback7(
3704
+ (coords) => {
3705
+ setLocation(coords);
3706
+ setIsPlacingLocation(false);
3707
+ pushLocationUpdateFor(coords, stateRef.current.heading);
3708
+ },
3709
+ [pushLocationUpdateFor]
3710
+ );
3711
+ useEffect8(() => {
3343
3712
  stateRef.current = {
3344
3713
  autoMove,
3345
3714
  heading,
@@ -3360,19 +3729,19 @@ var BridgeMocker = ({
3360
3729
  speedMultiplier,
3361
3730
  mockJWTToken
3362
3731
  ]);
3363
- useEffect7(() => {
3732
+ useEffect8(() => {
3364
3733
  setAutoMove(isMoving);
3365
3734
  }, [isMoving]);
3366
- useEffect7(() => {
3735
+ useEffect8(() => {
3367
3736
  setSpeedMultiplier(simulationSpeed);
3368
3737
  }, [simulationSpeed]);
3369
- useEffect7(() => {
3738
+ useEffect8(() => {
3370
3739
  if (simulationPath && simulationPath.length > 0) {
3371
3740
  pathIndexRef.current = 0;
3372
3741
  setLocation(simulationPath[0]);
3373
3742
  }
3374
3743
  }, [simulationPath]);
3375
- useEffect7(() => {
3744
+ useEffect8(() => {
3376
3745
  if (streamIntervalRef.current && activeConfigRef.current) {
3377
3746
  sdk.bridge.send(
3378
3747
  "START_LOCATION_STREAM",
@@ -3380,7 +3749,7 @@ var BridgeMocker = ({
3380
3749
  );
3381
3750
  }
3382
3751
  }, [speedMultiplier]);
3383
- useEffect7(() => {
3752
+ useEffect8(() => {
3384
3753
  const bridge = sdk.bridge;
3385
3754
  const originalSend = bridge.send.bind(bridge);
3386
3755
  const stopStream = () => {
@@ -3586,10 +3955,19 @@ var BridgeMocker = ({
3586
3955
  bridge.send = originalSend;
3587
3956
  };
3588
3957
  }, [appId]);
3958
+ const radiusInMeters = radiusUnit === "km" ? radiusMeters * 1e3 : radiusMeters;
3959
+ const mockLocationContextValue = useMemo7(
3960
+ () => ({
3961
+ isPlacingLocation,
3962
+ placeLocation,
3963
+ radiusMeters: radiusInMeters
3964
+ }),
3965
+ [isPlacingLocation, placeLocation, radiusInMeters]
3966
+ );
3589
3967
  if (!isReady) return null;
3590
- return /* @__PURE__ */ jsxs11("div", { className: "relative w-full h-full", children: [
3591
- /* @__PURE__ */ jsx15("div", { className: "w-full h-full", children }),
3592
- isMinimized ? /* @__PURE__ */ jsx15(
3968
+ return /* @__PURE__ */ jsxs12("div", { className: "relative w-full h-full", children: [
3969
+ /* @__PURE__ */ jsx16("div", { className: "w-full h-full", children: /* @__PURE__ */ jsx16(MockLocationContext.Provider, { value: mockLocationContextValue, children }) }),
3970
+ isMinimized ? /* @__PURE__ */ jsx16(
3593
3971
  Button,
3594
3972
  {
3595
3973
  className: "fixed bottom-4 right-4 w-8 h-8 rounded-full bg-slate-900/60 backdrop-blur-sm border border-slate-700/50 flex items-center justify-center text-slate-400 hover:text-blue-400 hover:bg-slate-900/80 z-[9999] transition-all shadow-lg group p-0!",
@@ -3597,7 +3975,7 @@ var BridgeMocker = ({
3597
3975
  title: "Open Simulation Controls",
3598
3976
  type: "button",
3599
3977
  variant: "ghost",
3600
- children: /* @__PURE__ */ jsxs11(
3978
+ children: /* @__PURE__ */ jsxs12(
3601
3979
  "svg",
3602
3980
  {
3603
3981
  className: "group-hover:scale-110 transition-transform",
@@ -3611,34 +3989,34 @@ var BridgeMocker = ({
3611
3989
  width: "14",
3612
3990
  xmlns: "http://www.w3.org/2000/svg",
3613
3991
  children: [
3614
- /* @__PURE__ */ jsx15("path", { d: "M21 4H3" }),
3615
- /* @__PURE__ */ jsx15("path", { d: "M10 8H3" }),
3616
- /* @__PURE__ */ jsx15("path", { d: "M21 12H3" }),
3617
- /* @__PURE__ */ jsx15("path", { d: "M10 16H3" }),
3618
- /* @__PURE__ */ jsx15("path", { d: "M21 20H3" }),
3619
- /* @__PURE__ */ jsx15("circle", { cx: "14", cy: "8", r: "2" }),
3620
- /* @__PURE__ */ jsx15("circle", { cx: "14", cy: "16", r: "2" })
3992
+ /* @__PURE__ */ jsx16("path", { d: "M21 4H3" }),
3993
+ /* @__PURE__ */ jsx16("path", { d: "M10 8H3" }),
3994
+ /* @__PURE__ */ jsx16("path", { d: "M21 12H3" }),
3995
+ /* @__PURE__ */ jsx16("path", { d: "M10 16H3" }),
3996
+ /* @__PURE__ */ jsx16("path", { d: "M21 20H3" }),
3997
+ /* @__PURE__ */ jsx16("circle", { cx: "14", cy: "8", r: "2" }),
3998
+ /* @__PURE__ */ jsx16("circle", { cx: "14", cy: "16", r: "2" })
3621
3999
  ]
3622
4000
  }
3623
4001
  )
3624
4002
  }
3625
- ) : /* @__PURE__ */ jsxs11(
4003
+ ) : /* @__PURE__ */ jsxs12(
3626
4004
  "div",
3627
4005
  {
3628
4006
  className: "fixed bottom-4 right-4 bg-slate-900 text-slate-300 font-mono text-xs rounded-xl shadow-2xl border border-slate-700 pointer-events-auto flex flex-col w-80 overflow-hidden animate-in slide-in-from-right duration-300",
3629
4007
  style: { zIndex: 9998 },
3630
4008
  children: [
3631
- /* @__PURE__ */ jsxs11(
4009
+ /* @__PURE__ */ jsxs12(
3632
4010
  "div",
3633
4011
  {
3634
4012
  className: "flex justify-between items-center p-3 border-b border-slate-800 cursor-pointer bg-slate-800/50 hover:bg-slate-800 transition-colors",
3635
4013
  onClick: () => setIsMinimized(true),
3636
4014
  children: [
3637
- /* @__PURE__ */ jsxs11("span", { className: "font-bold text-slate-100 flex items-center gap-2", children: [
3638
- /* @__PURE__ */ jsx15("span", { className: "text-lg", children: "\u{1F6E0}\uFE0F" }),
4015
+ /* @__PURE__ */ jsxs12("span", { className: "font-bold text-slate-100 flex items-center gap-2", children: [
4016
+ /* @__PURE__ */ jsx16("span", { className: "text-lg", children: "\u{1F6E0}\uFE0F" }),
3639
4017
  " Simulation Controls"
3640
4018
  ] }),
3641
- /* @__PURE__ */ jsx15(
4019
+ /* @__PURE__ */ jsx16(
3642
4020
  Button,
3643
4021
  {
3644
4022
  className: "text-slate-500 hover:text-white p-0 h-auto hover:bg-transparent cursor-pointer",
@@ -3650,10 +4028,10 @@ var BridgeMocker = ({
3650
4028
  ]
3651
4029
  }
3652
4030
  ),
3653
- /* @__PURE__ */ jsxs11("div", { className: "p-4 flex flex-col gap-3", children: [
3654
- /* @__PURE__ */ jsxs11("div", { className: "flex justify-between items-center", children: [
3655
- /* @__PURE__ */ jsxs11("label", { className: "flex items-center gap-2 cursor-pointer", children: [
3656
- /* @__PURE__ */ jsx15(
4031
+ /* @__PURE__ */ jsxs12("div", { className: "p-4 flex flex-col gap-3", children: [
4032
+ /* @__PURE__ */ jsxs12("div", { className: "flex justify-between items-center", children: [
4033
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-2 cursor-pointer", children: [
4034
+ /* @__PURE__ */ jsx16(
3657
4035
  "input",
3658
4036
  {
3659
4037
  checked: autoMove,
@@ -3662,20 +4040,20 @@ var BridgeMocker = ({
3662
4040
  type: "checkbox"
3663
4041
  }
3664
4042
  ),
3665
- /* @__PURE__ */ jsxs11("span", { className: "text-slate-200 font-bold", children: [
4043
+ /* @__PURE__ */ jsxs12("span", { className: "text-slate-200 font-bold", children: [
3666
4044
  "Auto-Move ",
3667
4045
  simulationPath ? "(Path)" : "(Random)"
3668
4046
  ] })
3669
4047
  ] }),
3670
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
3671
- /* @__PURE__ */ jsx15("span", { className: "text-[10px] text-slate-500", children: "Speed:" }),
3672
- /* @__PURE__ */ jsx15(
4048
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
4049
+ /* @__PURE__ */ jsx16("span", { className: "text-[10px] text-slate-500", children: "Speed:" }),
4050
+ /* @__PURE__ */ jsx16(
3673
4051
  "select",
3674
4052
  {
3675
4053
  className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5 text-white focus:outline-none focus:border-blue-500 text-[10px]",
3676
4054
  onChange: (e) => setSpeedMultiplier(Number(e.target.value)),
3677
4055
  value: speedMultiplier,
3678
- children: [1, 2, 5, 10, 50, 100, 250].map((s) => /* @__PURE__ */ jsxs11("option", { value: s, children: [
4056
+ children: [1, 2, 5, 10, 50, 100, 250].map((s) => /* @__PURE__ */ jsxs12("option", { value: s, children: [
3679
4057
  s,
3680
4058
  "x"
3681
4059
  ] }, s))
@@ -3683,10 +4061,10 @@ var BridgeMocker = ({
3683
4061
  )
3684
4062
  ] })
3685
4063
  ] }),
3686
- /* @__PURE__ */ jsxs11("div", { className: "grid grid-cols-2 gap-2", children: [
3687
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1", children: [
3688
- /* @__PURE__ */ jsx15("span", { className: "text-[10px] text-slate-500 text-left", children: "Lng" }),
3689
- /* @__PURE__ */ jsx15(
4064
+ /* @__PURE__ */ jsxs12("div", { className: "grid grid-cols-2 gap-2", children: [
4065
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-1", children: [
4066
+ /* @__PURE__ */ jsx16("span", { className: "text-[10px] text-slate-500 text-left", children: "Lng" }),
4067
+ /* @__PURE__ */ jsx16(
3690
4068
  "input",
3691
4069
  {
3692
4070
  className: "bg-slate-800 border border-slate-700 rounded px-2 py-1 text-white focus:outline-none focus:border-blue-500",
@@ -3701,9 +4079,9 @@ var BridgeMocker = ({
3701
4079
  }
3702
4080
  )
3703
4081
  ] }),
3704
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1", children: [
3705
- /* @__PURE__ */ jsx15("span", { className: "text-[10px] text-slate-500 text-left", children: "Lat" }),
3706
- /* @__PURE__ */ jsx15(
4082
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-1", children: [
4083
+ /* @__PURE__ */ jsx16("span", { className: "text-[10px] text-slate-500 text-left", children: "Lat" }),
4084
+ /* @__PURE__ */ jsx16(
3707
4085
  "input",
3708
4086
  {
3709
4087
  className: "bg-slate-800 border border-slate-700 rounded px-2 py-1 text-white focus:outline-none focus:border-blue-500",
@@ -3718,9 +4096,9 @@ var BridgeMocker = ({
3718
4096
  }
3719
4097
  )
3720
4098
  ] }),
3721
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1 col-span-2", children: [
3722
- /* @__PURE__ */ jsx15("span", { className: "text-[10px] text-slate-500", children: "Heading (Degrees)" }),
3723
- /* @__PURE__ */ jsx15(
4099
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-1 col-span-2", children: [
4100
+ /* @__PURE__ */ jsx16("span", { className: "text-[10px] text-slate-500", children: "Heading (Degrees)" }),
4101
+ /* @__PURE__ */ jsx16(
3724
4102
  "input",
3725
4103
  {
3726
4104
  className: "w-full accent-blue-500",
@@ -3734,11 +4112,63 @@ var BridgeMocker = ({
3734
4112
  value: heading
3735
4113
  }
3736
4114
  ),
3737
- /* @__PURE__ */ jsxs11("div", { className: "text-right text-slate-400", children: [
4115
+ /* @__PURE__ */ jsxs12("div", { className: "text-right text-slate-400", children: [
3738
4116
  heading,
3739
4117
  "\xB0"
3740
4118
  ] })
3741
4119
  ] })
4120
+ ] }),
4121
+ /* @__PURE__ */ jsx16(
4122
+ Button,
4123
+ {
4124
+ className: `w-full rounded px-2 py-1.5 text-[10px] font-bold border cursor-pointer ${isPlacingLocation ? "bg-blue-500/20 border-blue-500 text-blue-300" : "bg-slate-800 border-slate-700 text-slate-200 hover:bg-slate-700"}`,
4125
+ onClick: () => setIsPlacingLocation((prev) => !prev),
4126
+ type: "button",
4127
+ variant: "ghost",
4128
+ children: isPlacingLocation ? "\u{1F4CD} Click the map to drop location\u2026" : "\u{1F4CD} Drop location on map"
4129
+ }
4130
+ ),
4131
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-1 border-t border-slate-800 pt-3", children: [
4132
+ /* @__PURE__ */ jsxs12("div", { className: "flex justify-between items-center", children: [
4133
+ /* @__PURE__ */ jsx16("span", { className: "text-[10px] text-slate-500", children: "Scan Radius" }),
4134
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
4135
+ /* @__PURE__ */ jsx16(
4136
+ "input",
4137
+ {
4138
+ className: "w-16 bg-slate-800 border border-slate-700 rounded px-1.5 py-0.5 text-white text-[10px] focus:outline-none focus:border-blue-500",
4139
+ min: "0",
4140
+ onChange: (e) => setRadiusMeters(Math.max(0, Number(e.target.value) || 0)),
4141
+ type: "number",
4142
+ value: radiusMeters
4143
+ }
4144
+ ),
4145
+ /* @__PURE__ */ jsxs12(
4146
+ "select",
4147
+ {
4148
+ className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5 text-white text-[10px] focus:outline-none focus:border-blue-500",
4149
+ onChange: (e) => setRadiusUnit(e.target.value),
4150
+ value: radiusUnit,
4151
+ children: [
4152
+ /* @__PURE__ */ jsx16("option", { value: "m", children: "m" }),
4153
+ /* @__PURE__ */ jsx16("option", { value: "km", children: "km" })
4154
+ ]
4155
+ }
4156
+ )
4157
+ ] })
4158
+ ] }),
4159
+ /* @__PURE__ */ jsx16(
4160
+ "input",
4161
+ {
4162
+ className: "w-full accent-blue-500",
4163
+ max: radiusUnit === "km" ? 10 : 500,
4164
+ min: "0",
4165
+ onChange: (e) => setRadiusMeters(Number(e.target.value)),
4166
+ step: radiusUnit === "km" ? 0.1 : 5,
4167
+ type: "range",
4168
+ value: radiusMeters
4169
+ }
4170
+ ),
4171
+ /* @__PURE__ */ jsx16("div", { className: "text-right text-slate-400", children: radiusMeters === 0 ? "hidden" : `${radiusMeters}${radiusUnit} radius around user` })
3742
4172
  ] })
3743
4173
  ] })
3744
4174
  ]
@@ -3748,20 +4178,20 @@ var BridgeMocker = ({
3748
4178
  };
3749
4179
 
3750
4180
  // src/components/DebugConsole.tsx
3751
- import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef8, useState as useState9 } from "react";
3752
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4181
+ import { useCallback as useCallback8, useEffect as useEffect9, useRef as useRef9, useState as useState10 } from "react";
4182
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3753
4183
  var DebugConsole = ({
3754
4184
  isOpen: initialOpen = false,
3755
4185
  onClose
3756
4186
  }) => {
3757
- const [showConsole, setShowConsole] = useState9(initialOpen);
3758
- const [logs, setLogs] = useState9([]);
3759
- const scrollRef = useRef8(null);
3760
- useEffect8(() => {
4187
+ const [showConsole, setShowConsole] = useState10(initialOpen);
4188
+ const [logs, setLogs] = useState10([]);
4189
+ const scrollRef = useRef9(null);
4190
+ useEffect9(() => {
3761
4191
  setShowConsole(initialOpen);
3762
4192
  }, [initialOpen]);
3763
4193
  const clearLogs = () => setLogs([]);
3764
- const addLog = useCallback7((label, data) => {
4194
+ const addLog = useCallback8((label, data) => {
3765
4195
  if (label.includes("LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */)) return;
3766
4196
  const entry = {
3767
4197
  content: typeof data === "string" ? data : JSON.stringify(data, null, 2),
@@ -3771,7 +4201,7 @@ var DebugConsole = ({
3771
4201
  };
3772
4202
  setLogs((prev) => [entry, ...prev].slice(0, 100));
3773
4203
  }, []);
3774
- useEffect8(() => {
4204
+ useEffect9(() => {
3775
4205
  if (typeof window === "undefined") return;
3776
4206
  const bridge = sdk.bridge;
3777
4207
  const originalSend = bridge.send.bind(bridge);
@@ -3793,7 +4223,7 @@ var DebugConsole = ({
3793
4223
  };
3794
4224
  }, [addLog]);
3795
4225
  if (!showConsole) {
3796
- return /* @__PURE__ */ jsx16(
4226
+ return /* @__PURE__ */ jsx17(
3797
4227
  Button,
3798
4228
  {
3799
4229
  className: "fixed bottom-4 left-4 w-8 h-8 rounded-full bg-slate-900/60 backdrop-blur-sm border border-slate-700/50 flex items-center justify-center text-slate-400 hover:text-emerald-400 hover:bg-slate-900/80 transition-all z-[9998] shadow-lg group p-0!",
@@ -3801,7 +4231,7 @@ var DebugConsole = ({
3801
4231
  title: "Open Debug Console",
3802
4232
  type: "button",
3803
4233
  variant: "ghost",
3804
- children: /* @__PURE__ */ jsxs12(
4234
+ children: /* @__PURE__ */ jsxs13(
3805
4235
  "svg",
3806
4236
  {
3807
4237
  className: "group-hover:scale-110 transition-transform",
@@ -3815,31 +4245,31 @@ var DebugConsole = ({
3815
4245
  width: "14",
3816
4246
  xmlns: "http://www.w3.org/2000/svg",
3817
4247
  children: [
3818
- /* @__PURE__ */ jsx16("path", { d: "m8 2 1.88 1.88" }),
3819
- /* @__PURE__ */ jsx16("path", { d: "M14.12 3.88 16 2" }),
3820
- /* @__PURE__ */ jsx16("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
3821
- /* @__PURE__ */ jsx16("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
3822
- /* @__PURE__ */ jsx16("path", { d: "M12 20v-9" }),
3823
- /* @__PURE__ */ jsx16("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
3824
- /* @__PURE__ */ jsx16("path", { d: "M6 13H2" }),
3825
- /* @__PURE__ */ jsx16("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
3826
- /* @__PURE__ */ jsx16("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
3827
- /* @__PURE__ */ jsx16("path", { d: "M22 13h-4" }),
3828
- /* @__PURE__ */ jsx16("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
4248
+ /* @__PURE__ */ jsx17("path", { d: "m8 2 1.88 1.88" }),
4249
+ /* @__PURE__ */ jsx17("path", { d: "M14.12 3.88 16 2" }),
4250
+ /* @__PURE__ */ jsx17("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
4251
+ /* @__PURE__ */ jsx17("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
4252
+ /* @__PURE__ */ jsx17("path", { d: "M12 20v-9" }),
4253
+ /* @__PURE__ */ jsx17("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
4254
+ /* @__PURE__ */ jsx17("path", { d: "M6 13H2" }),
4255
+ /* @__PURE__ */ jsx17("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
4256
+ /* @__PURE__ */ jsx17("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
4257
+ /* @__PURE__ */ jsx17("path", { d: "M22 13h-4" }),
4258
+ /* @__PURE__ */ jsx17("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
3829
4259
  ]
3830
4260
  }
3831
4261
  )
3832
4262
  }
3833
4263
  );
3834
4264
  }
3835
- return /* @__PURE__ */ jsxs12("div", { className: "fixed inset-x-0 bottom-0 w-full bg-[#0a080f] border-t border-slate-800 z-[9998] p-4 shadow-2xl flex flex-col animate-in slide-in-from-bottom duration-300", children: [
3836
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between mb-2", children: [
3837
- /* @__PURE__ */ jsxs12("span", { className: "text-[10px] font-bold uppercase tracking-widest text-slate-400 flex items-center gap-2", children: [
3838
- /* @__PURE__ */ jsx16("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
4265
+ return /* @__PURE__ */ jsxs13("div", { className: "fixed inset-x-0 bottom-0 w-full bg-[#0a080f] border-t border-slate-800 z-[9998] p-4 shadow-2xl flex flex-col animate-in slide-in-from-bottom duration-300", children: [
4266
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between mb-2", children: [
4267
+ /* @__PURE__ */ jsxs13("span", { className: "text-[10px] font-bold uppercase tracking-widest text-slate-400 flex items-center gap-2", children: [
4268
+ /* @__PURE__ */ jsx17("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
3839
4269
  "UP SDK Debug Console"
3840
4270
  ] }),
3841
- /* @__PURE__ */ jsxs12("div", { className: "flex gap-4", children: [
3842
- /* @__PURE__ */ jsx16(
4271
+ /* @__PURE__ */ jsxs13("div", { className: "flex gap-4", children: [
4272
+ /* @__PURE__ */ jsx17(
3843
4273
  Button,
3844
4274
  {
3845
4275
  className: "text-[10px] text-slate-500 font-bold uppercase hover:text-slate-300 hover:bg-transparent transition-colors p-0 h-auto cursor-pointer",
@@ -3849,7 +4279,7 @@ var DebugConsole = ({
3849
4279
  children: "Clear"
3850
4280
  }
3851
4281
  ),
3852
- /* @__PURE__ */ jsx16(
4282
+ /* @__PURE__ */ jsx17(
3853
4283
  Button,
3854
4284
  {
3855
4285
  className: "text-[10px] text-pink-500 font-bold uppercase hover:text-pink-400 hover:bg-transparent transition-colors p-0 h-auto cursor-pointer",
@@ -3864,21 +4294,21 @@ var DebugConsole = ({
3864
4294
  )
3865
4295
  ] })
3866
4296
  ] }),
3867
- /* @__PURE__ */ jsx16(
4297
+ /* @__PURE__ */ jsx17(
3868
4298
  "div",
3869
4299
  {
3870
4300
  className: "bg-black/80 rounded-xl p-3 font-mono text-[10px] text-emerald-400 min-h-[120px] max-h-[240px] overflow-y-auto border border-slate-900 custom-scrollbar",
3871
4301
  ref: scrollRef,
3872
- children: logs.length === 0 ? /* @__PURE__ */ jsx16("div", { className: "text-slate-600 italic text-center py-8", children: "No logs yet. Interacting with the SDK will show events here." }) : logs.map((log) => /* @__PURE__ */ jsxs12(
4302
+ children: logs.length === 0 ? /* @__PURE__ */ jsx17("div", { className: "text-slate-600 italic text-center py-8", children: "No logs yet. Interacting with the SDK will show events here." }) : logs.map((log) => /* @__PURE__ */ jsxs13(
3873
4303
  "div",
3874
4304
  {
3875
4305
  className: "mb-3 pb-3 border-b border-slate-900/50 last:border-0 last:mb-0 last:pb-0",
3876
4306
  children: [
3877
- /* @__PURE__ */ jsxs12("div", { className: "flex gap-2 text-slate-500 mb-1 items-center", children: [
3878
- /* @__PURE__ */ jsx16("span", { className: "text-[8px]", children: log.time }),
3879
- /* @__PURE__ */ jsx16("span", { className: "font-bold text-white uppercase text-[8px] bg-slate-800 px-1.5 py-0.5 rounded leading-none", children: log.label })
4307
+ /* @__PURE__ */ jsxs13("div", { className: "flex gap-2 text-slate-500 mb-1 items-center", children: [
4308
+ /* @__PURE__ */ jsx17("span", { className: "text-[8px]", children: log.time }),
4309
+ /* @__PURE__ */ jsx17("span", { className: "font-bold text-white uppercase text-[8px] bg-slate-800 px-1.5 py-0.5 rounded leading-none", children: log.label })
3880
4310
  ] }),
3881
- /* @__PURE__ */ jsx16("pre", { className: "whitespace-pre-wrap break-all opacity-90", children: log.content })
4311
+ /* @__PURE__ */ jsx17("pre", { className: "whitespace-pre-wrap break-all opacity-90", children: log.content })
3882
4312
  ]
3883
4313
  },
3884
4314
  log.id
@@ -3889,25 +4319,254 @@ var DebugConsole = ({
3889
4319
  };
3890
4320
 
3891
4321
  // src/components/SDKProvider.tsx
3892
- import { createContext as createContext2, useContext as useContext5, useMemo as useMemo6 } from "react";
3893
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
3894
- var SDKContext = createContext2({ isMocked: false });
3895
- var useIsMocked = () => useContext5(SDKContext).isMocked;
4322
+ import { createContext as createContext3, useContext as useContext6, useMemo as useMemo8 } from "react";
4323
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
4324
+ var SDKContext = createContext3({ isMocked: false });
4325
+ var useIsMocked = () => useContext6(SDKContext).isMocked;
3896
4326
  var SDKProvider = ({
3897
4327
  children,
3898
4328
  debug = false,
3899
4329
  ...mockerProps
3900
4330
  }) => {
3901
4331
  const isMocked = typeof window !== "undefined" && !(window.__UP_REACT_NATIVE_APP__ === true || !!window.ReactNativeWebView);
3902
- const value = useMemo6(() => ({ isMocked }), [isMocked]);
3903
- const content = /* @__PURE__ */ jsxs13(Fragment5, { children: [
4332
+ const value = useMemo8(() => ({ isMocked }), [isMocked]);
4333
+ const content = /* @__PURE__ */ jsxs14(Fragment5, { children: [
3904
4334
  children,
3905
- (debug || isMocked) && /* @__PURE__ */ jsx17(DebugConsole, {})
4335
+ (debug || isMocked) && /* @__PURE__ */ jsx18(DebugConsole, {})
3906
4336
  ] });
3907
4337
  if (isMocked) {
3908
- return /* @__PURE__ */ jsx17(SDKContext.Provider, { value, children: /* @__PURE__ */ jsx17(BridgeMocker, { ...mockerProps, children: content }) });
4338
+ return /* @__PURE__ */ jsx18(SDKContext.Provider, { value, children: /* @__PURE__ */ jsx18(BridgeMocker, { ...mockerProps, children: content }) });
3909
4339
  }
3910
- return /* @__PURE__ */ jsx17(SDKContext.Provider, { value, children: content });
4340
+ return /* @__PURE__ */ jsx18(SDKContext.Provider, { value, children: content });
4341
+ };
4342
+
4343
+ // src/components/SwipeCarousel.tsx
4344
+ import {
4345
+ useEffect as useEffect10,
4346
+ useLayoutEffect,
4347
+ useRef as useRef10,
4348
+ useState as useState11
4349
+ } from "react";
4350
+ import { jsx as jsx19 } from "react/jsx-runtime";
4351
+ var DIRECTION_LOCK_THRESHOLD_PX = 8;
4352
+ var SWIPE_DISTANCE_THRESHOLD_PX = 40;
4353
+ var SWIPE_VELOCITY_THRESHOLD = 0.4;
4354
+ var EDGE_RESISTANCE = 0.35;
4355
+ var SNAP_EASING = "cubic-bezier(0.16, 1, 0.3, 1)";
4356
+ var SNAP_DURATION_MS = 300;
4357
+ var VERTICAL_DEAD_ZONE_PX = 15;
4358
+ var DEFAULT_VERTICAL_DISMISS_THRESHOLD_PX = 200;
4359
+ var DEFAULT_VERTICAL_DISMISS_VELOCITY = 0.5;
4360
+ var DEFAULT_OVERSCAN = 2;
4361
+ var DEFAULT_VISIBLE_OVERSCAN = 1;
4362
+ var DEFAULT_SCROLL_ROOT_SELECTOR = "[data-swipe-scroll-root]";
4363
+ var SNAP_TRANSITION = `transform ${SNAP_DURATION_MS}ms ${SNAP_EASING}`;
4364
+ var SwipeCarousel = ({
4365
+ items,
4366
+ activeId,
4367
+ onActiveChange,
4368
+ peek = 1.1,
4369
+ gap = 10,
4370
+ className,
4371
+ itemClassName,
4372
+ axisPriority = "horizontal",
4373
+ verticalDrag,
4374
+ overscan = DEFAULT_OVERSCAN,
4375
+ visibleOverscan = DEFAULT_VISIBLE_OVERSCAN
4376
+ }) => {
4377
+ const containerRef = useRef10(null);
4378
+ const [containerWidth, setContainerWidth] = useState11(0);
4379
+ const [dragX, setDragX] = useState11(0);
4380
+ const [dragY, setDragY] = useState11(0);
4381
+ const [isDraggingX, setIsDraggingX] = useState11(false);
4382
+ const [isDraggingY, setIsDraggingY] = useState11(false);
4383
+ const touchStateRef = useRef10(null);
4384
+ const [internalIndex, setInternalIndex] = useState11(
4385
+ () => Math.max(
4386
+ 0,
4387
+ items.findIndex((item) => item.id === activeId)
4388
+ )
4389
+ );
4390
+ const prevActiveIdRef = useRef10(activeId);
4391
+ useEffect10(() => {
4392
+ if (activeId !== prevActiveIdRef.current) {
4393
+ const idx = items.findIndex((item) => item.id === activeId);
4394
+ if (idx !== -1) setInternalIndex(idx);
4395
+ prevActiveIdRef.current = activeId;
4396
+ }
4397
+ }, [activeId, items]);
4398
+ const activeIndex = Math.min(
4399
+ Math.max(internalIndex, 0),
4400
+ Math.max(items.length - 1, 0)
4401
+ );
4402
+ useLayoutEffect(() => {
4403
+ const el = containerRef.current;
4404
+ if (!el) return;
4405
+ const update = () => setContainerWidth(el.clientWidth);
4406
+ update();
4407
+ const observer = new ResizeObserver(update);
4408
+ observer.observe(el);
4409
+ return () => observer.disconnect();
4410
+ }, []);
4411
+ const slideWidth = containerWidth / peek;
4412
+ const step = slideWidth + gap;
4413
+ const baseOffset = (containerWidth - slideWidth) / 2;
4414
+ const translateX = baseOffset - activeIndex * step + dragX;
4415
+ const commitIndex = (nextIndex) => {
4416
+ setInternalIndex(nextIndex);
4417
+ const nextItem = items[nextIndex];
4418
+ if (nextItem && nextItem.id !== activeId) onActiveChange(nextItem.id);
4419
+ };
4420
+ const handleTouchStart = (e) => {
4421
+ const touch = e.targetTouches[0];
4422
+ if (!touch) return;
4423
+ let verticalAllowed = false;
4424
+ if (verticalDrag) {
4425
+ const selector = verticalDrag.scrollRootSelector ?? DEFAULT_SCROLL_ROOT_SELECTOR;
4426
+ const scrollRoot = e.target.closest(
4427
+ selector
4428
+ );
4429
+ verticalAllowed = !scrollRoot || scrollRoot.scrollTop <= 2;
4430
+ }
4431
+ touchStateRef.current = {
4432
+ lastT: e.timeStamp,
4433
+ lastX: touch.clientX,
4434
+ lastY: touch.clientY,
4435
+ lockedAxis: null,
4436
+ startX: touch.clientX,
4437
+ startY: touch.clientY,
4438
+ velocityX: 0,
4439
+ velocityY: 0,
4440
+ verticalAllowed
4441
+ };
4442
+ };
4443
+ const handleTouchMove = (e) => {
4444
+ const touch = e.targetTouches[0];
4445
+ const state = touchStateRef.current;
4446
+ if (!touch || !state) return;
4447
+ const rawDx = touch.clientX - state.startX;
4448
+ const rawDy = touch.clientY - state.startY;
4449
+ if (!state.lockedAxis) {
4450
+ if (Math.abs(rawDx) < DIRECTION_LOCK_THRESHOLD_PX && Math.abs(rawDy) < DIRECTION_LOCK_THRESHOLD_PX) {
4451
+ return;
4452
+ }
4453
+ const horizontalWins = Math.abs(rawDx) > Math.abs(rawDy) || Math.abs(rawDx) === Math.abs(rawDy) && axisPriority === "horizontal";
4454
+ if (horizontalWins) {
4455
+ state.lockedAxis = "x";
4456
+ } else if (verticalDrag && state.verticalAllowed) {
4457
+ state.lockedAxis = "y";
4458
+ } else {
4459
+ state.lockedAxis = "passthrough";
4460
+ }
4461
+ }
4462
+ if (state.lockedAxis === "passthrough") return;
4463
+ const dt = e.timeStamp - state.lastT;
4464
+ if (dt > 0) {
4465
+ state.velocityX = (touch.clientX - state.lastX) / dt;
4466
+ state.velocityY = (touch.clientY - state.lastY) / dt;
4467
+ }
4468
+ state.lastX = touch.clientX;
4469
+ state.lastY = touch.clientY;
4470
+ state.lastT = e.timeStamp;
4471
+ if (state.lockedAxis === "x") {
4472
+ if (!isDraggingX) setIsDraggingX(true);
4473
+ const atStart = activeIndex === 0 && rawDx > 0;
4474
+ const atEnd = activeIndex === items.length - 1 && rawDx < 0;
4475
+ setDragX(atStart || atEnd ? rawDx * EDGE_RESISTANCE : rawDx);
4476
+ } else if (verticalDrag) {
4477
+ const direction = verticalDrag.direction ?? "down";
4478
+ let clamped = rawDy;
4479
+ if (direction === "down") clamped = Math.max(0, rawDy);
4480
+ if (direction === "up") clamped = Math.min(0, rawDy);
4481
+ const beyondDeadZone = Math.sign(clamped) * Math.max(0, Math.abs(clamped) - VERTICAL_DEAD_ZONE_PX);
4482
+ if (!isDraggingY) setIsDraggingY(true);
4483
+ setDragY(beyondDeadZone);
4484
+ verticalDrag.onDragChange?.(beyondDeadZone, true);
4485
+ }
4486
+ if (e.cancelable) {
4487
+ e.preventDefault();
4488
+ }
4489
+ };
4490
+ const endDrag = () => {
4491
+ const state = touchStateRef.current;
4492
+ touchStateRef.current = null;
4493
+ setIsDraggingX(false);
4494
+ setIsDraggingY(false);
4495
+ if (!state) {
4496
+ setDragX(0);
4497
+ setDragY(0);
4498
+ return;
4499
+ }
4500
+ if (state.lockedAxis === "x") {
4501
+ const horizontalTriggered = Math.abs(dragX) > SWIPE_DISTANCE_THRESHOLD_PX || Math.abs(state.velocityX) > SWIPE_VELOCITY_THRESHOLD;
4502
+ if (horizontalTriggered) {
4503
+ const nextIndex = dragX < 0 ? Math.min(items.length - 1, activeIndex + 1) : Math.max(0, activeIndex - 1);
4504
+ commitIndex(nextIndex);
4505
+ }
4506
+ } else if (state.lockedAxis === "y") {
4507
+ const verticalThreshold = verticalDrag?.dismissThreshold ?? DEFAULT_VERTICAL_DISMISS_THRESHOLD_PX;
4508
+ const verticalVelocityThreshold = verticalDrag?.dismissVelocity ?? DEFAULT_VERTICAL_DISMISS_VELOCITY;
4509
+ const direction = verticalDrag?.direction ?? "down";
4510
+ const directionalVelocityY = direction === "down" ? Math.max(0, state.velocityY) : direction === "up" ? Math.max(0, -state.velocityY) : Math.abs(state.velocityY);
4511
+ const verticalTriggered = Math.abs(dragY) > verticalThreshold || directionalVelocityY > verticalVelocityThreshold;
4512
+ if (verticalTriggered) verticalDrag?.onDismiss?.();
4513
+ }
4514
+ setDragX(0);
4515
+ setDragY(0);
4516
+ verticalDrag?.onDragChange?.(0, false);
4517
+ };
4518
+ const windowStart = Math.max(0, activeIndex - overscan);
4519
+ const windowEnd = Math.min(items.length - 1, activeIndex + overscan);
4520
+ return /* @__PURE__ */ jsx19(
4521
+ "div",
4522
+ {
4523
+ className: `relative overflow-hidden touch-pan-y ${className ?? ""}`,
4524
+ onTouchCancel: endDrag,
4525
+ onTouchEnd: endDrag,
4526
+ onTouchMove: handleTouchMove,
4527
+ onTouchStart: handleTouchStart,
4528
+ ref: containerRef,
4529
+ children: /* @__PURE__ */ jsx19(
4530
+ "div",
4531
+ {
4532
+ className: "relative h-full",
4533
+ style: {
4534
+ transform: `translateX(${translateX}px)`,
4535
+ transition: isDraggingX ? "none" : SNAP_TRANSITION,
4536
+ willChange: "transform"
4537
+ },
4538
+ children: items.map((item, index) => {
4539
+ if (index < windowStart || index > windowEnd) return null;
4540
+ const isActive = index === activeIndex;
4541
+ const verticalOffset = isActive ? dragY : 0;
4542
+ const distance = Math.abs(index - activeIndex);
4543
+ const isVisible = distance <= visibleOverscan;
4544
+ return /* @__PURE__ */ jsx19(
4545
+ "div",
4546
+ {
4547
+ className: `absolute top-0 left-0 h-full shrink-0 ${itemClassName ?? ""} ${item.className ?? ""}`,
4548
+ style: {
4549
+ opacity: isVisible ? 1 : 0,
4550
+ pointerEvents: isVisible ? void 0 : "none",
4551
+ transform: `translateX(${index * step}px) translateY(${verticalOffset}px)`,
4552
+ transition: isActive && isDraggingY ? "none" : SNAP_TRANSITION,
4553
+ width: `${slideWidth}px`,
4554
+ // Layer by distance from the active slide (not DOM/index
4555
+ // order) so it always paints above its neighbors on BOTH
4556
+ // sides symmetrically — with `peek` this close, unstyled
4557
+ // z-index left neighbor bleed (shadow/blur) showing through
4558
+ // asymmetrically depending on swipe direction.
4559
+ zIndex: overscan + 1 - distance
4560
+ },
4561
+ children: item.content
4562
+ },
4563
+ item.id
4564
+ );
4565
+ })
4566
+ }
4567
+ )
4568
+ }
4569
+ );
3911
4570
  };
3912
4571
 
3913
4572
  // src/index.ts
@@ -3926,9 +4585,11 @@ export {
3926
4585
  MapMenuItem,
3927
4586
  MapMenuTitle,
3928
4587
  MiniAppSDK,
4588
+ MockLocationContext,
3929
4589
  RouteLayer,
3930
4590
  SDKMapProvider,
3931
4591
  SDKProvider,
4592
+ SwipeCarousel,
3932
4593
  UIEvent,
3933
4594
  src_default as default,
3934
4595
  handleClearRoute,
@@ -3937,8 +4598,10 @@ export {
3937
4598
  renderMap,
3938
4599
  sdk,
3939
4600
  useIsMocked,
4601
+ useJourney,
3940
4602
  useLiveRouting,
3941
4603
  useMapControls,
4604
+ useMockLocation,
3942
4605
  useRoute,
3943
4606
  useTrimmedRoute,
3944
4607
  useUserLocation