@symbiote-native/engine 1.2.0 → 1.3.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/CMakeLists.txt +31 -1
- package/android/build.gradle +4 -1
- package/build/accessibility-info/index.android.js +21 -17
- package/build/accessibility-info/index.ios.js +2 -0
- package/build/accessibility-info/shared.d.ts +1 -1
- package/build/alert/shared.d.ts +1 -1
- package/build/alert/shared.js +3 -7
- package/build/app-state/index.d.ts +1 -1
- package/build/app-state/index.js +35 -27
- package/build/back-handler/index.d.ts +7 -2
- package/build/back-handler/index.js +19 -10
- package/build/events/index.js +10 -1
- package/build/fabric-props.js +1 -1
- package/build/fabric.d.ts +7 -0
- package/build/fabric.js +14 -0
- package/build/host-behavior.d.ts +1 -0
- package/build/image-loader.d.ts +4 -2
- package/build/image-loader.js +14 -25
- package/build/image-source-write.d.ts +1 -1
- package/build/image-source-write.js +14 -2
- package/build/index.d.ts +2 -2
- package/build/index.js +6 -6
- package/build/invariant.d.ts +1 -0
- package/build/invariant.js +10 -0
- package/build/keyboard/index.js +11 -32
- package/build/linking/index.android.js +5 -3
- package/build/linking/shared.d.ts +1 -1
- package/build/linking/shared.js +16 -19
- package/build/native-events.d.ts +5 -0
- package/build/native-events.js +21 -0
- package/build/node.js +18 -2
- package/build/permissions-android/index.android.d.ts +59 -0
- package/build/permissions-android/index.android.js +47 -0
- package/build/permissions-android/index.d.ts +1 -115
- package/build/permissions-android/index.ios.d.ts +59 -0
- package/build/permissions-android/index.ios.js +31 -0
- package/build/permissions-android/index.js +3 -184
- package/build/permissions-android/shared.d.ts +63 -0
- package/build/permissions-android/shared.js +66 -0
- package/build/platform/index.android.js +3 -5
- package/build/platform/index.ios.js +3 -3
- package/build/platform/shared.d.ts +4 -0
- package/build/platform/shared.js +9 -0
- package/build/platform-color/index.android.d.ts +4 -0
- package/build/platform-color/index.android.js +7 -0
- package/build/platform-color/index.d.ts +2 -20
- package/build/platform-color/index.js +1 -45
- package/build/platform-color/shared.d.ts +21 -0
- package/build/platform-color/shared.js +41 -0
- package/build/settings/index.android.d.ts +6 -0
- package/build/settings/index.android.js +21 -0
- package/build/settings/index.d.ts +1 -8
- package/build/settings/index.ios.d.ts +8 -0
- package/build/settings/index.ios.js +122 -0
- package/build/settings/index.js +3 -122
- package/build/share/index.android.js +9 -32
- package/build/share/index.ios.js +14 -15
- package/build/share/shared.d.ts +4 -2
- package/build/share/shared.js +7 -10
- package/build/status-bar/index.android.js +1 -1
- package/build/status-bar/index.ios.js +4 -3
- package/build/toast-android/index.android.d.ts +10 -0
- package/build/toast-android/index.android.js +108 -0
- package/build/toast-android/index.d.ts +1 -10
- package/build/toast-android/index.ios.d.ts +10 -0
- package/build/toast-android/index.ios.js +19 -0
- package/build/toast-android/index.js +3 -108
- package/cpp/SymbioteFabricProps.cpp +204 -120
- package/cpp/SymbioteFabricProps.h +5 -0
- package/cpp/SymbioteTree.cpp +20 -6
- package/package.json +2 -2
package/android/CMakeLists.txt
CHANGED
|
@@ -26,6 +26,19 @@ add_library(symbiote_engine
|
|
|
26
26
|
# So the JNI shim can say `#include "SymbioteEngineBindings.h"` rather than walk four levels up.
|
|
27
27
|
target_include_directories(symbiote_engine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../cpp)
|
|
28
28
|
|
|
29
|
+
# `measure` / `measureInWindow` / `measureLayout` need `react/renderer/dom/DOM.h`. libreactnative.so
|
|
30
|
+
# EXPORTS `facebook::react::dom::measure*`, but the prefab ships no `dom/` headers, so without this
|
|
31
|
+
# SymbioteTree.cpp's `__has_include` compiles the throwing stubs. Only that one header is copied into
|
|
32
|
+
# a private root: putting all of ReactCommon on the path would shadow the prefab's own headers.
|
|
33
|
+
set(SYMBIOTE_RN_DOM_HEADER ${REACT_NATIVE_DIR}/ReactCommon/react/renderer/dom/DOM.h)
|
|
34
|
+
if(DEFINED REACT_NATIVE_DIR AND EXISTS ${SYMBIOTE_RN_DOM_HEADER})
|
|
35
|
+
set(SYMBIOTE_RN_DOM_ROOT ${CMAKE_CURRENT_BINARY_DIR}/rn-dom-include)
|
|
36
|
+
file(COPY ${SYMBIOTE_RN_DOM_HEADER} DESTINATION ${SYMBIOTE_RN_DOM_ROOT}/react/renderer/dom)
|
|
37
|
+
target_include_directories(symbiote_engine PRIVATE ${SYMBIOTE_RN_DOM_ROOT})
|
|
38
|
+
else()
|
|
39
|
+
message(WARNING "symbiote_engine: ${SYMBIOTE_RN_DOM_HEADER} not found, measure* will throw")
|
|
40
|
+
endif()
|
|
41
|
+
|
|
29
42
|
set_target_properties(symbiote_engine PROPERTIES
|
|
30
43
|
CXX_STANDARD 20
|
|
31
44
|
CXX_STANDARD_REQUIRED ON
|
|
@@ -33,13 +46,30 @@ set_target_properties(symbiote_engine PROPERTIES
|
|
|
33
46
|
POSITION_INDEPENDENT_CODE ON
|
|
34
47
|
)
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
# Must match the flags libreactnative.so's folly was built with
|
|
50
|
+
# (ReactAndroid/cmake-utils/folly-flags.cmake). FOLLY_MOBILE is ABI, not tuning: without it arm64
|
|
51
|
+
# picks the SIMD F14 layout while RN's folly is the fallback one, and the link fails on
|
|
52
|
+
# `folly::f14::detail::F14LinkCheck<1>::check()` / `tlsPendingSafeInserts` from any folly::dynamic.
|
|
53
|
+
target_compile_definitions(symbiote_engine PRIVATE
|
|
54
|
+
-DFOLLY_NO_CONFIG=1
|
|
55
|
+
-DFOLLY_HAVE_CLOCK_GETTIME=1
|
|
56
|
+
-DFOLLY_USE_LIBCPP=1
|
|
57
|
+
-DFOLLY_CFG_NO_COROUTINES=1
|
|
58
|
+
-DFOLLY_MOBILE=1
|
|
59
|
+
-DFOLLY_HAVE_RECVMMSG=1
|
|
60
|
+
-DFOLLY_HAVE_PTHREAD=1
|
|
61
|
+
-DFOLLY_HAVE_XSI_STRERROR_R=1
|
|
62
|
+
)
|
|
37
63
|
|
|
38
64
|
# `jsi` and `reactnative` are the only two prefab modules ReactAndroid publishes
|
|
39
65
|
# (ReactAndroid/build.gradle.kts, the `prefab { }` block — the third is `hermestooling`).
|
|
40
66
|
# `reactnative` is the merged library that carries react/renderer/uimanager, react/renderer/mounting
|
|
41
67
|
# and the turbomodule JNI headers our C++ includes; there is no separate
|
|
42
68
|
# `react_renderer_uimanager` prefab target to link.
|
|
69
|
+
# 16 KB page-size devices (Android 15+) refuse a 4 KB-aligned LOAD segment and drop the whole app
|
|
70
|
+
# into compat mode. NDK r27 still links 4 KB by default; RN's own libraries already ship 16 KB.
|
|
71
|
+
target_link_options(symbiote_engine PRIVATE -Wl,-z,max-page-size=16384)
|
|
72
|
+
|
|
43
73
|
find_package(ReactAndroid REQUIRED CONFIG)
|
|
44
74
|
find_package(fbjni REQUIRED CONFIG)
|
|
45
75
|
|
package/android/build.gradle
CHANGED
|
@@ -37,7 +37,10 @@ android {
|
|
|
37
37
|
|
|
38
38
|
externalNativeBuild {
|
|
39
39
|
cmake {
|
|
40
|
-
|
|
40
|
+
// REACT_NATIVE_DIR: the app's own react-native (the plugin's `reactNativeDir`), so CMake can
|
|
41
|
+
// reach the one ReactCommon header the prefab does not ship (see CMakeLists.txt).
|
|
42
|
+
arguments "-DANDROID_STL=c++_shared",
|
|
43
|
+
"-DREACT_NATIVE_DIR=${project.extensions.getByName('react').reactNativeDir.get().asFile.absolutePath}"
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { createDeviceEventModule } from '../native-modules/index.js';
|
|
10
10
|
import {} from '../native-events.js';
|
|
11
11
|
import { dlog } from '../debug.js';
|
|
12
|
+
import { sendAccessibilityEventByTag } from '../fabric.js';
|
|
12
13
|
import { isBoolean, routeSendAccessibilityEvent, } from './shared.js';
|
|
13
14
|
// The Android native module name. This is the module the Android JS wrapper
|
|
14
15
|
// (INativeAccessibilityInfoAndroid) resolves: the stock RN `AccessibilityInfo` Turbo/legacy
|
|
@@ -19,6 +20,8 @@ const ACCESSIBILITY_MODULE = 'AccessibilityInfo';
|
|
|
19
20
|
// most of them; events with no Android source (iOS-only) are absent and yield an inert
|
|
20
21
|
// subscription. (RN maps both `change` and `screenReaderChanged` to touchExplorationDidChange.)
|
|
21
22
|
const ANDROID_DEVICE_EVENT = {
|
|
23
|
+
// RN's deprecated alias (AccessibilityInfo.js EventNames).
|
|
24
|
+
change: 'touchExplorationDidChange',
|
|
22
25
|
screenReaderChanged: 'touchExplorationDidChange',
|
|
23
26
|
reduceMotionChanged: 'reduceMotionDidChange',
|
|
24
27
|
highTextContrastChanged: 'highTextContrastDidChange',
|
|
@@ -39,19 +42,18 @@ function getModule() {
|
|
|
39
42
|
function getEmitter() {
|
|
40
43
|
return deviceEventModule.getEmitter();
|
|
41
44
|
}
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
function queryState(pick, label) {
|
|
45
|
+
const MODULE_UNAVAILABLE = 'NativeAccessibilityInfoAndroid is not available';
|
|
46
|
+
// Run a single-callback Android getter as a Promise, REJECTING when it cannot run, as RN does
|
|
47
|
+
// (AccessibilityInfo.js): a required getter names the module, an optional one names itself.
|
|
48
|
+
function queryState(pick, label, isOptional = false) {
|
|
46
49
|
const module = getModule();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return Promise.resolve(false);
|
|
50
|
+
const getter = module === null ? undefined : pick(module);
|
|
51
|
+
if (module === null || getter === undefined) {
|
|
52
|
+
const message = isOptional
|
|
53
|
+
? `NativeAccessibilityInfoAndroid.${label} is not available`
|
|
54
|
+
: MODULE_UNAVAILABLE;
|
|
55
|
+
dlog(`AccessibilityInfo(android).${label} -> rejected: ${message}`);
|
|
56
|
+
return Promise.reject(new Error(message));
|
|
55
57
|
}
|
|
56
58
|
return new Promise(resolve => {
|
|
57
59
|
getter.call(module, enabled => resolve(enabled));
|
|
@@ -70,17 +72,17 @@ class AccessibilityInfoAndroid {
|
|
|
70
72
|
return Promise.resolve(false);
|
|
71
73
|
}
|
|
72
74
|
isGrayscaleEnabled() {
|
|
73
|
-
return queryState(m => m.isGrayscaleEnabled, 'isGrayscaleEnabled');
|
|
75
|
+
return queryState(m => m.isGrayscaleEnabled, 'isGrayscaleEnabled', true);
|
|
74
76
|
}
|
|
75
77
|
isInvertColorsEnabled() {
|
|
76
|
-
return queryState(m => m.isInvertColorsEnabled, 'isInvertColorsEnabled');
|
|
78
|
+
return queryState(m => m.isInvertColorsEnabled, 'isInvertColorsEnabled', true);
|
|
77
79
|
}
|
|
78
80
|
// iOS-only query; resolve false (RN parity).
|
|
79
81
|
isReduceTransparencyEnabled() {
|
|
80
82
|
return Promise.resolve(false);
|
|
81
83
|
}
|
|
82
84
|
isHighTextContrastEnabled() {
|
|
83
|
-
return queryState(m => m.isHighTextContrastEnabled, 'isHighTextContrastEnabled');
|
|
85
|
+
return queryState(m => m.isHighTextContrastEnabled, 'isHighTextContrastEnabled', true);
|
|
84
86
|
}
|
|
85
87
|
// iOS-only "Increase Contrast"; Android has no equivalent, so resolve false (RN parity).
|
|
86
88
|
isDarkerSystemColorsEnabled() {
|
|
@@ -91,7 +93,7 @@ class AccessibilityInfoAndroid {
|
|
|
91
93
|
return Promise.resolve(false);
|
|
92
94
|
}
|
|
93
95
|
isAccessibilityServiceEnabled() {
|
|
94
|
-
return queryState(m => m.isAccessibilityServiceEnabled, 'isAccessibilityServiceEnabled');
|
|
96
|
+
return queryState(m => m.isAccessibilityServiceEnabled, 'isAccessibilityServiceEnabled', true);
|
|
95
97
|
}
|
|
96
98
|
// Post a string to be announced by the screen reader. No-op without a module.
|
|
97
99
|
announceForAccessibility(announcement) {
|
|
@@ -111,8 +113,10 @@ class AccessibilityInfoAndroid {
|
|
|
111
113
|
// bare reactTag can't be resolved back to its SymbioteNode (the mirror is node-keyed), so
|
|
112
114
|
// this best-effort path is a logged no-op. Callers should use sendAccessibilityEvent(node,
|
|
113
115
|
// 'focus') with a host ref, which routes a real node through the slot.
|
|
116
|
+
// RN: legacySendAccessibilityEvent(tag, 'focus') -> bridgeless UIManager resolves the tag.
|
|
114
117
|
setAccessibilityFocus(reactTag) {
|
|
115
|
-
dlog(`AccessibilityInfo(android).setAccessibilityFocus(${reactTag})
|
|
118
|
+
dlog(`AccessibilityInfo(android).setAccessibilityFocus(${reactTag})`);
|
|
119
|
+
sendAccessibilityEventByTag(reactTag, 'focus');
|
|
116
120
|
}
|
|
117
121
|
// Recommended UI-change timeout for this user. Resolves the original when the module or
|
|
118
122
|
// the query is absent (RN parity).
|
|
@@ -19,6 +19,8 @@ const ACCESSIBILITY_MODULE = 'AccessibilityManager';
|
|
|
19
19
|
// Public event name -> the iOS device event the native side emits. iOS keeps the names
|
|
20
20
|
// 1:1; the indirection exists only so the mapping stays explicit (Android renames them).
|
|
21
21
|
const IOS_DEVICE_EVENT = {
|
|
22
|
+
// RN's deprecated alias (AccessibilityInfo.js EventNames).
|
|
23
|
+
change: 'screenReaderChanged',
|
|
22
24
|
screenReaderChanged: 'screenReaderChanged',
|
|
23
25
|
reduceMotionChanged: 'reduceMotionChanged',
|
|
24
26
|
boldTextChanged: 'boldTextChanged',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ISymbioteNode } from '../node';
|
|
2
2
|
import type { IEventSubscription } from '../native-events';
|
|
3
3
|
export { isBoolean } from '../type-guards';
|
|
4
|
-
export type IAccessibilityChangeEventName = 'screenReaderChanged' | 'reduceMotionChanged' | 'boldTextChanged' | 'grayscaleChanged' | 'invertColorsChanged' | 'reduceTransparencyChanged' | 'darkerSystemColorsChanged' | 'announcementFinished' | 'accessibilityServiceChanged' | 'highTextContrastChanged';
|
|
4
|
+
export type IAccessibilityChangeEventName = 'change' | 'screenReaderChanged' | 'reduceMotionChanged' | 'boldTextChanged' | 'grayscaleChanged' | 'invertColorsChanged' | 'reduceTransparencyChanged' | 'darkerSystemColorsChanged' | 'announcementFinished' | 'accessibilityServiceChanged' | 'highTextContrastChanged';
|
|
5
5
|
export type IAccessibilityChangeEvent = IAccessibilityChangeEventName;
|
|
6
6
|
export type IAccessibilityHandle = ISymbioteNode | number | null | undefined;
|
|
7
7
|
export interface IAccessibilityAnnouncementFinishedEvent {
|
package/build/alert/shared.d.ts
CHANGED
|
@@ -16,4 +16,4 @@ export interface IAlertStatic {
|
|
|
16
16
|
alert(title?: string, message?: string, buttons?: IAlertButtons, options?: IAlertOptions): void;
|
|
17
17
|
}
|
|
18
18
|
export declare const DEFAULT_POSITIVE_TEXT = "OK";
|
|
19
|
-
export declare function normalizeButtons(buttons?: IAlertButtons): IAlertButtons;
|
|
19
|
+
export declare function normalizeButtons(buttons?: IAlertButtons | null): IAlertButtons;
|
package/build/alert/shared.js
CHANGED
|
@@ -6,12 +6,8 @@
|
|
|
6
6
|
// a factory. No native, no `Platform.OS` read here.
|
|
7
7
|
// The default positive label RN uses when a button carries no text.
|
|
8
8
|
export const DEFAULT_POSITIVE_TEXT = 'OK';
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// platform files start from this so the no-buttons case behaves identically.
|
|
9
|
+
// Android's `buttons ? buttons : [{text: 'OK'}]` (Alert.js): only an ABSENT list gets the default
|
|
10
|
+
// OK. An explicit empty array is a dialog with no buttons.
|
|
12
11
|
export function normalizeButtons(buttons) {
|
|
13
|
-
|
|
14
|
-
return [{ text: DEFAULT_POSITIVE_TEXT }];
|
|
15
|
-
}
|
|
16
|
-
return buttons;
|
|
12
|
+
return buttons ?? [{ text: DEFAULT_POSITIVE_TEXT }];
|
|
17
13
|
}
|
|
@@ -9,7 +9,7 @@ export type IAppStateStatus = 'inactive' | 'background' | 'active' | 'extension'
|
|
|
9
9
|
export type IAppStateEvent = (typeof APP_STATE_EVENT)[keyof typeof APP_STATE_EVENT];
|
|
10
10
|
declare class AppStateImpl {
|
|
11
11
|
get isAvailable(): boolean;
|
|
12
|
-
get currentState(): string | null;
|
|
12
|
+
get currentState(): string | null | undefined;
|
|
13
13
|
addEventListener(type: IAppStateEvent, handler: (...args: unknown[]) => void): IEventSubscription;
|
|
14
14
|
}
|
|
15
15
|
export declare const AppState: AppStateImpl;
|
package/build/app-state/index.js
CHANGED
|
@@ -24,30 +24,39 @@ const APP_STATE_EVENT = {
|
|
|
24
24
|
focus: 'focus',
|
|
25
25
|
blur: 'blur',
|
|
26
26
|
};
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// `appStateData.app_state`, read the way RN reads it: no shape check, `undefined` when absent,
|
|
28
|
+
// and a TypeError on a null payload as the property read would throw.
|
|
29
|
+
function appStateOf(payload) {
|
|
30
|
+
if (payload === null || payload === undefined) {
|
|
31
|
+
throw new TypeError(`Cannot read property 'app_state' of ${String(payload)}`);
|
|
32
|
+
}
|
|
33
|
+
const state = Reflect.get(Object(payload), 'app_state');
|
|
34
|
+
return typeof state === 'string' ? state : undefined;
|
|
29
35
|
}
|
|
30
|
-
//
|
|
31
|
-
// run without a fake __turboModuleProxy still loads it; resolution happens on first
|
|
32
|
-
// use. `null` when the module isn't linked.
|
|
36
|
+
// `null` until the module resolves (or when it is not linked), as RN's field starts.
|
|
33
37
|
let currentState = null;
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// change returns the new value even with nobody else listening (RN parity).
|
|
38
|
+
// AppState.js's constructor, run once when the emitter is built: seed from the constant, keep it
|
|
39
|
+
// fresh from 'appStateDidChange', and ask native for the live state — which wins, and is
|
|
40
|
+
// re-emitted to listeners, unless an event arrived first.
|
|
38
41
|
const deviceEventModule = createDeviceEventModule({
|
|
39
42
|
moduleName: APP_STATE_MODULE,
|
|
40
43
|
moduleLogPrefix: 'AppState: module',
|
|
41
44
|
onEmitterCreated: (emitter, module) => {
|
|
42
|
-
if (module
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if (module === null)
|
|
46
|
+
return;
|
|
47
|
+
currentState = module.getConstants().initialAppState;
|
|
48
|
+
let eventUpdated = false;
|
|
45
49
|
emitter.addListener(NATIVE_EVENT.stateDidChange, payload => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
dlog(`AppState: ${NATIVE_EVENT.stateDidChange} -> ${
|
|
49
|
-
currentState = payload.app_state;
|
|
50
|
+
eventUpdated = true;
|
|
51
|
+
currentState = appStateOf(payload);
|
|
52
|
+
dlog(`AppState: ${NATIVE_EVENT.stateDidChange} -> ${String(currentState)}`);
|
|
50
53
|
});
|
|
54
|
+
module.getCurrentAppState(data => {
|
|
55
|
+
if (!eventUpdated && currentState !== data.app_state) {
|
|
56
|
+
currentState = data.app_state;
|
|
57
|
+
emitter.emit(NATIVE_EVENT.stateDidChange, data);
|
|
58
|
+
}
|
|
59
|
+
}, error => dlog(`AppState.getCurrentAppState failed: ${String(error)}`));
|
|
51
60
|
},
|
|
52
61
|
});
|
|
53
62
|
function getModule() {
|
|
@@ -68,33 +77,32 @@ class AppStateImpl {
|
|
|
68
77
|
getEmitter();
|
|
69
78
|
return currentState;
|
|
70
79
|
}
|
|
71
|
-
// Subscribe to an AppState event
|
|
72
|
-
// `memoryWarning
|
|
73
|
-
//
|
|
74
|
-
// subscription (the counters are no-ops without a module).
|
|
80
|
+
// Subscribe to an AppState event, mapped onto the native `appStateDidChange` /
|
|
81
|
+
// `memoryWarning` / `appStateFocusChange`. Throws, as RN does, without a module or for an
|
|
82
|
+
// event it does not know.
|
|
75
83
|
addEventListener(type, handler) {
|
|
84
|
+
if (getModule() === null) {
|
|
85
|
+
throw new Error('Cannot use AppState when `isAvailable` is false.');
|
|
86
|
+
}
|
|
76
87
|
const eventEmitter = getEmitter();
|
|
77
88
|
dlog(`AppState.addEventListener -> ${type}`);
|
|
78
89
|
switch (type) {
|
|
79
90
|
case APP_STATE_EVENT.change:
|
|
80
|
-
return eventEmitter.addListener(NATIVE_EVENT.stateDidChange, payload =>
|
|
81
|
-
if (!isStateChangePayload(payload))
|
|
82
|
-
return;
|
|
83
|
-
handler(payload.app_state);
|
|
84
|
-
});
|
|
91
|
+
return eventEmitter.addListener(NATIVE_EVENT.stateDidChange, payload => handler(appStateOf(payload)));
|
|
85
92
|
case APP_STATE_EVENT.memoryWarning:
|
|
86
93
|
return eventEmitter.addListener(NATIVE_EVENT.memoryWarning, () => handler());
|
|
87
94
|
case APP_STATE_EVENT.focus:
|
|
88
95
|
return eventEmitter.addListener(NATIVE_EVENT.focusChange, hasFocus => {
|
|
89
|
-
if (hasFocus
|
|
96
|
+
if (hasFocus)
|
|
90
97
|
handler();
|
|
91
98
|
});
|
|
92
99
|
case APP_STATE_EVENT.blur:
|
|
93
100
|
return eventEmitter.addListener(NATIVE_EVENT.focusChange, hasFocus => {
|
|
94
|
-
if (hasFocus
|
|
101
|
+
if (!hasFocus)
|
|
95
102
|
handler();
|
|
96
103
|
});
|
|
97
104
|
}
|
|
105
|
+
throw new Error(`Trying to subscribe to unknown event: ${String(type)}`);
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
108
|
export const AppState = new AppStateImpl();
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { type IEventSubscription } from '../native-events';
|
|
2
|
+
declare const DEVICE_BACK_EVENT = "hardwareBackPress";
|
|
2
3
|
declare const BACK_PRESS_EVENT: {
|
|
3
4
|
readonly backPress: "backPress";
|
|
4
5
|
readonly hardwareBackPress: "hardwareBackPress";
|
|
5
6
|
};
|
|
6
7
|
export type IBackPressEventName = (typeof BACK_PRESS_EVENT)[keyof typeof BACK_PRESS_EVENT];
|
|
7
|
-
export type
|
|
8
|
+
export type IHardwareBackPressEvent = {
|
|
9
|
+
readonly type: typeof DEVICE_BACK_EVENT;
|
|
10
|
+
readonly timeStamp: number;
|
|
11
|
+
};
|
|
12
|
+
export type IBackPressHandler = (event: IHardwareBackPressEvent) => boolean | null | undefined | void;
|
|
8
13
|
declare class BackHandlerImpl {
|
|
9
14
|
exitApp(): void;
|
|
10
15
|
addEventListener(_eventName: IBackPressEventName, handler: IBackPressHandler): IEventSubscription;
|
|
11
|
-
removeEventListener(_eventName: IBackPressEventName, handler: IBackPressHandler): void;
|
|
12
16
|
}
|
|
13
17
|
export declare const BackHandler: BackHandlerImpl;
|
|
18
|
+
export declare function installBackHandler(): void;
|
|
14
19
|
export {};
|
|
@@ -23,6 +23,12 @@ const BACK_PRESS_EVENT = {
|
|
|
23
23
|
backPress: 'backPress',
|
|
24
24
|
hardwareBackPress: 'hardwareBackPress',
|
|
25
25
|
};
|
|
26
|
+
function nativeTimeStamp(nativeEvent) {
|
|
27
|
+
if (typeof nativeEvent !== 'object' || nativeEvent === null)
|
|
28
|
+
return undefined;
|
|
29
|
+
const timeStamp = Reflect.get(nativeEvent, 'timeStamp');
|
|
30
|
+
return typeof timeStamp === 'number' ? timeStamp : undefined;
|
|
31
|
+
}
|
|
26
32
|
// The registry, in registration order. Invoked in reverse on a back press.
|
|
27
33
|
const backPressSubscriptions = [];
|
|
28
34
|
function isHandled(result) {
|
|
@@ -50,10 +56,14 @@ function getEmitter() {
|
|
|
50
56
|
}
|
|
51
57
|
// Run handlers last-registered-first; the first to return true consumes the press
|
|
52
58
|
// and the rest are skipped. If none consume it, fall through to the native default.
|
|
53
|
-
function dispatchBackPress() {
|
|
59
|
+
function dispatchBackPress(nativeEvent) {
|
|
60
|
+
const event = {
|
|
61
|
+
type: DEVICE_BACK_EVENT,
|
|
62
|
+
timeStamp: nativeTimeStamp(nativeEvent) ?? performance.now(),
|
|
63
|
+
};
|
|
54
64
|
for (let i = backPressSubscriptions.length - 1; i >= 0; i--) {
|
|
55
65
|
const handler = backPressSubscriptions[i];
|
|
56
|
-
if (handler !== undefined && isHandled(handler())) {
|
|
66
|
+
if (handler !== undefined && isHandled(handler(event))) {
|
|
57
67
|
dlog(`BackHandler: back press consumed by handler ${i}`);
|
|
58
68
|
return;
|
|
59
69
|
}
|
|
@@ -90,13 +100,12 @@ class BackHandlerImpl {
|
|
|
90
100
|
},
|
|
91
101
|
};
|
|
92
102
|
}
|
|
93
|
-
// Legacy unsubscribe kept for RN parity. The modern path is the subscription's
|
|
94
|
-
// remove() returned by addEventListener.
|
|
95
|
-
removeEventListener(_eventName, handler) {
|
|
96
|
-
const index = backPressSubscriptions.indexOf(handler);
|
|
97
|
-
if (index !== -1) {
|
|
98
|
-
backPressSubscriptions.splice(index, 1);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
103
|
}
|
|
102
104
|
export const BackHandler = new BackHandlerImpl();
|
|
105
|
+
// Subscribes to `hardwareBackPress` up front, as RN does when BackHandler.android.js loads: Android
|
|
106
|
+
// exits only when JS answers the event with invokeDefaultBackPressHandler, so an app with no handler
|
|
107
|
+
// of its own would otherwise have a dead back button. Called by the host bootstrap, not at module
|
|
108
|
+
// load — a barrel-reached module's load-time side effect does not run in a release bundle.
|
|
109
|
+
export function installBackHandler() {
|
|
110
|
+
getEmitter();
|
|
111
|
+
}
|
package/build/events/index.js
CHANGED
|
@@ -380,8 +380,13 @@ export function installEventHandler() {
|
|
|
380
380
|
if (topLevelType === TOUCH_START) {
|
|
381
381
|
// The ternary is gone with the gate: `isSymbioteNode(instanceHandle)` was re-asked here
|
|
382
382
|
// three lines after the early return above already proved it.
|
|
383
|
-
if (isDebug())
|
|
383
|
+
if (isDebug()) {
|
|
384
384
|
dlog(`event ${TOUCH_START} on ${instanceHandle.component}`);
|
|
385
|
+
// A responder surviving into a one-touch start lost its end/cancel. If it is an ancestor
|
|
386
|
+
// of the target, this whole tap goes to it and the release finally clears it.
|
|
387
|
+
if (currentResponder !== undefined)
|
|
388
|
+
dlog(`touchStart while ${currentResponder.component} still holds the responder`);
|
|
389
|
+
}
|
|
385
390
|
// Update the touch bank, then attach it so responder handlers (PanResponder)
|
|
386
391
|
// read each touch's own previous->current delta; RN records before dispatch.
|
|
387
392
|
recordTouchTrack('start', nativeEvent);
|
|
@@ -519,6 +524,10 @@ export function installEventHandler() {
|
|
|
519
524
|
return;
|
|
520
525
|
}
|
|
521
526
|
if (topLevelType === TOUCH_CANCEL) {
|
|
527
|
+
// Android: a ScrollView whose scroller has not finished (fling tail, spring-back) intercepts
|
|
528
|
+
// the next down natively, and the tap reaches JS as start + cancel with no press.
|
|
529
|
+
if (isDebug())
|
|
530
|
+
dlog(`event ${TOUCH_CANCEL} on ${instanceHandle.component}`);
|
|
522
531
|
recordTouchTrack('end', nativeEvent);
|
|
523
532
|
attachTouchHistory(nativeEvent);
|
|
524
533
|
// A cancel is scoped to the finger(s) removed from `touches`, just like an end. An unrelated
|
package/build/fabric-props.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
// What is left is the framework-agnostic half: colour processing, the style hoist, and a node's own
|
|
30
30
|
// `payloadFold`.
|
|
31
31
|
import { RAW_TEXT_COMPONENT } from './node.js';
|
|
32
|
-
import { isProcessableColor, processColor } from './platform-color
|
|
32
|
+
import { isProcessableColor, processColor } from './platform-color';
|
|
33
33
|
import { configProcessedKeys } from './registry.js';
|
|
34
34
|
import { isRecord } from './type-guards.js';
|
|
35
35
|
// Color props must reach Fabric as platform ints, not CSS strings. Fabric's C++
|
package/build/fabric.d.ts
CHANGED
|
@@ -32,7 +32,14 @@ interface IFabricHost extends Omit<IFabricSlot, 'cloneNodeWithNewChildren' | 'cl
|
|
|
32
32
|
cloneNodeWithNewChildren(node: IFabricNode, children?: readonly IFabricNode[]): IFabricNode;
|
|
33
33
|
cloneNodeWithNewChildrenAndProps(node: IFabricNode, newProps: IFabricProps): IFabricNode;
|
|
34
34
|
cloneNodeWithNewChildrenAndProps(node: IFabricNode, children: readonly IFabricNode[], newProps: IFabricProps): IFabricNode;
|
|
35
|
+
findShadowNodeByTag_DEPRECATED?(tag: number): IFabricNode | null;
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* An accessibility event addressed by a bare native TAG, the way RN's bridgeless
|
|
39
|
+
* `UIManager.sendAccessibilityEvent` does it: resolve the tag to its committed shadow node, then send.
|
|
40
|
+
* An unknown tag is dropped, as RN drops it (with a log rather than a throw).
|
|
41
|
+
*/
|
|
42
|
+
export declare function sendAccessibilityEventByTag(tag: number, eventType: string): void;
|
|
36
43
|
declare global {
|
|
37
44
|
var nativeFabricUIManager: IFabricHost | undefined;
|
|
38
45
|
}
|
package/build/fabric.js
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
// host function, so we read each method once and cache a plain facade.
|
|
7
7
|
import { dlog } from './debug.js';
|
|
8
8
|
import { installNativeTreeHost } from './native-tree-host.js';
|
|
9
|
+
/**
|
|
10
|
+
* An accessibility event addressed by a bare native TAG, the way RN's bridgeless
|
|
11
|
+
* `UIManager.sendAccessibilityEvent` does it: resolve the tag to its committed shadow node, then send.
|
|
12
|
+
* An unknown tag is dropped, as RN drops it (with a log rather than a throw).
|
|
13
|
+
*/
|
|
14
|
+
export function sendAccessibilityEventByTag(tag, eventType) {
|
|
15
|
+
const host = globalThis.nativeFabricUIManager;
|
|
16
|
+
const node = host?.findShadowNodeByTag_DEPRECATED?.(tag);
|
|
17
|
+
if (host === undefined || node === undefined || node === null) {
|
|
18
|
+
dlog(`sendAccessibilityEvent("${eventType}") dropped: no view with tag #${tag}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
host.sendAccessibilityEvent(node, eventType);
|
|
22
|
+
}
|
|
9
23
|
let cached;
|
|
10
24
|
// BATCHING IS GONE, and it is worth one paragraph because the idea recurs. `batching-slot.ts`
|
|
11
25
|
// recorded this slot's calls and replayed them once per commit — three ways, the last handing the
|
package/build/host-behavior.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface IHostBehavior {
|
|
|
22
22
|
readonly ownedListeners?: readonly string[];
|
|
23
23
|
readonly slotProps?: Readonly<Record<string, string>>;
|
|
24
24
|
readonly slotPropsExcept?: readonly string[];
|
|
25
|
+
readonly slotValueFor?: (slotKey: string, value: unknown) => unknown;
|
|
25
26
|
readonly slotTakesNoChildren?: boolean;
|
|
26
27
|
readonly slotDerived?: readonly string[];
|
|
27
28
|
readonly claimedChildren?: Readonly<Record<string, IClaimMode>>;
|
package/build/image-loader.d.ts
CHANGED
|
@@ -6,8 +6,10 @@ export type IImageSize = {
|
|
|
6
6
|
export type IImageCacheStatus = 'memory' | 'disk' | 'disk/memory';
|
|
7
7
|
type ISizeSuccess = (width: number, height: number) => void;
|
|
8
8
|
type ISizeFailure = (error: unknown) => void;
|
|
9
|
-
declare function getSize(uri: string
|
|
10
|
-
declare function
|
|
9
|
+
declare function getSize(uri: string): Promise<IImageSize>;
|
|
10
|
+
declare function getSize(uri: string, success: ISizeSuccess, failure?: ISizeFailure): undefined;
|
|
11
|
+
declare function getSizeWithHeaders(uri: string, headers: Record<string, string>): Promise<IImageSize>;
|
|
12
|
+
declare function getSizeWithHeaders(uri: string, headers: Record<string, string>, success: ISizeSuccess, failure?: ISizeFailure): undefined;
|
|
11
13
|
declare function prefetch(uri: string, callback?: (requestId: number) => void): Promise<boolean>;
|
|
12
14
|
declare function abortPrefetch(requestId: number): void;
|
|
13
15
|
declare function queryCache(uris: string[]): Promise<Record<string, IImageCacheStatus>>;
|
package/build/image-loader.js
CHANGED
|
@@ -54,40 +54,29 @@ function requireLoader(method) {
|
|
|
54
54
|
}
|
|
55
55
|
return loader;
|
|
56
56
|
}
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
57
|
+
// Image.ios.js / Image.android.js: the promise when no success callback is given; otherwise the
|
|
58
|
+
// result goes to the callbacks and nothing is returned, a missing `failure` becoming a warning.
|
|
59
|
+
function deliverSize(promise, uri, success, failure) {
|
|
60
|
+
if (typeof success !== 'function')
|
|
61
|
+
return promise;
|
|
62
|
+
promise
|
|
63
|
+
.then(size => success(size.width, size.height))
|
|
64
|
+
.catch(typeof failure === 'function'
|
|
65
|
+
? failure
|
|
66
|
+
: () => console.warn('Failed to get size for image: ' + uri));
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
60
69
|
function getSize(uri, success, failure) {
|
|
61
70
|
const promise = Promise.resolve()
|
|
62
71
|
.then(() => requireLoader('getSize').getSize(uri))
|
|
63
72
|
.then(toImageSize);
|
|
64
|
-
|
|
65
|
-
promise
|
|
66
|
-
.then(size => success(size.width, size.height))
|
|
67
|
-
.catch((error) => {
|
|
68
|
-
if (typeof failure === 'function')
|
|
69
|
-
failure(error);
|
|
70
|
-
else
|
|
71
|
-
dlog(`Image.getSize failed for ${uri}: ${String(error)}`);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return promise;
|
|
73
|
+
return deliverSize(promise, uri, success, failure);
|
|
75
74
|
}
|
|
76
75
|
function getSizeWithHeaders(uri, headers, success, failure) {
|
|
77
76
|
const promise = Promise.resolve()
|
|
78
77
|
.then(() => requireLoader('getSizeWithHeaders').getSizeWithHeaders(uri, headers))
|
|
79
78
|
.then(toImageSize);
|
|
80
|
-
|
|
81
|
-
promise
|
|
82
|
-
.then(size => success(size.width, size.height))
|
|
83
|
-
.catch((error) => {
|
|
84
|
-
if (typeof failure === 'function')
|
|
85
|
-
failure(error);
|
|
86
|
-
else
|
|
87
|
-
dlog(`Image.getSizeWithHeaders failed for ${uri}: ${String(error)}`);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return promise;
|
|
79
|
+
return deliverSize(promise, uri, success, failure);
|
|
91
80
|
}
|
|
92
81
|
// Android keys an in-flight prefetch by a monotonic requestId (so abortRequest can cancel it);
|
|
93
82
|
// RN's Image.android.js generates the same way. iOS ignores the arg.
|
|
@@ -10,7 +10,7 @@ export declare const IMAGE_SOURCE_PROPS: ReadonlySet<string>;
|
|
|
10
10
|
* whatever it is handed, and a tag with no image behavior must keep its props verbatim — the
|
|
11
11
|
* control case in `image-payload.itest.ts` is what holds that line.
|
|
12
12
|
*/
|
|
13
|
-
export declare function resolveImageSourceProp(value: unknown): unknown;
|
|
13
|
+
export declare function resolveImageSourceProp(value: unknown, dropsSingleSourceHeaders?: boolean): unknown;
|
|
14
14
|
export declare const IMAGE_LOAD_EVENT_NAMES: ReadonlySet<string>;
|
|
15
15
|
/** Whether at least one of the four still has a listener installed on the node. */
|
|
16
16
|
export declare function anyImageLoadEventListenerWired(listeners: ReadonlyMap<string, unknown> | undefined): boolean;
|
|
@@ -30,13 +30,25 @@ export const IMAGE_SOURCE_PROPS = new Set([
|
|
|
30
30
|
* whatever it is handed, and a tag with no image behavior must keep its props verbatim — the
|
|
31
31
|
* control case in `image-payload.itest.ts` is what holds that line.
|
|
32
32
|
*/
|
|
33
|
-
export function resolveImageSourceProp(value) {
|
|
33
|
+
export function resolveImageSourceProp(value, dropsSingleSourceHeaders = false) {
|
|
34
34
|
if (value === undefined || value === null)
|
|
35
35
|
return value;
|
|
36
36
|
if (typeof value !== 'number' && typeof value !== 'object')
|
|
37
37
|
return value;
|
|
38
38
|
const resolved = resolveImageSource(value);
|
|
39
|
-
|
|
39
|
+
if (Array.isArray(resolved))
|
|
40
|
+
return resolved;
|
|
41
|
+
// Android `source` only: `Image.android.js` lifts headers to the native `headers` prop just for an
|
|
42
|
+
// ARRAY source, and ReactImageView ignores per-source headers, so RN sends none for a single
|
|
43
|
+
// object. Wrapping here erases that shape, so its headers go here too.
|
|
44
|
+
if (dropsSingleSourceHeaders &&
|
|
45
|
+
typeof resolved === 'object' &&
|
|
46
|
+
resolved !== null &&
|
|
47
|
+
'headers' in resolved) {
|
|
48
|
+
const { headers: _dropped, ...rest } = resolved;
|
|
49
|
+
return [rest];
|
|
50
|
+
}
|
|
51
|
+
return [resolved];
|
|
40
52
|
}
|
|
41
53
|
// `ReactImageView.setShouldNotifyLoadEvents` (Android) — `downloadListener` stays `null`, and
|
|
42
54
|
// none of these four ever fires, until this prop is `true`. `Image.android.js` sets it whenever
|
package/build/index.d.ts
CHANGED
|
@@ -90,8 +90,8 @@ export type { IKeyboardEventName, IKeyboardEvent, IKeyboardMetrics, } from './ke
|
|
|
90
90
|
export { currentlyFocusedInput, setInputFocused, setInputBlurred, blurTextInput, focusTextInput, } from './text-input-state';
|
|
91
91
|
export { LayoutAnimation } from './layout-animation';
|
|
92
92
|
export type { ILayoutAnimationType, ILayoutAnimationProperty, ILayoutAnimationConfig, ILayoutAnimationAnim, ILayoutAnimationTypes, ILayoutAnimationProperties, } from './layout-animation';
|
|
93
|
-
export { BackHandler } from './back-handler';
|
|
94
|
-
export type { IBackPressEventName, IBackPressHandler } from './back-handler';
|
|
93
|
+
export { BackHandler, installBackHandler } from './back-handler';
|
|
94
|
+
export type { IBackPressEventName, IBackPressHandler, IHardwareBackPressEvent, } from './back-handler';
|
|
95
95
|
export { PermissionsAndroid, PERMISSIONS, RESULTS, } from './permissions-android';
|
|
96
96
|
export type { IPermission, IPermissionStatus, IRationale, } from './permissions-android';
|
|
97
97
|
export { AccessibilityInfo } from './accessibility-info';
|