@ng-native/expo 0.17.1 → 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.
@@ -84,7 +84,16 @@ class KeyboardModule : Module() {
84
84
  val activity = appContext.currentActivity ?: return
85
85
  activity.runOnUiThread {
86
86
  if (watcher == null && observing) {
87
- watcher = KeyboardWatcher.watch(activity.window.decorView, listener)
87
+ val current =
88
+ KeyboardWatcher.watch(activity.window.decorView, listener) ?: return@runOnUiThread
89
+ watcher = current
90
+ // A keyboard already up, and still, sends nothing until it next moves: report it as one
91
+ // that starts and ends at once, as the watcher reports a jump. One that is moving reports
92
+ // itself through the rest of its motion.
93
+ if (current.inset > 0 && !current.moving) {
94
+ listener.onKeyboardStart(current.inset, 0)
95
+ listener.onKeyboardEnd(current.inset)
96
+ }
88
97
  }
89
98
  }
90
99
  }
package/dist/audio.d.ts CHANGED
@@ -1,4 +1,19 @@
1
+ /**
2
+ * `audioPlayer` and `audioRecorder`, an `expo-audio` player and recorder owned by the component
3
+ * that makes each.
4
+ *
5
+ * ```ts
6
+ * protected readonly player = audioPlayer(this.source, { timeUpdate: 0.5 });
7
+ * protected readonly recorder = audioRecorder(RecordingPresets.HIGH_QUALITY);
8
+ *
9
+ * // {{ player.state().currentTime }} {{ recorder.state().durationMillis }}
10
+ * ```
11
+ */
12
+ import { type Signal } from '@angular/core';
1
13
  import { type Player } from './player.ts';
14
+ type RecordingOptions = import('expo-audio').RecordingOptions;
15
+ type RecorderState = import('expo-audio').RecorderState;
16
+ type AudioRecorder = import('expo-audio').AudioRecorder;
2
17
  /**
3
18
  * An audio player owned by the current component, released when it is destroyed.
4
19
  *
@@ -8,3 +23,25 @@ import { type Player } from './player.ts';
8
23
  export declare function audioPlayer(source: import('expo-audio').AudioSource, options?: {
9
24
  timeUpdate?: number;
10
25
  }): Player<import('expo-audio').AudioPlayer>;
26
+ /** A component's own recorder: the module's, and its status as a signal. */
27
+ export interface Recorder<T> {
28
+ /** The module's own recorder, for `prepareToRecordAsync()`, `record()`, `stop()` and the rest. */
29
+ readonly native: T;
30
+ /** `getStatus()`, asked every `statusInterval` seconds while the component lives. */
31
+ readonly state: Signal<RecorderState>;
32
+ }
33
+ /**
34
+ * An audio recorder owned by the current component, released when it is destroyed: what
35
+ * `useAudioRecorder` and `useAudioRecorderState` give a React component.
36
+ *
37
+ * The recorder is made with `options` merged for the platform, as `useAudioRecorder` makes it, so
38
+ * call `prepareToRecordAsync()` with none. Android reads `isMeteringEnabled` from these alone: a
39
+ * recorder made without it and prepared with it has no `metering`.
40
+ *
41
+ * `statusInterval` is in seconds, as `audioPlayer`'s `timeUpdate` is, where
42
+ * `useAudioRecorderState` takes milliseconds.
43
+ */
44
+ export declare function audioRecorder(options: RecordingOptions, { statusInterval }?: {
45
+ statusInterval?: number;
46
+ }): Recorder<AudioRecorder>;
47
+ export {};
package/dist/audio.js CHANGED
@@ -1,14 +1,28 @@
1
1
  /**
2
- * `audioPlayer`, an `expo-audio` player owned by the component that makes it.
2
+ * `audioPlayer` and `audioRecorder`, an `expo-audio` player and recorder owned by the component
3
+ * that makes each.
3
4
  *
4
5
  * ```ts
5
6
  * protected readonly player = audioPlayer(this.source, { timeUpdate: 0.5 });
7
+ * protected readonly recorder = audioRecorder(RecordingPresets.HIGH_QUALITY);
6
8
  *
7
- * // {{ player.state().currentTime }}
9
+ * // {{ player.state().currentTime }} {{ recorder.state().durationMillis }}
8
10
  * ```
9
11
  */
10
- import { expoModuleNow, unavailable } from "./native.js";
12
+ import { DestroyRef, inject, signal } from '@angular/core';
13
+ import { currentPlatform, expoModuleNow, unavailable } from "./native.js";
11
14
  import { ownPlayer, watchAudioPlayer } from "./player.js";
15
+ /** `expo-audio` where the platform has it, or the error that says why not. */
16
+ function expoAudio() {
17
+ const expo = expoModuleNow('expo-audio', () => require('expo-audio'), [
18
+ 'ios',
19
+ 'android',
20
+ 'web',
21
+ ]);
22
+ if (!expo)
23
+ throw unavailable('expo-audio', 'Stand in for the player or recorder in the test.');
24
+ return expo;
25
+ }
12
26
  /**
13
27
  * An audio player owned by the current component, released when it is destroyed.
14
28
  *
@@ -16,11 +30,81 @@ import { ownPlayer, watchAudioPlayer } from "./player.js";
16
30
  * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.
17
31
  */
18
32
  export function audioPlayer(source, options = {}) {
19
- const expo = expoModuleNow('expo-audio', () => require('expo-audio'), ['ios', 'android', 'web']);
20
- if (!expo)
21
- throw unavailable('expo-audio', 'Stand in for the player in the test.');
33
+ const expo = expoAudio();
22
34
  const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;
23
35
  const native = expo.createAudioPlayer(source, { updateInterval });
24
36
  return ownPlayer(native, () => watchAudioPlayer(native));
25
37
  }
38
+ /**
39
+ * An audio recorder owned by the current component, released when it is destroyed: what
40
+ * `useAudioRecorder` and `useAudioRecorderState` give a React component.
41
+ *
42
+ * The recorder is made with `options` merged for the platform, as `useAudioRecorder` makes it, so
43
+ * call `prepareToRecordAsync()` with none. Android reads `isMeteringEnabled` from these alone: a
44
+ * recorder made without it and prepared with it has no `metering`.
45
+ *
46
+ * `statusInterval` is in seconds, as `audioPlayer`'s `timeUpdate` is, where
47
+ * `useAudioRecorderState` takes milliseconds.
48
+ */
49
+ export function audioRecorder(options, { statusInterval = 0.5 } = {}) {
50
+ // First, so a call outside an injection context throws before there is a recorder to leak.
51
+ const destroyRef = inject(DestroyRef);
52
+ const expo = expoAudio();
53
+ const native = makeRecorder(expo, forPlatform(options));
54
+ const state = signal(native.getStatus(), { ...(ngDevMode ? { debugName: "state" } : /* istanbul ignore next */ {}), equal: sameStatus });
55
+ const polling = setInterval(() => state.set(native.getStatus()), statusInterval * 1000);
56
+ destroyRef.onDestroy(() => {
57
+ clearInterval(polling);
58
+ releaseRecorder(native);
59
+ });
60
+ return { native, state: state.asReadonly() };
61
+ }
62
+ /**
63
+ * The platform's recorder class. On the web `expo-audio`'s `AudioModule` is a plain module whose
64
+ * recorder is `AudioRecorderWeb`; elsewhere it is the native module's `AudioRecorder`.
65
+ */
66
+ function makeRecorder(expo, options) {
67
+ const module = expo.AudioModule;
68
+ const Recorder = currentPlatform() === 'web' ? module['AudioRecorderWeb'] : module['AudioRecorder'];
69
+ return new Recorder(options);
70
+ }
71
+ /**
72
+ * What `useAudioRecorder` does when its component goes: the web recorder, which has no
73
+ * `release()`, clears its timeouts instead.
74
+ */
75
+ function releaseRecorder(native) {
76
+ const recorder = native;
77
+ if (currentPlatform() === 'web')
78
+ recorder.clearTimeouts?.();
79
+ else
80
+ recorder.release?.();
81
+ }
82
+ /** `expo-audio`'s own `createRecordingOptions`, which it does not export: the platform's merged in. */
83
+ function forPlatform(options) {
84
+ const common = {
85
+ extension: options.extension,
86
+ sampleRate: options.sampleRate,
87
+ numberOfChannels: options.numberOfChannels,
88
+ bitRate: options.bitRate,
89
+ isMeteringEnabled: options.isMeteringEnabled ?? false,
90
+ };
91
+ const platform = currentPlatform();
92
+ if (platform === 'ios')
93
+ return { ...common, directory: options.directory, ...options.ios };
94
+ if (platform === 'android')
95
+ return { ...common, directory: options.directory, ...options.android };
96
+ return { ...common, ...options.web };
97
+ }
98
+ /** What `useAudioRecorderState` counts as no change: up to 50 ms of duration and up to 0.1 dB of level, both included. */
99
+ function sameStatus(a, b) {
100
+ const metering = a.metering === undefined || b.metering === undefined
101
+ ? a.metering === b.metering
102
+ : Math.abs(a.metering - b.metering) <= 0.1;
103
+ return (a.canRecord === b.canRecord &&
104
+ a.isRecording === b.isRecording &&
105
+ a.mediaServicesDidReset === b.mediaServicesDidReset &&
106
+ a.url === b.url &&
107
+ Math.abs(a.durationMillis - b.durationMillis) <= 50 &&
108
+ metering);
109
+ }
26
110
  //# sourceMappingURL=audio.js.map
package/dist/audio.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAe,MAAM,aAAa,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,MAAwC,EACxC,UAAmC,EAAE;IAErC,MAAM,IAAI,GAAG,aAAa,CACxB,YAAY,EACZ,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,CAAgC,EAC1D,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAC1B,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,MAAM,WAAW,CAAC,YAAY,EAAE,sCAAsC,CAAC,CAAC;IACnF,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAChG,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;IAClE,OAAO,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["/**\n * `audioPlayer`, an `expo-audio` player owned by the component that makes it.\n *\n * ```ts\n * protected readonly player = audioPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // {{ player.state().currentTime }}\n * ```\n */\nimport { expoModuleNow, unavailable } from './native.ts';\nimport { ownPlayer, watchAudioPlayer, type Player } from './player.ts';\n\n/**\n * An audio player owned by the current component, released when it is destroyed.\n *\n * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is\n * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.\n */\nexport function audioPlayer(\n source: import('expo-audio').AudioSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-audio').AudioPlayer> {\n const expo = expoModuleNow(\n 'expo-audio',\n () => require('expo-audio') as typeof import('expo-audio'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw unavailable('expo-audio', 'Stand in for the player in the test.');\n const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;\n const native = expo.createAudioPlayer(source, { updateInterval });\n return ownPlayer(native, () => watchAudioPlayer(native));\n}\n"]}
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `audioPlayer` and `audioRecorder`, an `expo-audio` player and recorder owned by the component\n * that makes each.\n *\n * ```ts\n * protected readonly player = audioPlayer(this.source, { timeUpdate: 0.5 });\n * protected readonly recorder = audioRecorder(RecordingPresets.HIGH_QUALITY);\n *\n * // {{ player.state().currentTime }} {{ recorder.state().durationMillis }}\n * ```\n */\nimport { DestroyRef, inject, signal, type Signal } from '@angular/core';\nimport { currentPlatform, expoModuleNow, unavailable } from './native.ts';\nimport { ownPlayer, watchAudioPlayer, type Player } from './player.ts';\n\ntype ExpoAudio = typeof import('expo-audio');\ntype RecordingOptions = import('expo-audio').RecordingOptions;\ntype RecorderState = import('expo-audio').RecorderState;\ntype AudioRecorder = import('expo-audio').AudioRecorder;\n\n/** `expo-audio` where the platform has it, or the error that says why not. */\nfunction expoAudio(): ExpoAudio {\n const expo = expoModuleNow('expo-audio', () => require('expo-audio') as ExpoAudio, [\n 'ios',\n 'android',\n 'web',\n ]);\n if (!expo) throw unavailable('expo-audio', 'Stand in for the player or recorder in the test.');\n return expo;\n}\n\n/**\n * An audio player owned by the current component, released when it is destroyed.\n *\n * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is\n * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.\n */\nexport function audioPlayer(\n source: import('expo-audio').AudioSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-audio').AudioPlayer> {\n const expo = expoAudio();\n const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;\n const native = expo.createAudioPlayer(source, { updateInterval });\n return ownPlayer(native, () => watchAudioPlayer(native));\n}\n\n/** A component's own recorder: the module's, and its status as a signal. */\nexport interface Recorder<T> {\n /** The module's own recorder, for `prepareToRecordAsync()`, `record()`, `stop()` and the rest. */\n readonly native: T;\n /** `getStatus()`, asked every `statusInterval` seconds while the component lives. */\n readonly state: Signal<RecorderState>;\n}\n\n/**\n * An audio recorder owned by the current component, released when it is destroyed: what\n * `useAudioRecorder` and `useAudioRecorderState` give a React component.\n *\n * The recorder is made with `options` merged for the platform, as `useAudioRecorder` makes it, so\n * call `prepareToRecordAsync()` with none. Android reads `isMeteringEnabled` from these alone: a\n * recorder made without it and prepared with it has no `metering`.\n *\n * `statusInterval` is in seconds, as `audioPlayer`'s `timeUpdate` is, where\n * `useAudioRecorderState` takes milliseconds.\n */\nexport function audioRecorder(\n options: RecordingOptions,\n { statusInterval = 0.5 }: { statusInterval?: number } = {},\n): Recorder<AudioRecorder> {\n // First, so a call outside an injection context throws before there is a recorder to leak.\n const destroyRef = inject(DestroyRef);\n const expo = expoAudio();\n const native = makeRecorder(expo, forPlatform(options));\n const state = signal(native.getStatus(), { equal: sameStatus });\n const polling = setInterval(() => state.set(native.getStatus()), statusInterval * 1000);\n destroyRef.onDestroy(() => {\n clearInterval(polling);\n releaseRecorder(native);\n });\n return { native, state: state.asReadonly() };\n}\n\n/**\n * The platform's recorder class. On the web `expo-audio`'s `AudioModule` is a plain module whose\n * recorder is `AudioRecorderWeb`; elsewhere it is the native module's `AudioRecorder`.\n */\nfunction makeRecorder(expo: ExpoAudio, options: Record<string, unknown>): AudioRecorder {\n const module = expo.AudioModule as unknown as Record<string, new (o: unknown) => AudioRecorder>;\n const Recorder =\n currentPlatform() === 'web' ? module['AudioRecorderWeb'] : module['AudioRecorder'];\n return new Recorder!(options);\n}\n\n/**\n * What `useAudioRecorder` does when its component goes: the web recorder, which has no\n * `release()`, clears its timeouts instead.\n */\nfunction releaseRecorder(native: AudioRecorder): void {\n const recorder = native as AudioRecorder & { clearTimeouts?: () => void };\n if (currentPlatform() === 'web') recorder.clearTimeouts?.();\n else recorder.release?.();\n}\n\n/** `expo-audio`'s own `createRecordingOptions`, which it does not export: the platform's merged in. */\nfunction forPlatform(options: RecordingOptions): Record<string, unknown> {\n const common = {\n extension: options.extension,\n sampleRate: options.sampleRate,\n numberOfChannels: options.numberOfChannels,\n bitRate: options.bitRate,\n isMeteringEnabled: options.isMeteringEnabled ?? false,\n };\n const platform = currentPlatform();\n if (platform === 'ios') return { ...common, directory: options.directory, ...options.ios };\n if (platform === 'android')\n return { ...common, directory: options.directory, ...options.android };\n return { ...common, ...options.web };\n}\n\n/** What `useAudioRecorderState` counts as no change: up to 50 ms of duration and up to 0.1 dB of level, both included. */\nfunction sameStatus(a: RecorderState, b: RecorderState): boolean {\n const metering =\n a.metering === undefined || b.metering === undefined\n ? a.metering === b.metering\n : Math.abs(a.metering - b.metering) <= 0.1;\n return (\n a.canRecord === b.canRecord &&\n a.isRecording === b.isRecording &&\n a.mediaServicesDidReset === b.mediaServicesDidReset &&\n a.url === b.url &&\n Math.abs(a.durationMillis - b.durationMillis) <= 50 &&\n metering\n );\n}\n"]}
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { DestroyRef, InjectionToken, Service, inject, signal, untracked, } from '@angular/core';
11
11
  import { TopClaim, onWindowFront, sharedBySource } from '@ng-native/device';
12
+ import { nativePlatform } from '@ng-native/fabric';
12
13
  import { expoModule, present } from "./native.js";
13
14
  import * as i0 from "@angular/core";
14
15
  export class Brightness {
@@ -22,17 +23,51 @@ export class Brightness {
22
23
  if (!present(expo))
23
24
  return expo;
24
25
  // One per module, so every app the runtime mounts sets the same screen.
25
- return sharedBySource(expo, () => ({
26
- get: () => expo.getBrightnessAsync(),
27
- set: (level) => expo.setBrightnessAsync(level),
28
- restore: () => expo.restoreSystemBrightnessAsync(),
29
- onWindowFront,
30
- }));
26
+ return sharedBySource(Brightness, expo, () => {
27
+ if (nativePlatform() === 'android') {
28
+ return {
29
+ get: () => expo.getBrightnessAsync(),
30
+ set: (level) => expo.setBrightnessAsync(level),
31
+ restore: () => expo.restoreSystemBrightnessAsync(),
32
+ onWindowFront,
33
+ };
34
+ }
35
+ // iOS has no level of the app's own: `setBrightnessAsync` sets the screen's, which stays
36
+ // after the app until the device locks, and `restoreSystemBrightnessAsync` does nothing
37
+ // there. So the level the screen had before the first set is read, and set again. One
38
+ // queue, so a set asked for while a restore runs reads that level after it is back.
39
+ let before = null;
40
+ let holding = false;
41
+ let queue = Promise.resolve();
42
+ const inTurn = (step) => {
43
+ const turn = queue.then(step);
44
+ queue = turn.catch(() => { });
45
+ return turn;
46
+ };
47
+ return {
48
+ get: () => expo.getBrightnessAsync(),
49
+ set: (level) => inTurn(async () => {
50
+ if (!holding) {
51
+ before = await expo.getBrightnessAsync().catch(() => null);
52
+ holding = true;
53
+ }
54
+ await expo.setBrightnessAsync(level);
55
+ }),
56
+ restore: () => inTurn(async () => {
57
+ if (!holding)
58
+ return;
59
+ holding = false;
60
+ if (before !== null)
61
+ await expo.setBrightnessAsync(before);
62
+ }),
63
+ onWindowFront,
64
+ };
65
+ });
31
66
  },
32
67
  });
33
68
  native = inject(Brightness.SOURCE);
34
69
  /** Every app's levels: the screen is the runtime's, not an app's. */
35
- window = sharedBySource(this.native, () => new BrightnessWindow(this.native));
70
+ window = sharedBySource(BrightnessWindow, this.native, () => new BrightnessWindow(this.native));
36
71
  current = this.window.current;
37
72
  /** This app, among the apps setting the level. */
38
73
  owner = {};
@@ -100,9 +135,15 @@ class BrightnessWindow {
100
135
  }, () => { });
101
136
  }
102
137
  restore() {
103
- void this.native
138
+ const native = this.native;
139
+ void native
104
140
  ?.restore()
105
- .then(() => this.native?.get().then((level) => this.current.set(level)));
141
+ .then(() => native.get())
142
+ .then((level) => {
143
+ // A level set while the restore ran is the one showing, not the one read back.
144
+ if (untracked(this.levels.claims.all).length === 0)
145
+ this.current.set(level);
146
+ }, () => { });
106
147
  }
107
148
  }
108
149
  //# sourceMappingURL=brightness.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"brightness.js","sourceRoot":"","sources":["../src/brightness.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EACL,UAAU,EACV,cAAc,EACd,OAAO,EACP,MAAM,EACN,MAAM,EAEN,SAAS,GAEV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;;AAWlD,MAAM,OAAO,UAAU;IACrB,yEAAyE;IACzE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,iCAAiC,EACjC;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,mEAAmE;YACnE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAa,CAAC;YACzC,wEAAwE;YACxE,OAAO,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;gBACjC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE;gBACpC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC;gBAC9C,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,4BAA4B,EAAE;gBAClD,aAAa;aACd,CAAC,CAAC,CAAC;QACN,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACpD,qEAAqE;IACpD,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IAC/C,kDAAkD;IACjC,KAAK,GAAG,EAAE,CAAC;IAE5B,gGAAgG;IACvF,KAAK,GAAmB,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;IAE3D;QACE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YAChC,2FAA2F;YAC3F,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAC,KAAa;QACf,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAChD,6FAA6F;QAC7F,6DAA6D;QAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC3B,OAAO,GAAG,EAAE;YACV,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7B,CAAC,CAAC;IACJ,CAAC;IAED,OAAO;QACL,+FAA+F;QAC/F,2CAA2C;QAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;uGAtEU,UAAU;wGAAV,UAAU;;2FAAV,UAAU;kBADtB,OAAO;;AA0ER,8DAA8D;AAC9D,MAAM,gBAAgB;IACX,OAAO,GAA2B,MAAM,CAAC,CAAC;gFAAC,CAAC;IAC5C,MAAM,CAAmB;IACjB,MAAM,CAA0B;IAEjD,YAAY,MAA+B;QACzC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,IAAI,QAAQ,CACxB,CAAC,KAAK,EAAE,EAAE;YACR,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACxB,KAAK,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC,EACD,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EACpB,OAAO,CAAC,MAAM,CAAC,EAAE,aAAa,CAC/B,CAAC;QACF,8EAA8E;QAC9E,KAAK,OAAO,CAAC,MAAM,CAAC;YAClB,EAAE,GAAG,EAAE;aACN,IAAI,CACH,CAAC,KAAK,EAAE,EAAE;YACR,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9E,CAAC,EACD,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;IACN,CAAC;IAED,OAAO;QACL,KAAK,IAAI,CAAC,MAAM;YACd,EAAE,OAAO,EAAE;aACV,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;CACF","sourcesContent":["/**\n * `Brightness`, bound to `expo-brightness`.\n *\n * For the screens that have a reason to change it: a boarding pass, a QR code.\n *\n * The app's own brightness only. The system one is a global setting an app can leave changed\n * after it is gone, and needs a permission on Android - a decision to make deliberately against\n * Expo's own API rather than to reach through a convenience here.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n inject,\n signal,\n type Signal,\n untracked,\n type WritableSignal,\n} from '@angular/core';\nimport { TopClaim, onWindowFront, sharedBySource } from '@ng-native/device';\nimport { expoModule, present } from './native.ts';\n\nexport interface NativeBrightness {\n get(): Promise<number>;\n set(level: number): Promise<void>;\n restore(): Promise<void>;\n /** Calls `listener` when a window comes to the front, to be given the level again. */\n onWindowFront?(listener: () => void): () => void;\n}\n\n@Service()\nexport class Brightness {\n /** Overridden in a test to watch the level without changing a screen. */\n static readonly SOURCE = new InjectionToken<NativeBrightness | null>(\n 'angular-native.brightnessSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-brightness',\n () => require('expo-brightness') as typeof import('expo-brightness'),\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 // One per module, so every app the runtime mounts sets the same screen.\n return sharedBySource(expo, () => ({\n get: () => expo.getBrightnessAsync(),\n set: (level) => expo.setBrightnessAsync(level),\n restore: () => expo.restoreSystemBrightnessAsync(),\n onWindowFront,\n }));\n },\n },\n );\n\n private readonly native = inject(Brightness.SOURCE);\n /** Every app's levels: the screen is the runtime's, not an app's. */\n private readonly window = sharedBySource(this.native, () => new BrightnessWindow(this.native));\n private readonly current = this.window.current;\n /** This app, among the apps setting the level. */\n private readonly owner = {};\n\n /** Nought to one. One until the platform answers, since that is what a screen already shows. */\n readonly level: Signal<number> = this.current.asReadonly();\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n // An app that goes takes its levels with it, and leaves the screen to the apps still here.\n this.window.levels.claims.drop(this.owner);\n this.window.levels.apply();\n });\n }\n\n /**\n * Set it, and return the function that puts it back.\n *\n * Always paired, because the alternative is an app that leaves the screen at full brightness\n * after the boarding pass has been shown - which the user experiences as a battery fault.\n */\n set(level: number): () => void {\n const clamped = Math.min(1, Math.max(0, level));\n // A stack, as the status bar's is: a screen pushed over one that set a level of its own puts\n // back the level beneath it, not the system's, when it goes.\n const drop = this.window.levels.claims.push(this.owner, clamped);\n this.window.levels.apply();\n return () => {\n drop();\n this.window.levels.apply();\n };\n }\n\n restore(): void {\n // This app's levels are put back, so a release still to come has nothing left to undo. Another\n // app's stay: the screen is the runtime's.\n this.window.levels.claims.drop(this.owner);\n if (untracked(this.window.levels.claims.all).length > 0) {\n this.window.levels.apply();\n return;\n }\n this.window.levels.clear();\n this.window.restore();\n }\n}\n\n/** The screen's level, as every app on one module sets it. */\nclass BrightnessWindow {\n readonly current: WritableSignal<number> = signal(1);\n readonly levels: TopClaim<number>;\n private readonly native: NativeBrightness | null;\n\n constructor(native: NativeBrightness | null) {\n this.native = native;\n this.levels = new TopClaim(\n (level) => {\n this.current.set(level);\n void this.native?.set(level);\n },\n () => this.restore(),\n present(native)?.onWindowFront,\n );\n // What the screen shows, unless a level was set before the platform answered.\n void present(native)\n ?.get()\n .then(\n (level) => {\n if (untracked(this.levels.claims.all).length === 0) this.current.set(level);\n },\n () => {},\n );\n }\n\n restore(): void {\n void this.native\n ?.restore()\n .then(() => this.native?.get().then((level) => this.current.set(level)));\n }\n}\n"]}
1
+ {"version":3,"file":"brightness.js","sourceRoot":"","sources":["../src/brightness.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Brightness`, bound to `expo-brightness`.\n *\n * For the screens that have a reason to change it: a boarding pass, a QR code.\n *\n * The app's own brightness only. The system one is a global setting an app can leave changed\n * after it is gone, and needs a permission on Android - a decision to make deliberately against\n * Expo's own API rather than to reach through a convenience here.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n inject,\n signal,\n type Signal,\n untracked,\n type WritableSignal,\n} from '@angular/core';\nimport { TopClaim, onWindowFront, sharedBySource } from '@ng-native/device';\nimport { nativePlatform } from '@ng-native/fabric';\nimport { expoModule, present } from './native.ts';\n\nexport interface NativeBrightness {\n get(): Promise<number>;\n set(level: number): Promise<void>;\n restore(): Promise<void>;\n /** Calls `listener` when a window comes to the front, to be given the level again. */\n onWindowFront?(listener: () => void): () => void;\n}\n\n@Service()\nexport class Brightness {\n /** Overridden in a test to watch the level without changing a screen. */\n static readonly SOURCE = new InjectionToken<NativeBrightness | null>(\n 'angular-native.brightnessSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-brightness',\n () => require('expo-brightness') as typeof import('expo-brightness'),\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 // One per module, so every app the runtime mounts sets the same screen.\n return sharedBySource(Brightness, expo, () => {\n if (nativePlatform() === 'android') {\n return {\n get: () => expo.getBrightnessAsync(),\n set: (level) => expo.setBrightnessAsync(level),\n restore: () => expo.restoreSystemBrightnessAsync(),\n onWindowFront,\n };\n }\n // iOS has no level of the app's own: `setBrightnessAsync` sets the screen's, which stays\n // after the app until the device locks, and `restoreSystemBrightnessAsync` does nothing\n // there. So the level the screen had before the first set is read, and set again. One\n // queue, so a set asked for while a restore runs reads that level after it is back.\n let before: number | null = null;\n let holding = false;\n let queue: Promise<unknown> = Promise.resolve();\n const inTurn = (step: () => Promise<void>): Promise<void> => {\n const turn = queue.then(step);\n queue = turn.catch(() => {});\n return turn;\n };\n return {\n get: () => expo.getBrightnessAsync(),\n set: (level) =>\n inTurn(async () => {\n if (!holding) {\n before = await expo.getBrightnessAsync().catch(() => null);\n holding = true;\n }\n await expo.setBrightnessAsync(level);\n }),\n restore: () =>\n inTurn(async () => {\n if (!holding) return;\n holding = false;\n if (before !== null) await expo.setBrightnessAsync(before);\n }),\n onWindowFront,\n };\n });\n },\n },\n );\n\n private readonly native = inject(Brightness.SOURCE);\n /** Every app's levels: the screen is the runtime's, not an app's. */\n private readonly window = sharedBySource(\n BrightnessWindow,\n this.native,\n () => new BrightnessWindow(this.native),\n );\n private readonly current = this.window.current;\n /** This app, among the apps setting the level. */\n private readonly owner = {};\n\n /** Nought to one. One until the platform answers, since that is what a screen already shows. */\n readonly level: Signal<number> = this.current.asReadonly();\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n // An app that goes takes its levels with it, and leaves the screen to the apps still here.\n this.window.levels.claims.drop(this.owner);\n this.window.levels.apply();\n });\n }\n\n /**\n * Set it, and return the function that puts it back.\n *\n * Always paired, because the alternative is an app that leaves the screen at full brightness\n * after the boarding pass has been shown - which the user experiences as a battery fault.\n */\n set(level: number): () => void {\n const clamped = Math.min(1, Math.max(0, level));\n // A stack, as the status bar's is: a screen pushed over one that set a level of its own puts\n // back the level beneath it, not the system's, when it goes.\n const drop = this.window.levels.claims.push(this.owner, clamped);\n this.window.levels.apply();\n return () => {\n drop();\n this.window.levels.apply();\n };\n }\n\n restore(): void {\n // This app's levels are put back, so a release still to come has nothing left to undo. Another\n // app's stay: the screen is the runtime's.\n this.window.levels.claims.drop(this.owner);\n if (untracked(this.window.levels.claims.all).length > 0) {\n this.window.levels.apply();\n return;\n }\n this.window.levels.clear();\n this.window.restore();\n }\n}\n\n/** The screen's level, as every app on one module sets it. */\nclass BrightnessWindow {\n readonly current: WritableSignal<number> = signal(1);\n readonly levels: TopClaim<number>;\n private readonly native: NativeBrightness | null;\n\n constructor(native: NativeBrightness | null) {\n this.native = native;\n this.levels = new TopClaim(\n (level) => {\n this.current.set(level);\n void this.native?.set(level);\n },\n () => this.restore(),\n present(native)?.onWindowFront,\n );\n // What the screen shows, unless a level was set before the platform answered.\n void present(native)\n ?.get()\n .then(\n (level) => {\n if (untracked(this.levels.claims.all).length === 0) this.current.set(level);\n },\n () => {},\n );\n }\n\n restore(): void {\n const native = this.native;\n void native\n ?.restore()\n .then(() => native.get())\n .then(\n (level) => {\n // A level set while the restore ran is the one showing, not the one read back.\n if (untracked(this.levels.claims.all).length === 0) this.current.set(level);\n },\n () => {},\n );\n }\n}\n"]}
@@ -165,11 +165,13 @@ export declare class UiList {
165
165
  /**
166
166
  * The system's swipe actions on a list row, iOS only. The first child is the row; the actions go
167
167
  * in `<ui-slot name="actions" [extraProps]="{ edge: 'trailing', allowsFullSwipe: true }">`, one
168
- * slot per edge, as `ui-button`s.
168
+ * slot per edge, as `ui-button`s. A sideways drag on the row is the swipe's, so a `pressable` in a
169
+ * `ui-view-host` in the row does not press for it.
169
170
  */
170
171
  export declare class UiSwipeActions {
171
172
  protected readonly nativeView: true;
172
173
  readonly modifiers: import("@angular/core").InputSignal<readonly UiModifier[] | undefined>;
174
+ constructor();
173
175
  static ɵfac: i0.ɵɵFactoryDeclaration<UiSwipeActions, never>;
174
176
  static ɵcmp: i0.ɵɵComponentDeclaration<UiSwipeActions, "ui-swipe-actions", never, { "modifiers": { "alias": "modifiers"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
175
177
  }
@@ -852,6 +854,13 @@ export type UiPresentationDetent = 'medium' | 'large' | {
852
854
  } | {
853
855
  readonly height: number;
854
856
  };
857
+ /**
858
+ * Whether the screen behind a SwiftUI sheet takes touches, as `presentationBackgroundInteraction`
859
+ * says it: as the system decides, always, never, or while the sheet rests at or below a detent.
860
+ */
861
+ export type UiBottomSheetBackgroundInteraction = 'automatic' | 'enabled' | 'disabled' | {
862
+ readonly upThrough: UiPresentationDetent;
863
+ };
855
864
  /** The functions `@expo/ui` defines on Compose's sheet, called with the view's tag as `this`. */
856
865
  export interface UiBottomSheetViewFunctions {
857
866
  /** Slides the sheet away, answering once it has gone. */
@@ -939,6 +948,30 @@ export declare class UiBottomSheet {
939
948
  * way. Neither is the sheet as the system draws it.
940
949
  */
941
950
  readonly backgroundColor: import("@angular/core").InputSignal<string | undefined>;
951
+ /**
952
+ * The user cannot dismiss the sheet; only `open` closes it. On iOS it is SwiftUI's
953
+ * `interactiveDismissDisabled`: a swipe down takes the sheet to its smallest detent and no
954
+ * further, and a tap outside it does nothing. On Android a swipe, the back button and a tap
955
+ * outside the sheet are all turned off, as `@expo/ui`'s own sheet does; Compose's sheet has no
956
+ * way to keep the drag between its heights without the swipe that dismisses it, so that drag is
957
+ * off too.
958
+ */
959
+ readonly interactiveDismissDisabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
960
+ /**
961
+ * Whether the screen behind the sheet takes touches, iOS 16.4 and later only: SwiftUI's
962
+ * `presentationBackgroundInteraction`. `'enabled'` always, `'disabled'` never, `'automatic'` as
963
+ * the system decides, and `{ upThrough: 'medium' }` while the sheet rests at that detent or
964
+ * below it, which should be one of its `detents`. Unset, SwiftUI decides. Android has no
965
+ * equivalent: Compose's sheet is in a window of its own over the screen, which takes no touch
966
+ * while it shows.
967
+ */
968
+ readonly backgroundInteraction: import("@angular/core").InputSignal<UiBottomSheetBackgroundInteraction | undefined>;
969
+ /**
970
+ * More SwiftUI modifiers, iOS only, after the sheet's own on the `ui-group` around its content,
971
+ * where SwiftUI reads presentation modifiers from: `presentationCornerRadius`, for one. They
972
+ * apply to the content too. Android takes no notice of them.
973
+ */
974
+ readonly modifiers: import("@angular/core").InputSignal<readonly UiModifier[] | undefined>;
942
975
  /**
943
976
  * The sheet has finished closing, whether the user dismissed it or `open` was set false: the
944
977
  * moment another sheet or a dialog can be presented.
@@ -985,6 +1018,16 @@ export declare class UiBottomSheet {
985
1018
  * detents of a sheet not sized to its content are followed while it is open.
986
1019
  */
987
1020
  protected readonly presentation: Signal<readonly UiModifier[]>;
1021
+ /** Whether the screen behind the sheet takes touches, in `@expo/ui`'s shape, where one is set. */
1022
+ private readonly interaction;
1023
+ /**
1024
+ * How Compose's sheet may be dismissed other than by a swipe. Always committed, so that turning
1025
+ * `interactiveDismissDisabled` off again reaches native.
1026
+ */
1027
+ protected readonly dismissal: Signal<{
1028
+ shouldDismissOnBackPress: boolean;
1029
+ shouldDismissOnClickOutside: boolean;
1030
+ }>;
988
1031
  /** The `background-color` the element has from CSS, which the sheet draws rather than it. */
989
1032
  private readonly painted;
990
1033
  /** The sheet's colour: the input's, or else the element's from CSS. */
@@ -1036,6 +1079,6 @@ export declare class UiBottomSheet {
1036
1079
  /** The sheet has gone: its content leaves the tree. */
1037
1080
  private finish;
1038
1081
  static ɵfac: i0.ɵɵFactoryDeclaration<UiBottomSheet, never>;
1039
- static ɵcmp: i0.ɵɵComponentDeclaration<UiBottomSheet, "ui-bottom-sheet", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "fitToContents": { "alias": "fitToContents"; "required": false; "isSignal": true; }; "detents": { "alias": "detents"; "required": false; "isSignal": true; }; "showDragIndicator": { "alias": "showDragIndicator"; "required": false; "isSignal": true; }; "backgroundColor": { "alias": "backgroundColor"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "dismissed": "dismissed"; }, ["toolbarItems"], ["*", "ui-toolbar-item"], true, never>;
1082
+ static ɵcmp: i0.ɵɵComponentDeclaration<UiBottomSheet, "ui-bottom-sheet", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "fitToContents": { "alias": "fitToContents"; "required": false; "isSignal": true; }; "detents": { "alias": "detents"; "required": false; "isSignal": true; }; "showDragIndicator": { "alias": "showDragIndicator"; "required": false; "isSignal": true; }; "backgroundColor": { "alias": "backgroundColor"; "required": false; "isSignal": true; }; "interactiveDismissDisabled": { "alias": "interactiveDismissDisabled"; "required": false; "isSignal": true; }; "backgroundInteraction": { "alias": "backgroundInteraction"; "required": false; "isSignal": true; }; "modifiers": { "alias": "modifiers"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "dismissed": "dismissed"; }, ["toolbarItems"], ["*", "ui-toolbar-item"], true, never>;
1040
1083
  }
1041
1084
  export {};
@@ -289,15 +289,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
289
289
  host: { '[modifiers]': 'modifiers()' },
290
290
  }]
291
291
  }], propDecorators: { modifiers: [{ type: i0.Input, args: [{ isSignal: true, alias: "modifiers", required: false }] }] } });
292
+ /**
293
+ * How far a finger goes sideways, and further than it goes down, before a row's swipe actions take
294
+ * the touch. SwiftUI's own row button still taps at about 20 points and its actions come out by
295
+ * about 30.
296
+ */
297
+ const SWIPE_SLOP = 20;
298
+ /**
299
+ * Take a sideways drag from what holds the touch inside the row, as SwiftUI does: its list moves
300
+ * the row and never taps it. Native sends the app's views inside the row no cancel, since React
301
+ * Native's touch handler runs beside the list's gestures rather than giving way to them, so the
302
+ * row's own responder asks for the drag, and a `pressable` that gives it up does not press.
303
+ */
304
+ function takeSidewaysDrag() {
305
+ const node = inject(ElementRef).nativeElement;
306
+ const engine = node.engine ?? inject(HostEngine);
307
+ let from = null;
308
+ const stop = engine.setResponder(node, {
309
+ onStartShouldSetResponderCapture: (event) => {
310
+ from = { x: event.nativeEvent.pageX ?? 0, y: event.nativeEvent.pageY ?? 0 };
311
+ return false;
312
+ },
313
+ onMoveShouldSetResponderCapture: (event) => {
314
+ if (!from)
315
+ return false;
316
+ const across = Math.abs((event.nativeEvent.pageX ?? from.x) - from.x);
317
+ const down = Math.abs((event.nativeEvent.pageY ?? from.y) - from.y);
318
+ return across >= SWIPE_SLOP && across > down;
319
+ },
320
+ });
321
+ inject(DestroyRef).onDestroy(stop);
322
+ }
292
323
  /**
293
324
  * The system's swipe actions on a list row, iOS only. The first child is the row; the actions go
294
325
  * in `<ui-slot name="actions" [extraProps]="{ edge: 'trailing', allowsFullSwipe: true }">`, one
295
- * slot per edge, as `ui-button`s.
326
+ * slot per edge, as `ui-button`s. A sideways drag on the row is the swipe's, so a `pressable` in a
327
+ * `ui-view-host` in the row does not press for it.
296
328
  */
297
329
  export class UiSwipeActions {
298
330
  nativeView = nativeView();
299
331
  modifiers = input(/* @ts-ignore */
300
332
  ...(ngDevMode ? [undefined, { debugName: "modifiers" }] : /* istanbul ignore next */ []));
333
+ constructor() {
334
+ if (nativePlatform() !== 'android')
335
+ takeSidewaysDrag();
336
+ }
301
337
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: UiSwipeActions, deps: [], target: i0.ɵɵFactoryTarget.Component });
302
338
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: UiSwipeActions, isStandalone: true, selector: "ui-swipe-actions", inputs: { modifiers: { classPropertyName: "modifiers", publicName: "modifiers", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "modifiers": "modifiers()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
303
339
  }
@@ -308,7 +344,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
308
344
  template: '<ng-content />',
309
345
  host: { '[modifiers]': 'modifiers()' },
310
346
  }]
311
- }], propDecorators: { modifiers: [{ type: i0.Input, args: [{ isSignal: true, alias: "modifiers", required: false }] }] } });
347
+ }], ctorParameters: () => [], propDecorators: { modifiers: [{ type: i0.Input, args: [{ isSignal: true, alias: "modifiers", required: false }] }] } });
312
348
  /**
313
349
  * A SwiftUI `Slider`, or Compose's on Android. `valueChanged` reports each new value as the thumb
314
350
  * moves; note the d, which SwiftUI's event has and `@expo/ui`'s React prop does not.
@@ -1789,6 +1825,32 @@ export class UiBottomSheet {
1789
1825
  */
1790
1826
  backgroundColor = input(/* @ts-ignore */
1791
1827
  ...(ngDevMode ? [undefined, { debugName: "backgroundColor" }] : /* istanbul ignore next */ []));
1828
+ /**
1829
+ * The user cannot dismiss the sheet; only `open` closes it. On iOS it is SwiftUI's
1830
+ * `interactiveDismissDisabled`: a swipe down takes the sheet to its smallest detent and no
1831
+ * further, and a tap outside it does nothing. On Android a swipe, the back button and a tap
1832
+ * outside the sheet are all turned off, as `@expo/ui`'s own sheet does; Compose's sheet has no
1833
+ * way to keep the drag between its heights without the swipe that dismisses it, so that drag is
1834
+ * off too.
1835
+ */
1836
+ interactiveDismissDisabled = input(false, { ...(ngDevMode ? { debugName: "interactiveDismissDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1837
+ /**
1838
+ * Whether the screen behind the sheet takes touches, iOS 16.4 and later only: SwiftUI's
1839
+ * `presentationBackgroundInteraction`. `'enabled'` always, `'disabled'` never, `'automatic'` as
1840
+ * the system decides, and `{ upThrough: 'medium' }` while the sheet rests at that detent or
1841
+ * below it, which should be one of its `detents`. Unset, SwiftUI decides. Android has no
1842
+ * equivalent: Compose's sheet is in a window of its own over the screen, which takes no touch
1843
+ * while it shows.
1844
+ */
1845
+ backgroundInteraction = input(/* @ts-ignore */
1846
+ ...(ngDevMode ? [undefined, { debugName: "backgroundInteraction" }] : /* istanbul ignore next */ []));
1847
+ /**
1848
+ * More SwiftUI modifiers, iOS only, after the sheet's own on the `ui-group` around its content,
1849
+ * where SwiftUI reads presentation modifiers from: `presentationCornerRadius`, for one. They
1850
+ * apply to the content too. Android takes no notice of them.
1851
+ */
1852
+ modifiers = input(/* @ts-ignore */
1853
+ ...(ngDevMode ? [undefined, { debugName: "modifiers" }] : /* istanbul ignore next */ []));
1792
1854
  /**
1793
1855
  * The sheet has finished closing, whether the user dismissed it or `open` was set false: the
1794
1856
  * moment another sheet or a dialog can be presented.
@@ -1857,8 +1919,33 @@ export class UiBottomSheet {
1857
1919
  visibility: this.showDragIndicator() ? 'visible' : 'hidden',
1858
1920
  },
1859
1921
  ...this.background(),
1922
+ ...(this.interactiveDismissDisabled()
1923
+ ? [{ $type: 'interactiveDismissDisabled', isDisabled: true }]
1924
+ : []),
1925
+ ...this.interaction(),
1926
+ ...(this.modifiers() ?? []),
1860
1927
  ], /* @ts-ignore */
1861
1928
  ...(ngDevMode ? [{ debugName: "presentation" }] : /* istanbul ignore next */ []));
1929
+ /** Whether the screen behind the sheet takes touches, in `@expo/ui`'s shape, where one is set. */
1930
+ interaction = computed(() => {
1931
+ const given = this.backgroundInteraction();
1932
+ if (given === undefined)
1933
+ return [];
1934
+ const $type = 'presentationBackgroundInteraction';
1935
+ return typeof given === 'string'
1936
+ ? [{ $type, interactionType: given }]
1937
+ : [{ $type, interactionType: 'enabledUpThrough', detent: given.upThrough }];
1938
+ }, /* @ts-ignore */
1939
+ ...(ngDevMode ? [{ debugName: "interaction" }] : /* istanbul ignore next */ []));
1940
+ /**
1941
+ * How Compose's sheet may be dismissed other than by a swipe. Always committed, so that turning
1942
+ * `interactiveDismissDisabled` off again reaches native.
1943
+ */
1944
+ dismissal = computed(() => {
1945
+ const dismissible = !this.interactiveDismissDisabled();
1946
+ return { shouldDismissOnBackPress: dismissible, shouldDismissOnClickOutside: dismissible };
1947
+ }, /* @ts-ignore */
1948
+ ...(ngDevMode ? [{ debugName: "dismissal" }] : /* istanbul ignore next */ []));
1862
1949
  /** The `background-color` the element has from CSS, which the sheet draws rather than it. */
1863
1950
  painted = signal(undefined, /* @ts-ignore */
1864
1951
  ...(ngDevMode ? [{ debugName: "painted" }] : /* istanbul ignore next */ []));
@@ -1988,7 +2075,7 @@ export class UiBottomSheet {
1988
2075
  afterNextRender(() => this.mounted.set(this.open()), { injector: this.injector });
1989
2076
  }
1990
2077
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: UiBottomSheet, deps: [], target: i0.ɵɵFactoryTarget.Component });
1991
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: UiBottomSheet, isStandalone: true, selector: "ui-bottom-sheet", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, fitToContents: { classPropertyName: "fitToContents", publicName: "fitToContents", isSignal: true, isRequired: false, transformFunction: null }, detents: { classPropertyName: "detents", publicName: "detents", isSignal: true, isRequired: false, transformFunction: null }, showDragIndicator: { classPropertyName: "showDragIndicator", publicName: "showDragIndicator", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", dismissed: "dismissed" }, host: { properties: { "style.position": "\"absolute\"" } }, queries: [{ propertyName: "toolbarItems", predicate: UiToolbarItem, isSignal: true }], viewQueries: [{ propertyName: "sheet", first: true, predicate: ["sheet"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: `
2078
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: UiBottomSheet, isStandalone: true, selector: "ui-bottom-sheet", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, fitToContents: { classPropertyName: "fitToContents", publicName: "fitToContents", isSignal: true, isRequired: false, transformFunction: null }, detents: { classPropertyName: "detents", publicName: "detents", isSignal: true, isRequired: false, transformFunction: null }, showDragIndicator: { classPropertyName: "showDragIndicator", publicName: "showDragIndicator", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, interactiveDismissDisabled: { classPropertyName: "interactiveDismissDisabled", publicName: "interactiveDismissDisabled", isSignal: true, isRequired: false, transformFunction: null }, backgroundInteraction: { classPropertyName: "backgroundInteraction", publicName: "backgroundInteraction", isSignal: true, isRequired: false, transformFunction: null }, modifiers: { classPropertyName: "modifiers", publicName: "modifiers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", dismissed: "dismissed" }, host: { properties: { "style.position": "\"absolute\"" } }, queries: [{ propertyName: "toolbarItems", predicate: UiToolbarItem, isSignal: true }], viewQueries: [{ propertyName: "sheet", first: true, predicate: ["sheet"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: `
1992
2079
  <ng-template #content><ng-content /></ng-template>
1993
2080
  @if (swiftUI) {
1994
2081
  <ui-host style="position: absolute" pointerEvents="none" [style.width.px]="width()">
@@ -2029,6 +2116,8 @@ export class UiBottomSheet {
2029
2116
  [showDragHandle]="showDragIndicator()"
2030
2117
  [containerColor]="color()"
2031
2118
  [skipPartiallyExpanded]="fits()"
2119
+ [sheetGesturesEnabled]="!interactiveDismissDisabled()"
2120
+ [properties]="dismissal()"
2032
2121
  (dismissRequest)="dismissRequested($event)"
2033
2122
  >
2034
2123
  <ui-view-host layoutRoot [matchContents]="fits() || undefined">
@@ -2037,7 +2126,7 @@ export class UiBottomSheet {
2037
2126
  </ui-bottom-sheet-view>
2038
2127
  </ui-host>
2039
2128
  }
2040
- `, isInline: true, dependencies: [{ kind: "component", type: UiBottomSheetContent, selector: "ui-bottom-sheet-content", inputs: ["content"] }, { kind: "component", type: UiBottomSheetView, selector: "ui-bottom-sheet-view", inputs: ["isPresented", "fitToContents", "showDragHandle", "containerColor", "skipPartiallyExpanded"], outputs: ["isPresentedChange", "dismiss", "dismissRequest"] }, { kind: "component", type: UiGroup, selector: "ui-group", inputs: ["modifiers"] }, { kind: "component", type: UiHost, selector: "ui-host", inputs: ["matchContents", "ignoreSafeArea", "useViewportSizeMeasurement"] }, { kind: "component", type: UiNavigationStack, selector: "ui-navigation-stack", inputs: ["modifiers"] }, { kind: "component", type: UiSlot, selector: "ui-slot", inputs: ["name", "extraProps"] }, { kind: "component", type: UiSpacer, selector: "ui-spacer", inputs: ["modifiers"] }, { kind: "component", type: UiToolbar, selector: "ui-toolbar", inputs: ["modifiers"] }, { kind: "component", type: UiViewHost, selector: "ui-view-host", inputs: ["matchContents", "layoutRoot", "modifiers"] }, { kind: "component", type: UiZStack, selector: "ui-zstack", inputs: ["alignment", "modifiers"] }] });
2129
+ `, isInline: true, dependencies: [{ kind: "component", type: UiBottomSheetContent, selector: "ui-bottom-sheet-content", inputs: ["content"] }, { kind: "component", type: UiBottomSheetView, selector: "ui-bottom-sheet-view", inputs: ["isPresented", "fitToContents", "showDragHandle", "containerColor", "skipPartiallyExpanded", "sheetGesturesEnabled", "properties"], outputs: ["isPresentedChange", "dismiss", "dismissRequest"] }, { kind: "component", type: UiGroup, selector: "ui-group", inputs: ["modifiers"] }, { kind: "component", type: UiHost, selector: "ui-host", inputs: ["matchContents", "ignoreSafeArea", "useViewportSizeMeasurement"] }, { kind: "component", type: UiNavigationStack, selector: "ui-navigation-stack", inputs: ["modifiers"] }, { kind: "component", type: UiSlot, selector: "ui-slot", inputs: ["name", "extraProps"] }, { kind: "component", type: UiSpacer, selector: "ui-spacer", inputs: ["modifiers"] }, { kind: "component", type: UiToolbar, selector: "ui-toolbar", inputs: ["modifiers"] }, { kind: "component", type: UiViewHost, selector: "ui-view-host", inputs: ["matchContents", "layoutRoot", "modifiers"] }, { kind: "component", type: UiZStack, selector: "ui-zstack", inputs: ["alignment", "modifiers"] }] });
2041
2130
  }
2042
2131
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: UiBottomSheet, decorators: [{
2043
2132
  type: Component,
@@ -2096,6 +2185,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
2096
2185
  [showDragHandle]="showDragIndicator()"
2097
2186
  [containerColor]="color()"
2098
2187
  [skipPartiallyExpanded]="fits()"
2188
+ [sheetGesturesEnabled]="!interactiveDismissDisabled()"
2189
+ [properties]="dismissal()"
2099
2190
  (dismissRequest)="dismissRequested($event)"
2100
2191
  >
2101
2192
  <ui-view-host layoutRoot [matchContents]="fits() || undefined">
@@ -2109,5 +2200,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
2109
2200
  }]
2110
2201
  }], ctorParameters: () => [], propDecorators: { sheet: [{ type: i0.ViewChild, args: ['sheet', { ...{
2111
2202
  read: ElementRef,
2112
- }, isSignal: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], fitToContents: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitToContents", required: false }] }], detents: [{ type: i0.Input, args: [{ isSignal: true, alias: "detents", required: false }] }], showDragIndicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDragIndicator", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], toolbarItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => UiToolbarItem), { isSignal: true }] }] } });
2203
+ }, isSignal: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], fitToContents: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitToContents", required: false }] }], detents: [{ type: i0.Input, args: [{ isSignal: true, alias: "detents", required: false }] }], showDragIndicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDragIndicator", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], interactiveDismissDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactiveDismissDisabled", required: false }] }], backgroundInteraction: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundInteraction", required: false }] }], modifiers: [{ type: i0.Input, args: [{ isSignal: true, alias: "modifiers", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], toolbarItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => UiToolbarItem), { isSignal: true }] }] } });
2113
2204
  //# sourceMappingURL=expo-ui-components.js.map