@mocanvas/editor 4.12.3 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPAT.md +4 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/COMPAT.md
CHANGED
|
@@ -96,6 +96,10 @@ predicates (`canTabTo`, `canResizeChildren`, `canBeLaidOut`, `canCull`,
|
|
|
96
96
|
`canEditInReadonly`, …) and `createShapeForAsset`. **That is where the remaining
|
|
97
97
|
work is**, not in the symbol tables above.
|
|
98
98
|
|
|
99
|
+
`canCull` is absent from the class but honoured where it matters: a util that
|
|
100
|
+
implements it and returns `false` keeps its shape in the DOM overlay while the
|
|
101
|
+
shape is off screen (4.12.4), and out of `getCulledShapes()`.
|
|
102
|
+
|
|
99
103
|
You can measure a real application against this surface rather than trusting the
|
|
100
104
|
tables: `scripts/compat-parity.mjs` type-checks a consumer app with `tldraw`
|
|
101
105
|
remapped onto `@mocanvas/compat` and reports what breaks, grouped by symbol.
|
package/dist/index.d.ts
CHANGED
|
@@ -8438,6 +8438,8 @@ declare class Editor extends EventEmitter<EditorEvents> {
|
|
|
8438
8438
|
private syncedPageId;
|
|
8439
8439
|
/** Shapes whose last write to the engine threw; they are warned about once. */
|
|
8440
8440
|
private readonly brokenShapeIds;
|
|
8441
|
+
/** Shapes on the synced page the engine reports to the DOM overlay (`FLAG.OVERLAY` or `FLAG.LABEL`). */
|
|
8442
|
+
private readonly overlayFlaggedIds;
|
|
8441
8443
|
/** Handle of the camera animation in flight, if any. */
|
|
8442
8444
|
private cameraAnimation;
|
|
8443
8445
|
/**
|
|
@@ -8614,6 +8616,18 @@ declare class Editor extends EventEmitter<EditorEvents> {
|
|
|
8614
8616
|
updateCurrentPageState(partial: Partial<Omit<InstancePageState, "id" | "typeName" | "pageId">>): this;
|
|
8615
8617
|
private readonly _allShapes;
|
|
8616
8618
|
private readonly _currentPageShapes;
|
|
8619
|
+
/**
|
|
8620
|
+
* The DOM-overlay shapes whose util opts out of culling (`canCull() === false`), each with the
|
|
8621
|
+
* clip its ancestors give it, plus the paint rank of every shape on the page; `null` when there
|
|
8622
|
+
* are none.
|
|
8623
|
+
*
|
|
8624
|
+
* The engine reports only the overlay shapes inside the viewport, so without this a shape that
|
|
8625
|
+
* must outlive the viewport — a live embed, a playing video — was unmounted the moment it was
|
|
8626
|
+
* panned or zoomed out of view, and rebuilt when it came back. Recomputed whenever a shape record
|
|
8627
|
+
* on the page changes — every frame of a drag, but never for a camera move — and never at all
|
|
8628
|
+
* when no registered util implements `canCull`.
|
|
8629
|
+
*/
|
|
8630
|
+
private readonly _keptOverlay;
|
|
8617
8631
|
private readonly _currentPageShapeIds;
|
|
8618
8632
|
/**
|
|
8619
8633
|
* One shape by id.
|
|
@@ -9066,6 +9080,14 @@ declare class Editor extends EventEmitter<EditorEvents> {
|
|
|
9066
9080
|
private writeEngineGeometry;
|
|
9067
9081
|
/** Build and draw one frame. Called by the canvas component inside rAF. */
|
|
9068
9082
|
renderFrame(backend: RenderBackend): FrameBuffers;
|
|
9083
|
+
/**
|
|
9084
|
+
* Add the overlay shapes that may not be culled to this frame's overlay list, off screen or not,
|
|
9085
|
+
* and put the whole list in paint order. The order must not depend on what is on screen: a shape
|
|
9086
|
+
* that changes place among its siblings is moved in the DOM by React, which restyles and lays out
|
|
9087
|
+
* all of it — the very cost keeping it mounted avoids. A kept shape always carries the clip its
|
|
9088
|
+
* ancestors give it, on screen or off, so its DOM never changes shape at the viewport edge.
|
|
9089
|
+
*/
|
|
9090
|
+
private keepUncullableOverlay;
|
|
9069
9091
|
/**
|
|
9070
9092
|
* Device-pixel scale at which rasterized textures (text labels) should be
|
|
9071
9093
|
* drawn: the device pixel ratio times the zoom, bucketed to powers of two.
|
package/dist/index.js
CHANGED
|
@@ -7956,6 +7956,8 @@ var Editor = class extends EventEmitter {
|
|
|
7956
7956
|
syncedPageId = null;
|
|
7957
7957
|
/** Shapes whose last write to the engine threw; they are warned about once. */
|
|
7958
7958
|
brokenShapeIds = /* @__PURE__ */ new Set();
|
|
7959
|
+
/** Shapes on the synced page the engine reports to the DOM overlay (`FLAG.OVERLAY` or `FLAG.LABEL`). */
|
|
7960
|
+
overlayFlaggedIds = /* @__PURE__ */ new Set();
|
|
7959
7961
|
/** Handle of the camera animation in flight, if any. */
|
|
7960
7962
|
cameraAnimation;
|
|
7961
7963
|
/**
|
|
@@ -8441,6 +8443,45 @@ var Editor = class extends EventEmitter {
|
|
|
8441
8443
|
const pageId = this.getCurrentPageId();
|
|
8442
8444
|
return this._allShapes.get().filter((s) => this.getAncestorPageId(s) === pageId);
|
|
8443
8445
|
});
|
|
8446
|
+
/**
|
|
8447
|
+
* The DOM-overlay shapes whose util opts out of culling (`canCull() === false`), each with the
|
|
8448
|
+
* clip its ancestors give it, plus the paint rank of every shape on the page; `null` when there
|
|
8449
|
+
* are none.
|
|
8450
|
+
*
|
|
8451
|
+
* The engine reports only the overlay shapes inside the viewport, so without this a shape that
|
|
8452
|
+
* must outlive the viewport — a live embed, a playing video — was unmounted the moment it was
|
|
8453
|
+
* panned or zoomed out of view, and rebuilt when it came back. Recomputed whenever a shape record
|
|
8454
|
+
* on the page changes — every frame of a drag, but never for a camera move — and never at all
|
|
8455
|
+
* when no registered util implements `canCull`.
|
|
8456
|
+
*/
|
|
8457
|
+
_keptOverlay = computed("editor.keptOverlay", () => {
|
|
8458
|
+
const optsOut = Object.values(this.shapeUtils).some(
|
|
8459
|
+
(util) => typeof util.canCull === "function"
|
|
8460
|
+
);
|
|
8461
|
+
if (!optsOut) return null;
|
|
8462
|
+
const sorted = this.getCurrentPageShapesSorted();
|
|
8463
|
+
const ids = [];
|
|
8464
|
+
const clips = /* @__PURE__ */ new Map();
|
|
8465
|
+
for (const shape of sorted) {
|
|
8466
|
+
if (!this.overlayFlaggedIds.has(shape.id)) continue;
|
|
8467
|
+
const util = this.shapeUtils[shape.type];
|
|
8468
|
+
if (!util?.canCull || util.canCull(shape)) continue;
|
|
8469
|
+
if (isShapeHidden(this, shape)) continue;
|
|
8470
|
+
const mask = this.getShapeMask(shape);
|
|
8471
|
+
let clip;
|
|
8472
|
+
if (mask !== void 0) {
|
|
8473
|
+
if (mask.length === 0) continue;
|
|
8474
|
+
const box = Box.FromPoints(mask.map((p) => new Vec(p.x, p.y)));
|
|
8475
|
+
const bounds = this.getShapePageBounds(shape);
|
|
8476
|
+
if (!bounds || !bounds.collides(box)) continue;
|
|
8477
|
+
clip = [box.minX, box.minY, box.maxX, box.maxY];
|
|
8478
|
+
}
|
|
8479
|
+
ids.push(shape.id);
|
|
8480
|
+
clips.set(shape.id, clip);
|
|
8481
|
+
}
|
|
8482
|
+
if (ids.length === 0) return null;
|
|
8483
|
+
return { ids, clips, rank: new Map(sorted.map((shape, index) => [shape.id, index])) };
|
|
8484
|
+
});
|
|
8444
8485
|
_currentPageShapeIds = computed("editor.currentPageShapeIds", () => {
|
|
8445
8486
|
return new Set(this._currentPageShapes.get().map((s) => s.id));
|
|
8446
8487
|
});
|
|
@@ -10142,6 +10183,7 @@ var Editor = class extends EventEmitter {
|
|
|
10142
10183
|
}
|
|
10143
10184
|
for (const rec of Object.values(changes.removed)) {
|
|
10144
10185
|
if (rec.typeName !== "shape") continue;
|
|
10186
|
+
this.overlayFlaggedIds.delete(rec.id);
|
|
10145
10187
|
const h = this.handles.release(rec.id);
|
|
10146
10188
|
if (h !== void 0) {
|
|
10147
10189
|
this.engine.cmd.remove(h);
|
|
@@ -10158,6 +10200,7 @@ var Editor = class extends EventEmitter {
|
|
|
10158
10200
|
if (next.typeName !== "shape" || prev.typeName !== "shape") continue;
|
|
10159
10201
|
const onPage = this.getAncestorPageId(next) === pageId;
|
|
10160
10202
|
if (!onPage) {
|
|
10203
|
+
this.overlayFlaggedIds.delete(next.id);
|
|
10161
10204
|
const h = this.handles.release(next.id);
|
|
10162
10205
|
if (h !== void 0) {
|
|
10163
10206
|
this.engine.cmd.remove(h);
|
|
@@ -10195,6 +10238,7 @@ var Editor = class extends EventEmitter {
|
|
|
10195
10238
|
syncPage(pageId) {
|
|
10196
10239
|
this.syncedPageId = pageId;
|
|
10197
10240
|
this.handles.clear();
|
|
10241
|
+
this.overlayFlaggedIds.clear();
|
|
10198
10242
|
this.engine.cmd.clear();
|
|
10199
10243
|
for (const shape of this.getCurrentPageShapesSorted()) {
|
|
10200
10244
|
this.writeShapeToEngine(shape, true);
|
|
@@ -10244,6 +10288,8 @@ var Editor = class extends EventEmitter {
|
|
|
10244
10288
|
} else {
|
|
10245
10289
|
flags |= FLAG.OVERLAY;
|
|
10246
10290
|
}
|
|
10291
|
+
if (flags & (FLAG.OVERLAY | FLAG.LABEL)) this.overlayFlaggedIds.add(shape.id);
|
|
10292
|
+
else this.overlayFlaggedIds.delete(shape.id);
|
|
10247
10293
|
const bw = engineGeometry?.w ?? geometry?.bounds.w ?? 0;
|
|
10248
10294
|
const bh = engineGeometry?.h ?? geometry?.bounds.h ?? 0;
|
|
10249
10295
|
this.engine.cmd.upsert(h, this.kindId(shape.type), parent, zlo, zhi, flags, shape.x, shape.y, shape.rotation, bw, bh);
|
|
@@ -10303,6 +10349,7 @@ var Editor = class extends EventEmitter {
|
|
|
10303
10349
|
clips.push(o.clip);
|
|
10304
10350
|
}
|
|
10305
10351
|
}
|
|
10352
|
+
unsafe__withoutCapture(() => this.keepUncullableOverlay(ids, clips));
|
|
10306
10353
|
unsafe__withoutCapture(() => {
|
|
10307
10354
|
const prev = this._overlayShapeIds.get();
|
|
10308
10355
|
if (prev.length !== ids.length || prev.some((id, i) => id !== ids[i])) this._overlayShapeIds.set(ids);
|
|
@@ -10314,6 +10361,28 @@ var Editor = class extends EventEmitter {
|
|
|
10314
10361
|
});
|
|
10315
10362
|
return frame;
|
|
10316
10363
|
}
|
|
10364
|
+
/**
|
|
10365
|
+
* Add the overlay shapes that may not be culled to this frame's overlay list, off screen or not,
|
|
10366
|
+
* and put the whole list in paint order. The order must not depend on what is on screen: a shape
|
|
10367
|
+
* that changes place among its siblings is moved in the DOM by React, which restyles and lays out
|
|
10368
|
+
* all of it — the very cost keeping it mounted avoids. A kept shape always carries the clip its
|
|
10369
|
+
* ancestors give it, on screen or off, so its DOM never changes shape at the viewport edge.
|
|
10370
|
+
*/
|
|
10371
|
+
keepUncullableOverlay(ids, clips) {
|
|
10372
|
+
const kept = this._keptOverlay.get();
|
|
10373
|
+
if (!kept) return;
|
|
10374
|
+
const entries = ids.map((id, i) => ({ id, clip: kept.clips.has(id) ? kept.clips.get(id) : clips[i] }));
|
|
10375
|
+
const present = new Set(ids);
|
|
10376
|
+
for (const id of kept.ids) if (!present.has(id)) entries.push({ id, clip: kept.clips.get(id) });
|
|
10377
|
+
const rank = (id) => kept.rank.get(id) ?? Number.MAX_SAFE_INTEGER;
|
|
10378
|
+
entries.sort((a, b) => rank(a.id) - rank(b.id));
|
|
10379
|
+
ids.length = 0;
|
|
10380
|
+
clips.length = 0;
|
|
10381
|
+
for (const entry of entries) {
|
|
10382
|
+
ids.push(entry.id);
|
|
10383
|
+
clips.push(entry.clip);
|
|
10384
|
+
}
|
|
10385
|
+
}
|
|
10317
10386
|
// ---- textures ----------------------------------------------------------
|
|
10318
10387
|
/**
|
|
10319
10388
|
* Device-pixel scale at which rasterized textures (text labels) should be
|