embedded-react 0.3.0 → 0.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.
- package/aot/compile.mjs +2407 -697
- package/aot/screenshot-smoke.mjs +34 -17
- package/aot/style-map.mjs +156 -80
- package/assets/bake-font.mjs +45 -21
- package/assets/bake-image.mjs +7 -5
- package/assets/bake-svg.mjs +563 -0
- package/assets/build-builtin-font.mjs +25 -12
- package/assets/emit-c.mjs +52 -20
- package/assets/emit-container.mjs +5 -3
- package/assets/emit-pack.mjs +8 -2
- package/assets/index.mjs +25 -16
- package/assets/rasterize.mjs +45 -11
- package/assets/svg-loader.mjs +81 -0
- package/build.mjs +43 -20
- package/cli.mjs +134 -52
- package/pack-container.mjs +84 -35
- package/package.json +8 -3
- package/persist-transform.mjs +23 -9
- package/qjsc-wasm.mjs +19 -8
- package/sim/embedded-react.wasm +0 -0
- package/sim-server.mjs +160 -48
- package/src/embedded-react/Animated.js +51 -36
- package/src/embedded-react/AppRegistry.js +4 -4
- package/src/embedded-react/Easing.js +1 -1
- package/src/embedded-react/LayoutAnimation.js +13 -6
- package/src/embedded-react/StyleSheet.js +1 -1
- package/src/embedded-react/imperative.js +19 -7
- package/src/embedded-react/index.js +8 -8
- package/src/embedded-react/layout-anim-config.js +13 -9
- package/src/embedded-react/split-style.js +6 -6
- package/src/embedded-react/svg-ops.js +369 -41
- package/src/embedded-react/usePersistentState.js +3 -3
- package/src/host-config.js +137 -18
- package/src/native-ui.js +3 -1
- package/src/props.js +22 -10
- package/src/renderer.js +3 -3
package/qjsc-wasm.mjs
CHANGED
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
// bytecode container with NO native toolchain. The module is built with `-sENVIRONMENT=web,node`, so the
|
|
20
20
|
// same artifact that powers `dev` loads under Node here.
|
|
21
21
|
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
22
|
+
import {createRequire} from 'node:module';
|
|
23
|
+
import {dirname, resolve} from 'node:path';
|
|
24
|
+
import {fileURLToPath} from 'node:url';
|
|
25
|
+
import {existsSync} from 'node:fs';
|
|
26
26
|
|
|
27
27
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
28
28
|
|
|
@@ -33,12 +33,17 @@ const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
33
33
|
* @param {string} [simDir] Dir holding embedded-react.{js,wasm} (defaults to the package's sim/).
|
|
34
34
|
* @returns {Promise<Buffer>} The bytecode bytes.
|
|
35
35
|
*/
|
|
36
|
-
export async function compileToBytecode(
|
|
36
|
+
export async function compileToBytecode(
|
|
37
|
+
jsSource,
|
|
38
|
+
simDir = resolve(HERE, 'sim'),
|
|
39
|
+
) {
|
|
37
40
|
// The .cjs (not .js): this package is "type": "module", so Node would load the emscripten .js as ESM and
|
|
38
41
|
// its CommonJS factory export would never run. The .cjs is the same module forced to CommonJS for Node.
|
|
39
42
|
const loader = resolve(simDir, 'embedded-react.cjs');
|
|
40
43
|
if (!existsSync(loader)) {
|
|
41
|
-
throw new Error(
|
|
44
|
+
throw new Error(
|
|
45
|
+
`prebuilt simulator module not found at ${loader} (a published package ships it; from source run tools/web-sim/build.mjs)`,
|
|
46
|
+
);
|
|
42
47
|
}
|
|
43
48
|
const require = createRequire(import.meta.url);
|
|
44
49
|
const factory = require(loader); // forced CommonJS → MODULARIZE factory
|
|
@@ -53,7 +58,11 @@ export async function compileToBytecode(jsSource, simDir = resolve(HERE, 'sim'))
|
|
|
53
58
|
Module.HEAPU8[srcPtr + bytes.length] = 0;
|
|
54
59
|
const outLenPtr = Module._malloc(4);
|
|
55
60
|
|
|
56
|
-
const compile = Module.cwrap('er_web_compile_bytecode', 'number', [
|
|
61
|
+
const compile = Module.cwrap('er_web_compile_bytecode', 'number', [
|
|
62
|
+
'number',
|
|
63
|
+
'number',
|
|
64
|
+
'number',
|
|
65
|
+
]);
|
|
57
66
|
const bcPtr = compile(srcPtr, bytes.length, outLenPtr);
|
|
58
67
|
|
|
59
68
|
// ALLOW_MEMORY_GROWTH=1 can detach views across the malloc/compile calls — read through a FRESH buffer.
|
|
@@ -64,7 +73,9 @@ export async function compileToBytecode(jsSource, simDir = resolve(HERE, 'sim'))
|
|
|
64
73
|
Module._free(outLenPtr);
|
|
65
74
|
if (!bcPtr || !bcLen) {
|
|
66
75
|
if (bcPtr) Module._free(bcPtr);
|
|
67
|
-
throw new Error(
|
|
76
|
+
throw new Error(
|
|
77
|
+
'bytecode compile failed — check the bundle for a syntax error (see stderr above)',
|
|
78
|
+
);
|
|
68
79
|
}
|
|
69
80
|
const out = Buffer.from(heap().subarray(bcPtr, bcPtr + bcLen)); // copy out before freeing
|
|
70
81
|
Module._free(bcPtr);
|
package/sim/embedded-react.wasm
CHANGED
|
Binary file
|
package/sim-server.mjs
CHANGED
|
@@ -23,18 +23,27 @@
|
|
|
23
23
|
// Used by the consumer CLI (cli.mjs → `npx embedded-react dev` / `export`) and the repo dev loop
|
|
24
24
|
// (tools/web-sim/dev.mjs). The only differences are paths, passed in here.
|
|
25
25
|
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
const HERE = dirname(
|
|
26
|
+
import {createServer} from 'node:http';
|
|
27
|
+
import {readFile} from 'node:fs/promises';
|
|
28
|
+
import {existsSync, readFileSync, statSync} from 'node:fs';
|
|
29
|
+
import {createRequire} from 'node:module';
|
|
30
|
+
import {pathToFileURL} from 'node:url';
|
|
31
|
+
import {basename, dirname, extname, relative, resolve} from 'node:path';
|
|
32
|
+
|
|
33
|
+
const HERE = dirname(
|
|
34
|
+
new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'),
|
|
35
|
+
);
|
|
34
36
|
const require = createRequire(import.meta.url);
|
|
35
37
|
const esbuild = require('esbuild');
|
|
36
|
-
const {
|
|
37
|
-
|
|
38
|
+
const {bakeAssetPack} = await import(
|
|
39
|
+
pathToFileURL(resolve(HERE, 'assets/index.mjs')).href
|
|
40
|
+
);
|
|
41
|
+
const {registerSvgVectorLoader} = await import(
|
|
42
|
+
pathToFileURL(resolve(HERE, 'assets/svg-loader.mjs')).href
|
|
43
|
+
);
|
|
44
|
+
const {transformPersist, shouldPersist} = await import(
|
|
45
|
+
pathToFileURL(resolve(HERE, 'persist-transform.mjs')).href
|
|
46
|
+
);
|
|
38
47
|
|
|
39
48
|
const MIME = {
|
|
40
49
|
'.html': 'text/html; charset=utf-8',
|
|
@@ -42,8 +51,8 @@ const MIME = {
|
|
|
42
51
|
'.wasm': 'application/wasm',
|
|
43
52
|
'.pack': 'application/octet-stream',
|
|
44
53
|
};
|
|
45
|
-
const assetName =
|
|
46
|
-
const mtime =
|
|
54
|
+
const assetName = p => basename(p).replace(/\.[^.]+$/, '');
|
|
55
|
+
const mtime = p => {
|
|
47
56
|
try {
|
|
48
57
|
return statSync(p).mtimeMs;
|
|
49
58
|
} catch {
|
|
@@ -62,7 +71,15 @@ const mtime = (p) => {
|
|
|
62
71
|
* @param {() => void} [o.onRebuilt] Called after each successful build + asset bake (e.g. broadcast reload).
|
|
63
72
|
* @returns {{ options: object, bundlePath: string, packPath: string }}
|
|
64
73
|
*/
|
|
65
|
-
function createBundle({
|
|
74
|
+
function createBundle({
|
|
75
|
+
entry,
|
|
76
|
+
projectRoot,
|
|
77
|
+
libSrc,
|
|
78
|
+
nodePaths,
|
|
79
|
+
outDir,
|
|
80
|
+
persist = true,
|
|
81
|
+
onRebuilt,
|
|
82
|
+
}) {
|
|
66
83
|
const bundlePath = resolve(outDir, 'app.js');
|
|
67
84
|
const packPath = resolve(outDir, 'assets.pack');
|
|
68
85
|
const projNorm = projectRoot.replace(/\\/g, '/');
|
|
@@ -73,33 +90,62 @@ function createBundle({ entry, projectRoot, libSrc, nodePaths, outDir, persist =
|
|
|
73
90
|
async function bakePack() {
|
|
74
91
|
const discoveredSizes = [
|
|
75
92
|
...new Set(
|
|
76
|
-
[
|
|
93
|
+
[
|
|
94
|
+
...readFileSync(bundlePath, 'utf8').matchAll(
|
|
95
|
+
/\bfontSize\s*:\s*(\d+(?:\.\d+)?)/g,
|
|
96
|
+
),
|
|
97
|
+
].map(m => Math.round(Number(m[1]))),
|
|
77
98
|
),
|
|
78
99
|
].sort((a, b) => a - b);
|
|
79
100
|
|
|
80
101
|
let cfg = {};
|
|
81
102
|
const cp = resolve(projectRoot, 'assets.config.js');
|
|
82
|
-
if (existsSync(cp))
|
|
103
|
+
if (existsSync(cp))
|
|
104
|
+
cfg =
|
|
105
|
+
(await import(`${pathToFileURL(cp).href}?t=${mtime(cp)}`)).default ||
|
|
106
|
+
{};
|
|
83
107
|
const fontConfig = cfg.fonts || {};
|
|
84
108
|
|
|
85
109
|
const fontJobs = [...fonts.entries()].map(([family, path]) => {
|
|
86
110
|
const fc = fontConfig[family] || {};
|
|
87
|
-
const sizes =
|
|
88
|
-
|
|
111
|
+
const sizes =
|
|
112
|
+
fc.sizes && fc.sizes.length
|
|
113
|
+
? fc.sizes
|
|
114
|
+
: discoveredSizes.length
|
|
115
|
+
? discoveredSizes
|
|
116
|
+
: [16];
|
|
117
|
+
return {
|
|
118
|
+
path,
|
|
119
|
+
family,
|
|
120
|
+
sizes,
|
|
121
|
+
bpp: fc.bpp ?? 4,
|
|
122
|
+
glyphs: fc.glyphs ?? 'ascii',
|
|
123
|
+
};
|
|
89
124
|
});
|
|
90
|
-
const imageJobs = [...images.entries()].map(([name, path]) => ({
|
|
125
|
+
const imageJobs = [...images.entries()].map(([name, path]) => ({
|
|
126
|
+
path,
|
|
127
|
+
name,
|
|
128
|
+
}));
|
|
91
129
|
|
|
92
130
|
const sig = JSON.stringify({
|
|
93
|
-
i: imageJobs.map(
|
|
94
|
-
f: fontJobs
|
|
131
|
+
i: imageJobs.map(j => [j.name, j.path, mtime(j.path)]).sort(),
|
|
132
|
+
f: fontJobs
|
|
133
|
+
.map(j => [j.family, j.path, mtime(j.path), j.sizes, j.bpp, j.glyphs])
|
|
134
|
+
.sort(),
|
|
95
135
|
});
|
|
96
136
|
if (sig === lastAssetSig) return;
|
|
97
137
|
lastAssetSig = sig;
|
|
98
138
|
if (!imageJobs.length && !fontJobs.length) return; // built-in font only → no pack
|
|
99
139
|
|
|
100
140
|
try {
|
|
101
|
-
const s = bakeAssetPack({
|
|
102
|
-
|
|
141
|
+
const s = bakeAssetPack({
|
|
142
|
+
images: imageJobs,
|
|
143
|
+
fonts: fontJobs,
|
|
144
|
+
outPath: packPath,
|
|
145
|
+
});
|
|
146
|
+
console.log(
|
|
147
|
+
` assets → ${s.images} image(s), ${s.fonts} font size(s), ${s.bytes} B`,
|
|
148
|
+
);
|
|
103
149
|
} catch (e) {
|
|
104
150
|
console.error(` asset bake failed: ${e.message}`);
|
|
105
151
|
}
|
|
@@ -113,9 +159,9 @@ function createBundle({ entry, projectRoot, libSrc, nodePaths, outDir, persist =
|
|
|
113
159
|
platform: 'neutral',
|
|
114
160
|
target: 'es2020',
|
|
115
161
|
jsx: 'automatic',
|
|
116
|
-
alias: {
|
|
162
|
+
alias: {'embedded-react': libSrc},
|
|
117
163
|
nodePaths,
|
|
118
|
-
define: {
|
|
164
|
+
define: {'process.env.NODE_ENV': '"production"'},
|
|
119
165
|
legalComments: 'none',
|
|
120
166
|
logLevel: 'silent',
|
|
121
167
|
plugins: [
|
|
@@ -126,25 +172,38 @@ function createBundle({ entry, projectRoot, libSrc, nodePaths, outDir, persist =
|
|
|
126
172
|
images.clear();
|
|
127
173
|
fonts.clear();
|
|
128
174
|
});
|
|
129
|
-
b.onLoad({
|
|
175
|
+
b.onLoad({filter: /\.(jsx?|tsx?)$/}, a => {
|
|
130
176
|
// Transform ONLY the app's own source — never dependencies (see shouldPersist: excluding
|
|
131
177
|
// node_modules is what stops the library's usePersistentState being rewritten to call itself).
|
|
132
178
|
if (!persist || !shouldPersist(a.path, projNorm)) return undefined;
|
|
133
179
|
try {
|
|
134
|
-
return {
|
|
180
|
+
return {
|
|
181
|
+
contents: transformPersist(
|
|
182
|
+
readFileSync(a.path, 'utf8'),
|
|
183
|
+
relative(projectRoot, a.path).replace(/\\/g, '/'),
|
|
184
|
+
),
|
|
185
|
+
loader: 'jsx',
|
|
186
|
+
};
|
|
135
187
|
} catch (e) {
|
|
136
|
-
return {
|
|
188
|
+
return {errors: [{text: `persist transform: ${e.message}`}]};
|
|
137
189
|
}
|
|
138
190
|
});
|
|
139
|
-
b.onLoad({
|
|
191
|
+
b.onLoad({filter: /\.(png|jpe?g|webp|gif|bmp)$/i}, a => {
|
|
140
192
|
images.set(assetName(a.path), a.path);
|
|
141
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
contents: `module.exports = ${JSON.stringify(assetName(a.path))};`,
|
|
195
|
+
loader: 'js',
|
|
196
|
+
};
|
|
142
197
|
});
|
|
143
|
-
b.onLoad({
|
|
198
|
+
b.onLoad({filter: /\.(ttf|otf)$/i}, a => {
|
|
144
199
|
fonts.set(assetName(a.path), a.path);
|
|
145
|
-
return {
|
|
200
|
+
return {
|
|
201
|
+
contents: `module.exports = ${JSON.stringify(assetName(a.path))};`,
|
|
202
|
+
loader: 'js',
|
|
203
|
+
};
|
|
146
204
|
});
|
|
147
|
-
b
|
|
205
|
+
registerSvgVectorLoader(b, (name, p) => images.set(name, p)); // raster-fallback SVGs join the image pack
|
|
206
|
+
b.onEnd(async r => {
|
|
148
207
|
if (r.errors.length) {
|
|
149
208
|
console.error(`✗ build failed (${r.errors.length} error(s))`);
|
|
150
209
|
return;
|
|
@@ -157,15 +216,28 @@ function createBundle({ entry, projectRoot, libSrc, nodePaths, outDir, persist =
|
|
|
157
216
|
],
|
|
158
217
|
};
|
|
159
218
|
|
|
160
|
-
return {
|
|
219
|
+
return {options, bundlePath, packPath};
|
|
161
220
|
}
|
|
162
221
|
|
|
163
222
|
/**
|
|
164
223
|
* One-shot bundle + asset bake into outDir (app.js [+ assets.pack]). For the static export.
|
|
165
224
|
* Persist transform is off (a static page has no hot reload to preserve state across).
|
|
166
225
|
*/
|
|
167
|
-
export async function buildApp({
|
|
168
|
-
|
|
226
|
+
export async function buildApp({
|
|
227
|
+
entry,
|
|
228
|
+
projectRoot,
|
|
229
|
+
libSrc,
|
|
230
|
+
nodePaths,
|
|
231
|
+
outDir,
|
|
232
|
+
}) {
|
|
233
|
+
const {options} = createBundle({
|
|
234
|
+
entry,
|
|
235
|
+
projectRoot,
|
|
236
|
+
libSrc,
|
|
237
|
+
nodePaths,
|
|
238
|
+
outDir,
|
|
239
|
+
persist: false,
|
|
240
|
+
});
|
|
169
241
|
await esbuild.build(options);
|
|
170
242
|
}
|
|
171
243
|
|
|
@@ -177,16 +249,34 @@ export async function buildApp({ entry, projectRoot, libSrc, nodePaths, outDir }
|
|
|
177
249
|
* @param {string[]} o.nodePaths
|
|
178
250
|
* @param {number} o.port
|
|
179
251
|
*/
|
|
180
|
-
export async function runDevServer({
|
|
252
|
+
export async function runDevServer({
|
|
253
|
+
entry,
|
|
254
|
+
projectRoot,
|
|
255
|
+
libSrc,
|
|
256
|
+
nodePaths,
|
|
257
|
+
indexHtml,
|
|
258
|
+
simDir,
|
|
259
|
+
outDir,
|
|
260
|
+
port,
|
|
261
|
+
label,
|
|
262
|
+
}) {
|
|
181
263
|
const clients = new Set();
|
|
182
264
|
const broadcastReload = () => {
|
|
183
265
|
for (const res of clients) res.write('data: reload\n\n');
|
|
184
266
|
};
|
|
185
267
|
|
|
186
|
-
const {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
268
|
+
const {options} = createBundle({
|
|
269
|
+
entry,
|
|
270
|
+
projectRoot,
|
|
271
|
+
libSrc,
|
|
272
|
+
nodePaths,
|
|
273
|
+
outDir,
|
|
274
|
+
persist: true,
|
|
275
|
+
onRebuilt: () => {
|
|
276
|
+
console.log('↻ rebuilt → reloading');
|
|
277
|
+
broadcastReload();
|
|
278
|
+
},
|
|
279
|
+
});
|
|
190
280
|
|
|
191
281
|
const ctx = await esbuild.context(options);
|
|
192
282
|
await ctx.rebuild();
|
|
@@ -196,15 +286,26 @@ export async function runDevServer({ entry, projectRoot, libSrc, nodePaths, inde
|
|
|
196
286
|
// outDir. The page references ./public/<name>. window.__erHot is injected so the page connects to /reload.
|
|
197
287
|
const send = async (res, file) => {
|
|
198
288
|
try {
|
|
199
|
-
res
|
|
289
|
+
res
|
|
290
|
+
.writeHead(200, {
|
|
291
|
+
'content-type': MIME[extname(file)] || 'application/octet-stream',
|
|
292
|
+
'cache-control': 'no-store',
|
|
293
|
+
})
|
|
294
|
+
.end(await readFile(file));
|
|
200
295
|
} catch {
|
|
201
296
|
res.writeHead(404).end('not found');
|
|
202
297
|
}
|
|
203
298
|
};
|
|
204
299
|
const server = createServer(async (req, res) => {
|
|
205
|
-
const path = decodeURIComponent(
|
|
300
|
+
const path = decodeURIComponent(
|
|
301
|
+
new URL(req.url, 'http://localhost').pathname,
|
|
302
|
+
);
|
|
206
303
|
if (path === '/reload') {
|
|
207
|
-
res.writeHead(200, {
|
|
304
|
+
res.writeHead(200, {
|
|
305
|
+
'content-type': 'text/event-stream',
|
|
306
|
+
'cache-control': 'no-cache',
|
|
307
|
+
connection: 'keep-alive',
|
|
308
|
+
});
|
|
208
309
|
res.write('retry: 1000\n\n');
|
|
209
310
|
clients.add(res);
|
|
210
311
|
req.on('close', () => clients.delete(res));
|
|
@@ -212,8 +313,16 @@ export async function runDevServer({ entry, projectRoot, libSrc, nodePaths, inde
|
|
|
212
313
|
}
|
|
213
314
|
if (path === '/' || path === '/index.html') {
|
|
214
315
|
try {
|
|
215
|
-
const html = (await readFile(indexHtml, 'utf8')).replace(
|
|
216
|
-
|
|
316
|
+
const html = (await readFile(indexHtml, 'utf8')).replace(
|
|
317
|
+
'</head>',
|
|
318
|
+
'<script>window.__erHot=true</script></head>',
|
|
319
|
+
);
|
|
320
|
+
res
|
|
321
|
+
.writeHead(200, {
|
|
322
|
+
'content-type': MIME['.html'],
|
|
323
|
+
'cache-control': 'no-store',
|
|
324
|
+
})
|
|
325
|
+
.end(html);
|
|
217
326
|
} catch {
|
|
218
327
|
res.writeHead(404).end('not found');
|
|
219
328
|
}
|
|
@@ -221,13 +330,16 @@ export async function runDevServer({ entry, projectRoot, libSrc, nodePaths, inde
|
|
|
221
330
|
}
|
|
222
331
|
if (path.startsWith('/public/')) {
|
|
223
332
|
const name = basename(path); // flat filename only
|
|
224
|
-
const dir =
|
|
333
|
+
const dir =
|
|
334
|
+
name === 'embedded-react.js' || name === 'embedded-react.wasm'
|
|
335
|
+
? simDir
|
|
336
|
+
: outDir;
|
|
225
337
|
return void send(res, resolve(dir, name));
|
|
226
338
|
}
|
|
227
339
|
res.writeHead(404).end('not found');
|
|
228
340
|
});
|
|
229
341
|
|
|
230
|
-
await new Promise(
|
|
342
|
+
await new Promise(r => server.listen(port, r));
|
|
231
343
|
console.log(`embedded-react WASM sim → http://localhost:${port}/`);
|
|
232
344
|
console.log(`watching ${label} — edit & save to hot-reload. Ctrl-C to quit.`);
|
|
233
345
|
|
|
@@ -240,5 +352,5 @@ export async function runDevServer({ entry, projectRoot, libSrc, nodePaths, inde
|
|
|
240
352
|
};
|
|
241
353
|
process.on('SIGINT', shutdown);
|
|
242
354
|
process.on('SIGTERM', shutdown);
|
|
243
|
-
return {
|
|
355
|
+
return {ctx, server};
|
|
244
356
|
}
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
// Animated — the React Native analog, backed by the engine's native-driver value system
|
|
18
18
|
// (er_anim_value_*). An Animated.Value is a handle to an engine-side float; binding it to a node
|
|
19
19
|
// prop (via Animated.View) lets the engine advance the animation each frame with NO per-frame JS.
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
20
|
+
import {createElement, useRef, useEffect} from 'react';
|
|
21
|
+
import {NativeUI} from '../native-ui.js';
|
|
22
|
+
import {splitAnimatedStyle} from './split-style.js';
|
|
23
23
|
|
|
24
24
|
/** A standalone animatable value bound to an engine-side float. */
|
|
25
25
|
export class AnimatedValue {
|
|
@@ -74,7 +74,12 @@ export class AnimatedInterpolation {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
__bind(node, prop) {
|
|
77
|
-
NativeUI.animValueBindInterpolated(
|
|
77
|
+
NativeUI.animValueBindInterpolated(
|
|
78
|
+
this._parent._handle,
|
|
79
|
+
node,
|
|
80
|
+
prop,
|
|
81
|
+
this._config,
|
|
82
|
+
);
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
interpolate(config) {
|
|
@@ -86,7 +91,7 @@ export class AnimatedInterpolation {
|
|
|
86
91
|
/** Wraps fn so it runs at most once, regardless of how many times it is invoked. */
|
|
87
92
|
function once(fn) {
|
|
88
93
|
let called = false;
|
|
89
|
-
return
|
|
94
|
+
return arg => {
|
|
90
95
|
if (called) return;
|
|
91
96
|
called = true;
|
|
92
97
|
if (fn) fn(arg);
|
|
@@ -107,9 +112,9 @@ function makeAnimation(value, toValue, config) {
|
|
|
107
112
|
_value: value,
|
|
108
113
|
start(onComplete) {
|
|
109
114
|
// Always pass a wrapper so we can null out the (possibly recycled) handle on completion.
|
|
110
|
-
const cb =
|
|
115
|
+
const cb = finished => {
|
|
111
116
|
handle = 0;
|
|
112
|
-
if (onComplete) onComplete({
|
|
117
|
+
if (onComplete) onComplete({finished: !!finished});
|
|
113
118
|
};
|
|
114
119
|
handle = NativeUI.animValueAnimate(value._handle, toValue, config, cb);
|
|
115
120
|
},
|
|
@@ -120,19 +125,19 @@ function makeAnimation(value, toValue, config) {
|
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
export function timing(value, config = {}) {
|
|
123
|
-
const {
|
|
124
|
-
return makeAnimation(value, toValue, {
|
|
128
|
+
const {toValue = 0, useNativeDriver, ...rest} = config;
|
|
129
|
+
return makeAnimation(value, toValue, {type: 'timing', ...rest});
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
export function spring(value, config = {}) {
|
|
128
|
-
const {
|
|
129
|
-
return makeAnimation(value, toValue, {
|
|
133
|
+
const {toValue = 0, useNativeDriver, ...rest} = config;
|
|
134
|
+
return makeAnimation(value, toValue, {type: 'spring', ...rest});
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
export function decay(value, config = {}) {
|
|
133
|
-
const {
|
|
138
|
+
const {useNativeDriver, ...rest} = config;
|
|
134
139
|
// Decay coasts from its velocity; the engine ignores the target, so pass the current value.
|
|
135
|
-
return makeAnimation(value, value.__getValue(), {
|
|
140
|
+
return makeAnimation(value, value.__getValue(), {type: 'decay', ...rest});
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
// --- Composition -------------------------------------------------------------------------------
|
|
@@ -152,22 +157,23 @@ export function sequence(animations) {
|
|
|
152
157
|
current = 0;
|
|
153
158
|
stopped = false;
|
|
154
159
|
const done = once(onComplete);
|
|
155
|
-
const next =
|
|
160
|
+
const next = result => {
|
|
156
161
|
if (stopped || !result || result.finished === false) {
|
|
157
|
-
done({
|
|
162
|
+
done({finished: false});
|
|
158
163
|
return;
|
|
159
164
|
}
|
|
160
165
|
if (current >= animations.length) {
|
|
161
|
-
done({
|
|
166
|
+
done({finished: true});
|
|
162
167
|
return;
|
|
163
168
|
}
|
|
164
169
|
animations[current++].start(next);
|
|
165
170
|
};
|
|
166
|
-
next({
|
|
171
|
+
next({finished: true}); // kick off the first entry
|
|
167
172
|
},
|
|
168
173
|
stop() {
|
|
169
174
|
stopped = true;
|
|
170
|
-
if (current > 0 && current <= animations.length)
|
|
175
|
+
if (current > 0 && current <= animations.length)
|
|
176
|
+
animations[current - 1].stop();
|
|
171
177
|
},
|
|
172
178
|
};
|
|
173
179
|
}
|
|
@@ -181,12 +187,12 @@ export function parallel(animations, config) {
|
|
|
181
187
|
const done = once(onComplete);
|
|
182
188
|
const total = animations.length;
|
|
183
189
|
if (total === 0) {
|
|
184
|
-
done({
|
|
190
|
+
done({finished: true});
|
|
185
191
|
return;
|
|
186
192
|
}
|
|
187
193
|
let doneCount = 0;
|
|
188
194
|
let anyUnfinished = false;
|
|
189
|
-
const onChild =
|
|
195
|
+
const onChild = result => {
|
|
190
196
|
doneCount++;
|
|
191
197
|
if (!result || result.finished === false) {
|
|
192
198
|
anyUnfinished = true;
|
|
@@ -195,7 +201,7 @@ export function parallel(animations, config) {
|
|
|
195
201
|
for (const a of animations) a.stop();
|
|
196
202
|
}
|
|
197
203
|
}
|
|
198
|
-
if (doneCount >= total) done({
|
|
204
|
+
if (doneCount >= total) done({finished: !anyUnfinished});
|
|
199
205
|
};
|
|
200
206
|
for (const a of animations) a.start(onChild);
|
|
201
207
|
},
|
|
@@ -215,15 +221,15 @@ export function stagger(delay, animations) {
|
|
|
215
221
|
const done = once(onComplete);
|
|
216
222
|
const total = animations.length;
|
|
217
223
|
if (total === 0) {
|
|
218
|
-
done({
|
|
224
|
+
done({finished: true});
|
|
219
225
|
return;
|
|
220
226
|
}
|
|
221
227
|
let doneCount = 0;
|
|
222
228
|
let anyUnfinished = false;
|
|
223
|
-
const onChild =
|
|
229
|
+
const onChild = result => {
|
|
224
230
|
doneCount++;
|
|
225
231
|
if (!result || result.finished === false) anyUnfinished = true;
|
|
226
|
-
if (doneCount >= total) done({
|
|
232
|
+
if (doneCount >= total) done({finished: !anyUnfinished});
|
|
227
233
|
};
|
|
228
234
|
animations.forEach((a, i) => {
|
|
229
235
|
if (i === 0) {
|
|
@@ -253,7 +259,7 @@ export function delay(time) {
|
|
|
253
259
|
const done = once(onComplete);
|
|
254
260
|
timer = setTimeout(() => {
|
|
255
261
|
timer = 0;
|
|
256
|
-
done({
|
|
262
|
+
done({finished: true});
|
|
257
263
|
}, time);
|
|
258
264
|
},
|
|
259
265
|
stop() {
|
|
@@ -271,33 +277,42 @@ export function delay(time) {
|
|
|
271
277
|
* (resetBeforeIteration), matching RN — so a timing 0→1 repeats 0→1 rather than ping-ponging.
|
|
272
278
|
*/
|
|
273
279
|
export function loop(animation, config) {
|
|
274
|
-
const iterations =
|
|
280
|
+
const iterations =
|
|
281
|
+
config && Number.isInteger(config.iterations) ? config.iterations : -1;
|
|
275
282
|
const resetBeforeIteration = !config || config.resetBeforeIteration !== false;
|
|
276
283
|
let stopped = false;
|
|
277
284
|
return {
|
|
278
285
|
_value: animation._value,
|
|
279
286
|
start(onComplete) {
|
|
280
287
|
const done = once(onComplete);
|
|
281
|
-
const startValue =
|
|
288
|
+
const startValue =
|
|
289
|
+
resetBeforeIteration && animation._value
|
|
290
|
+
? animation._value.__getValue()
|
|
291
|
+
: null;
|
|
282
292
|
let count = 0;
|
|
283
293
|
const startIteration = () => {
|
|
284
294
|
if (stopped) {
|
|
285
|
-
done({
|
|
295
|
+
done({finished: false});
|
|
286
296
|
return;
|
|
287
297
|
}
|
|
288
298
|
if (iterations >= 0 && count >= iterations) {
|
|
289
|
-
done({
|
|
299
|
+
done({finished: true});
|
|
290
300
|
return;
|
|
291
301
|
}
|
|
292
302
|
count++;
|
|
293
|
-
if (
|
|
303
|
+
if (
|
|
304
|
+
resetBeforeIteration &&
|
|
305
|
+
animation._value &&
|
|
306
|
+
startValue != null &&
|
|
307
|
+
count > 1
|
|
308
|
+
) {
|
|
294
309
|
animation._value.setValue(startValue);
|
|
295
310
|
}
|
|
296
311
|
animation.start(onIterationDone);
|
|
297
312
|
};
|
|
298
|
-
const onIterationDone =
|
|
313
|
+
const onIterationDone = result => {
|
|
299
314
|
if (stopped || !result || result.finished === false) {
|
|
300
|
-
done({
|
|
315
|
+
done({finished: false});
|
|
301
316
|
return;
|
|
302
317
|
}
|
|
303
318
|
// Defer the next iteration to a fresh task instead of starting it inline. A child animation can
|
|
@@ -343,13 +358,13 @@ export function useAnimatedValue(initial = 0) {
|
|
|
343
358
|
*/
|
|
344
359
|
export function createAnimatedComponent(Component) {
|
|
345
360
|
return function AnimatedComponent(props) {
|
|
346
|
-
const {
|
|
347
|
-
const {
|
|
348
|
-
const ref =
|
|
361
|
+
const {style, ...rest} = props;
|
|
362
|
+
const {staticStyle, bindings} = splitAnimatedStyle(style);
|
|
363
|
+
const ref = node => {
|
|
349
364
|
if (node == null) return; // unmount
|
|
350
365
|
for (const b of bindings) b.value.__bind(node, b.prop);
|
|
351
366
|
};
|
|
352
|
-
return createElement(Component, {
|
|
367
|
+
return createElement(Component, {...rest, style: staticStyle, ref});
|
|
353
368
|
};
|
|
354
369
|
}
|
|
355
370
|
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
// "running the bundle IS starting the app", so registerComponent mounts immediately into a root
|
|
22
22
|
// sized from the host-injected `screen` global. (A host-driven runApplication can be split out
|
|
23
23
|
// later when the C host owns app lifecycle.)
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
24
|
+
import {createElement} from 'react';
|
|
25
|
+
import {createRoot} from '../renderer.js';
|
|
26
26
|
|
|
27
27
|
let registered = null;
|
|
28
28
|
|
|
@@ -32,7 +32,7 @@ export const AppRegistry = {
|
|
|
32
32
|
* the component (RN signature), so the component module isn't evaluated until registration.
|
|
33
33
|
*/
|
|
34
34
|
registerComponent(appKey, componentProvider) {
|
|
35
|
-
registered = {
|
|
35
|
+
registered = {appKey, Component: componentProvider()};
|
|
36
36
|
AppRegistry.runApplication(appKey);
|
|
37
37
|
return appKey;
|
|
38
38
|
},
|
|
@@ -43,7 +43,7 @@ export const AppRegistry = {
|
|
|
43
43
|
runApplication(appKey) {
|
|
44
44
|
if (!registered) return;
|
|
45
45
|
if (appKey && appKey !== registered.appKey) return;
|
|
46
|
-
const root = createRoot({
|
|
46
|
+
const root = createRoot({width: screen.width, height: screen.height});
|
|
47
47
|
root.render(createElement(registered.Component));
|
|
48
48
|
},
|
|
49
49
|
};
|