@ng-native/expo 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +4 -0
- package/android/src/main/java/dev/ngnative/expo/SearchFieldModule.kt +172 -0
- package/dist/app-info.d.ts +18 -15
- package/dist/app-info.js +17 -5
- package/dist/app-info.js.map +1 -1
- package/dist/apple-sign-in.js +5 -4
- package/dist/apple-sign-in.js.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/assets.js.map +1 -1
- package/dist/async-storage.js +4 -1
- package/dist/async-storage.js.map +1 -1
- package/dist/audio.js +2 -2
- package/dist/audio.js.map +1 -1
- package/dist/battery.js +4 -1
- package/dist/battery.js.map +1 -1
- package/dist/biometrics.js +5 -4
- package/dist/biometrics.js.map +1 -1
- package/dist/brightness.js +7 -2
- package/dist/brightness.js.map +1 -1
- package/dist/camera.d.ts +28 -2
- package/dist/camera.js +30 -3
- package/dist/camera.js.map +1 -1
- package/dist/clipboard.js +5 -2
- package/dist/clipboard.js.map +1 -1
- package/dist/database.js +2 -2
- package/dist/database.js.map +1 -1
- package/dist/expo-ui-components.d.ts +127 -12
- package/dist/expo-ui-components.js +354 -48
- package/dist/expo-ui-components.js.map +1 -1
- package/dist/expo-ui.js +7 -1
- package/dist/expo-ui.js.map +1 -1
- package/dist/foldable.d.ts +2 -0
- package/dist/foldable.js +15 -5
- package/dist/foldable.js.map +1 -1
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +12 -14
- package/dist/fonts.js.map +1 -1
- package/dist/haptics.js +2 -2
- package/dist/haptics.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/language-model.js +7 -3
- package/dist/language-model.js.map +1 -1
- package/dist/native.d.ts +17 -2
- package/dist/native.js +61 -3
- package/dist/native.js.map +1 -1
- package/dist/network.js +4 -1
- package/dist/network.js.map +1 -1
- package/dist/notifications.js +35 -8
- package/dist/notifications.js.map +1 -1
- package/dist/observed.js +5 -1
- package/dist/observed.js.map +1 -1
- package/dist/orientation.js +4 -1
- package/dist/orientation.js.map +1 -1
- package/dist/player.d.ts +9 -2
- package/dist/player.js +3 -2
- package/dist/player.js.map +1 -1
- package/dist/register-expo-view.d.ts +25 -2
- package/dist/register-expo-view.js +28 -7
- package/dist/register-expo-view.js.map +1 -1
- package/dist/screen-capture.js +2 -2
- package/dist/screen-capture.js.map +1 -1
- package/dist/search-field.d.ts +85 -0
- package/dist/search-field.js +160 -0
- package/dist/search-field.js.map +1 -0
- package/dist/secure-store.js +4 -1
- package/dist/secure-store.js.map +1 -1
- package/dist/sensors.js +6 -3
- package/dist/sensors.js.map +1 -1
- package/dist/splash-screen.js +2 -2
- package/dist/splash-screen.js.map +1 -1
- package/dist/store.js +4 -0
- package/dist/store.js.map +1 -1
- package/dist/tracking.js +2 -2
- package/dist/tracking.js.map +1 -1
- package/dist/ui-bottom-sheet-view.d.ts +3 -1
- package/dist/ui-bottom-sheet-view.js +6 -2
- package/dist/ui-bottom-sheet-view.js.map +1 -1
- package/dist/ui-font-names.d.ts +16 -0
- package/dist/ui-font-names.js +71 -0
- package/dist/ui-font-names.js.map +1 -0
- package/dist/updates.js +4 -1
- package/dist/updates.js.map +1 -1
- package/dist/video.js +3 -3
- package/dist/video.js.map +1 -1
- package/dist/watch.js +6 -5
- package/dist/watch.js.map +1 -1
- package/expo-module.config.json +5 -2
- package/ios/ExpoUIModifiersModule.swift +25 -0
- package/ios/InlineNavigationTitle.swift +12 -0
- package/ios/NgNativeExpo.podspec +3 -0
- package/ios/SearchField.swift +200 -0
- package/ios/SegmentedFont.swift +113 -0
- package/package.json +6 -2
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo's native views, driven directly.
|
|
3
|
+
*
|
|
4
|
+
* An Expo module that renders something registers a Fabric component whose name it derives
|
|
5
|
+
* rather than declares, and `requireNativeViewManager` is the only place that derivation is
|
|
6
|
+
* written down. Everything else that function does is React: it caches a host component, builds
|
|
7
|
+
* a view config for React's renderer, and wraps the result in a class whose whole job is to hold
|
|
8
|
+
* a ref. None of that is reachable without React, and none of it is needed - the engine writes
|
|
9
|
+
* props onto the shadow node itself, and Expo's Fabric views take their props as an untyped map,
|
|
10
|
+
* so there is no codegen'd prop list to satisfy.
|
|
11
|
+
*
|
|
12
|
+
* What is left is the name. That is what this package is.
|
|
13
|
+
*
|
|
14
|
+
* No decorators and no Angular here on purpose: this is a registration, and the components it
|
|
15
|
+
* registers are ordinary elements in a template.
|
|
16
|
+
*/
|
|
17
|
+
import { type PlatformViewName, type ViewNameOptions } from '@ng-native/fabric';
|
|
1
18
|
/**
|
|
2
19
|
* The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes
|
|
3
20
|
* it in `expo-modules-core`.
|
|
@@ -12,8 +29,12 @@
|
|
|
12
29
|
*/
|
|
13
30
|
export declare function expoViewName(moduleName: string, viewName?: string): string;
|
|
14
31
|
export interface ExpoViewOptions {
|
|
15
|
-
/**
|
|
16
|
-
|
|
32
|
+
/**
|
|
33
|
+
* A named view on a module that has several. Omit for the module's default view. A module whose
|
|
34
|
+
* view is named differently on each platform names both: `expo-video`'s is `VideoView` on iOS
|
|
35
|
+
* and `SurfaceVideoView` on Android.
|
|
36
|
+
*/
|
|
37
|
+
readonly viewName?: string | PlatformViewName;
|
|
17
38
|
/**
|
|
18
39
|
* Props the module's React component would have applied before native saw them. Expo's
|
|
19
40
|
* wrappers do less of this than React Native's, but they do some: `expo-image` resolves a
|
|
@@ -27,6 +48,8 @@ export interface ExpoViewOptions {
|
|
|
27
48
|
* reads `text`. The text written inside the element becomes that prop. See `ViewNameOptions`.
|
|
28
49
|
*/
|
|
29
50
|
readonly textContent?: string;
|
|
51
|
+
/** Rewrites the fonts the view's own props name. See `ViewNameOptions`. */
|
|
52
|
+
readonly fontNames?: ViewNameOptions['fontNames'];
|
|
30
53
|
}
|
|
31
54
|
/**
|
|
32
55
|
* Teach the engine an element name for an Expo module's view, e.g.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* registers are ordinary elements in a template.
|
|
16
16
|
*/
|
|
17
17
|
import { registerViewName } from '@ng-native/fabric';
|
|
18
|
-
import { optional } from "./native.js";
|
|
18
|
+
import { currentPlatform, optional } from "./native.js";
|
|
19
19
|
/**
|
|
20
20
|
* The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes
|
|
21
21
|
* it in `expo-modules-core`.
|
|
@@ -42,11 +42,22 @@ export function expoViewName(moduleName, viewName) {
|
|
|
42
42
|
* module installed so the native side registers the component; this is the JavaScript half only.
|
|
43
43
|
*/
|
|
44
44
|
export function registerExpoView(elementName, moduleName, options) {
|
|
45
|
-
|
|
45
|
+
const viewName = options?.viewName;
|
|
46
|
+
registerViewName(elementName, typeof viewName === 'object'
|
|
47
|
+
? {
|
|
48
|
+
ios: expoViewName(moduleName, viewName.ios),
|
|
49
|
+
android: expoViewName(moduleName, viewName.android),
|
|
50
|
+
}
|
|
51
|
+
: expoViewName(moduleName, viewName), options?.defaultProps, {
|
|
46
52
|
textContent: options?.textContent,
|
|
47
53
|
yieldsToComponents: options?.yieldsToComponents,
|
|
54
|
+
fontNames: options?.fontNames,
|
|
48
55
|
});
|
|
49
|
-
optional(() => require('expo').requireNativeView(moduleName,
|
|
56
|
+
optional(() => require('expo').requireNativeView(moduleName, typeof viewName === 'object'
|
|
57
|
+
? currentPlatform() === 'android'
|
|
58
|
+
? viewName.android
|
|
59
|
+
: viewName.ios
|
|
60
|
+
: viewName));
|
|
50
61
|
}
|
|
51
62
|
/**
|
|
52
63
|
* The views worth knowing the names of, so an app does not have to look each one up.
|
|
@@ -62,10 +73,20 @@ export function registerExpoView(elementName, moduleName, options) {
|
|
|
62
73
|
export const EXPO_VIEWS = {
|
|
63
74
|
/** `expo-image`. Takes `source` as a list, so a single `{uri}` is wrapped by the caller. */
|
|
64
75
|
'expo-image': ['ExpoImage', { defaultProps: { contentFit: 'cover' } }],
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
/**
|
|
77
|
+
* `expo-blur`. The one thing CSS cannot express: a blur of what is *behind* a view. The view is
|
|
78
|
+
* `ExpoBlurView` on the `ExpoBlur` module, as `expo-blur`'s own `NativeBlurModule` asks for it.
|
|
79
|
+
*/
|
|
80
|
+
'expo-blur': [
|
|
81
|
+
'ExpoBlur',
|
|
82
|
+
{ viewName: 'ExpoBlurView', defaultProps: { intensity: 50, tint: 'default' } },
|
|
83
|
+
],
|
|
84
|
+
/**
|
|
85
|
+
* `expo-video`. The player itself is a shared object the module hands out; this is its view,
|
|
86
|
+
* which has another name on Android, as `expo-video`'s own `NativeVideoView` asks for it. Bind
|
|
87
|
+
* `player` to the player's `id` (`videoPlayer`'s), not to the player.
|
|
88
|
+
*/
|
|
89
|
+
'expo-video': ['ExpoVideo', { viewName: { ios: 'VideoView', android: 'SurfaceVideoView' } }],
|
|
69
90
|
/**
|
|
70
91
|
* `expo-camera`. Its module's first view, so its default: `ExpoCamera_CameraView` exists only on
|
|
71
92
|
* iOS, after the Swift class, and commits as nothing on Android. `Camera` takes its pictures.
|
|
@@ -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 /** 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"]}
|
|
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, type PlatformViewName, type ViewNameOptions } from '@ng-native/fabric';\nimport { currentPlatform, 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 /**\n * A named view on a module that has several. Omit for the module's default view. A module whose\n * view is named differently on each platform names both: `expo-video`'s is `VideoView` on iOS\n * and `SurfaceVideoView` on Android.\n */\n readonly viewName?: string | PlatformViewName;\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 /** Rewrites the fonts the view's own props name. See `ViewNameOptions`. */\n readonly fontNames?: ViewNameOptions['fontNames'];\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 const viewName = options?.viewName;\n registerViewName(\n elementName,\n typeof viewName === 'object'\n ? {\n ios: expoViewName(moduleName, viewName.ios),\n android: expoViewName(moduleName, viewName.android),\n }\n : expoViewName(moduleName, viewName),\n options?.defaultProps,\n {\n textContent: options?.textContent,\n yieldsToComponents: options?.yieldsToComponents,\n fontNames: options?.fontNames,\n },\n );\n optional(() =>\n (\n require('expo') as { requireNativeView(module: string, view?: string): unknown }\n ).requireNativeView(\n moduleName,\n typeof viewName === 'object'\n ? currentPlatform() === 'android'\n ? viewName.android\n : viewName.ios\n : viewName,\n ),\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 /**\n * `expo-blur`. The one thing CSS cannot express: a blur of what is *behind* a view. The view is\n * `ExpoBlurView` on the `ExpoBlur` module, as `expo-blur`'s own `NativeBlurModule` asks for it.\n */\n 'expo-blur': [\n 'ExpoBlur',\n { viewName: 'ExpoBlurView', defaultProps: { intensity: 50, tint: 'default' } },\n ],\n /**\n * `expo-video`. The player itself is a shared object the module hands out; this is its view,\n * which has another name on Android, as `expo-video`'s own `NativeVideoView` asks for it. Bind\n * `player` to the player's `id` (`videoPlayer`'s), not to the player.\n */\n 'expo-video': ['ExpoVideo', { viewName: { ios: 'VideoView', android: 'SurfaceVideoView' } }],\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/screen-capture.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* stays at zero.
|
|
19
19
|
*/
|
|
20
20
|
import { DestroyRef, InjectionToken, Service, inject, signal } 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 ScreenCapture {
|
|
@@ -37,7 +37,7 @@ export class ScreenCapture {
|
|
|
37
37
|
/** How many screenshots the user has taken while the app was in front. */
|
|
38
38
|
screenshots = this.count.asReadonly();
|
|
39
39
|
constructor() {
|
|
40
|
-
const listener = this.native?.addScreenshotListener(() => this.count.update((n) => n + 1));
|
|
40
|
+
const listener = present(this.native)?.addScreenshotListener(() => this.count.update((n) => n + 1));
|
|
41
41
|
if (listener)
|
|
42
42
|
inject(DestroyRef).onDestroy(() => listener.remove());
|
|
43
43
|
}
|
|
@@ -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,85 @@
|
|
|
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
|
+
* Its type is the `font-family`, `font-size`, `font-weight` and `font-style` its CSS resolves to,
|
|
20
|
+
* as a `<text>`'s is.
|
|
21
|
+
*
|
|
22
|
+
* Expo Go cannot load native code of its own, so there the element draws nothing, and in
|
|
23
|
+
* development it says so once.
|
|
24
|
+
*/
|
|
25
|
+
import { InjectionToken } from '@angular/core';
|
|
26
|
+
import * as i0 from "@angular/core";
|
|
27
|
+
/** The functions the native module defines on the view, called with the view's tag as `this`. */
|
|
28
|
+
export interface SearchFieldFunctions {
|
|
29
|
+
focus?(this: {
|
|
30
|
+
nativeTag: number;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
blur?(this: {
|
|
33
|
+
nativeTag: number;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare class SearchField {
|
|
37
|
+
/**
|
|
38
|
+
* The functions native defines on the view, or null where the build has no native search
|
|
39
|
+
* field. Overridden in a test.
|
|
40
|
+
*/
|
|
41
|
+
static readonly SOURCE: InjectionToken<SearchFieldFunctions | null>;
|
|
42
|
+
/**
|
|
43
|
+
* The text in the field: typing writes it, and text set from code is put in the field. A
|
|
44
|
+
* `model()`, which is all `FormValueControl<string>` asks, so `[formField]` binds it.
|
|
45
|
+
*/
|
|
46
|
+
readonly value: import("@angular/core").ModelSignal<string>;
|
|
47
|
+
/** Refuse input and show the field dimmed. Signal Forms' `disabled()` rule sets it. */
|
|
48
|
+
readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
|
|
49
|
+
readonly placeholder: import("@angular/core").InputSignal<string | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* iOS: whether the Cancel button shows. Left unset it shows while the field has the keyboard,
|
|
52
|
+
* as in a search bar under a navigation bar.
|
|
53
|
+
*/
|
|
54
|
+
readonly showsCancelButton: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
55
|
+
readonly autoCapitalize: import("@angular/core").InputSignal<"none" | "words" | "sentences" | "characters" | undefined>;
|
|
56
|
+
/** The colour of the cursor and the Cancel button. */
|
|
57
|
+
readonly tintColor: import("@angular/core").InputSignal<string | undefined>;
|
|
58
|
+
/** The keyboard's search key was pressed, with the text in the field. */
|
|
59
|
+
readonly search: import("@angular/core").OutputEmitterRef<string>;
|
|
60
|
+
/** iOS: the Cancel button was pressed. The field is emptied and gives up the keyboard. */
|
|
61
|
+
readonly cancel: import("@angular/core").OutputEmitterRef<void>;
|
|
62
|
+
readonly searchFocus: import("@angular/core").OutputEmitterRef<void>;
|
|
63
|
+
readonly searchBlur: import("@angular/core").OutputEmitterRef<void>;
|
|
64
|
+
/** The field gave up the keyboard: what Signal Forms marks a field touched from. */
|
|
65
|
+
readonly touch: import("@angular/core").OutputEmitterRef<void>;
|
|
66
|
+
/**
|
|
67
|
+
* The keystroke the text answers, as React Native's text input counts them: native skips a text
|
|
68
|
+
* older than what has been typed since, which a fast typist's echo otherwise overwrites.
|
|
69
|
+
*/
|
|
70
|
+
protected readonly eventCount: import("@angular/core").WritableSignal<number>;
|
|
71
|
+
private readonly functions;
|
|
72
|
+
protected readonly missing: boolean;
|
|
73
|
+
private readonly node;
|
|
74
|
+
private readonly engine;
|
|
75
|
+
constructor();
|
|
76
|
+
/** Give the field the keyboard. Does nothing before the view has reached the screen. */
|
|
77
|
+
focus(): Promise<void>;
|
|
78
|
+
/** Take the keyboard away from the field. */
|
|
79
|
+
blur(): Promise<void>;
|
|
80
|
+
/** Empty the field, and the value with it. */
|
|
81
|
+
clear(): void;
|
|
82
|
+
private call;
|
|
83
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SearchField, never>;
|
|
84
|
+
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>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
* Its type is the `font-family`, `font-size`, `font-weight` and `font-style` its CSS resolves to,
|
|
20
|
+
* as a `<text>`'s is.
|
|
21
|
+
*
|
|
22
|
+
* Expo Go cannot load native code of its own, so there the element draws nothing, and in
|
|
23
|
+
* development it says so once.
|
|
24
|
+
*/
|
|
25
|
+
import { Component, booleanAttribute, DestroyRef, ElementRef, InjectionToken, inject, input, model, output, signal, } from '@angular/core';
|
|
26
|
+
import { Engine, HostEngine, nativePlatform, registerViewName, } from '@ng-native/fabric';
|
|
27
|
+
import { optional } from "./native.js";
|
|
28
|
+
import { expoViewName } from "./register-expo-view.js";
|
|
29
|
+
import { optionalBoolean } from "./transforms.js";
|
|
30
|
+
import * as i0 from "@angular/core";
|
|
31
|
+
const MODULE = 'NgNativeSearchField';
|
|
32
|
+
let warned = false;
|
|
33
|
+
/** Says once, in development, that the build has no native search field to draw. */
|
|
34
|
+
function warnMissing() {
|
|
35
|
+
if ((typeof ngDevMode !== 'undefined' && !ngDevMode) || warned)
|
|
36
|
+
return;
|
|
37
|
+
warned = true;
|
|
38
|
+
console.warn('[angular-native] <search-field> is native code of @ng-native/expo, which this build does not ' +
|
|
39
|
+
'have, so it draws nothing. Expo Go cannot load it: run a development build.');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The field's face by the PostScript name UIKit finds it by: expo-font registers it under the name
|
|
43
|
+
* the app gives it, which `UIFont(name:)` does not know. Android's font manager finds that name.
|
|
44
|
+
*/
|
|
45
|
+
function fieldFontName(props, postScriptName) {
|
|
46
|
+
const family = props['fontFamily'];
|
|
47
|
+
if (typeof family === 'string')
|
|
48
|
+
props['fontFamily'] = postScriptName(family) ?? family;
|
|
49
|
+
}
|
|
50
|
+
export class SearchField {
|
|
51
|
+
/**
|
|
52
|
+
* The functions native defines on the view, or null where the build has no native search
|
|
53
|
+
* field. Overridden in a test.
|
|
54
|
+
*/
|
|
55
|
+
static SOURCE = new InjectionToken('angular-native.searchFieldSource', {
|
|
56
|
+
factory: () => optional(() => {
|
|
57
|
+
const core = require('expo-modules-core');
|
|
58
|
+
const module = core.requireOptionalNativeModule(MODULE);
|
|
59
|
+
return module ? (module.ViewPrototypes?.[MODULE] ?? {}) : null;
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* The text in the field: typing writes it, and text set from code is put in the field. A
|
|
64
|
+
* `model()`, which is all `FormValueControl<string>` asks, so `[formField]` binds it.
|
|
65
|
+
*/
|
|
66
|
+
value = model('', /* @ts-ignore */
|
|
67
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
68
|
+
/** Refuse input and show the field dimmed. Signal Forms' `disabled()` rule sets it. */
|
|
69
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
70
|
+
placeholder = input(/* @ts-ignore */
|
|
71
|
+
...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
72
|
+
/**
|
|
73
|
+
* iOS: whether the Cancel button shows. Left unset it shows while the field has the keyboard,
|
|
74
|
+
* as in a search bar under a navigation bar.
|
|
75
|
+
*/
|
|
76
|
+
showsCancelButton = input(undefined, { ...(ngDevMode ? { debugName: "showsCancelButton" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
77
|
+
autoCapitalize = input(/* @ts-ignore */
|
|
78
|
+
...(ngDevMode ? [undefined, { debugName: "autoCapitalize" }] : /* istanbul ignore next */ []));
|
|
79
|
+
/** The colour of the cursor and the Cancel button. */
|
|
80
|
+
tintColor = input(/* @ts-ignore */
|
|
81
|
+
...(ngDevMode ? [undefined, { debugName: "tintColor" }] : /* istanbul ignore next */ []));
|
|
82
|
+
/** The keyboard's search key was pressed, with the text in the field. */
|
|
83
|
+
search = output();
|
|
84
|
+
/** iOS: the Cancel button was pressed. The field is emptied and gives up the keyboard. */
|
|
85
|
+
cancel = output();
|
|
86
|
+
searchFocus = output();
|
|
87
|
+
searchBlur = output();
|
|
88
|
+
/** The field gave up the keyboard: what Signal Forms marks a field touched from. */
|
|
89
|
+
touch = output();
|
|
90
|
+
/**
|
|
91
|
+
* The keystroke the text answers, as React Native's text input counts them: native skips a text
|
|
92
|
+
* older than what has been typed since, which a fast typist's echo otherwise overwrites.
|
|
93
|
+
*/
|
|
94
|
+
eventCount = signal(0, /* @ts-ignore */
|
|
95
|
+
...(ngDevMode ? [{ debugName: "eventCount" }] : /* istanbul ignore next */ []));
|
|
96
|
+
functions = inject(SearchField.SOURCE);
|
|
97
|
+
missing = !this.functions;
|
|
98
|
+
node = inject(ElementRef).nativeElement;
|
|
99
|
+
engine = inject(Engine, { optional: true });
|
|
100
|
+
constructor() {
|
|
101
|
+
registerViewName('search-field', expoViewName(MODULE), undefined, {
|
|
102
|
+
fontNames: nativePlatform() === 'ios' ? fieldFontName : undefined,
|
|
103
|
+
});
|
|
104
|
+
if (this.missing)
|
|
105
|
+
warnMissing();
|
|
106
|
+
const host = this.node.host ?? inject(HostEngine);
|
|
107
|
+
const listen = (event, handle) => host.setEventListener(this.node, event, (raw) => handle(raw?.nativeEvent ?? {}));
|
|
108
|
+
const stops = [
|
|
109
|
+
listen('topChangeText', ({ text, eventCount }) => {
|
|
110
|
+
if (typeof eventCount === 'number')
|
|
111
|
+
this.eventCount.set(eventCount);
|
|
112
|
+
this.value.set(String(text ?? ''));
|
|
113
|
+
}),
|
|
114
|
+
// Named apart from the outputs: a native event of an output's name reaches its listener too.
|
|
115
|
+
listen('topSubmitSearch', ({ text }) => this.search.emit(String(text ?? this.value()))),
|
|
116
|
+
listen('topPressCancel', () => this.cancel.emit()),
|
|
117
|
+
listen('topBeginEditing', () => this.searchFocus.emit()),
|
|
118
|
+
listen('topEndEditing', () => {
|
|
119
|
+
this.searchBlur.emit();
|
|
120
|
+
this.touch.emit();
|
|
121
|
+
}),
|
|
122
|
+
];
|
|
123
|
+
inject(DestroyRef).onDestroy(() => stops.forEach((stop) => stop()));
|
|
124
|
+
}
|
|
125
|
+
/** Give the field the keyboard. Does nothing before the view has reached the screen. */
|
|
126
|
+
async focus() {
|
|
127
|
+
await this.call('focus');
|
|
128
|
+
}
|
|
129
|
+
/** Take the keyboard away from the field. */
|
|
130
|
+
async blur() {
|
|
131
|
+
await this.call('blur');
|
|
132
|
+
}
|
|
133
|
+
/** Empty the field, and the value with it. */
|
|
134
|
+
clear() {
|
|
135
|
+
this.value.set('');
|
|
136
|
+
}
|
|
137
|
+
async call(name) {
|
|
138
|
+
const nativeTag = this.engine?.tagOf(this.node);
|
|
139
|
+
if (nativeTag == null)
|
|
140
|
+
return;
|
|
141
|
+
await this.functions?.[name]?.call({ nativeTag });
|
|
142
|
+
}
|
|
143
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SearchField, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
144
|
+
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; }"] });
|
|
145
|
+
}
|
|
146
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SearchField, decorators: [{
|
|
147
|
+
type: Component,
|
|
148
|
+
args: [{ selector: 'search-field', template: '', host: {
|
|
149
|
+
'[text]': 'value()',
|
|
150
|
+
'[enabled]': '!disabled()',
|
|
151
|
+
'[mostRecentEventCount]': 'eventCount()',
|
|
152
|
+
'[placeholder]': 'placeholder()',
|
|
153
|
+
'[showsCancelButton]': 'showsCancelButton()',
|
|
154
|
+
'[autoCapitalize]': 'autoCapitalize()',
|
|
155
|
+
'[tintColor]': 'tintColor()',
|
|
156
|
+
// Fabric draws a view the build does not have as a placeholder box naming it.
|
|
157
|
+
'[style.display]': 'missing ? "none" : undefined',
|
|
158
|
+
}, styles: [":host { height: 56px; }"] }]
|
|
159
|
+
}], 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"] }] } });
|
|
160
|
+
//# 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 * Its type is the `font-family`, `font-size`, `font-weight` and `font-style` its CSS resolves to,\n * as a `<text>`'s is.\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 nativePlatform,\n type PostScriptNames,\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/**\n * The field's face by the PostScript name UIKit finds it by: expo-font registers it under the name\n * the app gives it, which `UIFont(name:)` does not know. Android's font manager finds that name.\n */\nfunction fieldFontName(props: Record<string, unknown>, postScriptName: PostScriptNames): void {\n const family = props['fontFamily'];\n if (typeof family === 'string') props['fontFamily'] = postScriptName(family) ?? family;\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), undefined, {\n fontNames: nativePlatform() === 'ios' ? fieldFontName : undefined,\n });\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"]}
|