@myrmidon/gve-snapshot-rendition 2.0.6 → 2.0.7

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/index.js CHANGED
@@ -863,6 +863,24 @@ function getTextWidth(text, fontFamily, fontSize, bold = false, italic = false,
863
863
  }
864
864
  return width;
865
865
  }
866
+ /**
867
+ * Reference character used to compute the "average character width/height"
868
+ * that the "tw"/"th" offset units resolve against (see getAverageCharWidth).
869
+ * This is the same reference character used to size the space character.
870
+ */
871
+ const REFERENCE_CHAR = "m";
872
+ /**
873
+ * Get the average character width for the given font settings, using
874
+ * REFERENCE_CHAR ("m") as the reference glyph. This is the basis for the
875
+ * "tw" (text width) unit used by r_char-offsets, r_t-offset-x/y, and
876
+ * r_h-offset-x/y — NOT the bounding box of a whole reference text span.
877
+ *
878
+ * @param measurementRoot - Optional SVG element to use as measurement context
879
+ * (see getTextWidth for why this matters).
880
+ */
881
+ function getAverageCharWidth(fontFamily, fontSize, bold = false, italic = false, measurementRoot) {
882
+ return getTextWidth(REFERENCE_CHAR, fontFamily, fontSize, bold, italic, measurementRoot);
883
+ }
866
884
 
867
885
  /**
868
886
  * Text processing utilities.
@@ -1021,11 +1039,6 @@ function parseHintVars(value) {
1021
1039
  return result;
1022
1040
  }
1023
1041
 
1024
- /**
1025
- * Reference character for calculating space width.
1026
- * This can be changed if needed.
1027
- */
1028
- const REFERENCE_CHAR = "m";
1029
1042
  /**
1030
1043
  * Text layout calculator.
1031
1044
  * Handles positioning of base text characters, respecting line breaks,
@@ -1061,7 +1074,7 @@ class TextLayout {
1061
1074
  * Calculate the width of a space character based on reference character.
1062
1075
  */
1063
1076
  calculateSpaceWidth() {
1064
- const refCharWidth = getTextWidth(REFERENCE_CHAR, this._settings.fontFamily, this._settings.fontSize, this._settings.bold, this._settings.italic, this._measurementRoot // Use measurement root for consistent font metrics
1077
+ const refCharWidth = getAverageCharWidth(this._settings.fontFamily, this._settings.fontSize, this._settings.bold, this._settings.italic, this._measurementRoot // Use measurement root for consistent font metrics
1065
1078
  );
1066
1079
  return refCharWidth * this._settings.spaceWidthFraction;
1067
1080
  }
@@ -1079,7 +1092,7 @@ class TextLayout {
1079
1092
  // Compute the average character dimensions used to resolve "tw" and "th" units.
1080
1093
  // tw = average character width (width of the reference character 'm' in the current font)
1081
1094
  // th = average character height (≈ font size, which is the em-square in CSS/SVG pixels)
1082
- const avgCharWidth = getTextWidth(REFERENCE_CHAR, this._settings.fontFamily, this._settings.fontSize, this._settings.bold, this._settings.italic, this._measurementRoot);
1095
+ const avgCharWidth = getAverageCharWidth(this._settings.fontFamily, this._settings.fontSize, this._settings.bold, this._settings.italic, this._measurementRoot);
1083
1096
  const avgCharHeight = this._settings.fontSize;
1084
1097
  const positions = [];
1085
1098
  let currentX = this._settings.marginLeft;
@@ -1405,10 +1418,10 @@ class TextRenderer {
1405
1418
  let rbrs;
1406
1419
  if (config.textDisplacedSpan) {
1407
1420
  const nodes = getNodeSpan(allBaseNodes, config.textDisplacedSpan.nodeId, config.textDisplacedSpan.count);
1408
- rbrs = this.calculateRBRs(nodes);
1421
+ rbrs = this.calculateRBRs(nodes, rootSvg);
1409
1422
  }
1410
1423
  else {
1411
- rbrs = this.calculateRBRs(referenceNodes);
1424
+ rbrs = this.calculateRBRs(referenceNodes, rootSvg);
1412
1425
  }
1413
1426
  if (rbrs.length === 0) {
1414
1427
  this._logger.warn("No RBRs calculated for additional text, skipping");
@@ -1417,10 +1430,14 @@ class TextRenderer {
1417
1430
  // Use first RBR for positioning (additional text doesn't repeat per RBR)
1418
1431
  const rbr = rbrs[0];
1419
1432
  this._logger.debug("TextRenderer", `Using RBR for positioning`, rbr);
1420
- // 2. Calculate position and offsets
1433
+ // 2. Calculate position and offsets.
1434
+ // "tw"/"th" resolve against the average character width/height in the
1435
+ // current (possibly r_font-size/r_font-family-overridden) font, not the RBR.
1421
1436
  const position = config.textPosition || "o";
1422
- const offsetX = parseOffset(config.textOffsetX || 0, rbr.height, rbr.width);
1423
- const offsetY = parseOffset(config.textOffsetY || 0, rbr.height, rbr.width);
1437
+ const avgCharWidth = getAverageCharWidth(config.fontFamily, config.fontSize, config.bold, config.italic, rootSvg);
1438
+ const avgCharHeight = config.fontSize;
1439
+ const offsetX = parseOffset(config.textOffsetX || 0, avgCharHeight, avgCharWidth);
1440
+ const offsetY = parseOffset(config.textOffsetY || 0, avgCharHeight, avgCharWidth);
1424
1441
  // 3. Render characters at preliminary positions (baseline y=0) into a hidden
1425
1442
  // temporary group so we can measure the actual visual EBR via getBBox().
1426
1443
  // This mirrors the hint renderer's approach: render → measure → reposition.
@@ -1600,31 +1617,43 @@ class TextRenderer {
1600
1617
  /**
1601
1618
  * Calculate RBRs from reference nodes.
1602
1619
  * Nodes on different lines create separate RBRs.
1620
+ *
1621
+ * @param rootSvg - Root SVG element, used to read each character's laid-out
1622
+ * baseline `y` attribute for line-break detection (see below).
1603
1623
  */
1604
- calculateRBRs(nodes) {
1624
+ calculateRBRs(nodes, rootSvg) {
1605
1625
  if (nodes.length === 0)
1606
1626
  return [];
1607
1627
  const rbrs = [];
1608
1628
  const nodeElementIds = nodes.map((n) => `n_${n.id}`);
1609
- // Group nodes by line by checking their Y coordinates
1629
+ // Group nodes by line by checking their Y coordinates.
1630
+ // IMPORTANT: this compares the character's laid-out baseline Y (the `y`
1631
+ // attribute TextLayout assigned when positioning it), NOT its cached
1632
+ // getBBox().y. The tight ink bbox top varies per glyph shape (ascenders,
1633
+ // descenders, x-height-only glyphs), easily by more than the 5px
1634
+ // tolerance below, even on the very same line — using bbox.y here would
1635
+ // spuriously split one line into multiple RBRs.
1610
1636
  const lineGroups = [];
1611
1637
  let currentLineGroup = [];
1612
- let lastY = null;
1638
+ let lastBaselineY = null;
1613
1639
  for (const elementId of nodeElementIds) {
1614
1640
  const bounds = this._boundsCache.get(elementId);
1615
1641
  if (!bounds) {
1616
1642
  this._logger.warn(`Bounds not found for node element ${elementId}`);
1617
1643
  continue;
1618
1644
  }
1619
- // If Y coordinate significantly different, start new line group
1620
- if (lastY !== null && Math.abs(bounds.y - lastY) > 5) {
1645
+ const el = rootSvg?.querySelector(`#${elementId}`);
1646
+ const attrY = el?.getAttribute("y");
1647
+ const baselineY = attrY !== null && attrY !== undefined ? parseFloat(attrY) : bounds.y;
1648
+ // If baseline Y coordinate significantly different, start new line group
1649
+ if (lastBaselineY !== null && Math.abs(baselineY - lastBaselineY) > 5) {
1621
1650
  if (currentLineGroup.length > 0) {
1622
1651
  lineGroups.push([...currentLineGroup]);
1623
1652
  currentLineGroup = [];
1624
1653
  }
1625
1654
  }
1626
1655
  currentLineGroup.push(elementId);
1627
- lastY = bounds.y;
1656
+ lastBaselineY = baselineY;
1628
1657
  }
1629
1658
  // Add last group
1630
1659
  if (currentLineGroup.length > 0) {
@@ -1704,15 +1733,20 @@ class HintRenderer {
1704
1733
  let rbrs;
1705
1734
  if (effectiveHint.displacedRefSpan) {
1706
1735
  // Use displaced span instead of reference nodes
1707
- rbrs = this.calculateDisplacedRBRs(effectiveHint.displacedRefSpan, allBaseNodes);
1736
+ rbrs = this.calculateDisplacedRBRs(effectiveHint.displacedRefSpan, allBaseNodes, rootSvg);
1708
1737
  }
1709
1738
  else {
1710
- rbrs = this.calculateRBRs(referenceNodes);
1739
+ rbrs = this.calculateRBRs(referenceNodes, rootSvg);
1711
1740
  }
1712
1741
  if (rbrs.length === 0) {
1713
1742
  this._logger.warn(`No RBRs calculated for hint ${hintId}, skipping`);
1714
1743
  return;
1715
1744
  }
1745
+ // Average character width/height in the default text settings, used to
1746
+ // resolve "tw"/"th" units in offsetX/offsetY (r_h-offset-x/y). These are
1747
+ // NOT the RBR (reference text bounds) — see docs "Hints" properties.
1748
+ const avgCharWidth = getAverageCharWidth(this._settings.fontFamily, this._settings.fontSize, this._settings.bold, this._settings.italic, rootSvg);
1749
+ const avgCharHeight = this._settings.fontSize;
1716
1750
  // 5. Check if this is a placeholder hint
1717
1751
  const hasPlaceholder = hintGroup.querySelector("#placeholder") !== null;
1718
1752
  // Default-mode placeholder: a placeholder element WITHOUT class="fit".
@@ -1754,7 +1788,7 @@ class HintRenderer {
1754
1788
  // This is either the handle's bbox or the whole hint's bbox
1755
1789
  const ebrBounds = this.getEBRBounds(currentHintGroup);
1756
1790
  // 11. Apply positioning transform - align EBR with RBR based on position
1757
- await this.applyHintPositionTransform(currentHintGroup, effectiveHint, rbr, ebrBounds, scalingInfo);
1791
+ await this.applyHintPositionTransform(currentHintGroup, effectiveHint, rbr, ebrBounds, scalingInfo, avgCharWidth, avgCharHeight);
1758
1792
  // 10. Check if prolog panning is needed (element visibility check)
1759
1793
  // This must happen AFTER positioning and spreading, but BEFORE making visible
1760
1794
  if (panZoomInstance && viewportWidth && viewportHeight && this._settings.prologDuration > 0) {
@@ -1802,16 +1836,28 @@ class HintRenderer {
1802
1836
  /**
1803
1837
  * Calculate Reference Bounding Rectangles (RBRs) from reference nodes.
1804
1838
  * Nodes on different lines create separate RBRs.
1839
+ *
1840
+ * @param rootSvg - Root SVG element, used to read each character's laid-out
1841
+ * baseline `y` attribute for line-break detection (see below).
1805
1842
  */
1806
- calculateRBRs(nodes) {
1843
+ calculateRBRs(nodes, rootSvg) {
1807
1844
  if (nodes.length === 0)
1808
1845
  return [];
1809
1846
  const rbrs = [];
1810
1847
  this._logger.debug("HintRenderer", `Calculating RBRs for ${nodes.length} nodes`);
1811
- // Group nodes by line by checking their Y coordinates
1848
+ // Group nodes by line by checking their Y coordinates.
1849
+ // IMPORTANT: this compares the character's laid-out baseline Y (the `y`
1850
+ // attribute TextLayout assigned when positioning it), NOT its cached
1851
+ // getBBox().y. The tight ink bbox top varies per glyph shape (e.g. an
1852
+ // ascender like "t" vs. a descender like "g" vs. an x-height-only glyph
1853
+ // like "s"), easily by more than the 5px tolerance below, even though
1854
+ // the characters sit on the very same line. Using bbox.y here would
1855
+ // spuriously split a single line into multiple "line groups" — and thus
1856
+ // multiple RBRs, causing hints to be rendered (and animated) more than
1857
+ // once for what is really a single reference span.
1812
1858
  const lineGroups = [];
1813
1859
  let currentLineGroup = [];
1814
- let lastY = null;
1860
+ let lastBaselineY = null;
1815
1861
  const missingBounds = [];
1816
1862
  const skippedInvisible = [];
1817
1863
  for (const node of nodes) {
@@ -1830,15 +1876,21 @@ class HintRenderer {
1830
1876
  missingBounds.push(elementId);
1831
1877
  continue;
1832
1878
  }
1833
- // If Y coordinate significantly different, start new line group
1834
- if (lastY !== null && Math.abs(bounds.y - lastY) > 5) {
1879
+ // Prefer the laid-out baseline `y` attribute (deterministic, identical
1880
+ // for every character on the same line); fall back to the cached ink
1881
+ // bbox top only if the element can't be found (defensive).
1882
+ const el = rootSvg?.querySelector(`#${elementId}`);
1883
+ const attrY = el?.getAttribute("y");
1884
+ const baselineY = attrY !== null && attrY !== undefined ? parseFloat(attrY) : bounds.y;
1885
+ // If baseline Y coordinate significantly different, start new line group
1886
+ if (lastBaselineY !== null && Math.abs(baselineY - lastBaselineY) > 5) {
1835
1887
  if (currentLineGroup.length > 0) {
1836
1888
  lineGroups.push([...currentLineGroup]);
1837
1889
  currentLineGroup = [];
1838
1890
  }
1839
1891
  }
1840
1892
  currentLineGroup.push(elementId);
1841
- lastY = bounds.y;
1893
+ lastBaselineY = baselineY;
1842
1894
  }
1843
1895
  // Add last group
1844
1896
  if (currentLineGroup.length > 0) {
@@ -1863,7 +1915,7 @@ class HintRenderer {
1863
1915
  /**
1864
1916
  * Calculate RBRs from a displaced span reference (IDxN format).
1865
1917
  */
1866
- calculateDisplacedRBRs(displacedRefSpan, allBaseNodes) {
1918
+ calculateDisplacedRBRs(displacedRefSpan, allBaseNodes, rootSvg) {
1867
1919
  const parsed = parseDisplacedSpan(displacedRefSpan);
1868
1920
  if (!parsed) {
1869
1921
  this._logger.warn(`Invalid displaced span format: ${displacedRefSpan}`);
@@ -1874,7 +1926,7 @@ class HintRenderer {
1874
1926
  this._logger.warn(`No nodes found for displaced span ${displacedRefSpan}`);
1875
1927
  return [];
1876
1928
  }
1877
- return this.calculateRBRs(nodes);
1929
+ return this.calculateRBRs(nodes, rootSvg);
1878
1930
  }
1879
1931
  /**
1880
1932
  * Calculate scaling information for the hint.
@@ -1928,14 +1980,15 @@ class HintRenderer {
1928
1980
  * Per documentation: hints use the handle's bounding box as EBR if a handle element exists,
1929
1981
  * otherwise they use the whole hint's bounding box as EBR.
1930
1982
  */
1931
- async applyHintPositionTransform(hintGroup, hint, rbr, ebrBounds, scalingInfo) {
1983
+ async applyHintPositionTransform(hintGroup, hint, rbr, ebrBounds, scalingInfo, avgCharWidth, avgCharHeight) {
1932
1984
  // Calculate the alignment point on the RBR
1933
1985
  const rbrAlignPoint = this.calculateRBRAlignmentPoint(hint.position, rbr);
1934
1986
  // Calculate the alignment point on the EBR (in local/untransformed coordinates)
1935
1987
  const ebrAlignPoint = this.calculateEBRAlignmentPoint(hint.position, ebrBounds);
1936
- // Parse offsets (they can be px or "th"/"tw")
1937
- const offsetX = parseOffset(hint.offsetX, rbr.height, rbr.width);
1938
- const offsetY = parseOffset(hint.offsetY, rbr.height, rbr.width);
1988
+ // Parse offsets (they can be px or "th"/"tw", resolved against the average
1989
+ // character height/width, not the RBR — see docs "Hints" properties).
1990
+ const offsetX = parseOffset(hint.offsetX, avgCharHeight, avgCharWidth);
1991
+ const offsetY = parseOffset(hint.offsetY, avgCharHeight, avgCharWidth);
1939
1992
  // Apply offsets to the RBR alignment point
1940
1993
  const finalRbrX = rbrAlignPoint.x + offsetX;
1941
1994
  const finalRbrY = rbrAlignPoint.y + offsetY;
@@ -1980,6 +2033,7 @@ class HintRenderer {
1980
2033
  ebrAlignPoint_LOCAL: ebrAlignPoint,
1981
2034
  scalingInfo,
1982
2035
  rotation: hint.rotation,
2036
+ offsetInputs: { x: hint.offsetX, y: hint.offsetY, avgCharWidth, avgCharHeight },
1983
2037
  offsets: { x: offsetX, y: offsetY },
1984
2038
  finalRbrPoint: { x: finalRbrX, y: finalRbrY },
1985
2039
  transformString,
@@ -25917,7 +25971,7 @@ class GveSnapshotRendition extends HTMLElement {
25917
25971
  * of the web component is loaded.
25918
25972
  */
25919
25973
  static get version() {
25920
- return "2.0.6";
25974
+ return "2.0.7";
25921
25975
  }
25922
25976
  constructor() {
25923
25977
  super();
@@ -25930,6 +25984,12 @@ class GveSnapshotRendition extends HTMLElement {
25930
25984
  this._autoForwardEnabled = false;
25931
25985
  this._autoForwardTimerId = null;
25932
25986
  this._renderScheduled = false;
25987
+ // Guards against re-entrant navigation: a slideshow tick (or a rapid
25988
+ // double-click/keypress) firing while a previous goToVersionIndex() call
25989
+ // is still awaiting its hint/text animations would otherwise re-run the
25990
+ // whole render pipeline for the same transition, duplicating every
25991
+ // element and animation involved (see goToVersionIndex()).
25992
+ this._isNavigating = false;
25933
25993
  // Initialize with default settings
25934
25994
  this._settings = { ...DEFAULT_SETTINGS };
25935
25995
  this._autoForwardEnabled = this._settings.autoForwardOnGroup;
@@ -26634,55 +26694,72 @@ class GveSnapshotRendition extends HTMLElement {
26634
26694
  async goToVersionIndex(targetIndex) {
26635
26695
  if (!this._data || !this._textRenderer || !this._rootSvg)
26636
26696
  return;
26637
- this._logger.info(`Navigating from v${this._currentVersionIndex} to v${targetIndex}`);
26638
26697
  // Determine direction
26639
26698
  if (targetIndex === this._currentVersionIndex) {
26640
26699
  // Already at target
26641
26700
  return;
26642
26701
  }
26643
- // Stop any pending auto-forward when user manually navigates
26644
- this.stopAutoForward();
26645
- // Clear hilites before navigating
26646
- if (this._hilites && this._hilites.hasActiveHilites()) {
26647
- await this._hilites.clearHilites();
26648
- }
26649
- if (this._versionsListArea) {
26650
- this._versionsListArea.clearHilites();
26651
- }
26652
- if (targetIndex > this._currentVersionIndex) {
26653
- // FORWARD navigation: add elements
26654
- await this.navigateForward(targetIndex);
26655
- }
26656
- else {
26657
- // BACKWARD navigation: remove elements
26658
- await this.navigateBackward(targetIndex);
26659
- }
26660
- // Update versions list area
26661
- const versionTag = `v${targetIndex}`;
26662
- if (this._versionsListArea) {
26663
- this._versionsListArea.updateCurrentVersion(versionTag);
26702
+ // Re-entrancy guard: a previous call may still be awaiting its hint/text
26703
+ // animations (which can take arbitrarily long, since hints and their
26704
+ // animations are user-defined). _currentVersionIndex is only updated
26705
+ // once that call finishes, so without this guard a caller that fires
26706
+ // again in the meantime (e.g. a slideshow tick, or a fast double-click)
26707
+ // would re-run the whole render pipeline for the same transition,
26708
+ // duplicating every hint/text element and animation it touches.
26709
+ if (this._isNavigating) {
26710
+ this._logger.warn(`Ignoring navigation to v${targetIndex} - a navigation is already in progress`);
26711
+ return;
26664
26712
  }
26665
- // Update toolbar
26666
- if (this._toolbar) {
26667
- this._toolbar.setCurrentIndex(targetIndex);
26713
+ this._isNavigating = true;
26714
+ try {
26715
+ this._logger.info(`Navigating from v${this._currentVersionIndex} to v${targetIndex}`);
26716
+ // Stop any pending auto-forward when user manually navigates
26717
+ this.stopAutoForward();
26718
+ // Clear hilites before navigating
26719
+ if (this._hilites && this._hilites.hasActiveHilites()) {
26720
+ await this._hilites.clearHilites();
26721
+ }
26722
+ if (this._versionsListArea) {
26723
+ this._versionsListArea.clearHilites();
26724
+ }
26725
+ if (targetIndex > this._currentVersionIndex) {
26726
+ // FORWARD navigation: add elements
26727
+ await this.navigateForward(targetIndex);
26728
+ }
26729
+ else {
26730
+ // BACKWARD navigation: remove elements
26731
+ await this.navigateBackward(targetIndex);
26732
+ }
26733
+ // Update versions list area
26734
+ const versionTag = `v${targetIndex}`;
26735
+ if (this._versionsListArea) {
26736
+ this._versionsListArea.updateCurrentVersion(versionTag);
26737
+ }
26738
+ // Update toolbar
26739
+ if (this._toolbar) {
26740
+ this._toolbar.setCurrentIndex(targetIndex);
26741
+ }
26742
+ // Update version text area
26743
+ this.updateVersionText(targetIndex);
26744
+ // Update details area
26745
+ this.updateDetailsArea(targetIndex);
26746
+ // Re-setup hover listeners (new text elements may have been added)
26747
+ this.setupTextElementHoverListeners();
26748
+ // Check if we should auto-forward through grouped operations
26749
+ // Only auto-forward if:
26750
+ // 1. Feature is enabled
26751
+ // 2. We moved forward (targetIndex > previous index)
26752
+ // 3. Current operation has a groupId
26753
+ // 4. Not already in slideshow mode
26754
+ if (this._autoForwardEnabled &&
26755
+ targetIndex > 0 &&
26756
+ targetIndex <= (this._data?.steps?.length || 0) &&
26757
+ this._slideshowTimerId === null) {
26758
+ this.checkAndTriggerAutoForward(targetIndex);
26759
+ }
26668
26760
  }
26669
- // Update version text area
26670
- this.updateVersionText(targetIndex);
26671
- // Update details area
26672
- this.updateDetailsArea(targetIndex);
26673
- // Re-setup hover listeners (new text elements may have been added)
26674
- this.setupTextElementHoverListeners();
26675
- // Check if we should auto-forward through grouped operations
26676
- // Only auto-forward if:
26677
- // 1. Feature is enabled
26678
- // 2. We moved forward (targetIndex > previous index)
26679
- // 3. Current operation has a groupId
26680
- // 4. Not already in slideshow mode
26681
- if (this._autoForwardEnabled &&
26682
- targetIndex > 0 &&
26683
- targetIndex <= (this._data?.steps?.length || 0) &&
26684
- this._slideshowTimerId === null) {
26685
- this.checkAndTriggerAutoForward(targetIndex);
26761
+ finally {
26762
+ this._isNavigating = false;
26686
26763
  }
26687
26764
  }
26688
26765
  /**
@@ -27154,9 +27231,24 @@ class GveSnapshotRendition extends HTMLElement {
27154
27231
  this._toolbar.setSlideshowActive(true);
27155
27232
  this._toolbar.setNavigationDisabled(true);
27156
27233
  }
27157
- this._slideshowTimerId = window.setInterval(() => {
27158
- this.slideshowAdvance();
27159
- }, this._settings.slideshowInterval);
27234
+ // Use a self-rescheduling setTimeout rather than setInterval: each tick
27235
+ // is scheduled only after the previous slideshowAdvance() (and the
27236
+ // navigation/animations it awaits) has fully completed. A fixed-tick
27237
+ // setInterval would keep firing regardless of how long a transition's
27238
+ // (user-defined) hint animations actually take, and once a tick landed
27239
+ // mid-transition it would re-enter goToVersionIndex() for the very same
27240
+ // transition, duplicating every hint/text element it renders.
27241
+ const scheduleTick = () => {
27242
+ this._slideshowTimerId = window.setTimeout(async () => {
27243
+ await this.slideshowAdvance();
27244
+ // Only reschedule if still active - slideshowAdvance() may have
27245
+ // called stopSlideshow() (reached the end/start with no loop).
27246
+ if (this._slideshowTimerId !== null) {
27247
+ scheduleTick();
27248
+ }
27249
+ }, this._settings.slideshowInterval);
27250
+ };
27251
+ scheduleTick();
27160
27252
  }
27161
27253
  /**
27162
27254
  * Stop slideshow mode.
@@ -27167,7 +27259,7 @@ class GveSnapshotRendition extends HTMLElement {
27167
27259
  return;
27168
27260
  }
27169
27261
  this._logger.info("Stopping slideshow");
27170
- window.clearInterval(this._slideshowTimerId);
27262
+ window.clearTimeout(this._slideshowTimerId);
27171
27263
  this._slideshowTimerId = null;
27172
27264
  if (this._toolbar) {
27173
27265
  this._toolbar.setSlideshowActive(false);
@@ -40860,7 +40952,7 @@ function requireD () {
40860
40952
  + 'pragma private protected public pure ref return scope shared static struct '
40861
40953
  + 'super switch synchronized template this throw try typedef typeid typeof union '
40862
40954
  + 'unittest version void volatile while with __FILE__ __LINE__ __gshared|10 '
40863
- + '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.6',
40955
+ + '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.7',
40864
40956
  built_in:
40865
40957
  'bool cdouble cent cfloat char creal dchar delegate double dstring float function '
40866
40958
  + 'idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar '