@vectojs/core 1.4.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -112,6 +112,31 @@ This projection is intentionally thin. Applications still own accessible names,
112
112
  focus order, contrast, and correct control semantics. See the
113
113
  [accessibility guide](https://vectojs.org/learn/accessibility/).
114
114
 
115
+ ## Static content projection
116
+
117
+ Canvas-rendered text can opt into browser-native find, translation, selection, and copy without
118
+ turning the application into a DOM layout. Override `getContentProjection()` on the owning Entity;
119
+ the Scene creates a transparent, position-synchronized mirror while the VMT remains authoritative:
120
+
121
+ ```ts
122
+ class SelectableLabel extends Entity {
123
+ getContentProjection() {
124
+ return {
125
+ text: 'Selectable canvas text',
126
+ font: '18px Inter',
127
+ lineHeight: 24,
128
+ selectable: true,
129
+ };
130
+ }
131
+ }
132
+ ```
133
+
134
+ `selectable` controls whether the mirror receives pointer input. The Scene keeps projection order
135
+ aligned with VMT order, removes descendant mirrors with their subtree, and hides mirrors fully
136
+ outside the viewport or a `clipChildren` ancestor. Tooling can inspect the currently materialized
137
+ node with `scene.getContentElement(entityId)`. Virtualized or non-materialized off-viewport text is
138
+ not searchable until the application brings it into the active scene.
139
+
115
140
  ## Performance model
116
141
 
117
142
  Useful levers include on-demand rendering, viewport culling, spatial hashing, prepared text layout,
@@ -939,6 +939,31 @@ var Entity = class {
939
939
  }
940
940
  };
941
941
 
942
+ // src/text/Typography.ts
943
+ var typographyContext;
944
+ var baselineCache = /* @__PURE__ */ new Map();
945
+ function cssLineBoxBaseline(font, lineHeight) {
946
+ if (typeof document === "undefined") return lineHeight * 0.8;
947
+ const key = `${font}\0${lineHeight}`;
948
+ const cached = baselineCache.get(key);
949
+ if (cached !== void 0) return cached;
950
+ if (typographyContext === void 0) {
951
+ typographyContext = document.createElement("canvas").getContext("2d");
952
+ }
953
+ if (!typographyContext) return lineHeight * 0.8;
954
+ typographyContext.font = font;
955
+ const metrics = typographyContext.measureText("Mg");
956
+ const ascent = metrics.fontBoundingBoxAscent || metrics.actualBoundingBoxAscent;
957
+ const descent = metrics.fontBoundingBoxDescent || metrics.actualBoundingBoxDescent;
958
+ if (!(ascent > 0) || !(descent >= 0)) return lineHeight * 0.8;
959
+ const baseline = (lineHeight - ascent - descent) / 2 + ascent;
960
+ baselineCache.set(key, baseline);
961
+ return baseline;
962
+ }
963
+ function clearCssLineBoxMetrics() {
964
+ baselineCache.clear();
965
+ }
966
+
942
967
  // src/text/MSDFFont.ts
943
968
  function kernKey(a, b) {
944
969
  return a * 1114112 + b;
@@ -1442,6 +1467,8 @@ export {
1442
1467
  SpringDriver,
1443
1468
  VectoJSEvent,
1444
1469
  Entity,
1470
+ cssLineBoxBaseline,
1471
+ clearCssLineBoxMetrics,
1445
1472
  MSDFFont,
1446
1473
  MSDFTextEntity,
1447
1474
  SVGEntity
@@ -939,6 +939,31 @@ var Entity = (_class4 = class {
939
939
  }
940
940
  }, _class4);
941
941
 
942
+ // src/text/Typography.ts
943
+ var typographyContext;
944
+ var baselineCache = /* @__PURE__ */ new Map();
945
+ function cssLineBoxBaseline(font, lineHeight) {
946
+ if (typeof document === "undefined") return lineHeight * 0.8;
947
+ const key = `${font}\0${lineHeight}`;
948
+ const cached = baselineCache.get(key);
949
+ if (cached !== void 0) return cached;
950
+ if (typographyContext === void 0) {
951
+ typographyContext = document.createElement("canvas").getContext("2d");
952
+ }
953
+ if (!typographyContext) return lineHeight * 0.8;
954
+ typographyContext.font = font;
955
+ const metrics = typographyContext.measureText("Mg");
956
+ const ascent = metrics.fontBoundingBoxAscent || metrics.actualBoundingBoxAscent;
957
+ const descent = metrics.fontBoundingBoxDescent || metrics.actualBoundingBoxDescent;
958
+ if (!(ascent > 0) || !(descent >= 0)) return lineHeight * 0.8;
959
+ const baseline = (lineHeight - ascent - descent) / 2 + ascent;
960
+ baselineCache.set(key, baseline);
961
+ return baseline;
962
+ }
963
+ function clearCssLineBoxMetrics() {
964
+ baselineCache.clear();
965
+ }
966
+
942
967
  // src/text/MSDFFont.ts
943
968
  function kernKey(a, b) {
944
969
  return a * 1114112 + b;
@@ -1445,4 +1470,6 @@ var SVGEntity = (_class7 = class extends Entity {
1445
1470
 
1446
1471
 
1447
1472
 
1448
- exports.SpringPhysics = SpringPhysics; exports.Easing = Easing; exports.isTweenConfig = isTweenConfig; exports.TweenDriver = TweenDriver; exports.SpringDriver = SpringDriver; exports.VectoJSEvent = VectoJSEvent; exports.Entity = Entity; exports.MSDFFont = MSDFFont; exports.MSDFTextEntity = MSDFTextEntity; exports.SVGEntity = SVGEntity;
1473
+
1474
+
1475
+ exports.SpringPhysics = SpringPhysics; exports.Easing = Easing; exports.isTweenConfig = isTweenConfig; exports.TweenDriver = TweenDriver; exports.SpringDriver = SpringDriver; exports.VectoJSEvent = VectoJSEvent; exports.Entity = Entity; exports.cssLineBoxBaseline = cssLineBoxBaseline; exports.clearCssLineBoxMetrics = clearCssLineBoxMetrics; exports.MSDFFont = MSDFFont; exports.MSDFTextEntity = MSDFTextEntity; exports.SVGEntity = SVGEntity;
@@ -598,8 +598,11 @@ var LayoutEngine = class {
598
598
  currentLineNodes.push({
599
599
  char: glyph.char,
600
600
  x: currentX,
601
- // Drop smaller glyphs to the shared baseline (no-op when gfs === pMax).
602
- y: currentY + (pMax - gfs),
601
+ // Canvas text is positioned by baseline, while `y` is the local
602
+ // top used by the renderer. Offset smaller runs by their baseline
603
+ // delta, not by their full em-box delta, so mixed-size glyphs share
604
+ // one real baseline in every Canvas 2D implementation.
605
+ y: currentY + (pMax - gfs) * 0.8,
603
606
  width: charWidth,
604
607
  height: gfs,
605
608
  style: glyph.style,
@@ -598,8 +598,11 @@ var LayoutEngine = (_class = class {
598
598
  currentLineNodes.push({
599
599
  char: glyph.char,
600
600
  x: currentX,
601
- // Drop smaller glyphs to the shared baseline (no-op when gfs === pMax).
602
- y: currentY + (pMax - gfs),
601
+ // Canvas text is positioned by baseline, while `y` is the local
602
+ // top used by the renderer. Offset smaller runs by their baseline
603
+ // delta, not by their full em-box delta, so mixed-size glyphs share
604
+ // one real baseline in every Canvas 2D implementation.
605
+ y: currentY + (pMax - gfs) * 0.8,
603
606
  width: charWidth,
604
607
  height: gfs,
605
608
  style: glyph.style,
package/dist/index.d.ts CHANGED
@@ -25,3 +25,4 @@ export { SVGEntity } from './text/SVGEntity';
25
25
  export * from './tree/ComputeParticleEntity';
26
26
  export * from './text/ArabicShaper';
27
27
  export * from './text/BidiResolver';
28
+ export * from './text/Typography';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkXLZDOFSBjs = require('./chunk-XLZDOFSB.js');
6
+ var _chunkZBKKRYDHjs = require('./chunk-ZBKKRYDH.js');
7
7
 
8
8
 
9
9
 
@@ -24,7 +24,9 @@ var _chunkBPMNCGU7js = require('./chunk-BPMNCGU7.js');
24
24
 
25
25
 
26
26
 
27
- var _chunkNHNOKBD4js = require('./chunk-NHNOKBD4.js');
27
+
28
+
29
+ var _chunkDFNK6OV6js = require('./chunk-DFNK6OV6.js');
28
30
 
29
31
 
30
32
 
@@ -41,7 +43,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
41
43
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
42
44
  var PARTICLE_OFFSET_SIZE = 6;
43
45
  var PARTICLE_OFFSET_LIFE = 7;
44
- var ComputeParticleEntity = (_class = class extends _chunkNHNOKBD4js.Entity {
46
+ var ComputeParticleEntity = (_class = class extends _chunkDFNK6OV6js.Entity {
45
47
 
46
48
 
47
49
 
@@ -429,7 +431,7 @@ var Scene = (_class2 = class _Scene {
429
431
  this.a11ySyncInterval = _nullishCoalesce(options.a11ySyncInterval, () => ( 0));
430
432
  this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
431
433
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
432
- this.root = new class RootEntity extends _chunkNHNOKBD4js.Entity {
434
+ this.root = new class RootEntity extends _chunkDFNK6OV6js.Entity {
433
435
  isPointInside() {
434
436
  return false;
435
437
  }
@@ -438,7 +440,7 @@ var Scene = (_class2 = class _Scene {
438
440
  }
439
441
  }("root");
440
442
  this.root._scene = this;
441
- this.overlayRoot = new class OverlayRoot extends _chunkNHNOKBD4js.Entity {
443
+ this.overlayRoot = new class OverlayRoot extends _chunkDFNK6OV6js.Entity {
442
444
  isPointInside() {
443
445
  return false;
444
446
  }
@@ -505,6 +507,7 @@ var Scene = (_class2 = class _Scene {
505
507
  };
506
508
  if (typeof document !== "undefined" && document.fonts) {
507
509
  this.fontLoadHandler = () => {
510
+ _chunkDFNK6OV6js.clearCssLineBoxMetrics.call(void 0, );
508
511
  this.markDirty();
509
512
  };
510
513
  document.fonts.ready.then(this.fontLoadHandler);
@@ -553,6 +556,7 @@ var Scene = (_class2 = class _Scene {
553
556
  if (contentEl) {
554
557
  contentEl.remove();
555
558
  this.contentElements.delete(node.id);
559
+ this.a11yNeedsReorder = true;
556
560
  }
557
561
  const el = this.a11yElements.get(node.id);
558
562
  if (el) {
@@ -801,42 +805,42 @@ var Scene = (_class2 = class _Scene {
801
805
  el.style.background = "transparent";
802
806
  }
803
807
  el.addEventListener("click", (e) => {
804
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("click", node, e));
808
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("click", node, e));
805
809
  });
806
810
  el.addEventListener("mouseenter", (e) => {
807
811
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
808
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("hover", node, e, false));
812
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("hover", node, e, false));
809
813
  });
810
814
  el.addEventListener("mouseleave", (e) => {
811
815
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
812
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerleave", node, e, false));
816
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerleave", node, e, false));
813
817
  });
814
818
  const capEl = el;
815
819
  el.addEventListener("pointerdown", (e) => {
816
820
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
817
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerdown", node, e));
821
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerdown", node, e));
818
822
  });
819
823
  el.addEventListener("pointerup", (e) => {
820
824
  if (typeof capEl.releasePointerCapture === "function")
821
825
  capEl.releasePointerCapture(e.pointerId);
822
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerup", node, e));
826
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerup", node, e));
823
827
  });
824
828
  el.addEventListener(
825
829
  "pointermove",
826
- (e) => node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointermove", node, e))
830
+ (e) => node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointermove", node, e))
827
831
  );
828
832
  el.addEventListener(
829
833
  "wheel",
830
834
  (e) => {
831
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("wheel", node, e));
835
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("wheel", node, e));
832
836
  },
833
837
  { passive: false }
834
838
  );
835
839
  el.addEventListener("keydown", (e) => {
836
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("keydown", node, e));
840
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("keydown", node, e));
837
841
  });
838
842
  el.addEventListener("keyup", (e) => {
839
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("keyup", node, e));
843
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("keyup", node, e));
840
844
  });
841
845
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
842
846
  const input = el;
@@ -909,7 +913,7 @@ var Scene = (_class2 = class _Scene {
909
913
  el.addEventListener("keydown", (e) => {
910
914
  if (e.key === "Enter" || e.key === " ") {
911
915
  e.preventDefault();
912
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("click", node, e));
916
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("click", node, e));
913
917
  }
914
918
  });
915
919
  }
@@ -992,6 +996,16 @@ var Scene = (_class2 = class _Scene {
992
996
  el.setAttribute("aria-valuenow", attrs.value);
993
997
  }
994
998
  }
999
+ if (attrs.textInputStyle !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1000
+ const textStyle = attrs.textInputStyle;
1001
+ if (el.style.font !== textStyle.font) el.style.font = textStyle.font;
1002
+ const lineHeight = `${textStyle.lineHeight}px`;
1003
+ if (el.style.lineHeight !== lineHeight) el.style.lineHeight = lineHeight;
1004
+ const padding = `${textStyle.padding}px`;
1005
+ if (el.style.padding !== padding) el.style.padding = padding;
1006
+ if (el.style.boxSizing !== "border-box") el.style.boxSizing = "border-box";
1007
+ if (el instanceof HTMLTextAreaElement) el.style.resize = "none";
1008
+ }
995
1009
  if (node.a11yFullViewport) {
996
1010
  el.style.left = "0px";
997
1011
  el.style.top = "0px";
@@ -1028,6 +1042,7 @@ var Scene = (_class2 = class _Scene {
1028
1042
  if (el) {
1029
1043
  el.remove();
1030
1044
  this.contentElements.delete(node.id);
1045
+ this.a11yNeedsReorder = true;
1031
1046
  }
1032
1047
  return;
1033
1048
  }
@@ -1046,14 +1061,54 @@ var Scene = (_class2 = class _Scene {
1046
1061
  el.addEventListener(
1047
1062
  "wheel",
1048
1063
  (e2) => {
1049
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("wheel", node, e2));
1064
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("wheel", node, e2));
1050
1065
  },
1051
1066
  { passive: false }
1052
1067
  );
1053
1068
  this.a11yRoot.appendChild(el);
1054
1069
  this.contentElements.set(node.id, el);
1070
+ this.a11yNeedsReorder = true;
1071
+ }
1072
+ const lines = projection.lines;
1073
+ if (lines && lines.length > 0) {
1074
+ const signature = JSON.stringify({
1075
+ lines,
1076
+ fallbackFont: _nullishCoalesce(projection.font, () => ( "")),
1077
+ fallbackLineHeight: _nullishCoalesce(projection.lineHeight, () => ( 16))
1078
+ });
1079
+ if (el.dataset.vectoProjectionLines !== signature) {
1080
+ el.replaceChildren();
1081
+ for (let index = 0; index < lines.length; index++) {
1082
+ const line = lines[index];
1083
+ const lineElement = document.createElement("span");
1084
+ const lineFont = _nullishCoalesce(_nullishCoalesce(line.font, () => ( projection.font)), () => ( ""));
1085
+ const lineHeight2 = _nullishCoalesce(_nullishCoalesce(line.lineHeight, () => ( projection.lineHeight)), () => ( 16));
1086
+ lineElement.style.position = "absolute";
1087
+ lineElement.style.left = `${line.x}px`;
1088
+ lineElement.style.top = `${line.y + line.baseline - _chunkDFNK6OV6js.cssLineBoxBaseline.call(void 0, lineFont, lineHeight2)}px`;
1089
+ lineElement.style.whiteSpace = "pre";
1090
+ if (lineFont) lineElement.style.font = lineFont;
1091
+ lineElement.style.lineHeight = `${lineHeight2}px`;
1092
+ if (line.runs && line.runs.length > 0) {
1093
+ for (const run of line.runs) {
1094
+ const runElement = document.createElement("span");
1095
+ runElement.textContent = run.text;
1096
+ if (run.font) runElement.style.font = run.font;
1097
+ runElement.style.lineHeight = `${lineHeight2}px`;
1098
+ lineElement.appendChild(runElement);
1099
+ }
1100
+ } else {
1101
+ lineElement.textContent = line.text;
1102
+ }
1103
+ el.appendChild(lineElement);
1104
+ if (index < lines.length - 1) el.appendChild(document.createTextNode("\n"));
1105
+ }
1106
+ el.dataset.vectoProjectionLines = signature;
1107
+ }
1108
+ } else {
1109
+ if (el.textContent !== projection.text) el.textContent = projection.text;
1110
+ delete el.dataset.vectoProjectionLines;
1055
1111
  }
1056
- if (el.textContent !== projection.text) el.textContent = projection.text;
1057
1112
  const font = _nullishCoalesce(projection.font, () => ( ""));
1058
1113
  if (el.style.font !== font) el.style.font = font;
1059
1114
  const lineHeight = projection.lineHeight !== void 0 ? `${projection.lineHeight}px` : "";
@@ -1071,13 +1126,18 @@ var Scene = (_class2 = class _Scene {
1071
1126
  el.style.cursor = selectable ? "text" : "";
1072
1127
  }
1073
1128
  const { a, b, c, d, e, f } = node.getWorldTransform();
1074
- el.style.left = `${e}px`;
1075
- el.style.top = `${f}px`;
1129
+ const contentX = _nullishCoalesce(projection.contentX, () => ( 0));
1130
+ const contentY = _nullishCoalesce(projection.contentY, () => ( 0));
1131
+ const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - _chunkDFNK6OV6js.cssLineBoxBaseline.call(void 0, font, _nullishCoalesce(projection.lineHeight, () => ( 16)));
1132
+ const localY = contentY + baselineOffset;
1133
+ el.style.left = `${e + a * contentX + c * localY}px`;
1134
+ el.style.top = `${f + b * contentX + d * localY}px`;
1076
1135
  if (node.width > 0) el.style.width = `${node.width}px`;
1077
1136
  if (node.height > 0) el.style.height = `${node.height}px`;
1078
1137
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
1079
1138
  let visible = true;
1080
1139
  if (node.width > 0 && node.height > 0) {
1140
+ const worldCorners = [];
1081
1141
  let minX = Infinity;
1082
1142
  let minY = Infinity;
1083
1143
  let maxX = -Infinity;
@@ -1087,12 +1147,29 @@ var Scene = (_class2 = class _Scene {
1087
1147
  const ly = i & 2 ? node.height : 0;
1088
1148
  const wx = a * lx + c * ly + e;
1089
1149
  const wy = b * lx + d * ly + f;
1150
+ worldCorners.push({ x: wx, y: wy });
1090
1151
  if (wx < minX) minX = wx;
1091
1152
  if (wx > maxX) maxX = wx;
1092
1153
  if (wy < minY) minY = wy;
1093
1154
  if (wy > maxY) maxY = wy;
1094
1155
  }
1095
1156
  visible = maxX >= 0 && minX <= this.width && maxY >= 0 && minY <= this.height;
1157
+ for (let ancestor = node.parent; visible && ancestor; ancestor = ancestor.parent) {
1158
+ if (!ancestor.clipChildren || ancestor.width <= 0 || ancestor.height <= 0) continue;
1159
+ let localMinX = Infinity;
1160
+ let localMinY = Infinity;
1161
+ let localMaxX = -Infinity;
1162
+ let localMaxY = -Infinity;
1163
+ for (const corner of worldCorners) {
1164
+ const local = ancestor.worldToLocal(corner.x, corner.y);
1165
+ if (!local) continue;
1166
+ localMinX = Math.min(localMinX, local.x);
1167
+ localMinY = Math.min(localMinY, local.y);
1168
+ localMaxX = Math.max(localMaxX, local.x);
1169
+ localMaxY = Math.max(localMaxY, local.y);
1170
+ }
1171
+ visible = localMaxX >= 0 && localMinX <= ancestor.width && localMaxY >= 0 && localMinY <= ancestor.height;
1172
+ }
1096
1173
  }
1097
1174
  const display = visible ? "" : "none";
1098
1175
  if (el.style.display !== display) el.style.display = display;
@@ -1640,6 +1717,10 @@ var Scene = (_class2 = class _Scene {
1640
1717
  getA11yElement(entityId) {
1641
1718
  return this.a11yElements.get(entityId);
1642
1719
  }
1720
+ /** Gets the static-content DOM projection for an entity ID, when materialized. */
1721
+ getContentElement(entityId) {
1722
+ return this.contentElements.get(entityId);
1723
+ }
1643
1724
  /**
1644
1725
  * Gets the root entity of the scene.
1645
1726
  */
@@ -1808,10 +1889,10 @@ var Scene = (_class2 = class _Scene {
1808
1889
  // src/components/TextEntity.ts
1809
1890
  var sharedMeasurer;
1810
1891
  function defaultMeasurer() {
1811
- if (sharedMeasurer === void 0) sharedMeasurer = _chunkXLZDOFSBjs.createCanvasMeasurer.call(void 0, "sans-serif");
1892
+ if (sharedMeasurer === void 0) sharedMeasurer = _chunkZBKKRYDHjs.createCanvasMeasurer.call(void 0, "sans-serif");
1812
1893
  return sharedMeasurer;
1813
1894
  }
1814
- var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1895
+ var TextEntity = (_class3 = class extends _chunkDFNK6OV6js.Entity {
1815
1896
 
1816
1897
 
1817
1898
 
@@ -1828,7 +1909,7 @@ var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1828
1909
  this.text = text;
1829
1910
  this.atlas = atlas;
1830
1911
  this.fontSize = fontSize;
1831
- this.layout = new (0, _chunkXLZDOFSBjs.LayoutEngine)(maxWidth, 1e4, defaultMeasurer());
1912
+ this.layout = new (0, _chunkZBKKRYDHjs.LayoutEngine)(maxWidth, 1e4, defaultMeasurer());
1832
1913
  this.prepared = this.layout.prepare(this.text, this.atlas, this.fontSize);
1833
1914
  this.applyLayout();
1834
1915
  this.interactive = true;
@@ -1937,7 +2018,7 @@ var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1937
2018
  }, _class3);
1938
2019
 
1939
2020
  // src/components/GridTextEntity.ts
1940
- var GridTextEntity = (_class4 = class extends _chunkNHNOKBD4js.Entity {
2021
+ var GridTextEntity = (_class4 = class extends _chunkDFNK6OV6js.Entity {
1941
2022
 
1942
2023
  __init60() {this.fillStyle = "#ffffff"}
1943
2024
  __init61() {this.grid = []}
@@ -2031,7 +2112,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
2031
2112
  const ey = py - cy;
2032
2113
  return ex * ex + ey * ey;
2033
2114
  }
2034
- var SplineEntity = (_class5 = class extends _chunkNHNOKBD4js.Entity {
2115
+ var SplineEntity = (_class5 = class extends _chunkDFNK6OV6js.Entity {
2035
2116
 
2036
2117
 
2037
2118
 
@@ -2400,7 +2481,7 @@ var SpatialHashGrid = (_class6 = class {
2400
2481
  }, _class6);
2401
2482
 
2402
2483
  // src/tree/DOMPortalEntity.ts
2403
- var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2484
+ var DOMPortalEntity = (_class7 = class extends _chunkDFNK6OV6js.Entity {
2404
2485
 
2405
2486
  __init72() {this.isDOMPortal = true}
2406
2487
  __init73() {this.domListeners = []}
@@ -2435,7 +2516,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2435
2516
  const events = ["click", "pointerdown", "pointerup", "pointermove", "wheel"];
2436
2517
  for (const type of events) {
2437
2518
  const handler = (e) => {
2438
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(type, this, e));
2519
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(type, this, e));
2439
2520
  };
2440
2521
  this.domElement.addEventListener(type, handler);
2441
2522
  this.domListeners.push({ type, handler, capture: false });
@@ -2446,7 +2527,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2446
2527
  ];
2447
2528
  for (const { native, vecto } of hoverEvents) {
2448
2529
  const handler = (e) => {
2449
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(vecto, this, e, false));
2530
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(vecto, this, e, false));
2450
2531
  };
2451
2532
  this.domElement.addEventListener(native, handler);
2452
2533
  this.domListeners.push({ type: native, handler, capture: false });
@@ -2454,7 +2535,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2454
2535
  const focusEvents = ["focus", "blur"];
2455
2536
  for (const type of focusEvents) {
2456
2537
  const handler = (e) => {
2457
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(type, this, e, true));
2538
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(type, this, e, true));
2458
2539
  };
2459
2540
  this.domElement.addEventListener(type, handler, true);
2460
2541
  this.domListeners.push({ type, handler, capture: true });
@@ -2539,4 +2620,6 @@ Scene.registerWebGPUParticleSystemManager(_chunkBPMNCGU7js.WebGPUParticleSystemM
2539
2620
 
2540
2621
 
2541
2622
 
2542
- exports.ArabicShaper = _chunkCTZQOM5Zjs.ArabicShaper; exports.BidiResolver = _chunkCTZQOM5Zjs.BidiResolver; exports.CanvasRenderer = _chunkBPMNCGU7js.CanvasRenderer; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunkNHNOKBD4js.Easing; exports.Entity = _chunkNHNOKBD4js.Entity; exports.GridTextEntity = GridTextEntity; exports.LayoutEngine = _chunkXLZDOFSBjs.LayoutEngine; exports.LayoutResultBuffer = _chunkXLZDOFSBjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkCTZQOM5Zjs.LayoutWorkerManager; exports.MSDFFont = _chunkNHNOKBD4js.MSDFFont; exports.MSDFTextEntity = _chunkNHNOKBD4js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.SVGEntity = _chunkNHNOKBD4js.SVGEntity; exports.SVGRenderer = _chunkBPMNCGU7js.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunkNHNOKBD4js.SpringDriver; exports.SpringPhysics = _chunkNHNOKBD4js.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunkNHNOKBD4js.TweenDriver; exports.VectoJSEvent = _chunkNHNOKBD4js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkBPMNCGU7js.WebGPUParticleSystemManager; exports.computeLineSegments = _chunkXLZDOFSBjs.computeLineSegments; exports.createCanvasMeasurer = _chunkXLZDOFSBjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkBPMNCGU7js.createWebGLPointRenderer; exports.isSafeUrl = _chunkBPMNCGU7js.isSafeUrl; exports.isTweenConfig = _chunkNHNOKBD4js.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkBPMNCGU7js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkBPMNCGU7js.sanitizeUrl;
2623
+
2624
+
2625
+ exports.ArabicShaper = _chunkCTZQOM5Zjs.ArabicShaper; exports.BidiResolver = _chunkCTZQOM5Zjs.BidiResolver; exports.CanvasRenderer = _chunkBPMNCGU7js.CanvasRenderer; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunkDFNK6OV6js.Easing; exports.Entity = _chunkDFNK6OV6js.Entity; exports.GridTextEntity = GridTextEntity; exports.LayoutEngine = _chunkZBKKRYDHjs.LayoutEngine; exports.LayoutResultBuffer = _chunkZBKKRYDHjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkCTZQOM5Zjs.LayoutWorkerManager; exports.MSDFFont = _chunkDFNK6OV6js.MSDFFont; exports.MSDFTextEntity = _chunkDFNK6OV6js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.SVGEntity = _chunkDFNK6OV6js.SVGEntity; exports.SVGRenderer = _chunkBPMNCGU7js.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunkDFNK6OV6js.SpringDriver; exports.SpringPhysics = _chunkDFNK6OV6js.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunkDFNK6OV6js.TweenDriver; exports.VectoJSEvent = _chunkDFNK6OV6js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkBPMNCGU7js.WebGPUParticleSystemManager; exports.clearCssLineBoxMetrics = _chunkDFNK6OV6js.clearCssLineBoxMetrics; exports.computeLineSegments = _chunkZBKKRYDHjs.computeLineSegments; exports.createCanvasMeasurer = _chunkZBKKRYDHjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkBPMNCGU7js.createWebGLPointRenderer; exports.cssLineBoxBaseline = _chunkDFNK6OV6js.cssLineBoxBaseline; exports.isSafeUrl = _chunkBPMNCGU7js.isSafeUrl; exports.isTweenConfig = _chunkDFNK6OV6js.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkBPMNCGU7js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkBPMNCGU7js.sanitizeUrl;
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  LayoutResultBuffer,
4
4
  computeLineSegments,
5
5
  createCanvasMeasurer
6
- } from "./chunk-FNXOS7IA.mjs";
6
+ } from "./chunk-V7GUWG5C.mjs";
7
7
  import {
8
8
  CanvasRenderer,
9
9
  SVGRenderer,
@@ -23,8 +23,10 @@ import {
23
23
  SpringPhysics,
24
24
  TweenDriver,
25
25
  VectoJSEvent,
26
+ clearCssLineBoxMetrics,
27
+ cssLineBoxBaseline,
26
28
  isTweenConfig
27
- } from "./chunk-N7DNUDVY.mjs";
29
+ } from "./chunk-3CODWSF3.mjs";
28
30
  import {
29
31
  ArabicShaper,
30
32
  BidiResolver,
@@ -505,6 +507,7 @@ var Scene = class _Scene {
505
507
  };
506
508
  if (typeof document !== "undefined" && document.fonts) {
507
509
  this.fontLoadHandler = () => {
510
+ clearCssLineBoxMetrics();
508
511
  this.markDirty();
509
512
  };
510
513
  document.fonts.ready.then(this.fontLoadHandler);
@@ -553,6 +556,7 @@ var Scene = class _Scene {
553
556
  if (contentEl) {
554
557
  contentEl.remove();
555
558
  this.contentElements.delete(node.id);
559
+ this.a11yNeedsReorder = true;
556
560
  }
557
561
  const el = this.a11yElements.get(node.id);
558
562
  if (el) {
@@ -992,6 +996,16 @@ var Scene = class _Scene {
992
996
  el.setAttribute("aria-valuenow", attrs.value);
993
997
  }
994
998
  }
999
+ if (attrs.textInputStyle !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1000
+ const textStyle = attrs.textInputStyle;
1001
+ if (el.style.font !== textStyle.font) el.style.font = textStyle.font;
1002
+ const lineHeight = `${textStyle.lineHeight}px`;
1003
+ if (el.style.lineHeight !== lineHeight) el.style.lineHeight = lineHeight;
1004
+ const padding = `${textStyle.padding}px`;
1005
+ if (el.style.padding !== padding) el.style.padding = padding;
1006
+ if (el.style.boxSizing !== "border-box") el.style.boxSizing = "border-box";
1007
+ if (el instanceof HTMLTextAreaElement) el.style.resize = "none";
1008
+ }
995
1009
  if (node.a11yFullViewport) {
996
1010
  el.style.left = "0px";
997
1011
  el.style.top = "0px";
@@ -1028,6 +1042,7 @@ var Scene = class _Scene {
1028
1042
  if (el) {
1029
1043
  el.remove();
1030
1044
  this.contentElements.delete(node.id);
1045
+ this.a11yNeedsReorder = true;
1031
1046
  }
1032
1047
  return;
1033
1048
  }
@@ -1052,8 +1067,48 @@ var Scene = class _Scene {
1052
1067
  );
1053
1068
  this.a11yRoot.appendChild(el);
1054
1069
  this.contentElements.set(node.id, el);
1070
+ this.a11yNeedsReorder = true;
1071
+ }
1072
+ const lines = projection.lines;
1073
+ if (lines && lines.length > 0) {
1074
+ const signature = JSON.stringify({
1075
+ lines,
1076
+ fallbackFont: projection.font ?? "",
1077
+ fallbackLineHeight: projection.lineHeight ?? 16
1078
+ });
1079
+ if (el.dataset.vectoProjectionLines !== signature) {
1080
+ el.replaceChildren();
1081
+ for (let index = 0; index < lines.length; index++) {
1082
+ const line = lines[index];
1083
+ const lineElement = document.createElement("span");
1084
+ const lineFont = line.font ?? projection.font ?? "";
1085
+ const lineHeight2 = line.lineHeight ?? projection.lineHeight ?? 16;
1086
+ lineElement.style.position = "absolute";
1087
+ lineElement.style.left = `${line.x}px`;
1088
+ lineElement.style.top = `${line.y + line.baseline - cssLineBoxBaseline(lineFont, lineHeight2)}px`;
1089
+ lineElement.style.whiteSpace = "pre";
1090
+ if (lineFont) lineElement.style.font = lineFont;
1091
+ lineElement.style.lineHeight = `${lineHeight2}px`;
1092
+ if (line.runs && line.runs.length > 0) {
1093
+ for (const run of line.runs) {
1094
+ const runElement = document.createElement("span");
1095
+ runElement.textContent = run.text;
1096
+ if (run.font) runElement.style.font = run.font;
1097
+ runElement.style.lineHeight = `${lineHeight2}px`;
1098
+ lineElement.appendChild(runElement);
1099
+ }
1100
+ } else {
1101
+ lineElement.textContent = line.text;
1102
+ }
1103
+ el.appendChild(lineElement);
1104
+ if (index < lines.length - 1) el.appendChild(document.createTextNode("\n"));
1105
+ }
1106
+ el.dataset.vectoProjectionLines = signature;
1107
+ }
1108
+ } else {
1109
+ if (el.textContent !== projection.text) el.textContent = projection.text;
1110
+ delete el.dataset.vectoProjectionLines;
1055
1111
  }
1056
- if (el.textContent !== projection.text) el.textContent = projection.text;
1057
1112
  const font = projection.font ?? "";
1058
1113
  if (el.style.font !== font) el.style.font = font;
1059
1114
  const lineHeight = projection.lineHeight !== void 0 ? `${projection.lineHeight}px` : "";
@@ -1071,13 +1126,18 @@ var Scene = class _Scene {
1071
1126
  el.style.cursor = selectable ? "text" : "";
1072
1127
  }
1073
1128
  const { a, b, c, d, e, f } = node.getWorldTransform();
1074
- el.style.left = `${e}px`;
1075
- el.style.top = `${f}px`;
1129
+ const contentX = projection.contentX ?? 0;
1130
+ const contentY = projection.contentY ?? 0;
1131
+ const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - cssLineBoxBaseline(font, projection.lineHeight ?? 16);
1132
+ const localY = contentY + baselineOffset;
1133
+ el.style.left = `${e + a * contentX + c * localY}px`;
1134
+ el.style.top = `${f + b * contentX + d * localY}px`;
1076
1135
  if (node.width > 0) el.style.width = `${node.width}px`;
1077
1136
  if (node.height > 0) el.style.height = `${node.height}px`;
1078
1137
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
1079
1138
  let visible = true;
1080
1139
  if (node.width > 0 && node.height > 0) {
1140
+ const worldCorners = [];
1081
1141
  let minX = Infinity;
1082
1142
  let minY = Infinity;
1083
1143
  let maxX = -Infinity;
@@ -1087,12 +1147,29 @@ var Scene = class _Scene {
1087
1147
  const ly = i & 2 ? node.height : 0;
1088
1148
  const wx = a * lx + c * ly + e;
1089
1149
  const wy = b * lx + d * ly + f;
1150
+ worldCorners.push({ x: wx, y: wy });
1090
1151
  if (wx < minX) minX = wx;
1091
1152
  if (wx > maxX) maxX = wx;
1092
1153
  if (wy < minY) minY = wy;
1093
1154
  if (wy > maxY) maxY = wy;
1094
1155
  }
1095
1156
  visible = maxX >= 0 && minX <= this.width && maxY >= 0 && minY <= this.height;
1157
+ for (let ancestor = node.parent; visible && ancestor; ancestor = ancestor.parent) {
1158
+ if (!ancestor.clipChildren || ancestor.width <= 0 || ancestor.height <= 0) continue;
1159
+ let localMinX = Infinity;
1160
+ let localMinY = Infinity;
1161
+ let localMaxX = -Infinity;
1162
+ let localMaxY = -Infinity;
1163
+ for (const corner of worldCorners) {
1164
+ const local = ancestor.worldToLocal(corner.x, corner.y);
1165
+ if (!local) continue;
1166
+ localMinX = Math.min(localMinX, local.x);
1167
+ localMinY = Math.min(localMinY, local.y);
1168
+ localMaxX = Math.max(localMaxX, local.x);
1169
+ localMaxY = Math.max(localMaxY, local.y);
1170
+ }
1171
+ visible = localMaxX >= 0 && localMinX <= ancestor.width && localMaxY >= 0 && localMinY <= ancestor.height;
1172
+ }
1096
1173
  }
1097
1174
  const display = visible ? "" : "none";
1098
1175
  if (el.style.display !== display) el.style.display = display;
@@ -1640,6 +1717,10 @@ var Scene = class _Scene {
1640
1717
  getA11yElement(entityId) {
1641
1718
  return this.a11yElements.get(entityId);
1642
1719
  }
1720
+ /** Gets the static-content DOM projection for an entity ID, when materialized. */
1721
+ getContentElement(entityId) {
1722
+ return this.contentElements.get(entityId);
1723
+ }
1643
1724
  /**
1644
1725
  * Gets the root entity of the scene.
1645
1726
  */
@@ -2530,9 +2611,11 @@ export {
2530
2611
  TweenDriver,
2531
2612
  VectoJSEvent,
2532
2613
  WebGPUParticleSystemManager,
2614
+ clearCssLineBoxMetrics,
2533
2615
  computeLineSegments,
2534
2616
  createCanvasMeasurer,
2535
2617
  createWebGLPointRenderer,
2618
+ cssLineBoxBaseline,
2536
2619
  isSafeUrl,
2537
2620
  isTweenConfig,
2538
2621
  loadSpline,
package/dist/layout.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkXLZDOFSBjs = require('./chunk-XLZDOFSB.js');
6
+ var _chunkZBKKRYDHjs = require('./chunk-ZBKKRYDH.js');
7
7
 
8
8
 
9
9
  var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
@@ -13,4 +13,4 @@ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
13
13
 
14
14
 
15
15
 
16
- exports.LayoutEngine = _chunkXLZDOFSBjs.LayoutEngine; exports.LayoutResultBuffer = _chunkXLZDOFSBjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkCTZQOM5Zjs.LayoutWorkerManager; exports.computeLineSegments = _chunkXLZDOFSBjs.computeLineSegments; exports.createCanvasMeasurer = _chunkXLZDOFSBjs.createCanvasMeasurer;
16
+ exports.LayoutEngine = _chunkZBKKRYDHjs.LayoutEngine; exports.LayoutResultBuffer = _chunkZBKKRYDHjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkCTZQOM5Zjs.LayoutWorkerManager; exports.computeLineSegments = _chunkZBKKRYDHjs.computeLineSegments; exports.createCanvasMeasurer = _chunkZBKKRYDHjs.createCanvasMeasurer;
package/dist/layout.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  LayoutResultBuffer,
4
4
  computeLineSegments,
5
5
  createCanvasMeasurer
6
- } from "./chunk-FNXOS7IA.mjs";
6
+ } from "./chunk-V7GUWG5C.mjs";
7
7
  import {
8
8
  LayoutWorkerManager
9
9
  } from "./chunk-STWPTWO4.mjs";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Return the baseline offset inside a CSS line box for a canvas-compatible
3
+ * font. Canvas text and a native editor must use this identical value whenever
4
+ * one mirrors the other; CSS otherwise centers font metrics in the line box.
5
+ *
6
+ * The 0.8 fallback preserves the framework's deterministic, DOM-free text
7
+ * contract in SSR and test environments where Canvas 2D is unavailable.
8
+ */
9
+ export declare function cssLineBoxBaseline(font: string, lineHeight: number): number;
10
+ /** Clear cached browser font metrics after a webfont finishes loading. */
11
+ export declare function clearCssLineBoxMetrics(): void;
@@ -3,3 +3,4 @@ export * from './BidiResolver';
3
3
  export * from './MSDFFont';
4
4
  export * from './MSDFTextEntity';
5
5
  export * from './SVGEntity';
6
+ export * from './Typography';
package/dist/text.js CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkNHNOKBD4js = require('./chunk-NHNOKBD4.js');
5
+
6
+
7
+ var _chunkDFNK6OV6js = require('./chunk-DFNK6OV6.js');
6
8
 
7
9
 
8
10
 
@@ -13,4 +15,6 @@ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
13
15
 
14
16
 
15
17
 
16
- exports.ArabicShaper = _chunkCTZQOM5Zjs.ArabicShaper; exports.BidiResolver = _chunkCTZQOM5Zjs.BidiResolver; exports.MSDFFont = _chunkNHNOKBD4js.MSDFFont; exports.MSDFTextEntity = _chunkNHNOKBD4js.MSDFTextEntity; exports.SVGEntity = _chunkNHNOKBD4js.SVGEntity;
18
+
19
+
20
+ exports.ArabicShaper = _chunkCTZQOM5Zjs.ArabicShaper; exports.BidiResolver = _chunkCTZQOM5Zjs.BidiResolver; exports.MSDFFont = _chunkDFNK6OV6js.MSDFFont; exports.MSDFTextEntity = _chunkDFNK6OV6js.MSDFTextEntity; exports.SVGEntity = _chunkDFNK6OV6js.SVGEntity; exports.clearCssLineBoxMetrics = _chunkDFNK6OV6js.clearCssLineBoxMetrics; exports.cssLineBoxBaseline = _chunkDFNK6OV6js.cssLineBoxBaseline;
package/dist/text.mjs CHANGED
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  MSDFFont,
3
3
  MSDFTextEntity,
4
- SVGEntity
5
- } from "./chunk-N7DNUDVY.mjs";
4
+ SVGEntity,
5
+ clearCssLineBoxMetrics,
6
+ cssLineBoxBaseline
7
+ } from "./chunk-3CODWSF3.mjs";
6
8
  import {
7
9
  ArabicShaper,
8
10
  BidiResolver
@@ -12,5 +14,7 @@ export {
12
14
  BidiResolver,
13
15
  MSDFFont,
14
16
  MSDFTextEntity,
15
- SVGEntity
17
+ SVGEntity,
18
+ clearCssLineBoxMetrics,
19
+ cssLineBoxBaseline
16
20
  };
@@ -59,6 +59,27 @@ export interface BatchRect {
59
59
  * find-in-page, screen readers, SEO crawlers, translation, `#:~:text=`
60
60
  * fragments — operates on canvas-rendered text.
61
61
  */
62
+ export interface ContentProjectionRun {
63
+ /** Text written with one CSS font inside a projected visual line. */
64
+ text: string;
65
+ /** CSS font shorthand matching the canvas run. */
66
+ font?: string;
67
+ }
68
+ export interface ContentProjectionLine {
69
+ /** Text for browser find-in-page and native selection. */
70
+ text: string;
71
+ /** Local origin of the visual line inside the entity. */
72
+ x: number;
73
+ y: number;
74
+ /** Canvas baseline relative to `y`. */
75
+ baseline: number;
76
+ /** CSS font used for the line when it has no styled runs. */
77
+ font?: string;
78
+ /** Explicit line height for this line. */
79
+ lineHeight?: number;
80
+ /** Styled text runs in visual order. */
81
+ runs?: ContentProjectionRun[];
82
+ }
62
83
  export interface ContentProjection {
63
84
  /** The plain text content as rendered (line breaks as `\n`). */
64
85
  text: string;
@@ -71,6 +92,27 @@ export interface ContentProjection {
71
92
  * projection never intercepts pointer input meant for the canvas.
72
93
  */
73
94
  selectable?: boolean;
95
+ /** Local x-origin of the rendered text inside the owning entity. */
96
+ contentX?: number;
97
+ /** Local y-origin of the rendered text inside the owning entity. */
98
+ contentY?: number;
99
+ /**
100
+ * Canvas baseline relative to `contentY` for the first line. When supplied,
101
+ * Scene aligns the DOM line box baseline instead of assuming both engines
102
+ * interpret the entity top as a text baseline.
103
+ */
104
+ baseline?: number;
105
+ /** Explicit visual lines for mixed-style or internally inset text. */
106
+ lines?: ContentProjectionLine[];
107
+ }
108
+ /** Typography for a native input projected by the accessibility layer. */
109
+ export interface TextInputStyle {
110
+ /** CSS font shorthand shared with the canvas mirror. */
111
+ font: string;
112
+ /** Explicit line advance in CSS pixels. */
113
+ lineHeight: number;
114
+ /** Inner text inset in CSS pixels. */
115
+ padding: number;
74
116
  }
75
117
  /**
76
118
  * Semantic attributes an {@link Entity} can project into the accessibility /
@@ -114,6 +156,8 @@ export interface A11yAttributes {
114
156
  activedescendant?: string;
115
157
  valuemin?: string;
116
158
  valuemax?: string;
159
+ /** Explicit native editor typography; ignored for non-input elements. */
160
+ textInputStyle?: TextInputStyle;
117
161
  }
118
162
  /**
119
163
  * Union of all pointer/interaction events that can be emitted by an {@link Entity}.
@@ -330,6 +330,8 @@ export declare class Scene {
330
330
  * Gets the accessibility DOM element projected for the given entity ID.
331
331
  */
332
332
  getA11yElement(entityId: string): HTMLElement | undefined;
333
+ /** Gets the static-content DOM projection for an entity ID, when materialized. */
334
+ getContentElement(entityId: string): HTMLElement | undefined;
333
335
  /**
334
336
  * Gets the root entity of the scene.
335
337
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -54,7 +54,7 @@
54
54
  "scripts": {
55
55
  "build": "node scripts/build-worker.js && tsup && tsc -p tsconfig.build.json",
56
56
  "test": "vitest run",
57
- "test:e2e": "bun e2e/hidpi.e2e.ts"
57
+ "test:e2e": "bun e2e/hidpi.e2e.ts && bun e2e/text-projection.e2e.ts"
58
58
  },
59
59
  "dependencies": {},
60
60
  "devDependencies": {