react-native-impeller-platform 0.0.0-bootstrap.0 → 0.1.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 +164 -1
- package/package.json +47 -3
- package/react-native.config.js +24 -0
- package/src/bin/engine-path.js +12 -0
- package/src/cli/index.js +20 -0
- package/src/cli/init.js +112 -0
- package/src/cli/run.js +73 -0
- package/src/externaltexture/index.js +58 -0
- package/src/index.js +12 -0
- package/src/metro-shims/index.js +382 -0
- package/src/metro-shims/shims/Alert.impeller.js +89 -0
- package/src/metro-shims/shims/BaseViewConfig.impeller.js +81 -0
- package/src/metro-shims/shims/Button.impeller.js +100 -0
- package/src/metro-shims/shims/Image.impeller.js +97 -0
- package/src/metro-shims/shims/Platform.impeller-as-ios.js +44 -0
- package/src/metro-shims/shims/Platform.impeller.js +53 -0
- package/src/metro-shims/shims/ReactNativeFeatureFlags.impeller.js +40 -0
- package/src/metro-shims/shims/ReactNativeStyleAttributes.impeller.js +20 -0
- package/src/metro-shims/shims/TextInputState.impeller.js +100 -0
- package/src/metro-shims/shims/ViewConfigIgnore.impeller.js +55 -0
- package/src/metro-shims/shims/reanimated/clock.js +41 -0
- package/src/metro-shims/shims/reanimated/component.js +83 -0
- package/src/metro-shims/shims/reanimated/css.js +193 -0
- package/src/metro-shims/shims/reanimated/easing.js +47 -0
- package/src/metro-shims/shims/reanimated/hooks.js +191 -0
- package/src/metro-shims/shims/reanimated/interpolate.js +133 -0
- package/src/metro-shims/shims/reanimated/values.js +264 -0
- package/src/metro-shims/shims/reanimated.impeller.js +90 -0
- package/src/platformview/index.js +23 -0
- package/src/resolve.js +238 -0
- package/src/selftest.js +123 -0
- package/src/storage/index.js +161 -0
- package/src/viewshot/index.js +98 -0
- package/src/worldraster/index.js +75 -0
- package/templates/app/README.md.tmpl +18 -0
- package/templates/app/_gitignore +1 -0
- package/templates/app/impeller.config.json.tmpl +11 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// SC-26. CSS animations and transitions as react-native-css hands them to an
|
|
3
|
+
// Animated component (Reanimated 4's style props): animationName is a
|
|
4
|
+
// keyframes object {'from'|'to'|'0.5'|'75%, to': style}, durations and delays
|
|
5
|
+
// are ms, lists cycle per CSS. Evaluated on every render of the component at
|
|
6
|
+
// clock.now(); `active` says whether another frame is needed.
|
|
7
|
+
const {mix} = require('./interpolate');
|
|
8
|
+
const {toEasing} = require('./easing');
|
|
9
|
+
|
|
10
|
+
const CSS_KEYS = ['animationName', 'animationDuration', 'animationDelay', 'animationIterationCount',
|
|
11
|
+
'animationDirection', 'animationFillMode', 'animationPlayState', 'animationTimingFunction',
|
|
12
|
+
'transitionProperty', 'transitionDuration', 'transitionDelay', 'transitionTimingFunction',
|
|
13
|
+
'transitionBehavior'];
|
|
14
|
+
|
|
15
|
+
const list = (v) => (v === undefined ? [] : Array.isArray(v) ? v : [v]);
|
|
16
|
+
const nth = (v, i, d) => {
|
|
17
|
+
const l = list(v);
|
|
18
|
+
return l.length ? l[i % l.length] : d;
|
|
19
|
+
};
|
|
20
|
+
const same = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
21
|
+
|
|
22
|
+
// Split a flattened style into the paintable style and the CSS motion props.
|
|
23
|
+
function split(style) {
|
|
24
|
+
const base = {};
|
|
25
|
+
const css = {};
|
|
26
|
+
let any = false;
|
|
27
|
+
for (const k of Object.keys(style || {})) {
|
|
28
|
+
if (CSS_KEYS.includes(k)) {
|
|
29
|
+
css[k] = style[k];
|
|
30
|
+
any = true;
|
|
31
|
+
} else {
|
|
32
|
+
base[k] = style[k];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {base, css: any ? css : null};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function offsets(key) {
|
|
39
|
+
return String(key).split(',').map((s) => {
|
|
40
|
+
const t = s.trim();
|
|
41
|
+
if (t === 'from') return 0;
|
|
42
|
+
if (t === 'to') return 1;
|
|
43
|
+
return t.endsWith('%') ? parseFloat(t) / 100 : parseFloat(t);
|
|
44
|
+
}).filter((n) => n >= 0 && n <= 1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// keyframes object -> {prop: [{o, v, ease}]} sorted by offset.
|
|
48
|
+
function compile(frames) {
|
|
49
|
+
const tracks = {};
|
|
50
|
+
for (const key of Object.keys(frames || {})) {
|
|
51
|
+
const decl = frames[key] || {};
|
|
52
|
+
const ease = decl.animationTimingFunction;
|
|
53
|
+
for (const o of offsets(key)) {
|
|
54
|
+
for (const p of Object.keys(decl)) {
|
|
55
|
+
if (p === 'animationTimingFunction') continue;
|
|
56
|
+
(tracks[p] = tracks[p] || []).push({o, v: decl[p], ease});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const p of Object.keys(tracks)) tracks[p].sort((a, b) => a.o - b.o);
|
|
61
|
+
return tracks;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function neutral(prop, base) {
|
|
65
|
+
if (base[prop] !== undefined) return base[prop];
|
|
66
|
+
if (prop === 'opacity') return 1;
|
|
67
|
+
if (prop === 'transform') return [];
|
|
68
|
+
if (/color$/i.test(prop)) return 'transparent';
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sample(track, prop, base, p, ease) {
|
|
73
|
+
const pts = track.slice();
|
|
74
|
+
if (pts[0].o > 0) pts.unshift({o: 0, v: neutral(prop, base)});
|
|
75
|
+
if (pts[pts.length - 1].o < 1) pts.push({o: 1, v: neutral(prop, base)});
|
|
76
|
+
let i = 0;
|
|
77
|
+
while (i < pts.length - 2 && p > pts[i + 1].o) i++;
|
|
78
|
+
const a = pts[i];
|
|
79
|
+
const b = pts[i + 1];
|
|
80
|
+
const span = b.o - a.o;
|
|
81
|
+
const local = span <= 0 ? 1 : Math.min(1, Math.max(0, (p - a.o) / span));
|
|
82
|
+
const e = a.ease !== undefined ? toEasing(a.ease) : ease;
|
|
83
|
+
return mix(a.v, b.v, e(local));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Iteration progress of animation i at time t, or null when it has no effect.
|
|
87
|
+
function progress(st, css, i, t) {
|
|
88
|
+
const duration = nth(css.animationDuration, i, 0);
|
|
89
|
+
const delay = nth(css.animationDelay, i, 0);
|
|
90
|
+
const countRaw = nth(css.animationIterationCount, i, 1);
|
|
91
|
+
const count = countRaw === 'infinite' ? Infinity : Number(countRaw);
|
|
92
|
+
const dir = nth(css.animationDirection, i, 'normal');
|
|
93
|
+
const fill = nth(css.animationFillMode, i, 'none');
|
|
94
|
+
const active = ((st.pausedAt != null ? st.pausedAt : t) - st.origin) - delay;
|
|
95
|
+
const flip = (n, f) => {
|
|
96
|
+
const rev = dir === 'reverse' || (dir === 'alternate' && n % 2 === 1) || (dir === 'alternate-reverse' && n % 2 === 0);
|
|
97
|
+
return rev ? 1 - f : f;
|
|
98
|
+
};
|
|
99
|
+
if (active < 0) {
|
|
100
|
+
return {p: fill === 'backwards' || fill === 'both' ? flip(0, 0) : null, running: st.pausedAt == null};
|
|
101
|
+
}
|
|
102
|
+
const total = duration * count;
|
|
103
|
+
if (duration <= 0 || active >= total) {
|
|
104
|
+
if (fill !== 'forwards' && fill !== 'both') return {p: null, running: false};
|
|
105
|
+
const n = Math.max(0, Math.ceil(count) - 1);
|
|
106
|
+
const end = count % 1 === 0 || !isFinite(count) ? 1 : count % 1;
|
|
107
|
+
return {p: flip(n, end), running: false};
|
|
108
|
+
}
|
|
109
|
+
const n = Math.floor(active / duration);
|
|
110
|
+
return {p: flip(n, (active - n * duration) / duration), running: st.pausedAt == null};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function animations(state, base, css, out, t) {
|
|
114
|
+
const names = list(css.animationName);
|
|
115
|
+
const next = [];
|
|
116
|
+
let running = false;
|
|
117
|
+
names.forEach((frames, i) => {
|
|
118
|
+
if (!frames || typeof frames !== 'object') return;
|
|
119
|
+
const key = JSON.stringify(frames) + '|' + nth(css.animationDuration, i, 0);
|
|
120
|
+
let st = state.anims.find((s) => s.key === key && !next.includes(s));
|
|
121
|
+
if (!st) st = {key, tracks: compile(frames), origin: t, pausedAt: null};
|
|
122
|
+
const paused = nth(css.animationPlayState, i, 'running') === 'paused';
|
|
123
|
+
if (paused && st.pausedAt == null) st.pausedAt = t;
|
|
124
|
+
if (!paused && st.pausedAt != null) {
|
|
125
|
+
st.origin += t - st.pausedAt;
|
|
126
|
+
st.pausedAt = null;
|
|
127
|
+
}
|
|
128
|
+
next.push(st);
|
|
129
|
+
const r = progress(st, css, i, t);
|
|
130
|
+
running = running || r.running;
|
|
131
|
+
if (r.p == null) return;
|
|
132
|
+
const ease = toEasing(nth(css.animationTimingFunction, i, 'ease'));
|
|
133
|
+
for (const prop of Object.keys(st.tracks)) out[prop] = sample(st.tracks[prop], prop, base, r.p, ease);
|
|
134
|
+
});
|
|
135
|
+
state.anims = next;
|
|
136
|
+
return running;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function transitions(state, base, css, out, t) {
|
|
140
|
+
const props = list(css && css.transitionProperty);
|
|
141
|
+
const covered = (p) => {
|
|
142
|
+
const i = props.indexOf(p);
|
|
143
|
+
if (i >= 0) return i;
|
|
144
|
+
const all = props.indexOf('all');
|
|
145
|
+
return all >= 0 ? all : -1;
|
|
146
|
+
};
|
|
147
|
+
let running = false;
|
|
148
|
+
const keys = new Set([...Object.keys(base), ...Object.keys(state.last || {})]);
|
|
149
|
+
for (const p of keys) {
|
|
150
|
+
const target = base[p];
|
|
151
|
+
const prev = state.last ? state.last[p] : undefined;
|
|
152
|
+
const i = props.length ? covered(p) : -1;
|
|
153
|
+
if (state.last && i >= 0 && !same(prev, target)) {
|
|
154
|
+
const duration = nth(css.transitionDuration, i, 0);
|
|
155
|
+
const delay = nth(css.transitionDelay, i, 0);
|
|
156
|
+
if (duration > 0) {
|
|
157
|
+
const from = state.shown[p] !== undefined ? state.shown[p] : prev !== undefined ? prev : neutral(p, {});
|
|
158
|
+
state.tx[p] = {from, to: target, start: t + delay, duration, ease: toEasing(nth(css.transitionTimingFunction, i, 'ease'))};
|
|
159
|
+
} else {
|
|
160
|
+
delete state.tx[p];
|
|
161
|
+
}
|
|
162
|
+
} else if (i < 0) {
|
|
163
|
+
delete state.tx[p];
|
|
164
|
+
}
|
|
165
|
+
const tx = state.tx[p];
|
|
166
|
+
if (!tx) continue;
|
|
167
|
+
const f = (t - tx.start) / tx.duration;
|
|
168
|
+
if (f >= 1) {
|
|
169
|
+
delete state.tx[p];
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
running = true;
|
|
173
|
+
const to = tx.to !== undefined ? tx.to : neutral(p, {});
|
|
174
|
+
out[p] = f <= 0 ? tx.from : mix(tx.from, to, tx.ease(f));
|
|
175
|
+
}
|
|
176
|
+
state.last = base;
|
|
177
|
+
return running;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function newState() {
|
|
181
|
+
return {anims: [], tx: {}, last: null, shown: {}};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// The style to paint now, and whether the next frame differs.
|
|
185
|
+
function evaluate(state, base, css, t) {
|
|
186
|
+
const out = {...base};
|
|
187
|
+
const runT = transitions(state, base, css || {}, out, t);
|
|
188
|
+
const runA = css ? animations(state, base, css, out, t) : (state.anims = [], false);
|
|
189
|
+
state.shown = out;
|
|
190
|
+
return {style: out, active: runT || runA};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {split, evaluate, newState};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// SC-26. Easing for the reanimated stub: RN's own Easing (the same curves
|
|
3
|
+
// Reanimated ships) plus steps, and the CSS timing keywords react-native-css
|
|
4
|
+
// passes through animation/transition timing functions.
|
|
5
|
+
const {Easing: RNEasing} = require('react-native');
|
|
6
|
+
|
|
7
|
+
function steps(n = 10, roundToNextStep = true) {
|
|
8
|
+
const count = Math.max(1, Math.floor(n));
|
|
9
|
+
return (t) => {
|
|
10
|
+
const v = Math.min(Math.max(t, 0), 1) * count;
|
|
11
|
+
return (roundToNextStep ? Math.ceil(v) : Math.floor(v)) / count;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Easing = {...RNEasing, steps};
|
|
16
|
+
|
|
17
|
+
const CSS = {
|
|
18
|
+
linear: RNEasing.linear,
|
|
19
|
+
ease: RNEasing.bezier(0.25, 0.1, 0.25, 1),
|
|
20
|
+
'ease-in': RNEasing.bezier(0.42, 0, 1, 1),
|
|
21
|
+
'ease-out': RNEasing.bezier(0, 0, 0.58, 1),
|
|
22
|
+
'ease-in-out': RNEasing.bezier(0.42, 0, 0.58, 1),
|
|
23
|
+
'step-start': steps(1, true),
|
|
24
|
+
'step-end': steps(1, false),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// CSS cubic-bezier(): react-native-css calls this and puts the result in the
|
|
28
|
+
// style's timing function.
|
|
29
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
30
|
+
return RNEasing.bezier(x1, y1, x2, y2);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// CSS steps(n, jump-start|start|jump-end|end).
|
|
34
|
+
function cssSteps(n, position) {
|
|
35
|
+
return steps(n, position === 'start' || position === 'jump-start');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A timing function as it arrives in a style or an animation config: a
|
|
39
|
+
// function, Reanimated's {factory} form, or a CSS keyword (default ease).
|
|
40
|
+
function toEasing(fn, fallback = CSS.ease) {
|
|
41
|
+
if (typeof fn === 'function') return fn;
|
|
42
|
+
if (fn && typeof fn.factory === 'function') return fn.factory();
|
|
43
|
+
if (typeof fn === 'string' && CSS[fn]) return CSS[fn];
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {Easing, cubicBezier, cssSteps, toEasing};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// SC-26. Reanimated hooks for the stub. With no worklet plugin and no UI
|
|
3
|
+
// thread, an "updater" (useAnimatedStyle / useAnimatedProps) is a JS function
|
|
4
|
+
// re-run whenever a shared value it read changes; Animated components
|
|
5
|
+
// (component.js) subscribe to it and re-render.
|
|
6
|
+
const React = require('react');
|
|
7
|
+
const {onFrame, now} = require('./clock');
|
|
8
|
+
const {makeMutable, isAnimation, track} = require('./values');
|
|
9
|
+
|
|
10
|
+
const HANDLE = Symbol.for('rni.reanimated.handle');
|
|
11
|
+
|
|
12
|
+
function depsChanged(prev, next) {
|
|
13
|
+
if (!next || !prev || prev.length !== next.length) return true;
|
|
14
|
+
return next.some((d, i) => !Object.is(d, prev[i]));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class Updater {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.subs = new Set();
|
|
20
|
+
this.unsubs = [];
|
|
21
|
+
this.inline = new Map();
|
|
22
|
+
this.raw = {};
|
|
23
|
+
}
|
|
24
|
+
run(fn) {
|
|
25
|
+
const {result, deps} = track(fn);
|
|
26
|
+
this.unsubs.forEach((u) => u());
|
|
27
|
+
this.unsubs = Array.from(deps, (sv) => sv._subscribe(() => {
|
|
28
|
+
this.run(fn);
|
|
29
|
+
this.notify();
|
|
30
|
+
}));
|
|
31
|
+
this.raw = result || {};
|
|
32
|
+
this.startInline(this.raw, '');
|
|
33
|
+
}
|
|
34
|
+
// Inline animations, e.g. {opacity: withTiming(1)}: each key path gets its
|
|
35
|
+
// own shared value, restarted every time the updater runs.
|
|
36
|
+
startInline(obj, prefix) {
|
|
37
|
+
for (const k of Object.keys(obj)) {
|
|
38
|
+
const v = obj[k];
|
|
39
|
+
const path = prefix + k;
|
|
40
|
+
if (isAnimation(v)) {
|
|
41
|
+
let sv = this.inline.get(path);
|
|
42
|
+
if (!sv) {
|
|
43
|
+
sv = makeMutable(v.toValue !== undefined ? v.toValue : 0);
|
|
44
|
+
sv._subscribe(() => this.notify());
|
|
45
|
+
this.inline.set(path, sv);
|
|
46
|
+
}
|
|
47
|
+
sv.value = v;
|
|
48
|
+
} else if (k === 'transform' && Array.isArray(v)) {
|
|
49
|
+
v.forEach((e, i) => e && typeof e === 'object' && this.startInline(e, `${path}.${i}.`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
resolve(obj, prefix) {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const k of Object.keys(obj)) {
|
|
56
|
+
const v = obj[k];
|
|
57
|
+
const path = prefix + k;
|
|
58
|
+
if (isAnimation(v)) out[k] = this.inline.get(path).value;
|
|
59
|
+
else if (k === 'transform' && Array.isArray(v)) out[k] = v.map((e, i) => (e && typeof e === 'object' ? this.resolve(e, `${path}.${i}.`) : e));
|
|
60
|
+
else out[k] = v;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
current() {
|
|
65
|
+
return this.resolve(this.raw, '');
|
|
66
|
+
}
|
|
67
|
+
notify() {
|
|
68
|
+
for (const fn of Array.from(this.subs)) fn();
|
|
69
|
+
}
|
|
70
|
+
subscribe(fn) {
|
|
71
|
+
this.subs.add(fn);
|
|
72
|
+
return () => this.subs.delete(fn);
|
|
73
|
+
}
|
|
74
|
+
dispose() {
|
|
75
|
+
this.unsubs.forEach((u) => u());
|
|
76
|
+
this.unsubs = [];
|
|
77
|
+
for (const sv of this.inline.values()) sv._cancel();
|
|
78
|
+
this.subs.clear();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The object handed to `style`: the current values (so a plain View still
|
|
83
|
+
// paints them) carrying a hidden handle Animated components subscribe to.
|
|
84
|
+
function snapshot(u) {
|
|
85
|
+
return Object.defineProperty(u.current(), HANDLE, {value: u, enumerable: false});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function useUpdater(fn, deps) {
|
|
89
|
+
const ref = React.useRef(null);
|
|
90
|
+
if (!ref.current) ref.current = {u: new Updater(), deps: undefined, ran: false};
|
|
91
|
+
const s = ref.current;
|
|
92
|
+
if (!s.ran || depsChanged(s.deps, deps)) {
|
|
93
|
+
s.ran = true;
|
|
94
|
+
s.deps = deps;
|
|
95
|
+
s.u.run(fn);
|
|
96
|
+
}
|
|
97
|
+
React.useEffect(() => () => s.u.dispose(), [s]);
|
|
98
|
+
return snapshot(s.u);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const useAnimatedStyle = (fn, deps) => useUpdater(fn, deps);
|
|
102
|
+
const useAnimatedProps = (fn, deps) => useUpdater(fn, deps);
|
|
103
|
+
|
|
104
|
+
function useSharedValue(initial) {
|
|
105
|
+
const ref = React.useRef(null);
|
|
106
|
+
if (!ref.current) ref.current = makeMutable(initial);
|
|
107
|
+
const sv = ref.current;
|
|
108
|
+
React.useEffect(() => () => sv._cancel(), [sv]);
|
|
109
|
+
return sv;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Re-run `fn` (tracked) whenever deps change or a shared value it read changes.
|
|
113
|
+
function useTracked(fn, deps, onResult) {
|
|
114
|
+
const ref = React.useRef(null);
|
|
115
|
+
if (!ref.current) ref.current = {unsubs: [], deps: undefined, ran: false};
|
|
116
|
+
const s = ref.current;
|
|
117
|
+
s.fn = fn;
|
|
118
|
+
s.onResult = onResult;
|
|
119
|
+
const run = () => {
|
|
120
|
+
const {result, deps: seen} = track(() => s.fn());
|
|
121
|
+
s.unsubs.forEach((u) => u());
|
|
122
|
+
s.unsubs = Array.from(seen, (sv) => sv._subscribe(run));
|
|
123
|
+
s.onResult(result);
|
|
124
|
+
};
|
|
125
|
+
if (!s.ran || depsChanged(s.deps, deps)) {
|
|
126
|
+
s.ran = true;
|
|
127
|
+
s.deps = deps;
|
|
128
|
+
run();
|
|
129
|
+
}
|
|
130
|
+
React.useEffect(() => () => s.unsubs.forEach((u) => u()), [s]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function useDerivedValue(fn, deps) {
|
|
134
|
+
const sv = useSharedValue(undefined);
|
|
135
|
+
useTracked(fn, deps, (v) => {
|
|
136
|
+
sv.value = v;
|
|
137
|
+
});
|
|
138
|
+
return sv;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function useAnimatedReaction(prepare, react, deps) {
|
|
142
|
+
const prev = React.useRef(null);
|
|
143
|
+
useTracked(prepare, deps, (v) => {
|
|
144
|
+
const p = prev.current;
|
|
145
|
+
prev.current = v;
|
|
146
|
+
if (!Object.is(p, v)) react(v, p);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function useFrameCallback(callback, autostart = true) {
|
|
151
|
+
const cb = React.useRef(callback);
|
|
152
|
+
cb.current = callback;
|
|
153
|
+
const ref = React.useRef(null);
|
|
154
|
+
if (!ref.current) {
|
|
155
|
+
const fc = {isActive: false, callbackId: Math.floor(Math.random() * 1e9), stop: null};
|
|
156
|
+
fc.setActive = (on) => {
|
|
157
|
+
if (on === fc.isActive) return;
|
|
158
|
+
fc.isActive = on;
|
|
159
|
+
if (!on) return fc.stop && fc.stop();
|
|
160
|
+
let first = null;
|
|
161
|
+
let last = null;
|
|
162
|
+
fc.stop = onFrame((t) => {
|
|
163
|
+
if (first == null) first = t;
|
|
164
|
+
cb.current({timestamp: t, timeSincePreviousFrame: last == null ? null : t - last, timeSinceFirstFrame: t - first});
|
|
165
|
+
last = t;
|
|
166
|
+
return fc.isActive;
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
ref.current = fc;
|
|
170
|
+
}
|
|
171
|
+
const fc = ref.current;
|
|
172
|
+
React.useEffect(() => {
|
|
173
|
+
if (autostart) fc.setActive(true);
|
|
174
|
+
return () => fc.setActive(false);
|
|
175
|
+
}, [fc, autostart]);
|
|
176
|
+
return fc;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function useAnimatedScrollHandler(handlers) {
|
|
180
|
+
return React.useCallback((e) => {
|
|
181
|
+
const ev = e && e.nativeEvent ? e.nativeEvent : e;
|
|
182
|
+
if (typeof handlers === 'function') handlers(ev);
|
|
183
|
+
else if (handlers && handlers.onScroll) handlers.onScroll(ev);
|
|
184
|
+
}, [handlers]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
HANDLE, now,
|
|
189
|
+
useAnimatedStyle, useAnimatedProps, useSharedValue, useDerivedValue,
|
|
190
|
+
useAnimatedReaction, useFrameCallback, useAnimatedScrollHandler,
|
|
191
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// SC-26. Value mixing for the reanimated stub: numbers, unit strings
|
|
3
|
+
// ('360deg', '-25%'), colours and transform lists, plus Reanimated's public
|
|
4
|
+
// interpolate / interpolateColor.
|
|
5
|
+
const {processColor} = require('react-native');
|
|
6
|
+
|
|
7
|
+
const UNIT = /^(-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)([a-z%]*)$/i;
|
|
8
|
+
|
|
9
|
+
function parseUnit(v) {
|
|
10
|
+
const m = typeof v === 'string' ? UNIT.exec(v.trim()) : null;
|
|
11
|
+
return m ? {n: parseFloat(m[1]), u: m[2]} : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// RN's processColor gives 0xAARRGGBB off android; anything else is not a colour.
|
|
15
|
+
function parseColor(v) {
|
|
16
|
+
if (typeof v !== 'string' || parseUnit(v)) return null;
|
|
17
|
+
const c = processColor(v);
|
|
18
|
+
if (typeof c !== 'number') return null;
|
|
19
|
+
return [(c >>> 16) & 255, (c >>> 8) & 255, c & 255, ((c >>> 24) & 255) / 255];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mixColor(a, b, t) {
|
|
23
|
+
const ch = (i) => Math.round(a[i] + (b[i] - a[i]) * t);
|
|
24
|
+
const al = Math.round((a[3] + (b[3] - a[3]) * t) * 1000) / 1000;
|
|
25
|
+
return `rgba(${ch(0)}, ${ch(1)}, ${ch(2)}, ${al})`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function identityFor(key, like) {
|
|
29
|
+
if (key.startsWith('scale')) return 1;
|
|
30
|
+
if (key === 'perspective') return like;
|
|
31
|
+
const u = parseUnit(like);
|
|
32
|
+
if (u) return u.u ? `0${u.u}` : 0;
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const keyOf = (o) => Object.keys(o)[0];
|
|
37
|
+
|
|
38
|
+
// CSS transform-list interpolation: pair by position when the function
|
|
39
|
+
// names line up, padding the shorter list with identities; otherwise the
|
|
40
|
+
// union of both lists, each side missing a function at its identity.
|
|
41
|
+
function mixTransform(a, b, t) {
|
|
42
|
+
const A = Array.isArray(a) ? a : [];
|
|
43
|
+
const B = Array.isArray(b) ? b : [];
|
|
44
|
+
const n = Math.min(A.length, B.length);
|
|
45
|
+
let aligned = true;
|
|
46
|
+
for (let i = 0; i < n; i++) if (keyOf(A[i]) !== keyOf(B[i])) aligned = false;
|
|
47
|
+
let keys;
|
|
48
|
+
const pick = (L, k, i) => {
|
|
49
|
+
if (aligned) return L[i] ? L[i][k] : undefined;
|
|
50
|
+
const e = L.find((o) => keyOf(o) === k);
|
|
51
|
+
return e ? e[k] : undefined;
|
|
52
|
+
};
|
|
53
|
+
if (aligned) {
|
|
54
|
+
keys = (A.length >= B.length ? A : B).map(keyOf);
|
|
55
|
+
} else {
|
|
56
|
+
keys = A.map(keyOf);
|
|
57
|
+
for (const o of B) if (!keys.includes(keyOf(o))) keys.push(keyOf(o));
|
|
58
|
+
}
|
|
59
|
+
return keys.map((k, i) => {
|
|
60
|
+
let va = pick(A, k, i);
|
|
61
|
+
let vb = pick(B, k, i);
|
|
62
|
+
if (va === undefined) va = identityFor(k, vb);
|
|
63
|
+
if (vb === undefined) vb = identityFor(k, va);
|
|
64
|
+
return {[k]: mix(va, vb, t)};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// mix(a, b, t): the value t of the way from a to b. Values that cannot be
|
|
69
|
+
// interpolated switch at the midpoint, as CSS discrete animation does.
|
|
70
|
+
function mix(a, b, t) {
|
|
71
|
+
if (typeof a === 'number' && typeof b === 'number') return a + (b - a) * t;
|
|
72
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
73
|
+
const isT = (L) => Array.isArray(L) && L.every((o) => o && typeof o === 'object' && Object.keys(o).length === 1);
|
|
74
|
+
if ((a == null || isT(a)) && (b == null || isT(b))) return mixTransform(a, b, t);
|
|
75
|
+
}
|
|
76
|
+
const ua = typeof a === 'number' ? {n: a, u: ''} : parseUnit(a);
|
|
77
|
+
const ub = typeof b === 'number' ? {n: b, u: ''} : parseUnit(b);
|
|
78
|
+
if (ua && ub && (ua.u === ub.u || !ua.n || !ub.n)) {
|
|
79
|
+
const u = ua.u || ub.u;
|
|
80
|
+
const n = ua.n + (ub.n - ua.n) * t;
|
|
81
|
+
return u ? `${n}${u}` : n;
|
|
82
|
+
}
|
|
83
|
+
const ca = parseColor(a);
|
|
84
|
+
const cb = parseColor(b);
|
|
85
|
+
if (ca && cb) return mixColor(ca, cb, t);
|
|
86
|
+
return t < 0.5 ? a : b;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function segment(x, input) {
|
|
90
|
+
let i = 1;
|
|
91
|
+
while (i < input.length - 1 && x > input[i]) i++;
|
|
92
|
+
return i - 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extrapolate(x, edge, mode) {
|
|
96
|
+
if (mode === 'identity') return {value: x};
|
|
97
|
+
if (mode === 'clamp') return {value: edge};
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Reanimated's interpolate(x, input, output, extrapolation).
|
|
102
|
+
function interpolate(x, input, output, type) {
|
|
103
|
+
if (!Array.isArray(input) || !Array.isArray(output) || input.length < 2) return x;
|
|
104
|
+
const cfg = typeof type === 'object' && type ? type : {extrapolateLeft: type, extrapolateRight: type};
|
|
105
|
+
const left = cfg.extrapolateLeft || 'extend';
|
|
106
|
+
const right = cfg.extrapolateRight || 'extend';
|
|
107
|
+
const last = input.length - 1;
|
|
108
|
+
if (x < input[0]) {
|
|
109
|
+
const e = extrapolate(x, output[0], left);
|
|
110
|
+
if (e) return e.value;
|
|
111
|
+
} else if (x > input[last]) {
|
|
112
|
+
const e = extrapolate(x, output[last], right);
|
|
113
|
+
if (e) return e.value;
|
|
114
|
+
}
|
|
115
|
+
const i = segment(x, input);
|
|
116
|
+
const span = input[i + 1] - input[i];
|
|
117
|
+
const t = span === 0 ? 0 : (x - input[i]) / span;
|
|
118
|
+
return output[i] + (output[i + 1] - output[i]) * t;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Reanimated's interpolateColor(x, input, colours): RGB space, clamped.
|
|
122
|
+
function interpolateColor(x, input, output) {
|
|
123
|
+
const cols = output.map(parseColor);
|
|
124
|
+
if (cols.some((c) => !c)) return output[0];
|
|
125
|
+
if (x <= input[0]) return mixColor(cols[0], cols[0], 0);
|
|
126
|
+
const last = input.length - 1;
|
|
127
|
+
if (x >= input[last]) return mixColor(cols[last], cols[last], 0);
|
|
128
|
+
const i = segment(x, input);
|
|
129
|
+
const span = input[i + 1] - input[i];
|
|
130
|
+
return mixColor(cols[i], cols[i + 1], span === 0 ? 0 : (x - input[i]) / span);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {mix, interpolate, interpolateColor};
|