@symbiote-native/engine 0.5.0 → 1.0.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/README.md +39 -14
- package/android/CMakeLists.txt +51 -0
- package/android/build.gradle +90 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/SymbioteEngineJni.cpp +72 -0
- package/android/src/main/java/dev/symbiotenative/engine/SymbioteEngineModule.kt +43 -0
- package/android/src/main/java/dev/symbiotenative/engine/SymbioteEnginePackage.kt +35 -0
- package/build/accessibility-info/shared.js +1 -1
- package/build/accessibility-props.d.ts +1 -8
- package/build/accessibility-props.js +13 -16
- package/build/animated/animations/composition.d.ts +1 -1
- package/build/animated/animations/composition.js +18 -4
- package/build/animated/easing.d.ts +3 -2
- package/build/animated/easing.js +17 -88
- package/build/animated/event.js +6 -1
- package/build/animated/host-binding.d.ts +1 -1
- package/build/animated/host-binding.js +19 -4
- package/build/animated/index.d.ts +1 -1
- package/build/animated/mock.d.ts +1 -19
- package/build/animated/props.js +1 -1
- package/build/animated/rgba.js +16 -50
- package/build/events/index.js +88 -40
- package/build/fabric-props.d.ts +1 -1
- package/build/fabric-props.js +116 -184
- package/build/fabric.d.ts +9 -0
- package/build/fabric.js +32 -0
- package/build/host-access.d.ts +125 -0
- package/build/host-access.js +280 -0
- package/build/host-behavior.d.ts +84 -21
- package/build/host-behavior.js +196 -30
- package/build/image-source-write.d.ts +16 -0
- package/build/image-source-write.js +65 -0
- package/build/imperative.d.ts +49 -0
- package/build/imperative.js +258 -0
- package/build/index.d.ts +14 -7
- package/build/index.js +53 -10
- package/build/mutation-buffer.d.ts +222 -0
- package/build/mutation-buffer.js +491 -0
- package/build/native-engine.d.ts +182 -0
- package/build/native-engine.js +178 -0
- package/build/native-tree-host.d.ts +25 -0
- package/build/native-tree-host.js +66 -0
- package/build/node.d.ts +172 -57
- package/build/node.js +839 -383
- package/build/pan-responder/index.js +27 -52
- package/build/platform-color/index.d.ts +1 -1
- package/build/platform-color/index.js +11 -4
- package/build/process-background-image/index.js +30 -566
- package/build/process-background-longhands.d.ts +4 -0
- package/build/process-background-longhands.js +44 -0
- package/build/process-box-shadow/index.js +23 -187
- package/build/process-filter.js +27 -300
- package/build/process-transform/index.d.ts +1 -1
- package/build/process-transform/index.js +25 -107
- package/build/process-transform-origin/index.d.ts +1 -1
- package/build/process-transform-origin/index.js +29 -102
- package/build/registry.d.ts +36 -0
- package/build/registry.js +73 -0
- package/build/sound-manager/index.d.ts +3 -0
- package/build/sound-manager/index.js +36 -0
- package/build/structured-style.d.ts +10 -0
- package/build/structured-style.js +180 -0
- package/build/style-registry/index.d.ts +14 -0
- package/build/style-registry/index.js +60 -11
- package/build/surface.d.ts +31 -2
- package/build/surface.js +138 -56
- package/build/text-input-state.d.ts +1 -0
- package/build/text-input-state.js +17 -3
- package/build/tree-host.d.ts +307 -0
- package/build/tree-host.js +211 -0
- package/build/view-config.js +4 -4
- package/codegen-specs/NativeSymbioteEngine.ts +27 -0
- package/cpp/SymbioteDebug.cpp +51 -0
- package/cpp/SymbioteDebug.h +54 -0
- package/cpp/SymbioteEngineBindings.cpp +232 -0
- package/cpp/SymbioteEngineBindings.h +59 -0
- package/cpp/SymbioteFabricProps.cpp +2619 -0
- package/cpp/SymbioteFabricProps.h +223 -0
- package/cpp/SymbioteTree.cpp +2478 -0
- package/cpp/SymbioteTree.h +257 -0
- package/ios/SymbioteEngineModule.h +25 -0
- package/ios/SymbioteEngineModule.mm +44 -0
- package/package.json +31 -3
- package/react-native.config.cjs +23 -0
- package/symbiote-engine.podspec +42 -0
- package/build/animated/bezier.d.ts +0 -1
- package/build/animated/bezier.js +0 -102
- package/build/commit.d.ts +0 -49
- package/build/commit.js +0 -1058
- package/build/tags.d.ts +0 -2
- package/build/tags.js +0 -40
package/build/animated/event.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// and registers the key path with the stock native module, so the event drives
|
|
15
15
|
// the view with zero JS per event.
|
|
16
16
|
import { dlog } from '../debug.js';
|
|
17
|
-
import { getNativeTag, whenCommitted } from '../
|
|
17
|
+
import { getNativeTag, whenCommitted } from '../imperative.js';
|
|
18
18
|
import { isSymbioteNode } from '../node.js';
|
|
19
19
|
import { AnimatedNode, flushValue } from './graph.js';
|
|
20
20
|
import { isNativeAnimatedAvailable, nativeAnimated, } from './native/native-animated.js';
|
|
@@ -45,6 +45,11 @@ function collectMappedValues(mapping, path, out) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
+
// TODO(rn-parity, low priority): vendor's `AnimatedEvent.js` has `validateMapping`, a
|
|
49
|
+
// `__DEV__`-only check that throws when an event's actual shape doesn't match the argMapping
|
|
50
|
+
// structure. We have no equivalent — `extractAtPath` below silently no-ops on a mismatch instead.
|
|
51
|
+
// Judged non-actionable (misuse-detection guard, no runtime-visible effect for a correct mapping,
|
|
52
|
+
// same class as the `numColumns` invariant) — see the audit skill for the full reasoning.
|
|
48
53
|
// Pull the numeric field at `path` out of one event argument. Returns undefined
|
|
49
54
|
// when the path is absent or the leaf is not a number, so a malformed event is a
|
|
50
55
|
// no-op rather than a throw inside an event dispatch.
|
|
@@ -3,7 +3,7 @@ export declare function hasAnimatedBindings(): boolean;
|
|
|
3
3
|
/**
|
|
4
4
|
* Give a node a behavior-owned animated style layer, or drop it by passing `undefined`.
|
|
5
5
|
*
|
|
6
|
-
* The seam
|
|
6
|
+
* The seam `<touchable-opacity>` needs: RN runs its press fade from an `Animated.View` whose
|
|
7
7
|
* style is `[props.style, {opacity: anim}]` (TouchableOpacity.js:302), and a tag has no such
|
|
8
8
|
* wrapper. The layer is bound to the leaf, never folded into `node.props.style` — so the behavior
|
|
9
9
|
* can still read the AUTHOR's resting opacity back without seeing its own fade.
|
|
@@ -31,6 +31,7 @@ import { AnimatedStyle } from './style.js';
|
|
|
31
31
|
import { attachNativeEventHandler } from './event.js';
|
|
32
32
|
import { createAnimatedLeafLifecycle, } from './leaf-lifecycle.js';
|
|
33
33
|
import { setProp } from '../node.js';
|
|
34
|
+
import { propOf, subtreesOf } from '../host-access.js';
|
|
34
35
|
const bindings = new WeakMap();
|
|
35
36
|
// Nodes the commit sweep tore down, whether or not they carried a binding — the twin of
|
|
36
37
|
// host-behavior.ts's `tornDown`, and marked for every node for the same reason: the node a
|
|
@@ -124,7 +125,7 @@ function withBehaviorStyle(node, style) {
|
|
|
124
125
|
/**
|
|
125
126
|
* Give a node a behavior-owned animated style layer, or drop it by passing `undefined`.
|
|
126
127
|
*
|
|
127
|
-
* The seam
|
|
128
|
+
* The seam `<touchable-opacity>` needs: RN runs its press fade from an `Animated.View` whose
|
|
128
129
|
* style is `[props.style, {opacity: anim}]` (TouchableOpacity.js:302), and a tag has no such
|
|
129
130
|
* wrapper. The layer is bound to the leaf, never folded into `node.props.style` — so the behavior
|
|
130
131
|
* can still read the AUTHOR's resting opacity back without seeing its own fade.
|
|
@@ -138,7 +139,7 @@ export function setAnimatedBehaviorStyle(node, style) {
|
|
|
138
139
|
}
|
|
139
140
|
// Re-register against the style standing right now, so the pair the leaf holds is always
|
|
140
141
|
// (author, layer) whichever of the two moved last.
|
|
141
|
-
bindAnimatedValue(node, 'style', node
|
|
142
|
+
bindAnimatedValue(node, 'style', propOf(node, 'style'));
|
|
142
143
|
}
|
|
143
144
|
/**
|
|
144
145
|
* Resolve an animated value in a prop, returning what should be published for it.
|
|
@@ -251,6 +252,22 @@ export function detachAnimatedProps(node) {
|
|
|
251
252
|
* runs ~9 000 times per benchmark create.
|
|
252
253
|
*/
|
|
253
254
|
export function reattachAnimatedProps(node) {
|
|
255
|
+
if (!parked.has(node))
|
|
256
|
+
return;
|
|
257
|
+
// FLAT, one host read for the whole subtree — the twin of `reattachSubtree` in `host-behavior.ts`,
|
|
258
|
+
// which was converted when the teardown sweep turned out to be paying a JSI crossing per node.
|
|
259
|
+
// This one was missed then. It is reached far less often (the guard above is a WeakSet miss for
|
|
260
|
+
// every node of a freshly built tree) but costs the same per node when it does run, and a parked
|
|
261
|
+
// subtree is a whole screen's worth in the case it exists for — Svelte parking live nodes
|
|
262
|
+
// offscreen across commits.
|
|
263
|
+
//
|
|
264
|
+
// The recursion stopped at an unparked node where this skips it and carries on; the two agree
|
|
265
|
+
// because `parked` is filled by `detachAnimatedProps` over a whole detached subtree, so a node in
|
|
266
|
+
// it has its descendants in it.
|
|
267
|
+
for (const each of subtreesOf([node]))
|
|
268
|
+
reattachOneAnimated(each);
|
|
269
|
+
}
|
|
270
|
+
function reattachOneAnimated(node) {
|
|
254
271
|
if (!parked.has(node))
|
|
255
272
|
return;
|
|
256
273
|
parked.delete(node);
|
|
@@ -258,6 +275,4 @@ export function reattachAnimatedProps(node) {
|
|
|
258
275
|
if (binding !== undefined)
|
|
259
276
|
reconcile(node, binding);
|
|
260
277
|
reattachAnimatedEvents(node);
|
|
261
|
-
for (const child of node.children)
|
|
262
|
-
reattachAnimatedProps(child);
|
|
263
278
|
}
|
|
@@ -4,7 +4,7 @@ export { AnimatedValueXY, type IValueXY } from './value-xy';
|
|
|
4
4
|
export { AnimatedAddition, AnimatedSubtraction, AnimatedMultiplication, AnimatedDivision, AnimatedModulo, AnimatedDiffClamp, add, subtract, multiply, divide, modulo, diffClamp, } from './operators';
|
|
5
5
|
export { AnimatedEvent, event, forkEvent, unforkEvent, attachNativeEvent, attachNativeEventHandler, type IEventConfig, type IEventListener, type IAnimatedEventHandler, type INativeEventAttachment, } from './event';
|
|
6
6
|
export { AnimatedColor, type IRgbaValue, type IColorInput } from './color';
|
|
7
|
-
export { Easing, type IEasingFunction } from './easing';
|
|
7
|
+
export { Easing, type IEasing, type IEasingFunction } from './easing';
|
|
8
8
|
export { createNumericInterpolation, createInterpolation, checkValidRanges, type IInterpolationConfig, type IExtrapolateType, } from './interpolation';
|
|
9
9
|
export type { IAnimation, IEndCallback, IEndResult } from './animation';
|
|
10
10
|
export { TimingAnimation } from './animations/timing';
|
package/build/animated/mock.d.ts
CHANGED
|
@@ -16,25 +16,7 @@ export declare const AnimatedMock: {
|
|
|
16
16
|
Value: typeof AnimatedValue;
|
|
17
17
|
ValueXY: typeof AnimatedValueXY;
|
|
18
18
|
Color: typeof AnimatedColor;
|
|
19
|
-
Easing:
|
|
20
|
-
step0(n: number): number;
|
|
21
|
-
step1(n: number): number;
|
|
22
|
-
linear(t: number): number;
|
|
23
|
-
ease(t: number): number;
|
|
24
|
-
quad(t: number): number;
|
|
25
|
-
cubic(t: number): number;
|
|
26
|
-
poly(n: number): import("./easing").IEasingFunction;
|
|
27
|
-
sin(t: number): number;
|
|
28
|
-
circle(t: number): number;
|
|
29
|
-
exp(t: number): number;
|
|
30
|
-
elastic(bounciness?: number): import("./easing").IEasingFunction;
|
|
31
|
-
back(s?: number): import("./easing").IEasingFunction;
|
|
32
|
-
bounce(t: number): number;
|
|
33
|
-
bezier(x1: number, y1: number, x2: number, y2: number): import("./easing").IEasingFunction;
|
|
34
|
-
in(easing: import("./easing").IEasingFunction): import("./easing").IEasingFunction;
|
|
35
|
-
out(easing: import("./easing").IEasingFunction): import("./easing").IEasingFunction;
|
|
36
|
-
inOut(easing: import("./easing").IEasingFunction): import("./easing").IEasingFunction;
|
|
37
|
-
};
|
|
19
|
+
Easing: import("./easing").IEasing;
|
|
38
20
|
timing: typeof timing;
|
|
39
21
|
spring: typeof spring;
|
|
40
22
|
decay: typeof decay;
|
package/build/animated/props.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// a `update` function off the node, and under useDefineForClassFields a field
|
|
13
13
|
// initialiser would shadow it and break subclassing. (See shared graph.ts leafUpdate.)
|
|
14
14
|
import { AnimatedNode, AnimatedWithChildren } from './graph.js';
|
|
15
|
-
import { setNativeProps, getNativeTag } from '../
|
|
15
|
+
import { setNativeProps, getNativeTag } from '../imperative.js';
|
|
16
16
|
import { isSymbioteNode } from '../node.js';
|
|
17
17
|
import { registerPostCommit } from '../post-commit.js';
|
|
18
18
|
import { nativeAnimated, } from './native/native-animated.js';
|
package/build/animated/rgba.js
CHANGED
|
@@ -8,45 +8,25 @@
|
|
|
8
8
|
// which surfaces at load time as `TypeError: Class extends value undefined is not a
|
|
9
9
|
// constructor` — AnimatedWithChildren is still uninitialized when color.ts evaluates. Keeping
|
|
10
10
|
// the pure half in its own leaf module breaks it structurally rather than by import ordering.
|
|
11
|
+
// No types are published for this package, and an ambient .d.ts is not picked up by another
|
|
12
|
+
// package's separate TS program (see symbiote-rn-import-testability) - so the suppression is
|
|
13
|
+
// local, at the import site.
|
|
14
|
+
// @ts-expect-error - untyped, @noflow plain JS that every toolchain here parses as-is.
|
|
15
|
+
import normalizeColorUpstream from '@react-native/normalize-colors';
|
|
11
16
|
export const DEFAULT_COLOR = { r: 0, g: 0, b: 0, a: 1 };
|
|
12
|
-
// Decompose
|
|
13
|
-
//
|
|
14
|
-
// the default rather than throwing inside a render. Exported so
|
|
15
|
-
// color path
|
|
17
|
+
// Decompose any color RN itself accepts into channels: #hex, rgb()/rgba(), hsl()/hsla(), hwb(),
|
|
18
|
+
// the 150 CSS names, `transparent`, and a 0xRRGGBBAA number. undefined when unparseable, so the
|
|
19
|
+
// caller falls back to the default rather than throwing inside a render. Exported so
|
|
20
|
+
// interpolation's color path decodes through this one decoder rather than duplicating it.
|
|
21
|
+
//
|
|
22
|
+
// The parsing is upstream's. A hand-rolled version lived here and knew hex and rgb() only, so
|
|
23
|
+
// `new AnimatedColor('red')` animated from BLACK - the fallback, not an error, and nothing was
|
|
24
|
+
// red anywhere. Its packed output is rrggbbaa, which is the order the shifts below assume.
|
|
16
25
|
export function normalizeColor(color) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
r: (c >>> 24) & 255,
|
|
21
|
-
g: (c >>> 16) & 255,
|
|
22
|
-
b: (c >>> 8) & 255,
|
|
23
|
-
a: (c & 255) / 255,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
const trimmed = color.trim();
|
|
27
|
-
if (trimmed.startsWith('#'))
|
|
28
|
-
return parseHex(trimmed);
|
|
29
|
-
if (/^rgba?\(/i.test(trimmed))
|
|
30
|
-
return parseRgb(trimmed);
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
function parseHex(hex) {
|
|
34
|
-
let body = hex.slice(1);
|
|
35
|
-
if (body.length === 3 || body.length === 4) {
|
|
36
|
-
body = body
|
|
37
|
-
.split('')
|
|
38
|
-
.map(c => c + c)
|
|
39
|
-
.join('');
|
|
40
|
-
}
|
|
41
|
-
if (body.length !== 6 && body.length !== 8)
|
|
26
|
+
const packed = normalizeColorUpstream(color);
|
|
27
|
+
if (typeof packed !== 'number')
|
|
42
28
|
return undefined;
|
|
43
|
-
const
|
|
44
|
-
if (Number.isNaN(int))
|
|
45
|
-
return undefined;
|
|
46
|
-
if (body.length === 6) {
|
|
47
|
-
return { r: (int >>> 16) & 255, g: (int >>> 8) & 255, b: int & 255, a: 1 };
|
|
48
|
-
}
|
|
49
|
-
const c = int >>> 0;
|
|
29
|
+
const c = packed >>> 0;
|
|
50
30
|
return {
|
|
51
31
|
r: (c >>> 24) & 255,
|
|
52
32
|
g: (c >>> 16) & 255,
|
|
@@ -54,17 +34,3 @@ function parseHex(hex) {
|
|
|
54
34
|
a: (c & 255) / 255,
|
|
55
35
|
};
|
|
56
36
|
}
|
|
57
|
-
function parseRgb(str) {
|
|
58
|
-
const match = /^rgba?\(([^)]+)\)$/i.exec(str);
|
|
59
|
-
if (match === null)
|
|
60
|
-
return undefined;
|
|
61
|
-
const parts = match[1].split(',').map(part => Number.parseFloat(part.trim()));
|
|
62
|
-
if (parts.length < 3 || parts.some(n => Number.isNaN(n)))
|
|
63
|
-
return undefined;
|
|
64
|
-
return {
|
|
65
|
-
r: parts[0],
|
|
66
|
-
g: parts[1],
|
|
67
|
-
b: parts[2],
|
|
68
|
-
a: parts.length >= 4 ? parts[3] : 1,
|
|
69
|
-
};
|
|
70
|
-
}
|
package/build/events/index.js
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
// node it started on (or a descendant). Bubbling events walk target -> root,
|
|
6
6
|
// invoking each ancestor's listener until one calls stopPropagation. Layout is a
|
|
7
7
|
// direct event in RN and is delivered only to its own target.
|
|
8
|
-
import { dlog } from '../debug.js';
|
|
8
|
+
import { dlog, isDebug } from '../debug.js';
|
|
9
9
|
import { runWrapped } from '../dispatch.js';
|
|
10
10
|
import { getSlot } from '../fabric.js';
|
|
11
|
-
import {
|
|
11
|
+
import { isAnchor, isSymbioteNode, } from '../node.js';
|
|
12
12
|
import { registeredNativeEvent } from '../registry.js';
|
|
13
|
+
import { ancestorsOf, parentOf } from '../host-access.js';
|
|
14
|
+
import { setIsJSResponder } from '../imperative.js';
|
|
13
15
|
import { attachTouchHistory, recordTouchTrack, resetTouchHistory, touchHistory, } from '../touch-history.js';
|
|
14
16
|
import { isRecord } from '../type-guards.js';
|
|
15
17
|
// Raw Fabric event -> listener name, split by dispatch phase. Press is synthesized
|
|
@@ -168,7 +170,7 @@ function hasRemainingTouchWithin(owner, nativeEvent) {
|
|
|
168
170
|
// Whether any node from `target` up to the root listens for `listenerName`, used to
|
|
169
171
|
// arm the long-press timer only when a handler would actually receive it.
|
|
170
172
|
function hasListenerInPath(target, listenerName) {
|
|
171
|
-
for (let node = target; node; node = node
|
|
173
|
+
for (let node = target; node; node = parentOf(node)) {
|
|
172
174
|
if (node.listeners?.has(listenerName) === true)
|
|
173
175
|
return true;
|
|
174
176
|
}
|
|
@@ -190,17 +192,20 @@ function callOwnListener(node, listenerName, nativeEvent) {
|
|
|
190
192
|
}
|
|
191
193
|
// The node chain from `from` up to the root, deepest first. The single allocation
|
|
192
194
|
// the two-phase walk indexes both ways (capture reads it reversed).
|
|
195
|
+
//
|
|
196
|
+
// ONE CROSSING, not one per level. This used to climb `parentOf`, which crosses the host boundary
|
|
197
|
+
// each step — 18 questions per event on a depth-8 chain, and again on every frame of every drag for
|
|
198
|
+
// the responder's scope. The host holds the tree, so the chain is one answer it can give: the same
|
|
199
|
+
// argument `parentsOf` / `subtreesOf` won for the teardown sweep (F-2) and `nextSiblingOf` for Vue's
|
|
200
|
+
// quadratic (F-19).
|
|
193
201
|
function pathToRoot(from) {
|
|
194
|
-
|
|
195
|
-
for (let node = from; node; node = node.parent)
|
|
196
|
-
path.push(node);
|
|
197
|
-
return path;
|
|
202
|
+
return [...ancestorsOf(from)];
|
|
198
203
|
}
|
|
199
204
|
// Depth of a node below the root (root = 0). Aligns two nodes before the lockstep
|
|
200
205
|
// climb to their lowest common ancestor.
|
|
201
206
|
function depthOf(node) {
|
|
202
207
|
let depth = 0;
|
|
203
|
-
for (let n = node
|
|
208
|
+
for (let n = parentOf(node); n; n = parentOf(n))
|
|
204
209
|
depth++;
|
|
205
210
|
return depth;
|
|
206
211
|
}
|
|
@@ -213,18 +218,18 @@ function lowestCommonAncestor(a, b) {
|
|
|
213
218
|
let na = a;
|
|
214
219
|
let nb = b;
|
|
215
220
|
while (na && da > db) {
|
|
216
|
-
na = na
|
|
221
|
+
na = parentOf(na);
|
|
217
222
|
da--;
|
|
218
223
|
}
|
|
219
224
|
while (nb && db > da) {
|
|
220
|
-
nb = nb
|
|
225
|
+
nb = parentOf(nb);
|
|
221
226
|
db--;
|
|
222
227
|
}
|
|
223
228
|
while (na && nb) {
|
|
224
229
|
if (na === nb)
|
|
225
230
|
return na;
|
|
226
|
-
na = na
|
|
227
|
-
nb = nb
|
|
231
|
+
na = parentOf(na);
|
|
232
|
+
nb = parentOf(nb);
|
|
228
233
|
}
|
|
229
234
|
return undefined;
|
|
230
235
|
}
|
|
@@ -261,15 +266,14 @@ function findWantsResponder(path, captureName, bubbleName, nativeEvent, skip) {
|
|
|
261
266
|
//
|
|
262
267
|
// `blockNativeResponder` is the taker's own `onResponderGrant` return, exactly as RN reads it.
|
|
263
268
|
function handOverNativeResponder(from, to, blockNativeResponder) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
slot.setIsJSResponder(toHandle, true, blockNativeResponder);
|
|
269
|
+
if (isDebug()) {
|
|
270
|
+
dlog(`setIsJSResponder from=${from === undefined ? 'none' : 'yes'} ` +
|
|
271
|
+
`to=${to === undefined ? 'none' : 'yes'} block=${blockNativeResponder}`);
|
|
272
|
+
}
|
|
273
|
+
if (from !== undefined)
|
|
274
|
+
setIsJSResponder(from, false, blockNativeResponder);
|
|
275
|
+
if (to !== undefined)
|
|
276
|
+
setIsJSResponder(to, true, blockNativeResponder);
|
|
273
277
|
}
|
|
274
278
|
// Whether the taker asked native to stand down. RN reads this off the grant dispatch's return;
|
|
275
279
|
// an absent listener means no claim, which is RN's `blockNativeResponder || false`.
|
|
@@ -286,12 +290,33 @@ function negotiateResponder(target, phase, nativeEvent) {
|
|
|
286
290
|
// the responder) and skips the deepest node when it IS the responder (Responder-
|
|
287
291
|
// EventPlugin.setResponderAndExtractTransfer). At touch start currentResponder is
|
|
288
292
|
// cleared, so this collapses to the plain target->root start walk.
|
|
293
|
+
//
|
|
294
|
+
// ONE WALK, and the common case never needs a second. `pathToRoot` crosses the host boundary per
|
|
295
|
+
// level, and the previous shape paid `lowestCommonAncestor` — `depthOf(a)` plus `depthOf(b)` plus
|
|
296
|
+
// a lockstep climb — BEFORE walking again from the answer. Measured on a depth-8 chain: 19
|
|
297
|
+
// crossings per move, of which 18 were the ancestor search, on a path that runs 60 times a second
|
|
298
|
+
// for the whole of a drag (`__tests__/touch-crossing-cost.test.ts`).
|
|
299
|
+
//
|
|
300
|
+
// The scope RN computes is the lowest common ancestor of responder and target, and while a finger
|
|
301
|
+
// stays inside the view that claimed it — which is what a drag IS — the responder is an ANCESTOR
|
|
302
|
+
// of the target, so the LCA is the responder itself and it is already in the target's own path.
|
|
303
|
+
// An index lookup answers it with nothing crossed, and the scoped path is that path's suffix.
|
|
304
|
+
// The genuine cross-subtree case (the finger left the responder's subtree) still falls back to
|
|
305
|
+
// the full search, and it is a rare frame rather than every frame.
|
|
306
|
+
const targetPath = pathToRoot(target);
|
|
307
|
+
const heldAt = currentResponder === undefined ? -1 : targetPath.indexOf(currentResponder);
|
|
289
308
|
const from = currentResponder === undefined
|
|
290
309
|
? target
|
|
291
|
-
:
|
|
310
|
+
: heldAt >= 0
|
|
311
|
+
? currentResponder
|
|
312
|
+
: lowestCommonAncestor(currentResponder, target);
|
|
292
313
|
if (!from)
|
|
293
314
|
return;
|
|
294
|
-
const path =
|
|
315
|
+
const path = from === target
|
|
316
|
+
? targetPath
|
|
317
|
+
: heldAt >= 0
|
|
318
|
+
? targetPath.slice(heldAt)
|
|
319
|
+
: pathToRoot(from);
|
|
295
320
|
const skip = from === currentResponder ? from : undefined;
|
|
296
321
|
const wants = phase === 'start'
|
|
297
322
|
? findWantsResponder(path, START_SHOULD_SET_CAPTURE, START_SHOULD_SET, nativeEvent, skip)
|
|
@@ -299,16 +324,20 @@ function negotiateResponder(target, phase, nativeEvent) {
|
|
|
299
324
|
// Every exit is logged: a negotiation that declines is indistinguishable from one that never
|
|
300
325
|
// ran, and the two have opposite causes.
|
|
301
326
|
if (!wants) {
|
|
302
|
-
|
|
327
|
+
if (isDebug()) {
|
|
328
|
+
dlog(`responder ${phase}: nobody wants it (path=${path.length}${skip === undefined ? '' : ', one skipped'})`);
|
|
329
|
+
}
|
|
303
330
|
return;
|
|
304
331
|
}
|
|
305
332
|
if (wants === currentResponder) {
|
|
306
|
-
|
|
333
|
+
if (isDebug())
|
|
334
|
+
dlog(`responder ${phase}: ${wants.component} already holds it`);
|
|
307
335
|
return;
|
|
308
336
|
}
|
|
309
337
|
if (currentResponder === undefined) {
|
|
310
338
|
currentResponder = wants;
|
|
311
|
-
|
|
339
|
+
if (isDebug())
|
|
340
|
+
dlog(`responder granted to ${wants.component}`);
|
|
312
341
|
const granted = callOwnListener(wants, RESPONDER_GRANT, nativeEvent);
|
|
313
342
|
handOverNativeResponder(undefined, wants, blocksNative(granted));
|
|
314
343
|
return;
|
|
@@ -328,14 +357,16 @@ function negotiateResponder(target, phase, nativeEvent) {
|
|
|
328
357
|
// visible no-op event with no behavioral counterpart. We therefore fire grant
|
|
329
358
|
// before terminate on the consent path (matching RN's grant<terminate ordering) and
|
|
330
359
|
// omit it on reject; the consent OUTCOME is unchanged either way.
|
|
331
|
-
|
|
360
|
+
if (isDebug())
|
|
361
|
+
dlog(`responder transferred ${incumbent.component} -> ${wants.component}`);
|
|
332
362
|
const granted = callOwnListener(wants, RESPONDER_GRANT, nativeEvent);
|
|
333
363
|
callOwnListener(incumbent, RESPONDER_TERMINATE, nativeEvent);
|
|
334
364
|
currentResponder = wants;
|
|
335
365
|
handOverNativeResponder(incumbent, wants, blocksNative(granted));
|
|
336
366
|
}
|
|
337
367
|
else {
|
|
338
|
-
|
|
368
|
+
if (isDebug())
|
|
369
|
+
dlog(`responder takeover of ${incumbent.component} rejected`);
|
|
339
370
|
callOwnListener(wants, RESPONDER_REJECT, nativeEvent);
|
|
340
371
|
}
|
|
341
372
|
}
|
|
@@ -347,7 +378,10 @@ export function installEventHandler() {
|
|
|
347
378
|
if (!isSymbioteNode(instanceHandle))
|
|
348
379
|
return;
|
|
349
380
|
if (topLevelType === TOUCH_START) {
|
|
350
|
-
|
|
381
|
+
// The ternary is gone with the gate: `isSymbioteNode(instanceHandle)` was re-asked here
|
|
382
|
+
// three lines after the early return above already proved it.
|
|
383
|
+
if (isDebug())
|
|
384
|
+
dlog(`event ${TOUCH_START} on ${instanceHandle.component}`);
|
|
351
385
|
// Update the touch bank, then attach it so responder handlers (PanResponder)
|
|
352
386
|
// read each touch's own previous->current delta; RN records before dispatch.
|
|
353
387
|
recordTouchTrack('start', nativeEvent);
|
|
@@ -461,7 +495,8 @@ export function installEventHandler() {
|
|
|
461
495
|
bubble(press.owner, PRESS_OUT, nativeEvent);
|
|
462
496
|
}
|
|
463
497
|
if (!hadActivePress) {
|
|
464
|
-
|
|
498
|
+
if (isDebug())
|
|
499
|
+
dlog(`event ${TOUCH_END} ignored (no matching start)`);
|
|
465
500
|
}
|
|
466
501
|
else if (completedPresses.length === 0) {
|
|
467
502
|
dlog('press retained (another touch remains inside its owner)');
|
|
@@ -520,13 +555,17 @@ export function installEventHandler() {
|
|
|
520
555
|
}
|
|
521
556
|
const direct = DIRECT_EVENTS[topLevelType];
|
|
522
557
|
if (direct !== undefined) {
|
|
523
|
-
|
|
558
|
+
if (isDebug()) {
|
|
559
|
+
dlog(`event ${topLevelType} -> ${direct} (direct)`);
|
|
560
|
+
}
|
|
524
561
|
runWrapped(() => deliverDirect(instanceHandle, direct, nativeEvent));
|
|
525
562
|
return;
|
|
526
563
|
}
|
|
527
564
|
const bubbling = BUBBLING_EVENTS[topLevelType];
|
|
528
565
|
if (bubbling !== undefined) {
|
|
529
|
-
|
|
566
|
+
if (isDebug()) {
|
|
567
|
+
dlog(`event ${topLevelType} -> ${bubbling} (bubble)`);
|
|
568
|
+
}
|
|
530
569
|
runWrapped(() => bubble(instanceHandle, bubbling, nativeEvent));
|
|
531
570
|
return;
|
|
532
571
|
}
|
|
@@ -536,8 +575,10 @@ export function installEventHandler() {
|
|
|
536
575
|
// bubble: same split as the built-ins.
|
|
537
576
|
const registered = registeredNativeEvent(instanceHandle.component, topLevelType);
|
|
538
577
|
if (registered !== undefined) {
|
|
539
|
-
|
|
540
|
-
|
|
578
|
+
if (isDebug()) {
|
|
579
|
+
const phase = registered.direct ? 'direct' : 'bubble';
|
|
580
|
+
dlog(`event ${topLevelType} -> ${registered.listener} (${phase}, registered)`);
|
|
581
|
+
}
|
|
541
582
|
runWrapped(() => registered.direct
|
|
542
583
|
? deliverDirect(instanceHandle, registered.listener, nativeEvent)
|
|
543
584
|
: bubble(instanceHandle, registered.listener, nativeEvent));
|
|
@@ -547,7 +588,9 @@ export function installEventHandler() {
|
|
|
547
588
|
// config. A permanent diagnostic seam: if a native view fires something we drop
|
|
548
589
|
// on the floor (an event the ViewConfig didn't surface, or a name mismatch),
|
|
549
590
|
// this is where it shows up. Keeps "the handler silently did nothing" debuggable.
|
|
550
|
-
|
|
591
|
+
if (isDebug()) {
|
|
592
|
+
dlog(`event ${topLevelType} UNMATCHED on ${instanceHandle.component} (dropped)`);
|
|
593
|
+
}
|
|
551
594
|
});
|
|
552
595
|
}
|
|
553
596
|
// A press is honest only if the touch ends on the node it started on, or a
|
|
@@ -559,7 +602,7 @@ function endsWithin(endTarget, start) {
|
|
|
559
602
|
while (node) {
|
|
560
603
|
if (node === start)
|
|
561
604
|
return true;
|
|
562
|
-
node = node
|
|
605
|
+
node = parentOf(node);
|
|
563
606
|
}
|
|
564
607
|
return false;
|
|
565
608
|
}
|
|
@@ -591,7 +634,8 @@ function bubble(target, listenerName, nativeEvent) {
|
|
|
591
634
|
? undefined
|
|
592
635
|
: node.listeners?.get(captureName);
|
|
593
636
|
if (listener) {
|
|
594
|
-
|
|
637
|
+
if (isDebug())
|
|
638
|
+
dlog(`event ${listenerName} capture on ${node.component}`);
|
|
595
639
|
listener({
|
|
596
640
|
type: listenerName,
|
|
597
641
|
target,
|
|
@@ -605,8 +649,13 @@ function bubble(target, listenerName, nativeEvent) {
|
|
|
605
649
|
}
|
|
606
650
|
// Bubble phase: target -> root, invoking each ancestor's plain listener. Anchors are
|
|
607
651
|
// transparent here too, same reason (see the capture-phase comment above).
|
|
608
|
-
|
|
609
|
-
|
|
652
|
+
//
|
|
653
|
+
// THE SAME ARRAY, read forward. This used to re-walk `parentOf` from the target, which is a
|
|
654
|
+
// CROSSING of the host boundary per level — so one event asked the host for its ancestors twice,
|
|
655
|
+
// measured at 18 questions on a depth-8 chain where 9 is the floor. The comment above already
|
|
656
|
+
// says the path is built once "without a second allocation"; the second WALK is the expensive
|
|
657
|
+
// half, and it was the one left in. Every touch and every frame of every drag pays it.
|
|
658
|
+
for (const node of path) {
|
|
610
659
|
const listener = isAnchor(node)
|
|
611
660
|
? undefined
|
|
612
661
|
: node.listeners?.get(listenerName);
|
|
@@ -622,7 +671,6 @@ function bubble(target, listenerName, nativeEvent) {
|
|
|
622
671
|
if (stopped)
|
|
623
672
|
return;
|
|
624
673
|
}
|
|
625
|
-
node = node.parent;
|
|
626
674
|
}
|
|
627
675
|
}
|
|
628
676
|
// Direct (non-bubbling) delivery: only the target's own listener fires.
|
package/build/fabric-props.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { IFabricProps } from './fabric';
|
|
2
2
|
import { type ISymbioteNode } from './node';
|
|
3
|
-
export declare function fabricProps(node: ISymbioteNode): IFabricProps;
|
|
3
|
+
export declare function fabricProps(node: ISymbioteNode, nodeProps: Readonly<Record<string, unknown>>): IFabricProps;
|