@ng-native/fabric 0.0.1 → 0.1.1
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/LICENSE +21 -0
- package/README.md +54 -1
- package/dist/color-mix.d.ts +20 -0
- package/dist/color-mix.js +306 -0
- package/dist/color-mix.js.map +1 -0
- package/dist/css.d.ts +615 -0
- package/dist/css.js +1405 -0
- package/dist/css.js.map +1 -0
- package/dist/defer-triggers.d.ts +53 -0
- package/dist/defer-triggers.js +184 -0
- package/dist/defer-triggers.js.map +1 -0
- package/dist/engine.d.ts +1066 -0
- package/dist/engine.js +2764 -0
- package/dist/engine.js.map +1 -0
- package/dist/fabric.d.ts +20 -0
- package/dist/fabric.js +44 -0
- package/dist/fabric.js.map +1 -0
- package/dist/forms-inputs.d.ts +15 -0
- package/dist/forms-inputs.js +54 -0
- package/dist/forms-inputs.js.map +1 -0
- package/dist/host.d.ts +133 -0
- package/dist/host.js +55 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/inline-token.d.ts +12 -0
- package/dist/inline-token.js +52 -0
- package/dist/inline-token.js.map +1 -0
- package/dist/inline-transform.d.ts +14 -0
- package/dist/inline-transform.js +41 -0
- package/dist/inline-transform.js.map +1 -0
- package/dist/native-drive.d.ts +100 -0
- package/dist/native-drive.js +318 -0
- package/dist/native-drive.js.map +1 -0
- package/dist/relative-colour.d.ts +17 -0
- package/dist/relative-colour.js +71 -0
- package/dist/relative-colour.js.map +1 -0
- package/dist/scroll-animation.d.ts +41 -0
- package/dist/scroll-animation.js +169 -0
- package/dist/scroll-animation.js.map +1 -0
- package/dist/transition.d.ts +139 -0
- package/dist/transition.js +569 -0
- package/dist/transition.js.map +1 -0
- package/package.json +43 -3
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A view moved by a scroll view's offset on the native side, with no JavaScript in between.
|
|
3
|
+
*
|
|
4
|
+
* A sticky header moved by a transform set from each scroll event lags the scroll: the event
|
|
5
|
+
* reaches JavaScript, the transform is committed, and by the time Fabric mounts it the content has
|
|
6
|
+
* moved on, so on a fast fling the header shudders and comes away from the edge it is pinned to.
|
|
7
|
+
* React Native's own sticky headers do not, because `ScrollViewStickyHeader` maps the offset onto
|
|
8
|
+
* an animated value on the native side and interpolates the header's translate from it there.
|
|
9
|
+
* This is that mapping, for the engine's nodes: the scroll view's offset along one axis feeds a
|
|
10
|
+
* value, the value an interpolation, the interpolation a translate, and native applies it on every
|
|
11
|
+
* frame the scroll view moves.
|
|
12
|
+
*
|
|
13
|
+
* It goes through React Native's own `NativeAnimatedHelper`, whose node tags every other animated
|
|
14
|
+
* value in the app also comes from, so none of them can collide.
|
|
15
|
+
*/
|
|
16
|
+
export class NativeScrollDriver {
|
|
17
|
+
native;
|
|
18
|
+
/** One value per source view and feed, by the feed's key. */
|
|
19
|
+
offsets = new Map();
|
|
20
|
+
pending = new Set();
|
|
21
|
+
constructor(native) {
|
|
22
|
+
this.native = native;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Move a view by a scroll. `tagOf` answers a node's view tag once it has been committed, and
|
|
26
|
+
* null before, which is why the connection may wait for a commit: `connect` is tried again by
|
|
27
|
+
* `afterCommit` until both views exist.
|
|
28
|
+
*/
|
|
29
|
+
drive(view, source, feed, property, range, statics, tagOf, shift = null) {
|
|
30
|
+
const drive = { viewTag: null, range, shift, nodes: null, stopped: false };
|
|
31
|
+
const key = feedKey(feed);
|
|
32
|
+
const connect = () => {
|
|
33
|
+
if (drive.stopped)
|
|
34
|
+
return true;
|
|
35
|
+
const viewTag = tagOf(view);
|
|
36
|
+
const sourceTag = tagOf(source);
|
|
37
|
+
if (viewTag === null || sourceTag === null)
|
|
38
|
+
return false;
|
|
39
|
+
drive.viewTag = viewTag;
|
|
40
|
+
const offset = this.offset(source, sourceTag, feed, key);
|
|
41
|
+
drive.nodes = this.build(offset.value, viewTag, property, drive, statics);
|
|
42
|
+
this.flush();
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
if (!connect())
|
|
46
|
+
this.pending.add(connect);
|
|
47
|
+
return {
|
|
48
|
+
update: (next) => {
|
|
49
|
+
if (drive.stopped || sameRange(drive.range, next))
|
|
50
|
+
return;
|
|
51
|
+
drive.range = next;
|
|
52
|
+
if (!drive.nodes || drive.viewTag === null)
|
|
53
|
+
return;
|
|
54
|
+
this.tearDown(drive.nodes, drive.viewTag);
|
|
55
|
+
const offset = this.offsets.get(source).get(key);
|
|
56
|
+
drive.nodes = this.build(offset.value, drive.viewTag, property, drive, statics);
|
|
57
|
+
this.flush();
|
|
58
|
+
},
|
|
59
|
+
shift: (amount) => {
|
|
60
|
+
if (drive.stopped || drive.shift === null || drive.shift === amount)
|
|
61
|
+
return;
|
|
62
|
+
drive.shift = amount;
|
|
63
|
+
if (drive.nodes?.shift == null)
|
|
64
|
+
return;
|
|
65
|
+
this.native.API.setAnimatedNodeValue(drive.nodes.shift, amount);
|
|
66
|
+
this.flush();
|
|
67
|
+
},
|
|
68
|
+
stop: () => {
|
|
69
|
+
if (drive.stopped)
|
|
70
|
+
return;
|
|
71
|
+
drive.stopped = true;
|
|
72
|
+
this.pending.delete(connect);
|
|
73
|
+
if (!drive.nodes || drive.viewTag === null)
|
|
74
|
+
return;
|
|
75
|
+
this.tearDown(drive.nodes, drive.viewTag);
|
|
76
|
+
this.release(source, key);
|
|
77
|
+
this.flush();
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Play a view's opacity and transform by a scroll: each channel an interpolation from the
|
|
83
|
+
* offset, as `scroll-animation.ts` lays a `@keyframes` animation along it. `update` rebuilds
|
|
84
|
+
* the channels, when the scroll view says how far it scrolls; `stop` lets the view go.
|
|
85
|
+
*/
|
|
86
|
+
animate(view, source, feed, channels, tagOf) {
|
|
87
|
+
const state = {
|
|
88
|
+
channels,
|
|
89
|
+
viewTag: null,
|
|
90
|
+
nodes: null,
|
|
91
|
+
stopped: false,
|
|
92
|
+
};
|
|
93
|
+
const key = feedKey(feed);
|
|
94
|
+
const connect = () => {
|
|
95
|
+
if (state.stopped)
|
|
96
|
+
return true;
|
|
97
|
+
const viewTag = tagOf(view);
|
|
98
|
+
const sourceTag = tagOf(source);
|
|
99
|
+
if (viewTag === null || sourceTag === null)
|
|
100
|
+
return false;
|
|
101
|
+
state.viewTag = viewTag;
|
|
102
|
+
const offset = this.offset(source, sourceTag, feed, key);
|
|
103
|
+
state.nodes = this.buildChannels(offset.value, viewTag, state.channels);
|
|
104
|
+
this.flush();
|
|
105
|
+
return true;
|
|
106
|
+
};
|
|
107
|
+
if (!connect())
|
|
108
|
+
this.pending.add(connect);
|
|
109
|
+
return {
|
|
110
|
+
update: (next) => {
|
|
111
|
+
if (state.stopped)
|
|
112
|
+
return;
|
|
113
|
+
state.channels = next;
|
|
114
|
+
if (!state.nodes || state.viewTag === null)
|
|
115
|
+
return;
|
|
116
|
+
this.dropChannels(state.nodes, state.viewTag);
|
|
117
|
+
const offset = this.offsets.get(source).get(key);
|
|
118
|
+
state.nodes = this.buildChannels(offset.value, state.viewTag, next);
|
|
119
|
+
this.flush();
|
|
120
|
+
},
|
|
121
|
+
stop: () => {
|
|
122
|
+
if (state.stopped)
|
|
123
|
+
return;
|
|
124
|
+
state.stopped = true;
|
|
125
|
+
this.pending.delete(connect);
|
|
126
|
+
if (!state.nodes || state.viewTag === null)
|
|
127
|
+
return;
|
|
128
|
+
this.dropChannels(state.nodes, state.viewTag);
|
|
129
|
+
this.release(source, key);
|
|
130
|
+
this.flush();
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** The nodes for a set of channels, connected to the view; the props node first. */
|
|
135
|
+
buildChannels(value, viewTag, channels) {
|
|
136
|
+
const { API } = this.native;
|
|
137
|
+
const made = [];
|
|
138
|
+
const interpolation = (range) => {
|
|
139
|
+
const tag = this.native.generateNewNodeTag();
|
|
140
|
+
API.createAnimatedNode(tag, {
|
|
141
|
+
type: 'interpolation',
|
|
142
|
+
inputRange: [...range.input],
|
|
143
|
+
outputRange: [...range.output],
|
|
144
|
+
extrapolateLeft: 'clamp',
|
|
145
|
+
extrapolateRight: 'clamp',
|
|
146
|
+
});
|
|
147
|
+
API.connectAnimatedNodes(value, tag);
|
|
148
|
+
made.push(tag);
|
|
149
|
+
return tag;
|
|
150
|
+
};
|
|
151
|
+
const style = {};
|
|
152
|
+
if (channels.opacity)
|
|
153
|
+
style['opacity'] = interpolation(channels.opacity);
|
|
154
|
+
if (channels.transform.length) {
|
|
155
|
+
const transforms = channels.transform.map((channel) => 'range' in channel
|
|
156
|
+
? { type: 'animated', property: channel.property, nodeTag: interpolation(channel.range) }
|
|
157
|
+
: { type: 'static', property: channel.property, value: channel.value });
|
|
158
|
+
const transform = this.native.generateNewNodeTag();
|
|
159
|
+
API.createAnimatedNode(transform, { type: 'transform', transforms });
|
|
160
|
+
for (const entry of transforms) {
|
|
161
|
+
if (entry.type === 'animated')
|
|
162
|
+
API.connectAnimatedNodes(entry.nodeTag, transform);
|
|
163
|
+
}
|
|
164
|
+
style['transform'] = transform;
|
|
165
|
+
made.push(transform);
|
|
166
|
+
}
|
|
167
|
+
const styleTag = this.native.generateNewNodeTag();
|
|
168
|
+
API.createAnimatedNode(styleTag, { type: 'style', style });
|
|
169
|
+
for (const tag of Object.values(style))
|
|
170
|
+
API.connectAnimatedNodes(tag, styleTag);
|
|
171
|
+
const props = this.native.generateNewNodeTag();
|
|
172
|
+
API.createAnimatedNode(props, { type: 'props', props: { style: styleTag } });
|
|
173
|
+
API.connectAnimatedNodes(styleTag, props);
|
|
174
|
+
API.connectAnimatedNodeToView(props, viewTag);
|
|
175
|
+
return [props, styleTag, ...made.reverse()];
|
|
176
|
+
}
|
|
177
|
+
dropChannels(nodes, viewTag) {
|
|
178
|
+
const { API } = this.native;
|
|
179
|
+
API.disconnectAnimatedNodeFromView(nodes[0], viewTag);
|
|
180
|
+
for (const tag of nodes)
|
|
181
|
+
API.dropAnimatedNode(tag);
|
|
182
|
+
}
|
|
183
|
+
/** Connects every drive still waiting for its views, now that a commit may have made them. */
|
|
184
|
+
afterCommit() {
|
|
185
|
+
for (const connect of [...this.pending])
|
|
186
|
+
if (connect())
|
|
187
|
+
this.pending.delete(connect);
|
|
188
|
+
}
|
|
189
|
+
/** One value per source view and feed, fed by its events, shared by every view it drives. */
|
|
190
|
+
offset(source, viewTag, feed, key) {
|
|
191
|
+
let feeds = this.offsets.get(source);
|
|
192
|
+
if (!feeds)
|
|
193
|
+
this.offsets.set(source, (feeds = new Map()));
|
|
194
|
+
let offset = feeds.get(key);
|
|
195
|
+
if (!offset) {
|
|
196
|
+
const value = this.native.generateNewNodeTag();
|
|
197
|
+
this.native.API.createAnimatedNode(value, { type: 'value', value: 0, offset: 0 });
|
|
198
|
+
for (const event of feed.events) {
|
|
199
|
+
this.native.API.addAnimatedEventToView(viewTag, event, {
|
|
200
|
+
nativeEventPath: [...feed.path],
|
|
201
|
+
animatedValueTag: value,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
offset = { value, viewTag, feed, users: 0 };
|
|
205
|
+
feeds.set(key, offset);
|
|
206
|
+
}
|
|
207
|
+
offset.users++;
|
|
208
|
+
return offset;
|
|
209
|
+
}
|
|
210
|
+
release(source, key) {
|
|
211
|
+
const feeds = this.offsets.get(source);
|
|
212
|
+
const offset = feeds?.get(key);
|
|
213
|
+
if (!feeds || !offset || --offset.users > 0)
|
|
214
|
+
return;
|
|
215
|
+
feeds.delete(key);
|
|
216
|
+
if (feeds.size === 0)
|
|
217
|
+
this.offsets.delete(source);
|
|
218
|
+
for (const event of offset.feed.events) {
|
|
219
|
+
this.native.API.removeAnimatedEventFromView(offset.viewTag, event, offset.value);
|
|
220
|
+
}
|
|
221
|
+
this.native.API.dropAnimatedNode(offset.value);
|
|
222
|
+
}
|
|
223
|
+
build(value, viewTag, property, drive, statics) {
|
|
224
|
+
const { API } = this.native;
|
|
225
|
+
const range = drive.range;
|
|
226
|
+
const interpolation = this.native.generateNewNodeTag();
|
|
227
|
+
API.createAnimatedNode(interpolation, {
|
|
228
|
+
type: 'interpolation',
|
|
229
|
+
inputRange: [...range.input],
|
|
230
|
+
outputRange: [...range.output],
|
|
231
|
+
extrapolateLeft: 'clamp',
|
|
232
|
+
extrapolateRight: 'clamp',
|
|
233
|
+
});
|
|
234
|
+
let shift = null;
|
|
235
|
+
let sum = null;
|
|
236
|
+
if (drive.shift !== null) {
|
|
237
|
+
shift = this.native.generateNewNodeTag();
|
|
238
|
+
API.createAnimatedNode(shift, { type: 'value', value: drive.shift, offset: 0 });
|
|
239
|
+
sum = this.native.generateNewNodeTag();
|
|
240
|
+
API.createAnimatedNode(sum, { type: 'addition', input: [interpolation, shift] });
|
|
241
|
+
}
|
|
242
|
+
const transform = this.native.generateNewNodeTag();
|
|
243
|
+
API.createAnimatedNode(transform, {
|
|
244
|
+
type: 'transform',
|
|
245
|
+
transforms: [
|
|
246
|
+
{
|
|
247
|
+
type: 'animated',
|
|
248
|
+
property,
|
|
249
|
+
nodeTag: sum ?? interpolation,
|
|
250
|
+
},
|
|
251
|
+
...statics.flatMap((entry) => Object.entries(entry).map(([property, amount]) => ({
|
|
252
|
+
type: 'static',
|
|
253
|
+
property,
|
|
254
|
+
value: amount,
|
|
255
|
+
}))),
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
// A style node between, as `Animated` builds it: RN's props node on iOS reads its values from
|
|
259
|
+
// a style node and ignores a transform node connected to it directly.
|
|
260
|
+
const style = this.native.generateNewNodeTag();
|
|
261
|
+
API.createAnimatedNode(style, { type: 'style', style: { transform } });
|
|
262
|
+
const props = this.native.generateNewNodeTag();
|
|
263
|
+
API.createAnimatedNode(props, { type: 'props', props: { style } });
|
|
264
|
+
API.connectAnimatedNodes(value, interpolation);
|
|
265
|
+
if (shift !== null && sum !== null) {
|
|
266
|
+
API.connectAnimatedNodes(interpolation, sum);
|
|
267
|
+
API.connectAnimatedNodes(shift, sum);
|
|
268
|
+
API.connectAnimatedNodes(sum, transform);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
API.connectAnimatedNodes(interpolation, transform);
|
|
272
|
+
}
|
|
273
|
+
API.connectAnimatedNodes(transform, style);
|
|
274
|
+
API.connectAnimatedNodes(style, props);
|
|
275
|
+
API.connectAnimatedNodeToView(props, viewTag);
|
|
276
|
+
return { interpolation, shift, sum, transform, style, props };
|
|
277
|
+
}
|
|
278
|
+
tearDown(nodes, viewTag) {
|
|
279
|
+
const { API } = this.native;
|
|
280
|
+
API.disconnectAnimatedNodeFromView(nodes.props, viewTag);
|
|
281
|
+
API.dropAnimatedNode(nodes.props);
|
|
282
|
+
API.dropAnimatedNode(nodes.style);
|
|
283
|
+
API.dropAnimatedNode(nodes.transform);
|
|
284
|
+
if (nodes.sum !== null)
|
|
285
|
+
API.dropAnimatedNode(nodes.sum);
|
|
286
|
+
if (nodes.shift !== null)
|
|
287
|
+
API.dropAnimatedNode(nodes.shift);
|
|
288
|
+
API.dropAnimatedNode(nodes.interpolation);
|
|
289
|
+
}
|
|
290
|
+
flush() {
|
|
291
|
+
this.native.API.flushQueue?.();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function feedKey(feed) {
|
|
295
|
+
return `${feed.events.join(',')}:${feed.path.join('.')}`;
|
|
296
|
+
}
|
|
297
|
+
/** A scroll view's offset along an axis, as `onScroll` carries it. */
|
|
298
|
+
export function scrollFeed(axis) {
|
|
299
|
+
return { events: ['onScroll'], path: ['contentOffset', axis] };
|
|
300
|
+
}
|
|
301
|
+
function sameRange(a, b) {
|
|
302
|
+
return (a.input.length === b.input.length &&
|
|
303
|
+
a.input.every((value, i) => value === b.input[i]) &&
|
|
304
|
+
a.output.every((value, i) => value === b.output[i]));
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The translate a pinned view needs at each scroll offset: none until the offset reaches where
|
|
308
|
+
* the view starts, then the offset past that, until `until`, where the next pinned view pushes it
|
|
309
|
+
* off and it stays. `start` and `until` are in the scroll view's own coordinates.
|
|
310
|
+
*/
|
|
311
|
+
export function pinnedRange(start, until = Infinity) {
|
|
312
|
+
if (!Number.isFinite(until) || until <= start) {
|
|
313
|
+
// A large finite end, since native interpolation takes numbers and extends nothing here.
|
|
314
|
+
return { input: [start, start + 1e7], output: [0, 1e7] };
|
|
315
|
+
}
|
|
316
|
+
return { input: [start, until], output: [0, until - start] };
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=native-drive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-drive.js","sourceRoot":"","sources":["../src/native-drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiFH,MAAM,OAAO,kBAAkB;IACZ,MAAM,CAAiB;IACxC,6DAA6D;IAC5C,OAAO,GAAG,IAAI,GAAG,EAA+B,CAAC;IACjD,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IAEpD,YAAY,MAAsB;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,KAAK,CACH,IAAY,EACZ,MAAc,EACd,IAAe,EACf,QAAwB,EACxB,KAAkB,EAClB,OAAmC,EACnC,KAAsC,EACtC,QAAuB,IAAI;QAE3B,MAAM,KAAK,GAAU,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAClF,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,OAAO,GAAG,GAAY,EAAE;YAC5B,IAAI,KAAK,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YACzD,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;YACxB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACzD,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YAC1E,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QACF,IAAI,CAAC,OAAO,EAAE;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO;YACL,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACf,IAAI,KAAK,CAAC,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC;oBAAE,OAAO;gBAC1D,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;gBACnB,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;oBAAE,OAAO;gBACnD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;gBACnD,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;gBAChF,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;YACD,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE;gBAChB,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM;oBAAE,OAAO;gBAC5E,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC;gBACrB,IAAI,KAAK,CAAC,KAAK,EAAE,KAAK,IAAI,IAAI;oBAAE,OAAO;gBACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;gBAChE,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;YACD,IAAI,EAAE,GAAG,EAAE;gBACT,IAAI,KAAK,CAAC,OAAO;oBAAE,OAAO;gBAC1B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC7B,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;oBAAE,OAAO;gBACnD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC1C,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,OAAO,CACL,IAAY,EACZ,MAAc,EACd,IAAe,EACf,QAAwB,EACxB,KAAsC;QAEtC,MAAM,KAAK,GAAG;YACZ,QAAQ;YACR,OAAO,EAAE,IAAqB;YAC9B,KAAK,EAAE,IAAuB;YAC9B,OAAO,EAAE,KAAK;SACf,CAAC;QACF,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,OAAO,GAAG,GAAY,EAAE;YAC5B,IAAI,KAAK,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YACzD,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;YACxB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACzD,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;YACxE,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QACF,IAAI,CAAC,OAAO,EAAE;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO;YACL,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACf,IAAI,KAAK,CAAC,OAAO;oBAAE,OAAO;gBAC1B,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACtB,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;oBAAE,OAAO;gBACnD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;gBACnD,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBACpE,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;YACD,IAAI,EAAE,GAAG,EAAE;gBACT,IAAI,KAAK,CAAC,OAAO;oBAAE,OAAO;gBAC1B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC7B,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;oBAAE,OAAO;gBACnD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC9C,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC;SACF,CAAC;IACJ,CAAC;IAED,oFAAoF;IAC5E,aAAa,CAAC,KAAa,EAAE,OAAe,EAAE,QAAwB;QAC5E,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,MAAM,aAAa,GAAG,CAAC,KAAkB,EAAU,EAAE;YACnD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC7C,GAAG,CAAC,kBAAkB,CAAC,GAAG,EAAE;gBAC1B,IAAI,EAAE,eAAe;gBACrB,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;gBAC5B,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;gBAC9B,eAAe,EAAE,OAAO;gBACxB,gBAAgB,EAAE,OAAO;aAC1B,CAAC,CAAC;YACH,GAAG,CAAC,oBAAoB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACf,OAAO,GAAG,CAAC;QACb,CAAC,CAAC;QACF,MAAM,KAAK,GAA2B,EAAE,CAAC;QACzC,IAAI,QAAQ,CAAC,OAAO;YAAE,KAAK,CAAC,SAAS,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzE,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACpD,OAAO,IAAI,OAAO;gBAChB,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBACzF,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CACzE,CAAC;YACF,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACnD,GAAG,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;YACrE,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;gBAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;oBAAE,GAAG,CAAC,oBAAoB,CAAC,KAAK,CAAC,OAAQ,EAAE,SAAS,CAAC,CAAC;YACrF,CAAC;YACD,KAAK,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvB,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAClD,GAAG,CAAC,kBAAkB,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAChF,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC/C,GAAG,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7E,GAAG,CAAC,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC1C,GAAG,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;IAEO,YAAY,CAAC,KAAwB,EAAE,OAAe;QAC5D,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,GAAG,CAAC,8BAA8B,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,OAAO,CAAC,CAAC;QACvD,KAAK,MAAM,GAAG,IAAI,KAAK;YAAE,GAAG,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACrD,CAAC;IAED,8FAA8F;IAC9F,WAAW;QACT,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;YAAE,IAAI,OAAO,EAAE;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvF,CAAC;IAED,6FAA6F;IACrF,MAAM,CAAC,MAAc,EAAE,OAAe,EAAE,IAAe,EAAE,GAAW;QAC1E,IAAI,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1D,IAAI,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAClF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC,OAAO,EAAE,KAAK,EAAE;oBACrD,eAAe,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;oBAC/B,gBAAgB,EAAE,KAAK;iBACxB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;YAC5C,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACzB,CAAC;QACD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,OAAO,CAAC,MAAc,EAAE,GAAW;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,CAAC;YAAE,OAAO;QACpD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,2BAA2B,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;IAEO,KAAK,CACX,KAAa,EACb,OAAe,EACf,QAAwB,EACxB,KAAY,EACZ,OAAmC;QAEnC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAC1B,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QACvD,GAAG,CAAC,kBAAkB,CAAC,aAAa,EAAE;YACpC,IAAI,EAAE,eAAe;YACrB,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;YAC5B,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;YAC9B,eAAe,EAAE,OAAO;YACxB,gBAAgB,EAAE,OAAO;SAC1B,CAAC,CAAC;QACH,IAAI,KAAK,GAAkB,IAAI,CAAC;QAChC,IAAI,GAAG,GAAkB,IAAI,CAAC;QAC9B,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACzB,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACzC,GAAG,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAChF,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACvC,GAAG,CAAC,kBAAkB,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QACnD,GAAG,CAAC,kBAAkB,CAAC,SAAS,EAAE;YAChC,IAAI,EAAE,WAAW;YACjB,UAAU,EAAE;gBACV;oBACE,IAAI,EAAE,UAAU;oBAChB,QAAQ;oBACR,OAAO,EAAE,GAAG,IAAI,aAAa;iBAC9B;gBACD,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;oBACjD,IAAI,EAAE,QAAQ;oBACd,QAAQ;oBACR,KAAK,EAAE,MAAM;iBACd,CAAC,CAAC,CACJ;aACF;SACF,CAAC,CAAC;QACH,8FAA8F;QAC9F,sEAAsE;QACtE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC/C,GAAG,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC/C,GAAG,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACnE,GAAG,CAAC,oBAAoB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QAC/C,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACnC,GAAG,CAAC,oBAAoB,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;YAC7C,GAAG,CAAC,oBAAoB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YACrC,GAAG,CAAC,oBAAoB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,oBAAoB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACrD,CAAC;QACD,GAAG,CAAC,oBAAoB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC3C,GAAG,CAAC,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACvC,GAAG,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAChE,CAAC;IAEO,QAAQ,CAAC,KAAY,EAAE,OAAe;QAC5C,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,GAAG,CAAC,8BAA8B,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzD,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI;YAAE,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI;YAAE,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5D,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC5C,CAAC;IAEO,KAAK;QACX,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC;IACjC,CAAC;CACF;AAED,SAAS,OAAO,CAAC,IAAe;IAC9B,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,eAAe,EAAE,IAAI,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,SAAS,SAAS,CAAC,CAAc,EAAE,CAAc;IAC/C,OAAO,CACL,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM;QACjC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CACpD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,KAAK,GAAG,QAAQ;IACzD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;QAC9C,yFAAyF;QACzF,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC3D,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * A view moved by a scroll view's offset on the native side, with no JavaScript in between.\n *\n * A sticky header moved by a transform set from each scroll event lags the scroll: the event\n * reaches JavaScript, the transform is committed, and by the time Fabric mounts it the content has\n * moved on, so on a fast fling the header shudders and comes away from the edge it is pinned to.\n * React Native's own sticky headers do not, because `ScrollViewStickyHeader` maps the offset onto\n * an animated value on the native side and interpolates the header's translate from it there.\n * This is that mapping, for the engine's nodes: the scroll view's offset along one axis feeds a\n * value, the value an interpolation, the interpolation a translate, and native applies it on every\n * frame the scroll view moves.\n *\n * It goes through React Native's own `NativeAnimatedHelper`, whose node tags every other animated\n * value in the app also comes from, so none of them can collide.\n */\n\n/** The part of React Native's `NativeAnimatedHelper` default export this uses. */\nexport interface NativeAnimated {\n generateNewNodeTag(): number;\n readonly API: {\n createAnimatedNode(tag: number, config: object): void;\n connectAnimatedNodes(parentTag: number, childTag: number): void;\n disconnectAnimatedNodes(parentTag: number, childTag: number): void;\n connectAnimatedNodeToView(nodeTag: number, viewTag: number): void;\n disconnectAnimatedNodeFromView(nodeTag: number, viewTag: number): void;\n dropAnimatedNode(tag: number): void;\n addAnimatedEventToView(\n viewTag: number,\n eventName: string,\n mapping: { nativeEventPath: string[]; animatedValueTag: number },\n ): void;\n removeAnimatedEventFromView(viewTag: number, eventName: string, nodeTag: number): void;\n setAnimatedNodeValue(nodeTag: number, value: number): void;\n flushQueue?(): void;\n };\n}\n\nimport type { DrivenChannels } from './scroll-animation.ts';\n\nexport type ScrollAxis = 'x' | 'y';\n\n/** A native event that feeds a value: the events carrying it, and the path to it in each. */\nexport interface EventFeed {\n readonly events: readonly string[];\n readonly path: readonly string[];\n}\n\n/** What a driven view moves along. */\nexport type DrivenProperty = 'translateX' | 'translateY';\n\n/** Where a driven view sits for each scroll offset, as an interpolation clamped at both ends. */\nexport interface ScrollRange {\n readonly input: readonly number[];\n readonly output: readonly number[];\n}\n\n/** A transform native keeps beside the driven translate, such as an inverted list's flip. */\nexport type StaticTransform = Readonly<Record<string, number>>;\n\n/**\n * A view moved by a scroll. `update` moves it by a new range; `shift` adds an amount to where the\n * range puts it, for a drive made with one; `stop` lets it go.\n */\nexport interface ScrollDrive {\n update(range: ScrollRange): void;\n shift(amount: number): void;\n stop(): void;\n}\n\ninterface Offset {\n readonly value: number;\n readonly viewTag: number;\n readonly feed: EventFeed;\n users: number;\n}\n\ninterface Nodes {\n interpolation: number;\n /** The shift JavaScript sets, and the sum of it and the interpolation, for a shifted drive. */\n shift: number | null;\n sum: number | null;\n transform: number;\n style: number;\n props: number;\n}\n\ninterface Drive {\n viewTag: number | null;\n range: ScrollRange;\n /** The amount added to the range's translate, or null for a drive without one. */\n shift: number | null;\n nodes: Nodes | null;\n stopped: boolean;\n}\n\nexport class NativeScrollDriver {\n private readonly native: NativeAnimated;\n /** One value per source view and feed, by the feed's key. */\n private readonly offsets = new Map<object, Map<string, Offset>>();\n private readonly pending = new Set<() => boolean>();\n\n constructor(native: NativeAnimated) {\n this.native = native;\n }\n\n /**\n * Move a view by a scroll. `tagOf` answers a node's view tag once it has been committed, and\n * null before, which is why the connection may wait for a commit: `connect` is tried again by\n * `afterCommit` until both views exist.\n */\n drive(\n view: object,\n source: object,\n feed: EventFeed,\n property: DrivenProperty,\n range: ScrollRange,\n statics: readonly StaticTransform[],\n tagOf: (node: object) => number | null,\n shift: number | null = null,\n ): ScrollDrive {\n const drive: Drive = { viewTag: null, range, shift, nodes: null, stopped: false };\n const key = feedKey(feed);\n const connect = (): boolean => {\n if (drive.stopped) return true;\n const viewTag = tagOf(view);\n const sourceTag = tagOf(source);\n if (viewTag === null || sourceTag === null) return false;\n drive.viewTag = viewTag;\n const offset = this.offset(source, sourceTag, feed, key);\n drive.nodes = this.build(offset.value, viewTag, property, drive, statics);\n this.flush();\n return true;\n };\n if (!connect()) this.pending.add(connect);\n return {\n update: (next) => {\n if (drive.stopped || sameRange(drive.range, next)) return;\n drive.range = next;\n if (!drive.nodes || drive.viewTag === null) return;\n this.tearDown(drive.nodes, drive.viewTag);\n const offset = this.offsets.get(source)!.get(key)!;\n drive.nodes = this.build(offset.value, drive.viewTag, property, drive, statics);\n this.flush();\n },\n shift: (amount) => {\n if (drive.stopped || drive.shift === null || drive.shift === amount) return;\n drive.shift = amount;\n if (drive.nodes?.shift == null) return;\n this.native.API.setAnimatedNodeValue(drive.nodes.shift, amount);\n this.flush();\n },\n stop: () => {\n if (drive.stopped) return;\n drive.stopped = true;\n this.pending.delete(connect);\n if (!drive.nodes || drive.viewTag === null) return;\n this.tearDown(drive.nodes, drive.viewTag);\n this.release(source, key);\n this.flush();\n },\n };\n }\n\n /**\n * Play a view's opacity and transform by a scroll: each channel an interpolation from the\n * offset, as `scroll-animation.ts` lays a `@keyframes` animation along it. `update` rebuilds\n * the channels, when the scroll view says how far it scrolls; `stop` lets the view go.\n */\n animate(\n view: object,\n source: object,\n feed: EventFeed,\n channels: DrivenChannels,\n tagOf: (node: object) => number | null,\n ): { update(channels: DrivenChannels): void; stop(): void } {\n const state = {\n channels,\n viewTag: null as number | null,\n nodes: null as number[] | null,\n stopped: false,\n };\n const key = feedKey(feed);\n const connect = (): boolean => {\n if (state.stopped) return true;\n const viewTag = tagOf(view);\n const sourceTag = tagOf(source);\n if (viewTag === null || sourceTag === null) return false;\n state.viewTag = viewTag;\n const offset = this.offset(source, sourceTag, feed, key);\n state.nodes = this.buildChannels(offset.value, viewTag, state.channels);\n this.flush();\n return true;\n };\n if (!connect()) this.pending.add(connect);\n return {\n update: (next) => {\n if (state.stopped) return;\n state.channels = next;\n if (!state.nodes || state.viewTag === null) return;\n this.dropChannels(state.nodes, state.viewTag);\n const offset = this.offsets.get(source)!.get(key)!;\n state.nodes = this.buildChannels(offset.value, state.viewTag, next);\n this.flush();\n },\n stop: () => {\n if (state.stopped) return;\n state.stopped = true;\n this.pending.delete(connect);\n if (!state.nodes || state.viewTag === null) return;\n this.dropChannels(state.nodes, state.viewTag);\n this.release(source, key);\n this.flush();\n },\n };\n }\n\n /** The nodes for a set of channels, connected to the view; the props node first. */\n private buildChannels(value: number, viewTag: number, channels: DrivenChannels): number[] {\n const { API } = this.native;\n const made: number[] = [];\n const interpolation = (range: ScrollRange): number => {\n const tag = this.native.generateNewNodeTag();\n API.createAnimatedNode(tag, {\n type: 'interpolation',\n inputRange: [...range.input],\n outputRange: [...range.output],\n extrapolateLeft: 'clamp',\n extrapolateRight: 'clamp',\n });\n API.connectAnimatedNodes(value, tag);\n made.push(tag);\n return tag;\n };\n const style: Record<string, number> = {};\n if (channels.opacity) style['opacity'] = interpolation(channels.opacity);\n if (channels.transform.length) {\n const transforms = channels.transform.map((channel) =>\n 'range' in channel\n ? { type: 'animated', property: channel.property, nodeTag: interpolation(channel.range) }\n : { type: 'static', property: channel.property, value: channel.value },\n );\n const transform = this.native.generateNewNodeTag();\n API.createAnimatedNode(transform, { type: 'transform', transforms });\n for (const entry of transforms) {\n if (entry.type === 'animated') API.connectAnimatedNodes(entry.nodeTag!, transform);\n }\n style['transform'] = transform;\n made.push(transform);\n }\n const styleTag = this.native.generateNewNodeTag();\n API.createAnimatedNode(styleTag, { type: 'style', style });\n for (const tag of Object.values(style)) API.connectAnimatedNodes(tag, styleTag);\n const props = this.native.generateNewNodeTag();\n API.createAnimatedNode(props, { type: 'props', props: { style: styleTag } });\n API.connectAnimatedNodes(styleTag, props);\n API.connectAnimatedNodeToView(props, viewTag);\n return [props, styleTag, ...made.reverse()];\n }\n\n private dropChannels(nodes: readonly number[], viewTag: number): void {\n const { API } = this.native;\n API.disconnectAnimatedNodeFromView(nodes[0]!, viewTag);\n for (const tag of nodes) API.dropAnimatedNode(tag);\n }\n\n /** Connects every drive still waiting for its views, now that a commit may have made them. */\n afterCommit(): void {\n for (const connect of [...this.pending]) if (connect()) this.pending.delete(connect);\n }\n\n /** One value per source view and feed, fed by its events, shared by every view it drives. */\n private offset(source: object, viewTag: number, feed: EventFeed, key: string): Offset {\n let feeds = this.offsets.get(source);\n if (!feeds) this.offsets.set(source, (feeds = new Map()));\n let offset = feeds.get(key);\n if (!offset) {\n const value = this.native.generateNewNodeTag();\n this.native.API.createAnimatedNode(value, { type: 'value', value: 0, offset: 0 });\n for (const event of feed.events) {\n this.native.API.addAnimatedEventToView(viewTag, event, {\n nativeEventPath: [...feed.path],\n animatedValueTag: value,\n });\n }\n offset = { value, viewTag, feed, users: 0 };\n feeds.set(key, offset);\n }\n offset.users++;\n return offset;\n }\n\n private release(source: object, key: string): void {\n const feeds = this.offsets.get(source);\n const offset = feeds?.get(key);\n if (!feeds || !offset || --offset.users > 0) return;\n feeds.delete(key);\n if (feeds.size === 0) this.offsets.delete(source);\n for (const event of offset.feed.events) {\n this.native.API.removeAnimatedEventFromView(offset.viewTag, event, offset.value);\n }\n this.native.API.dropAnimatedNode(offset.value);\n }\n\n private build(\n value: number,\n viewTag: number,\n property: DrivenProperty,\n drive: Drive,\n statics: readonly StaticTransform[],\n ): Nodes {\n const { API } = this.native;\n const range = drive.range;\n const interpolation = this.native.generateNewNodeTag();\n API.createAnimatedNode(interpolation, {\n type: 'interpolation',\n inputRange: [...range.input],\n outputRange: [...range.output],\n extrapolateLeft: 'clamp',\n extrapolateRight: 'clamp',\n });\n let shift: number | null = null;\n let sum: number | null = null;\n if (drive.shift !== null) {\n shift = this.native.generateNewNodeTag();\n API.createAnimatedNode(shift, { type: 'value', value: drive.shift, offset: 0 });\n sum = this.native.generateNewNodeTag();\n API.createAnimatedNode(sum, { type: 'addition', input: [interpolation, shift] });\n }\n const transform = this.native.generateNewNodeTag();\n API.createAnimatedNode(transform, {\n type: 'transform',\n transforms: [\n {\n type: 'animated',\n property,\n nodeTag: sum ?? interpolation,\n },\n ...statics.flatMap((entry) =>\n Object.entries(entry).map(([property, amount]) => ({\n type: 'static',\n property,\n value: amount,\n })),\n ),\n ],\n });\n // A style node between, as `Animated` builds it: RN's props node on iOS reads its values from\n // a style node and ignores a transform node connected to it directly.\n const style = this.native.generateNewNodeTag();\n API.createAnimatedNode(style, { type: 'style', style: { transform } });\n const props = this.native.generateNewNodeTag();\n API.createAnimatedNode(props, { type: 'props', props: { style } });\n API.connectAnimatedNodes(value, interpolation);\n if (shift !== null && sum !== null) {\n API.connectAnimatedNodes(interpolation, sum);\n API.connectAnimatedNodes(shift, sum);\n API.connectAnimatedNodes(sum, transform);\n } else {\n API.connectAnimatedNodes(interpolation, transform);\n }\n API.connectAnimatedNodes(transform, style);\n API.connectAnimatedNodes(style, props);\n API.connectAnimatedNodeToView(props, viewTag);\n return { interpolation, shift, sum, transform, style, props };\n }\n\n private tearDown(nodes: Nodes, viewTag: number): void {\n const { API } = this.native;\n API.disconnectAnimatedNodeFromView(nodes.props, viewTag);\n API.dropAnimatedNode(nodes.props);\n API.dropAnimatedNode(nodes.style);\n API.dropAnimatedNode(nodes.transform);\n if (nodes.sum !== null) API.dropAnimatedNode(nodes.sum);\n if (nodes.shift !== null) API.dropAnimatedNode(nodes.shift);\n API.dropAnimatedNode(nodes.interpolation);\n }\n\n private flush(): void {\n this.native.API.flushQueue?.();\n }\n}\n\nfunction feedKey(feed: EventFeed): string {\n return `${feed.events.join(',')}:${feed.path.join('.')}`;\n}\n\n/** A scroll view's offset along an axis, as `onScroll` carries it. */\nexport function scrollFeed(axis: ScrollAxis): EventFeed {\n return { events: ['onScroll'], path: ['contentOffset', axis] };\n}\n\nfunction sameRange(a: ScrollRange, b: ScrollRange): boolean {\n return (\n a.input.length === b.input.length &&\n a.input.every((value, i) => value === b.input[i]) &&\n a.output.every((value, i) => value === b.output[i])\n );\n}\n\n/**\n * The translate a pinned view needs at each scroll offset: none until the offset reaches where\n * the view starts, then the offset past that, until `until`, where the next pinned view pushes it\n * off and it stays. `start` and `until` are in the scroll view's own coordinates.\n */\nexport function pinnedRange(start: number, until = Infinity): ScrollRange {\n if (!Number.isFinite(until) || until <= start) {\n // A large finite end, since native interpolation takes numbers and extends nothing here.\n return { input: [start, start + 1e7], output: [0, 1e7] };\n }\n return { input: [start, until], output: [0, until - start] };\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A relative colour with a token in it, worked out on device: `oklch(from var(--brand) l c h /
|
|
3
|
+
* 0.2)`. The compiler settles a relative colour of literals itself; one with a `var()` in it waits
|
|
4
|
+
* for the token.
|
|
5
|
+
*
|
|
6
|
+
* The origin is converted into the function's space, each channel keyword is the number it is
|
|
7
|
+
* there, on CSS Color 5's scale for it (`r g b` 0 to 255, hsl and hwb's `s l w b` 0 to 100), and
|
|
8
|
+
* the channels written make the new colour, brought back into sRGB as a mix is.
|
|
9
|
+
*/
|
|
10
|
+
import { type MixSpace } from './color-mix.ts';
|
|
11
|
+
/** A channel: a number, a keyword, or arithmetic on two channels. */
|
|
12
|
+
export type Channel = number | string | readonly [op: '+' | '-' | '*' | '/', Channel, Channel];
|
|
13
|
+
/**
|
|
14
|
+
* The colour the channels make from `origin`, as the `rgb()` or `rgba()` native reads. Undefined
|
|
15
|
+
* when the origin is not a colour this can read, such as a platform colour.
|
|
16
|
+
*/
|
|
17
|
+
export declare function relativeColour(space: MixSpace, origin: string, channels: readonly [Channel, Channel, Channel], alpha?: Channel): string | undefined;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A relative colour with a token in it, worked out on device: `oklch(from var(--brand) l c h /
|
|
3
|
+
* 0.2)`. The compiler settles a relative colour of literals itself; one with a `var()` in it waits
|
|
4
|
+
* for the token.
|
|
5
|
+
*
|
|
6
|
+
* The origin is converted into the function's space, each channel keyword is the number it is
|
|
7
|
+
* there, on CSS Color 5's scale for it (`r g b` 0 to 255, hsl and hwb's `s l w b` 0 to 100), and
|
|
8
|
+
* the channels written make the new colour, brought back into sRGB as a mix is.
|
|
9
|
+
*/
|
|
10
|
+
import { into, outOf } from "./color-mix.js";
|
|
11
|
+
import { parseColor } from "./transition.js";
|
|
12
|
+
/** What a keyword's number is, per unit of the space's own coordinate. */
|
|
13
|
+
const SCALES = {
|
|
14
|
+
srgb: [255, 255, 255],
|
|
15
|
+
hsl: [1, 100, 100],
|
|
16
|
+
hwb: [1, 100, 100],
|
|
17
|
+
lab: [1, 1, 1],
|
|
18
|
+
lch: [1, 1, 1],
|
|
19
|
+
oklab: [1, 1, 1],
|
|
20
|
+
oklch: [1, 1, 1],
|
|
21
|
+
};
|
|
22
|
+
const KEYWORDS = {
|
|
23
|
+
srgb: ['r', 'g', 'b'],
|
|
24
|
+
hsl: ['h', 's', 'l'],
|
|
25
|
+
hwb: ['h', 'w', 'b'],
|
|
26
|
+
lab: ['l', 'a', 'b'],
|
|
27
|
+
lch: ['l', 'c', 'h'],
|
|
28
|
+
oklab: ['l', 'a', 'b'],
|
|
29
|
+
oklch: ['l', 'c', 'h'],
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The colour the channels make from `origin`, as the `rgb()` or `rgba()` native reads. Undefined
|
|
33
|
+
* when the origin is not a colour this can read, such as a platform colour.
|
|
34
|
+
*/
|
|
35
|
+
export function relativeColour(space, origin, channels, alpha) {
|
|
36
|
+
const parsed = parseColor(origin);
|
|
37
|
+
if (!parsed)
|
|
38
|
+
return undefined;
|
|
39
|
+
const [r, g, b, originAlpha] = parsed;
|
|
40
|
+
const coords = into(space, [r / 255, g / 255, b / 255]);
|
|
41
|
+
const scale = SCALES[space];
|
|
42
|
+
const values = { alpha: originAlpha };
|
|
43
|
+
KEYWORDS[space].forEach((keyword, i) => {
|
|
44
|
+
// A missing component is zero in relative syntax: a grey has no hue.
|
|
45
|
+
values[keyword] = Number.isFinite(coords[i]) ? coords[i] * scale[i] : 0;
|
|
46
|
+
});
|
|
47
|
+
const made = channels.map((channel, i) => evaluate(channel, values) / scale[i]);
|
|
48
|
+
const opacity = Math.min(1, Math.max(0, alpha === undefined ? originAlpha : evaluate(alpha, values)));
|
|
49
|
+
const [red, green, blue] = outOf(space, made).map((c) => Math.round(c * 255));
|
|
50
|
+
const rounded = Math.round(opacity * 1000) / 1000;
|
|
51
|
+
return rounded >= 1
|
|
52
|
+
? `rgb(${red}, ${green}, ${blue})`
|
|
53
|
+
: `rgba(${red}, ${green}, ${blue}, ${rounded})`;
|
|
54
|
+
}
|
|
55
|
+
function evaluate(channel, values) {
|
|
56
|
+
if (typeof channel === 'number')
|
|
57
|
+
return channel;
|
|
58
|
+
if (typeof channel === 'string')
|
|
59
|
+
return values[channel] ?? 0;
|
|
60
|
+
const [op, a, b] = channel;
|
|
61
|
+
const left = evaluate(a, values);
|
|
62
|
+
const right = evaluate(b, values);
|
|
63
|
+
if (op === '+')
|
|
64
|
+
return left + right;
|
|
65
|
+
if (op === '-')
|
|
66
|
+
return left - right;
|
|
67
|
+
if (op === '*')
|
|
68
|
+
return left * right;
|
|
69
|
+
return left / right;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=relative-colour.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relative-colour.js","sourceRoot":"","sources":["../src/relative-colour.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A relative colour with a token in it, worked out on device: `oklch(from var(--brand) l c h /\n * 0.2)`. The compiler settles a relative colour of literals itself; one with a `var()` in it waits\n * for the token.\n *\n * The origin is converted into the function's space, each channel keyword is the number it is\n * there, on CSS Color 5's scale for it (`r g b` 0 to 255, hsl and hwb's `s l w b` 0 to 100), and\n * the channels written make the new colour, brought back into sRGB as a mix is.\n */\nimport { into, outOf, type MixSpace, type Vector } from './color-mix.ts';\nimport { parseColor } from './transition.ts';\n\n/** A channel: a number, a keyword, or arithmetic on two channels. */\nexport type Channel = number | string | readonly [op: '+' | '-' | '*' | '/', Channel, Channel];\n\n/** What a keyword's number is, per unit of the space's own coordinate. */\nconst SCALES: Record<MixSpace, Vector> = {\n srgb: [255, 255, 255],\n hsl: [1, 100, 100],\n hwb: [1, 100, 100],\n lab: [1, 1, 1],\n lch: [1, 1, 1],\n oklab: [1, 1, 1],\n oklch: [1, 1, 1],\n};\n\nconst KEYWORDS: Record<MixSpace, readonly [string, string, string]> = {\n srgb: ['r', 'g', 'b'],\n hsl: ['h', 's', 'l'],\n hwb: ['h', 'w', 'b'],\n lab: ['l', 'a', 'b'],\n lch: ['l', 'c', 'h'],\n oklab: ['l', 'a', 'b'],\n oklch: ['l', 'c', 'h'],\n};\n\n/**\n * The colour the channels make from `origin`, as the `rgb()` or `rgba()` native reads. Undefined\n * when the origin is not a colour this can read, such as a platform colour.\n */\nexport function relativeColour(\n space: MixSpace,\n origin: string,\n channels: readonly [Channel, Channel, Channel],\n alpha?: Channel,\n): string | undefined {\n const parsed = parseColor(origin);\n if (!parsed) return undefined;\n const [r, g, b, originAlpha] = parsed;\n const coords = into(space, [r / 255, g / 255, b / 255]);\n const scale = SCALES[space];\n const values: Record<string, number> = { alpha: originAlpha };\n KEYWORDS[space].forEach((keyword, i) => {\n // A missing component is zero in relative syntax: a grey has no hue.\n values[keyword] = Number.isFinite(coords[i]) ? coords[i]! * scale[i]! : 0;\n });\n const made = channels.map((channel, i) => evaluate(channel, values) / scale[i]!) as Vector;\n const opacity = Math.min(\n 1,\n Math.max(0, alpha === undefined ? originAlpha : evaluate(alpha, values)),\n );\n const [red, green, blue] = outOf(space, made).map((c) => Math.round(c * 255));\n const rounded = Math.round(opacity * 1000) / 1000;\n return rounded >= 1\n ? `rgb(${red}, ${green}, ${blue})`\n : `rgba(${red}, ${green}, ${blue}, ${rounded})`;\n}\n\nfunction evaluate(channel: Channel, values: Readonly<Record<string, number>>): number {\n if (typeof channel === 'number') return channel;\n if (typeof channel === 'string') return values[channel] ?? 0;\n const [op, a, b] = channel;\n const left = evaluate(a, values);\n const right = evaluate(b, values);\n if (op === '+') return left + right;\n if (op === '-') return left - right;\n if (op === '*') return left * right;\n return left / right;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `@keyframes` animation played by a scroll view's offset: `animation-timeline: scroll()`.
|
|
3
|
+
*
|
|
4
|
+
* Native interpolates linearly between the points it is given and clamps past both ends, which is
|
|
5
|
+
* all a scroll-driven animation needs from it once its keyframes are laid along the offset: each
|
|
6
|
+
* keyframe sits at its share of the range, a segment's timing function is sampled into points
|
|
7
|
+
* along it, and an unfilled end falls back to the resting value just outside the range. What
|
|
8
|
+
* native can animate this way is opacity and the transforms; anything else holds its first frame.
|
|
9
|
+
*/
|
|
10
|
+
import type { ScrollRange } from './native-drive.ts';
|
|
11
|
+
import { type AnimationSpec } from './transition.ts';
|
|
12
|
+
type Track = readonly {
|
|
13
|
+
offset: number;
|
|
14
|
+
value: unknown;
|
|
15
|
+
}[];
|
|
16
|
+
/** The channels a scroll-driven animation drives natively, in the order native applies them. */
|
|
17
|
+
export interface DrivenChannels {
|
|
18
|
+
readonly opacity?: ScrollRange;
|
|
19
|
+
/** Every transform entry the view paints: driven by the scroll, or held as it is. */
|
|
20
|
+
readonly transform: readonly TransformChannel[];
|
|
21
|
+
}
|
|
22
|
+
export type TransformChannel = {
|
|
23
|
+
readonly property: string;
|
|
24
|
+
readonly range: ScrollRange;
|
|
25
|
+
} | {
|
|
26
|
+
readonly property: string;
|
|
27
|
+
readonly value: number;
|
|
28
|
+
};
|
|
29
|
+
/** Where the range starts and ends on the scroll view's offset. */
|
|
30
|
+
export declare function rangeOf(spec: AnimationSpec, extent: number | null): [number, number];
|
|
31
|
+
/**
|
|
32
|
+
* The channels for an animation's tracks over a range, and the properties it cannot drive
|
|
33
|
+
* natively, which the caller holds at their first frame.
|
|
34
|
+
*/
|
|
35
|
+
export declare function scrollChannels(tracks: ReadonlyMap<string, Track>, spec: AnimationSpec, resting: Readonly<Record<string, unknown>>, range: readonly [number, number]): {
|
|
36
|
+
channels: DrivenChannels;
|
|
37
|
+
held: string[];
|
|
38
|
+
};
|
|
39
|
+
/** The values a scroll-driven animation commits: its frame at the offset the view starts at. */
|
|
40
|
+
export declare function firstFrame(tracks: ReadonlyMap<string, Track>, spec: AnimationSpec, range: readonly [number, number]): Record<string, unknown>;
|
|
41
|
+
export {};
|