@octane-xplat/camera 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # `@octane-xplat/camera`
2
+
3
+ ```sh
4
+ pnpm add @octane-xplat/camera
5
+ ```
6
+
7
+ Live camera preview for Octane xplat apps: `getUserMedia` video on web,
8
+ `AVCaptureSession` on iOS, CameraX on Android.
9
+
10
+ ```tsx
11
+ import { CameraView } from '@octane-xplat/camera'
12
+
13
+ export function Preview() {
14
+ return (
15
+ <CameraView
16
+ facing="back"
17
+ active
18
+ onReady={() => console.log('Preview running')}
19
+ onError={(error) => console.log(error.message)}
20
+ />
21
+ )
22
+ }
23
+ ```
24
+
25
+ This is the preview surface only — for one-shot still capture and library
26
+ picking use [`@octane-xplat/media`](../media/README.md). Permissions are
27
+ requested when the session starts; `onError` is how a denial shows up.
28
+ `ref` hands you the platform surface (`HTMLVideoElement` on web) for
29
+ controls the shared props don't cover, and the `ios`/`android`/`web`
30
+ escape props apply after the shared props.
31
+
32
+ ```tsx
33
+ // Preview.web.tsx — browser properties belong in this platform file.
34
+ import { CameraView } from '@octane-xplat/camera'
35
+
36
+ export function Preview() {
37
+ return (
38
+ <CameraView
39
+ web={{ playsInline: true }}
40
+ ref={(handle) => console.log(handle.native)}
41
+ onError={(error) => console.log(error.message)}
42
+ />
43
+ )
44
+ }
45
+ ```
46
+
47
+ Guide: [Building screens](../../docs/app/primitives.md) (leaf components);
48
+ component index: [`docs/app/components.md`](../../docs/app/components.md).
49
+ Exercised by [`CameraDemo`](../demos/src/CameraDemo.tsrx).
@@ -1,14 +1,15 @@
1
1
  import { cameraHardwarePresent, ensureCameraPermission } from "./camera-permission.mobile.js";
2
2
  import { applyEscapeProps, layoutChildProps, nativeAccessibilityRole, nativeAccessibilityState } from "./layout-child.js";
3
3
  import { cx } from "./cx.mobile.js";
4
- import { defineUniversalComponent, hookSlots, registerElement, universalPlan, universalProps, universalValue, useCallback, useEffect, useRef } from "@nativescript-community/octane";
4
+ import { defineUniversalComponent, hookSlots, registerElement, universalPlan, universalProps, universalValue, useCallback, useEffect, useImperativeHandle, useRef } from "@nativescript-community/octane";
5
5
  import { Application, View } from "@nativescript/core";
6
6
  //#region src/CameraView.android.tsrx
7
- var _hs$ = /* @__PURE__ */ hookSlots(4);
7
+ var _hs$ = /* @__PURE__ */ hookSlots(5);
8
8
  var _h$0 = /* @__PURE__ */ Symbol(_hs$);
9
9
  var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
10
10
  var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
11
11
  var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
12
+ var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
12
13
  var __octaneUniversalPlan0 = universalPlan("nativescript", {
13
14
  "kind": "host",
14
15
  "type": "gridlayout",
@@ -34,9 +35,10 @@ var CameraView = defineUniversalComponent("nativescript", function CameraView(pr
34
35
  const host = useRef(null, _h$0);
35
36
  const active = props.active !== false;
36
37
  const facing = props.facing ?? "back";
38
+ useImperativeHandle(props.ref, () => ({ native: host.current.android }), [], _h$1);
37
39
  const bindHost = useCallback((view) => {
38
40
  host.current = view;
39
- }, [], _h$1);
41
+ }, [], _h$2);
40
42
  useEffect(() => {
41
43
  const nativeView = host.current?.android;
42
44
  if (!nativeView) return;
@@ -87,13 +89,12 @@ var CameraView = defineUniversalComponent("nativescript", function CameraView(pr
87
89
  provider.unbind(preview);
88
90
  } catch {}
89
91
  };
90
- }, [active, facing], _h$2);
92
+ }, [active, facing], _h$3);
91
93
  useEffect(() => {
92
94
  const view = host.current;
93
95
  if (!view) return;
94
96
  applyEscapeProps(view.android, props);
95
- props.bind?.({ native: view.android });
96
- }, [props.bind, props.android], _h$3);
97
+ }, [props.ref, props.android], _h$4);
97
98
  return universalValue(__octaneUniversalPlan0, [universalProps([
98
99
  [
99
100
  "set",
@@ -113,7 +114,7 @@ var CameraView = defineUniversalComponent("nativescript", function CameraView(pr
113
114
  [
114
115
  "set",
115
116
  "className",
116
- cx("vx-stack", "vx-cameraview", props.className)
117
+ cx("vx-layers", "vx-cameraview", props.className)
117
118
  ],
118
119
  [
119
120
  "set",
@@ -2,13 +2,14 @@ import { cameraHardwarePresent, ensureCameraPermission } from "./camera-permissi
2
2
  import { startCameraPreview } from "./camera-session.ios.js";
3
3
  import { applyEscapeProps, layoutChildProps, nativeAccessibilityRole, nativeAccessibilityState } from "./layout-child.js";
4
4
  import { cx } from "./cx.mobile.js";
5
- import { defineUniversalComponent, hookSlots, universalPlan, universalProps, universalValue, useCallback, useEffect, useRef } from "@nativescript-community/octane";
5
+ import { defineUniversalComponent, hookSlots, universalPlan, universalProps, universalValue, useCallback, useEffect, useImperativeHandle, useRef } from "@nativescript-community/octane";
6
6
  //#region src/CameraView.ios.tsrx
7
- var _hs$ = /* @__PURE__ */ hookSlots(4);
7
+ var _hs$ = /* @__PURE__ */ hookSlots(5);
8
8
  var _h$0 = /* @__PURE__ */ Symbol(_hs$);
9
9
  var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
10
10
  var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
11
11
  var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
12
+ var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
12
13
  var __octaneUniversalPlan0 = universalPlan("nativescript", {
13
14
  "kind": "host",
14
15
  "type": "gridlayout",
@@ -18,9 +19,10 @@ var CameraView = defineUniversalComponent("nativescript", function CameraView(pr
18
19
  const host = useRef(null, _h$0);
19
20
  const active = props.active !== false;
20
21
  const facing = props.facing ?? "back";
22
+ useImperativeHandle(props.ref, () => ({ native: host.current.ios ?? host.current.nativeViewProtected }), [], _h$1);
21
23
  const bindHost = useCallback((view) => {
22
24
  host.current = view;
23
- }, [], _h$1);
25
+ }, [], _h$2);
24
26
  useEffect(() => {
25
27
  const view = host.current;
26
28
  if (!view || !active) return;
@@ -47,13 +49,12 @@ var CameraView = defineUniversalComponent("nativescript", function CameraView(pr
47
49
  disposed = true;
48
50
  stopPreview?.();
49
51
  };
50
- }, [active, facing], _h$2);
52
+ }, [active, facing], _h$3);
51
53
  useEffect(() => {
52
54
  const view = host.current;
53
55
  if (!view) return;
54
56
  applyEscapeProps(view.ios ?? view.nativeViewProtected, props);
55
- props.bind?.({ native: view.ios ?? view.nativeViewProtected });
56
- }, [props.bind, props.ios], _h$3);
57
+ }, [props.ref, props.ios], _h$4);
57
58
  return universalValue(__octaneUniversalPlan0, [universalProps([
58
59
  [
59
60
  "set",
@@ -78,7 +79,7 @@ var CameraView = defineUniversalComponent("nativescript", function CameraView(pr
78
79
  [
79
80
  "set",
80
81
  "className",
81
- cx("vx-stack", "vx-cameraview", props.className)
82
+ cx("vx-layers", "vx-cameraview", props.className)
82
83
  ],
83
84
  [
84
85
  "set",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "buildId": "c93085be-bb7c-4211-99d9-a71af74da15d",
3
+ "buildId": "fea07a95-22b3-4c09-94ed-4d8a93f4e236",
4
4
  "mode": "production",
5
5
  "capabilities": {
6
6
  "independentHydration": false
@@ -1,12 +1,12 @@
1
1
  import { applyEscapeProps, layoutChildProps } from "./layout-child.web.js";
2
- import { __s, bag15, bag5, child, clone, delegateEvents, evt2e, evt2u, hookSlots, ifBlock, markDangerouslySetInnerHTMLChildren, queueRefAttach, queueRefDetach, setStyleProperty, snapshotSpread, template, useEffect, useRef } from "octane";
2
+ import { __s, bag15, bag5, child, clone, delegateEvents, evt2e, evt2u, hookSlots, ifBlock, markDangerouslySetInnerHTMLChildren, queueRefAttach, queueRefDetach, setStyleProperty, snapshotSpread, template, useEffect, useImperativeHandle, useRef } from "octane";
3
3
  import { bindSignalHostPropSources, enableSignalBindings, replaceRef, setAttributeIfChanged } from "octane/internal/client";
4
4
  //#region src/CameraView.web.tsrx
5
5
  enableSignalBindings(1, true);
6
6
  delegateEvents(["playing"]);
7
7
  var _t$0 = /* @__PURE__ */ template("<video autoplay=\"\" playsinline=\"\" style=\"display: block; width: 100%; height: 100%; object-fit: cover;\"></video>");
8
8
  var _t$1 = /* @__PURE__ */ template("<div><!></div>");
9
- var _hs$ = /* @__PURE__ */ hookSlots(3);
9
+ var _hs$ = /* @__PURE__ */ hookSlots(5);
10
10
  var __event = (__eventArg, readyFired, props) => {
11
11
  if (readyFired.current) return;
12
12
  readyFired.current = true;
@@ -19,18 +19,19 @@ var _rm$1 = [
19
19
  ];
20
20
  function __then$0(__props, __s, __extra) {
21
21
  const facing = __extra[0];
22
- const props = __extra[1];
23
- const readyFired = __extra[2];
24
- const video = __extra[3];
22
+ const hostRef = __extra[1];
23
+ const props = __extra[2];
24
+ const readyFired = __extra[3];
25
+ const video = __extra[4];
25
26
  let _b = __s.slots[0];
26
27
  if (_b === void 0) {
27
28
  let _m0, _m1, _m4;
28
29
  const _root = clone(_t$0, void 0, "||");
29
30
  queueRefAttach(__s, _m1 = (element) => {
31
+ hostRef.current = element;
30
32
  video.current = element;
31
33
  if (!element) return;
32
34
  applyEscapeProps(element, props);
33
- props.bind?.({ native: element });
34
35
  }, _m0 = _root);
35
36
  (__s.cleanups ??= []).push(() => _b && queueRefDetach(_b.b, _b.a));
36
37
  _m4 = evt2e(_root, "$$playing", __event, readyFired, props);
@@ -39,10 +40,10 @@ function __then$0(__props, __s, __extra) {
39
40
  } else {
40
41
  {
41
42
  const _r = (element) => {
43
+ hostRef.current = element;
42
44
  video.current = element;
43
45
  if (!element) return;
44
46
  applyEscapeProps(element, props);
45
- props.bind?.({ native: element });
46
47
  };
47
48
  if (_r !== _b.b) _b.b = replaceRef(__s, _b.b, _r, _b.a);
48
49
  }
@@ -63,10 +64,11 @@ var _rm$3 = [
63
64
  "m"
64
65
  ];
65
66
  var CameraView = /* @__PURE__ */ __s(function CameraView(props, __s, __extra) {
66
- const video = useRef(null, _hs$ + 0);
67
+ const hostRef = useRef(null, _hs$ + 0);
68
+ const video = useRef(null, _hs$ + 1);
67
69
  const facing = props.facing ?? "back";
68
70
  const active = props.active !== false;
69
- const readyFired = useRef(false, _hs$ + 1);
71
+ const readyFired = useRef(false, _hs$ + 2);
70
72
  useEffect(() => {
71
73
  const element = video.current;
72
74
  if (!element || !active) return;
@@ -97,14 +99,15 @@ var CameraView = /* @__PURE__ */ __s(function CameraView(props, __s, __extra) {
97
99
  element.srcObject = null;
98
100
  if (stream) for (const track of stream.getTracks()) track.stop();
99
101
  };
100
- }, [active, facing], _hs$ + 2);
102
+ }, [active, facing], _hs$ + 3);
103
+ useImperativeHandle(props.ref, () => ({ native: hostRef.current }), void 0, _hs$ + 4);
101
104
  let _b = __s.slots[0];
102
105
  if (_b === void 0) {
103
106
  let _m0, _m1, _m2, _m3, _m4, _m5, _m6, _m7, _m8, _m9, _m10, _m11, _m12, _m13, _m14;
104
107
  const _root = clone(_t$1);
105
108
  _m0 = props.id;
106
109
  _m1 = [
107
- "vx-stack",
110
+ "vx-layers",
108
111
  "vx-cameraview",
109
112
  props.className
110
113
  ];
@@ -181,7 +184,7 @@ var CameraView = /* @__PURE__ */ __s(function CameraView(props, __s, __extra) {
181
184
  "aria-live",
182
185
  _m11
183
186
  ]
184
- ], "b:83c7570e", true);
187
+ ], "b:25f1aaa9", true);
185
188
  markDangerouslySetInnerHTMLChildren(_root);
186
189
  _m14 = child(_root);
187
190
  _b = bag15(__s, _root, _m0, _m1, _m2, _m3, _m4, _m5, _m6, _m7, _m8, _m9, _m10, _m11, _m12, _m13, _m14);
@@ -193,7 +196,7 @@ var CameraView = /* @__PURE__ */ __s(function CameraView(props, __s, __extra) {
193
196
  }
194
197
  {
195
198
  const _v = [
196
- "vx-stack",
199
+ "vx-layers",
197
200
  "vx-cameraview",
198
201
  props.className
199
202
  ];
@@ -298,12 +301,13 @@ var CameraView = /* @__PURE__ */ __s(function CameraView(props, __s, __extra) {
298
301
  "aria-live",
299
302
  _b.l
300
303
  ]
301
- ], "b:83c7570e", true);
304
+ ], "b:25f1aaa9", true);
302
305
  }
303
306
  {
304
307
  const __ifCondition = active;
305
308
  ifBlock(__s, 1, _b.m, __ifCondition, __then$0, null, _b.o, __ifCondition ? [
306
309
  facing,
310
+ hostRef,
307
311
  props,
308
312
  readyFired,
309
313
  video
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "buildId": "ed138ddb-08ea-4ba2-9a34-4fc0cb7c0dd8",
3
+ "buildId": "cf0744c7-ee47-4aa9-9731-473938c42593",
4
4
  "mode": "production",
5
5
  "capabilities": {
6
6
  "independentHydration": false
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "@octane-xplat/camera",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Live camera preview for Octane xplat — web getUserMedia + native capture (AVCapture / CameraX)",
5
5
  "keywords": [
6
- "octane",
7
- "xplat",
8
- "cross-platform",
6
+ "android",
9
7
  "camera",
10
8
  "camera-preview",
9
+ "cross-platform",
10
+ "ios",
11
+ "nativescript",
12
+ "octane",
11
13
  "photo",
12
14
  "video-capture",
13
- "nativescript",
14
- "ios",
15
- "android",
16
- "web"
15
+ "web",
16
+ "xplat"
17
17
  ],
18
- "license": "MIT",
19
18
  "homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/camera",
19
+ "license": "MIT",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/octane-xplat/octane-xplat.git",
22
+ "url": "https://github.com/octane-xplat/octane-xplat",
23
23
  "directory": "packages/camera"
24
24
  },
25
25
  "files": [
@@ -29,12 +29,6 @@
29
29
  "platforms"
30
30
  ],
31
31
  "type": "module",
32
- "nativescript": {
33
- "platforms": {
34
- "ios": "7.0.0",
35
- "android": "7.0.0"
36
- }
37
- },
38
32
  "exports": {
39
33
  ".": {
40
34
  "types": "./types/index.d.ts",
@@ -51,7 +45,7 @@
51
45
  "@nativescript/core": "9.1.2",
52
46
  "@octanejs/vite-plugin": "0.1.61",
53
47
  "octane": "0.6.3",
54
- "tsrx-typegen": "0.2.0",
48
+ "tsrx-typegen": "0.2.2",
55
49
  "typescript": "5.9.3",
56
50
  "vite": "8.3.0"
57
51
  },
@@ -71,6 +65,12 @@
71
65
  "optional": true
72
66
  }
73
67
  },
68
+ "nativescript": {
69
+ "platforms": {
70
+ "ios": "7.0.0",
71
+ "android": "7.0.0"
72
+ }
73
+ },
74
74
  "scripts": {
75
75
  "build": "vite build && vite build --mode native && pnpm typegen && node ../../scripts/check-native-dist.mjs",
76
76
  "typegen": "tsc -p tsconfig.types.json",
@@ -1,5 +1,5 @@
1
1
  /** @jsxImportSource @nativescript-community/octane */
2
- import { useCallback, useEffect, useRef } from 'octane'
2
+ import { useImperativeHandle, useCallback, useEffect, useRef } from 'octane'
3
3
  import { Application, View } from '@nativescript/core'
4
4
  import { registerElement } from '@nativescript-community/octane'
5
5
  import type { Attributes } from '@nativescript-community/octane/intrinsics'
@@ -28,6 +28,8 @@ export function CameraView(props: CameraViewProps) @{
28
28
  const host = useRef<any>(null)
29
29
  const active = props.active !== false
30
30
  const facing = props.facing ?? 'back'
31
+ useImperativeHandle(props.ref, () => ({ native: host.current.android }), []);
32
+
31
33
  const bindHost = useCallback((view: any) => {
32
34
  host.current = view
33
35
  }, [])
@@ -102,14 +104,13 @@ export function CameraView(props: CameraViewProps) @{
102
104
  const view = host.current
103
105
  if (!view) return
104
106
  applyEscapeProps(view.android, props)
105
- props.bind?.({ native: view.android })
106
- }, [props.bind, props.android])
107
+ }, [props.ref, props.android])
107
108
 
108
109
  <gridlayout
109
110
  id={props.id}
110
111
  rows="*"
111
112
  columns="*"
112
- className={cx('vx-stack', 'vx-cameraview', props.className)}
113
+ className={cx('vx-layers', 'vx-cameraview', props.className)}
113
114
  style={{ backgroundColor: '#000', ...props.style }}
114
115
  {...layoutChildProps(props)}
115
116
  accessible={props.accessible}
@@ -1,5 +1,5 @@
1
1
  /** @jsxImportSource @nativescript-community/octane */
2
- import { useCallback, useEffect, useRef } from 'octane'
2
+ import { useImperativeHandle, useCallback, useEffect, useRef } from 'octane'
3
3
  import type { CameraViewProps } from './props'
4
4
  import { cameraHardwarePresent, ensureCameraPermission } from './camera-permission.mobile'
5
5
  import { startCameraPreview } from './camera-session.ios'
@@ -11,6 +11,8 @@ export function CameraView(props: CameraViewProps) @{
11
11
  const host = useRef<any>(null)
12
12
  const active = props.active !== false
13
13
  const facing = props.facing ?? 'back'
14
+ useImperativeHandle(props.ref, () => ({ native: host.current.ios ?? host.current.nativeViewProtected }), []);
15
+
14
16
  const bindHost = useCallback((view: any) => {
15
17
  host.current = view
16
18
  }, [])
@@ -51,15 +53,14 @@ export function CameraView(props: CameraViewProps) @{
51
53
  const view = host.current
52
54
  if (!view) return
53
55
  applyEscapeProps(view.ios ?? view.nativeViewProtected, props)
54
- props.bind?.({ native: view.ios ?? view.nativeViewProtected })
55
- }, [props.bind, props.ios])
56
+ }, [props.ref, props.ios])
56
57
 
57
58
  <gridlayout
58
59
  ref={bindHost}
59
60
  id={props.id}
60
61
  rows="*"
61
62
  columns="*"
62
- className={cx('vx-stack', 'vx-cameraview', props.className)}
63
+ className={cx('vx-layers', 'vx-cameraview', props.className)}
63
64
  style={{ backgroundColor: '#000', ...props.style }}
64
65
  {...layoutChildProps(props)}
65
66
  accessible={props.accessible}
@@ -1,9 +1,10 @@
1
- import { useEffect, useRef } from 'octane'
1
+ import { useImperativeHandle, useEffect, useRef } from 'octane'
2
2
  import type { CameraViewProps } from './props'
3
3
  import { applyEscapeProps, layoutChildProps } from './layout-child.web'
4
4
 
5
5
  /** Live web preview backed by getUserMedia. */
6
6
  export function CameraView(props: CameraViewProps) @{
7
+ const hostRef = useRef<any>(null);
7
8
  const video = useRef<HTMLVideoElement | null>(null)
8
9
  const facing = props.facing ?? 'back'
9
10
  const active = props.active !== false
@@ -52,9 +53,11 @@ export function CameraView(props: CameraViewProps) @{
52
53
  }
53
54
  }, [active, facing])
54
55
 
56
+ useImperativeHandle(props.ref, () => ({ native: hostRef.current }), undefined);
57
+
55
58
  <div
56
59
  id={props.id}
57
- className={['vx-stack', 'vx-cameraview', props.className]}
60
+ className={['vx-layers', 'vx-cameraview', props.className]}
58
61
  {...layoutChildProps(props, {
59
62
  position: 'relative',
60
63
  overflow: 'hidden',
@@ -74,10 +77,10 @@ export function CameraView(props: CameraViewProps) @{
74
77
  @if (active) {
75
78
  <video
76
79
  ref={(element: HTMLVideoElement | null) => {
80
+ hostRef.current = element;
77
81
  video.current = element
78
82
  if (!element) return
79
83
  applyEscapeProps(element, props)
80
- props.bind?.({ native: element })
81
84
  }}
82
85
  muted={true}
83
86
  autoplay={true}
@@ -0,0 +1,5 @@
1
+ import type { UniversalComponent } from 'octane/universal'
2
+ import type { CameraViewProps } from './props'
3
+
4
+ /** TypeScript fallback — no Windows CameraView implementation exists yet. */
5
+ export declare const CameraView: UniversalComponent<CameraViewProps>
@@ -3,11 +3,17 @@ import { Application, isAndroid, isIOS } from '@nativescript/core'
3
3
  let requestCode = 7719
4
4
 
5
5
  export function cameraHardwarePresent(): boolean {
6
- if (isIOS) {return typeof (globalThis as any).AVCaptureDevice !== 'undefined'}
7
- if (!isAndroid) {return false}
6
+ if (isIOS) {
7
+ return typeof (globalThis as any).AVCaptureDevice !== 'undefined'
8
+ }
9
+
10
+ if (!isAndroid) {
11
+ return false
12
+ }
13
+
8
14
  try {
9
15
  const context = Application.android?.context ?? Application.android?.startActivity
10
- return context?.getPackageManager()?.hasSystemFeature('android.hardware.camera.any') !== false
16
+ return context?.getPackageManager()?.hasSystemFeature('android.hardware.camera.any') !== false
11
17
  } catch {
12
18
  return true
13
19
  }
@@ -19,8 +25,14 @@ export async function ensureCameraPermission(): Promise<boolean> {
19
25
  const av = (globalThis as any).AVCaptureDevice
20
26
  const mediaType = (globalThis as any).AVMediaTypeVideo ?? 'vide'
21
27
  const status = av?.authorizationStatusForMediaType?.(mediaType)
22
- if (status === 3) {return true}
23
- if (status === 1 || status === 2) {return false}
28
+ if (status === 3) {
29
+ return true
30
+ }
31
+
32
+ if (status === 1 || status === 2) {
33
+ return false
34
+ }
35
+
24
36
  return await new Promise<boolean>((resolve) =>
25
37
  av.requestAccessForMediaTypeCompletionHandler(mediaType, resolve),
26
38
  )
@@ -32,28 +44,27 @@ export async function ensureCameraPermission(): Promise<boolean> {
32
44
  if (
33
45
  activity?.checkSelfPermission?.(permission) ===
34
46
  android.content.pm.PackageManager.PERMISSION_GRANTED
35
- ) {return true}
47
+ ) {
48
+ return true
49
+ }
50
+
51
+ if (!activity) {
52
+ return false
53
+ }
36
54
 
37
- if (!activity) {return false}
38
55
  return await new Promise<boolean>((resolve) => {
39
56
  const currentRequest = requestCode++
40
57
  const onResult = (args: any) => {
41
- if (args.requestCode !== currentRequest) {return}
42
- Application.android.off(
43
- Application.android.activityRequestPermissionsEvent,
44
- onResult,
45
- )
58
+ if (args.requestCode !== currentRequest) {
59
+ return
60
+ }
61
+
62
+ Application.android.off(Application.android.activityRequestPermissionsEvent, onResult)
46
63
 
47
- resolve(
48
- args.grantResults?.[0] ===
49
- android.content.pm.PackageManager.PERMISSION_GRANTED,
50
- )
64
+ resolve(args.grantResults?.[0] === android.content.pm.PackageManager.PERMISSION_GRANTED)
51
65
  }
52
66
 
53
- Application.android.on(
54
- Application.android.activityRequestPermissionsEvent,
55
- onResult,
56
- )
67
+ Application.android.on(Application.android.activityRequestPermissionsEvent, onResult)
57
68
 
58
69
  activity.requestPermissions([permission], currentRequest)
59
70
  })
@@ -6,9 +6,10 @@ export function startCameraPreview(
6
6
  ): () => void {
7
7
  const av = globalThis as any
8
8
  const mediaType = av.AVMediaTypeVideo ?? 'vide'
9
- const desiredPosition = facing === 'front'
10
- ? av.AVCaptureDevicePositionFront ?? 2
11
- : av.AVCaptureDevicePositionBack ?? 1
9
+ const desiredPosition =
10
+ facing === 'front'
11
+ ? (av.AVCaptureDevicePositionFront ?? 2)
12
+ : (av.AVCaptureDevicePositionBack ?? 1)
12
13
 
13
14
  const devices = av.AVCaptureDevice.devicesWithMediaType(mediaType)
14
15
  let device: any = null
@@ -20,22 +21,32 @@ export function startCameraPreview(
20
21
  }
21
22
  }
22
23
 
23
- if (!device) {throw new Error('camera unavailable')}
24
+ if (!device) {
25
+ throw new Error('camera unavailable')
26
+ }
24
27
 
25
28
  const session = av.AVCaptureSession.new()
26
29
  const input = av.AVCaptureDeviceInput.deviceInputWithDeviceError(device)
27
- if (!input || !session.canAddInput(input)) {throw new Error('camera input unavailable')}
30
+ if (!input || !session.canAddInput(input)) {
31
+ throw new Error('camera input unavailable')
32
+ }
33
+
28
34
  session.addInput(input)
29
35
  const layer = av.AVCaptureVideoPreviewLayer.layerWithSession(session)
30
36
  layer.videoGravity = 'resizeAspectFill'
31
37
  const connection = layer.connection
32
38
  if (connection) {
33
39
  connection.automaticallyAdjustsVideoMirroring = false
34
- if (connection.isVideoMirroringSupported) {connection.isVideoMirrored = facing === 'front'}
40
+ if (connection.isVideoMirroringSupported) {
41
+ connection.isVideoMirrored = facing === 'front'
42
+ }
35
43
  }
36
44
 
37
45
  const nativeView = view.ios ?? view.nativeViewProtected
38
- if (!nativeView?.layer) {throw new Error('camera preview host unavailable')}
46
+ if (!nativeView?.layer) {
47
+ throw new Error('camera preview host unavailable')
48
+ }
49
+
39
50
  nativeView.layer.addSublayer(layer)
40
51
  const updateFrame = () => {
41
52
  layer.frame = nativeView.bounds
@@ -17,16 +17,23 @@ export function layoutChildProps(props: CameraViewProps): Record<string, any> {
17
17
  'alignSelf',
18
18
  'order',
19
19
  ] as const) {
20
- if (props[key] !== undefined) {result[key] = props[key]}
20
+ if (props[key] !== undefined) {
21
+ result[key] = props[key]
22
+ }
21
23
  }
22
24
 
23
25
  return result
24
26
  }
25
27
 
26
28
  export function applyEscapeProps(view: any, props: { ios?: any; android?: any }): void {
27
- if (!view) {return}
29
+ if (!view) {
30
+ return
31
+ }
32
+
28
33
  const bag = isIOS ? props.ios : props.android
29
- if (bag) {Object.assign(view, bag)}
34
+ if (bag) {
35
+ Object.assign(view, bag)
36
+ }
30
37
  }
31
38
 
32
39
  const nativeRoles: Record<string, string> = {
@@ -51,12 +58,24 @@ export function nativeAccessibilityRole(role?: string): string | undefined {
51
58
  return role ? nativeRoles[role] : undefined
52
59
  }
53
60
 
54
- export function nativeAccessibilityState(state?: CameraViewProps['accessibilityState']):
55
- | string
56
- | undefined {
57
- if (state?.disabled) {return 'disabled'}
58
- if (state?.selected) {return 'selected'}
59
- if (state?.checked === true) {return 'checked'}
60
- if (state?.checked === false) {return 'unchecked'}
61
+ export function nativeAccessibilityState(
62
+ state?: CameraViewProps['accessibilityState'],
63
+ ): string | undefined {
64
+ if (state?.disabled) {
65
+ return 'disabled'
66
+ }
67
+
68
+ if (state?.selected) {
69
+ return 'selected'
70
+ }
71
+
72
+ if (state?.checked === true) {
73
+ return 'checked'
74
+ }
75
+
76
+ if (state?.checked === false) {
77
+ return 'unchecked'
78
+ }
79
+
61
80
  return undefined
62
81
  }
@@ -14,21 +14,45 @@ export function layoutChildProps(props: CameraViewProps, baseStyle: any = props.
14
14
  style.gridColumn = `span ${props.colSpan}`
15
15
  }
16
16
 
17
- if (props.left !== undefined) {style.left = props.left}
18
- if (props.top !== undefined) {style.top = props.top}
19
- if (props.flexGrow !== undefined) {style.flexGrow = props.flexGrow}
20
- if (props.flexShrink !== undefined) {style.flexShrink = props.flexShrink}
21
- if (props.alignSelf !== undefined) {style.alignSelf = props.alignSelf}
22
- if (props.order !== undefined) {style.order = props.order}
17
+ if (props.left !== undefined) {
18
+ style.left = props.left
19
+ }
20
+
21
+ if (props.top !== undefined) {
22
+ style.top = props.top
23
+ }
24
+
25
+ if (props.flexGrow !== undefined) {
26
+ style.flexGrow = props.flexGrow
27
+ }
28
+
29
+ if (props.flexShrink !== undefined) {
30
+ style.flexShrink = props.flexShrink
31
+ }
32
+
33
+ if (props.alignSelf !== undefined) {
34
+ style.alignSelf = props.alignSelf
35
+ }
36
+
37
+ if (props.order !== undefined) {
38
+ style.order = props.order
39
+ }
40
+
23
41
  return { style }
24
42
  }
25
43
 
26
44
  export function applyEscapeProps(element: any, props: { web?: any }): void {
27
- if (!element || !props.web) {return}
45
+ if (!element || !props.web) {
46
+ return
47
+ }
48
+
28
49
  for (const [key, value] of Object.entries(props.web)) {
29
50
  if (key.includes('-')) {
30
- if (value == null || value === false) {element.removeAttribute(key)}
31
- else {element.setAttribute(key, value === true ? '' : String(value))}
51
+ if (value == null || value === false) {
52
+ element.removeAttribute(key)
53
+ } else {
54
+ element.setAttribute(key, value === true ? '' : String(value))
55
+ }
32
56
  } else if (key in element) {
33
57
  try {
34
58
  element[key] = value
package/src/props.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { Octane } from 'octane/jsx-runtime'
2
+
1
3
  export type CameraAccessibilityRole =
2
4
  | 'button'
3
5
  | 'link'
@@ -54,7 +56,7 @@ export interface CameraViewProps {
54
56
  onReady?: () => void
55
57
  onError?: (error: { message?: string }) => void
56
58
  /** Native preview surface for platform-specific controls. */
57
- bind?: (handle: CameraViewHandle) => void
59
+ ref?: Octane.Ref<CameraViewHandle>
58
60
  /** Platform-specific properties applied to the preview surface. */
59
61
  ios?: Record<string, any>
60
62
  android?: Record<string, any>
package/types/props.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Octane } from 'octane/jsx-runtime';
1
2
  export type CameraAccessibilityRole = 'button' | 'link' | 'search' | 'image' | 'heading' | 'adjustable' | 'summary' | 'text' | 'none' | 'progressbar' | 'checkbox' | 'switch' | 'radio' | 'spinbutton' | 'tab';
2
3
  export interface CameraViewHandle {
3
4
  /** The native preview surface (`HTMLVideoElement` on web). */
@@ -39,7 +40,7 @@ export interface CameraViewProps {
39
40
  message?: string;
40
41
  }) => void;
41
42
  /** Native preview surface for platform-specific controls. */
42
- bind?: (handle: CameraViewHandle) => void;
43
+ ref?: Octane.Ref<CameraViewHandle>;
43
44
  /** Platform-specific properties applied to the preview surface. */
44
45
  ios?: Record<string, any>;
45
46
  android?: Record<string, any>;