@ng-native/expo 0.2.0 → 0.4.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 (83) hide show
  1. package/README.md +32 -382
  2. package/dist/async-storage.d.ts +15 -0
  3. package/dist/async-storage.js +29 -0
  4. package/dist/async-storage.js.map +1 -0
  5. package/dist/audio.d.ts +10 -0
  6. package/dist/audio.js +26 -0
  7. package/dist/audio.js.map +1 -0
  8. package/dist/community-views.js +1 -1
  9. package/dist/community-views.js.map +1 -1
  10. package/dist/crypto.js +2 -2
  11. package/dist/crypto.js.map +1 -1
  12. package/dist/database.d.ts +18 -3
  13. package/dist/database.js +26 -7
  14. package/dist/database.js.map +1 -1
  15. package/dist/date-time-picker.d.ts +33 -0
  16. package/dist/date-time-picker.js +77 -0
  17. package/dist/date-time-picker.js.map +1 -0
  18. package/dist/dom-component.d.ts +5 -0
  19. package/dist/dom-component.js +57 -2
  20. package/dist/dom-component.js.map +1 -1
  21. package/dist/expo-ui-components.d.ts +292 -12
  22. package/dist/expo-ui-components.js +531 -41
  23. package/dist/expo-ui-components.js.map +1 -1
  24. package/dist/expo-ui.js +19 -6
  25. package/dist/expo-ui.js.map +1 -1
  26. package/dist/file-system.d.ts +7 -0
  27. package/dist/file-system.js +16 -3
  28. package/dist/file-system.js.map +1 -1
  29. package/dist/foldable.d.ts +78 -0
  30. package/dist/foldable.js +87 -0
  31. package/dist/foldable.js.map +1 -0
  32. package/dist/fonts.d.ts +3 -9
  33. package/dist/fonts.js +26 -16
  34. package/dist/fonts.js.map +1 -1
  35. package/dist/glass.d.ts +1 -1
  36. package/dist/image-picker.js +3 -9
  37. package/dist/image-picker.js.map +1 -1
  38. package/dist/index.d.ts +3 -2
  39. package/dist/index.js +3 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/live-activity.d.ts +88 -0
  42. package/dist/live-activity.js +156 -0
  43. package/dist/live-activity.js.map +1 -0
  44. package/dist/location.d.ts +17 -0
  45. package/dist/location.js +20 -7
  46. package/dist/location.js.map +1 -1
  47. package/dist/media-library.js +1 -3
  48. package/dist/media-library.js.map +1 -1
  49. package/dist/native.d.ts +12 -2
  50. package/dist/native.js +81 -10
  51. package/dist/native.js.map +1 -1
  52. package/dist/notifications.js +2 -4
  53. package/dist/notifications.js.map +1 -1
  54. package/dist/permissions.d.ts +2 -0
  55. package/dist/permissions.js +6 -0
  56. package/dist/permissions.js.map +1 -1
  57. package/dist/player.d.ts +24 -11
  58. package/dist/player.js +7 -26
  59. package/dist/player.js.map +1 -1
  60. package/dist/register-expo-view.d.ts +7 -0
  61. package/dist/register-expo-view.js +6 -1
  62. package/dist/register-expo-view.js.map +1 -1
  63. package/dist/screen-capture.js +2 -4
  64. package/dist/screen-capture.js.map +1 -1
  65. package/dist/secure-store.d.ts +15 -0
  66. package/dist/secure-store.js +30 -0
  67. package/dist/secure-store.js.map +1 -0
  68. package/dist/store.d.ts +5 -10
  69. package/dist/store.js +5 -36
  70. package/dist/store.js.map +1 -1
  71. package/dist/tracking.d.ts +1 -1
  72. package/dist/tracking.js +3 -5
  73. package/dist/tracking.js.map +1 -1
  74. package/dist/video.d.ts +5 -0
  75. package/dist/video.js +21 -0
  76. package/dist/video.js.map +1 -0
  77. package/dist/watch.d.ts +103 -0
  78. package/dist/watch.js +228 -0
  79. package/dist/watch.js.map +1 -0
  80. package/dist/widget.d.ts +63 -0
  81. package/dist/widget.js +98 -0
  82. package/dist/widget.js.map +1 -0
  83. package/package.json +53 -4
package/README.md CHANGED
@@ -14,32 +14,18 @@ npm install @angular/core expo
14
14
  Every Expo module (`expo-haptics`, `expo-clipboard`, `expo-file-system`, and the rest) is an
15
15
  optional peer dependency: install only the ones the app actually uses.
16
16
 
17
- ## Services
18
-
19
- One entry point per module, so importing haptics does not bundle the file system. Every source
20
- file lives in `src/`, as in every other package here; the entry points an app writes do not say so,
21
- because the `exports` map takes `@ng-native/expo/haptics` to `src/haptics.ts`. Most are
22
- `@Service()` classes that reach their native module through a static `SOURCE` token; `Fonts`,
23
- `SplashScreen`, `Storage`, `SecureStorage` and the sensors are `InjectionToken`s carrying their own
24
- factory. Either way there is nothing to provide and nothing to register - injecting it is the whole
25
- setup, and a service nobody injects is never constructed. Each token name is a type as well, so
26
- `inject(Storage)` and `private store: Storage` both work, as `inject(Clipboard)` does for a class.
17
+ ## Example
27
18
 
28
19
  ```ts
29
20
  import { Clipboard } from '@ng-native/expo/clipboard';
30
21
  import { FileSystem } from '@ng-native/expo/file-system';
31
- import { Fonts } from '@ng-native/expo/fonts';
32
22
  import { Haptics } from '@ng-native/expo/haptics';
33
- import { SplashScreen } from '@ng-native/expo/splash-screen';
34
23
 
35
24
  export class Notes {
36
25
  private readonly clipboard = inject(Clipboard);
37
26
  private readonly files = inject(FileSystem);
38
27
  private readonly haptics = inject(Haptics);
39
28
 
40
- /** A signal: how many times the pasteboard has changed. */
41
- protected readonly changes = this.clipboard.changes;
42
-
43
29
  protected async save(text: string): Promise<void> {
44
30
  this.files.write(this.files.document('notes.txt'), text);
45
31
  await this.clipboard.write(text);
@@ -48,370 +34,34 @@ export class Notes {
48
34
  }
49
35
  ```
50
36
 
51
- The names say what the thing is rather than who ships it, so app code asks for a clipboard rather
52
- than for a package. Two shapes are worth knowing about:
53
-
54
- - `Clipboard.changes` counts pasteboard changes; it does not hold the contents. On iOS 16 and
55
- later, _reading_ the clipboard is what prompts the user for permission, so a signal that held
56
- the text would prompt on every change - including changes made by other apps. The notification
57
- is free and the read is explicit.
58
- - `Fonts` is the odd one out: the method that matters is called _before_ the injector exists.
59
- See below.
60
- - `Haptics` returns nothing and swallows failures. Every Expo equivalent is a promise, and nobody
61
- awaits a vibration; a device with no Taptic Engine would otherwise produce an unhandled
62
- rejection for a feature that is by definition optional.
63
-
64
- ## What this package costs to install
65
-
66
- One package. Every Expo module is an **optional peer dependency**, so `pnpm add
67
- @ng-native/expo` adds exactly itself - an app installs `expo-haptics` because it wants
68
- haptics, not because this asked for it. There is one entry point per module and each imports only
69
- its own, so reaching for haptics does not pull in the video player.
70
-
71
- The version ranges track the SDK rather than each package, because since SDK 57 Expo does too:
72
- `expo-battery` and `expo-video` are both `57.x` and ship together. That is also why this is one
73
- package and not twenty: twenty would have to be version-bumped in lockstep anyway, and would need
74
- a twenty-first to share `Permission`, `observed` and the rest.
75
-
76
- Each service is one file: a `@Service()` class that reaches its module through an injected source
77
- token. Nothing in the package statically imports an Expo module, because a file that does cannot be
78
- loaded by Node at all - Expo's build output uses extensionless relative imports, and what it pulls
79
- in reaches `react-native`, which is Flow. A `require` inside the token's factory has neither
80
- problem: Metro still resolves the string literal and bundles the module exactly as before, and in
81
- Node the call throws and the service goes inert. On a device, a module the app should have and does
82
- not - never installed, or installed without the app being rebuilt since - throws a
83
- `MissingModuleError` naming it and the commands that fix it.
84
-
85
- That token is also the test seam, and it hangs off the service rather than sitting beside it -
86
- `Clipboard.SOURCE`, not `CLIPBOARD_SOURCE`. One export per capability, and a token that cannot
87
- drift away from the thing it belongs to. A fake is _provided_ rather than passed, so a test drives
88
- the real service:
89
-
90
- ```ts
91
- const clipboard = serviceWith(Clipboard.SOURCE, fake, () => new Clipboard());
92
- ```
93
-
94
- Four are not services and say so: `Sensor` and `Store` are one class behind several tokens
95
- (`Accelerometer` and `Barometer` are the same class; so are `Storage` and `SecureStorage`), and
96
- `Fonts` and `SplashScreen` are called before an injector exists - `splashScreen.hold()` is the
97
- first thing an entry file does. Those four take their platform as a constructor argument.
98
-
99
- The app still installs `expo-haptics`, `expo-clipboard` and `expo-file-system` itself. That is
100
- what links the native side; whether we import their JavaScript is a separate question, and
101
- `Haptics` does not.
102
-
103
- ## Fonts
104
-
105
- A font on a phone is not fetched when the text engine first needs it: it has to be registered
106
- with the platform before anything is laid out, or the first paint is in the fallback face and
107
- reflows when the real one lands. So a face is declared in CSS, collected at build time, and
108
- registered before the app mounts:
109
-
110
- ```css
111
- @font-face {
112
- font-family: Inter;
113
- src: url('./fonts/Inter-Regular.ttf');
114
- }
115
- @font-face {
116
- font-family: Inter;
117
- src: url('./fonts/Inter-Bold.ttf');
118
- font-weight: 700;
119
- }
120
- ```
121
-
122
- ```ts
123
- import { loadFonts } from '@ng-native/expo/fonts';
124
-
125
- await loadFonts(styleSheetOf(GlobalStyles));
126
- mount(rootTag, App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });
127
- ```
128
-
129
- `font-family: Inter` then means what it says. The `url()` becomes a `require` at build time, so
130
- the bundler ships the file - a plain path would be a font that is simply missing on the device
131
- with nothing anywhere to say why.
132
-
133
- **Weights and styles match as on the web.** Native looks a family up by name and that is all, so
134
- the second face above is registered as `Inter-700` as well, and the engine points a text with
135
- `font-family: Inter; font-weight: 700` at it, by CSS's matching rules, wherever the family and
136
- weight were set.
137
-
138
- `loadFonts` takes any number of sheets and does nothing at all when none of them declare a face,
139
- so bootstrap can call it unconditionally. Injecting `Fonts` afterwards answers what is loaded.
140
-
141
- ## Device state
142
-
143
- Every module that reports something changing has the same three parts: a getter, a listener, and
144
- a `useX()` hook that is those two plus React state. The hook is the only surface some of them
145
- offer for reading a value _over time_, so it is what is rebuilt here - once, as a signal - and
146
- each module is a thin binding onto it.
147
-
148
- ```ts
149
- private readonly network = inject(Network);
150
- private readonly battery = inject(Battery);
151
-
152
- protected readonly offline = computed(() => this.network.reachable() === false);
153
- protected readonly thrifty = computed(() => this.battery.saving() || this.battery.low());
154
- ```
155
-
156
- - `Network.reachable()` is `null` until the platform establishes it, which is **not** the same as
157
- offline: a captive-portal wifi is connected and reachable by nothing, and an offline banner that
158
- treats `null` as false flashes on every cold start.
159
- - `Battery.low()` is under a fifth _and not charging_; plugged in at 15% is not a reason to do
160
- less. Both signals start at their optimistic value, so an app never opens in its degraded mode.
161
- - `DeviceOrientation` is the device, not the window. A layout asking which way _it_ is laid out
162
- wants the `orientation` media feature and needs none of this; a camera preview needs this,
163
- because a screen pinned to portrait still has a phone that is sideways.
164
- - `Brightness.set()` and `DeviceOrientation.lock()` both return the function that undoes them,
165
- ready for `DestroyRef.onDestroy`. An app that leaves the screen at full brightness after showing
166
- a boarding pass is one the user experiences as a battery fault.
167
-
168
- ### Sensors and locale
169
-
170
- The sensors are one class parameterized by the reading, not one per sensor - they are all the same
171
- object underneath. Nothing subscribes until `start`, and the interval is explicit:
172
-
173
- ```ts
174
- inject(DestroyRef).onDestroy(inject(Accelerometer).start(50));
175
- ```
176
-
177
- The sensible interval is a property of what the app is doing with it, a compass wants a tenth of a
178
- second and a shake detector less, and every event is a change-detection pass - so a sensor left at
179
- its default is a phone that never idles, and a sensor nobody stops is a battery nobody gets back.
180
-
181
- `Locale` is the odd one: both getters are synchronous, so there is no default to sit at. It is a
182
- service because the answers _change_ - a user can switch language in Settings and come back, and a
183
- date that was formatted correctly is then wrong. The list is ordered by preference and always has
184
- at least one entry, which is the reason to prefer it over a single locale string: an app that
185
- supports the user's second language should use it rather than fall back to English. `rtl()` is a
186
- layout decision rather than a translation one, which is why it is a signal.
187
-
188
- ## Storage
189
-
190
- Both stores are already plain promises, so what is worth having is not a wrapper but a binding: a
191
- persisted value as a signal that writes through when set.
192
-
193
- ```ts
194
- private readonly store = inject(Storage);
195
- protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');
196
- ```
197
-
198
- Reading it is reading the preference; `theme.set('dark')` persists. The same key always hands back
199
- the same signal, so two components binding one preference stay in step, and a value written before
200
- the read comes back is not undone by it.
201
-
202
- `SecureStorage` is the keychain and the Android keystore - separate from `Storage` because the
203
- difference should be visible at the injection site. It can also answer synchronously, so a token
204
- never flashes its default.
205
-
206
- ## Permissions
207
-
208
- Every Expo module that needs one spells it the same way - `getXPermissionsAsync`,
209
- `requestXPermissionsAsync`, and a `useXPermissions()` hook that is those two plus React state. So
210
- there is one class here rather than one facade per module, and the module stays the app's own
211
- dependency:
212
-
213
- ```ts
214
- import { requestCameraPermissionsAsync, getCameraPermissionsAsync } from 'expo-camera';
215
- import { Permission } from '@ng-native/expo';
216
-
217
- readonly camera = Permission.of(getCameraPermissionsAsync, requestCameraPermissionsAsync);
218
-
219
- protected async open(): Promise<void> {
220
- if (await this.camera.ensure()) this.scanning.set(true);
221
- }
222
- ```
223
-
224
- `ensure()` is the method most call sites want: it checks first, so a permission the user granted
225
- months ago shows no dialog, and it does not ask when the platform has stopped asking - which
226
- would resolve to the same no while reading as though the user had been consulted. `blocked()` is
227
- how an app knows to offer Settings instead.
228
-
229
- `status()` distinguishes `unknown` from `undetermined`: nobody has asked the platform yet, versus
230
- the platform saying the user has not decided.
231
-
232
- ## Splash screen
233
-
234
- The ordering is the whole feature, and it is easy to get subtly wrong. The native splash hides
235
- itself as soon as the first frame is drawn, so an app that loads fonts _before_ mounting shows a
236
- blank window for as long as that takes:
237
-
238
- ```ts
239
- import { splashScreen } from '@ng-native/expo/splash-screen';
240
- import { loadFonts } from '@ng-native/expo/fonts';
241
-
242
- splashScreen.hold(); // module scope, before anything renders
243
-
244
- AppRegistry.registerRunnable('main', ({ rootTag }) => {
245
- const fonts = loadFonts(styleSheetOf(GlobalStyles));
246
- mount(Number(rootTag), App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });
247
- void splashScreen.hideWhenReady(fonts);
248
- });
249
- ```
250
-
251
- Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive is
252
- laid out again as each face registers, behind the splash, so nothing is ever seen in the fallback
253
- face.
254
- `hideWhenReady` waits one frame after the work, because hiding the instant it resolves uncovers
255
- the frame that was on screen while it ran - and it uncovers even when the work fails, rather than
256
- leaving a splash screen up forever.
257
-
258
- ## Media
259
-
260
- `useVideoPlayer` and `useAudioPlayer` are not where the player comes from - `createVideoPlayer`
261
- gives the same object. What the hooks add is the two things a component needs and cannot get
262
- otherwise: the player is _released_ when the component goes, and its changing state is _readable_,
263
- which its own mutable properties are not.
264
-
265
- ```ts
266
- protected readonly player = videoPlayer(source, { timeUpdate: 0.5 });
267
- ```
268
-
269
- ```html
270
- <expo-video [player]="player.native" /> <text>{{ player.state().currentTime }}</text>
271
- ```
272
-
273
- The player itself is passed straight through: it already has `play()`, `seekBy()` and the rest,
274
- and wrapping them would be a second place for each to be wrong. `timeUpdate` is off unless asked
275
- for, because it is the most frequent event either player emits and a screen with no progress bar
276
- should not pay for one.
277
-
278
- ## Notifications
279
-
280
- Only the part React was holding. Scheduling, channels, categories, badges and tokens are already
281
- plain promises, and an app calls those on the module directly.
282
-
283
- What is here is the difference between a notification _arriving_ and a user _tapping_ one - the
284
- second is a navigation instruction - and the response that **launched** the app, which happened
285
- before any listener existed. Missing that is the single most common way notification routing is
286
- got wrong.
287
-
288
- ```ts
289
- const tapped = inject(Notifications).take();
290
- if (tapped) this.router.navigateByUrl(routeFor(tapped));
291
- ```
292
-
293
- `take()` rather than the signal, because routing on a tap is a one-off and a signal is not: an
294
- effect on one fires again whenever its component is recreated, sending the user back to a screen
295
- they had navigated away from. A second tap on the same notification is a new instruction and is
296
- handed over again.
297
-
298
- Pair it with `Permission.of(getPermissionsAsync, requestPermissionsAsync)` from the module.
299
-
300
- ## Photos, files, location and privacy
301
-
302
- - **`ImagePicker`** (`image-picker.ts`): `pick()` and `capture()`, each resolving to the picked
303
- assets, empty if they canceled. `capture()` asks for the camera itself.
304
- - **`Location`** (`location.ts`): a `position` signal, filled by `current(accuracy)` once or by
305
- `start({ accuracy, distance })` until the function it returns is called.
306
- - **`Biometrics`** (`biometrics.ts`): `available()`, `kinds()` and `authenticate(message)`, which
307
- fails rather than passes when the module is missing.
308
- - **`Browser`** (`browser.ts`): `open(url)`, and `signIn(url, redirectUrl)`, which resolves to the
309
- URL the sign-in page redirected back to, or null.
310
- - **`AppInfo`** (`app-info.ts`): the version, build, identifier and device, as plain values.
311
- - **`Camera`** (`camera.ts`): a directive on `<expo-camera>` whose `takePicture()` takes a picture
312
- from the view on screen, without a React ref.
313
- - **`StoreReview`** (`store-review.ts`): `available()`, `hasAction()`, `request()` and
314
- `storeUrl()`. Whether the prompt appears is the platform's decision, and a call that resolves is
315
- not a prompt that was shown.
316
- - **`Tracking`** (`tracking.ts`): App Tracking Transparency's `permission`, `available()` and
317
- `advertisingId()`, which is null until tracking is allowed.
318
- - **`DocumentPicker`** (`document-picker.ts`): `pick(options)`, resolving to the files picked,
319
- empty if they canceled.
320
- - **`Crypto`** (`crypto.ts`): `randomUUID()`, `digestString()`, `digest()`, `randomBytes()` and
321
- `randomValues()`. The one service that throws without its module, since an empty identifier or
322
- hash is a wrong answer that looks right.
323
- - **`BackgroundTask`** (`background-task.ts`): `status()`, `register(name, options)`,
324
- `unregister(name)` and `triggerForTesting()`. The task itself is defined with
325
- `expo-task-manager` at the top level of `main.ts`, since the platform runs it without
326
- bootstrapping Angular.
327
- - **`ScreenCapture`** (`screen-capture.ts`): `prevent(key)` and `allow(key)`, which stay
328
- prevented while any key is held, and a `screenshots` signal counting the screenshots taken.
329
- - **`ImageEditor`** (`image-editor.ts`): `edit(uri, actions, options)` over
330
- `expo-image-manipulator`, resolving to the saved file and releasing the native context and
331
- image it made, and `manipulate(uri)` for the module's own context.
332
- - **`MediaLibrary`** (`media-library.ts`): `save(uri, album)`, which asks for write-only access
333
- itself, `assets(query)` and `metadata(query)` over the module's own `Query`, albums, the read
334
- and write permissions, and `watch()` for changes, which starts listening only when called.
335
-
336
- ## The rest
337
-
338
- Four more modules whose only entry point was a hook, each the same story: the work underneath is a
339
- plain promise, and what the hook adds is a _lifecycle_.
340
-
341
- - **`database(name, migrations)`** replaces `SQLiteProvider` and `useSQLiteContext`. Opened on
342
- first use rather than at startup - a database opened eagerly is a file handle and a journal for
343
- an app that may never read from it - and opened _once_ however many callers arrive while it is
344
- opening, because two connections to one file is a lock waiting to happen. Migrations run in
345
- order, each in a transaction, with the version recorded in SQLite's own `user_version`.
346
- - **`Updates`** checks and downloads in one call, because an app that checks without downloading
347
- has learned something it cannot act on. `apply()` **restarts the app**, which is why nothing here
348
- does it automatically: the moment is one the app knows and this does not. In Expo Go it reports
349
- itself disabled rather than offering a banner that can never resolve.
350
- - **`assets(modules)`** preloads the images a screen should not pop in with, and resolves even when
351
- one fails - a picture that will not appear is not a reason for the screen behind it to fail too.
352
- - **`KeepAwake.hold()`** returns the release, and is never a bare activate: a screen that holds the
353
- display on and never lets go is a phone that never sleeps, which the user experiences as a
354
- battery fault and never attributes to the app that caused it.
355
-
356
- ## Views
357
-
358
- Most Expo modules need nothing from this package. `requireNativeModule`, `EventEmitter`,
359
- `SharedObject` and `SharedRef` have no React in them, so a module that only calls into native is
360
- imported and used as its own documentation says - the services above are a nicer surface, not a
361
- shim. Autolinking is unaffected too: it is driven by the `expo` package and the
362
- `expo-module.config.json` in each module, neither of which knows what renderer is above it.
363
-
364
- What does need something is a module that renders. `requireNativeViewManager` returns a React
365
- class, and the Fabric component underneath it is registered under a name derived from the module
366
- name and, in Expo Go, from a per-project identifier. `registerExpoView` derives the same name and
367
- hands it to the engine:
368
-
369
- ```ts
370
- import {
371
- registerExpoView,
372
- registerExpoViews,
373
- registerExpoUiViews,
374
- registerNativeViews,
375
- } from '@ng-native/expo';
376
-
377
- registerExpoViews('expo-image', 'expo-blur'); // Expo's own
378
- registerNativeViews('web-view', 'slider'); // the community libraries in Expo's bundled list
379
- registerExpoUiViews(Platform.OS); // every @expo/ui control the platform has
380
- registerExpoView('my-module-view', 'MyModule'); // anything else
381
- ```
382
-
383
- ### @expo/ui
384
-
385
- The closest thing in the ecosystem to what this project is for: every one of these is a real
386
- platform control - a real `Picker`, a real `BottomSheet`, a real `Gauge` - rather than something
387
- drawn to look like one. `@expo/ui` reaches them through `requireNativeView('ExpoUI', ...)`, which
388
- is the derivation `registerExpoView` already mirrors, so the whole surface is a table of names.
389
-
390
- Element names are platform-neutral where both platforms have the control, so an app writes
391
- `<ui-slider>` once and `<ui-vstack>` gets Compose's `Column` on Android. Where only one platform
392
- has it - a `Gauge` is SwiftUI's, a `SearchBar` is Compose's - the element exists only there, since
393
- registering for the wrong platform is an element that commits as nothing.
394
-
395
- **A `<ui-host>` is required around any of them.** SwiftUI and Compose lay out their own subtrees,
396
- and the host view is what bridges Yoga's layout to theirs; without one the control has no size and
397
- does not appear - which looks exactly like a module that failed to install.
398
-
399
- `<expo-image>` is then an element like any other, and its props are the ones the module's native
400
- view declares - which is to say the ones the React component would have passed down, after
401
- whatever resolving it does in JavaScript:
402
-
403
- ```html
404
- <expo-image [source]="[{ uri }]" contentFit="cover" [transition]="{ duration: 400 }" />
405
- ```
406
-
407
- Most of that is deliberately not wrapped. An Angular component per Expo view, prop for prop, is
408
- a maintenance burden with no end; a name plus the module's own documentation covers the common
409
- case, and an app that wants a nicer surface can write the component it actually needs.
410
-
411
- The exception is strict templates, where an unknown element and every prop on it is a type error.
412
- The views apps reach for most (`ui-host`, `ui-menu`, `ui-button`, `ui-divider`, `ui-slot`,
413
- `ui-image`, `ui-text`, `ui-date-picker`) and `segmented-control` have thin typed components:
414
- import `UiHost`, `SegmentedControl` and the rest, and the element is typed while committing exactly
415
- as before. Keep registering the names too. The component supplies the types and the registration
416
- makes the element commit as the native view. Their events are outputs so `$event` is typed. The
417
- element's own event is what arrives, so bind them in a template.
37
+ One entry point per module, so importing haptics does not bundle the file system. Nothing is
38
+ provided anywhere - injecting a service is the whole setup, and a service nobody injects is never
39
+ constructed. See [Using a module](https://ng-native.com/packages/expo/using-a-module) for how the
40
+ `exports` map and the runtime fallback behind each service work.
41
+
42
+ ## What's in the package
43
+
44
+ - Device state - `Battery`, `Brightness`, `Network`, `Orientation`, `Locale`, the sensors, `Fonts`,
45
+ `AppInfo`, `KeepAwake`.
46
+ - Feedback - `Haptics`, `Clipboard`, `Notifications`, `StoreReview`.
47
+ - Storage and files - `Storage`, `SecureStorage`, `FileSystem`, `database()`, `assets()`.
48
+ - Media and camera - `ImagePicker`, `DocumentPicker`, `ImageEditor`, `MediaLibrary`, the `Camera`
49
+ directive, `videoPlayer()`/`audioPlayer()`.
50
+ - Location and identity - `Location`, `Biometrics`, Sign in with Apple, `Tracking`, `Crypto`,
51
+ `ScreenCapture`, `Browser`.
52
+ - App lifecycle - `splashScreen`, `Updates`, `BackgroundTask`.
53
+ - Native views - `registerExpoView()`/`registerExpoViews()`, and the `@expo/ui` SwiftUI and
54
+ Jetpack Compose controls.
55
+ - `Permission` - the `getXPermissionsAsync`/`requestXPermissionsAsync` pair any module exposes,
56
+ as one class.
57
+
58
+ ## Docs
59
+
60
+ - [Expo](https://ng-native.com/packages/expo), with the full module list by category
61
+ - [Using a module](https://ng-native.com/packages/expo/using-a-module) and
62
+ [permissions](https://ng-native.com/packages/expo/permissions)
63
+ - [Root README](https://github.com/ng-native/ng-native/blob/main/README.md)
64
+
65
+ ## License
66
+
67
+ MIT
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `Storage`, a `Store` over `@react-native-async-storage/async-storage`: a plain key-value file.
3
+ *
4
+ * ```ts
5
+ * private readonly store = inject(Storage);
6
+ * protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');
7
+ * ```
8
+ *
9
+ * See `Store` in `@ng-native/expo/store` for what the signal does.
10
+ */
11
+ import { InjectionToken } from '@angular/core';
12
+ import { Store } from './store.ts';
13
+ /** A plain key-value file. Bigger and faster than the keychain, and not private. */
14
+ export declare const Storage: InjectionToken<Store>;
15
+ export type Storage = Store;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `Storage`, a `Store` over `@react-native-async-storage/async-storage`: a plain key-value file.
3
+ *
4
+ * ```ts
5
+ * private readonly store = inject(Storage);
6
+ * protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');
7
+ * ```
8
+ *
9
+ * See `Store` in `@ng-native/expo/store` for what the signal does.
10
+ */
11
+ import { InjectionToken } from '@angular/core';
12
+ import { expoModule } from "./native.js";
13
+ import { Store } from "./store.js";
14
+ const plain = () => {
15
+ const module = expoModule('@react-native-async-storage/async-storage', () => require('@react-native-async-storage/async-storage'));
16
+ const store = module?.default;
17
+ if (!store)
18
+ return null;
19
+ return {
20
+ get: (key) => store.getItem(key),
21
+ set: (key, value) => store.setItem(key, value),
22
+ remove: (key) => store.removeItem(key),
23
+ };
24
+ };
25
+ /** A plain key-value file. Bigger and faster than the keychain, and not private. */
26
+ export const Storage = new InjectionToken('angular-native.storage', {
27
+ factory: () => new Store(plain()),
28
+ });
29
+ //# sourceMappingURL=async-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-storage.js","sourceRoot":"","sources":["../src/async-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAoB,MAAM,YAAY,CAAC;AAErD,MAAM,KAAK,GAAG,GAAuB,EAAE;IACrC,MAAM,MAAM,GAAG,UAAU,CACvB,2CAA2C,EAC3C,GAAG,EAAE,CACH,OAAO,CAAC,2CAA2C,CAElD,CACJ,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,EAAE,OAAO,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO;QACL,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAChC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC;QAC9C,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;KACvC,CAAC;AACJ,CAAC,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,cAAc,CAAQ,wBAAwB,EAAE;IACzE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;CAClC,CAAC,CAAC","sourcesContent":["/**\n * `Storage`, a `Store` over `@react-native-async-storage/async-storage`: a plain key-value file.\n *\n * ```ts\n * private readonly store = inject(Storage);\n * protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');\n * ```\n *\n * See `Store` in `@ng-native/expo/store` for what the signal does.\n */\nimport { InjectionToken } from '@angular/core';\nimport { expoModule } from './native.ts';\nimport { Store, type NativeStore } from './store.ts';\n\nconst plain = (): NativeStore | null => {\n const module = expoModule(\n '@react-native-async-storage/async-storage',\n () =>\n require('@react-native-async-storage/async-storage') as {\n default: typeof import('@react-native-async-storage/async-storage').default;\n },\n );\n const store = module?.default;\n if (!store) return null;\n return {\n get: (key) => store.getItem(key),\n set: (key, value) => store.setItem(key, value),\n remove: (key) => store.removeItem(key),\n };\n};\n\n/** A plain key-value file. Bigger and faster than the keychain, and not private. */\nexport const Storage = new InjectionToken<Store>('angular-native.storage', {\n factory: () => new Store(plain()),\n});\nexport type Storage = Store;\n"]}
@@ -0,0 +1,10 @@
1
+ import { type Player } from './player.ts';
2
+ /**
3
+ * An audio player owned by the current component, released when it is destroyed.
4
+ *
5
+ * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is
6
+ * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.
7
+ */
8
+ export declare function audioPlayer(source: import('expo-audio').AudioSource, options?: {
9
+ timeUpdate?: number;
10
+ }): Player<import('expo-audio').AudioPlayer>;
package/dist/audio.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `audioPlayer`, an `expo-audio` player owned by the component that makes it.
3
+ *
4
+ * ```ts
5
+ * protected readonly player = audioPlayer(this.source, { timeUpdate: 0.5 });
6
+ *
7
+ * // {{ player.state().currentTime }}
8
+ * ```
9
+ */
10
+ import { expoModule, unavailable } from "./native.js";
11
+ import { ownPlayer, watchAudioPlayer } from "./player.js";
12
+ /**
13
+ * An audio player owned by the current component, released when it is destroyed.
14
+ *
15
+ * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is
16
+ * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.
17
+ */
18
+ export function audioPlayer(source, options = {}) {
19
+ const expo = expoModule('expo-audio', () => require('expo-audio'), ['ios', 'android', 'web']);
20
+ if (!expo)
21
+ throw unavailable('expo-audio', 'Stand in for the player in the test.');
22
+ const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;
23
+ const native = expo.createAudioPlayer(source, { updateInterval });
24
+ return ownPlayer(native, () => watchAudioPlayer(native));
25
+ }
26
+ //# sourceMappingURL=audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAe,MAAM,aAAa,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,MAAwC,EACxC,UAAmC,EAAE;IAErC,MAAM,IAAI,GAAG,UAAU,CACrB,YAAY,EACZ,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,CAAgC,EAC1D,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAC1B,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,MAAM,WAAW,CAAC,YAAY,EAAE,sCAAsC,CAAC,CAAC;IACnF,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAChG,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;IAClE,OAAO,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["/**\n * `audioPlayer`, an `expo-audio` player owned by the component that makes it.\n *\n * ```ts\n * protected readonly player = audioPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // {{ player.state().currentTime }}\n * ```\n */\nimport { expoModule, unavailable } from './native.ts';\nimport { ownPlayer, watchAudioPlayer, type Player } from './player.ts';\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 unavailable('expo-audio', 'Stand in for the player in the test.');\n const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;\n const native = expo.createAudioPlayer(source, { updateInterval });\n return ownPlayer(native, () => watchAudioPlayer(native));\n}\n"]}
@@ -22,7 +22,7 @@ export const NATIVE_VIEWS = {
22
22
  'web-view': ['RNCWebView', { javaScriptEnabled: true, domStorageEnabled: true }],
23
23
  /** `@react-native-community/slider`. */
24
24
  slider: ['RNCSlider', { maximumValue: 1, minimumValue: 0, step: 0 }],
25
- /** `@react-native-community/datetimepicker`. Android renders a dialog, iOS an inline view. */
25
+ /** `@react-native-community/datetimepicker`. iOS only: on Android the library has no view. */
26
26
  'date-time-picker': ['RNDateTimePicker'],
27
27
  /** `@react-native-picker/picker`, whose items are `<picker-item>` children. */
28
28
  picker: ['RNCPicker'],
@@ -1 +1 @@
1
- {"version":3,"file":"community-views.js","sourceRoot":"","sources":["../src/community-views.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,gGAAgG;AAChG,MAAM,CAAC,MAAM,YAAY,GAAiE;IACxF,8BAA8B;IAC9B,UAAU,EAAE,CAAC,YAAY,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAChF,wCAAwC;IACxC,MAAM,EAAE,CAAC,WAAW,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACpE,8FAA8F;IAC9F,kBAAkB,EAAE,CAAC,kBAAkB,CAAC;IACxC,+EAA+E;IAC/E,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,aAAa,EAAE,CAAC,eAAe,CAAC;IAChC;;;;;;OAMG;IACH,mBAAmB,EAAE,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAChE,yFAAyF;IACzF,aAAa,EAAE,CAAC,eAAe,CAAC;IAChC,+EAA+E;IAC/E,YAAY,EAAE,CAAC,cAAc,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IAClD,6BAA6B;IAC7B,aAAa,EAAE,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtE;;;;;;;;OAQG;IACH,WAAW,EAAE,CAAC,iBAAiB,CAAC;IAChC,kEAAkE;IAClE,WAAW,EAAE,CAAC,aAAa,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAG,QAAgD;IACrF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,OAAO,GAAG,CAAC,CAAC;QACxF,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC","sourcesContent":["/**\n * Native views from the libraries an Expo app already reaches for, by element name.\n *\n * These are not Expo modules, so their Fabric names are their own - what codegen produced from\n * each library's spec - rather than the `ViewManagerAdapter_` names `registerExpoView` derives.\n * They are here because they are in Expo's own bundled module list, which is the list an app\n * actually installs from, and because the alternative is every app looking each name up in a\n * library's generated code.\n *\n * Names and defaults, not components. An Angular component per native view, prop for prop, is a\n * maintenance burden with no end and a second place for every prop to be wrong. An app writes the\n * small component for the one it uses - a selector, and an input per prop it binds, passed through\n * as a host binding - which keeps its template checked; see `ExpoImage` for the shape.\n *\n * A library 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 */\nimport { registerViewName } from '@ng-native/fabric';\n\n/** Element name -> the Fabric component name, and the props its React wrapper would default. */\nexport const NATIVE_VIEWS: Readonly<Record<string, [string, Record<string, unknown>?]>> = {\n /** `react-native-webview`. */\n 'web-view': ['RNCWebView', { javaScriptEnabled: true, domStorageEnabled: true }],\n /** `@react-native-community/slider`. */\n slider: ['RNCSlider', { maximumValue: 1, minimumValue: 0, step: 0 }],\n /** `@react-native-community/datetimepicker`. Android renders a dialog, iOS an inline view. */\n 'date-time-picker': ['RNDateTimePicker'],\n /** `@react-native-picker/picker`, whose items are `<picker-item>` children. */\n picker: ['RNCPicker'],\n 'picker-item': ['RNCPickerItem'],\n /**\n * `@react-native-segmented-control/segmented-control`. iOS only.\n *\n * A view manager from before Fabric, run through the interop layer, which only installs the\n * `onChange` block when the prop is set - as React's handler does by being there. Without it\n * the control changes on screen and nothing hears about it.\n */\n 'segmented-control': ['RNCSegmentedControl', { onChange: true }],\n /** `@react-native-masked-view/masked-view`: its `maskElement` is a prop, not a child. */\n 'masked-view': ['RNCMaskedView'],\n /** `react-native-pager-view`, the swipeable pager a tab layout is built on. */\n 'pager-view': ['RNCViewPager', { initialPage: 0 }],\n /** `lottie-react-native`. */\n 'lottie-view': ['LottieAnimationView', { autoPlay: true, loop: true }],\n /**\n * `@shopify/react-native-skia`.\n *\n * The view is real and this name is right, but it is worth being plain: Skia's drawing model is\n * React elements through a reconciler of its own - `<Canvas><Path/></Canvas>` - and none of that\n * is reachable here. What a `<skia-view>` will take is a `picture`, built with Skia's imperative\n * `PictureRecorder` API, which has no React in it. Anything drawn declaratively does not come\n * across, and a component that renders nothing is what that looks like.\n */\n 'skia-view': ['SkiaPictureView'],\n /** `react-native-view-shot`, which captures whatever it wraps. */\n 'view-shot': ['RNCViewShot'],\n};\n\n/**\n * Register several of the known views by element name.\n *\n * ```ts\n * registerNativeViews('web-view', 'slider');\n * ```\n *\n * Named one at a time rather than all at once on purpose: registering a view for a library the\n * app has not installed produces an element that commits as nothing, and finding out at startup\n * which libraries are present is not something JavaScript can do without importing them all.\n */\nexport function registerNativeViews(...elements: readonly (keyof typeof NATIVE_VIEWS)[]): void {\n for (const element of elements) {\n const known = NATIVE_VIEWS[element];\n if (!known) throw new Error(`[angular-native] no native view is known as '${element}'`);\n registerViewName(element, known[0], known[1]);\n }\n}\n"]}
1
+ {"version":3,"file":"community-views.js","sourceRoot":"","sources":["../src/community-views.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,gGAAgG;AAChG,MAAM,CAAC,MAAM,YAAY,GAAiE;IACxF,8BAA8B;IAC9B,UAAU,EAAE,CAAC,YAAY,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAChF,wCAAwC;IACxC,MAAM,EAAE,CAAC,WAAW,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACpE,8FAA8F;IAC9F,kBAAkB,EAAE,CAAC,kBAAkB,CAAC;IACxC,+EAA+E;IAC/E,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,aAAa,EAAE,CAAC,eAAe,CAAC;IAChC;;;;;;OAMG;IACH,mBAAmB,EAAE,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAChE,yFAAyF;IACzF,aAAa,EAAE,CAAC,eAAe,CAAC;IAChC,+EAA+E;IAC/E,YAAY,EAAE,CAAC,cAAc,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IAClD,6BAA6B;IAC7B,aAAa,EAAE,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtE;;;;;;;;OAQG;IACH,WAAW,EAAE,CAAC,iBAAiB,CAAC;IAChC,kEAAkE;IAClE,WAAW,EAAE,CAAC,aAAa,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAG,QAAgD;IACrF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,OAAO,GAAG,CAAC,CAAC;QACxF,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC","sourcesContent":["/**\n * Native views from the libraries an Expo app already reaches for, by element name.\n *\n * These are not Expo modules, so their Fabric names are their own - what codegen produced from\n * each library's spec - rather than the `ViewManagerAdapter_` names `registerExpoView` derives.\n * They are here because they are in Expo's own bundled module list, which is the list an app\n * actually installs from, and because the alternative is every app looking each name up in a\n * library's generated code.\n *\n * Names and defaults, not components. An Angular component per native view, prop for prop, is a\n * maintenance burden with no end and a second place for every prop to be wrong. An app writes the\n * small component for the one it uses - a selector, and an input per prop it binds, passed through\n * as a host binding - which keeps its template checked; see `ExpoImage` for the shape.\n *\n * A library 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 */\nimport { registerViewName } from '@ng-native/fabric';\n\n/** Element name -> the Fabric component name, and the props its React wrapper would default. */\nexport const NATIVE_VIEWS: Readonly<Record<string, [string, Record<string, unknown>?]>> = {\n /** `react-native-webview`. */\n 'web-view': ['RNCWebView', { javaScriptEnabled: true, domStorageEnabled: true }],\n /** `@react-native-community/slider`. */\n slider: ['RNCSlider', { maximumValue: 1, minimumValue: 0, step: 0 }],\n /** `@react-native-community/datetimepicker`. iOS only: on Android the library has no view. */\n 'date-time-picker': ['RNDateTimePicker'],\n /** `@react-native-picker/picker`, whose items are `<picker-item>` children. */\n picker: ['RNCPicker'],\n 'picker-item': ['RNCPickerItem'],\n /**\n * `@react-native-segmented-control/segmented-control`. iOS only.\n *\n * A view manager from before Fabric, run through the interop layer, which only installs the\n * `onChange` block when the prop is set - as React's handler does by being there. Without it\n * the control changes on screen and nothing hears about it.\n */\n 'segmented-control': ['RNCSegmentedControl', { onChange: true }],\n /** `@react-native-masked-view/masked-view`: its `maskElement` is a prop, not a child. */\n 'masked-view': ['RNCMaskedView'],\n /** `react-native-pager-view`, the swipeable pager a tab layout is built on. */\n 'pager-view': ['RNCViewPager', { initialPage: 0 }],\n /** `lottie-react-native`. */\n 'lottie-view': ['LottieAnimationView', { autoPlay: true, loop: true }],\n /**\n * `@shopify/react-native-skia`.\n *\n * The view is real and this name is right, but it is worth being plain: Skia's drawing model is\n * React elements through a reconciler of its own - `<Canvas><Path/></Canvas>` - and none of that\n * is reachable here. What a `<skia-view>` will take is a `picture`, built with Skia's imperative\n * `PictureRecorder` API, which has no React in it. Anything drawn declaratively does not come\n * across, and a component that renders nothing is what that looks like.\n */\n 'skia-view': ['SkiaPictureView'],\n /** `react-native-view-shot`, which captures whatever it wraps. */\n 'view-shot': ['RNCViewShot'],\n};\n\n/**\n * Register several of the known views by element name.\n *\n * ```ts\n * registerNativeViews('web-view', 'slider');\n * ```\n *\n * Named one at a time rather than all at once on purpose: registering a view for a library the\n * app has not installed produces an element that commits as nothing, and finding out at startup\n * which libraries are present is not something JavaScript can do without importing them all.\n */\nexport function registerNativeViews(...elements: readonly (keyof typeof NATIVE_VIEWS)[]): void {\n for (const element of elements) {\n const known = NATIVE_VIEWS[element];\n if (!known) throw new Error(`[angular-native] no native view is known as '${element}'`);\n registerViewName(element, known[0], known[1]);\n }\n}\n"]}
package/dist/crypto.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * the data is. So every call without the module throws, naming the package to install.
21
21
  */
22
22
  import { InjectionToken, Service, inject } from '@angular/core';
23
- import { expoModule } from "./native.js";
23
+ import { expoModule, unavailable } from "./native.js";
24
24
  import * as i0 from "@angular/core";
25
25
  /**
26
26
  * The hash algorithms, as `expo-crypto`'s own enum. Importing the enum from the module loads the
@@ -72,7 +72,7 @@ export class Crypto {
72
72
  }
73
73
  module() {
74
74
  if (!this.native)
75
- throw new Error('Crypto needs expo-crypto: npx expo install expo-crypto');
75
+ throw unavailable('expo-crypto', 'Provide Crypto.SOURCE with a stand-in.');
76
76
  return this.native;
77
77
  }
78
78
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Crypto, deps: [], target: i0.ɵɵFactoryTarget.Service });
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAKhE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;AAiBzC;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,OAAO;IACb,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;CAC8B,CAAC;AAE3C,mGAAmG;AACnG,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,QAAQ;CACiB,CAAC;AAGpC,MAAM,OAAO,MAAM;IACjB,wEAAwE;IACxE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CAAsB,6BAA6B,EAAE;QAC9F,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,aAAa,CAAS,CAAC;KAC/E,CAAC,CAAC;IAEc,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEhD,kEAAkE;IAClE,UAAU;QACR,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,CAAC;IACpC,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,YAAY,CAAC,GAAG,IAA+B;QACnD,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,MAAM,CAAC,GAAG,IAAoB;QAClC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,yEAAyE;IACzE,WAAW,CAAC,KAAa;QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IAED,8BAA8B;IAC9B,KAAK,CAAC,gBAAgB,CAAC,KAAa;QAClC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,iGAAiG;IACjG,YAAY,CAAuC,KAAQ;QACzD,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5F,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;uGAzCU,MAAM;wGAAN,MAAM;;2FAAN,MAAM;kBADlB,OAAO","sourcesContent":["/**\n * `Crypto`, bound to `expo-crypto`: random identifiers, random bytes, and hashes.\n *\n * ```ts\n * import { Crypto, CryptoDigestAlgorithm } from '@ng-native/expo/crypto';\n *\n * private readonly crypto = inject(Crypto);\n *\n * readonly id = this.crypto.randomUUID();\n * async fingerprint(text: string): Promise<string> {\n * return this.crypto.digestString(CryptoDigestAlgorithm.SHA256, text);\n * }\n * ```\n *\n * The module's functions under shorter names, with its own types. Hermes has no Web Crypto, so\n * this is where a UUID or a hash comes from on a device.\n *\n * Unlike the other Expo services, this one is not inert without its module. An empty identifier\n * is a key every record shares, and an empty hash matches every input; neither looks wrong until\n * the data is. So every call without the module throws, naming the package to install.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport type {\n CryptoDigestAlgorithm as ExpoDigestAlgorithm,\n CryptoEncoding as ExpoEncoding,\n} from 'expo-crypto';\nimport { expoModule } from './native.ts';\n\ntype Expo = typeof import('expo-crypto');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeCrypto = Pick<\n Expo,\n | 'randomUUID'\n | 'digestStringAsync'\n | 'digest'\n | 'getRandomBytes'\n | 'getRandomBytesAsync'\n | 'getRandomValues'\n>;\n\ntype Args<K extends keyof NativeCrypto> = Parameters<NativeCrypto[K]>;\n\n/**\n * The hash algorithms, as `expo-crypto`'s own enum. Importing the enum from the module loads the\n * module, which a test in Node cannot; these are the same strings, typed as the enum.\n */\nexport const CryptoDigestAlgorithm = {\n SHA1: 'SHA-1',\n SHA256: 'SHA-256',\n SHA384: 'SHA-384',\n SHA512: 'SHA-512',\n MD2: 'MD2',\n MD4: 'MD4',\n MD5: 'MD5',\n} as unknown as typeof ExpoDigestAlgorithm;\n\n/** How `digestString` writes its hash, as `expo-crypto`'s own enum: hex unless asked otherwise. */\nexport const CryptoEncoding = {\n HEX: 'hex',\n BASE64: 'base64',\n} as unknown as typeof ExpoEncoding;\n\n@Service()\nexport class Crypto {\n /** Overridden in a test to hand out identifiers that are not random. */\n static readonly SOURCE = new InjectionToken<NativeCrypto | null>('angular-native.cryptoSource', {\n factory: () => expoModule('expo-crypto', () => require('expo-crypto') as Expo),\n });\n\n private readonly native = inject(Crypto.SOURCE);\n\n /** A version 4 UUID, from the platform's secure random source. */\n randomUUID(): string {\n return this.module().randomUUID();\n }\n\n /** The hash of a string, as hex or base64. */\n async digestString(...args: Args<'digestStringAsync'>): Promise<string> {\n return this.module().digestStringAsync(...args);\n }\n\n /** The hash of bytes, as Web Crypto's `digest` answers it. */\n async digest(...args: Args<'digest'>): Promise<ArrayBuffer> {\n return this.module().digest(...args);\n }\n\n /** Cryptographically secure random bytes, between 0 and 1024 of them. */\n randomBytes(count: number): Uint8Array {\n return this.module().getRandomBytes(count);\n }\n\n /** The same, as a promise. */\n async randomBytesAsync(count: number): Promise<Uint8Array> {\n return this.module().getRandomBytesAsync(count);\n }\n\n /** Fills an integer typed array with secure random values and hands it back, as Web Crypto's. */\n randomValues<T extends Args<'getRandomValues'>[0]>(array: T): T {\n return this.module().getRandomValues(array);\n }\n\n private module(): NativeCrypto {\n if (!this.native) throw new Error('Crypto needs expo-crypto: npx expo install expo-crypto');\n return this.native;\n }\n}\n"]}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAKhE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;;AAiBtD;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,OAAO;IACb,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;CAC8B,CAAC;AAE3C,mGAAmG;AACnG,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,QAAQ;CACiB,CAAC;AAGpC,MAAM,OAAO,MAAM;IACjB,wEAAwE;IACxE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CAAsB,6BAA6B,EAAE;QAC9F,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,aAAa,CAAS,CAAC;KAC/E,CAAC,CAAC;IAEc,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEhD,kEAAkE;IAClE,UAAU;QACR,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,CAAC;IACpC,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,YAAY,CAAC,GAAG,IAA+B;QACnD,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,MAAM,CAAC,GAAG,IAAoB;QAClC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,yEAAyE;IACzE,WAAW,CAAC,KAAa;QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IAED,8BAA8B;IAC9B,KAAK,CAAC,gBAAgB,CAAC,KAAa;QAClC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,iGAAiG;IACjG,YAAY,CAAuC,KAAQ;QACzD,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,WAAW,CAAC,aAAa,EAAE,wCAAwC,CAAC,CAAC;QAC7F,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;uGAzCU,MAAM;wGAAN,MAAM;;2FAAN,MAAM;kBADlB,OAAO","sourcesContent":["/**\n * `Crypto`, bound to `expo-crypto`: random identifiers, random bytes, and hashes.\n *\n * ```ts\n * import { Crypto, CryptoDigestAlgorithm } from '@ng-native/expo/crypto';\n *\n * private readonly crypto = inject(Crypto);\n *\n * readonly id = this.crypto.randomUUID();\n * async fingerprint(text: string): Promise<string> {\n * return this.crypto.digestString(CryptoDigestAlgorithm.SHA256, text);\n * }\n * ```\n *\n * The module's functions under shorter names, with its own types. Hermes has no Web Crypto, so\n * this is where a UUID or a hash comes from on a device.\n *\n * Unlike the other Expo services, this one is not inert without its module. An empty identifier\n * is a key every record shares, and an empty hash matches every input; neither looks wrong until\n * the data is. So every call without the module throws, naming the package to install.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport type {\n CryptoDigestAlgorithm as ExpoDigestAlgorithm,\n CryptoEncoding as ExpoEncoding,\n} from 'expo-crypto';\nimport { expoModule, unavailable } from './native.ts';\n\ntype Expo = typeof import('expo-crypto');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeCrypto = Pick<\n Expo,\n | 'randomUUID'\n | 'digestStringAsync'\n | 'digest'\n | 'getRandomBytes'\n | 'getRandomBytesAsync'\n | 'getRandomValues'\n>;\n\ntype Args<K extends keyof NativeCrypto> = Parameters<NativeCrypto[K]>;\n\n/**\n * The hash algorithms, as `expo-crypto`'s own enum. Importing the enum from the module loads the\n * module, which a test in Node cannot; these are the same strings, typed as the enum.\n */\nexport const CryptoDigestAlgorithm = {\n SHA1: 'SHA-1',\n SHA256: 'SHA-256',\n SHA384: 'SHA-384',\n SHA512: 'SHA-512',\n MD2: 'MD2',\n MD4: 'MD4',\n MD5: 'MD5',\n} as unknown as typeof ExpoDigestAlgorithm;\n\n/** How `digestString` writes its hash, as `expo-crypto`'s own enum: hex unless asked otherwise. */\nexport const CryptoEncoding = {\n HEX: 'hex',\n BASE64: 'base64',\n} as unknown as typeof ExpoEncoding;\n\n@Service()\nexport class Crypto {\n /** Overridden in a test to hand out identifiers that are not random. */\n static readonly SOURCE = new InjectionToken<NativeCrypto | null>('angular-native.cryptoSource', {\n factory: () => expoModule('expo-crypto', () => require('expo-crypto') as Expo),\n });\n\n private readonly native = inject(Crypto.SOURCE);\n\n /** A version 4 UUID, from the platform's secure random source. */\n randomUUID(): string {\n return this.module().randomUUID();\n }\n\n /** The hash of a string, as hex or base64. */\n async digestString(...args: Args<'digestStringAsync'>): Promise<string> {\n return this.module().digestStringAsync(...args);\n }\n\n /** The hash of bytes, as Web Crypto's `digest` answers it. */\n async digest(...args: Args<'digest'>): Promise<ArrayBuffer> {\n return this.module().digest(...args);\n }\n\n /** Cryptographically secure random bytes, between 0 and 1024 of them. */\n randomBytes(count: number): Uint8Array {\n return this.module().getRandomBytes(count);\n }\n\n /** The same, as a promise. */\n async randomBytesAsync(count: number): Promise<Uint8Array> {\n return this.module().getRandomBytesAsync(count);\n }\n\n /** Fills an integer typed array with secure random values and hands it back, as Web Crypto's. */\n randomValues<T extends Args<'getRandomValues'>[0]>(array: T): T {\n return this.module().getRandomValues(array);\n }\n\n private module(): NativeCrypto {\n if (!this.native) throw unavailable('expo-crypto', 'Provide Crypto.SOURCE with a stand-in.');\n return this.native;\n }\n}\n"]}