@cloudparse/up-miniapps-sdk 0.2.0 → 0.2.1

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.cjs CHANGED
@@ -254,7 +254,9 @@ var MapDrawer = (0, import_react.forwardRef)(
254
254
  containerHeight,
255
255
  menuContent,
256
256
  enableTouchDrag = false,
257
- safeAreaInsets = { top: 0, bottom: 0, left: 0, right: 0 }
257
+ showScrollToTopButton = true,
258
+ scrollToTopThreshold = 300,
259
+ safeAreaInsets = { bottom: 0, left: 0, right: 0, top: 0 }
258
260
  }, ref) => {
259
261
  const scrollContainerRef = (0, import_react.useRef)(null);
260
262
  const containerRef = (0, import_react.useRef)(null);
@@ -275,81 +277,237 @@ var MapDrawer = (0, import_react.forwardRef)(
275
277
  }
276
278
  }
277
279
  };
278
- const handleTouchStart = (e) => {
279
- if (side !== "bottom") return;
280
- setTouchStartY(e.targetTouches[0].clientY);
281
- setCurrentTouchY(e.targetTouches[0].clientY);
280
+ const [showScrollTop, setShowScrollTop] = (0, import_react.useState)(false);
281
+ const handleScroll = (e) => {
282
+ const scrollTop = e.currentTarget.scrollTop;
283
+ setShowScrollTop(state !== "closed" && scrollTop > scrollToTopThreshold);
282
284
  };
283
- const handleTouchMove = (e) => {
284
- if (touchStartY === null || side !== "bottom") return;
285
- const isScrollAtTop = scrollContainerRef.current ? scrollContainerRef.current.scrollTop === 0 : true;
286
- const currentY = e.targetTouches[0].clientY;
287
- const deltaY = touchStartY - currentY;
288
- if (deltaY < 0 && !isScrollAtTop) {
289
- return;
290
- }
291
- setCurrentTouchY(currentY);
292
- };
293
- const handleTouchEnd = (e) => {
294
- if (touchStartY === null || currentTouchY === null || side !== "bottom")
295
- return;
296
- const touchEndY = e.changedTouches[0].clientY;
297
- const dragDistance = Math.abs(touchEndY - touchStartY);
298
- if (dragDistance < 10) {
285
+ (0, import_react.useEffect)(() => {
286
+ if (state === "closed") {
287
+ setShowScrollTop(false);
288
+ } else {
289
+ const scrollTop = scrollContainerRef.current?.scrollTop || 0;
290
+ setShowScrollTop(scrollTop > scrollToTopThreshold);
291
+ }
292
+ }, [state, scrollToTopThreshold]);
293
+ const isDraggingSheet = (0, import_react.useRef)(false);
294
+ const hasDeterminedDirection = (0, import_react.useRef)(false);
295
+ const isTouchOnContent = (0, import_react.useRef)(false);
296
+ const stateRef = (0, import_react.useRef)(state);
297
+ const touchStartYRef = (0, import_react.useRef)(touchStartY);
298
+ const currentTouchYRef = (0, import_react.useRef)(currentTouchY);
299
+ (0, import_react.useEffect)(() => {
300
+ stateRef.current = state;
301
+ }, [state]);
302
+ (0, import_react.useEffect)(() => {
303
+ touchStartYRef.current = touchStartY;
304
+ }, [touchStartY]);
305
+ (0, import_react.useEffect)(() => {
306
+ currentTouchYRef.current = currentTouchY;
307
+ }, [currentTouchY]);
308
+ (0, import_react.useEffect)(() => {
309
+ const container = containerRef.current;
310
+ if (!container || side !== "bottom" || !enableTouchDrag) return;
311
+ const handleNativeTouchStart = (e) => {
312
+ const clientY = e.targetTouches[0].clientY;
313
+ setTouchStartY(clientY);
314
+ setCurrentTouchY(clientY);
315
+ isDraggingSheet.current = false;
316
+ hasDeterminedDirection.current = false;
317
+ const target = e.target;
318
+ isTouchOnContent.current = scrollContainerRef.current ? scrollContainerRef.current.contains(target) : false;
319
+ };
320
+ const handleNativeTouchMove = (e) => {
321
+ const startY = touchStartYRef.current;
322
+ if (startY === null) return;
323
+ const currentY = e.targetTouches[0].clientY;
324
+ const deltaY = startY - currentY;
325
+ const scrollTop = scrollContainerRef.current ? scrollContainerRef.current.scrollTop : 0;
326
+ const clientHeight = scrollContainerRef.current ? scrollContainerRef.current.clientHeight : 0;
327
+ const scrollHeight = scrollContainerRef.current ? scrollContainerRef.current.scrollHeight : 0;
328
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
329
+ const currentState = stateRef.current;
330
+ if (!hasDeterminedDirection.current) {
331
+ if (!isTouchOnContent.current) {
332
+ isDraggingSheet.current = true;
333
+ hasDeterminedDirection.current = true;
334
+ } else if (currentState !== "full") {
335
+ if (deltaY > 0 && isAtBottom) {
336
+ isDraggingSheet.current = true;
337
+ hasDeterminedDirection.current = true;
338
+ } else if (deltaY < 0 && scrollTop <= 0) {
339
+ isDraggingSheet.current = true;
340
+ hasDeterminedDirection.current = true;
341
+ } else {
342
+ isDraggingSheet.current = false;
343
+ hasDeterminedDirection.current = true;
344
+ }
345
+ } else {
346
+ if (deltaY < 0 && scrollTop <= 0) {
347
+ isDraggingSheet.current = true;
348
+ hasDeterminedDirection.current = true;
349
+ } else {
350
+ isDraggingSheet.current = false;
351
+ hasDeterminedDirection.current = true;
352
+ }
353
+ }
354
+ } else if (!isDraggingSheet.current && isTouchOnContent.current) {
355
+ if (deltaY < 0 && scrollTop <= 0) {
356
+ isDraggingSheet.current = true;
357
+ setTouchStartY(currentY);
358
+ return;
359
+ }
360
+ if (deltaY > 0 && isAtBottom && currentState !== "full") {
361
+ isDraggingSheet.current = true;
362
+ setTouchStartY(currentY);
363
+ return;
364
+ }
365
+ }
366
+ if (isDraggingSheet.current) {
367
+ if (e.cancelable) {
368
+ e.preventDefault();
369
+ }
370
+ const parentContainer = container.parentElement;
371
+ if (parentContainer) {
372
+ const containerH2 = containerHeight || parentContainer.getBoundingClientRect().height;
373
+ const fullPx2 = resolveHeightToPixels(
374
+ drawerFullHeight || "85%",
375
+ containerH2
376
+ );
377
+ const peekPx2 = resolveHeightToPixels(
378
+ drawerPeekHeight || "15%",
379
+ containerH2
380
+ );
381
+ const closedPx = 32 + (currentState === "closed" ? safeAreaInsets.bottom : 0);
382
+ const dragOffset = currentY - startY;
383
+ let localClamped = dragOffset;
384
+ if (currentState === "full") {
385
+ localClamped = Math.max(
386
+ 0,
387
+ Math.min(fullPx2 - closedPx, dragOffset)
388
+ );
389
+ } else if (currentState === "peek") {
390
+ localClamped = Math.max(
391
+ -(fullPx2 - peekPx2),
392
+ Math.min(peekPx2 - closedPx, dragOffset)
393
+ );
394
+ } else if (currentState === "closed") {
395
+ localClamped = Math.min(
396
+ 0,
397
+ Math.max(-(fullPx2 - closedPx), dragOffset)
398
+ );
399
+ }
400
+ parentContainer.style.setProperty(
401
+ "--drag-offset",
402
+ `${localClamped}px`
403
+ );
404
+ }
405
+ }
406
+ };
407
+ const handleNativeTouchEnd = (e) => {
408
+ const startY = touchStartYRef.current;
409
+ const currentY = currentTouchYRef.current;
410
+ const parentContainer = container.parentElement;
411
+ if (startY === null || currentY === null) {
412
+ if (parentContainer) {
413
+ parentContainer.style.setProperty("--drag-offset", "0px");
414
+ }
415
+ return;
416
+ }
417
+ if (!isDraggingSheet.current) {
418
+ setTouchStartY(null);
419
+ setCurrentTouchY(null);
420
+ if (parentContainer) {
421
+ parentContainer.style.setProperty("--drag-offset", "0px");
422
+ }
423
+ return;
424
+ }
425
+ const touchEndY = e.changedTouches[0].clientY;
426
+ const dragDistance = Math.abs(touchEndY - startY);
427
+ if (dragDistance < 10) {
428
+ setTouchStartY(null);
429
+ setCurrentTouchY(null);
430
+ if (parentContainer) {
431
+ parentContainer.style.setProperty("--drag-offset", "0px");
432
+ }
433
+ return;
434
+ }
435
+ const containerH2 = containerHeight || (parentContainer ? parentContainer.getBoundingClientRect().height : window.innerHeight);
436
+ const containerTop = parentContainer ? parentContainer.getBoundingClientRect().top : 0;
437
+ const touchYInContainer = touchEndY - containerTop;
438
+ const drawerHeightFromBottom = containerH2 - touchYInContainer;
439
+ const fullPx2 = resolveHeightToPixels(
440
+ drawerFullHeight || "85%",
441
+ containerH2
442
+ );
443
+ const peekPx2 = resolveHeightToPixels(
444
+ drawerPeekHeight || "15%",
445
+ containerH2
446
+ );
447
+ const currentState = stateRef.current;
448
+ const closedPx = 32 + (currentState === "closed" ? safeAreaInsets.bottom : 0);
449
+ const distToFull = Math.abs(drawerHeightFromBottom - fullPx2);
450
+ const distToPeek = Math.abs(drawerHeightFromBottom - peekPx2);
451
+ const distToClosed = Math.abs(drawerHeightFromBottom - closedPx);
452
+ const minDist = Math.min(distToFull, distToPeek, distToClosed);
453
+ let targetState = "closed";
454
+ if (minDist === distToFull) {
455
+ targetState = "full";
456
+ } else if (minDist === distToPeek) {
457
+ targetState = "peek";
458
+ } else {
459
+ targetState = "closed";
460
+ }
299
461
  setTouchStartY(null);
300
462
  setCurrentTouchY(null);
301
- return;
302
- }
303
- const parentContainer = containerRef.current?.parentElement;
304
- const containerH = containerHeight || (parentContainer ? parentContainer.getBoundingClientRect().height : window.innerHeight);
305
- const containerTop = parentContainer ? parentContainer.getBoundingClientRect().top : 0;
306
- const touchYInContainer = touchEndY - containerTop;
307
- const drawerHeightFromBottom = containerH - touchYInContainer;
308
- const fullPx = resolveHeightToPixels(
309
- drawerFullHeight || "85%",
310
- containerH
311
- );
312
- const peekPx = resolveHeightToPixels(
313
- drawerPeekHeight || "15%",
314
- containerH
315
- );
316
- const closedPx = 32 + (state === "closed" ? safeAreaInsets.bottom : 0);
317
- const distToFull = Math.abs(drawerHeightFromBottom - fullPx);
318
- const distToPeek = Math.abs(drawerHeightFromBottom - peekPx);
319
- const distToClosed = Math.abs(drawerHeightFromBottom - closedPx);
320
- const minDist = Math.min(distToFull, distToPeek, distToClosed);
321
- if (minDist === distToFull) {
322
- onChangeState("full");
323
- } else if (minDist === distToPeek) {
324
- onChangeState("peek");
325
- } else {
326
- onChangeState("closed");
327
- }
328
- setTouchStartY(null);
329
- setCurrentTouchY(null);
330
- };
463
+ onChangeState(targetState);
464
+ requestAnimationFrame(() => {
465
+ if (parentContainer) {
466
+ parentContainer.style.setProperty("--drag-offset", "0px");
467
+ }
468
+ });
469
+ };
470
+ container.addEventListener("touchstart", handleNativeTouchStart, {
471
+ passive: true
472
+ });
473
+ container.addEventListener("touchmove", handleNativeTouchMove, {
474
+ passive: false
475
+ });
476
+ container.addEventListener("touchend", handleNativeTouchEnd, {
477
+ passive: true
478
+ });
479
+ return () => {
480
+ container.removeEventListener("touchstart", handleNativeTouchStart);
481
+ container.removeEventListener("touchmove", handleNativeTouchMove);
482
+ container.removeEventListener("touchend", handleNativeTouchEnd);
483
+ };
484
+ }, [
485
+ side,
486
+ enableTouchDrag,
487
+ containerHeight,
488
+ drawerFullHeight,
489
+ drawerPeekHeight,
490
+ safeAreaInsets,
491
+ onChangeState,
492
+ setTouchStartY,
493
+ setCurrentTouchY
494
+ ]);
331
495
  const isBackdropActive = state === "full";
332
496
  const backdropStyles = isBackdropActive ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none";
497
+ const containerH = containerHeight || window.innerHeight;
498
+ const fullPx = resolveHeightToPixels(drawerFullHeight || "85%", containerH);
499
+ const peekPx = resolveHeightToPixels(drawerPeekHeight || "15%", containerH);
500
+ const shiftPx = fullPx - peekPx;
333
501
  const getBottomDrawerTransform = () => {
334
- const containerH = containerHeight || window.innerHeight;
335
- const fullPx = resolveHeightToPixels(
336
- drawerFullHeight || "85%",
337
- containerH
338
- );
339
- const peekPx = resolveHeightToPixels(
340
- drawerPeekHeight || "15%",
341
- containerH
342
- );
343
502
  if (state === "full") {
344
- return `translateY(${clampedDragOffset}px)`;
503
+ return "translateY(var(--drag-offset, 0px))";
345
504
  }
346
505
  if (state === "peek") {
347
- const shiftPx2 = fullPx - peekPx;
348
- return `translateY(calc(${shiftPx2}px + ${clampedDragOffset}px))`;
506
+ return `translateY(calc(${shiftPx}px + var(--drag-offset, 0px)))`;
349
507
  }
350
508
  const closedVisibleHeight = 32;
351
- const shiftPx = fullPx - closedVisibleHeight;
352
- return `translateY(calc(${shiftPx}px + ${clampedDragOffset}px))`;
509
+ const closedShiftPx = fullPx - closedVisibleHeight;
510
+ return `translateY(calc(${closedShiftPx}px + var(--drag-offset, 0px)))`;
353
511
  };
354
512
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
355
513
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
@@ -379,12 +537,13 @@ var MapDrawer = (0, import_react.forwardRef)(
379
537
  bottom: `calc(1rem + ${etaBarHeight}px)`
380
538
  },
381
539
  children: [
382
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 flex flex-col h-full overflow-hidden pl-4 py-4 pr-1", children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 flex flex-col h-full overflow-hidden pl-4 py-4 pr-1 relative", children: [
383
541
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-between pb-3 border-b border-white/10 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-bold tracking-wider text-zinc-400 uppercase", children: "Menu" }) }),
384
542
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
385
543
  "div",
386
544
  {
387
545
  className: "flex-1 overflow-y-auto no-scrollbar pr-2",
546
+ onScroll: handleScroll,
388
547
  ref: scrollContainerRef,
389
548
  children: menuContent ? menuContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1.5", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
390
549
  "button",
@@ -408,6 +567,33 @@ var MapDrawer = (0, import_react.forwardRef)(
408
567
  item.id
409
568
  )) })
410
569
  }
570
+ ),
571
+ showScrollToTopButton && showScrollTop && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
572
+ "button",
573
+ {
574
+ className: "absolute bottom-6 right-6 z-50 flex items-center justify-center w-10 h-10 rounded-full bg-cyan-500 hover:bg-cyan-400 active:scale-95 text-black shadow-lg shadow-cyan-500/25 transition-all duration-200 cursor-pointer",
575
+ onClick: () => {
576
+ scrollContainerRef.current?.scrollTo({
577
+ behavior: "smooth",
578
+ top: 0
579
+ });
580
+ },
581
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
582
+ "svg",
583
+ {
584
+ fill: "none",
585
+ height: "20",
586
+ stroke: "currentColor",
587
+ strokeLinecap: "round",
588
+ strokeLinejoin: "round",
589
+ strokeWidth: "2.5",
590
+ viewBox: "0 0 24 24",
591
+ width: "20",
592
+ xmlns: "http://www.w3.org/2000/svg",
593
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
594
+ }
595
+ )
596
+ }
411
597
  )
412
598
  ] }),
413
599
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -427,14 +613,11 @@ var MapDrawer = (0, import_react.forwardRef)(
427
613
  "div",
428
614
  {
429
615
  className: `absolute w-full bg-zinc-950/85 backdrop-blur-md border border-white/10 rounded-t-2xl shadow-2xl flex flex-col z-40 select-none ${isDragging ? "transition-none" : "transition-transform duration-300 ease-out"}`,
430
- onTouchEnd: enableTouchDrag ? handleTouchEnd : void 0,
431
- onTouchMove: enableTouchDrag ? handleTouchMove : void 0,
432
- onTouchStart: enableTouchDrag ? handleTouchStart : void 0,
433
616
  ref: containerRef,
434
617
  style: {
435
618
  bottom: "0px",
436
- marginBottom: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px",
437
619
  height: typeof drawerFullHeight === "number" ? `${drawerFullHeight}px` : drawerFullHeight || "85%",
620
+ marginBottom: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px",
438
621
  transform: getBottomDrawerTransform(),
439
622
  transition: isDragging ? "none" : "transform 300ms ease-out, margin-bottom 250ms ease-in-out"
440
623
  },
@@ -447,41 +630,77 @@ var MapDrawer = (0, import_react.forwardRef)(
447
630
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-12 h-1 bg-white/25 rounded-full" })
448
631
  }
449
632
  ),
450
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
451
634
  "div",
452
635
  {
453
- className: "flex-1 flex flex-col overflow-hidden px-4 pb-4 pt-2 transition-[margin-top] duration-250 ease-in-out",
636
+ className: "flex-1 flex flex-col overflow-hidden px-4 pb-4 pt-2 transition-[margin-top] duration-250 ease-in-out relative",
454
637
  style: {
455
638
  marginTop: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px"
456
639
  },
457
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
458
- "div",
459
- {
460
- className: "flex-1 overflow-y-auto no-scrollbar",
461
- ref: scrollContainerRef,
462
- children: menuContent ? menuContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
463
- "button",
464
- {
465
- className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${item.disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
466
- disabled: item.disabled,
467
- onClick: () => {
468
- if (!item.disabled) {
469
- item.onPress();
470
- onChangeState("closed");
471
- }
640
+ children: [
641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
642
+ "div",
643
+ {
644
+ className: "flex-1 overflow-y-auto no-scrollbar",
645
+ onScroll: handleScroll,
646
+ ref: scrollContainerRef,
647
+ style: {
648
+ paddingBottom: state === "peek" ? `${shiftPx}px` : "0px"
649
+ },
650
+ children: menuContent ? menuContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
651
+ "button",
652
+ {
653
+ className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${item.disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
654
+ disabled: item.disabled,
655
+ onClick: () => {
656
+ if (!item.disabled) {
657
+ item.onPress();
658
+ onChangeState("closed");
659
+ }
660
+ },
661
+ children: [
662
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
663
+ item.icon,
664
+ item.badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: item.badge })
665
+ ] }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium tracking-wide", children: item.label })
667
+ ]
472
668
  },
473
- children: [
474
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
475
- item.icon,
476
- item.badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: item.badge })
477
- ] }),
478
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium tracking-wide", children: item.label })
479
- ]
669
+ item.id
670
+ )) })
671
+ }
672
+ ),
673
+ showScrollToTopButton && showScrollTop && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
674
+ "button",
675
+ {
676
+ className: "absolute right-6 z-50 flex items-center justify-center w-10 h-10 rounded-full bg-cyan-500 hover:bg-cyan-400 active:scale-95 text-black shadow-lg shadow-cyan-500/25 transition-all duration-200 cursor-pointer",
677
+ onClick: () => {
678
+ scrollContainerRef.current?.scrollTo({
679
+ behavior: "smooth",
680
+ top: 0
681
+ });
480
682
  },
481
- item.id
482
- )) })
483
- }
484
- )
683
+ style: {
684
+ bottom: state === "peek" ? `calc(${24 + shiftPx}px + var(--drag-offset, 0px))` : `calc(24px + var(--drag-offset, 0px))`
685
+ },
686
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
687
+ "svg",
688
+ {
689
+ fill: "none",
690
+ height: "20",
691
+ stroke: "currentColor",
692
+ strokeLinecap: "round",
693
+ strokeLinejoin: "round",
694
+ strokeWidth: "2.5",
695
+ viewBox: "0 0 24 24",
696
+ width: "20",
697
+ xmlns: "http://www.w3.org/2000/svg",
698
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
699
+ }
700
+ )
701
+ }
702
+ )
703
+ ]
485
704
  }
486
705
  )
487
706
  ]
@@ -735,7 +954,7 @@ var MapRightControls = ({
735
954
  customRecenterButton,
736
955
  customCompassButton,
737
956
  drawerPeekHeight,
738
- insets = { top: 0, bottom: 0, left: 0, right: 0 }
957
+ insets = { bottom: 0, left: 0, right: 0, top: 0 }
739
958
  }) => {
740
959
  const aboveControls = customControls.filter(
741
960
  (c) => c.position === "above-recenter"
@@ -747,7 +966,7 @@ var MapRightControls = ({
747
966
  const closedHeightOffset = "32px";
748
967
  const activeEtaHeight = etaData ? etaBarHeight : drawerState === "closed" ? insets.bottom : 0;
749
968
  const peekStyle = typeof drawerPeekHeight === "number" ? `${drawerPeekHeight}px` : drawerPeekHeight || "15%";
750
- const controlsBottom = side === "bottom" ? drawerState === "closed" ? `calc(${closedHeightOffset} + 1rem + ${activeEtaHeight}px - ${clampedDragOffset}px)` : `calc(${peekStyle} + 1rem + ${etaData ? etaBarHeight : 0}px - ${clampedDragOffset}px)` : `calc(1rem + ${insets.bottom}px)`;
969
+ 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)`;
751
970
  const isControlsVisible = side !== "bottom" || drawerState !== "full";
752
971
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
753
972
  "div",
@@ -903,10 +1122,10 @@ var SDKMapProvider = ({
903
1122
  });
904
1123
  }, []);
905
1124
  const [insets, setInsetsState] = (0, import_react2.useState)({
906
- top: 0,
907
1125
  bottom: 0,
908
1126
  left: 0,
909
- right: 0
1127
+ right: 0,
1128
+ top: 0
910
1129
  });
911
1130
  const setInsets = (0, import_react2.useCallback)((newInsets) => {
912
1131
  setInsetsState((prev) => ({ ...prev, ...newInsets }));
@@ -951,6 +1170,7 @@ var SDKMapProvider = ({
951
1170
  drawerState,
952
1171
  etaData,
953
1172
  hasControls,
1173
+ insets,
954
1174
  isFollowing,
955
1175
  pitch,
956
1176
  resetBearing,
@@ -958,10 +1178,9 @@ var SDKMapProvider = ({
958
1178
  setDrawerState,
959
1179
  setEtaData,
960
1180
  setHasControls,
1181
+ setInsets,
961
1182
  setIsFollowing,
962
- setPitch,
963
- insets,
964
- setInsets
1183
+ setPitch
965
1184
  }),
966
1185
  [
967
1186
  _registerCenterCallback,
@@ -1025,6 +1244,8 @@ var MapControls = ({
1025
1244
  menuContent,
1026
1245
  children,
1027
1246
  enableTouchDrag = false,
1247
+ showScrollToTopButton = true,
1248
+ scrollToTopThreshold = 300,
1028
1249
  isInternal = false
1029
1250
  }) => {
1030
1251
  const mapContext = (0, import_react3.useContext)(MapControlsContext);
@@ -1125,14 +1346,23 @@ var MapControls = ({
1125
1346
  }, [drawerSide]);
1126
1347
  const isDragging = touchStartY !== null && currentTouchY !== null;
1127
1348
  const dragOffset = isDragging ? currentTouchY - touchStartY : 0;
1349
+ const fullPx = resolveHeightToPixels2(drawerFullHeight || "85%", mapHeight);
1350
+ const peekPx = resolveHeightToPixels2(drawerPeekHeight || "15%", mapHeight);
1351
+ const bottomInset = mapContext?.insets.bottom || 0;
1352
+ const closedPx = 32 + (drawerState === "closed" ? bottomInset : 0);
1128
1353
  let clampedDragOffset = dragOffset;
1129
1354
  if (drawerState === "full") {
1130
- clampedDragOffset = Math.max(0, dragOffset);
1355
+ clampedDragOffset = Math.max(0, Math.min(fullPx - closedPx, dragOffset));
1356
+ } else if (drawerState === "peek") {
1357
+ clampedDragOffset = Math.max(
1358
+ -(fullPx - peekPx),
1359
+ // up to full
1360
+ Math.min(peekPx - closedPx, dragOffset)
1361
+ // down to closed
1362
+ );
1131
1363
  } else if (drawerState === "closed") {
1132
- clampedDragOffset = Math.min(0, dragOffset);
1364
+ clampedDragOffset = Math.min(0, Math.max(-(fullPx - closedPx), dragOffset));
1133
1365
  }
1134
- const fullPx = resolveHeightToPixels2(drawerFullHeight || "85%", mapHeight);
1135
- const peekPx = resolveHeightToPixels2(drawerPeekHeight || "15%", mapHeight);
1136
1366
  let baseHeight = 32;
1137
1367
  if (drawerState === "peek") {
1138
1368
  baseHeight = peekPx;
@@ -1142,7 +1372,6 @@ var MapControls = ({
1142
1372
  const currentDrawerHeight = baseHeight - clampedDragOffset;
1143
1373
  const startFadeHeight = peekPx + 20;
1144
1374
  const endFadeHeight = peekPx + (fullPx - peekPx) * 0.35;
1145
- const bottomInset = mapContext?.insets.bottom || 0;
1146
1375
  let etaOpacity = 1;
1147
1376
  let etaBottomStyle = "1rem";
1148
1377
  if (side === "bottom") {
@@ -1155,7 +1384,7 @@ var MapControls = ({
1155
1384
  etaOpacity = 1 - fraction;
1156
1385
  }
1157
1386
  const effectiveInset = drawerState === "closed" ? bottomInset : 0;
1158
- etaBottomStyle = `${currentDrawerHeight + effectiveInset + 16}px`;
1387
+ etaBottomStyle = `calc(${baseHeight + effectiveInset + 16}px - var(--drag-offset, 0px))`;
1159
1388
  } else {
1160
1389
  etaOpacity = drawerState === "full" ? 0 : 1;
1161
1390
  etaBottomStyle = `calc(1rem + ${bottomInset}px)`;
@@ -1179,8 +1408,10 @@ var MapControls = ({
1179
1408
  onChangeState: setDrawerState,
1180
1409
  ref: mergedRef,
1181
1410
  safeAreaInsets: mapContext?.insets,
1411
+ scrollToTopThreshold,
1182
1412
  setCurrentTouchY,
1183
1413
  setTouchStartY,
1414
+ showScrollToTopButton,
1184
1415
  side,
1185
1416
  state: drawerState,
1186
1417
  touchStartY
@@ -2591,7 +2822,7 @@ var RoutingService = {
2591
2822
  };
2592
2823
 
2593
2824
  // package.json
2594
- var version = "0.2.0";
2825
+ var version = "0.2.1";
2595
2826
 
2596
2827
  // src/sdk.ts
2597
2828
  function deepFreeze(obj) {