@ng-native/expo 0.0.1 → 0.1.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 (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +413 -1
  3. package/dist/app-info.d.ts +58 -0
  4. package/dist/app-info.js +71 -0
  5. package/dist/app-info.js.map +1 -0
  6. package/dist/apple-sign-in-button.d.ts +16 -0
  7. package/dist/apple-sign-in-button.js +55 -0
  8. package/dist/apple-sign-in-button.js.map +1 -0
  9. package/dist/apple-sign-in.d.ts +68 -0
  10. package/dist/apple-sign-in.js +120 -0
  11. package/dist/apple-sign-in.js.map +1 -0
  12. package/dist/assets.d.ts +24 -0
  13. package/dist/assets.js +47 -0
  14. package/dist/assets.js.map +1 -0
  15. package/dist/background-task.d.ts +66 -0
  16. package/dist/background-task.js +82 -0
  17. package/dist/background-task.js.map +1 -0
  18. package/dist/battery.d.ts +46 -0
  19. package/dist/battery.js +76 -0
  20. package/dist/battery.js.map +1 -0
  21. package/dist/biometrics.d.ts +45 -0
  22. package/dist/biometrics.js +53 -0
  23. package/dist/biometrics.js.map +1 -0
  24. package/dist/brightness.d.ts +38 -0
  25. package/dist/brightness.js +81 -0
  26. package/dist/brightness.js.map +1 -0
  27. package/dist/browser.d.ts +42 -0
  28. package/dist/browser.js +42 -0
  29. package/dist/browser.js.map +1 -0
  30. package/dist/camera.d.ts +48 -0
  31. package/dist/camera.js +59 -0
  32. package/dist/camera.js.map +1 -0
  33. package/dist/clipboard.d.ts +43 -0
  34. package/dist/clipboard.js +60 -0
  35. package/dist/clipboard.js.map +1 -0
  36. package/dist/community-views.d.ts +14 -0
  37. package/dist/community-views.js +76 -0
  38. package/dist/community-views.js.map +1 -0
  39. package/dist/crypto.d.ts +56 -0
  40. package/dist/crypto.js +84 -0
  41. package/dist/crypto.js.map +1 -0
  42. package/dist/database.d.ts +47 -0
  43. package/dist/database.js +108 -0
  44. package/dist/database.js.map +1 -0
  45. package/dist/document-picker.d.ts +34 -0
  46. package/dist/document-picker.js +40 -0
  47. package/dist/document-picker.js.map +1 -0
  48. package/dist/dom-component.d.ts +87 -0
  49. package/dist/dom-component.js +139 -0
  50. package/dist/dom-component.js.map +1 -0
  51. package/dist/expo-image.d.ts +33 -0
  52. package/dist/expo-image.js +57 -0
  53. package/dist/expo-image.js.map +1 -0
  54. package/dist/expo-ui-components.d.ts +327 -0
  55. package/dist/expo-ui-components.js +707 -0
  56. package/dist/expo-ui-components.js.map +1 -0
  57. package/dist/expo-ui.d.ts +46 -0
  58. package/dist/expo-ui.js +128 -0
  59. package/dist/expo-ui.js.map +1 -0
  60. package/dist/file-system.d.ts +63 -0
  61. package/dist/file-system.js +67 -0
  62. package/dist/file-system.js.map +1 -0
  63. package/dist/fonts.d.ts +90 -0
  64. package/dist/fonts.js +118 -0
  65. package/dist/fonts.js.map +1 -0
  66. package/dist/glass.d.ts +33 -0
  67. package/dist/glass.js +72 -0
  68. package/dist/glass.js.map +1 -0
  69. package/dist/haptics.d.ts +49 -0
  70. package/dist/haptics.js +64 -0
  71. package/dist/haptics.js.map +1 -0
  72. package/dist/image-editor.d.ts +62 -0
  73. package/dist/image-editor.js +101 -0
  74. package/dist/image-editor.js.map +1 -0
  75. package/dist/image-picker.d.ts +49 -0
  76. package/dist/image-picker.js +58 -0
  77. package/dist/image-picker.js.map +1 -0
  78. package/dist/index.d.ts +20 -0
  79. package/dist/index.js +21 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/keep-awake.d.ts +39 -0
  82. package/dist/keep-awake.js +80 -0
  83. package/dist/keep-awake.js.map +1 -0
  84. package/dist/language-model.d.ts +129 -0
  85. package/dist/language-model.js +225 -0
  86. package/dist/language-model.js.map +1 -0
  87. package/dist/locale.d.ts +51 -0
  88. package/dist/locale.js +78 -0
  89. package/dist/locale.js.map +1 -0
  90. package/dist/location.d.ts +68 -0
  91. package/dist/location.js +83 -0
  92. package/dist/location.js.map +1 -0
  93. package/dist/map-view.d.ts +203 -0
  94. package/dist/map-view.js +184 -0
  95. package/dist/map-view.js.map +1 -0
  96. package/dist/media-library.d.ts +103 -0
  97. package/dist/media-library.js +160 -0
  98. package/dist/media-library.js.map +1 -0
  99. package/dist/native-state.d.ts +22 -0
  100. package/dist/native-state.js +43 -0
  101. package/dist/native-state.js.map +1 -0
  102. package/dist/native.d.ts +42 -0
  103. package/dist/native.js +73 -0
  104. package/dist/native.js.map +1 -0
  105. package/dist/network.d.ts +35 -0
  106. package/dist/network.js +60 -0
  107. package/dist/network.js.map +1 -0
  108. package/dist/notifications.d.ts +175 -0
  109. package/dist/notifications.js +296 -0
  110. package/dist/notifications.js.map +1 -0
  111. package/dist/observed.d.ts +37 -0
  112. package/dist/observed.js +55 -0
  113. package/dist/observed.js.map +1 -0
  114. package/dist/orientation.d.ts +35 -0
  115. package/dist/orientation.js +78 -0
  116. package/dist/orientation.js.map +1 -0
  117. package/dist/permissions.d.ts +54 -0
  118. package/dist/permissions.js +72 -0
  119. package/dist/permissions.js.map +1 -0
  120. package/dist/player.d.ts +92 -0
  121. package/dist/player.js +141 -0
  122. package/dist/player.js.map +1 -0
  123. package/dist/register-expo-view.d.ts +55 -0
  124. package/dist/register-expo-view.js +110 -0
  125. package/dist/register-expo-view.js.map +1 -0
  126. package/dist/screen-capture.d.ts +58 -0
  127. package/dist/screen-capture.js +78 -0
  128. package/dist/screen-capture.js.map +1 -0
  129. package/dist/segmented-control.d.ts +32 -0
  130. package/dist/segmented-control.js +55 -0
  131. package/dist/segmented-control.js.map +1 -0
  132. package/dist/sensors.d.ts +85 -0
  133. package/dist/sensors.js +115 -0
  134. package/dist/sensors.js.map +1 -0
  135. package/dist/splash-screen.d.ts +73 -0
  136. package/dist/splash-screen.js +115 -0
  137. package/dist/splash-screen.js.map +1 -0
  138. package/dist/store-review.d.ts +43 -0
  139. package/dist/store-review.js +52 -0
  140. package/dist/store-review.js.map +1 -0
  141. package/dist/store.d.ts +108 -0
  142. package/dist/store.js +247 -0
  143. package/dist/store.js.map +1 -0
  144. package/dist/symbol.d.ts +36 -0
  145. package/dist/symbol.js +69 -0
  146. package/dist/symbol.js.map +1 -0
  147. package/dist/tracking.d.ts +45 -0
  148. package/dist/tracking.js +54 -0
  149. package/dist/tracking.js.map +1 -0
  150. package/dist/transforms.d.ts +2 -0
  151. package/dist/transforms.js +13 -0
  152. package/dist/transforms.js.map +1 -0
  153. package/dist/updates.d.ts +61 -0
  154. package/dist/updates.js +97 -0
  155. package/dist/updates.js.map +1 -0
  156. package/package.json +397 -3
@@ -0,0 +1,72 @@
1
+ /**
2
+ * A permission, as signals rather than a hook.
3
+ *
4
+ * Every Expo module that needs one exposes the same shape: `getXPermissionsAsync`,
5
+ * `requestXPermissionsAsync`, and a `useXPermissions()` hook that is those two plus React state.
6
+ * So this is not a facade per module - it is one class that takes the pair, and the module stays
7
+ * the app's own dependency:
8
+ *
9
+ * ```ts
10
+ * import * as Camera from 'expo-camera';
11
+ * import { Permission } from '@ng-native/expo';
12
+ *
13
+ * readonly camera = Permission.of(Camera.Camera.getCameraPermissionsAsync,
14
+ * Camera.Camera.requestCameraPermissionsAsync);
15
+ * ```
16
+ */
17
+ import { computed, signal } from '@angular/core';
18
+ export class Permission {
19
+ api;
20
+ last = signal(null, /* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "last" }] : /* istanbul ignore next */ []));
22
+ constructor(api) {
23
+ this.api = api;
24
+ }
25
+ /** `unknown` until something has asked the platform, which is not the same as `undetermined`. */
26
+ status = computed(() => this.last()?.status ?? 'unknown', /* @ts-ignore */
27
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
28
+ granted = computed(() => this.last()?.granted ?? false, /* @ts-ignore */
29
+ ...(ngDevMode ? [{ debugName: "granted" }] : /* istanbul ignore next */ []));
30
+ /** Refused, and the platform will not show a dialog again. Send them to Settings. */
31
+ blocked = computed(() => {
32
+ const response = this.last();
33
+ return response !== null && !response.granted && !response.canAskAgain;
34
+ }, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "blocked" }] : /* istanbul ignore next */ []));
36
+ /** From a module's own two functions, which is how every one of them spells this. */
37
+ static of(get, request) {
38
+ return new Permission({ get, request });
39
+ }
40
+ /** Ask the platform what it currently thinks, without showing anything to the user. */
41
+ async check() {
42
+ return this.record(await this.api.get());
43
+ }
44
+ /** Show the dialog. Resolves to what the user said. */
45
+ async request() {
46
+ return this.record(await this.api.request());
47
+ }
48
+ /**
49
+ * Have the permission if it can be had, asking only if asking would do something.
50
+ *
51
+ * The method most call sites want. A permission already granted needs no dialog, and one the
52
+ * platform has stopped asking about would return the same no while looking like the user had
53
+ * been consulted.
54
+ */
55
+ async ensure() {
56
+ if (this.last() === null)
57
+ await this.check();
58
+ const current = this.last();
59
+ if (current?.granted)
60
+ return true;
61
+ // Refused for good. Requesting again resolves to the same no without the platform showing
62
+ // anything, which reads at the call site as though the user had been asked twice.
63
+ if (current && !current.canAskAgain)
64
+ return false;
65
+ return this.request();
66
+ }
67
+ record(response) {
68
+ this.last.set(response);
69
+ return response.granted;
70
+ }
71
+ }
72
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAgB9D,MAAM,OAAO,UAAU;IACJ,GAAG,CAAgB;IACnB,IAAI,GAAG,MAAM,CAA4B,IAAI;6EAAC,CAAC;IAEhE,YAAY,GAAkB;QAC5B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,iGAAiG;IACxF,MAAM,GAAqD,QAAQ,CAC1E,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,IAAI,SAAS;+EACvC,CAAC;IAEO,OAAO,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,IAAI,KAAK;gFAAC,CAAC;IAElF,qFAAqF;IAC5E,OAAO,GAAoB,QAAQ,CAAC,GAAG,EAAE;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;IACzE,CAAC;gFAAC,CAAC;IAEH,qFAAqF;IACrF,MAAM,CAAC,EAAE,CACP,GAAsC,EACtC,OAA0C;QAE1C,OAAO,IAAI,UAAU,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM;QACV,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI;YAAE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,EAAE,OAAO;YAAE,OAAO,IAAI,CAAC;QAClC,0FAA0F;QAC1F,kFAAkF;QAClF,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QAClD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAEO,MAAM,CAAC,QAA4B;QACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;CACF","sourcesContent":["/**\n * A permission, as signals rather than a hook.\n *\n * Every Expo module that needs one exposes the same shape: `getXPermissionsAsync`,\n * `requestXPermissionsAsync`, and a `useXPermissions()` hook that is those two plus React state.\n * So this is not a facade per module - it is one class that takes the pair, and the module stays\n * the app's own dependency:\n *\n * ```ts\n * import * as Camera from 'expo-camera';\n * import { Permission } from '@ng-native/expo';\n *\n * readonly camera = Permission.of(Camera.Camera.getCameraPermissionsAsync,\n * Camera.Camera.requestCameraPermissionsAsync);\n * ```\n */\nimport { computed, signal, type Signal } from '@angular/core';\n\n/** What every Expo permission call answers with. */\nexport interface PermissionResponse {\n readonly status: 'granted' | 'denied' | 'undetermined';\n readonly granted: boolean;\n /** False once the platform will no longer show a dialog: the app must send them to Settings. */\n readonly canAskAgain: boolean;\n}\n\n/** The pair of functions a module exposes, which its hook is built out of. */\nexport interface PermissionApi {\n get(): Promise<PermissionResponse>;\n request(): Promise<PermissionResponse>;\n}\n\nexport class Permission {\n private readonly api: PermissionApi;\n private readonly last = signal<PermissionResponse | null>(null);\n\n constructor(api: PermissionApi) {\n this.api = api;\n }\n\n /** `unknown` until something has asked the platform, which is not the same as `undetermined`. */\n readonly status: Signal<PermissionResponse['status'] | 'unknown'> = computed(\n () => this.last()?.status ?? 'unknown',\n );\n\n readonly granted: Signal<boolean> = computed(() => this.last()?.granted ?? false);\n\n /** Refused, and the platform will not show a dialog again. Send them to Settings. */\n readonly blocked: Signal<boolean> = computed(() => {\n const response = this.last();\n return response !== null && !response.granted && !response.canAskAgain;\n });\n\n /** From a module's own two functions, which is how every one of them spells this. */\n static of(\n get: () => Promise<PermissionResponse>,\n request: () => Promise<PermissionResponse>,\n ): Permission {\n return new Permission({ get, request });\n }\n\n /** Ask the platform what it currently thinks, without showing anything to the user. */\n async check(): Promise<boolean> {\n return this.record(await this.api.get());\n }\n\n /** Show the dialog. Resolves to what the user said. */\n async request(): Promise<boolean> {\n return this.record(await this.api.request());\n }\n\n /**\n * Have the permission if it can be had, asking only if asking would do something.\n *\n * The method most call sites want. A permission already granted needs no dialog, and one the\n * platform has stopped asking about would return the same no while looking like the user had\n * been consulted.\n */\n async ensure(): Promise<boolean> {\n if (this.last() === null) await this.check();\n\n const current = this.last();\n if (current?.granted) return true;\n // Refused for good. Requesting again resolves to the same no without the platform showing\n // anything, which reads at the call site as though the user had been asked twice.\n if (current && !current.canAskAgain) return false;\n return this.request();\n }\n\n private record(response: PermissionResponse): boolean {\n this.last.set(response);\n return response.granted;\n }\n}\n"]}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Media players, bound to `expo-video` and `expo-audio`.
3
+ *
4
+ * ```ts
5
+ * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });
6
+ *
7
+ * // <expo-video [player]="player.native" />
8
+ * // {{ player.state().currentTime }}
9
+ * ```
10
+ *
11
+ * `useVideoPlayer` and `useAudioPlayer` are the documented way in, and the player they hand back
12
+ * is not the React part - `createVideoPlayer` gives the same object. What the hooks add is the
13
+ * two things a component actually needs and cannot get otherwise: the player is *released* when
14
+ * the component goes, and its changing state is *readable*, because the player's own properties
15
+ * are plain mutable fields that no template can watch.
16
+ *
17
+ * The player itself is passed straight through: it is a shared object with `play()`, `seekBy()`
18
+ * and the rest already on it, and wrapping those would be a second place for each to be wrong.
19
+ */
20
+ import { type Signal } from '@angular/core';
21
+ /** The player's own status, which is the same word in both modules. */
22
+ export type PlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';
23
+ /** The slice of a player this reads. Both modules' players satisfy it structurally. */
24
+ export interface NativePlayer {
25
+ addListener(event: string, listener: (payload: never) => void): {
26
+ remove(): void;
27
+ };
28
+ release?(): void;
29
+ /** Seconds between `timeUpdate` events. Zero, its default, means the event never fires. `expo-video` only. */
30
+ timeUpdateEventInterval?: number;
31
+ /** Read directly rather than from an event: `expo-audio`'s status carries no volume field. */
32
+ readonly volume?: number;
33
+ }
34
+ /**
35
+ * `expo-audio`'s one status event, `playbackStatusUpdate`. Unlike `expo-video`, which splits its
36
+ * state across several named events, `AudioPlayer` reports everything - including errors - on a
37
+ * single event, at the interval `updateInterval` was created with.
38
+ */
39
+ export interface NativeAudioStatus {
40
+ readonly playing: boolean;
41
+ readonly mute: boolean;
42
+ readonly duration: number;
43
+ readonly currentTime: number;
44
+ readonly isLoaded: boolean;
45
+ readonly didJustFinish: boolean;
46
+ readonly error: string | null;
47
+ }
48
+ export interface PlayerState {
49
+ readonly playing: boolean;
50
+ readonly status: PlayerStatus;
51
+ /** Seconds. Only moves while `timeUpdateEventInterval` is non-zero. */
52
+ readonly currentTime: number;
53
+ readonly duration: number;
54
+ readonly muted: boolean;
55
+ readonly volume: number;
56
+ /** Set once playback has run to the end, and cleared when it starts again. */
57
+ readonly ended: boolean;
58
+ }
59
+ /**
60
+ * Watch an `expo-video` player, and stop watching when the caller is done with it.
61
+ *
62
+ * `timeUpdate` defaults to off, so `currentTime` would never move. A caller that wants a progress
63
+ * bar asks for one by passing an interval, and one that does not pays nothing: the event is the
64
+ * most frequent thing the player emits.
65
+ *
66
+ * `expo-audio`'s player reports its state differently - one event, not several - so it is watched
67
+ * by `watchAudioPlayer` below rather than this function.
68
+ */
69
+ export declare function watchPlayer(player: NativePlayer | null, options?: {
70
+ timeUpdate?: number;
71
+ }): {
72
+ state: Signal<PlayerState>;
73
+ stop: () => void;
74
+ };
75
+ export interface Player<T> {
76
+ /** The module's own player, for `play()`, `seekBy()`, and to hand to its view. */
77
+ readonly native: T;
78
+ readonly state: Signal<PlayerState>;
79
+ }
80
+ /** A video player owned by the current component, released when it is destroyed. */
81
+ export declare function videoPlayer(source: import('expo-video').VideoSource, options?: {
82
+ timeUpdate?: number;
83
+ }): Player<import('expo-video').VideoPlayer>;
84
+ /**
85
+ * An audio player owned by the current component, released when it is destroyed.
86
+ *
87
+ * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is
88
+ * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.
89
+ */
90
+ export declare function audioPlayer(source: import('expo-audio').AudioSource, options?: {
91
+ timeUpdate?: number;
92
+ }): Player<import('expo-audio').AudioPlayer>;
package/dist/player.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Media players, bound to `expo-video` and `expo-audio`.
3
+ *
4
+ * ```ts
5
+ * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });
6
+ *
7
+ * // <expo-video [player]="player.native" />
8
+ * // {{ player.state().currentTime }}
9
+ * ```
10
+ *
11
+ * `useVideoPlayer` and `useAudioPlayer` are the documented way in, and the player they hand back
12
+ * is not the React part - `createVideoPlayer` gives the same object. What the hooks add is the
13
+ * two things a component actually needs and cannot get otherwise: the player is *released* when
14
+ * the component goes, and its changing state is *readable*, because the player's own properties
15
+ * are plain mutable fields that no template can watch.
16
+ *
17
+ * The player itself is passed straight through: it is a shared object with `play()`, `seekBy()`
18
+ * and the rest already on it, and wrapping those would be a second place for each to be wrong.
19
+ */
20
+ import { DestroyRef, inject, signal } from '@angular/core';
21
+ import { expoModule } from "./native.js";
22
+ const INITIAL = {
23
+ playing: false,
24
+ status: 'idle',
25
+ currentTime: 0,
26
+ duration: 0,
27
+ muted: false,
28
+ volume: 1,
29
+ ended: false,
30
+ };
31
+ /**
32
+ * Watch an `expo-video` player, and stop watching when the caller is done with it.
33
+ *
34
+ * `timeUpdate` defaults to off, so `currentTime` would never move. A caller that wants a progress
35
+ * bar asks for one by passing an interval, and one that does not pays nothing: the event is the
36
+ * most frequent thing the player emits.
37
+ *
38
+ * `expo-audio`'s player reports its state differently - one event, not several - so it is watched
39
+ * by `watchAudioPlayer` below rather than this function.
40
+ */
41
+ export function watchPlayer(player, options = {}) {
42
+ const state = signal(INITIAL, /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
44
+ if (!player)
45
+ return { state: state.asReadonly(), stop: () => { } };
46
+ const patch = (change) => state.update((last) => ({ ...last, ...change }));
47
+ const subscriptions = [
48
+ listen(player, 'statusChange', ({ status }) => patch({ status })),
49
+ listen(player, 'playingChange', ({ isPlaying }) => patch({ playing: isPlaying, ...(isPlaying ? { ended: false } : {}) })),
50
+ listen(player, 'mutedChange', ({ muted }) => patch({ muted })),
51
+ listen(player, 'volumeChange', ({ volume }) => patch({ volume })),
52
+ listen(player, 'playToEnd', () => patch({ ended: true, playing: false })),
53
+ listen(player, 'timeUpdate', ({ currentTime }) => patch({ currentTime })),
54
+ listen(player, 'sourceChange', () => patch({ ...INITIAL, status: 'loading' })),
55
+ // The only event that says how long the video is: nothing else carries a duration.
56
+ listen(player, 'sourceLoad', ({ duration }) => patch({ duration })),
57
+ ];
58
+ if (options.timeUpdate !== undefined)
59
+ player.timeUpdateEventInterval = options.timeUpdate;
60
+ return {
61
+ state: state.asReadonly(),
62
+ stop: () => {
63
+ for (const subscription of subscriptions)
64
+ subscription();
65
+ // Releasing is the half a hook does that nothing else will: a player left alive holds its
66
+ // decoder, its audio session and, on iOS, the now-playing controls.
67
+ player.release?.();
68
+ },
69
+ };
70
+ }
71
+ function listen(player, event, handler) {
72
+ const subscription = player.addListener(event, handler);
73
+ return () => subscription.remove();
74
+ }
75
+ /**
76
+ * Watch an `expo-audio` player, and stop watching when the caller is done with it.
77
+ *
78
+ * `AudioPlayer` reports its whole state on `playbackStatusUpdate`, at the interval it was created
79
+ * with, rather than splitting it across the several events `expo-video` uses - so unlike
80
+ * `watchPlayer`, there is one listener, not several, and nothing to reset on a source change: the
81
+ * next status update carries the new source's own state.
82
+ */
83
+ function watchAudioPlayer(player) {
84
+ const state = signal(INITIAL, /* @ts-ignore */
85
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
86
+ if (!player)
87
+ return { state: state.asReadonly(), stop: () => { } };
88
+ const subscription = listen(player, 'playbackStatusUpdate', (status) => state.set({
89
+ playing: status.playing,
90
+ status: status.error !== null ? 'error' : status.isLoaded ? 'readyToPlay' : 'loading',
91
+ currentTime: status.currentTime,
92
+ duration: status.duration,
93
+ muted: status.mute,
94
+ // Not on the status event; the player's own property is the current value.
95
+ volume: player.volume ?? INITIAL.volume,
96
+ ended: status.didJustFinish,
97
+ }));
98
+ return {
99
+ state: state.asReadonly(),
100
+ stop: () => {
101
+ subscription();
102
+ // Releasing is the half a hook does that nothing else will: a player left alive holds its
103
+ // decoder, its audio session and, on iOS, the now-playing controls.
104
+ player.release?.();
105
+ },
106
+ };
107
+ }
108
+ /** A video player owned by the current component, released when it is destroyed. */
109
+ export function videoPlayer(source, options = {}) {
110
+ const expo = expoModule('expo-video', () => require('expo-video'), ['ios', 'android', 'web']);
111
+ if (!expo)
112
+ throw new Error('[angular-native] expo-video is not installed');
113
+ const native = expo.createVideoPlayer(source);
114
+ return own(native, () => watchPlayer(native, options));
115
+ }
116
+ /**
117
+ * An audio player owned by the current component, released when it is destroyed.
118
+ *
119
+ * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is
120
+ * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.
121
+ */
122
+ export function audioPlayer(source, options = {}) {
123
+ const expo = expoModule('expo-audio', () => require('expo-audio'), ['ios', 'android', 'web']);
124
+ if (!expo)
125
+ throw new Error('[angular-native] expo-audio is not installed');
126
+ const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;
127
+ const native = expo.createAudioPlayer(source, { updateInterval });
128
+ return own(native, () => watchAudioPlayer(native));
129
+ }
130
+ /**
131
+ * Tie a player's life to the component that made it.
132
+ *
133
+ * Called in an injection context, which is the same constraint the hooks have and for the same
134
+ * reason: something has to know when the thing goes away.
135
+ */
136
+ function own(native, watch) {
137
+ const { state, stop } = watch();
138
+ inject(DestroyRef).onDestroy(stop);
139
+ return { native, state };
140
+ }
141
+ //# sourceMappingURL=player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Media players, bound to `expo-video` and `expo-audio`.\n *\n * ```ts\n * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // <expo-video [player]=\"player.native\" />\n * // {{ player.state().currentTime }}\n * ```\n *\n * `useVideoPlayer` and `useAudioPlayer` are the documented way in, and the player they hand back\n * is not the React part - `createVideoPlayer` gives the same object. What the hooks add is the\n * two things a component actually needs and cannot get otherwise: the player is *released* when\n * the component goes, and its changing state is *readable*, because the player's own properties\n * are plain mutable fields that no template can watch.\n *\n * The player itself is passed straight through: it is a shared object with `play()`, `seekBy()`\n * and the rest already on it, and wrapping those would be a second place for each to be wrong.\n */\nimport { DestroyRef, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** The player's own status, which is the same word in both modules. */\nexport type PlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\n/** The slice of a player this reads. Both modules' players satisfy it structurally. */\nexport interface NativePlayer {\n addListener(event: string, listener: (payload: never) => void): { remove(): void };\n release?(): void;\n /** Seconds between `timeUpdate` events. Zero, its default, means the event never fires. `expo-video` only. */\n timeUpdateEventInterval?: number;\n /** Read directly rather than from an event: `expo-audio`'s status carries no volume field. */\n readonly volume?: number;\n}\n\n/**\n * `expo-audio`'s one status event, `playbackStatusUpdate`. Unlike `expo-video`, which splits its\n * state across several named events, `AudioPlayer` reports everything - including errors - on a\n * single event, at the interval `updateInterval` was created with.\n */\nexport interface NativeAudioStatus {\n readonly playing: boolean;\n readonly mute: boolean;\n readonly duration: number;\n readonly currentTime: number;\n readonly isLoaded: boolean;\n readonly didJustFinish: boolean;\n readonly error: string | null;\n}\n\nexport interface PlayerState {\n readonly playing: boolean;\n readonly status: PlayerStatus;\n /** Seconds. Only moves while `timeUpdateEventInterval` is non-zero. */\n readonly currentTime: number;\n readonly duration: number;\n readonly muted: boolean;\n readonly volume: number;\n /** Set once playback has run to the end, and cleared when it starts again. */\n readonly ended: boolean;\n}\n\nconst INITIAL: PlayerState = {\n playing: false,\n status: 'idle',\n currentTime: 0,\n duration: 0,\n muted: false,\n volume: 1,\n ended: false,\n};\n\n/**\n * Watch an `expo-video` player, and stop watching when the caller is done with it.\n *\n * `timeUpdate` defaults to off, so `currentTime` would never move. A caller that wants a progress\n * bar asks for one by passing an interval, and one that does not pays nothing: the event is the\n * most frequent thing the player emits.\n *\n * `expo-audio`'s player reports its state differently - one event, not several - so it is watched\n * by `watchAudioPlayer` below rather than this function.\n */\nexport function watchPlayer(\n player: NativePlayer | null,\n options: { timeUpdate?: number } = {},\n): { state: Signal<PlayerState>; stop: () => void } {\n const state = signal(INITIAL);\n if (!player) return { state: state.asReadonly(), stop: () => {} };\n\n const patch = (change: Partial<PlayerState>) => state.update((last) => ({ ...last, ...change }));\n const subscriptions = [\n listen(player, 'statusChange', ({ status }: { status: PlayerStatus }) => patch({ status })),\n listen(player, 'playingChange', ({ isPlaying }: { isPlaying: boolean }) =>\n patch({ playing: isPlaying, ...(isPlaying ? { ended: false } : {}) }),\n ),\n listen(player, 'mutedChange', ({ muted }: { muted: boolean }) => patch({ muted })),\n listen(player, 'volumeChange', ({ volume }: { volume: number }) => patch({ volume })),\n listen(player, 'playToEnd', () => patch({ ended: true, playing: false })),\n listen(player, 'timeUpdate', ({ currentTime }: { currentTime: number }) =>\n patch({ currentTime }),\n ),\n listen(player, 'sourceChange', () => patch({ ...INITIAL, status: 'loading' })),\n // The only event that says how long the video is: nothing else carries a duration.\n listen(player, 'sourceLoad', ({ duration }: { duration: number }) => patch({ duration })),\n ];\n\n if (options.timeUpdate !== undefined) player.timeUpdateEventInterval = options.timeUpdate;\n\n return {\n state: state.asReadonly(),\n stop: () => {\n for (const subscription of subscriptions) subscription();\n // Releasing is the half a hook does that nothing else will: a player left alive holds its\n // decoder, its audio session and, on iOS, the now-playing controls.\n player.release?.();\n },\n };\n}\n\nfunction listen<T>(player: NativePlayer, event: string, handler: (payload: T) => void): () => void {\n const subscription = player.addListener(event, handler as (payload: never) => void);\n return () => subscription.remove();\n}\n\n/**\n * Watch an `expo-audio` player, and stop watching when the caller is done with it.\n *\n * `AudioPlayer` reports its whole state on `playbackStatusUpdate`, at the interval it was created\n * with, rather than splitting it across the several events `expo-video` uses - so unlike\n * `watchPlayer`, there is one listener, not several, and nothing to reset on a source change: the\n * next status update carries the new source's own state.\n */\nfunction watchAudioPlayer(player: NativePlayer | null): {\n state: Signal<PlayerState>;\n stop: () => void;\n} {\n const state = signal(INITIAL);\n if (!player) return { state: state.asReadonly(), stop: () => {} };\n\n const subscription = listen(player, 'playbackStatusUpdate', (status: NativeAudioStatus) =>\n state.set({\n playing: status.playing,\n status: status.error !== null ? 'error' : status.isLoaded ? 'readyToPlay' : 'loading',\n currentTime: status.currentTime,\n duration: status.duration,\n muted: status.mute,\n // Not on the status event; the player's own property is the current value.\n volume: player.volume ?? INITIAL.volume,\n ended: status.didJustFinish,\n }),\n );\n\n return {\n state: state.asReadonly(),\n stop: () => {\n subscription();\n // Releasing is the half a hook does that nothing else will: a player left alive holds its\n // decoder, its audio session and, on iOS, the now-playing controls.\n player.release?.();\n },\n };\n}\n\nexport interface Player<T> {\n /** The module's own player, for `play()`, `seekBy()`, and to hand to its view. */\n readonly native: T;\n readonly state: Signal<PlayerState>;\n}\n\n/** A video player owned by the current component, released when it is destroyed. */\nexport function videoPlayer(\n source: import('expo-video').VideoSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-video').VideoPlayer> {\n const expo = expoModule(\n 'expo-video',\n () => require('expo-video') as typeof import('expo-video'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw new Error('[angular-native] expo-video is not installed');\n const native = expo.createVideoPlayer(source);\n return own(native, () => watchPlayer(native, options));\n}\n\n/**\n * An audio player owned by the current component, released when it is destroyed.\n *\n * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is\n * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.\n */\nexport function audioPlayer(\n source: import('expo-audio').AudioSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-audio').AudioPlayer> {\n const expo = expoModule(\n 'expo-audio',\n () => require('expo-audio') as typeof import('expo-audio'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw new Error('[angular-native] expo-audio is not installed');\n const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;\n const native = expo.createAudioPlayer(source, { updateInterval });\n return own(native, () => watchAudioPlayer(native));\n}\n\n/**\n * Tie a player's life to the component that made it.\n *\n * Called in an injection context, which is the same constraint the hooks have and for the same\n * reason: something has to know when the thing goes away.\n */\nfunction own<T extends NativePlayer>(\n native: T,\n watch: () => { state: Signal<PlayerState>; stop: () => void },\n): Player<T> {\n const { state, stop } = watch();\n inject(DestroyRef).onDestroy(stop);\n return { native, state };\n}\n"]}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes
3
+ * it in `expo-modules-core`.
4
+ *
5
+ * The app identifier is the part that cannot be hardcoded. Expo Go runs many projects in one
6
+ * binary, so it namespaces every view name with a per-app suffix; a standalone build has no
7
+ * identifier and no suffix. Get it wrong and the view commits as `UnimplementedNativeView`
8
+ * with no error, which is the same failure mode as a misspelled name.
9
+ *
10
+ * A module with more than one view names the extra ones (`viewName`); the default view is the
11
+ * module itself.
12
+ */
13
+ export declare function expoViewName(moduleName: string, viewName?: string): string;
14
+ export interface ExpoViewOptions {
15
+ /** A named view on a module that has several. Omit for the module's default view. */
16
+ readonly viewName?: string;
17
+ /**
18
+ * Props the module's React component would have applied before native saw them. Expo's
19
+ * wrappers do less of this than React Native's, but they do some: `expo-image` resolves a
20
+ * source to an array and a `contentFit` to a string native understands.
21
+ */
22
+ readonly defaultProps?: Record<string, unknown>;
23
+ }
24
+ /**
25
+ * Teach the engine an element name for an Expo module's view, e.g.
26
+ * `registerExpoView('expo-image', 'ExpoImage')` makes `<expo-image>` commit as one.
27
+ *
28
+ * Call it at startup, before the first commit that uses the element. The app must have the
29
+ * module installed so the native side registers the component; this is the JavaScript half only.
30
+ */
31
+ export declare function registerExpoView(elementName: string, moduleName: string, options?: ExpoViewOptions): void;
32
+ /**
33
+ * The views worth knowing the names of, so an app does not have to look each one up.
34
+ *
35
+ * Deliberately names and defaults, not components. An Angular component per Expo view, prop for
36
+ * prop, is a maintenance burden with no end and a second place for every prop to be wrong; the
37
+ * element plus the module's own documentation covers the common case, and an app that wants a
38
+ * nicer surface writes the one component it actually needs.
39
+ *
40
+ * A module still has to be installed for its element to commit as anything: this is the
41
+ * JavaScript half. An element whose native side is missing commits as `UnimplementedNativeView`.
42
+ */
43
+ export declare const EXPO_VIEWS: Readonly<Record<string, [string, ExpoViewOptions?]>>;
44
+ /**
45
+ * Register several of the known views by element name.
46
+ *
47
+ * ```ts
48
+ * registerExpoViews('expo-image', 'expo-blur');
49
+ * ```
50
+ *
51
+ * Named one at a time rather than all at once on purpose: registering a view for a module the app
52
+ * has not installed produces an element that commits as nothing, and finding out at startup which
53
+ * modules are present is not something JavaScript can do without importing them all.
54
+ */
55
+ export declare function registerExpoViews(...elements: readonly (keyof typeof EXPO_VIEWS)[]): void;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Expo's native views, driven directly.
3
+ *
4
+ * An Expo module that renders something registers a Fabric component whose name it derives
5
+ * rather than declares, and `requireNativeViewManager` is the only place that derivation is
6
+ * written down. Everything else that function does is React: it caches a host component, builds
7
+ * a view config for React's renderer, and wraps the result in a class whose whole job is to hold
8
+ * a ref. None of that is reachable without React, and none of it is needed - the engine writes
9
+ * props onto the shadow node itself, and Expo's Fabric views take their props as an untyped map,
10
+ * so there is no codegen'd prop list to satisfy.
11
+ *
12
+ * What is left is the name. That is what this package is.
13
+ *
14
+ * No decorators and no Angular here on purpose: this is a registration, and the components it
15
+ * registers are ordinary elements in a template.
16
+ */
17
+ import { registerViewName } from '@ng-native/fabric';
18
+ /**
19
+ * The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes
20
+ * it in `expo-modules-core`.
21
+ *
22
+ * The app identifier is the part that cannot be hardcoded. Expo Go runs many projects in one
23
+ * binary, so it namespaces every view name with a per-app suffix; a standalone build has no
24
+ * identifier and no suffix. Get it wrong and the view commits as `UnimplementedNativeView`
25
+ * with no error, which is the same failure mode as a misspelled name.
26
+ *
27
+ * A module with more than one view names the extra ones (`viewName`); the default view is the
28
+ * module itself.
29
+ */
30
+ export function expoViewName(moduleName, viewName) {
31
+ const identifier = globalThis.expo?.__expo_app_identifier__ ?? '';
32
+ const suffix = identifier ? `_${identifier}` : '';
33
+ const module = viewName ? `${moduleName}_${viewName}` : moduleName;
34
+ return `ViewManagerAdapter_${module}${suffix}`;
35
+ }
36
+ /**
37
+ * Teach the engine an element name for an Expo module's view, e.g.
38
+ * `registerExpoView('expo-image', 'ExpoImage')` makes `<expo-image>` commit as one.
39
+ *
40
+ * Call it at startup, before the first commit that uses the element. The app must have the
41
+ * module installed so the native side registers the component; this is the JavaScript half only.
42
+ */
43
+ export function registerExpoView(elementName, moduleName, options) {
44
+ registerViewName(elementName, expoViewName(moduleName, options?.viewName), options?.defaultProps);
45
+ }
46
+ /**
47
+ * The views worth knowing the names of, so an app does not have to look each one up.
48
+ *
49
+ * Deliberately names and defaults, not components. An Angular component per Expo view, prop for
50
+ * prop, is a maintenance burden with no end and a second place for every prop to be wrong; the
51
+ * element plus the module's own documentation covers the common case, and an app that wants a
52
+ * nicer surface writes the one component it actually needs.
53
+ *
54
+ * A module still has to be installed for its element to commit as anything: this is the
55
+ * JavaScript half. An element whose native side is missing commits as `UnimplementedNativeView`.
56
+ */
57
+ export const EXPO_VIEWS = {
58
+ /** `expo-image`. Takes `source` as a list, so a single `{uri}` is wrapped by the caller. */
59
+ 'expo-image': ['ExpoImage', { defaultProps: { contentFit: 'cover' } }],
60
+ /** `expo-blur`. The one thing CSS cannot express: a blur of what is *behind* a view. */
61
+ 'expo-blur': ['ExpoBlurView', { defaultProps: { intensity: 50, tint: 'default' } }],
62
+ /** `expo-video`. The player itself is a shared object the module hands out; this is its view. */
63
+ 'expo-video': ['ExpoVideo', { viewName: 'VideoView' }],
64
+ /**
65
+ * `expo-camera`. Its module's first view, so its default: `ExpoCamera_CameraView` exists only on
66
+ * iOS, after the Swift class, and commits as nothing on Android. `Camera` takes its pictures.
67
+ */
68
+ 'expo-camera': ['ExpoCamera'],
69
+ /** `expo-apple-authentication`: Apple's Sign in with Apple button. iOS only. */
70
+ 'apple-sign-in-button': ['ExpoAppleAuthentication'],
71
+ /** `expo-symbols`: SF Symbols, iOS only, and the reason `icons` is not needed for them. */
72
+ 'expo-symbol': ['SymbolModule', { defaultProps: { type: 'monochrome' } }],
73
+ /** `expo-gl`, whose context is reached through an event rather than a prop. */
74
+ 'expo-gl': ['ExpoGL'],
75
+ /** `expo-glass-effect`: the iOS 26 material. Renders as a plain view where it is unavailable. */
76
+ 'expo-glass': ['ExpoGlassEffect', { viewName: 'GlassView' }],
77
+ /** Glass views that merge into one another when they come within `spacing` points. */
78
+ 'expo-glass-container': ['ExpoGlassEffect', { viewName: 'GlassContainer' }],
79
+ /** `expo-mesh-gradient`: the one gradient CSS cannot express, because it has no CSS spelling. */
80
+ 'expo-mesh-gradient': ['ExpoMeshGradient', { viewName: 'MeshGradientView' }],
81
+ /** `expo-live-photo`, iOS only. */
82
+ 'expo-live-photo': ['ExpoLivePhoto', { viewName: 'LivePhotoView' }],
83
+ /**
84
+ * `expo-maps`, Google's on Android and Apple's on iOS: two modules, each with one view, so each
85
+ * its module's default. `ExpoMaps` itself is the permissions module and has no view.
86
+ * `MapView` (`<expo-map>`) types both as one element.
87
+ */
88
+ 'expo-maps-google': ['ExpoGoogleMaps'],
89
+ 'expo-maps-apple': ['ExpoAppleMaps'],
90
+ };
91
+ /**
92
+ * Register several of the known views by element name.
93
+ *
94
+ * ```ts
95
+ * registerExpoViews('expo-image', 'expo-blur');
96
+ * ```
97
+ *
98
+ * Named one at a time rather than all at once on purpose: registering a view for a module the app
99
+ * has not installed produces an element that commits as nothing, and finding out at startup which
100
+ * modules are present is not something JavaScript can do without importing them all.
101
+ */
102
+ export function registerExpoViews(...elements) {
103
+ for (const element of elements) {
104
+ const known = EXPO_VIEWS[element];
105
+ if (!known)
106
+ throw new Error(`[angular-native] no Expo view is known as '${element}'`);
107
+ registerExpoView(element, known[0], known[1]);
108
+ }
109
+ }
110
+ //# sourceMappingURL=register-expo-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-expo-view.js","sourceRoot":"","sources":["../src/register-expo-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAUrD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,QAAiB;IAChE,MAAM,UAAU,GAAI,UAAoC,CAAC,IAAI,EAAE,uBAAuB,IAAI,EAAE,CAAC;IAC7F,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;IACnE,OAAO,sBAAsB,MAAM,GAAG,MAAM,EAAE,CAAC;AACjD,CAAC;AAaD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAAmB,EACnB,UAAkB,EAClB,OAAyB;IAEzB,gBAAgB,CAAC,WAAW,EAAE,YAAY,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;AACpG,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,UAAU,GAAyD;IAC9E,4FAA4F;IAC5F,YAAY,EAAE,CAAC,WAAW,EAAE,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;IACtE,wFAAwF;IACxF,WAAW,EAAE,CAAC,cAAc,EAAE,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IACnF,iGAAiG;IACjG,YAAY,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACtD;;;OAGG;IACH,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,gFAAgF;IAChF,sBAAsB,EAAE,CAAC,yBAAyB,CAAC;IACnD,2FAA2F;IAC3F,aAAa,EAAE,CAAC,cAAc,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC;IACzE,+EAA+E;IAC/E,SAAS,EAAE,CAAC,QAAQ,CAAC;IACrB,iGAAiG;IACjG,YAAY,EAAE,CAAC,iBAAiB,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IAC5D,sFAAsF;IACtF,sBAAsB,EAAE,CAAC,iBAAiB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC3E,iGAAiG;IACjG,oBAAoB,EAAE,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC5E,mCAAmC;IACnC,iBAAiB,EAAE,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;IACnE;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,gBAAgB,CAAC;IACtC,iBAAiB,EAAE,CAAC,eAAe,CAAC;CACrC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAG,QAA8C;IACjF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,OAAO,GAAG,CAAC,CAAC;QACtF,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC","sourcesContent":["/**\n * Expo's native views, driven directly.\n *\n * An Expo module that renders something registers a Fabric component whose name it derives\n * rather than declares, and `requireNativeViewManager` is the only place that derivation is\n * written down. Everything else that function does is React: it caches a host component, builds\n * a view config for React's renderer, and wraps the result in a class whose whole job is to hold\n * a ref. None of that is reachable without React, and none of it is needed - the engine writes\n * props onto the shadow node itself, and Expo's Fabric views take their props as an untyped map,\n * so there is no codegen'd prop list to satisfy.\n *\n * What is left is the name. That is what this package is.\n *\n * No decorators and no Angular here on purpose: this is a registration, and the components it\n * registers are ordinary elements in a template.\n */\nimport { registerViewName } from '@ng-native/fabric';\n\n/**\n * What Expo installs on the global when the app starts. Only the app identifier matters here;\n * `getViewConfig` is for React's renderer, which never runs.\n */\ninterface ExpoGlobal {\n readonly __expo_app_identifier__?: string;\n}\n\n/**\n * The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes\n * it in `expo-modules-core`.\n *\n * The app identifier is the part that cannot be hardcoded. Expo Go runs many projects in one\n * binary, so it namespaces every view name with a per-app suffix; a standalone build has no\n * identifier and no suffix. Get it wrong and the view commits as `UnimplementedNativeView`\n * with no error, which is the same failure mode as a misspelled name.\n *\n * A module with more than one view names the extra ones (`viewName`); the default view is the\n * module itself.\n */\nexport function expoViewName(moduleName: string, viewName?: string): string {\n const identifier = (globalThis as { expo?: ExpoGlobal }).expo?.__expo_app_identifier__ ?? '';\n const suffix = identifier ? `_${identifier}` : '';\n const module = viewName ? `${moduleName}_${viewName}` : moduleName;\n return `ViewManagerAdapter_${module}${suffix}`;\n}\n\nexport interface ExpoViewOptions {\n /** A named view on a module that has several. Omit for the module's default view. */\n readonly viewName?: string;\n /**\n * Props the module's React component would have applied before native saw them. Expo's\n * wrappers do less of this than React Native's, but they do some: `expo-image` resolves a\n * source to an array and a `contentFit` to a string native understands.\n */\n readonly defaultProps?: Record<string, unknown>;\n}\n\n/**\n * Teach the engine an element name for an Expo module's view, e.g.\n * `registerExpoView('expo-image', 'ExpoImage')` makes `<expo-image>` commit as one.\n *\n * Call it at startup, before the first commit that uses the element. The app must have the\n * module installed so the native side registers the component; this is the JavaScript half only.\n */\nexport function registerExpoView(\n elementName: string,\n moduleName: string,\n options?: ExpoViewOptions,\n): void {\n registerViewName(elementName, expoViewName(moduleName, options?.viewName), options?.defaultProps);\n}\n\n/**\n * The views worth knowing the names of, so an app does not have to look each one up.\n *\n * Deliberately names and defaults, not components. An Angular component per Expo view, prop for\n * prop, is a maintenance burden with no end and a second place for every prop to be wrong; the\n * element plus the module's own documentation covers the common case, and an app that wants a\n * nicer surface writes the one component it actually needs.\n *\n * A module still has to be installed for its element to commit as anything: this is the\n * JavaScript half. An element whose native side is missing commits as `UnimplementedNativeView`.\n */\nexport const EXPO_VIEWS: Readonly<Record<string, [string, ExpoViewOptions?]>> = {\n /** `expo-image`. Takes `source` as a list, so a single `{uri}` is wrapped by the caller. */\n 'expo-image': ['ExpoImage', { defaultProps: { contentFit: 'cover' } }],\n /** `expo-blur`. The one thing CSS cannot express: a blur of what is *behind* a view. */\n 'expo-blur': ['ExpoBlurView', { defaultProps: { intensity: 50, tint: 'default' } }],\n /** `expo-video`. The player itself is a shared object the module hands out; this is its view. */\n 'expo-video': ['ExpoVideo', { viewName: 'VideoView' }],\n /**\n * `expo-camera`. Its module's first view, so its default: `ExpoCamera_CameraView` exists only on\n * iOS, after the Swift class, and commits as nothing on Android. `Camera` takes its pictures.\n */\n 'expo-camera': ['ExpoCamera'],\n /** `expo-apple-authentication`: Apple's Sign in with Apple button. iOS only. */\n 'apple-sign-in-button': ['ExpoAppleAuthentication'],\n /** `expo-symbols`: SF Symbols, iOS only, and the reason `icons` is not needed for them. */\n 'expo-symbol': ['SymbolModule', { defaultProps: { type: 'monochrome' } }],\n /** `expo-gl`, whose context is reached through an event rather than a prop. */\n 'expo-gl': ['ExpoGL'],\n /** `expo-glass-effect`: the iOS 26 material. Renders as a plain view where it is unavailable. */\n 'expo-glass': ['ExpoGlassEffect', { viewName: 'GlassView' }],\n /** Glass views that merge into one another when they come within `spacing` points. */\n 'expo-glass-container': ['ExpoGlassEffect', { viewName: 'GlassContainer' }],\n /** `expo-mesh-gradient`: the one gradient CSS cannot express, because it has no CSS spelling. */\n 'expo-mesh-gradient': ['ExpoMeshGradient', { viewName: 'MeshGradientView' }],\n /** `expo-live-photo`, iOS only. */\n 'expo-live-photo': ['ExpoLivePhoto', { viewName: 'LivePhotoView' }],\n /**\n * `expo-maps`, Google's on Android and Apple's on iOS: two modules, each with one view, so each\n * its module's default. `ExpoMaps` itself is the permissions module and has no view.\n * `MapView` (`<expo-map>`) types both as one element.\n */\n 'expo-maps-google': ['ExpoGoogleMaps'],\n 'expo-maps-apple': ['ExpoAppleMaps'],\n};\n\n/**\n * Register several of the known views by element name.\n *\n * ```ts\n * registerExpoViews('expo-image', 'expo-blur');\n * ```\n *\n * Named one at a time rather than all at once on purpose: registering a view for a module the app\n * has not installed produces an element that commits as nothing, and finding out at startup which\n * modules are present is not something JavaScript can do without importing them all.\n */\nexport function registerExpoViews(...elements: readonly (keyof typeof EXPO_VIEWS)[]): void {\n for (const element of elements) {\n const known = EXPO_VIEWS[element];\n if (!known) throw new Error(`[angular-native] no Expo view is known as '${element}'`);\n registerExpoView(element, known[0], known[1]);\n }\n}\n"]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `ScreenCapture`, bound to `expo-screen-capture`: keeping a screen out of screenshots and
3
+ * recordings, and knowing when one was taken.
4
+ *
5
+ * ```ts
6
+ * private readonly capture = inject(ScreenCapture);
7
+ *
8
+ * constructor() {
9
+ * void this.capture.prevent('card-details');
10
+ * inject(DestroyRef).onDestroy(() => void this.capture.allow('card-details'));
11
+ * }
12
+ * ```
13
+ *
14
+ * `prevent()` and `allow()` take a key, and capture stays prevented while any key is still held:
15
+ * two screens that each prevent it do not undo each other. `screenshots` counts the screenshots
16
+ * the user has taken, and the listener behind it lives as long as the app does and is removed
17
+ * when the app is destroyed. Without the module installed, nothing is prevented and the count
18
+ * stays at zero.
19
+ */
20
+ import { InjectionToken, type Signal } from '@angular/core';
21
+ import { Permission } from './permissions.ts';
22
+ import * as i0 from "@angular/core";
23
+ type Expo = typeof import('expo-screen-capture');
24
+ /** The module's functions this service calls. The real module is one; a test provides a fake. */
25
+ export type NativeScreenCapture = Pick<Expo, 'isAvailableAsync' | 'preventScreenCaptureAsync' | 'allowScreenCaptureAsync' | 'enableAppSwitcherProtectionAsync' | 'disableAppSwitcherProtectionAsync' | 'addScreenshotListener' | 'getPermissionsAsync' | 'requestPermissionsAsync'>;
26
+ export declare class ScreenCapture {
27
+ /** Overridden in a test to take screenshots nobody took. */
28
+ static readonly SOURCE: InjectionToken<NativeScreenCapture | null>;
29
+ private readonly native;
30
+ private readonly count;
31
+ /**
32
+ * The permission screenshot detection needs on Android 13 and earlier, where it reads the
33
+ * photo library. Later Android needs none, and iOS always answers granted.
34
+ */
35
+ readonly permission: Permission;
36
+ /** How many screenshots the user has taken while the app was in front. */
37
+ readonly screenshots: Signal<number>;
38
+ constructor();
39
+ /** Whether the platform can prevent capture. */
40
+ available(): Promise<boolean>;
41
+ /**
42
+ * Keeps the app out of screenshots and screen recordings until `allow()` is called with the same
43
+ * key, or the app restarts. Omitting the key uses one shared default key.
44
+ */
45
+ prevent(key?: string): Promise<void>;
46
+ /** Releases the key; capture is allowed again once no key is held. */
47
+ allow(key?: string): Promise<void>;
48
+ /**
49
+ * iOS: blurs the app while it is not in focus - in the app switcher, in the background, and
50
+ * during interruptions - by `blurIntensity` from 0 to 1, half by default. Android already hides
51
+ * it in the app switcher while capture is prevented.
52
+ */
53
+ protectAppSwitcher(blurIntensity?: number): Promise<void>;
54
+ unprotectAppSwitcher(): Promise<void>;
55
+ static ɵfac: i0.ɵɵFactoryDeclaration<ScreenCapture, never>;
56
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
57
+ }
58
+ export {};