@braccato/core 1.18.0 → 1.19.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/dist/engine.d.ts +3 -1
- package/dist/engine.js +29 -18
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/engine.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ interface AnimEngineViewState {
|
|
|
31
31
|
};
|
|
32
32
|
passiveScrollAccumulatedTime: number;
|
|
33
33
|
passiveLastWallTime: number;
|
|
34
|
+
/** Where the passive drift last put the scroll, so a scroll that lands anywhere else is the reader's. */
|
|
35
|
+
passiveScrollTop: number | null;
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
38
|
* `Window` alone types neither `ResizeObserver` nor `DOMMatrix`: both are ambient `var`
|
|
@@ -116,7 +118,7 @@ export declare function resetScrollResume(engine: AnimationEngineInstance): void
|
|
|
116
118
|
*/
|
|
117
119
|
export declare function resumeAllAutoscroll(): void;
|
|
118
120
|
/**
|
|
119
|
-
* The user scrolled this view. Scrolls the engine itself performed are swallowed
|
|
121
|
+
* The user scrolled this view. Scrolls the engine itself performed are swallowed;
|
|
120
122
|
* a real one pauses autoscroll long enough to read where it landed, and offers the way back.
|
|
121
123
|
*
|
|
122
124
|
* @param isPassive - Whether the lyrics on screen are unsynced, and so are drifting on their own
|
package/dist/engine.js
CHANGED
|
@@ -26,6 +26,7 @@ const LYRICS_CHECK_INTERVAL_ERROR = "Error in lyrics check interval:";
|
|
|
26
26
|
const PAUSING_LYRICS_SCROLL_LOG = "Pausing Lyrics Autoscroll Due to User Scroll";
|
|
27
27
|
const USER_SCROLL_RESUME_DELAY_MS = 25000;
|
|
28
28
|
const PASSIVE_USER_SCROLL_RESUME_DELAY_MS = 5000;
|
|
29
|
+
const PASSIVE_DRIFT_SCROLL_TOLERANCE_PX = 1;
|
|
29
30
|
const LYRIC_ENDING_THRESHOLD_S = registerThemeSetting("blyrics-lyric-ending-threshold-s", 0.5);
|
|
30
31
|
const EARLY_SCROLL_CONSIDER = registerThemeSetting("blyrics-early-scroll-consider-s", 0.54);
|
|
31
32
|
const TIME_JUMP_THRESHOLD = 0.5;
|
|
@@ -134,6 +135,7 @@ export function createAnimationEngineInstance(engineDocument, engineWindow, host
|
|
|
134
135
|
},
|
|
135
136
|
passiveScrollAccumulatedTime: 0,
|
|
136
137
|
passiveLastWallTime: 0,
|
|
138
|
+
passiveScrollTop: null,
|
|
137
139
|
cachedTabRendererHeight: null,
|
|
138
140
|
cachedScrollInsets: NO_SCROLL_INSETS,
|
|
139
141
|
cachedFooterItem: null,
|
|
@@ -231,14 +233,18 @@ export function resumeAllAutoscroll() {
|
|
|
231
233
|
forEveryLiveView(resetScrollResume);
|
|
232
234
|
}
|
|
233
235
|
/**
|
|
234
|
-
* The user scrolled this view. Scrolls the engine itself performed are swallowed
|
|
236
|
+
* The user scrolled this view. Scrolls the engine itself performed are swallowed;
|
|
235
237
|
* a real one pauses autoscroll long enough to read where it landed, and offers the way back.
|
|
236
238
|
*
|
|
237
239
|
* @param isPassive - Whether the lyrics on screen are unsynced, and so are drifting on their own
|
|
238
240
|
* rather than following the song. Those resume sooner.
|
|
239
241
|
*/
|
|
240
242
|
export function noteUserScroll(engine, isPassive) {
|
|
241
|
-
if (
|
|
243
|
+
if (isPassive) {
|
|
244
|
+
if (isPassiveDriftScroll(engine))
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
else if (engine.skipScrolls > 0) {
|
|
242
248
|
engine.skipScrolls--;
|
|
243
249
|
engine.skipScrollsDecayTimes.shift();
|
|
244
250
|
return;
|
|
@@ -314,6 +320,7 @@ export function clearLyrics(engine) {
|
|
|
314
320
|
engine.lastScrollDebugContext.centers = [];
|
|
315
321
|
engine.passiveScrollAccumulatedTime = 0;
|
|
316
322
|
engine.passiveLastWallTime = 0;
|
|
323
|
+
engine.passiveScrollTop = null;
|
|
317
324
|
stopPassiveScrollLoop(engine);
|
|
318
325
|
engine.lines = [];
|
|
319
326
|
engine.cachedFooterItem = null;
|
|
@@ -2024,7 +2031,7 @@ function prepareUpcomingLineScroll(engine, lines, activeLine, scrollDeltaPx, fro
|
|
|
2024
2031
|
}
|
|
2025
2032
|
}
|
|
2026
2033
|
// -- Skip Scrolls Decay --------------------------
|
|
2027
|
-
function decaySkipScrolls(engine, now) {
|
|
2034
|
+
function decaySkipScrolls(engine, now, keepsReserve) {
|
|
2028
2035
|
let j = 0;
|
|
2029
2036
|
for (; j < engine.skipScrollsDecayTimes.length; j++) {
|
|
2030
2037
|
if (engine.skipScrollsDecayTimes[j] > now) {
|
|
@@ -2032,10 +2039,7 @@ function decaySkipScrolls(engine, now) {
|
|
|
2032
2039
|
}
|
|
2033
2040
|
}
|
|
2034
2041
|
engine.skipScrollsDecayTimes = engine.skipScrollsDecayTimes.slice(j);
|
|
2035
|
-
engine.skipScrolls
|
|
2036
|
-
if (engine.skipScrolls < 1) {
|
|
2037
|
-
engine.skipScrolls = 1;
|
|
2038
|
-
}
|
|
2042
|
+
engine.skipScrolls = Math.max(keepsReserve ? 1 : 0, engine.skipScrolls - j);
|
|
2039
2043
|
}
|
|
2040
2044
|
// -- Passive Scroll Engine --------------------------
|
|
2041
2045
|
/**
|
|
@@ -2135,15 +2139,18 @@ function passiveScrollEngine(engine, isPlaying) {
|
|
|
2135
2139
|
// Phase 4: hold at top
|
|
2136
2140
|
targetScroll = 0;
|
|
2137
2141
|
}
|
|
2138
|
-
const prevScrollTop = tabRenderer.scrollTop;
|
|
2139
2142
|
tabRenderer.scrollTop = targetScroll;
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2143
|
+
engine.passiveScrollTop = tabRenderer.scrollTop;
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* The drift writes the scroll every frame, and a browser folds that scroll and the reader's into one
|
|
2147
|
+
* event, so the drift's own scrolls are told apart by where they land rather than by counting them.
|
|
2148
|
+
*/
|
|
2149
|
+
function isPassiveDriftScroll(engine) {
|
|
2150
|
+
const scrollTop = engine.host.getScrollElement()?.scrollTop;
|
|
2151
|
+
return (engine.passiveScrollTop !== null &&
|
|
2152
|
+
scrollTop !== undefined &&
|
|
2153
|
+
Math.abs(scrollTop - engine.passiveScrollTop) < PASSIVE_DRIFT_SCROLL_TOLERANCE_PX);
|
|
2147
2154
|
}
|
|
2148
2155
|
/**
|
|
2149
2156
|
* Sets up a ResizeObserver on the tab renderer to cache its height.
|
|
@@ -2236,6 +2243,7 @@ export function resolveTickOptions(options) {
|
|
|
2236
2243
|
passiveScrollEnabled: options.passiveScrollEnabled ?? false,
|
|
2237
2244
|
playbackRate: resolvePlaybackRate(options.playbackRate),
|
|
2238
2245
|
followPausedSeeks: options.followPausedSeeks ?? false,
|
|
2246
|
+
countEveryUserScroll: options.countEveryUserScroll ?? false,
|
|
2239
2247
|
};
|
|
2240
2248
|
}
|
|
2241
2249
|
// A rate of zero or less would freeze every animation that follows the song, which is a second
|
|
@@ -2885,9 +2893,12 @@ export function tickView(engine, currentTime, options) {
|
|
|
2885
2893
|
}
|
|
2886
2894
|
scrollTop = scrollPos;
|
|
2887
2895
|
engine.scrollPos = scrollTop;
|
|
2896
|
+
const scrollTopBeforeWrite = tabRenderer.scrollTop;
|
|
2888
2897
|
tabRenderer.scrollTop = scrollTop;
|
|
2889
|
-
|
|
2890
|
-
|
|
2898
|
+
if (tabRenderer.scrollTop !== scrollTopBeforeWrite) {
|
|
2899
|
+
engine.skipScrolls += 1;
|
|
2900
|
+
engine.skipScrollsDecayTimes.push(Date.now() + 2000);
|
|
2901
|
+
}
|
|
2891
2902
|
}
|
|
2892
2903
|
}
|
|
2893
2904
|
if (engine.wasUserScrolling && engine.scrollResumeTime < Date.now()) {
|
|
@@ -2895,7 +2906,7 @@ export function tickView(engine, currentTime, options) {
|
|
|
2895
2906
|
lyricsElement.classList.remove(USER_SCROLLING_CLASS);
|
|
2896
2907
|
engine.wasUserScrolling = false;
|
|
2897
2908
|
}
|
|
2898
|
-
decaySkipScrolls(engine, now);
|
|
2909
|
+
decaySkipScrolls(engine, now, !options.countEveryUserScroll);
|
|
2899
2910
|
}
|
|
2900
2911
|
catch (err) {
|
|
2901
2912
|
if (!err.message?.includes("undefined")) {
|
package/dist/types.d.ts
CHANGED
|
@@ -41,6 +41,13 @@ export interface TickOptions {
|
|
|
41
41
|
* new time, as an editor scrubbing a paused song needs. Defaults to false.
|
|
42
42
|
*/
|
|
43
43
|
followPausedSeeks?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Whether every user scroll the host reports can pause autoscroll of synced lyrics. By default
|
|
46
|
+
* the view always holds one scroll back, for the one a refocused window fires, and a host that
|
|
47
|
+
* ticks every frame spends that reserve on each scroll the reader makes. Unsynced lyrics drifting
|
|
48
|
+
* on their own are not covered. Defaults to false.
|
|
49
|
+
*/
|
|
50
|
+
countEveryUserScroll?: boolean;
|
|
44
51
|
}
|
|
45
52
|
/**
|
|
46
53
|
* A tick with nothing left out. The engine reads every field unconditionally, so the defaults are
|