@vectojs/core 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  LayoutWorkerManager
3
- } from "./chunk-B3Z3JEJH.mjs";
3
+ } from "./chunk-STWPTWO4.mjs";
4
4
 
5
5
  // src/math/SpringPhysics.ts
6
+ var MAX_FRAME_DT = 0.25;
7
+ var MAX_STEP_DT = 1 / 120;
6
8
  var SpringPhysics = class {
7
9
  value;
8
10
  target;
@@ -22,11 +24,22 @@ var SpringPhysics = class {
22
24
  this.velocity = 0;
23
25
  return;
24
26
  }
25
- const forceSpring = -this.stiffness * (this.value - this.target);
26
- const forceDamping = -this.damping * this.velocity;
27
- const acceleration = (forceSpring + forceDamping) / this.mass;
28
- this.velocity += acceleration * dt;
29
- this.value += this.velocity * dt;
27
+ if (!(dt > 0)) return;
28
+ let remaining = dt < MAX_FRAME_DT ? dt : MAX_FRAME_DT;
29
+ while (remaining > 0) {
30
+ const step = remaining < MAX_STEP_DT ? remaining : MAX_STEP_DT;
31
+ const forceSpring = -this.stiffness * (this.value - this.target);
32
+ const forceDamping = -this.damping * this.velocity;
33
+ const acceleration = (forceSpring + forceDamping) / this.mass;
34
+ this.velocity += acceleration * step;
35
+ this.value += this.velocity * step;
36
+ remaining -= step;
37
+ if (this.isAtRest()) {
38
+ this.value = this.target;
39
+ this.velocity = 0;
40
+ return;
41
+ }
42
+ }
30
43
  }
31
44
  isAtRest() {
32
45
  return Math.abs(this.value - this.target) < this.valEpsilon && Math.abs(this.velocity) < this.velEpsilon;
@@ -117,6 +130,14 @@ var SpringDriver = class {
117
130
  };
118
131
 
119
132
  // src/tree/Entity.ts
133
+ var ANIMATABLE_PROPS = /* @__PURE__ */ new Set([
134
+ "x",
135
+ "y",
136
+ "scaleX",
137
+ "scaleY",
138
+ "rotation",
139
+ "opacity"
140
+ ]);
120
141
  var VectoJSEvent = class {
121
142
  /** The event name. */
122
143
  type;
@@ -568,7 +589,12 @@ var Entity = class {
568
589
  const end = anim.target[key];
569
590
  if (typeof start === "number" && typeof end === "number") {
570
591
  const easeOut = progress * (2 - progress);
571
- this[key] = start + (end - start) * easeOut;
592
+ const value = start + (end - start) * easeOut;
593
+ if (ANIMATABLE_PROPS.has(key)) {
594
+ this._applyAnimated(key, value);
595
+ } else {
596
+ this[key] = value;
597
+ }
572
598
  }
573
599
  }
574
600
  if (progress >= 1) {
@@ -619,6 +645,10 @@ var Entity = class {
619
645
  */
620
646
  destroy() {
621
647
  this.animations = [];
648
+ for (const driver of this._drivers.values()) {
649
+ this._settleDriver(driver);
650
+ }
651
+ this._drivers.clear();
622
652
  this.listeners.clear();
623
653
  this.captureListeners.clear();
624
654
  if (this.parent) {
@@ -862,6 +892,19 @@ var Entity = class {
862
892
  getBatchRect() {
863
893
  return null;
864
894
  }
895
+ /**
896
+ * Opt into DOM content projection for entities that render static text.
897
+ * The {@link Scene} mirrors the returned text as a transparent DOM node
898
+ * positioned over the drawn glyphs, making canvas text findable (Ctrl+F),
899
+ * readable by screen readers and crawlers, translatable, and — when
900
+ * `selectable` is set — natively selectable. Returns `null` by default.
901
+ * Read on the a11y sync cadence, so text changes propagate automatically.
902
+ *
903
+ * @returns The projection descriptor, or `null` to project nothing.
904
+ */
905
+ getContentProjection() {
906
+ return null;
907
+ }
865
908
  /**
866
909
  * Whether this entity still has a queued/running tween animation, or an
867
910
  * active {@link setTransition}/{@link animateTo}/{@link springTo} property
@@ -989,6 +1032,8 @@ var MSDFTextEntity = class extends Entity {
989
1032
  color;
990
1033
  letterSpacing;
991
1034
  lineHeight;
1035
+ maxWidth;
1036
+ maxHeight;
992
1037
  text = "";
993
1038
  lastRenderedSeqId = 0;
994
1039
  rgbColorCache = /* @__PURE__ */ new Map();
@@ -1003,17 +1048,27 @@ var MSDFTextEntity = class extends Entity {
1003
1048
  this.color = options.color ?? "#ffffff";
1004
1049
  this.letterSpacing = options.letterSpacing ?? 0;
1005
1050
  this.lineHeight = options.lineHeight;
1051
+ this.maxWidth = options.maxWidth ?? 1e3;
1052
+ this.maxHeight = options.maxHeight ?? 1e3;
1006
1053
  this.setText(text);
1007
1054
  }
1055
+ /** Change the wrap boundary and re-run layout for the current text. */
1056
+ setMaxWidth(maxWidth) {
1057
+ if (this.maxWidth === maxWidth) return;
1058
+ this.maxWidth = maxWidth;
1059
+ this.queueLayout();
1060
+ }
1008
1061
  setText(text) {
1009
1062
  if (this.text === text && this.layoutResult) return;
1010
1063
  this.text = text;
1064
+ this.queueLayout();
1065
+ }
1066
+ queueLayout() {
1011
1067
  LayoutWorkerManager.getInstance().queueLayout(this.id, this.text, {
1012
1068
  fontId: this.font.id,
1013
1069
  fontSize: this.fontSize,
1014
- maxWidth: 1e3,
1015
- // standard wrap boundary
1016
- maxHeight: 1e3,
1070
+ maxWidth: this.maxWidth,
1071
+ maxHeight: this.maxHeight,
1017
1072
  fontData: this.font.data,
1018
1073
  letterSpacing: this.letterSpacing,
1019
1074
  lineHeight: this.lineHeight,
@@ -1025,6 +1080,18 @@ var MSDFTextEntity = class extends Entity {
1025
1080
  }
1026
1081
  });
1027
1082
  }
1083
+ /**
1084
+ * Mirror the rendered text into the DOM content layer: find-in-page, screen
1085
+ * readers, crawlers, and translation see the same string the canvas draws.
1086
+ */
1087
+ getContentProjection() {
1088
+ if (!this.text) return null;
1089
+ return {
1090
+ text: this.text,
1091
+ font: `${this.fontSize}px ${this.fallbackFont}`,
1092
+ lineHeight: this.lineHeight
1093
+ };
1094
+ }
1028
1095
  isPointInside(globalX, globalY) {
1029
1096
  if (!this.layoutResult) return false;
1030
1097
  const local = this.worldToLocal(globalX, globalY);
@@ -1042,6 +1109,8 @@ var MSDFTextEntity = class extends Entity {
1042
1109
  if (scene && scene.pointRenderer && scene.glCanvas && canUsePointGlyphs) {
1043
1110
  scene.pointRenderer.setMSDFTexture(this.texture, this.font.distanceRange);
1044
1111
  const worldRot = Math.atan2(world.b, world.a);
1112
+ let worldOpacity = this.opacity;
1113
+ for (let p = this.parent; p; p = p.parent) worldOpacity *= p.opacity;
1045
1114
  const len2 = this.layoutResult.codePoints.length;
1046
1115
  for (let i = 0; i < len2; i++) {
1047
1116
  const code = this.layoutResult.codePoints[i];
@@ -1080,7 +1149,7 @@ var MSDFTextEntity = class extends Entity {
1080
1149
  ab.right / aw,
1081
1150
  v1,
1082
1151
  runColor,
1083
- this.opacity,
1152
+ worldOpacity,
1084
1153
  worldRot
1085
1154
  );
1086
1155
  }
@@ -96,6 +96,11 @@ export declare class SplineEntity extends Entity {
96
96
  private bounds;
97
97
  private offscreen;
98
98
  private baked;
99
+ /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
100
+ private bakedWidth;
101
+ private bakedHeight;
102
+ /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
103
+ private readonly containsGradient;
99
104
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
100
105
  private polylines;
101
106
  /**
@@ -1,4 +1,4 @@
1
- import { Entity } from '../tree/Entity';
1
+ import { Entity, type ContentProjection } from '../tree/Entity';
2
2
  import { IRenderer } from '../renderer/IRenderer';
3
3
  export declare class TextEntity extends Entity {
4
4
  text: string;
@@ -13,6 +13,11 @@ export declare class TextEntity extends Entity {
13
13
  lineWidth: number;
14
14
  private isHovered;
15
15
  constructor(text: string, atlas: any, maxWidth: number, fontSize?: number);
16
+ /**
17
+ * Mirror the rendered text into the DOM content layer: find-in-page, screen
18
+ * readers, crawlers, and translation see the same string the canvas draws.
19
+ */
20
+ getContentProjection(): ContentProjection | null;
16
21
  /**
17
22
  * Replace the text content. Runs the **cold** measurement pass (re-segment +
18
23
  * re-measure) since the glyphs changed, then re-lays out.