@oxyhq/bloom 1.1.0 → 1.2.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.
Files changed (150) hide show
  1. package/docs/media-flight.mdx +272 -0
  2. package/docs/migrating-the-media-gallery.mdx +103 -0
  3. package/docs/migrating-to-1.0.mdx +1 -1
  4. package/docs/zoomable-media-gallery.mdx +116 -0
  5. package/lib/commonjs/image-aspect-ratio-cache/aspect-ratio-cache.js +1 -1
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/index.web.js.map +1 -1
  8. package/lib/commonjs/media-flight/MediaFlightLayer.js +151 -0
  9. package/lib/commonjs/media-flight/MediaFlightLayer.js.map +1 -0
  10. package/lib/commonjs/media-flight/MediaSurface.js +188 -0
  11. package/lib/commonjs/media-flight/MediaSurface.js.map +1 -0
  12. package/lib/commonjs/media-flight/constants.js +39 -0
  13. package/lib/commonjs/media-flight/constants.js.map +1 -0
  14. package/lib/commonjs/media-flight/expo-video-module.js +148 -0
  15. package/lib/commonjs/media-flight/expo-video-module.js.map +1 -0
  16. package/lib/commonjs/media-flight/index.js +102 -0
  17. package/lib/commonjs/media-flight/index.js.map +1 -0
  18. package/lib/commonjs/media-flight/store.js +457 -0
  19. package/lib/commonjs/media-flight/store.js.map +1 -0
  20. package/lib/commonjs/media-flight/types.js +6 -0
  21. package/lib/commonjs/media-flight/types.js.map +1 -0
  22. package/lib/commonjs/media-flight/use-media-flight.js +41 -0
  23. package/lib/commonjs/media-flight/use-media-flight.js.map +1 -0
  24. package/lib/commonjs/tab-bar/TabBarBase.js +1 -1
  25. package/lib/commonjs/{zoomable-image-gallery/ZoomableImageGallery.js → zoomable-media-gallery/ZoomableMediaGallery.js} +185 -131
  26. package/lib/commonjs/zoomable-media-gallery/ZoomableMediaGallery.js.map +1 -0
  27. package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.js.map +1 -1
  28. package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/index.js +5 -5
  29. package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/index.js.map +1 -1
  30. package/lib/commonjs/zoomable-media-gallery/types.js +6 -0
  31. package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/types.js.map +1 -1
  32. package/lib/module/image-aspect-ratio-cache/aspect-ratio-cache.js +1 -1
  33. package/lib/module/index.js +2 -1
  34. package/lib/module/index.js.map +1 -1
  35. package/lib/module/index.web.js +2 -1
  36. package/lib/module/index.web.js.map +1 -1
  37. package/lib/module/media-flight/MediaFlightLayer.js +146 -0
  38. package/lib/module/media-flight/MediaFlightLayer.js.map +1 -0
  39. package/lib/module/media-flight/MediaSurface.js +180 -0
  40. package/lib/module/media-flight/MediaSurface.js.map +1 -0
  41. package/lib/module/media-flight/constants.js +35 -0
  42. package/lib/module/media-flight/constants.js.map +1 -0
  43. package/lib/module/media-flight/expo-video-module.js +142 -0
  44. package/lib/module/media-flight/expo-video-module.js.map +1 -0
  45. package/lib/module/media-flight/index.js +9 -0
  46. package/lib/module/media-flight/index.js.map +1 -0
  47. package/lib/module/media-flight/store.js +439 -0
  48. package/lib/module/media-flight/store.js.map +1 -0
  49. package/lib/module/media-flight/types.js +4 -0
  50. package/lib/module/media-flight/types.js.map +1 -0
  51. package/lib/module/media-flight/use-media-flight.js +36 -0
  52. package/lib/module/media-flight/use-media-flight.js.map +1 -0
  53. package/lib/module/tab-bar/TabBarBase.js +1 -1
  54. package/lib/module/{zoomable-image-gallery/ZoomableImageGallery.js → zoomable-media-gallery/ZoomableMediaGallery.js} +184 -130
  55. package/lib/module/zoomable-media-gallery/ZoomableMediaGallery.js.map +1 -0
  56. package/lib/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.js.map +1 -1
  57. package/lib/module/zoomable-media-gallery/index.js +5 -0
  58. package/lib/module/zoomable-media-gallery/index.js.map +1 -0
  59. package/lib/module/zoomable-media-gallery/types.js +4 -0
  60. package/lib/module/{zoomable-image-gallery → zoomable-media-gallery}/types.js.map +1 -1
  61. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/media-flight/MediaFlightLayer.d.ts +11 -0
  64. package/lib/typescript/commonjs/media-flight/MediaFlightLayer.d.ts.map +1 -0
  65. package/lib/typescript/commonjs/media-flight/MediaSurface.d.ts +106 -0
  66. package/lib/typescript/commonjs/media-flight/MediaSurface.d.ts.map +1 -0
  67. package/lib/typescript/commonjs/media-flight/constants.d.ts +18 -0
  68. package/lib/typescript/commonjs/media-flight/constants.d.ts.map +1 -0
  69. package/lib/typescript/commonjs/media-flight/expo-video-module.d.ts +103 -0
  70. package/lib/typescript/commonjs/media-flight/expo-video-module.d.ts.map +1 -0
  71. package/lib/typescript/commonjs/media-flight/index.d.ts +9 -0
  72. package/lib/typescript/commonjs/media-flight/index.d.ts.map +1 -0
  73. package/lib/typescript/commonjs/media-flight/store.d.ts +132 -0
  74. package/lib/typescript/commonjs/media-flight/store.d.ts.map +1 -0
  75. package/lib/typescript/commonjs/media-flight/types.d.ts +195 -0
  76. package/lib/typescript/commonjs/media-flight/types.d.ts.map +1 -0
  77. package/lib/typescript/commonjs/media-flight/use-media-flight.d.ts +4 -0
  78. package/lib/typescript/commonjs/media-flight/use-media-flight.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/zoomable-media-gallery/ZoomableMediaGallery.d.ts +5 -0
  80. package/lib/typescript/commonjs/zoomable-media-gallery/ZoomableMediaGallery.d.ts.map +1 -0
  81. package/lib/typescript/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/zoomable-media-gallery/index.d.ts +4 -0
  83. package/lib/typescript/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/index.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/zoomable-media-gallery/types.d.ts +90 -0
  85. package/lib/typescript/commonjs/zoomable-media-gallery/types.d.ts.map +1 -0
  86. package/lib/typescript/module/index.d.ts.map +1 -1
  87. package/lib/typescript/module/index.web.d.ts.map +1 -1
  88. package/lib/typescript/module/media-flight/MediaFlightLayer.d.ts +11 -0
  89. package/lib/typescript/module/media-flight/MediaFlightLayer.d.ts.map +1 -0
  90. package/lib/typescript/module/media-flight/MediaSurface.d.ts +106 -0
  91. package/lib/typescript/module/media-flight/MediaSurface.d.ts.map +1 -0
  92. package/lib/typescript/module/media-flight/constants.d.ts +18 -0
  93. package/lib/typescript/module/media-flight/constants.d.ts.map +1 -0
  94. package/lib/typescript/module/media-flight/expo-video-module.d.ts +103 -0
  95. package/lib/typescript/module/media-flight/expo-video-module.d.ts.map +1 -0
  96. package/lib/typescript/module/media-flight/index.d.ts +9 -0
  97. package/lib/typescript/module/media-flight/index.d.ts.map +1 -0
  98. package/lib/typescript/module/media-flight/store.d.ts +132 -0
  99. package/lib/typescript/module/media-flight/store.d.ts.map +1 -0
  100. package/lib/typescript/module/media-flight/types.d.ts +195 -0
  101. package/lib/typescript/module/media-flight/types.d.ts.map +1 -0
  102. package/lib/typescript/module/media-flight/use-media-flight.d.ts +4 -0
  103. package/lib/typescript/module/media-flight/use-media-flight.d.ts.map +1 -0
  104. package/lib/typescript/module/zoomable-media-gallery/ZoomableMediaGallery.d.ts +5 -0
  105. package/lib/typescript/module/zoomable-media-gallery/ZoomableMediaGallery.d.ts.map +1 -0
  106. package/lib/typescript/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts.map +1 -1
  107. package/lib/typescript/module/zoomable-media-gallery/index.d.ts +4 -0
  108. package/lib/typescript/module/{zoomable-image-gallery → zoomable-media-gallery}/index.d.ts.map +1 -1
  109. package/lib/typescript/module/zoomable-media-gallery/types.d.ts +90 -0
  110. package/lib/typescript/module/zoomable-media-gallery/types.d.ts.map +1 -0
  111. package/package.json +42 -8
  112. package/src/image-aspect-ratio-cache/aspect-ratio-cache.ts +1 -1
  113. package/src/index.ts +2 -1
  114. package/src/index.web.ts +2 -1
  115. package/src/media-flight/MediaFlightLayer.tsx +144 -0
  116. package/src/media-flight/MediaSurface.tsx +233 -0
  117. package/src/media-flight/constants.ts +32 -0
  118. package/src/media-flight/expo-video-module.ts +181 -0
  119. package/src/media-flight/index.ts +33 -0
  120. package/src/media-flight/store.ts +497 -0
  121. package/src/media-flight/types.ts +210 -0
  122. package/src/media-flight/use-media-flight.ts +42 -0
  123. package/src/tab-bar/TabBarBase.tsx +1 -1
  124. package/src/{zoomable-image-gallery/ZoomableImageGallery.tsx → zoomable-media-gallery/ZoomableMediaGallery.tsx} +192 -117
  125. package/src/zoomable-media-gallery/index.ts +11 -0
  126. package/src/zoomable-media-gallery/types.ts +100 -0
  127. package/docs/zoomable-image-gallery.mdx +0 -66
  128. package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js.map +0 -1
  129. package/lib/commonjs/zoomable-image-gallery/types.js +0 -2
  130. package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js.map +0 -1
  131. package/lib/module/zoomable-image-gallery/index.js +0 -5
  132. package/lib/module/zoomable-image-gallery/index.js.map +0 -1
  133. package/lib/module/zoomable-image-gallery/types.js +0 -2
  134. package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts +0 -5
  135. package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +0 -1
  136. package/lib/typescript/commonjs/zoomable-image-gallery/index.d.ts +0 -4
  137. package/lib/typescript/commonjs/zoomable-image-gallery/types.d.ts +0 -56
  138. package/lib/typescript/commonjs/zoomable-image-gallery/types.d.ts.map +0 -1
  139. package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts +0 -5
  140. package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +0 -1
  141. package/lib/typescript/module/zoomable-image-gallery/index.d.ts +0 -4
  142. package/lib/typescript/module/zoomable-image-gallery/types.d.ts +0 -56
  143. package/lib/typescript/module/zoomable-image-gallery/types.d.ts.map +0 -1
  144. package/src/zoomable-image-gallery/index.ts +0 -9
  145. package/src/zoomable-image-gallery/types.ts +0 -59
  146. /package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.js +0 -0
  147. /package/lib/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.js +0 -0
  148. /package/lib/typescript/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts +0 -0
  149. /package/lib/typescript/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts +0 -0
  150. /package/src/{zoomable-image-gallery → zoomable-media-gallery}/constants.ts +0 -0
@@ -2,7 +2,6 @@
2
2
 
3
3
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
4
  import { View, Text, StyleSheet, Pressable, Platform, ScrollView, Share, useWindowDimensions } from 'react-native';
5
- import { Image } from 'expo-image';
6
5
  import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, runOnJS, Easing } from 'react-native-reanimated';
7
6
  import { GestureHandlerRootView, Gesture, GestureDetector } from 'react-native-gesture-handler';
8
7
  import { borderRadius } from "../styles/tokens.js";
@@ -11,10 +10,42 @@ import { Portal } from '../portal';
11
10
  import { useInteractionState } from "../hooks/use-interaction-state.js";
12
11
  import { ArrowLeft_Stroke2_Corner0_Rounded, ArrowRight_Stroke2_Corner0_Rounded, ArrowOutOfBox_Stroke2_Corner0_Rounded } from "../icons/index.js";
13
12
  import { WEB_POSITION_FIXED } from "../styles/web-view-style.js";
13
+ import { MediaPoster, MediaSurface } from "../media-flight/MediaSurface.js";
14
14
  import { getAspectRatio, getIntrinsicSize, fetchAspectRatio, DEFAULT_ASPECT_RATIO } from "../image-aspect-ratio-cache/index.js";
15
15
  import { FIT_FRACTION, MAX_DRAG_FRACTION, SCALE_DRAG_FRACTION, MIN_DRAG_SCALE, DISMISS_FRACTION, AXIS_DECISION_OFFSET, OPEN_DURATION_WEB, CLOSE_DURATION_WEB, OPACITY_DURATION, OPEN_SPRING, CLOSE_SPRING, SNAP_BACK_SPRING, DEFAULT_CORNER_RADIUS, MIN_ZOOM_SCALE, MAX_ZOOM_SCALE, ZOOM_SNAP_THRESHOLD, DOUBLE_TAP_ZOOM_SCALE, ZOOM_PAN_DAMPING, ZOOM_PAN_MAX_BASE, THUMBNAIL_STRIP_TILE_SIZE } from "./constants.js";
16
16
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
- const AnimatedImage = Animated.createAnimatedComponent(Image);
17
+ /**
18
+ * The still-frame URI of an item: the image itself, or a video's poster.
19
+ * `undefined` for a video with no poster — there is nothing to probe, size
20
+ * against or show in the strip, and every caller below handles that.
21
+ */
22
+ function posterUri(item) {
23
+ return item.kind === 'video' ? item.poster : item.uri;
24
+ }
25
+
26
+ /** The aspect ratio already known for an item, from the shared probe cache. */
27
+ function cachedRatio(item) {
28
+ const uri = posterUri(item);
29
+ return uri === undefined ? undefined : getAspectRatio(uri);
30
+ }
31
+
32
+ /**
33
+ * A stable React key. An image is identified by its URI; a video is not (its
34
+ * player is an object with no URL), which is why `GalleryVideo` carries an
35
+ * explicit `id`. The index is folded in so a post repeating one image still
36
+ * pages correctly.
37
+ */
38
+ function mediaKey(item, index) {
39
+ return item.kind === 'video' ? `video-${item.id}-${index}` : `image-${item.uri}-${index}`;
40
+ }
41
+
42
+ /**
43
+ * What the share sheet is handed. A poster is not what "share this video"
44
+ * means, so a video shares its own `shareUrl` or nothing at all.
45
+ */
46
+ function shareUri(item) {
47
+ return item.kind === 'video' ? item.shareUrl : item.uri;
48
+ }
18
49
 
19
50
  // Web-only interaction hints (no-ops on native): the zoom surfaces are
20
51
  // tap-to-dismiss (`cursor: pointer`) and must not select text or drag the
@@ -57,7 +88,7 @@ function NavArrow({
57
88
  return /*#__PURE__*/_jsx(Pressable, {
58
89
  onPress: onPress,
59
90
  accessibilityRole: "button",
60
- accessibilityLabel: isLeft ? 'Previous image' : 'Next image',
91
+ accessibilityLabel: isLeft ? 'Previous item' : 'Next item',
61
92
  hitSlop: 8,
62
93
  onHoverIn: onHoverIn,
63
94
  onHoverOut: onHoverOut,
@@ -72,7 +103,7 @@ function NavArrow({
72
103
  });
73
104
  }
74
105
  /**
75
- * Resolve `cornerRadius` against the size an image is actually rendered at, so
106
+ * Resolve `cornerRadius` against the size an item is actually rendered at, so
76
107
  * `'circle'` stays a circle at every fitted size (and through the open/close
77
108
  * animation, which scales this same box).
78
109
  */
@@ -81,26 +112,42 @@ function resolveCornerRadius(cornerRadius, fit) {
81
112
  }
82
113
 
83
114
  /**
84
- * Fullscreen, swipeable image viewer that replicates the profile avatar's
85
- * measured-origin zoom transition (`ZoomableAvatar`) for rectangular post media:
115
+ * Fullscreen, swipeable MEDIA viewer images and consumer-owned videos in one
116
+ * pager — replicating the profile avatar's measured-origin zoom transition
117
+ * (`ZoomableAvatar`) for rectangular post media:
86
118
  *
87
119
  * - Open/close feel is identical to the avatar (same spring configs, web
88
120
  * timing/easing, blur backdrop, and the measure-origin technique). The viewer
89
121
  * renders through the Bloom `Portal` on BOTH platforms (RN's `Modal` is not
90
122
  * used on native — on the New Architecture / Fabric Android its host views
91
123
  * mount full-screen but never composite, leaving the viewer invisible).
92
- * - The OPENING (tapped) image animates from its measured rect to a centered,
124
+ * - The OPENING (tapped) item animates from its measured rect to a centered,
93
125
  * aspect-ratio-preserving fit within {@link FIT_FRACTION} of the screen. Once
94
126
  * the open animation settles, a horizontal paging `ScrollView` mounts seeded at
95
- * the tapped index so the user can swipe between every image in the post.
127
+ * the tapped index so the user can swipe between every item in the post.
96
128
  * - Gesture disambiguation: the pager owns horizontal swipes; a vertical-only
97
129
  * `Gesture.Pan` (`activeOffsetY` + `failOffsetX`) owns drag-to-dismiss, so the
98
130
  * two never fight.
131
+ *
132
+ * ## Video, and why it does not restart
133
+ *
134
+ * A video page is fed by a `VideoPlayer` the CONSUMER created and owns. Bloom
135
+ * never creates or destroys one: expo-video keeps the player OBJECT separate
136
+ * from the `VideoView` that shows it, and one player may feed several views, so
137
+ * handing the same player to this gallery moves a playing video into fullscreen
138
+ * without re-opening the stream. Exactly ONE view is mounted per opening — the
139
+ * active page's — while every other page and every strip tile renders the still
140
+ * (`MediaPoster`). The single unavoidable swap is the pre-pager open surface
141
+ * handing over to the pager's active page once the open animation settles.
142
+ *
143
+ * `expo-video` is an OPTIONAL peer loaded through `media-flight/expo-video-module`.
144
+ * Without it a video page degrades to its poster, once, with a dev warning.
99
145
  */
100
- const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
146
+ const ZoomableMediaGalleryInner = /*#__PURE__*/React.forwardRef(({
101
147
  measureThumb,
102
148
  cornerRadius = DEFAULT_CORNER_RADIUS,
103
- indicatorVariant = 'dots'
149
+ indicatorVariant = 'dots',
150
+ videoControls = false
104
151
  }, ref) => {
105
152
  const {
106
153
  width: SCREEN_WIDTH,
@@ -108,17 +155,17 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
108
155
  } = useWindowDimensions();
109
156
  const radiusFor = useCallback(fit => resolveCornerRadius(cornerRadius, fit), [cornerRadius]);
110
157
  const [isOpen, setIsOpen] = useState(false);
111
- // Once true, the swipeable pager is mounted and the single open-image hidden.
158
+ // Once true, the swipeable pager is mounted and the single open-surface hidden.
112
159
  const [pagerReady, setPagerReady] = useState(false);
113
- const [images, setImages] = useState([]);
160
+ const [items, setItems] = useState([]);
114
161
  const [activeIndex, setActiveIndex] = useState(0);
115
- // Whether the active image is currently pinched/double-tapped past its base
162
+ // Whether the active media is currently pinched/double-tapped past its base
116
163
  // size. Drives the pan-while-zoomed gesture, disables the pager's horizontal
117
- // paging + the dismiss drag so a pan moves the zoomed image instead.
164
+ // paging + the dismiss drag so a pan moves the zoomed media instead.
118
165
  const [isZoomed, setIsZoomed] = useState(false);
119
- // Aspect ratio of the OPENING image (drives the open-animation fit box).
166
+ // Aspect ratio of the OPENING item (drives the open-animation fit box).
120
167
  const [openRatio, setOpenRatio] = useState(DEFAULT_ASPECT_RATIO);
121
- // Per-image aspect ratios for the pager pages (index-aligned with `images`).
168
+ // Per-item aspect ratios for the pager pages (index-aligned with `items`).
122
169
  const [pageRatios, setPageRatios] = useState({});
123
170
 
124
171
  // Native `Share` is always available; web exposes a share button only when the
@@ -132,7 +179,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
132
179
  const translateY = useSharedValue(0);
133
180
  const opacity = useSharedValue(0);
134
181
 
135
- // Zoom transform of the active image, kept SEPARATE from the open/close +
182
+ // Zoom transform of the active media, kept SEPARATE from the open/close +
136
183
  // dismiss-drag transform above (reusing those would collide two meanings).
137
184
  // `zoom*` are the live/persisted values; `baseZoomScale` is captured at pinch
138
185
  // start and `savedZoomTranslate*` at pan start for incremental bookkeeping.
@@ -143,14 +190,14 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
143
190
  const savedZoomTranslateX = useSharedValue(0);
144
191
  const savedZoomTranslateY = useSharedValue(0);
145
192
 
146
- // Origin (offset from screen center) of the tapped image.
193
+ // Origin (offset from screen center) of the tapped item.
147
194
  const originX = useSharedValue(0);
148
195
  const originY = useSharedValue(0);
149
196
  const startX = useSharedValue(0);
150
197
  const startY = useSharedValue(0);
151
198
  // Per-drag baseline captured at pan start (transient).
152
199
  const startScale = useSharedValue(1);
153
- // Persistent scale of the open-image at its thumbnail origin (thumbWidth /
200
+ // Persistent scale of the open-surface at its thumbnail origin (thumbWidth /
154
201
  // fittedWidth). The open animation grows from this to 1; the close animation
155
202
  // shrinks back to it. Never overwritten by the drag gesture.
156
203
  const originScale = useSharedValue(1);
@@ -205,9 +252,9 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
205
252
  setActiveIndex(prev => prev === next ? prev : next);
206
253
  }, []);
207
254
 
208
- // Snap the active image back to its un-zoomed baseline (no animation) and clear
209
- // all zoom bookkeeping. Called on every path that changes the active page so a
210
- // freshly-viewed image always starts un-zoomed.
255
+ // Snap the active media back to its un-zoomed baseline (no animation) and
256
+ // clear all zoom bookkeeping. Called on every path that changes the active
257
+ // page so a freshly-viewed item always starts un-zoomed.
211
258
  const resetZoom = useCallback(() => {
212
259
  zoomScale.value = 1;
213
260
  zoomTranslateX.value = 0;
@@ -225,7 +272,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
225
272
  }), [SCREEN_WIDTH, SCREEN_HEIGHT]);
226
273
 
227
274
  // Largest width/height for `ratio` that fits inside `fitBox` without cropping
228
- // (contain), never exceeding the image's own resolution: filling the viewport
275
+ // (contain), never exceeding the media's own resolution: filling the viewport
229
276
  // with a 512px avatar or a small thumbnail just renders it soft. `uri` is what
230
277
  // lets us look the intrinsic size up; without it (not measured yet) the fit
231
278
  // box wins and the clamp applies on the next frame.
@@ -248,18 +295,24 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
248
295
  };
249
296
  }, [fitBox]);
250
297
 
251
- // Fitted size of the single open-image for the CURRENT page. On open this is
252
- // the opened image's fit (active index == opened index, ratio == `openRatio`);
253
- // after swiping it tracks the viewed image so the collapse-on-dismiss renders
254
- // and flies back the image actually on screen.
298
+ // Fitted size of the single open-surface for the CURRENT page. On open this is
299
+ // the opened item's fit (active index == opened index, ratio == `openRatio`);
300
+ // after swiping it tracks the viewed item so the collapse-on-dismiss renders
301
+ // and flies back the media actually on screen.
302
+ const activeItem = items[activeIndex];
255
303
  const activeRatio = pageRatios[activeIndex] ?? openRatio;
256
- const activeUri = images[activeIndex]?.uri;
304
+ const activeUri = activeItem === undefined ? undefined : posterUri(activeItem);
257
305
  const activeFit = useMemo(() => fitForRatio(activeRatio, activeUri), [fitForRatio, activeRatio, activeUri]);
258
306
 
259
- // Alt text (accessibility description) of the image currently on screen, shown
307
+ // Alt text (accessibility description) of the item currently on screen, shown
260
308
  // as a caption at the bottom of the viewer (Bluesky-style lightbox footer).
261
- const activeAlt = images[activeIndex]?.alt?.trim() || undefined;
309
+ const activeAlt = activeItem?.alt?.trim() || undefined;
310
+
311
+ // Probe and cache the ratio of the page at `index`. A video with no poster has
312
+ // nothing to probe — it keeps whatever ratio the consumer declared, or the
313
+ // gallery default — so this is a no-op rather than a guess.
262
314
  const ensureRatio = useCallback((index, uri) => {
315
+ if (uri === undefined) return;
263
316
  const cached = getAspectRatio(uri);
264
317
  if (cached !== undefined) {
265
318
  setPageRatios(prev => prev[index] === cached ? prev : {
@@ -287,7 +340,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
287
340
  opacity.value = 0;
288
341
  }, [opacity, scale, translateX, translateY]);
289
342
 
290
- // Fly the (possibly dragged) image back toward `target` — the rect of the
343
+ // Fly the (possibly dragged) media back toward `target` — the rect of the
291
344
  // thumbnail currently being viewed — shrinking to its footprint. Uses the
292
345
  // EXACT same close spring (native) / timing (web) as the avatar transition.
293
346
  const flyBackTo = useCallback(target => {
@@ -353,13 +406,13 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
353
406
  // fly-back from an already-moving image.
354
407
  if (dismissingRef.current) return;
355
408
  dismissingRef.current = true;
356
- // Collapse the pager back to the single open-image so the fly-back animates
357
- // one image (the current one) rather than the whole scrolled strip.
409
+ // Collapse the pager back to the single open-surface so the fly-back
410
+ // animates one item (the current one) rather than the whole scrolled strip.
358
411
  setPagerReady(false);
359
412
  const index = activeIndexRef.current;
360
- const current = images[index];
361
- // Recompute the fly-back target from the CURRENT image: the live rect of its
362
- // thumbnail + the same fitted box (`activeFit`) the open-image is rendered
413
+ const current = items[index];
414
+ // Recompute the fly-back target from the CURRENT item: the live rect of its
415
+ // thumbnail + the same fitted box (`activeFit`) the open-surface is rendered
363
416
  // at, using the same screen-center math as `open`. Falls back to a center
364
417
  // fade-out when the thumbnail can't be measured.
365
418
  if (measureThumb && current) {
@@ -379,7 +432,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
379
432
  return;
380
433
  }
381
434
  fadeOutCenter();
382
- }, [activeFit, fadeOutCenter, flyBackTo, images, measureThumb, SCREEN_HEIGHT, SCREEN_WIDTH]);
435
+ }, [activeFit, fadeOutCenter, flyBackTo, items, measureThumb, SCREEN_HEIGHT, SCREEN_WIDTH]);
383
436
 
384
437
  // Reveal the swipeable pager once the open animation has settled. The index it
385
438
  // lands on is `activeIndexRef` (set synchronously in `open`), seated by the
@@ -387,30 +440,32 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
387
440
  const revealPager = useCallback(() => {
388
441
  setPagerReady(true);
389
442
  }, []);
390
- const open = useCallback((nextImages, index, rect) => {
391
- if (isOpen || nextImages.length === 0) return;
443
+ const open = useCallback((nextItems, index, rect) => {
444
+ if (isOpen || nextItems.length === 0) return;
392
445
  dismissingRef.current = false;
393
- const safeIndex = Math.min(Math.max(index, 0), nextImages.length - 1);
394
- const target = nextImages[safeIndex];
446
+ const safeIndex = Math.min(Math.max(index, 0), nextItems.length - 1);
447
+ const target = nextItems[safeIndex];
395
448
  if (!target) return;
396
- const knownRatio = target.aspectRatio ?? getAspectRatio(target.uri);
449
+ const targetUri = posterUri(target);
450
+ const knownRatio = target.aspectRatio ?? cachedRatio(target);
397
451
  const ratio = knownRatio ?? DEFAULT_ASPECT_RATIO;
398
- setImages(nextImages);
452
+ setItems(nextItems);
399
453
  setActiveIndexBoth(safeIndex);
400
454
  setOpenRatio(ratio);
401
455
  // Seed every page whose ratio is already known (consumer-provided metadata
402
456
  // or a previously-cached probe) so swiping never hits the same snap — only
403
- // a genuinely-unknown image falls through to `ensureRatio`'s async probe.
457
+ // a genuinely-unknown item falls through to `ensureRatio`'s async probe.
404
458
  const initialRatios = {};
405
- nextImages.forEach((img, i) => {
406
- const r = img.aspectRatio ?? getAspectRatio(img.uri);
459
+ nextItems.forEach((item, i) => {
460
+ const r = item.aspectRatio ?? cachedRatio(item);
407
461
  if (r !== undefined) initialRatios[i] = r;
408
462
  });
409
463
  setPageRatios(initialRatios);
410
464
 
411
- // Resolve the opening ratio if it was not yet known, then re-fit.
412
- if (knownRatio === undefined) {
413
- void fetchAspectRatio(target.uri).then(resolved => {
465
+ // Resolve the opening ratio if it was not yet known, then re-fit. A video
466
+ // with no poster has nothing to probe and keeps the default.
467
+ if (knownRatio === undefined && targetUri !== undefined) {
468
+ void fetchAspectRatio(targetUri).then(resolved => {
414
469
  setOpenRatio(resolved);
415
470
  setPageRatios(prev => ({
416
471
  ...prev,
@@ -419,11 +474,11 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
419
474
  });
420
475
  }
421
476
 
422
- // The open-image box is rendered at its FINAL fitted size; the open
477
+ // The open-surface box is rendered at its FINAL fitted size; the open
423
478
  // animation grows it from the thumbnail's footprint (scale < 1) up to 1,
424
479
  // mirroring the avatar's small→big scale. The initial scale is the ratio
425
480
  // of the thumbnail width to the fitted width.
426
- const fitted = fitForRatio(ratio, target.uri);
481
+ const fitted = fitForRatio(ratio, targetUri);
427
482
  const centerX = SCREEN_WIDTH / 2;
428
483
  const centerY = SCREEN_HEIGHT / 2;
429
484
  if (rect && rect.width > 0) {
@@ -523,7 +578,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
523
578
  opacity: opacity.value
524
579
  }), [opacity]);
525
580
 
526
- // The single open-image animates from origin → fitted center.
581
+ // The single open-surface animates from origin → fitted center.
527
582
  const openImageStyle = useAnimatedStyle(() => ({
528
583
  transform: [{
529
584
  translateX: translateX.value
@@ -563,7 +618,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
563
618
  // close fly-back target). Shared by `onMomentumScrollEnd` (native) and `onScroll`
564
619
  // (web, where paging may not fire a reliable momentum-end).
565
620
  const updateIndexFromOffset = useCallback(offsetX => {
566
- const lastIndex = images.length - 1;
621
+ const lastIndex = items.length - 1;
567
622
  if (lastIndex < 0) return;
568
623
  const next = Math.min(Math.max(Math.round(offsetX / SCREEN_WIDTH), 0), lastIndex);
569
624
  if (next === activeIndexRef.current) return;
@@ -571,16 +626,16 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
571
626
  // the page scrolling away).
572
627
  resetZoom();
573
628
  setActiveIndexBoth(next);
574
- const img = images[next];
575
- if (img) ensureRatio(next, img.uri);
576
- }, [ensureRatio, images, resetZoom, setActiveIndexBoth, SCREEN_WIDTH]);
629
+ const item = items[next];
630
+ if (item) ensureRatio(next, posterUri(item));
631
+ }, [ensureRatio, items, resetZoom, setActiveIndexBoth, SCREEN_WIDTH]);
577
632
 
578
633
  // Programmatically page to `index` (arrow buttons, keyboard, thumbnail taps).
579
634
  // Clamps into range and lets the existing scroll handlers own the index/ratio
580
635
  // state-sync — this only triggers the scroll. No-op until the pager is mounted.
581
636
  const pageTo = useCallback(index => {
582
637
  if (!pagerReady) return;
583
- const lastIndex = images.length - 1;
638
+ const lastIndex = items.length - 1;
584
639
  if (lastIndex < 0) return;
585
640
  const clamped = Math.min(Math.max(index, 0), lastIndex);
586
641
  resetZoom();
@@ -589,7 +644,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
589
644
  y: 0,
590
645
  animated: true
591
646
  });
592
- }, [images.length, pagerReady, resetZoom, SCREEN_WIDTH]);
647
+ }, [items.length, pagerReady, resetZoom, SCREEN_WIDTH]);
593
648
  const onPagerScroll = useCallback(event => {
594
649
  updateIndexFromOffset(event.nativeEvent.contentOffset.x);
595
650
  }, [updateIndexFromOffset]);
@@ -710,10 +765,12 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
710
765
  // pinch + pan-while-zoomed.
711
766
  const activePageGesture = useMemo(() => Gesture.Simultaneous(tapGesture, pinchGesture, panWhileZoomedGesture), [tapGesture, pinchGesture, panWhileZoomedGesture]);
712
767
 
713
- // Share the active image's URI: the OS share sheet on native; the Web Share API
714
- // on supporting browsers (the button is only rendered where it exists).
768
+ // Share the active item's URI: the OS share sheet on native; the Web Share API
769
+ // on supporting browsers (the button is only rendered where it exists). A
770
+ // video shares its own `shareUrl`; its poster is not what "share this" means.
715
771
  const handleShare = useCallback(async () => {
716
- const uri = images[activeIndexRef.current]?.uri;
772
+ const item = items[activeIndexRef.current];
773
+ const uri = item === undefined ? undefined : shareUri(item);
717
774
  if (!uri) return;
718
775
  try {
719
776
  if (Platform.OS === 'web') {
@@ -731,9 +788,9 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
731
788
  // A user-cancelled share sheet rejects (AbortError on web) — that's
732
789
  // expected, not a failure. Anything else is a real error worth surfacing.
733
790
  if (err instanceof Error && err.name === 'AbortError') return;
734
- if (typeof console !== 'undefined' && console.error) console.error('Image share failed:', err);
791
+ if (typeof console !== 'undefined' && console.error) console.error('Media share failed:', err);
735
792
  }
736
- }, [images]);
793
+ }, [items]);
737
794
 
738
795
  // Keyboard navigation (web only), scoped to the open lifetime like Dialog.web's
739
796
  // Escape handler. Reads the live index from the ref so it need not re-subscribe
@@ -761,7 +818,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
761
818
  style: StyleSheet.absoluteFill,
762
819
  children: [/*#__PURE__*/_jsx(Backdrop, {
763
820
  onPress: handleDismiss,
764
- accessibilityLabel: "Close image viewer",
821
+ accessibilityLabel: "Close media viewer",
765
822
  progress: opacity
766
823
  }), /*#__PURE__*/_jsx(GestureDetector, {
767
824
  gesture: panGesture,
@@ -771,23 +828,22 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
771
828
  height: SCREEN_HEIGHT
772
829
  }, webUserSelectNoneStyle],
773
830
  pointerEvents: "box-none",
774
- children: [!pagerReady && /*#__PURE__*/_jsx(Pressable, {
831
+ children: [!pagerReady && activeItem !== undefined && /*#__PURE__*/_jsx(Pressable, {
775
832
  onPress: handleDismiss,
776
833
  style: webPointerStyle,
777
- children: /*#__PURE__*/_jsx(AnimatedImage, {
778
- source: {
779
- uri: images[activeIndex]?.uri
780
- },
781
- contentFit: "contain",
782
- style: [{
834
+ children: /*#__PURE__*/_jsx(Animated.View, {
835
+ style: [styles.mediaBox, {
783
836
  width: activeFit.width,
784
837
  height: activeFit.height,
785
838
  borderRadius: radiusFor(activeFit)
786
839
  }, openImageStyle],
787
- transition: 0,
788
- ...(Platform.OS === 'web' ? {
789
- draggable: false
790
- } : {})
840
+ children: /*#__PURE__*/_jsx(MediaSurface, {
841
+ content: activeItem,
842
+ contentFit: "contain",
843
+ nativeControls: videoControls,
844
+ accessibilityLabel: activeItem.alt,
845
+ style: StyleSheet.absoluteFill
846
+ })
791
847
  })
792
848
  }), pagerReady && /*#__PURE__*/_jsx(Animated.View, {
793
849
  style: [StyleSheet.absoluteFill, pagerContainerStyle],
@@ -810,73 +866,71 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
810
866
  } : {}),
811
867
  scrollEventThrottle: 16,
812
868
  style: StyleSheet.absoluteFill,
813
- children: images.map((img, idx) => {
814
- const ratio = pageRatios[idx] ?? getAspectRatio(img.uri) ?? DEFAULT_ASPECT_RATIO;
815
- const fit = fitForRatio(ratio, img.uri);
869
+ children: items.map((item, idx) => {
870
+ const uri = posterUri(item);
871
+ const ratio = pageRatios[idx] ?? cachedRatio(item) ?? item.aspectRatio ?? DEFAULT_ASPECT_RATIO;
872
+ const fit = fitForRatio(ratio, uri);
816
873
  // Only the active page is zoomable + gesture-wrapped (double-tap
817
- // / single-tap dismiss / pinch / pan). Off-screen pages stay a
818
- // plain image; the active image handles its own tap-to-dismiss
819
- // through the gesture, so it isn't wrapped in a Pressable.
874
+ // / single-tap dismiss / pinch / pan). It is also the ONLY page
875
+ // that mounts a real media surface: an off-screen page renders
876
+ // the still, because a second `VideoView` on the same player is
877
+ // exactly the duplicate surface this package exists to avoid.
878
+ // The active media handles its own tap-to-dismiss through the
879
+ // gesture, so it isn't wrapped in a Pressable.
820
880
  if (idx === activeIndex) {
821
881
  return /*#__PURE__*/_jsx(Pressable, {
822
882
  // The page fills the screen ON TOP of the backdrop (the
823
883
  // pager below it needs pointer events to swipe), so a
824
- // press on the empty area around the image can never
884
+ // press on the empty area around the media can never
825
885
  // reach the backdrop — the page dismisses it itself.
826
- // Same outcome as tapping the image, which already
886
+ // Same outcome as tapping the media, which already
827
887
  // dismisses through `singleTapDismissGesture`.
828
888
  onPress: handleDismiss,
829
- accessibilityLabel: "Close image viewer",
889
+ accessibilityLabel: "Close media viewer",
830
890
  style: [styles.page, {
831
891
  width: SCREEN_WIDTH,
832
892
  height: SCREEN_HEIGHT
833
893
  }, webPointerStyle],
834
894
  children: /*#__PURE__*/_jsx(GestureDetector, {
835
895
  gesture: activePageGesture,
836
- children: /*#__PURE__*/_jsx(AnimatedImage, {
837
- source: {
838
- uri: img.uri
839
- },
840
- contentFit: "contain",
841
- style: [{
896
+ children: /*#__PURE__*/_jsx(Animated.View, {
897
+ style: [styles.mediaBox, {
842
898
  width: fit.width,
843
899
  height: fit.height,
844
900
  borderRadius: radiusFor(fit)
845
901
  }, zoomImageStyle],
846
- transition: 0,
847
- ...(Platform.OS === 'web' ? {
848
- draggable: false
849
- } : {})
902
+ children: /*#__PURE__*/_jsx(MediaSurface, {
903
+ content: item,
904
+ contentFit: "contain",
905
+ nativeControls: videoControls,
906
+ accessibilityLabel: item.alt,
907
+ style: StyleSheet.absoluteFill
908
+ })
850
909
  })
851
910
  })
852
- }, `${img.uri}-${idx}`);
911
+ }, mediaKey(item, idx));
853
912
  }
854
913
  return /*#__PURE__*/_jsx(Pressable, {
855
914
  onPress: handleDismiss,
856
- accessibilityLabel: "Close image viewer",
915
+ accessibilityLabel: "Close media viewer",
857
916
  style: [styles.page, {
858
917
  width: SCREEN_WIDTH,
859
918
  height: SCREEN_HEIGHT
860
919
  }],
861
- children: /*#__PURE__*/_jsx(Image, {
862
- source: {
863
- uri: img.uri
864
- },
920
+ children: /*#__PURE__*/_jsx(MediaPoster, {
921
+ content: item,
865
922
  contentFit: "contain",
923
+ accessibilityLabel: item.alt,
866
924
  style: {
867
925
  width: fit.width,
868
926
  height: fit.height,
869
927
  borderRadius: radiusFor(fit)
870
- },
871
- transition: 0,
872
- ...(Platform.OS === 'web' ? {
873
- draggable: false
874
- } : {})
928
+ }
875
929
  })
876
- }, `${img.uri}-${idx}`);
930
+ }, mediaKey(item, idx));
877
931
  })
878
932
  })
879
- }), pagerReady && images.length > 1 && /*#__PURE__*/_jsxs(Animated.View, {
933
+ }), pagerReady && items.length > 1 && /*#__PURE__*/_jsxs(Animated.View, {
880
934
  style: [styles.indicatorWrap, backdropStyle],
881
935
  pointerEvents: "box-none",
882
936
  children: [/*#__PURE__*/_jsx(View, {
@@ -884,47 +938,41 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
884
938
  pointerEvents: "none",
885
939
  children: /*#__PURE__*/_jsx(Text, {
886
940
  style: styles.counterText,
887
- children: `${activeIndex + 1} / ${images.length}`
941
+ children: `${activeIndex + 1} / ${items.length}`
888
942
  })
889
943
  }), indicatorVariant === 'thumbnails' ? /*#__PURE__*/_jsx(ScrollView, {
890
944
  horizontal: true,
891
945
  showsHorizontalScrollIndicator: false,
892
946
  style: styles.thumbStripScroll,
893
947
  contentContainerStyle: styles.thumbStripContent,
894
- children: images.map((img, idx) => /*#__PURE__*/_jsx(Pressable, {
948
+ children: items.map((item, idx) => /*#__PURE__*/_jsx(Pressable, {
895
949
  onPress: () => pageTo(idx),
896
950
  accessibilityRole: "button",
897
- accessibilityLabel: `Go to image ${idx + 1} of ${images.length}`,
951
+ accessibilityLabel: `Go to item ${idx + 1} of ${items.length}`,
898
952
  style: [styles.thumbTile, idx === activeIndex ? styles.thumbTileActive : styles.thumbTileInactive, webPointerStyle],
899
- children: /*#__PURE__*/_jsx(Image, {
900
- source: {
901
- uri: img.uri
902
- },
953
+ children: /*#__PURE__*/_jsx(MediaPoster, {
954
+ content: item,
903
955
  contentFit: "cover",
904
- style: styles.thumbTileImage,
905
- transition: 0,
906
- ...(Platform.OS === 'web' ? {
907
- draggable: false
908
- } : {})
956
+ style: styles.thumbTileImage
909
957
  })
910
- }, `thumb-${img.uri}-${idx}`))
958
+ }, `thumb-${mediaKey(item, idx)}`))
911
959
  }) : /*#__PURE__*/_jsx(View, {
912
960
  style: styles.dotsRow,
913
961
  pointerEvents: "none",
914
- children: images.map((img, idx) => /*#__PURE__*/_jsx(View, {
962
+ children: items.map((item, idx) => /*#__PURE__*/_jsx(View, {
915
963
  style: [styles.dot, idx === activeIndex ? styles.dotActive : styles.dotInactive]
916
- }, `dot-${img.uri}-${idx}`))
964
+ }, `dot-${mediaKey(item, idx)}`))
917
965
  })]
918
- }), Platform.OS === 'web' && pagerReady && images.length > 1 && activeIndex > 0 && /*#__PURE__*/_jsx(NavArrow, {
966
+ }), Platform.OS === 'web' && pagerReady && items.length > 1 && activeIndex > 0 && /*#__PURE__*/_jsx(NavArrow, {
919
967
  direction: "left",
920
968
  onPress: () => pageTo(activeIndex - 1)
921
- }), Platform.OS === 'web' && pagerReady && images.length > 1 && activeIndex < images.length - 1 && /*#__PURE__*/_jsx(NavArrow, {
969
+ }), Platform.OS === 'web' && pagerReady && items.length > 1 && activeIndex < items.length - 1 && /*#__PURE__*/_jsx(NavArrow, {
922
970
  direction: "right",
923
971
  onPress: () => pageTo(activeIndex + 1)
924
972
  }), canShare && pagerReady && /*#__PURE__*/_jsx(Pressable, {
925
973
  onPress: handleShare,
926
974
  accessibilityRole: "button",
927
- accessibilityLabel: "Share image",
975
+ accessibilityLabel: "Share media",
928
976
  hitSlop: 8,
929
977
  style: [styles.shareButton, webPointerStyle],
930
978
  children: /*#__PURE__*/_jsx(ArrowOutOfBox_Stroke2_Corner0_Rounded, {
@@ -932,7 +980,7 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
932
980
  size: "md"
933
981
  })
934
982
  }), activeAlt ? /*#__PURE__*/_jsx(Animated.View, {
935
- style: [styles.altCaptionWrap, images.length > 1 && styles.altCaptionWrapWithIndicator, backdropStyle],
983
+ style: [styles.altCaptionWrap, items.length > 1 && styles.altCaptionWrapWithIndicator, backdropStyle],
936
984
  pointerEvents: "none",
937
985
  children: /*#__PURE__*/_jsx(View, {
938
986
  style: styles.altCaptionPill,
@@ -959,8 +1007,8 @@ const ZoomableImageGalleryInner = /*#__PURE__*/React.forwardRef(({
959
1007
  children: renderContent()
960
1008
  });
961
1009
  });
962
- ZoomableImageGalleryInner.displayName = 'ZoomableImageGallery';
963
- export const ZoomableImageGallery = ZoomableImageGalleryInner;
1010
+ ZoomableMediaGalleryInner.displayName = 'ZoomableMediaGallery';
1011
+ export const ZoomableMediaGallery = ZoomableMediaGalleryInner;
964
1012
  const styles = StyleSheet.create({
965
1013
  modalContainer: {
966
1014
  justifyContent: 'center',
@@ -1003,6 +1051,12 @@ const styles = StyleSheet.create({
1003
1051
  justifyContent: 'center',
1004
1052
  alignItems: 'center'
1005
1053
  },
1054
+ // The box the media is painted into. `overflow: hidden` is what makes the
1055
+ // box's corner radius clip a VIDEO — an image would round on its own, a
1056
+ // platform video surface will not.
1057
+ mediaBox: {
1058
+ overflow: 'hidden'
1059
+ },
1006
1060
  indicatorWrap: {
1007
1061
  position: 'absolute',
1008
1062
  bottom: 48,
@@ -1138,5 +1192,5 @@ const styles = StyleSheet.create({
1138
1192
  lineHeight: 18
1139
1193
  }
1140
1194
  });
1141
- export default ZoomableImageGallery;
1142
- //# sourceMappingURL=ZoomableImageGallery.js.map
1195
+ export default ZoomableMediaGallery;
1196
+ //# sourceMappingURL=ZoomableMediaGallery.js.map