@vectojs/core 1.33.0 → 1.34.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/index.js CHANGED
@@ -469,6 +469,21 @@ function rebaseChildBox(parent, parentOriginX, parentOriginY, child, childOrigin
469
469
  return REBASED_BOX;
470
470
  }
471
471
 
472
+ // src/tree/scene/content-break-carrier.ts
473
+ var HARD_BREAK_ONLY = /^[\r\n]+$/;
474
+ function appendContentBreak(owner, breakText) {
475
+ if (!breakText) return;
476
+ if (!HARD_BREAK_ONLY.test(breakText)) {
477
+ owner.appendChild(document.createTextNode(breakText));
478
+ return;
479
+ }
480
+ const span = document.createElement("span");
481
+ span.textContent = breakText;
482
+ span.style.fontSize = "0";
483
+ span.style.lineHeight = "0";
484
+ owner.appendChild(span);
485
+ }
486
+
472
487
  // src/tree/scene/content-line-window.ts
473
488
  function projectionLineWindow(lines, band, fallbackLineHeight) {
474
489
  const all = {
@@ -529,7 +544,8 @@ var ContentGridProjector = class {
529
544
  throw new Error("ContentProjection.grid.source must equal ContentProjection.text");
530
545
  }
531
546
  const gridWindow = projectionGridLineWindow(grid, projection.lines, lineBand);
532
- const signature = gridWindow.gated ? `${grid.revision}:${gridWindow.start}-${gridWindow.end}` : `${grid.revision}`;
547
+ const originSignature = _nullishCoalesce(_optionalChain([projection, 'access', _27 => _27.lines, 'optionalAccess', _28 => _28.find, 'call', _29 => _29((line) => line !== void 0), 'optionalAccess', _30 => _30.x]), () => ( 0));
548
+ const signature = gridWindow.gated ? `${grid.revision}:${gridWindow.start}-${gridWindow.end}:${originSignature}` : `${grid.revision}:${originSignature}`;
533
549
  if (el.dataset.vectoContentGrid !== signature) {
534
550
  const materializeStart = typeof performance !== "undefined" ? performance.now() : 0;
535
551
  this.contentProjection.clearGridState(entityId, el, false);
@@ -560,9 +576,9 @@ var ContentGridProjector = class {
560
576
  const domIndex = lineIndex - gridWindow.start;
561
577
  const gridLine = grid.lines[lineIndex];
562
578
  const projectedLine = projectionLines[lineIndex];
563
- const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _27 => _27.lineHeight]), () => ( grid.lineHeight));
564
- const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _28 => _28.baseline]), () => ( grid.baseline));
565
- const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _29 => _29.font]), () => ( grid.font));
579
+ const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _31 => _31.lineHeight]), () => ( grid.lineHeight));
580
+ const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _32 => _32.baseline]), () => ( grid.baseline));
581
+ const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _33 => _33.font]), () => ( grid.font));
566
582
  const lineSignature = contentGridLineSignature(
567
583
  grid,
568
584
  gridLine,
@@ -582,24 +598,24 @@ var ContentGridProjector = class {
582
598
  lineElement.dir = "ltr";
583
599
  lineElement.dataset.vectoGridLine = `${lineIndex}`;
584
600
  lineElement.style.position = "absolute";
585
- lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _30 => _30.x]), () => ( 0))}px`;
586
- lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _31 => _31.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
601
+ lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _34 => _34.x]), () => ( 0))}px`;
602
+ lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _35 => _35.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
587
603
  lineElement.style.width = `${gridLine.width}px`;
588
604
  lineElement.style.height = `${lineHeight}px`;
589
605
  lineElement.style.whiteSpace = "pre";
590
606
  lineElement.style.font = lineFont;
591
607
  lineElement.style.lineHeight = `${lineHeight}px`;
608
+ const breakText = grid.source.slice(gridLine.sourceEnd, gridLine.nextSourceStart);
592
609
  if (gridLine.cells.length === 0) {
593
- lineElement.textContent = grid.source.slice(gridLine.sourceEnd, gridLine.nextSourceStart);
610
+ appendContentBreak(lineElement, breakText);
594
611
  } else {
595
612
  let logicalX = 0;
596
613
  for (let cellIndex = 0; cellIndex < gridLine.cells.length; cellIndex++) {
597
614
  const cell = gridLine.cells[cellIndex];
598
615
  const cellElement = document.createElement("span");
599
616
  cellElement.dir = "ltr";
600
- const separator = cellIndex === gridLine.cells.length - 1 ? grid.source.slice(gridLine.sourceEnd, gridLine.nextSourceStart) : "";
601
617
  const sourceText = grid.source.slice(cell.sourceStart, cell.sourceEnd);
602
- cellElement.textContent = sourceText + separator;
618
+ cellElement.textContent = sourceText;
603
619
  cellElement.dataset.vectoGridCell = `${cellIndex}`;
604
620
  cellElement.dataset.vectoGridSourceLength = `${sourceText.length}`;
605
621
  cellElement.dataset.vectoGridSourceStart = `${cell.sourceStart}`;
@@ -625,6 +641,7 @@ var ContentGridProjector = class {
625
641
  lineElement.appendChild(cellElement);
626
642
  logicalX += cell.advance;
627
643
  }
644
+ appendContentBreak(lineElement, breakText);
628
645
  }
629
646
  if (lineIndex === 0) {
630
647
  for (const [basis, left, top] of [
@@ -651,7 +668,7 @@ var ContentGridProjector = class {
651
668
  }
652
669
  const windowLength = gridWindow.end - gridWindow.start;
653
670
  while (el.children.length > windowLength) {
654
- _optionalChain([el, 'access', _32 => _32.lastElementChild, 'optionalAccess', _33 => _33.remove, 'call', _34 => _34()]);
671
+ _optionalChain([el, 'access', _36 => _36.lastElementChild, 'optionalAccess', _37 => _37.remove, 'call', _38 => _38()]);
655
672
  }
656
673
  if (rebuiltSelectionLine) {
657
674
  this.contentProjection.restoreGridSelection(el, selectionSnapshot);
@@ -691,8 +708,8 @@ var ContentGridProjector = class {
691
708
  function contentGridLineSignature(grid, line, projected, lineHeight, baseline, font, isFirstLine) {
692
709
  const parts = [
693
710
  // Line box: position, size, and the font that resolves its baseline.
694
- `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _35 => _35.x]), () => ( 0))}`,
695
- `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _36 => _36.y]), () => ( ""))}`,
711
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _39 => _39.x]), () => ( 0))}`,
712
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _40 => _40.y]), () => ( ""))}`,
696
713
  `${lineHeight}`,
697
714
  `${baseline}`,
698
715
  font,
@@ -860,7 +877,7 @@ var A11yProjectionManager = (_class2 = class {constructor() { _class2.prototype.
860
877
  if (!selectionSnapshotTaken) {
861
878
  selectionSnapshotTaken = true;
862
879
  const live = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
863
- if (_optionalChain([live, 'optionalAccess', _37 => _37.anchorNode]) && live.focusNode) {
880
+ if (_optionalChain([live, 'optionalAccess', _41 => _41.anchorNode]) && live.focusNode) {
864
881
  selection = live;
865
882
  selAnchorNode = live.anchorNode;
866
883
  selFocusNode = live.focusNode;
@@ -995,7 +1012,7 @@ var CanvasGeometry = (_class3 = class {
995
1012
  }
996
1013
  /** Convert browser viewport coordinates into the scene's logical coordinates. */
997
1014
  clientToScene(clientX, clientY, width, height) {
998
- const rect = _optionalChain([this, 'access', _38 => _38.canvas, 'access', _39 => _39.getBoundingClientRect, 'optionalCall', _40 => _40()]);
1015
+ const rect = _optionalChain([this, 'access', _42 => _42.canvas, 'access', _43 => _43.getBoundingClientRect, 'optionalCall', _44 => _44()]);
999
1016
  if (!rect) return { x: clientX, y: clientY };
1000
1017
  const cssWidth = rect.width || this.canvas.clientWidth || width;
1001
1018
  const cssHeight = rect.height || this.canvas.clientHeight || height;
@@ -1033,16 +1050,18 @@ var CanvasGeometry = (_class3 = class {
1033
1050
  syncOverlay(width, height, glCanvas, gpuCanvas) {
1034
1051
  const parent = this.canvas.parentElement;
1035
1052
  if (!parent) return;
1036
- const canvasRect = _optionalChain([this, 'access', _41 => _41.canvas, 'access', _42 => _42.getBoundingClientRect, 'optionalCall', _43 => _43()]);
1037
- const parentRect = _optionalChain([parent, 'access', _44 => _44.getBoundingClientRect, 'optionalCall', _45 => _45()]);
1038
- const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _46 => _46.width]) || this.canvas.clientWidth || width;
1039
- const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _47 => _47.height]) || this.canvas.clientHeight || height;
1040
- const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _48 => _48.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _49 => _49.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
1041
- const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _50 => _50.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _51 => _51.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
1053
+ const canvasRect = _optionalChain([this, 'access', _45 => _45.canvas, 'access', _46 => _46.getBoundingClientRect, 'optionalCall', _47 => _47()]);
1054
+ const parentRect = _optionalChain([parent, 'access', _48 => _48.getBoundingClientRect, 'optionalCall', _49 => _49()]);
1055
+ const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _50 => _50.width]) || this.canvas.clientWidth || width;
1056
+ const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _51 => _51.height]) || this.canvas.clientHeight || height;
1057
+ const canvasPosition = typeof getComputedStyle === "function" ? getComputedStyle(this.canvas).position : "";
1058
+ const position = canvasPosition === "fixed" ? "fixed" : "absolute";
1059
+ const left = position === "fixed" ? _nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _52 => _52.left]), () => ( 0)) : (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _53 => _53.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _54 => _54.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
1060
+ const top = position === "fixed" ? _nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _55 => _55.top]), () => ( 0)) : (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _56 => _56.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _57 => _57.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
1042
1061
  const scaleX = width > 0 ? cssWidth / width : 1;
1043
1062
  const scaleY = height > 0 ? cssHeight / height : 1;
1044
1063
  const prev = this.overlayGeometry;
1045
- if (prev !== null && prev.left === left && prev.top === top && prev.cssWidth === cssWidth && prev.cssHeight === cssHeight && prev.width === width && prev.height === height) {
1064
+ if (prev !== null && prev.left === left && prev.top === top && prev.cssWidth === cssWidth && prev.cssHeight === cssHeight && prev.width === width && prev.height === height && prev.position === position) {
1046
1065
  return;
1047
1066
  }
1048
1067
  this.overlayGeometry = {
@@ -1051,10 +1070,12 @@ var CanvasGeometry = (_class3 = class {
1051
1070
  cssWidth,
1052
1071
  cssHeight,
1053
1072
  width,
1054
- height
1073
+ height,
1074
+ position
1055
1075
  };
1056
1076
  for (const root of [this.a11yRoot, this.portalRoot]) {
1057
1077
  if (!root) continue;
1078
+ root.style.position = position;
1058
1079
  root.style.left = `${left}px`;
1059
1080
  root.style.top = `${top}px`;
1060
1081
  root.style.width = `${width}px`;
@@ -1064,6 +1085,7 @@ var CanvasGeometry = (_class3 = class {
1064
1085
  }
1065
1086
  for (const canvas of [glCanvas, gpuCanvas]) {
1066
1087
  if (!canvas) continue;
1088
+ canvas.style.position = position;
1067
1089
  canvas.style.left = `${left}px`;
1068
1090
  canvas.style.top = `${top}px`;
1069
1091
  canvas.style.width = `${cssWidth}px`;
@@ -1601,7 +1623,7 @@ var HitTester = (_class6 = class {
1601
1623
  const idx = cell[k];
1602
1624
  if (x < minx[idx] || x > maxx[idx] || y < miny[idx] || y > maxy[idx]) continue;
1603
1625
  const entity = this.slotEntity[idx];
1604
- if (_optionalChain([entity, 'optionalAccess', _52 => _52.isPointInside, 'call', _53 => _53(x, y)]) && this.isHitEligible(entity, x, y)) {
1626
+ if (_optionalChain([entity, 'optionalAccess', _58 => _58.isPointInside, 'call', _59 => _59(x, y)]) && this.isHitEligible(entity, x, y)) {
1605
1627
  bestIndex = idx;
1606
1628
  bestEntity = entity;
1607
1629
  break;
@@ -1665,6 +1687,7 @@ var HitTester = (_class6 = class {
1665
1687
  }, _class6);
1666
1688
 
1667
1689
  // src/tree/scene/ContentProjectionManager.ts
1690
+ var PAGE_SCALE_BASIS_PX = 256;
1668
1691
  var ContentProjectionManager = (_class7 = class {
1669
1692
  /**
1670
1693
  * `a11yRoot` doubles as the projection root: carriers are appended to it, and
@@ -1756,16 +1779,16 @@ var ContentProjectionManager = (_class7 = class {
1756
1779
  * to the live DOM selection.
1757
1780
  */
1758
1781
  gridSelectionLine(el) {
1759
- const candidates = [_optionalChain([this, 'access', _54 => _54.anchor, 'optionalAccess', _55 => _55.node])];
1782
+ const candidates = [_optionalChain([this, 'access', _60 => _60.anchor, 'optionalAccess', _61 => _61.node])];
1760
1783
  if (typeof window !== "undefined" && typeof window.getSelection === "function") {
1761
1784
  const selection = window.getSelection();
1762
- candidates.push(_optionalChain([selection, 'optionalAccess', _56 => _56.anchorNode]), _optionalChain([selection, 'optionalAccess', _57 => _57.focusNode]));
1785
+ candidates.push(_optionalChain([selection, 'optionalAccess', _62 => _62.anchorNode]), _optionalChain([selection, 'optionalAccess', _63 => _63.focusNode]));
1763
1786
  }
1764
1787
  for (const candidate of candidates) {
1765
1788
  if (!candidate || !el.contains(candidate)) continue;
1766
1789
  let cursor = candidate;
1767
1790
  while (cursor && cursor.parentNode !== el) cursor = cursor.parentNode;
1768
- const lineIndex = _optionalChain([cursor, 'optionalAccess', _58 => _58.dataset, 'optionalAccess', _59 => _59.vectoGridLine]);
1791
+ const lineIndex = _optionalChain([cursor, 'optionalAccess', _64 => _64.dataset, 'optionalAccess', _65 => _65.vectoGridLine]);
1769
1792
  if (lineIndex !== void 0) return Number(lineIndex);
1770
1793
  }
1771
1794
  return null;
@@ -1790,10 +1813,10 @@ var ContentProjectionManager = (_class7 = class {
1790
1813
  releaseSelectionForRebuild(el) {
1791
1814
  if (!this.anchor && !this.selectionPresent()) return;
1792
1815
  const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
1793
- const ownsSelection = this.anchor && el.contains(this.anchor.node) || (_optionalChain([selection, 'optionalAccess', _60 => _60.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _61 => _61.focusNode]) ? el.contains(selection.focusNode) : false);
1816
+ const ownsSelection = this.anchor && el.contains(this.anchor.node) || (_optionalChain([selection, 'optionalAccess', _66 => _66.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _67 => _67.focusNode]) ? el.contains(selection.focusNode) : false);
1794
1817
  if (!ownsSelection) return;
1795
1818
  this.endDrag();
1796
- _optionalChain([selection, 'optionalAccess', _62 => _62.removeAllRanges, 'call', _63 => _63()]);
1819
+ _optionalChain([selection, 'optionalAccess', _68 => _68.removeAllRanges, 'call', _69 => _69()]);
1797
1820
  this.presentThisSync = null;
1798
1821
  }
1799
1822
  /**
@@ -1868,7 +1891,7 @@ var ContentProjectionManager = (_class7 = class {
1868
1891
  let cursor = node;
1869
1892
  while (cursor && cursor !== el) {
1870
1893
  const cell = cursor;
1871
- const start = _optionalChain([cell, 'access', _64 => _64.dataset, 'optionalAccess', _65 => _65.vectoGridSourceStart]);
1894
+ const start = _optionalChain([cell, 'access', _70 => _70.dataset, 'optionalAccess', _71 => _71.vectoGridSourceStart]);
1872
1895
  if (start !== void 0) {
1873
1896
  const sourceStart = Number(start);
1874
1897
  const sourceLength = Number(_nullishCoalesce(cell.dataset.vectoGridSourceLength, () => ( 0)));
@@ -1962,7 +1985,7 @@ var ContentProjectionManager = (_class7 = class {
1962
1985
  cancelAnimationFrame(calibrationFrame);
1963
1986
  }
1964
1987
  this.calibrationFrameHandles.delete(entityId);
1965
- _optionalChain([this, 'access', _66 => _66.calibrationProbes, 'access', _67 => _67.get, 'call', _68 => _68(entityId), 'optionalAccess', _69 => _69.remove, 'call', _70 => _70()]);
1988
+ _optionalChain([this, 'access', _72 => _72.calibrationProbes, 'access', _73 => _73.get, 'call', _74 => _74(entityId), 'optionalAccess', _75 => _75.remove, 'call', _76 => _76()]);
1966
1989
  this.calibrationProbes.delete(entityId);
1967
1990
  delete el.dataset.vectoGridCalibrationPending;
1968
1991
  delete el.dataset.vectoGridCalibration;
@@ -2026,7 +2049,7 @@ var ContentProjectionManager = (_class7 = class {
2026
2049
  if (previous !== void 0 && typeof cancelAnimationFrame === "function") {
2027
2050
  cancelAnimationFrame(previous);
2028
2051
  }
2029
- _optionalChain([this, 'access', _71 => _71.calibrationProbes, 'access', _72 => _72.get, 'call', _73 => _73(entityId), 'optionalAccess', _74 => _74.remove, 'call', _75 => _75()]);
2052
+ _optionalChain([this, 'access', _77 => _77.calibrationProbes, 'access', _78 => _78.get, 'call', _79 => _79(entityId), 'optionalAccess', _80 => _80.remove, 'call', _81 => _81()]);
2030
2053
  this.calibrationProbes.delete(entityId);
2031
2054
  const calibrationStart = typeof performance !== "undefined" ? performance.now() : 0;
2032
2055
  const probe = document.createElement("div");
@@ -2047,7 +2070,7 @@ var ContentProjectionManager = (_class7 = class {
2047
2070
  probeOrigin.style.top = "0";
2048
2071
  const probeX = document.createElement("span");
2049
2072
  probeX.style.position = "absolute";
2050
- probeX.style.left = "1px";
2073
+ probeX.style.left = `${PAGE_SCALE_BASIS_PX}px`;
2051
2074
  probeX.style.top = "0";
2052
2075
  probe.append(probeOrigin, probeX);
2053
2076
  const measurements = [];
@@ -2060,7 +2083,7 @@ var ContentProjectionManager = (_class7 = class {
2060
2083
  target.dataset.vectoGridCalib = generation;
2061
2084
  continue;
2062
2085
  }
2063
- const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _76 => _76.textContent, 'optionalAccess', _77 => _77.slice, 'call', _78 => _78(0, sourceLength)]), () => ( ""));
2086
+ const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _82 => _82.textContent, 'optionalAccess', _83 => _83.slice, 'call', _84 => _84(0, sourceLength)]), () => ( ""));
2064
2087
  if (!sourceText) {
2065
2088
  target.dataset.vectoGridCalib = generation;
2066
2089
  continue;
@@ -2120,7 +2143,7 @@ var ContentProjectionManager = (_class7 = class {
2120
2143
  const updates = [];
2121
2144
  const probeOriginRect = probeOrigin.getBoundingClientRect();
2122
2145
  const probeXRect = probeX.getBoundingClientRect();
2123
- const basisScale = Math.abs(probeXRect.left - probeOriginRect.left);
2146
+ const basisScale = Math.abs(probeXRect.left - probeOriginRect.left) / PAGE_SCALE_BASIS_PX;
2124
2147
  const projectionPageScaleX = Number.isFinite(basisScale) && basisScale > 0 ? basisScale : pageScaleX;
2125
2148
  let valid = true;
2126
2149
  for (const measurement of measurements) {
@@ -3456,9 +3479,9 @@ function parseCssMatrix(transform) {
3456
3479
  }
3457
3480
  function clientToGridLocal(contentEl, canvas, clientX, clientY) {
3458
3481
  const line = contentEl.querySelector("[data-vecto-grid-line]");
3459
- const originMarker = _optionalChain([line, 'optionalAccess', _79 => _79.querySelector, 'call', _80 => _80('[data-vecto-grid-basis="origin"]')]);
3460
- const xMarker = _optionalChain([line, 'optionalAccess', _81 => _81.querySelector, 'call', _82 => _82('[data-vecto-grid-basis="x"]')]);
3461
- const yMarker = _optionalChain([line, 'optionalAccess', _83 => _83.querySelector, 'call', _84 => _84('[data-vecto-grid-basis="y"]')]);
3482
+ const originMarker = _optionalChain([line, 'optionalAccess', _85 => _85.querySelector, 'call', _86 => _86('[data-vecto-grid-basis="origin"]')]);
3483
+ const xMarker = _optionalChain([line, 'optionalAccess', _87 => _87.querySelector, 'call', _88 => _88('[data-vecto-grid-basis="x"]')]);
3484
+ const yMarker = _optionalChain([line, 'optionalAccess', _89 => _89.querySelector, 'call', _90 => _90('[data-vecto-grid-basis="y"]')]);
3462
3485
  if (line && originMarker && xMarker && yMarker) {
3463
3486
  const origin = originMarker.getBoundingClientRect();
3464
3487
  const xPoint = xMarker.getBoundingClientRect();
@@ -3542,7 +3565,7 @@ function nearestTextPositionInLine(line, x, y) {
3542
3565
  nearest = { position: { node, offset }, distance: candidate.distance };
3543
3566
  }
3544
3567
  }
3545
- return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _85 => _85.position]), () => ( null));
3568
+ return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _91 => _91.position]), () => ( null));
3546
3569
  }
3547
3570
  function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
3548
3571
  if (contentEl.dataset.vectoContentGrid !== void 0) {
@@ -3556,7 +3579,7 @@ function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
3556
3579
  }
3557
3580
  targetLine = targetLine.parentElement;
3558
3581
  }
3559
- if (_optionalChain([targetLine, 'optionalAccess', _86 => _86.parentElement]) === contentEl) {
3582
+ if (_optionalChain([targetLine, 'optionalAccess', _92 => _92.parentElement]) === contentEl) {
3560
3583
  return nearestTextPositionInLine(targetLine, x, y);
3561
3584
  }
3562
3585
  let bestLine = null;
@@ -3809,7 +3832,7 @@ var Scene = (_class15 = class _Scene {
3809
3832
  __init102() {this.forcedColorsChangeHandler = null}
3810
3833
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
3811
3834
  get prefersReducedMotion() {
3812
- return this.respectReducedMotion && !!_optionalChain([this, 'access', _87 => _87.reducedMotionQuery, 'optionalAccess', _88 => _88.matches]);
3835
+ return this.respectReducedMotion && !!_optionalChain([this, 'access', _93 => _93.reducedMotionQuery, 'optionalAccess', _94 => _94.matches]);
3813
3836
  }
3814
3837
  /**
3815
3838
  * True when the OS is in a forced-colors mode (Windows High Contrast, and the
@@ -3820,7 +3843,7 @@ var Scene = (_class15 = class _Scene {
3820
3843
  * when the setting toggles.
3821
3844
  */
3822
3845
  get forcedColors() {
3823
- return !!_optionalChain([this, 'access', _89 => _89.forcedColorsQuery, 'optionalAccess', _90 => _90.matches]);
3846
+ return !!_optionalChain([this, 'access', _95 => _95.forcedColorsQuery, 'optionalAccess', _96 => _96.matches]);
3824
3847
  }
3825
3848
  // --- domain: a11y-projection — sync throttle and shadow-DOM state ---
3826
3849
  /**
@@ -4520,7 +4543,7 @@ var Scene = (_class15 = class _Scene {
4520
4543
  * active.
4521
4544
  */
4522
4545
  get webglDrawStats() {
4523
- return _nullishCoalesce(_optionalChain([this, 'access', _91 => _91.pointRenderer, 'optionalAccess', _92 => _92.stats, 'optionalCall', _93 => _93()]), () => ( null));
4546
+ return _nullishCoalesce(_optionalChain([this, 'access', _97 => _97.pointRenderer, 'optionalAccess', _98 => _98.stats, 'optionalCall', _99 => _99()]), () => ( null));
4524
4547
  }
4525
4548
  /**
4526
4549
  * Whether a WebGPU device is currently live for particle compute.
@@ -4589,8 +4612,8 @@ var Scene = (_class15 = class _Scene {
4589
4612
  static _devModeDetected() {
4590
4613
  if (_Scene._devMode) return true;
4591
4614
  const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
4592
- if (_optionalChain([gp, 'optionalAccess', _94 => _94.__DEV__])) return true;
4593
- if (_optionalChain([gp, 'optionalAccess', _95 => _95.process, 'optionalAccess', _96 => _96.env, 'optionalAccess', _97 => _97.NODE_ENV]) === "development") return true;
4615
+ if (_optionalChain([gp, 'optionalAccess', _100 => _100.__DEV__])) return true;
4616
+ if (_optionalChain([gp, 'optionalAccess', _101 => _101.process, 'optionalAccess', _102 => _102.env, 'optionalAccess', _103 => _103.NODE_ENV]) === "development") return true;
4594
4617
  return false;
4595
4618
  }
4596
4619
 
@@ -4652,9 +4675,9 @@ var Scene = (_class15 = class _Scene {
4652
4675
  let checked = 0;
4653
4676
  const walkProjections = (node) => {
4654
4677
  if (checked > 10) return;
4655
- const proj = _optionalChain([node, 'access', _98 => _98.getContentProjection, 'optionalCall', _99 => _99()]);
4656
- if (_optionalChain([proj, 'optionalAccess', _100 => _100.text]) && proj.selectable !== false) {
4657
- const el = _optionalChain([this, 'access', _101 => _101.contentElements, 'optionalAccess', _102 => _102.get, 'call', _103 => _103(node.id)]);
4678
+ const proj = _optionalChain([node, 'access', _104 => _104.getContentProjection, 'optionalCall', _105 => _105()]);
4679
+ if (_optionalChain([proj, 'optionalAccess', _106 => _106.text]) && proj.selectable !== false) {
4680
+ const el = _optionalChain([this, 'access', _107 => _107.contentElements, 'optionalAccess', _108 => _108.get, 'call', _109 => _109(node.id)]);
4658
4681
  if (el) {
4659
4682
  const projectedText = el.textContent || "";
4660
4683
  const windowed = el.dataset.vectoProjectionWindow !== void 0;
@@ -4678,8 +4701,8 @@ var Scene = (_class15 = class _Scene {
4678
4701
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
4679
4702
  this.maxDPR = options.maxDPR;
4680
4703
  if (this.disableWindowResize) {
4681
- const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _104 => _104.style, 'optionalAccess', _105 => _105.width]));
4682
- const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _106 => _106.style, 'optionalAccess', _107 => _107.height]));
4704
+ const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _110 => _110.style, 'optionalAccess', _111 => _111.width]));
4705
+ const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _112 => _112.style, 'optionalAccess', _113 => _113.height]));
4683
4706
  this.width = _nullishCoalesce(styleWidth, () => ( (canvas.width || canvas.clientWidth || 0)));
4684
4707
  this.height = _nullishCoalesce(styleHeight, () => ( (canvas.height || canvas.clientHeight || 0)));
4685
4708
  } else {
@@ -4687,7 +4710,7 @@ var Scene = (_class15 = class _Scene {
4687
4710
  this.height = typeof window !== "undefined" ? window.innerHeight : canvas.clientHeight || canvas.height || 600;
4688
4711
  }
4689
4712
  const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
4690
- const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _108 => _108.env, 'optionalAccess', _109 => _109.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _110 => _110.env, 'optionalAccess', _111 => _111.VITEST]) === "true");
4713
+ const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _114 => _114.env, 'optionalAccess', _115 => _115.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _116 => _116.env, 'optionalAccess', _117 => _117.VITEST]) === "true");
4691
4714
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
4692
4715
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
4693
4716
  this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
@@ -4707,7 +4730,7 @@ var Scene = (_class15 = class _Scene {
4707
4730
  if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
4708
4731
  this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
4709
4732
  this.forcedColorsChangeHandler = () => this.markDirty();
4710
- _optionalChain([this, 'access', _112 => _112.forcedColorsQuery, 'access', _113 => _113.addEventListener, 'optionalCall', _114 => _114("change", this.forcedColorsChangeHandler)]);
4733
+ _optionalChain([this, 'access', _118 => _118.forcedColorsQuery, 'access', _119 => _119.addEventListener, 'optionalCall', _120 => _120("change", this.forcedColorsChangeHandler)]);
4711
4734
  }
4712
4735
  this.root = new class RootEntity extends _chunkUEIZDNK7js.Entity {
4713
4736
  isPointInside() {
@@ -4738,9 +4761,9 @@ var Scene = (_class15 = class _Scene {
4738
4761
  this.maxDPR
4739
4762
  );
4740
4763
  }
4741
- _optionalChain([this, 'access', _115 => _115.renderer, 'access', _116 => _116.onContextRestored, 'optionalCall', _117 => _117(() => {
4764
+ _optionalChain([this, 'access', _121 => _121.renderer, 'access', _122 => _122.onContextRestored, 'optionalCall', _123 => _123(() => {
4742
4765
  this.markDirty();
4743
- if (_optionalChain([this, 'access', _118 => _118.renderer, 'access', _119 => _119.isContextLost, 'optionalCall', _120 => _120()]) !== true) this.render(this.renderer);
4766
+ if (_optionalChain([this, 'access', _124 => _124.renderer, 'access', _125 => _125.isContextLost, 'optionalCall', _126 => _126()]) !== true) this.render(this.renderer);
4744
4767
  })]);
4745
4768
  if (typeof document !== "undefined") {
4746
4769
  this.a11yRoot = document.createElement("div");
@@ -4913,16 +4936,16 @@ var Scene = (_class15 = class _Scene {
4913
4936
  watchDevicePixelRatio() {
4914
4937
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
4915
4938
  if (this.dprMediaQuery && this.dprChangeHandler) {
4916
- _optionalChain([this, 'access', _121 => _121.dprMediaQuery, 'access', _122 => _122.removeEventListener, 'optionalCall', _123 => _123("change", this.dprChangeHandler)]);
4939
+ _optionalChain([this, 'access', _127 => _127.dprMediaQuery, 'access', _128 => _128.removeEventListener, 'optionalCall', _129 => _129("change", this.dprChangeHandler)]);
4917
4940
  }
4918
4941
  const dpr = window.devicePixelRatio || 1;
4919
4942
  const query = window.matchMedia(`(resolution: ${dpr}dppx)`);
4920
4943
  const handler = () => {
4921
4944
  this.resize(this.width, this.height);
4922
- if (_optionalChain([this, 'access', _124 => _124.renderer, 'access', _125 => _125.isContextLost, 'optionalCall', _126 => _126()]) !== true) this.render(this.renderer);
4945
+ if (_optionalChain([this, 'access', _130 => _130.renderer, 'access', _131 => _131.isContextLost, 'optionalCall', _132 => _132()]) !== true) this.render(this.renderer);
4923
4946
  this.watchDevicePixelRatio();
4924
4947
  };
4925
- _optionalChain([query, 'access', _127 => _127.addEventListener, 'optionalCall', _128 => _128("change", handler)]);
4948
+ _optionalChain([query, 'access', _133 => _133.addEventListener, 'optionalCall', _134 => _134("change", handler)]);
4926
4949
  this.dprMediaQuery = query;
4927
4950
  this.dprChangeHandler = handler;
4928
4951
  }
@@ -4942,7 +4965,7 @@ var Scene = (_class15 = class _Scene {
4942
4965
  if (typeof gl.addEventListener !== "function") return;
4943
4966
  this.glContextLostHandler = (e) => {
4944
4967
  e.preventDefault();
4945
- _optionalChain([this, 'access', _129 => _129.pointRenderer, 'optionalAccess', _130 => _130.destroy, 'call', _131 => _131()]);
4968
+ _optionalChain([this, 'access', _135 => _135.pointRenderer, 'optionalAccess', _136 => _136.destroy, 'call', _137 => _137()]);
4946
4969
  this.pointRenderer = null;
4947
4970
  };
4948
4971
  this.glContextRestoredHandler = () => {
@@ -5143,12 +5166,12 @@ var Scene = (_class15 = class _Scene {
5143
5166
  this.canvasResizeObserver = null;
5144
5167
  }
5145
5168
  if (this.dprMediaQuery && this.dprChangeHandler) {
5146
- _optionalChain([this, 'access', _132 => _132.dprMediaQuery, 'access', _133 => _133.removeEventListener, 'optionalCall', _134 => _134("change", this.dprChangeHandler)]);
5169
+ _optionalChain([this, 'access', _138 => _138.dprMediaQuery, 'access', _139 => _139.removeEventListener, 'optionalCall', _140 => _140("change", this.dprChangeHandler)]);
5147
5170
  this.dprMediaQuery = null;
5148
5171
  this.dprChangeHandler = null;
5149
5172
  }
5150
5173
  if (this.forcedColorsQuery && this.forcedColorsChangeHandler) {
5151
- _optionalChain([this, 'access', _135 => _135.forcedColorsQuery, 'access', _136 => _136.removeEventListener, 'optionalCall', _137 => _137("change", this.forcedColorsChangeHandler)]);
5174
+ _optionalChain([this, 'access', _141 => _141.forcedColorsQuery, 'access', _142 => _142.removeEventListener, 'optionalCall', _143 => _143("change", this.forcedColorsChangeHandler)]);
5152
5175
  this.forcedColorsQuery = null;
5153
5176
  this.forcedColorsChangeHandler = null;
5154
5177
  }
@@ -5166,9 +5189,9 @@ var Scene = (_class15 = class _Scene {
5166
5189
  }
5167
5190
  this.pointerEventTarget = null;
5168
5191
  }
5169
- _optionalChain([this, 'access', _138 => _138.a11yRoot, 'optionalAccess', _139 => _139.remove, 'call', _140 => _140()]);
5192
+ _optionalChain([this, 'access', _144 => _144.a11yRoot, 'optionalAccess', _145 => _145.remove, 'call', _146 => _146()]);
5170
5193
  this.focusSentinel = null;
5171
- _optionalChain([this, 'access', _141 => _141.portalRoot, 'optionalAccess', _142 => _142.remove, 'call', _143 => _143()]);
5194
+ _optionalChain([this, 'access', _147 => _147.portalRoot, 'optionalAccess', _148 => _148.remove, 'call', _149 => _149()]);
5172
5195
  this.a11yElements.clear();
5173
5196
  for (const el of this.contentElements.values()) el.remove();
5174
5197
  this.contentElements.clear();
@@ -5184,10 +5207,10 @@ var Scene = (_class15 = class _Scene {
5184
5207
  }
5185
5208
  this.glContextLostHandler = null;
5186
5209
  this.glContextRestoredHandler = null;
5187
- _optionalChain([this, 'access', _144 => _144.pointRenderer, 'optionalAccess', _145 => _145.destroy, 'call', _146 => _146()]);
5188
- _optionalChain([this, 'access', _147 => _147.renderer, 'access', _148 => _148.dispose, 'optionalCall', _149 => _149()]);
5189
- _optionalChain([this, 'access', _150 => _150.glCanvas, 'optionalAccess', _151 => _151.remove, 'call', _152 => _152()]);
5190
- _optionalChain([this, 'access', _153 => _153.gpuCanvas, 'optionalAccess', _154 => _154.remove, 'call', _155 => _155()]);
5210
+ _optionalChain([this, 'access', _150 => _150.pointRenderer, 'optionalAccess', _151 => _151.destroy, 'call', _152 => _152()]);
5211
+ _optionalChain([this, 'access', _153 => _153.renderer, 'access', _154 => _154.dispose, 'optionalCall', _155 => _155()]);
5212
+ _optionalChain([this, 'access', _156 => _156.glCanvas, 'optionalAccess', _157 => _157.remove, 'call', _158 => _158()]);
5213
+ _optionalChain([this, 'access', _159 => _159.gpuCanvas, 'optionalAccess', _160 => _160.remove, 'call', _161 => _161()]);
5191
5214
  this.gpuCanvas = null;
5192
5215
  this.gpuContext = null;
5193
5216
  if (this.recoveryTimerId) {
@@ -5199,7 +5222,7 @@ var Scene = (_class15 = class _Scene {
5199
5222
  this.manager = null;
5200
5223
  }
5201
5224
  if (this.device) {
5202
- _optionalChain([this, 'access', _156 => _156.device, 'access', _157 => _157.destroy, 'optionalCall', _158 => _158()]);
5225
+ _optionalChain([this, 'access', _162 => _162.device, 'access', _163 => _163.destroy, 'optionalCall', _164 => _164()]);
5203
5226
  this.device = null;
5204
5227
  }
5205
5228
  }
@@ -5523,7 +5546,7 @@ var Scene = (_class15 = class _Scene {
5523
5546
  if (this.mouseX > -9e3 && !this.projectsSelectableText(node)) {
5524
5547
  if (node.isPointInside(this.mouseX, this.mouseY)) return true;
5525
5548
  }
5526
- const existing = _optionalChain([this, 'access', _159 => _159.a11yElements, 'optionalAccess', _160 => _160.get, 'call', _161 => _161(node.id)]);
5549
+ const existing = _optionalChain([this, 'access', _165 => _165.a11yElements, 'optionalAccess', _166 => _166.get, 'call', _167 => _167(node.id)]);
5527
5550
  if (existing && typeof document !== "undefined" && document.activeElement === existing) {
5528
5551
  return true;
5529
5552
  }
@@ -5537,8 +5560,8 @@ var Scene = (_class15 = class _Scene {
5537
5560
  * selection.
5538
5561
  */
5539
5562
  projectsSelectableText(node) {
5540
- const projection = _optionalChain([node, 'access', _162 => _162.getContentProjection, 'optionalCall', _163 => _163()]);
5541
- return !!_optionalChain([projection, 'optionalAccess', _164 => _164.text]) && projection.selectable !== false;
5563
+ const projection = _optionalChain([node, 'access', _168 => _168.getContentProjection, 'optionalCall', _169 => _169()]);
5564
+ return !!_optionalChain([projection, 'optionalAccess', _170 => _170.text]) && projection.selectable !== false;
5542
5565
  }
5543
5566
  /**
5544
5567
  * Keep `entity`'s a11y shadow node projected while it has
@@ -5736,7 +5759,7 @@ var Scene = (_class15 = class _Scene {
5736
5759
  el.addEventListener("compositionupdate", (e) => {
5737
5760
  const data = _nullishCoalesce(e.data, () => ( ""));
5738
5761
  composition = {
5739
- start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _165 => _165.start]), () => ( 0)),
5762
+ start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _171 => _171.start]), () => ( 0)),
5740
5763
  length: data.length
5741
5764
  };
5742
5765
  forward();
@@ -5978,7 +6001,7 @@ var Scene = (_class15 = class _Scene {
5978
6001
  const display = visible ? "" : "none";
5979
6002
  if (el.style.display !== display) el.style.display = display;
5980
6003
  }
5981
- _optionalChain([emitInitialScroll, 'optionalCall', _166 => _166()]);
6004
+ _optionalChain([emitInitialScroll, 'optionalCall', _172 => _172()]);
5982
6005
  }
5983
6006
  if (this.phases.enabled) {
5984
6007
  const projectionStart = performance.now();
@@ -6267,7 +6290,7 @@ var Scene = (_class15 = class _Scene {
6267
6290
  for (let runIndex = 0; runIndex < line.runs.length; runIndex++) {
6268
6291
  const run = line.runs[runIndex];
6269
6292
  const runElement = document.createElement("span");
6270
- runElement.textContent = run.text + (runIndex === line.runs.length - 1 ? separator : "");
6293
+ runElement.textContent = run.text;
6271
6294
  if (run.font) runElement.style.font = run.font;
6272
6295
  runElement.style.lineHeight = `${lineHeight2}px`;
6273
6296
  if (positioned && run.x !== void 0) {
@@ -6283,8 +6306,9 @@ var Scene = (_class15 = class _Scene {
6283
6306
  lineElement.appendChild(runElement);
6284
6307
  }
6285
6308
  } else {
6286
- lineElement.textContent = line.text + separator;
6309
+ lineElement.textContent = line.text;
6287
6310
  }
6311
+ appendContentBreak(lineElement, separator);
6288
6312
  el.appendChild(lineElement);
6289
6313
  }
6290
6314
  });
@@ -6335,6 +6359,9 @@ var Scene = (_class15 = class _Scene {
6335
6359
  if (node.width > 0) el.style.width = `${node.width}px`;
6336
6360
  if (node.height > 0) el.style.height = `${node.height}px`;
6337
6361
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
6362
+ const clipToBounds = projection.clipToBounds === true;
6363
+ const clipPath = clipToBounds && node.width > 0 && node.height > 0 ? `inset(${-localY}px ${contentX}px ${localY}px ${-contentX}px)` : "";
6364
+ if (el.style.clipPath !== clipPath) el.style.clipPath = clipPath;
6338
6365
  const residentTier = semanticMargin > interactionMargin;
6339
6366
  const display = visible || residentTier && !this.projectionBoxVisible(node, worldTf, 0, true) ? "" : "none";
6340
6367
  if (el.style.display !== display) el.style.display = display;
@@ -6373,8 +6400,8 @@ var Scene = (_class15 = class _Scene {
6373
6400
  next.f = f2;
6374
6401
  next.tier = tier;
6375
6402
  next.hasBand = lineBand !== null;
6376
- next.bandMin = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _167 => _167.minY]), () => ( 0));
6377
- next.bandMax = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _168 => _168.maxY]), () => ( 0));
6403
+ next.bandMin = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _173 => _173.minY]), () => ( 0));
6404
+ next.bandMax = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _174 => _174.maxY]), () => ( 0));
6378
6405
  next.visible = visible;
6379
6406
  next.width = node.width;
6380
6407
  next.height = node.height;
@@ -6560,7 +6587,7 @@ var Scene = (_class15 = class _Scene {
6560
6587
  * (and {@link respectReducedMotion} is on). `0` means uncapped.
6561
6588
  */
6562
6589
  effectiveMaxFPS() {
6563
- const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _169 => _169.reducedMotionQuery, 'optionalAccess', _170 => _170.matches]);
6590
+ const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _175 => _175.reducedMotionQuery, 'optionalAccess', _176 => _176.matches]);
6564
6591
  if (reduced)
6565
6592
  return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
6566
6593
  return this.maxFPS;
@@ -6655,7 +6682,7 @@ var Scene = (_class15 = class _Scene {
6655
6682
  * @param time - Current absolute time in milliseconds (default 0).
6656
6683
  */
6657
6684
  render(renderer, dt = 0, time = 0) {
6658
- if (_optionalChain([renderer, 'access', _171 => _171.isContextLost, 'optionalCall', _172 => _172()])) return;
6685
+ if (_optionalChain([renderer, 'access', _177 => _177.isContextLost, 'optionalCall', _178 => _178()])) return;
6659
6686
  const isMainRenderer = renderer === this.renderer;
6660
6687
  if (isMainRenderer && this.a11yRoot && this.canvas.parentElement) {
6661
6688
  const parentStyle = this.canvas.parentElement.style;
@@ -6804,7 +6831,7 @@ var Scene = (_class15 = class _Scene {
6804
6831
  }
6805
6832
  renderer.clear();
6806
6833
  if (isMainRenderer) {
6807
- _optionalChain([this, 'access', _173 => _173.pointRenderer, 'optionalAccess', _174 => _174.begin, 'call', _175 => _175()]);
6834
+ _optionalChain([this, 'access', _179 => _179.pointRenderer, 'optionalAccess', _180 => _180.begin, 'call', _181 => _181()]);
6808
6835
  }
6809
6836
  const vw = this.width;
6810
6837
  const vh = this.height;
@@ -7018,9 +7045,9 @@ var Scene = (_class15 = class _Scene {
7018
7045
  const flushT0 = this.phases.enabled ? performance.now() : 0;
7019
7046
  renderer.flush();
7020
7047
  if (isMainRenderer) {
7021
- _optionalChain([this, 'access', _176 => _176.pointRenderer, 'optionalAccess', _177 => _177.flush, 'call', _178 => _178()]);
7048
+ _optionalChain([this, 'access', _182 => _182.pointRenderer, 'optionalAccess', _183 => _183.flush, 'call', _184 => _184()]);
7022
7049
  }
7023
- _optionalChain([renderer, 'access', _179 => _179.present, 'optionalCall', _180 => _180()]);
7050
+ _optionalChain([renderer, 'access', _185 => _185.present, 'optionalCall', _186 => _186()]);
7024
7051
  if (this.phases.enabled) this.phases.record("flush", performance.now() - flushT0);
7025
7052
  if (flushTiming) endVectoUserTiming(flushTiming);
7026
7053
  if (this._devActive) {
@@ -7397,7 +7424,7 @@ var GridTextEntity = (_class17 = class extends _chunkUEIZDNK7js.Entity {
7397
7424
  updateGrid(ascii) {
7398
7425
  this.grid = ascii;
7399
7426
  this.rows = ascii.length;
7400
- this.cols = _optionalChain([ascii, 'access', _181 => _181[0], 'optionalAccess', _182 => _182.length]) || 0;
7427
+ this.cols = _optionalChain([ascii, 'access', _187 => _187[0], 'optionalAccess', _188 => _188.length]) || 0;
7401
7428
  }
7402
7429
  isPointInside(_globalX, _globalY) {
7403
7430
  return false;
@@ -7548,7 +7575,7 @@ var SplineEntity = (_class18 = class extends _chunkUEIZDNK7js.Entity {
7548
7575
  */
7549
7576
  computeContainsGradient() {
7550
7577
  const isGradient = (c) => c !== null && !Array.isArray(c);
7551
- return (_nullishCoalesce(_optionalChain([this, 'access', _183 => _183._doc, 'access', _184 => _184.equations, 'optionalAccess', _185 => _185.some, 'call', _186 => _186((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _187 => _187._doc, 'access', _188 => _188.paths, 'optionalAccess', _189 => _189.some, 'call', _190 => _190((p) => isGradient(p.color_rgb))]), () => ( false)));
7578
+ return (_nullishCoalesce(_optionalChain([this, 'access', _189 => _189._doc, 'access', _190 => _190.equations, 'optionalAccess', _191 => _191.some, 'call', _192 => _192((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _193 => _193._doc, 'access', _194 => _194.paths, 'optionalAccess', _195 => _195.some, 'call', _196 => _196((p) => isGradient(p.color_rgb))]), () => ( false)));
7552
7579
  }
7553
7580
  /** Clear cached baked canvas and hit-test polylines. */
7554
7581
  invalidateCache() {
package/dist/index.mjs CHANGED
@@ -468,6 +468,21 @@ function rebaseChildBox(parent, parentOriginX, parentOriginY, child, childOrigin
468
468
  return REBASED_BOX;
469
469
  }
470
470
 
471
+ // src/tree/scene/content-break-carrier.ts
472
+ var HARD_BREAK_ONLY = /^[\r\n]+$/;
473
+ function appendContentBreak(owner, breakText) {
474
+ if (!breakText) return;
475
+ if (!HARD_BREAK_ONLY.test(breakText)) {
476
+ owner.appendChild(document.createTextNode(breakText));
477
+ return;
478
+ }
479
+ const span = document.createElement("span");
480
+ span.textContent = breakText;
481
+ span.style.fontSize = "0";
482
+ span.style.lineHeight = "0";
483
+ owner.appendChild(span);
484
+ }
485
+
471
486
  // src/tree/scene/content-line-window.ts
472
487
  function projectionLineWindow(lines, band, fallbackLineHeight) {
473
488
  const all = {
@@ -528,7 +543,8 @@ var ContentGridProjector = class {
528
543
  throw new Error("ContentProjection.grid.source must equal ContentProjection.text");
529
544
  }
530
545
  const gridWindow = projectionGridLineWindow(grid, projection.lines, lineBand);
531
- const signature = gridWindow.gated ? `${grid.revision}:${gridWindow.start}-${gridWindow.end}` : `${grid.revision}`;
546
+ const originSignature = projection.lines?.find((line) => line !== void 0)?.x ?? 0;
547
+ const signature = gridWindow.gated ? `${grid.revision}:${gridWindow.start}-${gridWindow.end}:${originSignature}` : `${grid.revision}:${originSignature}`;
532
548
  if (el.dataset.vectoContentGrid !== signature) {
533
549
  const materializeStart = typeof performance !== "undefined" ? performance.now() : 0;
534
550
  this.contentProjection.clearGridState(entityId, el, false);
@@ -588,17 +604,17 @@ var ContentGridProjector = class {
588
604
  lineElement.style.whiteSpace = "pre";
589
605
  lineElement.style.font = lineFont;
590
606
  lineElement.style.lineHeight = `${lineHeight}px`;
607
+ const breakText = grid.source.slice(gridLine.sourceEnd, gridLine.nextSourceStart);
591
608
  if (gridLine.cells.length === 0) {
592
- lineElement.textContent = grid.source.slice(gridLine.sourceEnd, gridLine.nextSourceStart);
609
+ appendContentBreak(lineElement, breakText);
593
610
  } else {
594
611
  let logicalX = 0;
595
612
  for (let cellIndex = 0; cellIndex < gridLine.cells.length; cellIndex++) {
596
613
  const cell = gridLine.cells[cellIndex];
597
614
  const cellElement = document.createElement("span");
598
615
  cellElement.dir = "ltr";
599
- const separator = cellIndex === gridLine.cells.length - 1 ? grid.source.slice(gridLine.sourceEnd, gridLine.nextSourceStart) : "";
600
616
  const sourceText = grid.source.slice(cell.sourceStart, cell.sourceEnd);
601
- cellElement.textContent = sourceText + separator;
617
+ cellElement.textContent = sourceText;
602
618
  cellElement.dataset.vectoGridCell = `${cellIndex}`;
603
619
  cellElement.dataset.vectoGridSourceLength = `${sourceText.length}`;
604
620
  cellElement.dataset.vectoGridSourceStart = `${cell.sourceStart}`;
@@ -624,6 +640,7 @@ var ContentGridProjector = class {
624
640
  lineElement.appendChild(cellElement);
625
641
  logicalX += cell.advance;
626
642
  }
643
+ appendContentBreak(lineElement, breakText);
627
644
  }
628
645
  if (lineIndex === 0) {
629
646
  for (const [basis, left, top] of [
@@ -1036,12 +1053,14 @@ var CanvasGeometry = class {
1036
1053
  const parentRect = parent.getBoundingClientRect?.();
1037
1054
  const cssWidth = canvasRect?.width || this.canvas.clientWidth || width;
1038
1055
  const cssHeight = canvasRect?.height || this.canvas.clientHeight || height;
1039
- const left = (canvasRect?.left ?? 0) - (parentRect?.left ?? 0) - (parent.clientLeft || 0) + parent.scrollLeft;
1040
- const top = (canvasRect?.top ?? 0) - (parentRect?.top ?? 0) - (parent.clientTop || 0) + parent.scrollTop;
1056
+ const canvasPosition = typeof getComputedStyle === "function" ? getComputedStyle(this.canvas).position : "";
1057
+ const position = canvasPosition === "fixed" ? "fixed" : "absolute";
1058
+ const left = position === "fixed" ? canvasRect?.left ?? 0 : (canvasRect?.left ?? 0) - (parentRect?.left ?? 0) - (parent.clientLeft || 0) + parent.scrollLeft;
1059
+ const top = position === "fixed" ? canvasRect?.top ?? 0 : (canvasRect?.top ?? 0) - (parentRect?.top ?? 0) - (parent.clientTop || 0) + parent.scrollTop;
1041
1060
  const scaleX = width > 0 ? cssWidth / width : 1;
1042
1061
  const scaleY = height > 0 ? cssHeight / height : 1;
1043
1062
  const prev = this.overlayGeometry;
1044
- if (prev !== null && prev.left === left && prev.top === top && prev.cssWidth === cssWidth && prev.cssHeight === cssHeight && prev.width === width && prev.height === height) {
1063
+ if (prev !== null && prev.left === left && prev.top === top && prev.cssWidth === cssWidth && prev.cssHeight === cssHeight && prev.width === width && prev.height === height && prev.position === position) {
1045
1064
  return;
1046
1065
  }
1047
1066
  this.overlayGeometry = {
@@ -1050,10 +1069,12 @@ var CanvasGeometry = class {
1050
1069
  cssWidth,
1051
1070
  cssHeight,
1052
1071
  width,
1053
- height
1072
+ height,
1073
+ position
1054
1074
  };
1055
1075
  for (const root of [this.a11yRoot, this.portalRoot]) {
1056
1076
  if (!root) continue;
1077
+ root.style.position = position;
1057
1078
  root.style.left = `${left}px`;
1058
1079
  root.style.top = `${top}px`;
1059
1080
  root.style.width = `${width}px`;
@@ -1063,6 +1084,7 @@ var CanvasGeometry = class {
1063
1084
  }
1064
1085
  for (const canvas of [glCanvas, gpuCanvas]) {
1065
1086
  if (!canvas) continue;
1087
+ canvas.style.position = position;
1066
1088
  canvas.style.left = `${left}px`;
1067
1089
  canvas.style.top = `${top}px`;
1068
1090
  canvas.style.width = `${cssWidth}px`;
@@ -1664,6 +1686,7 @@ var HitTester = class {
1664
1686
  };
1665
1687
 
1666
1688
  // src/tree/scene/ContentProjectionManager.ts
1689
+ var PAGE_SCALE_BASIS_PX = 256;
1667
1690
  var ContentProjectionManager = class {
1668
1691
  /**
1669
1692
  * `a11yRoot` doubles as the projection root: carriers are appended to it, and
@@ -2046,7 +2069,7 @@ var ContentProjectionManager = class {
2046
2069
  probeOrigin.style.top = "0";
2047
2070
  const probeX = document.createElement("span");
2048
2071
  probeX.style.position = "absolute";
2049
- probeX.style.left = "1px";
2072
+ probeX.style.left = `${PAGE_SCALE_BASIS_PX}px`;
2050
2073
  probeX.style.top = "0";
2051
2074
  probe.append(probeOrigin, probeX);
2052
2075
  const measurements = [];
@@ -2119,7 +2142,7 @@ var ContentProjectionManager = class {
2119
2142
  const updates = [];
2120
2143
  const probeOriginRect = probeOrigin.getBoundingClientRect();
2121
2144
  const probeXRect = probeX.getBoundingClientRect();
2122
- const basisScale = Math.abs(probeXRect.left - probeOriginRect.left);
2145
+ const basisScale = Math.abs(probeXRect.left - probeOriginRect.left) / PAGE_SCALE_BASIS_PX;
2123
2146
  const projectionPageScaleX = Number.isFinite(basisScale) && basisScale > 0 ? basisScale : pageScaleX;
2124
2147
  let valid = true;
2125
2148
  for (const measurement of measurements) {
@@ -6266,7 +6289,7 @@ var Scene = class _Scene {
6266
6289
  for (let runIndex = 0; runIndex < line.runs.length; runIndex++) {
6267
6290
  const run = line.runs[runIndex];
6268
6291
  const runElement = document.createElement("span");
6269
- runElement.textContent = run.text + (runIndex === line.runs.length - 1 ? separator : "");
6292
+ runElement.textContent = run.text;
6270
6293
  if (run.font) runElement.style.font = run.font;
6271
6294
  runElement.style.lineHeight = `${lineHeight2}px`;
6272
6295
  if (positioned && run.x !== void 0) {
@@ -6282,8 +6305,9 @@ var Scene = class _Scene {
6282
6305
  lineElement.appendChild(runElement);
6283
6306
  }
6284
6307
  } else {
6285
- lineElement.textContent = line.text + separator;
6308
+ lineElement.textContent = line.text;
6286
6309
  }
6310
+ appendContentBreak(lineElement, separator);
6287
6311
  el.appendChild(lineElement);
6288
6312
  }
6289
6313
  });
@@ -6334,6 +6358,9 @@ var Scene = class _Scene {
6334
6358
  if (node.width > 0) el.style.width = `${node.width}px`;
6335
6359
  if (node.height > 0) el.style.height = `${node.height}px`;
6336
6360
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
6361
+ const clipToBounds = projection.clipToBounds === true;
6362
+ const clipPath = clipToBounds && node.width > 0 && node.height > 0 ? `inset(${-localY}px ${contentX}px ${localY}px ${-contentX}px)` : "";
6363
+ if (el.style.clipPath !== clipPath) el.style.clipPath = clipPath;
6337
6364
  const residentTier = semanticMargin > interactionMargin;
6338
6365
  const display = visible || residentTier && !this.projectionBoxVisible(node, worldTf, 0, true) ? "" : "none";
6339
6366
  if (el.style.display !== display) el.style.display = display;
@@ -178,6 +178,20 @@ export interface ContentProjection {
178
178
  * shaping, and bidi boundaries cannot drift between the two surfaces.
179
179
  */
180
180
  grid?: PreparedContentGrid;
181
+ /**
182
+ * Confine the projected text's paint to the projection element's own box.
183
+ *
184
+ * Opt in when the entity's `render()` clips its own drawing, so the two
185
+ * surfaces agree on where the content ends. Without it the canvas clips and
186
+ * the DOM copy does not, and a selection highlight over content wider than
187
+ * the box paints past the entity onto whatever is drawn beside it — the
188
+ * defect a horizontally scrollable code block exhibits.
189
+ *
190
+ * Off by default, because the projection element is deliberately unclipped:
191
+ * `Scene` relies on that so selection can start in an entity's blank/padding
192
+ * regions and extend beyond its bounds.
193
+ */
194
+ clipToBounds?: boolean;
181
195
  }
182
196
  /** Typography for a native input projected by the accessibility layer. */
183
197
  export interface TextInputStyle {
@@ -16,8 +16,9 @@
16
16
  *
17
17
  * - {@link clientToScene} — browser viewport coordinates to logical ones.
18
18
  * - {@link syncOverlay} — keeps the a11y, portal, WebGL and WebGPU layers
19
- * aligned with the canvas box, including the memo that makes an unchanged
20
- * frame write nothing.
19
+ * aligned with the canvas box, including the `position` that layers share with
20
+ * the canvas so a scroll needs no JS compensation, and the memo that makes an
21
+ * unchanged frame write nothing.
21
22
  * - {@link effectiveDPR} and {@link sizeGpuCanvas} — the DPR clamp and the
22
23
  * backing-store sizing that follows from it.
23
24
  *
@@ -74,6 +75,16 @@ export interface OverlayGeometry {
74
75
  cssHeight: number;
75
76
  width: number;
76
77
  height: number;
78
+ /**
79
+ * `position` written to the overlay layers, mirroring the canvas's own.
80
+ *
81
+ * Part of the memo because it is part of the written state: a canvas that
82
+ * becomes `fixed` after the first sync (a scroll-driven CSS class, a
83
+ * full-screen toggle) changes nothing else in here — same box, same logical
84
+ * size — so without this the memo would short-circuit and leave the overlay
85
+ * positioned the old way.
86
+ */
87
+ position: 'absolute' | 'fixed';
77
88
  }
78
89
  export declare class CanvasGeometry {
79
90
  private readonly canvas;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * How a projected line carries its trailing hard break.
3
+ *
4
+ * A projected line must hold its own line break in the DOM: copy, find-in-page
5
+ * and screen readers all read the projection, and a block whose lines are
6
+ * separate absolutely-positioned carriers has no flow structure a browser could
7
+ * synthesize a newline from. So the break character is written out explicitly.
8
+ *
9
+ * Writing it as an ordinary inline character is what this module exists to stop.
10
+ * The carriers are `white-space: pre`, so a trailing `\n` is a real preserved
11
+ * character in an inline context, and a browser gives it a selection rectangle
12
+ * of **zero width and full line height** at the end of the line. Chrome paints
13
+ * that rectangle, so selecting a line drew a caret-like vertical bar just past
14
+ * the last glyph — ink the canvas never drew, at a position no glyph occupies.
15
+ *
16
+ * Measured in real headed Chrome (DPR 1.76) on a live page, selecting one
17
+ * paragraph line of `1. 极致的性能与定制化:\n`: four selection rects, the last
18
+ * `x 495.18, w 0, h 31.82`. The same line with the break character removed
19
+ * produced one rect and no bar. Both projection paths were affected — a
20
+ * `CodeBlock` fixture reported one such rect on every line that owned a break
21
+ * (3 of 4 rows, including the empty row whose entire content is the break).
22
+ *
23
+ * ## Why the character stays in the DOM
24
+ *
25
+ * Deleting it fixes the bar and breaks copy: measured on the same page,
26
+ * stripping the trailing `\n` from every line of a block made
27
+ * `getSelection().toString()` return the block as one unbroken run. The break
28
+ * has to remain selectable text; it merely must not paint.
29
+ *
30
+ * `font-size: 0` is what separates those two. A zero font size leaves the
31
+ * character in the text content and in the selected string, while collapsing the
32
+ * line box it would otherwise contribute — measured on the same block, the
33
+ * zero-width full-height rect became `w 0, h 0` and the selected string still
34
+ * ended in `\n`.
35
+ *
36
+ * Rejected alternatives, each measured on the same live block:
37
+ *
38
+ * - **An absolutely positioned zero-size box** (`position: absolute; width: 0;
39
+ * height: 0; overflow: hidden`) still produced the full-height rect, *and*
40
+ * dropped the newline from the selected string — out of flow, the break no
41
+ * longer serialises. Worse on both counts.
42
+ * - **Removing the character** and relying on the carriers' own box structure.
43
+ * The line carriers do compute to `display: block`, so it is a reasonable
44
+ * guess that the browser would synthesize the break — it does not. Copy came
45
+ * back unbroken (see above).
46
+ *
47
+ * ## Why a shared module
48
+ *
49
+ * Both projection branches own break text and both had the defect:
50
+ * `Scene.syncContentProjection`'s carrier branch (plain and positioned-run
51
+ * lines) and {@link ContentGridProjector.syncGrid} (the last cell of a line, and
52
+ * an empty line's whole content). This follows the `content-line-window`
53
+ * precedent — a stateless helper both sides of the grid cut call, rather than a
54
+ * member on either — so the two cannot drift into disagreeing about how a break
55
+ * is represented, which would make copy fidelity depend on which branch drew a
56
+ * given block.
57
+ *
58
+ * Stateless by design: no `Scene`, no entities, no state beyond the node passed
59
+ * in.
60
+ */
61
+ /**
62
+ * Append `breakText` to `owner`, suppressing its paint only if it is a hard
63
+ * break.
64
+ *
65
+ * A no-op for empty text, so callers can pass a separator that may legitimately
66
+ * be absent (the document's last line owns no break) without branching.
67
+ *
68
+ * A hard break goes into a `font-size: 0` span: still selectable, copyable and
69
+ * announced, but contributing no line box. Any other separator — a soft-wrap
70
+ * space — is appended as a plain text node, because its width is part of the
71
+ * line the canvas drew.
72
+ *
73
+ * The span is deliberately **not** `aria-hidden` and does **not** set
74
+ * `user-select: none`: the break is real content that a screen reader and a copy
75
+ * both want. Only its painted geometry is suppressed.
76
+ */
77
+ export declare function appendContentBreak(owner: Node, breakText: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },