@braccato/core 0.1.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +267 -32
- package/dist/LICENSE +21 -0
- package/dist/README.md +298 -0
- package/dist/constants.d.ts +25 -0
- package/dist/constants.js +42 -0
- package/dist/element.d.ts +155 -0
- package/dist/element.js +655 -0
- package/dist/engine.d.ts +309 -0
- package/dist/engine.js +1940 -0
- package/dist/index.d.ts +12 -128
- package/dist/index.js +11 -949
- package/dist/inject.d.ts +37 -0
- package/dist/inject.js +376 -0
- package/dist/instrumental.d.ts +10 -0
- package/dist/instrumental.js +78 -0
- package/dist/renderer.d.ts +29 -0
- package/dist/renderer.js +349 -0
- package/dist/seek.d.ts +1 -0
- package/dist/seek.js +27 -0
- package/dist/styles/instrumental.css +103 -0
- package/dist/styles/lyrics.css +274 -0
- package/dist/styles/variables.css +181 -0
- package/dist/text.d.ts +11 -0
- package/dist/text.js +54 -0
- package/dist/themeSettings.d.ts +23 -0
- package/dist/themeSettings.js +126 -0
- package/dist/types.d.ts +199 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +21 -0
- package/dist/util.js +64 -0
- package/dist/view.d.ts +22 -0
- package/dist/view.js +93 -0
- package/package.json +34 -21
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { LineData } from "./inject.js";
|
|
2
|
+
import type { LyricsRendererHost, LyricSyncType, ResolvedTickOptions, TickOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Everything that belongs to a single rendered lyrics view: one scroll container, one selection,
|
|
5
|
+
* one set of pending scroll work.
|
|
6
|
+
*/
|
|
7
|
+
interface AnimEngineViewState {
|
|
8
|
+
/**
|
|
9
|
+
* The render records this view built, one per lyric line. They hold this view's elements and
|
|
10
|
+
* this view's `Animation` objects, so a second view never shares them.
|
|
11
|
+
*/
|
|
12
|
+
lines: LineData[];
|
|
13
|
+
lyricsContainer: HTMLElement | null;
|
|
14
|
+
syncType: LyricSyncType;
|
|
15
|
+
lyricWidth: number;
|
|
16
|
+
lyricHeight: number;
|
|
17
|
+
skipScrolls: number;
|
|
18
|
+
skipScrollsDecayTimes: number[];
|
|
19
|
+
scrollResumeTime: number;
|
|
20
|
+
scrollPos: number;
|
|
21
|
+
selectedElementIndex: number;
|
|
22
|
+
nextScrollAllowedTime: number;
|
|
23
|
+
wasUserScrolling: boolean;
|
|
24
|
+
lastActiveElements: LineData[];
|
|
25
|
+
queuedScroll: boolean;
|
|
26
|
+
lastScrollDebugContext: {
|
|
27
|
+
activeElms: LineData[];
|
|
28
|
+
centers: number[];
|
|
29
|
+
lyricScrollTime: number;
|
|
30
|
+
};
|
|
31
|
+
passiveScrollAccumulatedTime: number;
|
|
32
|
+
passiveLastWallTime: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `Window` alone types neither `ResizeObserver` nor `DOMMatrix`: both are ambient `var`
|
|
36
|
+
* declarations, so they only appear through `typeof globalThis`.
|
|
37
|
+
*/
|
|
38
|
+
type EngineWindow = Window & typeof globalThis;
|
|
39
|
+
export interface AnimationEngineInstance extends AnimEngineViewState {
|
|
40
|
+
document: Document;
|
|
41
|
+
window: EngineWindow;
|
|
42
|
+
host: LyricsRendererHost;
|
|
43
|
+
cachedTabRendererHeight: number | null;
|
|
44
|
+
tabRendererResizeObserver: ResizeObserver | null;
|
|
45
|
+
observedTabRenderer: HTMLElement | null;
|
|
46
|
+
lineScrollAnimations: LineScrollAnimationRecord[];
|
|
47
|
+
lineScrollAnimationToken: number;
|
|
48
|
+
pendingLineScroll: PendingLineScroll | null;
|
|
49
|
+
lineScrollElementTokens: WeakMap<HTMLElement, number>;
|
|
50
|
+
visibleWillChangeElements: Set<HTMLElement>;
|
|
51
|
+
cachedDurations: Map<string, number>;
|
|
52
|
+
cachedCSSValues: Map<string, string>;
|
|
53
|
+
cachedAnimationSettings: AnimationSettings | null;
|
|
54
|
+
/**
|
|
55
|
+
* Last value the tick was given. The passive scroll loop runs from an animation frame rather than
|
|
56
|
+
* from a tick, so it has no options object to read.
|
|
57
|
+
*/
|
|
58
|
+
passiveScrollEnabled: boolean;
|
|
59
|
+
passiveRAFId: number | null;
|
|
60
|
+
pendingLyricsUpdateFrame: number | null;
|
|
61
|
+
learnedAnimationTimingOffsetMs: number;
|
|
62
|
+
animationTimingVisibilityLogUntil: number;
|
|
63
|
+
/**
|
|
64
|
+
* Releases everything the instance holds on its window: the reduced motion listener, the tab
|
|
65
|
+
* renderer observer and any frame it still has queued.
|
|
66
|
+
*/
|
|
67
|
+
destroy(): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Runs an operation against every live instance. Cheap enough for a scroll or a song change, and
|
|
71
|
+
* deliberately not used inside the tick, which runs per frame and per line.
|
|
72
|
+
*/
|
|
73
|
+
export declare function forEveryLiveView(runOperation: (engine: AnimationEngineInstance) => void): void;
|
|
74
|
+
export declare function createAnimationEngineInstance(engineDocument: Document, engineWindow: EngineWindow, host: LyricsRendererHost): AnimationEngineInstance;
|
|
75
|
+
/**
|
|
76
|
+
* Forgets the last player snapshot, so the next tick is treated as the first one of a new song
|
|
77
|
+
* rather than as a jump away from the previous one.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resetPlaybackClock(): void;
|
|
80
|
+
/**
|
|
81
|
+
* The user asked for autoscroll back, now.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resetScrollResume(engine: AnimationEngineInstance): void;
|
|
84
|
+
/**
|
|
85
|
+
* The user asked for autoscroll back, now. Resuming is a property of playback rather than of one
|
|
86
|
+
* view, so every live instance resumes. Published in this shape rather than as the registry walk
|
|
87
|
+
* and the per view operation it is built from, so that nothing outside the module gets to name a
|
|
88
|
+
* particular view.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resumeAllAutoscroll(): void;
|
|
91
|
+
/**
|
|
92
|
+
* The user scrolled this view. Scrolls the engine itself performed are swallowed one at a time;
|
|
93
|
+
* a real one pauses autoscroll long enough to read where it landed, and offers the way back.
|
|
94
|
+
*
|
|
95
|
+
* @param isPassive - Whether the lyrics on screen are unsynced, and so are drifting on their own
|
|
96
|
+
* rather than following the song. Those resume sooner.
|
|
97
|
+
*/
|
|
98
|
+
export declare function noteUserScroll(engine: AnimationEngineInstance, isPassive: boolean): void;
|
|
99
|
+
/**
|
|
100
|
+
* Reports whether the container is a different size than the lines were last measured against, and
|
|
101
|
+
* clears the scroll cooldown when it is so the caller's re-measurement can scroll immediately. The
|
|
102
|
+
* new size is recorded by that re-measurement, not here.
|
|
103
|
+
*/
|
|
104
|
+
export declare function noteContainerResize(engine: AnimationEngineInstance, width: number, height: number): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Takes the lines this view is showing off the screen, keeping the container they were in, and
|
|
107
|
+
* reports whether there was anything there to take.
|
|
108
|
+
*/
|
|
109
|
+
export declare function clearOnScreenLyrics(engine: AnimationEngineInstance): boolean;
|
|
110
|
+
export declare function hasRenderedLines(engine: AnimationEngineInstance): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The render records this view built. Handing them out is a leak: they carry this view's elements
|
|
113
|
+
* and its `Animation` objects, so a caller that rewrites line times or hangs translations off them
|
|
114
|
+
* can only ever reach one view. Phase 5 revisits it, when both of those have to reach two.
|
|
115
|
+
*/
|
|
116
|
+
export declare function getRenderedLines(engine: AnimationEngineInstance): LineData[];
|
|
117
|
+
export declare function getRenderedSyncType(engine: AnimationEngineInstance): LyricSyncType;
|
|
118
|
+
/**
|
|
119
|
+
* Drops the song this view was rendering: its selection, its pending scroll work, its animations
|
|
120
|
+
* and its render records. The records go before the caller clears the DOM, so the elements they
|
|
121
|
+
* hold are released along with it.
|
|
122
|
+
*/
|
|
123
|
+
export declare function clearLyrics(engine: AnimationEngineInstance): void;
|
|
124
|
+
type LineScrollSide = "above" | "active" | "below";
|
|
125
|
+
interface LineScrollAnimationRecord {
|
|
126
|
+
animation: Animation;
|
|
127
|
+
lineElement: HTMLElement;
|
|
128
|
+
token: number;
|
|
129
|
+
}
|
|
130
|
+
interface AnimationConfig {
|
|
131
|
+
enabled: {
|
|
132
|
+
lineScale: boolean;
|
|
133
|
+
wordWobble: boolean;
|
|
134
|
+
highlightSwipe: boolean;
|
|
135
|
+
highlightGlow: boolean;
|
|
136
|
+
highlightFade: boolean;
|
|
137
|
+
scroll: boolean;
|
|
138
|
+
instrumental: boolean;
|
|
139
|
+
};
|
|
140
|
+
line: {
|
|
141
|
+
durationMs: number;
|
|
142
|
+
enterEasing: string;
|
|
143
|
+
exitEasing: string;
|
|
144
|
+
enterFrom: string;
|
|
145
|
+
enterTo: string;
|
|
146
|
+
exitFrom: string;
|
|
147
|
+
exitTo: string;
|
|
148
|
+
};
|
|
149
|
+
highlight: {
|
|
150
|
+
fadeInDurationMs: number;
|
|
151
|
+
fadeOutDurationMs: number;
|
|
152
|
+
fadeInEasing: string;
|
|
153
|
+
fadeOutEasing: string;
|
|
154
|
+
swipeEasing: string;
|
|
155
|
+
swipeStartFrom: string;
|
|
156
|
+
swipeEndFrom: string;
|
|
157
|
+
swipeStartTo: string;
|
|
158
|
+
swipeEndTo: string;
|
|
159
|
+
glowFrom: string;
|
|
160
|
+
glowTo: string;
|
|
161
|
+
glowDurationRatio: number;
|
|
162
|
+
glowMinDurationMs: number;
|
|
163
|
+
glowEasing: string;
|
|
164
|
+
};
|
|
165
|
+
word: {
|
|
166
|
+
wobbleDurationMs: number;
|
|
167
|
+
wobbleEasing: string;
|
|
168
|
+
wobblePeakEasing: string;
|
|
169
|
+
wobbleEndEasing: string;
|
|
170
|
+
wobbleFrom: string;
|
|
171
|
+
wobblePeak: string;
|
|
172
|
+
wobbleSettle: string;
|
|
173
|
+
wobbleTo: string;
|
|
174
|
+
wobblePeakOffset: number;
|
|
175
|
+
wobbleSettleOffset: number;
|
|
176
|
+
};
|
|
177
|
+
instrumental: {
|
|
178
|
+
fillFadeDurationMs: number;
|
|
179
|
+
fillFadeEasing: string;
|
|
180
|
+
fillFrom: string;
|
|
181
|
+
fillTo: string;
|
|
182
|
+
fillEasing: string;
|
|
183
|
+
waveFrom: string;
|
|
184
|
+
waveTo: string;
|
|
185
|
+
waveEasing: string;
|
|
186
|
+
waveOscillationDurationMs: number;
|
|
187
|
+
waveOscillationEasing: string;
|
|
188
|
+
};
|
|
189
|
+
scroll: {
|
|
190
|
+
durationMs: number;
|
|
191
|
+
easing: string;
|
|
192
|
+
};
|
|
193
|
+
lineScroll: {
|
|
194
|
+
durationMs: number;
|
|
195
|
+
easing: string;
|
|
196
|
+
differentialEffects: boolean;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
interface AnimationSettings {
|
|
200
|
+
config: AnimationConfig;
|
|
201
|
+
scrollTiming: {
|
|
202
|
+
earlyScrollConsiderS: number;
|
|
203
|
+
queueScrollMs: number;
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
export declare function noteVisibilityChange(engine: AnimationEngineInstance): void;
|
|
207
|
+
export declare function clearStyleCaches(engine: AnimationEngineInstance): void;
|
|
208
|
+
interface PreparedLineScroll {
|
|
209
|
+
lineElement: HTMLElement;
|
|
210
|
+
side: LineScrollSide;
|
|
211
|
+
token: number;
|
|
212
|
+
}
|
|
213
|
+
interface ResolvedLineScroll extends PreparedLineScroll {
|
|
214
|
+
durationMs: number;
|
|
215
|
+
startEasing: string;
|
|
216
|
+
endEasing: string;
|
|
217
|
+
startTranslate: string;
|
|
218
|
+
endTranslate: string;
|
|
219
|
+
}
|
|
220
|
+
interface LineScrollPlan {
|
|
221
|
+
items: ResolvedLineScroll[];
|
|
222
|
+
}
|
|
223
|
+
interface PendingLineScroll {
|
|
224
|
+
plan: LineScrollPlan;
|
|
225
|
+
activeLineElement: HTMLElement;
|
|
226
|
+
fromScrollTop: number;
|
|
227
|
+
toScrollTop: number;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Unsynced lyrics that this view still has on screen. `syncType` outlives the lyrics it was derived
|
|
231
|
+
* from, so the container is the term that says they are still there.
|
|
232
|
+
*/
|
|
233
|
+
export declare function hasUnsyncedLyrics(engine: AnimationEngineInstance): boolean;
|
|
234
|
+
/**
|
|
235
|
+
* "lyrics-missing" means this instance has nothing left to render. Whether that ends ticking is the
|
|
236
|
+
* driver's call, not the instance's: one instance running dry must not silence another.
|
|
237
|
+
*/
|
|
238
|
+
export type AnimationTickStatus = "ok" | "lyrics-missing";
|
|
239
|
+
/**
|
|
240
|
+
* Fills in everything a caller left out of a tick. The tick reads each of these arithmetically, so
|
|
241
|
+
* a missing one would not fail: it would quietly turn the playback time into NaN and leave the view
|
|
242
|
+
* matching no line at all.
|
|
243
|
+
*/
|
|
244
|
+
export declare function resolveTickOptions(options: TickOptions): ResolvedTickOptions;
|
|
245
|
+
/**
|
|
246
|
+
* Renders one view against a tick with nothing left out.
|
|
247
|
+
*/
|
|
248
|
+
export declare function tickView(engine: AnimationEngineInstance, currentTime: number, options: ResolvedTickOptions): AnimationTickStatus;
|
|
249
|
+
/**
|
|
250
|
+
* What a view has to know to size its scroll padding. Split out from the element reads so the
|
|
251
|
+
* arithmetic can be checked against the degenerate cases, which is where it went wrong: an
|
|
252
|
+
* unrendered container answers zero to every measurement.
|
|
253
|
+
*/
|
|
254
|
+
interface ScrollPaddingMeasurements {
|
|
255
|
+
viewportHeight: number;
|
|
256
|
+
targetScrollRatio: number;
|
|
257
|
+
/** The container's own height with whatever bottom padding it currently carries taken back off. */
|
|
258
|
+
contentHeight: number;
|
|
259
|
+
firstLineHeight: number;
|
|
260
|
+
/** Distance from the top of the container to the middle of the last line, or null with no lines. */
|
|
261
|
+
lastLineCentre: number | null;
|
|
262
|
+
lastLineHeight: number;
|
|
263
|
+
footerHeight: number;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sizes the padding above the first line and below the last one so either can sit at the view's
|
|
267
|
+
* target scroll position.
|
|
268
|
+
*
|
|
269
|
+
* The first two candidates are exact, and both are measured from the container. They are worth
|
|
270
|
+
* nothing while it is not rendering: every line reports zero, which reads as content that already
|
|
271
|
+
* runs past the last line and asks for no padding at all, and the last lines of the song then have
|
|
272
|
+
* nowhere to scroll to. The viewport keeps its height whether the lyrics render or not, so the
|
|
273
|
+
* space it alone demands below the last line is always knowable, and it is the floor. Over-padding
|
|
274
|
+
* costs nothing visible; under-padding strands the end of every song.
|
|
275
|
+
*/
|
|
276
|
+
export declare function computeScrollPadding(measurements: ScrollPaddingMeasurements): {
|
|
277
|
+
top: number;
|
|
278
|
+
bottom: number;
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* Re-reads the view's layout: the scroll padding first, then the line positions the padding moved.
|
|
282
|
+
*
|
|
283
|
+
* @param measureLines - Pass false while the lines are not being rendered. An unrendered container
|
|
284
|
+
* measures every line as zero height at zero offset, which would leave the scroll maths with
|
|
285
|
+
* nothing to work from once rendering resumes.
|
|
286
|
+
*/
|
|
287
|
+
export declare function relayout(engine: AnimationEngineInstance, measureLines: boolean): void;
|
|
288
|
+
/**
|
|
289
|
+
* Renders this view again against the last player snapshot, without moving the clock on. The
|
|
290
|
+
* options are built now rather than handed in, so a caller that reads settings at tick time still
|
|
291
|
+
* reads them at tick time.
|
|
292
|
+
*
|
|
293
|
+
* @param buildTickOptions - Given the snapshot the tick will run against, returns what to render it
|
|
294
|
+
* with.
|
|
295
|
+
*/
|
|
296
|
+
export declare function retickFromPlaybackClock(engine: AnimationEngineInstance, buildTickOptions: (eventCreationTime: number, isPlaying: boolean) => TickOptions): AnimationTickStatus;
|
|
297
|
+
/**
|
|
298
|
+
* Called when a new lyrics element is added to trigger re-sync.
|
|
299
|
+
* Debounced via requestAnimationFrame to avoid O(n²) layout thrashing
|
|
300
|
+
* when translations/romanizations load (each addition would otherwise
|
|
301
|
+
* re-measure ALL lines).
|
|
302
|
+
*
|
|
303
|
+
* @param isViewRendering - Asked on the frame rather than now. A driver that has stopped ticking is
|
|
304
|
+
* one whose lines may no longer be rendered, and an unrendered line measures as nothing, so a
|
|
305
|
+
* false answer declines both the re-measurement and the re-tick.
|
|
306
|
+
* @param retick - Runs after the re-measurement, on the frame.
|
|
307
|
+
*/
|
|
308
|
+
export declare function scheduleLyricPositionUpdate(engine: AnimationEngineInstance, isViewRendering: () => boolean, retick: () => void): void;
|
|
309
|
+
export {};
|