@vectojs/core 1.30.0 → 1.31.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.
@@ -1095,6 +1095,34 @@ var Entity = class {
1095
1095
  void hint;
1096
1096
  return null;
1097
1097
  }
1098
+ /**
1099
+ * A cheap, monotonically-increasing stamp of this entity's projected content.
1100
+ *
1101
+ * Purely an optimization, and opt-in: returning `null` (the default) means
1102
+ * "I cannot cheaply tell whether my content changed", and the {@link Scene}
1103
+ * then rebuilds the projection every synced frame exactly as before. An
1104
+ * implementation must bump the value whenever anything
1105
+ * {@link getContentProjection} would report changes — text, fonts, line
1106
+ * geometry, `selectable`, grid revision.
1107
+ *
1108
+ * When two consecutive syncs report the same epoch AND the entity's geometry
1109
+ * is unchanged, `Scene` skips the block *before* calling
1110
+ * {@link getContentProjection}. That matters because the projection call is
1111
+ * O(glyphs-in-block) and the DOM diff around it costs about the same again:
1112
+ * measured on a 1500-resident-block document, a sync in which the projected
1113
+ * text was byte-identical before and after still cost 17.875 ms, and skipping
1114
+ * unchanged blocks took that to 0.475 ms (carryctx CTX-0199, vectojs#343).
1115
+ *
1116
+ * Correctness is entirely on the implementer: a stale epoch means stale DOM,
1117
+ * so bump it in the same place the content is invalidated rather than trying
1118
+ * to enumerate mutation sites afterwards. Any monotonic counter works; the
1119
+ * value is only ever compared for equality with the previous sync's.
1120
+ *
1121
+ * @returns The current content epoch, or `null` to disable skipping.
1122
+ */
1123
+ getContentEpoch() {
1124
+ return null;
1125
+ }
1098
1126
  /**
1099
1127
  * Whether this entity still has a queued/running tween animation, or an
1100
1128
  * active {@link setTransition}/{@link animateTo}/{@link springTo} property
@@ -1138,6 +1166,8 @@ var MSDFTextEntity = class extends Entity {
1138
1166
  layoutText = "";
1139
1167
  text = "";
1140
1168
  lastRenderedSeqId = 0;
1169
+ /** Bumped by {@link queueLayout}; read by `Scene` to skip an unchanged sync. */
1170
+ contentEpoch = 0;
1141
1171
  // Atlas-decode subscription (see watchAtlasDecode). Held so `destroy()` can
1142
1172
  // release it: the handler closes over `this`, so leaving it attached to a
1143
1173
  // long-lived shared atlas image would retain the whole entity.
@@ -1257,6 +1287,7 @@ var MSDFTextEntity = class extends Entity {
1257
1287
  });
1258
1288
  }
1259
1289
  queueLayout() {
1290
+ this.contentEpoch++;
1260
1291
  LayoutWorkerManager.getInstance().queueLayout(this.id, this.layoutText, {
1261
1292
  fontId: this.font.id,
1262
1293
  fontSize: this.fontSize,
@@ -1286,6 +1317,9 @@ var MSDFTextEntity = class extends Entity {
1286
1317
  lineHeight: this.lineHeight
1287
1318
  };
1288
1319
  }
1320
+ getContentEpoch() {
1321
+ return this.contentEpoch;
1322
+ }
1289
1323
  isPointInside(globalX, globalY) {
1290
1324
  if (!this.layoutResult) return false;
1291
1325
  const local = this.worldToLocal(globalX, globalY);
@@ -1097,6 +1097,34 @@ var Entity = (_class2 = class {
1097
1097
  void hint;
1098
1098
  return null;
1099
1099
  }
1100
+ /**
1101
+ * A cheap, monotonically-increasing stamp of this entity's projected content.
1102
+ *
1103
+ * Purely an optimization, and opt-in: returning `null` (the default) means
1104
+ * "I cannot cheaply tell whether my content changed", and the {@link Scene}
1105
+ * then rebuilds the projection every synced frame exactly as before. An
1106
+ * implementation must bump the value whenever anything
1107
+ * {@link getContentProjection} would report changes — text, fonts, line
1108
+ * geometry, `selectable`, grid revision.
1109
+ *
1110
+ * When two consecutive syncs report the same epoch AND the entity's geometry
1111
+ * is unchanged, `Scene` skips the block *before* calling
1112
+ * {@link getContentProjection}. That matters because the projection call is
1113
+ * O(glyphs-in-block) and the DOM diff around it costs about the same again:
1114
+ * measured on a 1500-resident-block document, a sync in which the projected
1115
+ * text was byte-identical before and after still cost 17.875 ms, and skipping
1116
+ * unchanged blocks took that to 0.475 ms (carryctx CTX-0199, vectojs#343).
1117
+ *
1118
+ * Correctness is entirely on the implementer: a stale epoch means stale DOM,
1119
+ * so bump it in the same place the content is invalidated rather than trying
1120
+ * to enumerate mutation sites afterwards. Any monotonic counter works; the
1121
+ * value is only ever compared for equality with the previous sync's.
1122
+ *
1123
+ * @returns The current content epoch, or `null` to disable skipping.
1124
+ */
1125
+ getContentEpoch() {
1126
+ return null;
1127
+ }
1100
1128
  /**
1101
1129
  * Whether this entity still has a queued/running tween animation, or an
1102
1130
  * active {@link setTransition}/{@link animateTo}/{@link springTo} property
@@ -1140,16 +1168,18 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1140
1168
  __init41() {this.layoutText = ""}
1141
1169
  __init42() {this.text = ""}
1142
1170
  __init43() {this.lastRenderedSeqId = 0}
1171
+ /** Bumped by {@link queueLayout}; read by `Scene` to skip an unchanged sync. */
1172
+ __init44() {this.contentEpoch = 0}
1143
1173
  // Atlas-decode subscription (see watchAtlasDecode). Held so `destroy()` can
1144
1174
  // release it: the handler closes over `this`, so leaving it attached to a
1145
1175
  // long-lived shared atlas image would retain the whole entity.
1146
- __init44() {this.atlasDecodeTarget = null}
1147
- __init45() {this.atlasDecodeHandler = null}
1148
- __init46() {this.rgbColorCache = /* @__PURE__ */ new Map()}
1149
- __init47() {this.fontStringCache = []}
1150
- __init48() {this.layoutResult = null}
1176
+ __init45() {this.atlasDecodeTarget = null}
1177
+ __init46() {this.atlasDecodeHandler = null}
1178
+ __init47() {this.rgbColorCache = /* @__PURE__ */ new Map()}
1179
+ __init48() {this.fontStringCache = []}
1180
+ __init49() {this.layoutResult = null}
1151
1181
  constructor(text, options) {
1152
- super();_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);_class3.prototype.__init46.call(this);_class3.prototype.__init47.call(this);_class3.prototype.__init48.call(this);;
1182
+ super();_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);_class3.prototype.__init46.call(this);_class3.prototype.__init47.call(this);_class3.prototype.__init48.call(this);_class3.prototype.__init49.call(this);;
1153
1183
  this.font = options.font;
1154
1184
  this.texture = options.texture;
1155
1185
  this.fallbackFont = _nullishCoalesce(options.fallbackFont, () => ( "sans-serif"));
@@ -1259,6 +1289,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1259
1289
  });
1260
1290
  }
1261
1291
  queueLayout() {
1292
+ this.contentEpoch++;
1262
1293
  _layout.LayoutWorkerManager.getInstance().queueLayout(this.id, this.layoutText, {
1263
1294
  fontId: this.font.id,
1264
1295
  fontSize: this.fontSize,
@@ -1288,6 +1319,9 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1288
1319
  lineHeight: this.lineHeight
1289
1320
  };
1290
1321
  }
1322
+ getContentEpoch() {
1323
+ return this.contentEpoch;
1324
+ }
1291
1325
  isPointInside(globalX, globalY) {
1292
1326
  if (!this.layoutResult) return false;
1293
1327
  const local = this.worldToLocal(globalX, globalY);
@@ -1421,23 +1455,23 @@ var SVGEntity = (_class4 = class extends Entity {
1421
1455
  * rasterized. Set to `'transparent'` to opt out and keep the box empty.
1422
1456
  * Default `'rgba(248,113,113,0.9)'`.
1423
1457
  */
1424
- __init49() {this.fallbackStroke = "rgba(248,113,113,0.9)"}
1458
+ __init50() {this.fallbackStroke = "rgba(248,113,113,0.9)"}
1425
1459
  /** Fill behind the fallback marker. Default `'rgba(248,113,113,0.12)'`. */
1426
- __init50() {this.fallbackFill = "rgba(248,113,113,0.12)"}
1427
- __init51() {this.svgSource = ""}
1428
- __init52() {this.imageBitmap = null}
1429
- __init53() {this.imageElement = null}
1430
- __init54() {this.blobURL = null}
1431
- __init55() {this.currentImg = null}
1432
- __init56() {this.lodTimeout = null}
1433
- __init57() {this.rasterFailed = false}
1434
- __init58() {this.cachedDoc = null}
1435
- __init59() {this.baseWidth = 100}
1436
- __init60() {this.baseHeight = 100}
1437
- __init61() {this.lastRasterizedScale = 1}
1438
- __init62() {this.targetScale = 1}
1460
+ __init51() {this.fallbackFill = "rgba(248,113,113,0.12)"}
1461
+ __init52() {this.svgSource = ""}
1462
+ __init53() {this.imageBitmap = null}
1463
+ __init54() {this.imageElement = null}
1464
+ __init55() {this.blobURL = null}
1465
+ __init56() {this.currentImg = null}
1466
+ __init57() {this.lodTimeout = null}
1467
+ __init58() {this.rasterFailed = false}
1468
+ __init59() {this.cachedDoc = null}
1469
+ __init60() {this.baseWidth = 100}
1470
+ __init61() {this.baseHeight = 100}
1471
+ __init62() {this.lastRasterizedScale = 1}
1472
+ __init63() {this.targetScale = 1}
1439
1473
  constructor(svgSource, id) {
1440
- super(id);_class4.prototype.__init49.call(this);_class4.prototype.__init50.call(this);_class4.prototype.__init51.call(this);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);_class4.prototype.__init58.call(this);_class4.prototype.__init59.call(this);_class4.prototype.__init60.call(this);_class4.prototype.__init61.call(this);_class4.prototype.__init62.call(this);;
1474
+ super(id);_class4.prototype.__init50.call(this);_class4.prototype.__init51.call(this);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);_class4.prototype.__init58.call(this);_class4.prototype.__init59.call(this);_class4.prototype.__init60.call(this);_class4.prototype.__init61.call(this);_class4.prototype.__init62.call(this);_class4.prototype.__init63.call(this);;
1441
1475
  this.setSVGSource(svgSource);
1442
1476
  }
1443
1477
  setSVGSource(svgSource) {
@@ -12,12 +12,15 @@ export declare class TextEntity extends Entity {
12
12
  hoveredFillStyle: string | any;
13
13
  lineWidth: number;
14
14
  private isHovered;
15
+ /** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
16
+ private contentEpoch;
15
17
  constructor(text: string, atlas: any, maxWidth: number, fontSize?: number);
16
18
  /**
17
19
  * Mirror the rendered text into the DOM content layer: find-in-page, screen
18
20
  * readers, crawlers, and translation see the same string the canvas draws.
19
21
  */
20
22
  getContentProjection(): ContentProjection | null;
23
+ getContentEpoch(): number;
21
24
  /**
22
25
  * Replace the text content. Runs the **cold** measurement pass (re-segment +
23
26
  * re-measure) since the glyphs changed, then re-lays out.