@ng-native/expo 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -0
- package/android/src/main/java/dev/ngnative/expo/SearchFieldModule.kt +111 -0
- package/dist/app-info.d.ts +18 -15
- package/dist/app-info.js +17 -5
- package/dist/app-info.js.map +1 -1
- package/dist/apple-sign-in.js +5 -4
- package/dist/apple-sign-in.js.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/assets.js.map +1 -1
- package/dist/async-storage.js +4 -1
- package/dist/async-storage.js.map +1 -1
- package/dist/audio.js +2 -2
- package/dist/audio.js.map +1 -1
- package/dist/battery.js +4 -1
- package/dist/battery.js.map +1 -1
- package/dist/biometrics.js +5 -4
- package/dist/biometrics.js.map +1 -1
- package/dist/brightness.js +7 -2
- package/dist/brightness.js.map +1 -1
- package/dist/camera.js +4 -1
- package/dist/camera.js.map +1 -1
- package/dist/clipboard.js +5 -2
- package/dist/clipboard.js.map +1 -1
- package/dist/database.js +2 -2
- package/dist/database.js.map +1 -1
- package/dist/expo-ui-components.d.ts +101 -9
- package/dist/expo-ui-components.js +305 -43
- package/dist/expo-ui-components.js.map +1 -1
- package/dist/expo-ui.js +7 -1
- package/dist/expo-ui.js.map +1 -1
- package/dist/foldable.d.ts +2 -0
- package/dist/foldable.js +15 -5
- package/dist/foldable.js.map +1 -1
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +12 -14
- package/dist/fonts.js.map +1 -1
- package/dist/haptics.js +2 -2
- package/dist/haptics.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/language-model.js +7 -3
- package/dist/language-model.js.map +1 -1
- package/dist/native.d.ts +17 -2
- package/dist/native.js +61 -3
- package/dist/native.js.map +1 -1
- package/dist/network.js +4 -1
- package/dist/network.js.map +1 -1
- package/dist/notifications.js +35 -8
- package/dist/notifications.js.map +1 -1
- package/dist/observed.js +5 -1
- package/dist/observed.js.map +1 -1
- package/dist/orientation.js +4 -1
- package/dist/orientation.js.map +1 -1
- package/dist/register-expo-view.d.ts +19 -0
- package/dist/register-expo-view.js +1 -0
- package/dist/register-expo-view.js.map +1 -1
- package/dist/screen-capture.js +2 -2
- package/dist/screen-capture.js.map +1 -1
- package/dist/search-field.d.ts +83 -0
- package/dist/search-field.js +147 -0
- package/dist/search-field.js.map +1 -0
- package/dist/secure-store.js +4 -1
- package/dist/secure-store.js.map +1 -1
- package/dist/sensors.js +6 -3
- package/dist/sensors.js.map +1 -1
- package/dist/splash-screen.js +2 -2
- package/dist/splash-screen.js.map +1 -1
- package/dist/store.js +4 -0
- package/dist/store.js.map +1 -1
- package/dist/tracking.js +2 -2
- package/dist/tracking.js.map +1 -1
- package/dist/ui-font-names.d.ts +16 -0
- package/dist/ui-font-names.js +71 -0
- package/dist/ui-font-names.js.map +1 -0
- package/dist/updates.js +4 -1
- package/dist/updates.js.map +1 -1
- package/dist/video.js +2 -2
- package/dist/video.js.map +1 -1
- package/dist/watch.js +6 -5
- package/dist/watch.js.map +1 -1
- package/expo-module.config.json +5 -2
- package/ios/ExpoUIModifiersModule.swift +25 -0
- package/ios/InlineNavigationTitle.swift +12 -0
- package/ios/NgNativeExpo.podspec +3 -0
- package/ios/SearchField.swift +130 -0
- package/ios/SegmentedFont.swift +113 -0
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-capture.js","sourceRoot":"","sources":["../src/screen-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"screen-capture.js","sourceRoot":"","sources":["../src/screen-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;;AAkB3D,MAAM,OAAO,aAAa;IACxB,4DAA4D;IAC5D,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,oCAAoC,EACpC;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAS,CAAC;KAClF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,GAAG,MAAM,CAAC,CAAC;8EAAC,CAAC;IAEnC;;;OAGG;IACM,UAAU,GAAe,UAAU,CAAC,EAAE,CAC7C,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,mBAAmB,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAC1E,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,uBAAuB,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAC/E,CAAC;IAEF,0EAA0E;IACjE,WAAW,GAAmB,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;IAE/D;QACE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC,GAAG,EAAE,CAChE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAChC,CAAC;QACF,IAAI,QAAQ;YAAE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,gDAAgD;IAChD,KAAK,CAAC,SAAS;QACb,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,IAAI,KAAK,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAC,GAAY;QACxB,MAAM,IAAI,CAAC,MAAM,EAAE,yBAAyB,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,KAAK,CAAC,GAAY;QACtB,MAAM,IAAI,CAAC,MAAM,EAAE,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CAAC,aAAsB;QAC7C,MAAM,IAAI,CAAC,MAAM,EAAE,gCAAgC,CAAC,aAAa,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,MAAM,IAAI,CAAC,MAAM,EAAE,iCAAiC,EAAE,CAAC;IACzD,CAAC;uGA7DU,aAAa;wGAAb,aAAa;;2FAAb,aAAa;kBADzB,OAAO","sourcesContent":["/**\n * `ScreenCapture`, bound to `expo-screen-capture`: keeping a screen out of screenshots and\n * recordings, and knowing when one was taken.\n *\n * ```ts\n * private readonly capture = inject(ScreenCapture);\n *\n * constructor() {\n * void this.capture.prevent('card-details');\n * inject(DestroyRef).onDestroy(() => void this.capture.allow('card-details'));\n * }\n * ```\n *\n * `prevent()` and `allow()` take a key, and capture stays prevented while any key is still held:\n * two screens that each prevent it do not undo each other. `screenshots` counts the screenshots\n * the user has taken, and the listener behind it lives as long as the app does and is removed\n * when the app is destroyed. Without the module installed, nothing is prevented and the count\n * stays at zero.\n */\nimport { DestroyRef, InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { expoModule, present } from './native.ts';\nimport { Permission, UNAVAILABLE } from './permissions.ts';\n\ntype Expo = typeof import('expo-screen-capture');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeScreenCapture = Pick<\n Expo,\n | 'isAvailableAsync'\n | 'preventScreenCaptureAsync'\n | 'allowScreenCaptureAsync'\n | 'enableAppSwitcherProtectionAsync'\n | 'disableAppSwitcherProtectionAsync'\n | 'addScreenshotListener'\n | 'getPermissionsAsync'\n | 'requestPermissionsAsync'\n>;\n\n@Service()\nexport class ScreenCapture {\n /** Overridden in a test to take screenshots nobody took. */\n static readonly SOURCE = new InjectionToken<NativeScreenCapture | null>(\n 'angular-native.screenCaptureSource',\n {\n factory: () =>\n expoModule('expo-screen-capture', () => require('expo-screen-capture') as Expo),\n },\n );\n\n private readonly native = inject(ScreenCapture.SOURCE);\n private readonly count = signal(0);\n\n /**\n * The permission screenshot detection needs on Android 13 and earlier, where it reads the\n * photo library. Later Android needs none, and iOS always answers granted.\n */\n readonly permission: Permission = Permission.of(\n () => this.native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** How many screenshots the user has taken while the app was in front. */\n readonly screenshots: Signal<number> = this.count.asReadonly();\n\n constructor() {\n const listener = present(this.native)?.addScreenshotListener(() =>\n this.count.update((n) => n + 1),\n );\n if (listener) inject(DestroyRef).onDestroy(() => listener.remove());\n }\n\n /** Whether the platform can prevent capture. */\n async available(): Promise<boolean> {\n return (await this.native?.isAvailableAsync()) ?? false;\n }\n\n /**\n * Keeps the app out of screenshots and screen recordings until `allow()` is called with the same\n * key, or the app restarts. Omitting the key uses one shared default key.\n */\n async prevent(key?: string): Promise<void> {\n await this.native?.preventScreenCaptureAsync(key);\n }\n\n /** Releases the key; capture is allowed again once no key is held. */\n async allow(key?: string): Promise<void> {\n await this.native?.allowScreenCaptureAsync(key);\n }\n\n /**\n * iOS: blurs the app while it is not in focus - in the app switcher, in the background, and\n * during interruptions - by `blurIntensity` from 0 to 1, half by default. Android already hides\n * it in the app switcher while capture is prevented.\n */\n async protectAppSwitcher(blurIntensity?: number): Promise<void> {\n await this.native?.enableAppSwitcherProtectionAsync(blurIntensity);\n }\n\n async unprotectAppSwitcher(): Promise<void> {\n await this.native?.disableAppSwitcherProtectionAsync();\n }\n}\n"]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<search-field>`: the platform's own search field, anywhere a view can go. `<native-search-bar>`
|
|
3
|
+
* from `@ng-native/router` is the one in a navigation bar; this is the one for everywhere else, a
|
|
4
|
+
* filter sheet, a page, a list's header:
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <ui-bottom-sheet [(open)]="filtering">
|
|
8
|
+
* <search-field placeholder="Search colours" [(value)]="query" />
|
|
9
|
+
* ...
|
|
10
|
+
* </ui-bottom-sheet>
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* It is `@ng-native/expo`'s own native view: UIKit's `UISearchBar` on iOS, with its Cancel button,
|
|
14
|
+
* dictation and clear button, and AndroidX's `SearchView` on Android, which has no Cancel button.
|
|
15
|
+
* The events are `<native-search-bar>`'s: `(search)` for the keyboard's search key with the text,
|
|
16
|
+
* `(cancel)`, `(searchFocus)` and `(searchBlur)`, and `focus()`, `blur()` and `clear()` from code.
|
|
17
|
+
* The text is a two-way `value` rather than the bar's `query`, because it is a Signal Forms control
|
|
18
|
+
* as `<text-input>` is: `[formField]` binds `value`, `disabled`, and `touch` on blur.
|
|
19
|
+
*
|
|
20
|
+
* Expo Go cannot load native code of its own, so there the element draws nothing, and in
|
|
21
|
+
* development it says so once.
|
|
22
|
+
*/
|
|
23
|
+
import { InjectionToken } from '@angular/core';
|
|
24
|
+
import * as i0 from "@angular/core";
|
|
25
|
+
/** The functions the native module defines on the view, called with the view's tag as `this`. */
|
|
26
|
+
export interface SearchFieldFunctions {
|
|
27
|
+
focus?(this: {
|
|
28
|
+
nativeTag: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
blur?(this: {
|
|
31
|
+
nativeTag: number;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare class SearchField {
|
|
35
|
+
/**
|
|
36
|
+
* The functions native defines on the view, or null where the build has no native search
|
|
37
|
+
* field. Overridden in a test.
|
|
38
|
+
*/
|
|
39
|
+
static readonly SOURCE: InjectionToken<SearchFieldFunctions | null>;
|
|
40
|
+
/**
|
|
41
|
+
* The text in the field: typing writes it, and text set from code is put in the field. A
|
|
42
|
+
* `model()`, which is all `FormValueControl<string>` asks, so `[formField]` binds it.
|
|
43
|
+
*/
|
|
44
|
+
readonly value: import("@angular/core").ModelSignal<string>;
|
|
45
|
+
/** Refuse input and show the field dimmed. Signal Forms' `disabled()` rule sets it. */
|
|
46
|
+
readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
|
|
47
|
+
readonly placeholder: import("@angular/core").InputSignal<string | undefined>;
|
|
48
|
+
/**
|
|
49
|
+
* iOS: whether the Cancel button shows. Left unset it shows while the field has the keyboard,
|
|
50
|
+
* as in a search bar under a navigation bar.
|
|
51
|
+
*/
|
|
52
|
+
readonly showsCancelButton: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
53
|
+
readonly autoCapitalize: import("@angular/core").InputSignal<"none" | "words" | "sentences" | "characters" | undefined>;
|
|
54
|
+
/** The colour of the cursor and the Cancel button. */
|
|
55
|
+
readonly tintColor: import("@angular/core").InputSignal<string | undefined>;
|
|
56
|
+
/** The keyboard's search key was pressed, with the text in the field. */
|
|
57
|
+
readonly search: import("@angular/core").OutputEmitterRef<string>;
|
|
58
|
+
/** iOS: the Cancel button was pressed. The field is emptied and gives up the keyboard. */
|
|
59
|
+
readonly cancel: import("@angular/core").OutputEmitterRef<void>;
|
|
60
|
+
readonly searchFocus: import("@angular/core").OutputEmitterRef<void>;
|
|
61
|
+
readonly searchBlur: import("@angular/core").OutputEmitterRef<void>;
|
|
62
|
+
/** The field gave up the keyboard: what Signal Forms marks a field touched from. */
|
|
63
|
+
readonly touch: import("@angular/core").OutputEmitterRef<void>;
|
|
64
|
+
/**
|
|
65
|
+
* The keystroke the text answers, as React Native's text input counts them: native skips a text
|
|
66
|
+
* older than what has been typed since, which a fast typist's echo otherwise overwrites.
|
|
67
|
+
*/
|
|
68
|
+
protected readonly eventCount: import("@angular/core").WritableSignal<number>;
|
|
69
|
+
private readonly functions;
|
|
70
|
+
protected readonly missing: boolean;
|
|
71
|
+
private readonly node;
|
|
72
|
+
private readonly engine;
|
|
73
|
+
constructor();
|
|
74
|
+
/** Give the field the keyboard. Does nothing before the view has reached the screen. */
|
|
75
|
+
focus(): Promise<void>;
|
|
76
|
+
/** Take the keyboard away from the field. */
|
|
77
|
+
blur(): Promise<void>;
|
|
78
|
+
/** Empty the field, and the value with it. */
|
|
79
|
+
clear(): void;
|
|
80
|
+
private call;
|
|
81
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SearchField, never>;
|
|
82
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SearchField, "search-field", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "showsCancelButton": { "alias": "showsCancelButton"; "required": false; "isSignal": true; }; "autoCapitalize": { "alias": "autoCapitalize"; "required": false; "isSignal": true; }; "tintColor": { "alias": "tintColor"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; "cancel": "cancel"; "searchFocus": "searchFocus"; "searchBlur": "searchBlur"; "touch": "touch"; }, never, never, true, never>;
|
|
83
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<search-field>`: the platform's own search field, anywhere a view can go. `<native-search-bar>`
|
|
3
|
+
* from `@ng-native/router` is the one in a navigation bar; this is the one for everywhere else, a
|
|
4
|
+
* filter sheet, a page, a list's header:
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <ui-bottom-sheet [(open)]="filtering">
|
|
8
|
+
* <search-field placeholder="Search colours" [(value)]="query" />
|
|
9
|
+
* ...
|
|
10
|
+
* </ui-bottom-sheet>
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* It is `@ng-native/expo`'s own native view: UIKit's `UISearchBar` on iOS, with its Cancel button,
|
|
14
|
+
* dictation and clear button, and AndroidX's `SearchView` on Android, which has no Cancel button.
|
|
15
|
+
* The events are `<native-search-bar>`'s: `(search)` for the keyboard's search key with the text,
|
|
16
|
+
* `(cancel)`, `(searchFocus)` and `(searchBlur)`, and `focus()`, `blur()` and `clear()` from code.
|
|
17
|
+
* The text is a two-way `value` rather than the bar's `query`, because it is a Signal Forms control
|
|
18
|
+
* as `<text-input>` is: `[formField]` binds `value`, `disabled`, and `touch` on blur.
|
|
19
|
+
*
|
|
20
|
+
* Expo Go cannot load native code of its own, so there the element draws nothing, and in
|
|
21
|
+
* development it says so once.
|
|
22
|
+
*/
|
|
23
|
+
import { Component, booleanAttribute, DestroyRef, ElementRef, InjectionToken, inject, input, model, output, signal, } from '@angular/core';
|
|
24
|
+
import { Engine, HostEngine, registerViewName, } from '@ng-native/fabric';
|
|
25
|
+
import { optional } from "./native.js";
|
|
26
|
+
import { expoViewName } from "./register-expo-view.js";
|
|
27
|
+
import { optionalBoolean } from "./transforms.js";
|
|
28
|
+
import * as i0 from "@angular/core";
|
|
29
|
+
const MODULE = 'NgNativeSearchField';
|
|
30
|
+
let warned = false;
|
|
31
|
+
/** Says once, in development, that the build has no native search field to draw. */
|
|
32
|
+
function warnMissing() {
|
|
33
|
+
if ((typeof ngDevMode !== 'undefined' && !ngDevMode) || warned)
|
|
34
|
+
return;
|
|
35
|
+
warned = true;
|
|
36
|
+
console.warn('[angular-native] <search-field> is native code of @ng-native/expo, which this build does not ' +
|
|
37
|
+
'have, so it draws nothing. Expo Go cannot load it: run a development build.');
|
|
38
|
+
}
|
|
39
|
+
export class SearchField {
|
|
40
|
+
/**
|
|
41
|
+
* The functions native defines on the view, or null where the build has no native search
|
|
42
|
+
* field. Overridden in a test.
|
|
43
|
+
*/
|
|
44
|
+
static SOURCE = new InjectionToken('angular-native.searchFieldSource', {
|
|
45
|
+
factory: () => optional(() => {
|
|
46
|
+
const core = require('expo-modules-core');
|
|
47
|
+
const module = core.requireOptionalNativeModule(MODULE);
|
|
48
|
+
return module ? (module.ViewPrototypes?.[MODULE] ?? {}) : null;
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* The text in the field: typing writes it, and text set from code is put in the field. A
|
|
53
|
+
* `model()`, which is all `FormValueControl<string>` asks, so `[formField]` binds it.
|
|
54
|
+
*/
|
|
55
|
+
value = model('', /* @ts-ignore */
|
|
56
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
57
|
+
/** Refuse input and show the field dimmed. Signal Forms' `disabled()` rule sets it. */
|
|
58
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
59
|
+
placeholder = input(/* @ts-ignore */
|
|
60
|
+
...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
61
|
+
/**
|
|
62
|
+
* iOS: whether the Cancel button shows. Left unset it shows while the field has the keyboard,
|
|
63
|
+
* as in a search bar under a navigation bar.
|
|
64
|
+
*/
|
|
65
|
+
showsCancelButton = input(undefined, { ...(ngDevMode ? { debugName: "showsCancelButton" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
66
|
+
autoCapitalize = input(/* @ts-ignore */
|
|
67
|
+
...(ngDevMode ? [undefined, { debugName: "autoCapitalize" }] : /* istanbul ignore next */ []));
|
|
68
|
+
/** The colour of the cursor and the Cancel button. */
|
|
69
|
+
tintColor = input(/* @ts-ignore */
|
|
70
|
+
...(ngDevMode ? [undefined, { debugName: "tintColor" }] : /* istanbul ignore next */ []));
|
|
71
|
+
/** The keyboard's search key was pressed, with the text in the field. */
|
|
72
|
+
search = output();
|
|
73
|
+
/** iOS: the Cancel button was pressed. The field is emptied and gives up the keyboard. */
|
|
74
|
+
cancel = output();
|
|
75
|
+
searchFocus = output();
|
|
76
|
+
searchBlur = output();
|
|
77
|
+
/** The field gave up the keyboard: what Signal Forms marks a field touched from. */
|
|
78
|
+
touch = output();
|
|
79
|
+
/**
|
|
80
|
+
* The keystroke the text answers, as React Native's text input counts them: native skips a text
|
|
81
|
+
* older than what has been typed since, which a fast typist's echo otherwise overwrites.
|
|
82
|
+
*/
|
|
83
|
+
eventCount = signal(0, /* @ts-ignore */
|
|
84
|
+
...(ngDevMode ? [{ debugName: "eventCount" }] : /* istanbul ignore next */ []));
|
|
85
|
+
functions = inject(SearchField.SOURCE);
|
|
86
|
+
missing = !this.functions;
|
|
87
|
+
node = inject(ElementRef).nativeElement;
|
|
88
|
+
engine = inject(Engine, { optional: true });
|
|
89
|
+
constructor() {
|
|
90
|
+
registerViewName('search-field', expoViewName(MODULE));
|
|
91
|
+
if (this.missing)
|
|
92
|
+
warnMissing();
|
|
93
|
+
const host = this.node.host ?? inject(HostEngine);
|
|
94
|
+
const listen = (event, handle) => host.setEventListener(this.node, event, (raw) => handle(raw?.nativeEvent ?? {}));
|
|
95
|
+
const stops = [
|
|
96
|
+
listen('topChangeText', ({ text, eventCount }) => {
|
|
97
|
+
if (typeof eventCount === 'number')
|
|
98
|
+
this.eventCount.set(eventCount);
|
|
99
|
+
this.value.set(String(text ?? ''));
|
|
100
|
+
}),
|
|
101
|
+
// Named apart from the outputs: a native event of an output's name reaches its listener too.
|
|
102
|
+
listen('topSubmitSearch', ({ text }) => this.search.emit(String(text ?? this.value()))),
|
|
103
|
+
listen('topPressCancel', () => this.cancel.emit()),
|
|
104
|
+
listen('topBeginEditing', () => this.searchFocus.emit()),
|
|
105
|
+
listen('topEndEditing', () => {
|
|
106
|
+
this.searchBlur.emit();
|
|
107
|
+
this.touch.emit();
|
|
108
|
+
}),
|
|
109
|
+
];
|
|
110
|
+
inject(DestroyRef).onDestroy(() => stops.forEach((stop) => stop()));
|
|
111
|
+
}
|
|
112
|
+
/** Give the field the keyboard. Does nothing before the view has reached the screen. */
|
|
113
|
+
async focus() {
|
|
114
|
+
await this.call('focus');
|
|
115
|
+
}
|
|
116
|
+
/** Take the keyboard away from the field. */
|
|
117
|
+
async blur() {
|
|
118
|
+
await this.call('blur');
|
|
119
|
+
}
|
|
120
|
+
/** Empty the field, and the value with it. */
|
|
121
|
+
clear() {
|
|
122
|
+
this.value.set('');
|
|
123
|
+
}
|
|
124
|
+
async call(name) {
|
|
125
|
+
const nativeTag = this.engine?.tagOf(this.node);
|
|
126
|
+
if (nativeTag == null)
|
|
127
|
+
return;
|
|
128
|
+
await this.functions?.[name]?.call({ nativeTag });
|
|
129
|
+
}
|
|
130
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SearchField, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
131
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: SearchField, isStandalone: true, selector: "search-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showsCancelButton: { classPropertyName: "showsCancelButton", publicName: "showsCancelButton", isSignal: true, isRequired: false, transformFunction: null }, autoCapitalize: { classPropertyName: "autoCapitalize", publicName: "autoCapitalize", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", search: "search", cancel: "cancel", searchFocus: "searchFocus", searchBlur: "searchBlur", touch: "touch" }, host: { properties: { "text": "value()", "enabled": "!disabled()", "mostRecentEventCount": "eventCount()", "placeholder": "placeholder()", "showsCancelButton": "showsCancelButton()", "autoCapitalize": "autoCapitalize()", "tintColor": "tintColor()", "style.display": "missing ? \"none\" : undefined" } }, ngImport: i0, template: '', isInline: true, styles: [":host { height: 56px; }"] });
|
|
132
|
+
}
|
|
133
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SearchField, decorators: [{
|
|
134
|
+
type: Component,
|
|
135
|
+
args: [{ selector: 'search-field', template: '', host: {
|
|
136
|
+
'[text]': 'value()',
|
|
137
|
+
'[enabled]': '!disabled()',
|
|
138
|
+
'[mostRecentEventCount]': 'eventCount()',
|
|
139
|
+
'[placeholder]': 'placeholder()',
|
|
140
|
+
'[showsCancelButton]': 'showsCancelButton()',
|
|
141
|
+
'[autoCapitalize]': 'autoCapitalize()',
|
|
142
|
+
'[tintColor]': 'tintColor()',
|
|
143
|
+
// Fabric draws a view the build does not have as a placeholder box naming it.
|
|
144
|
+
'[style.display]': 'missing ? "none" : undefined',
|
|
145
|
+
}, styles: [":host { height: 56px; }"] }]
|
|
146
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], showsCancelButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showsCancelButton", required: false }] }], autoCapitalize: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCapitalize", required: false }] }], tintColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "tintColor", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], searchFocus: [{ type: i0.Output, args: ["searchFocus"] }], searchBlur: [{ type: i0.Output, args: ["searchBlur"] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
|
|
147
|
+
//# sourceMappingURL=search-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-field.js","sourceRoot":"","sources":["../src/search-field.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `<search-field>`: the platform's own search field, anywhere a view can go. `<native-search-bar>`\n * from `@ng-native/router` is the one in a navigation bar; this is the one for everywhere else, a\n * filter sheet, a page, a list's header:\n *\n * ```html\n * <ui-bottom-sheet [(open)]=\"filtering\">\n * <search-field placeholder=\"Search colours\" [(value)]=\"query\" />\n * ...\n * </ui-bottom-sheet>\n * ```\n *\n * It is `@ng-native/expo`'s own native view: UIKit's `UISearchBar` on iOS, with its Cancel button,\n * dictation and clear button, and AndroidX's `SearchView` on Android, which has no Cancel button.\n * The events are `<native-search-bar>`'s: `(search)` for the keyboard's search key with the text,\n * `(cancel)`, `(searchFocus)` and `(searchBlur)`, and `focus()`, `blur()` and `clear()` from code.\n * The text is a two-way `value` rather than the bar's `query`, because it is a Signal Forms control\n * as `<text-input>` is: `[formField]` binds `value`, `disabled`, and `touch` on blur.\n *\n * Expo Go cannot load native code of its own, so there the element draws nothing, and in\n * development it says so once.\n */\nimport {\n Component,\n booleanAttribute,\n DestroyRef,\n ElementRef,\n InjectionToken,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport {\n Engine,\n type EngineNode,\n HostEngine,\n type NativeSyntheticEvent,\n registerViewName,\n} from '@ng-native/fabric';\nimport { optional } from './native.ts';\nimport { expoViewName } from './register-expo-view.ts';\nimport { optionalBoolean } from './transforms.ts';\n\n/** The functions the native module defines on the view, called with the view's tag as `this`. */\nexport interface SearchFieldFunctions {\n focus?(this: { nativeTag: number }): Promise<void>;\n blur?(this: { nativeTag: number }): Promise<void>;\n}\n\ndeclare const ngDevMode: unknown;\n\nconst MODULE = 'NgNativeSearchField';\n\nlet warned = false;\n\n/** Says once, in development, that the build has no native search field to draw. */\nfunction warnMissing(): void {\n if ((typeof ngDevMode !== 'undefined' && !ngDevMode) || warned) return;\n warned = true;\n console.warn(\n '[angular-native] <search-field> is native code of @ng-native/expo, which this build does not ' +\n 'have, so it draws nothing. Expo Go cannot load it: run a development build.',\n );\n}\n\n@Component({\n selector: 'search-field',\n template: '',\n styles: ':host { height: 56px; }',\n host: {\n '[text]': 'value()',\n '[enabled]': '!disabled()',\n '[mostRecentEventCount]': 'eventCount()',\n '[placeholder]': 'placeholder()',\n '[showsCancelButton]': 'showsCancelButton()',\n '[autoCapitalize]': 'autoCapitalize()',\n '[tintColor]': 'tintColor()',\n // Fabric draws a view the build does not have as a placeholder box naming it.\n '[style.display]': 'missing ? \"none\" : undefined',\n },\n})\nexport class SearchField {\n /**\n * The functions native defines on the view, or null where the build has no native search\n * field. Overridden in a test.\n */\n static readonly SOURCE = new InjectionToken<SearchFieldFunctions | null>(\n 'angular-native.searchFieldSource',\n {\n factory: () =>\n optional(() => {\n const core = require('expo-modules-core') as typeof import('expo-modules-core');\n const module = core.requireOptionalNativeModule<{\n ViewPrototypes?: Record<string, SearchFieldFunctions>;\n }>(MODULE);\n return module ? (module.ViewPrototypes?.[MODULE] ?? {}) : null;\n }),\n },\n );\n\n /**\n * The text in the field: typing writes it, and text set from code is put in the field. A\n * `model()`, which is all `FormValueControl<string>` asks, so `[formField]` binds it.\n */\n readonly value = model('');\n /** Refuse input and show the field dimmed. Signal Forms' `disabled()` rule sets it. */\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly placeholder = input<string>();\n /**\n * iOS: whether the Cancel button shows. Left unset it shows while the field has the keyboard,\n * as in a search bar under a navigation bar.\n */\n readonly showsCancelButton = input(undefined, { transform: optionalBoolean });\n readonly autoCapitalize = input<'none' | 'words' | 'sentences' | 'characters'>();\n /** The colour of the cursor and the Cancel button. */\n readonly tintColor = input<string>();\n\n /** The keyboard's search key was pressed, with the text in the field. */\n readonly search = output<string>();\n /** iOS: the Cancel button was pressed. The field is emptied and gives up the keyboard. */\n readonly cancel = output<void>();\n readonly searchFocus = output<void>();\n readonly searchBlur = output<void>();\n /** The field gave up the keyboard: what Signal Forms marks a field touched from. */\n readonly touch = output<void>();\n\n /**\n * The keystroke the text answers, as React Native's text input counts them: native skips a text\n * older than what has been typed since, which a fast typist's echo otherwise overwrites.\n */\n protected readonly eventCount = signal(0);\n\n private readonly functions = inject(SearchField.SOURCE);\n protected readonly missing = !this.functions;\n private readonly node = inject(ElementRef).nativeElement as EngineNode;\n private readonly engine = inject(Engine, { optional: true });\n\n constructor() {\n registerViewName('search-field', expoViewName(MODULE));\n if (this.missing) warnMissing();\n const host =\n (this.node as { host?: Pick<Engine, 'setEventListener'> }).host ?? inject(HostEngine);\n const listen = (event: string, handle: (payload: Record<string, unknown>) => void) =>\n host.setEventListener(this.node, event, (raw) =>\n handle((raw as NativeSyntheticEvent<Record<string, unknown>>)?.nativeEvent ?? {}),\n );\n const stops = [\n listen('topChangeText', ({ text, eventCount }) => {\n if (typeof eventCount === 'number') this.eventCount.set(eventCount);\n this.value.set(String(text ?? ''));\n }),\n // Named apart from the outputs: a native event of an output's name reaches its listener too.\n listen('topSubmitSearch', ({ text }) => this.search.emit(String(text ?? this.value()))),\n listen('topPressCancel', () => this.cancel.emit()),\n listen('topBeginEditing', () => this.searchFocus.emit()),\n listen('topEndEditing', () => {\n this.searchBlur.emit();\n this.touch.emit();\n }),\n ];\n inject(DestroyRef).onDestroy(() => stops.forEach((stop) => stop()));\n }\n\n /** Give the field the keyboard. Does nothing before the view has reached the screen. */\n async focus(): Promise<void> {\n await this.call('focus');\n }\n\n /** Take the keyboard away from the field. */\n async blur(): Promise<void> {\n await this.call('blur');\n }\n\n /** Empty the field, and the value with it. */\n clear(): void {\n this.value.set('');\n }\n\n private async call(name: 'focus' | 'blur'): Promise<void> {\n const nativeTag = this.engine?.tagOf(this.node);\n if (nativeTag == null) return;\n await this.functions?.[name]?.call({ nativeTag });\n }\n}\n"]}
|
package/dist/secure-store.js
CHANGED
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
* See `Store` in `@ng-native/expo/store` for what the signal does.
|
|
10
10
|
*/
|
|
11
11
|
import { InjectionToken } from '@angular/core';
|
|
12
|
-
import { expoModule } from "./native.js";
|
|
12
|
+
import { expoModule, present } from "./native.js";
|
|
13
13
|
import { Store } from "./store.js";
|
|
14
14
|
const secure = () => {
|
|
15
15
|
const expo = expoModule('expo-secure-store', () => require('expo-secure-store'));
|
|
16
16
|
if (!expo)
|
|
17
17
|
return null;
|
|
18
|
+
// Missing from the build: the stand-in, to say so when it is used.
|
|
19
|
+
if (!present(expo))
|
|
20
|
+
return expo;
|
|
18
21
|
return {
|
|
19
22
|
get: (key) => expo.getItemAsync(key),
|
|
20
23
|
set: (key, value) => expo.setItemAsync(key, value),
|
package/dist/secure-store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-store.js","sourceRoot":"","sources":["../src/secure-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"secure-store.js","sourceRoot":"","sources":["../src/secure-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,KAAK,EAAoB,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,GAAG,GAAuB,EAAE;IACtC,MAAM,IAAI,GAAG,UAAU,CACrB,mBAAmB,EACnB,GAAG,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAuC,CACzE,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,mEAAmE;IACnE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAa,CAAC;IACzC,OAAO;QACL,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC;QACpC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC;QAClD,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;QAC1C,iFAAiF;QACjF,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;KACpC,CAAC;AACJ,CAAC,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,cAAc,CAAQ,8BAA8B,EAAE;IACrF,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;CACnC,CAAC,CAAC","sourcesContent":["/**\n * `SecureStorage`, a `Store` over `expo-secure-store`: the keychain and the Android keystore.\n *\n * ```ts\n * private readonly store = inject(SecureStorage);\n * protected readonly token = this.store.signal<string | null>('token', null);\n * ```\n *\n * See `Store` in `@ng-native/expo/store` for what the signal does.\n */\nimport { InjectionToken } from '@angular/core';\nimport { expoModule, present } from './native.ts';\nimport { Store, type NativeStore } from './store.ts';\n\nconst secure = (): NativeStore | null => {\n const expo = expoModule(\n 'expo-secure-store',\n () => require('expo-secure-store') as typeof import('expo-secure-store'),\n );\n if (!expo) return null;\n // Missing from the build: the stand-in, to say so when it is used.\n if (!present(expo)) return expo as never;\n return {\n get: (key) => expo.getItemAsync(key),\n set: (key, value) => expo.setItemAsync(key, value),\n remove: (key) => expo.deleteItemAsync(key),\n // The keychain can answer without waiting, so a token never flashes its default.\n getSync: (key) => expo.getItem(key),\n };\n};\n\n/** The keychain and the Android keystore. Small values, and worth the size limit. */\nexport const SecureStorage = new InjectionToken<Store>('angular-native.secureStorage', {\n factory: () => new Store(secure()),\n});\nexport type SecureStorage = Store;\n"]}
|
package/dist/sensors.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* never idles.
|
|
21
21
|
*/
|
|
22
22
|
import { InjectionToken, signal } from '@angular/core';
|
|
23
|
-
import { expoModule } from "./native.js";
|
|
23
|
+
import { expoModule, present as moduleThere } from "./native.js";
|
|
24
24
|
export class Sensor {
|
|
25
25
|
native;
|
|
26
26
|
current;
|
|
@@ -43,7 +43,8 @@ export class Sensor {
|
|
|
43
43
|
this.current = signal(zero, /* @ts-ignore */
|
|
44
44
|
...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
45
45
|
this.reading = this.current.asReadonly();
|
|
46
|
-
|
|
46
|
+
// Not there at all is not available; a missing module says so when it is started.
|
|
47
|
+
void (moduleThere(native)?.isAvailableAsync() ?? Promise.resolve(false)).then((yes) => this.present.set(yes), () => this.present.set(false));
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
49
50
|
* Start reading, at `intervalMs` between readings. Returns the function that stops.
|
|
@@ -89,7 +90,9 @@ function sensorToken(pick, zero, name) {
|
|
|
89
90
|
return new InjectionToken(`angular-native.${name}`, {
|
|
90
91
|
factory: () => {
|
|
91
92
|
const expo = expoModule('expo-sensors', () => require('expo-sensors'));
|
|
92
|
-
|
|
93
|
+
// Missing from the build, the stand-in is the sensor, to say so when it is used.
|
|
94
|
+
const sensor = expo && (moduleThere(expo) ? pick(expo) : expo);
|
|
95
|
+
return new Sensor(sensor ? sensor : null, zero);
|
|
93
96
|
},
|
|
94
97
|
});
|
|
95
98
|
}
|
package/dist/sensors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensors.js","sourceRoot":"","sources":["../src/sensors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The motion sensors, bound to `expo-sensors`.\n *\n * ```ts\n * private readonly motion = inject(Accelerometer);\n *\n * constructor() {\n * inject(DestroyRef).onDestroy(this.motion.start(50));\n * }\n * ```\n *\n * Every one of them is the same object - `addListener`, `setUpdateInterval`, `isAvailableAsync` -\n * so this is one class parameterised by the reading, not one per sensor. Six services sharing one\n * class is why these are tokens rather than six decorated classes: a `@Service` class cannot be\n * generic in the thing it reports.\n *\n * Nothing is subscribed until `start`, and the interval is explicit: the sensible one is a\n * property of what the app is doing - a compass wants a tenth of a second, a shake detector less -\n * and every event is a change-detection pass, so a sensor left at its default is a phone that\n * never idles.\n */\nimport { InjectionToken, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** What a three-axis sensor reports. Barometer and light report their own shapes. */\nexport interface Vector {\n readonly x: number;\n readonly y: number;\n readonly z: number;\n}\n\nexport interface NativeSensor<T> {\n addListener(listener: (reading: T) => void): { remove(): void };\n setUpdateInterval(intervalMs: number): void;\n isAvailableAsync(): Promise<boolean>;\n}\n\nexport class Sensor<T> {\n private readonly native: NativeSensor<T> | null;\n private readonly current: ReturnType<typeof signal<T>>;\n private readonly present = signal<boolean | null>(null);\n private subscription: { remove(): void } | null = null;\n\n /** The most recent reading, or the stated zero until the sensor has said anything. */\n readonly reading: Signal<T>;\n\n /**\n * Whether the device has this sensor. Null until the platform has answered, which is one turn\n * away: `isAvailableAsync` is asynchronous even where the answer is a constant.\n *\n * A signal rather than the promise the module offers, because the question is asked in a\n * template - `@if (barometer.available())` - and a promise there is truthy on the first frame\n * and every frame after, which is the failure this project keeps guarding against.\n */\n readonly available: Signal<boolean | null> = this.present.asReadonly();\n\n constructor(native: NativeSensor<T> | null, zero: T) {\n this.native = native;\n this.current = signal(zero);\n this.reading = this.current.asReadonly();\n void (native?.isAvailableAsync() ?? Promise.resolve(false)).then(\n (yes) => this.present.set(yes),\n () => this.present.set(false),\n );\n }\n\n /**\n * Start reading, at `intervalMs` between readings. Returns the function that stops.\n *\n * Explicit rather than automatic, because the sensible interval is a property of what the app is\n * doing with it - a compass needs a tenth of a second and a shake detector needs less - and\n * because a sensor nobody stops is a battery nobody gets back.\n */\n start(intervalMs = 100): () => void {\n // One sensor serves the whole app, so each start is a claim of its own, and its stop gives\n // back that claim alone: the sensor reads while anyone still wants it, as often as the most\n // demanding of them asks.\n const claim = { intervalMs };\n this.claims.add(claim);\n this.native?.setUpdateInterval(this.fastest());\n this.subscription ??= this.native?.addListener((reading) => this.current.set(reading)) ?? null;\n return () => {\n if (!this.claims.delete(claim)) return;\n if (this.claims.size === 0) this.stop();\n else this.native?.setUpdateInterval(this.fastest());\n };\n }\n\n /** Every `start` not yet stopped. */\n private readonly claims = new Set<{ readonly intervalMs: number }>();\n\n private fastest(): number {\n return Math.min(...[...this.claims].map((claim) => claim.intervalMs));\n }\n\n /** Stop reading for everyone who started it. */\n stop(): void {\n this.claims.clear();\n this.subscription?.remove();\n this.subscription = null;\n }\n}\n\nexport const ORIGIN: Vector = { x: 0, y: 0, z: 0 };\n\n/** Every reading carries the time it was taken, so the zero has to as well. */\nconst STILL = { x: 0, y: 0, z: 0, timestamp: 0 };\n\ntype Sensors = typeof import('expo-sensors');\n\n/** One token per sensor, each reading the module only if it is installed. */\nfunction sensorToken<T>(pick: (expo: Sensors) => unknown, zero: T, name: string) {\n return new InjectionToken<Sensor<T>>(`angular-native.${name}`, {\n factory: () => {\n const expo = expoModule('expo-sensors', () => require('expo-sensors') as Sensors);\n return new Sensor<T>(expo ? (pick(expo) as NativeSensor<T>) : null, zero);\n },\n });\n}\n\n/** In g. Includes gravity, so a phone lying flat reads about 1 on z. */\nexport const Accelerometer = sensorToken<import('expo-sensors').AccelerometerMeasurement>(\n (expo) => expo.Accelerometer,\n STILL,\n 'accelerometer',\n);\nexport type Accelerometer = Sensor<import('expo-sensors').AccelerometerMeasurement>;\n\n/** Rotation, in radians per second. */\nexport const Gyroscope = sensorToken<import('expo-sensors').GyroscopeMeasurement>(\n (expo) => expo.Gyroscope,\n STILL,\n 'gyroscope',\n);\nexport type Gyroscope = Sensor<import('expo-sensors').GyroscopeMeasurement>;\n\n/** The magnetic field, in microteslas. What a compass is built on. */\nexport const Magnetometer = sensorToken<import('expo-sensors').MagnetometerMeasurement>(\n (expo) => expo.Magnetometer,\n STILL,\n 'magnetometer',\n);\nexport type Magnetometer = Sensor<import('expo-sensors').MagnetometerMeasurement>;\n\n/** Everything at once, plus the orientation and rotation rate derived from it. */\nexport const DeviceMotion = sensorToken<import('expo-sensors').DeviceMotionMeasurement>(\n (expo) => expo.DeviceMotion,\n {\n acceleration: null,\n accelerationIncludingGravity: STILL,\n rotation: { alpha: 0, beta: 0, gamma: 0 },\n rotationRate: null,\n orientation: 0,\n interval: 0,\n } as import('expo-sensors').DeviceMotionMeasurement,\n 'deviceMotion',\n);\nexport type DeviceMotion = Sensor<import('expo-sensors').DeviceMotionMeasurement>;\n\n/** Air pressure, in hectopascals. Present on fewer devices than the rest; check `available`. */\nexport const Barometer = sensorToken<import('expo-sensors').BarometerMeasurement>(\n (expo) => expo.Barometer,\n { pressure: 0, relativeAltitude: 0 } as import('expo-sensors').BarometerMeasurement,\n 'barometer',\n);\nexport type Barometer = Sensor<import('expo-sensors').BarometerMeasurement>;\n\n/** Ambient light in lux. Android only. */\nexport const LightSensor = sensorToken<import('expo-sensors').LightSensorMeasurement>(\n (expo) => expo.LightSensor,\n { illuminance: 0 } as import('expo-sensors').LightSensorMeasurement,\n 'lightSensor',\n);\nexport type LightSensor = Sensor<import('expo-sensors').LightSensorMeasurement>;\n"]}
|
|
1
|
+
{"version":3,"file":"sensors.js","sourceRoot":"","sources":["../src/sensors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The motion sensors, bound to `expo-sensors`.\n *\n * ```ts\n * private readonly motion = inject(Accelerometer);\n *\n * constructor() {\n * inject(DestroyRef).onDestroy(this.motion.start(50));\n * }\n * ```\n *\n * Every one of them is the same object - `addListener`, `setUpdateInterval`, `isAvailableAsync` -\n * so this is one class parameterised by the reading, not one per sensor. Six services sharing one\n * class is why these are tokens rather than six decorated classes: a `@Service` class cannot be\n * generic in the thing it reports.\n *\n * Nothing is subscribed until `start`, and the interval is explicit: the sensible one is a\n * property of what the app is doing - a compass wants a tenth of a second, a shake detector less -\n * and every event is a change-detection pass, so a sensor left at its default is a phone that\n * never idles.\n */\nimport { InjectionToken, signal, type Signal } from '@angular/core';\nimport { expoModule, present as moduleThere } from './native.ts';\n\n/** What a three-axis sensor reports. Barometer and light report their own shapes. */\nexport interface Vector {\n readonly x: number;\n readonly y: number;\n readonly z: number;\n}\n\nexport interface NativeSensor<T> {\n addListener(listener: (reading: T) => void): { remove(): void };\n setUpdateInterval(intervalMs: number): void;\n isAvailableAsync(): Promise<boolean>;\n}\n\nexport class Sensor<T> {\n private readonly native: NativeSensor<T> | null;\n private readonly current: ReturnType<typeof signal<T>>;\n private readonly present = signal<boolean | null>(null);\n private subscription: { remove(): void } | null = null;\n\n /** The most recent reading, or the stated zero until the sensor has said anything. */\n readonly reading: Signal<T>;\n\n /**\n * Whether the device has this sensor. Null until the platform has answered, which is one turn\n * away: `isAvailableAsync` is asynchronous even where the answer is a constant.\n *\n * A signal rather than the promise the module offers, because the question is asked in a\n * template - `@if (barometer.available())` - and a promise there is truthy on the first frame\n * and every frame after, which is the failure this project keeps guarding against.\n */\n readonly available: Signal<boolean | null> = this.present.asReadonly();\n\n constructor(native: NativeSensor<T> | null, zero: T) {\n this.native = native;\n this.current = signal(zero);\n this.reading = this.current.asReadonly();\n // Not there at all is not available; a missing module says so when it is started.\n void (moduleThere(native)?.isAvailableAsync() ?? Promise.resolve(false)).then(\n (yes) => this.present.set(yes),\n () => this.present.set(false),\n );\n }\n\n /**\n * Start reading, at `intervalMs` between readings. Returns the function that stops.\n *\n * Explicit rather than automatic, because the sensible interval is a property of what the app is\n * doing with it - a compass needs a tenth of a second and a shake detector needs less - and\n * because a sensor nobody stops is a battery nobody gets back.\n */\n start(intervalMs = 100): () => void {\n // One sensor serves the whole app, so each start is a claim of its own, and its stop gives\n // back that claim alone: the sensor reads while anyone still wants it, as often as the most\n // demanding of them asks.\n const claim = { intervalMs };\n this.claims.add(claim);\n this.native?.setUpdateInterval(this.fastest());\n this.subscription ??= this.native?.addListener((reading) => this.current.set(reading)) ?? null;\n return () => {\n if (!this.claims.delete(claim)) return;\n if (this.claims.size === 0) this.stop();\n else this.native?.setUpdateInterval(this.fastest());\n };\n }\n\n /** Every `start` not yet stopped. */\n private readonly claims = new Set<{ readonly intervalMs: number }>();\n\n private fastest(): number {\n return Math.min(...[...this.claims].map((claim) => claim.intervalMs));\n }\n\n /** Stop reading for everyone who started it. */\n stop(): void {\n this.claims.clear();\n this.subscription?.remove();\n this.subscription = null;\n }\n}\n\nexport const ORIGIN: Vector = { x: 0, y: 0, z: 0 };\n\n/** Every reading carries the time it was taken, so the zero has to as well. */\nconst STILL = { x: 0, y: 0, z: 0, timestamp: 0 };\n\ntype Sensors = typeof import('expo-sensors');\n\n/** One token per sensor, each reading the module only if it is installed. */\nfunction sensorToken<T>(pick: (expo: Sensors) => unknown, zero: T, name: string) {\n return new InjectionToken<Sensor<T>>(`angular-native.${name}`, {\n factory: () => {\n const expo = expoModule('expo-sensors', () => require('expo-sensors') as Sensors);\n // Missing from the build, the stand-in is the sensor, to say so when it is used.\n const sensor = expo && (moduleThere(expo) ? pick(expo) : expo);\n return new Sensor<T>(sensor ? (sensor as NativeSensor<T>) : null, zero);\n },\n });\n}\n\n/** In g. Includes gravity, so a phone lying flat reads about 1 on z. */\nexport const Accelerometer = sensorToken<import('expo-sensors').AccelerometerMeasurement>(\n (expo) => expo.Accelerometer,\n STILL,\n 'accelerometer',\n);\nexport type Accelerometer = Sensor<import('expo-sensors').AccelerometerMeasurement>;\n\n/** Rotation, in radians per second. */\nexport const Gyroscope = sensorToken<import('expo-sensors').GyroscopeMeasurement>(\n (expo) => expo.Gyroscope,\n STILL,\n 'gyroscope',\n);\nexport type Gyroscope = Sensor<import('expo-sensors').GyroscopeMeasurement>;\n\n/** The magnetic field, in microteslas. What a compass is built on. */\nexport const Magnetometer = sensorToken<import('expo-sensors').MagnetometerMeasurement>(\n (expo) => expo.Magnetometer,\n STILL,\n 'magnetometer',\n);\nexport type Magnetometer = Sensor<import('expo-sensors').MagnetometerMeasurement>;\n\n/** Everything at once, plus the orientation and rotation rate derived from it. */\nexport const DeviceMotion = sensorToken<import('expo-sensors').DeviceMotionMeasurement>(\n (expo) => expo.DeviceMotion,\n {\n acceleration: null,\n accelerationIncludingGravity: STILL,\n rotation: { alpha: 0, beta: 0, gamma: 0 },\n rotationRate: null,\n orientation: 0,\n interval: 0,\n } as import('expo-sensors').DeviceMotionMeasurement,\n 'deviceMotion',\n);\nexport type DeviceMotion = Sensor<import('expo-sensors').DeviceMotionMeasurement>;\n\n/** Air pressure, in hectopascals. Present on fewer devices than the rest; check `available`. */\nexport const Barometer = sensorToken<import('expo-sensors').BarometerMeasurement>(\n (expo) => expo.Barometer,\n { pressure: 0, relativeAltitude: 0 } as import('expo-sensors').BarometerMeasurement,\n 'barometer',\n);\nexport type Barometer = Sensor<import('expo-sensors').BarometerMeasurement>;\n\n/** Ambient light in lux. Android only. */\nexport const LightSensor = sensorToken<import('expo-sensors').LightSensorMeasurement>(\n (expo) => expo.LightSensor,\n { illuminance: 0 } as import('expo-sensors').LightSensorMeasurement,\n 'lightSensor',\n);\nexport type LightSensor = Sensor<import('expo-sensors').LightSensorMeasurement>;\n"]}
|
package/dist/splash-screen.js
CHANGED
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
* platform rather than injecting it, because the first call happens before there is an injector.
|
|
30
30
|
*/
|
|
31
31
|
import { InjectionToken } from '@angular/core';
|
|
32
|
-
import {
|
|
32
|
+
import { expoModuleNow } from "./native.js";
|
|
33
33
|
export function expoSplashScreen() {
|
|
34
|
-
const expo =
|
|
34
|
+
const expo = expoModuleNow('expo-splash-screen', () => require('expo-splash-screen'));
|
|
35
35
|
if (!expo)
|
|
36
36
|
return null;
|
|
37
37
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splash-screen.js","sourceRoot":"","sources":["../src/splash-screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"splash-screen.js","sourceRoot":"","sources":["../src/splash-screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQ5C,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,aAAa,CACxB,oBAAoB,EACpB,GAAG,EAAE,CAAC,OAAO,CAAC,oBAAoB,CAAwC,CAC3E,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO;QACL,oBAAoB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE;QACvD,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE;KAClC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,MAAM;IACA,MAAM,CAAgE;IAC/E,QAAQ,GAA0D,IAAI,CAAC;IACvE,IAAI,GAAG,KAAK,CAAC;IAErB;;;OAGG;IACH,YAAY,MAAqE;QAC/E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,IAAY,MAAM;QAChB,IAAI,CAAC,QAAQ,KAAK;YAChB,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;SACxE,CAAC;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,8FAA8F;IAC9F,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,KAAK,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAChD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,aAAa,CAAC,IAAsB,EAAE,SAAS,GAAG,YAAY;QAClE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC;QACb,CAAC;gBAAS,CAAC;YACT,MAAM,SAAS,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,YAAY,GAAG,GAAkB,EAAE,CACvC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,MAAM,GAAG,GAAI,UAAmE;SAC7E,qBAAqB,CAAC;IACzB,IAAI,GAAG;QAAE,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;;QACzB,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,cAAc,CAAS,6BAA6B,EAAE;IACpF,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY;CAC5B,CAAC,CAAC","sourcesContent":["/**\n * `SplashScreen`, bound to `expo-splash-screen`. Held until the app has something worth showing.\n *\n * The whole point is the ordering, and it is easy to get subtly wrong: the native screen hides\n * itself as soon as the first frame is drawn, so an app that loads fonts before mounting shows a\n * blank window for as long as that takes. Preventing the auto-hide has to happen at module scope,\n * before any of that, and hiding has to happen after the first real frame:\n *\n * ```ts\n * import { splashScreen } from '@ng-native/expo/splash-screen';\n * import { loadFonts } from '@ng-native/expo/fonts';\n *\n * // At module scope, before anything renders.\n * splashScreen.hold();\n *\n * AppRegistry.registerRunnable('main', ({ rootTag }) => {\n * const fonts = loadFonts(styleSheetOf(GlobalStyles));\n * mount(Number(rootTag), App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });\n * void splashScreen.hideWhenReady(fonts);\n * });\n * ```\n *\n * Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive\n * is laid out again as each face registers, behind the splash, so nothing is ever seen in the\n * fallback face.\n *\n * `hideWhenReady` is that shape as one call, which is the only thing worth wrapping here: every\n * function underneath is already a plain promise with no React in it. The class takes its\n * platform rather than injecting it, because the first call happens before there is an injector.\n */\nimport { InjectionToken } from '@angular/core';\nimport { expoModuleNow } from './native.ts';\n\n/** The slice of `expo-splash-screen` this needs. */\nexport interface NativeSplashScreen {\n preventAutoHideAsync(): Promise<boolean>;\n hideAsync(): Promise<void>;\n}\n\nexport function expoSplashScreen(): NativeSplashScreen | null {\n const expo = expoModuleNow(\n 'expo-splash-screen',\n () => require('expo-splash-screen') as typeof import('expo-splash-screen'),\n );\n if (!expo) return null;\n return {\n preventAutoHideAsync: () => expo.preventAutoHideAsync(),\n hideAsync: () => expo.hideAsync(),\n };\n}\n\nexport class Splash {\n private readonly source: NativeSplashScreen | null | (() => NativeSplashScreen | null);\n private resolved: { readonly native: NativeSplashScreen | null } | null = null;\n private held = false;\n\n /**\n * The module, or a function that reaches it: the shared instance is created when its file is\n * imported, and reaching the module then would fail an import rather than the first call.\n */\n constructor(native: NativeSplashScreen | null | (() => NativeSplashScreen | null)) {\n this.source = native;\n }\n\n private get native(): NativeSplashScreen | null {\n this.resolved ??= {\n native: typeof this.source === 'function' ? this.source() : this.source,\n };\n return this.resolved.native;\n }\n\n /** Whether the module is installed. Without it the screen hides itself on the first frame. */\n get available(): boolean {\n return this.native !== null;\n }\n\n /**\n * Keep the splash up. Called at module scope, before anything is mounted.\n *\n * Failures are swallowed: the only ones are races with the screen already having gone, and an\n * app that cannot hold its splash should still start.\n */\n hold(): void {\n if (!this.native || this.held) return;\n this.held = true;\n void this.native.preventAutoHideAsync().catch(() => {});\n }\n\n /** Let it go. Safe to call whether or not it was ever held. */\n async hide(): Promise<void> {\n if (!this.native) return;\n this.held = false;\n await this.native.hideAsync().catch(() => {});\n }\n\n /**\n * Hide once `work` is done *and* the first frame after it has been drawn.\n *\n * Hiding the moment the work resolves uncovers the frame that was on screen while it ran, which\n * is the empty one. Waiting a frame is what makes the handover invisible, and is why this is a\n * method rather than two calls at a call site.\n */\n async hideWhenReady(work: Promise<unknown>, nextFrame = defaultFrame): Promise<void> {\n try {\n await work;\n } finally {\n await nextFrame();\n await this.hide();\n }\n }\n}\n\n/** One turn of the frame loop, or the macrotask queue where there is no loop to wait on. */\nconst defaultFrame = (): Promise<void> =>\n new Promise((resolve) => {\n const raf = (globalThis as { requestAnimationFrame?: (cb: () => void) => void })\n .requestAnimationFrame;\n if (raf) raf(() => resolve());\n else setTimeout(resolve, 0);\n });\n\n/**\n * The one instance, exported directly because it is needed before an injector exists: holding the\n * splash is the first thing an entry file does.\n */\nexport const splashScreen = new Splash(expoSplashScreen);\n\nexport const SplashScreen = new InjectionToken<Splash>('angular-native.splashScreen', {\n factory: () => splashScreen,\n});\nexport type SplashScreen = Splash;\n"]}
|
package/dist/store.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* over one class, for the same reason the sensors are.
|
|
24
24
|
*/
|
|
25
25
|
import { computed, signal } from '@angular/core';
|
|
26
|
+
import { present } from "./native.js";
|
|
26
27
|
export class Store {
|
|
27
28
|
native;
|
|
28
29
|
bound = new Map();
|
|
@@ -69,6 +70,9 @@ export class Store {
|
|
|
69
70
|
* The same key always returns the same signal, so two components binding to it stay in step.
|
|
70
71
|
*/
|
|
71
72
|
signal(key, initial) {
|
|
73
|
+
// A read is a use: a missing module says what to install on every one, before a key is kept.
|
|
74
|
+
if (this.native && !present(this.native))
|
|
75
|
+
void this.native.get(key);
|
|
72
76
|
const existing = this.bound.get(key);
|
|
73
77
|
if (existing)
|
|
74
78
|
return existing.value;
|
package/dist/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Store`, what `Storage` (`@ng-native/expo/async-storage`) and `SecureStorage`\n * (`@ng-native/expo/secure-store`) both are, over whichever native store each is bound to.\n *\n * ```ts\n * private readonly store = inject(Storage);\n * protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');\n * ```\n *\n * The signal is bound both ways: reading it is reading the preference, setting it persists.\n *\n * The underlying APIs are already plain promises with no React in them, so what is built is not a\n * wrapper but a *binding*: `store.signal('theme', 'light')` reads it back once, writes it through\n * whenever it is set, and is an ordinary signal in between. Everything is JSON, because both\n * stores hold strings and a store that silently held `\"[object Object]\"` would be worse than one\n * that held nothing.\n *\n * Two services rather than one because the difference matters and should be visible at the\n * injection site, and two entry points because each needs its own native module: Metro fails a\n * build on a `require` it cannot resolve, so one entry point for both made an app install both. `SecureStorage` is the keychain and the Android keystore: small values, an\n * async write, and a size limit that is not documented but is real. `Storage` is a plain\n * key-value file: bigger, faster, and readable by anyone with the device unlocked. Two tokens\n * over one class, for the same reason the sensors are.\n */\nimport { computed, signal, type Signal, type WritableSignal } from '@angular/core';\n\n/** What both `AsyncStorage` and `SecureStore` offer, reduced to the three verbs. */\nexport interface NativeStore {\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n remove(key: string): Promise<void>;\n /** Where the platform can answer without waiting - `SecureStore` can, `AsyncStorage` cannot. */\n getSync?(key: string): string | null;\n}\n\n/** A key's signal, with what `remove()` needs to put it back. */\ninterface Binding {\n readonly value: WritableSignal<unknown>;\n /** The signal's own `set`, which does not write through. */\n readonly reset: (value: unknown) => void;\n readonly initial: unknown;\n}\n\nexport class Store {\n private readonly native: NativeStore | null;\n private readonly bound = new Map<string, Binding>();\n /** Keys written by the app before their read came back. The read must not undo a write. */\n private readonly written = new Set<string>();\n /**\n * Updates made to a key whose read has not come back, in order. An update changes the stored\n * value rather than replacing it, so these wait for it and are applied to it, not to `initial`.\n */\n private readonly waiting = new Map<string, ((current: unknown) => unknown)[]>();\n /** Reads started and not yet back, successful or not. */\n private readonly reading = signal(0);\n private readonly failure = signal<Error | null>(null);\n /** Writes sent to the platform and not yet answered. What `flush()` waits for. */\n private readonly writes = new Set<Promise<void>>();\n /** The first write to fail since the last `flush()`, held until a flush reports it. */\n private unreported: { readonly error: unknown } | null = null;\n\n constructor(native: NativeStore | null) {\n this.native = native;\n }\n\n /**\n * Whether every key asked for so far has been read back. False while any read is outstanding,\n * so it goes back to false when a new key is asked for, and true again once that read returns.\n *\n * A read that fails still counts as back: the signal keeps its `initial`, and the failure is on\n * `error`. A loading screen held on `ready` should not wait forever for a store that is broken.\n */\n readonly ready: Signal<boolean> = computed(() => this.reading() === 0);\n\n /**\n * The most recent read or write that failed, or null while none has.\n *\n * A write through `set()` has no promise to reject, so this is where a full disk or a locked\n * keychain is reported. `flush()` is the way to wait on the writes themselves.\n */\n readonly error: Signal<Error | null> = this.failure.asReadonly();\n\n /**\n * The stored value at `key`, as a signal that writes through when set.\n *\n * Starts at `initial` and stays there until the read returns, which is one turn away at best -\n * unless the platform can answer synchronously, in which case it never shows `initial` at all.\n * The same key always returns the same signal, so two components binding to it stay in step.\n */\n signal<T>(key: string, initial: T): WritableSignal<T> {\n const existing = this.bound.get(key);\n if (existing) return existing.value as WritableSignal<T>;\n\n const stored = this.readSync(key);\n const value = signal<T>(stored === ABSENT ? initial : (stored as T));\n\n const set = value.set.bind(value);\n const update = value.update.bind(value);\n value.set = (next: T) => {\n this.written.add(key);\n this.waiting.delete(key);\n set(next);\n this.write(key, next);\n };\n value.update = (fn: (current: T) => T) => {\n // Shown at once, on what is known so far, and written once the stored value is known.\n const waiting = this.waiting.get(key);\n update(fn);\n if (waiting) {\n waiting.push(fn as (current: unknown) => unknown);\n return;\n }\n this.written.add(key);\n this.write(key, value());\n };\n this.bound.set(key, {\n value: value as WritableSignal<unknown>,\n reset: set as (value: unknown) => void,\n initial,\n });\n\n // A platform that answered synchronously has said what is stored; one that cannot has not.\n if (stored === ABSENT && this.native && !this.native.getSync) this.waiting.set(key, []);\n void this.hydrate(key, set, initial);\n return value;\n }\n\n /**\n * Forget a key, in the store and in the signal bound to it.\n *\n * The signal goes back to the `initial` it was first asked for with, and stays the one signal\n * for the key. Rejects if the platform could not remove it, which also sets `error`.\n */\n async remove(key: string): Promise<void> {\n this.written.add(key);\n this.waiting.delete(key);\n const binding = this.bound.get(key);\n binding?.reset(binding.initial);\n await this.track(this.native?.remove(key));\n }\n\n /**\n * Wait for every write sent so far. Rejects with the first of them that failed since the last\n * `flush()`, whether it failed before this call or while waiting, so a screen that has to know a\n * value reached the disk - before signing out, say - can find out. Each failure is reported by\n * one flush only.\n */\n async flush(): Promise<void> {\n await Promise.allSettled([...this.writes]);\n const failed = this.unreported;\n this.unreported = null;\n if (failed) throw failed.error;\n }\n\n private readSync(key: string): unknown {\n let raw: string | null | undefined;\n try {\n raw = this.native?.getSync?.(key);\n } catch (error) {\n this.fail(error);\n return ABSENT;\n }\n return raw == null ? ABSENT : decode(raw);\n }\n\n /**\n * Read the stored value in, unless the app has already set one.\n *\n * The race is real and silent: a screen that writes a preference during startup would otherwise\n * have it overwritten a tick later by whatever was there before.\n */\n private async hydrate<T>(key: string, set: (value: T) => void, initial: T): Promise<void> {\n this.reading.update((n) => n + 1);\n let stored: unknown = ABSENT;\n try {\n const raw = await this.native?.get(key);\n if (raw != null) stored = decode(raw);\n } catch (error) {\n this.fail(error);\n } finally {\n this.settle(key, set, stored === ABSENT ? initial : stored, stored !== ABSENT);\n this.reading.update((n) => n - 1);\n }\n }\n\n /** The read is back: show what was stored, with any updates that waited for it applied. */\n private settle<T>(key: string, set: (value: T) => void, base: unknown, found: boolean): void {\n const updates = this.waiting.get(key);\n this.waiting.delete(key);\n if (this.written.has(key)) return;\n if (!updates?.length) {\n if (found) set(base as T);\n return;\n }\n const value = updates.reduce((current, fn) => fn(current), base);\n this.written.add(key);\n set(value as T);\n this.write(key, value);\n }\n\n private write(key: string, value: unknown): void {\n const sent =\n value === undefined ? this.native?.remove(key) : this.native?.set(key, JSON.stringify(value));\n // Observed here, so a failure lands on `error` and in `flush()` rather than going unhandled.\n this.track(sent).catch(() => {});\n }\n\n /** A platform call, kept until it answers, with its failure reported on `error`. */\n private track(sent: Promise<void> | undefined): Promise<void> {\n if (!sent) return Promise.resolve();\n const tracked = sent.then(\n () => undefined,\n (error: unknown) => {\n this.fail(error);\n this.unreported ??= { error };\n throw error;\n },\n );\n this.writes.add(tracked);\n const forget = () => void this.writes.delete(tracked);\n tracked.then(forget, forget);\n return tracked;\n }\n\n private fail(error: unknown): void {\n this.failure.set(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/** What `decode` answers for a string that is not JSON, since `null` is a value JSON can hold. */\nconst ABSENT: unique symbol = Symbol('absent');\n\n/**\n * A stored string as the value it encodes.\n *\n * A value that will not parse is treated as absent rather than thrown: it is a store written by\n * an older version of the app, and the right answer is the default the caller already supplied.\n * A stored `null` is not that: it parses, and a nullable signal gets it back.\n */\nfunction decode(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return ABSENT;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Store`, what `Storage` (`@ng-native/expo/async-storage`) and `SecureStorage`\n * (`@ng-native/expo/secure-store`) both are, over whichever native store each is bound to.\n *\n * ```ts\n * private readonly store = inject(Storage);\n * protected readonly theme = this.store.signal<'light' | 'dark'>('theme', 'light');\n * ```\n *\n * The signal is bound both ways: reading it is reading the preference, setting it persists.\n *\n * The underlying APIs are already plain promises with no React in them, so what is built is not a\n * wrapper but a *binding*: `store.signal('theme', 'light')` reads it back once, writes it through\n * whenever it is set, and is an ordinary signal in between. Everything is JSON, because both\n * stores hold strings and a store that silently held `\"[object Object]\"` would be worse than one\n * that held nothing.\n *\n * Two services rather than one because the difference matters and should be visible at the\n * injection site, and two entry points because each needs its own native module: Metro fails a\n * build on a `require` it cannot resolve, so one entry point for both made an app install both. `SecureStorage` is the keychain and the Android keystore: small values, an\n * async write, and a size limit that is not documented but is real. `Storage` is a plain\n * key-value file: bigger, faster, and readable by anyone with the device unlocked. Two tokens\n * over one class, for the same reason the sensors are.\n */\nimport { computed, signal, type Signal, type WritableSignal } from '@angular/core';\nimport { present } from './native.ts';\n\n/** What both `AsyncStorage` and `SecureStore` offer, reduced to the three verbs. */\nexport interface NativeStore {\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n remove(key: string): Promise<void>;\n /** Where the platform can answer without waiting - `SecureStore` can, `AsyncStorage` cannot. */\n getSync?(key: string): string | null;\n}\n\n/** A key's signal, with what `remove()` needs to put it back. */\ninterface Binding {\n readonly value: WritableSignal<unknown>;\n /** The signal's own `set`, which does not write through. */\n readonly reset: (value: unknown) => void;\n readonly initial: unknown;\n}\n\nexport class Store {\n private readonly native: NativeStore | null;\n private readonly bound = new Map<string, Binding>();\n /** Keys written by the app before their read came back. The read must not undo a write. */\n private readonly written = new Set<string>();\n /**\n * Updates made to a key whose read has not come back, in order. An update changes the stored\n * value rather than replacing it, so these wait for it and are applied to it, not to `initial`.\n */\n private readonly waiting = new Map<string, ((current: unknown) => unknown)[]>();\n /** Reads started and not yet back, successful or not. */\n private readonly reading = signal(0);\n private readonly failure = signal<Error | null>(null);\n /** Writes sent to the platform and not yet answered. What `flush()` waits for. */\n private readonly writes = new Set<Promise<void>>();\n /** The first write to fail since the last `flush()`, held until a flush reports it. */\n private unreported: { readonly error: unknown } | null = null;\n\n constructor(native: NativeStore | null) {\n this.native = native;\n }\n\n /**\n * Whether every key asked for so far has been read back. False while any read is outstanding,\n * so it goes back to false when a new key is asked for, and true again once that read returns.\n *\n * A read that fails still counts as back: the signal keeps its `initial`, and the failure is on\n * `error`. A loading screen held on `ready` should not wait forever for a store that is broken.\n */\n readonly ready: Signal<boolean> = computed(() => this.reading() === 0);\n\n /**\n * The most recent read or write that failed, or null while none has.\n *\n * A write through `set()` has no promise to reject, so this is where a full disk or a locked\n * keychain is reported. `flush()` is the way to wait on the writes themselves.\n */\n readonly error: Signal<Error | null> = this.failure.asReadonly();\n\n /**\n * The stored value at `key`, as a signal that writes through when set.\n *\n * Starts at `initial` and stays there until the read returns, which is one turn away at best -\n * unless the platform can answer synchronously, in which case it never shows `initial` at all.\n * The same key always returns the same signal, so two components binding to it stay in step.\n */\n signal<T>(key: string, initial: T): WritableSignal<T> {\n // A read is a use: a missing module says what to install on every one, before a key is kept.\n if (this.native && !present(this.native)) void this.native.get(key);\n const existing = this.bound.get(key);\n if (existing) return existing.value as WritableSignal<T>;\n\n const stored = this.readSync(key);\n const value = signal<T>(stored === ABSENT ? initial : (stored as T));\n\n const set = value.set.bind(value);\n const update = value.update.bind(value);\n value.set = (next: T) => {\n this.written.add(key);\n this.waiting.delete(key);\n set(next);\n this.write(key, next);\n };\n value.update = (fn: (current: T) => T) => {\n // Shown at once, on what is known so far, and written once the stored value is known.\n const waiting = this.waiting.get(key);\n update(fn);\n if (waiting) {\n waiting.push(fn as (current: unknown) => unknown);\n return;\n }\n this.written.add(key);\n this.write(key, value());\n };\n this.bound.set(key, {\n value: value as WritableSignal<unknown>,\n reset: set as (value: unknown) => void,\n initial,\n });\n\n // A platform that answered synchronously has said what is stored; one that cannot has not.\n if (stored === ABSENT && this.native && !this.native.getSync) this.waiting.set(key, []);\n void this.hydrate(key, set, initial);\n return value;\n }\n\n /**\n * Forget a key, in the store and in the signal bound to it.\n *\n * The signal goes back to the `initial` it was first asked for with, and stays the one signal\n * for the key. Rejects if the platform could not remove it, which also sets `error`.\n */\n async remove(key: string): Promise<void> {\n this.written.add(key);\n this.waiting.delete(key);\n const binding = this.bound.get(key);\n binding?.reset(binding.initial);\n await this.track(this.native?.remove(key));\n }\n\n /**\n * Wait for every write sent so far. Rejects with the first of them that failed since the last\n * `flush()`, whether it failed before this call or while waiting, so a screen that has to know a\n * value reached the disk - before signing out, say - can find out. Each failure is reported by\n * one flush only.\n */\n async flush(): Promise<void> {\n await Promise.allSettled([...this.writes]);\n const failed = this.unreported;\n this.unreported = null;\n if (failed) throw failed.error;\n }\n\n private readSync(key: string): unknown {\n let raw: string | null | undefined;\n try {\n raw = this.native?.getSync?.(key);\n } catch (error) {\n this.fail(error);\n return ABSENT;\n }\n return raw == null ? ABSENT : decode(raw);\n }\n\n /**\n * Read the stored value in, unless the app has already set one.\n *\n * The race is real and silent: a screen that writes a preference during startup would otherwise\n * have it overwritten a tick later by whatever was there before.\n */\n private async hydrate<T>(key: string, set: (value: T) => void, initial: T): Promise<void> {\n this.reading.update((n) => n + 1);\n let stored: unknown = ABSENT;\n try {\n const raw = await this.native?.get(key);\n if (raw != null) stored = decode(raw);\n } catch (error) {\n this.fail(error);\n } finally {\n this.settle(key, set, stored === ABSENT ? initial : stored, stored !== ABSENT);\n this.reading.update((n) => n - 1);\n }\n }\n\n /** The read is back: show what was stored, with any updates that waited for it applied. */\n private settle<T>(key: string, set: (value: T) => void, base: unknown, found: boolean): void {\n const updates = this.waiting.get(key);\n this.waiting.delete(key);\n if (this.written.has(key)) return;\n if (!updates?.length) {\n if (found) set(base as T);\n return;\n }\n const value = updates.reduce((current, fn) => fn(current), base);\n this.written.add(key);\n set(value as T);\n this.write(key, value);\n }\n\n private write(key: string, value: unknown): void {\n const sent =\n value === undefined ? this.native?.remove(key) : this.native?.set(key, JSON.stringify(value));\n // Observed here, so a failure lands on `error` and in `flush()` rather than going unhandled.\n this.track(sent).catch(() => {});\n }\n\n /** A platform call, kept until it answers, with its failure reported on `error`. */\n private track(sent: Promise<void> | undefined): Promise<void> {\n if (!sent) return Promise.resolve();\n const tracked = sent.then(\n () => undefined,\n (error: unknown) => {\n this.fail(error);\n this.unreported ??= { error };\n throw error;\n },\n );\n this.writes.add(tracked);\n const forget = () => void this.writes.delete(tracked);\n tracked.then(forget, forget);\n return tracked;\n }\n\n private fail(error: unknown): void {\n this.failure.set(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/** What `decode` answers for a string that is not JSON, since `null` is a value JSON can hold. */\nconst ABSENT: unique symbol = Symbol('absent');\n\n/**\n * A stored string as the value it encodes.\n *\n * A value that will not parse is treated as absent rather than thrown: it is a store written by\n * an older version of the app, and the right answer is the default the caller already supplied.\n * A stored `null` is not that: it parses, and a nullable signal gets it back.\n */\nfunction decode(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return ABSENT;\n }\n}\n"]}
|
package/dist/tracking.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* identifier.
|
|
19
19
|
*/
|
|
20
20
|
import { InjectionToken, Service, inject } from '@angular/core';
|
|
21
|
-
import { expoModule } from "./native.js";
|
|
21
|
+
import { expoModule, present } from "./native.js";
|
|
22
22
|
import { Permission, UNAVAILABLE } from "./permissions.js";
|
|
23
23
|
import * as i0 from "@angular/core";
|
|
24
24
|
export class Tracking {
|
|
@@ -34,7 +34,7 @@ export class Tracking {
|
|
|
34
34
|
permission = Permission.of(() => this.native?.getTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE), () => this.native?.requestTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE));
|
|
35
35
|
/** Whether the device has the tracking API. Where it does not, the permission answers granted. */
|
|
36
36
|
get available() {
|
|
37
|
-
return this.native?.isAvailable() ?? false;
|
|
37
|
+
return present(this.native)?.isAvailable() ?? false;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* The advertising identifier: the IDFA on iOS, the advertising ID on Android. Null on iOS until
|
package/dist/tracking.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracking.js","sourceRoot":"","sources":["../src/tracking.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tracking.js","sourceRoot":"","sources":["../src/tracking.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;;AAc3D,MAAM,OAAO,QAAQ;IACnB,yEAAyE;IACzE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,+BAA+B,EAC/B;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CACR,4BAA4B,EAC5B,GAAG,EAAE,CAAC,OAAO,CAAC,4BAA4B,CAAS,CACpD;KACJ,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAElD;;;OAGG;IACM,UAAU,GAAe,UAAU,CAAC,EAAE,CAC7C,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,2BAA2B,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAClF,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,+BAA+B,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CACvF,CAAC;IAEF,kGAAkG;IAClG,IAAI,SAAS;QACX,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,IAAI,KAAK,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,aAAa;QACX,OAAO,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,IAAI,CAAC;IACjD,CAAC;uGAnCU,QAAQ;wGAAR,QAAQ;;2FAAR,QAAQ;kBADpB,OAAO","sourcesContent":["/**\n * `Tracking`, bound to `expo-tracking-transparency`: Apple's App Tracking Transparency permission.\n *\n * ```ts\n * private readonly tracking = inject(Tracking);\n *\n * async startAnalytics(): Promise<void> {\n * const allowed = await this.tracking.permission.ensure();\n * this.analytics.start({ advertisingId: allowed ? this.tracking.advertisingId() : null });\n * }\n * ```\n *\n * iOS 14.5 and later ask before an app may track the user across other companies' apps and\n * websites, and hand out the advertising identifier only once the user has said yes. The question\n * is asked once: after an answer, the platform shows no dialog again, so `blocked()` is how an app\n * knows to offer Settings instead. Android and the web have no such permission, and the module\n * answers granted there. Without the module installed, the permission is refused and there is no\n * identifier.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule, present } from './native.ts';\nimport { Permission, UNAVAILABLE } from './permissions.ts';\n\ntype Expo = typeof import('expo-tracking-transparency');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeTracking = Pick<\n Expo,\n | 'getTrackingPermissionsAsync'\n | 'requestTrackingPermissionsAsync'\n | 'isAvailable'\n | 'getAdvertisingId'\n>;\n\n@Service()\nexport class Tracking {\n /** Overridden in a test to answer a tracking prompt nobody was shown. */\n static readonly SOURCE = new InjectionToken<NativeTracking | null>(\n 'angular-native.trackingSource',\n {\n factory: () =>\n expoModule(\n 'expo-tracking-transparency',\n () => require('expo-tracking-transparency') as Expo,\n ),\n },\n );\n\n private readonly native = inject(Tracking.SOURCE);\n\n /**\n * The permission to track. `ensure()` shows Apple's dialog only if it has not been answered;\n * the text in it is `NSUserTrackingUsageDescription`, set through the module's config plugin.\n */\n readonly permission: Permission = Permission.of(\n () => this.native?.getTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestTrackingPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** Whether the device has the tracking API. Where it does not, the permission answers granted. */\n get available(): boolean {\n return present(this.native)?.isAvailable() ?? false;\n }\n\n /**\n * The advertising identifier: the IDFA on iOS, the advertising ID on Android. Null on iOS until\n * tracking is allowed, in the simulator, and on Android with ad tracking limited.\n */\n advertisingId(): string | null {\n return this.native?.getAdvertisingId() ?? null;\n }\n}\n"]}
|