@mocanvas/mocanvas 4.1.0 → 4.2.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/MIGRATION.md +36 -0
- package/dist/index.d.ts +23 -11
- package/dist/index.js +31 -6
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/MIGRATION.md
CHANGED
|
@@ -253,6 +253,40 @@ during the zero-rename step. Under pnpm's strict `node_modules` that means
|
|
|
253
253
|
adding `@mocanvas/mocanvas` to your own dependencies alongside
|
|
254
254
|
`@mocanvas/compat`.
|
|
255
255
|
|
|
256
|
+
**If your own CSS reads tldraw's custom properties, add a second line:**
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import "@mocanvas/compat/compat.css"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
This is the half of the compat layer that nothing checks for you. The package
|
|
263
|
+
alias covers the symbols TypeScript sees; it cannot cover the `--tl-*` names in
|
|
264
|
+
your stylesheets. Without it a migration goes green — build, types, tests — and
|
|
265
|
+
the damage shows up only to the eye, because a `var()` that resolves to nothing
|
|
266
|
+
does not fail, it deletes the declaration it sits in (and inside `calc()`, the
|
|
267
|
+
whole property). One consumer found it as a cursor whose white outline had
|
|
268
|
+
quietly stopped being drawn.
|
|
269
|
+
|
|
270
|
+
`compat.css` maps tldraw's tokens onto mocanvas's and gives **every** one a
|
|
271
|
+
literal fallback, so a token with no counterpart cannot take a rule down with
|
|
272
|
+
it. Two groups are worth knowing about:
|
|
273
|
+
|
|
274
|
+
| tldraw token | What you get |
|
|
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. |
|
|
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
|
+
|
|
279
|
+
Everything else maps onto a themed mocanvas variable and follows a theme swap
|
|
280
|
+
or a flip to dark. The stylesheet is scoped to `.mocanvas` rather than `:root`,
|
|
281
|
+
so a page that still renders a real tldraw editor somewhere keeps its own
|
|
282
|
+
values.
|
|
283
|
+
|
|
284
|
+
**Next.js**: an app coming from tldraw almost certainly lists it in
|
|
285
|
+
`serverExternalPackages`. Next matches that by package name, so leaving
|
|
286
|
+
`@mocanvas/mocanvas` in the list turns `…/mocanvas.css` into a request Node has
|
|
287
|
+
to resolve — and Node has no loader for `.css`. Take mocanvas out of the list
|
|
288
|
+
entirely; there is nothing in it that needs to be external.
|
|
289
|
+
|
|
256
290
|
### Step 2 — drop the prefixes
|
|
257
291
|
|
|
258
292
|
Once the app builds and runs against `@mocanvas/compat`, rename `TLFoo` →
|
|
@@ -663,6 +697,8 @@ aspirational.
|
|
|
663
697
|
| `ShapeUtil.toSvg`, `ShapeUtil.toBackgroundSvg` | Not `ShapeUtil` members. Custom shapes contribute to SVG export through `registerShapeSvgRenderer(type, renderer)`; without one they fall back to `geometryFallbackSvg`. |
|
|
664
698
|
| Sync protocol | Wire compatibility with tldraw's own sync protocol is not planned. `@mocanvas/sync` is a working transport of its own — presence records, `store.mergeRemoteChanges`, and a relay — but it does not speak tldraw's wire format. |
|
|
665
699
|
| `image` shape on the GPU | The texture path exists in the engine (`StyleWords.texture` + `uploadTexture`) but the `image` shape still draws an `<img>` in the DOM overlay. |
|
|
700
|
+
| `FrameShapeUtil` default size | mocanvas creates a frame at **160×90**; tldraw creates one at 320×180. Same aspect, half the size. It only bites code that creates a frame *programmatically* without passing `w`/`h` — drawing one with the tool sizes it from the drag either way. Pass explicit dimensions if the size matters to you. Aligning the default is a behaviour change and is not being made in a patch release. |
|
|
701
|
+
| An unregistered shape type in `store.put()` | Accepted, deliberately, and confirmed rather than fixed. A `.tldr` written by a build that knows a shape type this one does not must survive a load/save round trip instead of being dropped on the next save, so an unknown `type` passes its props through untouched. Its *other* fields are still validated — `x` must be a number whatever the type is — and a type you did register is validated in full. See `COMPAT.md`. |
|
|
666
702
|
| Text rendering | DOM overlay. Glyph-atlas text in WASM is phase 3. |
|
|
667
703
|
| GPU frame clipping | The `CLIP` flag and `isClipShape` hook are wired end to end, but no built-in shape enables it yet (phase 3). |
|
|
668
704
|
| WebGPU backend | Phase 3. WebGL2 is the only backend today, behind `RenderBackend`. |
|
package/dist/index.d.ts
CHANGED
|
@@ -445,12 +445,25 @@ declare class ZoomBrushOverlayUtil extends BaseBrushOverlayUtil<TLZoomBrushOverl
|
|
|
445
445
|
* the same person on every screen in the room.
|
|
446
446
|
*/
|
|
447
447
|
|
|
448
|
-
/**
|
|
448
|
+
/**
|
|
449
|
+
* Shared configuration for the collaborator painters.
|
|
450
|
+
*
|
|
451
|
+
* These exist so a subclass that redraws a cursor can read the label's
|
|
452
|
+
* measurements instead of hard-coding them next to a `super.render` it does not
|
|
453
|
+
* control. `zIndex` is not here because it is a static on the util itself —
|
|
454
|
+
* `class Mine extends CollaboratorCursorOverlayUtil { static override zIndex = 1100 }`.
|
|
455
|
+
*/
|
|
449
456
|
interface CollaboratorOverlayUtilOptions {
|
|
450
457
|
/** Opacity applied to everything drawn for an idle collaborator. */
|
|
451
458
|
idleOpacity: number;
|
|
459
|
+
/** Point size of the name and chat chips. */
|
|
460
|
+
fontSize: number;
|
|
461
|
+
/** Widest a name chip may draw before its text is clipped with an ellipsis. */
|
|
462
|
+
nameMaxWidth: number;
|
|
463
|
+
/** The same, for a chat message, which is usually allowed more room. */
|
|
464
|
+
chatMaxWidth: number;
|
|
452
465
|
}
|
|
453
|
-
/** The
|
|
466
|
+
/** The dials every collaborator overlay has. */
|
|
454
467
|
declare const DEFAULT_COLLABORATOR_OVERLAY_OPTIONS: CollaboratorOverlayUtilOptions;
|
|
455
468
|
/**
|
|
456
469
|
* The half every collaborator painter shares: who to draw, and how faded.
|
|
@@ -484,7 +497,7 @@ declare class CollaboratorCursorOverlayUtil extends CollaboratorOverlayUtil {
|
|
|
484
497
|
static options: CollaboratorOverlayUtilOptions;
|
|
485
498
|
isActive(): boolean;
|
|
486
499
|
getOverlays(): TLCollaboratorCursorOverlay[];
|
|
487
|
-
render(ctx: CanvasRenderingContext2D): void;
|
|
500
|
+
render(ctx: CanvasRenderingContext2D, given?: TLCollaboratorCursorOverlay[]): void;
|
|
488
501
|
}
|
|
489
502
|
/** Another person's selection brush, in their colour. */
|
|
490
503
|
declare class CollaboratorBrushOverlayUtil extends CollaboratorOverlayUtil {
|
|
@@ -878,14 +891,6 @@ declare function traceTaperedStroke(ctx: CanvasRenderingContext2D, points: reado
|
|
|
878
891
|
* go with the rest of the guide.
|
|
879
892
|
*/
|
|
880
893
|
declare function traceCross(ctx: CanvasRenderingContext2D, x: number, y: number, arm: number): void;
|
|
881
|
-
/**
|
|
882
|
-
* Draw a short text label on a filled, rounded chip — a collaborator's name,
|
|
883
|
-
* a zoom read-out.
|
|
884
|
-
*
|
|
885
|
-
* Returns the chip's width so a caller can lay several out in a row. Measuring
|
|
886
|
-
* costs a `measureText`, which is why the result is handed back rather than
|
|
887
|
-
* recomputed.
|
|
888
|
-
*/
|
|
889
894
|
declare function drawLabelChip(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, opts: {
|
|
890
895
|
background: string;
|
|
891
896
|
color: string;
|
|
@@ -893,6 +898,13 @@ declare function drawLabelChip(ctx: CanvasRenderingContext2D, text: string, x: n
|
|
|
893
898
|
paddingX: number;
|
|
894
899
|
paddingY: number;
|
|
895
900
|
radius: number;
|
|
901
|
+
/**
|
|
902
|
+
* Widest the whole chip may draw. Text that does not fit is cut and ends
|
|
903
|
+
* with an ellipsis. Omitted means no cap, which is what every caller did
|
|
904
|
+
* before — and is why one long collaborator name could draw a chip across
|
|
905
|
+
* the board it was labelling.
|
|
906
|
+
*/
|
|
907
|
+
maxWidth?: number;
|
|
896
908
|
}): number;
|
|
897
909
|
|
|
898
910
|
/** Side of the drawing grid. */
|
package/dist/index.js
CHANGED
|
@@ -393,10 +393,25 @@ function traceCross(ctx, x, y, arm) {
|
|
|
393
393
|
ctx.moveTo(x - arm, y + arm);
|
|
394
394
|
ctx.lineTo(x + arm, y - arm);
|
|
395
395
|
}
|
|
396
|
+
function truncateToWidth(ctx, text, maxWidth) {
|
|
397
|
+
if (maxWidth === void 0 || maxWidth <= 0) return text;
|
|
398
|
+
if (ctx.measureText(text).width <= maxWidth) return text;
|
|
399
|
+
const ellipsis = "\u2026";
|
|
400
|
+
if (ctx.measureText(ellipsis).width > maxWidth) return "";
|
|
401
|
+
let lo = 0;
|
|
402
|
+
let hi = text.length;
|
|
403
|
+
while (lo < hi) {
|
|
404
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
405
|
+
if (ctx.measureText(text.slice(0, mid) + ellipsis).width <= maxWidth) lo = mid;
|
|
406
|
+
else hi = mid - 1;
|
|
407
|
+
}
|
|
408
|
+
return text.slice(0, lo) + ellipsis;
|
|
409
|
+
}
|
|
396
410
|
function drawLabelChip(ctx, text, x, y, opts) {
|
|
397
411
|
ctx.font = opts.font;
|
|
398
412
|
ctx.textBaseline = "top";
|
|
399
413
|
ctx.textAlign = "left";
|
|
414
|
+
text = truncateToWidth(ctx, text, opts.maxWidth === void 0 ? void 0 : opts.maxWidth - opts.paddingX * 2);
|
|
400
415
|
const metrics = ctx.measureText(text);
|
|
401
416
|
const ascent = metrics.actualBoundingBoxAscent;
|
|
402
417
|
const descent = metrics.actualBoundingBoxDescent;
|
|
@@ -643,8 +658,14 @@ var ZoomBrushOverlayUtil = class extends BaseBrushOverlayUtil {
|
|
|
643
658
|
}
|
|
644
659
|
};
|
|
645
660
|
var DEFAULT_COLLABORATOR_OVERLAY_OPTIONS = {
|
|
646
|
-
idleOpacity: 0.5
|
|
661
|
+
idleOpacity: 0.5,
|
|
662
|
+
fontSize: 12,
|
|
663
|
+
nameMaxWidth: 120,
|
|
664
|
+
chatMaxWidth: 200
|
|
647
665
|
};
|
|
666
|
+
function chipFont(fontSize) {
|
|
667
|
+
return `${fontSize}px system-ui, -apple-system, "Segoe UI", sans-serif`;
|
|
668
|
+
}
|
|
648
669
|
var CollaboratorOverlayUtil = class extends OverlayUtil {
|
|
649
670
|
/** Everybody present and on this page. Never the local user. */
|
|
650
671
|
getPeople() {
|
|
@@ -686,11 +707,11 @@ var CollaboratorCursorOverlayUtil = class extends CollaboratorOverlayUtil {
|
|
|
686
707
|
}
|
|
687
708
|
return out;
|
|
688
709
|
}
|
|
689
|
-
render(ctx) {
|
|
690
|
-
const overlays = this.getOverlays();
|
|
710
|
+
render(ctx, given) {
|
|
711
|
+
const overlays = given ?? this.getOverlays();
|
|
691
712
|
if (overlays.length === 0) return;
|
|
692
713
|
const editor = this.editor;
|
|
693
|
-
const idleOpacity = this.options
|
|
714
|
+
const { idleOpacity, fontSize, nameMaxWidth, chatMaxWidth } = this.options;
|
|
694
715
|
isolate(ctx, (c) => {
|
|
695
716
|
for (const overlay of overlays) {
|
|
696
717
|
const at = editor.pageToViewport(overlay.point);
|
|
@@ -713,6 +734,7 @@ var CollaboratorCursorOverlayUtil = class extends CollaboratorOverlayUtil {
|
|
|
713
734
|
c.fill();
|
|
714
735
|
c.stroke();
|
|
715
736
|
c.restore();
|
|
737
|
+
const isChat = !!overlay.chatMessage;
|
|
716
738
|
const label = overlay.chatMessage || overlay.userName;
|
|
717
739
|
if (label) {
|
|
718
740
|
drawLabelChip(c, label, at.x + 12, at.y + 16, {
|
|
@@ -720,10 +742,13 @@ var CollaboratorCursorOverlayUtil = class extends CollaboratorOverlayUtil {
|
|
|
720
742
|
// White reads on all eight presence colours, which are chosen to be
|
|
721
743
|
// dark enough for exactly this.
|
|
722
744
|
color: "#ffffff",
|
|
723
|
-
font:
|
|
745
|
+
font: chipFont(fontSize),
|
|
724
746
|
paddingX: 6,
|
|
725
747
|
paddingY: 3,
|
|
726
|
-
radius: 4
|
|
748
|
+
radius: 4,
|
|
749
|
+
// Without a cap a long name drew a chip as wide as the name, which
|
|
750
|
+
// on a zoomed-out board covered the drawing it was labelling.
|
|
751
|
+
maxWidth: isChat ? chatMaxWidth : nameMaxWidth
|
|
727
752
|
});
|
|
728
753
|
}
|
|
729
754
|
}
|