@ng-native/expo 0.17.0 → 0.18.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/src/main/java/dev/ngnative/expo/KeyboardModule.kt +10 -1
- package/dist/audio.d.ts +37 -0
- package/dist/audio.js +90 -6
- package/dist/audio.js.map +1 -1
- package/dist/brightness.js +50 -9
- package/dist/brightness.js.map +1 -1
- package/dist/expo-ui-components.d.ts +45 -2
- package/dist/expo-ui-components.js +96 -5
- package/dist/expo-ui-components.js.map +1 -1
- package/dist/file-system.d.ts +4 -1
- package/dist/file-system.js +6 -2
- package/dist/file-system.js.map +1 -1
- package/dist/foldable.d.ts +36 -3
- package/dist/foldable.js +66 -13
- package/dist/foldable.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keep-awake.js +2 -2
- package/dist/keep-awake.js.map +1 -1
- package/dist/keyboard.js +15 -0
- package/dist/keyboard.js.map +1 -1
- package/dist/orientation.js +4 -3
- package/dist/orientation.js.map +1 -1
- package/dist/screen-capture.js +2 -2
- package/dist/screen-capture.js.map +1 -1
- package/dist/search-field.js +1 -1
- package/dist/search-field.js.map +1 -1
- package/dist/symbol.d.ts +3 -1
- package/dist/symbol.js +26 -7
- package/dist/symbol.js.map +1 -1
- package/dist/ui-bottom-sheet-view.d.ts +10 -1
- package/dist/ui-bottom-sheet-view.js +10 -2
- package/dist/ui-bottom-sheet-view.js.map +1 -1
- package/expo-module.config.json +6 -1
- package/ios/SymbolImageSize.swift +53 -0
- package/package.json +82 -78
package/dist/file-system.d.ts
CHANGED
|
@@ -19,7 +19,9 @@ export interface NativeFile {
|
|
|
19
19
|
overwrite?: boolean;
|
|
20
20
|
intermediates?: boolean;
|
|
21
21
|
}): void;
|
|
22
|
+
/** Synchronous up to SDK 57. From SDK 58 it answers a promise, and `writeSync` is synchronous. */
|
|
22
23
|
write(content: string | Uint8Array): void;
|
|
24
|
+
writeSync?(content: string | Uint8Array): void;
|
|
23
25
|
text(): Promise<string>;
|
|
24
26
|
textSync(): string;
|
|
25
27
|
/** The contents as bytes: a PDF, an image, anything that is not text. */
|
|
@@ -61,7 +63,8 @@ export declare class FileSystem {
|
|
|
61
63
|
/**
|
|
62
64
|
* Write text, or bytes for anything that is not text, whether or not the file is there already.
|
|
63
65
|
* `write` alone throws on a file that has never been created, which is the first thing everyone
|
|
64
|
-
* hits. It replaces what was there
|
|
66
|
+
* hits. It replaces what was there, and has written by the time it returns: from SDK 58 through
|
|
67
|
+
* `writeSync`, since `write` there answers a promise.
|
|
65
68
|
*/
|
|
66
69
|
write(file: NativeFile, content: string | Uint8Array): void;
|
|
67
70
|
private installed;
|
package/dist/file-system.js
CHANGED
|
@@ -60,12 +60,16 @@ export class FileSystem {
|
|
|
60
60
|
/**
|
|
61
61
|
* Write text, or bytes for anything that is not text, whether or not the file is there already.
|
|
62
62
|
* `write` alone throws on a file that has never been created, which is the first thing everyone
|
|
63
|
-
* hits. It replaces what was there
|
|
63
|
+
* hits. It replaces what was there, and has written by the time it returns: from SDK 58 through
|
|
64
|
+
* `writeSync`, since `write` there answers a promise.
|
|
64
65
|
*/
|
|
65
66
|
write(file, content) {
|
|
66
67
|
if (!file.exists)
|
|
67
68
|
file.create({ intermediates: true });
|
|
68
|
-
file.
|
|
69
|
+
if (file.writeSync)
|
|
70
|
+
file.writeSync(content);
|
|
71
|
+
else
|
|
72
|
+
file.write(content);
|
|
69
73
|
}
|
|
70
74
|
installed() {
|
|
71
75
|
if (!this.native) {
|
package/dist/file-system.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-system.js","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;;
|
|
1
|
+
{"version":3,"file":"file-system.js","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;;AAmCtD;;;;;;GAMG;AAEH,MAAM,OAAO,UAAU;IACrB,kEAAkE;IAClE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,iCAAiC,EACjC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,kBAAkB,EAClB,GAAG,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAsC,CACvE,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,OAAO;gBACL,6EAA6E;gBAC7E,IAAI,cAAc;oBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC1B,CAAC;gBACD,IAAI,iBAAiB;oBACnB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC7B,CAAC;gBACD,IAAI,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CACxB,IAAI,IAAI,CAAC,IAAI,CAAC,SAAgD,EAAE,IAAI,CAAC;gBACvE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;aACpC,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpD,wEAAwE;IACxE,KAAK,CAAC,IAAY;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,wDAAwD;IACxD,QAAQ,CAAC,IAAY;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,GAAW;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,4FAA4F,CAC7F,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAgB,EAAE,OAA4B;QAClD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;;YACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAEO,SAAS;QACf,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,WAAW,CAAC,kBAAkB,EAAE,4CAA4C,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;uGAxEU,UAAU;wGAAV,UAAU;;2FAAV,UAAU;kBADtB,OAAO","sourcesContent":["/**\n * `FileSystem`, bound to `expo-file-system`.\n *\n * Files located by what they are for rather than by path. `File` and `Paths` are Expo's own\n * classes over a native one, so they are used as published; this only decides which directory a\n * name lands in and hands the file back.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule, unavailable } from './native.ts';\n\n/**\n * A file. Structural on purpose: this is Expo's `File`, described in terms a test can satisfy, so\n * a fake needs no Expo. An app gets the real object back and may use the rest of its surface.\n */\nexport interface NativeFile {\n readonly uri: string;\n readonly exists: boolean;\n readonly size: number;\n create(options?: { overwrite?: boolean; intermediates?: boolean }): void;\n /** Synchronous up to SDK 57. From SDK 58 it answers a promise, and `writeSync` is synchronous. */\n write(content: string | Uint8Array): void;\n writeSync?(content: string | Uint8Array): void;\n text(): Promise<string>;\n textSync(): string;\n /** The contents as bytes: a PDF, an image, anything that is not text. */\n bytes(): Promise<Uint8Array>;\n delete(): void;\n}\n\n/** A directory, as far as this needs to know: something a file can be named inside. */\nexport type NativeDirectory = object;\n\n/** The slice of `expo-file-system` this needs. */\nexport interface NativeFiles {\n /** Cleared by the system under storage pressure. */\n readonly cacheDirectory: NativeDirectory;\n /** Survives, and is backed up. */\n readonly documentDirectory: NativeDirectory;\n file(directory: NativeDirectory, name: string): NativeFile;\n /** The file at a uri, such as one a picker answers with. */\n fileAt?(uri: string): NativeFile;\n}\n\n/**\n * The two directories an app actually chooses between, plus the create-then-write dance that\n * writing a file for the first time needs.\n *\n * ponytail: a locator, not a file API. Everything else - moving, listing, downloading, streaming\n * - is on the object this hands back, documented by Expo, and not worth restating here.\n */\n@Service()\nexport class FileSystem {\n /** Overridden in a test to write files that are not on a disk. */\n static readonly SOURCE = new InjectionToken<NativeFiles | null>(\n 'angular-native.fileSystemSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-file-system',\n () => require('expo-file-system') as typeof import('expo-file-system'),\n );\n if (!expo) return null;\n return {\n // Both are getters on Expo's side, handing back a fresh Directory each time.\n get cacheDirectory() {\n return expo.Paths.cache;\n },\n get documentDirectory() {\n return expo.Paths.document;\n },\n file: (directory, name) =>\n new expo.File(directory as InstanceType<typeof expo.Directory>, name),\n fileAt: (uri) => new expo.File(uri),\n };\n },\n },\n );\n\n private readonly native = inject(FileSystem.SOURCE);\n\n /** A file the system may delete when the device runs low on storage. */\n cache(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.cacheDirectory, name);\n }\n\n /** A file that survives, and is included in backups. */\n document(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.documentDirectory, name);\n }\n\n /**\n * The file at a uri: a document the user picked, a photo from the library, a download. What a\n * picker answers with is a uri, and this is the file to read from it.\n */\n file(uri: string): NativeFile {\n const native = this.installed();\n if (!native.fileAt) {\n throw new Error(\n '[angular-native] this FileSystem.SOURCE has no fileAt(uri), which file() opens a uri with.',\n );\n }\n return native.fileAt(uri);\n }\n\n /**\n * Write text, or bytes for anything that is not text, whether or not the file is there already.\n * `write` alone throws on a file that has never been created, which is the first thing everyone\n * hits. It replaces what was there, and has written by the time it returns: from SDK 58 through\n * `writeSync`, since `write` there answers a promise.\n */\n write(file: NativeFile, content: string | Uint8Array): void {\n if (!file.exists) file.create({ intermediates: true });\n if (file.writeSync) file.writeSync(content);\n else file.write(content);\n }\n\n private installed(): NativeFiles {\n if (!this.native) {\n throw unavailable('expo-file-system', 'Provide FileSystem.SOURCE with a stand-in.');\n }\n return this.native;\n }\n}\n"]}
|
package/dist/foldable.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `Foldable`, bound to `expo-foldables`: the hinge of a foldable device, an iPhone
|
|
3
|
-
* Android foldable.
|
|
2
|
+
* `Foldable`, bound to `expo-hinge` and `expo-foldables`: the hinge of a foldable device, an iPhone
|
|
3
|
+
* Duo or an Android foldable.
|
|
4
|
+
*
|
|
5
|
+
* The posture and the angle come from `expo-hinge` (Expo SDK 58) while it reports a hinge, and from
|
|
6
|
+
* `expo-foldables` otherwise. The fold comes from `expo-foldables` only, since `expo-hinge` does not
|
|
7
|
+
* say where the fold crosses the window.
|
|
4
8
|
*
|
|
5
9
|
* On a phone that does not fold, on the web and in a test, it answers like a phone: not available,
|
|
6
10
|
* `'unknown'`, no fold and no angle.
|
|
@@ -44,14 +48,38 @@ export interface NativeHinge {
|
|
|
44
48
|
minDeltaDegrees?: number;
|
|
45
49
|
}): Subscription;
|
|
46
50
|
}
|
|
51
|
+
/** What `expo-hinge` reports: the angle in degrees, and how far open the hinge is. */
|
|
52
|
+
export interface ExpoHingeReading {
|
|
53
|
+
readonly angle: number;
|
|
54
|
+
readonly status: 'closed' | 'partiallyOpen' | 'fullyOpen' | 'unknown';
|
|
55
|
+
}
|
|
56
|
+
/** `expo-hinge`, as `Foldable.HINGE` provides it. A test passes a stand-in. */
|
|
57
|
+
export interface ExpoHinge {
|
|
58
|
+
/** Null on a device without a hinge, or an OS that cannot report one. */
|
|
59
|
+
getHinge(): ExpoHingeReading | null;
|
|
60
|
+
addHingeListener(listener: (event: {
|
|
61
|
+
hinge: ExpoHingeReading | null;
|
|
62
|
+
}) => void): Subscription;
|
|
63
|
+
}
|
|
47
64
|
export declare class Foldable {
|
|
48
65
|
static readonly SOURCE: InjectionToken<NativeHinge | null>;
|
|
66
|
+
/**
|
|
67
|
+
* `expo-hinge`, reached through its native module rather than its package, so an app without it
|
|
68
|
+
* still builds. Null where it is not in the app.
|
|
69
|
+
*/
|
|
70
|
+
static readonly HINGE: InjectionToken<ExpoHinge | null>;
|
|
49
71
|
private readonly native;
|
|
72
|
+
private readonly expoHinge;
|
|
50
73
|
private readonly state;
|
|
74
|
+
private readonly reading;
|
|
51
75
|
private readonly angleState;
|
|
52
76
|
private angleSubscription;
|
|
53
77
|
/** Whether the device reports a hinge. It can turn true shortly after launch. */
|
|
54
78
|
readonly available: Signal<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* `expo-foldables`' posture wherever `expo-hinge` cannot say, as on Android while the window does
|
|
81
|
+
* not span the fold: `expo-foldables` works a closed device out from its angle.
|
|
82
|
+
*/
|
|
55
83
|
readonly posture: Signal<HingePosture>;
|
|
56
84
|
/**
|
|
57
85
|
* Where the fold crosses the window, in window coordinates: a view that does not start at the
|
|
@@ -68,7 +96,12 @@ export declare class Foldable {
|
|
|
68
96
|
/** Half open with the fold running across, like a laptop. */
|
|
69
97
|
readonly tabletop: Signal<boolean>;
|
|
70
98
|
constructor();
|
|
71
|
-
|
|
99
|
+
private followExpoHinge;
|
|
100
|
+
private followFoldables;
|
|
101
|
+
/**
|
|
102
|
+
* The hinge's state, read as a use: a module missing from the build says what to install, unless
|
|
103
|
+
* `expo-hinge` is there to answer instead.
|
|
104
|
+
*/
|
|
72
105
|
private known;
|
|
73
106
|
private update;
|
|
74
107
|
private stopFollowingAngle;
|
package/dist/foldable.js
CHANGED
|
@@ -1,27 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `Foldable`, bound to `expo-foldables`: the hinge of a foldable device, an iPhone
|
|
3
|
-
* Android foldable.
|
|
2
|
+
* `Foldable`, bound to `expo-hinge` and `expo-foldables`: the hinge of a foldable device, an iPhone
|
|
3
|
+
* Duo or an Android foldable.
|
|
4
|
+
*
|
|
5
|
+
* The posture and the angle come from `expo-hinge` (Expo SDK 58) while it reports a hinge, and from
|
|
6
|
+
* `expo-foldables` otherwise. The fold comes from `expo-foldables` only, since `expo-hinge` does not
|
|
7
|
+
* say where the fold crosses the window.
|
|
4
8
|
*
|
|
5
9
|
* On a phone that does not fold, on the web and in a test, it answers like a phone: not available,
|
|
6
10
|
* `'unknown'`, no fold and no angle.
|
|
7
11
|
*/
|
|
8
12
|
import { DestroyRef, InjectionToken, Service, computed, inject, signal, } from '@angular/core';
|
|
9
|
-
import { expoModule, present } from "./native.js";
|
|
13
|
+
import { expoModule, optional, present } from "./native.js";
|
|
10
14
|
import * as i0 from "@angular/core";
|
|
15
|
+
const POSTURES = {
|
|
16
|
+
closed: 'closed',
|
|
17
|
+
partiallyOpen: 'partially-open',
|
|
18
|
+
fullyOpen: 'fully-open',
|
|
19
|
+
unknown: 'unknown',
|
|
20
|
+
};
|
|
11
21
|
export class Foldable {
|
|
12
22
|
static SOURCE = new InjectionToken('angular-native.hingeSource', {
|
|
13
23
|
factory: () => expoModule('expo-foldables', () => require('expo-foldables').Hinge),
|
|
14
24
|
});
|
|
25
|
+
/**
|
|
26
|
+
* `expo-hinge`, reached through its native module rather than its package, so an app without it
|
|
27
|
+
* still builds. Null where it is not in the app.
|
|
28
|
+
*/
|
|
29
|
+
static HINGE = new InjectionToken('angular-native.expoHinge', {
|
|
30
|
+
factory: () => {
|
|
31
|
+
const module = optional(() => require('expo-modules-core').requireOptionalNativeModule('ExpoHinge'));
|
|
32
|
+
if (!module)
|
|
33
|
+
return null;
|
|
34
|
+
return {
|
|
35
|
+
getHinge: () => module.getHinge?.() ?? null,
|
|
36
|
+
addHingeListener: (listener) => module.addListener?.('hingeChange', listener) ?? { remove: () => { } },
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
});
|
|
15
40
|
native = inject(Foldable.SOURCE);
|
|
41
|
+
expoHinge = inject(Foldable.HINGE);
|
|
16
42
|
state = signal(undefined, /* @ts-ignore */
|
|
17
43
|
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
44
|
+
reading = signal(null, /* @ts-ignore */
|
|
45
|
+
...(ngDevMode ? [{ debugName: "reading" }] : /* istanbul ignore next */ []));
|
|
18
46
|
angleState = signal(null, /* @ts-ignore */
|
|
19
47
|
...(ngDevMode ? [{ debugName: "angleState" }] : /* istanbul ignore next */ []));
|
|
20
48
|
angleSubscription = null;
|
|
21
49
|
/** Whether the device reports a hinge. It can turn true shortly after launch. */
|
|
22
|
-
available = computed(() => this.state() !== undefined, /* @ts-ignore */
|
|
50
|
+
available = computed(() => this.reading() !== null || this.state() !== undefined, /* @ts-ignore */
|
|
23
51
|
...(ngDevMode ? [{ debugName: "available" }] : /* istanbul ignore next */ []));
|
|
24
|
-
|
|
52
|
+
/**
|
|
53
|
+
* `expo-foldables`' posture wherever `expo-hinge` cannot say, as on Android while the window does
|
|
54
|
+
* not span the fold: `expo-foldables` works a closed device out from its angle.
|
|
55
|
+
*/
|
|
56
|
+
posture = computed(() => {
|
|
57
|
+
const posture = POSTURES[this.reading()?.status ?? 'unknown'] ?? 'unknown';
|
|
58
|
+
return posture === 'unknown' ? (this.known()?.posture ?? 'unknown') : posture;
|
|
59
|
+
}, /* @ts-ignore */
|
|
25
60
|
...(ngDevMode ? [{ debugName: "posture" }] : /* istanbul ignore next */ []));
|
|
26
61
|
/**
|
|
27
62
|
* Where the fold crosses the window, in window coordinates: a view that does not start at the
|
|
@@ -32,6 +67,9 @@ export class Foldable {
|
|
|
32
67
|
...(ngDevMode ? [{ debugName: "fold" }] : /* istanbul ignore next */ []));
|
|
33
68
|
/** The hinge angle in degrees, 0 shut and 180 flat. Null without an angle sensor. */
|
|
34
69
|
angle = computed(() => {
|
|
70
|
+
const reading = this.reading();
|
|
71
|
+
if (reading)
|
|
72
|
+
return reading.angle;
|
|
35
73
|
this.known();
|
|
36
74
|
return this.angleState();
|
|
37
75
|
}, /* @ts-ignore */
|
|
@@ -46,19 +84,34 @@ export class Foldable {
|
|
|
46
84
|
tabletop = computed(() => this.halfOpen('horizontal'), /* @ts-ignore */
|
|
47
85
|
...(ngDevMode ? [{ debugName: "tabletop" }] : /* istanbul ignore next */ []));
|
|
48
86
|
constructor() {
|
|
49
|
-
const
|
|
50
|
-
if (!native)
|
|
51
|
-
return;
|
|
52
|
-
const stateSubscription = native.addOnStateChangeListener((state) => this.update(state));
|
|
53
|
-
this.update(native.getState());
|
|
87
|
+
const subscriptions = [this.followExpoHinge(), this.followFoldables()];
|
|
54
88
|
inject(DestroyRef).onDestroy(() => {
|
|
55
|
-
|
|
89
|
+
subscriptions.forEach((subscription) => subscription?.remove());
|
|
56
90
|
this.angleSubscription?.remove();
|
|
57
91
|
});
|
|
58
92
|
}
|
|
59
|
-
|
|
93
|
+
followExpoHinge() {
|
|
94
|
+
const expoHinge = this.expoHinge;
|
|
95
|
+
if (!expoHinge)
|
|
96
|
+
return null;
|
|
97
|
+
const subscription = expoHinge.addHingeListener(({ hinge }) => this.reading.set(hinge));
|
|
98
|
+
this.reading.set(expoHinge.getHinge());
|
|
99
|
+
return subscription;
|
|
100
|
+
}
|
|
101
|
+
followFoldables() {
|
|
102
|
+
const native = present(this.native);
|
|
103
|
+
if (!native)
|
|
104
|
+
return null;
|
|
105
|
+
const subscription = native.addOnStateChangeListener((state) => this.update(state));
|
|
106
|
+
this.update(native.getState());
|
|
107
|
+
return subscription;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The hinge's state, read as a use: a module missing from the build says what to install, unless
|
|
111
|
+
* `expo-hinge` is there to answer instead.
|
|
112
|
+
*/
|
|
60
113
|
known() {
|
|
61
|
-
if (this.native && !present(this.native))
|
|
114
|
+
if (this.native && !present(this.native) && !this.expoHinge)
|
|
62
115
|
this.native.getState();
|
|
63
116
|
return this.state();
|
|
64
117
|
}
|
package/dist/foldable.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"foldable.js","sourceRoot":"","sources":["../src/foldable.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Foldable`, bound to `expo-foldables`: the hinge of a foldable device, an iPhone Duo or an\n * Android foldable.\n *\n * On a phone that does not fold, on the web and in a test, it answers like a phone: not available,\n * `'unknown'`, no fold and no angle.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n computed,\n inject,\n signal,\n type Signal,\n} from '@angular/core';\nimport { expoModule, present } from './native.ts';\n\n/** How far the hinge is open. `'unknown'` without a hinge, or while the platform cannot say. */\nexport type HingePosture = 'closed' | 'partially-open' | 'fully-open' | 'unknown';\n\n/** A rectangle in window coordinates, in points: measured from the window's top left corner. */\nexport interface FoldRect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/** Where the fold crosses the app's window. */\nexport interface Fold {\n /** The fold's area, in window coordinates. Zero wide or tall where the fold is a line. */\n readonly bounds: FoldRect;\n readonly orientation: 'horizontal' | 'vertical';\n /** Whether the fold splits the window in two, so content should stay off it. */\n readonly isSeparating: boolean;\n /** `'full'` where the fold area shows nothing, as with a physical gap between two screens. */\n readonly occlusion: 'none' | 'full';\n}\n\n/** What `expo-foldables` reports: the posture, and the fold while it crosses the window. */\nexport interface HingeState {\n readonly posture: HingePosture;\n readonly fold?: Fold;\n}\n\ninterface Subscription {\n remove(): void;\n}\n\n/** `expo-foldables`' `Hinge`, as `Foldable.SOURCE` provides it. A test passes a stand-in. */\nexport interface NativeHinge {\n readonly isAvailable: boolean;\n readonly isAngleAvailable: boolean;\n getState(): HingeState | undefined;\n addOnStateChangeListener(listener: (state: HingeState | undefined) => void): Subscription;\n addOnAngleChangeListener(\n listener: (angle: number) => void,\n options?: { minDeltaDegrees?: number },\n ): Subscription;\n}\n\n@Service()\nexport class Foldable {\n static readonly SOURCE = new InjectionToken<NativeHinge | null>('angular-native.hingeSource', {\n factory: () =>\n expoModule(\n 'expo-foldables',\n () => (require('expo-foldables') as { Hinge: NativeHinge }).Hinge,\n ),\n });\n\n private readonly native = inject(Foldable.SOURCE);\n private readonly state = signal<HingeState | undefined>(undefined);\n private readonly angleState = signal<number | null>(null);\n private angleSubscription: Subscription | null = null;\n\n /** Whether the device reports a hinge. It can turn true shortly after launch. */\n readonly available: Signal<boolean> = computed(() => this.state() !== undefined);\n readonly posture: Signal<HingePosture> = computed(() => this.known()?.posture ?? 'unknown');\n /**\n * Where the fold crosses the window, in window coordinates: a view that does not start at the\n * window's left edge subtracts its own offset. Null while it crosses no window, as on a closed\n * phone's cover display.\n */\n readonly fold: Signal<Fold | null> = computed(() => this.known()?.fold ?? null);\n /** The hinge angle in degrees, 0 shut and 180 flat. Null without an angle sensor. */\n readonly angle: Signal<number | null> = computed(() => {\n this.known();\n return this.angleState();\n });\n /** Whether the fold splits the window, so content should stay off it. */\n readonly separating: Signal<boolean> = computed(() => this.fold()?.isSeparating ?? false);\n /** Half open with the fold running down the middle, like a book. */\n readonly book: Signal<boolean> = computed(() => this.halfOpen('vertical'));\n /** Half open with the fold running across, like a laptop. */\n readonly tabletop: Signal<boolean> = computed(() => this.halfOpen('horizontal'));\n\n constructor() {\n const native = present(this.native);\n if (!native) return;\n const stateSubscription = native.addOnStateChangeListener((state) => this.update(state));\n this.update(native.getState());\n inject(DestroyRef).onDestroy(() => {\n stateSubscription.remove();\n this.angleSubscription?.remove();\n });\n }\n\n /** The hinge's state, read as a use: a module missing from the build says what to install. */\n private known(): HingeState | undefined {\n if (this.native && !present(this.native)) this.native.getState();\n return this.state();\n }\n\n private update(state: HingeState | undefined): void {\n this.state.set(state);\n if (state === undefined) {\n this.stopFollowingAngle();\n } else {\n this.followAngle();\n }\n }\n\n private stopFollowingAngle(): void {\n this.angleSubscription?.remove();\n this.angleSubscription = null;\n this.angleState.set(null);\n }\n\n private followAngle(): void {\n const native = this.native;\n if (!native || this.angleSubscription || !native.isAngleAvailable) return;\n this.angleSubscription = native.addOnAngleChangeListener(\n (angle) => this.angleState.set(angle),\n { minDeltaDegrees: 0.5 },\n );\n }\n\n private halfOpen(orientation: Fold['orientation']): boolean {\n const fold = this.fold();\n return (\n this.posture() === 'partially-open' &&\n fold?.isSeparating === true &&\n fold.orientation === orientation\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"foldable.js","sourceRoot":"","sources":["../src/foldable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,UAAU,EACV,cAAc,EACd,OAAO,EACP,QAAQ,EACR,MAAM,EACN,MAAM,GAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;;AAmE5D,MAAM,QAAQ,GAA+D;IAC3E,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,gBAAgB;IAC/B,SAAS,EAAE,YAAY;IACvB,OAAO,EAAE,SAAS;CACnB,CAAC;AAGF,MAAM,OAAO,QAAQ;IACnB,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CAAqB,4BAA4B,EAAE;QAC5F,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CACR,gBAAgB,EAChB,GAAG,EAAE,CAAE,OAAO,CAAC,gBAAgB,CAA4B,CAAC,KAAK,CAClE;KACJ,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAU,KAAK,GAAG,IAAI,cAAc,CAAmB,0BAA0B,EAAE;QACvF,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,CAEzB,OAAO,CAAC,mBAAmB,CAC5B,CAAC,2BAA2B,CAAkB,WAAW,CAAC,CAC5D,CAAC;YACF,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YACzB,OAAO;gBACL,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,IAAI,IAAI;gBAC3C,gBAAgB,EAAE,CAAC,QAAQ,EAAE,EAAE,CAC7B,MAAM,CAAC,WAAW,EAAE,CAAC,aAAa,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE;aACxE,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IAEc,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnC,KAAK,GAAG,MAAM,CAAyB,SAAS;8EAAC,CAAC;IAClD,OAAO,GAAG,MAAM,CAA0B,IAAI;gFAAC,CAAC;IAChD,UAAU,GAAG,MAAM,CAAgB,IAAI;mFAAC,CAAC;IAClD,iBAAiB,GAAwB,IAAI,CAAC;IAEtD,iFAAiF;IACxE,SAAS,GAAoB,QAAQ,CAC5C,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,KAAK,SAAS;kFAC5D,CAAC;IACF;;;OAGG;IACM,OAAO,GAAyB,QAAQ,CAAC,GAAG,EAAE;QACrD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,IAAI,SAAS,CAAC,IAAI,SAAS,CAAC;QAC3E,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,OAAO,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChF,CAAC;gFAAC,CAAC;IACH;;;;OAIG;IACM,IAAI,GAAwB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,IAAI,IAAI;6EAAC,CAAC;IAChF,qFAAqF;IAC5E,KAAK,GAA0B,QAAQ,CAAC,GAAG,EAAE;QACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC;QAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;IAC3B,CAAC;8EAAC,CAAC;IACH,yEAAyE;IAChE,UAAU,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,YAAY,IAAI,KAAK;mFAAC,CAAC;IAC1F,oEAAoE;IAC3D,IAAI,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;6EAAC,CAAC;IAC3E,6DAA6D;IACpD,QAAQ,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;iFAAC,CAAC;IAEjF;QACE,MAAM,aAAa,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YAChC,aAAa,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;YAChE,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,eAAe;QACrB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QACjC,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAC5B,MAAM,YAAY,GAAG,SAAS,CAAC,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACxF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvC,OAAO,YAAY,CAAC;IACtB,CAAC;IAEO,eAAe;QACrB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,YAAY,GAAG,MAAM,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACpF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC/B,OAAO,YAAY,CAAC;IACtB,CAAC;IAED;;;OAGG;IACK,KAAK;QACX,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpF,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAEO,MAAM,CAAC,KAA6B;QAC1C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IAEO,kBAAkB;QACxB,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC;QACjC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAEO,WAAW;QACjB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,iBAAiB,IAAI,CAAC,MAAM,CAAC,gBAAgB;YAAE,OAAO;QAC1E,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC,wBAAwB,CACtD,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EACrC,EAAE,eAAe,EAAE,GAAG,EAAE,CACzB,CAAC;IACJ,CAAC;IAEO,QAAQ,CAAC,WAAgC;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,OAAO,CACL,IAAI,CAAC,OAAO,EAAE,KAAK,gBAAgB;YACnC,IAAI,EAAE,YAAY,KAAK,IAAI;YAC3B,IAAI,CAAC,WAAW,KAAK,WAAW,CACjC,CAAC;IACJ,CAAC;uGApIU,QAAQ;wGAAR,QAAQ;;2FAAR,QAAQ;kBADpB,OAAO","sourcesContent":["/**\n * `Foldable`, bound to `expo-hinge` and `expo-foldables`: the hinge of a foldable device, an iPhone\n * Duo or an Android foldable.\n *\n * The posture and the angle come from `expo-hinge` (Expo SDK 58) while it reports a hinge, and from\n * `expo-foldables` otherwise. The fold comes from `expo-foldables` only, since `expo-hinge` does not\n * say where the fold crosses the window.\n *\n * On a phone that does not fold, on the web and in a test, it answers like a phone: not available,\n * `'unknown'`, no fold and no angle.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n computed,\n inject,\n signal,\n type Signal,\n} from '@angular/core';\nimport { expoModule, optional, present } from './native.ts';\n\n/** How far the hinge is open. `'unknown'` without a hinge, or while the platform cannot say. */\nexport type HingePosture = 'closed' | 'partially-open' | 'fully-open' | 'unknown';\n\n/** A rectangle in window coordinates, in points: measured from the window's top left corner. */\nexport interface FoldRect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/** Where the fold crosses the app's window. */\nexport interface Fold {\n /** The fold's area, in window coordinates. Zero wide or tall where the fold is a line. */\n readonly bounds: FoldRect;\n readonly orientation: 'horizontal' | 'vertical';\n /** Whether the fold splits the window in two, so content should stay off it. */\n readonly isSeparating: boolean;\n /** `'full'` where the fold area shows nothing, as with a physical gap between two screens. */\n readonly occlusion: 'none' | 'full';\n}\n\n/** What `expo-foldables` reports: the posture, and the fold while it crosses the window. */\nexport interface HingeState {\n readonly posture: HingePosture;\n readonly fold?: Fold;\n}\n\ninterface Subscription {\n remove(): void;\n}\n\n/** `expo-foldables`' `Hinge`, as `Foldable.SOURCE` provides it. A test passes a stand-in. */\nexport interface NativeHinge {\n readonly isAvailable: boolean;\n readonly isAngleAvailable: boolean;\n getState(): HingeState | undefined;\n addOnStateChangeListener(listener: (state: HingeState | undefined) => void): Subscription;\n addOnAngleChangeListener(\n listener: (angle: number) => void,\n options?: { minDeltaDegrees?: number },\n ): Subscription;\n}\n\n/** What `expo-hinge` reports: the angle in degrees, and how far open the hinge is. */\nexport interface ExpoHingeReading {\n readonly angle: number;\n readonly status: 'closed' | 'partiallyOpen' | 'fullyOpen' | 'unknown';\n}\n\n/** `expo-hinge`, as `Foldable.HINGE` provides it. A test passes a stand-in. */\nexport interface ExpoHinge {\n /** Null on a device without a hinge, or an OS that cannot report one. */\n getHinge(): ExpoHingeReading | null;\n addHingeListener(listener: (event: { hinge: ExpoHingeReading | null }) => void): Subscription;\n}\n\ninterface ExpoHingeModule {\n getHinge?(): ExpoHingeReading | null;\n addListener?(\n event: 'hingeChange',\n listener: (event: { hinge: ExpoHingeReading | null }) => void,\n ): Subscription;\n}\n\nconst POSTURES: Readonly<Record<ExpoHingeReading['status'], HingePosture>> = {\n closed: 'closed',\n partiallyOpen: 'partially-open',\n fullyOpen: 'fully-open',\n unknown: 'unknown',\n};\n\n@Service()\nexport class Foldable {\n static readonly SOURCE = new InjectionToken<NativeHinge | null>('angular-native.hingeSource', {\n factory: () =>\n expoModule(\n 'expo-foldables',\n () => (require('expo-foldables') as { Hinge: NativeHinge }).Hinge,\n ),\n });\n\n /**\n * `expo-hinge`, reached through its native module rather than its package, so an app without it\n * still builds. Null where it is not in the app.\n */\n static readonly HINGE = new InjectionToken<ExpoHinge | null>('angular-native.expoHinge', {\n factory: () => {\n const module = optional(() =>\n (\n require('expo-modules-core') as typeof import('expo-modules-core')\n ).requireOptionalNativeModule<ExpoHingeModule>('ExpoHinge'),\n );\n if (!module) return null;\n return {\n getHinge: () => module.getHinge?.() ?? null,\n addHingeListener: (listener) =>\n module.addListener?.('hingeChange', listener) ?? { remove: () => {} },\n };\n },\n });\n\n private readonly native = inject(Foldable.SOURCE);\n private readonly expoHinge = inject(Foldable.HINGE);\n private readonly state = signal<HingeState | undefined>(undefined);\n private readonly reading = signal<ExpoHingeReading | null>(null);\n private readonly angleState = signal<number | null>(null);\n private angleSubscription: Subscription | null = null;\n\n /** Whether the device reports a hinge. It can turn true shortly after launch. */\n readonly available: Signal<boolean> = computed(\n () => this.reading() !== null || this.state() !== undefined,\n );\n /**\n * `expo-foldables`' posture wherever `expo-hinge` cannot say, as on Android while the window does\n * not span the fold: `expo-foldables` works a closed device out from its angle.\n */\n readonly posture: Signal<HingePosture> = computed(() => {\n const posture = POSTURES[this.reading()?.status ?? 'unknown'] ?? 'unknown';\n return posture === 'unknown' ? (this.known()?.posture ?? 'unknown') : posture;\n });\n /**\n * Where the fold crosses the window, in window coordinates: a view that does not start at the\n * window's left edge subtracts its own offset. Null while it crosses no window, as on a closed\n * phone's cover display.\n */\n readonly fold: Signal<Fold | null> = computed(() => this.known()?.fold ?? null);\n /** The hinge angle in degrees, 0 shut and 180 flat. Null without an angle sensor. */\n readonly angle: Signal<number | null> = computed(() => {\n const reading = this.reading();\n if (reading) return reading.angle;\n this.known();\n return this.angleState();\n });\n /** Whether the fold splits the window, so content should stay off it. */\n readonly separating: Signal<boolean> = computed(() => this.fold()?.isSeparating ?? false);\n /** Half open with the fold running down the middle, like a book. */\n readonly book: Signal<boolean> = computed(() => this.halfOpen('vertical'));\n /** Half open with the fold running across, like a laptop. */\n readonly tabletop: Signal<boolean> = computed(() => this.halfOpen('horizontal'));\n\n constructor() {\n const subscriptions = [this.followExpoHinge(), this.followFoldables()];\n inject(DestroyRef).onDestroy(() => {\n subscriptions.forEach((subscription) => subscription?.remove());\n this.angleSubscription?.remove();\n });\n }\n\n private followExpoHinge(): Subscription | null {\n const expoHinge = this.expoHinge;\n if (!expoHinge) return null;\n const subscription = expoHinge.addHingeListener(({ hinge }) => this.reading.set(hinge));\n this.reading.set(expoHinge.getHinge());\n return subscription;\n }\n\n private followFoldables(): Subscription | null {\n const native = present(this.native);\n if (!native) return null;\n const subscription = native.addOnStateChangeListener((state) => this.update(state));\n this.update(native.getState());\n return subscription;\n }\n\n /**\n * The hinge's state, read as a use: a module missing from the build says what to install, unless\n * `expo-hinge` is there to answer instead.\n */\n private known(): HingeState | undefined {\n if (this.native && !present(this.native) && !this.expoHinge) this.native.getState();\n return this.state();\n }\n\n private update(state: HingeState | undefined): void {\n this.state.set(state);\n if (state === undefined) {\n this.stopFollowingAngle();\n } else {\n this.followAngle();\n }\n }\n\n private stopFollowingAngle(): void {\n this.angleSubscription?.remove();\n this.angleSubscription = null;\n this.angleState.set(null);\n }\n\n private followAngle(): void {\n const native = this.native;\n if (!native || this.angleSubscription || !native.isAngleAvailable) return;\n this.angleSubscription = native.addOnAngleChangeListener(\n (angle) => this.angleState.set(angle),\n { minDeltaDegrees: 0.5 },\n );\n }\n\n private halfOpen(orientation: Fold['orientation']): boolean {\n const fold = this.fold();\n return (\n this.posture() === 'partially-open' &&\n fold?.isSeparating === true &&\n fold.orientation === orientation\n );\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { NATIVE_VIEWS, registerNativeViews } from './community-views.ts';
|
|
|
13
13
|
export { registerExpoUiViews } from './expo-ui.ts';
|
|
14
14
|
export { nativeState, type NativeState } from './native-state.ts';
|
|
15
15
|
export { EXPO_VIEWS, expoViewName, registerExpoView, registerExpoViews, type ExpoViewOptions, } from './register-expo-view.ts';
|
|
16
|
-
export { UiAccessoryWidgetBackground, UiButton, UiCapsule, UiCircle, UiContextMenu, UiEllipse, UiLabel, UiLink, UiRectangle, UiRoundedRectangle, UiUnevenRoundedRectangle, UiViewHost, UiZStack, type UiZStackAlignment, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiDropdownMenuItem, UiExposedDropdownMenu, UiExposedDropdownMenuBox, UiSegmentedButton, UiSingleChoiceSegmentedButtonRow, UiColorPicker, type UiColorChangeEvent, UiForm, UiChart, type UiChartAreaStyle, type UiChartBarStyle, type UiChartDataPoint, type UiChartLineStyle, type UiChartPieStyle, type UiChartPointStyle, type UiChartPointSymbol, type UiChartRectangleStyle, type UiChartRuleStyle, type UiChartType, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, type UiStepperChangeEvent, UiTextField, type UiTextFieldChangeEvent, UiToggle, type UiToggleChangeEvent, UiSlider, type UiSliderChangeEvent, UiSlot, UiSwipeActions, UiText, UiVStack, type UiDate, type UiDateChangeEvent, type UiTimerInterval, type UiPickerOption, type UiModifier, UiBottomSheet, type UiBottomSheetViewFunctions, type UiPresentationDetent, UiGroup, UiNavigationStack, UiToolbar, UiToolbarItem, type UiToolbarItemPlacement, } from './expo-ui-components.ts';
|
|
16
|
+
export { UiAccessoryWidgetBackground, UiButton, UiCapsule, UiCircle, UiContextMenu, UiEllipse, UiLabel, UiLink, UiRectangle, UiRoundedRectangle, UiUnevenRoundedRectangle, UiViewHost, UiZStack, type UiZStackAlignment, UiDatePicker, UiDivider, UiHost, UiImage, UiMenu, UiPicker, UiDropdownMenuItem, UiExposedDropdownMenu, UiExposedDropdownMenuBox, UiSegmentedButton, UiSingleChoiceSegmentedButtonRow, UiColorPicker, type UiColorChangeEvent, UiForm, UiChart, type UiChartAreaStyle, type UiChartBarStyle, type UiChartDataPoint, type UiChartLineStyle, type UiChartPieStyle, type UiChartPointStyle, type UiChartPointSymbol, type UiChartRectangleStyle, type UiChartRuleStyle, type UiChartType, UiGauge, UiHStack, UiLabeledContent, UiList, UiProgress, UiSection, UiSpacer, UiStepper, type UiStepperChangeEvent, UiTextField, type UiTextFieldChangeEvent, UiToggle, type UiToggleChangeEvent, UiSlider, type UiSliderChangeEvent, UiSlot, UiSwipeActions, UiText, UiVStack, type UiDate, type UiDateChangeEvent, type UiTimerInterval, type UiPickerOption, type UiModifier, UiBottomSheet, type UiBottomSheetViewFunctions, type UiBottomSheetBackgroundInteraction, type UiPresentationDetent, UiGroup, UiNavigationStack, UiToolbar, UiToolbarItem, type UiToolbarItemPlacement, } from './expo-ui-components.ts';
|
|
17
17
|
export { ExpoImage, type ExpoImageContentFit, type ExpoImageSource } from './expo-image.ts';
|
|
18
18
|
export { ExpoGlass, ExpoGlassContainer, liquidGlassAvailable, type GlassEffectStyleConfig, type GlassStyle, } from './glass.ts';
|
|
19
19
|
export { ExpoSymbol, type SymbolAnimationSpec, type SymbolResizeMode, type SymbolScale, type SymbolType, type SymbolWeight, } from './symbol.ts';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,UAAU,GAIX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,QAAQ,EACR,WAAW,GAEZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EACL,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,2BAA2B,EAC3B,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAa,EACb,SAAS,EACT,OAAO,EACP,MAAM,EACN,WAAW,EACX,kBAAkB,EAClB,wBAAwB,EACxB,UAAU,EACV,QAAQ,EAER,YAAY,EACZ,SAAS,EACT,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,EACR,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,iBAAiB,EACjB,gCAAgC,EAChC,aAAa,EAEb,MAAM,EACN,OAAO,EAWP,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EAET,WAAW,EAEX,QAAQ,EAER,QAAQ,EAER,MAAM,EACN,cAAc,EACd,MAAM,EACN,QAAQ,EAMR,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,UAAU,GAIX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,QAAQ,EACR,WAAW,GAEZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EACL,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,2BAA2B,EAC3B,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAa,EACb,SAAS,EACT,OAAO,EACP,MAAM,EACN,WAAW,EACX,kBAAkB,EAClB,wBAAwB,EACxB,UAAU,EACV,QAAQ,EAER,YAAY,EACZ,SAAS,EACT,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,EACR,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,iBAAiB,EACjB,gCAAgC,EAChC,aAAa,EAEb,MAAM,EACN,OAAO,EAWP,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EAET,WAAW,EAEX,QAAQ,EAER,QAAQ,EAER,MAAM,EACN,cAAc,EACd,MAAM,EACN,QAAQ,EAMR,aAAa,EAIb,OAAO,EACP,iBAAiB,EACjB,SAAS,EACT,aAAa,GAEd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAkD,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GAGrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,UAAU,GAMX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAkC,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,gBAAgB,GAGjB,MAAM,wBAAwB,CAAC","sourcesContent":["/**\n * What the package exports without naming a module.\n *\n * Everything with an Expo module behind it has its own entry point - `@ng-native/expo/\n * battery.ts` and friends - so that importing haptics does not make an app install the video\n * player. What is left here is the shared machinery: the permission shape every module spells\n * identically, the signal-from-a-listener those services are built out of, and the view names.\n */\nexport { observed, type Observed } from './observed.ts';\nexport {\n Permission,\n type ForegroundEvents,\n type PermissionApi,\n type PermissionResponse,\n} from './permissions.ts';\nexport {\n MissingModuleError,\n expoModule,\n optional,\n unavailable,\n type ModulePlatform,\n} from './native.ts';\nexport { NATIVE_VIEWS, registerNativeViews } from './community-views.ts';\nexport { registerExpoUiViews } from './expo-ui.ts';\nexport { nativeState, type NativeState } from './native-state.ts';\nexport {\n EXPO_VIEWS,\n expoViewName,\n registerExpoView,\n registerExpoViews,\n type ExpoViewOptions,\n} from './register-expo-view.ts';\nexport {\n UiAccessoryWidgetBackground,\n UiButton,\n UiCapsule,\n UiCircle,\n UiContextMenu,\n UiEllipse,\n UiLabel,\n UiLink,\n UiRectangle,\n UiRoundedRectangle,\n UiUnevenRoundedRectangle,\n UiViewHost,\n UiZStack,\n type UiZStackAlignment,\n UiDatePicker,\n UiDivider,\n UiHost,\n UiImage,\n UiMenu,\n UiPicker,\n UiDropdownMenuItem,\n UiExposedDropdownMenu,\n UiExposedDropdownMenuBox,\n UiSegmentedButton,\n UiSingleChoiceSegmentedButtonRow,\n UiColorPicker,\n type UiColorChangeEvent,\n UiForm,\n UiChart,\n type UiChartAreaStyle,\n type UiChartBarStyle,\n type UiChartDataPoint,\n type UiChartLineStyle,\n type UiChartPieStyle,\n type UiChartPointStyle,\n type UiChartPointSymbol,\n type UiChartRectangleStyle,\n type UiChartRuleStyle,\n type UiChartType,\n UiGauge,\n UiHStack,\n UiLabeledContent,\n UiList,\n UiProgress,\n UiSection,\n UiSpacer,\n UiStepper,\n type UiStepperChangeEvent,\n UiTextField,\n type UiTextFieldChangeEvent,\n UiToggle,\n type UiToggleChangeEvent,\n UiSlider,\n type UiSliderChangeEvent,\n UiSlot,\n UiSwipeActions,\n UiText,\n UiVStack,\n type UiDate,\n type UiDateChangeEvent,\n type UiTimerInterval,\n type UiPickerOption,\n type UiModifier,\n UiBottomSheet,\n type UiBottomSheetViewFunctions,\n type UiBottomSheetBackgroundInteraction,\n type UiPresentationDetent,\n UiGroup,\n UiNavigationStack,\n UiToolbar,\n UiToolbarItem,\n type UiToolbarItemPlacement,\n} from './expo-ui-components.ts';\nexport { ExpoImage, type ExpoImageContentFit, type ExpoImageSource } from './expo-image.ts';\nexport {\n ExpoGlass,\n ExpoGlassContainer,\n liquidGlassAvailable,\n type GlassEffectStyleConfig,\n type GlassStyle,\n} from './glass.ts';\nexport {\n ExpoSymbol,\n type SymbolAnimationSpec,\n type SymbolResizeMode,\n type SymbolScale,\n type SymbolType,\n type SymbolWeight,\n} from './symbol.ts';\nexport { DateTimePicker, type DateTimePickerChangeEvent } from './date-time-picker.ts';\nexport {\n SegmentedControl,\n type SegmentedControlChangeEvent,\n type SegmentedControlFont,\n} from './segmented-control.ts';\n"]}
|
package/dist/keep-awake.js
CHANGED
|
@@ -23,7 +23,7 @@ export class KeepAwake {
|
|
|
23
23
|
if (!expo)
|
|
24
24
|
return null;
|
|
25
25
|
// One per module, so every app the runtime mounts holds the same screen.
|
|
26
|
-
return sharedBySource(expo, () => ({
|
|
26
|
+
return sharedBySource(KeepAwake, expo, () => ({
|
|
27
27
|
activate: (tag) => expo.activateKeepAwakeAsync(tag),
|
|
28
28
|
deactivate: (tag) => expo.deactivateKeepAwake(tag),
|
|
29
29
|
onWindowFront,
|
|
@@ -32,7 +32,7 @@ export class KeepAwake {
|
|
|
32
32
|
});
|
|
33
33
|
native = inject(KeepAwake.SOURCE);
|
|
34
34
|
/** Every app's holds: the screen is the runtime's, not an app's. */
|
|
35
|
-
window = sharedBySource(this.native, () => new KeepAwakeWindow(this.native));
|
|
35
|
+
window = sharedBySource(KeepAwakeWindow, this.native, () => new KeepAwakeWindow(this.native));
|
|
36
36
|
/** This app's holds not yet released, which go with it. */
|
|
37
37
|
own = new Set();
|
|
38
38
|
/** The tags currently holding the screen on, so a debug screen can say which. */
|
package/dist/keep-awake.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keep-awake.js","sourceRoot":"","sources":["../src/keep-awake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,UAAU,EACV,cAAc,EACd,OAAO,EACP,QAAQ,EACR,MAAM,EACN,MAAM,GAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;AASzC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAGrC,MAAM,OAAO,SAAS;IACpB,yEAAyE;IACzE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,gCAAgC,EAChC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,iBAAiB,EACjB,GAAG,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAqC,CACrE,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,yEAAyE;YACzE,OAAO,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"keep-awake.js","sourceRoot":"","sources":["../src/keep-awake.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `KeepAwake`, bound to `expo-keep-awake`.\n *\n * ```ts\n * inject(DestroyRef).onDestroy(inject(KeepAwake).hold('recording'));\n * ```\n *\n * Never a bare activate: a screen that holds the display on and never releases it is a phone that\n * never sleeps, which the user experiences as a battery fault and never attributes to the app.\n *\n * Tags are how two screens can each hold it without either releasing the other's.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n computed,\n inject,\n signal,\n type Signal,\n} from '@angular/core';\nimport { onWindowFront, sharedBySource } from '@ng-native/device';\nimport { expoModule } from './native.ts';\n\nexport interface NativeKeepAwake {\n activate(tag: string): Promise<void>;\n deactivate(tag: string): Promise<void>;\n /** Calls `listener` when a window comes to the front, to be held on again. */\n onWindowFront?(listener: () => void): () => void;\n}\n\nconst DEFAULT_TAG = 'angular-native';\n\n@Service()\nexport class KeepAwake {\n /** Overridden in a test to record a hold without keeping a screen on. */\n static readonly SOURCE = new InjectionToken<NativeKeepAwake | null>(\n 'angular-native.keepAwakeSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-keep-awake',\n () => require('expo-keep-awake') as typeof import('expo-keep-awake'),\n );\n if (!expo) return null;\n // One per module, so every app the runtime mounts holds the same screen.\n return sharedBySource(KeepAwake, expo, () => ({\n activate: (tag) => expo.activateKeepAwakeAsync(tag),\n deactivate: (tag) => expo.deactivateKeepAwake(tag),\n onWindowFront,\n }));\n },\n },\n );\n\n private readonly native = inject(KeepAwake.SOURCE);\n /** Every app's holds: the screen is the runtime's, not an app's. */\n private readonly window = sharedBySource(\n KeepAwakeWindow,\n this.native,\n () => new KeepAwakeWindow(this.native),\n );\n /** This app's holds not yet released, which go with it. */\n private readonly own = new Set<() => void>();\n\n /** The tags currently holding the screen on, so a debug screen can say which. */\n readonly holders: Signal<ReadonlySet<string>> = this.window.held.asReadonly();\n\n /** Whether anything currently holds the screen on. */\n readonly active: Signal<boolean> = computed(() => this.holders().size > 0);\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n for (const release of [...this.own]) release();\n });\n }\n\n /**\n * Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.\n *\n * Never a bare `activate()`, because the release is the half that gets forgotten and the half\n * that matters. A hold still there when the app goes is released with it.\n */\n hold(tag = DEFAULT_TAG): () => void {\n const release = this.window.hold(tag);\n const own = () => {\n this.own.delete(own);\n release();\n };\n this.own.add(own);\n return own;\n }\n}\n\n/** The screen, as every app on one module holds it. */\nclass KeepAwakeWindow {\n readonly held = signal<ReadonlySet<string>>(new Set());\n /** How many holds each tag has that are not yet released. */\n private readonly counts = new Map<string, number>();\n private readonly native: NativeKeepAwake | null;\n\n constructor(native: NativeKeepAwake | null) {\n this.native = native;\n // Lives as long as the module, which is the runtime's.\n native?.onWindowFront?.(() => void this.holdAgain());\n }\n\n hold(tag: string): () => void {\n // Counted per tag: two screens of one kind hold the same one, and the native module keeps a\n // set of tags, so the first to release would otherwise let go for both.\n const count = this.counts.get(tag) ?? 0;\n this.counts.set(tag, count + 1);\n if (count === 0) {\n this.held.update((tags) => new Set(tags).add(tag));\n void this.native?.activate(tag).catch(() => {});\n }\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const left = (this.counts.get(tag) ?? 1) - 1;\n if (left > 0) {\n this.counts.set(tag, left);\n return;\n }\n this.counts.delete(tag);\n this.held.update((tags) => {\n const next = new Set(tags);\n next.delete(tag);\n return next;\n });\n void this.native?.deactivate(tag).catch(() => {});\n };\n }\n\n /**\n * A recreated activity's window starts without the flag, and the native module, which keeps its\n * tags for the runtime, sets it only for the first one. So every tag is let go, which clears it,\n * and taken again once it has gone, which sets it on the window now in front: the module's\n * calls are not promised to run in order. A tag released in between is not taken again.\n */\n private async holdAgain(): Promise<void> {\n const native = this.native;\n if (!native) return;\n const tags = [...this.counts.keys()];\n await Promise.allSettled(tags.map((tag) => native.deactivate(tag)));\n for (const tag of tags) {\n if (this.counts.has(tag)) void native.activate(tag).catch(() => {});\n }\n }\n}\n"]}
|
package/dist/keyboard.js
CHANGED
|
@@ -59,6 +59,16 @@ export function nativeKeyboardSource(native, fallback = keyboardSource()) {
|
|
|
59
59
|
clearTimeout(waiting);
|
|
60
60
|
report({ ...metricsOf(change), duration: change.duration, easing: change.easing });
|
|
61
61
|
});
|
|
62
|
+
// A motion watching began part way through sends its frames and end but not its change,
|
|
63
|
+
// which went before: where its last frame left the keyboard is reported at the end, as a
|
|
64
|
+
// jump, unless something already said so.
|
|
65
|
+
let reached = null;
|
|
66
|
+
const frames = native.addListener('onKeyboardMove', (frame) => {
|
|
67
|
+
if (!('end' in frame))
|
|
68
|
+
reached = metricsOf(frame);
|
|
69
|
+
else if (reached && (last === null || Math.abs(reached.height - last) >= SAME_HEIGHT))
|
|
70
|
+
report({ ...reached, duration: 0 });
|
|
71
|
+
});
|
|
62
72
|
const stop = fallback.subscribe((metrics) => {
|
|
63
73
|
clearTimeout(waiting);
|
|
64
74
|
waiting = setTimeout(() => {
|
|
@@ -69,6 +79,7 @@ export function nativeKeyboardSource(native, fallback = keyboardSource()) {
|
|
|
69
79
|
return () => {
|
|
70
80
|
clearTimeout(waiting);
|
|
71
81
|
subscription.remove();
|
|
82
|
+
frames.remove();
|
|
72
83
|
stop();
|
|
73
84
|
};
|
|
74
85
|
},
|
|
@@ -77,6 +88,10 @@ export function nativeKeyboardSource(native, fallback = keyboardSource()) {
|
|
|
77
88
|
return () => subscription.remove();
|
|
78
89
|
},
|
|
79
90
|
dismiss: () => fallback.dismiss(),
|
|
91
|
+
// The module's `screenY` is from the top of the screen as React Native's is.
|
|
92
|
+
get windowTop() {
|
|
93
|
+
return fallback.windowTop;
|
|
94
|
+
},
|
|
80
95
|
};
|
|
81
96
|
}
|
|
82
97
|
/**
|
package/dist/keyboard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../src/keyboard.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../src/keyboard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `provideNativeKeyboard()`: the keyboard's motion on Android, from this package's native half.\n *\n * React Native on Android reports the keyboard once the window's insets have changed, as it starts\n * to move, with no duration: a `<keyboard-avoiding-view>` jumps to where the keyboard is going and a\n * `<keyboard-dock>` jumps onto it, while the keyboard slides up underneath. iOS reports the motion\n * as it starts, with its duration and curve, and the same layouts move with the keyboard there.\n *\n * With this, Android does too. The `Keyboard` service hears of the motion as it starts, with\n * Android's duration, and its `position` follows every frame of it, which a\n * `<keyboard-avoiding-view>` moves with, since Android animates no layout alongside the keyboard by\n * default; and a `<keyboard-dock>` follows it frame by frame on the native side, as one does on iOS\n * with react-native-keyboard-controller. Off\n * Android, and in a build without this package's native half, such as Expo Go, it provides nothing\n * and React Native's own events are what there is.\n *\n * ```ts\n * bootstrap(App, { providers: [provideNativeKeyboard()] });\n * ```\n */\nimport { type Provider } from '@angular/core';\nimport {\n KEYBOARD_MOTION,\n Keyboard,\n keyboardSource,\n type KeyboardMetrics,\n type KeyboardSource,\n} from '@ng-native/device';\nimport { currentPlatform } from './native.ts';\nimport { expoViewName } from './register-expo-view.ts';\n\nconst MODULE = 'NgNativeKeyboard';\n\n/** What the native module sends as the keyboard starts to move. */\nexport interface NativeKeyboardChange {\n /** Points above the system bars; zero for a keyboard going away. */\n readonly height: number;\n /** The keyboard's top edge on the screen, in points. Absent for a keyboard going away. */\n readonly screenY?: number;\n readonly duration: number;\n readonly easing: string;\n}\n\n/** Where the keyboard is at one frame of its motion, or that the motion is over. */\nexport type NativeKeyboardFrame =\n { readonly height: number; readonly screenY?: number } | { readonly end: true };\n\n/** This package's native keyboard module. A test provides a stand-in. */\nexport interface NativeKeyboard {\n addListener(\n event: 'onKeyboardChange',\n listener: (change: NativeKeyboardChange) => void,\n ): { remove(): void };\n addListener(\n event: 'onKeyboardMove',\n listener: (frame: NativeKeyboardFrame) => void,\n ): { remove(): void };\n}\n\n/** How long React Native's own event waits for the native one that says the same, in ms. */\nconst OVERTAKEN_MS = 100;\n\n/** Heights closer than this, in points, are one: the two report it through different roundings. */\nconst SAME_HEIGHT = 0.5;\n\n/** The module where the build has it, on Android. */\nfunction nativeKeyboard(): NativeKeyboard | null {\n if (currentPlatform() !== 'android') return null;\n return (\n (globalThis as { expo?: { modules?: Record<string, NativeKeyboard | undefined> } }).expo\n ?.modules?.[MODULE] ?? null\n );\n}\n\n/** A native height or edge as the metrics carry it: only the fields that are there. */\nconst metricsOf = (at: {\n readonly height: number;\n readonly screenY?: number;\n}): KeyboardMetrics => ({\n height: at.height,\n ...(at.screenY !== undefined && { screenY: at.screenY }),\n});\n\n/**\n * The keyboard as the native module reports it, as it starts to move and at every frame of the\n * motion, and as React Native's own events report it: they still come, and they are what a window\n * of its own, a modal's, has. They arrive at about the moment the native one does, sometimes just\n * before it, so one waits a moment and is dropped when the native one says the same; reported\n * first, it would hand a layout following the frames the destination before the motion began.\n */\nexport function nativeKeyboardSource(\n native: NativeKeyboard,\n fallback: KeyboardSource = keyboardSource(),\n): KeyboardSource {\n return {\n subscribe: (listener) => {\n let last: number | null = null;\n let waiting: ReturnType<typeof setTimeout> | undefined;\n const report = (metrics: KeyboardMetrics) => {\n last = metrics.height;\n listener(metrics);\n };\n const subscription = native.addListener('onKeyboardChange', (change) => {\n clearTimeout(waiting);\n report({ ...metricsOf(change), duration: change.duration, easing: change.easing });\n });\n // A motion watching began part way through sends its frames and end but not its change,\n // which went before: where its last frame left the keyboard is reported at the end, as a\n // jump, unless something already said so.\n let reached: KeyboardMetrics | null = null;\n const frames = native.addListener('onKeyboardMove', (frame) => {\n if (!('end' in frame)) reached = metricsOf(frame);\n else if (reached && (last === null || Math.abs(reached.height - last) >= SAME_HEIGHT))\n report({ ...reached, duration: 0 });\n });\n const stop = fallback.subscribe((metrics) => {\n clearTimeout(waiting);\n waiting = setTimeout(() => {\n if (last === null || Math.abs(metrics.height - last) >= SAME_HEIGHT) report(metrics);\n }, OVERTAKEN_MS);\n });\n return () => {\n clearTimeout(waiting);\n subscription.remove();\n frames.remove();\n stop();\n };\n },\n subscribeMotion: (listener) => {\n const subscription = native.addListener('onKeyboardMove', (frame) =>\n listener('end' in frame ? null : metricsOf(frame)),\n );\n return () => subscription.remove();\n },\n dismiss: () => fallback.dismiss(),\n // The module's `screenY` is from the top of the screen as React Native's is.\n get windowTop() {\n return fallback.windowTop;\n },\n };\n}\n\n/**\n * Follow the keyboard on Android as it moves rather than once it has: see this file. Nothing to\n * install beyond this package; a build made before it had this native module, and Expo Go, keep\n * React Native's own events.\n */\nexport function provideNativeKeyboard(): Provider[] {\n return [\n {\n provide: Keyboard.SOURCE,\n useFactory: () => {\n const native = nativeKeyboard();\n return native ? nativeKeyboardSource(native) : keyboardSource();\n },\n },\n {\n provide: KEYBOARD_MOTION,\n useFactory: () => (nativeKeyboard() ? { viewName: expoViewName(MODULE) } : null),\n },\n ];\n}\n"]}
|
package/dist/orientation.js
CHANGED
|
@@ -21,8 +21,9 @@ export class DeviceOrientation {
|
|
|
21
21
|
static SOURCE = new InjectionToken('angular-native.orientationSource', {
|
|
22
22
|
factory: () => {
|
|
23
23
|
const expo = expoModule('expo-screen-orientation', () => require('expo-screen-orientation'));
|
|
24
|
+
// A copy: with no screen to share, each app locks its own.
|
|
24
25
|
if (!expo)
|
|
25
|
-
return NOTHING;
|
|
26
|
+
return { ...NOTHING };
|
|
26
27
|
// Missing from the build: stand-ins, to say so when they are used.
|
|
27
28
|
if (!present(expo))
|
|
28
29
|
return { reported: expo, lock: expo, unlock: expo };
|
|
@@ -40,7 +41,7 @@ export class DeviceOrientation {
|
|
|
40
41
|
landscape: expo.OrientationLock.LANDSCAPE,
|
|
41
42
|
};
|
|
42
43
|
// One per module, so every app the runtime mounts locks the same screen.
|
|
43
|
-
return sharedBySource(expo, () => ({
|
|
44
|
+
return sharedBySource(DeviceOrientation, expo, () => ({
|
|
44
45
|
reported: observedFrom(async () => ORIENTATIONS[await expo.getOrientationAsync()] ?? 'unknown', (listener) => expo.addOrientationChangeListener(({ orientationInfo }) => listener(ORIENTATIONS[orientationInfo.orientation] ?? 'unknown'))),
|
|
45
46
|
lock: (lock) => expo.lockAsync(LOCKS[lock]),
|
|
46
47
|
unlock: () => expo.unlockAsync(),
|
|
@@ -53,7 +54,7 @@ export class DeviceOrientation {
|
|
|
53
54
|
landscape = computed(() => this.orientation().startsWith('landscape'), /* @ts-ignore */
|
|
54
55
|
...(ngDevMode ? [{ debugName: "landscape" }] : /* istanbul ignore next */ []));
|
|
55
56
|
/** Every app's locks: the screen is the runtime's, not an app's. */
|
|
56
|
-
window = sharedBySource(this.source, () => lockWindow(this.source));
|
|
57
|
+
window = sharedBySource(lockWindow, this.source, () => lockWindow(this.source));
|
|
57
58
|
/** This app, among the apps locking the screen. */
|
|
58
59
|
owner = {};
|
|
59
60
|
constructor() {
|
package/dist/orientation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orientation.js","sourceRoot":"","sources":["../src/orientation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAiB,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;;AAgBlD,MAAM,OAAO,GAAsB;IACjC,QAAQ,EAAE,IAAI;IACd,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;IAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;CAChC,CAAC;AAGF,MAAM,OAAO,iBAAiB;IAC5B,iEAAiE;IACjE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,kCAAkC,EAClC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,yBAAyB,EACzB,GAAG,EAAE,CAAC,OAAO,CAAC,yBAAyB,CAA6C,CACrF,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"orientation.js","sourceRoot":"","sources":["../src/orientation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `DeviceOrientation`, bound to `expo-screen-orientation`.\n *\n * Named for the device rather than the screen because that is what it reports: a layout asking\n * which way *it* is laid out wants the `orientation` media feature, which needs none of this.\n * This is what a camera preview or a video player needs - a screen locked to portrait still has a\n * phone that is sideways.\n */\nimport { DestroyRef, InjectionToken, Service, computed, inject, type Signal } from '@angular/core';\nimport { TopClaim, onWindowFront, sharedBySource } from '@ng-native/device';\nimport { observed, observedFrom, type Observed } from './observed.ts';\nimport { expoModule, present } from './native.ts';\n\nexport type Orientation =\n 'unknown' | 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right';\n\n/** What a screen may rotate to. `default` is portrait plus, on a phone, nothing else. */\nexport type OrientationLock = 'default' | 'all' | 'portrait' | 'landscape';\n\nexport interface OrientationSource {\n readonly reported: Observed<Orientation> | null;\n lock(lock: OrientationLock): Promise<void>;\n unlock(): Promise<void>;\n /** Calls `listener` when a window comes to the front, to be locked again. */\n onWindowFront?(listener: () => void): () => void;\n}\n\nconst NOTHING: OrientationSource = {\n reported: null,\n lock: () => Promise.resolve(),\n unlock: () => Promise.resolve(),\n};\n\n@Service()\nexport class DeviceOrientation {\n /** Overridden in a test to rotate a device that is not there. */\n static readonly SOURCE = new InjectionToken<OrientationSource>(\n 'angular-native.orientationSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-screen-orientation',\n () => require('expo-screen-orientation') as typeof import('expo-screen-orientation'),\n );\n // A copy: with no screen to share, each app locks its own.\n if (!expo) return { ...NOTHING };\n // Missing from the build: stand-ins, to say so when they are used.\n if (!present(expo)) return { reported: expo, lock: expo, unlock: expo } as never;\n\n const ORIENTATIONS: Record<number, Orientation> = {\n [expo.Orientation.UNKNOWN]: 'unknown',\n [expo.Orientation.PORTRAIT_UP]: 'portrait',\n [expo.Orientation.PORTRAIT_DOWN]: 'portrait-upside-down',\n [expo.Orientation.LANDSCAPE_LEFT]: 'landscape-left',\n [expo.Orientation.LANDSCAPE_RIGHT]: 'landscape-right',\n };\n const LOCKS: Record<OrientationLock, import('expo-screen-orientation').OrientationLock> = {\n default: expo.OrientationLock.DEFAULT,\n all: expo.OrientationLock.ALL,\n portrait: expo.OrientationLock.PORTRAIT,\n landscape: expo.OrientationLock.LANDSCAPE,\n };\n\n // One per module, so every app the runtime mounts locks the same screen.\n return sharedBySource(DeviceOrientation, expo, () => ({\n reported: observedFrom(\n async () => ORIENTATIONS[await expo.getOrientationAsync()] ?? 'unknown',\n (listener) =>\n expo.addOrientationChangeListener(({ orientationInfo }) =>\n listener(ORIENTATIONS[orientationInfo.orientation] ?? 'unknown'),\n ),\n ),\n lock: (lock) => expo.lockAsync(LOCKS[lock]),\n unlock: () => expo.unlockAsync(),\n onWindowFront,\n }));\n },\n },\n );\n\n private readonly source = inject(DeviceOrientation.SOURCE);\n\n readonly orientation: Signal<Orientation> = observed(this.source.reported, 'unknown');\n\n readonly landscape: Signal<boolean> = computed(() => this.orientation().startsWith('landscape'));\n\n /** Every app's locks: the screen is the runtime's, not an app's. */\n private readonly window = sharedBySource(lockWindow, this.source, () => lockWindow(this.source));\n /** This app, among the apps locking the screen. */\n private readonly owner = {};\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n // An app that goes takes its locks with it, and leaves the screen to the apps still here.\n this.window.claims.drop(this.owner);\n this.window.apply();\n });\n }\n\n /**\n * Pin the screen. Returns the function that unpins it, so a screen that wants landscape while\n * it is up can hand it to `DestroyRef.onDestroy` and not think about it again.\n */\n lock(lock: OrientationLock): () => void {\n // A stack, as the status bar's is: a screen pushed over one with a lock of its own releases\n // only its own, and the lock beneath it comes back rather than the whole app unlocking.\n const drop = this.window.claims.push(this.owner, lock);\n this.window.apply();\n return () => {\n drop();\n this.window.apply();\n };\n }\n}\n\nfunction lockWindow(source: OrientationSource): TopClaim<OrientationLock> {\n return new TopClaim(\n (lock) => void source.lock(lock),\n () => void source.unlock(),\n source.onWindowFront,\n );\n}\n"]}
|
package/dist/screen-capture.js
CHANGED
|
@@ -34,7 +34,7 @@ export class ScreenCapture {
|
|
|
34
34
|
if (!loaded)
|
|
35
35
|
return expo;
|
|
36
36
|
// One per module, so every app the runtime mounts holds the same keys.
|
|
37
|
-
return sharedBySource(loaded, () => ({
|
|
37
|
+
return sharedBySource(ScreenCapture, loaded, () => ({
|
|
38
38
|
...loaded,
|
|
39
39
|
onWindowFront,
|
|
40
40
|
preventAgain: async () => {
|
|
@@ -50,7 +50,7 @@ export class ScreenCapture {
|
|
|
50
50
|
count = signal(0, /* @ts-ignore */
|
|
51
51
|
...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
52
52
|
/** Every app's keys: the window is the runtime's, not an app's. */
|
|
53
|
-
window = sharedBySource(this.native, () => new CaptureWindow(this.native));
|
|
53
|
+
window = sharedBySource(CaptureWindow, this.native, () => new CaptureWindow(this.native));
|
|
54
54
|
/** This app, among the apps holding keys. */
|
|
55
55
|
owner = {};
|
|
56
56
|
/**
|