@braccato/core 1.2.0 → 1.3.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/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/engine.js +34 -35
- package/dist/inject.d.ts +5 -2
- package/dist/inject.js +58 -53
- package/dist/styles/lyrics.css +19 -43
- package/package.json +1 -1
package/dist/constants.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare const BACKGROUND_LINE_CLASS: "blyrics-background-line";
|
|
|
19
19
|
export declare const WORD_GROUP_CLASS: "blyrics-word-group";
|
|
20
20
|
export declare const LONG_WORD_GROUP_CLASS: "blyrics-word-group-long";
|
|
21
21
|
export declare const WORD_HIGHLIGHT_CLASS: "blyrics-word-highlight";
|
|
22
|
+
export declare const HIGHLIGHT_RUN_CLASS: "blyrics-highlight-run";
|
|
22
23
|
export declare const LINE_SYNCED_WORD_CLASS: "blyrics-line-synced-word";
|
|
23
24
|
export declare const BIDI_RUN_CLASS: "blyrics-bidi-run";
|
|
24
25
|
export declare const BIDI_SENSITIVE_CLASS: "blyrics-bidi-sensitive";
|
package/dist/constants.js
CHANGED
|
@@ -25,6 +25,7 @@ export const BACKGROUND_LINE_CLASS = "blyrics-background-line";
|
|
|
25
25
|
export const WORD_GROUP_CLASS = "blyrics-word-group";
|
|
26
26
|
export const LONG_WORD_GROUP_CLASS = "blyrics-word-group-long";
|
|
27
27
|
export const WORD_HIGHLIGHT_CLASS = "blyrics-word-highlight";
|
|
28
|
+
export const HIGHLIGHT_RUN_CLASS = "blyrics-highlight-run";
|
|
28
29
|
export const LINE_SYNCED_WORD_CLASS = "blyrics-line-synced-word";
|
|
29
30
|
export const BIDI_RUN_CLASS = "blyrics-bidi-run";
|
|
30
31
|
export const BIDI_SENSITIVE_CLASS = "blyrics-bidi-sensitive";
|
package/dist/engine.js
CHANGED
|
@@ -302,10 +302,16 @@ function resetLineAnimationState(lineData) {
|
|
|
302
302
|
resetLineAnimations(lineData);
|
|
303
303
|
markLineAnimationsStopped(lineData);
|
|
304
304
|
}
|
|
305
|
+
function togglePartClass(part, className, force) {
|
|
306
|
+
part.lyricElement.classList.toggle(className, force);
|
|
307
|
+
if ("highlightElement" in part) {
|
|
308
|
+
part.highlightElement.classList.toggle(className, force);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
305
311
|
function setAnimationsPlayState(lineData, isPlaying) {
|
|
306
312
|
const children = [lineData, ...lineData.parts];
|
|
307
313
|
for (const part of children) {
|
|
308
|
-
part
|
|
314
|
+
togglePartClass(part, PAUSED_CLASS, !isPlaying);
|
|
309
315
|
for (const animation of part.animations) {
|
|
310
316
|
if (isPlaying) {
|
|
311
317
|
animation.play();
|
|
@@ -319,11 +325,10 @@ function setAnimationsPlayState(lineData, isPlaying) {
|
|
|
319
325
|
function clearLineStateClasses(lineData) {
|
|
320
326
|
lineData.lyricElement.classList.remove(ANIMATING_CLASS);
|
|
321
327
|
for (const part of [lineData, ...lineData.parts]) {
|
|
322
|
-
part
|
|
328
|
+
togglePartClass(part, PAUSED_CLASS, false);
|
|
323
329
|
}
|
|
324
330
|
}
|
|
325
331
|
const LINE_SYNCED_WORD_CLASS = "blyrics-line-synced-word";
|
|
326
|
-
const WORD_HIGHLIGHT_SELECTOR = ".blyrics-word-highlight";
|
|
327
332
|
const INSTRUMENTAL_FILL_SELECTOR = ".blyrics--instrumental-fill";
|
|
328
333
|
const INSTRUMENTAL_WAVE_CLIP_SELECTOR = ".blyrics--wave-clip";
|
|
329
334
|
const INSTRUMENTAL_WAVE_PATH_SELECTOR = ".blyrics--wave-path";
|
|
@@ -610,13 +615,6 @@ function activeTextInstantKeyframes(config) {
|
|
|
610
615
|
},
|
|
611
616
|
];
|
|
612
617
|
}
|
|
613
|
-
function highlightTarget(part) {
|
|
614
|
-
const highlight = part.lyricElement.querySelector(WORD_HIGHLIGHT_SELECTOR);
|
|
615
|
-
if (highlight) {
|
|
616
|
-
return { element: highlight, options: {} };
|
|
617
|
-
}
|
|
618
|
-
return { element: part.lyricElement, options: { pseudoElement: "::after" } };
|
|
619
|
-
}
|
|
620
618
|
function lineSyncedTextKeyframes(config) {
|
|
621
619
|
return [
|
|
622
620
|
{
|
|
@@ -649,33 +647,30 @@ function fadeOutTextKeyframes(config) {
|
|
|
649
647
|
}
|
|
650
648
|
function startRichSyncedHighlightAnimations(engine, part, config, swipeTimeMs, wordTimeMs, swipeDurationMs, glowDurationMs, appliedTimingOffsetMs) {
|
|
651
649
|
const animations = [];
|
|
652
|
-
const
|
|
650
|
+
const highlight = part.highlightElement;
|
|
653
651
|
let swipeAnimation;
|
|
654
652
|
if (config.enabled.highlightSwipe) {
|
|
655
|
-
swipeAnimation = trackLyricAnimationTiming(engine,
|
|
653
|
+
swipeAnimation = trackLyricAnimationTiming(engine, highlight.animate(activeTextGradientKeyframes(config), {
|
|
656
654
|
duration: swipeDurationMs,
|
|
657
655
|
easing: config.highlight.swipeEasing,
|
|
658
656
|
fill: "forwards",
|
|
659
|
-
...target.options,
|
|
660
657
|
}), { appliedTimingOffsetMs, offsetMs: swipeTimeMs - wordTimeMs });
|
|
661
658
|
swipeAnimation.currentTime = correctedAnimationTimeMs(swipeTimeMs, appliedTimingOffsetMs, swipeDurationMs);
|
|
662
659
|
animations.push(swipeAnimation);
|
|
663
660
|
}
|
|
664
|
-
const opacityAnimation = trackLyricAnimationTiming(engine,
|
|
661
|
+
const opacityAnimation = trackLyricAnimationTiming(engine, highlight.animate(config.enabled.highlightSwipe ? activeTextVisibleKeyframes() : activeTextInstantKeyframes(config), {
|
|
665
662
|
duration: 1,
|
|
666
663
|
easing: "linear",
|
|
667
664
|
fill: "forwards",
|
|
668
|
-
...target.options,
|
|
669
665
|
}), { appliedTimingOffsetMs, offsetMs: 0 });
|
|
670
666
|
opacityAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, 1);
|
|
671
667
|
animations.push(opacityAnimation);
|
|
672
668
|
let glowAnimation;
|
|
673
669
|
if (config.enabled.highlightGlow) {
|
|
674
|
-
glowAnimation = trackLyricAnimationTiming(engine,
|
|
670
|
+
glowAnimation = trackLyricAnimationTiming(engine, highlight.animate(activeTextGlowKeyframes(config), {
|
|
675
671
|
duration: glowDurationMs,
|
|
676
672
|
easing: config.highlight.glowEasing,
|
|
677
673
|
fill: "forwards",
|
|
678
|
-
...target.options,
|
|
679
674
|
}), { appliedTimingOffsetMs, offsetMs: 0 });
|
|
680
675
|
glowAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, glowDurationMs);
|
|
681
676
|
animations.push(glowAnimation);
|
|
@@ -685,22 +680,20 @@ function startRichSyncedHighlightAnimations(engine, part, config, swipeTimeMs, w
|
|
|
685
680
|
function startLineSyncedHighlightAnimations(engine, part, config, wordTimeMs, glowDurationMs, appliedTimingOffsetMs) {
|
|
686
681
|
const animations = [];
|
|
687
682
|
const fadeInDuration = config.enabled.highlightFade ? config.highlight.fadeInDurationMs : 1;
|
|
688
|
-
const
|
|
689
|
-
const opacityAnimation = trackLyricAnimationTiming(engine,
|
|
683
|
+
const highlight = part.highlightElement;
|
|
684
|
+
const opacityAnimation = trackLyricAnimationTiming(engine, highlight.animate(lineSyncedTextKeyframes(config), {
|
|
690
685
|
duration: fadeInDuration,
|
|
691
686
|
easing: config.enabled.highlightFade ? config.highlight.fadeInEasing : "linear",
|
|
692
687
|
fill: "forwards",
|
|
693
|
-
...target.options,
|
|
694
688
|
}), { appliedTimingOffsetMs, offsetMs: 0 });
|
|
695
689
|
opacityAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, fadeInDuration);
|
|
696
690
|
animations.push(opacityAnimation);
|
|
697
691
|
let glowAnimation;
|
|
698
692
|
if (config.enabled.highlightGlow) {
|
|
699
|
-
glowAnimation = trackLyricAnimationTiming(engine,
|
|
693
|
+
glowAnimation = trackLyricAnimationTiming(engine, highlight.animate(activeTextGlowKeyframes(config), {
|
|
700
694
|
duration: glowDurationMs,
|
|
701
695
|
easing: config.highlight.glowEasing,
|
|
702
696
|
fill: "forwards",
|
|
703
|
-
...target.options,
|
|
704
697
|
}), { appliedTimingOffsetMs, offsetMs: 0 });
|
|
705
698
|
glowAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, glowDurationMs);
|
|
706
699
|
animations.push(glowAnimation);
|
|
@@ -753,8 +746,9 @@ function startWordAnimations(engine, part, config, currentTime, appliedTimingOff
|
|
|
753
746
|
const highlightAnimations = isLineSyncedWord
|
|
754
747
|
? startLineSyncedHighlightAnimations(engine, part, config, wordTimeMs, config.highlight.glowMinDurationMs, appliedTimingOffsetMs)
|
|
755
748
|
: startRichSyncedHighlightAnimations(engine, part, config, swipeTimeMs, wordTimeMs, swipeDurationMs, glowDurationMs, appliedTimingOffsetMs);
|
|
756
|
-
const
|
|
757
|
-
|
|
749
|
+
const wobbleAnimations = [];
|
|
750
|
+
if (config.enabled.wordWobble) {
|
|
751
|
+
const wobbleKeyframes = [
|
|
758
752
|
{ transform: config.word.wobbleFrom },
|
|
759
753
|
{
|
|
760
754
|
transform: config.word.wobblePeak,
|
|
@@ -769,18 +763,25 @@ function startWordAnimations(engine, part, config, currentTime, appliedTimingOff
|
|
|
769
763
|
offset: Math.max(config.word.wobblePeakOffset, config.word.wobbleSettleOffset),
|
|
770
764
|
},
|
|
771
765
|
{ transform: config.word.wobbleTo, easing: config.word.wobbleEndEasing },
|
|
772
|
-
]
|
|
766
|
+
];
|
|
767
|
+
const wobbleOptions = {
|
|
773
768
|
duration: config.word.wobbleDurationMs,
|
|
774
769
|
easing: config.word.wobbleEasing,
|
|
775
770
|
fill: "forwards",
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
771
|
+
};
|
|
772
|
+
const wobbleStartMs = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, config.word.wobbleDurationMs);
|
|
773
|
+
// The wobble is a paint transform, so the highlight copy must carry it too or the active
|
|
774
|
+
// sweep drifts off the word.
|
|
775
|
+
for (const wordElement of [part.lyricElement, part.highlightElement]) {
|
|
776
|
+
const animation = trackLyricAnimationTiming(engine, wordElement.animate(wobbleKeyframes, wobbleOptions), {
|
|
777
|
+
appliedTimingOffsetMs,
|
|
778
|
+
offsetMs: 0,
|
|
779
|
+
});
|
|
780
|
+
animation.currentTime = wobbleStartMs;
|
|
781
|
+
wobbleAnimations.push(animation);
|
|
782
|
+
}
|
|
780
783
|
}
|
|
781
|
-
part.animations =
|
|
782
|
-
? [...highlightAnimations.animations, wobbleAnimation]
|
|
783
|
-
: highlightAnimations.animations;
|
|
784
|
+
part.animations = [...highlightAnimations.animations, ...wobbleAnimations];
|
|
784
785
|
}
|
|
785
786
|
function startLineAnimations(engine, lineData, config, currentTime) {
|
|
786
787
|
const appliedTimingOffsetMs = engine.learnedAnimationTimingOffsetMs;
|
|
@@ -796,12 +797,10 @@ function startLineAnimations(engine, lineData, config, currentTime) {
|
|
|
796
797
|
function startWordExitAnimation(part, config) {
|
|
797
798
|
resetPartAnimations(part);
|
|
798
799
|
const fadeDuration = config.enabled.highlightFade ? config.highlight.fadeOutDurationMs : 1;
|
|
799
|
-
const
|
|
800
|
-
const animation = target.element.animate(fadeOutTextKeyframes(config), {
|
|
800
|
+
const animation = part.highlightElement.animate(fadeOutTextKeyframes(config), {
|
|
801
801
|
duration: fadeDuration,
|
|
802
802
|
easing: config.enabled.highlightFade ? config.highlight.fadeOutEasing : "linear",
|
|
803
803
|
fill: "none",
|
|
804
|
-
...target.options,
|
|
805
804
|
});
|
|
806
805
|
part.animations = [animation];
|
|
807
806
|
animation.addEventListener("finish", () => {
|
package/dist/inject.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare let disableRichsync: import("./themeSettings.js").Setting;
|
|
|
3
3
|
export declare function findNearestAgent(lyrics: Lyric[], fromIndex: number): string | undefined;
|
|
4
4
|
export declare function isNearestLyricRtl(lyrics: Lyric[], fromIndex: number): boolean;
|
|
5
5
|
export declare function deriveSyncType(lyrics: Lyric[]): LyricSyncType;
|
|
6
|
-
export interface
|
|
6
|
+
export interface AnimationData {
|
|
7
7
|
/**
|
|
8
8
|
* Time of this part in seconds
|
|
9
9
|
*/
|
|
@@ -15,6 +15,9 @@ export interface PartData {
|
|
|
15
15
|
lyricElement: HTMLElement;
|
|
16
16
|
animations: Animation[];
|
|
17
17
|
}
|
|
18
|
+
export interface PartData extends AnimationData {
|
|
19
|
+
highlightElement: HTMLElement;
|
|
20
|
+
}
|
|
18
21
|
export type LineData = {
|
|
19
22
|
parts: PartData[];
|
|
20
23
|
isScrolled: boolean;
|
|
@@ -25,7 +28,7 @@ export type LineData = {
|
|
|
25
28
|
isSelected: boolean;
|
|
26
29
|
height: number;
|
|
27
30
|
position: number;
|
|
28
|
-
} &
|
|
31
|
+
} & AnimationData;
|
|
29
32
|
export declare function newLineData(lyricElement: HTMLElement, startTimeMs: number, durationMs: number): LineData;
|
|
30
33
|
export declare function applyDirection(element: HTMLElement, text: string): void;
|
|
31
34
|
export declare function createLyricsLine(doc: Document, parts: LyricPart[], line: LineData, lyricElement: HTMLElement, options?: {
|
package/dist/inject.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// animates is one word, so every part is split on whitespace with its timing pro-rated across the
|
|
12
12
|
// split by character count. A line that arrives with no timed parts at all is rebuilt the same way
|
|
13
13
|
// into zero duration words, so line synced lyrics reach the DOM the sweep already knows.
|
|
14
|
-
import { BACKGROUND_LINE_CLASS, BACKGROUND_LYRIC_CLASS, BIDI_RUN_CLASS, BIDI_SENSITIVE_CLASS, CONTENT_LINE_CLASS, EXPLICIT_WORD_CLASS, LINE_MAIN_CLASS, LINE_SYNCED_WORD_CLASS, LONG_WORD_GROUP_CLASS, ROMANIZED_LYRICS_CLASS, RTL_CLASS, TRANSLATED_LYRICS_CLASS, WORD_CLASS, WORD_GROUP_CLASS, WORD_HIGHLIGHT_CLASS, ZERO_DURATION_ANIMATION_CLASS, } from "./constants.js";
|
|
14
|
+
import { BACKGROUND_LINE_CLASS, BACKGROUND_LYRIC_CLASS, BIDI_RUN_CLASS, BIDI_SENSITIVE_CLASS, CONTENT_LINE_CLASS, EXPLICIT_WORD_CLASS, HIGHLIGHT_RUN_CLASS, LINE_MAIN_CLASS, LINE_SYNCED_WORD_CLASS, LONG_WORD_GROUP_CLASS, ROMANIZED_LYRICS_CLASS, RTL_CLASS, TRANSLATED_LYRICS_CLASS, WORD_CLASS, WORD_GROUP_CLASS, WORD_HIGHLIGHT_CLASS, ZERO_DURATION_ANIMATION_CLASS, } from "./constants.js";
|
|
15
15
|
import { getSeekTimeFromClick } from "./seek.js";
|
|
16
16
|
import { testRtl } from "./text.js";
|
|
17
17
|
import { registerThemeSetting } from "./themeSettings.js";
|
|
@@ -60,11 +60,12 @@ export function deriveSyncType(lyrics) {
|
|
|
60
60
|
return "richsync";
|
|
61
61
|
return lyrics.every(item => item.startTimeMs === 0) ? "none" : "synced";
|
|
62
62
|
}
|
|
63
|
-
function newPartData(part, span) {
|
|
63
|
+
function newPartData(part, span, highlight) {
|
|
64
64
|
return {
|
|
65
65
|
time: part.startTimeMs / 1000,
|
|
66
66
|
duration: part.durationMs / 1000,
|
|
67
67
|
lyricElement: span,
|
|
68
|
+
highlightElement: highlight,
|
|
68
69
|
animations: [],
|
|
69
70
|
};
|
|
70
71
|
}
|
|
@@ -193,67 +194,57 @@ function appendLongWordBreaks(doc, span, text, threshold) {
|
|
|
193
194
|
}
|
|
194
195
|
return true;
|
|
195
196
|
}
|
|
196
|
-
function cloneTextWithBreaks(doc, source) {
|
|
197
|
-
const fragment = doc.createDocumentFragment();
|
|
198
|
-
for (const node of source.childNodes) {
|
|
199
|
-
fragment.appendChild(node.cloneNode(true));
|
|
200
|
-
}
|
|
201
|
-
return fragment;
|
|
202
|
-
}
|
|
203
197
|
function createTimedWordSpan(doc, part, wrapThreshold) {
|
|
204
198
|
const span = doc.createElement("span");
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
highlight.setAttribute("aria-hidden", "true");
|
|
228
|
-
highlight.appendChild(cloneTextWithBreaks(doc, span));
|
|
229
|
-
span.appendChild(highlight);
|
|
199
|
+
const highlight = doc.createElement("span");
|
|
200
|
+
highlight.classList.add(WORD_HIGHLIGHT_CLASS);
|
|
201
|
+
for (const wordElement of [span, highlight]) {
|
|
202
|
+
wordElement.classList.add(WORD_CLASS);
|
|
203
|
+
wordElement.dir = "auto";
|
|
204
|
+
if (part.durationMs === 0) {
|
|
205
|
+
wordElement.classList.add(ZERO_DURATION_ANIMATION_CLASS);
|
|
206
|
+
wordElement.classList.add(LINE_SYNCED_WORD_CLASS);
|
|
207
|
+
}
|
|
208
|
+
if (testRtl(part.words))
|
|
209
|
+
wordElement.classList.add(RTL_CLASS);
|
|
210
|
+
if (part.durationMs > longWordThreshold.getNumberValue())
|
|
211
|
+
wordElement.dataset.longWord = "true";
|
|
212
|
+
if (part.isBackground)
|
|
213
|
+
wordElement.classList.add(BACKGROUND_LYRIC_CLASS);
|
|
214
|
+
if (part.explicit)
|
|
215
|
+
wordElement.classList.add(EXPLICIT_WORD_CLASS);
|
|
216
|
+
appendLongWordBreaks(doc, wordElement, part.words, wrapThreshold);
|
|
217
|
+
wordElement.dataset.time = String(part.startTimeMs / 1000);
|
|
218
|
+
wordElement.dataset.duration = String(part.durationMs / 1000);
|
|
219
|
+
wordElement.dataset.content = part.words;
|
|
220
|
+
wordElement.style.setProperty("--blyrics-duration", part.durationMs + "ms");
|
|
230
221
|
}
|
|
231
|
-
|
|
232
|
-
span.dataset.duration = String(part.durationMs / 1000);
|
|
233
|
-
span.dataset.content = part.words;
|
|
234
|
-
span.style.setProperty("--blyrics-duration", part.durationMs + "ms");
|
|
235
|
-
return span;
|
|
222
|
+
return { span, highlight };
|
|
236
223
|
}
|
|
237
224
|
function createWordGroup(doc, group, lineData) {
|
|
238
225
|
const wrapThreshold = Math.max(1, longWordWrapThreshold.getNumberValue());
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
groupElement
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
groupElement.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
226
|
+
const lyricGroup = doc.createElement("span");
|
|
227
|
+
const highlightGroup = doc.createElement("span");
|
|
228
|
+
for (const groupElement of [lyricGroup, highlightGroup]) {
|
|
229
|
+
groupElement.classList.add(WORD_GROUP_CLASS);
|
|
230
|
+
groupElement.dir = "auto";
|
|
231
|
+
groupElement.dataset.content = group.text;
|
|
232
|
+
if (group.text.length > wrapThreshold * 2) {
|
|
233
|
+
groupElement.classList.add(LONG_WORD_GROUP_CLASS);
|
|
234
|
+
}
|
|
235
|
+
if (group.isBackground) {
|
|
236
|
+
groupElement.classList.add(BACKGROUND_LYRIC_CLASS);
|
|
237
|
+
}
|
|
248
238
|
}
|
|
249
239
|
for (const token of group.tokens) {
|
|
250
240
|
if (token.kind === "space")
|
|
251
241
|
continue;
|
|
252
|
-
const span = createTimedWordSpan(doc, token.part, wrapThreshold);
|
|
253
|
-
lineData.parts.push(newPartData(token.part, span));
|
|
254
|
-
|
|
242
|
+
const { span, highlight } = createTimedWordSpan(doc, token.part, wrapThreshold);
|
|
243
|
+
lineData.parts.push(newPartData(token.part, span, highlight));
|
|
244
|
+
lyricGroup.appendChild(span);
|
|
245
|
+
highlightGroup.appendChild(highlight);
|
|
255
246
|
}
|
|
256
|
-
return
|
|
247
|
+
return { lyricGroup, highlightGroup };
|
|
257
248
|
}
|
|
258
249
|
function createContentLine(doc, className, text) {
|
|
259
250
|
const line = doc.createElement("div");
|
|
@@ -268,6 +259,12 @@ function createBidiRun(doc, text) {
|
|
|
268
259
|
applyDirection(run, text);
|
|
269
260
|
return run;
|
|
270
261
|
}
|
|
262
|
+
function createHighlightRun(doc, text) {
|
|
263
|
+
const run = createBidiRun(doc, text);
|
|
264
|
+
run.classList.add(HIGHLIGHT_RUN_CLASS);
|
|
265
|
+
run.setAttribute("aria-hidden", "true");
|
|
266
|
+
return run;
|
|
267
|
+
}
|
|
271
268
|
export function createLyricsLine(doc, parts, line, lyricElement, options = { splitBackgroundLine: true }) {
|
|
272
269
|
const lineText = parts.map(part => part.words).join("");
|
|
273
270
|
const mainText = options.splitBackgroundLine
|
|
@@ -282,13 +279,17 @@ export function createLyricsLine(doc, parts, line, lyricElement, options = { spl
|
|
|
282
279
|
.join("");
|
|
283
280
|
const main = createContentLine(doc, LINE_MAIN_CLASS, mainText);
|
|
284
281
|
const mainRun = createBidiRun(doc, mainText);
|
|
282
|
+
const mainHighlightRun = createHighlightRun(doc, mainText);
|
|
285
283
|
const groupedTokens = groupTokensByWord(normalizeParts(parts));
|
|
286
284
|
const backgroundLine = createContentLine(doc, BACKGROUND_LINE_CLASS, backgroundText);
|
|
287
285
|
const backgroundRun = createBidiRun(doc, backgroundText);
|
|
286
|
+
const backgroundHighlightRun = createHighlightRun(doc, backgroundText);
|
|
288
287
|
let hasBackground = false;
|
|
289
288
|
let pendingForegroundSpace = "";
|
|
290
289
|
let pendingBackgroundSpace = "";
|
|
290
|
+
main.appendChild(mainHighlightRun);
|
|
291
291
|
main.appendChild(mainRun);
|
|
292
|
+
backgroundLine.appendChild(backgroundHighlightRun);
|
|
292
293
|
backgroundLine.appendChild(backgroundRun);
|
|
293
294
|
for (const item of groupedTokens) {
|
|
294
295
|
if ("kind" in item) {
|
|
@@ -299,11 +300,15 @@ export function createLyricsLine(doc, parts, line, lyricElement, options = { spl
|
|
|
299
300
|
else {
|
|
300
301
|
const shouldUseBackgroundLine = options.splitBackgroundLine && item.isBackground;
|
|
301
302
|
const target = shouldUseBackgroundLine ? backgroundRun : mainRun;
|
|
303
|
+
const highlightRun = shouldUseBackgroundLine ? backgroundHighlightRun : mainHighlightRun;
|
|
302
304
|
const pendingSpace = shouldUseBackgroundLine ? pendingBackgroundSpace : pendingForegroundSpace;
|
|
303
305
|
if (target.childNodes.length > 0 && pendingSpace.length > 0) {
|
|
304
306
|
target.appendChild(doc.createTextNode(pendingSpace));
|
|
307
|
+
highlightRun.appendChild(doc.createTextNode(pendingSpace));
|
|
305
308
|
}
|
|
306
|
-
|
|
309
|
+
const { lyricGroup, highlightGroup } = createWordGroup(doc, item, line);
|
|
310
|
+
target.appendChild(lyricGroup);
|
|
311
|
+
highlightRun.appendChild(highlightGroup);
|
|
307
312
|
if (shouldUseBackgroundLine) {
|
|
308
313
|
hasBackground = true;
|
|
309
314
|
pendingBackgroundSpace = "";
|
package/dist/styles/lyrics.css
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
display: block;
|
|
39
39
|
max-width: 100%;
|
|
40
40
|
overflow-wrap: normal;
|
|
41
|
+
position: relative;
|
|
41
42
|
text-align: inherit;
|
|
42
43
|
unicode-bidi: plaintext;
|
|
43
44
|
white-space: normal;
|
|
@@ -47,18 +48,28 @@
|
|
|
47
48
|
display: block;
|
|
48
49
|
margin-top: 0.1em;
|
|
49
50
|
max-width: 100%;
|
|
51
|
+
position: relative;
|
|
50
52
|
text-align: inherit;
|
|
51
53
|
unicode-bidi: plaintext;
|
|
52
54
|
white-space: normal;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
.blyrics-bidi-run {
|
|
56
|
-
display:
|
|
58
|
+
display: block;
|
|
57
59
|
max-width: 100%;
|
|
58
60
|
unicode-bidi: normal;
|
|
59
61
|
white-space: inherit;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
.blyrics-highlight-run {
|
|
65
|
+
inset: 0 0 auto;
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
position: absolute;
|
|
68
|
+
user-select: none;
|
|
69
|
+
width: auto;
|
|
70
|
+
z-index: 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
.blyrics-word-group {
|
|
63
74
|
display: inline-block;
|
|
64
75
|
max-width: 100%;
|
|
@@ -116,68 +127,33 @@
|
|
|
116
127
|
initial-value: -0.1;
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
.blyrics--word
|
|
120
|
-
.blyrics-word-highlight {
|
|
130
|
+
.blyrics--word.blyrics-word-highlight {
|
|
121
131
|
color: transparent;
|
|
122
132
|
background-image: linear-gradient(
|
|
123
133
|
90deg,
|
|
124
134
|
var(--blyrics-lyric-active-color)
|
|
125
|
-
calc(
|
|
126
|
-
100% * var(--lyric-transition-amount-start) - 4rem *
|
|
127
|
-
var(--lyric-transition-amount-start) + 2rem
|
|
128
|
-
),
|
|
135
|
+
calc(100% * var(--lyric-transition-amount-start)),
|
|
129
136
|
#00000000
|
|
130
|
-
calc(
|
|
131
|
-
100% * var(--lyric-transition-amount-end) - 4rem *
|
|
132
|
-
var(--lyric-transition-amount-end) + 2rem + 1px
|
|
133
|
-
)
|
|
137
|
+
calc(100% * var(--lyric-transition-amount-end) + 1px)
|
|
134
138
|
);
|
|
135
139
|
background-clip: text;
|
|
136
|
-
box-sizing: content-box;
|
|
137
140
|
filter: drop-shadow(0 0 0 var(--blyrics-glow-color));
|
|
138
|
-
left: -2rem;
|
|
139
141
|
opacity: 0;
|
|
140
|
-
padding: 2rem;
|
|
141
142
|
pointer-events: none;
|
|
142
|
-
position: absolute;
|
|
143
|
-
top: -2rem;
|
|
144
|
-
white-space: inherit;
|
|
145
|
-
width: 100%;
|
|
146
143
|
--lyric-transition-amount-start: var(
|
|
147
144
|
--blyrics-highlight-swipe-start-from,
|
|
148
145
|
-0.2
|
|
149
146
|
);
|
|
150
147
|
--lyric-transition-amount-end: var(--blyrics-highlight-swipe-end-from, -0.1);
|
|
151
148
|
}
|
|
152
|
-
|
|
153
|
-
.blyrics
|
|
154
|
-
content: attr(data-content);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.blyrics-word-highlight {
|
|
158
|
-
user-select: none;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.blyrics--word:has(.blyrics-word-highlight)::after {
|
|
162
|
-
content: none;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.blyrics-rtl.blyrics--word::after,
|
|
166
|
-
.blyrics--word.blyrics-rtl::after,
|
|
167
|
-
.blyrics-rtl .blyrics-word-highlight,
|
|
168
|
-
.blyrics--word.blyrics-rtl .blyrics-word-highlight {
|
|
149
|
+
.blyrics-word-highlight.blyrics-rtl,
|
|
150
|
+
.blyrics-rtl .blyrics-word-highlight {
|
|
169
151
|
background-image: linear-gradient(
|
|
170
152
|
270deg,
|
|
171
153
|
var(--blyrics-lyric-active-color)
|
|
172
|
-
calc(
|
|
173
|
-
100% * var(--lyric-transition-amount-start) - 4rem *
|
|
174
|
-
var(--lyric-transition-amount-start) + 2rem
|
|
175
|
-
),
|
|
154
|
+
calc(100% * var(--lyric-transition-amount-start)),
|
|
176
155
|
#00000000
|
|
177
|
-
calc(
|
|
178
|
-
100% * var(--lyric-transition-amount-end) - 4rem *
|
|
179
|
-
var(--lyric-transition-amount-end) + 2rem + 1px
|
|
180
|
-
)
|
|
156
|
+
calc(100% * var(--lyric-transition-amount-end) + 1px)
|
|
181
157
|
);
|
|
182
158
|
}
|
|
183
159
|
|