@symbiote-native/engine 1.3.0 → 1.4.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.
Files changed (79) hide show
  1. package/build/accessibility-props.d.ts +0 -11
  2. package/build/accessibility-props.js +30 -68
  3. package/build/animated/graph.js +1 -1
  4. package/build/animated/leaf-lifecycle.js +2 -2
  5. package/build/asset-source-resolver.d.ts +2 -0
  6. package/build/asset-source-resolver.js +13 -0
  7. package/build/back-handler/index.d.ts +1 -5
  8. package/build/back-handler/index.js +0 -6
  9. package/build/debug.js +8 -22
  10. package/build/dispatch.js +3 -9
  11. package/build/events/delivery.d.ts +9 -0
  12. package/build/events/delivery.js +143 -0
  13. package/build/events/index.js +199 -660
  14. package/build/events/names.d.ts +24 -0
  15. package/build/events/names.js +80 -0
  16. package/build/events/press.d.ts +20 -0
  17. package/build/events/press.js +89 -0
  18. package/build/events/responder.d.ts +6 -0
  19. package/build/events/responder.js +124 -0
  20. package/build/fabric-props.js +74 -179
  21. package/build/fabric.d.ts +0 -13
  22. package/build/fabric.js +18 -38
  23. package/build/host-access.d.ts +1 -128
  24. package/build/host-access.js +96 -205
  25. package/build/host-behavior.d.ts +0 -100
  26. package/build/host-behavior.js +125 -311
  27. package/build/image-loader.js +10 -23
  28. package/build/image-source-resolver.js +3 -7
  29. package/build/image-source-write.d.ts +0 -11
  30. package/build/image-source-write.js +14 -34
  31. package/build/imperative.d.ts +2 -28
  32. package/build/imperative.js +60 -93
  33. package/build/index.d.ts +6 -2
  34. package/build/index.js +29 -39
  35. package/build/mutation-buffer.d.ts +3 -177
  36. package/build/mutation-buffer.js +162 -316
  37. package/build/native-engine.d.ts +6 -102
  38. package/build/native-engine.js +60 -141
  39. package/build/native-events.js +9 -18
  40. package/build/native-tree-host.d.ts +0 -21
  41. package/build/native-tree-host.js +15 -31
  42. package/build/node-events.d.ts +11 -0
  43. package/build/node-events.js +145 -0
  44. package/build/node-instance.d.ts +8 -0
  45. package/build/node-instance.js +168 -0
  46. package/build/node-props.d.ts +13 -0
  47. package/build/node-props.js +131 -0
  48. package/build/node-route.d.ts +2 -0
  49. package/build/node-route.js +151 -0
  50. package/build/node-style.d.ts +15 -0
  51. package/build/node-style.js +214 -0
  52. package/build/node-tree.d.ts +6 -0
  53. package/build/node-tree.js +159 -0
  54. package/build/node-types.d.ts +70 -0
  55. package/build/node-types.js +36 -0
  56. package/build/node.d.ts +7 -309
  57. package/build/node.js +9 -1564
  58. package/build/post-commit.js +3 -8
  59. package/build/process-aspect-ratio.js +3 -7
  60. package/build/process-background-longhands.js +10 -19
  61. package/build/process-filter.js +11 -19
  62. package/build/process-font-variant.js +3 -7
  63. package/build/registry.d.ts +0 -33
  64. package/build/registry.js +22 -57
  65. package/build/report-error.js +4 -18
  66. package/build/structured-style.d.ts +0 -9
  67. package/build/structured-style.js +16 -31
  68. package/build/styles.js +3 -6
  69. package/build/surface.d.ts +0 -26
  70. package/build/surface.js +29 -76
  71. package/build/text-input-state.js +4 -8
  72. package/build/touch-history.js +5 -11
  73. package/build/tree-host.d.ts +7 -270
  74. package/build/tree-host.js +63 -153
  75. package/build/view-config.js +17 -37
  76. package/cpp/SymbioteEngineBindings.cpp +19 -18
  77. package/cpp/SymbioteTree.cpp +81 -156
  78. package/cpp/SymbioteTree.h +6 -0
  79. package/package.json +2 -2
@@ -0,0 +1,24 @@
1
+ export declare const BUBBLING_EVENTS: Readonly<Record<string, string>>;
2
+ export declare const DIRECT_EVENTS: Readonly<Record<string, string>>;
3
+ export declare const TOUCH_START = "topTouchStart";
4
+ export declare const TOUCH_MOVE = "topTouchMove";
5
+ export declare const TOUCH_END = "topTouchEnd";
6
+ export declare const TOUCH_CANCEL = "topTouchCancel";
7
+ export declare const PRESS = "press";
8
+ export declare const RESPONDER_GRANT = "responderGrant";
9
+ export declare const RESPONDER_REJECT = "responderReject";
10
+ export declare const RESPONDER_START = "responderStart";
11
+ export declare const RESPONDER_MOVE = "responderMove";
12
+ export declare const RESPONDER_END = "responderEnd";
13
+ export declare const RESPONDER_RELEASE = "responderRelease";
14
+ export declare const RESPONDER_TERMINATE = "responderTerminate";
15
+ export declare const RESPONDER_TERMINATION_REQUEST = "responderTerminationRequest";
16
+ export declare const SHOULD_SET_NAMES: Readonly<Record<'start' | 'move', {
17
+ readonly capture: string;
18
+ readonly bubble: string;
19
+ }>>;
20
+ export declare const PRESS_IN = "pressIn";
21
+ export declare const PRESS_OUT = "pressOut";
22
+ export declare const LONG_PRESS = "longPress";
23
+ export declare const DEFAULT_LONG_PRESS_MS = 500;
24
+ export declare const LONG_PRESS_DEACTIVATION_DISTANCE = 10;
@@ -0,0 +1,80 @@
1
+ // Raw Fabric event name -> listener name, and the synthesized names that have no raw event at all.
2
+ // Kept apart from the handler so the file holding the strings is not the file read while debugging
3
+ // a gesture
4
+ // Bubbling events walk target -> root, direct ones fire on the target alone. `press` is synthesized
5
+ // from a touch sequence and `layout` is direct, so neither is in these tables
6
+ export const BUBBLING_EVENTS = {
7
+ topFocus: 'focus',
8
+ topBlur: 'blur',
9
+ topChange: 'change',
10
+ topEndEditing: 'endEditing',
11
+ topSubmitEditing: 'submitEditing',
12
+ topKeyPress: 'keyPress',
13
+ };
14
+ export const DIRECT_EVENTS = {
15
+ topLayout: 'layout',
16
+ topScroll: 'scroll',
17
+ topScrollBeginDrag: 'scrollBeginDrag',
18
+ topScrollEndDrag: 'scrollEndDrag',
19
+ topMomentumScrollBegin: 'momentumScrollBegin',
20
+ topMomentumScrollEnd: 'momentumScrollEnd',
21
+ topSelectionChange: 'selectionChange',
22
+ topContentSizeChange: 'contentSizeChange',
23
+ topLoadStart: 'loadStart',
24
+ topLoad: 'load',
25
+ topLoadEnd: 'loadEnd',
26
+ topError: 'error',
27
+ topProgress: 'progress',
28
+ topPartialLoad: 'partialLoad',
29
+ topRefresh: 'refresh',
30
+ topShow: 'show',
31
+ topRequestClose: 'requestClose',
32
+ topDismiss: 'dismiss',
33
+ topOrientationChange: 'orientationChange',
34
+ topTextLayout: 'textLayout',
35
+ topScrollToTop: 'scrollToTop',
36
+ // From RN's base ViewConfig, so any view may emit them. `accessibilityAction` fires on both
37
+ // platforms; the other three have no Android producer and are inert there
38
+ topAccessibilityAction: 'accessibilityAction',
39
+ topAccessibilityTap: 'accessibilityTap',
40
+ topMagicTap: 'magicTap',
41
+ topAccessibilityEscape: 'accessibilityEscape',
42
+ };
43
+ export const TOUCH_START = 'topTouchStart';
44
+ export const TOUCH_MOVE = 'topTouchMove';
45
+ export const TOUCH_END = 'topTouchEnd';
46
+ export const TOUCH_CANCEL = 'topTouchCancel';
47
+ export const PRESS = 'press';
48
+ // The responder protocol (PanResponder / Touchable), post-`on` names: `onResponderMove` is
49
+ // `responderMove` here. RN's negotiation and its two-phase walk live in `./responder`
50
+ export const RESPONDER_GRANT = 'responderGrant';
51
+ export const RESPONDER_REJECT = 'responderReject';
52
+ export const RESPONDER_START = 'responderStart';
53
+ export const RESPONDER_MOVE = 'responderMove';
54
+ export const RESPONDER_END = 'responderEnd';
55
+ export const RESPONDER_RELEASE = 'responderRelease';
56
+ export const RESPONDER_TERMINATE = 'responderTerminate';
57
+ export const RESPONDER_TERMINATION_REQUEST = 'responderTerminationRequest';
58
+ // The should-set pair asked per phase, as one table т.к. the only thing separating a start
59
+ // negotiation from a move one is which two names get asked
60
+ export const SHOULD_SET_NAMES = {
61
+ start: {
62
+ capture: 'startShouldSetResponderCapture',
63
+ bubble: 'startShouldSetResponder',
64
+ },
65
+ move: {
66
+ capture: 'moveShouldSetResponderCapture',
67
+ bubble: 'moveShouldSetResponder',
68
+ },
69
+ };
70
+ // Synthesized alongside `press` so a Pressable can paint its pressed state. Both fire on the node
71
+ // the touch STARTED on, `pressOut` on end and on cancel
72
+ export const PRESS_IN = 'pressIn';
73
+ export const PRESS_OUT = 'pressOut';
74
+ // Synthesized from a sustained hold, so a bare Text or View gets `onLongPress` with no native
75
+ // event behind it. A fired long press suppresses the tap on release
76
+ export const LONG_PRESS = 'longPress';
77
+ export const DEFAULT_LONG_PRESS_MS = 500;
78
+ // How far the touch may drift before the pending long press is cancelled
79
+ // (`Pressability.DEFAULT_LONG_PRESS_DEACTIVATION_DISTANCE`)
80
+ export const LONG_PRESS_DEACTIVATION_DISTANCE = 10;
@@ -0,0 +1,20 @@
1
+ import { type ISymbioteNode } from '../node';
2
+ export interface IPressGesture {
3
+ owner: ISymbioteNode;
4
+ longPressTimer: ReturnType<typeof setTimeout> | undefined;
5
+ longPressFired: boolean;
6
+ longPressStart: {
7
+ x: number;
8
+ y: number;
9
+ } | undefined;
10
+ }
11
+ export declare const activePresses: Set<IPressGesture>;
12
+ export declare function clearLongPress(press: IPressGesture): void;
13
+ export declare function takeAllPresses(): IPressGesture[];
14
+ export declare function findActivePress(target: ISymbioteNode): IPressGesture | undefined;
15
+ export declare function takeEndedPresses(nativeEvent: Record<string, unknown>): IPressGesture[];
16
+ export declare function readTouchPoint(nativeEvent: Record<string, unknown>): {
17
+ x: number;
18
+ y: number;
19
+ } | undefined;
20
+ export declare function hasRemainingTouchWithin(owner: ISymbioteNode, nativeEvent: Record<string, unknown>): boolean;
@@ -0,0 +1,89 @@
1
+ // The synthesized tap: Fabric has no `press` event, so one is built from a touch sequence. This
2
+ // module owns the in-flight gestures and the long-press clock, nothing about delivery
3
+ // A press is INDEPENDENT of the responder: `negotiateResponder` runs on the same touch frames and
4
+ // neither consumes the other, т.к. a View can be a Pressable and a PanResponder target at once
5
+ import { isRecord } from '../type-guards.js';
6
+ import { isSymbioteNode } from '../node.js';
7
+ import { endsWithin } from './delivery.js';
8
+ // Each unrelated target owns an independent press, and further fingers under the same owner JOIN
9
+ // it, so one target still sees exactly one `pressIn` / `press` / `pressOut` lifecycle
10
+ export const activePresses = new Set();
11
+ export function clearLongPress(press) {
12
+ if (press.longPressTimer !== undefined) {
13
+ clearTimeout(press.longPressTimer);
14
+ press.longPressTimer = undefined;
15
+ }
16
+ }
17
+ export function takeAllPresses() {
18
+ const presses = [...activePresses];
19
+ activePresses.clear();
20
+ for (const press of presses)
21
+ clearLongPress(press);
22
+ return presses;
23
+ }
24
+ export function findActivePress(target) {
25
+ for (const press of activePresses) {
26
+ if (endsWithin(target, press.owner))
27
+ return press;
28
+ }
29
+ return undefined;
30
+ }
31
+ // Every press whose last finger left in this frame, removed from the active set. An owner with a
32
+ // finger still down keeps its press, which is what makes a multi-touch lift not a release
33
+ export function takeEndedPresses(nativeEvent) {
34
+ const ended = [];
35
+ for (const press of activePresses) {
36
+ if (hasRemainingTouchWithin(press.owner, nativeEvent))
37
+ continue;
38
+ activePresses.delete(press);
39
+ clearLongPress(press);
40
+ ended.push(press);
41
+ }
42
+ return ended;
43
+ }
44
+ function pagePointOf(source) {
45
+ if (!source)
46
+ return undefined;
47
+ const { pageX, pageY } = source;
48
+ if (typeof pageX === 'number' && typeof pageY === 'number')
49
+ return { x: pageX, y: pageY };
50
+ return undefined;
51
+ }
52
+ // The changed finger's page coordinate, falling back to the active-touch list and then to
53
+ // undefined, which callers read as "skip the coordinate-dependent logic" rather than guessing
54
+ export function readTouchPoint(nativeEvent) {
55
+ const direct = pagePointOf(nativeEvent);
56
+ if (direct)
57
+ return direct;
58
+ // `changedTouches` identifies the finger for THIS frame, so it is read before the full
59
+ // active-touch list, whose first entry may belong to another simultaneously pressed target
60
+ for (const key of ['changedTouches', 'touches']) {
61
+ const touches = nativeEvent[key];
62
+ if (!Array.isArray(touches))
63
+ continue;
64
+ for (const touch of touches) {
65
+ if (!isRecord(touch))
66
+ continue;
67
+ const point = pagePointOf(touch);
68
+ if (point)
69
+ return point;
70
+ }
71
+ }
72
+ return undefined;
73
+ }
74
+ // Whether any touch still down started inside `owner`. RN's `noResponderTouches` walks
75
+ // `nativeEvent.touches` the same way; a headless smoke fires an empty `{}`, so no remaining touch,
76
+ // which preserves single-touch behavior
77
+ export function hasRemainingTouchWithin(owner, nativeEvent) {
78
+ const touches = nativeEvent.touches;
79
+ if (!Array.isArray(touches))
80
+ return false;
81
+ for (const touch of touches) {
82
+ if (!isRecord(touch))
83
+ continue;
84
+ const target = touch.target;
85
+ if (isSymbioteNode(target) && endsWithin(target, owner))
86
+ return true;
87
+ }
88
+ return false;
89
+ }
@@ -0,0 +1,6 @@
1
+ import { type ISymbioteNode } from '../node';
2
+ export declare function heldResponder(): ISymbioteNode | undefined;
3
+ export declare function clearResponder(): void;
4
+ /** Release the responder to native, on a final touch end or a terminate. */
5
+ export declare function releaseResponderToNative(node: ISymbioteNode): void;
6
+ export declare function negotiateResponder(target: ISymbioteNode, phase: 'start' | 'move', nativeEvent: Record<string, unknown>): void;
@@ -0,0 +1,124 @@
1
+ // The responder protocol: who owns the in-flight gesture, and the negotiation that moves it.
2
+ // Split from `./index` so the touch handler reads as four phases rather than as the negotiation
3
+ // `currentResponder` is module state reached from outside through `heldResponder` and
4
+ // `clearResponder`, т.к. the touch handler must read it between its own phases
5
+ import { dlog, isDebug } from '../debug.js';
6
+ import { setIsJSResponder } from '../imperative.js';
7
+ import { hasListenerFor } from '../node.js';
8
+ import { callOwnListener, lowestCommonAncestor, pathToRoot } from './delivery.js';
9
+ import { RESPONDER_GRANT, RESPONDER_REJECT, RESPONDER_TERMINATE, RESPONDER_TERMINATION_REQUEST, SHOULD_SET_NAMES, } from './names.js';
10
+ // The node that claimed the responder for the in-flight touch, or undefined when nobody did.
11
+ // Receives move and release/terminate
12
+ let currentResponder;
13
+ export function heldResponder() {
14
+ return currentResponder;
15
+ }
16
+ export function clearResponder() {
17
+ currentResponder = undefined;
18
+ }
19
+ // RN's two-phase should-set walk: CAPTURE root to deepest, then BUBBLE deepest to root, first true
20
+ // wins. `skip` drops the deepest node when it IS the current responder, т.к. asking the holder to
21
+ // re-claim would let its should-set consume the frame from under its own `onResponderMove`
22
+ function findWantsResponder(path, phase, nativeEvent, skip) {
23
+ const names = SHOULD_SET_NAMES[phase];
24
+ for (let i = path.length - 1; i >= 0; i--) {
25
+ if (path[i] !== skip &&
26
+ callOwnListener(path[i], names.capture, nativeEvent) === true) {
27
+ return path[i];
28
+ }
29
+ }
30
+ for (const node of path) {
31
+ if (node !== skip &&
32
+ callOwnListener(node, names.bubble, nativeEvent) === true)
33
+ return node;
34
+ }
35
+ return undefined;
36
+ }
37
+ // Tell native which node owns the gesture, RN's `injectGlobalResponderHandler`. The OLD owner
38
+ // first, then the new one, both in one call so no site can do half of it
39
+ // WITHOUT THIS A JS RESPONDER LOSES TO ANY SCROLL VIEW ABOVE IT, invisibly: the claim returns true,
40
+ // the native UIScrollView never learns of it, and every later move arrives as `topScroll`
41
+ function handOverNativeResponder(from, to, blockNativeResponder) {
42
+ if (isDebug()) {
43
+ dlog(`setIsJSResponder from=${from === undefined ? 'none' : 'yes'} ` +
44
+ `to=${to === undefined ? 'none' : 'yes'} block=${blockNativeResponder}`);
45
+ }
46
+ if (from !== undefined)
47
+ setIsJSResponder(from, false, blockNativeResponder);
48
+ if (to !== undefined)
49
+ setIsJSResponder(to, true, blockNativeResponder);
50
+ }
51
+ /** Release the responder to native, on a final touch end or a terminate. */
52
+ export function releaseResponderToNative(node) {
53
+ handOverNativeResponder(node, undefined, false);
54
+ }
55
+ // The slice of the target's ancestry the negotiation may ask, plus the node it must not ask
56
+ // ONE WALK, and the common case never needs a second: while a finger stays inside the view that
57
+ // claimed it, the responder IS an ancestor of the target, so the scope RN computes as a lowest
58
+ // common ancestor is already in the target's own path and an index lookup answers it
59
+ function scopeFor(target) {
60
+ const targetPath = pathToRoot(target);
61
+ if (currentResponder === undefined)
62
+ return { path: targetPath, skip: undefined };
63
+ const heldAt = targetPath.indexOf(currentResponder);
64
+ if (heldAt >= 0)
65
+ return { path: targetPath.slice(heldAt), skip: currentResponder };
66
+ // The finger left the responder's subtree, a rare frame rather than every frame
67
+ const from = lowestCommonAncestor(currentResponder, target);
68
+ return { path: from === undefined ? [] : pathToRoot(from), skip: undefined };
69
+ }
70
+ function grantTo(wants, nativeEvent) {
71
+ currentResponder = wants;
72
+ if (isDebug())
73
+ dlog(`responder granted to ${wants.component}`);
74
+ const granted = callOwnListener(wants, RESPONDER_GRANT, nativeEvent);
75
+ handOverNativeResponder(undefined, wants, granted === true);
76
+ }
77
+ // RN's `setResponderAndExtractTransfer` order: grant the TAKER, then terminate the incumbent
78
+ // RN also grants ahead of the termination request purely to read the block-native return. We have
79
+ // no native surface to block, so on the REJECT path that grant is omitted as a visible no-op
80
+ function transferTo(incumbent, wants, nativeEvent) {
81
+ // A missing termination-request listener means implicit consent (RN's default true), so only an
82
+ // explicit non-true answer keeps the incumbent
83
+ const guarded = hasListenerFor(incumbent, RESPONDER_TERMINATION_REQUEST);
84
+ const allowed = !guarded ||
85
+ callOwnListener(incumbent, RESPONDER_TERMINATION_REQUEST, nativeEvent) ===
86
+ true;
87
+ if (!allowed) {
88
+ if (isDebug())
89
+ dlog(`responder takeover of ${incumbent.component} rejected`);
90
+ callOwnListener(wants, RESPONDER_REJECT, nativeEvent);
91
+ return;
92
+ }
93
+ if (isDebug())
94
+ dlog(`responder transferred ${incumbent.component} -> ${wants.component}`);
95
+ const granted = callOwnListener(wants, RESPONDER_GRANT, nativeEvent);
96
+ callOwnListener(incumbent, RESPONDER_TERMINATE, nativeEvent);
97
+ currentResponder = wants;
98
+ handOverNativeResponder(incumbent, wants, granted === true);
99
+ }
100
+ // Negotiate (or re-negotiate) the responder for a touch start or move. With nobody holding it the
101
+ // winner is granted; with an incumbent it is asked to relinquish first
102
+ export function negotiateResponder(target, phase, nativeEvent) {
103
+ const { path, skip } = scopeFor(target);
104
+ if (path.length === 0)
105
+ return;
106
+ const wants = findWantsResponder(path, phase, nativeEvent, skip);
107
+ // Every exit is logged: a negotiation that declines is indistinguishable from one that never
108
+ // ran, and the two have opposite causes
109
+ if (!wants) {
110
+ if (isDebug()) {
111
+ dlog(`responder ${phase}: nobody wants it (path=${path.length}${skip === undefined ? '' : ', one skipped'})`);
112
+ }
113
+ return;
114
+ }
115
+ if (wants === currentResponder) {
116
+ if (isDebug())
117
+ dlog(`responder ${phase}: ${wants.component} already holds it`);
118
+ return;
119
+ }
120
+ if (currentResponder === undefined)
121
+ grantTo(wants, nativeEvent);
122
+ else
123
+ transferTo(currentResponder, wants, nativeEvent);
124
+ }