@ng-native/expo 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +4 -0
- package/android/src/main/java/dev/ngnative/expo/SearchFieldModule.kt +172 -0
- package/dist/app-info.d.ts +18 -15
- package/dist/app-info.js +17 -5
- package/dist/app-info.js.map +1 -1
- package/dist/apple-sign-in.js +5 -4
- package/dist/apple-sign-in.js.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/assets.js.map +1 -1
- package/dist/async-storage.js +4 -1
- package/dist/async-storage.js.map +1 -1
- package/dist/audio.js +2 -2
- package/dist/audio.js.map +1 -1
- package/dist/battery.js +4 -1
- package/dist/battery.js.map +1 -1
- package/dist/biometrics.js +5 -4
- package/dist/biometrics.js.map +1 -1
- package/dist/brightness.js +7 -2
- package/dist/brightness.js.map +1 -1
- package/dist/camera.d.ts +28 -2
- package/dist/camera.js +30 -3
- package/dist/camera.js.map +1 -1
- package/dist/clipboard.js +5 -2
- package/dist/clipboard.js.map +1 -1
- package/dist/database.js +2 -2
- package/dist/database.js.map +1 -1
- package/dist/expo-ui-components.d.ts +127 -12
- package/dist/expo-ui-components.js +354 -48
- package/dist/expo-ui-components.js.map +1 -1
- package/dist/expo-ui.js +7 -1
- package/dist/expo-ui.js.map +1 -1
- package/dist/foldable.d.ts +2 -0
- package/dist/foldable.js +15 -5
- package/dist/foldable.js.map +1 -1
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +12 -14
- package/dist/fonts.js.map +1 -1
- package/dist/haptics.js +2 -2
- package/dist/haptics.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/language-model.js +7 -3
- package/dist/language-model.js.map +1 -1
- package/dist/native.d.ts +17 -2
- package/dist/native.js +61 -3
- package/dist/native.js.map +1 -1
- package/dist/network.js +4 -1
- package/dist/network.js.map +1 -1
- package/dist/notifications.js +35 -8
- package/dist/notifications.js.map +1 -1
- package/dist/observed.js +5 -1
- package/dist/observed.js.map +1 -1
- package/dist/orientation.js +4 -1
- package/dist/orientation.js.map +1 -1
- package/dist/player.d.ts +9 -2
- package/dist/player.js +3 -2
- package/dist/player.js.map +1 -1
- package/dist/register-expo-view.d.ts +25 -2
- package/dist/register-expo-view.js +28 -7
- package/dist/register-expo-view.js.map +1 -1
- package/dist/screen-capture.js +2 -2
- package/dist/screen-capture.js.map +1 -1
- package/dist/search-field.d.ts +85 -0
- package/dist/search-field.js +160 -0
- package/dist/search-field.js.map +1 -0
- package/dist/secure-store.js +4 -1
- package/dist/secure-store.js.map +1 -1
- package/dist/sensors.js +6 -3
- package/dist/sensors.js.map +1 -1
- package/dist/splash-screen.js +2 -2
- package/dist/splash-screen.js.map +1 -1
- package/dist/store.js +4 -0
- package/dist/store.js.map +1 -1
- package/dist/tracking.js +2 -2
- package/dist/tracking.js.map +1 -1
- package/dist/ui-bottom-sheet-view.d.ts +3 -1
- package/dist/ui-bottom-sheet-view.js +6 -2
- package/dist/ui-bottom-sheet-view.js.map +1 -1
- package/dist/ui-font-names.d.ts +16 -0
- package/dist/ui-font-names.js +71 -0
- package/dist/ui-font-names.js.map +1 -0
- package/dist/updates.js +4 -1
- package/dist/updates.js.map +1 -1
- package/dist/video.js +3 -3
- package/dist/video.js.map +1 -1
- package/dist/watch.js +6 -5
- package/dist/watch.js.map +1 -1
- package/expo-module.config.json +5 -2
- package/ios/ExpoUIModifiersModule.swift +25 -0
- package/ios/InlineNavigationTitle.swift +12 -0
- package/ios/NgNativeExpo.podspec +3 -0
- package/ios/SearchField.swift +200 -0
- package/ios/SegmentedFont.swift +113 -0
- package/package.json +6 -2
package/dist/splash-screen.js
CHANGED
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
* platform rather than injecting it, because the first call happens before there is an injector.
|
|
30
30
|
*/
|
|
31
31
|
import { InjectionToken } from '@angular/core';
|
|
32
|
-
import {
|
|
32
|
+
import { expoModuleNow } from "./native.js";
|
|
33
33
|
export function expoSplashScreen() {
|
|
34
|
-
const expo =
|
|
34
|
+
const expo = expoModuleNow('expo-splash-screen', () => require('expo-splash-screen'));
|
|
35
35
|
if (!expo)
|
|
36
36
|
return null;
|
|
37
37
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splash-screen.js","sourceRoot":"","sources":["../src/splash-screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"splash-screen.js","sourceRoot":"","sources":["../src/splash-screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQ5C,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,aAAa,CACxB,oBAAoB,EACpB,GAAG,EAAE,CAAC,OAAO,CAAC,oBAAoB,CAAwC,CAC3E,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO;QACL,oBAAoB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE;QACvD,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE;KAClC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,MAAM;IACA,MAAM,CAAgE;IAC/E,QAAQ,GAA0D,IAAI,CAAC;IACvE,IAAI,GAAG,KAAK,CAAC;IAErB;;;OAGG;IACH,YAAY,MAAqE;QAC/E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,IAAY,MAAM;QAChB,IAAI,CAAC,QAAQ,KAAK;YAChB,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;SACxE,CAAC;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,8FAA8F;IAC9F,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,KAAK,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAChD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,aAAa,CAAC,IAAsB,EAAE,SAAS,GAAG,YAAY;QAClE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC;QACb,CAAC;gBAAS,CAAC;YACT,MAAM,SAAS,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,YAAY,GAAG,GAAkB,EAAE,CACvC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,MAAM,GAAG,GAAI,UAAmE;SAC7E,qBAAqB,CAAC;IACzB,IAAI,GAAG;QAAE,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;;QACzB,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,cAAc,CAAS,6BAA6B,EAAE;IACpF,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY;CAC5B,CAAC,CAAC","sourcesContent":["/**\n * `SplashScreen`, bound to `expo-splash-screen`. Held until the app has something worth showing.\n *\n * The whole point is the ordering, and it is easy to get subtly wrong: the native screen hides\n * itself as soon as the first frame is drawn, so an app that loads fonts before mounting shows a\n * blank window for as long as that takes. Preventing the auto-hide has to happen at module scope,\n * before any of that, and hiding has to happen after the first real frame:\n *\n * ```ts\n * import { splashScreen } from '@ng-native/expo/splash-screen';\n * import { loadFonts } from '@ng-native/expo/fonts';\n *\n * // At module scope, before anything renders.\n * splashScreen.hold();\n *\n * AppRegistry.registerRunnable('main', ({ rootTag }) => {\n * const fonts = loadFonts(styleSheetOf(GlobalStyles));\n * mount(Number(rootTag), App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });\n * void splashScreen.hideWhenReady(fonts);\n * });\n * ```\n *\n * Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive\n * is laid out again as each face registers, behind the splash, so nothing is ever seen in the\n * fallback face.\n *\n * `hideWhenReady` is that shape as one call, which is the only thing worth wrapping here: every\n * function underneath is already a plain promise with no React in it. The class takes its\n * platform rather than injecting it, because the first call happens before there is an injector.\n */\nimport { InjectionToken } from '@angular/core';\nimport { expoModuleNow } from './native.ts';\n\n/** The slice of `expo-splash-screen` this needs. */\nexport interface NativeSplashScreen {\n preventAutoHideAsync(): Promise<boolean>;\n hideAsync(): Promise<void>;\n}\n\nexport function expoSplashScreen(): NativeSplashScreen | null {\n const expo = expoModuleNow(\n 'expo-splash-screen',\n () => require('expo-splash-screen') as typeof import('expo-splash-screen'),\n );\n if (!expo) return null;\n return {\n preventAutoHideAsync: () => expo.preventAutoHideAsync(),\n hideAsync: () => expo.hideAsync(),\n };\n}\n\nexport class Splash {\n private readonly source: NativeSplashScreen | null | (() => NativeSplashScreen | null);\n private resolved: { readonly native: NativeSplashScreen | null } | null = null;\n private held = false;\n\n /**\n * The module, or a function that reaches it: the shared instance is created when its file is\n * imported, and reaching the module then would fail an import rather than the first call.\n */\n constructor(native: NativeSplashScreen | null | (() => NativeSplashScreen | null)) {\n this.source = native;\n }\n\n private get native(): NativeSplashScreen | null {\n this.resolved ??= {\n native: typeof this.source === 'function' ? this.source() : this.source,\n };\n return this.resolved.native;\n }\n\n /** Whether the module is installed. Without it the screen hides itself on the first frame. */\n get available(): boolean {\n return this.native !== null;\n }\n\n /**\n * Keep the splash up. Called at module scope, before anything is mounted.\n *\n * Failures are swallowed: the only ones are races with the screen already having gone, and an\n * app that cannot hold its splash should still start.\n */\n hold(): void {\n if (!this.native || this.held) return;\n this.held = true;\n void this.native.preventAutoHideAsync().catch(() => {});\n }\n\n /** Let it go. Safe to call whether or not it was ever held. */\n async hide(): Promise<void> {\n if (!this.native) return;\n this.held = false;\n await this.native.hideAsync().catch(() => {});\n }\n\n /**\n * Hide once `work` is done *and* the first frame after it has been drawn.\n *\n * Hiding the moment the work resolves uncovers the frame that was on screen while it ran, which\n * is the empty one. Waiting a frame is what makes the handover invisible, and is why this is a\n * method rather than two calls at a call site.\n */\n async hideWhenReady(work: Promise<unknown>, nextFrame = defaultFrame): Promise<void> {\n try {\n await work;\n } finally {\n await nextFrame();\n await this.hide();\n }\n }\n}\n\n/** One turn of the frame loop, or the macrotask queue where there is no loop to wait on. */\nconst defaultFrame = (): Promise<void> =>\n new Promise((resolve) => {\n const raf = (globalThis as { requestAnimationFrame?: (cb: () => void) => void })\n .requestAnimationFrame;\n if (raf) raf(() => resolve());\n else setTimeout(resolve, 0);\n });\n\n/**\n * The one instance, exported directly because it is needed before an injector exists: holding the\n * splash is the first thing an entry file does.\n */\nexport const splashScreen = new Splash(expoSplashScreen);\n\nexport const SplashScreen = new InjectionToken<Splash>('angular-native.splashScreen', {\n factory: () => splashScreen,\n});\nexport type SplashScreen = Splash;\n"]}
|
package/dist/store.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* over one class, for the same reason the sensors are.
|
|
24
24
|
*/
|
|
25
25
|
import { computed, signal } from '@angular/core';
|
|
26
|
+
import { present } from "./native.js";
|
|
26
27
|
export class Store {
|
|
27
28
|
native;
|
|
28
29
|
bound = new Map();
|
|
@@ -69,6 +70,9 @@ export class Store {
|
|
|
69
70
|
* The same key always returns the same signal, so two components binding to it stay in step.
|
|
70
71
|
*/
|
|
71
72
|
signal(key, initial) {
|
|
73
|
+
// A read is a use: a missing module says what to install on every one, before a key is kept.
|
|
74
|
+
if (this.native && !present(this.native))
|
|
75
|
+
void this.native.get(key);
|
|
72
76
|
const existing = this.bound.get(key);
|
|
73
77
|
if (existing)
|
|
74
78
|
return existing.value;
|
package/dist/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Store`, what `Storage` (`@ng-native/expo/async-storage`) and `SecureStorage`\n * (`@ng-native/expo/secure-store`) both are, over whichever native store each is bound to.\n *\n * ```ts\n * private readonly store = inject(Storage);\n * protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');\n * ```\n *\n * The signal is bound both ways: reading it is reading the preference, setting it persists.\n *\n * The underlying APIs are already plain promises with no React in them, so what is built is not a\n * wrapper but a *binding*: `store.signal('theme', 'light')` reads it back once, writes it through\n * whenever it is set, and is an ordinary signal in between. Everything is JSON, because both\n * stores hold strings and a store that silently held `\"[object Object]\"` would be worse than one\n * that held nothing.\n *\n * Two services rather than one because the difference matters and should be visible at the\n * injection site, and two entry points because each needs its own native module: Metro fails a\n * build on a `require` it cannot resolve, so one entry point for both made an app install both. `SecureStorage` is the keychain and the Android keystore: small values, an\n * async write, and a size limit that is not documented but is real. `Storage` is a plain\n * key-value file: bigger, faster, and readable by anyone with the device unlocked. Two tokens\n * over one class, for the same reason the sensors are.\n */\nimport { computed, signal, type Signal, type WritableSignal } from '@angular/core';\n\n/** What both `AsyncStorage` and `SecureStore` offer, reduced to the three verbs. */\nexport interface NativeStore {\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n remove(key: string): Promise<void>;\n /** Where the platform can answer without waiting - `SecureStore` can, `AsyncStorage` cannot. */\n getSync?(key: string): string | null;\n}\n\n/** A key's signal, with what `remove()` needs to put it back. */\ninterface Binding {\n readonly value: WritableSignal<unknown>;\n /** The signal's own `set`, which does not write through. */\n readonly reset: (value: unknown) => void;\n readonly initial: unknown;\n}\n\nexport class Store {\n private readonly native: NativeStore | null;\n private readonly bound = new Map<string, Binding>();\n /** Keys written by the app before their read came back. The read must not undo a write. */\n private readonly written = new Set<string>();\n /**\n * Updates made to a key whose read has not come back, in order. An update changes the stored\n * value rather than replacing it, so these wait for it and are applied to it, not to `initial`.\n */\n private readonly waiting = new Map<string, ((current: unknown) => unknown)[]>();\n /** Reads started and not yet back, successful or not. */\n private readonly reading = signal(0);\n private readonly failure = signal<Error | null>(null);\n /** Writes sent to the platform and not yet answered. What `flush()` waits for. */\n private readonly writes = new Set<Promise<void>>();\n /** The first write to fail since the last `flush()`, held until a flush reports it. */\n private unreported: { readonly error: unknown } | null = null;\n\n constructor(native: NativeStore | null) {\n this.native = native;\n }\n\n /**\n * Whether every key asked for so far has been read back. False while any read is outstanding,\n * so it goes back to false when a new key is asked for, and true again once that read returns.\n *\n * A read that fails still counts as back: the signal keeps its `initial`, and the failure is on\n * `error`. A loading screen held on `ready` should not wait forever for a store that is broken.\n */\n readonly ready: Signal<boolean> = computed(() => this.reading() === 0);\n\n /**\n * The most recent read or write that failed, or null while none has.\n *\n * A write through `set()` has no promise to reject, so this is where a full disk or a locked\n * keychain is reported. `flush()` is the way to wait on the writes themselves.\n */\n readonly error: Signal<Error | null> = this.failure.asReadonly();\n\n /**\n * The stored value at `key`, as a signal that writes through when set.\n *\n * Starts at `initial` and stays there until the read returns, which is one turn away at best -\n * unless the platform can answer synchronously, in which case it never shows `initial` at all.\n * The same key always returns the same signal, so two components binding to it stay in step.\n */\n signal<T>(key: string, initial: T): WritableSignal<T> {\n const existing = this.bound.get(key);\n if (existing) return existing.value as WritableSignal<T>;\n\n const stored = this.readSync(key);\n const value = signal<T>(stored === ABSENT ? initial : (stored as T));\n\n const set = value.set.bind(value);\n const update = value.update.bind(value);\n value.set = (next: T) => {\n this.written.add(key);\n this.waiting.delete(key);\n set(next);\n this.write(key, next);\n };\n value.update = (fn: (current: T) => T) => {\n // Shown at once, on what is known so far, and written once the stored value is known.\n const waiting = this.waiting.get(key);\n update(fn);\n if (waiting) {\n waiting.push(fn as (current: unknown) => unknown);\n return;\n }\n this.written.add(key);\n this.write(key, value());\n };\n this.bound.set(key, {\n value: value as WritableSignal<unknown>,\n reset: set as (value: unknown) => void,\n initial,\n });\n\n // A platform that answered synchronously has said what is stored; one that cannot has not.\n if (stored === ABSENT && this.native && !this.native.getSync) this.waiting.set(key, []);\n void this.hydrate(key, set, initial);\n return value;\n }\n\n /**\n * Forget a key, in the store and in the signal bound to it.\n *\n * The signal goes back to the `initial` it was first asked for with, and stays the one signal\n * for the key. Rejects if the platform could not remove it, which also sets `error`.\n */\n async remove(key: string): Promise<void> {\n this.written.add(key);\n this.waiting.delete(key);\n const binding = this.bound.get(key);\n binding?.reset(binding.initial);\n await this.track(this.native?.remove(key));\n }\n\n /**\n * Wait for every write sent so far. Rejects with the first of them that failed since the last\n * `flush()`, whether it failed before this call or while waiting, so a screen that has to know a\n * value reached the disk - before signing out, say - can find out. Each failure is reported by\n * one flush only.\n */\n async flush(): Promise<void> {\n await Promise.allSettled([...this.writes]);\n const failed = this.unreported;\n this.unreported = null;\n if (failed) throw failed.error;\n }\n\n private readSync(key: string): unknown {\n let raw: string | null | undefined;\n try {\n raw = this.native?.getSync?.(key);\n } catch (error) {\n this.fail(error);\n return ABSENT;\n }\n return raw == null ? ABSENT : decode(raw);\n }\n\n /**\n * Read the stored value in, unless the app has already set one.\n *\n * The race is real and silent: a screen that writes a preference during startup would otherwise\n * have it overwritten a tick later by whatever was there before.\n */\n private async hydrate<T>(key: string, set: (value: T) => void, initial: T): Promise<void> {\n this.reading.update((n) => n + 1);\n let stored: unknown = ABSENT;\n try {\n const raw = await this.native?.get(key);\n if (raw != null) stored = decode(raw);\n } catch (error) {\n this.fail(error);\n } finally {\n this.settle(key, set, stored === ABSENT ? initial : stored, stored !== ABSENT);\n this.reading.update((n) => n - 1);\n }\n }\n\n /** The read is back: show what was stored, with any updates that waited for it applied. */\n private settle<T>(key: string, set: (value: T) => void, base: unknown, found: boolean): void {\n const updates = this.waiting.get(key);\n this.waiting.delete(key);\n if (this.written.has(key)) return;\n if (!updates?.length) {\n if (found) set(base as T);\n return;\n }\n const value = updates.reduce((current, fn) => fn(current), base);\n this.written.add(key);\n set(value as T);\n this.write(key, value);\n }\n\n private write(key: string, value: unknown): void {\n const sent =\n value === undefined ? this.native?.remove(key) : this.native?.set(key, JSON.stringify(value));\n // Observed here, so a failure lands on `error` and in `flush()` rather than going unhandled.\n this.track(sent).catch(() => {});\n }\n\n /** A platform call, kept until it answers, with its failure reported on `error`. */\n private track(sent: Promise<void> | undefined): Promise<void> {\n if (!sent) return Promise.resolve();\n const tracked = sent.then(\n () => undefined,\n (error: unknown) => {\n this.fail(error);\n this.unreported ??= { error };\n throw error;\n },\n );\n this.writes.add(tracked);\n const forget = () => void this.writes.delete(tracked);\n tracked.then(forget, forget);\n return tracked;\n }\n\n private fail(error: unknown): void {\n this.failure.set(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/** What `decode` answers for a string that is not JSON, since `null` is a value JSON can hold. */\nconst ABSENT: unique symbol = Symbol('absent');\n\n/**\n * A stored string as the value it encodes.\n *\n * A value that will not parse is treated as absent rather than thrown: it is a store written by\n * an older version of the app, and the right answer is the default the caller already supplied.\n * A stored `null` is not that: it parses, and a nullable signal gets it back.\n */\nfunction decode(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return ABSENT;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Store`, what `Storage` (`@ng-native/expo/async-storage`) and `SecureStorage`\n * (`@ng-native/expo/secure-store`) both are, over whichever native store each is bound to.\n *\n * ```ts\n * private readonly store = inject(Storage);\n * protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');\n * ```\n *\n * The signal is bound both ways: reading it is reading the preference, setting it persists.\n *\n * The underlying APIs are already plain promises with no React in them, so what is built is not a\n * wrapper but a *binding*: `store.signal('theme', 'light')` reads it back once, writes it through\n * whenever it is set, and is an ordinary signal in between. Everything is JSON, because both\n * stores hold strings and a store that silently held `\"[object Object]\"` would be worse than one\n * that held nothing.\n *\n * Two services rather than one because the difference matters and should be visible at the\n * injection site, and two entry points because each needs its own native module: Metro fails a\n * build on a `require` it cannot resolve, so one entry point for both made an app install both. `SecureStorage` is the keychain and the Android keystore: small values, an\n * async write, and a size limit that is not documented but is real. `Storage` is a plain\n * key-value file: bigger, faster, and readable by anyone with the device unlocked. Two tokens\n * over one class, for the same reason the sensors are.\n */\nimport { computed, signal, type Signal, type WritableSignal } from '@angular/core';\nimport { present } from './native.ts';\n\n/** What both `AsyncStorage` and `SecureStore` offer, reduced to the three verbs. */\nexport interface NativeStore {\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n remove(key: string): Promise<void>;\n /** Where the platform can answer without waiting - `SecureStore` can, `AsyncStorage` cannot. */\n getSync?(key: string): string | null;\n}\n\n/** A key's signal, with what `remove()` needs to put it back. */\ninterface Binding {\n readonly value: WritableSignal<unknown>;\n /** The signal's own `set`, which does not write through. */\n readonly reset: (value: unknown) => void;\n readonly initial: unknown;\n}\n\nexport class Store {\n private readonly native: NativeStore | null;\n private readonly bound = new Map<string, Binding>();\n /** Keys written by the app before their read came back. The read must not undo a write. */\n private readonly written = new Set<string>();\n /**\n * Updates made to a key whose read has not come back, in order. An update changes the stored\n * value rather than replacing it, so these wait for it and are applied to it, not to `initial`.\n */\n private readonly waiting = new Map<string, ((current: unknown) => unknown)[]>();\n /** Reads started and not yet back, successful or not. */\n private readonly reading = signal(0);\n private readonly failure = signal<Error | null>(null);\n /** Writes sent to the platform and not yet answered. What `flush()` waits for. */\n private readonly writes = new Set<Promise<void>>();\n /** The first write to fail since the last `flush()`, held until a flush reports it. */\n private unreported: { readonly error: unknown } | null = null;\n\n constructor(native: NativeStore | null) {\n this.native = native;\n }\n\n /**\n * Whether every key asked for so far has been read back. False while any read is outstanding,\n * so it goes back to false when a new key is asked for, and true again once that read returns.\n *\n * A read that fails still counts as back: the signal keeps its `initial`, and the failure is on\n * `error`. A loading screen held on `ready` should not wait forever for a store that is broken.\n */\n readonly ready: Signal<boolean> = computed(() => this.reading() === 0);\n\n /**\n * The most recent read or write that failed, or null while none has.\n *\n * A write through `set()` has no promise to reject, so this is where a full disk or a locked\n * keychain is reported. `flush()` is the way to wait on the writes themselves.\n */\n readonly error: Signal<Error | null> = this.failure.asReadonly();\n\n /**\n * The stored value at `key`, as a signal that writes through when set.\n *\n * Starts at `initial` and stays there until the read returns, which is one turn away at best -\n * unless the platform can answer synchronously, in which case it never shows `initial` at all.\n * The same key always returns the same signal, so two components binding to it stay in step.\n */\n signal<T>(key: string, initial: T): WritableSignal<T> {\n // A read is a use: a missing module says what to install on every one, before a key is kept.\n if (this.native && !present(this.native)) void this.native.get(key);\n const existing = this.bound.get(key);\n if (existing) return existing.value as WritableSignal<T>;\n\n const stored = this.readSync(key);\n const value = signal<T>(stored === ABSENT ? initial : (stored as T));\n\n const set = value.set.bind(value);\n const update = value.update.bind(value);\n value.set = (next: T) => {\n this.written.add(key);\n this.waiting.delete(key);\n set(next);\n this.write(key, next);\n };\n value.update = (fn: (current: T) => T) => {\n // Shown at once, on what is known so far, and written once the stored value is known.\n const waiting = this.waiting.get(key);\n update(fn);\n if (waiting) {\n waiting.push(fn as (current: unknown) => unknown);\n return;\n }\n this.written.add(key);\n this.write(key, value());\n };\n this.bound.set(key, {\n value: value as WritableSignal<unknown>,\n reset: set as (value: unknown) => void,\n initial,\n });\n\n // A platform that answered synchronously has said what is stored; one that cannot has not.\n if (stored === ABSENT && this.native && !this.native.getSync) this.waiting.set(key, []);\n void this.hydrate(key, set, initial);\n return value;\n }\n\n /**\n * Forget a key, in the store and in the signal bound to it.\n *\n * The signal goes back to the `initial` it was first asked for with, and stays the one signal\n * for the key. Rejects if the platform could not remove it, which also sets `error`.\n */\n async remove(key: string): Promise<void> {\n this.written.add(key);\n this.waiting.delete(key);\n const binding = this.bound.get(key);\n binding?.reset(binding.initial);\n await this.track(this.native?.remove(key));\n }\n\n /**\n * Wait for every write sent so far. Rejects with the first of them that failed since the last\n * `flush()`, whether it failed before this call or while waiting, so a screen that has to know a\n * value reached the disk - before signing out, say - can find out. Each failure is reported by\n * one flush only.\n */\n async flush(): Promise<void> {\n await Promise.allSettled([...this.writes]);\n const failed = this.unreported;\n this.unreported = null;\n if (failed) throw failed.error;\n }\n\n private readSync(key: string): unknown {\n let raw: string | null | undefined;\n try {\n raw = this.native?.getSync?.(key);\n } catch (error) {\n this.fail(error);\n return ABSENT;\n }\n return raw == null ? ABSENT : decode(raw);\n }\n\n /**\n * Read the stored value in, unless the app has already set one.\n *\n * The race is real and silent: a screen that writes a preference during startup would otherwise\n * have it overwritten a tick later by whatever was there before.\n */\n private async hydrate<T>(key: string, set: (value: T) => void, initial: T): Promise<void> {\n this.reading.update((n) => n + 1);\n let stored: unknown = ABSENT;\n try {\n const raw = await this.native?.get(key);\n if (raw != null) stored = decode(raw);\n } catch (error) {\n this.fail(error);\n } finally {\n this.settle(key, set, stored === ABSENT ? initial : stored, stored !== ABSENT);\n this.reading.update((n) => n - 1);\n }\n }\n\n /** The read is back: show what was stored, with any updates that waited for it applied. */\n private settle<T>(key: string, set: (value: T) => void, base: unknown, found: boolean): void {\n const updates = this.waiting.get(key);\n this.waiting.delete(key);\n if (this.written.has(key)) return;\n if (!updates?.length) {\n if (found) set(base as T);\n return;\n }\n const value = updates.reduce((current, fn) => fn(current), base);\n this.written.add(key);\n set(value as T);\n this.write(key, value);\n }\n\n private write(key: string, value: unknown): void {\n const sent =\n value === undefined ? this.native?.remove(key) : this.native?.set(key, JSON.stringify(value));\n // Observed here, so a failure lands on `error` and in `flush()` rather than going unhandled.\n this.track(sent).catch(() => {});\n }\n\n /** A platform call, kept until it answers, with its failure reported on `error`. */\n private track(sent: Promise<void> | undefined): Promise<void> {\n if (!sent) return Promise.resolve();\n const tracked = sent.then(\n () => undefined,\n (error: unknown) => {\n this.fail(error);\n this.unreported ??= { error };\n throw error;\n },\n );\n this.writes.add(tracked);\n const forget = () => void this.writes.delete(tracked);\n tracked.then(forget, forget);\n return tracked;\n }\n\n private fail(error: unknown): void {\n this.failure.set(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/** What `decode` answers for a string that is not JSON, since `null` is a value JSON can hold. */\nconst ABSENT: unique symbol = Symbol('absent');\n\n/**\n * A stored string as the value it encodes.\n *\n * A value that will not parse is treated as absent rather than thrown: it is a store written by\n * an older version of the app, and the right answer is the default the caller already supplied.\n * A stored `null` is not that: it parses, and a nullable signal gets it back.\n */\nfunction decode(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return ABSENT;\n }\n}\n"]}
|
package/dist/tracking.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* identifier.
|
|
19
19
|
*/
|
|
20
20
|
import { InjectionToken, Service, inject } from '@angular/core';
|
|
21
|
-
import { expoModule } from "./native.js";
|
|
21
|
+
import { expoModule, present } from "./native.js";
|
|
22
22
|
import { Permission, UNAVAILABLE } from "./permissions.js";
|
|
23
23
|
import * as i0 from "@angular/core";
|
|
24
24
|
export class Tracking {
|
|
@@ -34,7 +34,7 @@ export class Tracking {
|
|
|
34
34
|
permission = Permission.of(() => this.native?.getTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE), () => this.native?.requestTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE));
|
|
35
35
|
/** Whether the device has the tracking API. Where it does not, the permission answers granted. */
|
|
36
36
|
get available() {
|
|
37
|
-
return this.native?.isAvailable() ?? false;
|
|
37
|
+
return present(this.native)?.isAvailable() ?? false;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* The advertising identifier: the IDFA on iOS, the advertising ID on Android. Null on iOS until
|
package/dist/tracking.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracking.js","sourceRoot":"","sources":["../src/tracking.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tracking.js","sourceRoot":"","sources":["../src/tracking.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;;AAc3D,MAAM,OAAO,QAAQ;IACnB,yEAAyE;IACzE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,+BAA+B,EAC/B;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CACR,4BAA4B,EAC5B,GAAG,EAAE,CAAC,OAAO,CAAC,4BAA4B,CAAS,CACpD;KACJ,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAElD;;;OAGG;IACM,UAAU,GAAe,UAAU,CAAC,EAAE,CAC7C,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,2BAA2B,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAClF,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,+BAA+B,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CACvF,CAAC;IAEF,kGAAkG;IAClG,IAAI,SAAS;QACX,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,IAAI,KAAK,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,aAAa;QACX,OAAO,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,IAAI,CAAC;IACjD,CAAC;uGAnCU,QAAQ;wGAAR,QAAQ;;2FAAR,QAAQ;kBADpB,OAAO","sourcesContent":["/**\n * `Tracking`, bound to `expo-tracking-transparency`: Apple's App Tracking Transparency permission.\n *\n * ```ts\n * private readonly tracking = inject(Tracking);\n *\n * async startAnalytics(): Promise<void> {\n * const allowed = await this.tracking.permission.ensure();\n * this.analytics.start({ advertisingId: allowed ? this.tracking.advertisingId() : null });\n * }\n * ```\n *\n * iOS 14.5 and later ask before an app may track the user across other companies' apps and\n * websites, and hand out the advertising identifier only once the user has said yes. The question\n * is asked once: after an answer, the platform shows no dialog again, so `blocked()` is how an app\n * knows to offer Settings instead. Android and the web have no such permission, and the module\n * answers granted there. Without the module installed, the permission is refused and there is no\n * identifier.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule, present } from './native.ts';\nimport { Permission, UNAVAILABLE } from './permissions.ts';\n\ntype Expo = typeof import('expo-tracking-transparency');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeTracking = Pick<\n Expo,\n | 'getTrackingPermissionsAsync'\n | 'requestTrackingPermissionsAsync'\n | 'isAvailable'\n | 'getAdvertisingId'\n>;\n\n@Service()\nexport class Tracking {\n /** Overridden in a test to answer a tracking prompt nobody was shown. */\n static readonly SOURCE = new InjectionToken<NativeTracking | null>(\n 'angular-native.trackingSource',\n {\n factory: () =>\n expoModule(\n 'expo-tracking-transparency',\n () => require('expo-tracking-transparency') as Expo,\n ),\n },\n );\n\n private readonly native = inject(Tracking.SOURCE);\n\n /**\n * The permission to track. `ensure()` shows Apple's dialog only if it has not been answered;\n * the text in it is `NSUserTrackingUsageDescription`, set through the module's config plugin.\n */\n readonly permission: Permission = Permission.of(\n () => this.native?.getTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** Whether the device has the tracking API. Where it does not, the permission answers granted. */\n get available(): boolean {\n return present(this.native)?.isAvailable() ?? false;\n }\n\n /**\n * The advertising identifier: the IDFA on iOS, the advertising ID on Android. Null on iOS until\n * tracking is allowed, in the simulator, and on Android with ad tracking limited.\n */\n advertisingId(): string | null {\n return this.native?.getAdvertisingId() ?? null;\n }\n}\n"]}
|
|
@@ -11,6 +11,8 @@ export declare class UiBottomSheetView {
|
|
|
11
11
|
readonly fitToContents: import("@angular/core").InputSignal<boolean | undefined>;
|
|
12
12
|
/** Compose: whether the drag handle is drawn. */
|
|
13
13
|
readonly showDragHandle: import("@angular/core").InputSignal<boolean | undefined>;
|
|
14
|
+
/** Compose: the sheet's colour behind its content. A colour prop, so the engine converts it. */
|
|
15
|
+
readonly containerColor: import("@angular/core").InputSignal<unknown>;
|
|
14
16
|
/** Compose: the sheet opens at its full height rather than resting at half the screen's. */
|
|
15
17
|
readonly skipPartiallyExpanded: import("@angular/core").InputSignal<boolean | undefined>;
|
|
16
18
|
/** SwiftUI: the user dismissed the sheet, or it was presented by something other than the prop. */
|
|
@@ -21,5 +23,5 @@ export declare class UiBottomSheetView {
|
|
|
21
23
|
readonly dismissRequest: import("@angular/core").OutputEmitterRef<NativeSyntheticEvent<Record<string, never>>>;
|
|
22
24
|
constructor();
|
|
23
25
|
static ɵfac: i0.ɵɵFactoryDeclaration<UiBottomSheetView, never>;
|
|
24
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<UiBottomSheetView, "ui-bottom-sheet-view", never, { "isPresented": { "alias": "isPresented"; "required": false; "isSignal": true; }; "fitToContents": { "alias": "fitToContents"; "required": false; "isSignal": true; }; "showDragHandle": { "alias": "showDragHandle"; "required": false; "isSignal": true; }; "skipPartiallyExpanded": { "alias": "skipPartiallyExpanded"; "required": false; "isSignal": true; }; }, { "isPresentedChange": "isPresentedChange"; "dismiss": "dismiss"; "dismissRequest": "dismissRequest"; }, never, ["*"], true, never>;
|
|
26
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<UiBottomSheetView, "ui-bottom-sheet-view", never, { "isPresented": { "alias": "isPresented"; "required": false; "isSignal": true; }; "fitToContents": { "alias": "fitToContents"; "required": false; "isSignal": true; }; "showDragHandle": { "alias": "showDragHandle"; "required": false; "isSignal": true; }; "containerColor": { "alias": "containerColor"; "required": false; "isSignal": true; }; "skipPartiallyExpanded": { "alias": "skipPartiallyExpanded"; "required": false; "isSignal": true; }; }, { "isPresentedChange": "isPresentedChange"; "dismiss": "dismiss"; "dismissRequest": "dismissRequest"; }, never, ["*"], true, never>;
|
|
25
27
|
}
|
|
@@ -23,6 +23,9 @@ export class UiBottomSheetView {
|
|
|
23
23
|
/** Compose: whether the drag handle is drawn. */
|
|
24
24
|
showDragHandle = input(/* @ts-ignore */
|
|
25
25
|
...(ngDevMode ? [undefined, { debugName: "showDragHandle" }] : /* istanbul ignore next */ []));
|
|
26
|
+
/** Compose: the sheet's colour behind its content. A colour prop, so the engine converts it. */
|
|
27
|
+
containerColor = input(/* @ts-ignore */
|
|
28
|
+
...(ngDevMode ? [undefined, { debugName: "containerColor" }] : /* istanbul ignore next */ []));
|
|
26
29
|
/** Compose: the sheet opens at its full height rather than resting at half the screen's. */
|
|
27
30
|
skipPartiallyExpanded = input(/* @ts-ignore */
|
|
28
31
|
...(ngDevMode ? [undefined, { debugName: "skipPartiallyExpanded" }] : /* istanbul ignore next */ []));
|
|
@@ -37,7 +40,7 @@ export class UiBottomSheetView {
|
|
|
37
40
|
keepNativeView(inject(ElementRef).nativeElement);
|
|
38
41
|
}
|
|
39
42
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: UiBottomSheetView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
40
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: UiBottomSheetView, isStandalone: true, selector: "ui-bottom-sheet-view", inputs: { isPresented: { classPropertyName: "isPresented", publicName: "isPresented", isSignal: true, isRequired: false, transformFunction: null }, fitToContents: { classPropertyName: "fitToContents", publicName: "fitToContents", isSignal: true, isRequired: false, transformFunction: null }, showDragHandle: { classPropertyName: "showDragHandle", publicName: "showDragHandle", isSignal: true, isRequired: false, transformFunction: null }, skipPartiallyExpanded: { classPropertyName: "skipPartiallyExpanded", publicName: "skipPartiallyExpanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isPresentedChange: "isPresentedChange", dismiss: "dismiss", dismissRequest: "dismissRequest" }, host: { properties: { "isPresented": "isPresented()", "fitToContents": "fitToContents()", "showDragHandle": "showDragHandle()", "skipPartiallyExpanded": "skipPartiallyExpanded()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
|
|
43
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: UiBottomSheetView, isStandalone: true, selector: "ui-bottom-sheet-view", inputs: { isPresented: { classPropertyName: "isPresented", publicName: "isPresented", isSignal: true, isRequired: false, transformFunction: null }, fitToContents: { classPropertyName: "fitToContents", publicName: "fitToContents", isSignal: true, isRequired: false, transformFunction: null }, showDragHandle: { classPropertyName: "showDragHandle", publicName: "showDragHandle", isSignal: true, isRequired: false, transformFunction: null }, containerColor: { classPropertyName: "containerColor", publicName: "containerColor", isSignal: true, isRequired: false, transformFunction: null }, skipPartiallyExpanded: { classPropertyName: "skipPartiallyExpanded", publicName: "skipPartiallyExpanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isPresentedChange: "isPresentedChange", dismiss: "dismiss", dismissRequest: "dismissRequest" }, host: { properties: { "isPresented": "isPresented()", "fitToContents": "fitToContents()", "showDragHandle": "showDragHandle()", "containerColor": "containerColor()", "skipPartiallyExpanded": "skipPartiallyExpanded()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
|
|
41
44
|
}
|
|
42
45
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: UiBottomSheetView, decorators: [{
|
|
43
46
|
type: Component,
|
|
@@ -48,8 +51,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
|
|
|
48
51
|
'[isPresented]': 'isPresented()',
|
|
49
52
|
'[fitToContents]': 'fitToContents()',
|
|
50
53
|
'[showDragHandle]': 'showDragHandle()',
|
|
54
|
+
'[containerColor]': 'containerColor()',
|
|
51
55
|
'[skipPartiallyExpanded]': 'skipPartiallyExpanded()',
|
|
52
56
|
},
|
|
53
57
|
}]
|
|
54
|
-
}], ctorParameters: () => [], propDecorators: { isPresented: [{ type: i0.Input, args: [{ isSignal: true, alias: "isPresented", required: false }] }], fitToContents: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitToContents", required: false }] }], showDragHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDragHandle", required: false }] }], skipPartiallyExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipPartiallyExpanded", required: false }] }], isPresentedChange: [{ type: i0.Output, args: ["isPresentedChange"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }], dismissRequest: [{ type: i0.Output, args: ["dismissRequest"] }] } });
|
|
58
|
+
}], ctorParameters: () => [], propDecorators: { isPresented: [{ type: i0.Input, args: [{ isSignal: true, alias: "isPresented", required: false }] }], fitToContents: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitToContents", required: false }] }], showDragHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDragHandle", required: false }] }], containerColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerColor", required: false }] }], skipPartiallyExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipPartiallyExpanded", required: false }] }], isPresentedChange: [{ type: i0.Output, args: ["isPresentedChange"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }], dismissRequest: [{ type: i0.Output, args: ["dismissRequest"] }] } });
|
|
55
59
|
//# sourceMappingURL=ui-bottom-sheet-view.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-bottom-sheet-view.js","sourceRoot":"","sources":["../src/ui-bottom-sheet-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAA6B,cAAc,EAAE,MAAM,mBAAmB,CAAC;;
|
|
1
|
+
{"version":3,"file":"ui-bottom-sheet-view.js","sourceRoot":"","sources":["../src/ui-bottom-sheet-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAA6B,cAAc,EAAE,MAAM,mBAAmB,CAAC;;AAgB9E,MAAM,OAAO,iBAAiB;IAC5B,sFAAsF;IAC7E,WAAW,GAAG,KAAK;+FAAW,CAAC;IACxC,oEAAoE;IAC3D,aAAa,GAAG,KAAK;iGAAW,CAAC;IAC1C,iDAAiD;IACxC,cAAc,GAAG,KAAK;kGAAW,CAAC;IAC3C,gGAAgG;IACvF,cAAc,GAAG,KAAK;kGAAW,CAAC;IAC3C,4FAA4F;IACnF,qBAAqB,GAAG,KAAK;yGAAW,CAAC;IAClD,mGAAmG;IAC1F,iBAAiB,GAAG,MAAM,EAA+B,CAAC;IACnE,wEAAwE;IAC/D,OAAO,GAAG,MAAM,EAA+C,CAAC;IACzE,mFAAmF;IAC1E,cAAc,GAAG,MAAM,EAA+C,CAAC;IAEhF;QACE,iFAAiF;QACjF,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAa,CAAC,CAAC;IACnD,CAAC;uGArBU,iBAAiB;2FAAjB,iBAAiB,6oCATlB,gBAAgB;;2FASf,iBAAiB;kBAX7B,SAAS;mBAAC;oBACT,QAAQ,EAAE,sBAAsB;oBAChC,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE;wBACJ,eAAe,EAAE,eAAe;wBAChC,iBAAiB,EAAE,iBAAiB;wBACpC,kBAAkB,EAAE,kBAAkB;wBACtC,kBAAkB,EAAE,kBAAkB;wBACtC,yBAAyB,EAAE,yBAAyB;qBACrD;iBACF","sourcesContent":["/**\n * `<ui-bottom-sheet-view>`: `@expo/ui`'s sheet itself, with each platform's props typed.\n *\n * `UiBottomSheet` in `expo-ui-components.ts` is the component an app uses, and this is the native\n * view inside it: SwiftUI's `BottomSheetView` on iOS, Compose's `ModalBottomSheetView` on Android.\n * It has an element name of its own because `ui-bottom-sheet` is that component's, and a\n * component's template takes its own selector to be the component again.\n *\n * The two views share no prop. An input left unset never reaches native, so the component sets\n * the ones the platform reads. The events are outputs for their type only, as with the other\n * `@expo/ui` components: the element's own event is what arrives.\n */\nimport { Component, ElementRef, inject, input, output } from '@angular/core';\nimport { type NativeSyntheticEvent, keepNativeView } from '@ng-native/fabric';\n\n/** What SwiftUI's sheet sends when the user dismisses it: that it is no longer presented. */\nexport type UiBottomSheetPresentedEvent = NativeSyntheticEvent<{ readonly isPresented: boolean }>;\n\n@Component({\n selector: 'ui-bottom-sheet-view',\n template: '<ng-content />',\n host: {\n '[isPresented]': 'isPresented()',\n '[fitToContents]': 'fitToContents()',\n '[showDragHandle]': 'showDragHandle()',\n '[containerColor]': 'containerColor()',\n '[skipPartiallyExpanded]': 'skipPartiallyExpanded()',\n },\n})\nexport class UiBottomSheetView {\n /** SwiftUI: whether the sheet is presented. The view stays in the tree either way. */\n readonly isPresented = input<boolean>();\n /** SwiftUI: the sheet's one detent is the height of its content. */\n readonly fitToContents = input<boolean>();\n /** Compose: whether the drag handle is drawn. */\n readonly showDragHandle = input<boolean>();\n /** Compose: the sheet's colour behind its content. A colour prop, so the engine converts it. */\n readonly containerColor = input<unknown>();\n /** Compose: the sheet opens at its full height rather than resting at half the screen's. */\n readonly skipPartiallyExpanded = input<boolean>();\n /** SwiftUI: the user dismissed the sheet, or it was presented by something other than the prop. */\n readonly isPresentedChange = output<UiBottomSheetPresentedEvent>();\n /** SwiftUI: the sheet has finished dismissing, whoever dismissed it. */\n readonly dismiss = output<NativeSyntheticEvent<Record<string, never>>>();\n /** Compose: the user dismissed the sheet, which stays until it leaves the tree. */\n readonly dismissRequest = output<NativeSyntheticEvent<Record<string, never>>>();\n\n constructor() {\n // The host is the registered native view, not a plain one: see `keepNativeView`.\n keepNativeView(inject(ElementRef).nativeElement);\n }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A SwiftUI `font` modifier, given the name SwiftUI finds a face by.
|
|
3
|
+
*
|
|
4
|
+
* expo-font registers a face under the name the app gives it (`Brand-700`, as `loadFonts` names
|
|
5
|
+
* the faces a stylesheet declares), and React Native's text finds it there through expo-font's
|
|
6
|
+
* alias. SwiftUI's `font` modifier looks the name up in CoreText, which knows only the PostScript
|
|
7
|
+
* name inside the file, and draws the system font for any other, without a word. So a modifier
|
|
8
|
+
* that names a face as a `<text>` would is committed with the face's PostScript name, which the
|
|
9
|
+
* build read from its file. Compose finds a face by the name it is registered under, so this is
|
|
10
|
+
* for iOS alone.
|
|
11
|
+
*/
|
|
12
|
+
import type { PostScriptNames } from '@ng-native/fabric';
|
|
13
|
+
/** Note the names `Fonts` registered faces under. See `fontModifierNames`. */
|
|
14
|
+
export declare function noteRegisteredNames(names: Iterable<string>): void;
|
|
15
|
+
/** Rewrites the families of `props.modifiers` that name a face, as `ViewNameOptions.fontNames`. */
|
|
16
|
+
export declare function fontModifierNames(props: Record<string, unknown>, postScriptName: PostScriptNames): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** The modifiers that take a family: `@expo/ui`'s `font`, and the segmented picker's copy of it. */
|
|
2
|
+
const TAKES_FAMILY = new Set(['font', 'ngNativeSegmentedFont']);
|
|
3
|
+
/**
|
|
4
|
+
* Each list rewritten, with the families it was rewritten for, so a list committed again while
|
|
5
|
+
* those still resolve as they did is the same list, which native is not sent twice.
|
|
6
|
+
*/
|
|
7
|
+
const rewritten = new WeakMap();
|
|
8
|
+
/** Names registered with expo-font by `Fonts`, which SwiftUI finds only by PostScript name. */
|
|
9
|
+
const registeredNames = new Set();
|
|
10
|
+
/** Families already warned about, once each. */
|
|
11
|
+
const warned = new Set();
|
|
12
|
+
/**
|
|
13
|
+
* Families a modifier named that had no PostScript name, in development: a font still loading
|
|
14
|
+
* when the view was committed is warned about once it registers, since nothing commits it again.
|
|
15
|
+
*/
|
|
16
|
+
const unfound = new Set();
|
|
17
|
+
/** Note the names `Fonts` registered faces under. See `fontModifierNames`. */
|
|
18
|
+
export function noteRegisteredNames(names) {
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
registeredNames.add(name);
|
|
21
|
+
if (unfound.has(name))
|
|
22
|
+
warnUnfound(name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Rewrites the families of `props.modifiers` that name a face, as `ViewNameOptions.fontNames`. */
|
|
26
|
+
export function fontModifierNames(props, postScriptName) {
|
|
27
|
+
const modifiers = props['modifiers'];
|
|
28
|
+
if (!Array.isArray(modifiers))
|
|
29
|
+
return;
|
|
30
|
+
const list = modifiers;
|
|
31
|
+
const families = list.map((modifier) => resolved(modifier, postScriptName));
|
|
32
|
+
if (families.every((family, i) => family === list[i]?.['family']))
|
|
33
|
+
return;
|
|
34
|
+
const known = rewritten.get(list);
|
|
35
|
+
if (known?.families.every((family, i) => family === families[i])) {
|
|
36
|
+
props['modifiers'] = known.modifiers;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const next = list.map((modifier, i) => families[i] === modifier?.['family'] ? modifier : { ...modifier, family: families[i] });
|
|
40
|
+
rewritten.set(list, { modifiers: next, families });
|
|
41
|
+
props['modifiers'] = next;
|
|
42
|
+
}
|
|
43
|
+
/** The family a modifier is committed with: the PostScript name of the face it names, or its own. */
|
|
44
|
+
function resolved(modifier, postScriptName) {
|
|
45
|
+
const family = modifier?.['family'];
|
|
46
|
+
if (typeof family !== 'string' || !TAKES_FAMILY.has(modifier.$type))
|
|
47
|
+
return family;
|
|
48
|
+
const name = postScriptName(family);
|
|
49
|
+
if (name === undefined)
|
|
50
|
+
warnUnfound(family);
|
|
51
|
+
return name ?? family;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* In development, a family registered by name that no face read at build time has a PostScript
|
|
55
|
+
* name for: a font loaded by hand with `FontRegistry.load`, or a face whose file could not be
|
|
56
|
+
* read. A name nothing registered is left unsaid, since a font built into the system is found by a
|
|
57
|
+
* name no app registers.
|
|
58
|
+
*/
|
|
59
|
+
function warnUnfound(family) {
|
|
60
|
+
if (typeof ngDevMode !== 'undefined' && !ngDevMode)
|
|
61
|
+
return;
|
|
62
|
+
unfound.add(family);
|
|
63
|
+
if (!registeredNames.has(family) || warned.has(family))
|
|
64
|
+
return;
|
|
65
|
+
warned.add(family);
|
|
66
|
+
console.warn(`[angular-native] a SwiftUI 'font' modifier names '${family}', which expo-font registered ` +
|
|
67
|
+
'but SwiftUI cannot find by that name: it draws the system font. Give the modifier the ' +
|
|
68
|
+
"PostScript name inside the font's file instead, or declare the face with @font-face in a " +
|
|
69
|
+
'stylesheet, whose file the build reads the name from.');
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=ui-font-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-font-names.js","sourceRoot":"","sources":["../src/ui-font-names.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A SwiftUI `font` modifier, given the name SwiftUI finds a face by.\n *\n * expo-font registers a face under the name the app gives it (`Brand-700`, as `loadFonts` names\n * the faces a stylesheet declares), and React Native's text finds it there through expo-font's\n * alias. SwiftUI's `font` modifier looks the name up in CoreText, which knows only the PostScript\n * name inside the file, and draws the system font for any other, without a word. So a modifier\n * that names a face as a `<text>` would is committed with the face's PostScript name, which the\n * build read from its file. Compose finds a face by the name it is registered under, so this is\n * for iOS alone.\n */\nimport type { PostScriptNames } from '@ng-native/fabric';\nimport type { UiModifier } from './expo-ui-components.ts';\n\n/** The modifiers that take a family: `@expo/ui`'s `font`, and the segmented picker's copy of it. */\nconst TAKES_FAMILY = new Set(['font', 'ngNativeSegmentedFont']);\n\n/**\n * Each list rewritten, with the families it was rewritten for, so a list committed again while\n * those still resolve as they did is the same list, which native is not sent twice.\n */\nconst rewritten = new WeakMap<\n readonly UiModifier[],\n { readonly modifiers: UiModifier[]; readonly families: readonly unknown[] }\n>();\n\n/** Names registered with expo-font by `Fonts`, which SwiftUI finds only by PostScript name. */\nconst registeredNames = new Set<string>();\n\n/** Families already warned about, once each. */\nconst warned = new Set<string>();\n\n/**\n * Families a modifier named that had no PostScript name, in development: a font still loading\n * when the view was committed is warned about once it registers, since nothing commits it again.\n */\nconst unfound = new Set<string>();\n\ndeclare const ngDevMode: unknown;\n\n/** Note the names `Fonts` registered faces under. See `fontModifierNames`. */\nexport function noteRegisteredNames(names: Iterable<string>): void {\n for (const name of names) {\n registeredNames.add(name);\n if (unfound.has(name)) warnUnfound(name);\n }\n}\n\n/** Rewrites the families of `props.modifiers` that name a face, as `ViewNameOptions.fontNames`. */\nexport function fontModifierNames(\n props: Record<string, unknown>,\n postScriptName: PostScriptNames,\n): void {\n const modifiers = props['modifiers'];\n if (!Array.isArray(modifiers)) return;\n const list = modifiers as readonly UiModifier[];\n const families = list.map((modifier) => resolved(modifier, postScriptName));\n if (families.every((family, i) => family === list[i]?.['family'])) return;\n const known = rewritten.get(list);\n if (known?.families.every((family, i) => family === families[i])) {\n props['modifiers'] = known.modifiers;\n return;\n }\n const next = list.map((modifier, i) =>\n families[i] === modifier?.['family'] ? modifier : { ...modifier, family: families[i] },\n );\n rewritten.set(list, { modifiers: next, families });\n props['modifiers'] = next;\n}\n\n/** The family a modifier is committed with: the PostScript name of the face it names, or its own. */\nfunction resolved(modifier: UiModifier, postScriptName: PostScriptNames): unknown {\n const family = modifier?.['family'];\n if (typeof family !== 'string' || !TAKES_FAMILY.has(modifier.$type)) return family;\n const name = postScriptName(family);\n if (name === undefined) warnUnfound(family);\n return name ?? family;\n}\n\n/**\n * In development, a family registered by name that no face read at build time has a PostScript\n * name for: a font loaded by hand with `FontRegistry.load`, or a face whose file could not be\n * read. A name nothing registered is left unsaid, since a font built into the system is found by a\n * name no app registers.\n */\nfunction warnUnfound(family: string): void {\n if (typeof ngDevMode !== 'undefined' && !ngDevMode) return;\n unfound.add(family);\n if (!registeredNames.has(family) || warned.has(family)) return;\n warned.add(family);\n console.warn(\n `[angular-native] a SwiftUI 'font' modifier names '${family}', which expo-font registered ` +\n 'but SwiftUI cannot find by that name: it draws the system font. Give the modifier the ' +\n \"PostScript name inside the font's file instead, or declare the face with @font-face in a \" +\n 'stylesheet, whose file the build reads the name from.',\n );\n}\n"]}
|
package/dist/updates.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* a moment the user will not lose anything at. Nothing here does it on its own.
|
|
16
16
|
*/
|
|
17
17
|
import { InjectionToken, Service, inject, signal } from '@angular/core';
|
|
18
|
-
import { expoModule } from "./native.js";
|
|
18
|
+
import { expoModule, present } from "./native.js";
|
|
19
19
|
import * as i0 from "@angular/core";
|
|
20
20
|
export class Updates {
|
|
21
21
|
/** Overridden in a test to offer an update without a server. */
|
|
@@ -24,6 +24,9 @@ export class Updates {
|
|
|
24
24
|
const expo = expoModule('expo-updates', () => require('expo-updates'));
|
|
25
25
|
if (!expo)
|
|
26
26
|
return null;
|
|
27
|
+
// Missing from the build: the stand-in, to say so when it is used.
|
|
28
|
+
if (!present(expo))
|
|
29
|
+
return expo;
|
|
27
30
|
return {
|
|
28
31
|
// False in development and in Expo Go, where the bundle comes from Metro. A banner shown
|
|
29
32
|
// there is one that can never resolve.
|
package/dist/updates.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../src/updates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;
|
|
1
|
+
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../src/updates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Updates`, bound to `expo-updates`.\n *\n * ```ts\n * private readonly updates = inject(Updates);\n *\n * // On foreground, where a restart costs the user nothing.\n * async refresh() {\n * if (await this.updates.check()) await this.updates.apply();\n * }\n * ```\n *\n * The awkward part is not checking or downloading - both are plain promises - it is that applying\n * one *restarts the app*. So the shape that matters is: know one is ready, and let the app choose\n * a moment the user will not lose anything at. Nothing here does it on its own.\n */\nimport { InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { expoModule, present } from './native.ts';\n\nexport interface NativeUpdates {\n readonly enabled: boolean;\n check(): Promise<{ isAvailable: boolean }>;\n fetch(): Promise<{ isNew: boolean }>;\n reload(): Promise<void>;\n}\n\nexport type UpdateState = 'idle' | 'checking' | 'downloading' | 'ready' | 'error';\n\n@Service()\nexport class Updates {\n /** Overridden in a test to offer an update without a server. */\n static readonly SOURCE = new InjectionToken<NativeUpdates | null>(\n 'angular-native.updatesSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-updates',\n () => require('expo-updates') as typeof import('expo-updates'),\n );\n if (!expo) return null;\n // Missing from the build: the stand-in, to say so when it is used.\n if (!present(expo)) return expo as never;\n return {\n // False in development and in Expo Go, where the bundle comes from Metro. A banner shown\n // there is one that can never resolve.\n enabled: expo.isEnabled,\n check: async () => ({ isAvailable: (await expo.checkForUpdateAsync()).isAvailable }),\n fetch: async () => ({ isNew: (await expo.fetchUpdateAsync()).isNew }),\n reload: () => expo.reloadAsync(),\n };\n },\n },\n );\n\n private readonly native = inject(Updates.SOURCE);\n private readonly current = signal<UpdateState>('idle');\n private readonly failure = signal<unknown>(null);\n private readonly downloaded = signal(false);\n\n readonly state: Signal<UpdateState> = this.current.asReadonly();\n readonly error: Signal<unknown> = this.failure.asReadonly();\n\n /**\n * Downloaded and waiting, including while a later check runs and after one fails. The only time\n * `apply()` does anything.\n */\n readonly ready: Signal<boolean> = this.downloaded.asReadonly();\n\n /**\n * Disabled in development and in Expo Go, where the bundle comes from Metro. An app that shows\n * an \"update available\" banner should hide it here rather than show one that can never resolve.\n */\n get enabled(): boolean {\n return this.native?.enabled ?? false;\n }\n\n /**\n * Check, and download if there is one. Resolves to whether an update is now waiting.\n *\n * One method rather than two because there is nothing useful to do between them: an app that\n * checks and does not download has learnt something it cannot act on.\n */\n async check(): Promise<boolean> {\n if (!this.native?.enabled) return false;\n\n try {\n this.current.set('checking');\n const { isAvailable } = await this.native.check();\n if (isAvailable) {\n this.current.set('downloading');\n const { isNew } = await this.native.fetch();\n if (isNew) this.downloaded.set(true);\n }\n this.failure.set(null);\n this.current.set(this.downloaded() ? 'ready' : 'idle');\n } catch (error) {\n this.failure.set(error);\n this.current.set(this.downloaded() ? 'ready' : 'error');\n }\n return this.downloaded();\n }\n\n /**\n * Restart into the downloaded update.\n *\n * **This restarts the app**, which is why it is never automatic: the moment to do it is one the\n * app knows and this does not - not mid-form, not mid-upload, usually on next foreground.\n */\n async apply(): Promise<void> {\n if (this.downloaded()) await this.native?.reload();\n }\n}\n"]}
|
package/dist/video.js
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
* ```ts
|
|
5
5
|
* protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });
|
|
6
6
|
*
|
|
7
|
-
* // <expo-video [player]="player.
|
|
7
|
+
* // <expo-video [player]="player.id" />
|
|
8
8
|
* // {{ player.state().currentTime }}
|
|
9
9
|
* ```
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { expoModuleNow, unavailable } from "./native.js";
|
|
12
12
|
import { ownPlayer, watchPlayer } from "./player.js";
|
|
13
13
|
/** A video player owned by the current component, released when it is destroyed. */
|
|
14
14
|
export function videoPlayer(source, options = {}) {
|
|
15
|
-
const expo =
|
|
15
|
+
const expo = expoModuleNow('expo-video', () => require('expo-video'), ['ios', 'android', 'web']);
|
|
16
16
|
if (!expo)
|
|
17
17
|
throw unavailable('expo-video', 'Stand in for the player in the test.');
|
|
18
18
|
const native = expo.createVideoPlayer(source);
|
package/dist/video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;AAElE,oFAAoF;AACpF,MAAM,UAAU,WAAW,CACzB,MAAwC,EACxC,UAAmC,EAAE;IAErC,MAAM,IAAI,GAAG,aAAa,CACxB,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,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * `videoPlayer`, an `expo-video` player owned by the component that makes it.\n *\n * ```ts\n * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // <expo-video [player]=\"player.id\" />\n * // {{ player.state().currentTime }}\n * ```\n */\nimport { expoModuleNow, unavailable } from './native.ts';\nimport { ownPlayer, watchPlayer, type Player } from './player.ts';\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 = expoModuleNow(\n 'expo-video',\n () => require('expo-video') as typeof import('expo-video'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw unavailable('expo-video', 'Stand in for the player in the test.');\n const native = expo.createVideoPlayer(source);\n return ownPlayer(native, () => watchPlayer(native, options));\n}\n"]}
|
package/dist/watch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DestroyRef, ErrorHandler, InjectionToken, Service, inject, signal, } from '@angular/core';
|
|
2
|
-
import { expoModule } from "./native.js";
|
|
2
|
+
import { expoModule, present } from "./native.js";
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
const ERRORS = [
|
|
5
5
|
['activation-error', 'activation'],
|
|
@@ -46,7 +46,7 @@ export class Watch {
|
|
|
46
46
|
...(ngDevMode ? [{ debugName: "transfersState" }] : /* istanbul ignore next */ []));
|
|
47
47
|
errorState = signal(null, /* @ts-ignore */
|
|
48
48
|
...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
49
|
-
available = this.native !== null;
|
|
49
|
+
available = present(this.native) !== null;
|
|
50
50
|
paired = this.pairedState.asReadonly();
|
|
51
51
|
installed = this.installedState.asReadonly();
|
|
52
52
|
reachable = this.reachableState.asReadonly();
|
|
@@ -57,7 +57,7 @@ export class Watch {
|
|
|
57
57
|
transfers = this.transfersState.asReadonly();
|
|
58
58
|
error = this.errorState.asReadonly();
|
|
59
59
|
constructor() {
|
|
60
|
-
const native = this.native;
|
|
60
|
+
const native = present(this.native);
|
|
61
61
|
if (!native)
|
|
62
62
|
return;
|
|
63
63
|
const events = native.watchEvents;
|
|
@@ -134,7 +134,8 @@ export class Watch {
|
|
|
134
134
|
update(context) {
|
|
135
135
|
if (!this.native)
|
|
136
136
|
return;
|
|
137
|
-
|
|
137
|
+
// Missing from the build, the stand-in is called rather than held for later: it says so.
|
|
138
|
+
if (this.ready || !present(this.native))
|
|
138
139
|
this.native.updateApplicationContext(context);
|
|
139
140
|
else
|
|
140
141
|
this.pendingContext = context;
|
|
@@ -157,7 +158,7 @@ export class Watch {
|
|
|
157
158
|
const native = this.native;
|
|
158
159
|
if (!native)
|
|
159
160
|
return;
|
|
160
|
-
if (!this.ready)
|
|
161
|
+
if (!this.ready && present(native))
|
|
161
162
|
this.pendingTransfers.push([complication, userInfo]);
|
|
162
163
|
else if (complication)
|
|
163
164
|
native.transferCurrentComplicationUserInfo(userInfo);
|