@symbiote-native/engine 0.1.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/LICENSE +21 -0
- package/README.md +124 -0
- package/build/accessibility-info/index.android.d.ts +3 -0
- package/build/accessibility-info/index.android.js +166 -0
- package/build/accessibility-info/index.d.ts +1 -0
- package/build/accessibility-info/index.ios.d.ts +3 -0
- package/build/accessibility-info/index.ios.js +219 -0
- package/build/accessibility-info/index.js +5 -0
- package/build/accessibility-info/shared.d.ts +34 -0
- package/build/accessibility-info/shared.js +13 -0
- package/build/action-sheet-ios/index.d.ts +36 -0
- package/build/action-sheet-ios/index.js +74 -0
- package/build/alert/index.android.d.ts +5 -0
- package/build/alert/index.android.js +117 -0
- package/build/alert/index.d.ts +1 -0
- package/build/alert/index.ios.d.ts +7 -0
- package/build/alert/index.ios.js +83 -0
- package/build/alert/index.js +8 -0
- package/build/alert/shared.d.ts +19 -0
- package/build/alert/shared.js +17 -0
- package/build/animated/animated-component-shared.d.ts +5 -0
- package/build/animated/animated-component-shared.js +54 -0
- package/build/animated/animation.d.ts +9 -0
- package/build/animated/animation.js +6 -0
- package/build/animated/animations/base.d.ts +27 -0
- package/build/animated/animations/base.js +90 -0
- package/build/animated/animations/composition.d.ts +38 -0
- package/build/animated/animations/composition.js +236 -0
- package/build/animated/animations/decay.d.ts +22 -0
- package/build/animated/animations/decay.js +65 -0
- package/build/animated/animations/raf.d.ts +5 -0
- package/build/animated/animations/raf.js +39 -0
- package/build/animated/animations/spring-config.d.ts +6 -0
- package/build/animated/animations/spring-config.js +55 -0
- package/build/animated/animations/spring.d.ts +50 -0
- package/build/animated/animations/spring.js +207 -0
- package/build/animated/animations/timing.d.ts +27 -0
- package/build/animated/animations/timing.js +101 -0
- package/build/animated/animations/tracking.d.ts +14 -0
- package/build/animated/animations/tracking.js +43 -0
- package/build/animated/bezier.d.ts +1 -0
- package/build/animated/bezier.js +101 -0
- package/build/animated/color.d.ts +37 -0
- package/build/animated/color.js +183 -0
- package/build/animated/easing.d.ts +20 -0
- package/build/animated/easing.js +96 -0
- package/build/animated/event.d.ts +36 -0
- package/build/animated/event.js +252 -0
- package/build/animated/graph.d.ts +38 -0
- package/build/animated/graph.js +227 -0
- package/build/animated/index.d.ts +20 -0
- package/build/animated/index.js +28 -0
- package/build/animated/interpolation-node.d.ts +16 -0
- package/build/animated/interpolation-node.js +57 -0
- package/build/animated/interpolation.d.ts +22 -0
- package/build/animated/interpolation.js +199 -0
- package/build/animated/mock.d.ts +56 -0
- package/build/animated/mock.js +127 -0
- package/build/animated/native/native-animated.d.ts +43 -0
- package/build/animated/native/native-animated.js +146 -0
- package/build/animated/operators.d.ts +80 -0
- package/build/animated/operators.js +266 -0
- package/build/animated/props.d.ts +20 -0
- package/build/animated/props.js +187 -0
- package/build/animated/style.d.ts +26 -0
- package/build/animated/style.js +187 -0
- package/build/animated/value-xy.d.ts +35 -0
- package/build/animated/value-xy.js +106 -0
- package/build/animated/value.d.ts +36 -0
- package/build/animated/value.js +185 -0
- package/build/app-registry/index.d.ts +40 -0
- package/build/app-registry/index.js +144 -0
- package/build/app-state/index.d.ts +16 -0
- package/build/app-state/index.js +105 -0
- package/build/appearance/index.d.ts +12 -0
- package/build/appearance/index.js +84 -0
- package/build/back-handler/index.d.ts +14 -0
- package/build/back-handler/index.js +106 -0
- package/build/commit.d.ts +16 -0
- package/build/commit.js +678 -0
- package/build/debug.d.ts +5 -0
- package/build/debug.js +18 -0
- package/build/dimensions/index.d.ts +28 -0
- package/build/dimensions/index.js +148 -0
- package/build/dispatch.d.ts +2 -0
- package/build/dispatch.js +18 -0
- package/build/events/index.d.ts +1 -0
- package/build/events/index.js +691 -0
- package/build/fabric.d.ts +32 -0
- package/build/fabric.js +59 -0
- package/build/host-instance/index.d.ts +11 -0
- package/build/host-instance/index.js +49 -0
- package/build/i18n-manager/index.d.ts +13 -0
- package/build/i18n-manager/index.js +91 -0
- package/build/index.d.ts +80 -0
- package/build/index.js +72 -0
- package/build/interaction-manager/index.d.ts +45 -0
- package/build/interaction-manager/index.js +222 -0
- package/build/keyboard/index.d.ts +31 -0
- package/build/keyboard/index.js +142 -0
- package/build/layout-animation/index.d.ts +66 -0
- package/build/layout-animation/index.js +183 -0
- package/build/linking/index.android.d.ts +2 -0
- package/build/linking/index.android.js +18 -0
- package/build/linking/index.d.ts +1 -0
- package/build/linking/index.ios.d.ts +2 -0
- package/build/linking/index.ios.js +9 -0
- package/build/linking/index.js +6 -0
- package/build/linking/shared.d.ts +32 -0
- package/build/linking/shared.js +98 -0
- package/build/native-events.d.ts +24 -0
- package/build/native-events.js +129 -0
- package/build/native-modules.d.ts +6 -0
- package/build/native-modules.js +57 -0
- package/build/node.d.ts +36 -0
- package/build/node.js +194 -0
- package/build/pan-responder/index.d.ts +53 -0
- package/build/pan-responder/index.js +353 -0
- package/build/permissions-android/index.d.ts +115 -0
- package/build/permissions-android/index.js +185 -0
- package/build/pixel-ratio/index.d.ts +8 -0
- package/build/pixel-ratio/index.js +27 -0
- package/build/platform/index.android.d.ts +22 -0
- package/build/platform/index.android.js +60 -0
- package/build/platform/index.d.ts +1 -0
- package/build/platform/index.ios.d.ts +18 -0
- package/build/platform/index.ios.js +62 -0
- package/build/platform/index.js +5 -0
- package/build/platform/shared.d.ts +25 -0
- package/build/platform/shared.js +41 -0
- package/build/platform-color.d.ts +19 -0
- package/build/platform-color.js +25 -0
- package/build/post-commit.d.ts +4 -0
- package/build/post-commit.js +16 -0
- package/build/process-aspect-ratio.d.ts +1 -0
- package/build/process-aspect-ratio.js +34 -0
- package/build/process-background-image/index.d.ts +28 -0
- package/build/process-background-image/index.js +557 -0
- package/build/process-box-shadow/index.d.ts +11 -0
- package/build/process-box-shadow/index.js +193 -0
- package/build/process-filter.d.ts +31 -0
- package/build/process-filter.js +304 -0
- package/build/process-font-variant.d.ts +1 -0
- package/build/process-font-variant.js +17 -0
- package/build/process-transform/index.d.ts +5 -0
- package/build/process-transform/index.js +120 -0
- package/build/process-transform-origin/index.d.ts +3 -0
- package/build/process-transform-origin/index.js +108 -0
- package/build/registry.d.ts +31 -0
- package/build/registry.js +145 -0
- package/build/settings/index.d.ts +8 -0
- package/build/settings/index.js +126 -0
- package/build/share/index.android.d.ts +3 -0
- package/build/share/index.android.js +56 -0
- package/build/share/index.d.ts +1 -0
- package/build/share/index.ios.d.ts +3 -0
- package/build/share/index.ios.js +47 -0
- package/build/share/index.js +6 -0
- package/build/share/shared.d.ts +32 -0
- package/build/share/shared.js +32 -0
- package/build/status-bar/index.android.d.ts +5 -0
- package/build/status-bar/index.android.js +83 -0
- package/build/status-bar/index.d.ts +1 -0
- package/build/status-bar/index.ios.d.ts +5 -0
- package/build/status-bar/index.ios.js +66 -0
- package/build/status-bar/index.js +4 -0
- package/build/status-bar/shared.d.ts +22 -0
- package/build/status-bar/shared.js +22 -0
- package/build/style/index.d.ts +1 -0
- package/build/style/index.js +30 -0
- package/build/style-registry/index.d.ts +11 -0
- package/build/style-registry/index.js +165 -0
- package/build/style-sheet/index.d.ts +20 -0
- package/build/style-sheet/index.js +121 -0
- package/build/styles.d.ts +220 -0
- package/build/styles.js +7 -0
- package/build/surface.d.ts +16 -0
- package/build/surface.js +67 -0
- package/build/tags.d.ts +1 -0
- package/build/tags.js +10 -0
- package/build/text-input-state.d.ts +5 -0
- package/build/text-input-state.js +29 -0
- package/build/toast-android/index.d.ts +10 -0
- package/build/toast-android/index.js +108 -0
- package/build/vibration/index.android.d.ts +2 -0
- package/build/vibration/index.android.js +18 -0
- package/build/vibration/index.d.ts +1 -0
- package/build/vibration/index.ios.d.ts +2 -0
- package/build/vibration/index.ios.js +54 -0
- package/build/vibration/index.js +6 -0
- package/build/vibration/shared.d.ts +15 -0
- package/build/vibration/shared.js +68 -0
- package/build/view-config.d.ts +1 -0
- package/build/view-config.js +114 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @symbiote-native/engine
|
|
2
|
+
|
|
3
|
+
The **retained shadow-tree engine** at the bottom of [SymbioteJS](../../README.md) — the one
|
|
4
|
+
package every framework adapter (`@symbiote-native/react`, `@symbiote-native/vue`, `@symbiote-native/angular`, …)
|
|
5
|
+
drives, and the only place the mutation→clone-on-write translation into React Native's Fabric
|
|
6
|
+
exists. It holds a retained, mutable tree of nodes that an adapter mutates cheaply
|
|
7
|
+
(`appendChild` / `setProp` / `removeChild` …), then on commit diffs that tree against Fabric's
|
|
8
|
+
current one, clones only what changed, and calls `completeRoot` — the persistent, clone-on-write
|
|
9
|
+
dance Fabric requires, done **once**, for every framework.
|
|
10
|
+
|
|
11
|
+
> New to SymbioteJS? The [root README](../../README.md) has the architecture and the one fact it
|
|
12
|
+
> rests on — React is just *one client* of `nativeFabricUIManager`. This package is what sits
|
|
13
|
+
> between every adapter and that native slot.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Who calls this, directly vs. indirectly
|
|
18
|
+
|
|
19
|
+
**Most consumers never import this package by name.** An app written against
|
|
20
|
+
`@symbiote-native/react`/`@symbiote-native/vue`/`@symbiote-native/angular` never calls `createElement` or
|
|
21
|
+
`setProp` itself — the adapter's reconciler does that on the app's behalf. You reach for
|
|
22
|
+
`@symbiote-native/engine` directly only when:
|
|
23
|
+
|
|
24
|
+
- you are **writing or debugging a framework adapter** (a `react-reconciler` host config, a Vue
|
|
25
|
+
`createRenderer`, an Angular `Renderer2`) — this is its primary audience;
|
|
26
|
+
- you need one of the **framework-agnostic runtime modules** it re-exports (`Platform`,
|
|
27
|
+
`StyleSheet`, `Dimensions`, `Alert`, `Animated`, …) — every adapter re-exports these verbatim, so
|
|
28
|
+
most apps still reach them through `@symbiote-native/react` etc., not this package.
|
|
29
|
+
|
|
30
|
+
The mutation API below is intentionally low-level and closely mirrors Fabric's own persistent
|
|
31
|
+
semantics — it is an internal seam, not an app-facing API.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## The mutation API — `core/engine/src/node.ts`
|
|
36
|
+
|
|
37
|
+
The entire surface a renderer seam drives:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import {
|
|
41
|
+
createElement, createRawText, createAnchor,
|
|
42
|
+
appendChild, insertBefore, removeChild,
|
|
43
|
+
routeProp, setEventListener, setProp, setText,
|
|
44
|
+
} from '@symbiote-native/engine';
|
|
45
|
+
|
|
46
|
+
const node = createElement('RCTView'); // component IS the Fabric view name
|
|
47
|
+
const text = createRawText('Hello');
|
|
48
|
+
appendChild(node, text);
|
|
49
|
+
routeProp(node, 'onPress', () => {}); // ← the flat-bag entry point (React/Vue/Solid):
|
|
50
|
+
// decides event-vs-prop via the ViewConfig,
|
|
51
|
+
// NOT by the "onX" naming convention
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`routeProp` is the one call a flat-bag adapter should route every prop through — a **structural**
|
|
55
|
+
adapter (Angular's `Renderer2.listen`, Svelte's `addEventListener`) already knows the event name
|
|
56
|
+
and calls `setEventListener` directly instead.
|
|
57
|
+
|
|
58
|
+
### Committing — `SymbioteSurface`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { createSurface } from '@symbiote-native/engine';
|
|
62
|
+
|
|
63
|
+
const surface = createSurface(rootTag);
|
|
64
|
+
surface.appendChild(root, node);
|
|
65
|
+
surface.commit(); // synchronous — for a framework that already batches (React)
|
|
66
|
+
// surface.requestCommit(); // microtask-coalesced — for reactive frameworks (Vue/Svelte/Angular)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Every imperative call in the bridge below (`dispatchViewCommand`, `measure`, `setNativeProps`, …)
|
|
70
|
+
is gated on the node having actually committed — see `whenCommitted` for wiring a native call
|
|
71
|
+
before a tag is guaranteed to exist.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## What else it exports
|
|
76
|
+
|
|
77
|
+
- **The imperative/native bridge** — `dispatchViewCommand`, `measure` / `measureInWindow` /
|
|
78
|
+
`measureLayout`, `getNativeTag`, `getNativeNode`, `setNativeProps`, `sendAccessibilityEvent`,
|
|
79
|
+
`whenCommitted`, `toPublicInstance` (the `ref` handle every adapter grafts onto a host node).
|
|
80
|
+
- **Runtime modules**, framework-agnostic, re-exported by every adapter: `Platform`,
|
|
81
|
+
`StyleSheet` (+ `computeHairlineWidth`), `Dimensions`, `PixelRatio`, `Appearance`, `AppState`,
|
|
82
|
+
`Keyboard`, `AccessibilityInfo`, `BackHandler`, `PermissionsAndroid`, `LayoutAnimation`,
|
|
83
|
+
`InteractionManager`, `PanResponder`, and the imperative modules `Alert`, `Share`,
|
|
84
|
+
`ActionSheetIOS`, `Linking`, `Vibration`, `ToastAndroid`, `Settings`, `I18nManager`.
|
|
85
|
+
- **`Animated`** — both the JS and native driver (`timing` / `spring` / `decay` / `loop` /
|
|
86
|
+
`ValueXY` / tracking / `diffClamp` / `Easing`), including the native-event attachment path
|
|
87
|
+
(`attachNativeEvent`, `AnimatedEvent`).
|
|
88
|
+
- **The style pipeline** — `flattenStyle`, the CSS-style processors RN itself runs in JS
|
|
89
|
+
(`processBoxShadow`, `processFilter`, `processTransform`, `processTransformOrigin`,
|
|
90
|
+
`processAspectRatio`, `processFontVariant`, `processBackgroundImage`), and the runtime
|
|
91
|
+
**class-name registry** (`registerStyles` / `resolveClassName` / `scopeClassName`) that
|
|
92
|
+
`@symbiote-native/css-parser`'s build-time output resolves against — shared by every adapter's
|
|
93
|
+
`class` / `className` / `addClass` prop path.
|
|
94
|
+
- **`AppRegistry` core** (`createAppRegistry`) — registry bookkeeping + headless-task plumbing;
|
|
95
|
+
each adapter supplies only its own `runnableFor`.
|
|
96
|
+
- **`dlog` / `isDebug`** — the diagnostic-logging seam every adapter and this package route
|
|
97
|
+
through, gated by the `DEBUG` env var, never a bare `console.log`.
|
|
98
|
+
|
|
99
|
+
## What it does NOT do
|
|
100
|
+
|
|
101
|
+
- It does not know about React, Vue, Angular, JSX, templates, or reactivity — an adapter maps its
|
|
102
|
+
own framework idioms onto this API, never the other way around.
|
|
103
|
+
- It does not touch Fabric C++, JSI, or Yoga directly — it calls `nativeFabricUIManager`
|
|
104
|
+
(`createNode` / `cloneNodeWithNewProps` / `appendChildToSet` / `completeRoot`), the same
|
|
105
|
+
framework-agnostic seam React's own renderer uses.
|
|
106
|
+
- It is not a component library — visual components (Switch, Modal, the lists, …) live in
|
|
107
|
+
[`@symbiote-native/components`](../components), built on top of this package's `Descriptor`-free
|
|
108
|
+
mutation API.
|
|
109
|
+
|
|
110
|
+
## Related packages
|
|
111
|
+
|
|
112
|
+
- [`@symbiote-native/components`](../components) — the framework-agnostic component layer (state +
|
|
113
|
+
render), built on this engine.
|
|
114
|
+
- [`@symbiote-native/react`](../../adapters/react) / [`@symbiote-native/vue`](../../adapters/vue) /
|
|
115
|
+
[`@symbiote-native/angular`](../../adapters/angular) — the framework adapters that drive this API.
|
|
116
|
+
- [`@symbiote-native/css-parser`](../css-parser) — compiles CSS into the style objects this package's
|
|
117
|
+
`style-registry` resolves at runtime.
|
|
118
|
+
|
|
119
|
+
## Test it
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pnpm test # vitest, from the workspace root — headless, against a fake Fabric slot
|
|
123
|
+
DEBUG=1 pnpm test # same, with diagnostic logs on
|
|
124
|
+
```
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type IAccessibilityInfoStatic } from './shared';
|
|
2
|
+
export type { IAccessibilityChangeEvent, IAccessibilityChangeEventName, IAccessibilityChangeEventHandler, IAccessibilityAnnouncementFinishedEvent, IAnnounceForAccessibilityOptions, IAccessibilityEventType, } from './shared';
|
|
3
|
+
export declare const AccessibilityInfo: IAccessibilityInfoStatic;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// AccessibilityInfo on Android wraps the stock RN `AccessibilityInfo` native module
|
|
2
|
+
// (NO native code added; it ships with react-native). Android's getters take a SINGLE
|
|
3
|
+
// success callback (no error callback) and a different method set than iOS: screen-reader
|
|
4
|
+
// is `isTouchExplorationEnabled`, plus reduce-motion / invert-colors / grayscale /
|
|
5
|
+
// high-text-contrast / accessibility-service, and `getRecommendedTimeoutMillis`. The
|
|
6
|
+
// device-event NAMES also differ from iOS (e.g. screen-reader is `touchExplorationDidChange`,
|
|
7
|
+
// reduce-motion is `reduceMotionDidChange`). Metro picks this on an Android host. Mirrors
|
|
8
|
+
// RN's AccessibilityInfo.js Android branches.
|
|
9
|
+
import { getNativeModule } from '../native-modules';
|
|
10
|
+
import { installDeviceEventHub, NativeEventEmitter, } from '../native-events';
|
|
11
|
+
import { isSymbioteNode } from '../node';
|
|
12
|
+
import { sendAccessibilityEvent as sharedSendAccessibilityEvent } from '../commit';
|
|
13
|
+
import { dlog } from '../debug';
|
|
14
|
+
import { isBoolean, } from './shared';
|
|
15
|
+
// The Android native module name. This is the module the Android JS wrapper
|
|
16
|
+
// (INativeAccessibilityInfoAndroid) resolves: the stock RN `AccessibilityInfo` Turbo/legacy
|
|
17
|
+
// module. Per the symbiote invariant, a module name is only provable on a real host (a
|
|
18
|
+
// headless fake answers to any name); this Android name is DEVICE-VERIFY-PENDING. See
|
|
19
|
+
// .docs/native-module-platform-routing.md.
|
|
20
|
+
const ACCESSIBILITY_MODULE = 'AccessibilityInfo';
|
|
21
|
+
// Public event name -> the Android device event the native side emits. Android renames
|
|
22
|
+
// most of them; events with no Android source (iOS-only) are absent and yield an inert
|
|
23
|
+
// subscription. (RN maps both `change` and `screenReaderChanged` to touchExplorationDidChange.)
|
|
24
|
+
const ANDROID_DEVICE_EVENT = {
|
|
25
|
+
screenReaderChanged: 'touchExplorationDidChange',
|
|
26
|
+
reduceMotionChanged: 'reduceMotionDidChange',
|
|
27
|
+
highTextContrastChanged: 'highTextContrastDidChange',
|
|
28
|
+
accessibilityServiceChanged: 'accessibilityServiceDidChange',
|
|
29
|
+
invertColorsChanged: 'invertColorDidChange',
|
|
30
|
+
grayscaleChanged: 'grayscaleModeDidChange',
|
|
31
|
+
};
|
|
32
|
+
// Lazily resolved so importing this module has no native side effect. `null` when unlinked.
|
|
33
|
+
let accessibilityModule;
|
|
34
|
+
let emitter;
|
|
35
|
+
function getModule() {
|
|
36
|
+
if (accessibilityModule === undefined) {
|
|
37
|
+
accessibilityModule = getNativeModule(ACCESSIBILITY_MODULE);
|
|
38
|
+
dlog(`AccessibilityInfo(android): module ${accessibilityModule ? 'resolved' : 'NOT resolved (null)'}`);
|
|
39
|
+
}
|
|
40
|
+
return accessibilityModule;
|
|
41
|
+
}
|
|
42
|
+
function getEmitter() {
|
|
43
|
+
if (emitter === undefined) {
|
|
44
|
+
installDeviceEventHub();
|
|
45
|
+
emitter = new NativeEventEmitter(getModule() ?? undefined);
|
|
46
|
+
}
|
|
47
|
+
return emitter;
|
|
48
|
+
}
|
|
49
|
+
// Run a single-callback Android getter as a Promise. Resolves false when the module is
|
|
50
|
+
// unlinked OR the optional method is absent on this host; mirrors RN's "missing query ->
|
|
51
|
+
// false" contract for the cross-platform getters. The dlog records the miss.
|
|
52
|
+
function queryState(pick, label) {
|
|
53
|
+
const module = getModule();
|
|
54
|
+
if (module === null) {
|
|
55
|
+
dlog(`AccessibilityInfo(android).${label} -> no module (false)`);
|
|
56
|
+
return Promise.resolve(false);
|
|
57
|
+
}
|
|
58
|
+
const getter = pick(module);
|
|
59
|
+
if (getter === undefined) {
|
|
60
|
+
dlog(`AccessibilityInfo(android).${label} -> method absent (false)`);
|
|
61
|
+
return Promise.resolve(false);
|
|
62
|
+
}
|
|
63
|
+
return new Promise(resolve => {
|
|
64
|
+
getter.call(module, enabled => resolve(enabled));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
class AccessibilityInfoAndroid {
|
|
68
|
+
// Screen reader on Android == touch exploration (TalkBack).
|
|
69
|
+
isScreenReaderEnabled() {
|
|
70
|
+
return queryState(m => m.isTouchExplorationEnabled, 'isScreenReaderEnabled');
|
|
71
|
+
}
|
|
72
|
+
isReduceMotionEnabled() {
|
|
73
|
+
return queryState(m => m.isReduceMotionEnabled, 'isReduceMotionEnabled');
|
|
74
|
+
}
|
|
75
|
+
// iOS-only query; Android has no bold-text setting, so resolve false (RN parity).
|
|
76
|
+
isBoldTextEnabled() {
|
|
77
|
+
return Promise.resolve(false);
|
|
78
|
+
}
|
|
79
|
+
isGrayscaleEnabled() {
|
|
80
|
+
return queryState(m => m.isGrayscaleEnabled, 'isGrayscaleEnabled');
|
|
81
|
+
}
|
|
82
|
+
isInvertColorsEnabled() {
|
|
83
|
+
return queryState(m => m.isInvertColorsEnabled, 'isInvertColorsEnabled');
|
|
84
|
+
}
|
|
85
|
+
// iOS-only query; resolve false (RN parity).
|
|
86
|
+
isReduceTransparencyEnabled() {
|
|
87
|
+
return Promise.resolve(false);
|
|
88
|
+
}
|
|
89
|
+
isHighTextContrastEnabled() {
|
|
90
|
+
return queryState(m => m.isHighTextContrastEnabled, 'isHighTextContrastEnabled');
|
|
91
|
+
}
|
|
92
|
+
// iOS-only "Increase Contrast"; Android has no equivalent, so resolve false (RN parity).
|
|
93
|
+
isDarkerSystemColorsEnabled() {
|
|
94
|
+
return Promise.resolve(false);
|
|
95
|
+
}
|
|
96
|
+
// iOS-only reduce-motion sub-setting; resolve false on Android (RN parity).
|
|
97
|
+
prefersCrossFadeTransitions() {
|
|
98
|
+
return Promise.resolve(false);
|
|
99
|
+
}
|
|
100
|
+
isAccessibilityServiceEnabled() {
|
|
101
|
+
return queryState(m => m.isAccessibilityServiceEnabled, 'isAccessibilityServiceEnabled');
|
|
102
|
+
}
|
|
103
|
+
// Post a string to be announced by the screen reader. No-op without a module.
|
|
104
|
+
announceForAccessibility(announcement) {
|
|
105
|
+
const module = getModule();
|
|
106
|
+
if (module === null) {
|
|
107
|
+
dlog('AccessibilityInfo(android).announceForAccessibility -> no module (no-op)');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
module.announceForAccessibility(announcement);
|
|
111
|
+
}
|
|
112
|
+
// Android ignores queue/priority options (iOS-only) and posts the announcement plainly
|
|
113
|
+
// (RN parity).
|
|
114
|
+
announceForAccessibilityWithOptions(announcement, _options) {
|
|
115
|
+
this.announceForAccessibility(announcement);
|
|
116
|
+
}
|
|
117
|
+
// Deprecated focus-by-tag entry. The Fabric slot keys on a node's committed handle, and a
|
|
118
|
+
// bare reactTag can't be resolved back to its SymbioteNode (the mirror is node-keyed), so
|
|
119
|
+
// this best-effort path is a logged no-op. Callers should use sendAccessibilityEvent(node,
|
|
120
|
+
// 'focus') with a host ref, which routes a real node through the slot.
|
|
121
|
+
setAccessibilityFocus(reactTag) {
|
|
122
|
+
dlog(`AccessibilityInfo(android).setAccessibilityFocus(${reactTag}) -> tag-only, no node to route (no-op)`);
|
|
123
|
+
}
|
|
124
|
+
// Recommended UI-change timeout for this user. Resolves the original when the module or
|
|
125
|
+
// the query is absent (RN parity).
|
|
126
|
+
getRecommendedTimeoutMillis(originalTimeout) {
|
|
127
|
+
const module = getModule();
|
|
128
|
+
if (module === null || module.getRecommendedTimeoutMillis === undefined) {
|
|
129
|
+
return Promise.resolve(originalTimeout);
|
|
130
|
+
}
|
|
131
|
+
const query = module.getRecommendedTimeoutMillis;
|
|
132
|
+
return new Promise(resolve => {
|
|
133
|
+
query.call(module, originalTimeout, timeout => resolve(timeout));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Emit a named accessibility event at a view through the Fabric slot. RN's Fabric path
|
|
137
|
+
// hands the public-instance handle to nativeFabricUIManager.sendAccessibilityEvent with the
|
|
138
|
+
// STRING eventType; the C++ side maps it to the platform's AccessibilityEvent kind. The
|
|
139
|
+
// handle here IS the SymbioteNode (symbiote augments the node in place as its public
|
|
140
|
+
// instance), so resolve it with the runtime guard and route through shared.
|
|
141
|
+
sendAccessibilityEvent(handle, eventType) {
|
|
142
|
+
if (!isSymbioteNode(handle)) {
|
|
143
|
+
dlog(`AccessibilityInfo(android).sendAccessibilityEvent("${eventType}") -> handle is not a node (no-op)`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
dlog(`AccessibilityInfo(android).sendAccessibilityEvent("${eventType}") -> slot`);
|
|
147
|
+
sharedSendAccessibilityEvent(handle, eventType);
|
|
148
|
+
}
|
|
149
|
+
// Subscribe to an accessibility-state change. Android events all carry a bare boolean.
|
|
150
|
+
// Never throws: a public event with no Android device mapping yields an inert
|
|
151
|
+
// subscription; a missing module yields a live-but-silent one.
|
|
152
|
+
addEventListener(eventName, handler) {
|
|
153
|
+
const deviceEvent = ANDROID_DEVICE_EVENT[eventName];
|
|
154
|
+
dlog(`AccessibilityInfo(android).addEventListener -> ${eventName} (device: ${deviceEvent ?? 'none'})`);
|
|
155
|
+
if (deviceEvent === undefined) {
|
|
156
|
+
return { remove() { } };
|
|
157
|
+
}
|
|
158
|
+
const eventEmitter = getEmitter();
|
|
159
|
+
return eventEmitter.addListener(deviceEvent, payload => {
|
|
160
|
+
if (!isBoolean(payload))
|
|
161
|
+
return;
|
|
162
|
+
handler(payload);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export const AccessibilityInfo = new AccessibilityInfoAndroid();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.ios';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type IAccessibilityInfoStatic } from './shared';
|
|
2
|
+
export type { IAccessibilityChangeEvent, IAccessibilityChangeEventName, IAccessibilityChangeEventHandler, IAccessibilityAnnouncementFinishedEvent, IAnnounceForAccessibilityOptions, IAccessibilityEventType, } from './shared';
|
|
3
|
+
export declare const AccessibilityInfo: IAccessibilityInfoStatic;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// AccessibilityInfo on iOS wraps the `AccessibilityManager` native module: callback-
|
|
2
|
+
// based state getters (VoiceOver / reduce-motion / bold-text / grayscale / invert-colors /
|
|
3
|
+
// reduce-transparency / darker-system-colors), announce + focus side effects, and the
|
|
4
|
+
// observe-counters for the device-event subscription. Subscribes to iOS device events
|
|
5
|
+
// (`screenReaderChanged` / `reduceMotionChanged` / `boldTextChanged` / …) via a
|
|
6
|
+
// NativeEventEmitter and re-broadcasts to JS listeners. Metro picks this on an iOS host;
|
|
7
|
+
// the bare accessibility-info.ts re-exports it as the default for tsc / tsx / headless.
|
|
8
|
+
// Mirrors RN's AccessibilityInfo.js iOS branches.
|
|
9
|
+
import { getNativeModule } from '../native-modules';
|
|
10
|
+
import { installDeviceEventHub, NativeEventEmitter, } from '../native-events';
|
|
11
|
+
import { isSymbioteNode } from '../node';
|
|
12
|
+
import { sendAccessibilityEvent as sharedSendAccessibilityEvent } from '../commit';
|
|
13
|
+
import { dlog } from '../debug';
|
|
14
|
+
import { isBoolean, } from './shared';
|
|
15
|
+
// The iOS native module name RN registers this under. NOTE: this is the name the iOS JS
|
|
16
|
+
// wrapper (INativeAccessibilityManagerIOS) resolves via
|
|
17
|
+
// `TurboModuleRegistry.get('AccessibilityManager')`, NOT the spec filename
|
|
18
|
+
// `NativeAccessibilityManager`. Per the symbiote invariant, a module name is only provable
|
|
19
|
+
// on a real host (a headless fake answers to any name); this iOS name is device-verified
|
|
20
|
+
// (the pre-split file shipped it). See .docs/native-module-platform-routing.md.
|
|
21
|
+
const ACCESSIBILITY_MODULE = 'AccessibilityManager';
|
|
22
|
+
// Public event name -> the iOS device event the native side emits. iOS keeps the names
|
|
23
|
+
// 1:1; the indirection exists only so the mapping stays explicit (Android renames them).
|
|
24
|
+
const IOS_DEVICE_EVENT = {
|
|
25
|
+
screenReaderChanged: 'screenReaderChanged',
|
|
26
|
+
reduceMotionChanged: 'reduceMotionChanged',
|
|
27
|
+
boldTextChanged: 'boldTextChanged',
|
|
28
|
+
grayscaleChanged: 'grayscaleChanged',
|
|
29
|
+
invertColorsChanged: 'invertColorsChanged',
|
|
30
|
+
reduceTransparencyChanged: 'reduceTransparencyChanged',
|
|
31
|
+
darkerSystemColorsChanged: 'darkerSystemColorsChanged',
|
|
32
|
+
announcementFinished: 'announcementFinished',
|
|
33
|
+
};
|
|
34
|
+
// Lazily resolved so importing this module has no native side effect: a headless run
|
|
35
|
+
// without a fake __turboModuleProxy still loads it; resolution happens on first use.
|
|
36
|
+
// `null` when the module isn't linked.
|
|
37
|
+
let accessibilityModule;
|
|
38
|
+
let emitter;
|
|
39
|
+
function getModule() {
|
|
40
|
+
if (accessibilityModule === undefined) {
|
|
41
|
+
accessibilityModule = getNativeModule(ACCESSIBILITY_MODULE);
|
|
42
|
+
dlog(`AccessibilityInfo(ios): module ${accessibilityModule ? 'resolved' : 'NOT resolved (null)'}`);
|
|
43
|
+
}
|
|
44
|
+
return accessibilityModule;
|
|
45
|
+
}
|
|
46
|
+
function getEmitter() {
|
|
47
|
+
if (emitter === undefined) {
|
|
48
|
+
// WHY lazy: install on first subscribe so the hub exists before native emits,
|
|
49
|
+
// without a hard bootstrap-order dependency. Idempotent.
|
|
50
|
+
installDeviceEventHub();
|
|
51
|
+
emitter = new NativeEventEmitter(getModule() ?? undefined);
|
|
52
|
+
}
|
|
53
|
+
return emitter;
|
|
54
|
+
}
|
|
55
|
+
// Run a callback-based native getter as a Promise; resolves false when the module is
|
|
56
|
+
// unlinked, mirroring RN's "unavailable query -> false" contract for the cross-platform
|
|
57
|
+
// getters. (RN rejects on iOS, but a false fallback keeps the unified surface uniform with
|
|
58
|
+
// Android's missing-method getters; the dlog records the miss.)
|
|
59
|
+
function queryState(pick, label) {
|
|
60
|
+
const module = getModule();
|
|
61
|
+
if (module === null) {
|
|
62
|
+
dlog(`AccessibilityInfo(ios).${label} -> no module (false)`);
|
|
63
|
+
return Promise.resolve(false);
|
|
64
|
+
}
|
|
65
|
+
const getter = pick(module);
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
getter.call(module, enabled => resolve(enabled), error => reject(error));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Like queryState, but for an OPTIONAL native getter (newer iOS surfaces): resolves false
|
|
71
|
+
// when the module is unlinked OR the method is absent on this host, instead of throwing.
|
|
72
|
+
function queryOptionalState(pick, label) {
|
|
73
|
+
const module = getModule();
|
|
74
|
+
if (module === null) {
|
|
75
|
+
dlog(`AccessibilityInfo(ios).${label} -> no module (false)`);
|
|
76
|
+
return Promise.resolve(false);
|
|
77
|
+
}
|
|
78
|
+
const getter = pick(module);
|
|
79
|
+
if (getter === undefined) {
|
|
80
|
+
dlog(`AccessibilityInfo(ios).${label} -> method absent (false)`);
|
|
81
|
+
return Promise.resolve(false);
|
|
82
|
+
}
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
getter.call(module, enabled => resolve(enabled), error => reject(error));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
class AccessibilityInfoIOS {
|
|
88
|
+
isScreenReaderEnabled() {
|
|
89
|
+
return queryState(m => m.getCurrentVoiceOverState, 'isScreenReaderEnabled');
|
|
90
|
+
}
|
|
91
|
+
isReduceMotionEnabled() {
|
|
92
|
+
return queryState(m => m.getCurrentReduceMotionState, 'isReduceMotionEnabled');
|
|
93
|
+
}
|
|
94
|
+
isBoldTextEnabled() {
|
|
95
|
+
return queryState(m => m.getCurrentBoldTextState, 'isBoldTextEnabled');
|
|
96
|
+
}
|
|
97
|
+
isGrayscaleEnabled() {
|
|
98
|
+
return queryState(m => m.getCurrentGrayscaleState, 'isGrayscaleEnabled');
|
|
99
|
+
}
|
|
100
|
+
isInvertColorsEnabled() {
|
|
101
|
+
return queryState(m => m.getCurrentInvertColorsState, 'isInvertColorsEnabled');
|
|
102
|
+
}
|
|
103
|
+
isReduceTransparencyEnabled() {
|
|
104
|
+
return queryState(m => m.getCurrentReduceTransparencyState, 'isReduceTransparencyEnabled');
|
|
105
|
+
}
|
|
106
|
+
// iOS "Increase Contrast": Settings > Accessibility > Display & Text Size. The native
|
|
107
|
+
// getter is optional (older hosts lack it); resolve false when absent rather than reject,
|
|
108
|
+
// keeping the unified surface non-throwing (RN rejects, we mirror the false fallback).
|
|
109
|
+
isDarkerSystemColorsEnabled() {
|
|
110
|
+
return queryOptionalState(m => m.getCurrentDarkerSystemColorsState, 'isDarkerSystemColorsEnabled');
|
|
111
|
+
}
|
|
112
|
+
// iOS reduce-motion sub-setting (prefer cross-fade over slide). Optional native getter;
|
|
113
|
+
// resolve false when absent (RN parity for the unavailable case).
|
|
114
|
+
prefersCrossFadeTransitions() {
|
|
115
|
+
return queryOptionalState(m => m.getCurrentPrefersCrossFadeTransitionsState, 'prefersCrossFadeTransitions');
|
|
116
|
+
}
|
|
117
|
+
// Android-only query; iOS has no high-text-contrast concept, so resolve false (RN parity).
|
|
118
|
+
isHighTextContrastEnabled() {
|
|
119
|
+
return Promise.resolve(false);
|
|
120
|
+
}
|
|
121
|
+
// Android-only query; on iOS RN rejects. We resolve false to keep the unified surface
|
|
122
|
+
// non-throwing; the dlog records that it's a no-op on this platform.
|
|
123
|
+
isAccessibilityServiceEnabled() {
|
|
124
|
+
dlog('AccessibilityInfo(ios).isAccessibilityServiceEnabled -> Android-only (false)');
|
|
125
|
+
return Promise.resolve(false);
|
|
126
|
+
}
|
|
127
|
+
// Post a string to be announced by the screen reader. No-op without a module.
|
|
128
|
+
announceForAccessibility(announcement) {
|
|
129
|
+
const module = getModule();
|
|
130
|
+
if (module === null) {
|
|
131
|
+
dlog('AccessibilityInfo(ios).announceForAccessibility -> no module (no-op)');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
module.announceForAccessibility(announcement);
|
|
135
|
+
}
|
|
136
|
+
// Announce with queue/priority options. Falls back to the plain announce when the host
|
|
137
|
+
// lacks the options-aware method (older iOS), mirroring RN.
|
|
138
|
+
announceForAccessibilityWithOptions(announcement, options) {
|
|
139
|
+
const module = getModule();
|
|
140
|
+
if (module === null) {
|
|
141
|
+
dlog('AccessibilityInfo(ios).announceForAccessibilityWithOptions -> no module (no-op)');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (module.announceForAccessibilityWithOptions) {
|
|
145
|
+
module.announceForAccessibilityWithOptions(announcement, options);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
module.announceForAccessibility(announcement);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Move accessibility focus to the view with the given react tag. No-op without a module.
|
|
152
|
+
// RN deprecates this in favor of sendAccessibilityEvent; kept for parity.
|
|
153
|
+
setAccessibilityFocus(reactTag) {
|
|
154
|
+
const module = getModule();
|
|
155
|
+
if (module === null) {
|
|
156
|
+
dlog('AccessibilityInfo(ios).setAccessibilityFocus -> no module (no-op)');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
dlog(`AccessibilityInfo(ios).setAccessibilityFocus -> ${reactTag}`);
|
|
160
|
+
module.setAccessibilityFocus(reactTag);
|
|
161
|
+
}
|
|
162
|
+
// iOS has no recommended-timeout query; resolve the original (RN parity).
|
|
163
|
+
getRecommendedTimeoutMillis(originalTimeout) {
|
|
164
|
+
return Promise.resolve(originalTimeout);
|
|
165
|
+
}
|
|
166
|
+
// Emit an accessibility event at a view through the Fabric slot. RN's Fabric
|
|
167
|
+
// sendAccessibilityEvent hands the public-instance handle straight to
|
|
168
|
+
// nativeFabricUIManager.sendAccessibilityEvent with the STRING eventType, and the C++
|
|
169
|
+
// side maps it. The handle here IS the SymbioteNode (symbiote augments the node in place
|
|
170
|
+
// as its public instance), so resolve it with the runtime guard and route through shared.
|
|
171
|
+
// RN early-returns 'click' on iOS only (AccessibilityInfo.js) because VoiceOver has no click
|
|
172
|
+
// producer, so preserve that one no-op; every other event reaches the slot.
|
|
173
|
+
sendAccessibilityEvent(handle, eventType) {
|
|
174
|
+
if (eventType === 'click') {
|
|
175
|
+
dlog('AccessibilityInfo(ios).sendAccessibilityEvent("click") -> iOS no-op (RN parity)');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!isSymbioteNode(handle)) {
|
|
179
|
+
dlog(`AccessibilityInfo(ios).sendAccessibilityEvent("${eventType}") -> handle is not a node (no-op)`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
dlog(`AccessibilityInfo(ios).sendAccessibilityEvent("${eventType}") -> slot`);
|
|
183
|
+
sharedSendAccessibilityEvent(handle, eventType);
|
|
184
|
+
}
|
|
185
|
+
// Subscribe to an accessibility-state change. A handler for a boolean event receives a
|
|
186
|
+
// boolean; the iOS-only `announcementFinished` carries the announcement payload. Never
|
|
187
|
+
// throws: a public event with no iOS device mapping yields an inert subscription, and a
|
|
188
|
+
// missing module yields a live-but-silent one (the counters are no-ops without a module).
|
|
189
|
+
addEventListener(eventName, handler) {
|
|
190
|
+
const deviceEvent = IOS_DEVICE_EVENT[eventName];
|
|
191
|
+
dlog(`AccessibilityInfo(ios).addEventListener -> ${eventName} (device: ${deviceEvent ?? 'none'})`);
|
|
192
|
+
if (deviceEvent === undefined) {
|
|
193
|
+
return { remove() { } };
|
|
194
|
+
}
|
|
195
|
+
const eventEmitter = getEmitter();
|
|
196
|
+
return eventEmitter.addListener(deviceEvent, payload => {
|
|
197
|
+
// Most events carry a bare boolean; announcementFinished carries an object. Forward
|
|
198
|
+
// each in its own shape, dropping payloads that match neither so we never forward
|
|
199
|
+
// garbage to the handler.
|
|
200
|
+
if (eventName === 'announcementFinished') {
|
|
201
|
+
if (isAnnouncementFinished(payload))
|
|
202
|
+
handler(payload);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (!isBoolean(payload))
|
|
206
|
+
return;
|
|
207
|
+
handler(payload);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function isAnnouncementFinished(payload) {
|
|
212
|
+
return (typeof payload === 'object' &&
|
|
213
|
+
payload !== null &&
|
|
214
|
+
'announcement' in payload &&
|
|
215
|
+
typeof payload.announcement === 'string' &&
|
|
216
|
+
'success' in payload &&
|
|
217
|
+
typeof payload.success === 'boolean');
|
|
218
|
+
}
|
|
219
|
+
export const AccessibilityInfo = new AccessibilityInfoIOS();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Base / default AccessibilityInfo: re-exports the iOS build. Metro overrides this with
|
|
2
|
+
// accessibility-info.ios.ts / .android.ts on a real host; under tsx / tsc / web the host
|
|
3
|
+
// config resolves here. Filename is the selector, no Platform.OS read. See
|
|
4
|
+
// accessibility-info-shared.ts.
|
|
5
|
+
export * from './index.ios';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ISymbioteNode } from '../node';
|
|
2
|
+
import type { IEventSubscription } from '../native-events';
|
|
3
|
+
export type IAccessibilityChangeEventName = 'screenReaderChanged' | 'reduceMotionChanged' | 'boldTextChanged' | 'grayscaleChanged' | 'invertColorsChanged' | 'reduceTransparencyChanged' | 'darkerSystemColorsChanged' | 'announcementFinished' | 'accessibilityServiceChanged' | 'highTextContrastChanged';
|
|
4
|
+
export type IAccessibilityChangeEvent = IAccessibilityChangeEventName;
|
|
5
|
+
export type IAccessibilityHandle = ISymbioteNode | number | null | undefined;
|
|
6
|
+
export interface IAccessibilityAnnouncementFinishedEvent {
|
|
7
|
+
announcement: string;
|
|
8
|
+
success: boolean;
|
|
9
|
+
}
|
|
10
|
+
export type IAccessibilityChangeEventHandler = (state: boolean | IAccessibilityAnnouncementFinishedEvent) => void;
|
|
11
|
+
export interface IAnnounceForAccessibilityOptions {
|
|
12
|
+
queue?: boolean;
|
|
13
|
+
priority?: 'low' | 'default' | 'high';
|
|
14
|
+
}
|
|
15
|
+
export interface IAccessibilityInfoStatic {
|
|
16
|
+
isScreenReaderEnabled(): Promise<boolean>;
|
|
17
|
+
isReduceMotionEnabled(): Promise<boolean>;
|
|
18
|
+
isBoldTextEnabled(): Promise<boolean>;
|
|
19
|
+
isGrayscaleEnabled(): Promise<boolean>;
|
|
20
|
+
isInvertColorsEnabled(): Promise<boolean>;
|
|
21
|
+
isReduceTransparencyEnabled(): Promise<boolean>;
|
|
22
|
+
isHighTextContrastEnabled(): Promise<boolean>;
|
|
23
|
+
isAccessibilityServiceEnabled(): Promise<boolean>;
|
|
24
|
+
isDarkerSystemColorsEnabled(): Promise<boolean>;
|
|
25
|
+
prefersCrossFadeTransitions(): Promise<boolean>;
|
|
26
|
+
announceForAccessibility(announcement: string): void;
|
|
27
|
+
announceForAccessibilityWithOptions(announcement: string, options: IAnnounceForAccessibilityOptions): void;
|
|
28
|
+
setAccessibilityFocus(reactTag: number): void;
|
|
29
|
+
getRecommendedTimeoutMillis(originalTimeout: number): Promise<number>;
|
|
30
|
+
sendAccessibilityEvent(handle: IAccessibilityHandle, eventType: IAccessibilityEventType): void;
|
|
31
|
+
addEventListener(eventName: IAccessibilityChangeEventName, handler: IAccessibilityChangeEventHandler): IEventSubscription;
|
|
32
|
+
}
|
|
33
|
+
export type IAccessibilityEventType = 'click' | 'focus' | 'viewHoverEnter' | 'windowStateChange';
|
|
34
|
+
export declare function isBoolean(value: unknown): value is boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// AccessibilityInfo, shared contract. The component renders NO Fabric view; it
|
|
2
|
+
// imperatively queries an accessibility native module and subscribes to its device
|
|
3
|
+
// events. What DIVERGES by platform is the native module (iOS `AccessibilityManager`
|
|
4
|
+
// with callback getters; Android stock `AccessibilityInfo` with single-callback
|
|
5
|
+
// getters), which getters exist, and the DEVICE-EVENT NAME a public
|
|
6
|
+
// event maps to (iOS `screenReaderChanged` vs Android `touchExplorationDidChange`).
|
|
7
|
+
// So the .ios/.android files own the native calls and per-platform event-name map;
|
|
8
|
+
// the public types + the shared method surface live here. Filename selects, no
|
|
9
|
+
// Platform.OS read (see ADR 0012 + native_module_name_is_platform_specific). Mirrors
|
|
10
|
+
// RN's Libraries/Components/AccessibilityInfo/AccessibilityInfo.js.
|
|
11
|
+
export function isBoolean(value) {
|
|
12
|
+
return typeof value === 'boolean';
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface IActionSheetIOSOptions {
|
|
2
|
+
title?: string;
|
|
3
|
+
message?: string;
|
|
4
|
+
options: string[];
|
|
5
|
+
destructiveButtonIndex?: number | number[];
|
|
6
|
+
destructiveButtonIndices?: number[];
|
|
7
|
+
cancelButtonIndex?: number;
|
|
8
|
+
anchor?: number;
|
|
9
|
+
tintColor?: unknown;
|
|
10
|
+
cancelButtonTintColor?: unknown;
|
|
11
|
+
disabledButtonTintColor?: unknown;
|
|
12
|
+
userInterfaceStyle?: string;
|
|
13
|
+
disabledButtonIndices?: number[];
|
|
14
|
+
}
|
|
15
|
+
export interface IShareActionSheetIOSOptions {
|
|
16
|
+
message?: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
subject?: string;
|
|
19
|
+
anchor?: number;
|
|
20
|
+
tintColor?: unknown;
|
|
21
|
+
cancelButtonTintColor?: unknown;
|
|
22
|
+
disabledButtonTintColor?: unknown;
|
|
23
|
+
excludedActivityTypes?: string[];
|
|
24
|
+
userInterfaceStyle?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface IShareActionSheetError {
|
|
27
|
+
domain: string;
|
|
28
|
+
code: string;
|
|
29
|
+
userInfo?: Record<string, unknown>;
|
|
30
|
+
message: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const ActionSheetIOS: {
|
|
33
|
+
showActionSheetWithOptions(options: IActionSheetIOSOptions, callback: (buttonIndex: number) => void): void;
|
|
34
|
+
showShareActionSheetWithOptions(options: IShareActionSheetIOSOptions, failureCallback: (error: IShareActionSheetError) => void, successCallback: (completed: boolean, activityType?: string) => void): void;
|
|
35
|
+
dismissActionSheet(): void;
|
|
36
|
+
};
|