@symbiote-native/engine 0.1.6 → 0.2.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/build/accessibility-info/index.android.js +1 -1
- package/build/accessibility-info/index.ios.js +1 -1
- package/build/action-sheet-ios/index.js +1 -1
- package/build/alert/index.android.js +1 -1
- package/build/alert/index.ios.js +11 -1
- package/build/animated/color.d.ts +2 -8
- package/build/animated/color.js +4 -45
- package/build/animated/easing.js +0 -8
- package/build/animated/graph.d.ts +13 -5
- package/build/animated/graph.js +85 -11
- package/build/animated/index.d.ts +2 -2
- package/build/animated/index.js +2 -2
- package/build/animated/interpolation.js +1 -1
- package/build/animated/leaf-lifecycle.d.ts +7 -0
- package/build/animated/leaf-lifecycle.js +185 -0
- package/build/animated/native/native-animated.js +2 -1
- package/build/animated/rgba.d.ts +8 -0
- package/build/animated/rgba.js +55 -0
- package/build/animated/value.js +12 -7
- package/build/app-registry/index.js +1 -1
- package/build/app-state/index.js +1 -1
- package/build/appearance/index.js +1 -1
- package/build/back-handler/index.js +1 -1
- package/build/commit.js +1 -5
- package/build/debug.d.ts +1 -1
- package/build/debug.js +10 -3
- package/build/dimensions/index.js +1 -1
- package/build/events/index.js +0 -2
- package/build/i18n-manager/index.js +1 -1
- package/build/image-loader.js +1 -1
- package/build/index.d.ts +4 -2
- package/build/index.js +3 -2
- package/build/keyboard/index.js +1 -1
- package/build/layout-animation/index.js +1 -2
- package/build/linking/shared.js +1 -1
- package/build/permissions-android/index.js +1 -1
- package/build/platform/shared.js +1 -1
- package/build/process-font-variant.js +0 -1
- package/build/process-transform/index.js +13 -19
- package/build/report-error.d.ts +5 -0
- package/build/report-error.js +60 -0
- package/build/settings/index.js +1 -1
- package/build/share/index.android.js +1 -1
- package/build/share/index.ios.js +1 -1
- package/build/status-bar/index.android.js +6 -7
- package/build/status-bar/index.ios.js +1 -1
- package/build/style-registry/index.js +95 -16
- package/build/style-sheet/index.js +5 -3
- package/build/surface.js +2 -1
- package/build/toast-android/index.js +1 -1
- package/build/vibration/index.android.js +2 -3
- package/build/vibration/shared.js +1 -1
- package/package.json +3 -2
- package/build/animated/interpolation-node.d.ts +0 -15
- package/build/animated/interpolation-node.js +0 -58
- package/build/image-loader/index.d.ts +0 -24
- package/build/image-loader/index.js +0 -175
- package/build/image-source-resolver/index.d.ts +0 -9
- package/build/image-source-resolver/index.js +0 -16
- package/build/native-modules.d.ts +0 -18
- package/build/native-modules.js +0 -120
- 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
|
@@ -11,11 +11,27 @@
|
|
|
11
11
|
// can write EITHER `class="sectionLabel"` OR `class="section-label"` - resolveOne below
|
|
12
12
|
// falls back to the kebab->camel form on a miss, since authors don't reliably write the
|
|
13
13
|
// exact camelCase key.
|
|
14
|
-
// Compound lookup tries every ordering of 2-4 space-separated class parts
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// Compound lookup tries every ordering of 2-4 space-separated class parts (e.g. "btn primary"
|
|
15
|
+
// -> "btnPrimary" / "primaryBtn"), mirroring CSS compound-selector registration
|
|
16
|
+
// (`.btn.primary { }`), and layers whatever it finds ON TOP of the per-class merge.
|
|
17
17
|
const COMPOUND_MIN_PARTS = 2;
|
|
18
18
|
const COMPOUND_MAX_PARTS = 4;
|
|
19
|
+
// A scope suffix as the `<style scoped>` compilers emit it: `card__data-v-1a2b3c4d` (Vue) or
|
|
20
|
+
// `card__svelte-1a2b3c4d` (Svelte), the hash being css-parser's base36 hashFilePath. Matched by
|
|
21
|
+
// SHAPE, never by "there is a `__` somewhere in the name" — a BEM class (`card__title`) must
|
|
22
|
+
// never be read as a scoped `card`, which would silently merge the block's styles into the
|
|
23
|
+
// element's.
|
|
24
|
+
const SCOPE_SEPARATOR = '__';
|
|
25
|
+
const SCOPE_SUFFIX_PATTERN = /^(?:data-v|svelte)-[0-9a-z]+$/;
|
|
26
|
+
function splitScopedToken(token) {
|
|
27
|
+
const separator = token.lastIndexOf(SCOPE_SEPARATOR);
|
|
28
|
+
if (separator <= 0)
|
|
29
|
+
return null;
|
|
30
|
+
const scope = token.slice(separator + SCOPE_SEPARATOR.length);
|
|
31
|
+
if (!SCOPE_SUFFIX_PATTERN.test(scope))
|
|
32
|
+
return null;
|
|
33
|
+
return { base: token.slice(0, separator), scope };
|
|
34
|
+
}
|
|
19
35
|
const globalStyles = new Map();
|
|
20
36
|
// Called by generated code from side-effect style imports. Last import wins on a
|
|
21
37
|
// name collision, matching CSS cascade behavior.
|
|
@@ -49,18 +65,28 @@ export function resolveClassName(className) {
|
|
|
49
65
|
const trimmed = className.trim();
|
|
50
66
|
if (!trimmed)
|
|
51
67
|
return {};
|
|
52
|
-
const
|
|
68
|
+
const parts = trimmed.split(/\s+/).filter(Boolean);
|
|
69
|
+
// A single token is nothing but the exact-match lookup, so it goes through resolveOne, which
|
|
70
|
+
// adds the scoped-token base layering below. A multi-token string still tries the whole string
|
|
71
|
+
// as one key first — `$style.card`-style output arrives pre-resolved and must not be split.
|
|
72
|
+
if (parts.length <= 1)
|
|
73
|
+
return resolveOne(trimmed);
|
|
74
|
+
const exactMatch = lookupKey(trimmed);
|
|
53
75
|
if (exactMatch)
|
|
54
76
|
return exactMatch;
|
|
55
|
-
const
|
|
77
|
+
const merged = parts.reduce((acc, cls) => {
|
|
78
|
+
return { ...acc, ...resolveOne(cls) };
|
|
79
|
+
}, {});
|
|
80
|
+
// A compound rule LAYERS OVER the single-class rules rather than replacing them, matching the
|
|
81
|
+
// cascade: `.card { padding: 8; background: white }` + `.card.big { padding: 16 }` on
|
|
82
|
+
// `class="card big"` is padding 16 AND background white. Returning the compound alone (what
|
|
83
|
+
// this did before) silently dropped every property the compound did not itself restate.
|
|
56
84
|
if (parts.length >= COMPOUND_MIN_PARTS && parts.length <= COMPOUND_MAX_PARTS) {
|
|
57
85
|
const compound = tryCompoundLookup(parts);
|
|
58
86
|
if (compound)
|
|
59
|
-
return compound;
|
|
87
|
+
return { ...merged, ...compound };
|
|
60
88
|
}
|
|
61
|
-
return
|
|
62
|
-
return { ...acc, ...resolveOne(cls) };
|
|
63
|
-
}, {});
|
|
89
|
+
return merged;
|
|
64
90
|
}
|
|
65
91
|
function generateCompoundPermutations(parts) {
|
|
66
92
|
if (parts.length < COMPOUND_MIN_PARTS)
|
|
@@ -73,13 +99,13 @@ function generateCompoundPermutations(parts) {
|
|
|
73
99
|
}
|
|
74
100
|
function generateKPermutations(parts, size) {
|
|
75
101
|
if (size === 0)
|
|
76
|
-
return [
|
|
102
|
+
return [[]];
|
|
77
103
|
if (parts.length === 0)
|
|
78
104
|
return [];
|
|
79
105
|
const result = [];
|
|
80
106
|
function helper(current, remaining, depth) {
|
|
81
107
|
if (depth === size) {
|
|
82
|
-
result.push(
|
|
108
|
+
result.push(current);
|
|
83
109
|
return;
|
|
84
110
|
}
|
|
85
111
|
for (let i = 0; i < remaining.length; i++) {
|
|
@@ -113,16 +139,69 @@ export function kebabToCamel(value) {
|
|
|
113
139
|
function tryCompoundLookup(parts) {
|
|
114
140
|
if (parts.length < COMPOUND_MIN_PARTS)
|
|
115
141
|
return null;
|
|
116
|
-
for (const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
for (const subset of generateCompoundPermutations(parts)) {
|
|
143
|
+
for (const key of compoundKeysFor(subset)) {
|
|
144
|
+
const style = globalStyles.get(key);
|
|
145
|
+
if (style)
|
|
146
|
+
return style;
|
|
147
|
+
}
|
|
120
148
|
}
|
|
121
149
|
return null;
|
|
122
150
|
}
|
|
151
|
+
function compoundKeysFor(subset) {
|
|
152
|
+
const scoped = scopedCompoundKey(subset);
|
|
153
|
+
return scoped === null ? [toCompoundKey(subset)] : [toCompoundKey(subset), scoped];
|
|
154
|
+
}
|
|
155
|
+
// The scope suffix is appended per TOKEN in the markup (`class="card__svelte-h big__svelte-h"`)
|
|
156
|
+
// but appears ONCE, at the end, in the registered key a compound rule produces
|
|
157
|
+
// (`.card.big` -> `cardBig__svelte-h`) — the compiler collapses the selector to one name and
|
|
158
|
+
// suffixes that. Those two operations do not commute, so the key built from the raw tokens
|
|
159
|
+
// (`card__svelte-hBig__svelte-h`) can never match and every scoped compound rule was dead.
|
|
160
|
+
// Rebuild the key the way registration did: strip the suffix, join the bases, re-append once.
|
|
161
|
+
//
|
|
162
|
+
// An UNSCOPED token in the subset contributes its own name and no scope, rather than aborting.
|
|
163
|
+
// That is the `:global()` case, and it is why a partial `:global()` reaches the markup it was
|
|
164
|
+
// written for: `.card :global(.reset)` registers the key `cardReset`, which the scoper suffixes
|
|
165
|
+
// as a whole (the escape hatch exempts the `reset` MARKUP TOKEN, not the collapsed key), while
|
|
166
|
+
// the element carries `card__svelte-h reset`. The same shape covers a class handed down from a
|
|
167
|
+
// parent component, which likewise arrives unsuffixed.
|
|
168
|
+
//
|
|
169
|
+
// Widening, stated so it stays deliberate: a fully-scoped `.card.reset` collapses to that same
|
|
170
|
+
// key, so an element carrying a FOREIGN `reset` now matches a rule the author scoped to their
|
|
171
|
+
// own. The key format cannot tell the two apart — telling them apart needs a registry indexed by
|
|
172
|
+
// token set, with per-token scope. The scopes still have to agree: two tokens from two different
|
|
173
|
+
// components have no single suffix to factor out and stay unmatched.
|
|
174
|
+
function scopedCompoundKey(subset) {
|
|
175
|
+
const bases = [];
|
|
176
|
+
let scope;
|
|
177
|
+
for (const token of subset) {
|
|
178
|
+
const split = splitScopedToken(token);
|
|
179
|
+
if (split === null) {
|
|
180
|
+
bases.push(token);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (scope !== undefined && split.scope !== scope)
|
|
184
|
+
return null;
|
|
185
|
+
scope = split.scope;
|
|
186
|
+
bases.push(split.base);
|
|
187
|
+
}
|
|
188
|
+
return scope === undefined ? null : toCompoundKey(bases) + SCOPE_SEPARATOR + scope;
|
|
189
|
+
}
|
|
190
|
+
// A scoped token layers over its own unscoped name: on the web the element carries BOTH classes
|
|
191
|
+
// (`class="card svelte-h"`), so a global `.card` in App.css still applies underneath the
|
|
192
|
+
// component's `<style>` rule. Rewriting `card` -> `card__svelte-h` here is how the scope is
|
|
193
|
+
// expressed instead of a second class, so the base has to be re-consulted explicitly or that
|
|
194
|
+
// global rule silently disappears the moment a component defines a class of the same name.
|
|
123
195
|
function resolveOne(name) {
|
|
124
196
|
const trimmed = name.trim();
|
|
125
197
|
if (!trimmed)
|
|
126
198
|
return {};
|
|
127
|
-
|
|
199
|
+
const scoped = lookupKey(trimmed);
|
|
200
|
+
const split = splitScopedToken(trimmed);
|
|
201
|
+
if (split === null)
|
|
202
|
+
return scoped ?? {};
|
|
203
|
+
return { ...lookupKey(split.base), ...scoped };
|
|
204
|
+
}
|
|
205
|
+
function lookupKey(name) {
|
|
206
|
+
return globalStyles.get(name) ?? globalStyles.get(kebabToCamel(name));
|
|
128
207
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// constants. The typed ViewStyle/TextStyle live next door in styles.ts, so `create`
|
|
6
6
|
// is typed against them like RN's, not left generic over a plain Record.
|
|
7
7
|
import { dlog } from '../debug.js';
|
|
8
|
-
import { getNativeModule } from '../native-modules.js';
|
|
8
|
+
import { getNativeModule } from '../native-modules/index.js';
|
|
9
9
|
import { flattenStyle } from '../style/index.js';
|
|
10
10
|
// RN's hairline factor: a "1 physical pixel" line is ~0.4 logical px, rounded to the
|
|
11
11
|
// nearest device pixel. Named so the 0.4 isn't a bare magic number (RN source uses
|
|
@@ -49,8 +49,10 @@ const absoluteFill = Object.freeze({
|
|
|
49
49
|
top: 0,
|
|
50
50
|
bottom: 0,
|
|
51
51
|
});
|
|
52
|
-
// RN's composeStyles:
|
|
53
|
-
// (which flatten later collapses, later keys winning).
|
|
52
|
+
// RN's composeStyles: a null/undefined side yields the other, else the pair [a, b]
|
|
53
|
+
// (which flatten later collapses, later keys winning). The test is null/undefined, NOT
|
|
54
|
+
// falsy - compose(0, y) returns [0, y], matching react-native's own
|
|
55
|
+
// src/private/styles/composeStyles.js, which branches on `== null`.
|
|
54
56
|
function compose(style1, style2) {
|
|
55
57
|
if (style1 === null || style1 === undefined)
|
|
56
58
|
return style2;
|
package/build/surface.js
CHANGED
|
@@ -62,6 +62,7 @@ export class SymbioteSurface {
|
|
|
62
62
|
}
|
|
63
63
|
export function createSurface(rootTag) {
|
|
64
64
|
installEventHandler();
|
|
65
|
+
const surface = new SymbioteSurface(rootTag);
|
|
65
66
|
dlog(`surface created root=${rootTag}`);
|
|
66
|
-
return
|
|
67
|
+
return surface;
|
|
67
68
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// showWithGravity(message: string, duration: number, gravity: number)
|
|
15
15
|
// showWithGravityAndOffset(message, duration, gravity, xOffset, yOffset)
|
|
16
16
|
import { dlog } from '../debug.js';
|
|
17
|
-
import { getNativeModule } from '../native-modules.js';
|
|
17
|
+
import { getNativeModule } from '../native-modules/index.js';
|
|
18
18
|
// The native module name RN registers this under. NOTE: this is the name the spec
|
|
19
19
|
// resolves via `TurboModuleRegistry.getEnforcing<Spec>('ToastAndroid')`, NOT the
|
|
20
20
|
// spec filename `INativeToastAndroid`. A module name is only provable on a real
|
|
@@ -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
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// the base vibration.ts re-exports the iOS build for web/headless. There is no runtime
|
|
17
17
|
// `Platform.OS` read; the filename is the selector. No Fabric view, pure JS->native.
|
|
18
18
|
import { dlog } from '../debug.js';
|
|
19
|
-
import { getNativeModule } from '../native-modules.js';
|
|
19
|
+
import { getNativeModule } from '../native-modules/index.js';
|
|
20
20
|
const VIBRATION_MODULE = 'Vibration';
|
|
21
21
|
// RN's _default_vibration_length: the single-buzz duration when no pattern is given,
|
|
22
22
|
// and the per-segment buzz length the iOS scheduler emits between waits.
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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
|
+
"license": "MIT",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"react-native": ">=0.86"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@symbiote-native/test-utils": "0.1.
|
|
36
|
+
"@symbiote-native/test-utils": "0.1.6"
|
|
36
37
|
},
|
|
37
38
|
"scripts": {
|
|
38
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,24 +0,0 @@
|
|
|
1
|
-
import { type IImageSourceProp } from '../image-source-resolver';
|
|
2
|
-
export type IImageSize = {
|
|
3
|
-
width: number;
|
|
4
|
-
height: number;
|
|
5
|
-
};
|
|
6
|
-
export type IImageCacheStatus = 'memory' | 'disk' | 'disk/memory';
|
|
7
|
-
type ISizeSuccess = (width: number, height: number) => void;
|
|
8
|
-
type ISizeFailure = (error: unknown) => void;
|
|
9
|
-
declare function getSize(uri: string, success?: ISizeSuccess, failure?: ISizeFailure): Promise<IImageSize>;
|
|
10
|
-
declare function getSizeWithHeaders(uri: string, headers: Record<string, string>, success?: ISizeSuccess, failure?: ISizeFailure): Promise<IImageSize>;
|
|
11
|
-
declare function prefetch(uri: string, callback?: (requestId: number) => void): Promise<boolean>;
|
|
12
|
-
declare function abortPrefetch(requestId: number): void;
|
|
13
|
-
declare function queryCache(uris: string[]): Promise<Record<string, IImageCacheStatus>>;
|
|
14
|
-
declare function resolveAssetSource(source: IImageSourceProp): unknown;
|
|
15
|
-
export type IImageStatics = {
|
|
16
|
-
getSize: typeof getSize;
|
|
17
|
-
getSizeWithHeaders: typeof getSizeWithHeaders;
|
|
18
|
-
prefetch: typeof prefetch;
|
|
19
|
-
abortPrefetch: typeof abortPrefetch;
|
|
20
|
-
queryCache: typeof queryCache;
|
|
21
|
-
resolveAssetSource: typeof resolveAssetSource;
|
|
22
|
-
};
|
|
23
|
-
export declare const imageStatics: IImageStatics;
|
|
24
|
-
export {};
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
// Image static methods (RN's Image.getSize / prefetch / queryCache / etc).
|
|
2
|
-
//
|
|
3
|
-
// These mirror RN's iOS Image statics (Libraries/Image/Image.ios.js), which delegate to the
|
|
4
|
-
// `ImageLoader` native (Turbo)Module declared in NativeImageLoaderIOS.js. The Android spec
|
|
5
|
-
// (NativeImageLoaderAndroid.js) registers under the SAME module name ('ImageLoader'), so this
|
|
6
|
-
// stays a flat, non-platform-split module - only the Android prefetch call signature differs (a
|
|
7
|
-
// second `requestId` arg), branched on Platform.OS below, not on module name. NOTE the asymmetry
|
|
8
|
-
// in the iOS spec: `getSize` resolves a `[width, height]` ARRAY, while `getSizeWithHeaders`
|
|
9
|
-
// resolves a `{width, height}` OBJECT, both are guarded below before reading. The native result
|
|
10
|
-
// crosses the I/O boundary as `unknown`; we never cast it, we narrow its shape.
|
|
11
|
-
//
|
|
12
|
-
// This is a stateful, native-bridge-touching imperative module (module-level ImageLoader cache +
|
|
13
|
-
// prefetch requestId counter) with no view of its own - it belongs in @symbiote-native/engine
|
|
14
|
-
// alongside Alert/Share, not in a view/render-*.ts file (whose contract is zero state / zero
|
|
15
|
-
// native bridge).
|
|
16
|
-
import { dlog } from '../debug.js';
|
|
17
|
-
import { resolveImageSource } from '../image-source-resolver.js';
|
|
18
|
-
import { getNativeModule } from '../native-modules.js';
|
|
19
|
-
import { Platform } from '../platform';
|
|
20
|
-
import { isNumber } from '../type-guards.js';
|
|
21
|
-
// The iOS native module name RN registers this under (NativeImageLoaderIOS.js resolves
|
|
22
|
-
// `TurboModuleRegistry.getEnforcing<Spec>('ImageLoader')`). A module name like this is only
|
|
23
|
-
// provable on a real host - a headless fake answers to any name - so this iOS name is
|
|
24
|
-
// device-verify-pending.
|
|
25
|
-
const IMAGE_LOADER_MODULE = 'ImageLoader';
|
|
26
|
-
let imageLoaderModule;
|
|
27
|
-
function getImageLoader() {
|
|
28
|
-
if (imageLoaderModule === undefined) {
|
|
29
|
-
imageLoaderModule = getNativeModule(IMAGE_LOADER_MODULE);
|
|
30
|
-
dlog(`Image: ImageLoader module ${imageLoaderModule ? 'resolved' : 'NOT resolved (null)'}`);
|
|
31
|
-
}
|
|
32
|
-
return imageLoaderModule;
|
|
33
|
-
}
|
|
34
|
-
// Narrow native's getSize result. The spec resolves a `[width, height]` array, but tolerate a
|
|
35
|
-
// `{width, height}` object too (getSizeWithHeaders uses that shape).
|
|
36
|
-
function toImageSize(result) {
|
|
37
|
-
if (Array.isArray(result) && isNumber(result[0]) && isNumber(result[1])) {
|
|
38
|
-
return { width: result[0], height: result[1] };
|
|
39
|
-
}
|
|
40
|
-
if (typeof result === 'object' && result !== null) {
|
|
41
|
-
const width = Reflect.get(result, 'width');
|
|
42
|
-
const height = Reflect.get(result, 'height');
|
|
43
|
-
if (isNumber(width) && isNumber(height))
|
|
44
|
-
return { width, height };
|
|
45
|
-
}
|
|
46
|
-
throw new Error(`Image: unexpected size result from native: ${JSON.stringify(result)}`);
|
|
47
|
-
}
|
|
48
|
-
function requireLoader(method) {
|
|
49
|
-
const loader = getImageLoader();
|
|
50
|
-
if (loader === null) {
|
|
51
|
-
throw new Error(`Image.${method}: ImageLoader native module is not available ` +
|
|
52
|
-
'(running headless or not linked on this host).');
|
|
53
|
-
}
|
|
54
|
-
return loader;
|
|
55
|
-
}
|
|
56
|
-
// Resolve image dimensions, optionally via success/failure callbacks. Always returns the Promise
|
|
57
|
-
// too (RN returns void when a callback is passed, but a promise-and-callback shape is friendlier
|
|
58
|
-
// and a strict superset).
|
|
59
|
-
function getSize(uri, success, failure) {
|
|
60
|
-
const promise = Promise.resolve()
|
|
61
|
-
.then(() => requireLoader('getSize').getSize(uri))
|
|
62
|
-
.then(toImageSize);
|
|
63
|
-
if (typeof success === 'function') {
|
|
64
|
-
promise
|
|
65
|
-
.then(size => success(size.width, size.height))
|
|
66
|
-
.catch((error) => {
|
|
67
|
-
if (typeof failure === 'function')
|
|
68
|
-
failure(error);
|
|
69
|
-
else
|
|
70
|
-
dlog(`Image.getSize failed for ${uri}: ${String(error)}`);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
return promise;
|
|
74
|
-
}
|
|
75
|
-
function getSizeWithHeaders(uri, headers, success, failure) {
|
|
76
|
-
const promise = Promise.resolve()
|
|
77
|
-
.then(() => requireLoader('getSizeWithHeaders').getSizeWithHeaders(uri, headers))
|
|
78
|
-
.then(toImageSize);
|
|
79
|
-
if (typeof success === 'function') {
|
|
80
|
-
promise
|
|
81
|
-
.then(size => success(size.width, size.height))
|
|
82
|
-
.catch((error) => {
|
|
83
|
-
if (typeof failure === 'function')
|
|
84
|
-
failure(error);
|
|
85
|
-
else
|
|
86
|
-
dlog(`Image.getSizeWithHeaders failed for ${uri}: ${String(error)}`);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return promise;
|
|
90
|
-
}
|
|
91
|
-
// Android keys an in-flight prefetch by a monotonic requestId (so abortRequest can cancel it);
|
|
92
|
-
// RN's Image.android.js generates the same way. iOS ignores the arg.
|
|
93
|
-
let prefetchRequestId = 0;
|
|
94
|
-
// Download a remote image into the disk cache. Resolves to whether it succeeded. `callback`
|
|
95
|
-
// receives the requestId (RN's Image.android.js shape) so the caller can later pass it to
|
|
96
|
-
// abortPrefetch.
|
|
97
|
-
async function prefetch(uri, callback) {
|
|
98
|
-
prefetchRequestId += 1;
|
|
99
|
-
const requestId = prefetchRequestId;
|
|
100
|
-
if (typeof callback === 'function')
|
|
101
|
-
callback(requestId);
|
|
102
|
-
const loader = requireLoader('prefetch');
|
|
103
|
-
return (Promise.resolve()
|
|
104
|
-
// Android's prefetchImage keys an abortable request on requestId; iOS takes ONLY the uri and
|
|
105
|
-
// throws on an extra arg (bridgeless TurboModule arg-count check). Match RN's per-platform call.
|
|
106
|
-
.then(() => Platform.OS === 'android'
|
|
107
|
-
? loader.prefetchImage(uri, requestId)
|
|
108
|
-
: loader.prefetchImage(uri))
|
|
109
|
-
.then(result => result === true)
|
|
110
|
-
.catch((error) => {
|
|
111
|
-
dlog(`Image.prefetch failed for ${uri}: ${String(error)}`);
|
|
112
|
-
throw error;
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
// Cancel an in-flight prefetch by the requestId prefetch handed back. Android only (mirrors
|
|
116
|
-
// Image.android.js -> NativeImageLoaderAndroid.abortRequest); a missing abortRequest (iOS,
|
|
117
|
-
// headless) is a no-op rather than a throw.
|
|
118
|
-
function abortPrefetch(requestId) {
|
|
119
|
-
const loader = getImageLoader();
|
|
120
|
-
if (loader === null || typeof loader.abortRequest !== 'function') {
|
|
121
|
-
dlog(`Image.abortPrefetch(${requestId}): no abortRequest on this host, ignoring`);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
loader.abortRequest(requestId);
|
|
125
|
-
}
|
|
126
|
-
// Narrow native's queryCache result: an object mapping each known uri to its cache status.
|
|
127
|
-
// Unknown statuses are dropped rather than trusted blindly.
|
|
128
|
-
const CACHE_STATUS = {
|
|
129
|
-
memory: 'memory',
|
|
130
|
-
disk: 'disk',
|
|
131
|
-
'disk/memory': 'disk/memory',
|
|
132
|
-
};
|
|
133
|
-
function toCacheRecord(result) {
|
|
134
|
-
const record = {};
|
|
135
|
-
if (typeof result !== 'object' || result === null)
|
|
136
|
-
return record;
|
|
137
|
-
for (const key of Object.keys(result)) {
|
|
138
|
-
const value = Reflect.get(result, key);
|
|
139
|
-
if (typeof value === 'string' && Object.hasOwn(CACHE_STATUS, value)) {
|
|
140
|
-
record[key] = CACHE_STATUS[value];
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return record;
|
|
144
|
-
}
|
|
145
|
-
async function queryCache(uris) {
|
|
146
|
-
return Promise.resolve()
|
|
147
|
-
.then(() => {
|
|
148
|
-
const loader = requireLoader('queryCache');
|
|
149
|
-
// The native queryCache never rejects (RCTImageLoader resolves getImageCacheStatus), so a
|
|
150
|
-
// rejection here is a JS/native boundary fault: log whether the method is even callable and
|
|
151
|
-
// the arg shape, to tell "not a function" (interop gap) from a marshalling reject.
|
|
152
|
-
dlog(`Image.queryCache: typeof loader.queryCache=${typeof loader.queryCache} uris=${uris.length}`);
|
|
153
|
-
return loader.queryCache(uris);
|
|
154
|
-
})
|
|
155
|
-
.then(toCacheRecord)
|
|
156
|
-
.catch((error) => {
|
|
157
|
-
dlog(`Image.queryCache failed: ${String(error)}`);
|
|
158
|
-
throw error;
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
// PURE JS: run the currently-installed source resolver (the same machinery the Image component
|
|
162
|
-
// uses via resolveImageSource). RN's resolveAssetSource turns a require() asset id into
|
|
163
|
-
// {uri, scale, ...}; the app injects the real one with setImageSourceResolver, and this exposes
|
|
164
|
-
// its output to callers directly.
|
|
165
|
-
function resolveAssetSource(source) {
|
|
166
|
-
return resolveImageSource(source);
|
|
167
|
-
}
|
|
168
|
-
export const imageStatics = {
|
|
169
|
-
getSize,
|
|
170
|
-
getSizeWithHeaders,
|
|
171
|
-
prefetch,
|
|
172
|
-
abortPrefetch,
|
|
173
|
-
queryCache,
|
|
174
|
-
resolveAssetSource,
|
|
175
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare function setImageSourceResolver(resolve: (source: unknown) => unknown): void;
|
|
2
|
-
export declare function resolveImageSource(source: unknown): unknown;
|
|
3
|
-
export type IImageSource = {
|
|
4
|
-
uri?: string;
|
|
5
|
-
scale?: number;
|
|
6
|
-
width?: number;
|
|
7
|
-
height?: number;
|
|
8
|
-
};
|
|
9
|
-
export type IImageSourceProp = IImageSource | IImageSource[] | number;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// Image source resolution seam: require('./x.png') asset ids and {uri} sources are resolved by
|
|
2
|
-
// RN's own resolveAssetSource before reaching the shared render fn. The actual resolution is
|
|
3
|
-
// RN-platform-specific, so it is injected here rather than imported, mirroring platform-color.ts's
|
|
4
|
-
// processColor seam - this keeps @symbiote-native/components free of a react-native dependency (and
|
|
5
|
-
// the headless harness working). BOTH the pure renderImage view (@symbiote-native/components) and
|
|
6
|
-
// this package's own image-loader statics (resolveAssetSource) call resolveImageSource; neither
|
|
7
|
-
// reaches into the mutable resolver directly.
|
|
8
|
-
let sourceResolver = source => source;
|
|
9
|
-
export function setImageSourceResolver(resolve) {
|
|
10
|
-
sourceResolver = resolve;
|
|
11
|
-
}
|
|
12
|
-
// Public mirror of RN's Image.resolveAssetSource: run a source through the injected resolver.
|
|
13
|
-
// Headless (no resolver wired) it is the identity, so smokes see the input unchanged.
|
|
14
|
-
export function resolveImageSource(source) {
|
|
15
|
-
return sourceResolver(source);
|
|
16
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { NativeEventEmitter } from './native-events';
|
|
2
|
-
declare global {
|
|
3
|
-
var __turboModuleProxy: (<T>(name: string) => T | null) | undefined;
|
|
4
|
-
var nativeModuleProxy: Record<string, unknown> | undefined;
|
|
5
|
-
}
|
|
6
|
-
export declare function getNativeModule<T>(name: string): T | null;
|
|
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>;
|