@symbiote-native/engine 0.1.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/build/action-sheet-ios/index.js +4 -1
- package/build/alert/index.ios.js +10 -0
- package/build/animated/animations/composition.js +3 -1
- package/build/animated/animations/spring-config.js +4 -1
- package/build/animated/animations/spring.js +23 -9
- package/build/animated/animations/timing.js +3 -2
- package/build/animated/bezier.js +2 -1
- package/build/animated/color.d.ts +2 -8
- package/build/animated/color.js +8 -47
- package/build/animated/easing.js +0 -8
- package/build/animated/event.js +7 -3
- package/build/animated/graph.d.ts +13 -5
- package/build/animated/graph.js +92 -13
- package/build/animated/index.d.ts +3 -3
- package/build/animated/index.js +3 -3
- package/build/animated/interpolation.js +8 -3
- package/build/animated/leaf-lifecycle.d.ts +7 -0
- package/build/animated/leaf-lifecycle.js +185 -0
- package/build/animated/mock.js +1 -1
- package/build/animated/native/native-animated.js +2 -1
- package/build/animated/operators.js +27 -6
- package/build/animated/props.js +1 -1
- package/build/animated/rgba.d.ts +8 -0
- package/build/animated/rgba.js +70 -0
- package/build/animated/style.js +13 -3
- package/build/animated/value-xy.d.ts +8 -5
- package/build/animated/value-xy.js +4 -1
- package/build/animated/value.js +12 -7
- package/build/appearance/index.js +4 -1
- package/build/commit.d.ts +13 -0
- package/build/commit.js +146 -14
- package/build/debug.d.ts +1 -1
- package/build/debug.js +10 -3
- package/build/events/index.js +19 -10
- package/build/fabric-props.js +8 -2
- package/build/image-loader.js +3 -2
- package/build/index.d.ts +23 -19
- package/build/index.js +19 -11
- package/build/interaction-manager/index.js +2 -1
- package/build/layout-animation/index.js +12 -5
- package/build/native-events.js +2 -1
- package/build/native-modules/index.js +3 -1
- package/build/node.d.ts +25 -0
- package/build/node.js +168 -2
- package/build/pan-responder/index.js +20 -9
- package/build/permissions-android/index.js +3 -1
- package/build/platform/index.android.d.ts +1 -1
- package/build/platform/index.android.js +1 -1
- package/build/platform/index.ios.d.ts +1 -1
- package/build/platform-color/index.js +3 -1
- package/build/post-commit.d.ts +1 -0
- package/build/post-commit.js +6 -0
- package/build/process-background-image/index.js +30 -9
- package/build/process-filter.js +11 -4
- package/build/process-font-variant.js +0 -1
- package/build/process-transform/index.js +13 -19
- package/build/registry.js +10 -2
- package/build/report-error.d.ts +5 -0
- package/build/report-error.js +63 -0
- package/build/share/index.android.js +1 -1
- package/build/share/index.ios.js +1 -1
- package/build/status-bar/index.android.js +8 -10
- package/build/status-bar/index.ios.js +1 -1
- package/build/style-registry/index.d.ts +9 -2
- package/build/style-registry/index.js +149 -95
- package/build/style-registry/scope.d.ts +15 -1
- package/build/style-registry/scope.js +29 -27
- package/build/style-sheet/index.js +4 -2
- package/build/styles.d.ts +11 -1
- package/build/surface.d.ts +1 -1
- package/build/surface.js +7 -1
- package/build/tags.d.ts +1 -0
- package/build/tags.js +31 -1
- package/build/touch-history.js +9 -2
- package/build/vibration/index.android.js +2 -3
- package/build/vibration/index.ios.js +1 -1
- package/build/view-config.js +14 -2
- package/package.json +2 -2
- package/build/animated/interpolation-node.d.ts +0 -15
- package/build/animated/interpolation-node.js +0 -58
- package/build/style-registry/scope/index.d.ts +0 -3
- package/build/style-registry/scope/index.js +0 -41
- package/build/touch-history/index.d.ts +0 -23
- package/build/touch-history/index.js +0 -150
- package/build/type-guards/index.d.ts +0 -4
- package/build/type-guards/index.js +0 -15
package/build/styles.d.ts
CHANGED
|
@@ -187,7 +187,11 @@ export interface IViewStyle {
|
|
|
187
187
|
shadowRadius?: number;
|
|
188
188
|
elevation?: number;
|
|
189
189
|
transform?: ITransformProp[];
|
|
190
|
-
transformOrigin?: [
|
|
190
|
+
transformOrigin?: [
|
|
191
|
+
string | number,
|
|
192
|
+
string | number,
|
|
193
|
+
string | number
|
|
194
|
+
] | string;
|
|
191
195
|
boxShadow?: IBoxShadowValue[] | string;
|
|
192
196
|
filter?: IFilterFunction[] | string;
|
|
193
197
|
experimental_backgroundImage?: IBackgroundImageValue[] | string;
|
|
@@ -207,6 +211,12 @@ export interface ITextStyle extends IViewStyle {
|
|
|
207
211
|
textDecorationLine?: 'none' | 'underline' | 'line-through' | 'underline line-through';
|
|
208
212
|
textDecorationColor?: IColorValue;
|
|
209
213
|
textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed';
|
|
214
|
+
textShadowColor?: IColorValue;
|
|
215
|
+
textShadowOffset?: {
|
|
216
|
+
width: number;
|
|
217
|
+
height: number;
|
|
218
|
+
};
|
|
219
|
+
textShadowRadius?: number;
|
|
210
220
|
fontVariant?: string[];
|
|
211
221
|
writingDirection?: 'auto' | 'ltr' | 'rtl';
|
|
212
222
|
includeFontPadding?: boolean;
|
package/build/surface.d.ts
CHANGED
package/build/surface.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { commitChildren } from './commit.js';
|
|
5
5
|
import { dlog } from './debug.js';
|
|
6
6
|
import { installEventHandler } from './events/index.js';
|
|
7
|
+
import { markDirty } from './node.js';
|
|
7
8
|
export class SymbioteSurface {
|
|
8
9
|
rootTag;
|
|
9
10
|
children = [];
|
|
@@ -46,6 +47,9 @@ export class SymbioteSurface {
|
|
|
46
47
|
this.commit();
|
|
47
48
|
});
|
|
48
49
|
}
|
|
50
|
+
// Splices `parent.children` directly instead of going through node.ts's removeChild, so it
|
|
51
|
+
// owes the same dirty-mark - otherwise a node pulled out of a subtree here leaves that subtree
|
|
52
|
+
// looking clean and the commit walk skips right over the hole.
|
|
49
53
|
detach(child) {
|
|
50
54
|
const parent = child.parent;
|
|
51
55
|
if (parent) {
|
|
@@ -53,6 +57,7 @@ export class SymbioteSurface {
|
|
|
53
57
|
if (index >= 0)
|
|
54
58
|
parent.children.splice(index, 1);
|
|
55
59
|
child.parent = undefined;
|
|
60
|
+
markDirty(parent);
|
|
56
61
|
return;
|
|
57
62
|
}
|
|
58
63
|
const topIndex = this.children.indexOf(child);
|
|
@@ -62,6 +67,7 @@ export class SymbioteSurface {
|
|
|
62
67
|
}
|
|
63
68
|
export function createSurface(rootTag) {
|
|
64
69
|
installEventHandler();
|
|
70
|
+
const surface = new SymbioteSurface(rootTag);
|
|
65
71
|
dlog(`surface created root=${rootTag}`);
|
|
66
|
-
return
|
|
72
|
+
return surface;
|
|
67
73
|
}
|
package/build/tags.d.ts
CHANGED
package/build/tags.js
CHANGED
|
@@ -2,9 +2,39 @@
|
|
|
2
2
|
// for root tags). A tag is minted once when a node is first created and then
|
|
3
3
|
// stays with it across clone-on-write commits (the clone keeps the family), so
|
|
4
4
|
// this counter only ever moves forward: one tag per node, not per commit.
|
|
5
|
-
|
|
5
|
+
import { dlog } from './debug.js';
|
|
6
|
+
// WHY IT DOES NOT START AT 2. React's own Fabric renderer allocates tags the exact same way and
|
|
7
|
+
// from the exact same seed — `let nextReactTag = 2; ... nextReactTag += 2` in
|
|
8
|
+
// react-native-renderer/src/ReactFiberConfigFabric.js. We replace React as the APP's renderer, but
|
|
9
|
+
// React is still in the bundle and still drives one Fabric surface of its own: **LogBox**. So the
|
|
10
|
+
// first JS error in a symbiote app opens a redbox, React mints tag 2 for it, and Fabric aborts the
|
|
11
|
+
// process on a Create for a tag the engine registered at app start:
|
|
12
|
+
//
|
|
13
|
+
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
|
|
14
|
+
// reason: 'RCTComponentViewRegistry: Attempt to dequeue already registered component.'
|
|
15
|
+
// -[RCTComponentViewRegistry dequeueComponentViewWithComponentHandle:tag:]
|
|
16
|
+
// RCTPerformMountInstructions(...)
|
|
17
|
+
//
|
|
18
|
+
// `RCTComponentViewRegistry` is process-wide, not per-surface, so the two allocators must not
|
|
19
|
+
// overlap. Diagnosed 2026-08-20 on examples/svelte: an `effect_update_depth_exceeded` on the API
|
|
20
|
+
// Playground screen turned into a SIGABRT with no JS stack, because the redbox that would have
|
|
21
|
+
// named the file killed the app instead of showing. Starting a whole surface's worth of tags above
|
|
22
|
+
// React's seed keeps the redbox a redbox: LogBox needs a few hundred nodes, so it can never walk
|
|
23
|
+
// 500k allocations up into this range.
|
|
24
|
+
const REACT_LOGBOX_TAG_RESERVE = 1_000_000;
|
|
25
|
+
let next = REACT_LOGBOX_TAG_RESERVE + 2;
|
|
26
|
+
let announced = false;
|
|
6
27
|
export function nextTag() {
|
|
28
|
+
if (!announced) {
|
|
29
|
+
announced = true;
|
|
30
|
+
// Printed once, next to the first `commit ... createNode tag=` line: a device log that shows a
|
|
31
|
+
// Fabric tag below this base was minted by React (LogBox), not by us.
|
|
32
|
+
dlog(`tag allocator base=${next} (React's own Fabric tags stay below it)`);
|
|
33
|
+
}
|
|
7
34
|
const tag = next;
|
|
8
35
|
next += 2;
|
|
9
36
|
return tag;
|
|
10
37
|
}
|
|
38
|
+
// The lowest tag this allocator can ever hand out. Exported for the regression test that pins the
|
|
39
|
+
// no-overlap invariant against React's own allocator; nothing at runtime reads it.
|
|
40
|
+
export const FIRST_TAG = REACT_LOGBOX_TAG_RESERVE + 2;
|
package/build/touch-history.js
CHANGED
|
@@ -21,7 +21,9 @@ export const touchHistory = {
|
|
|
21
21
|
mostRecentTimeStamp: 0,
|
|
22
22
|
};
|
|
23
23
|
function toFiniteNumber(value) {
|
|
24
|
-
return typeof value === 'number' && Number.isFinite(value)
|
|
24
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
25
|
+
? value
|
|
26
|
+
: undefined;
|
|
25
27
|
}
|
|
26
28
|
// Pull a recordable touch out of an untyped entry. RN's getTouchIdentifier throws on
|
|
27
29
|
// a null id; we skip instead, so events without touch geometry leave the bank untouched.
|
|
@@ -35,7 +37,12 @@ function normalizeTouch(raw) {
|
|
|
35
37
|
return undefined;
|
|
36
38
|
if (identifier < 0 || identifier > MAX_TOUCH_BANK)
|
|
37
39
|
return undefined;
|
|
38
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
identifier,
|
|
42
|
+
pageX,
|
|
43
|
+
pageY,
|
|
44
|
+
timestamp: toFiniteNumber(raw.timestamp) ?? 0,
|
|
45
|
+
};
|
|
39
46
|
}
|
|
40
47
|
// The changed touches for this frame (start/move/end), defensively read.
|
|
41
48
|
function changedTouchesOf(nativeEvent) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Vibration: Android build. Android has a native pattern scheduler, so the whole pattern
|
|
2
|
-
// goes to native `vibrateByPattern(pattern, repeat)
|
|
3
|
-
//
|
|
4
|
-
// on an Android host.
|
|
2
|
+
// goes to native `vibrateByPattern(pattern, repeat)` (repeat encoding below). Everything
|
|
3
|
+
// else is the shared core. Metro picks this file on an Android host.
|
|
5
4
|
//
|
|
6
5
|
// device-verify-pending: the `Vibration` name and the `vibrateByPattern` routing are
|
|
7
6
|
// confirmed from RN source but not yet exercised on a real Android host: only a
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Everything else (the single-number buzz, cancel, the module resolver) is the shared
|
|
4
4
|
// core. Metro picks this file on an iOS host; the base vibration.ts re-exports it for
|
|
5
5
|
// web/headless.
|
|
6
|
-
import { createVibration, DEFAULT_VIBRATION_LENGTH } from './shared.js';
|
|
6
|
+
import { createVibration, DEFAULT_VIBRATION_LENGTH, } from './shared.js';
|
|
7
7
|
// JS scheduler state, mirroring RN's _vibrating / _id. `scheduleId` guards against a
|
|
8
8
|
// stale timer from a previous run firing into a new one.
|
|
9
9
|
let vibrating = false;
|
package/build/view-config.js
CHANGED
|
@@ -55,7 +55,12 @@ const TEXT_INPUT_EVENTS = [
|
|
|
55
55
|
'selectionChange',
|
|
56
56
|
'contentSizeChange',
|
|
57
57
|
];
|
|
58
|
-
const MODAL_EVENTS = [
|
|
58
|
+
const MODAL_EVENTS = [
|
|
59
|
+
'show',
|
|
60
|
+
'dismiss',
|
|
61
|
+
'requestClose',
|
|
62
|
+
'orientationChange',
|
|
63
|
+
];
|
|
59
64
|
// A scroll view's events are the same on both axes and both platforms; only the native
|
|
60
65
|
// NAME differs (iOS RCTScrollView for both; Android RCTScrollView vertical vs
|
|
61
66
|
// AndroidHorizontalScrollView horizontal). Declared once, keyed under each name below. The
|
|
@@ -78,7 +83,14 @@ const TEXT_EVENTS = ['textLayout'];
|
|
|
78
83
|
// SymbioteNode.component (what createNode is called with). A component absent here
|
|
79
84
|
// still gets BASE_EVENTS, so a new primitive has working press/layout for free.
|
|
80
85
|
const COMPONENT_EVENTS = {
|
|
81
|
-
RCTImageView: [
|
|
86
|
+
RCTImageView: [
|
|
87
|
+
'loadStart',
|
|
88
|
+
'load',
|
|
89
|
+
'loadEnd',
|
|
90
|
+
'error',
|
|
91
|
+
'progress',
|
|
92
|
+
'partialLoad',
|
|
93
|
+
],
|
|
82
94
|
RCTScrollView: SCROLL_EVENTS,
|
|
83
95
|
AndroidHorizontalScrollView: SCROLL_EVENTS,
|
|
84
96
|
RCTSinglelineTextInputView: TEXT_INPUT_EVENTS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "SymbioteNative's retained shadow-tree engine — clone-on-write commit path + event normalization over React Native Fabric, shared by every framework adapter.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"react-native": ">=0.86"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@symbiote-native/test-utils": "0.
|
|
36
|
+
"@symbiote-native/test-utils": "0.2.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"typecheck": "tsc --build",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { AnimatedNode, AnimatedWithChildren } from './graph';
|
|
2
|
-
import { type IInterpolationConfig } from './interpolation';
|
|
3
|
-
import type { INativeNodeConfig, IPlatformConfig } from './native/native-animated';
|
|
4
|
-
export declare class AnimatedInterpolation extends AnimatedWithChildren {
|
|
5
|
-
private readonly parent;
|
|
6
|
-
private readonly config;
|
|
7
|
-
private interpolation;
|
|
8
|
-
constructor(parent: AnimatedNode, config: IInterpolationConfig);
|
|
9
|
-
private getInterpolation;
|
|
10
|
-
__getValue(): number | string;
|
|
11
|
-
__attach(): void;
|
|
12
|
-
__detach(): void;
|
|
13
|
-
__makeNative(platformConfig?: IPlatformConfig): void;
|
|
14
|
-
__getNativeConfig(): INativeNodeConfig;
|
|
15
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// AnimatedInterpolation: a graph node that maps its parent's numeric value
|
|
2
|
-
// through an interpolation. Ported from RN's AnimatedInterpolation.js: numeric,
|
|
3
|
-
// string-with-units, and color output ranges (the value graph; native config
|
|
4
|
-
// removed). Platform (Native) colors stay out of scope, color.ts defers them.
|
|
5
|
-
import { AnimatedNode, AnimatedWithChildren, registerInterpolationFactory } from './graph.js';
|
|
6
|
-
import { checkValidRanges, createInterpolation } from './interpolation.js';
|
|
7
|
-
export class AnimatedInterpolation extends AnimatedWithChildren {
|
|
8
|
-
parent;
|
|
9
|
-
config;
|
|
10
|
-
interpolation;
|
|
11
|
-
constructor(parent, config) {
|
|
12
|
-
super();
|
|
13
|
-
this.parent = parent;
|
|
14
|
-
this.config = config;
|
|
15
|
-
// Validate eagerly so a bad range fails at construction, not first frame.
|
|
16
|
-
checkValidRanges(config.inputRange, config.outputRange);
|
|
17
|
-
}
|
|
18
|
-
getInterpolation() {
|
|
19
|
-
if (this.interpolation === undefined) {
|
|
20
|
-
this.interpolation = createInterpolation(this.config);
|
|
21
|
-
}
|
|
22
|
-
return this.interpolation;
|
|
23
|
-
}
|
|
24
|
-
__getValue() {
|
|
25
|
-
const parentValue = this.parent.__getValue();
|
|
26
|
-
if (typeof parentValue !== 'number') {
|
|
27
|
-
throw new Error('Cannot interpolate an input which is not a number');
|
|
28
|
-
}
|
|
29
|
-
return this.getInterpolation()(parentValue);
|
|
30
|
-
}
|
|
31
|
-
__attach() {
|
|
32
|
-
this.parent.__addChild(this);
|
|
33
|
-
super.__attach();
|
|
34
|
-
}
|
|
35
|
-
__detach() {
|
|
36
|
-
this.parent.__removeChild(this);
|
|
37
|
-
super.__detach();
|
|
38
|
-
}
|
|
39
|
-
// Make the upstream value native first, so the parent->interpolation edge can be
|
|
40
|
-
// wired when this node is reached from a leaf rather than from the value.
|
|
41
|
-
__makeNative(platformConfig) {
|
|
42
|
-
this.parent.__makeNative(platformConfig);
|
|
43
|
-
super.__makeNative(platformConfig);
|
|
44
|
-
}
|
|
45
|
-
__getNativeConfig() {
|
|
46
|
-
return {
|
|
47
|
-
type: 'interpolation',
|
|
48
|
-
inputRange: this.config.inputRange,
|
|
49
|
-
outputRange: this.config.outputRange,
|
|
50
|
-
extrapolateLeft: this.config.extrapolateLeft ?? this.config.extrapolate ?? 'extend',
|
|
51
|
-
extrapolateRight: this.config.extrapolateRight ?? this.config.extrapolate ?? 'extend',
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// Hand graph.ts's AnimatedNode.interpolate() the real constructor at module load,
|
|
56
|
-
// so every node subclass's base-class interpolate() produces an actual
|
|
57
|
-
// AnimatedInterpolation without graph.ts ever value-importing this module.
|
|
58
|
-
registerInterpolationFactory((parent, config) => new AnimatedInterpolation(parent, config));
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export type IClassToggleMap = Record<string, boolean | undefined>;
|
|
2
|
-
export type IScopableClassValue = string | IClassToggleMap | Array<string | IClassToggleMap> | undefined | null;
|
|
3
|
-
export declare function scopeClassName(value: IScopableClassValue, localNames: ReadonlySet<string>, scopeId: string): IScopableClassValue;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// Vue `<style scoped>` class-name rewriter. Distinct responsibility from the sibling
|
|
2
|
-
// ./index.ts (the CSS class -> style registry): this module does pure NAME rewriting,
|
|
3
|
-
// no registry lookup, no CSS parsing. It runs at the compiled call site of a Vue SFC's
|
|
4
|
-
// scoped-style template - `adapters/vue/metro-vue-transformer.cjs` emits calls to
|
|
5
|
-
// scopeClassName (imported there as `__scopeClass`) - BEFORE Vue's own normalizeClass()
|
|
6
|
-
// collapses string/object/array `class` values to a final string, so it must pre-process
|
|
7
|
-
// all three shapes normalizeClass understands. resolveClassName in ./index.ts still does
|
|
8
|
-
// the actual style lookup, unchanged, against the rewritten (possibly suffixed) name.
|
|
9
|
-
import { kebabToCamel } from '../index.js';
|
|
10
|
-
// Suffixes every class token that this file's scoped block locally defines with
|
|
11
|
-
// `__${scopeId}`, leaving unrecognized tokens (globals, external classes) untouched.
|
|
12
|
-
export function scopeClassName(value, localNames, scopeId) {
|
|
13
|
-
if (value === undefined || value === null)
|
|
14
|
-
return value;
|
|
15
|
-
if (Array.isArray(value)) {
|
|
16
|
-
return value.map(item => scopeClassEntry(item, localNames, scopeId));
|
|
17
|
-
}
|
|
18
|
-
return scopeClassEntry(value, localNames, scopeId);
|
|
19
|
-
}
|
|
20
|
-
// A token arrives as either the camelCase registry key (`sectionLabel`) or its kebab-case
|
|
21
|
-
// authoring form (`section-label`) - normalize to camelCase FIRST, then decide scoping, so
|
|
22
|
-
// `localNames` (always camelCase, built from the css-parser's registered keys) recognizes a
|
|
23
|
-
// kebab-written token. The emitted (possibly suffixed) name is always the camelCase form.
|
|
24
|
-
function scopeToken(token, localNames, scopeId) {
|
|
25
|
-
const camelToken = kebabToCamel(token);
|
|
26
|
-
return localNames.has(camelToken) ? `${camelToken}__${scopeId}` : camelToken;
|
|
27
|
-
}
|
|
28
|
-
function scopeClassEntry(value, localNames, scopeId) {
|
|
29
|
-
if (typeof value === 'object') {
|
|
30
|
-
const scoped = {};
|
|
31
|
-
for (const [name, enabled] of Object.entries(value)) {
|
|
32
|
-
scoped[scopeToken(name, localNames, scopeId)] = enabled;
|
|
33
|
-
}
|
|
34
|
-
return scoped;
|
|
35
|
-
}
|
|
36
|
-
return value
|
|
37
|
-
.split(/\s+/)
|
|
38
|
-
.filter(Boolean)
|
|
39
|
-
.map(token => scopeToken(token, localNames, scopeId))
|
|
40
|
-
.join(' ');
|
|
41
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
interface ITouchRecord {
|
|
2
|
-
touchActive: boolean;
|
|
3
|
-
startPageX: number;
|
|
4
|
-
startPageY: number;
|
|
5
|
-
startTimeStamp: number;
|
|
6
|
-
currentPageX: number;
|
|
7
|
-
currentPageY: number;
|
|
8
|
-
currentTimeStamp: number;
|
|
9
|
-
previousPageX: number;
|
|
10
|
-
previousPageY: number;
|
|
11
|
-
previousTimeStamp: number;
|
|
12
|
-
}
|
|
13
|
-
interface ITouchHistory {
|
|
14
|
-
touchBank: ITouchRecord[];
|
|
15
|
-
numberActiveTouches: number;
|
|
16
|
-
indexOfSingleActiveTouch: number;
|
|
17
|
-
mostRecentTimeStamp: number;
|
|
18
|
-
}
|
|
19
|
-
export declare const touchHistory: ITouchHistory;
|
|
20
|
-
export declare function recordTouchTrack(kind: 'start' | 'move' | 'end', nativeEvent: Record<string, unknown>): void;
|
|
21
|
-
export declare function resetTouchHistory(): void;
|
|
22
|
-
export declare function attachTouchHistory(nativeEvent: Record<string, unknown>): void;
|
|
23
|
-
export {};
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// Per-touch position/time tracking, ported from RN's
|
|
2
|
-
// react-native-renderer/.../legacy-events/ResponderTouchHistoryStore.js. PanResponder's
|
|
3
|
-
// multitouch dx/vx math needs each touch's own previous->current delta (RN counts only
|
|
4
|
-
// touches that moved since `_accountsForMovesUpTo`), which a grant-relative centroid of
|
|
5
|
-
// ALL live touches cannot reconstruct. We maintain the bank as touches flow and ATTACH
|
|
6
|
-
// `touchHistory` onto the nativeEvent reaching responder handlers, exactly how
|
|
7
|
-
// ResponderEventPlugin.js sets `*.touchHistory`.
|
|
8
|
-
//
|
|
9
|
-
// events/index.ts consumes only this file's public surface (recordTouchTrack,
|
|
10
|
-
// attachTouchHistory, resetTouchHistory, touchHistory); everything else here is a
|
|
11
|
-
// private implementation detail of the bank.
|
|
12
|
-
import { isRecord } from '../type-guards.js';
|
|
13
|
-
// RN's bank is indexed by touch identifier and warns above 20; we never warn (headless
|
|
14
|
-
// events may carry larger or absent ids), we just skip anything out of a sane range.
|
|
15
|
-
const MAX_TOUCH_BANK = 20;
|
|
16
|
-
const touchBank = [];
|
|
17
|
-
export const touchHistory = {
|
|
18
|
-
touchBank,
|
|
19
|
-
numberActiveTouches: 0,
|
|
20
|
-
indexOfSingleActiveTouch: -1,
|
|
21
|
-
mostRecentTimeStamp: 0,
|
|
22
|
-
};
|
|
23
|
-
function toFiniteNumber(value) {
|
|
24
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
25
|
-
}
|
|
26
|
-
// Pull a recordable touch out of an untyped entry. RN's getTouchIdentifier throws on
|
|
27
|
-
// a null id; we skip instead, so events without touch geometry leave the bank untouched.
|
|
28
|
-
function normalizeTouch(raw) {
|
|
29
|
-
if (!isRecord(raw))
|
|
30
|
-
return undefined;
|
|
31
|
-
const identifier = toFiniteNumber(raw.identifier);
|
|
32
|
-
const pageX = toFiniteNumber(raw.pageX);
|
|
33
|
-
const pageY = toFiniteNumber(raw.pageY);
|
|
34
|
-
if (identifier === undefined || pageX === undefined || pageY === undefined)
|
|
35
|
-
return undefined;
|
|
36
|
-
if (identifier < 0 || identifier > MAX_TOUCH_BANK)
|
|
37
|
-
return undefined;
|
|
38
|
-
return { identifier, pageX, pageY, timestamp: toFiniteNumber(raw.timestamp) ?? 0 };
|
|
39
|
-
}
|
|
40
|
-
// The changed touches for this frame (start/move/end), defensively read.
|
|
41
|
-
function changedTouchesOf(nativeEvent) {
|
|
42
|
-
const raw = nativeEvent.changedTouches;
|
|
43
|
-
if (!Array.isArray(raw))
|
|
44
|
-
return [];
|
|
45
|
-
const out = [];
|
|
46
|
-
for (const entry of raw) {
|
|
47
|
-
const touch = normalizeTouch(entry);
|
|
48
|
-
if (touch !== undefined)
|
|
49
|
-
out.push(touch);
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
// Count of all touches still down (RN reads nativeEvent.touches.length directly).
|
|
54
|
-
function activeTouchCount(nativeEvent) {
|
|
55
|
-
const raw = nativeEvent.touches;
|
|
56
|
-
return Array.isArray(raw) ? raw.length : 0;
|
|
57
|
-
}
|
|
58
|
-
function recordTouchStart(touch) {
|
|
59
|
-
const record = touchBank[touch.identifier];
|
|
60
|
-
if (record) {
|
|
61
|
-
record.touchActive = true;
|
|
62
|
-
record.startPageX = touch.pageX;
|
|
63
|
-
record.startPageY = touch.pageY;
|
|
64
|
-
record.startTimeStamp = touch.timestamp;
|
|
65
|
-
record.currentPageX = touch.pageX;
|
|
66
|
-
record.currentPageY = touch.pageY;
|
|
67
|
-
record.currentTimeStamp = touch.timestamp;
|
|
68
|
-
record.previousPageX = touch.pageX;
|
|
69
|
-
record.previousPageY = touch.pageY;
|
|
70
|
-
record.previousTimeStamp = touch.timestamp;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
touchBank[touch.identifier] = {
|
|
74
|
-
touchActive: true,
|
|
75
|
-
startPageX: touch.pageX,
|
|
76
|
-
startPageY: touch.pageY,
|
|
77
|
-
startTimeStamp: touch.timestamp,
|
|
78
|
-
currentPageX: touch.pageX,
|
|
79
|
-
currentPageY: touch.pageY,
|
|
80
|
-
currentTimeStamp: touch.timestamp,
|
|
81
|
-
previousPageX: touch.pageX,
|
|
82
|
-
previousPageY: touch.pageY,
|
|
83
|
-
previousTimeStamp: touch.timestamp,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
touchHistory.mostRecentTimeStamp = touch.timestamp;
|
|
87
|
-
}
|
|
88
|
-
// Move and end share the previous<-current shift; only `touchActive` differs.
|
|
89
|
-
function shiftTouchRecord(touch, active) {
|
|
90
|
-
const record = touchBank[touch.identifier];
|
|
91
|
-
if (!record)
|
|
92
|
-
return;
|
|
93
|
-
record.touchActive = active;
|
|
94
|
-
record.previousPageX = record.currentPageX;
|
|
95
|
-
record.previousPageY = record.currentPageY;
|
|
96
|
-
record.previousTimeStamp = record.currentTimeStamp;
|
|
97
|
-
record.currentPageX = touch.pageX;
|
|
98
|
-
record.currentPageY = touch.pageY;
|
|
99
|
-
record.currentTimeStamp = touch.timestamp;
|
|
100
|
-
touchHistory.mostRecentTimeStamp = touch.timestamp;
|
|
101
|
-
}
|
|
102
|
-
function arrayFirst(value) {
|
|
103
|
-
return Array.isArray(value) ? value[0] : undefined;
|
|
104
|
-
}
|
|
105
|
-
// Maintain the bank as a touch frame flows. Mirrors RN's recordTouchTrack: moveish
|
|
106
|
-
// shifts records, startish records + recomputes numberActiveTouches, endish marks the
|
|
107
|
-
// record inactive + rescans for the single remaining touch. `kind` is the touch phase.
|
|
108
|
-
export function recordTouchTrack(kind, nativeEvent) {
|
|
109
|
-
if (kind === 'move') {
|
|
110
|
-
for (const touch of changedTouchesOf(nativeEvent))
|
|
111
|
-
shiftTouchRecord(touch, true);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (kind === 'start') {
|
|
115
|
-
for (const touch of changedTouchesOf(nativeEvent))
|
|
116
|
-
recordTouchStart(touch);
|
|
117
|
-
touchHistory.numberActiveTouches = activeTouchCount(nativeEvent);
|
|
118
|
-
if (touchHistory.numberActiveTouches === 1) {
|
|
119
|
-
const first = normalizeTouch(arrayFirst(nativeEvent.touches));
|
|
120
|
-
touchHistory.indexOfSingleActiveTouch = first?.identifier ?? -1;
|
|
121
|
-
}
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
for (const touch of changedTouchesOf(nativeEvent))
|
|
125
|
-
shiftTouchRecord(touch, false);
|
|
126
|
-
touchHistory.numberActiveTouches = activeTouchCount(nativeEvent);
|
|
127
|
-
if (touchHistory.numberActiveTouches === 1) {
|
|
128
|
-
for (let i = 0; i < touchBank.length; i++) {
|
|
129
|
-
const record = touchBank[i];
|
|
130
|
-
if (record !== undefined && record.touchActive) {
|
|
131
|
-
touchHistory.indexOfSingleActiveTouch = i;
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
// Drop all touch state. Called on a fully-released / cancelled gesture so a stale bank
|
|
138
|
-
// never leaks geometry into the next gesture's first frame.
|
|
139
|
-
export function resetTouchHistory() {
|
|
140
|
-
touchBank.length = 0;
|
|
141
|
-
touchHistory.numberActiveTouches = 0;
|
|
142
|
-
touchHistory.indexOfSingleActiveTouch = -1;
|
|
143
|
-
touchHistory.mostRecentTimeStamp = 0;
|
|
144
|
-
}
|
|
145
|
-
// Attach the live touch history onto the event the responder handlers receive, matching
|
|
146
|
-
// ResponderEventPlugin.js (`grantEvent.touchHistory = ...`, etc.). PanResponder reads
|
|
147
|
-
// it for the per-touch dx/vx math; handlers that ignore it are unaffected.
|
|
148
|
-
export function attachTouchHistory(nativeEvent) {
|
|
149
|
-
nativeEvent.touchHistory = touchHistory;
|
|
150
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
2
|
-
export declare function isBoolean(value: unknown): value is boolean;
|
|
3
|
-
export declare function isNumber(value: unknown): value is number;
|
|
4
|
-
export declare function isString(value: unknown): value is string;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Runtime guards to narrow `unknown` at trust boundaries (native payloads, ViewConfig
|
|
2
|
-
// attributes, style values) without an `as` cast. `isRecord` excludes arrays - most
|
|
3
|
-
// call sites mean "a native payload keyed by string," never a list.
|
|
4
|
-
export function isRecord(value) {
|
|
5
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
-
}
|
|
7
|
-
export function isBoolean(value) {
|
|
8
|
-
return typeof value === 'boolean';
|
|
9
|
-
}
|
|
10
|
-
export function isNumber(value) {
|
|
11
|
-
return typeof value === 'number';
|
|
12
|
-
}
|
|
13
|
-
export function isString(value) {
|
|
14
|
-
return typeof value === 'string';
|
|
15
|
-
}
|