@flemo/core 2.2.0 → 2.2.2
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/dist/core/engine/__tests__/createTransitionEngine.cameraSpan.test.d.ts +1 -0
- package/dist/dom/attributes.d.ts +16 -1
- package/dist/dom/staging.d.ts +10 -9
- package/dist/index.mjs +2084 -1560
- package/dist/morph/__tests__/morphContents.test.d.ts +1 -0
- package/dist/morph/__tests__/morphFace.test.d.ts +1 -0
- package/dist/morph/__tests__/morphLine.test.d.ts +1 -0
- package/dist/morph/__tests__/pinParts.test.d.ts +1 -0
- package/dist/morph/attachMorph.d.ts +14 -0
- package/dist/morph/morphContents.d.ts +36 -0
- package/dist/morph/morphFace.d.ts +66 -0
- package/dist/morph/morphGeometry.d.ts +48 -0
- package/dist/morph/morphKeyframes.d.ts +226 -4
- package/dist/morph/morphLine.d.ts +96 -0
- package/dist/morph/morphPose.d.ts +47 -0
- package/dist/morph/morphSheet.d.ts +9 -0
- package/dist/morph/morphSide.d.ts +7 -8
- package/dist/morph/pinParts.d.ts +27 -0
- package/dist/transition/__tests__/gestureScrub.test.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -8,6 +8,18 @@ export interface AttachMorphOptions {
|
|
|
8
8
|
name?: MorphTransitionName;
|
|
9
9
|
/** The navigate store of the Router scope this element belongs to. */
|
|
10
10
|
navigateStore: NavigateStoreApi;
|
|
11
|
+
/**
|
|
12
|
+
* Which side of which flight this end is on, from the binding.
|
|
13
|
+
*
|
|
14
|
+
* Only ever needed where the DOM cannot answer: a shared bar is rendered
|
|
15
|
+
* outside the screen scope it belongs to, so walking up from a morph in one
|
|
16
|
+
* leaves the screen entirely or lands on an enclosing Router's. Everything
|
|
17
|
+
* written inside a screen is answered by the walk and needs none of this.
|
|
18
|
+
*/
|
|
19
|
+
ownership?: {
|
|
20
|
+
status: NavigateStatus;
|
|
21
|
+
active: boolean;
|
|
22
|
+
} | null;
|
|
11
23
|
}
|
|
12
24
|
interface MorphFlight {
|
|
13
25
|
finish: () => void;
|
|
@@ -15,6 +27,8 @@ interface MorphFlight {
|
|
|
15
27
|
element: HTMLElement;
|
|
16
28
|
duration: number;
|
|
17
29
|
start: number;
|
|
30
|
+
/** The flat lead-in inside `start`, so a nested set bakes the same one. */
|
|
31
|
+
head: number;
|
|
18
32
|
ease: AnimationOptions["ease"];
|
|
19
33
|
/**
|
|
20
34
|
* Put the landing's safety net away, and set it again.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHETHER A BOX'S CONTENTS MOVE WHEN THE BOX CHANGES SIZE.
|
|
3
|
+
*
|
|
4
|
+
* Animating a morph's box is what makes it a growth rather than a stretch: the
|
|
5
|
+
* subtree lays itself out at every size on the way, which is the only way text
|
|
6
|
+
* can re-wrap into its new shape. That is a layout and a fresh raster of the
|
|
7
|
+
* subtree on every frame, and WebKit re-snaps the backing to the device grid
|
|
8
|
+
* each time, so a subtree that does not need to move gets carried a device
|
|
9
|
+
* pixel back and forth for the whole flight anyway.
|
|
10
|
+
*
|
|
11
|
+
* Measured on a consumer's pill: every descendant held ONE position for all
|
|
12
|
+
* twenty-three frames of the flight, and only the box's own near edge moved.
|
|
13
|
+
* Right-aligned contents in a box that grows leftward do not go anywhere. The
|
|
14
|
+
* per-frame layout produced no layout change at all, and the tremble was the
|
|
15
|
+
* whole of what it bought.
|
|
16
|
+
*
|
|
17
|
+
* Where that is TRUE, holding the box at the size that contains both ends and
|
|
18
|
+
* cutting the near edge back with a clip is the same picture, drawn once. Where
|
|
19
|
+
* it is FALSE the flight must animate the box for real, because something
|
|
20
|
+
* inside genuinely has a different place at the two ends.
|
|
21
|
+
*
|
|
22
|
+
* This is the difference between measuring that and guessing it from the box's
|
|
23
|
+
* shape. A shape says nothing about a consumer's subtree; two laid-out ends do.
|
|
24
|
+
*/
|
|
25
|
+
/** The corner a flight's box is anchored on, and grows away from. */
|
|
26
|
+
export interface MorphAnchor {
|
|
27
|
+
x: "left" | "right";
|
|
28
|
+
y: "top" | "bottom";
|
|
29
|
+
}
|
|
30
|
+
export declare const contentsHoldAcrossBox: (element: HTMLElement, from: {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}, to: {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}, anchor: MorphAnchor) => boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The two ratios a face's height is built from, per em. */
|
|
2
|
+
export interface FaceRatios {
|
|
3
|
+
ascent: number;
|
|
4
|
+
descent: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The face's ascent and descent per em, or null where they cannot be had.
|
|
8
|
+
*
|
|
9
|
+
* Keyed on the font shorthand, so every element wearing the same type shares
|
|
10
|
+
* one answer for the session.
|
|
11
|
+
*/
|
|
12
|
+
export declare const faceRatios: (font: {
|
|
13
|
+
family: string;
|
|
14
|
+
weight: string | number;
|
|
15
|
+
style: string;
|
|
16
|
+
}) => FaceRatios | null;
|
|
17
|
+
/** The grids an engine is known to snap the two halves of a face height to. */
|
|
18
|
+
export declare const faceGrids: () => number[];
|
|
19
|
+
/**
|
|
20
|
+
* The face height the line box will be built from, at one size.
|
|
21
|
+
*
|
|
22
|
+
* `scale` is the reciprocal of the grid: 1 for whole pixels, the device pixel
|
|
23
|
+
* ratio for device pixels.
|
|
24
|
+
*/
|
|
25
|
+
export declare const faceHeightAt: (size: number, ratios: FaceRatios, scale: number) => number;
|
|
26
|
+
/** The two halves of a face's box at one size, as the font reports them. */
|
|
27
|
+
export interface FaceParts {
|
|
28
|
+
ascent: number;
|
|
29
|
+
descent: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The face's ascent and descent at one size, as the font reports them.
|
|
33
|
+
*
|
|
34
|
+
* The same numbers the line box is built from, got without laying anything out.
|
|
35
|
+
* The BASELINE sits at the ascent below the inline box's top, so a flight that
|
|
36
|
+
* holds its half-leading still is only half done: the ascent is on the same
|
|
37
|
+
* grid and steps just as often (see morphLine).
|
|
38
|
+
*/
|
|
39
|
+
export declare const faceParts: (size: number, font: {
|
|
40
|
+
family: string;
|
|
41
|
+
weight: string | number;
|
|
42
|
+
style: string;
|
|
43
|
+
}, scale: number) => FaceParts | null;
|
|
44
|
+
export declare const runAdvance: (text: string, size: number, font: {
|
|
45
|
+
family: string;
|
|
46
|
+
weight: string | number;
|
|
47
|
+
style: string;
|
|
48
|
+
}) => number | null;
|
|
49
|
+
/** The face's own height at one size: its two halves added up. */
|
|
50
|
+
export declare const faceHeight: (size: number, font: {
|
|
51
|
+
family: string;
|
|
52
|
+
weight: string | number;
|
|
53
|
+
style: string;
|
|
54
|
+
}, scale: number) => number | null;
|
|
55
|
+
/**
|
|
56
|
+
* Every size in `[from, to]` at which the face height MIGHT change.
|
|
57
|
+
*
|
|
58
|
+
* Arithmetic only, and deliberately: each half of the height steps where
|
|
59
|
+
* `size * ratio` crosses a half of the grid, which gives the candidates. Where
|
|
60
|
+
* each one really falls is not settled here, because the thing that has to be
|
|
61
|
+
* exact is the TIME the flight meets it, and only the caller knows the curve
|
|
62
|
+
* that maps one to the other (see morphLine).
|
|
63
|
+
*/
|
|
64
|
+
export declare const faceAims: (from: number, to: number, ratios: FaceRatios, scale: number) => number[];
|
|
65
|
+
/** Whether two faces are the same height, ascent and descent alike. */
|
|
66
|
+
export declare const sameFace: (a: FaceParts, b: FaceParts) => boolean;
|
|
@@ -54,8 +54,56 @@ export interface MorphSnapshot {
|
|
|
54
54
|
* complete as the last thing someone noticed on glass.
|
|
55
55
|
*/
|
|
56
56
|
paint: Record<string, string>;
|
|
57
|
+
/**
|
|
58
|
+
* Whether this end is ONE LINE of text.
|
|
59
|
+
*
|
|
60
|
+
* A flight animates the element's box, and the element in flight is the
|
|
61
|
+
* ARRIVAL's tree — so it re-wraps at every width on the way, under the
|
|
62
|
+
* arrival's own line-breaking rules rather than the departure's. Where both
|
|
63
|
+
* ends are single lines that is always wrong: the label was one line, the
|
|
64
|
+
* heading is one line, and the flight puts a second one in between. Measured
|
|
65
|
+
* on the playground's poster grid, on an iPhone: a cell's meta line broke
|
|
66
|
+
* after the middle dot for the first four frames of a push, because the
|
|
67
|
+
* detail's span has no `truncate` and the cell's width does not fit it.
|
|
68
|
+
*
|
|
69
|
+
* Read for both ends so the flight can hold a single line when it knows the
|
|
70
|
+
* journey has no honest reason to have two (see `holdsOneLine`).
|
|
71
|
+
*/
|
|
72
|
+
singleLine: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The height of this end's first line of text, in px — the face's own ascent
|
|
75
|
+
* plus descent at the size it renders.
|
|
76
|
+
*
|
|
77
|
+
* It is the other half of where a line SITS in its box: the half-leading is
|
|
78
|
+
* `(line-height - this) / 2`, and both engines render that floored to whole
|
|
79
|
+
* pixels. A flight that interpolates the leading has to know where those
|
|
80
|
+
* boundaries are to avoid ending on one (see morphLine's `leadingBias`).
|
|
81
|
+
*
|
|
82
|
+
* Null for anything that is not one run of text, and for a measurement taken
|
|
83
|
+
* through an ancestor's scale, which is not the face's own height.
|
|
84
|
+
*/
|
|
85
|
+
textHeight: number | null;
|
|
86
|
+
/**
|
|
87
|
+
* Where this end's line actually SITS: the rendered distance from the box's
|
|
88
|
+
* top to the top of its text run, in px.
|
|
89
|
+
*
|
|
90
|
+
* The engines floor the half-leading to whole pixels, and this is the floored
|
|
91
|
+
* answer they gave — not a derivation. It is what the landing will paint, and
|
|
92
|
+
* a correction that cannot reproduce it from `line-height` and `textHeight`
|
|
93
|
+
* has misread the rule and declines to act (see morphLine's `leadingBias`).
|
|
94
|
+
*/
|
|
95
|
+
leadOffset: number | null;
|
|
57
96
|
}
|
|
58
97
|
export declare const rectCentre: (rect: MorphRect) => PosePoint;
|
|
98
|
+
/**
|
|
99
|
+
* Is a box of this height a single line of type?
|
|
100
|
+
*
|
|
101
|
+
* Exported because the height to ask about is not always the measured one: a
|
|
102
|
+
* NESTED arrival is measured inside a container that is already staged at its
|
|
103
|
+
* from-box, so its staged height is the wrapped one — the very thing the answer
|
|
104
|
+
* is used to prevent. Its rest height is the honest one.
|
|
105
|
+
*/
|
|
106
|
+
export declare const isSingleLine: (height: number, lineHeight: number | null, fontSize: number | null) => boolean;
|
|
59
107
|
export declare const captureMorphSnapshot: (element: HTMLElement) => MorphSnapshot;
|
|
60
108
|
/**
|
|
61
109
|
* A painted rect mapped back into the space its ancestor's transform is applied
|
|
@@ -19,6 +19,24 @@ export interface MorphTravel {
|
|
|
19
19
|
duration: number;
|
|
20
20
|
/** Seconds from the release, platform head included. */
|
|
21
21
|
start: number;
|
|
22
|
+
/**
|
|
23
|
+
* The platform's flat lead-in, ALREADY COUNTED IN `start`.
|
|
24
|
+
*
|
|
25
|
+
* A HEAD IS NOT A DELAY. Waiting it out as a delay leaves the animation
|
|
26
|
+
* uncommitted until the instant it must move, so a first frame that arrives
|
|
27
|
+
* late arrives PARTWAY THROUGH — the curve is entered wherever the clock
|
|
28
|
+
* says and everything before that is never drawn. Given here, the same
|
|
29
|
+
* seconds are baked into the keyframes as a flat stop instead: the animation
|
|
30
|
+
* is running and still, a late first frame lands inside the lead-in, and the
|
|
31
|
+
* curve plays from 0. The screens have ridden it this way since the head was
|
|
32
|
+
* invented; this is the flight riding it the same way.
|
|
33
|
+
*
|
|
34
|
+
* Painted frames off a consumer's phone, 60fps: the first frame the box was
|
|
35
|
+
* drawn on was already 67% of the way through its travel. The computed value
|
|
36
|
+
* ramped correctly throughout, which is why every main-thread probe called it
|
|
37
|
+
* healthy.
|
|
38
|
+
*/
|
|
39
|
+
head?: number;
|
|
22
40
|
ease: AnimationOptions["ease"];
|
|
23
41
|
}
|
|
24
42
|
export interface MorphKeyframeSet {
|
|
@@ -31,6 +49,61 @@ export interface MorphKeyframeSet {
|
|
|
31
49
|
* one, the corner where the corner is all that changes.
|
|
32
50
|
*/
|
|
33
51
|
geometryName: string;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the geometry keyframe is one the COMPOSITOR can run.
|
|
54
|
+
*
|
|
55
|
+
* True only where the travel is a transform and nothing else. A keyframe that
|
|
56
|
+
* also carries a box, a type size or a clip is resolved by the MAIN THREAD,
|
|
57
|
+
* once per frame it manages to produce — and anything that has to stay
|
|
58
|
+
* registered with it has to be presented from there too (see the camera).
|
|
59
|
+
*/
|
|
60
|
+
geometryAccelerated: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* The `translate` the caller must set on the element, or null.
|
|
63
|
+
*
|
|
64
|
+
* Non-null where the travel is driven through registered properties, which
|
|
65
|
+
* is what keeps its position on the same thread as its size.
|
|
66
|
+
*/
|
|
67
|
+
translate: string | null;
|
|
68
|
+
/**
|
|
69
|
+
* The `width` and `height` the caller must set on the element, or null.
|
|
70
|
+
*
|
|
71
|
+
* Non-null where the box's size is driven through registered properties,
|
|
72
|
+
* which is what keeps an engine from dropping it (see morphPose).
|
|
73
|
+
*/
|
|
74
|
+
size: {
|
|
75
|
+
width: string;
|
|
76
|
+
height: string;
|
|
77
|
+
} | null;
|
|
78
|
+
/**
|
|
79
|
+
* The width the box is HELD at for the whole flight, or null.
|
|
80
|
+
*
|
|
81
|
+
* The viewport x of a far edge both ends agree on, when there is one.
|
|
82
|
+
*
|
|
83
|
+
* The element is placed FROM it — `left: calc(<edge>px - var(--flemo-box-w))`
|
|
84
|
+
* — so the edge is derived from the very channel the width animates on and
|
|
85
|
+
* the two round together. Reached as position + size instead, the engine
|
|
86
|
+
* rounds each to its own layout unit and their sum oscillates: measured on a
|
|
87
|
+
* consumer's pill, 366.000 with the anchor against 365.985 ± 0.015 without,
|
|
88
|
+
* reversing six times in twenty-three frames, and every right-aligned thing
|
|
89
|
+
* inside it followed.
|
|
90
|
+
*/
|
|
91
|
+
heldEdge: number | null;
|
|
92
|
+
/**
|
|
93
|
+
* The `transform` the caller must set on the element, or null.
|
|
94
|
+
*
|
|
95
|
+
* Non-null only for a PINNED set, whose keyframes animate the pose's
|
|
96
|
+
* coordinates rather than the transform itself, and which therefore needs the
|
|
97
|
+
* transform that reads them (see PINNED_POSE_TRANSFORM).
|
|
98
|
+
*/
|
|
99
|
+
transform: string | null;
|
|
100
|
+
/**
|
|
101
|
+
* The `letter-spacing` the caller must set on the element, or null.
|
|
102
|
+
*
|
|
103
|
+
* Non-null where the tracking carries a correction, which is written as a sum
|
|
104
|
+
* of the author's own and the correction's, each on its own clock.
|
|
105
|
+
*/
|
|
106
|
+
letterSpacing: string | null;
|
|
34
107
|
}
|
|
35
108
|
/**
|
|
36
109
|
* The keyframes and the `animation` shorthand for one side of a morph.
|
|
@@ -64,11 +137,76 @@ export declare const buildMorphKeyframes: (input: {
|
|
|
64
137
|
from: MorphRect;
|
|
65
138
|
to: MorphRect;
|
|
66
139
|
} | null;
|
|
140
|
+
/**
|
|
141
|
+
* Whether the two ends lay their CONTENTS out in the same places, measured.
|
|
142
|
+
*
|
|
143
|
+
* A box animates for real wherever this is false, because something inside
|
|
144
|
+
* has a different place at the two ends and only a layout per frame can take
|
|
145
|
+
* it there. Where it is true the subtree is the same picture at every size on
|
|
146
|
+
* the way, so the box is laid out ONCE and the near edge is cut back with a
|
|
147
|
+
* clip instead (see the reveal below). It is measured rather than inferred
|
|
148
|
+
* from the box's shape: a shape says nothing about a consumer's subtree.
|
|
149
|
+
*/
|
|
150
|
+
contentsHold?: boolean;
|
|
67
151
|
/** Type morphs by growing, not by being scaled: px at each end. */
|
|
68
152
|
fontSize?: {
|
|
69
153
|
from: number;
|
|
70
154
|
to: number;
|
|
71
155
|
} | null;
|
|
156
|
+
/**
|
|
157
|
+
* The line-height as a STAIRCASE, holding the leading still for the flight.
|
|
158
|
+
*
|
|
159
|
+
* Supersedes `lineHeight` where it is given: the two cannot both author the
|
|
160
|
+
* property, and the staircase is the one that keeps the glyphs from stepping
|
|
161
|
+
* inside the box (see morphLine). It rides its own animation because it needs
|
|
162
|
+
* its own timing — each stop HOLDS until the next, which is what a staircase
|
|
163
|
+
* is, and the geometry keyframe cannot hold one channel while easing the rest.
|
|
164
|
+
*/
|
|
165
|
+
leading?: {
|
|
166
|
+
at: number;
|
|
167
|
+
lineHeight: number;
|
|
168
|
+
}[] | null;
|
|
169
|
+
/**
|
|
170
|
+
* The ascent's staircase, carried backwards on the box so the two cancel.
|
|
171
|
+
*
|
|
172
|
+
* A held leading still leaves the BASELINE stepping, because it sits an
|
|
173
|
+
* ascent below the inline box's top and the ascent is on the same grid. The
|
|
174
|
+
* two terms are both grid-locked, so nothing done to the line-height can make
|
|
175
|
+
* their sum smooth. The box under them is not grid-locked, so the flight
|
|
176
|
+
* sends the box the OTHER way by the same amount and the glyphs come out
|
|
177
|
+
* still: the box travels to `top + ascent` and a transform takes the ascent
|
|
178
|
+
* straight back off, exactly at both ends and within half a pixel between.
|
|
179
|
+
*
|
|
180
|
+
* A box wobbling half a pixel is nothing to look at; a line of type doing it
|
|
181
|
+
* is the tremor this exists to remove.
|
|
182
|
+
*/
|
|
183
|
+
lift?: {
|
|
184
|
+
at: number;
|
|
185
|
+
ascent: number;
|
|
186
|
+
}[] | null;
|
|
187
|
+
/**
|
|
188
|
+
* What the baseline owes at the START, in px, because the staircase holds the
|
|
189
|
+
* ARRIVAL's leading from the first frame (see attachMorph).
|
|
190
|
+
*
|
|
191
|
+
* Paid on the same channel as the ascent's cancellation and with the same
|
|
192
|
+
* shape: the whole amount at the departure, nothing at the landing, so what
|
|
193
|
+
* the flight travels is unchanged and only its first frame moves.
|
|
194
|
+
*/
|
|
195
|
+
leadStart?: number | null;
|
|
196
|
+
/**
|
|
197
|
+
* The correction that keeps a growing run of glyphs from drifting apart.
|
|
198
|
+
*
|
|
199
|
+
* A run's width against its size is one curve, and where it leaves the
|
|
200
|
+
* straight line between its ends every glyph carries the error that piled up
|
|
201
|
+
* before it. Spread the negative of that over the gaps and it cancels (see
|
|
202
|
+
* morphLine). It rides `letter-spacing` beside the author's own tracking, on
|
|
203
|
+
* its own clock, and is RAMPED rather than held because what it cancels is a
|
|
204
|
+
* curve rather than a staircase.
|
|
205
|
+
*/
|
|
206
|
+
track?: {
|
|
207
|
+
at: number;
|
|
208
|
+
fix: number;
|
|
209
|
+
}[] | null;
|
|
72
210
|
/** Type's other two dimensions, so it re-typesets rather than merely re-sizing. */
|
|
73
211
|
fontWeight?: {
|
|
74
212
|
from: number;
|
|
@@ -131,12 +269,32 @@ export declare const buildMorphKeyframes: (input: {
|
|
|
131
269
|
from: MorphClipInset;
|
|
132
270
|
to: MorphClipInset;
|
|
133
271
|
} | null;
|
|
272
|
+
/**
|
|
273
|
+
* The corner the box wears, so a clip that reveals it cuts a rounded shape
|
|
274
|
+
* rather than a square one (see the reveal below).
|
|
275
|
+
*/
|
|
276
|
+
radius?: string | null;
|
|
134
277
|
fade: {
|
|
135
278
|
from: TransitionTarget | null;
|
|
136
279
|
to: TransitionTarget | null;
|
|
137
280
|
duration: number;
|
|
138
281
|
/** Seconds to hold the from-pose before the fade runs, on top of `travel.start`. */
|
|
139
282
|
delay?: number;
|
|
283
|
+
/**
|
|
284
|
+
* The curve, where the flight's own is not the right one.
|
|
285
|
+
*
|
|
286
|
+
* A HAND-OVER IS A STEP, NOT A RAMP. Two opacity ramps crossing never
|
|
287
|
+
* compose back to what they replaced: at the midpoint of a 1-to-0 against a
|
|
288
|
+
* 0-to-1, alpha compositing leaves 1 - (1 - 0.5) * 0.5 = 0.75 of the pair,
|
|
289
|
+
* and the engines do not even sample the two on the same phase. Device-read
|
|
290
|
+
* on a consumer's tab switch: the copy at 0.48 against the arrival at 0.33,
|
|
291
|
+
* two frames of a washed-out box mid-travel — read as a blink.
|
|
292
|
+
*
|
|
293
|
+
* A step at the same instant on both sides has no such midpoint. Both are
|
|
294
|
+
* pure functions of one timeline, so every frame that renders at all
|
|
295
|
+
* renders exactly one of them, and a missed frame cannot land between.
|
|
296
|
+
*/
|
|
297
|
+
easing?: string;
|
|
140
298
|
} | null;
|
|
141
299
|
/**
|
|
142
300
|
* Everything the two ends paint differently — corner, surface, border,
|
|
@@ -147,15 +305,69 @@ export declare const buildMorphKeyframes: (input: {
|
|
|
147
305
|
from: string;
|
|
148
306
|
to: string;
|
|
149
307
|
}[];
|
|
308
|
+
/**
|
|
309
|
+
* Keep this set on the main thread even where its geometry is a transform the
|
|
310
|
+
* compositor could have run.
|
|
311
|
+
*
|
|
312
|
+
* A flight is one composition, and its parts are placed relative to each
|
|
313
|
+
* other. The moment one of them travels by its box — which is every flight
|
|
314
|
+
* where the element GROWS rather than being scaled — the frames it can be
|
|
315
|
+
* drawn on are the main thread's, and a part that keeps advancing without it
|
|
316
|
+
* separates from it by however far behind that thread is. A ghost is the
|
|
317
|
+
* clearest case: it is a copy of the departure whose only job is to sit on
|
|
318
|
+
* the element it dissolves into, and one that leads prints the card twice.
|
|
319
|
+
*
|
|
320
|
+
* So the flight decides once, and every part it emits abides by it. Ignored
|
|
321
|
+
* where the geometry is already layout-bound, which needs no help.
|
|
322
|
+
*/
|
|
323
|
+
pinned?: boolean;
|
|
324
|
+
/**
|
|
325
|
+
* Drive the travel's own position through registered properties.
|
|
326
|
+
*
|
|
327
|
+
* False only where they could not be registered, and there the position goes
|
|
328
|
+
* back to `left` and `top`: a literal `translate` would be run by WebKit's
|
|
329
|
+
* compositor while the size it belongs to waits for the main thread.
|
|
330
|
+
*/
|
|
331
|
+
travelPinned?: boolean;
|
|
150
332
|
}) => MorphKeyframeSet;
|
|
151
333
|
/**
|
|
152
334
|
* The CAMERA: the transform that takes a screen from resting to "zoomed onto
|
|
153
335
|
* this element", for a flight that carries its screen.
|
|
154
336
|
*
|
|
155
|
-
* One uniform scale and one translate
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
337
|
+
* One uniform scale and one translate.
|
|
338
|
+
*
|
|
339
|
+
* THE CAMERA IS NOT AN ANIMATION OF ITS OWN. It is defined as exactly the zoom
|
|
340
|
+
* that carries the element from one end of the flight to the other, and the two
|
|
341
|
+
* are emitted on one clock: same duration, same delay, same easing, released
|
|
342
|
+
* together. Measured, they agree to a thousandth of a frame at every sample.
|
|
343
|
+
*
|
|
344
|
+
* They still do not agree ON GLASS, because they are not PRESENTED by the same
|
|
345
|
+
* thread. A transform is one of the few things a compositor can run by itself,
|
|
346
|
+
* so the camera advances every vsync whatever the page is doing; the element
|
|
347
|
+
* travels by its box, which no compositor can interpolate, so it advances only
|
|
348
|
+
* on frames the main thread manages to produce. Isolated on both engines: with
|
|
349
|
+
* the main thread blocked mid-flight, a transform twin of a box travel ran 146px
|
|
350
|
+
* (Blink) and 167px (WebKit) ahead of it before the box moved at all. That gap
|
|
351
|
+
* is the whole defect — a card trailing the grid it is supposed to be opening
|
|
352
|
+
* out of, reported from iOS Safari as the camera being a beat ahead of the card.
|
|
353
|
+
*
|
|
354
|
+
* So where the element it carries is main-thread bound, the camera is too: the
|
|
355
|
+
* transform is composed from REGISTERED custom properties and the keyframes
|
|
356
|
+
* animate those, which no compositor can run because the substitution is style
|
|
357
|
+
* resolution's work. It then advances on exactly the frames the element does,
|
|
358
|
+
* and the pair is rigid again at whatever rate the device can hold.
|
|
359
|
+
*
|
|
360
|
+
* That is the only lever that works. `calc(var())` in the timing — the one this
|
|
361
|
+
* codebase already knew took a fade off WebKit's compositor — leaves a literal
|
|
362
|
+
* transform accelerated on both engines, as do constant `left`, `background-color`
|
|
363
|
+
* and `clip-path` channels alongside it; all four were measured to run away from
|
|
364
|
+
* the main thread exactly as the plain transform did. It costs nothing: the
|
|
365
|
+
* screen keeps its layer, so the per-frame work is a style resolution and a
|
|
366
|
+
* transform update, and frame times were indistinguishable from the literal form
|
|
367
|
+
* at 1x, 6x and 12x CPU throttle.
|
|
368
|
+
*
|
|
369
|
+
* Where the element's own travel IS a transform, the camera stays literal and
|
|
370
|
+
* accelerated: there is then nothing main-thread bound for it to wait for.
|
|
159
371
|
*
|
|
160
372
|
* The scale comes from WIDTH alone. The element's own box changes aspect across
|
|
161
373
|
* the flight, so no single uniform scale can match both axes, and width is the
|
|
@@ -182,8 +394,18 @@ export declare const buildCameraKeyframes: (input: {
|
|
|
182
394
|
settling: boolean;
|
|
183
395
|
duration: number;
|
|
184
396
|
start: number;
|
|
397
|
+
/** The flight's flat lead-in, baked here too: the camera is one of its parts. */
|
|
398
|
+
head?: number;
|
|
185
399
|
ease: AnimationOptions["ease"];
|
|
186
400
|
selector: string;
|
|
401
|
+
/**
|
|
402
|
+
* Emit the literal transform the compositor can run.
|
|
403
|
+
*
|
|
404
|
+
* True only where the element this camera carries is itself on the
|
|
405
|
+
* compositor; false pins the camera to the main thread's cadence, which is
|
|
406
|
+
* where a box travel lives (see above).
|
|
407
|
+
*/
|
|
408
|
+
accelerated: boolean;
|
|
187
409
|
}) => {
|
|
188
410
|
rules: string[];
|
|
189
411
|
name: string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { AnimationOptions } from '../transition/cssTypes';
|
|
2
|
+
/** The properties the hold writes, so a caller can reason about what it costs. */
|
|
3
|
+
export declare const LINE_HOLD: {
|
|
4
|
+
readonly whiteSpace: "nowrap";
|
|
5
|
+
readonly overflow: "hidden";
|
|
6
|
+
readonly textOverflow: "ellipsis";
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Hold the flying element to one line for the duration of a flight.
|
|
10
|
+
*
|
|
11
|
+
* Written as inline style, which is what the landing restores wholesale — the
|
|
12
|
+
* hold needs no undo of its own.
|
|
13
|
+
*/
|
|
14
|
+
export declare const holdOneLine: (element: HTMLElement) => void;
|
|
15
|
+
/** Whether a flight between these two ends should hold a single line. */
|
|
16
|
+
export declare const holdsOneLine: (from: boolean, to: boolean) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Extra leading, in px, for BOTH ends of a flight so the half-leading it
|
|
19
|
+
* renders never crosses a pixel boundary.
|
|
20
|
+
*
|
|
21
|
+
* Zero when either end cannot be measured, and zero when the travel is wider
|
|
22
|
+
* than one pixel of half-leading — no offset fits it in one, and a step
|
|
23
|
+
* somewhere is then the honest outcome. Mid-flight is where it belongs.
|
|
24
|
+
*/
|
|
25
|
+
export interface LeadingEnd {
|
|
26
|
+
lineHeight: number | null;
|
|
27
|
+
textHeight: number | null;
|
|
28
|
+
/** What the engine actually rendered for this end (see MorphSnapshot). */
|
|
29
|
+
leadOffset: number | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The half-leading a flight owes at its START, in px.
|
|
33
|
+
*
|
|
34
|
+
* The staircase holds ONE leading for the whole flight so the rendered
|
|
35
|
+
* half-leading cannot step, and the one it holds is the ARRIVAL's, because that
|
|
36
|
+
* is the value the landing has to restore. At the other end that makes the
|
|
37
|
+
* first frame render a line-height the departure never had, and half of that
|
|
38
|
+
* difference is where the departure's baseline sat.
|
|
39
|
+
*
|
|
40
|
+
* Half of the difference is not the answer, though: the engine puts the
|
|
41
|
+
* half-leading on a grid, so a line-height a pixel apart can render a whole
|
|
42
|
+
* pixel apart rather than half of one. Both ends reported where their line was
|
|
43
|
+
* actually put, so the grid is the one that reproduces both — the same
|
|
44
|
+
* authority `leadingBias` answers to — and where no candidate does, the
|
|
45
|
+
* arithmetic difference is the honest fallback.
|
|
46
|
+
*/
|
|
47
|
+
export declare const leadingOwed: (from: LeadingEnd, to: LeadingEnd, stops: LeadingStop[] | null) => number;
|
|
48
|
+
export declare const leadingBias: (from: LeadingEnd, to: LeadingEnd) => number;
|
|
49
|
+
export default holdOneLine;
|
|
50
|
+
export interface LeadingEndType {
|
|
51
|
+
fontSize: number | null;
|
|
52
|
+
lineHeight: number | null;
|
|
53
|
+
/** The face height the engine actually reported for this end. */
|
|
54
|
+
textHeight: number | null;
|
|
55
|
+
}
|
|
56
|
+
export interface LeadingStop {
|
|
57
|
+
/** Percent of the flight, 0 to 100. */
|
|
58
|
+
at: number;
|
|
59
|
+
lineHeight: number;
|
|
60
|
+
/**
|
|
61
|
+
* The face's ascent from this stop on.
|
|
62
|
+
*
|
|
63
|
+
* A held leading is only half the answer. The BASELINE sits an ascent below
|
|
64
|
+
* the inline box's top, and the ascent is on the same grid the leading is, so
|
|
65
|
+
* it steps just as often — device-measured at seventeen steps of half a pixel
|
|
66
|
+
* across one flight of forty-nine frames, which is a jump every third frame.
|
|
67
|
+
*
|
|
68
|
+
* Neither term can be made smooth: both are on the grid, so their sum is too,
|
|
69
|
+
* and a baseline that has nine and a half pixels of grid to climb must climb
|
|
70
|
+
* it in steps. What CAN be smooth is the box under them, because a box's
|
|
71
|
+
* position is not on any grid — so the flight carries the ascent's staircase
|
|
72
|
+
* BACKWARDS on the box and lets the two cancel (see `lift`).
|
|
73
|
+
*/
|
|
74
|
+
ascent: number;
|
|
75
|
+
}
|
|
76
|
+
export declare const leadingStops: (from: LeadingEndType, to: LeadingEndType, font: {
|
|
77
|
+
family: string;
|
|
78
|
+
weight: string | number;
|
|
79
|
+
style: string;
|
|
80
|
+
} | null, ease: AnimationOptions["ease"]) => LeadingStop[] | null;
|
|
81
|
+
export interface TrackStop {
|
|
82
|
+
/** Percent of the flight, 0 to 100. */
|
|
83
|
+
at: number;
|
|
84
|
+
/** The correction, in px, to add to every gap between the glyphs. */
|
|
85
|
+
fix: number;
|
|
86
|
+
}
|
|
87
|
+
/** The tracking correction for a growing run, or null where there is none to make. */
|
|
88
|
+
export declare const trackStops: (text: string, from: {
|
|
89
|
+
fontSize: number | null;
|
|
90
|
+
}, to: {
|
|
91
|
+
fontSize: number | null;
|
|
92
|
+
}, font: {
|
|
93
|
+
family: string;
|
|
94
|
+
weight: string | number;
|
|
95
|
+
style: string;
|
|
96
|
+
} | null, ease: AnimationOptions["ease"]) => TrackStop[] | null;
|
|
@@ -40,5 +40,52 @@ export declare const poseToCss: (pose: MorphPose) => string;
|
|
|
40
40
|
* measured travel and an author's flourish stack on one element instead of
|
|
41
41
|
* needing a wrapper each.
|
|
42
42
|
*/
|
|
43
|
+
export declare const composePoses: (poses: MorphPose[]) => MorphPose[];
|
|
43
44
|
export declare const composePosesToCss: (poses: MorphPose[]) => string;
|
|
45
|
+
/** The size an element wears while its box is driven through the channel. */
|
|
46
|
+
/** The channel a travelling box carries its width on, for anything placed from it. */
|
|
47
|
+
export declare const BOX_WIDTH_PROPERTY: "--flemo-box-w";
|
|
48
|
+
export declare const PINNED_BOX: string;
|
|
49
|
+
export declare const PINNED_BOX_HEIGHT: string;
|
|
50
|
+
/** One end of a pinned box, as the keyframe declarations that drive it. */
|
|
51
|
+
export declare const pinnedBoxDecls: (width: number, height: number, indent?: string) => string;
|
|
52
|
+
export declare const RULER: number;
|
|
53
|
+
export declare const onRuler: (value: number) => number;
|
|
54
|
+
export declare const pinnedBoxDeclsOnRuler: (width: number, height: number, indent?: string) => string;
|
|
55
|
+
/** The `letter-spacing` an element wears while its tracking is corrected. */
|
|
56
|
+
export declare const PINNED_TRACK: string;
|
|
57
|
+
/** One end of the authored tracking. */
|
|
58
|
+
export declare const pinnedTrackDecl: (value: number, indent?: string) => string;
|
|
59
|
+
/** One stop of the correction that keeps a run from drifting apart. */
|
|
60
|
+
export declare const pinnedTrackFixDecl: (value: number, indent?: string) => string;
|
|
61
|
+
export declare const PINNED_TRAVEL: string;
|
|
62
|
+
/** One end of a pinned travel, as the keyframe declarations that drive it. */
|
|
63
|
+
export declare const pinnedTravelDecls: (x: number, y: number, indent?: string) => string;
|
|
64
|
+
/** One stop of the ascent's staircase, on the half of the pair that holds. */
|
|
65
|
+
export declare const pinnedLiftDecl: (ascent: number, indent?: string) => string;
|
|
66
|
+
/** The `transform` an element wears while its pose is pinned. */
|
|
67
|
+
export declare const PINNED_POSE_TRANSFORM: string;
|
|
68
|
+
/**
|
|
69
|
+
* The registrations the pinned form needs, inserted once per document.
|
|
70
|
+
*
|
|
71
|
+
* They have to be REGISTERED: an unregistered custom property is a string to
|
|
72
|
+
* the engine and animates discretely, which would teleport a pose at its
|
|
73
|
+
* midpoint instead of interpolating it.
|
|
74
|
+
*
|
|
75
|
+
* One set of names for every flight rather than one per participant, because a
|
|
76
|
+
* registration is document-wide and re-registering invalidates style for the
|
|
77
|
+
* whole page. `inherits: false` is what makes that safe: each element holds its
|
|
78
|
+
* own values, so two flights never read each other's and no descendant inherits
|
|
79
|
+
* a pose meant for its parent.
|
|
80
|
+
*/
|
|
81
|
+
export declare const PINNED_POSE_PROPERTY_RULES: string[];
|
|
82
|
+
/**
|
|
83
|
+
* One pose as the keyframe declarations that drive `PINNED_POSE_TRANSFORM`.
|
|
84
|
+
*
|
|
85
|
+
* Every channel is written at both ends even where it does not change, because
|
|
86
|
+
* these are the coordinates of one transform rather than five animations: an
|
|
87
|
+
* end that omitted a channel would interpolate it from its registered initial
|
|
88
|
+
* value instead of holding it.
|
|
89
|
+
*/
|
|
90
|
+
export declare const pinnedPoseDecls: (pose: MorphPose, indent?: string) => string;
|
|
44
91
|
export declare const interpolatePose: (from: MorphPose, to: MorphPose, progress: number) => MorphPose;
|
|
@@ -7,3 +7,12 @@
|
|
|
7
7
|
* would by then point at someone else's keyframes.
|
|
8
8
|
*/
|
|
9
9
|
export declare const insertMorphRules: (rules: string[]) => (() => void);
|
|
10
|
+
/**
|
|
11
|
+
* Register the pinned pose's custom properties, and report whether they took.
|
|
12
|
+
*
|
|
13
|
+
* A browser that does not understand `@property` refuses the rule, and there
|
|
14
|
+
* every pose has to stay literal: unregistered, those properties would animate
|
|
15
|
+
* discretely and teleport a pose at its midpoint rather than interpolating it.
|
|
16
|
+
* A part that leads the rest of its flight is a flaw; one that jumps is a break.
|
|
17
|
+
*/
|
|
18
|
+
export declare const ensurePinnedPoses: () => boolean;
|