@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.cjs CHANGED
@@ -521,6 +521,9 @@ function ExpandableText({
521
521
  var MIN_SCALE = 1;
522
522
  var MAX_SCALE = 4;
523
523
  var DOUBLE_TAP_MS = 300;
524
+ var SWIPE_LOCK_PX = 8;
525
+ var SWIPE_COMMIT_PX = 56;
526
+ var SWIPE_SNAP_MS = 180;
524
527
  var IDENTITY = { scale: 1, tx: 0, ty: 0 };
525
528
  function clamp(value, min, max) {
526
529
  return Math.min(max, Math.max(min, value));
@@ -538,6 +541,10 @@ function zoomToward(prev, nextScale, originX, originY) {
538
541
  function pointerDistance(a, b) {
539
542
  return Math.hypot(b.x - a.x, b.y - a.y);
540
543
  }
544
+ function prefersReducedMotion() {
545
+ if (typeof window === "undefined" || !window.matchMedia) return false;
546
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
547
+ }
541
548
  function ImageLightbox({
542
549
  images,
543
550
  index,
@@ -553,15 +560,24 @@ function ImageLightbox({
553
560
  const [transform, setTransform] = React6.useState(IDENTITY);
554
561
  const transformRef = React6.useRef(transform);
555
562
  transformRef.current = transform;
563
+ const [isSwipeDragging, setIsSwipeDragging] = React6.useState(false);
564
+ const [snapBack, setSnapBack] = React6.useState(false);
556
565
  const stageRef = React6.useRef(null);
557
566
  const pointersRef = React6.useRef(/* @__PURE__ */ new Map());
558
567
  const pinchStartRef = React6.useRef(null);
559
568
  const panRef = React6.useRef(null);
569
+ const swipeRef = React6.useRef(null);
560
570
  const lastTapRef = React6.useRef(
561
571
  null
562
572
  );
573
+ const indexRef = React6.useRef(index);
574
+ indexRef.current = index;
575
+ const imagesLengthRef = React6.useRef(images.length);
576
+ imagesLengthRef.current = images.length;
563
577
  const resetZoom = React6.useCallback(() => {
564
578
  setTransform(IDENTITY);
579
+ setIsSwipeDragging(false);
580
+ setSnapBack(false);
565
581
  }, []);
566
582
  React6.useEffect(() => {
567
583
  resetZoom();
@@ -618,6 +634,10 @@ function ImageLightbox({
618
634
  stage.addEventListener("wheel", handleWheel, { passive: false });
619
635
  return () => stage.removeEventListener("wheel", handleWheel);
620
636
  }, [stageOrigin]);
637
+ function clearSwipe() {
638
+ swipeRef.current = null;
639
+ setIsSwipeDragging(false);
640
+ }
621
641
  function beginPinch() {
622
642
  const pts = [...pointersRef.current.values()];
623
643
  const [a, b] = pts;
@@ -632,13 +652,18 @@ function ImageLightbox({
632
652
  transform: { ...transformRef.current }
633
653
  };
634
654
  panRef.current = null;
655
+ clearSwipe();
656
+ setSnapBack(false);
635
657
  }
636
658
  function onPointerDown(e) {
637
659
  e.stopPropagation();
638
660
  const stage = stageRef.current;
639
661
  if (!stage) return;
640
- stage.setPointerCapture(e.pointerId);
662
+ if (typeof stage.setPointerCapture === "function") {
663
+ stage.setPointerCapture(e.pointerId);
664
+ }
641
665
  pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
666
+ setSnapBack(false);
642
667
  if (pointersRef.current.size === 2) {
643
668
  beginPinch();
644
669
  return;
@@ -652,6 +677,17 @@ function ImageLightbox({
652
677
  originTy: transformRef.current.ty,
653
678
  moved: false
654
679
  };
680
+ return;
681
+ }
682
+ if (hasMultiple) {
683
+ swipeRef.current = {
684
+ pointerId: e.pointerId,
685
+ startX: e.clientX,
686
+ startY: e.clientY,
687
+ moved: false,
688
+ axis: null
689
+ };
690
+ setIsSwipeDragging(true);
655
691
  }
656
692
  }
657
693
  function onPointerMove(e) {
@@ -676,19 +712,62 @@ function ImageLightbox({
676
712
  }
677
713
  const pan = panRef.current;
678
714
  if (pan && pan.pointerId === e.pointerId) {
679
- const dx = e.clientX - pan.startX;
680
- const dy = e.clientY - pan.startY;
681
- if (Math.abs(dx) > 2 || Math.abs(dy) > 2) pan.moved = true;
715
+ const dx2 = e.clientX - pan.startX;
716
+ const dy2 = e.clientY - pan.startY;
717
+ if (Math.abs(dx2) > 2 || Math.abs(dy2) > 2) pan.moved = true;
682
718
  setTransform((prev) => ({
683
719
  ...prev,
684
- tx: pan.originTx + dx,
685
- ty: pan.originTy + dy
720
+ tx: pan.originTx + dx2,
721
+ ty: pan.originTy + dy2
686
722
  }));
723
+ return;
724
+ }
725
+ const swipe = swipeRef.current;
726
+ if (!swipe || swipe.pointerId !== e.pointerId) return;
727
+ const dx = e.clientX - swipe.startX;
728
+ const dy = e.clientY - swipe.startY;
729
+ if (Math.abs(dx) > 2 || Math.abs(dy) > 2) swipe.moved = true;
730
+ if (swipe.axis === null) {
731
+ const absX = Math.abs(dx);
732
+ const absY = Math.abs(dy);
733
+ if (absX < SWIPE_LOCK_PX && absY < SWIPE_LOCK_PX) return;
734
+ swipe.axis = absX >= absY ? "x" : "y";
735
+ if (swipe.axis === "y") {
736
+ clearSwipe();
737
+ setTransform(IDENTITY);
738
+ return;
739
+ }
740
+ }
741
+ if (swipe.axis === "x") {
742
+ setTransform({ scale: MIN_SCALE, tx: dx, ty: 0 });
743
+ }
744
+ }
745
+ function finishSwipe(e) {
746
+ const swipe = swipeRef.current;
747
+ if (!swipe || swipe.pointerId !== e.pointerId) return false;
748
+ const dx = e.clientX - swipe.startX;
749
+ const wasMoved = swipe.moved;
750
+ const axis = swipe.axis;
751
+ clearSwipe();
752
+ if (axis === "x" && Math.abs(dx) >= SWIPE_COMMIT_PX) {
753
+ const length = imagesLengthRef.current;
754
+ const current = indexRef.current;
755
+ if (dx < 0) {
756
+ onIndexChange((current + 1) % length);
757
+ } else {
758
+ onIndexChange((current - 1 + length) % length);
759
+ }
760
+ return true;
761
+ }
762
+ if (wasMoved || transformRef.current.tx !== 0) {
763
+ if (!prefersReducedMotion()) setSnapBack(true);
764
+ setTransform(IDENTITY);
687
765
  }
766
+ return wasMoved;
688
767
  }
689
768
  function onPointerUp(e) {
690
769
  const stage = stageRef.current;
691
- if (stage?.hasPointerCapture(e.pointerId)) {
770
+ if (stage && typeof stage.hasPointerCapture === "function" && stage.hasPointerCapture(e.pointerId) && typeof stage.releasePointerCapture === "function") {
692
771
  stage.releasePointerCapture(e.pointerId);
693
772
  }
694
773
  pointersRef.current.delete(e.pointerId);
@@ -709,6 +788,8 @@ function ImageLightbox({
709
788
  };
710
789
  }
711
790
  }
791
+ const swipeHandled = finishSwipe(e);
792
+ if (swipeHandled) return;
712
793
  const pan = panRef.current;
713
794
  if (pan && pan.pointerId === e.pointerId) {
714
795
  const wasMoved = pan.moved;
@@ -730,6 +811,7 @@ function ImageLightbox({
730
811
  e.stopPropagation();
731
812
  }
732
813
  const { scale, tx, ty } = transform;
814
+ const transition = snapBack && !isSwipeDragging && !prefersReducedMotion() ? `transform ${SWIPE_SNAP_MS}ms ease-out` : void 0;
733
815
  return reactDom.createPortal(
734
816
  /* @__PURE__ */ jsxRuntime.jsxs(
735
817
  "div",
@@ -769,6 +851,7 @@ function ImageLightbox({
769
851
  ref: stageRef,
770
852
  className: "relative flex max-h-full max-w-full touch-none items-center justify-center",
771
853
  "data-scale": scale,
854
+ "data-swipe-dragging": isSwipeDragging ? "true" : "false",
772
855
  onClick: stopDialogClose,
773
856
  onPointerDown,
774
857
  onPointerMove,
@@ -783,8 +866,10 @@ function ImageLightbox({
783
866
  className: "max-h-[calc(100dvh-2rem)] max-w-full select-none rounded-lg object-contain",
784
867
  style: {
785
868
  transform: `translate(${tx}px, ${ty}px) scale(${scale})`,
869
+ transition,
786
870
  cursor: scale > MIN_SCALE ? "grab" : "default"
787
- }
871
+ },
872
+ onTransitionEnd: () => setSnapBack(false)
788
873
  }
789
874
  )
790
875
  }
@@ -3710,6 +3795,19 @@ var PLAYBACK_RATES = [0.75, 1, 1.25, 1.5, 2];
3710
3795
  var CONTROLS_HIDE_MS = 3e3;
3711
3796
  var LIVE_EDGE_THRESHOLD_S = 8;
3712
3797
  var SEEK_STEP_S = 5;
3798
+ function asWebkitVideo(video) {
3799
+ return video;
3800
+ }
3801
+ function hasWebkitVideoFullscreen(video) {
3802
+ const webkit = asWebkitVideo(video);
3803
+ return typeof webkit?.webkitEnterFullscreen === "function";
3804
+ }
3805
+ function hasStandardFullscreen() {
3806
+ if (typeof document === "undefined") return false;
3807
+ if (document.fullscreenEnabled) return true;
3808
+ const doc = document;
3809
+ return !!doc.webkitFullscreenEnabled;
3810
+ }
3713
3811
  function formatTime(seconds) {
3714
3812
  if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
3715
3813
  const total = Math.floor(seconds);
@@ -3783,10 +3881,10 @@ function VideoPlayer({
3783
3881
  null
3784
3882
  );
3785
3883
  const [behindLive, setBehindLive] = React6__namespace.useState(false);
3884
+ const [webkitFullscreenSupported, setWebkitFullscreenSupported] = React6__namespace.useState(false);
3786
3885
  const hideTimerRef = React6__namespace.useRef(null);
3787
3886
  const pipSupported = typeof document !== "undefined" && "pictureInPictureEnabled" in document && document.pictureInPictureEnabled;
3788
- const fullscreenSupported = typeof document !== "undefined" && (!!document.fullscreenEnabled || // Safari
3789
- "webkitFullscreenEnabled" in document);
3887
+ const fullscreenSupported = hasStandardFullscreen() || webkitFullscreenSupported;
3790
3888
  React6__namespace.useEffect(() => {
3791
3889
  const video = videoRef.current;
3792
3890
  if (!video || !active) return;
@@ -3820,11 +3918,26 @@ function VideoPlayer({
3820
3918
  video.removeEventListener("ratechange", onRate);
3821
3919
  };
3822
3920
  }, [videoRef, active, mode]);
3921
+ React6__namespace.useEffect(() => {
3922
+ setWebkitFullscreenSupported(hasWebkitVideoFullscreen(videoRef.current));
3923
+ }, [videoRef, active]);
3823
3924
  React6__namespace.useEffect(() => {
3824
3925
  const onFsChange = () => setIsFullscreen(document.fullscreenElement === containerRef.current);
3825
3926
  document.addEventListener("fullscreenchange", onFsChange);
3826
3927
  return () => document.removeEventListener("fullscreenchange", onFsChange);
3827
3928
  }, []);
3929
+ React6__namespace.useEffect(() => {
3930
+ const video = asWebkitVideo(videoRef.current);
3931
+ if (!video) return;
3932
+ const onBegin = () => setIsFullscreen(true);
3933
+ const onEnd = () => setIsFullscreen(false);
3934
+ video.addEventListener("webkitbeginfullscreen", onBegin);
3935
+ video.addEventListener("webkitendfullscreen", onEnd);
3936
+ return () => {
3937
+ video.removeEventListener("webkitbeginfullscreen", onBegin);
3938
+ video.removeEventListener("webkitendfullscreen", onEnd);
3939
+ };
3940
+ }, [videoRef, active]);
3828
3941
  React6__namespace.useEffect(() => {
3829
3942
  const video = videoRef.current;
3830
3943
  if (!video) return;
@@ -3889,11 +4002,21 @@ function VideoPlayer({
3889
4002
  }
3890
4003
  function toggleFullscreen() {
3891
4004
  const el = containerRef.current;
3892
- if (!el) return;
3893
- if (document.fullscreenElement === el) {
4005
+ const video = asWebkitVideo(videoRef.current);
4006
+ if (el && document.fullscreenElement === el) {
3894
4007
  void document.exitFullscreen();
3895
- } else {
3896
- void el.requestFullscreen?.();
4008
+ return;
4009
+ }
4010
+ if (video?.webkitDisplayingFullscreen && video.webkitExitFullscreen) {
4011
+ video.webkitExitFullscreen();
4012
+ return;
4013
+ }
4014
+ if (el && hasStandardFullscreen() && typeof el.requestFullscreen === "function") {
4015
+ void el.requestFullscreen();
4016
+ return;
4017
+ }
4018
+ if (video && typeof video.webkitEnterFullscreen === "function") {
4019
+ video.webkitEnterFullscreen();
3897
4020
  }
3898
4021
  }
3899
4022
  function togglePip() {