@motionscript/code 0.1.0-alpha.3 → 0.1.0-alpha.5
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/CHANGELOG.md +45 -0
- package/LICENSE +201 -201
- package/NOTICE +48 -0
- package/README.md +161 -108
- package/dist/browser/THIRD_PARTY_LICENSES.txt +431 -0
- package/dist/browser/index.js +7 -6
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/manifest.json +84 -2
- package/dist/code-range.d.ts +6 -0
- package/dist/code-range.d.ts.map +1 -1
- package/dist/code-range.js +8 -0
- package/dist/code-range.js.map +1 -1
- package/dist/diff.d.ts +14 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +134 -2
- package/dist/diff.js.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -1
- package/dist/engine.js.map +1 -1
- package/dist/highlight.d.ts +10 -2
- package/dist/highlight.d.ts.map +1 -1
- package/dist/highlight.js +40 -6
- package/dist/highlight.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layout.js.map +1 -1
- package/dist/node.d.ts +42 -7
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +174 -47
- package/dist/node.js.map +1 -1
- package/dist/nodes.js.map +1 -1
- package/dist/render.d.ts +6 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +59 -0
- package/dist/render.js.map +1 -1
- package/dist/style.d.ts +3 -3
- package/dist/style.d.ts.map +1 -1
- package/dist/style.js +10 -6
- package/dist/style.js.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +5 -1
- package/dist/tokens.js.map +1 -1
- package/dist/transitions.d.ts +19 -1
- package/dist/transitions.d.ts.map +1 -1
- package/dist/transitions.js.map +1 -1
- package/package.json +7 -5
- package/registry.json +6 -6
- package/src/code-range.ts +165 -156
- package/src/diff.ts +351 -216
- package/src/engine.ts +21 -21
- package/src/highlight.ts +340 -291
- package/src/index.ts +3 -3
- package/src/layout.ts +157 -157
- package/src/node.ts +1038 -904
- package/src/nodes.ts +18 -18
- package/src/render.ts +312 -247
- package/src/style.ts +255 -251
- package/src/tokens.ts +79 -75
- package/src/transitions.ts +204 -184
package/src/tokens.ts
CHANGED
|
@@ -1,75 +1,79 @@
|
|
|
1
|
-
import { canHighlight, highlightToTokens } from "./highlight";
|
|
2
|
-
import { CodeTheme } from "./style";
|
|
3
|
-
|
|
4
|
-
// IdToken is our internal wrapper around a highlighter token that gives every
|
|
5
|
-
// token a stable identity across line insertions/removals. All animation state
|
|
6
|
-
// is keyed by `id`, so concurrent edits at different positions don't clobber
|
|
7
|
-
// each other while one is mid-animation.
|
|
8
|
-
export interface IdToken {
|
|
9
|
-
id: number;
|
|
10
|
-
content: string;
|
|
11
|
-
color?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// IdLine wraps a row of tokens with its own stable id, so per-line animations
|
|
15
|
-
// (heightScale during insert/remove) survive line insertions/removals.
|
|
16
|
-
export interface IdLine {
|
|
17
|
-
id: number;
|
|
18
|
-
tokens: IdToken[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Module-global, monotonic id counters shared across all Code nodes — ids only
|
|
22
|
-
// need to be unique within a node, but a global counter is the simplest way to
|
|
23
|
-
// guarantee that and never collides.
|
|
24
|
-
let nextTokenId = 1;
|
|
25
|
-
let nextLineId = 1;
|
|
26
|
-
|
|
27
|
-
export function makeIdToken(content: string, color?: string): IdToken {
|
|
28
|
-
return { id: nextTokenId++, content, color };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function makeIdLine(tokens: IdToken[]): IdLine {
|
|
32
|
-
return { id: nextLineId++, tokens };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function tokenizeCodeToIdLines(code: string, language: string, theme: CodeTheme): IdLine[] {
|
|
36
|
-
// Until the language parser has loaded (it streams in via the asset loader),
|
|
37
|
-
// fall back to flat, uncolored lines instead of throwing on an unloaded
|
|
38
|
-
// grammar. The node re-tokenizes once the load completes.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
after.push(
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
1
|
+
import { canHighlight, highlightToTokens, normalizeSource, resolveTheme } from "./highlight";
|
|
2
|
+
import { CodeTheme } from "./style";
|
|
3
|
+
|
|
4
|
+
// IdToken is our internal wrapper around a highlighter token that gives every
|
|
5
|
+
// token a stable identity across line insertions/removals. All animation state
|
|
6
|
+
// is keyed by `id`, so concurrent edits at different positions don't clobber
|
|
7
|
+
// each other while one is mid-animation.
|
|
8
|
+
export interface IdToken {
|
|
9
|
+
id: number;
|
|
10
|
+
content: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// IdLine wraps a row of tokens with its own stable id, so per-line animations
|
|
15
|
+
// (heightScale during insert/remove) survive line insertions/removals.
|
|
16
|
+
export interface IdLine {
|
|
17
|
+
id: number;
|
|
18
|
+
tokens: IdToken[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Module-global, monotonic id counters shared across all Code nodes — ids only
|
|
22
|
+
// need to be unique within a node, but a global counter is the simplest way to
|
|
23
|
+
// guarantee that and never collides.
|
|
24
|
+
let nextTokenId = 1;
|
|
25
|
+
let nextLineId = 1;
|
|
26
|
+
|
|
27
|
+
export function makeIdToken(content: string, color?: string): IdToken {
|
|
28
|
+
return { id: nextTokenId++, content, color };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function makeIdLine(tokens: IdToken[]): IdLine {
|
|
32
|
+
return { id: nextLineId++, tokens };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function tokenizeCodeToIdLines(code: string, language: string, theme: CodeTheme): IdLine[] {
|
|
36
|
+
// Until the language parser has loaded (it streams in via the asset loader),
|
|
37
|
+
// fall back to flat, uncolored lines instead of throwing on an unloaded
|
|
38
|
+
// grammar. The node re-tokenizes once the load completes.
|
|
39
|
+
code = normalizeSource(code);
|
|
40
|
+
if (!canHighlight(language, theme)) {
|
|
41
|
+
// Resolved for its warning: an unknown theme is reported whether or not
|
|
42
|
+
// the grammar has landed yet.
|
|
43
|
+
resolveTheme(theme);
|
|
44
|
+
return code.split('\n').map(line => makeIdLine([makeIdToken(line, '#d1d5db')]));
|
|
45
|
+
}
|
|
46
|
+
return highlightToTokens(code, language, theme).map(line =>
|
|
47
|
+
makeIdLine(line.map(tok => makeIdToken(tok.content, tok.color))),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Split `tokens` at character offset `col` (relative to the joined content of
|
|
53
|
+
* the tokens). If `col` falls inside a token, that token is split into two new
|
|
54
|
+
* tokens with fresh ids (the original id is discarded — it didn't exist as a
|
|
55
|
+
* single unit on either side of the cut).
|
|
56
|
+
*/
|
|
57
|
+
export function splitTokensAt(
|
|
58
|
+
tokens: IdToken[],
|
|
59
|
+
col: number,
|
|
60
|
+
): { before: IdToken[]; after: IdToken[] } {
|
|
61
|
+
const before: IdToken[] = [];
|
|
62
|
+
const after: IdToken[] = [];
|
|
63
|
+
let off = 0;
|
|
64
|
+
for (const tok of tokens) {
|
|
65
|
+
const tStart = off;
|
|
66
|
+
const tEnd = off + tok.content.length;
|
|
67
|
+
if (tEnd <= col) {
|
|
68
|
+
before.push(tok);
|
|
69
|
+
} else if (tStart >= col) {
|
|
70
|
+
after.push(tok);
|
|
71
|
+
} else {
|
|
72
|
+
const cut = col - tStart;
|
|
73
|
+
before.push(makeIdToken(tok.content.slice(0, cut), tok.color));
|
|
74
|
+
after.push(makeIdToken(tok.content.slice(cut), tok.color));
|
|
75
|
+
}
|
|
76
|
+
off = tEnd;
|
|
77
|
+
}
|
|
78
|
+
return { before, after };
|
|
79
|
+
}
|
package/src/transitions.ts
CHANGED
|
@@ -1,184 +1,204 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// token
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
out
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
in
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
*
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
1
|
+
import type { NormalizedColor } from "@motionscript/sdk";
|
|
2
|
+
import type { CodeLayout } from "./layout";
|
|
3
|
+
import { IdLine } from "./tokens";
|
|
4
|
+
|
|
5
|
+
// A single animated token entry owned by one dim transition. Keyed by stable
|
|
6
|
+
// token id rather than (line, tokenIndex) so concurrent animations don't clobber
|
|
7
|
+
// each other when one shifts the indices of tokens the other is animating.
|
|
8
|
+
export interface AnimToken {
|
|
9
|
+
id: number;
|
|
10
|
+
opacity: number[];
|
|
11
|
+
opacityKeys: number[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A highlight cross-fade: opacity, and nothing else.
|
|
16
|
+
*
|
|
17
|
+
* Structure is untouched, so several of these stack happily and a listing held
|
|
18
|
+
* under a highlight still hits the static-layout cache.
|
|
19
|
+
*/
|
|
20
|
+
export interface DimTransition {
|
|
21
|
+
kind: "dim";
|
|
22
|
+
tokens: AnimToken[];
|
|
23
|
+
progress: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* When each part of a structural edit happens, in normalized transition time.
|
|
28
|
+
*
|
|
29
|
+
* The order is what an edit *looks like* being made: what is going away leaves
|
|
30
|
+
* first, what stays reflows into its new place, and only then does what is new
|
|
31
|
+
* appear. Running them together is what made a replacement look like a
|
|
32
|
+
* cross-dissolve of two unrelated listings.
|
|
33
|
+
*/
|
|
34
|
+
export interface Phases {
|
|
35
|
+
/** Removed tokens fade out. Null when the edit removes nothing. */
|
|
36
|
+
out: [number, number] | null;
|
|
37
|
+
/** Surviving tokens travel, and the block resizes. Always present. */
|
|
38
|
+
move: [number, number];
|
|
39
|
+
/** Added tokens fade (and slide) in. Null when the edit adds nothing. */
|
|
40
|
+
in: [number, number] | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* One edit in flight: the structure before, the structure after, and who is
|
|
45
|
+
* doing what.
|
|
46
|
+
*
|
|
47
|
+
* Both endpoints are fixed for the transition's whole life, which is why the
|
|
48
|
+
* two resolved layouts hang off it — they are computed once and reused for
|
|
49
|
+
* every frame the edit spans, rather than rebuilt per frame from a structure
|
|
50
|
+
* that is being mutated underneath.
|
|
51
|
+
*/
|
|
52
|
+
export interface StructuralTransition {
|
|
53
|
+
kind: "structural";
|
|
54
|
+
from: IdLine[];
|
|
55
|
+
to: IdLine[];
|
|
56
|
+
removedIds: Set<number>;
|
|
57
|
+
addedIds: Set<number>;
|
|
58
|
+
/**
|
|
59
|
+
* Ids of rows in `to` that did not exist before, and so enter as a whole
|
|
60
|
+
* row rather than as tokens spliced into one.
|
|
61
|
+
*
|
|
62
|
+
* They enter by fading in place, and that is the *only* thing they do. An
|
|
63
|
+
* earlier version slid each one in from the edge the block had just grown
|
|
64
|
+
* along — appended rows rising from below, prepended ones descending, ones
|
|
65
|
+
* spliced into the middle arriving from the left. Every one of those is a
|
|
66
|
+
* claim about where the line came from, and a listing has no such story to
|
|
67
|
+
* tell: text is written where it belongs, it does not travel there. Sliding
|
|
68
|
+
* also puts the row under the neighbours it is meant to sit between for the
|
|
69
|
+
* length of its entrance, which reads as the listing redrawing itself.
|
|
70
|
+
*/
|
|
71
|
+
enteringLineIds: Set<number>;
|
|
72
|
+
fromColorById: Map<number, string | undefined>;
|
|
73
|
+
phases: Phases;
|
|
74
|
+
progress: number;
|
|
75
|
+
/** @internal Layout cache — see {@link StructuralTransition}. */
|
|
76
|
+
layoutKey: string | null;
|
|
77
|
+
fromLayout: CodeLayout | null;
|
|
78
|
+
toLayout: CodeLayout | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A filled band behind whole rows: the first and last line it covers, by line id so it follows them through an edit, and its paint. */
|
|
82
|
+
export interface RowBand {
|
|
83
|
+
startId: number;
|
|
84
|
+
endId: number;
|
|
85
|
+
color: NormalizedColor;
|
|
86
|
+
radius: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One set of row bands becoming another. Bands at the same position in both
|
|
91
|
+
* lists travel and recolour; one with no counterpart fades.
|
|
92
|
+
*/
|
|
93
|
+
export interface RowsTransition {
|
|
94
|
+
kind: "rows";
|
|
95
|
+
from: RowBand[];
|
|
96
|
+
to: RowBand[];
|
|
97
|
+
progress: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type CodeTransition = DimTransition | StructuralTransition | RowsTransition;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Decides *when* (in normalized transition time) each part of a structural
|
|
104
|
+
* edit happens, given only whether the edit removes/adds anything.
|
|
105
|
+
* `phasesFor` below is the default implementation (exported as
|
|
106
|
+
* `defaultCodePhases`) — pass a `phaseStrategy` of this shape to `<Code>` to
|
|
107
|
+
* replace it with your own timing.
|
|
108
|
+
*/
|
|
109
|
+
export type CodePhaseStrategy = (hasRemoved: boolean, hasAdded: boolean) => Phases;
|
|
110
|
+
|
|
111
|
+
export interface TokenState {
|
|
112
|
+
opacity: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Sample a piecewise-linear curve (keys → values) at progress `p` in [0, 1]. */
|
|
116
|
+
export function sampleCurve(keys: number[], values: number[], p: number): number {
|
|
117
|
+
if (p <= keys[0]) return values[0];
|
|
118
|
+
if (p >= keys[keys.length - 1]) return values[values.length - 1];
|
|
119
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
120
|
+
if (p <= keys[i + 1]) {
|
|
121
|
+
const span = keys[i + 1] - keys[i];
|
|
122
|
+
const local = span === 0 ? 0 : (p - keys[i]) / span;
|
|
123
|
+
return values[i] + (values[i + 1] - values[i]) * local;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return values[values.length - 1];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Progress *within* a phase window, clamped to [0, 1]. A null window is complete. */
|
|
130
|
+
export function windowProgress(w: [number, number] | null, p: number): number {
|
|
131
|
+
if (!w) return 1;
|
|
132
|
+
const [a, b] = w;
|
|
133
|
+
if (p <= a) return 0;
|
|
134
|
+
if (p >= b) return 1;
|
|
135
|
+
return b === a ? 1 : (p - a) / (b - a);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Smooth start and end, so a phase never begins or ends with a velocity step. */
|
|
139
|
+
export function smoothstep(t: number): number {
|
|
140
|
+
return t * t * (3 - 2 * t);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function makeAnim(id: number, curve: { keys: number[]; values: number[] }): AnimToken {
|
|
144
|
+
return { id, opacity: curve.values, opacityKeys: curve.keys };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The phase schedule for an edit, shaped by what the edit actually contains.
|
|
149
|
+
*
|
|
150
|
+
* A fixed schedule wastes time it does not need: a pure append has nothing to
|
|
151
|
+
* fade out, so holding the first 40% of the duration empty just makes the
|
|
152
|
+
* animation feel late. Each shape gets the whole duration spent on the parts
|
|
153
|
+
* that exist, with adjacent phases overlapping enough to read as one movement.
|
|
154
|
+
*/
|
|
155
|
+
export function phasesFor(hasRemoved: boolean, hasAdded: boolean): Phases {
|
|
156
|
+
// The overlaps are deliberate and small. Butt-joining the phases leaves the
|
|
157
|
+
// block sitting empty at its new size for a beat between the old content
|
|
158
|
+
// leaving and the new content arriving; letting the arrival start while the
|
|
159
|
+
// reflow's eased tail is still running closes that gap without blurring the
|
|
160
|
+
// order the edit reads in.
|
|
161
|
+
if (hasRemoved && hasAdded) return { out: [0, 0.38], move: [0.10, 0.68], in: [0.52, 1] };
|
|
162
|
+
if (hasRemoved) return { out: [0, 0.45], move: [0.22, 1], in: null };
|
|
163
|
+
if (hasAdded) return { out: null, move: [0, 0.55], in: [0.38, 1] };
|
|
164
|
+
return { out: null, move: [0, 1], in: null };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** {@link phasesFor} under the name a `phaseStrategy` default is looked up by. */
|
|
168
|
+
export const defaultCodePhases: CodePhaseStrategy = phasesFor;
|
|
169
|
+
|
|
170
|
+
/** How far through the "everything reflows" phase a frame is, eased. A settled frame (no edit) is always fully moved. */
|
|
171
|
+
export function moveProgress(edit: { phases: Phases; progress: number } | null): number {
|
|
172
|
+
return edit ? smoothstep(windowProgress(edit.phases.move, edit.progress)) : 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the per-token opacity multiplier for the current frame from the
|
|
177
|
+
* persistent highlight dim plus every active dim transition.
|
|
178
|
+
*/
|
|
179
|
+
export function resolveTokenStates(
|
|
180
|
+
tokenLines: IdLine[],
|
|
181
|
+
transitions: CodeTransition[],
|
|
182
|
+
highlightDimOpacity: number | null,
|
|
183
|
+
highlightedIds: Set<number>,
|
|
184
|
+
): Map<number, TokenState> {
|
|
185
|
+
const out = new Map<number, TokenState>();
|
|
186
|
+
|
|
187
|
+
if (highlightDimOpacity !== null) {
|
|
188
|
+
const dim = highlightDimOpacity;
|
|
189
|
+
for (const line of tokenLines) {
|
|
190
|
+
for (const tok of line.tokens) {
|
|
191
|
+
out.set(tok.id, { opacity: highlightedIds.has(tok.id) ? 1 : dim });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const tr of transitions) {
|
|
197
|
+
if (tr.kind !== "dim") continue;
|
|
198
|
+
for (const at of tr.tokens) {
|
|
199
|
+
out.set(at.id, { opacity: sampleCurve(at.opacityKeys, at.opacity, tr.progress) });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return out;
|
|
204
|
+
}
|