@mocanvas/editor 4.12.2 → 4.12.4
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 +84 -7
- 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
|
});
|
|
@@ -9130,7 +9171,9 @@ var Editor = class extends EventEmitter {
|
|
|
9130
9171
|
}
|
|
9131
9172
|
if (patches.length) this.updateShapes(patches);
|
|
9132
9173
|
});
|
|
9133
|
-
|
|
9174
|
+
const duplicates = ids.map((id) => idMap.get(id));
|
|
9175
|
+
this.setSelectedShapes(duplicates);
|
|
9176
|
+
return duplicates;
|
|
9134
9177
|
}
|
|
9135
9178
|
/** Serializable content for the clipboard: shapes (with descendants) and bindings between them. */
|
|
9136
9179
|
getContentFromCurrentPage(ids) {
|
|
@@ -10140,6 +10183,7 @@ var Editor = class extends EventEmitter {
|
|
|
10140
10183
|
}
|
|
10141
10184
|
for (const rec of Object.values(changes.removed)) {
|
|
10142
10185
|
if (rec.typeName !== "shape") continue;
|
|
10186
|
+
this.overlayFlaggedIds.delete(rec.id);
|
|
10143
10187
|
const h = this.handles.release(rec.id);
|
|
10144
10188
|
if (h !== void 0) {
|
|
10145
10189
|
this.engine.cmd.remove(h);
|
|
@@ -10156,6 +10200,7 @@ var Editor = class extends EventEmitter {
|
|
|
10156
10200
|
if (next.typeName !== "shape" || prev.typeName !== "shape") continue;
|
|
10157
10201
|
const onPage = this.getAncestorPageId(next) === pageId;
|
|
10158
10202
|
if (!onPage) {
|
|
10203
|
+
this.overlayFlaggedIds.delete(next.id);
|
|
10159
10204
|
const h = this.handles.release(next.id);
|
|
10160
10205
|
if (h !== void 0) {
|
|
10161
10206
|
this.engine.cmd.remove(h);
|
|
@@ -10193,6 +10238,7 @@ var Editor = class extends EventEmitter {
|
|
|
10193
10238
|
syncPage(pageId) {
|
|
10194
10239
|
this.syncedPageId = pageId;
|
|
10195
10240
|
this.handles.clear();
|
|
10241
|
+
this.overlayFlaggedIds.clear();
|
|
10196
10242
|
this.engine.cmd.clear();
|
|
10197
10243
|
for (const shape of this.getCurrentPageShapesSorted()) {
|
|
10198
10244
|
this.writeShapeToEngine(shape, true);
|
|
@@ -10242,6 +10288,8 @@ var Editor = class extends EventEmitter {
|
|
|
10242
10288
|
} else {
|
|
10243
10289
|
flags |= FLAG.OVERLAY;
|
|
10244
10290
|
}
|
|
10291
|
+
if (flags & (FLAG.OVERLAY | FLAG.LABEL)) this.overlayFlaggedIds.add(shape.id);
|
|
10292
|
+
else this.overlayFlaggedIds.delete(shape.id);
|
|
10245
10293
|
const bw = engineGeometry?.w ?? geometry?.bounds.w ?? 0;
|
|
10246
10294
|
const bh = engineGeometry?.h ?? geometry?.bounds.h ?? 0;
|
|
10247
10295
|
this.engine.cmd.upsert(h, this.kindId(shape.type), parent, zlo, zhi, flags, shape.x, shape.y, shape.rotation, bw, bh);
|
|
@@ -10301,6 +10349,7 @@ var Editor = class extends EventEmitter {
|
|
|
10301
10349
|
clips.push(o.clip);
|
|
10302
10350
|
}
|
|
10303
10351
|
}
|
|
10352
|
+
unsafe__withoutCapture(() => this.keepUncullableOverlay(ids, clips));
|
|
10304
10353
|
unsafe__withoutCapture(() => {
|
|
10305
10354
|
const prev = this._overlayShapeIds.get();
|
|
10306
10355
|
if (prev.length !== ids.length || prev.some((id, i) => id !== ids[i])) this._overlayShapeIds.set(ids);
|
|
@@ -10312,6 +10361,28 @@ var Editor = class extends EventEmitter {
|
|
|
10312
10361
|
});
|
|
10313
10362
|
return frame;
|
|
10314
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
|
+
}
|
|
10315
10386
|
// ---- textures ----------------------------------------------------------
|
|
10316
10387
|
/**
|
|
10317
10388
|
* Device-pixel scale at which rasterized textures (text labels) should be
|
|
@@ -14868,9 +14939,11 @@ function useCanvasEvents(editor) {
|
|
|
14868
14939
|
};
|
|
14869
14940
|
const isHandled = (e) => editor.isEventHandled(e);
|
|
14870
14941
|
const isInFrontOfCanvas = (target) => target instanceof Element && target.closest(IN_FRONT_OF_CANVAS_SELECTOR) !== null;
|
|
14942
|
+
const isElsewhere = (e) => e.currentTarget instanceof Node && e.target instanceof Node && !e.currentTarget.contains(e.target);
|
|
14943
|
+
const ownPointers = /* @__PURE__ */ new Set();
|
|
14871
14944
|
return {
|
|
14872
14945
|
onPointerDown(e) {
|
|
14873
|
-
if (isHandled(e) || isInFrontOfCanvas(e.target)) return;
|
|
14946
|
+
if (isHandled(e) || isInFrontOfCanvas(e.target) || isElsewhere(e)) return;
|
|
14874
14947
|
if (e.button === 2) {
|
|
14875
14948
|
editor.dispatch(pointerInfo(e, "right_click"));
|
|
14876
14949
|
return;
|
|
@@ -14878,6 +14951,7 @@ function useCanvasEvents(editor) {
|
|
|
14878
14951
|
if (e.button === 1) {
|
|
14879
14952
|
editor.dispatch(pointerInfo(e, "middle_click"));
|
|
14880
14953
|
}
|
|
14954
|
+
ownPointers.add(e.pointerId);
|
|
14881
14955
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
14882
14956
|
editor.getContainer().focus({ preventScroll: true });
|
|
14883
14957
|
const down = pointerInfo(e, "pointer_down");
|
|
@@ -14885,16 +14959,18 @@ function useCanvasEvents(editor) {
|
|
|
14885
14959
|
editor.dispatch(down);
|
|
14886
14960
|
},
|
|
14887
14961
|
onPointerMove(e) {
|
|
14888
|
-
if (isHandled(e) || isInFrontOfCanvas(e.target)) return;
|
|
14962
|
+
if (isHandled(e) || isInFrontOfCanvas(e.target) || isElsewhere(e)) return;
|
|
14889
14963
|
if (e.pointerType === "mouse" && e.buttons === 0 && editor.inputs.isPointing) {
|
|
14964
|
+
ownPointers.delete(e.pointerId);
|
|
14890
14965
|
editor.dispatch(pointerInfo(e, "pointer_up"));
|
|
14891
14966
|
return;
|
|
14892
14967
|
}
|
|
14893
14968
|
editor.dispatch(pointerInfo(e, "pointer_move"));
|
|
14894
14969
|
},
|
|
14895
14970
|
onPointerUp(e) {
|
|
14896
|
-
if (isHandled(e) || isInFrontOfCanvas(e.target)) return;
|
|
14971
|
+
if (isHandled(e) || isInFrontOfCanvas(e.target) || isElsewhere(e)) return;
|
|
14897
14972
|
if (e.button === 2) return;
|
|
14973
|
+
if (!ownPointers.delete(e.pointerId)) return;
|
|
14898
14974
|
const el = e.currentTarget;
|
|
14899
14975
|
if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);
|
|
14900
14976
|
const up = pointerInfo(e, "pointer_up");
|
|
@@ -14902,12 +14978,13 @@ function useCanvasEvents(editor) {
|
|
|
14902
14978
|
editor.click.handlePointerEvent(up);
|
|
14903
14979
|
},
|
|
14904
14980
|
onPointerCancel(e) {
|
|
14905
|
-
if (isHandled(e) || isInFrontOfCanvas(e.target)) return;
|
|
14981
|
+
if (isHandled(e) || isInFrontOfCanvas(e.target) || isElsewhere(e)) return;
|
|
14982
|
+
if (!ownPointers.delete(e.pointerId)) return;
|
|
14906
14983
|
editor.dispatch(pointerInfo(e, "pointer_up"));
|
|
14907
14984
|
editor.cancel();
|
|
14908
14985
|
},
|
|
14909
14986
|
onWheel(e) {
|
|
14910
|
-
if (isHandled(e) || isInFrontOfCanvas(e.target)) return;
|
|
14987
|
+
if (isHandled(e) || isInFrontOfCanvas(e.target) || isElsewhere(e)) return;
|
|
14911
14988
|
e.preventDefault();
|
|
14912
14989
|
const point = localPoint(editor, e);
|
|
14913
14990
|
const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 400 : 1;
|
|
@@ -14921,7 +14998,7 @@ function useCanvasEvents(editor) {
|
|
|
14921
14998
|
editor.dispatch(info);
|
|
14922
14999
|
},
|
|
14923
15000
|
onContextMenu(e) {
|
|
14924
|
-
if (isInFrontOfCanvas(e.target)) return;
|
|
15001
|
+
if (isInFrontOfCanvas(e.target) || isElsewhere(e)) return;
|
|
14925
15002
|
e.preventDefault();
|
|
14926
15003
|
},
|
|
14927
15004
|
onKeyDown(e) {
|