react-native-image-stitcher 0.13.0 → 0.14.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 (39) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +33 -17
  3. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +33 -5
  4. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +73 -1
  5. package/dist/camera/Camera.d.ts +71 -16
  6. package/dist/camera/Camera.js +167 -51
  7. package/dist/camera/CameraView.d.ts +6 -0
  8. package/dist/camera/CameraView.js +2 -2
  9. package/dist/camera/CaptureHeader.js +39 -16
  10. package/dist/camera/CapturePreview.js +13 -1
  11. package/dist/camera/CaptureThumbnailStrip.d.ts +25 -1
  12. package/dist/camera/CaptureThumbnailStrip.js +17 -4
  13. package/dist/camera/PanoramaBandOverlay.d.ts +76 -0
  14. package/dist/camera/PanoramaBandOverlay.js +90 -33
  15. package/dist/camera/PanoramaConfirmModal.js +11 -1
  16. package/dist/camera/selectCaptureDevice.d.ts +93 -0
  17. package/dist/camera/selectCaptureDevice.js +131 -0
  18. package/dist/camera/useCapture.d.ts +40 -0
  19. package/dist/camera/useCapture.js +50 -12
  20. package/dist/camera/useContentRotation.d.ts +99 -0
  21. package/dist/camera/useContentRotation.js +124 -0
  22. package/dist/index.d.ts +1 -3
  23. package/dist/index.js +6 -5
  24. package/package.json +1 -1
  25. package/src/camera/Camera.tsx +281 -118
  26. package/src/camera/CameraView.tsx +9 -0
  27. package/src/camera/CaptureHeader.tsx +39 -16
  28. package/src/camera/CapturePreview.tsx +12 -0
  29. package/src/camera/CaptureThumbnailStrip.tsx +44 -4
  30. package/src/camera/PanoramaBandOverlay.tsx +97 -35
  31. package/src/camera/PanoramaConfirmModal.tsx +10 -0
  32. package/src/camera/__tests__/bandThumbRotation.test.ts +120 -0
  33. package/src/camera/__tests__/homeIndicatorEdge.test.ts +116 -0
  34. package/src/camera/__tests__/selectCaptureDevice.test.ts +177 -0
  35. package/src/camera/__tests__/useContentRotation.test.ts +89 -0
  36. package/src/camera/selectCaptureDevice.ts +187 -0
  37. package/src/camera/useCapture.ts +99 -11
  38. package/src/camera/useContentRotation.ts +149 -0
  39. package/src/index.ts +6 -2
package/CHANGELOG.md CHANGED
@@ -16,6 +16,111 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [0.14.0] — 2026-06-01
20
+
21
+ ### Fixed — Android AR single-photo orientation (landscape was sideways)
22
+
23
+ Android AR `takePhoto` baked the wrong rotation into landscape captures
24
+ under a portrait-locked host: it derived the EXIF orientation from the
25
+ window display rotation (`WindowManager.defaultDisplay.rotation`), which
26
+ stays `ROTATION_0` when the activity is portrait-locked regardless of how
27
+ the device is physically held — so a landscape photo got a portrait EXIF
28
+ tag and came out 90° CW. The JS layer already passed the gyro device
29
+ orientation to `RNSARSession.takePhoto` (since v0.12), and iOS consumed
30
+ it, but the Android native side dropped it. Now Android threads the
31
+ device orientation through `takePhoto → requestTakePhoto → encodeToJpeg`,
32
+ mapping it to the correct `Surface.ROTATION_*` / EXIF tag. iOS unchanged
33
+ (already correct). Verified on-device (Samsung A35) in both landscape
34
+ orientations.
35
+
36
+ ### Added — `captureSources` constraint prop
37
+
38
+ `<Camera>` gains `captureSources?: 'ar' | 'non-ar' | 'both'` (default
39
+ `'both'`) — a constraint on which capture sources the host allows, layered
40
+ over `defaultCaptureSource` (which picks the initial source within it):
41
+
42
+ - `'both'` — AR + non-AR; the runtime AR toggle is shown (unchanged
43
+ default behaviour).
44
+ - `'ar'` — AR only; the AR toggle is hidden (nothing to switch to) and
45
+ the 0.5×/1× lens chooser is hidden (ARKit/ARCore can't use the
46
+ ultra-wide), keeping capture on the AR-capable 1× lens.
47
+ - `'non-ar'`— non-AR only; the AR toggle is hidden, the lens chooser stays.
48
+
49
+ A single-source constraint overrides a conflicting `defaultCaptureSource`.
50
+ Exported type: `CaptureSourcesMode`. Verified on-device (A35) across all
51
+ three modes.
52
+
53
+ ### Fixed — capability-aware lens selection (ultra-wide + flash on 0.5×)
54
+
55
+ `<Camera>` now selects the back camera device by real capability instead
56
+ of requesting a single physical lens per zoom level. `selectCaptureDevice`:
57
+
58
+ - **Prefers a multi-cam device** that spans wide + ultra-wide (lens
59
+ switched via `zoom`; torch available on every lens). On devices that
60
+ expose such a device (e.g. iPhone 16 Pro — verified `multicam`), this
61
+ fixes the user-reported "0.5× shows the wide-angle FOV" bug AND makes
62
+ flash work on 0.5× (the mounted multi-cam device carries the torch).
63
+ - **Falls back to a standalone ultra-wide** device-swap where no multi-cam
64
+ device exists (e.g. Samsung A35 — verified `standalone-uw`; vision-camera
65
+ surfaces the physical cameras separately there). 0.5× still shows the
66
+ ultra-wide FOV; flash hides because that standalone device is torchless.
67
+
68
+ `has0_5x` is now derived from the real device inventory (was hardcoded
69
+ `true`), so the lens chooser hides on wide-only hardware. 13 unit tests
70
+ cover the selection matrix incl. both edge cases (ultra-wide only in a
71
+ multi-cam group; ultra-wide only standalone).
72
+
73
+ Verified on-device: iPhone 16 Pro (multicam — 0.5× FOV + flash both work)
74
+ and Samsung A35 (standalone-uw — 0.5× FOV works, flash correctly hidden).
75
+
76
+ ### Added — Android portrait lock (SDK-enforced)
77
+
78
+ `<Camera>` now locks its host Activity to portrait on Android while
79
+ mounted, via `Activity.setRequestedOrientation`, **regardless of the
80
+ host app's `AndroidManifest` `screenOrientation`**. A landscape or
81
+ unlocked host still gets a portrait camera screen. The Activity's
82
+ prior orientation is captured on mount and restored on unmount.
83
+ Implemented in the native `RNSARSession` module (`lockPortrait()` /
84
+ `unlockOrientation()`) and driven from a `<Camera>` mount effect, so
85
+ it covers both the AR (ARCore) and non-AR (vision-camera) paths.
86
+ There is no opt-out — Android capture is portrait-only by design.
87
+
88
+ iOS is intentionally unchanged: supported orientations remain owned by
89
+ the host `Info.plist`. **Portrait is the recommended configuration on
90
+ both platforms; landscape is supported on iOS** for hosts that need it.
91
+
92
+ ### Fixed — landscape preview + thumbnail orientation (non-locked iOS)
93
+
94
+ - **Preview squish / sideways** under a non-locked host was caused by
95
+ an in-development `patch-package` patch to vision-camera's
96
+ `OrientationManager` (both `.kt` and `.swift`) that derived the
97
+ PREVIEW orientation from the accelerometer instead of the interface
98
+ orientation. In a portrait host held landscape this forced a
99
+ landscape preview into a portrait surface. The patch was removed and
100
+ vision-camera restored to pristine on both platforms.
101
+ - **Band keyframe thumbnails rotated 90°**: the per-keyframe tiles in
102
+ `PanoramaBandOverlay` were double-rotated — the saved `keyframe-N.jpg`
103
+ is sensor-native landscape + EXIF Orientation 6, which `<Image>`
104
+ already auto-rotates, so the extra JS transform was redundant in the
105
+ portrait-locked (`vertical=false`) path. The transform is now applied
106
+ only in the `vertical=true` (non-locked landscape) path.
107
+ - **Stitched-preview / confirm modals stuck portrait**: `CapturePreview`
108
+ and `PanoramaConfirmModal` were missing `supportedOrientations`
109
+ (RN's iOS `<Modal>` defaults to portrait-only). Both now declare all
110
+ four, matching `OrientationDriftModal` + `PanoramaSettingsModal`.
111
+ - **Idle thumbnail strip horizontal in landscape**: `CaptureThumbnailStrip`
112
+ gained a `vertical` prop (wired from the same `isSideEdge` signal as
113
+ the band) so the idle strip stacks vertically along the home-indicator
114
+ edge under a non-locked host instead of running across the screen.
115
+
116
+ ### Removed — pan-guidance overlays no longer public
117
+
118
+ `IncrementalPanGuide` (drift marker) and `PanoramaGuidance` (pan-speed
119
+ pill) are no longer exported, and the `panGuide` / `panoramaGuidance`
120
+ props were removed from `<Camera>`. The components remain in the tree
121
+ as internal-only code (not rendered). Hosts that were passing these
122
+ props should remove them.
123
+
19
124
  ## [0.13.0] — 2026-05-29
20
125
 
21
126
  ### Added — Layer-2 components absorbed into `<Camera>` (opt-out)
package/README.md CHANGED
@@ -104,7 +104,11 @@ export function CaptureScreen() {
104
104
 
105
105
  ## `<Camera>` props (summary)
106
106
 
107
- See `src/camera/Camera.tsx` for the full TSDoc. Highlights:
107
+ > **Full reference:** [`docs/camera-component.md`](docs/camera-component.md)
108
+ > covers every prop with purpose, default, behaviour notes, the
109
+ > `CameraCaptureResult` / `CameraError` shapes, orientation
110
+ > behaviour, and common compositions. This README summary lists
111
+ > the highlights only.
108
112
 
109
113
  ### Initial values (uncontrolled — read once at mount)
110
114
 
@@ -142,22 +146,34 @@ See `src/camera/Camera.tsx` for the full TSDoc. Highlights:
142
146
 
143
147
  ## Orientation support
144
148
 
145
- `<Camera>` works in any device orientation regardless of host
146
- configuration. No host setup required the SDK adapts at runtime.
147
-
148
- **Portrait-locked host** (Info.plist `UISupportedInterfaceOrientations`
149
- restricted to Portrait — recommended for kiosks / single-task apps):
150
- the screen stays portrait; the SDK uses sensor-derived orientation
151
- for capture-mode selection and overlay layout. This is the simpler
152
- configuration and the historical default.
153
-
154
- **Non-locked host** (Info.plist supports all 4 orientations recommended
155
- for apps with other landscape-friendly screens): the screen rotates
156
- with the device. `<Camera>`'s controls (shutter, lens chip, AR toggle)
157
- anchor to the home-indicator edge so they stay within thumb reach
158
- regardless of tilt matching iOS Camera's behaviour. The
159
- orientation-aware logic combines `useWindowDimensions()` (JS-layout)
160
- with `useDeviceOrientation()` (sensor) to compute the correct anchor.
149
+ > **Recommended: portrait.** `<Camera>` is designed and tuned for
150
+ > portrait capture, and that is the recommended way to use it on both
151
+ > platforms. Landscape is supported on iOS for hosts that need it
152
+ > (see below); on Android the camera is always portrait.
153
+
154
+ **Android always portrait (SDK-enforced).** On Android `<Camera>`
155
+ locks its host Activity to portrait while mounted (via
156
+ `Activity.setRequestedOrientation`), **regardless of the host app's
157
+ manifest** — even a fully landscape or unlocked host gets a portrait
158
+ camera screen. The prior orientation is restored when `<Camera>`
159
+ unmounts. No host setup is required and there is no opt-out: Android
160
+ capture is portrait-only by design.
161
+
162
+ **iOS portrait recommended, landscape supported.** iOS supported
163
+ orientations are owned by the host's `Info.plist`
164
+ (`UISupportedInterfaceOrientations`); the SDK does not override them.
165
+
166
+ - *Portrait-only host* (Info.plist = Portrait — **recommended**): the
167
+ screen stays portrait; the SDK uses sensor-derived orientation for
168
+ capture-mode selection and overlay layout. Simplest configuration.
169
+ - *Non-locked host* (Info.plist supports all 4 — supported for apps
170
+ with other landscape-friendly screens): the screen rotates with the
171
+ device. `<Camera>`'s controls (shutter, lens chip, AR toggle) and
172
+ the live thumbnail strip/band anchor to the home-indicator edge so
173
+ they stay within thumb reach regardless of tilt — matching iOS
174
+ Camera's behaviour. The orientation-aware logic combines
175
+ `useWindowDimensions()` (JS-layout) with `useDeviceOrientation()`
176
+ (sensor) to compute the correct anchor.
161
177
 
162
178
  **Mid-capture rotation safety** — the incremental engine doesn't
163
179
  support cross-orientation captures (a portrait capture's keyframes
@@ -89,6 +89,14 @@ class RNSARCameraView @JvmOverloads constructor(
89
89
  internal data class TakePhotoRequest(
90
90
  val outputPath: String,
91
91
  val quality: Int,
92
+ // v0.13.2 — physical device orientation at capture time, from the
93
+ // JS `useDeviceOrientation()` hook (one of 'portrait' /
94
+ // 'portrait-upside-down' / 'landscape-left' / 'landscape-right').
95
+ // Used INSTEAD of the window display rotation so AR photos come
96
+ // out upright even under a PORTRAIT-LOCKED host (where the window
97
+ // rotation is always ROTATION_0 regardless of how the device is
98
+ // held — the cause of the "landscape AR photo is sideways" bug).
99
+ val orientation: String,
92
100
  val promise: com.facebook.react.bridge.Promise,
93
101
  )
94
102
  private val pendingTakePhoto =
@@ -101,9 +109,10 @@ class RNSARCameraView @JvmOverloads constructor(
101
109
  internal fun requestTakePhoto(
102
110
  outputPath: String,
103
111
  quality: Int,
112
+ orientation: String,
104
113
  promise: com.facebook.react.bridge.Promise,
105
114
  ) {
106
- val req = TakePhotoRequest(outputPath, quality, promise)
115
+ val req = TakePhotoRequest(outputPath, quality, orientation, promise)
107
116
  val previous = pendingTakePhoto.getAndSet(req)
108
117
  previous?.promise?.reject(
109
118
  "ar-photo-superseded",
@@ -339,10 +348,15 @@ class RNSARCameraView @JvmOverloads constructor(
339
348
  image,
340
349
  req.outputPath,
341
350
  jpegQuality = req.quality.coerceIn(1, 100),
342
- displayRotation = if (lastDisplayRotation >= 0)
343
- lastDisplayRotation
344
- else
345
- Surface.ROTATION_0,
351
+ // v0.13.2 derive the encode rotation from the PHYSICAL
352
+ // device orientation (JS gyro), not the window display
353
+ // rotation. Under a portrait-locked host the window stays
354
+ // ROTATION_0 regardless of how the device is held, so the
355
+ // old `lastDisplayRotation` path baked a portrait EXIF tag
356
+ // onto landscape captures → sideways photo. The
357
+ // device-orientation → Surface.ROTATION_* mapping below
358
+ // feeds encodeToJpeg's existing EXIF table.
359
+ displayRotation = deviceOrientationToSurfaceRotation(req.orientation),
346
360
  )
347
361
  if (written == null) {
348
362
  req.promise.reject(
@@ -632,6 +646,20 @@ class RNSARCameraView @JvmOverloads constructor(
632
646
  )
633
647
  }
634
648
 
649
+ /// v0.13.2 — map the JS physical device orientation to the
650
+ /// `Surface.ROTATION_*` value `YuvImageConverter.encodeToJpeg`
651
+ /// expects. Mirrors the equivalence documented in encodeToJpeg's
652
+ /// EXIF table (ROTATION_0=portrait, _90=landscape-left,
653
+ /// _180=portrait-upside-down, _270=landscape-right). Unknown /
654
+ /// missing → portrait (the safe pre-v0.12 default).
655
+ private fun deviceOrientationToSurfaceRotation(orientation: String): Int =
656
+ when (orientation) {
657
+ "landscape-left" -> Surface.ROTATION_90
658
+ "portrait-upside-down" -> Surface.ROTATION_180
659
+ "landscape-right" -> Surface.ROTATION_270
660
+ else -> Surface.ROTATION_0 // "portrait" + fallback
661
+ }
662
+
635
663
  private fun applyDisplayGeometry() {
636
664
  val session = sessionRef.get() ?: return
637
665
  val rotation = (context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)
@@ -3,6 +3,7 @@ package io.imagestitcher.rn
3
3
 
4
4
  import android.app.Activity
5
5
  import android.content.Context
6
+ import android.content.pm.ActivityInfo
6
7
  import android.util.Log
7
8
  import com.facebook.react.bridge.Arguments
8
9
  import com.facebook.react.bridge.Promise
@@ -58,6 +59,65 @@ class RNSARSession(reactContext: ReactApplicationContext)
58
59
  private val poseLog = mutableListOf<RNSARFramePose>()
59
60
  private val poseLogLock = ReentrantReadWriteLock()
60
61
 
62
+ // ── v0.13.1 — Android <Camera> portrait lock ────────────────────
63
+ //
64
+ // Unlike iOS (where supported orientations are a static Info.plist
65
+ // declaration the app can't override per-view at runtime), Android
66
+ // lets a view force its host Activity's orientation via
67
+ // `Activity.requestedOrientation`. The SDK's `<Camera>` uses this
68
+ // to guarantee a portrait capture surface regardless of the host
69
+ // app's manifest — even a fully landscape/unlocked host gets a
70
+ // portrait camera while `<Camera>` is mounted.
71
+ //
72
+ // `lockPortrait()` is called from `Camera.tsx`'s mount effect and
73
+ // covers BOTH capture paths (AR ARCore view + non-AR vision-camera)
74
+ // because the lock lives on the Activity, not on either camera view.
75
+ // `unlockOrientation()` (mount-effect cleanup) restores the EXACT
76
+ // orientation the Activity had before we locked, so hosts with
77
+ // mixed-orientation screens get their prior setting back rather than
78
+ // a generic default.
79
+ //
80
+ // SCREEN_ORIENTATION_UNSET (-2) is our "nothing captured yet"
81
+ // sentinel; we never pass it to setRequestedOrientation.
82
+ private var priorRequestedOrientation: Int = ORIENTATION_UNSET
83
+
84
+ @ReactMethod
85
+ fun lockPortrait() {
86
+ val activity: Activity = reactApplicationContext.currentActivity ?: run {
87
+ Log.w(TAG, "lockPortrait: no current activity — skipping")
88
+ return
89
+ }
90
+ activity.runOnUiThread {
91
+ // Capture the prior value ONCE (first lock wins). Guards
92
+ // against a remount double-capturing our own portrait value
93
+ // and losing the host's real prior orientation.
94
+ if (priorRequestedOrientation == ORIENTATION_UNSET) {
95
+ priorRequestedOrientation = activity.requestedOrientation
96
+ }
97
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
98
+ }
99
+ }
100
+
101
+ @ReactMethod
102
+ fun unlockOrientation() {
103
+ val activity: Activity = reactApplicationContext.currentActivity ?: run {
104
+ Log.w(TAG, "unlockOrientation: no current activity — skipping")
105
+ return
106
+ }
107
+ activity.runOnUiThread {
108
+ if (priorRequestedOrientation != ORIENTATION_UNSET) {
109
+ activity.requestedOrientation = priorRequestedOrientation
110
+ priorRequestedOrientation = ORIENTATION_UNSET
111
+ } else {
112
+ // No capture on record (lock never ran or already
113
+ // restored) — fall back to UNSPECIFIED so we don't pin
114
+ // the host to whatever we last set.
115
+ activity.requestedOrientation =
116
+ ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
117
+ }
118
+ }
119
+ }
120
+
61
121
  @ReactMethod
62
122
  fun isSupported(promise: Promise) {
63
123
  // `checkAvailability` can return UNKNOWN_CHECKING if the
@@ -395,6 +455,13 @@ class RNSARSession(reactContext: ReactApplicationContext)
395
455
  }
396
456
  val rawPath = if (options.hasKey("path")) options.getString("path") ?: "" else ""
397
457
  val quality = if (options.hasKey("quality")) options.getInt("quality") else 90
458
+ // v0.13.2 — physical device orientation from JS (useDeviceOrientation).
459
+ // Drives the saved JPEG's rotation so landscape AR captures are
460
+ // upright even under a portrait-locked host. Defaults to
461
+ // 'portrait' (pre-v0.12 behaviour) when the host omits it.
462
+ val orientation =
463
+ if (options.hasKey("orientation")) options.getString("orientation") ?: "portrait"
464
+ else "portrait"
398
465
  val resolvedPath: String = if (rawPath.isNotEmpty()) {
399
466
  rawPath
400
467
  } else {
@@ -404,7 +471,7 @@ class RNSARSession(reactContext: ReactApplicationContext)
404
471
  "RNImageStitcher-ar-${java.util.UUID.randomUUID()}.jpg",
405
472
  ).absolutePath
406
473
  }
407
- view.requestTakePhoto(resolvedPath, quality, promise)
474
+ view.requestTakePhoto(resolvedPath, quality, orientation, promise)
408
475
  }
409
476
 
410
477
  @ReactMethod
@@ -776,6 +843,11 @@ class RNSARSession(reactContext: ReactApplicationContext)
776
843
  private const val TAG = "RNSARSession"
777
844
  private const val MAX_POSE_LOG = 600 // ~10 s @ 60Hz
778
845
 
846
+ // Sentinel for "no prior Activity orientation captured yet".
847
+ // Distinct from any real ActivityInfo.SCREEN_ORIENTATION_*
848
+ // value (those are >= -1); -2 is unused by the framework.
849
+ private const val ORIENTATION_UNSET = -2
850
+
779
851
  /**
780
852
  * Convenience accessor for the AR camera view (in Phase 4.4)
781
853
  * to reach the singleton-installed module instance. We use
@@ -44,7 +44,19 @@ import type { DrawableFrameProcessor, ReadonlyFrameProcessor } from 'react-nativ
44
44
  import { type CaptureHeaderProps } from './CaptureHeader';
45
45
  import { type CapturePreviewAction } from './CapturePreview';
46
46
  import { type CaptureThumbnailItem } from './CaptureThumbnailStrip';
47
+ import { type DeviceOrientation } from './useDeviceOrientation';
47
48
  export type CaptureSource = 'ar' | 'non-ar';
49
+ /**
50
+ * v0.13.2 — which capture sources the host ALLOWS. A constraint on top
51
+ * of `defaultCaptureSource` (which picks the initial source within this
52
+ * constraint):
53
+ * 'both' — AR and non-AR both available; AR toggle is shown.
54
+ * 'ar' — AR only; AR toggle hidden (nothing to switch to), and the
55
+ * 0.5× lens chooser is hidden (ARKit/ARCore don't expose the
56
+ * ultra-wide).
57
+ * 'non-ar' — non-AR only; AR toggle hidden.
58
+ */
59
+ export type CaptureSourcesMode = 'ar' | 'non-ar' | 'both';
48
60
  export type CameraLens = '1x' | '0.5x';
49
61
  export type StitchMode = 'auto' | 'panorama' | 'scans';
50
62
  export type Blender = 'multiband' | 'feather';
@@ -142,6 +154,19 @@ export interface CameraProps {
142
154
  enablePhotoMode?: boolean;
143
155
  enablePanoramaMode?: boolean;
144
156
  showSettingsButton?: boolean;
157
+ /**
158
+ * v0.13.2 — which capture sources the host allows (default `'both'`).
159
+ * Constrains both the runtime AR toggle and `defaultCaptureSource`:
160
+ * - `'both'` : AR + non-AR; the AR toggle is shown so the user can
161
+ * switch at runtime.
162
+ * - `'ar'` : AR only. AR toggle hidden (nothing to toggle); the
163
+ * 0.5× lens chooser is also hidden (ARKit/ARCore can't use the
164
+ * ultra-wide), so the camera stays on the AR-capable 1× lens.
165
+ * - `'non-ar'`: non-AR only. AR toggle hidden.
166
+ * When set to a single source, that source wins regardless of
167
+ * `defaultCaptureSource`.
168
+ */
169
+ captureSources?: CaptureSourcesMode;
145
170
  style?: StyleProp<ViewStyle>;
146
171
  /**
147
172
  * Which incremental stitcher engine to drive. Default
@@ -256,22 +281,6 @@ export interface CameraProps {
256
281
  * `flash` prop) can opt out by setting this to `false`.
257
282
  */
258
283
  showFlashButton?: boolean;
259
- /**
260
- * v0.13.0 — show the built-in IncrementalPanGuide ("keep the
261
- * arrow on the line" drift marker) while recording. Defaults
262
- * to `true`. The guide is gyroscope-driven and only active
263
- * during the recording phase (no idle sensor cost). Hosts that
264
- * want their own pan-guide chrome can opt out via `false`.
265
- */
266
- panGuide?: boolean;
267
- /**
268
- * v0.13.0 — show the built-in PanoramaGuidance pan-speed pill
269
- * ("Pan slowly" / "Slow down" / "Too fast") while recording.
270
- * Defaults to `true`. Gyroscope-driven, only active during
271
- * recording. Hosts that want their own speed chrome can opt
272
- * out via `false`.
273
- */
274
- panoramaGuidance?: boolean;
275
284
  /**
276
285
  * v0.13.0 — built-in CaptureHeader title. When set, `<Camera>`
277
286
  * renders a top-of-screen header showing this title (centred)
@@ -476,4 +485,50 @@ export interface CameraProps {
476
485
  * The public `<Camera>` component.
477
486
  */
478
487
  export declare function Camera(props: CameraProps): React.JSX.Element;
488
+ /**
489
+ * v0.12.0 — JS edge corresponding to the physical home-indicator
490
+ * side of the device. This is where the shutter + controls anchor
491
+ * to so they're always within thumb reach of the user's grip
492
+ * (matching iOS Camera's behaviour).
493
+ *
494
+ * Combines two signals:
495
+ * - `jsLandscape`: whether the OS rotated the framebuffer. True
496
+ * only for non-locked hosts in device-landscape.
497
+ * - `deviceOrient`: physical device orientation from the sensor.
498
+ *
499
+ * Truth table:
500
+ * | jsLandscape | deviceOrient | edge |
501
+ * |--- |--- |--- |
502
+ * | false | any | bottom | (portrait JS coords —
503
+ * | | | | device-bottom = JS-bottom
504
+ * | | | | in both locked and
505
+ * | | | | non-locked-portrait)
506
+ * | true | landscape-left | right | (screen rotated, home
507
+ * | | | | indicator on user-right)
508
+ * | true | landscape-right | left | (mirror)
509
+ *
510
+ * Caveats:
511
+ * - Non-locked + upside-down doesn't surface JS-top here because
512
+ * upside-down doesn't change window dimensions; we can't
513
+ * distinguish locked-portrait-with-device-flipped from
514
+ * non-locked-portrait-with-screen-flipped-180°. Defaults to
515
+ * JS-bottom which matches the more common locked case. Add
516
+ * handling here when a host needs upside-down support.
517
+ * - jsLandscape=true with non-landscape device shouldn't happen
518
+ * in steady state — only during a transition mid-rotation.
519
+ * Falls through to 'right' as a defensive default.
520
+ */
521
+ type HomeIndicatorEdge = 'bottom' | 'top' | 'left' | 'right';
522
+ declare function homeIndicatorEdge(jsLandscape: boolean, deviceOrient: DeviceOrientation): HomeIndicatorEdge;
523
+ /**
524
+ * v0.12.0 — true when the anchor edge is on a side (left/right), so
525
+ * the band + shutter row need to be vertical strips. Top/bottom
526
+ * anchors yield horizontal strips.
527
+ */
528
+ declare function isSideEdge(edge: HomeIndicatorEdge): boolean;
529
+ /** @internal test-only — see `homeIndicatorEdge`. */
530
+ export declare const _homeIndicatorEdgeForTests: typeof homeIndicatorEdge;
531
+ /** @internal test-only — see `isSideEdge`. */
532
+ export declare const _isSideEdgeForTests: typeof isSideEdge;
533
+ export {};
479
534
  //# sourceMappingURL=Camera.d.ts.map