@sesionario/blocks 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -119,8 +119,8 @@ type ImageLightboxProps = {
119
119
  };
120
120
  /**
121
121
  * Fullscreen image gallery viewer. Portal overlay with Esc/click-to-close,
122
- * arrow-key navigation, wheel/pinch zoom, and pan when zoomed. Locks page
123
- * scroll while mounted.
122
+ * arrow-key / swipe navigation, wheel/pinch zoom, and pan when zoomed. Locks
123
+ * page scroll while mounted.
124
124
  */
125
125
  declare function ImageLightbox({ images, index, onIndexChange, onClose, labels, }: ImageLightboxProps): React.ReactPortal;
126
126
 
package/dist/index.d.ts CHANGED
@@ -119,8 +119,8 @@ type ImageLightboxProps = {
119
119
  };
120
120
  /**
121
121
  * Fullscreen image gallery viewer. Portal overlay with Esc/click-to-close,
122
- * arrow-key navigation, wheel/pinch zoom, and pan when zoomed. Locks page
123
- * scroll while mounted.
122
+ * arrow-key / swipe navigation, wheel/pinch zoom, and pan when zoomed. Locks
123
+ * page scroll while mounted.
124
124
  */
125
125
  declare function ImageLightbox({ images, index, onIndexChange, onClose, labels, }: ImageLightboxProps): React.ReactPortal;
126
126
 
package/dist/index.js CHANGED
@@ -490,6 +490,9 @@ function ExpandableText({
490
490
  var MIN_SCALE = 1;
491
491
  var MAX_SCALE = 4;
492
492
  var DOUBLE_TAP_MS = 300;
493
+ var SWIPE_LOCK_PX = 8;
494
+ var SWIPE_COMMIT_PX = 56;
495
+ var SWIPE_SNAP_MS = 180;
493
496
  var IDENTITY = { scale: 1, tx: 0, ty: 0 };
494
497
  function clamp(value, min, max) {
495
498
  return Math.min(max, Math.max(min, value));
@@ -507,6 +510,10 @@ function zoomToward(prev, nextScale, originX, originY) {
507
510
  function pointerDistance(a, b) {
508
511
  return Math.hypot(b.x - a.x, b.y - a.y);
509
512
  }
513
+ function prefersReducedMotion() {
514
+ if (typeof window === "undefined" || !window.matchMedia) return false;
515
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
516
+ }
510
517
  function ImageLightbox({
511
518
  images,
512
519
  index,
@@ -522,15 +529,24 @@ function ImageLightbox({
522
529
  const [transform, setTransform] = useState(IDENTITY);
523
530
  const transformRef = useRef(transform);
524
531
  transformRef.current = transform;
532
+ const [isSwipeDragging, setIsSwipeDragging] = useState(false);
533
+ const [snapBack, setSnapBack] = useState(false);
525
534
  const stageRef = useRef(null);
526
535
  const pointersRef = useRef(/* @__PURE__ */ new Map());
527
536
  const pinchStartRef = useRef(null);
528
537
  const panRef = useRef(null);
538
+ const swipeRef = useRef(null);
529
539
  const lastTapRef = useRef(
530
540
  null
531
541
  );
542
+ const indexRef = useRef(index);
543
+ indexRef.current = index;
544
+ const imagesLengthRef = useRef(images.length);
545
+ imagesLengthRef.current = images.length;
532
546
  const resetZoom = useCallback(() => {
533
547
  setTransform(IDENTITY);
548
+ setIsSwipeDragging(false);
549
+ setSnapBack(false);
534
550
  }, []);
535
551
  useEffect(() => {
536
552
  resetZoom();
@@ -587,6 +603,10 @@ function ImageLightbox({
587
603
  stage.addEventListener("wheel", handleWheel, { passive: false });
588
604
  return () => stage.removeEventListener("wheel", handleWheel);
589
605
  }, [stageOrigin]);
606
+ function clearSwipe() {
607
+ swipeRef.current = null;
608
+ setIsSwipeDragging(false);
609
+ }
590
610
  function beginPinch() {
591
611
  const pts = [...pointersRef.current.values()];
592
612
  const [a, b] = pts;
@@ -601,13 +621,18 @@ function ImageLightbox({
601
621
  transform: { ...transformRef.current }
602
622
  };
603
623
  panRef.current = null;
624
+ clearSwipe();
625
+ setSnapBack(false);
604
626
  }
605
627
  function onPointerDown(e) {
606
628
  e.stopPropagation();
607
629
  const stage = stageRef.current;
608
630
  if (!stage) return;
609
- stage.setPointerCapture(e.pointerId);
631
+ if (typeof stage.setPointerCapture === "function") {
632
+ stage.setPointerCapture(e.pointerId);
633
+ }
610
634
  pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
635
+ setSnapBack(false);
611
636
  if (pointersRef.current.size === 2) {
612
637
  beginPinch();
613
638
  return;
@@ -621,6 +646,17 @@ function ImageLightbox({
621
646
  originTy: transformRef.current.ty,
622
647
  moved: false
623
648
  };
649
+ return;
650
+ }
651
+ if (hasMultiple) {
652
+ swipeRef.current = {
653
+ pointerId: e.pointerId,
654
+ startX: e.clientX,
655
+ startY: e.clientY,
656
+ moved: false,
657
+ axis: null
658
+ };
659
+ setIsSwipeDragging(true);
624
660
  }
625
661
  }
626
662
  function onPointerMove(e) {
@@ -645,19 +681,62 @@ function ImageLightbox({
645
681
  }
646
682
  const pan = panRef.current;
647
683
  if (pan && pan.pointerId === e.pointerId) {
648
- const dx = e.clientX - pan.startX;
649
- const dy = e.clientY - pan.startY;
650
- if (Math.abs(dx) > 2 || Math.abs(dy) > 2) pan.moved = true;
684
+ const dx2 = e.clientX - pan.startX;
685
+ const dy2 = e.clientY - pan.startY;
686
+ if (Math.abs(dx2) > 2 || Math.abs(dy2) > 2) pan.moved = true;
651
687
  setTransform((prev) => ({
652
688
  ...prev,
653
- tx: pan.originTx + dx,
654
- ty: pan.originTy + dy
689
+ tx: pan.originTx + dx2,
690
+ ty: pan.originTy + dy2
655
691
  }));
692
+ return;
693
+ }
694
+ const swipe = swipeRef.current;
695
+ if (!swipe || swipe.pointerId !== e.pointerId) return;
696
+ const dx = e.clientX - swipe.startX;
697
+ const dy = e.clientY - swipe.startY;
698
+ if (Math.abs(dx) > 2 || Math.abs(dy) > 2) swipe.moved = true;
699
+ if (swipe.axis === null) {
700
+ const absX = Math.abs(dx);
701
+ const absY = Math.abs(dy);
702
+ if (absX < SWIPE_LOCK_PX && absY < SWIPE_LOCK_PX) return;
703
+ swipe.axis = absX >= absY ? "x" : "y";
704
+ if (swipe.axis === "y") {
705
+ clearSwipe();
706
+ setTransform(IDENTITY);
707
+ return;
708
+ }
709
+ }
710
+ if (swipe.axis === "x") {
711
+ setTransform({ scale: MIN_SCALE, tx: dx, ty: 0 });
712
+ }
713
+ }
714
+ function finishSwipe(e) {
715
+ const swipe = swipeRef.current;
716
+ if (!swipe || swipe.pointerId !== e.pointerId) return false;
717
+ const dx = e.clientX - swipe.startX;
718
+ const wasMoved = swipe.moved;
719
+ const axis = swipe.axis;
720
+ clearSwipe();
721
+ if (axis === "x" && Math.abs(dx) >= SWIPE_COMMIT_PX) {
722
+ const length = imagesLengthRef.current;
723
+ const current = indexRef.current;
724
+ if (dx < 0) {
725
+ onIndexChange((current + 1) % length);
726
+ } else {
727
+ onIndexChange((current - 1 + length) % length);
728
+ }
729
+ return true;
730
+ }
731
+ if (wasMoved || transformRef.current.tx !== 0) {
732
+ if (!prefersReducedMotion()) setSnapBack(true);
733
+ setTransform(IDENTITY);
656
734
  }
735
+ return wasMoved;
657
736
  }
658
737
  function onPointerUp(e) {
659
738
  const stage = stageRef.current;
660
- if (stage?.hasPointerCapture(e.pointerId)) {
739
+ if (stage && typeof stage.hasPointerCapture === "function" && stage.hasPointerCapture(e.pointerId) && typeof stage.releasePointerCapture === "function") {
661
740
  stage.releasePointerCapture(e.pointerId);
662
741
  }
663
742
  pointersRef.current.delete(e.pointerId);
@@ -678,6 +757,8 @@ function ImageLightbox({
678
757
  };
679
758
  }
680
759
  }
760
+ const swipeHandled = finishSwipe(e);
761
+ if (swipeHandled) return;
681
762
  const pan = panRef.current;
682
763
  if (pan && pan.pointerId === e.pointerId) {
683
764
  const wasMoved = pan.moved;
@@ -699,6 +780,7 @@ function ImageLightbox({
699
780
  e.stopPropagation();
700
781
  }
701
782
  const { scale, tx, ty } = transform;
783
+ const transition = snapBack && !isSwipeDragging && !prefersReducedMotion() ? `transform ${SWIPE_SNAP_MS}ms ease-out` : void 0;
702
784
  return createPortal(
703
785
  /* @__PURE__ */ jsxs(
704
786
  "div",
@@ -738,6 +820,7 @@ function ImageLightbox({
738
820
  ref: stageRef,
739
821
  className: "relative flex max-h-full max-w-full touch-none items-center justify-center",
740
822
  "data-scale": scale,
823
+ "data-swipe-dragging": isSwipeDragging ? "true" : "false",
741
824
  onClick: stopDialogClose,
742
825
  onPointerDown,
743
826
  onPointerMove,
@@ -752,8 +835,10 @@ function ImageLightbox({
752
835
  className: "max-h-[calc(100dvh-2rem)] max-w-full select-none rounded-lg object-contain",
753
836
  style: {
754
837
  transform: `translate(${tx}px, ${ty}px) scale(${scale})`,
838
+ transition,
755
839
  cursor: scale > MIN_SCALE ? "grab" : "default"
756
- }
840
+ },
841
+ onTransitionEnd: () => setSnapBack(false)
757
842
  }
758
843
  )
759
844
  }
@@ -3679,6 +3764,19 @@ var PLAYBACK_RATES = [0.75, 1, 1.25, 1.5, 2];
3679
3764
  var CONTROLS_HIDE_MS = 3e3;
3680
3765
  var LIVE_EDGE_THRESHOLD_S = 8;
3681
3766
  var SEEK_STEP_S = 5;
3767
+ function asWebkitVideo(video) {
3768
+ return video;
3769
+ }
3770
+ function hasWebkitVideoFullscreen(video) {
3771
+ const webkit = asWebkitVideo(video);
3772
+ return typeof webkit?.webkitEnterFullscreen === "function";
3773
+ }
3774
+ function hasStandardFullscreen() {
3775
+ if (typeof document === "undefined") return false;
3776
+ if (document.fullscreenEnabled) return true;
3777
+ const doc = document;
3778
+ return !!doc.webkitFullscreenEnabled;
3779
+ }
3682
3780
  function formatTime(seconds) {
3683
3781
  if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
3684
3782
  const total = Math.floor(seconds);
@@ -3752,10 +3850,10 @@ function VideoPlayer({
3752
3850
  null
3753
3851
  );
3754
3852
  const [behindLive, setBehindLive] = React6.useState(false);
3853
+ const [webkitFullscreenSupported, setWebkitFullscreenSupported] = React6.useState(false);
3755
3854
  const hideTimerRef = React6.useRef(null);
3756
3855
  const pipSupported = typeof document !== "undefined" && "pictureInPictureEnabled" in document && document.pictureInPictureEnabled;
3757
- const fullscreenSupported = typeof document !== "undefined" && (!!document.fullscreenEnabled || // Safari
3758
- "webkitFullscreenEnabled" in document);
3856
+ const fullscreenSupported = hasStandardFullscreen() || webkitFullscreenSupported;
3759
3857
  React6.useEffect(() => {
3760
3858
  const video = videoRef.current;
3761
3859
  if (!video || !active) return;
@@ -3789,11 +3887,26 @@ function VideoPlayer({
3789
3887
  video.removeEventListener("ratechange", onRate);
3790
3888
  };
3791
3889
  }, [videoRef, active, mode]);
3890
+ React6.useEffect(() => {
3891
+ setWebkitFullscreenSupported(hasWebkitVideoFullscreen(videoRef.current));
3892
+ }, [videoRef, active]);
3792
3893
  React6.useEffect(() => {
3793
3894
  const onFsChange = () => setIsFullscreen(document.fullscreenElement === containerRef.current);
3794
3895
  document.addEventListener("fullscreenchange", onFsChange);
3795
3896
  return () => document.removeEventListener("fullscreenchange", onFsChange);
3796
3897
  }, []);
3898
+ React6.useEffect(() => {
3899
+ const video = asWebkitVideo(videoRef.current);
3900
+ if (!video) return;
3901
+ const onBegin = () => setIsFullscreen(true);
3902
+ const onEnd = () => setIsFullscreen(false);
3903
+ video.addEventListener("webkitbeginfullscreen", onBegin);
3904
+ video.addEventListener("webkitendfullscreen", onEnd);
3905
+ return () => {
3906
+ video.removeEventListener("webkitbeginfullscreen", onBegin);
3907
+ video.removeEventListener("webkitendfullscreen", onEnd);
3908
+ };
3909
+ }, [videoRef, active]);
3797
3910
  React6.useEffect(() => {
3798
3911
  const video = videoRef.current;
3799
3912
  if (!video) return;
@@ -3858,11 +3971,21 @@ function VideoPlayer({
3858
3971
  }
3859
3972
  function toggleFullscreen() {
3860
3973
  const el = containerRef.current;
3861
- if (!el) return;
3862
- if (document.fullscreenElement === el) {
3974
+ const video = asWebkitVideo(videoRef.current);
3975
+ if (el && document.fullscreenElement === el) {
3863
3976
  void document.exitFullscreen();
3864
- } else {
3865
- void el.requestFullscreen?.();
3977
+ return;
3978
+ }
3979
+ if (video?.webkitDisplayingFullscreen && video.webkitExitFullscreen) {
3980
+ video.webkitExitFullscreen();
3981
+ return;
3982
+ }
3983
+ if (el && hasStandardFullscreen() && typeof el.requestFullscreen === "function") {
3984
+ void el.requestFullscreen();
3985
+ return;
3986
+ }
3987
+ if (video && typeof video.webkitEnterFullscreen === "function") {
3988
+ video.webkitEnterFullscreen();
3866
3989
  }
3867
3990
  }
3868
3991
  function togglePip() {