@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.
- package/docs/media-flight.mdx +272 -0
- package/docs/migrating-the-media-gallery.mdx +103 -0
- package/docs/migrating-to-1.0.mdx +1 -1
- package/docs/zoomable-media-gallery.mdx +116 -0
- package/lib/commonjs/image-aspect-ratio-cache/aspect-ratio-cache.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/media-flight/MediaFlightLayer.js +151 -0
- package/lib/commonjs/media-flight/MediaFlightLayer.js.map +1 -0
- package/lib/commonjs/media-flight/MediaSurface.js +188 -0
- package/lib/commonjs/media-flight/MediaSurface.js.map +1 -0
- package/lib/commonjs/media-flight/constants.js +39 -0
- package/lib/commonjs/media-flight/constants.js.map +1 -0
- package/lib/commonjs/media-flight/expo-video-module.js +148 -0
- package/lib/commonjs/media-flight/expo-video-module.js.map +1 -0
- package/lib/commonjs/media-flight/index.js +102 -0
- package/lib/commonjs/media-flight/index.js.map +1 -0
- package/lib/commonjs/media-flight/store.js +457 -0
- package/lib/commonjs/media-flight/store.js.map +1 -0
- package/lib/commonjs/media-flight/types.js +6 -0
- package/lib/commonjs/media-flight/types.js.map +1 -0
- package/lib/commonjs/media-flight/use-media-flight.js +41 -0
- package/lib/commonjs/media-flight/use-media-flight.js.map +1 -0
- package/lib/commonjs/tab-bar/TabBarBase.js +1 -1
- package/lib/commonjs/{zoomable-image-gallery/ZoomableImageGallery.js → zoomable-media-gallery/ZoomableMediaGallery.js} +185 -131
- package/lib/commonjs/zoomable-media-gallery/ZoomableMediaGallery.js.map +1 -0
- package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.js.map +1 -1
- package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/index.js +5 -5
- package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/index.js.map +1 -1
- package/lib/commonjs/zoomable-media-gallery/types.js +6 -0
- package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/types.js.map +1 -1
- package/lib/module/image-aspect-ratio-cache/aspect-ratio-cache.js +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/media-flight/MediaFlightLayer.js +146 -0
- package/lib/module/media-flight/MediaFlightLayer.js.map +1 -0
- package/lib/module/media-flight/MediaSurface.js +180 -0
- package/lib/module/media-flight/MediaSurface.js.map +1 -0
- package/lib/module/media-flight/constants.js +35 -0
- package/lib/module/media-flight/constants.js.map +1 -0
- package/lib/module/media-flight/expo-video-module.js +142 -0
- package/lib/module/media-flight/expo-video-module.js.map +1 -0
- package/lib/module/media-flight/index.js +9 -0
- package/lib/module/media-flight/index.js.map +1 -0
- package/lib/module/media-flight/store.js +439 -0
- package/lib/module/media-flight/store.js.map +1 -0
- package/lib/module/media-flight/types.js +4 -0
- package/lib/module/media-flight/types.js.map +1 -0
- package/lib/module/media-flight/use-media-flight.js +36 -0
- package/lib/module/media-flight/use-media-flight.js.map +1 -0
- package/lib/module/tab-bar/TabBarBase.js +1 -1
- package/lib/module/{zoomable-image-gallery/ZoomableImageGallery.js → zoomable-media-gallery/ZoomableMediaGallery.js} +184 -130
- package/lib/module/zoomable-media-gallery/ZoomableMediaGallery.js.map +1 -0
- package/lib/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.js.map +1 -1
- package/lib/module/zoomable-media-gallery/index.js +5 -0
- package/lib/module/zoomable-media-gallery/index.js.map +1 -0
- package/lib/module/zoomable-media-gallery/types.js +4 -0
- package/lib/module/{zoomable-image-gallery → zoomable-media-gallery}/types.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/media-flight/MediaFlightLayer.d.ts +11 -0
- package/lib/typescript/commonjs/media-flight/MediaFlightLayer.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-flight/MediaSurface.d.ts +106 -0
- package/lib/typescript/commonjs/media-flight/MediaSurface.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-flight/constants.d.ts +18 -0
- package/lib/typescript/commonjs/media-flight/constants.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-flight/expo-video-module.d.ts +103 -0
- package/lib/typescript/commonjs/media-flight/expo-video-module.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-flight/index.d.ts +9 -0
- package/lib/typescript/commonjs/media-flight/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-flight/store.d.ts +132 -0
- package/lib/typescript/commonjs/media-flight/store.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-flight/types.d.ts +195 -0
- package/lib/typescript/commonjs/media-flight/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-flight/use-media-flight.d.ts +4 -0
- package/lib/typescript/commonjs/media-flight/use-media-flight.d.ts.map +1 -0
- package/lib/typescript/commonjs/zoomable-media-gallery/ZoomableMediaGallery.d.ts +5 -0
- package/lib/typescript/commonjs/zoomable-media-gallery/ZoomableMediaGallery.d.ts.map +1 -0
- package/lib/typescript/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts.map +1 -1
- package/lib/typescript/commonjs/zoomable-media-gallery/index.d.ts +4 -0
- package/lib/typescript/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/zoomable-media-gallery/types.d.ts +90 -0
- package/lib/typescript/commonjs/zoomable-media-gallery/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/media-flight/MediaFlightLayer.d.ts +11 -0
- package/lib/typescript/module/media-flight/MediaFlightLayer.d.ts.map +1 -0
- package/lib/typescript/module/media-flight/MediaSurface.d.ts +106 -0
- package/lib/typescript/module/media-flight/MediaSurface.d.ts.map +1 -0
- package/lib/typescript/module/media-flight/constants.d.ts +18 -0
- package/lib/typescript/module/media-flight/constants.d.ts.map +1 -0
- package/lib/typescript/module/media-flight/expo-video-module.d.ts +103 -0
- package/lib/typescript/module/media-flight/expo-video-module.d.ts.map +1 -0
- package/lib/typescript/module/media-flight/index.d.ts +9 -0
- package/lib/typescript/module/media-flight/index.d.ts.map +1 -0
- package/lib/typescript/module/media-flight/store.d.ts +132 -0
- package/lib/typescript/module/media-flight/store.d.ts.map +1 -0
- package/lib/typescript/module/media-flight/types.d.ts +195 -0
- package/lib/typescript/module/media-flight/types.d.ts.map +1 -0
- package/lib/typescript/module/media-flight/use-media-flight.d.ts +4 -0
- package/lib/typescript/module/media-flight/use-media-flight.d.ts.map +1 -0
- package/lib/typescript/module/zoomable-media-gallery/ZoomableMediaGallery.d.ts +5 -0
- package/lib/typescript/module/zoomable-media-gallery/ZoomableMediaGallery.d.ts.map +1 -0
- package/lib/typescript/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts.map +1 -1
- package/lib/typescript/module/zoomable-media-gallery/index.d.ts +4 -0
- package/lib/typescript/module/{zoomable-image-gallery → zoomable-media-gallery}/index.d.ts.map +1 -1
- package/lib/typescript/module/zoomable-media-gallery/types.d.ts +90 -0
- package/lib/typescript/module/zoomable-media-gallery/types.d.ts.map +1 -0
- package/package.json +42 -8
- package/src/image-aspect-ratio-cache/aspect-ratio-cache.ts +1 -1
- package/src/index.ts +2 -1
- package/src/index.web.ts +2 -1
- package/src/media-flight/MediaFlightLayer.tsx +144 -0
- package/src/media-flight/MediaSurface.tsx +233 -0
- package/src/media-flight/constants.ts +32 -0
- package/src/media-flight/expo-video-module.ts +181 -0
- package/src/media-flight/index.ts +33 -0
- package/src/media-flight/store.ts +497 -0
- package/src/media-flight/types.ts +210 -0
- package/src/media-flight/use-media-flight.ts +42 -0
- package/src/tab-bar/TabBarBase.tsx +1 -1
- package/src/{zoomable-image-gallery/ZoomableImageGallery.tsx → zoomable-media-gallery/ZoomableMediaGallery.tsx} +192 -117
- package/src/zoomable-media-gallery/index.ts +11 -0
- package/src/zoomable-media-gallery/types.ts +100 -0
- package/docs/zoomable-image-gallery.mdx +0 -66
- package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js.map +0 -1
- package/lib/commonjs/zoomable-image-gallery/types.js +0 -2
- package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js.map +0 -1
- package/lib/module/zoomable-image-gallery/index.js +0 -5
- package/lib/module/zoomable-image-gallery/index.js.map +0 -1
- package/lib/module/zoomable-image-gallery/types.js +0 -2
- package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts +0 -5
- package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +0 -1
- package/lib/typescript/commonjs/zoomable-image-gallery/index.d.ts +0 -4
- package/lib/typescript/commonjs/zoomable-image-gallery/types.d.ts +0 -56
- package/lib/typescript/commonjs/zoomable-image-gallery/types.d.ts.map +0 -1
- package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts +0 -5
- package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +0 -1
- package/lib/typescript/module/zoomable-image-gallery/index.d.ts +0 -4
- package/lib/typescript/module/zoomable-image-gallery/types.d.ts +0 -56
- package/lib/typescript/module/zoomable-image-gallery/types.d.ts.map +0 -1
- package/src/zoomable-image-gallery/index.ts +0 -9
- package/src/zoomable-image-gallery/types.ts +0 -59
- /package/lib/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.js +0 -0
- /package/lib/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.js +0 -0
- /package/lib/typescript/commonjs/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts +0 -0
- /package/lib/typescript/module/{zoomable-image-gallery → zoomable-media-gallery}/constants.d.ts +0 -0
- /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
|
-
|
|
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
|
|
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
|
|
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
|
|
85
|
-
* measured-origin zoom transition
|
|
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)
|
|
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
|
|
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
|
|
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-
|
|
158
|
+
// Once true, the swipeable pager is mounted and the single open-surface hidden.
|
|
112
159
|
const [pagerReady, setPagerReady] = useState(false);
|
|
113
|
-
const [
|
|
160
|
+
const [items, setItems] = useState([]);
|
|
114
161
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
115
|
-
// Whether the active
|
|
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
|
|
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
|
|
166
|
+
// Aspect ratio of the OPENING item (drives the open-animation fit box).
|
|
120
167
|
const [openRatio, setOpenRatio] = useState(DEFAULT_ASPECT_RATIO);
|
|
121
|
-
// Per-
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
209
|
-
// all zoom bookkeeping. Called on every path that changes the active
|
|
210
|
-
// freshly-viewed
|
|
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
|
|
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-
|
|
252
|
-
// the opened
|
|
253
|
-
// after swiping it tracks the viewed
|
|
254
|
-
// and flies back the
|
|
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 =
|
|
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
|
|
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 =
|
|
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)
|
|
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-
|
|
357
|
-
// one
|
|
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 =
|
|
361
|
-
// Recompute the fly-back target from the CURRENT
|
|
362
|
-
// thumbnail + the same fitted box (`activeFit`) the open-
|
|
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,
|
|
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((
|
|
391
|
-
if (isOpen ||
|
|
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),
|
|
394
|
-
const target =
|
|
446
|
+
const safeIndex = Math.min(Math.max(index, 0), nextItems.length - 1);
|
|
447
|
+
const target = nextItems[safeIndex];
|
|
395
448
|
if (!target) return;
|
|
396
|
-
const
|
|
449
|
+
const targetUri = posterUri(target);
|
|
450
|
+
const knownRatio = target.aspectRatio ?? cachedRatio(target);
|
|
397
451
|
const ratio = knownRatio ?? DEFAULT_ASPECT_RATIO;
|
|
398
|
-
|
|
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
|
|
457
|
+
// a genuinely-unknown item falls through to `ensureRatio`'s async probe.
|
|
404
458
|
const initialRatios = {};
|
|
405
|
-
|
|
406
|
-
const r =
|
|
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
|
-
|
|
413
|
-
|
|
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-
|
|
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,
|
|
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-
|
|
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 =
|
|
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
|
|
575
|
-
if (
|
|
576
|
-
}, [ensureRatio,
|
|
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 =
|
|
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
|
-
}, [
|
|
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
|
|
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
|
|
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('
|
|
791
|
+
if (typeof console !== 'undefined' && console.error) console.error('Media share failed:', err);
|
|
735
792
|
}
|
|
736
|
-
}, [
|
|
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
|
|
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(
|
|
778
|
-
|
|
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
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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:
|
|
814
|
-
const
|
|
815
|
-
const
|
|
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).
|
|
818
|
-
//
|
|
819
|
-
//
|
|
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
|
|
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
|
|
886
|
+
// Same outcome as tapping the media, which already
|
|
827
887
|
// dismisses through `singleTapDismissGesture`.
|
|
828
888
|
onPress: handleDismiss,
|
|
829
|
-
accessibilityLabel: "Close
|
|
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(
|
|
837
|
-
|
|
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
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
},
|
|
911
|
+
}, mediaKey(item, idx));
|
|
853
912
|
}
|
|
854
913
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
855
914
|
onPress: handleDismiss,
|
|
856
|
-
accessibilityLabel: "Close
|
|
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(
|
|
862
|
-
|
|
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
|
-
},
|
|
930
|
+
}, mediaKey(item, idx));
|
|
877
931
|
})
|
|
878
932
|
})
|
|
879
|
-
}), pagerReady &&
|
|
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} / ${
|
|
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:
|
|
948
|
+
children: items.map((item, idx) => /*#__PURE__*/_jsx(Pressable, {
|
|
895
949
|
onPress: () => pageTo(idx),
|
|
896
950
|
accessibilityRole: "button",
|
|
897
|
-
accessibilityLabel: `Go to
|
|
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(
|
|
900
|
-
|
|
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-${
|
|
958
|
+
}, `thumb-${mediaKey(item, idx)}`))
|
|
911
959
|
}) : /*#__PURE__*/_jsx(View, {
|
|
912
960
|
style: styles.dotsRow,
|
|
913
961
|
pointerEvents: "none",
|
|
914
|
-
children:
|
|
962
|
+
children: items.map((item, idx) => /*#__PURE__*/_jsx(View, {
|
|
915
963
|
style: [styles.dot, idx === activeIndex ? styles.dotActive : styles.dotInactive]
|
|
916
|
-
}, `dot-${
|
|
964
|
+
}, `dot-${mediaKey(item, idx)}`))
|
|
917
965
|
})]
|
|
918
|
-
}), Platform.OS === 'web' && pagerReady &&
|
|
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 &&
|
|
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
|
|
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,
|
|
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
|
-
|
|
963
|
-
export const
|
|
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
|
|
1142
|
-
//# sourceMappingURL=
|
|
1195
|
+
export default ZoomableMediaGallery;
|
|
1196
|
+
//# sourceMappingURL=ZoomableMediaGallery.js.map
|