{
  "version": 3,
  "sources": ["../../../src/lib/overlays/CollaboratorCursorOverlayUtil.ts"],
  "sourcesContent": ["import { OverlayUtil, PI2, TLOverlay } from '@tldraw/editor'\n\n/** @public */\nexport interface TLCollaboratorCursorOverlay extends TLOverlay {\n\tprops: {\n\t\tx: number\n\t\ty: number\n\t\tcolor: string\n\t\tname: string | null\n\t\tchatMessage: string\n\t}\n}\n\n// Lazy-initialized Path2D objects for the cursor arrow shape (deferred to avoid jsdom issues in tests).\n// Head and tail are separate Path2D objects per layer so each is filled in its own draw call,\n// mirroring the original SVG's two-<path> layout per group.\nlet _shadowHead: Path2D | null = null\nlet _shadowTail: Path2D | null = null\n\nlet _whiteHead: Path2D | null = null\nlet _whiteTail: Path2D | null = null\n\nlet _fillHead: Path2D | null = null\nlet _fillTail: Path2D | null = null\n\nfunction getCursorPaths() {\n\tif (!_shadowHead) {\n\t\t// Shadow layer (original SVG translate(-11,-11) pre-applied):\n\t\t_shadowHead = new Path2D('M 1 13.4219 V -2.593 L 12.591 9.026 H 5.81 L 5.399 9.15 Z')\n\t\t_shadowTail = new Path2D('M 10.0595 13.9498 L 6.3715 15.4978 L 2.4965 6.2803 L 6.1845 4.7323 Z')\n\t\t// White outline layer (original SVG translate(-12,-12) pre-applied):\n\t\t_whiteHead = new Path2D('M 0 12.4219 V -3.593 L 11.591 8.026 H 4.81 L 4.399 8.15 Z')\n\t\t_whiteTail = new Path2D('M 9.0595 12.9498 L 5.3715 14.4978 L 1.4965 5.2803 L 5.1845 3.7323 Z')\n\t\t// Colored fill layer (original SVG translate(-12,-12) pre-applied):\n\t\t_fillHead = new Path2D('M 1 -1.186 V 10.002 L 3.969 7.136 L 4.397 6.997 H 9.165 Z')\n\t\t_fillTail = new Path2D('M 7.751 12.4155 L 5.907 13.1895 L 2.807 5.8155 L 4.648 5.0405 Z')\n\t}\n\treturn {\n\t\tshadowHead: _shadowHead,\n\t\tshadowTail: _shadowTail!,\n\t\twhiteHead: _whiteHead!,\n\t\twhiteTail: _whiteTail!,\n\t\tfillHead: _fillHead!,\n\t\tfillTail: _fillTail!,\n\t}\n}\n\nconst TRUNCATE_CACHE_MAX = 200\nconst DEFAULT_LABEL_FONT_FAMILY = \"'tldraw_sans', sans-serif\"\n\nfunction getLabelFontFamily(editorContainer: HTMLElement, editorWindow: Window): string {\n\tconst fontFamily = editorWindow\n\t\t.getComputedStyle(editorContainer)\n\t\t.getPropertyValue('--tl-font-sans')\n\t\t.trim()\n\n\treturn fontFamily && !fontFamily.includes('var(') ? fontFamily : DEFAULT_LABEL_FONT_FAMILY\n}\n\n/**\n * Overlay util for collaborator cursors (arrow + name tag + chat message).\n *\n * @public\n */\nexport class CollaboratorCursorOverlayUtil extends OverlayUtil<TLCollaboratorCursorOverlay> {\n\tstatic override type = 'collaborator_cursor'\n\toverride options = { zIndex: 1100, fontSize: 12, nameMaxWidth: 120, chatMaxWidth: 200 }\n\n\t// Cache truncated text results to avoid repeated measureText loops.\n\t// Key format: `${maxWidth}|${text}` with an upper bound on cache size.\n\t// Per-editor so multiple <Tldraw /> instances on one page don't trample\n\t// each other's entries.\n\tprivate _truncateCache = new Map<string, string>()\n\n\toverride isActive(): boolean {\n\t\treturn this.editor.getVisibleCollaboratorsOnCurrentPage().some((presence) => !!presence.cursor)\n\t}\n\n\toverride getOverlays(): TLCollaboratorCursorOverlay[] {\n\t\tconst overlays: TLCollaboratorCursorOverlay[] = []\n\n\t\t// Visibility (activity state, following, highlighting) is handled by the\n\t\t// editor. The main-canvas viewport cull lives in `render` so off-screen\n\t\t// cursors still show on the minimap via `renderMinimap`.\n\t\tfor (const presence of this.editor.getVisibleCollaboratorsOnCurrentPage()) {\n\t\t\tconst { cursor, color, userName, chatMessage, userId } = presence\n\t\t\tif (!cursor) continue\n\n\t\t\toverlays.push({\n\t\t\t\tid: `collaborator_cursor:${userId}`,\n\t\t\t\ttype: 'collaborator_cursor',\n\t\t\t\tprops: {\n\t\t\t\t\tx: cursor.x,\n\t\t\t\t\ty: cursor.y,\n\t\t\t\t\tcolor,\n\t\t\t\t\tname: userName !== 'New User' ? userName : null,\n\t\t\t\t\tchatMessage: chatMessage ?? '',\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\treturn overlays\n\t}\n\n\toverride render(ctx: CanvasRenderingContext2D, overlays: TLCollaboratorCursorOverlay[]): void {\n\t\tconst zoom = this.editor.getZoomLevel()\n\t\tconst scale = 1 / zoom\n\t\tconst viewport = this.editor.getViewportPageBounds()\n\t\tconst viewportMarginX = 12 / zoom\n\t\tconst viewportMarginY = 16 / zoom\n\t\tconst labelFontFamily = getLabelFontFamily(\n\t\t\tthis.editor.getContainer(),\n\t\t\tthis.editor.getContainerWindow()\n\t\t)\n\t\tlet paths: ReturnType<typeof getCursorPaths> | null = null\n\n\t\tfor (const overlay of overlays) {\n\t\t\tconst { x, y, color, name, chatMessage } = overlay.props\n\n\t\t\t// Cull cursors outside the main viewport (with a small margin for\n\t\t\t// the cursor glyph). Off-screen cursors still show on the minimap\n\t\t\t// via `renderMinimap`.\n\t\t\tif (\n\t\t\t\tx < viewport.minX - viewportMarginX ||\n\t\t\t\ty < viewport.minY - viewportMarginY ||\n\t\t\t\tx > viewport.maxX - viewportMarginX ||\n\t\t\t\ty > viewport.maxY - viewportMarginY\n\t\t\t) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tctx.save()\n\t\t\tctx.translate(x, y)\n\t\t\tctx.scale(scale, scale)\n\n\t\t\t// Draw cursor arrow\n\t\t\tpaths ??= getCursorPaths()\n\n\t\t\t// Shadow\n\t\t\tctx.fillStyle = 'rgba(0,0,0,0.2)'\n\t\t\tctx.fill(paths.shadowHead)\n\t\t\tctx.fill(paths.shadowTail)\n\n\t\t\t// White outline\n\t\t\tctx.fillStyle = '#ffffff'\n\t\t\tctx.fill(paths.whiteHead)\n\t\t\tctx.fill(paths.whiteTail)\n\n\t\t\t// Colored fill\n\t\t\tctx.fillStyle = color\n\t\t\tctx.fill(paths.fillHead)\n\t\t\tctx.fill(paths.fillTail)\n\n\t\t\t// Draw name tag / chat\n\t\t\tif (chatMessage) {\n\t\t\t\tif (name) {\n\t\t\t\t\tthis._drawNameTitle(ctx, name, color, labelFontFamily)\n\t\t\t\t}\n\t\t\t\tthis._drawChatBubble(ctx, chatMessage, color, labelFontFamily)\n\t\t\t} else if (name) {\n\t\t\t\tthis._drawNameTag(ctx, name, color, labelFontFamily)\n\t\t\t}\n\n\t\t\tctx.restore()\n\t\t}\n\t}\n\n\t/** Name tag (no chat) - colored background with white text */\n\tprivate _drawNameTag(\n\t\tctx: CanvasRenderingContext2D,\n\t\tname: string,\n\t\tcolor: string,\n\t\tfontFamily: string\n\t) {\n\t\tconst { fontSize, nameMaxWidth } = this.options\n\t\tctx.font = `${fontSize}px ${fontFamily}`\n\t\tconst text = this._truncateText(ctx, name, nameMaxWidth)\n\t\tconst metrics = ctx.measureText(text)\n\t\tconst textWidth = Math.min(metrics.width, nameMaxWidth)\n\t\tconst px = 6\n\t\tconst py = 3\n\t\tconst x = 13\n\t\tconst y = 16\n\t\tconst h = fontSize + py * 2\n\t\tconst w = textWidth + px * 2\n\n\t\t// Background\n\t\tctx.fillStyle = color\n\t\tctx.beginPath()\n\t\tctx.roundRect(x, y, w, h, 4)\n\t\tctx.fill()\n\n\t\t// Text\n\t\tctx.fillStyle = '#ffffff'\n\t\tctx.textBaseline = 'top'\n\t\tctx.fillText(text, x + px, y + py)\n\t}\n\n\t/** Name title (when chat is present) - text with shadow, no background */\n\tprivate _drawNameTitle(\n\t\tctx: CanvasRenderingContext2D,\n\t\tname: string,\n\t\tcolor: string,\n\t\tfontFamily: string\n\t) {\n\t\tconst { fontSize, nameMaxWidth } = this.options\n\t\tctx.font = `${fontSize}px ${fontFamily}`\n\t\tconst text = this._truncateText(ctx, name, nameMaxWidth)\n\t\tconst x = 13\n\t\tconst y = -2\n\n\t\t// Text outline (simulate text-shadow)\n\t\tctx.strokeStyle = '#ffffff'\n\t\tctx.lineWidth = 3\n\t\tctx.lineJoin = 'round'\n\t\tctx.textBaseline = 'top'\n\t\tctx.strokeText(text, x, y)\n\n\t\t// Text fill\n\t\tctx.fillStyle = color\n\t\tctx.fillText(text, x, y)\n\t}\n\n\t/** Chat bubble - colored background with white text */\n\tprivate _drawChatBubble(\n\t\tctx: CanvasRenderingContext2D,\n\t\tchatMessage: string,\n\t\tcolor: string,\n\t\tfontFamily: string\n\t) {\n\t\tconst { fontSize, chatMaxWidth } = this.options\n\t\tctx.font = `${fontSize}px ${fontFamily}`\n\t\tconst text = this._truncateText(ctx, chatMessage, chatMaxWidth)\n\t\tconst metrics = ctx.measureText(text)\n\t\tconst textWidth = Math.min(metrics.width, chatMaxWidth)\n\t\tconst px = 6\n\t\tconst py = 3\n\t\tconst x = 13\n\t\tconst y = 16\n\t\tconst h = fontSize + py * 2\n\t\tconst w = textWidth + px * 2\n\n\t\t// Background\n\t\tctx.fillStyle = color\n\t\tctx.beginPath()\n\t\tctx.roundRect(x, y, w, h, 4)\n\t\tctx.fill()\n\n\t\t// Text\n\t\tctx.fillStyle = '#ffffff'\n\t\tctx.textBaseline = 'top'\n\t\tctx.fillText(text, x + px, y + py)\n\t}\n\n\toverride renderMinimap(\n\t\tctx: CanvasRenderingContext2D,\n\t\toverlays: TLCollaboratorCursorOverlay[],\n\t\tzoom: number\n\t): void {\n\t\t// Small filled dot per collaborator, clamped inside the minimap's\n\t\t// page bounds is left to the caller \u2014 we just render at the cursor\n\t\t// page-position and let the viewport rounded-rect indicate framing.\n\t\tconst radius = 3 / zoom\n\t\tfor (const overlay of overlays) {\n\t\t\tconst { x, y, color } = overlay.props\n\t\t\tctx.beginPath()\n\t\t\tctx.arc(x, y, radius, 0, PI2)\n\t\t\tctx.fillStyle = color\n\t\t\tctx.fill()\n\t\t}\n\t}\n\n\tprivate _truncateText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number): string {\n\t\tconst key = `${ctx.font}|${maxWidth}|${text}`\n\t\tconst cached = this._truncateCache.get(key)\n\t\tif (cached !== undefined) return cached\n\n\t\tif (ctx.measureText(text).width <= maxWidth) {\n\t\t\treturn this._setTruncatedTextCache(key, text)\n\t\t}\n\n\t\tconst ellipsis = '\u2026'\n\t\tlet low = 0\n\t\tlet high = text.length\n\t\twhile (low < high) {\n\t\t\tconst mid = Math.ceil((low + high) / 2)\n\t\t\tif (ctx.measureText(text.slice(0, mid) + ellipsis).width <= maxWidth) {\n\t\t\t\tlow = mid\n\t\t\t} else {\n\t\t\t\thigh = mid - 1\n\t\t\t}\n\t\t}\n\n\t\treturn this._setTruncatedTextCache(key, text.slice(0, low) + ellipsis)\n\t}\n\n\tprivate _setTruncatedTextCache(key: string, result: string): string {\n\t\tif (this._truncateCache.size >= TRUNCATE_CACHE_MAX) this._truncateCache.clear()\n\t\tthis._truncateCache.set(key, result)\n\t\treturn result\n\t}\n}\n"],
  "mappings": "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",
  "names": []
}
