@vectojs/core 1.5.0 → 1.6.1

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,11 @@ 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
+ Interactive projected nodes capture the active pointer on `pointerdown` and route
116
+ `pointermove`, `pointerup`, and `pointercancel` through normal VMT capture/bubble propagation.
117
+ Treat `pointercancel` as rollback: discard transient gesture state and do not create durable
118
+ history. Pointer capture is released safely on both completion paths.
119
+
115
120
  ## Static content projection
116
121
 
117
122
  Canvas-rendered text can opt into browser-native find, translation, selection, and copy without
@@ -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);
@@ -802,42 +805,56 @@ var Scene = (_class2 = class _Scene {
802
805
  el.style.background = "transparent";
803
806
  }
804
807
  el.addEventListener("click", (e) => {
805
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("click", node, e));
808
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("click", node, e));
806
809
  });
807
810
  el.addEventListener("mouseenter", (e) => {
808
811
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
809
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("hover", node, e, false));
812
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("hover", node, e, false));
810
813
  });
811
814
  el.addEventListener("mouseleave", (e) => {
812
815
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
813
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerleave", node, e, false));
816
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerleave", node, e, false));
814
817
  });
815
818
  const capEl = el;
819
+ const releasePointer = (event) => {
820
+ if (typeof capEl.releasePointerCapture !== "function") return;
821
+ if (typeof capEl.hasPointerCapture === "function" && !capEl.hasPointerCapture(event.pointerId)) {
822
+ return;
823
+ }
824
+ try {
825
+ capEl.releasePointerCapture(event.pointerId);
826
+ } catch (error) {
827
+ if (!(error instanceof DOMException) || error.name !== "NotFoundError") throw error;
828
+ }
829
+ };
816
830
  el.addEventListener("pointerdown", (e) => {
817
831
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
818
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerdown", node, e));
832
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerdown", node, e));
819
833
  });
820
834
  el.addEventListener("pointerup", (e) => {
821
- if (typeof capEl.releasePointerCapture === "function")
822
- capEl.releasePointerCapture(e.pointerId);
823
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerup", node, e));
835
+ releasePointer(e);
836
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerup", node, e));
837
+ });
838
+ el.addEventListener("pointercancel", (e) => {
839
+ releasePointer(e);
840
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointercancel", node, e));
824
841
  });
825
842
  el.addEventListener(
826
843
  "pointermove",
827
- (e) => node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointermove", node, e))
844
+ (e) => node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointermove", node, e))
828
845
  );
829
846
  el.addEventListener(
830
847
  "wheel",
831
848
  (e) => {
832
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("wheel", node, e));
849
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("wheel", node, e));
833
850
  },
834
851
  { passive: false }
835
852
  );
836
853
  el.addEventListener("keydown", (e) => {
837
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("keydown", node, e));
854
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("keydown", node, e));
838
855
  });
839
856
  el.addEventListener("keyup", (e) => {
840
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("keyup", node, e));
857
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("keyup", node, e));
841
858
  });
842
859
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
843
860
  const input = el;
@@ -910,7 +927,7 @@ var Scene = (_class2 = class _Scene {
910
927
  el.addEventListener("keydown", (e) => {
911
928
  if (e.key === "Enter" || e.key === " ") {
912
929
  e.preventDefault();
913
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("click", node, e));
930
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("click", node, e));
914
931
  }
915
932
  });
916
933
  }
@@ -993,6 +1010,16 @@ var Scene = (_class2 = class _Scene {
993
1010
  el.setAttribute("aria-valuenow", attrs.value);
994
1011
  }
995
1012
  }
1013
+ if (attrs.textInputStyle !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1014
+ const textStyle = attrs.textInputStyle;
1015
+ if (el.style.font !== textStyle.font) el.style.font = textStyle.font;
1016
+ const lineHeight = `${textStyle.lineHeight}px`;
1017
+ if (el.style.lineHeight !== lineHeight) el.style.lineHeight = lineHeight;
1018
+ const padding = `${textStyle.padding}px`;
1019
+ if (el.style.padding !== padding) el.style.padding = padding;
1020
+ if (el.style.boxSizing !== "border-box") el.style.boxSizing = "border-box";
1021
+ if (el instanceof HTMLTextAreaElement) el.style.resize = "none";
1022
+ }
996
1023
  if (node.a11yFullViewport) {
997
1024
  el.style.left = "0px";
998
1025
  el.style.top = "0px";
@@ -1048,7 +1075,7 @@ var Scene = (_class2 = class _Scene {
1048
1075
  el.addEventListener(
1049
1076
  "wheel",
1050
1077
  (e2) => {
1051
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("wheel", node, e2));
1078
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("wheel", node, e2));
1052
1079
  },
1053
1080
  { passive: false }
1054
1081
  );
@@ -1056,7 +1083,46 @@ var Scene = (_class2 = class _Scene {
1056
1083
  this.contentElements.set(node.id, el);
1057
1084
  this.a11yNeedsReorder = true;
1058
1085
  }
1059
- if (el.textContent !== projection.text) el.textContent = projection.text;
1086
+ const lines = projection.lines;
1087
+ if (lines && lines.length > 0) {
1088
+ const signature = JSON.stringify({
1089
+ lines,
1090
+ fallbackFont: _nullishCoalesce(projection.font, () => ( "")),
1091
+ fallbackLineHeight: _nullishCoalesce(projection.lineHeight, () => ( 16))
1092
+ });
1093
+ if (el.dataset.vectoProjectionLines !== signature) {
1094
+ el.replaceChildren();
1095
+ for (let index = 0; index < lines.length; index++) {
1096
+ const line = lines[index];
1097
+ const lineElement = document.createElement("span");
1098
+ const lineFont = _nullishCoalesce(_nullishCoalesce(line.font, () => ( projection.font)), () => ( ""));
1099
+ const lineHeight2 = _nullishCoalesce(_nullishCoalesce(line.lineHeight, () => ( projection.lineHeight)), () => ( 16));
1100
+ lineElement.style.position = "absolute";
1101
+ lineElement.style.left = `${line.x}px`;
1102
+ lineElement.style.top = `${line.y + line.baseline - _chunkDFNK6OV6js.cssLineBoxBaseline.call(void 0, lineFont, lineHeight2)}px`;
1103
+ lineElement.style.whiteSpace = "pre";
1104
+ if (lineFont) lineElement.style.font = lineFont;
1105
+ lineElement.style.lineHeight = `${lineHeight2}px`;
1106
+ if (line.runs && line.runs.length > 0) {
1107
+ for (const run of line.runs) {
1108
+ const runElement = document.createElement("span");
1109
+ runElement.textContent = run.text;
1110
+ if (run.font) runElement.style.font = run.font;
1111
+ runElement.style.lineHeight = `${lineHeight2}px`;
1112
+ lineElement.appendChild(runElement);
1113
+ }
1114
+ } else {
1115
+ lineElement.textContent = line.text;
1116
+ }
1117
+ el.appendChild(lineElement);
1118
+ if (index < lines.length - 1) el.appendChild(document.createTextNode("\n"));
1119
+ }
1120
+ el.dataset.vectoProjectionLines = signature;
1121
+ }
1122
+ } else {
1123
+ if (el.textContent !== projection.text) el.textContent = projection.text;
1124
+ delete el.dataset.vectoProjectionLines;
1125
+ }
1060
1126
  const font = _nullishCoalesce(projection.font, () => ( ""));
1061
1127
  if (el.style.font !== font) el.style.font = font;
1062
1128
  const lineHeight = projection.lineHeight !== void 0 ? `${projection.lineHeight}px` : "";
@@ -1074,8 +1140,12 @@ var Scene = (_class2 = class _Scene {
1074
1140
  el.style.cursor = selectable ? "text" : "";
1075
1141
  }
1076
1142
  const { a, b, c, d, e, f } = node.getWorldTransform();
1077
- el.style.left = `${e}px`;
1078
- el.style.top = `${f}px`;
1143
+ const contentX = _nullishCoalesce(projection.contentX, () => ( 0));
1144
+ const contentY = _nullishCoalesce(projection.contentY, () => ( 0));
1145
+ const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - _chunkDFNK6OV6js.cssLineBoxBaseline.call(void 0, font, _nullishCoalesce(projection.lineHeight, () => ( 16)));
1146
+ const localY = contentY + baselineOffset;
1147
+ el.style.left = `${e + a * contentX + c * localY}px`;
1148
+ el.style.top = `${f + b * contentX + d * localY}px`;
1079
1149
  if (node.width > 0) el.style.width = `${node.width}px`;
1080
1150
  if (node.height > 0) el.style.height = `${node.height}px`;
1081
1151
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
@@ -1833,10 +1903,10 @@ var Scene = (_class2 = class _Scene {
1833
1903
  // src/components/TextEntity.ts
1834
1904
  var sharedMeasurer;
1835
1905
  function defaultMeasurer() {
1836
- if (sharedMeasurer === void 0) sharedMeasurer = _chunkXLZDOFSBjs.createCanvasMeasurer.call(void 0, "sans-serif");
1906
+ if (sharedMeasurer === void 0) sharedMeasurer = _chunkZBKKRYDHjs.createCanvasMeasurer.call(void 0, "sans-serif");
1837
1907
  return sharedMeasurer;
1838
1908
  }
1839
- var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1909
+ var TextEntity = (_class3 = class extends _chunkDFNK6OV6js.Entity {
1840
1910
 
1841
1911
 
1842
1912
 
@@ -1853,7 +1923,7 @@ var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1853
1923
  this.text = text;
1854
1924
  this.atlas = atlas;
1855
1925
  this.fontSize = fontSize;
1856
- this.layout = new (0, _chunkXLZDOFSBjs.LayoutEngine)(maxWidth, 1e4, defaultMeasurer());
1926
+ this.layout = new (0, _chunkZBKKRYDHjs.LayoutEngine)(maxWidth, 1e4, defaultMeasurer());
1857
1927
  this.prepared = this.layout.prepare(this.text, this.atlas, this.fontSize);
1858
1928
  this.applyLayout();
1859
1929
  this.interactive = true;
@@ -1962,7 +2032,7 @@ var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1962
2032
  }, _class3);
1963
2033
 
1964
2034
  // src/components/GridTextEntity.ts
1965
- var GridTextEntity = (_class4 = class extends _chunkNHNOKBD4js.Entity {
2035
+ var GridTextEntity = (_class4 = class extends _chunkDFNK6OV6js.Entity {
1966
2036
 
1967
2037
  __init60() {this.fillStyle = "#ffffff"}
1968
2038
  __init61() {this.grid = []}
@@ -2056,7 +2126,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
2056
2126
  const ey = py - cy;
2057
2127
  return ex * ex + ey * ey;
2058
2128
  }
2059
- var SplineEntity = (_class5 = class extends _chunkNHNOKBD4js.Entity {
2129
+ var SplineEntity = (_class5 = class extends _chunkDFNK6OV6js.Entity {
2060
2130
 
2061
2131
 
2062
2132
 
@@ -2425,7 +2495,7 @@ var SpatialHashGrid = (_class6 = class {
2425
2495
  }, _class6);
2426
2496
 
2427
2497
  // src/tree/DOMPortalEntity.ts
2428
- var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2498
+ var DOMPortalEntity = (_class7 = class extends _chunkDFNK6OV6js.Entity {
2429
2499
 
2430
2500
  __init72() {this.isDOMPortal = true}
2431
2501
  __init73() {this.domListeners = []}
@@ -2457,10 +2527,17 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2457
2527
  });
2458
2528
  this.resizeObserver.observe(this.domElement);
2459
2529
  }
2460
- const events = ["click", "pointerdown", "pointerup", "pointermove", "wheel"];
2530
+ const events = [
2531
+ "click",
2532
+ "pointerdown",
2533
+ "pointerup",
2534
+ "pointercancel",
2535
+ "pointermove",
2536
+ "wheel"
2537
+ ];
2461
2538
  for (const type of events) {
2462
2539
  const handler = (e) => {
2463
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(type, this, e));
2540
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(type, this, e));
2464
2541
  };
2465
2542
  this.domElement.addEventListener(type, handler);
2466
2543
  this.domListeners.push({ type, handler, capture: false });
@@ -2471,7 +2548,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2471
2548
  ];
2472
2549
  for (const { native, vecto } of hoverEvents) {
2473
2550
  const handler = (e) => {
2474
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(vecto, this, e, false));
2551
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(vecto, this, e, false));
2475
2552
  };
2476
2553
  this.domElement.addEventListener(native, handler);
2477
2554
  this.domListeners.push({ type: native, handler, capture: false });
@@ -2479,7 +2556,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2479
2556
  const focusEvents = ["focus", "blur"];
2480
2557
  for (const type of focusEvents) {
2481
2558
  const handler = (e) => {
2482
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(type, this, e, true));
2559
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(type, this, e, true));
2483
2560
  };
2484
2561
  this.domElement.addEventListener(type, handler, true);
2485
2562
  this.domListeners.push({ type, handler, capture: true });
@@ -2564,4 +2641,6 @@ Scene.registerWebGPUParticleSystemManager(_chunkBPMNCGU7js.WebGPUParticleSystemM
2564
2641
 
2565
2642
 
2566
2643
 
2567
- 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;
2644
+
2645
+
2646
+ 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);
@@ -813,15 +816,29 @@ var Scene = class _Scene {
813
816
  node.dispatchEvent(new VectoJSEvent("pointerleave", node, e, false));
814
817
  });
815
818
  const capEl = el;
819
+ const releasePointer = (event) => {
820
+ if (typeof capEl.releasePointerCapture !== "function") return;
821
+ if (typeof capEl.hasPointerCapture === "function" && !capEl.hasPointerCapture(event.pointerId)) {
822
+ return;
823
+ }
824
+ try {
825
+ capEl.releasePointerCapture(event.pointerId);
826
+ } catch (error) {
827
+ if (!(error instanceof DOMException) || error.name !== "NotFoundError") throw error;
828
+ }
829
+ };
816
830
  el.addEventListener("pointerdown", (e) => {
817
831
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
818
832
  node.dispatchEvent(new VectoJSEvent("pointerdown", node, e));
819
833
  });
820
834
  el.addEventListener("pointerup", (e) => {
821
- if (typeof capEl.releasePointerCapture === "function")
822
- capEl.releasePointerCapture(e.pointerId);
835
+ releasePointer(e);
823
836
  node.dispatchEvent(new VectoJSEvent("pointerup", node, e));
824
837
  });
838
+ el.addEventListener("pointercancel", (e) => {
839
+ releasePointer(e);
840
+ node.dispatchEvent(new VectoJSEvent("pointercancel", node, e));
841
+ });
825
842
  el.addEventListener(
826
843
  "pointermove",
827
844
  (e) => node.dispatchEvent(new VectoJSEvent("pointermove", node, e))
@@ -993,6 +1010,16 @@ var Scene = class _Scene {
993
1010
  el.setAttribute("aria-valuenow", attrs.value);
994
1011
  }
995
1012
  }
1013
+ if (attrs.textInputStyle !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1014
+ const textStyle = attrs.textInputStyle;
1015
+ if (el.style.font !== textStyle.font) el.style.font = textStyle.font;
1016
+ const lineHeight = `${textStyle.lineHeight}px`;
1017
+ if (el.style.lineHeight !== lineHeight) el.style.lineHeight = lineHeight;
1018
+ const padding = `${textStyle.padding}px`;
1019
+ if (el.style.padding !== padding) el.style.padding = padding;
1020
+ if (el.style.boxSizing !== "border-box") el.style.boxSizing = "border-box";
1021
+ if (el instanceof HTMLTextAreaElement) el.style.resize = "none";
1022
+ }
996
1023
  if (node.a11yFullViewport) {
997
1024
  el.style.left = "0px";
998
1025
  el.style.top = "0px";
@@ -1056,7 +1083,46 @@ var Scene = class _Scene {
1056
1083
  this.contentElements.set(node.id, el);
1057
1084
  this.a11yNeedsReorder = true;
1058
1085
  }
1059
- if (el.textContent !== projection.text) el.textContent = projection.text;
1086
+ const lines = projection.lines;
1087
+ if (lines && lines.length > 0) {
1088
+ const signature = JSON.stringify({
1089
+ lines,
1090
+ fallbackFont: projection.font ?? "",
1091
+ fallbackLineHeight: projection.lineHeight ?? 16
1092
+ });
1093
+ if (el.dataset.vectoProjectionLines !== signature) {
1094
+ el.replaceChildren();
1095
+ for (let index = 0; index < lines.length; index++) {
1096
+ const line = lines[index];
1097
+ const lineElement = document.createElement("span");
1098
+ const lineFont = line.font ?? projection.font ?? "";
1099
+ const lineHeight2 = line.lineHeight ?? projection.lineHeight ?? 16;
1100
+ lineElement.style.position = "absolute";
1101
+ lineElement.style.left = `${line.x}px`;
1102
+ lineElement.style.top = `${line.y + line.baseline - cssLineBoxBaseline(lineFont, lineHeight2)}px`;
1103
+ lineElement.style.whiteSpace = "pre";
1104
+ if (lineFont) lineElement.style.font = lineFont;
1105
+ lineElement.style.lineHeight = `${lineHeight2}px`;
1106
+ if (line.runs && line.runs.length > 0) {
1107
+ for (const run of line.runs) {
1108
+ const runElement = document.createElement("span");
1109
+ runElement.textContent = run.text;
1110
+ if (run.font) runElement.style.font = run.font;
1111
+ runElement.style.lineHeight = `${lineHeight2}px`;
1112
+ lineElement.appendChild(runElement);
1113
+ }
1114
+ } else {
1115
+ lineElement.textContent = line.text;
1116
+ }
1117
+ el.appendChild(lineElement);
1118
+ if (index < lines.length - 1) el.appendChild(document.createTextNode("\n"));
1119
+ }
1120
+ el.dataset.vectoProjectionLines = signature;
1121
+ }
1122
+ } else {
1123
+ if (el.textContent !== projection.text) el.textContent = projection.text;
1124
+ delete el.dataset.vectoProjectionLines;
1125
+ }
1060
1126
  const font = projection.font ?? "";
1061
1127
  if (el.style.font !== font) el.style.font = font;
1062
1128
  const lineHeight = projection.lineHeight !== void 0 ? `${projection.lineHeight}px` : "";
@@ -1074,8 +1140,12 @@ var Scene = class _Scene {
1074
1140
  el.style.cursor = selectable ? "text" : "";
1075
1141
  }
1076
1142
  const { a, b, c, d, e, f } = node.getWorldTransform();
1077
- el.style.left = `${e}px`;
1078
- el.style.top = `${f}px`;
1143
+ const contentX = projection.contentX ?? 0;
1144
+ const contentY = projection.contentY ?? 0;
1145
+ const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - cssLineBoxBaseline(font, projection.lineHeight ?? 16);
1146
+ const localY = contentY + baselineOffset;
1147
+ el.style.left = `${e + a * contentX + c * localY}px`;
1148
+ el.style.top = `${f + b * contentX + d * localY}px`;
1079
1149
  if (node.width > 0) el.style.width = `${node.width}px`;
1080
1150
  if (node.height > 0) el.style.height = `${node.height}px`;
1081
1151
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
@@ -2457,7 +2527,14 @@ var DOMPortalEntity = class extends Entity {
2457
2527
  });
2458
2528
  this.resizeObserver.observe(this.domElement);
2459
2529
  }
2460
- const events = ["click", "pointerdown", "pointerup", "pointermove", "wheel"];
2530
+ const events = [
2531
+ "click",
2532
+ "pointerdown",
2533
+ "pointerup",
2534
+ "pointercancel",
2535
+ "pointermove",
2536
+ "wheel"
2537
+ ];
2461
2538
  for (const type of events) {
2462
2539
  const handler = (e) => {
2463
2540
  this.dispatchEvent(new VectoJSEvent(type, this, e));
@@ -2555,9 +2632,11 @@ export {
2555
2632
  TweenDriver,
2556
2633
  VectoJSEvent,
2557
2634
  WebGPUParticleSystemManager,
2635
+ clearCssLineBoxMetrics,
2558
2636
  computeLineSegments,
2559
2637
  createCanvasMeasurer,
2560
2638
  createWebGLPointRenderer,
2639
+ cssLineBoxBaseline,
2561
2640
  isSafeUrl,
2562
2641
  isTweenConfig,
2563
2642
  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,11 +156,13 @@ 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}.
120
164
  */
121
- export type VectoEvent = 'click' | 'hover' | 'pointerdown' | 'pointerup' | 'pointermove' | 'pointerleave' | 'change' | 'focus' | 'blur' | 'wheel' | 'keydown' | 'keyup';
165
+ export type VectoEvent = 'click' | 'hover' | 'pointerdown' | 'pointerup' | 'pointercancel' | 'pointermove' | 'pointerleave' | 'change' | 'focus' | 'blur' | 'wheel' | 'keydown' | 'keyup';
122
166
  /** Options for {@link Entity.on} / {@link Entity.off}. */
123
167
  export interface ListenerOptions {
124
168
  /** Register the listener for the capture phase (root→target) instead of bubble. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
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": {