@mocanvas/editor 4.14.0 → 4.15.1

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/CUSTOM_SHAPES.md CHANGED
@@ -212,6 +212,25 @@ push it out; coming back on screen then costs a style change, not a remount.
212
212
  `0` unmounts at the viewport edge. A util whose `canCull()` returns `false` is
213
213
  never unmounted and never hidden for being off screen.
214
214
 
215
+ **The zoom in your CSS.** `--mocanvas-zoom` and `--mocanvas-scale` are live on
216
+ the canvas container, but the overlay declares them as constants so that a zoom
217
+ does not restyle every node of every mounted shape — the single largest cost of
218
+ a zoom gesture on a board of DOM cards. A shape whose own CSS reads them says so:
219
+
220
+ ```ts
221
+ class CardUtil extends BaseBoxShapeUtil<CardShape> {
222
+ override usesCameraCssVars() {
223
+ return true // this card counter-scales its label
224
+ }
225
+ }
226
+ ```
227
+
228
+ That shape then gets the live values on its own wrapper, and its subtree is the
229
+ only one that restyles on a zoom. `options.overlayCameraCssVars: "inherit"`
230
+ turns the mechanism off altogether, for an app that would rather pay than name
231
+ its shapes. Everything outside the overlay — your own chrome, panels, menus —
232
+ reads the live values as before.
233
+
215
234
  ### `getIndicatorPath` — the selection outline
216
235
 
217
236
  ```ts
package/MIGRATION.md CHANGED
@@ -273,7 +273,7 @@ it. Two groups are worth knowing about:
273
273
 
274
274
  | tldraw token | What you get |
275
275
  | --- | --- |
276
- | `--tl-zoom`, `--tl-scale` | Real values. mocanvas now stamps `--mocanvas-zoom` and `--mocanvas-scale` on the canvas container and restamps them on zoom, so `calc()` rules that hold a constant on-screen size keep working. |
276
+ | `--tl-zoom`, `--tl-scale` | Real values. mocanvas stamps `--mocanvas-zoom` and `--mocanvas-scale` on the canvas container and restamps them on zoom, so `calc()` rules that hold a constant on-screen size keep working. Inside the DOM overlay they are constants unless the shape's util says `usesCameraCssVars()` — a tldraw shape whose CSS counter-scales with `--tl-scale` needs that line, or `options.overlayCameraCssVars: "inherit"`. |
277
277
  | `--tl-color-overlay`, `--tl-color-background-overlay`, `--tl-color-warn` | A static fallback. mocanvas has no equivalent, so these do not follow your theme — set them yourself if they matter. |
278
278
 
279
279
  Everything else maps onto a themed mocanvas variable and follows a theme swap
package/dist/index.d.ts CHANGED
@@ -3483,6 +3483,19 @@ declare abstract class ShapeUtil<T extends UnknownShape = UnknownShape, D extend
3483
3483
  getRenderStyle(_shape: T): StyleWords | null;
3484
3484
  /** Whether the shape should be drawn by the DOM overlay even if it has a render style (e.g. while editing). */
3485
3485
  needsOverlay(shape: T): boolean;
3486
+ /**
3487
+ * Whether this shape's own CSS reads `--mocanvas-zoom` / `--mocanvas-scale`.
3488
+ *
3489
+ * The overlay stops those variables inheriting into mounted shapes, because a
3490
+ * custom property that changes on every zoom frame restyles every node below
3491
+ * it — the cost of a zoom on a board of DOM cards. A shape that says `true`
3492
+ * here gets the live values on its own wrapper, and pays that restyle for
3493
+ * itself: the counter-scaled label, the hairline that must stay 1px.
3494
+ *
3495
+ * Default `false`. `options.overlayCameraCssVars: "inherit"` turns the whole
3496
+ * mechanism off for an app that would rather not name its shapes.
3497
+ */
3498
+ usesCameraCssVars(_shape: T): boolean;
3486
3499
  /**
3487
3500
  * Whether `component` should be rendered in the DOM overlay *in addition to*
3488
3501
  * the GPU geometry (e.g. a text label on a filled shape).
@@ -8331,6 +8344,23 @@ interface EditorConfig extends TldrawOptions {
8331
8344
  * this option did.
8332
8345
  */
8333
8346
  overlayRetainLimit: number;
8347
+ /**
8348
+ * Whether the DOM overlay lets `--mocanvas-zoom` / `--mocanvas-scale` inherit
8349
+ * into the shapes below it.
8350
+ *
8351
+ * `"cut"` (the default) has the overlay declare them as constants, so the
8352
+ * container's — rewritten on every frame of a zoom — stop there. A changed
8353
+ * inherited custom property restyles every descendant, and on a board of
8354
+ * mounted DOM cards that is every node of every shape: 41 ms of style
8355
+ * recalculation per zoom step over 1,000 live cards, against 0.1 ms with the
8356
+ * inheritance cut (`apps/bench`, `dom-overlay.mjs`).
8357
+ *
8358
+ * A shape that reads the live zoom in its own CSS says so with
8359
+ * `ShapeUtil.usesCameraCssVars()` and gets the real values on its wrapper.
8360
+ * `"inherit"` restores the old behaviour for an app whose shape CSS reads the
8361
+ * variables and that would rather pay than name the shapes.
8362
+ */
8363
+ overlayCameraCssVars: "cut" | "inherit";
8334
8364
  }
8335
8365
  declare const DEFAULT_EDITOR_CONFIG: EditorConfig;
8336
8366
  interface HitTestOptions {
package/dist/index.js CHANGED
@@ -7853,7 +7853,8 @@ var DEFAULT_EDITOR_CONFIG = {
7853
7853
  // SEMANTICS-ASSUMED: a few screens' worth of cards on a dense board. Measured
7854
7854
  // in apps/bench (`dom-overlay.mjs`): a pan or zoom there and back stops
7855
7855
  // remounting at this size; the cost is the DOM of this many off-screen shapes.
7856
- overlayRetainLimit: 256
7856
+ overlayRetainLimit: 256,
7857
+ overlayCameraCssVars: "cut"
7857
7858
  };
7858
7859
  var RAD_PER_DEG = Math.PI / 180;
7859
7860
  var WHEEL_ZOOM_DELTA_CAP = 50;
@@ -13911,6 +13912,21 @@ var ShapeUtil = class {
13911
13912
  needsOverlay(shape) {
13912
13913
  return this.editor.getEditingShapeId() === shape.id;
13913
13914
  }
13915
+ /**
13916
+ * Whether this shape's own CSS reads `--mocanvas-zoom` / `--mocanvas-scale`.
13917
+ *
13918
+ * The overlay stops those variables inheriting into mounted shapes, because a
13919
+ * custom property that changes on every zoom frame restyles every node below
13920
+ * it — the cost of a zoom on a board of DOM cards. A shape that says `true`
13921
+ * here gets the live values on its own wrapper, and pays that restyle for
13922
+ * itself: the counter-scaled label, the hairline that must stay 1px.
13923
+ *
13924
+ * Default `false`. `options.overlayCameraCssVars: "inherit"` turns the whole
13925
+ * mechanism off for an app that would rather not name its shapes.
13926
+ */
13927
+ usesCameraCssVars(_shape) {
13928
+ return false;
13929
+ }
13914
13930
  /**
13915
13931
  * Whether `component` should be rendered in the DOM overlay *in addition to*
13916
13932
  * the GPU geometry (e.g. a text label on a filled shape).
@@ -15374,6 +15390,24 @@ function useCameraCssVarsOnElement(editor, ref, style) {
15374
15390
  });
15375
15391
  }, [editor, ref, ownZoom, ownScale]);
15376
15392
  }
15393
+ function useCameraCssVarsOnShape(editor, enabled) {
15394
+ const ref = useRef(null);
15395
+ useLayoutEffect(() => {
15396
+ const el = ref.current;
15397
+ if (!el) return;
15398
+ if (!enabled) {
15399
+ el.style.removeProperty("--mocanvas-zoom");
15400
+ el.style.removeProperty("--mocanvas-scale");
15401
+ return;
15402
+ }
15403
+ return react("canvas.shapeCameraCssVars", () => {
15404
+ const vars = getCameraCssVars(editor.getZoomLevel());
15405
+ el.style.setProperty("--mocanvas-zoom", vars["--mocanvas-zoom"]);
15406
+ el.style.setProperty("--mocanvas-scale", vars["--mocanvas-scale"]);
15407
+ });
15408
+ }, [editor, enabled]);
15409
+ return ref;
15410
+ }
15377
15411
  var GridLayer = track$1(function GridLayer2({ editor, Grid }) {
15378
15412
  const camera = editor.getCamera();
15379
15413
  const size = editor.getDocumentSettings().gridSize;
@@ -15393,7 +15427,8 @@ var OverlayLayer = track$1(function OverlayLayer2({ editor }) {
15393
15427
  el.style.transform = `scale(${cam.z}) translate(${cam.x}px, ${cam.y}px)`;
15394
15428
  });
15395
15429
  }, [editor]);
15396
- return /* @__PURE__ */ jsx("div", { ref, className: "mocanvas-overlay", style: overlayLayerStyle, children: ids.map((id, i) => {
15430
+ const cut = editor.options.overlayCameraCssVars !== "inherit";
15431
+ return /* @__PURE__ */ jsx("div", { ref, className: "mocanvas-overlay", style: cut ? overlayLayerStyleCut : overlayLayerStyle, children: ids.map((id, i) => {
15397
15432
  const shape = editor.getShape(id);
15398
15433
  if (!shape) return null;
15399
15434
  return /* @__PURE__ */ jsx(
@@ -15410,6 +15445,11 @@ var OverlayLayer = track$1(function OverlayLayer2({ editor }) {
15410
15445
  }) });
15411
15446
  });
15412
15447
  var overlayLayerStyle = { ...layerStyle, pointerEvents: "none", transformOrigin: "0 0" };
15448
+ var overlayLayerStyleCut = {
15449
+ ...overlayLayerStyle,
15450
+ ["--mocanvas-zoom"]: "1",
15451
+ ["--mocanvas-scale"]: "1"
15452
+ };
15413
15453
  var RETAINED_STYLE = { contentVisibility: "hidden", visibility: "hidden" };
15414
15454
  var OverlayShape = track$1(function OverlayShape2({
15415
15455
  editor,
@@ -15421,10 +15461,13 @@ var OverlayShape = track$1(function OverlayShape2({
15421
15461
  const util = editor.getShapeUtil(shape);
15422
15462
  const m = editor.getShapePageTransform(shape);
15423
15463
  const bounds = editor.getShapeGeometryBounds(shape);
15464
+ const asksForCamera = util.usesCameraCssVars?.(shape) === true;
15465
+ const cameraRef = useCameraCssVarsOnShape(editor, asksForCamera);
15424
15466
  const offset = clip ? `translate(${-clip[0]}px, ${-clip[1]}px) ` : "";
15425
15467
  const el = /* @__PURE__ */ jsx(
15426
15468
  "div",
15427
15469
  {
15470
+ ref: cameraRef,
15428
15471
  className: "mocanvas-shape",
15429
15472
  "data-shape-id": shape.id,
15430
15473
  "data-shape-type": shape.type,