@vectojs/core 1.5.0 → 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.
@@ -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,42 @@ 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;
816
819
  el.addEventListener("pointerdown", (e) => {
817
820
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
818
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerdown", node, e));
821
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerdown", node, e));
819
822
  });
820
823
  el.addEventListener("pointerup", (e) => {
821
824
  if (typeof capEl.releasePointerCapture === "function")
822
825
  capEl.releasePointerCapture(e.pointerId);
823
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointerup", node, e));
826
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointerup", node, e));
824
827
  });
825
828
  el.addEventListener(
826
829
  "pointermove",
827
- (e) => node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("pointermove", node, e))
830
+ (e) => node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("pointermove", node, e))
828
831
  );
829
832
  el.addEventListener(
830
833
  "wheel",
831
834
  (e) => {
832
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("wheel", node, e));
835
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("wheel", node, e));
833
836
  },
834
837
  { passive: false }
835
838
  );
836
839
  el.addEventListener("keydown", (e) => {
837
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("keydown", node, e));
840
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("keydown", node, e));
838
841
  });
839
842
  el.addEventListener("keyup", (e) => {
840
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("keyup", node, e));
843
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("keyup", node, e));
841
844
  });
842
845
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
843
846
  const input = el;
@@ -910,7 +913,7 @@ var Scene = (_class2 = class _Scene {
910
913
  el.addEventListener("keydown", (e) => {
911
914
  if (e.key === "Enter" || e.key === " ") {
912
915
  e.preventDefault();
913
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("click", node, e));
916
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("click", node, e));
914
917
  }
915
918
  });
916
919
  }
@@ -993,6 +996,16 @@ var Scene = (_class2 = class _Scene {
993
996
  el.setAttribute("aria-valuenow", attrs.value);
994
997
  }
995
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
+ }
996
1009
  if (node.a11yFullViewport) {
997
1010
  el.style.left = "0px";
998
1011
  el.style.top = "0px";
@@ -1048,7 +1061,7 @@ var Scene = (_class2 = class _Scene {
1048
1061
  el.addEventListener(
1049
1062
  "wheel",
1050
1063
  (e2) => {
1051
- node.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)("wheel", node, e2));
1064
+ node.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)("wheel", node, e2));
1052
1065
  },
1053
1066
  { passive: false }
1054
1067
  );
@@ -1056,7 +1069,46 @@ var Scene = (_class2 = class _Scene {
1056
1069
  this.contentElements.set(node.id, el);
1057
1070
  this.a11yNeedsReorder = true;
1058
1071
  }
1059
- if (el.textContent !== projection.text) el.textContent = projection.text;
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;
1111
+ }
1060
1112
  const font = _nullishCoalesce(projection.font, () => ( ""));
1061
1113
  if (el.style.font !== font) el.style.font = font;
1062
1114
  const lineHeight = projection.lineHeight !== void 0 ? `${projection.lineHeight}px` : "";
@@ -1074,8 +1126,12 @@ var Scene = (_class2 = class _Scene {
1074
1126
  el.style.cursor = selectable ? "text" : "";
1075
1127
  }
1076
1128
  const { a, b, c, d, e, f } = node.getWorldTransform();
1077
- el.style.left = `${e}px`;
1078
- 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`;
1079
1135
  if (node.width > 0) el.style.width = `${node.width}px`;
1080
1136
  if (node.height > 0) el.style.height = `${node.height}px`;
1081
1137
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
@@ -1833,10 +1889,10 @@ var Scene = (_class2 = class _Scene {
1833
1889
  // src/components/TextEntity.ts
1834
1890
  var sharedMeasurer;
1835
1891
  function defaultMeasurer() {
1836
- 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");
1837
1893
  return sharedMeasurer;
1838
1894
  }
1839
- var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1895
+ var TextEntity = (_class3 = class extends _chunkDFNK6OV6js.Entity {
1840
1896
 
1841
1897
 
1842
1898
 
@@ -1853,7 +1909,7 @@ var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1853
1909
  this.text = text;
1854
1910
  this.atlas = atlas;
1855
1911
  this.fontSize = fontSize;
1856
- this.layout = new (0, _chunkXLZDOFSBjs.LayoutEngine)(maxWidth, 1e4, defaultMeasurer());
1912
+ this.layout = new (0, _chunkZBKKRYDHjs.LayoutEngine)(maxWidth, 1e4, defaultMeasurer());
1857
1913
  this.prepared = this.layout.prepare(this.text, this.atlas, this.fontSize);
1858
1914
  this.applyLayout();
1859
1915
  this.interactive = true;
@@ -1962,7 +2018,7 @@ var TextEntity = (_class3 = class extends _chunkNHNOKBD4js.Entity {
1962
2018
  }, _class3);
1963
2019
 
1964
2020
  // src/components/GridTextEntity.ts
1965
- var GridTextEntity = (_class4 = class extends _chunkNHNOKBD4js.Entity {
2021
+ var GridTextEntity = (_class4 = class extends _chunkDFNK6OV6js.Entity {
1966
2022
 
1967
2023
  __init60() {this.fillStyle = "#ffffff"}
1968
2024
  __init61() {this.grid = []}
@@ -2056,7 +2112,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
2056
2112
  const ey = py - cy;
2057
2113
  return ex * ex + ey * ey;
2058
2114
  }
2059
- var SplineEntity = (_class5 = class extends _chunkNHNOKBD4js.Entity {
2115
+ var SplineEntity = (_class5 = class extends _chunkDFNK6OV6js.Entity {
2060
2116
 
2061
2117
 
2062
2118
 
@@ -2425,7 +2481,7 @@ var SpatialHashGrid = (_class6 = class {
2425
2481
  }, _class6);
2426
2482
 
2427
2483
  // src/tree/DOMPortalEntity.ts
2428
- var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2484
+ var DOMPortalEntity = (_class7 = class extends _chunkDFNK6OV6js.Entity {
2429
2485
 
2430
2486
  __init72() {this.isDOMPortal = true}
2431
2487
  __init73() {this.domListeners = []}
@@ -2460,7 +2516,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2460
2516
  const events = ["click", "pointerdown", "pointerup", "pointermove", "wheel"];
2461
2517
  for (const type of events) {
2462
2518
  const handler = (e) => {
2463
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(type, this, e));
2519
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(type, this, e));
2464
2520
  };
2465
2521
  this.domElement.addEventListener(type, handler);
2466
2522
  this.domListeners.push({ type, handler, capture: false });
@@ -2471,7 +2527,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2471
2527
  ];
2472
2528
  for (const { native, vecto } of hoverEvents) {
2473
2529
  const handler = (e) => {
2474
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(vecto, this, e, false));
2530
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(vecto, this, e, false));
2475
2531
  };
2476
2532
  this.domElement.addEventListener(native, handler);
2477
2533
  this.domListeners.push({ type: native, handler, capture: false });
@@ -2479,7 +2535,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkNHNOKBD4js.Entity {
2479
2535
  const focusEvents = ["focus", "blur"];
2480
2536
  for (const type of focusEvents) {
2481
2537
  const handler = (e) => {
2482
- this.dispatchEvent(new (0, _chunkNHNOKBD4js.VectoJSEvent)(type, this, e, true));
2538
+ this.dispatchEvent(new (0, _chunkDFNK6OV6js.VectoJSEvent)(type, this, e, true));
2483
2539
  };
2484
2540
  this.domElement.addEventListener(type, handler, true);
2485
2541
  this.domListeners.push({ type, handler, capture: true });
@@ -2564,4 +2620,6 @@ Scene.registerWebGPUParticleSystemManager(_chunkBPMNCGU7js.WebGPUParticleSystemM
2564
2620
 
2565
2621
 
2566
2622
 
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;
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);
@@ -993,6 +996,16 @@ var Scene = class _Scene {
993
996
  el.setAttribute("aria-valuenow", attrs.value);
994
997
  }
995
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
+ }
996
1009
  if (node.a11yFullViewport) {
997
1010
  el.style.left = "0px";
998
1011
  el.style.top = "0px";
@@ -1056,7 +1069,46 @@ var Scene = class _Scene {
1056
1069
  this.contentElements.set(node.id, el);
1057
1070
  this.a11yNeedsReorder = true;
1058
1071
  }
1059
- if (el.textContent !== projection.text) el.textContent = projection.text;
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;
1111
+ }
1060
1112
  const font = projection.font ?? "";
1061
1113
  if (el.style.font !== font) el.style.font = font;
1062
1114
  const lineHeight = projection.lineHeight !== void 0 ? `${projection.lineHeight}px` : "";
@@ -1074,8 +1126,12 @@ var Scene = class _Scene {
1074
1126
  el.style.cursor = selectable ? "text" : "";
1075
1127
  }
1076
1128
  const { a, b, c, d, e, f } = node.getWorldTransform();
1077
- el.style.left = `${e}px`;
1078
- 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`;
1079
1135
  if (node.width > 0) el.style.width = `${node.width}px`;
1080
1136
  if (node.height > 0) el.style.height = `${node.height}px`;
1081
1137
  el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
@@ -2555,9 +2611,11 @@ export {
2555
2611
  TweenDriver,
2556
2612
  VectoJSEvent,
2557
2613
  WebGPUParticleSystemManager,
2614
+ clearCssLineBoxMetrics,
2558
2615
  computeLineSegments,
2559
2616
  createCanvasMeasurer,
2560
2617
  createWebGLPointRenderer,
2618
+ cssLineBoxBaseline,
2561
2619
  isSafeUrl,
2562
2620
  isTweenConfig,
2563
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}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.5.0",
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": {