@symbiote-native/engine 0.1.4 → 0.1.5
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/build/accessibility-info/index.android.js +17 -29
- package/build/accessibility-info/index.ios.js +23 -37
- package/build/accessibility-info/shared.d.ts +3 -2
- package/build/accessibility-info/shared.js +25 -2
- package/build/action-sheet-ios/index.d.ts +6 -0
- package/build/action-sheet-ios/index.js +1 -1
- package/build/alert/index.android.js +1 -3
- package/build/animated/animations/base.js +5 -14
- package/build/animated/event.js +7 -9
- package/build/animated/graph.d.ts +6 -0
- package/build/animated/graph.js +14 -0
- package/build/animated/interpolation-node.d.ts +0 -1
- package/build/animated/interpolation-node.js +5 -4
- package/build/animated/native/native-animated.js +1 -3
- package/build/animated/operators.d.ts +14 -34
- package/build/animated/operators.js +19 -95
- package/build/animated/value.d.ts +2 -4
- package/build/animated/value.js +17 -5
- package/build/app-state/index.js +17 -22
- package/build/appearance/index.js +17 -25
- package/build/back-handler/index.js +15 -18
- package/build/commit.d.ts +1 -3
- package/build/commit.js +10 -207
- package/build/dimensions/index.js +25 -20
- package/build/events/index.js +6 -151
- package/build/fabric-props.d.ts +3 -0
- package/build/fabric-props.js +186 -0
- package/build/i18n-manager/index.js +3 -5
- package/build/image-loader.d.ts +24 -0
- package/build/image-loader.js +175 -0
- package/build/image-source-resolver.d.ts +9 -0
- package/build/image-source-resolver.js +16 -0
- package/build/index.d.ts +10 -3
- package/build/index.js +11 -2
- package/build/keyboard/index.js +19 -34
- package/build/layout-animation/index.d.ts +1 -0
- package/build/layout-animation/index.js +53 -35
- package/build/native-modules.d.ts +12 -0
- package/build/native-modules.js +64 -1
- package/build/node.d.ts +1 -0
- package/build/node.js +9 -0
- package/build/pan-responder/index.js +1 -3
- package/build/permissions-android/index.js +1 -3
- package/build/platform-color.d.ts +3 -0
- package/build/platform-color.js +24 -0
- package/build/process-background-image/index.js +7 -14
- package/build/process-box-shadow/index.js +1 -5
- package/build/process-filter.js +2 -8
- package/build/registry.js +3 -5
- package/build/settings/index.js +1 -3
- package/build/share/index.ios.js +1 -1
- package/build/status-bar/index.android.js +1 -1
- package/build/style/index.js +2 -4
- package/build/style-registry/index.d.ts +1 -3
- package/build/style-registry/index.js +15 -48
- package/build/style-registry/scope.d.ts +3 -0
- package/build/style-registry/scope.js +41 -0
- package/build/touch-history.d.ts +23 -0
- package/build/touch-history.js +150 -0
- package/build/type-guards.d.ts +4 -0
- package/build/type-guards.js +15 -0
- package/package.json +1 -1
|
@@ -9,20 +9,17 @@
|
|
|
9
9
|
// these in the adapter, not in shared, like Keyboard / StatusBar).
|
|
10
10
|
import { getNativeModule } from '../native-modules.js';
|
|
11
11
|
import { dlog } from '../debug.js';
|
|
12
|
+
import { isRecord } from '../type-guards.js';
|
|
12
13
|
// ---- native module routing ----------------------------------------------
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
const NATIVE_UI_MANAGER_NAME = {
|
|
23
|
-
primary: 'UIManager',
|
|
24
|
-
fallback: 'FabricUIManager',
|
|
25
|
-
};
|
|
14
|
+
// RN's actual dual-path (LayoutAnimation.js): the Fabric global slot
|
|
15
|
+
// (`global.nativeFabricUIManager`, a JSI slot read directly, not a TurboModule
|
|
16
|
+
// lookup) is tried FIRST; only when it lacks `configureNextLayoutAnimation` does
|
|
17
|
+
// RN fall back to the TurboModule `UIManager.configureNextLayoutAnimation`
|
|
18
|
+
// (`TurboModuleRegistry.getEnforcing('UIManager')` in NativeUIManager.js; iOS's
|
|
19
|
+
// RCTUIManager.mm registers under the same bare "UIManager" name via
|
|
20
|
+
// RCT_EXPORT_MODULE()). There is only ONE correct TurboModule name; RN never
|
|
21
|
+
// registers a module under "FabricUIManager".
|
|
22
|
+
const NATIVE_UI_MANAGER_MODULE_NAME = 'UIManager';
|
|
26
23
|
// ---- public type surface (ported from RN's ReactNativeTypes) -------------
|
|
27
24
|
const ANIMATION_TYPE = {
|
|
28
25
|
spring: 'spring',
|
|
@@ -38,28 +35,39 @@ const ANIMATION_PROPERTY = {
|
|
|
38
35
|
scaleY: 'scaleY',
|
|
39
36
|
scaleXY: 'scaleXY',
|
|
40
37
|
};
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// in
|
|
46
|
-
//
|
|
38
|
+
// Narrows the Fabric global slot down to whether it ALSO carries the
|
|
39
|
+
// layout-animation hook. `fabric.ts`'s `IFabricSlot` deliberately omits this
|
|
40
|
+
// method (out of scope for the engine's own mutation API), so the slot's static
|
|
41
|
+
// type never has it either; this is the runtime feature-detect RN itself does
|
|
42
|
+
// (`FabricUIManager?.configureNextLayoutAnimation` in LayoutAnimation.js) before
|
|
43
|
+
// calling it, no `as` cast needed.
|
|
44
|
+
function hasConfigureNextLayoutAnimation(value) {
|
|
45
|
+
return isRecord(value) && typeof value.configureNextLayoutAnimation === 'function';
|
|
46
|
+
}
|
|
47
|
+
// Resolved FRESH on every configureNext, deliberately NOT memoized. Either mechanism can
|
|
48
|
+
// come and go at runtime (the Fabric global installs during bootstrap; a TurboModule can be
|
|
49
|
+
// absent at one moment and linked later), and a cached answer would pin the first result;
|
|
50
|
+
// both lookups are cheap, so per-call resolution costs nothing and stays correct.
|
|
51
|
+
// (Memoizing here also broke the headless smoke, which flips a fake module on/off in one
|
|
52
|
+
// process: a cached module survived the flip-off, so configureNext kept calling native when
|
|
53
|
+
// the module was meant to be absent.)
|
|
47
54
|
function resolveUIManager() {
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (module !== null) {
|
|
55
|
-
// DEVICE-VERIFY-PENDING seam: this line on the simulator/device is the only
|
|
56
|
-
// proof the chosen name is the real one. Keep it permanently (P0 logging gate).
|
|
57
|
-
dlog(`LayoutAnimation: resolved native UIManager via "${name}"`);
|
|
58
|
-
return module;
|
|
59
|
-
}
|
|
55
|
+
// Mechanism 1: the Fabric global slot, read directly: a JSI global, not a
|
|
56
|
+
// TurboModule lookup.
|
|
57
|
+
const fabricUIManager = globalThis.nativeFabricUIManager;
|
|
58
|
+
if (hasConfigureNextLayoutAnimation(fabricUIManager)) {
|
|
59
|
+
dlog('LayoutAnimation: resolved native UIManager via the Fabric global slot');
|
|
60
|
+
return fabricUIManager;
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
// Mechanism 2: the TurboModule fallback (RN's non-Fabric path).
|
|
63
|
+
const module = getNativeModule(NATIVE_UI_MANAGER_MODULE_NAME);
|
|
64
|
+
if (module !== null) {
|
|
65
|
+
dlog(`LayoutAnimation: resolved native UIManager via "${NATIVE_UI_MANAGER_MODULE_NAME}"`);
|
|
66
|
+
return module;
|
|
67
|
+
}
|
|
68
|
+
dlog('LayoutAnimation: no native UIManager resolved (no Fabric global slot, ' +
|
|
69
|
+
`"${NATIVE_UI_MANAGER_MODULE_NAME}" module not linked); configureNext is a no-op ` +
|
|
70
|
+
'(headless or module not linked)');
|
|
63
71
|
return null;
|
|
64
72
|
}
|
|
65
73
|
// ---- config builder ------------------------------------------------------
|
|
@@ -106,7 +114,7 @@ function setLayoutAnimationEnabled(value) {
|
|
|
106
114
|
// ---- configureNext -------------------------------------------------------
|
|
107
115
|
// Configures the next commit to be animated. NATIVE drives completion:
|
|
108
116
|
// `onAnimationDidEnd` is passed straight through as the native success callback,
|
|
109
|
-
// so it fires exactly when the native animation
|
|
117
|
+
// so it fires exactly when the native animation finishes, including
|
|
110
118
|
// when native extends it past `duration` (spring overshoot, OS slowdown,
|
|
111
119
|
// reduce-motion). `onAnimationDidFail` fires only if native config parsing fails.
|
|
112
120
|
// When no native module is linked (headless), this is a logged no-op; an app
|
|
@@ -140,7 +148,7 @@ function configureNext(config, onAnimationDidEnd, onAnimationDidFail) {
|
|
|
140
148
|
onAnimationDidEnd?.();
|
|
141
149
|
};
|
|
142
150
|
dlog(`LayoutAnimation.configureNext: dispatching config (duration=${config.duration})`);
|
|
143
|
-
//
|
|
151
|
+
// Default to a no-op when the caller doesn't supply one.
|
|
144
152
|
manager.configureNextLayoutAnimation(config, onComplete, onAnimationDidFail ?? (() => { }));
|
|
145
153
|
}
|
|
146
154
|
// ---- the LayoutAnimation facade ------------------------------------------
|
|
@@ -178,5 +186,15 @@ class LayoutAnimationImpl {
|
|
|
178
186
|
checkConfig(..._args) {
|
|
179
187
|
dlog('LayoutAnimation.checkConfig(...) has been disabled.');
|
|
180
188
|
}
|
|
189
|
+
// Coerce an arbitrary string (e.g. a keyboard event's `easing` field) onto a known
|
|
190
|
+
// ILayoutAnimationType, falling back to 'keyboard' when it isn't a key of `Types`.
|
|
191
|
+
// Owned here, not by a caller: `Types` is this class's own frozen table, so by
|
|
192
|
+
// Information Expert the coercion belongs on the type it reads (Keyboard's
|
|
193
|
+
// scheduleLayoutAnimation is the first caller; RN's own
|
|
194
|
+
// `LayoutAnimation.Types[easing] || 'keyboard'` is the same rule).
|
|
195
|
+
coerceType(easing) {
|
|
196
|
+
const types = this.Types;
|
|
197
|
+
return types[easing] ?? ANIMATION_TYPE.keyboard;
|
|
198
|
+
}
|
|
181
199
|
}
|
|
182
200
|
export const LayoutAnimation = new LayoutAnimationImpl();
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
import { NativeEventEmitter } from './native-events';
|
|
1
2
|
declare global {
|
|
2
3
|
var __turboModuleProxy: (<T>(name: string) => T | null) | undefined;
|
|
3
4
|
var nativeModuleProxy: Record<string, unknown> | undefined;
|
|
4
5
|
}
|
|
5
6
|
export declare function getNativeModule<T>(name: string): T | null;
|
|
6
7
|
export declare function getEnforcingNativeModule<T>(name: string): T;
|
|
8
|
+
export interface IDeviceEventModuleConfig<TModule> {
|
|
9
|
+
moduleName: string;
|
|
10
|
+
moduleLogPrefix: string;
|
|
11
|
+
bindModuleToEmitter?: boolean;
|
|
12
|
+
onEmitterCreated?: (emitter: NativeEventEmitter, module: TModule | null) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface IDeviceEventModule<TModule> {
|
|
15
|
+
getModule(): TModule | null;
|
|
16
|
+
getEmitter(): NativeEventEmitter;
|
|
17
|
+
}
|
|
18
|
+
export declare function createDeviceEventModule<TModule>(config: IDeviceEventModuleConfig<TModule>): IDeviceEventModule<TModule>;
|
package/build/native-modules.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
// one more client of the same global, exactly as `getSlot` is for the view tree.
|
|
6
6
|
//
|
|
7
7
|
// This is first-party access, for symbiote's own modules (StatusBarManager,
|
|
8
|
-
// KeyboardObserver,
|
|
8
|
+
// KeyboardObserver, ...). Third-party RN packages import `TurboModuleRegistry` from
|
|
9
9
|
// `'react-native'` and read the same global themselves; they do not go through
|
|
10
10
|
// here.
|
|
11
11
|
import { dlog } from './debug.js';
|
|
12
|
+
import { installDeviceEventHub, NativeEventEmitter, } from './native-events.js';
|
|
13
|
+
import { isRecord } from './type-guards.js';
|
|
12
14
|
// The native module value crosses from an untyped HostObject into our types here;
|
|
13
15
|
// the caller vouches for its shape via T (the single trust-boundary narrowing, no
|
|
14
16
|
// per-call `as`). Native modules are always non-null objects.
|
|
@@ -55,3 +57,64 @@ export function getEnforcingNativeModule(name) {
|
|
|
55
57
|
}
|
|
56
58
|
return module;
|
|
57
59
|
}
|
|
60
|
+
// ---- device-event module factory ------------------------------------------
|
|
61
|
+
//
|
|
62
|
+
// AccessibilityInfo (iOS + Android), AppState, Appearance, BackHandler, Keyboard,
|
|
63
|
+
// and Dimensions each hand-rolled the identical plumbing: lazily resolve a native
|
|
64
|
+
// module, lazily build a NativeEventEmitter bound to it, install the device-event
|
|
65
|
+
// hub on first subscribe. `createLinking` (linking/shared.ts) proved this factors
|
|
66
|
+
// out safely for Linking's iOS/Android split; this is the general form for every
|
|
67
|
+
// other lazy-module + emitter pair in the runtime-module layer.
|
|
68
|
+
//
|
|
69
|
+
// Each caller's DEGRADE POLICY stays its own, via config - the factory owns only
|
|
70
|
+
// the plumbing:
|
|
71
|
+
// - `bindModuleToEmitter` (default true): whether the resolved module is wired
|
|
72
|
+
// into the emitter so its addListener/removeListeners observe-counters get
|
|
73
|
+
// pinged. Dimensions' DeviceInfo module has no observe-counters, so it opts out
|
|
74
|
+
// (matching its original `new NativeEventEmitter(undefined)`).
|
|
75
|
+
// - `onEmitterCreated`: runs exactly once, right after the emitter is built -
|
|
76
|
+
// the hook for a caller's own permanent self-subscription (AppState/Appearance/
|
|
77
|
+
// BackHandler/Keyboard each keep a cache fresh or dispatch a chain this way) or
|
|
78
|
+
// one-time hydration from the module's constants (AppState's initial state,
|
|
79
|
+
// Dimensions' initial metrics - the latter also relies on this hook running
|
|
80
|
+
// `addListener` BEFORE the constants read, preserving the original
|
|
81
|
+
// subscribe-before-resolve ordering that guards against missing an update).
|
|
82
|
+
// A structural check, not a cast: narrows an arbitrary resolved module down to
|
|
83
|
+
// IEventEmitterModule only when it actually carries both observe-counter methods.
|
|
84
|
+
// Needed because `TModule` is unconstrained (Dimensions' INativeDeviceInfo doesn't
|
|
85
|
+
// extend IEventEmitterModule at all) - NativeEventEmitter itself re-checks the same
|
|
86
|
+
// shape internally, so this exists to satisfy the type system, not to change
|
|
87
|
+
// behavior.
|
|
88
|
+
function hasEventEmitterShape(value) {
|
|
89
|
+
return (isRecord(value) &&
|
|
90
|
+
typeof value.addListener === 'function' &&
|
|
91
|
+
typeof value.removeListeners === 'function');
|
|
92
|
+
}
|
|
93
|
+
// Build one module's lazy-resolve + lazy-emitter pair. Each call owns its own
|
|
94
|
+
// cache, so two callers (or two platform builds loaded together in one smoke) stay
|
|
95
|
+
// independent - the same guarantee `createLinking` documents.
|
|
96
|
+
export function createDeviceEventModule(config) {
|
|
97
|
+
let module;
|
|
98
|
+
let emitter;
|
|
99
|
+
function getModule() {
|
|
100
|
+
if (module === undefined) {
|
|
101
|
+
module = getNativeModule(config.moduleName);
|
|
102
|
+
dlog(`${config.moduleLogPrefix} ${module ? 'resolved' : 'NOT resolved (null)'}`);
|
|
103
|
+
}
|
|
104
|
+
return module;
|
|
105
|
+
}
|
|
106
|
+
function getEmitter() {
|
|
107
|
+
if (emitter === undefined) {
|
|
108
|
+
// WHY lazy: install on first subscribe so the hub exists before native
|
|
109
|
+
// emits, without a hard bootstrap-order dependency. Idempotent.
|
|
110
|
+
installDeviceEventHub();
|
|
111
|
+
const resolved = getModule();
|
|
112
|
+
const bindModule = config.bindModuleToEmitter ?? true;
|
|
113
|
+
const boundModule = bindModule && resolved !== null && hasEventEmitterShape(resolved) ? resolved : undefined;
|
|
114
|
+
emitter = new NativeEventEmitter(boundModule);
|
|
115
|
+
config.onEmitterCreated?.(emitter, resolved);
|
|
116
|
+
}
|
|
117
|
+
return emitter;
|
|
118
|
+
}
|
|
119
|
+
return { getModule, getEmitter };
|
|
120
|
+
}
|
package/build/node.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface ISymbioteEvent {
|
|
|
10
10
|
stopPropagation: () => void;
|
|
11
11
|
}
|
|
12
12
|
export type IListener = (event: ISymbioteEvent) => unknown;
|
|
13
|
+
export declare function isSymbioteEvent(value: unknown): value is ISymbioteEvent;
|
|
13
14
|
export interface ISymbioteNode {
|
|
14
15
|
readonly [BRAND]: true;
|
|
15
16
|
readonly component: string;
|
package/build/node.js
CHANGED
|
@@ -15,6 +15,15 @@ const BRAND = Symbol('symbiote.node');
|
|
|
15
15
|
export const RAW_TEXT_COMPONENT = 'RCTRawText';
|
|
16
16
|
export const TEXT_COMPONENT = 'RCTText';
|
|
17
17
|
export const VIRTUAL_TEXT_COMPONENT = 'RCTVirtualText';
|
|
18
|
+
// Runtime guard narrowing `unknown` to ISymbioteEvent (no `as` cast). Lives with the interface
|
|
19
|
+
// it tests, so every adapter checking for a Symbiote event shares one guard instead of writing
|
|
20
|
+
// its own copy.
|
|
21
|
+
export function isSymbioteEvent(value) {
|
|
22
|
+
if (typeof value !== 'object' || value === null)
|
|
23
|
+
return false;
|
|
24
|
+
const nativeEvent = Reflect.get(value, 'nativeEvent');
|
|
25
|
+
return typeof nativeEvent === 'object' && nativeEvent !== null;
|
|
26
|
+
}
|
|
18
27
|
export function createElement(component, isText = false) {
|
|
19
28
|
return {
|
|
20
29
|
[BRAND]: true,
|
|
@@ -12,12 +12,10 @@
|
|
|
12
12
|
// changed ones on `changedTouches`), so the centroid/velocity math here reads
|
|
13
13
|
// those directly while keeping RN's accumulate-deltas-over-time behavior.
|
|
14
14
|
import { dlog } from '../debug.js';
|
|
15
|
+
import { isRecord } from '../type-guards.js';
|
|
15
16
|
const SINGLE_TOUCH_COUNT = 1;
|
|
16
17
|
// onShouldBlockNativeResponder defaults to true (RN: block native by default).
|
|
17
18
|
const DEFAULT_BLOCK_NATIVE_RESPONDER = true;
|
|
18
|
-
function isRecord(value) {
|
|
19
|
-
return typeof value === 'object' && value !== null;
|
|
20
|
-
}
|
|
21
19
|
function toFiniteNumber(value) {
|
|
22
20
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
23
21
|
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// requestMultiplePermissions(permissions): Promise<{[permission]: string}>
|
|
20
20
|
import { getNativeModule } from '../native-modules.js';
|
|
21
21
|
import { dlog } from '../debug.js';
|
|
22
|
+
import { isBoolean } from '../type-guards.js';
|
|
22
23
|
// The native module name RN registers this TurboModule under
|
|
23
24
|
// (`TurboModuleRegistry.get<Spec>('PermissionsAndroid')`). NOTE: a module name is
|
|
24
25
|
// only provable on a real host (a headless fake answers to any name); this name
|
|
@@ -79,9 +80,6 @@ export const PERMISSIONS = Object.freeze({
|
|
|
79
80
|
POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS',
|
|
80
81
|
NEARBY_WIFI_DEVICES: 'android.permission.NEARBY_WIFI_DEVICES',
|
|
81
82
|
});
|
|
82
|
-
function isBoolean(value) {
|
|
83
|
-
return typeof value === 'boolean';
|
|
84
|
-
}
|
|
85
83
|
// Narrow the native string result into a known RESULTS value. Any unrecognized
|
|
86
84
|
// string is passed through as a IPermissionStatus only when it matches; otherwise
|
|
87
85
|
// we fall back to DENIED: a runtime guard at the trust boundary, not an `as`.
|
|
@@ -17,3 +17,6 @@ export type IColorValue = string | IOpaqueColorValue;
|
|
|
17
17
|
export declare function PlatformColor(...names: string[]): IOpaqueColorValue;
|
|
18
18
|
export declare function DynamicColorIOS(tuple: IDynamicColorIOSTuple): IOpaqueColorValue;
|
|
19
19
|
export declare function isOpaqueColorValue(value: unknown): value is IOpaqueColorValue;
|
|
20
|
+
export declare function setColorProcessor(process: (value: IColorValue) => unknown): void;
|
|
21
|
+
export declare function processColor(color: IColorValue): unknown;
|
|
22
|
+
export declare function isProcessableColor(value: unknown): value is IColorValue;
|
package/build/platform-color.js
CHANGED
|
@@ -23,3 +23,27 @@ export function DynamicColorIOS(tuple) {
|
|
|
23
23
|
export function isOpaqueColorValue(value) {
|
|
24
24
|
return typeof value === 'object' && value !== null && ('semantic' in value || 'dynamic' in value);
|
|
25
25
|
}
|
|
26
|
+
// Color props must reach Fabric as platform ints, not CSS strings. Fabric's C++ color
|
|
27
|
+
// parser silently drops strings. The actual conversion (processColor) is RN-platform-specific,
|
|
28
|
+
// so it is injected here rather than imported, keeping shared free of a react-native dependency
|
|
29
|
+
// (and the headless harness working). This module is the sole owner of "run a value through the
|
|
30
|
+
// platform color processor" - every color-touching consumer (commit's fabricProps,
|
|
31
|
+
// process-box-shadow, process-filter, process-background-image, StatusBar android) imports
|
|
32
|
+
// processColor/isProcessableColor from here, never from commit.ts.
|
|
33
|
+
let colorProcessor = value => value;
|
|
34
|
+
export function setColorProcessor(process) {
|
|
35
|
+
colorProcessor = process;
|
|
36
|
+
}
|
|
37
|
+
// Public mirror of RN's processColor: run a color through the injected platform processor (the
|
|
38
|
+
// canary wires RN's own). Off a real host it resolves CSS strings and opaque PlatformColor
|
|
39
|
+
// objects to the platform ints Fabric expects; headless (no processor wired) it is the identity,
|
|
40
|
+
// so smokes see the input unchanged.
|
|
41
|
+
export function processColor(color) {
|
|
42
|
+
return colorProcessor(color);
|
|
43
|
+
}
|
|
44
|
+
// A color-keyed value the platform processor must convert before Fabric: a CSS string, or an
|
|
45
|
+
// opaque PlatformColor / DynamicColorIOS object. Numbers (already platform ints) and undefined
|
|
46
|
+
// are left untouched.
|
|
47
|
+
export function isProcessableColor(value) {
|
|
48
|
+
return typeof value === 'string' || isOpaqueColorValue(value);
|
|
49
|
+
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
// JS-side port of RN's processBackgroundImage (Libraries/StyleSheet/processBackgroundImage.js).
|
|
2
2
|
// Same root cause as boxShadow/filter/transform: `experimental_backgroundImage` registers with
|
|
3
3
|
// enableNativeCSSParsing(), which DEFAULTS TO FALSE, so RN's stock path parses the CSS gradient
|
|
4
|
-
// string / structured array in JS and sends only the processed array to native
|
|
4
|
+
// string / structured array in JS and sends only the processed array to native - Fabric's C++
|
|
5
5
|
// never sees the raw string. This restores that missing JS parse.
|
|
6
|
-
|
|
7
|
-
// processColor is referenced from ../commit at RUNTIME only (inside function bodies), never at
|
|
8
|
-
// module-init, so the cyclic import (commit -> here -> commit) has no TDZ hazard — same pattern
|
|
9
|
-
// as process-box-shadow/process-filter.
|
|
10
|
-
import { processColor } from '../commit.js';
|
|
11
|
-
import { isOpaqueColorValue } from '../platform-color.js';
|
|
6
|
+
import { isOpaqueColorValue, processColor } from '../platform-color.js';
|
|
12
7
|
import { dlog } from '../debug.js';
|
|
8
|
+
import { isRecord } from '../type-guards.js';
|
|
13
9
|
// RN processBackgroundImage.js: pre-compiled patterns.
|
|
14
10
|
const NEWLINE_REGEX = /\n/g;
|
|
15
11
|
const GRADIENT_REGEX = /^(linear|radial)-gradient\(((?:\([^)]*\)|[^()])*)\)/;
|
|
@@ -23,9 +19,6 @@ const LINEAR_GRADIENT_DEFAULT_DIRECTION = { type: 'angle', value: 180 };
|
|
|
23
19
|
const DEFAULT_RADIAL_SHAPE = 'ellipse';
|
|
24
20
|
const DEFAULT_RADIAL_SIZE = 'farthest-corner';
|
|
25
21
|
const DEFAULT_RADIAL_POSITION = { top: '50%', left: '50%' };
|
|
26
|
-
function isRecord(value) {
|
|
27
|
-
return typeof value === 'object' && value !== null;
|
|
28
|
-
}
|
|
29
22
|
function isStringOrNumber(value) {
|
|
30
23
|
return typeof value === 'string' || typeof value === 'number';
|
|
31
24
|
}
|
|
@@ -221,7 +214,7 @@ function processBackgroundImageArray(rawList) {
|
|
|
221
214
|
//#region CSS string form
|
|
222
215
|
// Split a `background-image` value on its TOP-LEVEL commas only (multiple gradients, or a
|
|
223
216
|
// color function's internal commas like `rgba(0, 0, 0, .3)`, must not be confused for one
|
|
224
|
-
// another)
|
|
217
|
+
// another) - depth-tracking instead of a lookahead regex because a gradient's own arg list can
|
|
225
218
|
// itself contain nested parens (`linear-gradient(to right, rgba(0,0,0,.5), blue)`).
|
|
226
219
|
function splitGradients(input) {
|
|
227
220
|
const result = [];
|
|
@@ -320,7 +313,7 @@ function parseLinearGradientCSSString(gradientContent) {
|
|
|
320
313
|
}
|
|
321
314
|
// The `at <position>` clause of `radial-gradient(... at <position>, <color-stops>)`. Drains
|
|
322
315
|
// `tokens` (the SAME queue the caller is walking) via `shift()`, mirroring RN's in-place mutation
|
|
323
|
-
// of `firstPartTokens`
|
|
316
|
+
// of `firstPartTokens` - not a copy, so the caller sees it emptied after this returns.
|
|
324
317
|
function parseRadialGradientPositionTokens(tokens) {
|
|
325
318
|
if (tokens.length === 0)
|
|
326
319
|
return null;
|
|
@@ -511,7 +504,7 @@ function parseRadialGradientCSSString(gradientContent) {
|
|
|
511
504
|
position = parsedPosition;
|
|
512
505
|
break;
|
|
513
506
|
}
|
|
514
|
-
// No shape/size/position token found in this iteration
|
|
507
|
+
// No shape/size/position token found in this iteration - the rest of the first part is a
|
|
515
508
|
// color stop, not gradient config.
|
|
516
509
|
if (!hasShapeSizeOrPositionString)
|
|
517
510
|
break;
|
|
@@ -545,7 +538,7 @@ function parseBackgroundImageCSSString(cssString) {
|
|
|
545
538
|
return gradients;
|
|
546
539
|
}
|
|
547
540
|
//#endregion CSS string form
|
|
548
|
-
// RN processBackgroundImage.js's default export. Returns [] on any invalid gradient
|
|
541
|
+
// RN processBackgroundImage.js's default export. Returns [] on any invalid gradient - web
|
|
549
542
|
// semantics: an invalid `background-image` paints none of it rather than a partial gradient.
|
|
550
543
|
export function processBackgroundImage(backgroundImage) {
|
|
551
544
|
if (backgroundImage == null)
|
|
@@ -4,11 +4,7 @@
|
|
|
4
4
|
// processed array to native; Fabric's C++ never sees the raw string. symbiote was
|
|
5
5
|
// forwarding the raw value, which native (CSS parsing off) silently ignores: shadow
|
|
6
6
|
// rendered nothing. This restores the missing JS parse + per-color processColor.
|
|
7
|
-
|
|
8
|
-
// processColor is referenced from ./commit at RUNTIME only (inside the function bodies),
|
|
9
|
-
// never at module-init, so the cyclic import (commit -> here -> commit) has no TDZ hazard.
|
|
10
|
-
import { processColor } from '../commit.js';
|
|
11
|
-
import { isOpaqueColorValue } from '../platform-color.js';
|
|
7
|
+
import { isOpaqueColorValue, processColor } from '../platform-color.js';
|
|
12
8
|
import { dlog } from '../debug.js';
|
|
13
9
|
// RN processBoxShadow.js:16-19: split args only on the delimiters that are NOT inside
|
|
14
10
|
// a parenthesized color like rgba(0,0,0,1).
|
package/build/process-filter.js
CHANGED
|
@@ -4,12 +4,9 @@
|
|
|
4
4
|
// structured result. symbiote forwarded the raw value; the array form already worked
|
|
5
5
|
// (Fabric accepts it raw, `filter:[{brightness:0.5}]` was device-verified), but the
|
|
6
6
|
// CSS-string form and drop-shadow color processing were missing. This restores them.
|
|
7
|
-
|
|
8
|
-
// processColor is referenced from ./commit at RUNTIME only (inside function bodies),
|
|
9
|
-
// never at module-init, so the cyclic import (commit -> here -> commit) has no TDZ hazard.
|
|
10
|
-
import { processColor } from './commit.js';
|
|
11
|
-
import { isOpaqueColorValue } from './platform-color.js';
|
|
7
|
+
import { isOpaqueColorValue, processColor } from './platform-color.js';
|
|
12
8
|
import { dlog } from './debug.js';
|
|
9
|
+
import { isRecord } from './type-guards.js';
|
|
13
10
|
// RN processFilter.js:19-24: pre-compiled patterns.
|
|
14
11
|
const NEWLINE_REGEX = /\n/g;
|
|
15
12
|
const FILTER_FUNCTION_REGEX = /([\w-]+)\(([^()]*|\([^()]*\)|[^()]*\([^()]*\)[^()]*)\)/g;
|
|
@@ -17,9 +14,6 @@ const ARGS_WITH_UNITS_REGEX = /([+-]?\d*(\.\d+)?)([a-zA-Z%]+)?/g;
|
|
|
17
14
|
const WHITESPACE_SPLIT_REGEX = /\s+(?![^(]*\))/;
|
|
18
15
|
const LENGTH_PARSE_REGEX = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
|
|
19
16
|
const RADIANS_TO_DEGREES = 180 / Math.PI;
|
|
20
|
-
function isRecord(value) {
|
|
21
|
-
return typeof value === 'object' && value !== null;
|
|
22
|
-
}
|
|
23
17
|
// A length field may be a CSS string or a number; resolve to a number or null.
|
|
24
18
|
function resolveLength(value) {
|
|
25
19
|
if (typeof value === 'string')
|
package/build/registry.js
CHANGED
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
// shared must stay react-native-free (the headless harness runs in plain Node), so
|
|
12
12
|
// the ViewConfig lookup is INJECTED, exactly like the color processor: the adapter
|
|
13
13
|
// wires `setNativeViewConfigSource(ReactNativeViewConfigRegistry.get)` on a real
|
|
14
|
-
// host
|
|
14
|
+
// host, where that one source covers BOTH RN core and every library.
|
|
15
15
|
//
|
|
16
16
|
// The ONLY explicit list is OUR OWN built-in primitives (BUILTIN_COMPONENTS): a
|
|
17
17
|
// finite set we own, which keep their hand-tuned tables (view-config events, commit
|
|
18
18
|
// COLOR_PROPS) and are never read from the source, so they can't drift. Everything
|
|
19
19
|
// NOT in that set derives. The list never grows with the community; it grows only
|
|
20
20
|
// when we add a core primitive of our own.
|
|
21
|
+
import { isRecord } from './type-guards.js';
|
|
21
22
|
const EMPTY = { listeners: new Set(), byRaw: new Map(), processors: new Map() };
|
|
22
23
|
// OUR own primitives: the finite set shared hand-tunes (view-config events,
|
|
23
24
|
// commit COLOR_PROPS). The source is never consulted for these, so they can't
|
|
@@ -59,9 +60,6 @@ export function registerComponent(name, registration = {}) {
|
|
|
59
60
|
list.push(registration);
|
|
60
61
|
resolvedCache.delete(name);
|
|
61
62
|
}
|
|
62
|
-
function isRecord(value) {
|
|
63
|
-
return typeof value === 'object' && value !== null;
|
|
64
|
-
}
|
|
65
63
|
// onChange -> change (mirrors node.ts listenerName; the split of the handler prop).
|
|
66
64
|
function splitListener(handlerProp) {
|
|
67
65
|
return handlerProp.charAt(2).toLowerCase() + handlerProp.slice(3);
|
|
@@ -92,7 +90,7 @@ function deriveFromConfig(config, into) {
|
|
|
92
90
|
const attribute = validAttributes[key];
|
|
93
91
|
if (isRecord(attribute)) {
|
|
94
92
|
const process = attribute.process;
|
|
95
|
-
// The codegen config already carries the right processor (processColor,
|
|
93
|
+
// The codegen config already carries the right processor (processColor, ...);
|
|
96
94
|
// wrap it so the typed Function becomes a PropProcessor without a cast.
|
|
97
95
|
if (typeof process === 'function')
|
|
98
96
|
into.processors.set(key, value => process(value));
|
package/build/settings/index.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { installDeviceEventHub, NativeEventEmitter, } from '../native-events.js';
|
|
9
9
|
import { getNativeModule } from '../native-modules.js';
|
|
10
10
|
import { dlog } from '../debug.js';
|
|
11
|
+
import { isRecord } from '../type-guards.js';
|
|
11
12
|
// The iOS native module name RN registers this under. NOTE: this is the name the
|
|
12
13
|
// iOS JS wrapper resolves via `TurboModuleRegistry.getEnforcing('SettingsManager')`.
|
|
13
14
|
// The spec filename is `INativeSettingsManager`. A module name is only provable on a
|
|
@@ -19,9 +20,6 @@ const SETTINGS_UPDATED_EVENT = 'settingsUpdated';
|
|
|
19
20
|
// watchIds are handed out as the registry index, so the first watcher is 0. A
|
|
20
21
|
// cleared slot keeps its index (the array never shrinks) so later ids stay stable.
|
|
21
22
|
const FIRST_WATCH_ID = 0;
|
|
22
|
-
function isRecord(value) {
|
|
23
|
-
return typeof value === 'object' && value !== null;
|
|
24
|
-
}
|
|
25
23
|
// Lazily resolved so importing this module has no native side effect. `null` when
|
|
26
24
|
// the module isn't linked (headless): the snapshot then starts empty and `set`
|
|
27
25
|
// updates only JS state + fires watchers.
|
package/build/share/index.ios.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// web/headless.
|
|
7
7
|
import { dlog } from '../debug.js';
|
|
8
8
|
import { getNativeModule } from '../native-modules.js';
|
|
9
|
+
import { ACTION_SHEET_MANAGER, } from '../action-sheet-ios/index.js';
|
|
9
10
|
import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION } from './shared.js';
|
|
10
|
-
const ACTION_SHEET_MANAGER = 'ActionSheetManager';
|
|
11
11
|
export const Share = {
|
|
12
12
|
...shareActions,
|
|
13
13
|
// Open the iOS share sheet for `content`. Resolves with the user's action
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// relayout survives and the bar updates without blanking (verified on device: show/hide +
|
|
12
12
|
// light/dark text). See render.ts's installStopSurfaceGlobal.
|
|
13
13
|
import { getNativeModule } from '../native-modules.js';
|
|
14
|
-
import { processColor } from '../commit.js';
|
|
15
14
|
import { dlog } from '../debug.js';
|
|
15
|
+
import { processColor } from '../platform-color.js';
|
|
16
16
|
import { STATUS_BAR_MANAGER } from './shared.js';
|
|
17
17
|
// processColor returns `unknown` (its result is platform-dependent); narrow to the
|
|
18
18
|
// number Fabric/native expects, like RN's invariant before setColor. A non-number
|
package/build/style/index.js
CHANGED
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
// POSITION only, never on a property VALUE. `transform: [{translateX: 5}]` is an
|
|
8
8
|
// array-valued prop and `shadowOffset: {width, height}` an object-valued prop; both
|
|
9
9
|
// are copied through untouched. Only the top-level style slot is flattened.
|
|
10
|
-
|
|
11
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
-
}
|
|
10
|
+
import { isRecord } from '../type-guards.js';
|
|
13
11
|
export function flattenStyle(style) {
|
|
14
12
|
if (Array.isArray(style)) {
|
|
15
13
|
const result = {};
|
|
@@ -22,7 +20,7 @@ export function flattenStyle(style) {
|
|
|
22
20
|
}
|
|
23
21
|
return result;
|
|
24
22
|
}
|
|
25
|
-
if (
|
|
23
|
+
if (isRecord(style)) {
|
|
26
24
|
// Shallow copy of own enumerable keys: values (arrays, nested objects) pass through.
|
|
27
25
|
return { ...style };
|
|
28
26
|
}
|
|
@@ -5,7 +5,5 @@ export declare function registerStyles(styles: Record<string, IResolvedStyle>):
|
|
|
5
5
|
export declare function clearGlobalStyles(): void;
|
|
6
6
|
export declare function isClassNameValue(value: unknown): value is IClassNameValue;
|
|
7
7
|
export declare function resolveClassName(className: IClassNameValue): IResolvedStyle;
|
|
8
|
-
export
|
|
9
|
-
export type IScopableClassValue = string | IClassToggleMap | Array<string | IClassToggleMap> | undefined | null;
|
|
10
|
-
export declare function scopeClassName(value: IScopableClassValue, localNames: ReadonlySet<string>, scopeId: string): IScopableClassValue;
|
|
8
|
+
export declare function kebabToCamel(value: string): string;
|
|
11
9
|
export {};
|