@octane-xplat/camera 0.9.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 CHANGED
@@ -1,31 +1,49 @@
1
1
  # `@octane-xplat/camera`
2
2
 
3
- Live camera preview for Octane xplat apps: `getUserMedia` video on web,
4
- `AVCaptureSession` on iOS, CameraX on Android.
5
-
6
3
  ```sh
7
4
  pnpm add @octane-xplat/camera
8
5
  ```
9
6
 
7
+ Live camera preview for Octane xplat apps: `getUserMedia` video on web,
8
+ `AVCaptureSession` on iOS, CameraX on Android.
9
+
10
10
  ```tsx
11
11
  import { CameraView } from '@octane-xplat/camera'
12
12
 
13
- ;<CameraView
14
- facing="back" // 'front' | 'back', default 'back'
15
- active={streaming} // default true — false releases the session
16
- onReady={() => console.log('preview running')}
17
- onError={(e) => console.log(e.message)}
18
- bind={(h) => (handle = h)} // h.native is the preview surface
19
- />
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
+ }
20
23
  ```
21
24
 
22
25
  This is the preview surface only — for one-shot still capture and library
23
26
  picking use [`@octane-xplat/media`](../media/README.md). Permissions are
24
27
  requested when the session starts; `onError` is how a denial shows up.
25
- `bind` hands you the platform surface (`HTMLVideoElement` on web) for
28
+ `ref` hands you the platform surface (`HTMLVideoElement` on web) for
26
29
  controls the shared props don't cover, and the `ios`/`android`/`web`
27
30
  escape props apply after the shared props.
28
31
 
29
- Guide: [Building screens](../../docs/primitives.md) (leaf components);
30
- component index: [`docs/components.md`](../../docs/components.md).
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).
31
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": "43ccef1b-b61d-45ed-b9be-3e99093c9451",
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": "b4334c49-36f2-46b2-ba99-abac851b84f2",
3
+ "buildId": "cf0744c7-ee47-4aa9-9731-473938c42593",
4
4
  "mode": "production",
5
5
  "capabilities": {
6
6
  "independentHydration": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octane-xplat/camera",
3
- "version": "0.9.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
6
  "android",
@@ -45,7 +45,7 @@
45
45
  "@nativescript/core": "9.1.2",
46
46
  "@octanejs/vite-plugin": "0.1.61",
47
47
  "octane": "0.6.3",
48
- "tsrx-typegen": "0.2.1",
48
+ "tsrx-typegen": "0.2.2",
49
49
  "typescript": "5.9.3",
50
50
  "vite": "8.3.0"
51
51
  },
@@ -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>
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>;