@braccato/core 1.6.0 → 1.6.2

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.
@@ -20,6 +20,7 @@ export declare const BACKGROUND_LINE_CLASS: "blyrics-background-line";
20
20
  export declare const WORD_GROUP_CLASS: "blyrics-word-group";
21
21
  export declare const LONG_WORD_GROUP_CLASS: "blyrics-word-group-long";
22
22
  export declare const WORD_HIGHLIGHT_CLASS: "blyrics-word-highlight";
23
+ export declare const WORD_HIGHLIGHT_LETTERED_CLASS: "blyrics-word-highlight--lettered";
23
24
  export declare const HIGHLIGHT_RUN_CLASS: "blyrics-highlight-run";
24
25
  export declare const LINE_SYNCED_WORD_CLASS: "blyrics-line-synced-word";
25
26
  export declare const BIDI_RUN_CLASS: "blyrics-bidi-run";
package/dist/constants.js CHANGED
@@ -26,6 +26,7 @@ export const BACKGROUND_LINE_CLASS = "blyrics-background-line";
26
26
  export const WORD_GROUP_CLASS = "blyrics-word-group";
27
27
  export const LONG_WORD_GROUP_CLASS = "blyrics-word-group-long";
28
28
  export const WORD_HIGHLIGHT_CLASS = "blyrics-word-highlight";
29
+ export const WORD_HIGHLIGHT_LETTERED_CLASS = "blyrics-word-highlight--lettered";
29
30
  export const HIGHLIGHT_RUN_CLASS = "blyrics-highlight-run";
30
31
  export const LINE_SYNCED_WORD_CLASS = "blyrics-line-synced-word";
31
32
  export const BIDI_RUN_CLASS = "blyrics-bidi-run";
package/dist/engine.d.ts CHANGED
@@ -41,8 +41,6 @@ export interface AnimationEngineInstance extends AnimEngineViewState {
41
41
  window: EngineWindow;
42
42
  host: LyricsRendererHost;
43
43
  cachedTabRendererHeight: number | null;
44
- cachedMaxScrollTop: number | null;
45
- cachedScrollTop: number | null;
46
44
  cachedFooterItem: LineScrollItem | null;
47
45
  cachedLineScrollTiming: Map<string, LineScrollTiming>;
48
46
  tabRendererResizeObserver: ResizeObserver | null;
package/dist/engine.js CHANGED
@@ -137,8 +137,6 @@ export function createAnimationEngineInstance(engineDocument, engineWindow, host
137
137
  passiveScrollAccumulatedTime: 0,
138
138
  passiveLastWallTime: 0,
139
139
  cachedTabRendererHeight: null,
140
- cachedMaxScrollTop: null,
141
- cachedScrollTop: null,
142
140
  cachedFooterItem: null,
143
141
  cachedLineScrollTiming: new Map(),
144
142
  tabRendererResizeObserver: null,
@@ -1749,7 +1747,6 @@ function passiveScrollEngine(engine, isPlaying) {
1749
1747
  const prevScrollTop = tabRenderer.scrollTop;
1750
1748
  tabRenderer.scrollTop = targetScroll;
1751
1749
  const appliedScrollTop = tabRenderer.scrollTop;
1752
- engine.cachedScrollTop = appliedScrollTop;
1753
1750
  // Only skip the next scroll event if scrollTop actually changed.
1754
1751
  // When it doesn't change (pause phases, sub-pixel rounding), no programmatic
1755
1752
  // scroll event fires, so setting skipScrolls would eat user scroll events instead.
@@ -1769,19 +1766,11 @@ function setupTabRendererObserver(engine, element) {
1769
1766
  dropPendingLineScroll(engine);
1770
1767
  if (element && element.isConnected) {
1771
1768
  engine.cachedTabRendererHeight = element.getBoundingClientRect().height;
1772
- refreshScrollMetrics(engine, element);
1773
1769
  }
1774
1770
  });
1775
1771
  engine.tabRendererResizeObserver.observe(element);
1776
1772
  engine.observedTabRenderer = element;
1777
1773
  engine.cachedTabRendererHeight = element.getBoundingClientRect().height;
1778
- refreshScrollMetrics(engine, element);
1779
- }
1780
- // Scroll position and bounds are cached here (and refreshed where layout is measured on purpose) so
1781
- // the tick never reads them off the DOM, which would force a synchronous layout flush mid-frame.
1782
- function refreshScrollMetrics(engine, tabRenderer) {
1783
- engine.cachedMaxScrollTop = Math.max(0, tabRenderer.scrollHeight - tabRenderer.clientHeight);
1784
- engine.cachedScrollTop = tabRenderer.scrollTop;
1785
1774
  }
1786
1775
  /**
1787
1776
  * Fills in everything a caller left out of a tick. The tick reads each of these arithmetically, so
@@ -1883,17 +1872,9 @@ export function tickView(engine, currentTime, options) {
1883
1872
  setupTabRendererObserver(engine, tabRenderer);
1884
1873
  }
1885
1874
  const tabRendererHeight = engine.cachedTabRendererHeight ?? tabRenderer.getBoundingClientRect().height;
1886
- // Read the DOM position only while the user scrolls (they own it then); otherwise reuse the cached
1887
- // value the engine last wrote, so the tick never forces a mid-frame layout flush.
1888
- let scrollTop;
1889
- if (engine.scrollResumeTime >= now || engine.cachedScrollTop === null) {
1890
- scrollTop = tabRenderer.scrollTop;
1891
- engine.cachedScrollTop = scrollTop;
1892
- }
1893
- else {
1894
- scrollTop = engine.cachedScrollTop;
1895
- }
1896
- const maxScrollTop = engine.cachedMaxScrollTop ?? Math.max(0, tabRenderer.scrollHeight - tabRenderer.clientHeight);
1875
+ // Read before the loop's class writes so this layout read is batched and forces no mid-frame reflow.
1876
+ let scrollTop = tabRenderer.scrollTop;
1877
+ const maxScrollTop = Math.max(0, tabRenderer.scrollHeight - tabRenderer.clientHeight);
1897
1878
  if (animationConfig.enabled.scroll) {
1898
1879
  updateVisibleLyricWillChange(engine, lines, scrollTop, engine.pendingLineScroll?.toScrollTop ?? scrollTop, tabRendererHeight);
1899
1880
  }
@@ -2152,7 +2133,6 @@ export function tickView(engine, currentTime, options) {
2152
2133
  scrollTop = scrollPos;
2153
2134
  engine.scrollPos = scrollTop;
2154
2135
  tabRenderer.scrollTop = scrollTop;
2155
- engine.cachedScrollTop = scrollTop;
2156
2136
  engine.skipScrolls += 1;
2157
2137
  engine.skipScrollsDecayTimes.push(Date.now() + 2000);
2158
2138
  }
@@ -2269,9 +2249,6 @@ export function relayout(engine, measureLines) {
2269
2249
  measureFooterItem(engine, lyricsElement);
2270
2250
  // Re-arm from the fresh measurements, so a line skipped after this holds its new placeholder height.
2271
2251
  setupLineCullObserver(engine);
2272
- const tabRenderer = engine.host.getScrollElement();
2273
- if (tabRenderer)
2274
- refreshScrollMetrics(engine, tabRenderer);
2275
2252
  engine.wasUserScrolling = true; // trigger rescrolls
2276
2253
  engine.host.debug?.resize();
2277
2254
  }
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, HIGHLIGHT_RUN_CLASS, LETTER_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, LETTER_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, WORD_HIGHLIGHT_LETTERED_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";
@@ -235,10 +235,13 @@ function createTimedWordSpan(doc, part, wrapThreshold, perLetter) {
235
235
  wordElement.classList.add(EXPLICIT_WORD_CLASS);
236
236
  if (perLetter) {
237
237
  const collected = appendLetters(doc, wordElement, part.words);
238
- if (wordElement === span)
238
+ if (wordElement === span) {
239
239
  letters = collected;
240
- else
240
+ }
241
+ else {
241
242
  highlightLetters = collected;
243
+ wordElement.classList.add(WORD_HIGHLIGHT_LETTERED_CLASS);
244
+ }
242
245
  }
243
246
  else {
244
247
  appendLongWordBreaks(doc, wordElement, part.words, wrapThreshold);
@@ -160,7 +160,7 @@
160
160
  display: inline-block;
161
161
  }
162
162
 
163
- .blyrics-word-highlight:has(.blyrics--letter) {
163
+ .blyrics-word-highlight.blyrics-word-highlight--lettered {
164
164
  background-image: none;
165
165
  }
166
166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braccato/core",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Synchronized lyrics renderer with word-by-word animations",
5
5
  "type": "module",
6
6
  "license": "MIT",