@braccato/core 1.3.0 → 1.4.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/README.md +5 -1
- package/dist/README.md +5 -1
- package/dist/engine.js +54 -1
- package/dist/inject.d.ts +1 -0
- package/dist/inject.js +1 -0
- package/dist/styles/lyrics.css +9 -0
- package/dist/styles/variables.css +2 -0
- package/dist/types.d.ts +8 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -235,7 +235,11 @@ package's stylesheets are yours to load for the same reason.
|
|
|
235
235
|
`@braccato/core` is the facade and registers nothing. `createLyricsRenderer(options)` returns one
|
|
236
236
|
`LyricsRenderer`: give it lyrics, tick it, and it owns the DOM it builds and every re-measurement
|
|
237
237
|
that DOM needs. `resetPlaybackClock`, `resumeAllAutoscroll`, `injectRomanization` and
|
|
238
|
-
`injectTranslation` are published beside it, for what one instance cannot answer for on its own.
|
|
238
|
+
`injectTranslation` are published beside it, for what one instance cannot answer for on its own. Hang
|
|
239
|
+
one onto a built line and call `renderer.scheduleLyricPositionUpdate` the way you already would to
|
|
240
|
+
catch the layout up: the hung line floats into place while the lines around it slide to make room,
|
|
241
|
+
rather than the rest of the lyric jumping down. `--blyrics-animate-decoration-entry` set to `0` drops
|
|
242
|
+
both for an instant insert, and reduced-motion does the same.
|
|
239
243
|
|
|
240
244
|
`@braccato/core/element` registers `<braccato-lyrics>`, and `<better-lyrics>` beside it, on import.
|
|
241
245
|
Registration is a side effect, which is why it is entered separately.
|
package/dist/README.md
CHANGED
|
@@ -235,7 +235,11 @@ package's stylesheets are yours to load for the same reason.
|
|
|
235
235
|
`@braccato/core` is the facade and registers nothing. `createLyricsRenderer(options)` returns one
|
|
236
236
|
`LyricsRenderer`: give it lyrics, tick it, and it owns the DOM it builds and every re-measurement
|
|
237
237
|
that DOM needs. `resetPlaybackClock`, `resumeAllAutoscroll`, `injectRomanization` and
|
|
238
|
-
`injectTranslation` are published beside it, for what one instance cannot answer for on its own.
|
|
238
|
+
`injectTranslation` are published beside it, for what one instance cannot answer for on its own. Hang
|
|
239
|
+
one onto a built line and call `renderer.scheduleLyricPositionUpdate` the way you already would to
|
|
240
|
+
catch the layout up: the hung line floats into place while the lines around it slide to make room,
|
|
241
|
+
rather than the rest of the lyric jumping down. `--blyrics-animate-decoration-entry` set to `0` drops
|
|
242
|
+
both for an instant insert, and reduced-motion does the same.
|
|
239
243
|
|
|
240
244
|
`@braccato/core/element` registers `<braccato-lyrics>`, and `<better-lyrics>` beside it, on import.
|
|
241
245
|
Registration is a side effect, which is why it is entered separately.
|
package/dist/engine.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// playback clock the last tick wrote. Their unit is a bundle rather than a document, and this
|
|
17
17
|
// module is bundled into the isolated world and the page world separately, so those are two clocks
|
|
18
18
|
// that never meet. `themeSettings.ts` holds the third thing under that rule.
|
|
19
|
-
import { ANIMATING_CLASS, CURRENT_LYRICS_CLASS, FOOTER_CLASS, LINE_CLASS, PAUSED_CLASS, USER_SCROLLING_CLASS, } from "./constants.js";
|
|
19
|
+
import { ANIMATING_CLASS, CURRENT_LYRICS_CLASS, FOOTER_CLASS, LINE_CLASS, PAUSED_CLASS, ROMANIZED_LYRICS_CLASS, TRANSLATED_LYRICS_CLASS, USER_SCROLLING_CLASS, } from "./constants.js";
|
|
20
20
|
import { INSTRUMENTAL_WAVE_PATH_HIGH, INSTRUMENTAL_WAVE_PATH_LOW } from "./instrumental.js";
|
|
21
21
|
import { registerThemeSetting } from "./themeSettings.js";
|
|
22
22
|
import { clamp, getRelativeLayoutBounds, positiveModulo, roundedMs, toMs } from "./util.js";
|
|
@@ -1930,10 +1930,61 @@ export function relayout(engine, measureLines) {
|
|
|
1930
1930
|
const bounds = getRelativeLayoutBounds(lyricsElement, line.lyricElement);
|
|
1931
1931
|
line.position = bounds.y;
|
|
1932
1932
|
line.height = bounds.height;
|
|
1933
|
+
line.decorations = new Map(lineDecorators(line.lyricElement).map(element => [element, getRelativeLayoutBounds(lyricsElement, element).y]));
|
|
1933
1934
|
}
|
|
1934
1935
|
engine.wasUserScrolling = true; // trigger rescrolls
|
|
1935
1936
|
engine.host.debug?.resize();
|
|
1936
1937
|
}
|
|
1938
|
+
// -- Decoration slide --------------------------
|
|
1939
|
+
const DECORATION_SLIDE_MS = 350;
|
|
1940
|
+
const DECORATION_SLIDE_EASING = "cubic-bezier(0.25, 1, 0.5, 1)";
|
|
1941
|
+
const DECORATION_MIN_SHIFT_PX = 0.5;
|
|
1942
|
+
function slideElementBy(element, dy) {
|
|
1943
|
+
element.animate({ translate: [`0 ${dy}px`, "0 0"] }, { duration: DECORATION_SLIDE_MS, easing: DECORATION_SLIDE_EASING, composite: "add" });
|
|
1944
|
+
}
|
|
1945
|
+
function decorationSlideAllowed(engine, container) {
|
|
1946
|
+
if (engine.window.matchMedia(REDUCED_MOTION_QUERY).matches)
|
|
1947
|
+
return false;
|
|
1948
|
+
return (engine.window.getComputedStyle(container).getPropertyValue("--blyrics-animate-decoration-entry").trim() !== "0");
|
|
1949
|
+
}
|
|
1950
|
+
function lineDecorators(lineElement) {
|
|
1951
|
+
return [
|
|
1952
|
+
...lineElement.querySelectorAll(`.${ROMANIZED_LYRICS_CLASS}`),
|
|
1953
|
+
...lineElement.querySelectorAll(`.${TRANSLATED_LYRICS_CLASS}`),
|
|
1954
|
+
];
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Snapshots where the lines and their decorators sit now, so the remeasure that follows can slide
|
|
1958
|
+
* each one from there to wherever a streamed decoration just pushed it. Returns null when nothing
|
|
1959
|
+
* should animate, and otherwise a function to call once the new layout is measured: the line rides
|
|
1960
|
+
* its own shift and a decorator rides only the part of its shift its line did not already carry, so
|
|
1961
|
+
* a survivor whose line held still still slides into the gap a removed sibling left.
|
|
1962
|
+
*/
|
|
1963
|
+
function captureDecorationSlide(engine) {
|
|
1964
|
+
const container = engine.lyricsContainer;
|
|
1965
|
+
if (!container || !decorationSlideAllowed(engine, container))
|
|
1966
|
+
return null;
|
|
1967
|
+
const before = engine.lines.map(line => ({
|
|
1968
|
+
line,
|
|
1969
|
+
position: line.position,
|
|
1970
|
+
decorations: new Map(line.decorations),
|
|
1971
|
+
}));
|
|
1972
|
+
return () => {
|
|
1973
|
+
for (const { line, position, decorations } of before) {
|
|
1974
|
+
const lineShift = position - line.position;
|
|
1975
|
+
if (Math.abs(lineShift) >= DECORATION_MIN_SHIFT_PX)
|
|
1976
|
+
slideElementBy(line.lyricElement, lineShift);
|
|
1977
|
+
for (const [element, was] of decorations) {
|
|
1978
|
+
const now = line.decorations.get(element);
|
|
1979
|
+
if (now === undefined)
|
|
1980
|
+
continue;
|
|
1981
|
+
const shift = was - now - lineShift;
|
|
1982
|
+
if (Math.abs(shift) >= DECORATION_MIN_SHIFT_PX)
|
|
1983
|
+
slideElementBy(element, shift);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1937
1988
|
// -- Debounced Lyrics Update --------------------------
|
|
1938
1989
|
function cancelLyricPositionUpdate(engine) {
|
|
1939
1990
|
if (engine.pendingLyricsUpdateFrame === null)
|
|
@@ -1971,7 +2022,9 @@ export function scheduleLyricPositionUpdate(engine, isViewRendering, retick) {
|
|
|
1971
2022
|
engine.pendingLyricsUpdateFrame = engine.window.requestAnimationFrame(() => {
|
|
1972
2023
|
engine.pendingLyricsUpdateFrame = null;
|
|
1973
2024
|
const isRendering = isViewRendering();
|
|
2025
|
+
const playSlide = isRendering ? captureDecorationSlide(engine) : null;
|
|
1974
2026
|
relayout(engine, isRendering);
|
|
2027
|
+
playSlide?.();
|
|
1975
2028
|
if (!isRendering)
|
|
1976
2029
|
return;
|
|
1977
2030
|
retick();
|
package/dist/inject.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export type LineData = {
|
|
|
28
28
|
isSelected: boolean;
|
|
29
29
|
height: number;
|
|
30
30
|
position: number;
|
|
31
|
+
decorations: Map<HTMLElement, number>;
|
|
31
32
|
} & AnimationData;
|
|
32
33
|
export declare function newLineData(lyricElement: HTMLElement, startTimeMs: number, durationMs: number): LineData;
|
|
33
34
|
export declare function applyDirection(element: HTMLElement, text: string): void;
|
package/dist/inject.js
CHANGED
package/dist/styles/lyrics.css
CHANGED
|
@@ -184,6 +184,8 @@
|
|
|
184
184
|
|
|
185
185
|
.blyrics--translated,
|
|
186
186
|
.blyrics--romanized {
|
|
187
|
+
animation: blyrics-decoration-in calc(var(--blyrics-animate-decoration-entry) * 0.35s)
|
|
188
|
+
cubic-bezier(0.25, 1, 0.5, 1) both;
|
|
187
189
|
color: var(--blyrics-translated-color);
|
|
188
190
|
display: block;
|
|
189
191
|
font-family: var(--blyrics-translated-font-family);
|
|
@@ -197,6 +199,13 @@
|
|
|
197
199
|
white-space: normal;
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
@keyframes blyrics-decoration-in {
|
|
203
|
+
from {
|
|
204
|
+
opacity: 0;
|
|
205
|
+
transform: translateY(-0.4em);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
200
209
|
.blyrics--romanized {
|
|
201
210
|
background: rgba(255, 255, 255, 0.05);
|
|
202
211
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
--blyrics-animate-highlight-fade: 1;
|
|
64
64
|
--blyrics-animate-scroll: 1;
|
|
65
65
|
--blyrics-animate-instrumental: 1;
|
|
66
|
+
--blyrics-animate-decoration-entry: 1;
|
|
66
67
|
|
|
67
68
|
--blyrics-loader-transition-duration: 0.6s;
|
|
68
69
|
--blyrics-loader-transition-easing: cubic-bezier(0.22, 1, 0.36, 1);
|
|
@@ -176,6 +177,7 @@
|
|
|
176
177
|
--blyrics-animate-line-scale: 0;
|
|
177
178
|
--blyrics-animate-word-wobble: 0;
|
|
178
179
|
--blyrics-animate-instrumental: 0;
|
|
180
|
+
--blyrics-animate-decoration-entry: 0;
|
|
179
181
|
|
|
180
182
|
--blyrics-scale: 1;
|
|
181
183
|
--blyrics-active-scale: 1;
|
package/dist/types.d.ts
CHANGED
|
@@ -163,9 +163,14 @@ export interface LyricsRenderer {
|
|
|
163
163
|
*/
|
|
164
164
|
clearOnScreenLyrics(): boolean;
|
|
165
165
|
/**
|
|
166
|
-
* Re-measures the lines on the next frame and renders the view again against them.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
166
|
+
* Re-measures the lines on the next frame and renders the view again against them. This is the door
|
|
167
|
+
* a consumer knocks on after streaming in a translation or romanization, and it is what slides the
|
|
168
|
+
* lines the new decoration moved from where they were to where it put them, together with any
|
|
169
|
+
* surviving decorator that shifted within its own line, so the rest of the lyric eases down rather
|
|
170
|
+
* than jumping. The slide honours reduced motion and `--blyrics-animate-decoration-entry`; with
|
|
171
|
+
* either off the lines are re-measured with no slide. The predicate is the caller's half of whether
|
|
172
|
+
* that frame does anything, and only that half: whether the lines are on the screen to be measured
|
|
173
|
+
* at all is the renderer's own question, and it asks it itself.
|
|
169
174
|
*
|
|
170
175
|
* @param isTicking - Whether whoever drives this view is still driving it, asked on the frame
|
|
171
176
|
* rather than now. A driver that has stopped is one whose lines may no longer be rendered, and
|