@ng-native/expo 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +413 -1
  3. package/dist/app-info.d.ts +58 -0
  4. package/dist/app-info.js +71 -0
  5. package/dist/app-info.js.map +1 -0
  6. package/dist/apple-sign-in-button.d.ts +16 -0
  7. package/dist/apple-sign-in-button.js +55 -0
  8. package/dist/apple-sign-in-button.js.map +1 -0
  9. package/dist/apple-sign-in.d.ts +68 -0
  10. package/dist/apple-sign-in.js +120 -0
  11. package/dist/apple-sign-in.js.map +1 -0
  12. package/dist/assets.d.ts +24 -0
  13. package/dist/assets.js +47 -0
  14. package/dist/assets.js.map +1 -0
  15. package/dist/background-task.d.ts +66 -0
  16. package/dist/background-task.js +82 -0
  17. package/dist/background-task.js.map +1 -0
  18. package/dist/battery.d.ts +46 -0
  19. package/dist/battery.js +76 -0
  20. package/dist/battery.js.map +1 -0
  21. package/dist/biometrics.d.ts +45 -0
  22. package/dist/biometrics.js +53 -0
  23. package/dist/biometrics.js.map +1 -0
  24. package/dist/brightness.d.ts +38 -0
  25. package/dist/brightness.js +81 -0
  26. package/dist/brightness.js.map +1 -0
  27. package/dist/browser.d.ts +42 -0
  28. package/dist/browser.js +42 -0
  29. package/dist/browser.js.map +1 -0
  30. package/dist/camera.d.ts +48 -0
  31. package/dist/camera.js +59 -0
  32. package/dist/camera.js.map +1 -0
  33. package/dist/clipboard.d.ts +43 -0
  34. package/dist/clipboard.js +60 -0
  35. package/dist/clipboard.js.map +1 -0
  36. package/dist/community-views.d.ts +14 -0
  37. package/dist/community-views.js +76 -0
  38. package/dist/community-views.js.map +1 -0
  39. package/dist/crypto.d.ts +56 -0
  40. package/dist/crypto.js +84 -0
  41. package/dist/crypto.js.map +1 -0
  42. package/dist/database.d.ts +47 -0
  43. package/dist/database.js +108 -0
  44. package/dist/database.js.map +1 -0
  45. package/dist/document-picker.d.ts +34 -0
  46. package/dist/document-picker.js +40 -0
  47. package/dist/document-picker.js.map +1 -0
  48. package/dist/dom-component.d.ts +87 -0
  49. package/dist/dom-component.js +139 -0
  50. package/dist/dom-component.js.map +1 -0
  51. package/dist/expo-image.d.ts +33 -0
  52. package/dist/expo-image.js +57 -0
  53. package/dist/expo-image.js.map +1 -0
  54. package/dist/expo-ui-components.d.ts +327 -0
  55. package/dist/expo-ui-components.js +707 -0
  56. package/dist/expo-ui-components.js.map +1 -0
  57. package/dist/expo-ui.d.ts +46 -0
  58. package/dist/expo-ui.js +128 -0
  59. package/dist/expo-ui.js.map +1 -0
  60. package/dist/file-system.d.ts +63 -0
  61. package/dist/file-system.js +67 -0
  62. package/dist/file-system.js.map +1 -0
  63. package/dist/fonts.d.ts +90 -0
  64. package/dist/fonts.js +118 -0
  65. package/dist/fonts.js.map +1 -0
  66. package/dist/glass.d.ts +33 -0
  67. package/dist/glass.js +72 -0
  68. package/dist/glass.js.map +1 -0
  69. package/dist/haptics.d.ts +49 -0
  70. package/dist/haptics.js +64 -0
  71. package/dist/haptics.js.map +1 -0
  72. package/dist/image-editor.d.ts +62 -0
  73. package/dist/image-editor.js +101 -0
  74. package/dist/image-editor.js.map +1 -0
  75. package/dist/image-picker.d.ts +49 -0
  76. package/dist/image-picker.js +58 -0
  77. package/dist/image-picker.js.map +1 -0
  78. package/dist/index.d.ts +20 -0
  79. package/dist/index.js +21 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/keep-awake.d.ts +39 -0
  82. package/dist/keep-awake.js +80 -0
  83. package/dist/keep-awake.js.map +1 -0
  84. package/dist/language-model.d.ts +129 -0
  85. package/dist/language-model.js +225 -0
  86. package/dist/language-model.js.map +1 -0
  87. package/dist/locale.d.ts +51 -0
  88. package/dist/locale.js +78 -0
  89. package/dist/locale.js.map +1 -0
  90. package/dist/location.d.ts +68 -0
  91. package/dist/location.js +83 -0
  92. package/dist/location.js.map +1 -0
  93. package/dist/map-view.d.ts +203 -0
  94. package/dist/map-view.js +184 -0
  95. package/dist/map-view.js.map +1 -0
  96. package/dist/media-library.d.ts +103 -0
  97. package/dist/media-library.js +160 -0
  98. package/dist/media-library.js.map +1 -0
  99. package/dist/native-state.d.ts +22 -0
  100. package/dist/native-state.js +43 -0
  101. package/dist/native-state.js.map +1 -0
  102. package/dist/native.d.ts +42 -0
  103. package/dist/native.js +73 -0
  104. package/dist/native.js.map +1 -0
  105. package/dist/network.d.ts +35 -0
  106. package/dist/network.js +60 -0
  107. package/dist/network.js.map +1 -0
  108. package/dist/notifications.d.ts +175 -0
  109. package/dist/notifications.js +296 -0
  110. package/dist/notifications.js.map +1 -0
  111. package/dist/observed.d.ts +37 -0
  112. package/dist/observed.js +55 -0
  113. package/dist/observed.js.map +1 -0
  114. package/dist/orientation.d.ts +35 -0
  115. package/dist/orientation.js +78 -0
  116. package/dist/orientation.js.map +1 -0
  117. package/dist/permissions.d.ts +54 -0
  118. package/dist/permissions.js +72 -0
  119. package/dist/permissions.js.map +1 -0
  120. package/dist/player.d.ts +92 -0
  121. package/dist/player.js +141 -0
  122. package/dist/player.js.map +1 -0
  123. package/dist/register-expo-view.d.ts +55 -0
  124. package/dist/register-expo-view.js +110 -0
  125. package/dist/register-expo-view.js.map +1 -0
  126. package/dist/screen-capture.d.ts +58 -0
  127. package/dist/screen-capture.js +78 -0
  128. package/dist/screen-capture.js.map +1 -0
  129. package/dist/segmented-control.d.ts +32 -0
  130. package/dist/segmented-control.js +55 -0
  131. package/dist/segmented-control.js.map +1 -0
  132. package/dist/sensors.d.ts +85 -0
  133. package/dist/sensors.js +115 -0
  134. package/dist/sensors.js.map +1 -0
  135. package/dist/splash-screen.d.ts +73 -0
  136. package/dist/splash-screen.js +115 -0
  137. package/dist/splash-screen.js.map +1 -0
  138. package/dist/store-review.d.ts +43 -0
  139. package/dist/store-review.js +52 -0
  140. package/dist/store-review.js.map +1 -0
  141. package/dist/store.d.ts +108 -0
  142. package/dist/store.js +247 -0
  143. package/dist/store.js.map +1 -0
  144. package/dist/symbol.d.ts +36 -0
  145. package/dist/symbol.js +69 -0
  146. package/dist/symbol.js.map +1 -0
  147. package/dist/tracking.d.ts +45 -0
  148. package/dist/tracking.js +54 -0
  149. package/dist/tracking.js.map +1 -0
  150. package/dist/transforms.d.ts +2 -0
  151. package/dist/transforms.js +13 -0
  152. package/dist/transforms.js.map +1 -0
  153. package/dist/updates.d.ts +61 -0
  154. package/dist/updates.js +97 -0
  155. package/dist/updates.js.map +1 -0
  156. package/package.json +397 -3
package/dist/fonts.js ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * `Fonts`, bound to `expo-font`.
3
+ *
4
+ * A font on a phone is not fetched when the text engine first needs it. It has to be registered
5
+ * with the platform up front, or the first paint is in the fallback face and reflows when the
6
+ * real one lands - which is why the interesting call here is the one an app makes *before* it
7
+ * mounts:
8
+ *
9
+ * ```ts
10
+ * import { loadFonts } from '@ng-native/expo/fonts';
11
+ *
12
+ * await loadFonts(styleSheetOf(GlobalStyles));
13
+ * mount(rootTag, App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });
14
+ * ```
15
+ *
16
+ * The class takes its platform rather than injecting it, because that call happens before there
17
+ * is an injector to inject from. `inject(Fonts)` is for a screen that wants to ask what loaded.
18
+ *
19
+ * Unlike the other services here this goes through `expo-font`'s JavaScript rather than its
20
+ * native module: the module takes descriptors the JS builds out of an asset, and rebuilding them
21
+ * here would be copying out its internals rather than using it.
22
+ */
23
+ import { InjectionToken, computed, signal } from '@angular/core';
24
+ import { expoModule } from "./native.js";
25
+ /**
26
+ * Loading is by family name, which is also all native matches on.
27
+ *
28
+ * 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.
33
+ */
34
+ export function registrationsFor(faces) {
35
+ const map = {};
36
+ for (const face of faces) {
37
+ 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;
42
+ }
43
+ return map;
44
+ }
45
+ export function expoFonts() {
46
+ const expo = expoModule('expo-font', () => require('expo-font'));
47
+ if (!expo)
48
+ return null;
49
+ return {
50
+ loadAsync: (map) => expo.loadAsync(map),
51
+ isLoaded: (family) => expo.isLoaded(family),
52
+ getLoadedFonts: () => expo.getLoadedFonts(),
53
+ };
54
+ }
55
+ export class FontRegistry {
56
+ native;
57
+ /**
58
+ * Bumped whenever faces are registered.
59
+ *
60
+ * The platform's own list is a plain array that changes underneath us, so a template binding to
61
+ * it would render once and never again - and the one moment it changes is exactly the moment a
62
+ * screen waiting on a font wants to hear about. This makes both readers reactive without
63
+ * mirroring the list.
64
+ */
65
+ generation = signal(0, /* @ts-ignore */
66
+ ...(ngDevMode ? [{ debugName: "generation" }] : /* istanbul ignore next */ []));
67
+ constructor(native) {
68
+ this.native = native;
69
+ }
70
+ /** Whether `expo-font` is installed at all. Without it a custom face simply never appears. */
71
+ get available() {
72
+ return this.native !== null;
73
+ }
74
+ /** Every family the platform can currently find, custom or bundled. */
75
+ families = computed(() => {
76
+ this.generation();
77
+ return this.native?.getLoadedFonts() ?? [];
78
+ }, /* @ts-ignore */
79
+ ...(ngDevMode ? [{ debugName: "families" }] : /* istanbul ignore next */ []));
80
+ /** Whether a family is registered. Reactive: re-read after a `load` that adds one. */
81
+ has(family) {
82
+ this.generation();
83
+ return this.native?.isLoaded(family) ?? false;
84
+ }
85
+ /**
86
+ * Register every `@font-face` a compiled sheet declares.
87
+ *
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.
91
+ */
92
+ async loadSheet(...sheets) {
93
+ const faces = sheets.flatMap((sheet) => [...(sheet?.fonts ?? [])]);
94
+ if (!faces.length)
95
+ return;
96
+ await this.load(registrationsFor(faces));
97
+ }
98
+ /** Register faces by name, for a font that did not come from a stylesheet. */
99
+ async load(map) {
100
+ if (!this.native)
101
+ return;
102
+ await this.native.loadAsync(map);
103
+ this.generation.update((n) => n + 1);
104
+ }
105
+ }
106
+ /** Injected where an app wants to ask what is loaded. */
107
+ export const Fonts = new InjectionToken('angular-native.fonts', {
108
+ factory: () => new FontRegistry(expoFonts()),
109
+ });
110
+ /**
111
+ * Register every face the given sheets declare, before the app mounts.
112
+ *
113
+ * A function rather than a service because that is when it is needed: there is no injector yet.
114
+ */
115
+ export function loadFonts(...sheets) {
116
+ return new FontRegistry(expoFonts()).loadSheet(...sheets);
117
+ }
118
+ //# sourceMappingURL=fonts.js.map
@@ -0,0 +1 @@
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,UAAU,EAAE,MAAM,aAAa,CAAC;AAuBzC;;;;;;;;GAQG;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,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QAClF,IAAI,IAAI,CAAC,KAAK;YAAE,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IACpE,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,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,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 { 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"]}
@@ -0,0 +1,33 @@
1
+ import * as i0 from "@angular/core";
2
+ export type GlassStyle = 'regular' | 'clear' | 'none';
3
+ /** A style, and whether a change to it animates. */
4
+ export interface GlassEffectStyleConfig {
5
+ readonly style: GlassStyle;
6
+ readonly animate?: boolean;
7
+ /** Seconds. */
8
+ readonly animationDuration?: number;
9
+ }
10
+ export declare class ExpoGlass {
11
+ /** `regular` by default; `clear` is more transparent; `none` turns the effect off. */
12
+ readonly glassEffectStyle: import("@angular/core").InputSignal<GlassStyle | GlassEffectStyleConfig | undefined>;
13
+ /** A colour the glass is tinted with, usually translucent. */
14
+ readonly tintColor: import("@angular/core").InputSignal<string | undefined>;
15
+ /** Reacts to touch, as a control made of glass does. */
16
+ readonly isInteractive: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
17
+ /** The appearance the glass takes, when it should not follow the app's. */
18
+ readonly colorScheme: import("@angular/core").InputSignal<"auto" | "light" | "dark" | undefined>;
19
+ static ɵfac: i0.ɵɵFactoryDeclaration<ExpoGlass, never>;
20
+ static ɵcmp: i0.ɵɵComponentDeclaration<ExpoGlass, "expo-glass", never, { "glassEffectStyle": { "alias": "glassEffectStyle"; "required": false; "isSignal": true; }; "tintColor": { "alias": "tintColor"; "required": false; "isSignal": true; }; "isInteractive": { "alias": "isInteractive"; "required": false; "isSignal": true; }; "colorScheme": { "alias": "colorScheme"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
21
+ }
22
+ /** Glass views that merge into one another when they come within `spacing` points. */
23
+ export declare class ExpoGlassContainer {
24
+ readonly spacing: import("@angular/core").InputSignalWithTransform<number | undefined, unknown>;
25
+ static ɵfac: i0.ɵɵFactoryDeclaration<ExpoGlassContainer, never>;
26
+ static ɵcmp: i0.ɵɵComponentDeclaration<ExpoGlassContainer, "expo-glass-container", never, { "spacing": { "alias": "spacing"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
27
+ }
28
+ /**
29
+ * Whether the app renders with Liquid Glass: iOS 26 and later, with `expo-glass-effect`
30
+ * installed. The user can still reduce transparency in the accessibility settings, which the
31
+ * platform then honours on its own.
32
+ */
33
+ export declare function liquidGlassAvailable(): boolean;
package/dist/glass.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * `<expo-glass>` and `<expo-glass-container>`: iOS 26's Liquid Glass, from `expo-glass-effect`,
3
+ * typed so a template is checked against the props. Registered with
4
+ * `registerExpoViews('expo-glass', 'expo-glass-container')`. Where Liquid Glass is unavailable -
5
+ * before iOS 26, and on Android - the view renders as a plain view, so a fallback background is the
6
+ * app's to set; `liquidGlassAvailable()` says which it will be.
7
+ *
8
+ * ```html
9
+ * <expo-glass-container spacing="10">
10
+ * <expo-glass class="size-16 rounded-full" glassEffectStyle="clear" isInteractive />
11
+ * <expo-glass class="size-16 rounded-full" tintColor="#0a84ff33" />
12
+ * </expo-glass-container>
13
+ * ```
14
+ *
15
+ * The corners are the element's own `border-radius`, which native reads from the same props.
16
+ */
17
+ import { Component, input } from '@angular/core';
18
+ import { optionalBoolean, optionalNumber } from "./transforms.js";
19
+ import * as i0 from "@angular/core";
20
+ export class ExpoGlass {
21
+ /** `regular` by default; `clear` is more transparent; `none` turns the effect off. */
22
+ glassEffectStyle = input(/* @ts-ignore */
23
+ ...(ngDevMode ? [undefined, { debugName: "glassEffectStyle" }] : /* istanbul ignore next */ []));
24
+ /** A colour the glass is tinted with, usually translucent. */
25
+ tintColor = input(/* @ts-ignore */
26
+ ...(ngDevMode ? [undefined, { debugName: "tintColor" }] : /* istanbul ignore next */ []));
27
+ /** Reacts to touch, as a control made of glass does. */
28
+ isInteractive = input(undefined, { ...(ngDevMode ? { debugName: "isInteractive" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
29
+ /** The appearance the glass takes, when it should not follow the app's. */
30
+ colorScheme = input(/* @ts-ignore */
31
+ ...(ngDevMode ? [undefined, { debugName: "colorScheme" }] : /* istanbul ignore next */ []));
32
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ExpoGlass, deps: [], target: i0.ɵɵFactoryTarget.Component });
33
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: ExpoGlass, isStandalone: true, selector: "expo-glass", inputs: { glassEffectStyle: { classPropertyName: "glassEffectStyle", publicName: "glassEffectStyle", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null }, isInteractive: { classPropertyName: "isInteractive", publicName: "isInteractive", isSignal: true, isRequired: false, transformFunction: null }, colorScheme: { classPropertyName: "colorScheme", publicName: "colorScheme", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "glassEffectStyle": "glassEffectStyle()", "tintColor": "tintColor()", "isInteractive": "isInteractive()", "colorScheme": "colorScheme()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
34
+ }
35
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ExpoGlass, decorators: [{
36
+ type: Component,
37
+ args: [{
38
+ selector: 'expo-glass',
39
+ template: '<ng-content />',
40
+ host: {
41
+ '[glassEffectStyle]': 'glassEffectStyle()',
42
+ '[tintColor]': 'tintColor()',
43
+ '[isInteractive]': 'isInteractive()',
44
+ '[colorScheme]': 'colorScheme()',
45
+ },
46
+ }]
47
+ }], propDecorators: { glassEffectStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "glassEffectStyle", required: false }] }], tintColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "tintColor", required: false }] }], isInteractive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isInteractive", required: false }] }], colorScheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "colorScheme", required: false }] }] } });
48
+ /** Glass views that merge into one another when they come within `spacing` points. */
49
+ export class ExpoGlassContainer {
50
+ spacing = input(undefined, { ...(ngDevMode ? { debugName: "spacing" } : /* istanbul ignore next */ {}), transform: optionalNumber });
51
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ExpoGlassContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
52
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: ExpoGlassContainer, isStandalone: true, selector: "expo-glass-container", inputs: { spacing: { classPropertyName: "spacing", publicName: "spacing", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "spacing": "spacing()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
53
+ }
54
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ExpoGlassContainer, decorators: [{
55
+ type: Component,
56
+ args: [{
57
+ selector: 'expo-glass-container',
58
+ template: '<ng-content />',
59
+ host: { '[spacing]': 'spacing()' },
60
+ }]
61
+ }], propDecorators: { spacing: [{ type: i0.Input, args: [{ isSignal: true, alias: "spacing", required: false }] }] } });
62
+ /**
63
+ * Whether the app renders with Liquid Glass: iOS 26 and later, with `expo-glass-effect`
64
+ * installed. The user can still reduce transparency in the accessibility settings, which the
65
+ * platform then honours on its own.
66
+ */
67
+ export function liquidGlassAvailable() {
68
+ const modules = globalThis.expo?.modules;
69
+ const glass = modules?.['ExpoGlassEffect'];
70
+ return glass?.isLiquidGlassAvailable === true;
71
+ }
72
+ //# sourceMappingURL=glass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glass.js","sourceRoot":"","sources":["../src/glass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;;AAsBlE,MAAM,OAAO,SAAS;IACpB,sFAAsF;IAC7E,gBAAgB,GAAG,KAAK;oGAAuC,CAAC;IACzE,8DAA8D;IACrD,SAAS,GAAG,KAAK;6FAAU,CAAC;IACrC,wDAAwD;IAC/C,aAAa,GAAG,KAAK,CAAU,SAAS,qFAAI,SAAS,EAAE,eAAe,GAAG,CAAC;IACnF,2EAA2E;IAClE,WAAW,GAAG,KAAK;+FAA6B,CAAC;uGAR/C,SAAS;2FAAT,SAAS,6yBARV,gBAAgB;;2FAQf,SAAS;kBAVrB,SAAS;mBAAC;oBACT,QAAQ,EAAE,YAAY;oBACtB,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE;wBACJ,oBAAoB,EAAE,oBAAoB;wBAC1C,aAAa,EAAE,aAAa;wBAC5B,iBAAiB,EAAE,iBAAiB;wBACpC,eAAe,EAAE,eAAe;qBACjC;iBACF;;AAYD,sFAAsF;AAMtF,MAAM,OAAO,kBAAkB;IACpB,OAAO,GAAG,KAAK,CAAS,SAAS,+EAAI,SAAS,EAAE,cAAc,GAAG,CAAC;uGADhE,kBAAkB;2FAAlB,kBAAkB,4QAHnB,gBAAgB;;2FAGf,kBAAkB;kBAL9B,SAAS;mBAAC;oBACT,QAAQ,EAAE,sBAAsB;oBAChC,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE;iBACnC;;AAKD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,OAAO,GAAI,UAA+D,CAAC,IAAI,EAAE,OAAO,CAAC;IAC/F,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC,iBAAiB,CAAqD,CAAC;IAC/F,OAAO,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;AAChD,CAAC","sourcesContent":["/**\n * `<expo-glass>` and `<expo-glass-container>`: iOS 26's Liquid Glass, from `expo-glass-effect`,\n * typed so a template is checked against the props. Registered with\n * `registerExpoViews('expo-glass', 'expo-glass-container')`. Where Liquid Glass is unavailable -\n * before iOS 26, and on Android - the view renders as a plain view, so a fallback background is the\n * app's to set; `liquidGlassAvailable()` says which it will be.\n *\n * ```html\n * <expo-glass-container spacing=\"10\">\n * <expo-glass class=\"size-16 rounded-full\" glassEffectStyle=\"clear\" isInteractive />\n * <expo-glass class=\"size-16 rounded-full\" tintColor=\"#0a84ff33\" />\n * </expo-glass-container>\n * ```\n *\n * The corners are the element's own `border-radius`, which native reads from the same props.\n */\nimport { Component, input } from '@angular/core';\nimport { optionalBoolean, optionalNumber } from './transforms.ts';\n\nexport type GlassStyle = 'regular' | 'clear' | 'none';\n\n/** A style, and whether a change to it animates. */\nexport interface GlassEffectStyleConfig {\n readonly style: GlassStyle;\n readonly animate?: boolean;\n /** Seconds. */\n readonly animationDuration?: number;\n}\n\n@Component({\n selector: 'expo-glass',\n template: '<ng-content />',\n host: {\n '[glassEffectStyle]': 'glassEffectStyle()',\n '[tintColor]': 'tintColor()',\n '[isInteractive]': 'isInteractive()',\n '[colorScheme]': 'colorScheme()',\n },\n})\nexport class ExpoGlass {\n /** `regular` by default; `clear` is more transparent; `none` turns the effect off. */\n readonly glassEffectStyle = input<GlassStyle | GlassEffectStyleConfig>();\n /** A colour the glass is tinted with, usually translucent. */\n readonly tintColor = input<string>();\n /** Reacts to touch, as a control made of glass does. */\n readonly isInteractive = input<boolean>(undefined, { transform: optionalBoolean });\n /** The appearance the glass takes, when it should not follow the app's. */\n readonly colorScheme = input<'auto' | 'light' | 'dark'>();\n}\n\n/** Glass views that merge into one another when they come within `spacing` points. */\n@Component({\n selector: 'expo-glass-container',\n template: '<ng-content />',\n host: { '[spacing]': 'spacing()' },\n})\nexport class ExpoGlassContainer {\n readonly spacing = input<number>(undefined, { transform: optionalNumber });\n}\n\n/**\n * Whether the app renders with Liquid Glass: iOS 26 and later, with `expo-glass-effect`\n * installed. The user can still reduce transparency in the accessibility settings, which the\n * platform then honours on its own.\n */\nexport function liquidGlassAvailable(): boolean {\n const modules = (globalThis as { expo?: { modules?: Record<string, unknown> } }).expo?.modules;\n const glass = modules?.['ExpoGlassEffect'] as { isLiquidGlassAvailable?: unknown } | undefined;\n return glass?.isLiquidGlassAvailable === true;\n}\n"]}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `Haptics`, bound to Expo's native module.
3
+ *
4
+ * Injected, not imported as functions: `inject(Haptics).impact()`. Nothing needs providing - the
5
+ * source token carries its own factory, and a service nobody injects is never constructed.
6
+ *
7
+ * The native module is used directly rather than through `expo-haptics`'s JavaScript, which adds
8
+ * one thing: a throw when the module is missing. Asking for it optionally answers the same
9
+ * question without the throw, and sidesteps the enum its functions take. The package still has
10
+ * to be installed - that is what links the native side.
11
+ */
12
+ import { InjectionToken } from '@angular/core';
13
+ import * as i0 from "@angular/core";
14
+ /** How hard the collision felt. `rigid` and `soft` are iOS 13+. */
15
+ export type ImpactStyle = 'light' | 'medium' | 'heavy' | 'rigid' | 'soft';
16
+ /** What happened, rather than how it felt. iOS plays a distinct pattern for each. */
17
+ export type NotificationType = 'success' | 'warning' | 'error';
18
+ /** The slice of `expo-haptics` this needs. */
19
+ export interface NativeHaptics {
20
+ impactAsync(style: ImpactStyle): Promise<void>;
21
+ notificationAsync(type: NotificationType): Promise<void>;
22
+ selectionAsync(): Promise<void>;
23
+ }
24
+ /**
25
+ * Every method returns nothing and swallows failures.
26
+ *
27
+ * Expo's are all promises, and a caller who does not await one gets an unhandled rejection on a
28
+ * device with no Taptic Engine, in a simulator, or on an Android build with vibration permission
29
+ * off - none of which is a reason for anything to go wrong on screen. Nobody awaits a vibration.
30
+ *
31
+ * ponytail: silence means a broken haptic looks like a working one. If that ever needs
32
+ * debugging, log under `__DEV__` rather than making callers handle a rejection.
33
+ */
34
+ export declare class Haptics {
35
+ /** Overridden in a test to record feedback that never reaches a Taptic Engine. */
36
+ static readonly SOURCE: InjectionToken<NativeHaptics | null>;
37
+ private readonly native;
38
+ /** Whether the native module is installed at all. */
39
+ get available(): boolean;
40
+ /** A collision between interface elements. */
41
+ impact(style?: ImpactStyle): void;
42
+ /** A task succeeded or failed. */
43
+ notify(type: NotificationType): void;
44
+ /** A selection changed: the lightest of the three. */
45
+ select(): void;
46
+ private play;
47
+ static ɵfac: i0.ɵɵFactoryDeclaration<Haptics, never>;
48
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
49
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `Haptics`, bound to Expo's native module.
3
+ *
4
+ * Injected, not imported as functions: `inject(Haptics).impact()`. Nothing needs providing - the
5
+ * source token carries its own factory, and a service nobody injects is never constructed.
6
+ *
7
+ * The native module is used directly rather than through `expo-haptics`'s JavaScript, which adds
8
+ * one thing: a throw when the module is missing. Asking for it optionally answers the same
9
+ * question without the throw, and sidesteps the enum its functions take. The package still has
10
+ * to be installed - that is what links the native side.
11
+ */
12
+ import { InjectionToken, Service, inject } from '@angular/core';
13
+ import { expoModule, optional } from "./native.js";
14
+ import * as i0 from "@angular/core";
15
+ /**
16
+ * Every method returns nothing and swallows failures.
17
+ *
18
+ * Expo's are all promises, and a caller who does not await one gets an unhandled rejection on a
19
+ * device with no Taptic Engine, in a simulator, or on an Android build with vibration permission
20
+ * off - none of which is a reason for anything to go wrong on screen. Nobody awaits a vibration.
21
+ *
22
+ * ponytail: silence means a broken haptic looks like a working one. If that ever needs
23
+ * debugging, log under `__DEV__` rather than making callers handle a rejection.
24
+ */
25
+ export class Haptics {
26
+ /** Overridden in a test to record feedback that never reaches a Taptic Engine. */
27
+ static SOURCE = new InjectionToken('angular-native.hapticsSource', {
28
+ factory: () => {
29
+ const core = optional(() => require('expo-modules-core'));
30
+ return expoModule('expo-haptics', () => core?.requireOptionalNativeModule('ExpoHaptics'));
31
+ },
32
+ });
33
+ native = inject(Haptics.SOURCE);
34
+ /** Whether the native module is installed at all. */
35
+ get available() {
36
+ return this.native !== null;
37
+ }
38
+ /** A collision between interface elements. */
39
+ impact(style = 'medium') {
40
+ this.play(() => this.native?.impactAsync(style));
41
+ }
42
+ /** A task succeeded or failed. */
43
+ notify(type) {
44
+ this.play(() => this.native?.notificationAsync(type));
45
+ }
46
+ /** A selection changed: the lightest of the three. */
47
+ select() {
48
+ this.play(() => this.native?.selectionAsync());
49
+ }
50
+ play(feedback) {
51
+ try {
52
+ void feedback()?.catch(() => { });
53
+ }
54
+ catch {
55
+ // A module that is present but not linked throws synchronously.
56
+ }
57
+ }
58
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Haptics, deps: [], target: i0.ɵɵFactoryTarget.Service });
59
+ static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: Haptics });
60
+ }
61
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Haptics, decorators: [{
62
+ type: Service
63
+ }] });
64
+ //# sourceMappingURL=haptics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"haptics.js","sourceRoot":"","sources":["../src/haptics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;;AAenD;;;;;;;;;GASG;AAEH,MAAM,OAAO,OAAO;IAClB,kFAAkF;IAClF,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,8BAA8B,EAC9B;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,QAAQ,CACnB,GAAG,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAuC,CACzE,CAAC;YACF,OAAO,UAAU,CAAC,cAAc,EAAE,GAAG,EAAE,CACrC,IAAI,EAAE,2BAA2B,CAAgB,aAAa,CAAC,CAChE,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAEjD,qDAAqD;IACrD,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED,8CAA8C;IAC9C,MAAM,CAAC,QAAqB,QAAQ;QAClC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,kCAAkC;IAClC,MAAM,CAAC,IAAsB;QAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,sDAAsD;IACtD,MAAM;QACJ,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;IACjD,CAAC;IAEO,IAAI,CAAC,QAAyC;QACpD,IAAI,CAAC;YACH,KAAK,QAAQ,EAAE,EAAE,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,gEAAgE;QAClE,CAAC;IACH,CAAC;uGA5CU,OAAO;wGAAP,OAAO;;2FAAP,OAAO;kBADnB,OAAO","sourcesContent":["/**\n * `Haptics`, bound to Expo's native module.\n *\n * Injected, not imported as functions: `inject(Haptics).impact()`. Nothing needs providing - the\n * source token carries its own factory, and a service nobody injects is never constructed.\n *\n * The native module is used directly rather than through `expo-haptics`'s JavaScript, which adds\n * one thing: a throw when the module is missing. Asking for it optionally answers the same\n * question without the throw, and sidesteps the enum its functions take. The package still has\n * to be installed - that is what links the native side.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule, optional } from './native.ts';\n\n/** How hard the collision felt. `rigid` and `soft` are iOS 13+. */\nexport type ImpactStyle = 'light' | 'medium' | 'heavy' | 'rigid' | 'soft';\n\n/** What happened, rather than how it felt. iOS plays a distinct pattern for each. */\nexport type NotificationType = 'success' | 'warning' | 'error';\n\n/** The slice of `expo-haptics` this needs. */\nexport interface NativeHaptics {\n impactAsync(style: ImpactStyle): Promise<void>;\n notificationAsync(type: NotificationType): Promise<void>;\n selectionAsync(): Promise<void>;\n}\n\n/**\n * Every method returns nothing and swallows failures.\n *\n * Expo's are all promises, and a caller who does not await one gets an unhandled rejection on a\n * device with no Taptic Engine, in a simulator, or on an Android build with vibration permission\n * off - none of which is a reason for anything to go wrong on screen. Nobody awaits a vibration.\n *\n * ponytail: silence means a broken haptic looks like a working one. If that ever needs\n * debugging, log under `__DEV__` rather than making callers handle a rejection.\n */\n@Service()\nexport class Haptics {\n /** Overridden in a test to record feedback that never reaches a Taptic Engine. */\n static readonly SOURCE = new InjectionToken<NativeHaptics | null>(\n 'angular-native.hapticsSource',\n {\n factory: () => {\n const core = optional(\n () => require('expo-modules-core') as typeof import('expo-modules-core'),\n );\n return expoModule('expo-haptics', () =>\n core?.requireOptionalNativeModule<NativeHaptics>('ExpoHaptics'),\n );\n },\n },\n );\n\n private readonly native = inject(Haptics.SOURCE);\n\n /** Whether the native module is installed at all. */\n get available(): boolean {\n return this.native !== null;\n }\n\n /** A collision between interface elements. */\n impact(style: ImpactStyle = 'medium'): void {\n this.play(() => this.native?.impactAsync(style));\n }\n\n /** A task succeeded or failed. */\n notify(type: NotificationType): void {\n this.play(() => this.native?.notificationAsync(type));\n }\n\n /** A selection changed: the lightest of the three. */\n select(): void {\n this.play(() => this.native?.selectionAsync());\n }\n\n private play(feedback: () => Promise<void> | undefined): void {\n try {\n void feedback()?.catch(() => {});\n } catch {\n // A module that is present but not linked throws synchronously.\n }\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `ImageEditor`, bound to `expo-image-manipulator`: resizing, cropping, rotating, flipping and
3
+ * re-encoding an image file.
4
+ *
5
+ * ```ts
6
+ * import { ImageEditor, SaveFormat } from '@ng-native/expo/image-editor';
7
+ *
8
+ * private readonly editor = inject(ImageEditor);
9
+ *
10
+ * async thumbnail(uri: string): Promise<string | null> {
11
+ * const image = await this.editor.edit(uri, [{ resize: { width: 256 } }], {
12
+ * format: SaveFormat.WEBP,
13
+ * compress: 0.8,
14
+ * });
15
+ * return image?.uri ?? null;
16
+ * }
17
+ * ```
18
+ *
19
+ * `edit()` takes the module's own actions and save options - the shape of its `manipulateAsync`
20
+ * - and runs them through the module's current contextual API: one context, each action applied
21
+ * in order, the result rendered and saved to a new file in the cache. The context and the
22
+ * rendered image are native memory nothing else holds, so both are released once the file is
23
+ * written, including when it fails. `manipulate()` hands over the module's context itself, for a
24
+ * caller that chains its own steps and releases it after.
25
+ *
26
+ * Without the module installed, `edit()` resolves to null and `manipulate()` is null.
27
+ */
28
+ import { InjectionToken } from '@angular/core';
29
+ import type { Action, FlipType as ExpoFlipType, ImageManipulatorContext, ImageResult, SaveFormat as ExpoSaveFormat, SaveOptions } from 'expo-image-manipulator';
30
+ import * as i0 from "@angular/core";
31
+ type Expo = typeof import('expo-image-manipulator');
32
+ /** The module's API this service calls. The real module is one; a test provides a fake. */
33
+ export type NativeImageEditor = Pick<Expo, 'ImageManipulator'>;
34
+ /**
35
+ * The formats an edited image is saved in, as `expo-image-manipulator`'s own enum. Importing the
36
+ * enum from the module loads the module, which a test in Node cannot; these are the same strings,
37
+ * typed as the enum.
38
+ */
39
+ export declare const SaveFormat: typeof ExpoSaveFormat;
40
+ /** The directions an image is flipped in, as the module's own enum. */
41
+ export declare const FlipType: typeof ExpoFlipType;
42
+ export declare class ImageEditor {
43
+ /** Overridden in a test to edit images without decoding any. */
44
+ static readonly SOURCE: InjectionToken<NativeImageEditor | null>;
45
+ private readonly native;
46
+ /**
47
+ * Applies each action in order and saves the result as a new file: a JPEG unless `format` says
48
+ * otherwise, `compress` from 0 to 1, and `base64` to have the contents back as well. Each action
49
+ * has exactly one key: `resize`, `rotate` (degrees, clockwise), `flip`, `crop`, or on the web
50
+ * `extent`.
51
+ */
52
+ edit(source: string, actions?: readonly Action[], options?: SaveOptions): Promise<ImageResult | null>;
53
+ /**
54
+ * The module's own context for a file or data URI, for chaining steps by hand: `resize()`,
55
+ * `rotate()`, `flip()`, `crop()` and `reset()`, then `renderAsync()`. The caller
56
+ * releases it, and the image it renders, once done.
57
+ */
58
+ manipulate(source: string): ImageManipulatorContext | null;
59
+ static ɵfac: i0.ɵɵFactoryDeclaration<ImageEditor, never>;
60
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
61
+ }
62
+ export {};
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `ImageEditor`, bound to `expo-image-manipulator`: resizing, cropping, rotating, flipping and
3
+ * re-encoding an image file.
4
+ *
5
+ * ```ts
6
+ * import { ImageEditor, SaveFormat } from '@ng-native/expo/image-editor';
7
+ *
8
+ * private readonly editor = inject(ImageEditor);
9
+ *
10
+ * async thumbnail(uri: string): Promise<string | null> {
11
+ * const image = await this.editor.edit(uri, [{ resize: { width: 256 } }], {
12
+ * format: SaveFormat.WEBP,
13
+ * compress: 0.8,
14
+ * });
15
+ * return image?.uri ?? null;
16
+ * }
17
+ * ```
18
+ *
19
+ * `edit()` takes the module's own actions and save options - the shape of its `manipulateAsync`
20
+ * - and runs them through the module's current contextual API: one context, each action applied
21
+ * in order, the result rendered and saved to a new file in the cache. The context and the
22
+ * rendered image are native memory nothing else holds, so both are released once the file is
23
+ * written, including when it fails. `manipulate()` hands over the module's context itself, for a
24
+ * caller that chains its own steps and releases it after.
25
+ *
26
+ * Without the module installed, `edit()` resolves to null and `manipulate()` is null.
27
+ */
28
+ import { InjectionToken, Service, inject } from '@angular/core';
29
+ import { expoModule } from "./native.js";
30
+ import * as i0 from "@angular/core";
31
+ /**
32
+ * The formats an edited image is saved in, as `expo-image-manipulator`'s own enum. Importing the
33
+ * enum from the module loads the module, which a test in Node cannot; these are the same strings,
34
+ * typed as the enum.
35
+ */
36
+ export const SaveFormat = {
37
+ JPEG: 'jpeg',
38
+ PNG: 'png',
39
+ WEBP: 'webp',
40
+ };
41
+ /** The directions an image is flipped in, as the module's own enum. */
42
+ export const FlipType = {
43
+ Vertical: 'vertical',
44
+ Horizontal: 'horizontal',
45
+ };
46
+ export class ImageEditor {
47
+ /** Overridden in a test to edit images without decoding any. */
48
+ static SOURCE = new InjectionToken('angular-native.imageEditorSource', {
49
+ factory: () => expoModule('expo-image-manipulator', () => require('expo-image-manipulator')),
50
+ });
51
+ native = inject(ImageEditor.SOURCE);
52
+ /**
53
+ * Applies each action in order and saves the result as a new file: a JPEG unless `format` says
54
+ * otherwise, `compress` from 0 to 1, and `base64` to have the contents back as well. Each action
55
+ * has exactly one key: `resize`, `rotate` (degrees, clockwise), `flip`, `crop`, or on the web
56
+ * `extent`.
57
+ */
58
+ async edit(source, actions = [], options = {}) {
59
+ const context = this.manipulate(source);
60
+ if (!context)
61
+ return null;
62
+ let image;
63
+ try {
64
+ for (const action of actions)
65
+ apply(context, action);
66
+ image = await context.renderAsync();
67
+ return await image.saveAsync({ format: SaveFormat.JPEG, ...options });
68
+ }
69
+ finally {
70
+ context.release();
71
+ image?.release();
72
+ }
73
+ }
74
+ /**
75
+ * The module's own context for a file or data URI, for chaining steps by hand: `resize()`,
76
+ * `rotate()`, `flip()`, `crop()` and `reset()`, then `renderAsync()`. The caller
77
+ * releases it, and the image it renders, once done.
78
+ */
79
+ manipulate(source) {
80
+ return this.native?.ImageManipulator.manipulate(source) ?? null;
81
+ }
82
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ImageEditor, deps: [], target: i0.ɵɵFactoryTarget.Service });
83
+ static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: ImageEditor });
84
+ }
85
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ImageEditor, decorators: [{
86
+ type: Service
87
+ }] });
88
+ function apply(context, action) {
89
+ if ('resize' in action)
90
+ context.resize(action.resize);
91
+ else if ('rotate' in action)
92
+ context.rotate(action.rotate);
93
+ else if ('flip' in action)
94
+ context.flip(action.flip);
95
+ else if ('crop' in action)
96
+ context.crop(action.crop);
97
+ // The web's context alone has `extent`; elsewhere the action is skipped, as the module skips it.
98
+ else if ('extent' in action)
99
+ context.extent?.(action.extent);
100
+ }
101
+ //# sourceMappingURL=image-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-editor.js","sourceRoot":"","sources":["../src/image-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAShE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;AAOzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,MAAM;CACuB,CAAC;AAEtC,uEAAuE;AACvE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;CACS,CAAC;AAGpC,MAAM,OAAO,WAAW;IACtB,gEAAgE;IAChE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,kCAAkC,EAClC;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,wBAAwB,CAAS,CAAC;KACxF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAErD;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CACR,MAAc,EACd,UAA6B,EAAE,EAC/B,UAAuB,EAAE;QAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,IAAI,KAA8E,CAAC;QACnF,IAAI,CAAC;YACH,KAAK,MAAM,MAAM,IAAI,OAAO;gBAAE,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACrD,KAAK,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;YACpC,OAAO,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QACxE,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,OAAO,EAAE,CAAC;YAClB,KAAK,EAAE,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,UAAU,CAAC,MAAc;QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IAClE,CAAC;uGA3CU,WAAW;wGAAX,WAAW;;2FAAX,WAAW;kBADvB,OAAO;;AA+CR,SAAS,KAAK,CAAC,OAAgC,EAAE,MAAc;IAC7D,IAAI,QAAQ,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;SACjD,IAAI,QAAQ,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;SACtD,IAAI,MAAM,IAAI,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SAChD,IAAI,MAAM,IAAI,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACrD,iGAAiG;SAC5F,IAAI,QAAQ,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * `ImageEditor`, bound to `expo-image-manipulator`: resizing, cropping, rotating, flipping and\n * re-encoding an image file.\n *\n * ```ts\n * import { ImageEditor, SaveFormat } from '@ng-native/expo/image-editor';\n *\n * private readonly editor = inject(ImageEditor);\n *\n * async thumbnail(uri: string): Promise<string | null> {\n * const image = await this.editor.edit(uri, [{ resize: { width: 256 } }], {\n * format: SaveFormat.WEBP,\n * compress: 0.8,\n * });\n * return image?.uri ?? null;\n * }\n * ```\n *\n * `edit()` takes the module's own actions and save options - the shape of its `manipulateAsync`\n * - and runs them through the module's current contextual API: one context, each action applied\n * in order, the result rendered and saved to a new file in the cache. The context and the\n * rendered image are native memory nothing else holds, so both are released once the file is\n * written, including when it fails. `manipulate()` hands over the module's context itself, for a\n * caller that chains its own steps and releases it after.\n *\n * Without the module installed, `edit()` resolves to null and `manipulate()` is null.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport type {\n Action,\n FlipType as ExpoFlipType,\n ImageManipulatorContext,\n ImageResult,\n SaveFormat as ExpoSaveFormat,\n SaveOptions,\n} from 'expo-image-manipulator';\nimport { expoModule } from './native.ts';\n\ntype Expo = typeof import('expo-image-manipulator');\n\n/** The module's API this service calls. The real module is one; a test provides a fake. */\nexport type NativeImageEditor = Pick<Expo, 'ImageManipulator'>;\n\n/**\n * The formats an edited image is saved in, as `expo-image-manipulator`'s own enum. Importing the\n * enum from the module loads the module, which a test in Node cannot; these are the same strings,\n * typed as the enum.\n */\nexport const SaveFormat = {\n JPEG: 'jpeg',\n PNG: 'png',\n WEBP: 'webp',\n} as unknown as typeof ExpoSaveFormat;\n\n/** The directions an image is flipped in, as the module's own enum. */\nexport const FlipType = {\n Vertical: 'vertical',\n Horizontal: 'horizontal',\n} as unknown as typeof ExpoFlipType;\n\n@Service()\nexport class ImageEditor {\n /** Overridden in a test to edit images without decoding any. */\n static readonly SOURCE = new InjectionToken<NativeImageEditor | null>(\n 'angular-native.imageEditorSource',\n {\n factory: () =>\n expoModule('expo-image-manipulator', () => require('expo-image-manipulator') as Expo),\n },\n );\n\n private readonly native = inject(ImageEditor.SOURCE);\n\n /**\n * Applies each action in order and saves the result as a new file: a JPEG unless `format` says\n * otherwise, `compress` from 0 to 1, and `base64` to have the contents back as well. Each action\n * has exactly one key: `resize`, `rotate` (degrees, clockwise), `flip`, `crop`, or on the web\n * `extent`.\n */\n async edit(\n source: string,\n actions: readonly Action[] = [],\n options: SaveOptions = {},\n ): Promise<ImageResult | null> {\n const context = this.manipulate(source);\n if (!context) return null;\n let image: Awaited<ReturnType<ImageManipulatorContext['renderAsync']>> | undefined;\n try {\n for (const action of actions) apply(context, action);\n image = await context.renderAsync();\n return await image.saveAsync({ format: SaveFormat.JPEG, ...options });\n } finally {\n context.release();\n image?.release();\n }\n }\n\n /**\n * The module's own context for a file or data URI, for chaining steps by hand: `resize()`,\n * `rotate()`, `flip()`, `crop()` and `reset()`, then `renderAsync()`. The caller\n * releases it, and the image it renders, once done.\n */\n manipulate(source: string): ImageManipulatorContext | null {\n return this.native?.ImageManipulator.manipulate(source) ?? null;\n }\n}\n\nfunction apply(context: ImageManipulatorContext, action: Action): void {\n if ('resize' in action) context.resize(action.resize);\n else if ('rotate' in action) context.rotate(action.rotate);\n else if ('flip' in action) context.flip(action.flip);\n else if ('crop' in action) context.crop(action.crop);\n // The web's context alone has `extent`; elsewhere the action is skipped, as the module skips it.\n else if ('extent' in action) context.extent?.(action.extent);\n}\n"]}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `ImagePicker`, bound to `expo-image-picker`.
3
+ *
4
+ * ```ts
5
+ * private readonly picker = inject(ImagePicker);
6
+ * const [photo] = await this.picker.pick({ mediaTypes: ['images'] });
7
+ * ```
8
+ *
9
+ * The options are Expo's own, passed through unchanged. What this adds is the two things every
10
+ * call site otherwise writes: a cancelled picker answers with no assets rather than a result to
11
+ * unwrap, and `capture()` asks for the camera before opening it, where Expo's would reject.
12
+ */
13
+ import { InjectionToken } from '@angular/core';
14
+ import { Permission, type PermissionResponse } from './permissions.ts';
15
+ import * as i0 from "@angular/core";
16
+ export type PickerOptions = import('expo-image-picker').ImagePickerOptions;
17
+ export type PickedAsset = import('expo-image-picker').ImagePickerAsset;
18
+ /** The slice of `expo-image-picker` this needs. */
19
+ export interface NativeImagePicker {
20
+ launchImageLibraryAsync(options?: PickerOptions): Promise<PickerResult>;
21
+ launchCameraAsync(options?: PickerOptions): Promise<PickerResult>;
22
+ getMediaLibraryPermissionsAsync(): Promise<PermissionResponse>;
23
+ requestMediaLibraryPermissionsAsync(): Promise<PermissionResponse>;
24
+ getCameraPermissionsAsync(): Promise<PermissionResponse>;
25
+ requestCameraPermissionsAsync(): Promise<PermissionResponse>;
26
+ }
27
+ interface PickerResult {
28
+ readonly canceled: boolean;
29
+ readonly assets: readonly PickedAsset[] | null;
30
+ }
31
+ export declare class ImagePicker {
32
+ /** Overridden in a test to pick photos without a photo library. */
33
+ static readonly SOURCE: InjectionToken<NativeImagePicker | null>;
34
+ private readonly native;
35
+ /**
36
+ * The photo library. The system picker needs no permission to pick from, so `pick()` never
37
+ * asks; this is for an app that reads the library itself.
38
+ */
39
+ readonly libraryPermission: Permission;
40
+ /** The camera, which `capture()` asks for itself. */
41
+ readonly cameraPermission: Permission;
42
+ /** Pick from the photo library. Empty if they cancelled. */
43
+ pick(options?: PickerOptions): Promise<readonly PickedAsset[]>;
44
+ /** Take a photo or video with the system camera. Empty if they cancelled or refused it. */
45
+ capture(options?: PickerOptions): Promise<readonly PickedAsset[]>;
46
+ static ɵfac: i0.ɵɵFactoryDeclaration<ImagePicker, never>;
47
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
48
+ }
49
+ export {};