@ng-native/expo 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,10 +18,12 @@ optional peer dependency: install only the ones the app actually uses.
18
18
 
19
19
  One entry point per module, so importing haptics does not bundle the file system. Every source
20
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`. Each is an
22
- `InjectionToken` carrying its own factory, so there is nothing to provide and nothing to register -
23
- injecting it is the whole setup, and a service nobody injects is never constructed. Each name is a
24
- type as well as a token, so `inject(Clipboard)` and `private clipboard: Clipboard` both work.
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.
25
27
 
26
28
  ```ts
27
29
  import { Clipboard } from '@ng-native/expo/clipboard';
@@ -128,11 +130,10 @@ mount(rootTag, App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalSty
128
130
  the bundler ships the file - a plain path would be a font that is simply missing on the device
129
131
  with nothing anywhere to say why.
130
132
 
131
- **There is no font matching on a device.** Native looks a family up by name and that is all, so a
132
- bold cut is a family of its own: the second face above is registered as `Inter-700` as well, and
133
- a rule that wants it asks for `font-family: Inter-700`. Writing `font-weight: 700` against a
134
- family with one registered face gets whatever the platform synthesizes, exactly as it would in a
135
- React Native app.
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.
136
137
 
137
138
  `loadFonts` takes any number of sheets and does nothing at all when none of them declare a face,
138
139
  so bootstrap can call it unconditionally. Injecting `Fonts` afterwards answers what is loaded.
@@ -248,7 +249,8 @@ AppRegistry.registerRunnable('main', ({ rootTag }) => {
248
249
  ```
249
250
 
250
251
  Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive is
251
- behind the splash, so nothing is ever seen in the fallback face and nothing reflows.
252
+ laid out again as each face registers, behind the splash, so nothing is ever seen in the fallback
253
+ face.
252
254
  `hideWhenReady` waits one frame after the work, because hiding the instant it resolves uncovers
253
255
  the frame that was on screen while it ran - and it uncovers even when the work fails, rather than
254
256
  leaving a splash screen up forever.
@@ -345,8 +347,8 @@ plain promise, and what the hook adds is a _lifecycle_.
345
347
  has learned something it cannot act on. `apply()` **restarts the app**, which is why nothing here
346
348
  does it automatically: the moment is one the app knows and this does not. In Expo Go it reports
347
349
  itself disabled rather than offering a banner that can never resolve.
348
- - **`Assets`** preloads the images a screen should not pop in with, and resolves even when one
349
- fails - a picture that will not appear is not a reason for the screen behind it to fail too.
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.
350
352
  - **`KeepAwake.hold()`** returns the release, and is never a bare activate: a screen that holds the
351
353
  display on and never lets go is a phone that never sleeps, which the user experiences as a
352
354
  battery fault and never attributes to the app that caused it.
@@ -57,7 +57,7 @@ export declare class FileSystem {
57
57
  * hits. It replaces what was there.
58
58
  */
59
59
  write(file: NativeFile, content: string | Uint8Array): void;
60
- private file;
60
+ private installed;
61
61
  static ɵfac: i0.ɵɵFactoryDeclaration<FileSystem, never>;
62
62
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
63
63
  }
@@ -37,11 +37,13 @@ export class FileSystem {
37
37
  native = inject(FileSystem.SOURCE);
38
38
  /** A file the system may delete when the device runs low on storage. */
39
39
  cache(name) {
40
- return this.file(this.native.cacheDirectory, name);
40
+ const native = this.installed();
41
+ return native.file(native.cacheDirectory, name);
41
42
  }
42
43
  /** A file that survives, and is included in backups. */
43
44
  document(name) {
44
- return this.file(this.native.documentDirectory, name);
45
+ const native = this.installed();
46
+ return native.file(native.documentDirectory, name);
45
47
  }
46
48
  /**
47
49
  * Write text, or bytes for anything that is not text, whether or not the file is there already.
@@ -53,10 +55,10 @@ export class FileSystem {
53
55
  file.create({ intermediates: true });
54
56
  file.write(content);
55
57
  }
56
- file(directory, name) {
58
+ installed() {
57
59
  if (!this.native)
58
60
  throw new Error('[angular-native] expo-file-system is not installed');
59
- return this.native.file(directory, name);
61
+ return this.native;
60
62
  }
61
63
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: FileSystem, deps: [], target: i0.ɵɵFactoryTarget.Service });
62
64
  static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: FileSystem });
@@ -1 +1 @@
1
- {"version":3,"file":"file-system.js","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;AA+BzC;;;;;;GAMG;AAEH,MAAM,OAAO,UAAU;IACrB,kEAAkE;IAClE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,iCAAiC,EACjC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,kBAAkB,EAClB,GAAG,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAsC,CACvE,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,OAAO;gBACL,6EAA6E;gBAC7E,IAAI,cAAc;oBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC1B,CAAC;gBACD,IAAI,iBAAiB;oBACnB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC7B,CAAC;gBACD,IAAI,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CACxB,IAAI,IAAI,CAAC,IAAI,CAAC,SAAgD,EAAE,IAAI,CAAC;aACxE,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpD,wEAAwE;IACxE,KAAK,CAAC,IAAY;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,wDAAwD;IACxD,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAgB,EAAE,OAA4B;QAClD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAEO,IAAI,CAAC,SAA0B,EAAE,IAAY;QACnD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxF,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;uGAnDU,UAAU;wGAAV,UAAU;;2FAAV,UAAU;kBADtB,OAAO","sourcesContent":["/**\n * `FileSystem`, bound to `expo-file-system`.\n *\n * Files located by what they are for rather than by path. `File` and `Paths` are Expo's own\n * classes over a native one, so they are used as published; this only decides which directory a\n * name lands in and hands the file back.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/**\n * A file. Structural on purpose: this is Expo's `File`, described in terms a test can satisfy, so\n * a fake needs no Expo. An app gets the real object back and may use the rest of its surface.\n */\nexport interface NativeFile {\n readonly uri: string;\n readonly exists: boolean;\n readonly size: number;\n create(options?: { overwrite?: boolean; intermediates?: boolean }): void;\n write(content: string | Uint8Array): void;\n text(): Promise<string>;\n textSync(): string;\n /** The contents as bytes: a PDF, an image, anything that is not text. */\n bytes(): Promise<Uint8Array>;\n delete(): void;\n}\n\n/** A directory, as far as this needs to know: something a file can be named inside. */\nexport type NativeDirectory = object;\n\n/** The slice of `expo-file-system` this needs. */\nexport interface NativeFiles {\n /** Cleared by the system under storage pressure. */\n readonly cacheDirectory: NativeDirectory;\n /** Survives, and is backed up. */\n readonly documentDirectory: NativeDirectory;\n file(directory: NativeDirectory, name: string): NativeFile;\n}\n\n/**\n * The two directories an app actually chooses between, plus the create-then-write dance that\n * writing a file for the first time needs.\n *\n * ponytail: a locator, not a file API. Everything else - moving, listing, downloading, streaming\n * - is on the object this hands back, documented by Expo, and not worth restating here.\n */\n@Service()\nexport class FileSystem {\n /** Overridden in a test to write files that are not on a disk. */\n static readonly SOURCE = new InjectionToken<NativeFiles | null>(\n 'angular-native.fileSystemSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-file-system',\n () => require('expo-file-system') as typeof import('expo-file-system'),\n );\n if (!expo) return null;\n return {\n // Both are getters on Expo's side, handing back a fresh Directory each time.\n get cacheDirectory() {\n return expo.Paths.cache;\n },\n get documentDirectory() {\n return expo.Paths.document;\n },\n file: (directory, name) =>\n new expo.File(directory as InstanceType<typeof expo.Directory>, name),\n };\n },\n },\n );\n\n private readonly native = inject(FileSystem.SOURCE);\n\n /** A file the system may delete when the device runs low on storage. */\n cache(name: string): NativeFile {\n return this.file(this.native!.cacheDirectory, name);\n }\n\n /** A file that survives, and is included in backups. */\n document(name: string): NativeFile {\n return this.file(this.native!.documentDirectory, name);\n }\n\n /**\n * Write text, or bytes for anything that is not text, whether or not the file is there already.\n * `write` alone throws on a file that has never been created, which is the first thing everyone\n * hits. It replaces what was there.\n */\n write(file: NativeFile, content: string | Uint8Array): void {\n if (!file.exists) file.create({ intermediates: true });\n file.write(content);\n }\n\n private file(directory: NativeDirectory, name: string): NativeFile {\n if (!this.native) throw new Error('[angular-native] expo-file-system is not installed');\n return this.native.file(directory, name);\n }\n}\n"]}
1
+ {"version":3,"file":"file-system.js","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;AA+BzC;;;;;;GAMG;AAEH,MAAM,OAAO,UAAU;IACrB,kEAAkE;IAClE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,iCAAiC,EACjC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,kBAAkB,EAClB,GAAG,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAsC,CACvE,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,OAAO;gBACL,6EAA6E;gBAC7E,IAAI,cAAc;oBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC1B,CAAC;gBACD,IAAI,iBAAiB;oBACnB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC7B,CAAC;gBACD,IAAI,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CACxB,IAAI,IAAI,CAAC,IAAI,CAAC,SAAgD,EAAE,IAAI,CAAC;aACxE,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpD,wEAAwE;IACxE,KAAK,CAAC,IAAY;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,wDAAwD;IACxD,QAAQ,CAAC,IAAY;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAgB,EAAE,OAA4B;QAClD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAEO,SAAS;QACf,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxF,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;uGArDU,UAAU;wGAAV,UAAU;;2FAAV,UAAU;kBADtB,OAAO","sourcesContent":["/**\n * `FileSystem`, bound to `expo-file-system`.\n *\n * Files located by what they are for rather than by path. `File` and `Paths` are Expo's own\n * classes over a native one, so they are used as published; this only decides which directory a\n * name lands in and hands the file back.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/**\n * A file. Structural on purpose: this is Expo's `File`, described in terms a test can satisfy, so\n * a fake needs no Expo. An app gets the real object back and may use the rest of its surface.\n */\nexport interface NativeFile {\n readonly uri: string;\n readonly exists: boolean;\n readonly size: number;\n create(options?: { overwrite?: boolean; intermediates?: boolean }): void;\n write(content: string | Uint8Array): void;\n text(): Promise<string>;\n textSync(): string;\n /** The contents as bytes: a PDF, an image, anything that is not text. */\n bytes(): Promise<Uint8Array>;\n delete(): void;\n}\n\n/** A directory, as far as this needs to know: something a file can be named inside. */\nexport type NativeDirectory = object;\n\n/** The slice of `expo-file-system` this needs. */\nexport interface NativeFiles {\n /** Cleared by the system under storage pressure. */\n readonly cacheDirectory: NativeDirectory;\n /** Survives, and is backed up. */\n readonly documentDirectory: NativeDirectory;\n file(directory: NativeDirectory, name: string): NativeFile;\n}\n\n/**\n * The two directories an app actually chooses between, plus the create-then-write dance that\n * writing a file for the first time needs.\n *\n * ponytail: a locator, not a file API. Everything else - moving, listing, downloading, streaming\n * - is on the object this hands back, documented by Expo, and not worth restating here.\n */\n@Service()\nexport class FileSystem {\n /** Overridden in a test to write files that are not on a disk. */\n static readonly SOURCE = new InjectionToken<NativeFiles | null>(\n 'angular-native.fileSystemSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-file-system',\n () => require('expo-file-system') as typeof import('expo-file-system'),\n );\n if (!expo) return null;\n return {\n // Both are getters on Expo's side, handing back a fresh Directory each time.\n get cacheDirectory() {\n return expo.Paths.cache;\n },\n get documentDirectory() {\n return expo.Paths.document;\n },\n file: (directory, name) =>\n new expo.File(directory as InstanceType<typeof expo.Directory>, name),\n };\n },\n },\n );\n\n private readonly native = inject(FileSystem.SOURCE);\n\n /** A file the system may delete when the device runs low on storage. */\n cache(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.cacheDirectory, name);\n }\n\n /** A file that survives, and is included in backups. */\n document(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.documentDirectory, name);\n }\n\n /**\n * Write text, or bytes for anything that is not text, whether or not the file is there already.\n * `write` alone throws on a file that has never been created, which is the first thing everyone\n * hits. It replaces what was there.\n */\n write(file: NativeFile, content: string | Uint8Array): void {\n if (!file.exists) file.create({ intermediates: true });\n file.write(content);\n }\n\n private installed(): NativeFiles {\n if (!this.native) throw new Error('[angular-native] expo-file-system is not installed');\n return this.native;\n }\n}\n"]}
package/dist/fonts.d.ts CHANGED
@@ -43,10 +43,12 @@ export interface NativeFonts {
43
43
  * Loading is by family name, which is also all native matches on.
44
44
  *
45
45
  * There is no weight matching on a device: `font-family: Inter` finds the face registered under
46
- * exactly that name, and a bold cut is a family of its own. A face that declares a weight is
47
- * therefore registered under a composed name as well as its own, so a stylesheet can say
48
- * `font-family: Inter-700` and get the file it declared - and a sheet that only ever registers
49
- * one weight per family needs to know none of this.
46
+ * exactly that name, and a bold cut is a family of its own. A face that declares a weight or a
47
+ * style is therefore registered under a composed name as well (`Inter-700`, `Inter-italic`,
48
+ * `Inter-700-italic`), which is the name the engine points a matching text at.
49
+ *
50
+ * A face with both a weight and a style also answers to each alone, as it did before it had a
51
+ * name of its own, but only where no face declared exactly that.
50
52
  */
51
53
  export declare function registrationsFor(faces: readonly FontFace[]): Record<string, unknown>;
52
54
  export declare function expoFonts(): NativeFonts | null;
@@ -71,9 +73,9 @@ export declare class FontRegistry {
71
73
  /**
72
74
  * Register every `@font-face` a compiled sheet declares.
73
75
  *
74
- * Awaited before `mount` in an app that uses one: text laid out before this resolves is laid
75
- * out in the fallback face. Sheets with no faces resolve immediately, so calling it
76
- * unconditionally at bootstrap costs nothing.
76
+ * Called before `mount` in an app that uses one. Text laid out before a face registers is in
77
+ * the fallback face until it does, and is laid out again then. Sheets with no faces resolve
78
+ * immediately, so calling it unconditionally at bootstrap costs nothing.
77
79
  */
78
80
  loadSheet(...sheets: readonly (SheetWithFonts | null | undefined)[]): Promise<void>;
79
81
  /** Register faces by name, for a font that did not come from a stylesheet. */
package/dist/fonts.js CHANGED
@@ -21,24 +21,30 @@
21
21
  * here would be copying out its internals rather than using it.
22
22
  */
23
23
  import { InjectionToken, computed, signal } from '@angular/core';
24
+ import { faceName, fontsRegistered } from '@ng-native/fabric';
24
25
  import { expoModule } from "./native.js";
25
26
  /**
26
27
  * Loading is by family name, which is also all native matches on.
27
28
  *
28
29
  * There is no weight matching on a device: `font-family: Inter` finds the face registered under
29
- * exactly that name, and a bold cut is a family of its own. A face that declares a weight is
30
- * therefore registered under a composed name as well as its own, so a stylesheet can say
31
- * `font-family: Inter-700` and get the file it declared - and a sheet that only ever registers
32
- * one weight per family needs to know none of this.
30
+ * exactly that name, and a bold cut is a family of its own. A face that declares a weight or a
31
+ * style is therefore registered under a composed name as well (`Inter-700`, `Inter-italic`,
32
+ * `Inter-700-italic`), which is the name the engine points a matching text at.
33
+ *
34
+ * A face with both a weight and a style also answers to each alone, as it did before it had a
35
+ * name of its own, but only where no face declared exactly that.
33
36
  */
34
37
  export function registrationsFor(faces) {
35
38
  const map = {};
36
39
  for (const face of faces) {
37
40
  map[face.family] ??= face.source;
38
- if (face.weight !== undefined)
39
- map[`${face.family}-${face.weight}`] = face.source;
40
- if (face.style)
41
- map[`${face.family}-${face.style}`] = face.source;
41
+ map[faceName(face)] = face.source;
42
+ }
43
+ for (const face of faces) {
44
+ if (face.weight === undefined || !face.style)
45
+ continue;
46
+ map[`${face.family}-${face.weight}`] ??= face.source;
47
+ map[`${face.family}-${face.style}`] ??= face.source;
42
48
  }
43
49
  return map;
44
50
  }
@@ -85,9 +91,9 @@ export class FontRegistry {
85
91
  /**
86
92
  * Register every `@font-face` a compiled sheet declares.
87
93
  *
88
- * Awaited before `mount` in an app that uses one: text laid out before this resolves is laid
89
- * out in the fallback face. Sheets with no faces resolve immediately, so calling it
90
- * unconditionally at bootstrap costs nothing.
94
+ * Called before `mount` in an app that uses one. Text laid out before a face registers is in
95
+ * the fallback face until it does, and is laid out again then. Sheets with no faces resolve
96
+ * immediately, so calling it unconditionally at bootstrap costs nothing.
91
97
  */
92
98
  async loadSheet(...sheets) {
93
99
  const faces = sheets.flatMap((sheet) => [...(sheet?.fonts ?? [])]);
@@ -97,9 +103,17 @@ export class FontRegistry {
97
103
  }
98
104
  /** Register faces by name, for a font that did not come from a stylesheet. */
99
105
  async load(map) {
100
- if (!this.native)
106
+ const native = this.native;
107
+ if (!native)
101
108
  return;
102
- await this.native.loadAsync(map);
109
+ try {
110
+ await native.loadAsync(map);
111
+ }
112
+ finally {
113
+ // Text already laid out in the fallback face keeps it until it is laid out again. Only the
114
+ // faces that registered: one failing fails the whole load, not the others in it.
115
+ fontsRegistered(Object.keys(map).filter((family) => native.isLoaded(family)));
116
+ }
103
117
  this.generation.update((n) => n + 1);
104
118
  }
105
119
  }
package/dist/fonts.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fonts.js","sourceRoot":"","sources":["../src/fonts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Fonts`, bound to `expo-font`.\n *\n * A font on a phone is not fetched when the text engine first needs it. It has to be registered\n * with the platform up front, or the first paint is in the fallback face and reflows when the\n * real one lands - which is why the interesting call here is the one an app makes *before* it\n * mounts:\n *\n * ```ts\n * import { loadFonts } from '@ng-native/expo/fonts';\n *\n * await loadFonts(styleSheetOf(GlobalStyles));\n * mount(rootTag, App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });\n * ```\n *\n * The class takes its platform rather than injecting it, because that call happens before there\n * is an injector to inject from. `inject(Fonts)` is for a screen that wants to ask what loaded.\n *\n * Unlike the other services here this goes through `expo-font`'s JavaScript rather than its\n * native module: the module takes descriptors the JS builds out of an asset, and rebuilding them\n * here would be copying out its internals rather than using it.\n */\nimport { InjectionToken, computed, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** A face a stylesheet declared, as the compiler collected it. */\nexport interface FontFace {\n readonly family: string;\n /** What `require('./Inter.ttf')` returned: an asset id, or a module the bundler resolved. */\n readonly source: unknown;\n readonly weight?: number;\n readonly style?: string;\n}\n\n/** Just enough of a compiled stylesheet to find the faces in it. */\nexport interface SheetWithFonts {\n readonly fonts?: readonly FontFace[];\n}\n\n/** The slice of `expo-font` this needs. */\nexport interface NativeFonts {\n loadAsync(map: Record<string, unknown>): Promise<void>;\n isLoaded(family: string): boolean;\n getLoadedFonts(): string[];\n}\n\n/**\n * Loading is by family name, which is also all native matches on.\n *\n * There is no weight matching on a device: `font-family: Inter` finds the face registered under\n * exactly that name, and a bold cut is a family of its own. A face that declares a weight is\n * therefore registered under a composed name as well as its own, so a stylesheet can say\n * `font-family: Inter-700` and get the file it declared - and a sheet that only ever registers\n * one weight per family needs to know none of this.\n */\nexport function registrationsFor(faces: readonly FontFace[]): Record<string, unknown> {\n const map: Record<string, unknown> = {};\n for (const face of faces) {\n map[face.family] ??= face.source;\n if (face.weight !== undefined) map[`${face.family}-${face.weight}`] = face.source;\n if (face.style) map[`${face.family}-${face.style}`] = face.source;\n }\n return map;\n}\n\nexport function expoFonts(): NativeFonts | null {\n const expo = expoModule('expo-font', () => require('expo-font') as typeof import('expo-font'));\n if (!expo) return null;\n return {\n loadAsync: (map) => expo.loadAsync(map as Parameters<typeof expo.loadAsync>[0]),\n isLoaded: (family) => expo.isLoaded(family),\n getLoadedFonts: () => expo.getLoadedFonts(),\n };\n}\n\nexport class FontRegistry {\n private readonly native: NativeFonts | null;\n /**\n * Bumped whenever faces are registered.\n *\n * The platform's own list is a plain array that changes underneath us, so a template binding to\n * it would render once and never again - and the one moment it changes is exactly the moment a\n * screen waiting on a font wants to hear about. This makes both readers reactive without\n * mirroring the list.\n */\n private readonly generation = signal(0);\n\n constructor(native: NativeFonts | null) {\n this.native = native;\n }\n\n /** Whether `expo-font` is installed at all. Without it a custom face simply never appears. */\n get available(): boolean {\n return this.native !== null;\n }\n\n /** Every family the platform can currently find, custom or bundled. */\n readonly families: Signal<readonly string[]> = computed(() => {\n this.generation();\n return this.native?.getLoadedFonts() ?? [];\n });\n\n /** Whether a family is registered. Reactive: re-read after a `load` that adds one. */\n has(family: string): boolean {\n this.generation();\n return this.native?.isLoaded(family) ?? false;\n }\n\n /**\n * Register every `@font-face` a compiled sheet declares.\n *\n * Awaited before `mount` in an app that uses one: text laid out before this resolves is laid\n * out in the fallback face. Sheets with no faces resolve immediately, so calling it\n * unconditionally at bootstrap costs nothing.\n */\n async loadSheet(...sheets: readonly (SheetWithFonts | null | undefined)[]): Promise<void> {\n const faces = sheets.flatMap((sheet) => [...(sheet?.fonts ?? [])]);\n if (!faces.length) return;\n await this.load(registrationsFor(faces));\n }\n\n /** Register faces by name, for a font that did not come from a stylesheet. */\n async load(map: Record<string, unknown>): Promise<void> {\n if (!this.native) return;\n await this.native.loadAsync(map);\n this.generation.update((n) => n + 1);\n }\n}\n\n/** Injected where an app wants to ask what is loaded. */\nexport const Fonts = new InjectionToken<FontRegistry>('angular-native.fonts', {\n factory: () => new FontRegistry(expoFonts()),\n});\nexport type Fonts = FontRegistry;\n\n/**\n * Register every face the given sheets declare, before the app mounts.\n *\n * A function rather than a service because that is when it is needed: there is no injector yet.\n */\nexport function loadFonts(\n ...sheets: readonly (SheetWithFonts | null | undefined)[]\n): Promise<void> {\n return new FontRegistry(expoFonts()).loadSheet(...sheets);\n}\n"]}
1
+ {"version":3,"file":"fonts.js","sourceRoot":"","sources":["../src/fonts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuBzC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA0B;IACzD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC;QACjC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IACpC,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,SAAS;QACvD,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC;QACrD,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC;IACtD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,CAA+B,CAAC,CAAC;IAC/F,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO;QACL,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAA2C,CAAC;QAC/E,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,YAAY;IACN,MAAM,CAAqB;IAC5C;;;;;;;OAOG;IACc,UAAU,GAAG,MAAM,CAAC,CAAC;mFAAC,CAAC;IAExC,YAAY,MAA0B;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,8FAA8F;IAC9F,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED,uEAAuE;IAC9D,QAAQ,GAA8B,QAAQ,CAAC,GAAG,EAAE;QAC3D,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;iFAAC,CAAC;IAEH,sFAAsF;IACtF,GAAG,CAAC,MAAc;QAChB,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC;IAChD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CAAC,GAAG,MAAsD;QACvE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,IAAI,CAAC,GAA4B;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;gBAAS,CAAC;YACT,2FAA2F;YAC3F,iFAAiF;YACjF,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAChF,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,CAAC;CACF;AAED,yDAAyD;AACzD,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,cAAc,CAAe,sBAAsB,EAAE;IAC5E,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;CAC7C,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,UAAU,SAAS,CACvB,GAAG,MAAsD;IAEzD,OAAO,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC;AAC5D,CAAC","sourcesContent":["/**\n * `Fonts`, bound to `expo-font`.\n *\n * A font on a phone is not fetched when the text engine first needs it. It has to be registered\n * with the platform up front, or the first paint is in the fallback face and reflows when the\n * real one lands - which is why the interesting call here is the one an app makes *before* it\n * mounts:\n *\n * ```ts\n * import { loadFonts } from '@ng-native/expo/fonts';\n *\n * await loadFonts(styleSheetOf(GlobalStyles));\n * mount(rootTag, App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });\n * ```\n *\n * The class takes its platform rather than injecting it, because that call happens before there\n * is an injector to inject from. `inject(Fonts)` is for a screen that wants to ask what loaded.\n *\n * Unlike the other services here this goes through `expo-font`'s JavaScript rather than its\n * native module: the module takes descriptors the JS builds out of an asset, and rebuilding them\n * here would be copying out its internals rather than using it.\n */\nimport { InjectionToken, computed, signal, type Signal } from '@angular/core';\nimport { faceName, fontsRegistered } from '@ng-native/fabric';\nimport { expoModule } from './native.ts';\n\n/** A face a stylesheet declared, as the compiler collected it. */\nexport interface FontFace {\n readonly family: string;\n /** What `require('./Inter.ttf')` returned: an asset id, or a module the bundler resolved. */\n readonly source: unknown;\n readonly weight?: number;\n readonly style?: string;\n}\n\n/** Just enough of a compiled stylesheet to find the faces in it. */\nexport interface SheetWithFonts {\n readonly fonts?: readonly FontFace[];\n}\n\n/** The slice of `expo-font` this needs. */\nexport interface NativeFonts {\n loadAsync(map: Record<string, unknown>): Promise<void>;\n isLoaded(family: string): boolean;\n getLoadedFonts(): string[];\n}\n\n/**\n * Loading is by family name, which is also all native matches on.\n *\n * There is no weight matching on a device: `font-family: Inter` finds the face registered under\n * exactly that name, and a bold cut is a family of its own. A face that declares a weight or a\n * style is therefore registered under a composed name as well (`Inter-700`, `Inter-italic`,\n * `Inter-700-italic`), which is the name the engine points a matching text at.\n *\n * A face with both a weight and a style also answers to each alone, as it did before it had a\n * name of its own, but only where no face declared exactly that.\n */\nexport function registrationsFor(faces: readonly FontFace[]): Record<string, unknown> {\n const map: Record<string, unknown> = {};\n for (const face of faces) {\n map[face.family] ??= face.source;\n map[faceName(face)] = face.source;\n }\n for (const face of faces) {\n if (face.weight === undefined || !face.style) continue;\n map[`${face.family}-${face.weight}`] ??= face.source;\n map[`${face.family}-${face.style}`] ??= face.source;\n }\n return map;\n}\n\nexport function expoFonts(): NativeFonts | null {\n const expo = expoModule('expo-font', () => require('expo-font') as typeof import('expo-font'));\n if (!expo) return null;\n return {\n loadAsync: (map) => expo.loadAsync(map as Parameters<typeof expo.loadAsync>[0]),\n isLoaded: (family) => expo.isLoaded(family),\n getLoadedFonts: () => expo.getLoadedFonts(),\n };\n}\n\nexport class FontRegistry {\n private readonly native: NativeFonts | null;\n /**\n * Bumped whenever faces are registered.\n *\n * The platform's own list is a plain array that changes underneath us, so a template binding to\n * it would render once and never again - and the one moment it changes is exactly the moment a\n * screen waiting on a font wants to hear about. This makes both readers reactive without\n * mirroring the list.\n */\n private readonly generation = signal(0);\n\n constructor(native: NativeFonts | null) {\n this.native = native;\n }\n\n /** Whether `expo-font` is installed at all. Without it a custom face simply never appears. */\n get available(): boolean {\n return this.native !== null;\n }\n\n /** Every family the platform can currently find, custom or bundled. */\n readonly families: Signal<readonly string[]> = computed(() => {\n this.generation();\n return this.native?.getLoadedFonts() ?? [];\n });\n\n /** Whether a family is registered. Reactive: re-read after a `load` that adds one. */\n has(family: string): boolean {\n this.generation();\n return this.native?.isLoaded(family) ?? false;\n }\n\n /**\n * Register every `@font-face` a compiled sheet declares.\n *\n * Called before `mount` in an app that uses one. Text laid out before a face registers is in\n * the fallback face until it does, and is laid out again then. Sheets with no faces resolve\n * immediately, so calling it unconditionally at bootstrap costs nothing.\n */\n async loadSheet(...sheets: readonly (SheetWithFonts | null | undefined)[]): Promise<void> {\n const faces = sheets.flatMap((sheet) => [...(sheet?.fonts ?? [])]);\n if (!faces.length) return;\n await this.load(registrationsFor(faces));\n }\n\n /** Register faces by name, for a font that did not come from a stylesheet. */\n async load(map: Record<string, unknown>): Promise<void> {\n const native = this.native;\n if (!native) return;\n try {\n await native.loadAsync(map);\n } finally {\n // Text already laid out in the fallback face keeps it until it is laid out again. Only the\n // faces that registered: one failing fails the whole load, not the others in it.\n fontsRegistered(Object.keys(map).filter((family) => native.isLoaded(family)));\n }\n this.generation.update((n) => n + 1);\n }\n}\n\n/** Injected where an app wants to ask what is loaded. */\nexport const Fonts = new InjectionToken<FontRegistry>('angular-native.fonts', {\n factory: () => new FontRegistry(expoFonts()),\n});\nexport type Fonts = FontRegistry;\n\n/**\n * Register every face the given sheets declare, before the app mounts.\n *\n * A function rather than a service because that is when it is needed: there is no injector yet.\n */\nexport function loadFonts(\n ...sheets: readonly (SheetWithFonts | null | undefined)[]\n): Promise<void> {\n return new FontRegistry(expoFonts()).loadSheet(...sheets);\n}\n"]}
@@ -21,7 +21,8 @@
21
21
  * ```
22
22
  *
23
23
  * Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive
24
- * is behind the splash, so nothing is ever seen in the fallback face and nothing reflows.
24
+ * is laid out again as each face registers, behind the splash, so nothing is ever seen in the
25
+ * fallback face.
25
26
  *
26
27
  * `hideWhenReady` is that shape as one call, which is the only thing worth wrapping here: every
27
28
  * function underneath is already a plain promise with no React in it. The class takes its
@@ -21,7 +21,8 @@
21
21
  * ```
22
22
  *
23
23
  * Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive
24
- * is behind the splash, so nothing is ever seen in the fallback face and nothing reflows.
24
+ * is laid out again as each face registers, behind the splash, so nothing is ever seen in the
25
+ * fallback face.
25
26
  *
26
27
  * `hideWhenReady` is that shape as one call, which is the only thing worth wrapping here: every
27
28
  * function underneath is already a plain promise with no React in it. The class takes its
@@ -1 +1 @@
1
- {"version":3,"file":"splash-screen.js","sourceRoot":"","sources":["../src/splash-screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,UAAU,CACrB,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 behind the splash, so nothing is ever seen in the fallback face and nothing reflows.\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 { expoModule } 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 = expoModule(\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"]}
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,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,UAAU,CACrB,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 { expoModule } 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 = expoModule(\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
@@ -132,7 +132,14 @@ export class Store {
132
132
  throw failed.error;
133
133
  }
134
134
  readSync(key) {
135
- const raw = this.native?.getSync?.(key);
135
+ let raw;
136
+ try {
137
+ raw = this.native?.getSync?.(key);
138
+ }
139
+ catch (error) {
140
+ this.fail(error);
141
+ return ABSENT;
142
+ }
136
143
  return raw == null ? ABSENT : decode(raw);
137
144
  }
138
145
  /**
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 * `Storage` and `SecureStorage`, bound to `@react-native-async-storage/async-storage` and\n * `expo-secure-store`.\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. `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 { InjectionToken, computed, signal, type Signal, type WritableSignal } from '@angular/core';\nimport { expoModule } 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 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 const raw = this.native?.getSync?.(key);\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\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\nconst secure = (): NativeStore | null => {\n const expo = expoModule(\n 'expo-secure-store',\n () => require('expo-secure-store') as typeof import('expo-secure-store'),\n );\n if (!expo) return null;\n return {\n get: (key) => expo.getItemAsync(key),\n set: (key, value) => expo.setItemAsync(key, value),\n remove: (key) => expo.deleteItemAsync(key),\n // The keychain can answer without waiting, so a token never flashes its default.\n getSync: (key) => expo.getItem(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\n/** The keychain and the Android keystore. Small values, and worth the size limit. */\nexport const SecureStorage = new InjectionToken<Store>('angular-native.secureStorage', {\n factory: () => new Store(secure()),\n});\nexport type SecureStorage = Store;\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Storage` and `SecureStorage`, bound to `@react-native-async-storage/async-storage` and\n * `expo-secure-store`.\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. `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 { InjectionToken, computed, signal, type Signal, type WritableSignal } from '@angular/core';\nimport { expoModule } 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 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\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\nconst secure = (): NativeStore | null => {\n const expo = expoModule(\n 'expo-secure-store',\n () => require('expo-secure-store') as typeof import('expo-secure-store'),\n );\n if (!expo) return null;\n return {\n get: (key) => expo.getItemAsync(key),\n set: (key, value) => expo.setItemAsync(key, value),\n remove: (key) => expo.deleteItemAsync(key),\n // The keychain can answer without waiting, so a token never flashes its default.\n getSync: (key) => expo.getItem(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\n/** The keychain and the Android keystore. Small values, and worth the size limit. */\nexport const SecureStorage = new InjectionToken<Store>('angular-native.secureStorage', {\n factory: () => new Store(secure()),\n});\nexport type SecureStorage = Store;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-native/expo",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Expo modules as Angular services for Angular Native, plus registerExpoView for a module's native view.",
5
5
  "keywords": [
6
6
  "angular",
@@ -174,7 +174,7 @@
174
174
  "access": "public"
175
175
  },
176
176
  "dependencies": {
177
- "@ng-native/fabric": "0.1.1"
177
+ "@ng-native/fabric": "0.1.3"
178
178
  },
179
179
  "nx": {
180
180
  "tags": [
@@ -234,41 +234,41 @@
234
234
  },
235
235
  "devDependencies": {
236
236
  "@angular/core": "22.2.0",
237
- "@expo/ui": "^57.0.16",
237
+ "@expo/ui": "^57.0.21",
238
238
  "@react-native-async-storage/async-storage": "^3.1.1",
239
- "expo": "^57.0.20",
239
+ "expo": "^57.0.26",
240
240
  "expo-apple-authentication": "~57.0.2",
241
- "expo-application": "^57.0.2",
242
- "expo-asset": "^57.0.16",
243
- "expo-audio": "^57.0.4",
244
- "expo-background-task": "^57.0.20",
245
- "expo-battery": "^57.0.2",
246
- "expo-brightness": "^57.0.1",
247
- "expo-camera": "^57.0.5",
241
+ "expo-application": "^57.0.3",
242
+ "expo-asset": "^57.0.18",
243
+ "expo-audio": "^57.0.5",
244
+ "expo-background-task": "^57.0.21",
245
+ "expo-battery": "^57.0.3",
246
+ "expo-brightness": "^57.0.2",
247
+ "expo-camera": "^57.0.6",
248
248
  "expo-crypto": "^57.0.3",
249
249
  "expo-device": "^57.0.2",
250
- "expo-document-picker": "^57.0.2",
250
+ "expo-document-picker": "^57.0.3",
251
251
  "expo-image-manipulator": "^57.0.20",
252
252
  "expo-image-picker": "^57.0.20",
253
- "expo-keep-awake": "^57.0.1",
253
+ "expo-keep-awake": "^57.0.2",
254
254
  "expo-local-authentication": "^57.0.3",
255
255
  "expo-local-llm": "^0.6.0",
256
- "expo-localization": "^57.0.1",
256
+ "expo-localization": "^57.0.2",
257
257
  "expo-location": "^57.0.20",
258
258
  "expo-maps": "^57.0.3",
259
259
  "expo-media-library": "^57.0.5",
260
- "expo-network": "^57.0.1",
261
- "expo-notifications": "^57.0.17",
260
+ "expo-network": "^57.0.2",
261
+ "expo-notifications": "^57.0.21",
262
262
  "expo-screen-capture": "^57.0.3",
263
263
  "expo-screen-orientation": "^57.0.2",
264
- "expo-secure-store": "^57.0.3",
265
- "expo-sensors": "^57.0.2",
266
- "expo-splash-screen": "^57.0.8",
267
- "expo-sqlite": "^57.0.2",
264
+ "expo-secure-store": "^57.0.4",
265
+ "expo-sensors": "^57.0.3",
266
+ "expo-splash-screen": "^57.0.9",
267
+ "expo-sqlite": "^57.0.3",
268
268
  "expo-store-review": "^57.0.3",
269
269
  "expo-tracking-transparency": "^57.0.2",
270
- "expo-updates": "^57.0.21",
271
- "expo-video": "^57.0.3",
270
+ "expo-updates": "^57.0.24",
271
+ "expo-video": "^57.0.5",
272
272
  "expo-web-browser": "^57.0.3"
273
273
  },
274
274
  "peerDependenciesMeta": {