zkit-ui 2.0.5 → 2.0.6

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 (75) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryLayoutSupport.java → NativeImagePreviewLayoutSupport.java} +12 -12
  3. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryMediaItem.java → NativeImagePreviewMediaItem.java} +2 -2
  4. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewModule.kt +43 -0
  5. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryPagerAdapter.java → NativeImagePreviewPagerAdapter.java} +18 -18
  6. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GallerySharedElementGeometry.java → NativeImagePreviewSharedElementGeometry.java} +2 -2
  7. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementNames.java +39 -0
  8. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GallerySharedElementState.java → NativeImagePreviewSharedElementState.java} +4 -4
  9. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GallerySourceViewRegistry.java → NativeImagePreviewSourceViewRegistry.java} +2 -2
  10. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryTransitionSnapshotView.java → NativeImagePreviewTransitionSnapshotView.java} +3 -3
  11. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryVideoPlayerView.java → NativeImagePreviewVideoPlayerView.java} +7 -7
  12. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GaleriaView.kt → NativeImagePreviewView.kt} +18 -18
  13. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryViewerOverlayView.java → NativeImagePreviewViewerOverlayView.java} +49 -49
  14. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryViewerPageView.java → NativeImagePreviewViewerPageView.java} +27 -27
  15. package/android/src/main/java/cn/fontree/zkit/ui/preview/{GalleryViewerTransitionController.java → NativeImagePreviewViewerTransitionController.java} +22 -22
  16. package/dist/ui/NativeImagePreview/NativeImagePreview.js +6 -6
  17. package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts +1 -0
  18. package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts.map +1 -1
  19. package/dist/ui/NativeImagePreview/NativeImagePreview.web.js +105 -11
  20. package/dist/ui/NativeImagePreview/context.d.ts +1 -1
  21. package/dist/ui/NativeImagePreview/context.d.ts.map +1 -1
  22. package/dist/ui/NativeImagePreview/types.d.ts +3 -1
  23. package/dist/ui/NativeImagePreview/types.d.ts.map +1 -1
  24. package/dist/ui/NativeImagePreview/utils.d.ts.map +1 -1
  25. package/dist/ui/NativeImagePreview/utils.js +1 -0
  26. package/dist/ui/NativeImagePreview/web/geometry.d.ts +20 -0
  27. package/dist/ui/NativeImagePreview/web/geometry.d.ts.map +1 -0
  28. package/dist/ui/NativeImagePreview/web/geometry.js +121 -0
  29. package/dist/ui/NativeImagePreview/web/media.d.ts +6 -0
  30. package/dist/ui/NativeImagePreview/web/media.d.ts.map +1 -0
  31. package/dist/ui/NativeImagePreview/web/media.js +90 -0
  32. package/dist/ui/NativeImagePreview/web/styles.css +137 -0
  33. package/dist/ui/NativeImagePreview/web/transition.d.ts +15 -0
  34. package/dist/ui/NativeImagePreview/web/transition.d.ts.map +1 -0
  35. package/dist/ui/NativeImagePreview/web/transition.js +77 -0
  36. package/dist/ui/NativeImagePreview/web/types.d.ts +74 -0
  37. package/dist/ui/NativeImagePreview/web/types.d.ts.map +1 -0
  38. package/dist/ui/NativeImagePreview/web/types.js +3 -0
  39. package/dist/ui/NativeImagePreview/web/viewer.d.ts +104 -0
  40. package/dist/ui/NativeImagePreview/web/viewer.d.ts.map +1 -0
  41. package/dist/ui/NativeImagePreview/web/viewer.js +894 -0
  42. package/expo-module.config.json +2 -2
  43. package/ios/{GalleryViewer/GaleriaImageLoader.swift → NativeImagePreview/NativeImagePreviewImageLoader.swift} +7 -7
  44. package/ios/NativeImagePreview/NativeImagePreviewImageViewInteraction.swift +245 -0
  45. package/ios/{GalleryViewer/GaleriaLayoutSupport.swift → NativeImagePreview/NativeImagePreviewLayoutSupport.swift} +19 -19
  46. package/ios/NativeImagePreview/NativeImagePreviewMedia.swift +28 -0
  47. package/ios/NativeImagePreview/NativeImagePreviewSourceViewRegistry.swift +80 -0
  48. package/ios/{GalleryViewer/GaleriaVideoPlayerView.swift → NativeImagePreview/NativeImagePreviewVideoPlayerView.swift} +1 -1
  49. package/ios/{GalleryViewer/GaleriaViewerConfiguration.swift → NativeImagePreview/NativeImagePreviewViewerConfiguration.swift} +7 -7
  50. package/ios/{GalleryViewer/GaleriaViewerController.swift → NativeImagePreview/NativeImagePreviewViewerController.swift} +36 -36
  51. package/ios/{GalleryViewer/GaleriaViewerPageView.swift → NativeImagePreview/NativeImagePreviewViewerPageView.swift} +33 -33
  52. package/ios/{GalleryViewer/GaleriaViewerTransitionCoordinator.swift → NativeImagePreview/NativeImagePreviewViewerTransitionCoordinator.swift} +16 -16
  53. package/ios/{GaleriaModule.swift → NativeImagePreviewModule.swift} +4 -4
  54. package/ios/{GaleriaView.swift → NativeImagePreviewView.swift} +26 -27
  55. package/package.json +18 -12
  56. package/src/services/ImagePreviewService/README.md +1 -1
  57. package/src/types.d.ts +1 -0
  58. package/src/ui/NativeImagePreview/NativeImagePreview.tsx +7 -7
  59. package/src/ui/NativeImagePreview/NativeImagePreview.web.tsx +175 -11
  60. package/src/ui/NativeImagePreview/README.md +3 -3
  61. package/src/ui/NativeImagePreview/context.tsx +1 -1
  62. package/src/ui/NativeImagePreview/types.ts +3 -1
  63. package/src/ui/NativeImagePreview/utils.ts +1 -0
  64. package/src/ui/NativeImagePreview/web/geometry.ts +134 -0
  65. package/src/ui/NativeImagePreview/web/media.ts +91 -0
  66. package/src/ui/NativeImagePreview/web/styles.css +137 -0
  67. package/src/ui/NativeImagePreview/web/transition.ts +111 -0
  68. package/src/ui/NativeImagePreview/web/types.ts +88 -0
  69. package/src/ui/NativeImagePreview/web/viewer.ts +1024 -0
  70. package/android/src/main/java/cn/fontree/zkit/ui/preview/GaleriaModule.kt +0 -43
  71. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementNames.java +0 -39
  72. package/ios/GalleryViewer/GaleriaImageViewInteraction.swift +0 -245
  73. package/ios/GalleryViewer/GaleriaMedia.swift +0 -28
  74. package/ios/GalleryViewer/GaleriaSourceViewRegistry.swift +0 -80
  75. /package/android/src/main/res/layout/{galeria_player_view.xml → native_image_preview_player_view.xml} +0 -0
@@ -1,10 +1,10 @@
1
1
  import ExpoModulesCore
2
2
  import UIKit
3
3
 
4
- class GaleriaView: ExpoView {
4
+ class NativeImagePreviewView: ExpoView {
5
5
  private var childImageView: UIImageView?
6
6
  private weak var registeredAnchorImageView: UIImageView?
7
- private var registeredGalleryId: String?
7
+ private var registeredPreviewGroupId: String?
8
8
  private var registeredIndex: Int?
9
9
 
10
10
  deinit {
@@ -57,11 +57,10 @@ class GaleriaView: ExpoView {
57
57
  #endif
58
58
 
59
59
  #if RCT_NEW_ARCH_ENABLED
60
- // https://github.com/nandorojo/galeria/issues/19
61
- // Cleanup gesture recognizers from the image view to work with fabric view recycling
60
+ // Cleanup gesture recognizers from the image view to work with Fabric view recycling.
62
61
  override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
63
62
  unregisterCurrentAnchor()
64
- childImageView?.removeGaleriaViewerInteraction()
63
+ childImageView?.removeNativeImagePreviewViewerInteraction()
65
64
  childImageView = nil
66
65
  super.unmountChildComponentView(childComponentView, index: index)
67
66
  }
@@ -71,7 +70,7 @@ class GaleriaView: ExpoView {
71
70
  var urls: [String]?
72
71
  var items: [[String: Any]]?
73
72
  var initialIndex: Int?
74
- var galleryId: String?
73
+ var previewGroupId: String?
75
74
  var closeIconName: String?
76
75
  var rightNavItemIconName: String?
77
76
  let onPressRightNavItemIcon = EventDispatcher()
@@ -99,7 +98,7 @@ class GaleriaView: ExpoView {
99
98
  ) {
100
99
  let configuration = buildViewerConfiguration()
101
100
 
102
- let imageItems: [GaleriaMediaItem] = items.compactMap { item in
101
+ let imageItems: [NativeImagePreviewMediaItem] = items.compactMap { item in
103
102
  guard let type = item["type"] as? String else { return nil }
104
103
 
105
104
  if type == "video", let urlStr = item["url"] as? String, let url = URL(string: urlStr) {
@@ -107,25 +106,25 @@ class GaleriaView: ExpoView {
107
106
  if let posterStr = item["poster"] as? String {
108
107
  posterUrl = URL(string: posterStr)
109
108
  }
110
- return GaleriaMediaItem.video(url: url, poster: posterUrl)
109
+ return NativeImagePreviewMediaItem.video(url: url, poster: posterUrl)
111
110
  } else if type == "image", let urlStr = item["url"] as? String {
112
111
  if urlStr.hasPrefix("http://") || urlStr.hasPrefix("https://") || urlStr.hasPrefix("file://") {
113
112
  if let url = URL(string: urlStr) {
114
- return GaleriaMediaItem.imageURL(url, placeholder: nil)
113
+ return NativeImagePreviewMediaItem.imageURL(url, placeholder: nil)
115
114
  }
116
115
  } else {
117
- return GaleriaMediaItem.imageURL(URL(fileURLWithPath: urlStr), placeholder: nil)
116
+ return NativeImagePreviewMediaItem.imageURL(URL(fileURLWithPath: urlStr), placeholder: nil)
118
117
  }
119
118
  }
120
119
  return nil
121
120
  }
122
121
 
123
- let datasource = GaleriaArrayDataSource(items: imageItems)
124
- childImage.setupGaleriaViewer(
122
+ let datasource = NativeImagePreviewArrayDataSource(items: imageItems)
123
+ childImage.setupNativeImagePreviewViewer(
125
124
  dataSource: datasource,
126
125
  initialIndex: initialIndex,
127
126
  configuration: configuration,
128
- galleryId: galleryId)
127
+ previewGroupId: previewGroupId)
129
128
  }
130
129
 
131
130
 
@@ -143,11 +142,11 @@ class GaleriaView: ExpoView {
143
142
  return URL(fileURLWithPath: string)
144
143
  }
145
144
 
146
- childImage.setupGaleriaViewer(
145
+ childImage.setupNativeImagePreviewViewer(
147
146
  urls: urlObjects,
148
147
  initialIndex: initialIndex,
149
148
  configuration: configuration,
150
- galleryId: galleryId)
149
+ previewGroupId: previewGroupId)
151
150
  }
152
151
 
153
152
  private func setupViewerWithSingleImage(_ childImage: UIImageView) {
@@ -157,11 +156,11 @@ class GaleriaView: ExpoView {
157
156
  }
158
157
  let configuration = buildViewerConfiguration()
159
158
 
160
- childImage.setupGaleriaViewer(images: [img], configuration: configuration, galleryId: galleryId)
159
+ childImage.setupNativeImagePreviewViewer(images: [img], configuration: configuration, previewGroupId: previewGroupId)
161
160
  }
162
161
 
163
- private func buildViewerConfiguration() -> GaleriaViewerConfiguration {
164
- var configuration = GaleriaViewerConfiguration(theme: theme.toViewerTheme())
162
+ private func buildViewerConfiguration() -> NativeImagePreviewViewerConfiguration {
163
+ var configuration = NativeImagePreviewViewerConfiguration(theme: theme.toViewerTheme())
165
164
  let iconColor = theme.iconColor()
166
165
 
167
166
  if let closeIconName = closeIconName,
@@ -191,7 +190,7 @@ class GaleriaView: ExpoView {
191
190
  private func registerCurrentAnchorIfNeeded() {
192
191
  guard
193
192
  window != nil,
194
- let galleryId,
193
+ let previewGroupId,
195
194
  let initialIndex,
196
195
  let childImage = getChildImageView()
197
196
  else {
@@ -202,30 +201,30 @@ class GaleriaView: ExpoView {
202
201
  if
203
202
  let registeredAnchorImageView,
204
203
  (registeredAnchorImageView !== childImage
205
- || registeredGalleryId != galleryId
204
+ || registeredPreviewGroupId != previewGroupId
206
205
  || registeredIndex != initialIndex)
207
206
  {
208
207
  unregisterCurrentAnchor()
209
208
  }
210
209
 
211
- GaleriaSourceViewRegistry.shared.register(
210
+ NativeImagePreviewSourceViewRegistry.shared.register(
212
211
  childImage,
213
- galleryId: galleryId,
212
+ previewGroupId: previewGroupId,
214
213
  index: initialIndex)
215
214
  registeredAnchorImageView = childImage
216
- registeredGalleryId = galleryId
215
+ registeredPreviewGroupId = previewGroupId
217
216
  registeredIndex = initialIndex
218
217
  }
219
218
 
220
219
  private func unregisterCurrentAnchor() {
221
220
  if let registeredAnchorImageView = registeredAnchorImageView {
222
- GaleriaSourceViewRegistry.shared.unregister(
221
+ NativeImagePreviewSourceViewRegistry.shared.unregister(
223
222
  registeredAnchorImageView,
224
- galleryId: registeredGalleryId,
223
+ previewGroupId: registeredPreviewGroupId,
225
224
  index: registeredIndex)
226
225
  }
227
226
  registeredAnchorImageView = nil
228
- registeredGalleryId = nil
227
+ registeredPreviewGroupId = nil
229
228
  registeredIndex = nil
230
229
  }
231
230
  }
@@ -234,7 +233,7 @@ enum Theme: String, Enumerable {
234
233
  case dark
235
234
  case light
236
235
 
237
- func toViewerTheme() -> GaleriaViewerTheme {
236
+ func toViewerTheme() -> NativeImagePreviewViewerTheme {
238
237
  switch self {
239
238
  case .dark:
240
239
  return .dark
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zkit-ui",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -170,7 +170,10 @@
170
170
  "./assets/*": "./dist/assets/*",
171
171
  "./package.json": "./package.json"
172
172
  },
173
- "sideEffects": false,
173
+ "sideEffects": [
174
+ "*.css",
175
+ "**/*.css"
176
+ ],
174
177
  "repository": {
175
178
  "type": "git",
176
179
  "url": "git+https://github.com/Concur-max/zkit.git",
@@ -181,7 +184,8 @@
181
184
  "url": "https://github.com/Concur-max/zkit/issues"
182
185
  },
183
186
  "files": [
184
- "android",
187
+ "android/build.gradle",
188
+ "android/src",
185
189
  "dist",
186
190
  "expo-module.config.json",
187
191
  "src",
@@ -193,6 +197,14 @@
193
197
  "publishConfig": {
194
198
  "registry": "https://registry.npmjs.org/"
195
199
  },
200
+ "scripts": {
201
+ "build": "node scripts/build.cjs",
202
+ "verify:entrypoints": "node scripts/verify-entrypoints.cjs",
203
+ "verify": "pnpm run build && pnpm run verify:entrypoints",
204
+ "typecheck": "tsc -p tsconfig.json --noEmit",
205
+ "prepack": "pnpm run build",
206
+ "prepublishOnly": "pnpm run build"
207
+ },
196
208
  "dependencies": {
197
209
  "@vant/area-data": "^2.1.0",
198
210
  "dayjs": "^1.11.13",
@@ -219,6 +231,7 @@
219
231
  },
220
232
  "devDependencies": {
221
233
  "@expo/vector-icons": "^15.1.1",
234
+ "@lodev09/react-native-true-sheet": "workspace:*",
222
235
  "@shopify/flash-list": "^2.3.1",
223
236
  "@types/react": "~19.1.17",
224
237
  "dayjs": "^1.11.13",
@@ -235,13 +248,6 @@
235
248
  "react-native-svg": "^15.12.1",
236
249
  "react-native-worklets": "0.5.1",
237
250
  "sonner-native": "^0.22.2",
238
- "typescript": "~5.9.3",
239
- "@lodev09/react-native-true-sheet": "3.10.0"
240
- },
241
- "scripts": {
242
- "build": "node scripts/build.cjs",
243
- "verify:entrypoints": "node scripts/verify-entrypoints.cjs",
244
- "verify": "pnpm run build && pnpm run verify:entrypoints",
245
- "typecheck": "tsc -p tsconfig.json --noEmit"
251
+ "typescript": "~5.9.3"
246
252
  }
247
- }
253
+ }
@@ -77,7 +77,7 @@ import { NativeImagePreview } from 'zkit-ui/native-image-preview';
77
77
  两者不要混用成同一个本地封装:
78
78
 
79
79
  - `ImagePreview`:普通全屏预览,支持声明式 `open/value` 和命令式 `imagePreview.open()`。
80
- - `NativeImagePreview`:原生共享转场预览,需要包住真实缩略图节点,iOS / Android 走原生 viewer。
80
+ - `NativeImagePreview`:共享转场预览,需要包住真实缩略图节点,iOS / Android 走原生 viewer,Web 走 H5 viewer
81
81
 
82
82
  ## 手动确认点
83
83
 
package/src/types.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module '*.css';
@@ -25,7 +25,7 @@ type NativePreviewViewProps = {
25
25
  children: React.ReactElement;
26
26
  index: number;
27
27
  style?: StyleProp<ViewStyle>;
28
- galleryId: string;
28
+ previewGroupId: string;
29
29
  items: readonly NativeImagePreviewNativeItem[];
30
30
  theme: 'dark' | 'light';
31
31
  edgeToEdge: boolean;
@@ -39,7 +39,7 @@ const NativePreviewView = requireNativeView<NativePreviewViewProps>(
39
39
 
40
40
  const ANDROID_EDGE_TO_EDGE = Platform.OS === 'android' ? isEdgeToEdge() : false;
41
41
 
42
- function createGalleryId() {
42
+ function createPreviewGroupId() {
43
43
  return `zkit-native-image-preview-${Date.now().toString(36)}-${Math.random()
44
44
  .toString(36)
45
45
  .slice(2, 10)}`;
@@ -55,9 +55,9 @@ const NativeImagePreviewRoot = React.memo(function NativeImagePreview({
55
55
  onChange,
56
56
  }: NativeImagePreviewProps) {
57
57
  const systemColorScheme = useColorScheme();
58
- const galleryIdRef = React.useRef<string | null>(null);
59
- if (galleryIdRef.current == null) {
60
- galleryIdRef.current = createGalleryId();
58
+ const previewGroupIdRef = React.useRef<string | null>(null);
59
+ if (previewGroupIdRef.current == null) {
60
+ previewGroupIdRef.current = createPreviewGroupId();
61
61
  }
62
62
 
63
63
  const resolvedItems = React.useMemo(
@@ -71,7 +71,7 @@ const NativeImagePreviewRoot = React.memo(function NativeImagePreview({
71
71
  const resolvedColorScheme = resolveNativeImagePreviewColorScheme(colorScheme, systemColorScheme);
72
72
  const contextValue = React.useMemo(
73
73
  () => ({
74
- galleryId: galleryIdRef.current!,
74
+ previewGroupId: previewGroupIdRef.current!,
75
75
  items: resolvedItems,
76
76
  nativeItems,
77
77
  colorScheme: resolvedColorScheme,
@@ -129,7 +129,7 @@ const NativeImagePreviewItem = React.memo(function NativeImagePreviewItem({
129
129
  <NativePreviewView
130
130
  closeIconName={context.nativeProps?.iosCloseIconName}
131
131
  edgeToEdge={resolvedEdgeToEdge}
132
- galleryId={context.galleryId}
132
+ previewGroupId={context.previewGroupId}
133
133
  index={clampPreviewIndex(index, context.nativeItems.length)}
134
134
  items={context.nativeItems}
135
135
  onIndexChange={handleIndexChange}
@@ -1,35 +1,199 @@
1
+ /// <reference lib="dom" />
2
+
3
+ import './web/styles.css';
4
+
1
5
  import * as React from 'react';
2
- import { View } from 'react-native';
6
+ import { View, useColorScheme } from 'react-native';
7
+ import { clampPreviewIndex, normalizeNativeImagePreviewItems, resolveNativeImagePreviewColorScheme } from './utils';
3
8
  import type {
9
+ NativeImagePreviewChangeMeta,
4
10
  NativeImagePreviewComponent,
5
11
  NativeImagePreviewItemProps,
6
12
  NativeImagePreviewProps,
13
+ NativeImagePreviewResolvedItem,
7
14
  } from './types';
15
+ import { createNativeImagePreviewWeb } from './web/viewer';
16
+ import type {
17
+ NativeImagePreviewWebChangeMeta,
18
+ NativeImagePreviewWebController,
19
+ NativeImagePreviewWebMediaItem,
20
+ } from './web/types';
8
21
 
9
- let didWarnUnsupportedWeb = false;
22
+ type SourceElement = HTMLElement | null;
10
23
 
11
- function warnUnsupportedWeb() {
12
- if (!__DEV__ || didWarnUnsupportedWeb) return;
24
+ type NativeImagePreviewWebContextValue = {
25
+ disabled: boolean;
26
+ items: readonly NativeImagePreviewResolvedItem[];
27
+ open: (
28
+ index: number,
29
+ onItemChange?: (value: number, meta: NativeImagePreviewChangeMeta) => void
30
+ ) => void;
31
+ sourceElementsRef: { current: SourceElement[] };
32
+ };
13
33
 
14
- didWarnUnsupportedWeb = true;
15
- console.warn(
16
- '[zkit-ui][NativeImagePreview] Native shared-transition preview is only available in iOS and Android native builds.'
17
- );
34
+ const NativeImagePreviewWebContext =
35
+ React.createContext<NativeImagePreviewWebContextValue | null>(null);
36
+
37
+ const WebView = View as unknown as React.ElementType;
38
+
39
+ function toWebPreviewItems(
40
+ items: readonly NativeImagePreviewResolvedItem[]
41
+ ): NativeImagePreviewWebMediaItem[] {
42
+ return items.map(({ alt, height, id, poster, type, url, width }) => ({
43
+ alt: alt ?? id,
44
+ height,
45
+ id,
46
+ poster,
47
+ type,
48
+ url,
49
+ width,
50
+ }));
51
+ }
52
+
53
+ function findPreviewSourceElement(element: SourceElement): SourceElement {
54
+ if (!element) return null;
55
+ if (element instanceof HTMLImageElement || element instanceof HTMLVideoElement) {
56
+ return element;
57
+ }
58
+ return element.querySelector('img, video') ?? element;
59
+ }
60
+
61
+ function toChangeMeta(
62
+ meta: NativeImagePreviewWebChangeMeta,
63
+ items: readonly NativeImagePreviewResolvedItem[]
64
+ ): NativeImagePreviewChangeMeta {
65
+ return {
66
+ reason: meta.reason,
67
+ item: items[meta.index] ?? null,
68
+ };
18
69
  }
19
70
 
20
71
  const NativeImagePreviewRoot = React.memo(function NativeImagePreview({
21
72
  children,
73
+ colorScheme = 'dark',
74
+ disabled = false,
75
+ items,
76
+ onChange,
22
77
  }: NativeImagePreviewProps) {
23
- React.useEffect(warnUnsupportedWeb, []);
78
+ const systemColorScheme = useColorScheme();
79
+ const controllerRef = React.useRef<NativeImagePreviewWebController | null>(null);
80
+ const sourceElementsRef = React.useRef<SourceElement[]>([]);
81
+ const resolvedItems = React.useMemo(
82
+ () => normalizeNativeImagePreviewItems(items),
83
+ [items]
84
+ );
85
+ const webItems = React.useMemo(
86
+ () => toWebPreviewItems(resolvedItems),
87
+ [resolvedItems]
88
+ );
89
+ const resolvedColorScheme = resolveNativeImagePreviewColorScheme(colorScheme, systemColorScheme);
90
+
91
+ React.useEffect(() => {
92
+ const controller = createNativeImagePreviewWeb();
93
+ controllerRef.current = controller;
94
+
95
+ return () => {
96
+ controller.destroy();
97
+ controllerRef.current = null;
98
+ };
99
+ }, []);
24
100
 
25
- return <>{children}</>;
101
+ const open = React.useCallback<NativeImagePreviewWebContextValue['open']>(
102
+ (index, onItemChange) => {
103
+ const controller = controllerRef.current;
104
+ if (disabled || !controller || webItems.length === 0) return;
105
+
106
+ const value = clampPreviewIndex(index, webItems.length);
107
+ const getSourceElement = (nextIndex: number) =>
108
+ findPreviewSourceElement(sourceElementsRef.current[nextIndex] ?? null);
109
+
110
+ void controller.open({
111
+ getSourceElement,
112
+ index: value,
113
+ items: webItems,
114
+ objectFit: 'cover',
115
+ sourceElement: getSourceElement(value),
116
+ sourceImageSize: resolvedItems[value]
117
+ ? {
118
+ width: resolvedItems[value].width ?? 0,
119
+ height: resolvedItems[value].height ?? 0,
120
+ }
121
+ : null,
122
+ sourceObjectFit: 'cover',
123
+ sourceVisibility: 'hidden',
124
+ theme: resolvedColorScheme,
125
+ onChange: (nextValue, meta) => {
126
+ const changeMeta = toChangeMeta(meta, resolvedItems);
127
+ onChange?.(nextValue, changeMeta);
128
+ onItemChange?.(nextValue, changeMeta);
129
+ },
130
+ });
131
+ },
132
+ [disabled, onChange, resolvedColorScheme, resolvedItems, webItems]
133
+ );
134
+
135
+ const contextValue = React.useMemo<NativeImagePreviewWebContextValue>(
136
+ () => ({
137
+ disabled,
138
+ items: resolvedItems,
139
+ open,
140
+ sourceElementsRef,
141
+ }),
142
+ [disabled, open, resolvedItems]
143
+ );
144
+
145
+ return (
146
+ <NativeImagePreviewWebContext.Provider value={contextValue}>
147
+ {children}
148
+ </NativeImagePreviewWebContext.Provider>
149
+ );
26
150
  });
27
151
 
28
152
  const NativeImagePreviewItem = React.memo(function NativeImagePreviewItem({
29
153
  children,
154
+ disabled = false,
155
+ index,
156
+ onChange,
30
157
  style,
31
158
  }: NativeImagePreviewItemProps) {
32
- return <View style={style}>{children}</View>;
159
+ const context = React.useContext(NativeImagePreviewWebContext);
160
+ const sourceRef = React.useCallback(
161
+ (node: SourceElement) => {
162
+ if (!context) return;
163
+ context.sourceElementsRef.current[index] = node;
164
+ },
165
+ [context, index]
166
+ );
167
+ const itemDisabled = !context || context.disabled || disabled || context.items.length === 0;
168
+
169
+ const open = React.useCallback(() => {
170
+ if (itemDisabled) return;
171
+ context.open(index, onChange);
172
+ }, [context, index, itemDisabled, onChange]);
173
+
174
+ const handleKeyDown = React.useCallback(
175
+ (event: React.KeyboardEvent) => {
176
+ if (itemDisabled) return;
177
+ if (event.key !== 'Enter' && event.key !== ' ') return;
178
+ event.preventDefault();
179
+ open();
180
+ },
181
+ [itemDisabled, open]
182
+ );
183
+
184
+ return (
185
+ <WebView
186
+ ref={sourceRef}
187
+ aria-label={itemDisabled ? undefined : 'Open image preview'}
188
+ onClick={itemDisabled ? undefined : open}
189
+ onKeyDown={itemDisabled ? undefined : handleKeyDown}
190
+ role={itemDisabled ? undefined : 'button'}
191
+ style={style}
192
+ tabIndex={itemDisabled ? undefined : 0}
193
+ >
194
+ {children}
195
+ </WebView>
196
+ );
33
197
  });
34
198
 
35
199
  export const NativeImagePreview = Object.assign(NativeImagePreviewRoot, {
@@ -1,6 +1,6 @@
1
1
  # NativeImagePreview
2
2
 
3
- `NativeImagePreview` 是强调原生共享转场体验的图片预览组件。它和普通 `ImagePreview` 服务分工不同:`ImagePreview` 适合命令式打开图片;`NativeImagePreview` 适合从真实缩略图自然过渡到全屏 viewer,并在 iOS / Android 上走原生手势、缩放、滑动和拖拽关闭路径。
3
+ `NativeImagePreview` 是强调真实缩略图锚点与共享转场体验的图片预览组件。它和普通 `ImagePreview` 服务分工不同:`ImagePreview` 适合命令式打开图片;`NativeImagePreview` 适合从真实缩略图自然过渡到全屏 viewer,并在 iOS / Android 上走原生手势、缩放、滑动和拖拽关闭路径,在 Web 上走 H5 共享转场 viewer。
4
4
 
5
5
  ## 适用场景
6
6
 
@@ -95,14 +95,14 @@ import { imagePreview } from 'zkit-ui/image-preview';
95
95
  imagePreview.open({ images, index: 0 });
96
96
  ```
97
97
 
98
- `ImagePreview` 是普通全屏预览服务,适合没有稳定缩略图锚点的命令式场景。`NativeImagePreview` 是声明式共享转场组件,必须就地包住真实图片节点。两者不要混成同一个封装,否则很容易把原生共享转场退化成普通弹层。
98
+ `ImagePreview` 是普通全屏预览服务,适合没有稳定缩略图锚点的命令式场景。`NativeImagePreview` 是声明式共享转场组件,必须就地包住真实图片节点。两者不要混成同一个封装,否则很容易把共享转场退化成普通弹层。
99
99
 
100
100
  ## 平台行为
101
101
 
102
102
  - iOS / Android:使用原生 viewer 和真实图片锚点,主打共享转场、缩放、滑动和拖拽关闭。
103
+ - Web:使用 H5 viewer 和真实 DOM 图片锚点,支持共享转场、缩放、滑动、视频播放和拖拽关闭。
103
104
  - 首次接入或变更原生依赖后需要重新构建 native app;仅刷新 JS bundle 不会注册新的原生 ViewManager。
104
105
  - 原生模块未注册时不会降级到 `ImagePreview`。这是 native 接入或构建错误,需要重新安装依赖并重建 native app。
105
- - Web:仅保留子节点渲染,不提供预览能力,也不会模拟普通 `ImagePreview`。
106
106
 
107
107
  ## 手动确认点
108
108
 
@@ -8,7 +8,7 @@ import type {
8
8
  } from './types';
9
9
 
10
10
  export type NativeImagePreviewContextValue = {
11
- galleryId: string;
11
+ previewGroupId: string;
12
12
  items: readonly NativeImagePreviewResolvedItem[];
13
13
  nativeItems: readonly NativeImagePreviewNativeItem[];
14
14
  colorScheme: NativeImagePreviewResolvedColorScheme;
@@ -15,6 +15,7 @@ export type NativeImagePreviewSource = ImageSourcePropType | string;
15
15
 
16
16
  export type NativeImagePreviewItemDescriptor = {
17
17
  id?: string;
18
+ alt?: string;
18
19
  type?: NativeImagePreviewMediaType;
19
20
  url?: string;
20
21
  source?: NativeImagePreviewSource;
@@ -34,6 +35,7 @@ export type NativeImagePreviewResolvedItem = {
34
35
  poster?: string;
35
36
  width?: number;
36
37
  height?: number;
38
+ alt?: string;
37
39
  raw: NativeImagePreviewItem;
38
40
  };
39
41
 
@@ -46,7 +48,7 @@ export type NativeImagePreviewNativeItem = {
46
48
  height?: number;
47
49
  };
48
50
 
49
- export type NativeImagePreviewChangeReason = 'swipe';
51
+ export type NativeImagePreviewChangeReason = 'api' | 'swipe';
50
52
 
51
53
  export type NativeImagePreviewChangeEventPayload = {
52
54
  currentIndex: number;
@@ -56,6 +56,7 @@ function normalizePreviewItem(
56
56
  poster,
57
57
  width: item.width,
58
58
  height: item.height,
59
+ alt: item.alt,
59
60
  raw: item,
60
61
  };
61
62
  }