@ng-native/expo 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/audio.js +2 -2
  2. package/dist/audio.js.map +1 -1
  3. package/dist/community-views.js +1 -1
  4. package/dist/community-views.js.map +1 -1
  5. package/dist/crypto.js +2 -2
  6. package/dist/crypto.js.map +1 -1
  7. package/dist/database.d.ts +18 -3
  8. package/dist/database.js +26 -7
  9. package/dist/database.js.map +1 -1
  10. package/dist/date-time-picker.d.ts +33 -0
  11. package/dist/date-time-picker.js +77 -0
  12. package/dist/date-time-picker.js.map +1 -0
  13. package/dist/dom-component.d.ts +5 -0
  14. package/dist/dom-component.js +57 -2
  15. package/dist/dom-component.js.map +1 -1
  16. package/dist/expo-ui-components.d.ts +150 -2
  17. package/dist/expo-ui-components.js +170 -3
  18. package/dist/expo-ui-components.js.map +1 -1
  19. package/dist/expo-ui.js +11 -1
  20. package/dist/expo-ui.js.map +1 -1
  21. package/dist/file-system.d.ts +7 -0
  22. package/dist/file-system.js +16 -3
  23. package/dist/file-system.js.map +1 -1
  24. package/dist/fonts.d.ts +0 -9
  25. package/dist/fonts.js +19 -14
  26. package/dist/fonts.js.map +1 -1
  27. package/dist/glass.d.ts +1 -1
  28. package/dist/index.d.ts +3 -2
  29. package/dist/index.js +3 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/live-activity.js +7 -1
  32. package/dist/live-activity.js.map +1 -1
  33. package/dist/location.d.ts +17 -0
  34. package/dist/location.js +18 -0
  35. package/dist/location.js.map +1 -1
  36. package/dist/native.d.ts +8 -1
  37. package/dist/native.js +21 -6
  38. package/dist/native.js.map +1 -1
  39. package/dist/register-expo-view.d.ts +2 -0
  40. package/dist/register-expo-view.js +1 -0
  41. package/dist/register-expo-view.js.map +1 -1
  42. package/dist/video.js +2 -2
  43. package/dist/video.js.map +1 -1
  44. package/dist/widget.d.ts +63 -0
  45. package/dist/widget.js +98 -0
  46. package/dist/widget.js.map +1 -0
  47. package/package.json +6 -2
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 { 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 *\n * A missing `expo-font` rejects the promise rather than throwing, so a caller's `.catch` can mount\n * the app in the fallback face. Sheets that declare no face resolve without reaching for it.\n */\nexport async function loadFonts(\n ...sheets: readonly (SheetWithFonts | null | undefined)[]\n): Promise<void> {\n if (!sheets.some((sheet) => sheet?.fonts?.length)) return;\n await new FontRegistry(expoFonts()).loadSheet(...sheets);\n}\n"]}
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 {\n faceName,\n fontsLoading,\n fontsRegistered,\n fontsSettled,\n onFontsRegistered,\n} 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\n/**\n * Bumped whenever faces are registered, by any registry.\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. Shared by every registry, because `loadFonts()` registers through its own\n * and a screen reads through the injected one.\n */\nconst generation = signal(0);\nonFontsRegistered(() => generation.update((n) => n + 1));\n\nexport class FontRegistry {\n private readonly native: NativeFonts | null;\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 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 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 // Before the first await, so text the app lays out meanwhile waits for the face by name.\n const families = Object.keys(map);\n fontsLoading(families);\n try {\n await native.loadAsync(map);\n } finally {\n fontsSettled(families);\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 }\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 *\n * A missing `expo-font` rejects the promise rather than throwing, so a caller's `.catch` can mount\n * the app in the fallback face. Sheets that declare no face resolve without reaching for it.\n */\nexport async function loadFonts(\n ...sheets: readonly (SheetWithFonts | null | undefined)[]\n): Promise<void> {\n if (!sheets.some((sheet) => sheet?.fonts?.length)) return;\n await new FontRegistry(expoFonts()).loadSheet(...sheets);\n}\n"]}
package/dist/glass.d.ts CHANGED
@@ -15,7 +15,7 @@ export declare class ExpoGlass {
15
15
  /** Reacts to touch, as a control made of glass does. */
16
16
  readonly isInteractive: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
17
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>;
18
+ readonly colorScheme: import("@angular/core").InputSignal<"dark" | "light" | "auto" | undefined>;
19
19
  static ɵfac: i0.ɵɵFactoryDeclaration<ExpoGlass, never>;
20
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
21
  }
package/dist/index.d.ts CHANGED
@@ -8,13 +8,14 @@
8
8
  */
9
9
  export { observed, type Observed } from './observed.ts';
10
10
  export { Permission, type PermissionApi, type PermissionResponse } from './permissions.ts';
11
- export { MissingModuleError, expoModule, optional, type ModulePlatform } from './native.ts';
11
+ export { MissingModuleError, expoModule, optional, unavailable, type ModulePlatform, } from './native.ts';
12
12
  export { NATIVE_VIEWS, registerNativeViews } from './community-views.ts';
13
13
  export { registerExpoUiViews } from './expo-ui.ts';
14
14
  export { nativeState, type NativeState } from './native-state.ts';
15
15
  export { EXPO_VIEWS, expoViewName, registerExpoView, registerExpoViews, type ExpoViewOptions, } from './register-expo-view.ts';
16
- export { UiAccessoryWidgetBackground, UiButton, UiCapsule, UiCircle, UiEllipse, UiLabel, UiLink, UiRectangle, UiRoundedRectangle, UiUnevenRoundedRectangle, UiZStack, type UiZStackAlignment, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiColorPicker, type UiColorChangeEvent, UiForm, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, type UiStepperChangeEvent, UiTextField, type UiTextFieldChangeEvent, UiToggle, type UiToggleChangeEvent, UiSlider, type UiSliderChangeEvent, UiSlot, UiSwipeActions, UiText, UiVStack, type UiDateChangeEvent, type UiPickerOption, type UiModifier, } from './expo-ui-components.ts';
16
+ export { UiAccessoryWidgetBackground, UiButton, UiCapsule, UiCircle, UiEllipse, UiLabel, UiLink, UiRectangle, UiRoundedRectangle, UiUnevenRoundedRectangle, UiZStack, type UiZStackAlignment, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiColorPicker, type UiColorChangeEvent, UiForm, UiChart, type UiChartAreaStyle, type UiChartBarStyle, type UiChartDataPoint, type UiChartLineStyle, type UiChartPieStyle, type UiChartPointStyle, type UiChartPointSymbol, type UiChartRectangleStyle, type UiChartRuleStyle, type UiChartType, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, type UiStepperChangeEvent, UiTextField, type UiTextFieldChangeEvent, UiToggle, type UiToggleChangeEvent, UiSlider, type UiSliderChangeEvent, UiSlot, UiSwipeActions, UiText, UiVStack, type UiDateChangeEvent, type UiPickerOption, type UiModifier, } from './expo-ui-components.ts';
17
17
  export { ExpoImage, type ExpoImageContentFit, type ExpoImageSource } from './expo-image.ts';
18
18
  export { ExpoGlass, ExpoGlassContainer, liquidGlassAvailable, type GlassEffectStyleConfig, type GlassStyle, } from './glass.ts';
19
19
  export { ExpoSymbol, type SymbolAnimationSpec, type SymbolResizeMode, type SymbolScale, type SymbolType, type SymbolWeight, } from './symbol.ts';
20
+ export { DateTimePicker, type DateTimePickerChangeEvent } from './date-time-picker.ts';
20
21
  export { SegmentedControl, type SegmentedControlChangeEvent, type SegmentedControlFont, } from './segmented-control.ts';
package/dist/index.js CHANGED
@@ -8,14 +8,15 @@
8
8
  */
9
9
  export { observed } from "./observed.js";
10
10
  export { Permission } from "./permissions.js";
11
- export { MissingModuleError, expoModule, optional } from "./native.js";
11
+ export { MissingModuleError, expoModule, optional, unavailable, } from "./native.js";
12
12
  export { NATIVE_VIEWS, registerNativeViews } from "./community-views.js";
13
13
  export { registerExpoUiViews } from "./expo-ui.js";
14
14
  export { nativeState } from "./native-state.js";
15
15
  export { EXPO_VIEWS, expoViewName, registerExpoView, registerExpoViews, } from "./register-expo-view.js";
16
- export { UiAccessoryWidgetBackground, UiButton, UiCapsule, UiCircle, UiEllipse, UiLabel, UiLink, UiRectangle, UiRoundedRectangle, UiUnevenRoundedRectangle, UiZStack, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiColorPicker, UiForm, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, UiTextField, UiToggle, UiSlider, UiSlot, UiSwipeActions, UiText, UiVStack, } from "./expo-ui-components.js";
16
+ export { UiAccessoryWidgetBackground, UiButton, UiCapsule, UiCircle, UiEllipse, UiLabel, UiLink, UiRectangle, UiRoundedRectangle, UiUnevenRoundedRectangle, UiZStack, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiColorPicker, UiForm, UiChart, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, UiTextField, UiToggle, UiSlider, UiSlot, UiSwipeActions, UiText, UiVStack, } from "./expo-ui-components.js";
17
17
  export { ExpoImage } from "./expo-image.js";
18
18
  export { ExpoGlass, ExpoGlassContainer, liquidGlassAvailable, } from "./glass.js";
19
19
  export { ExpoSymbol, } from "./symbol.js";
20
+ export { DateTimePicker } from "./date-time-picker.js";
20
21
  export { SegmentedControl, } from "./segmented-control.js";
21
22
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,UAAU,EAA+C,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,QAAQ,EAAuB,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EACL,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,2BAA2B,EAC3B,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,EACT,OAAO,EACP,MAAM,EACN,WAAW,EACX,kBAAkB,EAClB,wBAAwB,EACxB,QAAQ,EAER,YAAY,EACZ,SAAS,EACT,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,EACR,aAAa,EAEb,MAAM,EACN,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EAET,WAAW,EAEX,QAAQ,EAER,QAAQ,EAER,MAAM,EACN,cAAc,EACd,MAAM,EACN,QAAQ,GAIT,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAkD,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GAGrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,UAAU,GAMX,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,GAGjB,MAAM,wBAAwB,CAAC","sourcesContent":["/**\n * What the package exports without naming a module.\n *\n * Everything with an Expo module behind it has its own entry point - `@ng-native/expo/\n * battery.ts` and friends - so that importing haptics does not make an app install the video\n * player. What is left here is the shared machinery: the permission shape every module spells\n * identically, the signal-from-a-listener those services are built out of, and the view names.\n */\nexport { observed, type Observed } from './observed.ts';\nexport { Permission, type PermissionApi, type PermissionResponse } from './permissions.ts';\nexport { MissingModuleError, expoModule, optional, type ModulePlatform } from './native.ts';\nexport { NATIVE_VIEWS, registerNativeViews } from './community-views.ts';\nexport { registerExpoUiViews } from './expo-ui.ts';\nexport { nativeState, type NativeState } from './native-state.ts';\nexport {\n EXPO_VIEWS,\n expoViewName,\n registerExpoView,\n registerExpoViews,\n type ExpoViewOptions,\n} from './register-expo-view.ts';\nexport {\n UiAccessoryWidgetBackground,\n UiButton,\n UiCapsule,\n UiCircle,\n UiEllipse,\n UiLabel,\n UiLink,\n UiRectangle,\n UiRoundedRectangle,\n UiUnevenRoundedRectangle,\n UiZStack,\n type UiZStackAlignment,\n UiDatePicker,\n UiDivider,\n UiHost,\n UiImage,\n UiMenu,\n UiPicker,\n UiColorPicker,\n type UiColorChangeEvent,\n UiForm,\n UiGauge,\n UiHStack,\n UiLabeledContent,\n UiList,\n UiProgress,\n UiSection,\n UiSpacer,\n UiStepper,\n type UiStepperChangeEvent,\n UiTextField,\n type UiTextFieldChangeEvent,\n UiToggle,\n type UiToggleChangeEvent,\n UiSlider,\n type UiSliderChangeEvent,\n UiSlot,\n UiSwipeActions,\n UiText,\n UiVStack,\n type UiDateChangeEvent,\n type UiPickerOption,\n type UiModifier,\n} from './expo-ui-components.ts';\nexport { ExpoImage, type ExpoImageContentFit, type ExpoImageSource } from './expo-image.ts';\nexport {\n ExpoGlass,\n ExpoGlassContainer,\n liquidGlassAvailable,\n type GlassEffectStyleConfig,\n type GlassStyle,\n} from './glass.ts';\nexport {\n ExpoSymbol,\n type SymbolAnimationSpec,\n type SymbolResizeMode,\n type SymbolScale,\n type SymbolType,\n type SymbolWeight,\n} from './symbol.ts';\nexport {\n SegmentedControl,\n type SegmentedControlChangeEvent,\n type SegmentedControlFont,\n} from './segmented-control.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,UAAU,EAA+C,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,QAAQ,EACR,WAAW,GAEZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EACL,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,2BAA2B,EAC3B,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,EACT,OAAO,EACP,MAAM,EACN,WAAW,EACX,kBAAkB,EAClB,wBAAwB,EACxB,QAAQ,EAER,YAAY,EACZ,SAAS,EACT,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,EACR,aAAa,EAEb,MAAM,EACN,OAAO,EAWP,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EAET,WAAW,EAEX,QAAQ,EAER,QAAQ,EAER,MAAM,EACN,cAAc,EACd,MAAM,EACN,QAAQ,GAIT,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAkD,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GAGrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,UAAU,GAMX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAkC,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,gBAAgB,GAGjB,MAAM,wBAAwB,CAAC","sourcesContent":["/**\n * What the package exports without naming a module.\n *\n * Everything with an Expo module behind it has its own entry point - `@ng-native/expo/\n * battery.ts` and friends - so that importing haptics does not make an app install the video\n * player. What is left here is the shared machinery: the permission shape every module spells\n * identically, the signal-from-a-listener those services are built out of, and the view names.\n */\nexport { observed, type Observed } from './observed.ts';\nexport { Permission, type PermissionApi, type PermissionResponse } from './permissions.ts';\nexport {\n MissingModuleError,\n expoModule,\n optional,\n unavailable,\n type ModulePlatform,\n} from './native.ts';\nexport { NATIVE_VIEWS, registerNativeViews } from './community-views.ts';\nexport { registerExpoUiViews } from './expo-ui.ts';\nexport { nativeState, type NativeState } from './native-state.ts';\nexport {\n EXPO_VIEWS,\n expoViewName,\n registerExpoView,\n registerExpoViews,\n type ExpoViewOptions,\n} from './register-expo-view.ts';\nexport {\n UiAccessoryWidgetBackground,\n UiButton,\n UiCapsule,\n UiCircle,\n UiEllipse,\n UiLabel,\n UiLink,\n UiRectangle,\n UiRoundedRectangle,\n UiUnevenRoundedRectangle,\n UiZStack,\n type UiZStackAlignment,\n UiDatePicker,\n UiDivider,\n UiHost,\n UiImage,\n UiMenu,\n UiPicker,\n UiColorPicker,\n type UiColorChangeEvent,\n UiForm,\n UiChart,\n type UiChartAreaStyle,\n type UiChartBarStyle,\n type UiChartDataPoint,\n type UiChartLineStyle,\n type UiChartPieStyle,\n type UiChartPointStyle,\n type UiChartPointSymbol,\n type UiChartRectangleStyle,\n type UiChartRuleStyle,\n type UiChartType,\n UiGauge,\n UiHStack,\n UiLabeledContent,\n UiList,\n UiProgress,\n UiSection,\n UiSpacer,\n UiStepper,\n type UiStepperChangeEvent,\n UiTextField,\n type UiTextFieldChangeEvent,\n UiToggle,\n type UiToggleChangeEvent,\n UiSlider,\n type UiSliderChangeEvent,\n UiSlot,\n UiSwipeActions,\n UiText,\n UiVStack,\n type UiDateChangeEvent,\n type UiPickerOption,\n type UiModifier,\n} from './expo-ui-components.ts';\nexport { ExpoImage, type ExpoImageContentFit, type ExpoImageSource } from './expo-image.ts';\nexport {\n ExpoGlass,\n ExpoGlassContainer,\n liquidGlassAvailable,\n type GlassEffectStyleConfig,\n type GlassStyle,\n} from './glass.ts';\nexport {\n ExpoSymbol,\n type SymbolAnimationSpec,\n type SymbolResizeMode,\n type SymbolScale,\n type SymbolType,\n type SymbolWeight,\n} from './symbol.ts';\nexport { DateTimePicker, type DateTimePickerChangeEvent } from './date-time-picker.ts';\nexport {\n SegmentedControl,\n type SegmentedControlChangeEvent,\n type SegmentedControlFont,\n} from './segmented-control.ts';\n"]}
@@ -33,7 +33,7 @@ export function createLiveActivity(name, layout) {
33
33
  export function createWidget(name, layout) {
34
34
  const expo = widgets();
35
35
  if (!expo)
36
- return STAND_IN;
36
+ return WIDGET_STAND_IN;
37
37
  return expo.createWidget(name, compiled('createWidget', name, layout));
38
38
  }
39
39
  /** `expo-widgets`, which answers with stand-ins itself where there are no widgets; null in Node. */
@@ -51,6 +51,12 @@ const STAND_IN = {
51
51
  start: () => ({ getId: () => '' }),
52
52
  getInstances: () => [],
53
53
  };
54
+ /** A widget in Node: it draws nothing and holds no timeline, and `widget()` keeps it in step. */
55
+ const WIDGET_STAND_IN = {
56
+ updateSnapshot: () => { },
57
+ getTimeline: async () => [],
58
+ reload: () => { },
59
+ };
54
60
  /**
55
61
  * Keeps a Live Activity of the factory's kind in step with `props`: once started, every change to the
56
62
  * signal updates it. One left running from before the app started is picked up rather than started
@@ -1 +1 @@
1
- {"version":3,"file":"live-activity.js","sourceRoot":"","sources":["../src/live-activity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * iOS Live Activities and home-screen widgets, through `expo-widgets`: `createLiveActivity` and\n * `createWidget` draw one from an Angular component's template, and `liveActivity` keeps an\n * activity in step with a signal, on the lock screen and in the Dynamic Island. Off iOS,\n * `expo-widgets` starts a stand-in with no id, and `start()` answers false.\n */\nimport {\n DestroyRef,\n ErrorHandler,\n effect,\n inject,\n signal,\n untracked,\n type InputSignal,\n type Signal,\n type Type,\n} from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** A widget layout: an Angular component whose `props` input the layout draws from. */\nexport type WidgetLayout<T extends object> = Type<{ readonly props: InputSignal<T> }>;\n\n/**\n * A Live Activity drawn by `layout`, an Angular component, as `expo-widgets`' `createLiveActivity`\n * makes one. `name` matches the activity's name in the `expo-widgets` plugin's config.\n *\n * ```ts\n * export const scoreActivity = createLiveActivity('Score', ScoreLayout);\n * ```\n *\n * `@ng-native/metro`'s transformer compiles the layout's template, at build time, to the source the\n * widget extension evaluates, and drops the class, so the app never runs it: the extension does,\n * with no Angular. So the class holds only its inputs, members holding a modifier from\n * `@expo/ui/swift-ui/modifiers`, and members holding a literal, and its template is inline.\n */\nexport function createLiveActivity<T extends object>(\n name: string,\n layout: WidgetLayout<T>,\n): import('expo-widgets').LiveActivityFactory<T> {\n const expo = widgets();\n if (!expo) return STAND_IN as never;\n return expo.createLiveActivity<T>(name, compiled('createLiveActivity', name, layout));\n}\n\n/**\n * A home-screen widget drawn by `layout`, as `expo-widgets`' `createWidget` makes one. The layout\n * reads what the widget is drawn in, such as its `widgetFamily`, from an `environment` input beside\n * `props`. Compiled as `createLiveActivity`'s layout is.\n */\nexport function createWidget<T extends object>(\n name: string,\n layout: WidgetLayout<T>,\n): import('expo-widgets').Widget<T> {\n const expo = widgets();\n if (!expo) return STAND_IN as never;\n return expo.createWidget<T>(name, compiled('createWidget', name, layout));\n}\n\n/** `expo-widgets`, which answers with stand-ins itself where there are no widgets; null in Node. */\nfunction widgets() {\n return expoModule(\n 'expo-widgets',\n () => require('expo-widgets') as typeof import('expo-widgets'),\n ['ios', 'android', 'web'],\n );\n}\n\n/** The layout's source, which the transformer put in place of the class. */\nfunction compiled(call: string, name: string, layout: unknown): never {\n if (typeof layout === 'string') return layout as never;\n throw new Error(\n `${call}('${name}', ...) takes a layout @ng-native/metro's transformer compiles, and it did not run on this file.`,\n );\n}\n\n/** What there is in Node, where no widget can be drawn: an activity that never starts. */\nconst STAND_IN = {\n start: () => ({ getId: () => '' }),\n getInstances: () => [],\n};\n\n/**\n * How an ended activity leaves the lock screen: `'default'` keeps it, with its final props, for up\n * to four hours; `'immediate'` removes it; `{ after }` keeps it until then.\n */\nexport type LiveActivityDismissal = 'default' | 'immediate' | { readonly after: Date };\n\ninterface Subscription {\n remove(): void;\n}\n\n/** One running activity, as `expo-widgets`' `LiveActivity` is. */\nexport interface NativeLiveActivity<T extends object> {\n getId(): string;\n update(props: T, staleDate?: Date): Promise<void>;\n end(dismissalPolicy?: LiveActivityDismissal, props?: T, contentDate?: Date): Promise<void>;\n getPushToken(): Promise<string | null>;\n addPushTokenListener(listener: (event: { pushToken: string }) => void): Subscription;\n}\n\n/** What `createLiveActivity` answers: starts activities of one kind, and lists the running ones. */\nexport interface LiveActivityFactory<T extends object> {\n start(props: T, url?: string, staleDate?: Date): NativeLiveActivity<T>;\n getInstances(): NativeLiveActivity<T>[];\n}\n\n/** A Live Activity kept in step with a signal. */\nexport interface LiveActivityRef {\n /** Whether an activity of this kind is running and following the signal. */\n readonly active: Signal<boolean>;\n /** The running activity's id, or null. */\n readonly id: Signal<string | null>;\n /** The token a server sends the activity's updates to over APNs, once iOS issues one. */\n readonly pushToken: Signal<string | null>;\n /**\n * Why the last start or update did not happen. A start the system refuses, as when Live\n * Activities are turned off in Settings, is only kept here; a failed update also goes to the\n * `ErrorHandler`.\n */\n readonly error: Signal<unknown>;\n /**\n * Shows the activity with the signal's value, or picks up one already running. Answers whether it\n * is running now; `url` opens the app at that link when the activity is tapped.\n */\n start(options?: { url?: string }): boolean;\n /** Ends every activity of this kind with the signal's value. */\n end(dismissal?: LiveActivityDismissal): Promise<void>;\n}\n\n/**\n * Keeps a Live Activity of the factory's kind in step with `props`: once started, every change to the\n * signal updates it. One left running from before the app started is picked up rather than started\n * again, since iOS caps how many an app runs. Destroying the injector stops the updates and leaves\n * the activity running, as it outlives the app. Call it in an injection context.\n */\nexport function liveActivity<T extends object>(\n factory: LiveActivityFactory<T>,\n props: Signal<T>,\n): LiveActivityRef {\n const errors = inject(ErrorHandler);\n const active = signal(false);\n const id = signal<string | null>(null);\n const pushToken = signal<string | null>(null);\n const error = signal<unknown>(null);\n let current: NativeLiveActivity<T> | null = null;\n let tokenSubscription: Subscription | null = null;\n\n const report = (failure: unknown) => {\n error.set(failure);\n errors.handleError(failure);\n };\n\n const running = (): NativeLiveActivity<T>[] => {\n try {\n return factory.getInstances();\n } catch (failure) {\n error.set(failure);\n return [];\n }\n };\n\n const forget = () => {\n tokenSubscription?.remove();\n tokenSubscription = null;\n current = null;\n active.set(false);\n id.set(null);\n pushToken.set(null);\n };\n\n const adopt = (activity: NativeLiveActivity<T>) => {\n current = activity;\n active.set(true);\n id.set(activity.getId());\n error.set(null);\n tokenSubscription = activity.addPushTokenListener((event) => pushToken.set(event.pushToken));\n activity.getPushToken().then(\n (token) => current === activity && token !== null && pushToken.set(token),\n () => {},\n );\n };\n\n const existing = running()[0];\n if (existing) adopt(existing);\n\n effect(() => {\n const next = props();\n const activity = current;\n if (activity) untracked(() => activity.update(next).catch(report));\n });\n\n inject(DestroyRef).onDestroy(() => {\n tokenSubscription?.remove();\n tokenSubscription = null;\n });\n\n return {\n active: active.asReadonly(),\n id: id.asReadonly(),\n pushToken: pushToken.asReadonly(),\n error: error.asReadonly(),\n start(options = {}) {\n if (current) return true;\n const value = untracked(props);\n try {\n const already = running()[0];\n const activity = already ?? factory.start(value, options.url);\n if (!activity.getId()) {\n error.set(new Error('Live Activities are only on iOS.'));\n return false;\n }\n adopt(activity);\n if (already) already.update(value).catch(report);\n return true;\n } catch (failure) {\n error.set(failure);\n return false;\n }\n },\n async end(dismissal = 'default') {\n const value = untracked(props);\n const activities = new Set(running());\n if (current) activities.add(current);\n forget();\n await Promise.all([...activities].map((activity) => activity.end(dismissal, value))).catch(\n report,\n );\n },\n };\n}\n"]}
1
+ {"version":3,"file":"live-activity.js","sourceRoot":"","sources":["../src/live-activity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * iOS Live Activities and home-screen widgets, through `expo-widgets`: `createLiveActivity` and\n * `createWidget` draw one from an Angular component's template, and `liveActivity` keeps an\n * activity in step with a signal, on the lock screen and in the Dynamic Island. Off iOS,\n * `expo-widgets` starts a stand-in with no id, and `start()` answers false.\n */\nimport {\n DestroyRef,\n ErrorHandler,\n effect,\n inject,\n signal,\n untracked,\n type InputSignal,\n type Signal,\n type Type,\n} from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** A widget layout: an Angular component whose `props` input the layout draws from. */\nexport type WidgetLayout<T extends object> = Type<{ readonly props: InputSignal<T> }>;\n\n/**\n * A Live Activity drawn by `layout`, an Angular component, as `expo-widgets`' `createLiveActivity`\n * makes one. `name` matches the activity's name in the `expo-widgets` plugin's config.\n *\n * ```ts\n * export const scoreActivity = createLiveActivity('Score', ScoreLayout);\n * ```\n *\n * `@ng-native/metro`'s transformer compiles the layout's template, at build time, to the source the\n * widget extension evaluates, and drops the class, so the app never runs it: the extension does,\n * with no Angular. So the class holds only its inputs, members holding a modifier from\n * `@expo/ui/swift-ui/modifiers`, and members holding a literal, and its template is inline.\n */\nexport function createLiveActivity<T extends object>(\n name: string,\n layout: WidgetLayout<T>,\n): import('expo-widgets').LiveActivityFactory<T> {\n const expo = widgets();\n if (!expo) return STAND_IN as never;\n return expo.createLiveActivity<T>(name, compiled('createLiveActivity', name, layout));\n}\n\n/**\n * A home-screen widget drawn by `layout`, as `expo-widgets`' `createWidget` makes one. The layout\n * reads what the widget is drawn in, such as its `widgetFamily`, from an `environment` input beside\n * `props`. Compiled as `createLiveActivity`'s layout is.\n */\nexport function createWidget<T extends object>(\n name: string,\n layout: WidgetLayout<T>,\n): import('expo-widgets').Widget<T> {\n const expo = widgets();\n if (!expo) return WIDGET_STAND_IN as never;\n return expo.createWidget<T>(name, compiled('createWidget', name, layout));\n}\n\n/** `expo-widgets`, which answers with stand-ins itself where there are no widgets; null in Node. */\nfunction widgets() {\n return expoModule(\n 'expo-widgets',\n () => require('expo-widgets') as typeof import('expo-widgets'),\n ['ios', 'android', 'web'],\n );\n}\n\n/** The layout's source, which the transformer put in place of the class. */\nfunction compiled(call: string, name: string, layout: unknown): never {\n if (typeof layout === 'string') return layout as never;\n throw new Error(\n `${call}('${name}', ...) takes a layout @ng-native/metro's transformer compiles, and it did not run on this file.`,\n );\n}\n\n/** What there is in Node, where no widget can be drawn: an activity that never starts. */\nconst STAND_IN = {\n start: () => ({ getId: () => '' }),\n getInstances: () => [],\n};\n\n/** A widget in Node: it draws nothing and holds no timeline, and `widget()` keeps it in step. */\nconst WIDGET_STAND_IN = {\n updateSnapshot: () => {},\n getTimeline: async () => [],\n reload: () => {},\n};\n\n/**\n * How an ended activity leaves the lock screen: `'default'` keeps it, with its final props, for up\n * to four hours; `'immediate'` removes it; `{ after }` keeps it until then.\n */\nexport type LiveActivityDismissal = 'default' | 'immediate' | { readonly after: Date };\n\ninterface Subscription {\n remove(): void;\n}\n\n/** One running activity, as `expo-widgets`' `LiveActivity` is. */\nexport interface NativeLiveActivity<T extends object> {\n getId(): string;\n update(props: T, staleDate?: Date): Promise<void>;\n end(dismissalPolicy?: LiveActivityDismissal, props?: T, contentDate?: Date): Promise<void>;\n getPushToken(): Promise<string | null>;\n addPushTokenListener(listener: (event: { pushToken: string }) => void): Subscription;\n}\n\n/** What `createLiveActivity` answers: starts activities of one kind, and lists the running ones. */\nexport interface LiveActivityFactory<T extends object> {\n start(props: T, url?: string, staleDate?: Date): NativeLiveActivity<T>;\n getInstances(): NativeLiveActivity<T>[];\n}\n\n/** A Live Activity kept in step with a signal. */\nexport interface LiveActivityRef {\n /** Whether an activity of this kind is running and following the signal. */\n readonly active: Signal<boolean>;\n /** The running activity's id, or null. */\n readonly id: Signal<string | null>;\n /** The token a server sends the activity's updates to over APNs, once iOS issues one. */\n readonly pushToken: Signal<string | null>;\n /**\n * Why the last start or update did not happen. A start the system refuses, as when Live\n * Activities are turned off in Settings, is only kept here; a failed update also goes to the\n * `ErrorHandler`.\n */\n readonly error: Signal<unknown>;\n /**\n * Shows the activity with the signal's value, or picks up one already running. Answers whether it\n * is running now; `url` opens the app at that link when the activity is tapped.\n */\n start(options?: { url?: string }): boolean;\n /** Ends every activity of this kind with the signal's value. */\n end(dismissal?: LiveActivityDismissal): Promise<void>;\n}\n\n/**\n * Keeps a Live Activity of the factory's kind in step with `props`: once started, every change to the\n * signal updates it. One left running from before the app started is picked up rather than started\n * again, since iOS caps how many an app runs. Destroying the injector stops the updates and leaves\n * the activity running, as it outlives the app. Call it in an injection context.\n */\nexport function liveActivity<T extends object>(\n factory: LiveActivityFactory<T>,\n props: Signal<T>,\n): LiveActivityRef {\n const errors = inject(ErrorHandler);\n const active = signal(false);\n const id = signal<string | null>(null);\n const pushToken = signal<string | null>(null);\n const error = signal<unknown>(null);\n let current: NativeLiveActivity<T> | null = null;\n let tokenSubscription: Subscription | null = null;\n\n const report = (failure: unknown) => {\n error.set(failure);\n errors.handleError(failure);\n };\n\n const running = (): NativeLiveActivity<T>[] => {\n try {\n return factory.getInstances();\n } catch (failure) {\n error.set(failure);\n return [];\n }\n };\n\n const forget = () => {\n tokenSubscription?.remove();\n tokenSubscription = null;\n current = null;\n active.set(false);\n id.set(null);\n pushToken.set(null);\n };\n\n const adopt = (activity: NativeLiveActivity<T>) => {\n current = activity;\n active.set(true);\n id.set(activity.getId());\n error.set(null);\n tokenSubscription = activity.addPushTokenListener((event) => pushToken.set(event.pushToken));\n activity.getPushToken().then(\n (token) => current === activity && token !== null && pushToken.set(token),\n () => {},\n );\n };\n\n const existing = running()[0];\n if (existing) adopt(existing);\n\n effect(() => {\n const next = props();\n const activity = current;\n if (activity) untracked(() => activity.update(next).catch(report));\n });\n\n inject(DestroyRef).onDestroy(() => {\n tokenSubscription?.remove();\n tokenSubscription = null;\n });\n\n return {\n active: active.asReadonly(),\n id: id.asReadonly(),\n pushToken: pushToken.asReadonly(),\n error: error.asReadonly(),\n start(options = {}) {\n if (current) return true;\n const value = untracked(props);\n try {\n const already = running()[0];\n const activity = already ?? factory.start(value, options.url);\n if (!activity.getId()) {\n error.set(new Error('Live Activities are only on iOS.'));\n return false;\n }\n adopt(activity);\n if (already) already.update(value).catch(report);\n return true;\n } catch (failure) {\n error.set(failure);\n return false;\n }\n },\n async end(dismissal = 'default') {\n const value = untracked(props);\n const activities = new Set(running());\n if (current) activities.add(current);\n forget();\n await Promise.all([...activities].map((activity) => activity.end(dismissal, value))).catch(\n report,\n );\n },\n };\n}\n"]}
@@ -39,6 +39,13 @@ export interface WatchOptions {
39
39
  readonly interval?: number;
40
40
  }
41
41
  type ExpoFix = import('expo-location').LocationObject;
42
+ /** A point on the map: what an address geocodes to, and what `reverseGeocode` takes. */
43
+ export interface Coordinates {
44
+ readonly latitude: number;
45
+ readonly longitude: number;
46
+ }
47
+ /** A place as the platform's geocoder names it: street, city, region, country and the rest. */
48
+ export type Address = import('expo-location').LocationGeocodedAddress;
42
49
  type ExpoOptions = import('expo-location').LocationOptions;
43
50
  /** The slice of `expo-location` this needs. */
44
51
  export interface NativeLocation {
@@ -48,6 +55,8 @@ export interface NativeLocation {
48
55
  watchPositionAsync(options: ExpoOptions, callback: (fix: ExpoFix) => void): Promise<{
49
56
  remove(): void;
50
57
  }>;
58
+ geocodeAsync?(address: string): Promise<readonly Coordinates[]>;
59
+ reverseGeocodeAsync?(coordinates: Coordinates): Promise<readonly Address[]>;
51
60
  }
52
61
  export declare class Location {
53
62
  /** Overridden in a test to be somewhere without a GPS. */
@@ -60,6 +69,14 @@ export declare class Location {
60
69
  readonly position: Signal<Position | null>;
61
70
  /** Read the position once. Null without the permission or the module. */
62
71
  current(accuracy?: LocationAccuracy): Promise<Position | null>;
72
+ /**
73
+ * The places an address could be, best match first, from the platform's own geocoder. Empty
74
+ * for an address it does not know, without the permission, and where the source has no
75
+ * geocoder, as off a device. An app built without `expo-location` throws `MissingModuleError`.
76
+ */
77
+ geocode(address: string): Promise<Coordinates[]>;
78
+ /** The addresses at a point. Empty, and throwing, where `geocode` is. */
79
+ reverseGeocode(coordinates: Coordinates): Promise<Address[]>;
63
80
  /** Follow the position into the signal. Resolves to the function that stops. */
64
81
  start(options?: WatchOptions): Promise<() => void>;
65
82
  static ɵfac: i0.ɵɵFactoryDeclaration<Location, never>;
package/dist/location.js CHANGED
@@ -46,6 +46,24 @@ export class Location {
46
46
  this.latest.set(positionOf(fix));
47
47
  return this.latest();
48
48
  }
49
+ /**
50
+ * The places an address could be, best match first, from the platform's own geocoder. Empty
51
+ * for an address it does not know, without the permission, and where the source has no
52
+ * geocoder, as off a device. An app built without `expo-location` throws `MissingModuleError`.
53
+ */
54
+ async geocode(address) {
55
+ if (!this.native?.geocodeAsync || !(await this.permission.ensure()))
56
+ return [];
57
+ const found = await this.native.geocodeAsync(address);
58
+ return found.map(({ latitude, longitude }) => ({ latitude, longitude }));
59
+ }
60
+ /** The addresses at a point. Empty, and throwing, where `geocode` is. */
61
+ async reverseGeocode(coordinates) {
62
+ if (!this.native?.reverseGeocodeAsync || !(await this.permission.ensure()))
63
+ return [];
64
+ const { latitude, longitude } = coordinates;
65
+ return [...(await this.native.reverseGeocodeAsync({ latitude, longitude }))];
66
+ }
49
67
  /** Follow the position into the signal. Resolves to the function that stops. */
50
68
  async start(options = {}) {
51
69
  if (!this.native || !(await this.permission.ensure()))
@@ -1 +1 @@
1
- {"version":3,"file":"location.js","sourceRoot":"","sources":["../src/location.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,WAAW,EAA2B,MAAM,kBAAkB,CAAC;;AAKpF,sEAAsE;AACtE,MAAM,QAAQ,GAAqC;IACjD,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,CAAC;CACd,CAAC;AAuCF,MAAM,OAAO,QAAQ;IACnB,0DAA0D;IAC1D,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,+BAA+B,EAC/B;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,eAAe,CAAmB,CAAC,IAAI,IAAI;KACxF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,GAAG,MAAM,CAAkB,IAAI;+EAAC,CAAC;IAExD,qFAAqF;IAC5E,UAAU,GAAG,UAAU,CAAC,EAAE,CACjC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,6BAA6B,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EACpF,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,iCAAiC,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CACzF,CAAC;IAEF,wDAAwD;IAC/C,QAAQ,GAA4B,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAEtE,yEAAyE;IACzE,KAAK,CAAC,OAAO,CAAC,WAA6B,UAAU;QACnD,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QACnE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACxF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,KAAK,CAAC,UAAwB,EAAE;QACpC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;QACvE,MAAM,IAAI,GAAgB,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,IAAI,UAAU,CAAC,EAAE,CAAC;QACjF,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;QAC7E,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC;QACzE,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CACtE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CACjC,CAAC;QACF,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;IACrC,CAAC;uGAxCU,QAAQ;wGAAR,QAAQ;;2FAAR,QAAQ;kBADpB,OAAO;;AA4CR,SAAS,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,EAAW;IAChD,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS;KACV,CAAC;AACJ,CAAC","sourcesContent":["/**\n * `Location`, bound to `expo-location`.\n *\n * ```ts\n * private readonly location = inject(Location);\n * protected readonly here = this.location.position;\n *\n * const stop = await this.location.start({ accuracy: 'high', distance: 10 });\n * ```\n *\n * A `position` signal, which a one-off `current()` and a running `start()` both write to. Nothing\n * is watched until `start`, and the accuracy is explicit, for the same reason a sensor's interval\n * is: GPS at navigation accuracy is a battery the user notices. Both ask for the foreground\n * permission first, and do nothing without it.\n */\nimport { InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\nimport { Permission, UNAVAILABLE, type PermissionResponse } from './permissions.ts';\n\n/** How precise, in Expo's order: `lowest` is kilometres, `navigation` is metres and a hot GPS. */\nexport type LocationAccuracy = 'lowest' | 'low' | 'balanced' | 'high' | 'highest' | 'navigation';\n\n/** Expo's `LocationAccuracy` enum, whose values are these numbers. */\nconst ACCURACY: Record<LocationAccuracy, number> = {\n lowest: 1,\n low: 2,\n balanced: 3,\n high: 4,\n highest: 5,\n navigation: 6,\n};\n\nexport interface Position {\n readonly latitude: number;\n readonly longitude: number;\n /** Metres above sea level, where the platform knows. */\n readonly altitude: number | null;\n /** The radius, in metres, the fix is good to. */\n readonly accuracy: number | null;\n /** Degrees from true north, while moving. */\n readonly heading: number | null;\n /** Metres per second. */\n readonly speed: number | null;\n readonly timestamp: number;\n}\n\nexport interface WatchOptions {\n readonly accuracy?: LocationAccuracy;\n /** Metres moved before the next update. */\n readonly distance?: number;\n /** Milliseconds between updates. Android only; iOS updates on distance. */\n readonly interval?: number;\n}\n\ntype ExpoFix = import('expo-location').LocationObject;\ntype ExpoOptions = import('expo-location').LocationOptions;\n\n/** The slice of `expo-location` this needs. */\nexport interface NativeLocation {\n getForegroundPermissionsAsync(): Promise<PermissionResponse>;\n requestForegroundPermissionsAsync(): Promise<PermissionResponse>;\n getCurrentPositionAsync(options?: ExpoOptions): Promise<ExpoFix>;\n watchPositionAsync(\n options: ExpoOptions,\n callback: (fix: ExpoFix) => void,\n ): Promise<{ remove(): void }>;\n}\n\n@Service()\nexport class Location {\n /** Overridden in a test to be somewhere without a GPS. */\n static readonly SOURCE = new InjectionToken<NativeLocation | null>(\n 'angular-native.locationSource',\n {\n factory: () =>\n expoModule('expo-location', () => require('expo-location') as NativeLocation) ?? null,\n },\n );\n\n private readonly native = inject(Location.SOURCE);\n private readonly latest = signal<Position | null>(null);\n\n /** The foreground permission, which `current()` and `start()` ask for themselves. */\n readonly permission = Permission.of(\n () => this.native?.getForegroundPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestForegroundPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** The last position read, or null before the first. */\n readonly position: Signal<Position | null> = this.latest.asReadonly();\n\n /** Read the position once. Null without the permission or the module. */\n async current(accuracy: LocationAccuracy = 'balanced'): Promise<Position | null> {\n if (!this.native || !(await this.permission.ensure())) return null;\n const fix = await this.native.getCurrentPositionAsync({ accuracy: ACCURACY[accuracy] });\n this.latest.set(positionOf(fix));\n return this.latest();\n }\n\n /** Follow the position into the signal. Resolves to the function that stops. */\n async start(options: WatchOptions = {}): Promise<() => void> {\n if (!this.native || !(await this.permission.ensure())) return () => {};\n const expo: ExpoOptions = { accuracy: ACCURACY[options.accuracy ?? 'balanced'] };\n if (options.distance !== undefined) expo.distanceInterval = options.distance;\n if (options.interval !== undefined) expo.timeInterval = options.interval;\n const subscription = await this.native.watchPositionAsync(expo, (fix) =>\n this.latest.set(positionOf(fix)),\n );\n return () => subscription.remove();\n }\n}\n\nfunction positionOf({ coords, timestamp }: ExpoFix): Position {\n return {\n latitude: coords.latitude,\n longitude: coords.longitude,\n altitude: coords.altitude,\n accuracy: coords.accuracy,\n heading: coords.heading,\n speed: coords.speed,\n timestamp,\n };\n}\n"]}
1
+ {"version":3,"file":"location.js","sourceRoot":"","sources":["../src/location.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Location`, bound to `expo-location`.\n *\n * ```ts\n * private readonly location = inject(Location);\n * protected readonly here = this.location.position;\n *\n * const stop = await this.location.start({ accuracy: 'high', distance: 10 });\n * ```\n *\n * A `position` signal, which a one-off `current()` and a running `start()` both write to. Nothing\n * is watched until `start`, and the accuracy is explicit, for the same reason a sensor's interval\n * is: GPS at navigation accuracy is a battery the user notices. Both ask for the foreground\n * permission first, and do nothing without it.\n */\nimport { InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\nimport { Permission, UNAVAILABLE, type PermissionResponse } from './permissions.ts';\n\n/** How precise, in Expo's order: `lowest` is kilometres, `navigation` is metres and a hot GPS. */\nexport type LocationAccuracy = 'lowest' | 'low' | 'balanced' | 'high' | 'highest' | 'navigation';\n\n/** Expo's `LocationAccuracy` enum, whose values are these numbers. */\nconst ACCURACY: Record<LocationAccuracy, number> = {\n lowest: 1,\n low: 2,\n balanced: 3,\n high: 4,\n highest: 5,\n navigation: 6,\n};\n\nexport interface Position {\n readonly latitude: number;\n readonly longitude: number;\n /** Metres above sea level, where the platform knows. */\n readonly altitude: number | null;\n /** The radius, in metres, the fix is good to. */\n readonly accuracy: number | null;\n /** Degrees from true north, while moving. */\n readonly heading: number | null;\n /** Metres per second. */\n readonly speed: number | null;\n readonly timestamp: number;\n}\n\nexport interface WatchOptions {\n readonly accuracy?: LocationAccuracy;\n /** Metres moved before the next update. */\n readonly distance?: number;\n /** Milliseconds between updates. Android only; iOS updates on distance. */\n readonly interval?: number;\n}\n\ntype ExpoFix = import('expo-location').LocationObject;\n/** A point on the map: what an address geocodes to, and what `reverseGeocode` takes. */\nexport interface Coordinates {\n readonly latitude: number;\n readonly longitude: number;\n}\n\n/** A place as the platform's geocoder names it: street, city, region, country and the rest. */\nexport type Address = import('expo-location').LocationGeocodedAddress;\n\ntype ExpoOptions = import('expo-location').LocationOptions;\n\n/** The slice of `expo-location` this needs. */\nexport interface NativeLocation {\n getForegroundPermissionsAsync(): Promise<PermissionResponse>;\n requestForegroundPermissionsAsync(): Promise<PermissionResponse>;\n getCurrentPositionAsync(options?: ExpoOptions): Promise<ExpoFix>;\n watchPositionAsync(\n options: ExpoOptions,\n callback: (fix: ExpoFix) => void,\n ): Promise<{ remove(): void }>;\n geocodeAsync?(address: string): Promise<readonly Coordinates[]>;\n reverseGeocodeAsync?(coordinates: Coordinates): Promise<readonly Address[]>;\n}\n\n@Service()\nexport class Location {\n /** Overridden in a test to be somewhere without a GPS. */\n static readonly SOURCE = new InjectionToken<NativeLocation | null>(\n 'angular-native.locationSource',\n {\n factory: () =>\n expoModule('expo-location', () => require('expo-location') as NativeLocation) ?? null,\n },\n );\n\n private readonly native = inject(Location.SOURCE);\n private readonly latest = signal<Position | null>(null);\n\n /** The foreground permission, which `current()` and `start()` ask for themselves. */\n readonly permission = Permission.of(\n () => this.native?.getForegroundPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestForegroundPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** The last position read, or null before the first. */\n readonly position: Signal<Position | null> = this.latest.asReadonly();\n\n /** Read the position once. Null without the permission or the module. */\n async current(accuracy: LocationAccuracy = 'balanced'): Promise<Position | null> {\n if (!this.native || !(await this.permission.ensure())) return null;\n const fix = await this.native.getCurrentPositionAsync({ accuracy: ACCURACY[accuracy] });\n this.latest.set(positionOf(fix));\n return this.latest();\n }\n\n /**\n * The places an address could be, best match first, from the platform's own geocoder. Empty\n * for an address it does not know, without the permission, and where the source has no\n * geocoder, as off a device. An app built without `expo-location` throws `MissingModuleError`.\n */\n async geocode(address: string): Promise<Coordinates[]> {\n if (!this.native?.geocodeAsync || !(await this.permission.ensure())) return [];\n const found = await this.native.geocodeAsync(address);\n return found.map(({ latitude, longitude }) => ({ latitude, longitude }));\n }\n\n /** The addresses at a point. Empty, and throwing, where `geocode` is. */\n async reverseGeocode(coordinates: Coordinates): Promise<Address[]> {\n if (!this.native?.reverseGeocodeAsync || !(await this.permission.ensure())) return [];\n const { latitude, longitude } = coordinates;\n return [...(await this.native.reverseGeocodeAsync({ latitude, longitude }))];\n }\n\n /** Follow the position into the signal. Resolves to the function that stops. */\n async start(options: WatchOptions = {}): Promise<() => void> {\n if (!this.native || !(await this.permission.ensure())) return () => {};\n const expo: ExpoOptions = { accuracy: ACCURACY[options.accuracy ?? 'balanced'] };\n if (options.distance !== undefined) expo.distanceInterval = options.distance;\n if (options.interval !== undefined) expo.timeInterval = options.interval;\n const subscription = await this.native.watchPositionAsync(expo, (fix) =>\n this.latest.set(positionOf(fix)),\n );\n return () => subscription.remove();\n }\n}\n\nfunction positionOf({ coords, timestamp }: ExpoFix): Position {\n return {\n latitude: coords.latitude,\n longitude: coords.longitude,\n altitude: coords.altitude,\n accuracy: coords.accuracy,\n heading: coords.heading,\n speed: coords.speed,\n timestamp,\n };\n}\n"]}
package/dist/native.d.ts CHANGED
@@ -25,8 +25,15 @@ export declare class MissingModuleError extends Error {
25
25
  readonly name = "MissingModuleError";
26
26
  /** The package, as it is installed: `expo-haptics`. */
27
27
  readonly module: string;
28
- constructor(module: string, platform: ModulePlatform, cause?: unknown);
28
+ constructor(module: string, platform: ModulePlatform, cause?: unknown, message?: string);
29
29
  }
30
+ /**
31
+ * The `MissingModuleError` to throw where `expoModule` answered null: the module is installed or not, but there is
32
+ * nothing to load it on. In Node, where a test runs, that is every native module, and "install it"
33
+ * would be advice to do what is already done; `inTest` says what a test does instead. On a platform
34
+ * the module does not support, it says that.
35
+ */
36
+ export declare function unavailable(module: string, inTest: string): MissingModuleError;
30
37
  /** The platform the app is running on, or null in Node, where there is none to ask. */
31
38
  export declare function currentPlatform(): ModulePlatform | null;
32
39
  /**
package/dist/native.js CHANGED
@@ -30,15 +30,30 @@ export class MissingModuleError extends Error {
30
30
  name = 'MissingModuleError';
31
31
  /** The package, as it is installed: `expo-haptics`. */
32
32
  module;
33
- constructor(module, platform, cause) {
34
- super(platform === 'web'
35
- ? `${module} is not installed. Install it with "npx expo install ${module}", then restart the dev server.`
36
- : `${module} is not in this build of the app. Install it with "npx expo install ${module}", ` +
37
- `then rebuild the app ("npx expo run:${platform}", or a new EAS build): a development ` +
38
- `build, and Expo Go, contain only the native modules they were built with.`, { cause });
33
+ constructor(module, platform, cause, message) {
34
+ super(message ??
35
+ (platform === 'web'
36
+ ? `${module} is not installed. Install it with "npx expo install ${module}", then restart the dev server.`
37
+ : `${module} is not in this build of the app. Install it with "npx expo install ${module}", ` +
38
+ `then rebuild the app ("npx expo run:${platform}", or a new EAS build): a development ` +
39
+ `build, and Expo Go, contain only the native modules they were built with.`), { cause });
39
40
  this.module = module;
40
41
  }
41
42
  }
43
+ /**
44
+ * The `MissingModuleError` to throw where `expoModule` answered null: the module is installed or not, but there is
45
+ * nothing to load it on. In Node, where a test runs, that is every native module, and "install it"
46
+ * would be advice to do what is already done; `inTest` says what a test does instead. On a platform
47
+ * the module does not support, it says that.
48
+ */
49
+ export function unavailable(module, inTest) {
50
+ const platform = currentPlatform();
51
+ const message = platform === null
52
+ ? `[angular-native] ${module} has no native module to load in Node, where a test runs. ${inTest}`
53
+ : `[angular-native] ${module} is not available on ${platform}.`;
54
+ // The platform is only read for the default message, which this replaces.
55
+ return new MissingModuleError(module, platform ?? 'web', undefined, message);
56
+ }
42
57
  /** The platform the app is running on, or null in Node, where there is none to ask. */
43
58
  export function currentPlatform() {
44
59
  return optional(() => require('react-native').Platform.OS);
@@ -1 +1 @@
1
- {"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reaching an Expo module without importing one.\n *\n * A static `import ... from 'expo-battery'` makes the file unloadable by Node, which is what used\n * to force every service into two files: the behaviour somewhere the test suite could reach, and\n * the binding somewhere it could not. A `require` inside a function has neither problem. Metro\n * still resolves the string literal at build time, so the module is bundled exactly as before and\n * an app that has not installed it still gets a build error rather than a surprise at runtime;\n * Node never evaluates the call at all, because nothing in a test asks for the real platform.\n *\n * The `catch` is doing two jobs, and only one of them is Node. In an ESM test `require` is not\n * defined, so calling it throws `ReferenceError` and the service goes inert - the same state as\n * a device that has no such sensor. On a device the throw is a module that is present but not\n * linked, which is the same answer for the same reason.\n */\nexport function optional<T>(load: () => T): T | null {\n try {\n return load();\n } catch {\n return null;\n }\n}\n\n/** The platforms an Expo module is written for. */\nexport type ModulePlatform = 'ios' | 'android' | 'web';\n\n/**\n * A module the app needs and does not have: the package is not installed, or it is and the app was\n * not rebuilt since, so its native half is not in the binary. Development builds and Expo Go carry\n * only the native modules they were built with.\n */\nexport class MissingModuleError extends Error {\n override readonly name = 'MissingModuleError';\n /** The package, as it is installed: `expo-haptics`. */\n readonly module: string;\n\n constructor(module: string, platform: ModulePlatform, cause?: unknown) {\n super(\n platform === 'web'\n ? `${module} is not installed. Install it with \"npx expo install ${module}\", then restart the dev server.`\n : `${module} is not in this build of the app. Install it with \"npx expo install ${module}\", ` +\n `then rebuild the app (\"npx expo run:${platform}\", or a new EAS build): a development ` +\n `build, and Expo Go, contain only the native modules they were built with.`,\n { cause },\n );\n this.module = module;\n }\n}\n\n/** The platform the app is running on, or null in Node, where there is none to ask. */\nexport function currentPlatform(): ModulePlatform | null {\n return optional(\n () => (require('react-native') as { Platform: { OS: string } }).Platform.OS as ModulePlatform,\n );\n}\n\n/**\n * For each Expo package whose JavaScript requires a native module as it is evaluated, and so\n * throws when its native half is not in the build, one of those native modules. Any one of them\n * says whether the package's native half is there, and each of these is registered on both iOS and\n * Android.\n */\nconst NATIVE_MODULES: Readonly<Record<string, string>> = {\n 'expo-application': 'ExpoApplication',\n 'expo-asset': 'ExpoAsset',\n 'expo-audio': 'ExpoAudio',\n 'expo-background-task': 'ExpoBackgroundTask',\n 'expo-battery': 'ExpoBattery',\n 'expo-brightness': 'ExpoBrightness',\n 'expo-clipboard': 'ExpoClipboard',\n 'expo-crypto': 'ExpoCrypto',\n 'expo-device': 'ExpoDevice',\n 'expo-document-picker': 'ExpoDocumentPicker',\n 'expo-file-system': 'FileSystem',\n 'expo-foldables': 'ExpoFoldables',\n 'expo-font': 'ExpoFontLoader',\n 'expo-image-manipulator': 'ExpoImageManipulator',\n 'expo-image-picker': 'ExponentImagePicker',\n 'expo-keep-awake': 'ExpoKeepAwake',\n 'expo-local-authentication': 'ExpoLocalAuthentication',\n 'expo-local-llm': 'ExpoLocalLlm',\n 'expo-localization': 'ExpoLocalization',\n 'expo-location': 'ExpoLocation',\n 'expo-media-library': 'ExpoMediaLibrary',\n 'expo-network': 'ExpoNetwork',\n 'expo-notifications': 'ExpoNotificationPresenter',\n 'expo-screen-capture': 'ExpoScreenCapture',\n 'expo-screen-orientation': 'ExpoScreenOrientation',\n 'expo-secure-store': 'ExpoSecureStore',\n 'expo-sensors': 'ExponentAccelerometer',\n 'expo-sqlite': 'ExpoSQLite',\n 'expo-store-review': 'ExpoStoreReview',\n 'expo-tracking-transparency': 'ExpoTrackingTransparency',\n 'expo-updates': 'ExpoUpdates',\n 'expo-video': 'ExpoVideo',\n 'expo-web-browser': 'ExpoWebBrowser',\n};\n\n/**\n * Whether `module`'s native half is missing from the build on `platform`, asked of Expo before its\n * JavaScript is evaluated. That JavaScript throws while it is being evaluated when its native module\n * is not there, and Metro reports that as fatal, before any `catch` here, whenever it is not inside\n * another module's load: in a service's factory, say. Not on the web, where a package registers its\n * module only once it is evaluated.\n */\nfunction nativeHalfMissing(module: string, platform: ModulePlatform | null): boolean {\n const name = NATIVE_MODULES[module];\n if (!name || platform === null || platform === 'web') return false;\n const core = optional(() => require('expo-modules-core') as typeof import('expo-modules-core'));\n return core !== null && !core.requireOptionalNativeModule(name);\n}\n\n/**\n * The Expo module a service needs.\n *\n * Where the module exists for the platform the app is on, a module that is missing is a mistake\n * the developer can fix, so this throws a `MissingModuleError` that says how - rather than leave a\n * feature that silently does nothing. Anywhere it cannot exist - an iOS-only module on Android, the\n * web, and Node, where a test runs with no platform at all - this answers null, and the service\n * goes inert, as code shared across platforms needs it to.\n *\n * `load` answering null or undefined counts as missing too: `requireOptionalNativeModule` answers\n * null rather than throwing. So does a package whose native half is not in the build, which `load`\n * is not called for: see `nativeHalfMissing`.\n */\nexport function expoModule<T>(\n module: string,\n load: () => T | null | undefined,\n platforms: readonly ModulePlatform[] = ['ios', 'android'],\n): T | null {\n const platform = currentPlatform();\n let failure: unknown;\n try {\n const loaded = nativeHalfMissing(module, platform) ? null : load();\n if (loaded != null) return loaded;\n } catch (error) {\n failure = error;\n }\n if (platform === null || !platforms.includes(platform)) return null;\n throw new MissingModuleError(module, platform, failure);\n}\n"]}
1
+ {"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reaching an Expo module without importing one.\n *\n * A static `import ... from 'expo-battery'` makes the file unloadable by Node, which is what used\n * to force every service into two files: the behaviour somewhere the test suite could reach, and\n * the binding somewhere it could not. A `require` inside a function has neither problem. Metro\n * still resolves the string literal at build time, so the module is bundled exactly as before and\n * an app that has not installed it still gets a build error rather than a surprise at runtime;\n * Node never evaluates the call at all, because nothing in a test asks for the real platform.\n *\n * The `catch` is doing two jobs, and only one of them is Node. In an ESM test `require` is not\n * defined, so calling it throws `ReferenceError` and the service goes inert - the same state as\n * a device that has no such sensor. On a device the throw is a module that is present but not\n * linked, which is the same answer for the same reason.\n */\nexport function optional<T>(load: () => T): T | null {\n try {\n return load();\n } catch {\n return null;\n }\n}\n\n/** The platforms an Expo module is written for. */\nexport type ModulePlatform = 'ios' | 'android' | 'web';\n\n/**\n * A module the app needs and does not have: the package is not installed, or it is and the app was\n * not rebuilt since, so its native half is not in the binary. Development builds and Expo Go carry\n * only the native modules they were built with.\n */\nexport class MissingModuleError extends Error {\n override readonly name = 'MissingModuleError';\n /** The package, as it is installed: `expo-haptics`. */\n readonly module: string;\n\n constructor(module: string, platform: ModulePlatform, cause?: unknown, message?: string) {\n super(\n message ??\n (platform === 'web'\n ? `${module} is not installed. Install it with \"npx expo install ${module}\", then restart the dev server.`\n : `${module} is not in this build of the app. Install it with \"npx expo install ${module}\", ` +\n `then rebuild the app (\"npx expo run:${platform}\", or a new EAS build): a development ` +\n `build, and Expo Go, contain only the native modules they were built with.`),\n { cause },\n );\n this.module = module;\n }\n}\n\n/**\n * The `MissingModuleError` to throw where `expoModule` answered null: the module is installed or not, but there is\n * nothing to load it on. In Node, where a test runs, that is every native module, and \"install it\"\n * would be advice to do what is already done; `inTest` says what a test does instead. On a platform\n * the module does not support, it says that.\n */\nexport function unavailable(module: string, inTest: string): MissingModuleError {\n const platform = currentPlatform();\n const message =\n platform === null\n ? `[angular-native] ${module} has no native module to load in Node, where a test runs. ${inTest}`\n : `[angular-native] ${module} is not available on ${platform}.`;\n // The platform is only read for the default message, which this replaces.\n return new MissingModuleError(module, platform ?? 'web', undefined, message);\n}\n\n/** The platform the app is running on, or null in Node, where there is none to ask. */\nexport function currentPlatform(): ModulePlatform | null {\n return optional(\n () => (require('react-native') as { Platform: { OS: string } }).Platform.OS as ModulePlatform,\n );\n}\n\n/**\n * For each Expo package whose JavaScript requires a native module as it is evaluated, and so\n * throws when its native half is not in the build, one of those native modules. Any one of them\n * says whether the package's native half is there, and each of these is registered on both iOS and\n * Android.\n */\nconst NATIVE_MODULES: Readonly<Record<string, string>> = {\n 'expo-application': 'ExpoApplication',\n 'expo-asset': 'ExpoAsset',\n 'expo-audio': 'ExpoAudio',\n 'expo-background-task': 'ExpoBackgroundTask',\n 'expo-battery': 'ExpoBattery',\n 'expo-brightness': 'ExpoBrightness',\n 'expo-clipboard': 'ExpoClipboard',\n 'expo-crypto': 'ExpoCrypto',\n 'expo-device': 'ExpoDevice',\n 'expo-document-picker': 'ExpoDocumentPicker',\n 'expo-file-system': 'FileSystem',\n 'expo-foldables': 'ExpoFoldables',\n 'expo-font': 'ExpoFontLoader',\n 'expo-image-manipulator': 'ExpoImageManipulator',\n 'expo-image-picker': 'ExponentImagePicker',\n 'expo-keep-awake': 'ExpoKeepAwake',\n 'expo-local-authentication': 'ExpoLocalAuthentication',\n 'expo-local-llm': 'ExpoLocalLlm',\n 'expo-localization': 'ExpoLocalization',\n 'expo-location': 'ExpoLocation',\n 'expo-media-library': 'ExpoMediaLibrary',\n 'expo-network': 'ExpoNetwork',\n 'expo-notifications': 'ExpoNotificationPresenter',\n 'expo-screen-capture': 'ExpoScreenCapture',\n 'expo-screen-orientation': 'ExpoScreenOrientation',\n 'expo-secure-store': 'ExpoSecureStore',\n 'expo-sensors': 'ExponentAccelerometer',\n 'expo-sqlite': 'ExpoSQLite',\n 'expo-store-review': 'ExpoStoreReview',\n 'expo-tracking-transparency': 'ExpoTrackingTransparency',\n 'expo-updates': 'ExpoUpdates',\n 'expo-video': 'ExpoVideo',\n 'expo-web-browser': 'ExpoWebBrowser',\n};\n\n/**\n * Whether `module`'s native half is missing from the build on `platform`, asked of Expo before its\n * JavaScript is evaluated. That JavaScript throws while it is being evaluated when its native module\n * is not there, and Metro reports that as fatal, before any `catch` here, whenever it is not inside\n * another module's load: in a service's factory, say. Not on the web, where a package registers its\n * module only once it is evaluated.\n */\nfunction nativeHalfMissing(module: string, platform: ModulePlatform | null): boolean {\n const name = NATIVE_MODULES[module];\n if (!name || platform === null || platform === 'web') return false;\n const core = optional(() => require('expo-modules-core') as typeof import('expo-modules-core'));\n return core !== null && !core.requireOptionalNativeModule(name);\n}\n\n/**\n * The Expo module a service needs.\n *\n * Where the module exists for the platform the app is on, a module that is missing is a mistake\n * the developer can fix, so this throws a `MissingModuleError` that says how - rather than leave a\n * feature that silently does nothing. Anywhere it cannot exist - an iOS-only module on Android, the\n * web, and Node, where a test runs with no platform at all - this answers null, and the service\n * goes inert, as code shared across platforms needs it to.\n *\n * `load` answering null or undefined counts as missing too: `requireOptionalNativeModule` answers\n * null rather than throwing. So does a package whose native half is not in the build, which `load`\n * is not called for: see `nativeHalfMissing`.\n */\nexport function expoModule<T>(\n module: string,\n load: () => T | null | undefined,\n platforms: readonly ModulePlatform[] = ['ios', 'android'],\n): T | null {\n const platform = currentPlatform();\n let failure: unknown;\n try {\n const loaded = nativeHalfMissing(module, platform) ? null : load();\n if (loaded != null) return loaded;\n } catch (error) {\n failure = error;\n }\n if (platform === null || !platforms.includes(platform)) return null;\n throw new MissingModuleError(module, platform, failure);\n}\n"]}
@@ -20,6 +20,8 @@ export interface ExpoViewOptions {
20
20
  * source to an array and a `contentFit` to a string native understands.
21
21
  */
22
22
  readonly defaultProps?: Record<string, unknown>;
23
+ /** Whether a component of the app's own with this selector is a plain view. */
24
+ readonly yieldsToComponents?: boolean;
23
25
  /**
24
26
  * The prop the view reads its text from, where it takes no child views, as `@expo/ui`'s `Text`
25
27
  * reads `text`. The text written inside the element becomes that prop. See `ViewNameOptions`.
@@ -44,6 +44,7 @@ export function expoViewName(moduleName, viewName) {
44
44
  export function registerExpoView(elementName, moduleName, options) {
45
45
  registerViewName(elementName, expoViewName(moduleName, options?.viewName), options?.defaultProps, {
46
46
  textContent: options?.textContent,
47
+ yieldsToComponents: options?.yieldsToComponents,
47
48
  });
48
49
  optional(() => require('expo').requireNativeView(moduleName, options?.viewName));
49
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"register-expo-view.js","sourceRoot":"","sources":["../src/register-expo-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Expo's native views, driven directly.\n *\n * An Expo module that renders something registers a Fabric component whose name it derives\n * rather than declares, and `requireNativeViewManager` is the only place that derivation is\n * written down. Everything else that function does is React: it caches a host component, builds\n * a view config for React's renderer, and wraps the result in a class whose whole job is to hold\n * a ref. None of that is reachable without React, and none of it is needed - the engine writes\n * props onto the shadow node itself, and Expo's Fabric views take their props as an untyped map,\n * so there is no codegen'd prop list to satisfy.\n *\n * What is left is the name. That is what this package is.\n *\n * No decorators and no Angular here on purpose: this is a registration, and the components it\n * registers are ordinary elements in a template.\n */\nimport { registerViewName } from '@ng-native/fabric';\nimport { optional } from './native.ts';\n\n/**\n * What Expo installs on the global when the app starts. Only the app identifier matters here;\n * `getViewConfig` is for React's renderer, which never runs.\n */\ninterface ExpoGlobal {\n readonly __expo_app_identifier__?: string;\n}\n\n/**\n * The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes\n * it in `expo-modules-core`.\n *\n * The app identifier is the part that cannot be hardcoded. Expo Go runs many projects in one\n * binary, so it namespaces every view name with a per-app suffix; a standalone build has no\n * identifier and no suffix. Get it wrong and the view commits as `UnimplementedNativeView`\n * with no error, which is the same failure mode as a misspelled name.\n *\n * A module with more than one view names the extra ones (`viewName`); the default view is the\n * module itself.\n */\nexport function expoViewName(moduleName: string, viewName?: string): string {\n const identifier = (globalThis as { expo?: ExpoGlobal }).expo?.__expo_app_identifier__ ?? '';\n const suffix = identifier ? `_${identifier}` : '';\n const module = viewName ? `${moduleName}_${viewName}` : moduleName;\n return `ViewManagerAdapter_${module}${suffix}`;\n}\n\nexport interface ExpoViewOptions {\n /** A named view on a module that has several. Omit for the module's default view. */\n readonly viewName?: string;\n /**\n * Props the module's React component would have applied before native saw them. Expo's\n * wrappers do less of this than React Native's, but they do some: `expo-image` resolves a\n * source to an array and a `contentFit` to a string native understands.\n */\n readonly defaultProps?: Record<string, unknown>;\n /**\n * The prop the view reads its text from, where it takes no child views, as `@expo/ui`'s `Text`\n * reads `text`. The text written inside the element becomes that prop. See `ViewNameOptions`.\n */\n readonly textContent?: string;\n}\n\n/**\n * Teach the engine an element name for an Expo module's view, e.g.\n * `registerExpoView('expo-image', 'ExpoImage')` makes `<expo-image>` commit as one.\n *\n * Call it at startup, before the first commit that uses the element. The app must have the\n * module installed so the native side registers the component; this is the JavaScript half only.\n */\nexport function registerExpoView(\n elementName: string,\n moduleName: string,\n options?: ExpoViewOptions,\n): void {\n registerViewName(\n elementName,\n expoViewName(moduleName, options?.viewName),\n options?.defaultProps,\n {\n textContent: options?.textContent,\n },\n );\n optional(() =>\n (\n require('expo') as { requireNativeView(module: string, view?: string): unknown }\n ).requireNativeView(moduleName, options?.viewName),\n );\n}\n\n/**\n * The views worth knowing the names of, so an app does not have to look each one up.\n *\n * Deliberately names and defaults, not components. An Angular component per Expo view, prop for\n * prop, is a maintenance burden with no end and a second place for every prop to be wrong; the\n * element plus the module's own documentation covers the common case, and an app that wants a\n * nicer surface writes the one component it actually needs.\n *\n * A module still has to be installed for its element to commit as anything: this is the\n * JavaScript half. An element whose native side is missing commits as `UnimplementedNativeView`.\n */\nexport const EXPO_VIEWS: Readonly<Record<string, [string, ExpoViewOptions?]>> = {\n /** `expo-image`. Takes `source` as a list, so a single `{uri}` is wrapped by the caller. */\n 'expo-image': ['ExpoImage', { defaultProps: { contentFit: 'cover' } }],\n /** `expo-blur`. The one thing CSS cannot express: a blur of what is *behind* a view. */\n 'expo-blur': ['ExpoBlurView', { defaultProps: { intensity: 50, tint: 'default' } }],\n /** `expo-video`. The player itself is a shared object the module hands out; this is its view. */\n 'expo-video': ['ExpoVideo', { viewName: 'VideoView' }],\n /**\n * `expo-camera`. Its module's first view, so its default: `ExpoCamera_CameraView` exists only on\n * iOS, after the Swift class, and commits as nothing on Android. `Camera` takes its pictures.\n */\n 'expo-camera': ['ExpoCamera'],\n /** `expo-apple-authentication`: Apple's Sign in with Apple button. iOS only. */\n 'apple-sign-in-button': ['ExpoAppleAuthentication'],\n /** `expo-symbols`: SF Symbols, iOS only, and the reason `icons` is not needed for them. */\n 'expo-symbol': ['SymbolModule', { defaultProps: { type: 'monochrome' } }],\n /** `expo-gl`, whose context is reached through an event rather than a prop. */\n 'expo-gl': ['ExpoGL'],\n /** `expo-glass-effect`: the iOS 26 material. Renders as a plain view where it is unavailable. */\n 'expo-glass': ['ExpoGlassEffect', { viewName: 'GlassView' }],\n /** Glass views that merge into one another when they come within `spacing` points. */\n 'expo-glass-container': ['ExpoGlassEffect', { viewName: 'GlassContainer' }],\n /** `expo-mesh-gradient`: the one gradient CSS cannot express, because it has no CSS spelling. */\n 'expo-mesh-gradient': ['ExpoMeshGradient', { viewName: 'MeshGradientView' }],\n /** `expo-live-photo`, iOS only. */\n 'expo-live-photo': ['ExpoLivePhoto', { viewName: 'LivePhotoView' }],\n /**\n * `expo-maps`, Google's on Android and Apple's on iOS: two modules, each with one view, so each\n * its module's default. `ExpoMaps` itself is the permissions module and has no view.\n * `MapView` (`<expo-map>`) types both as one element.\n */\n 'expo-maps-google': ['ExpoGoogleMaps'],\n 'expo-maps-apple': ['ExpoAppleMaps'],\n};\n\n/**\n * Register several of the known views by element name.\n *\n * ```ts\n * registerExpoViews('expo-image', 'expo-blur');\n * ```\n *\n * Named one at a time rather than all at once on purpose: registering a view for a module the app\n * has not installed produces an element that commits as nothing, and finding out at startup which\n * modules are present is not something JavaScript can do without importing them all.\n */\nexport function registerExpoViews(...elements: readonly (keyof typeof EXPO_VIEWS)[]): void {\n for (const element of elements) {\n const known = EXPO_VIEWS[element];\n if (!known) throw new Error(`[angular-native] no Expo view is known as '${element}'`);\n registerExpoView(element, known[0], known[1]);\n }\n}\n"]}
1
+ {"version":3,"file":"register-expo-view.js","sourceRoot":"","sources":["../src/register-expo-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Expo's native views, driven directly.\n *\n * An Expo module that renders something registers a Fabric component whose name it derives\n * rather than declares, and `requireNativeViewManager` is the only place that derivation is\n * written down. Everything else that function does is React: it caches a host component, builds\n * a view config for React's renderer, and wraps the result in a class whose whole job is to hold\n * a ref. None of that is reachable without React, and none of it is needed - the engine writes\n * props onto the shadow node itself, and Expo's Fabric views take their props as an untyped map,\n * so there is no codegen'd prop list to satisfy.\n *\n * What is left is the name. That is what this package is.\n *\n * No decorators and no Angular here on purpose: this is a registration, and the components it\n * registers are ordinary elements in a template.\n */\nimport { registerViewName } from '@ng-native/fabric';\nimport { optional } from './native.ts';\n\n/**\n * What Expo installs on the global when the app starts. Only the app identifier matters here;\n * `getViewConfig` is for React's renderer, which never runs.\n */\ninterface ExpoGlobal {\n readonly __expo_app_identifier__?: string;\n}\n\n/**\n * The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes\n * it in `expo-modules-core`.\n *\n * The app identifier is the part that cannot be hardcoded. Expo Go runs many projects in one\n * binary, so it namespaces every view name with a per-app suffix; a standalone build has no\n * identifier and no suffix. Get it wrong and the view commits as `UnimplementedNativeView`\n * with no error, which is the same failure mode as a misspelled name.\n *\n * A module with more than one view names the extra ones (`viewName`); the default view is the\n * module itself.\n */\nexport function expoViewName(moduleName: string, viewName?: string): string {\n const identifier = (globalThis as { expo?: ExpoGlobal }).expo?.__expo_app_identifier__ ?? '';\n const suffix = identifier ? `_${identifier}` : '';\n const module = viewName ? `${moduleName}_${viewName}` : moduleName;\n return `ViewManagerAdapter_${module}${suffix}`;\n}\n\nexport interface ExpoViewOptions {\n /** A named view on a module that has several. Omit for the module's default view. */\n readonly viewName?: string;\n /**\n * Props the module's React component would have applied before native saw them. Expo's\n * wrappers do less of this than React Native's, but they do some: `expo-image` resolves a\n * source to an array and a `contentFit` to a string native understands.\n */\n readonly defaultProps?: Record<string, unknown>;\n /** Whether a component of the app's own with this selector is a plain view. */\n readonly yieldsToComponents?: boolean;\n /**\n * The prop the view reads its text from, where it takes no child views, as `@expo/ui`'s `Text`\n * reads `text`. The text written inside the element becomes that prop. See `ViewNameOptions`.\n */\n readonly textContent?: string;\n}\n\n/**\n * Teach the engine an element name for an Expo module's view, e.g.\n * `registerExpoView('expo-image', 'ExpoImage')` makes `<expo-image>` commit as one.\n *\n * Call it at startup, before the first commit that uses the element. The app must have the\n * module installed so the native side registers the component; this is the JavaScript half only.\n */\nexport function registerExpoView(\n elementName: string,\n moduleName: string,\n options?: ExpoViewOptions,\n): void {\n registerViewName(\n elementName,\n expoViewName(moduleName, options?.viewName),\n options?.defaultProps,\n {\n textContent: options?.textContent,\n yieldsToComponents: options?.yieldsToComponents,\n },\n );\n optional(() =>\n (\n require('expo') as { requireNativeView(module: string, view?: string): unknown }\n ).requireNativeView(moduleName, options?.viewName),\n );\n}\n\n/**\n * The views worth knowing the names of, so an app does not have to look each one up.\n *\n * Deliberately names and defaults, not components. An Angular component per Expo view, prop for\n * prop, is a maintenance burden with no end and a second place for every prop to be wrong; the\n * element plus the module's own documentation covers the common case, and an app that wants a\n * nicer surface writes the one component it actually needs.\n *\n * A module still has to be installed for its element to commit as anything: this is the\n * JavaScript half. An element whose native side is missing commits as `UnimplementedNativeView`.\n */\nexport const EXPO_VIEWS: Readonly<Record<string, [string, ExpoViewOptions?]>> = {\n /** `expo-image`. Takes `source` as a list, so a single `{uri}` is wrapped by the caller. */\n 'expo-image': ['ExpoImage', { defaultProps: { contentFit: 'cover' } }],\n /** `expo-blur`. The one thing CSS cannot express: a blur of what is *behind* a view. */\n 'expo-blur': ['ExpoBlurView', { defaultProps: { intensity: 50, tint: 'default' } }],\n /** `expo-video`. The player itself is a shared object the module hands out; this is its view. */\n 'expo-video': ['ExpoVideo', { viewName: 'VideoView' }],\n /**\n * `expo-camera`. Its module's first view, so its default: `ExpoCamera_CameraView` exists only on\n * iOS, after the Swift class, and commits as nothing on Android. `Camera` takes its pictures.\n */\n 'expo-camera': ['ExpoCamera'],\n /** `expo-apple-authentication`: Apple's Sign in with Apple button. iOS only. */\n 'apple-sign-in-button': ['ExpoAppleAuthentication'],\n /** `expo-symbols`: SF Symbols, iOS only, and the reason `icons` is not needed for them. */\n 'expo-symbol': ['SymbolModule', { defaultProps: { type: 'monochrome' } }],\n /** `expo-gl`, whose context is reached through an event rather than a prop. */\n 'expo-gl': ['ExpoGL'],\n /** `expo-glass-effect`: the iOS 26 material. Renders as a plain view where it is unavailable. */\n 'expo-glass': ['ExpoGlassEffect', { viewName: 'GlassView' }],\n /** Glass views that merge into one another when they come within `spacing` points. */\n 'expo-glass-container': ['ExpoGlassEffect', { viewName: 'GlassContainer' }],\n /** `expo-mesh-gradient`: the one gradient CSS cannot express, because it has no CSS spelling. */\n 'expo-mesh-gradient': ['ExpoMeshGradient', { viewName: 'MeshGradientView' }],\n /** `expo-live-photo`, iOS only. */\n 'expo-live-photo': ['ExpoLivePhoto', { viewName: 'LivePhotoView' }],\n /**\n * `expo-maps`, Google's on Android and Apple's on iOS: two modules, each with one view, so each\n * its module's default. `ExpoMaps` itself is the permissions module and has no view.\n * `MapView` (`<expo-map>`) types both as one element.\n */\n 'expo-maps-google': ['ExpoGoogleMaps'],\n 'expo-maps-apple': ['ExpoAppleMaps'],\n};\n\n/**\n * Register several of the known views by element name.\n *\n * ```ts\n * registerExpoViews('expo-image', 'expo-blur');\n * ```\n *\n * Named one at a time rather than all at once on purpose: registering a view for a module the app\n * has not installed produces an element that commits as nothing, and finding out at startup which\n * modules are present is not something JavaScript can do without importing them all.\n */\nexport function registerExpoViews(...elements: readonly (keyof typeof EXPO_VIEWS)[]): void {\n for (const element of elements) {\n const known = EXPO_VIEWS[element];\n if (!known) throw new Error(`[angular-native] no Expo view is known as '${element}'`);\n registerExpoView(element, known[0], known[1]);\n }\n}\n"]}
package/dist/video.js CHANGED
@@ -8,13 +8,13 @@
8
8
  * // {{ player.state().currentTime }}
9
9
  * ```
10
10
  */
11
- import { expoModule } from "./native.js";
11
+ import { expoModule, unavailable } from "./native.js";
12
12
  import { ownPlayer, watchPlayer } from "./player.js";
13
13
  /** A video player owned by the current component, released when it is destroyed. */
14
14
  export function videoPlayer(source, options = {}) {
15
15
  const expo = expoModule('expo-video', () => require('expo-video'), ['ios', 'android', 'web']);
16
16
  if (!expo)
17
- throw new Error('[angular-native] expo-video is not installed');
17
+ throw unavailable('expo-video', 'Stand in for the player in the test.');
18
18
  const native = expo.createVideoPlayer(source);
19
19
  return ownPlayer(native, () => watchPlayer(native, options));
20
20
  }
package/dist/video.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;AAElE,oFAAoF;AACpF,MAAM,UAAU,WAAW,CACzB,MAAwC,EACxC,UAAmC,EAAE;IAErC,MAAM,IAAI,GAAG,UAAU,CACrB,YAAY,EACZ,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,CAAgC,EAC1D,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAC1B,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * `videoPlayer`, an `expo-video` player owned by the component that makes it.\n *\n * ```ts\n * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // <expo-video [player]=\"player.native\" />\n * // {{ player.state().currentTime }}\n * ```\n */\nimport { expoModule } from './native.ts';\nimport { ownPlayer, watchPlayer, type Player } from './player.ts';\n\n/** A video player owned by the current component, released when it is destroyed. */\nexport function videoPlayer(\n source: import('expo-video').VideoSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-video').VideoPlayer> {\n const expo = expoModule(\n 'expo-video',\n () => require('expo-video') as typeof import('expo-video'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw new Error('[angular-native] expo-video is not installed');\n const native = expo.createVideoPlayer(source);\n return ownPlayer(native, () => watchPlayer(native, options));\n}\n"]}
1
+ {"version":3,"file":"video.js","sourceRoot":"","sources":["../src/video.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;AAElE,oFAAoF;AACpF,MAAM,UAAU,WAAW,CACzB,MAAwC,EACxC,UAAmC,EAAE;IAErC,MAAM,IAAI,GAAG,UAAU,CACrB,YAAY,EACZ,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,CAAgC,EAC1D,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAC1B,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,MAAM,WAAW,CAAC,YAAY,EAAE,sCAAsC,CAAC,CAAC;IACnF,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * `videoPlayer`, an `expo-video` player owned by the component that makes it.\n *\n * ```ts\n * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // <expo-video [player]=\"player.native\" />\n * // {{ player.state().currentTime }}\n * ```\n */\nimport { expoModule, unavailable } from './native.ts';\nimport { ownPlayer, watchPlayer, type Player } from './player.ts';\n\n/** A video player owned by the current component, released when it is destroyed. */\nexport function videoPlayer(\n source: import('expo-video').VideoSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-video').VideoPlayer> {\n const expo = expoModule(\n 'expo-video',\n () => require('expo-video') as typeof import('expo-video'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw unavailable('expo-video', 'Stand in for the player in the test.');\n const native = expo.createVideoPlayer(source);\n return ownPlayer(native, () => watchPlayer(native, options));\n}\n"]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `widget`, which keeps a home screen widget from `expo-widgets` in step with a signal, and hands the
3
+ * app the taps on its buttons.
4
+ *
5
+ * A widget button runs in the widget extension, not in the app, which iOS keeps suspended, so a
6
+ * tap's own event is lost unless the app is running. The widget's layout records each tap in its
7
+ * own props instead, as `taps`, and `widget` collects them when the app next runs: at once while it
8
+ * is running, and when it comes back to the foreground. It takes the widget `createWidget` makes in
9
+ * the app's layout file, so this package imports `expo-widgets` only for its tap events.
10
+ */
11
+ import { InjectionToken, type Signal } from '@angular/core';
12
+ /** One entry of a widget's timeline, as `expo-widgets` stores it. */
13
+ export interface WidgetEntry<T extends object> {
14
+ readonly date: Date;
15
+ /** The entry's props, with the targets of the buttons tapped since the app last collected them. */
16
+ readonly props: T & {
17
+ readonly taps?: readonly string[];
18
+ };
19
+ }
20
+ /** What `createWidget` answers. */
21
+ export interface NativeWidget<T extends object> {
22
+ updateSnapshot(props: T): void;
23
+ getTimeline(): Promise<readonly WidgetEntry<T>[]>;
24
+ reload(): void;
25
+ }
26
+ /** When to collect taps, and when to ask iOS to redraw the widget. */
27
+ export interface WidgetEvents {
28
+ /** A widget button was tapped while the app was running. */
29
+ onTap(listener: () => void): () => void;
30
+ onForeground(listener: () => void): () => void;
31
+ onBackground(listener: () => void): () => void;
32
+ }
33
+ export interface WidgetOptions {
34
+ /**
35
+ * Called with the targets of the buttons tapped since the last call, oldest first, once the write
36
+ * that clears them from the widget has worked, so a tap is handed over once. A handler that throws
37
+ * goes to the `ErrorHandler`, and its taps are still cleared.
38
+ */
39
+ readonly onTaps?: (taps: readonly string[]) => void;
40
+ }
41
+ /** A home screen widget kept in step with a signal. */
42
+ export interface WidgetRef {
43
+ /**
44
+ * Why the last sync did not happen: the taps could not be read, or the widget not written. It also
45
+ * goes to the `ErrorHandler`, and is null again once a sync works. A failing `onTaps` is not kept
46
+ * here.
47
+ */
48
+ readonly error: Signal<unknown>;
49
+ /** Collects the taps now, then shows the signal's value. */
50
+ sync(): Promise<void>;
51
+ /** Asks iOS to redraw the widget. */
52
+ reload(): void;
53
+ }
54
+ /** Overridden in a test to tap a widget that is not there. */
55
+ export declare const WIDGET_EVENTS: InjectionToken<WidgetEvents>;
56
+ /**
57
+ * Shows `props` on the widget and follows the signal, and hands taps on its buttons to `onTaps`.
58
+ * A write replaces the props that hold the taps, so every write comes after the taps are read, and
59
+ * nothing is written while they cannot be; a tap is handed over once the write that clears it has
60
+ * worked. Going to the background asks iOS to redraw the widget, which it otherwise does on its own
61
+ * schedule. Destroying the injector stops both. Call it in an injection context.
62
+ */
63
+ export declare function widget<T extends object>(native: NativeWidget<T>, props: Signal<T>, options?: WidgetOptions): WidgetRef;