@tldraw/editor 4.2.0-next.f100cedfc45b → 4.3.0-canary.d8da2a99f394
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/dist-cjs/index.d.ts +4 -2
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/editor/Editor.js +10 -6
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +4 -3
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +8 -4
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +4 -2
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/editor/Editor.mjs +10 -6
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +4 -3
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +8 -4
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/editor/Editor.test.ts +268 -0
- package/src/lib/editor/Editor.ts +10 -6
- package/src/lib/hooks/useCanvasEvents.ts +4 -3
- package/src/lib/license/Watermark.tsx +8 -5
- package/src/version.ts +3 -3
|
@@ -7,6 +7,7 @@ import { getPointerInfo } from "../utils/getPointerInfo.mjs";
|
|
|
7
7
|
import { useEditor } from "./useEditor.mjs";
|
|
8
8
|
function useCanvasEvents() {
|
|
9
9
|
const editor = useEditor();
|
|
10
|
+
const ownerDocument = editor.getContainer().ownerDocument;
|
|
10
11
|
const currentTool = useValue("current tool", () => editor.getCurrentTool(), [editor]);
|
|
11
12
|
const events = useMemo(
|
|
12
13
|
function canvasEvents() {
|
|
@@ -135,11 +136,11 @@ function useCanvasEvents() {
|
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
+
ownerDocument.body.addEventListener("pointermove", onPointerMove);
|
|
139
140
|
return () => {
|
|
140
|
-
|
|
141
|
+
ownerDocument.body.removeEventListener("pointermove", onPointerMove);
|
|
141
142
|
};
|
|
142
|
-
}, [editor, currentTool]);
|
|
143
|
+
}, [editor, currentTool, ownerDocument]);
|
|
143
144
|
return events;
|
|
144
145
|
}
|
|
145
146
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/hooks/useCanvasEvents.ts"],
|
|
4
|
-
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport React, { useEffect, useMemo } from 'react'\nimport { RIGHT_MOUSE_BUTTON } from '../constants'\nimport { tlenv } from '../globals/environment'\nimport { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom'\nimport { getPointerInfo } from '../utils/getPointerInfo'\nimport { useEditor } from './useEditor'\n\nexport function useCanvasEvents() {\n\tconst editor = useEditor()\n\tconst currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])\n\n\tconst events = useMemo(\n\t\tfunction canvasEvents() {\n\t\t\tfunction onPointerDown(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\n\t\t\t\tif (e.button === RIGHT_MOUSE_BUTTON) {\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\t\tname: 'right_click',\n\t\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 5) return\n\n\t\t\t\tsetPointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerUp(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return\n\n\t\t\t\treleasePointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_up',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerEnter(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })\n\t\t\t}\n\n\t\t\tfunction onPointerLeave(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })\n\t\t\t}\n\n\t\t\tfunction onTouchStart(e: React.TouchEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tfunction onTouchEnd(e: React.TouchEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\t// check that e.target is an HTMLElement\n\t\t\t\tif (!(e.target instanceof HTMLElement)) return\n\n\t\t\t\tconst editingShapeId = editor.getEditingShape()?.id\n\t\t\t\tif (\n\t\t\t\t\t// if the target is not inside the editing shape\n\t\t\t\t\t!(editingShapeId && e.target.closest(`[data-shape-id=\"${editingShapeId}\"]`)) &&\n\t\t\t\t\t// and the target is not an clickable element\n\t\t\t\t\te.target.tagName !== 'A' &&\n\t\t\t\t\t// or a TextArea.tsx ?\n\t\t\t\t\te.target.tagName !== 'TEXTAREA' &&\n\t\t\t\t\t!e.target.isContentEditable\n\t\t\t\t) {\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onDragOver(e: React.DragEvent<Element>) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tasync function onDrop(e: React.DragEvent<Element>) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tpreventDefault(e)\n\t\t\t\te.stopPropagation()\n\n\t\t\t\tif (e.dataTransfer?.files?.length) {\n\t\t\t\t\tconst files = Array.from(e.dataTransfer.files)\n\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'files',\n\t\t\t\t\t\tfiles,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst url = e.dataTransfer.getData('url')\n\t\t\t\tif (url) {\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'url',\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onClick(e: React.MouseEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\te.stopPropagation()\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tonPointerDown,\n\t\t\t\tonPointerUp,\n\t\t\t\tonPointerEnter,\n\t\t\t\tonPointerLeave,\n\t\t\t\tonDragOver,\n\t\t\t\tonDrop,\n\t\t\t\tonTouchStart,\n\t\t\t\tonTouchEnd,\n\t\t\t\tonClick,\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\t// onPointerMove is special: where we're only interested in the other events when they're\n\t// happening _on_ the canvas (as opposed to outside of it, or on UI floating over it), we want\n\t// the pointer position to be up to date regardless of whether it's over the tldraw canvas or\n\t// not. So instead of returning a listener to be attached to the canvas, we directly attach a\n\t// listener to the whole document instead.\n\tuseEffect(() => {\n\t\tlet lastX: number, lastY: number\n\n\t\tfunction onPointerMove(e: PointerEvent) {\n\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\teditor.markEventAsHandled(e)\n\n\t\t\tif (e.clientX === lastX && e.clientY === lastY) return\n\t\t\tlastX = e.clientX\n\t\t\tlastY = e.clientY\n\n\t\t\t// For tools that benefit from a higher fidelity of events,\n\t\t\t// we dispatch the coalesced events.\n\t\t\t// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.\n\t\t\t// Specifically, in local mode (non-https) mode, iOS does not `useCoalescedEvents`\n\t\t\t// so it appears like the ink is working locally, when really it's just that `useCoalescedEvents`\n\t\t\t// is disabled. The intent here is to have `useCoalescedEvents` disabled for iOS.\n\t\t\tconst events =\n\t\t\t\t!tlenv.isIos && currentTool.useCoalescedEvents && e.getCoalescedEvents\n\t\t\t\t\t? e.getCoalescedEvents()\n\t\t\t\t\t: [e]\n\n\t\t\tfor (const singleEvent of events) {\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t...getPointerInfo(editor, singleEvent),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport React, { useEffect, useMemo } from 'react'\nimport { RIGHT_MOUSE_BUTTON } from '../constants'\nimport { tlenv } from '../globals/environment'\nimport { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom'\nimport { getPointerInfo } from '../utils/getPointerInfo'\nimport { useEditor } from './useEditor'\n\nexport function useCanvasEvents() {\n\tconst editor = useEditor()\n\tconst ownerDocument = editor.getContainer().ownerDocument\n\tconst currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])\n\n\tconst events = useMemo(\n\t\tfunction canvasEvents() {\n\t\t\tfunction onPointerDown(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\n\t\t\t\tif (e.button === RIGHT_MOUSE_BUTTON) {\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\t\tname: 'right_click',\n\t\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 5) return\n\n\t\t\t\tsetPointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerUp(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tif (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return\n\n\t\t\t\treleasePointerCapture(e.currentTarget, e)\n\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_up',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerEnter(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })\n\t\t\t}\n\n\t\t\tfunction onPointerLeave(e: React.PointerEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tif (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return\n\t\t\t\tconst canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'\n\t\t\t\teditor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })\n\t\t\t}\n\n\t\t\tfunction onTouchStart(e: React.TouchEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tfunction onTouchEnd(e: React.TouchEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\t// check that e.target is an HTMLElement\n\t\t\t\tif (!(e.target instanceof HTMLElement)) return\n\n\t\t\t\tconst editingShapeId = editor.getEditingShape()?.id\n\t\t\t\tif (\n\t\t\t\t\t// if the target is not inside the editing shape\n\t\t\t\t\t!(editingShapeId && e.target.closest(`[data-shape-id=\"${editingShapeId}\"]`)) &&\n\t\t\t\t\t// and the target is not an clickable element\n\t\t\t\t\te.target.tagName !== 'A' &&\n\t\t\t\t\t// or a TextArea.tsx ?\n\t\t\t\t\te.target.tagName !== 'TEXTAREA' &&\n\t\t\t\t\t!e.target.isContentEditable\n\t\t\t\t) {\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onDragOver(e: React.DragEvent<Element>) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tasync function onDrop(e: React.DragEvent<Element>) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\tpreventDefault(e)\n\t\t\t\te.stopPropagation()\n\n\t\t\t\tif (e.dataTransfer?.files?.length) {\n\t\t\t\t\tconst files = Array.from(e.dataTransfer.files)\n\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'files',\n\t\t\t\t\t\tfiles,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst url = e.dataTransfer.getData('url')\n\t\t\t\tif (url) {\n\t\t\t\t\tawait editor.putExternalContent({\n\t\t\t\t\t\ttype: 'url',\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tpoint: editor.screenToPage({ x: e.clientX, y: e.clientY }),\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onClick(e: React.MouseEvent) {\n\t\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\t\te.stopPropagation()\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tonPointerDown,\n\t\t\t\tonPointerUp,\n\t\t\t\tonPointerEnter,\n\t\t\t\tonPointerLeave,\n\t\t\t\tonDragOver,\n\t\t\t\tonDrop,\n\t\t\t\tonTouchStart,\n\t\t\t\tonTouchEnd,\n\t\t\t\tonClick,\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\t// onPointerMove is special: where we're only interested in the other events when they're\n\t// happening _on_ the canvas (as opposed to outside of it, or on UI floating over it), we want\n\t// the pointer position to be up to date regardless of whether it's over the tldraw canvas or\n\t// not. So instead of returning a listener to be attached to the canvas, we directly attach a\n\t// listener to the whole document instead.\n\tuseEffect(() => {\n\t\tlet lastX: number, lastY: number\n\n\t\tfunction onPointerMove(e: PointerEvent) {\n\t\t\tif (editor.wasEventAlreadyHandled(e)) return\n\t\t\teditor.markEventAsHandled(e)\n\n\t\t\tif (e.clientX === lastX && e.clientY === lastY) return\n\t\t\tlastX = e.clientX\n\t\t\tlastY = e.clientY\n\n\t\t\t// For tools that benefit from a higher fidelity of events,\n\t\t\t// we dispatch the coalesced events.\n\t\t\t// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.\n\t\t\t// Specifically, in local mode (non-https) mode, iOS does not `useCoalescedEvents`\n\t\t\t// so it appears like the ink is working locally, when really it's just that `useCoalescedEvents`\n\t\t\t// is disabled. The intent here is to have `useCoalescedEvents` disabled for iOS.\n\t\t\tconst events =\n\t\t\t\t!tlenv.isIos && currentTool.useCoalescedEvents && e.getCoalescedEvents\n\t\t\t\t\t? e.getCoalescedEvents()\n\t\t\t\t\t: [e]\n\n\t\t\tfor (const singleEvent of events) {\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t...getPointerInfo(editor, singleEvent),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\townerDocument.body.addEventListener('pointermove', onPointerMove)\n\t\treturn () => {\n\t\t\townerDocument.body.removeEventListener('pointermove', onPointerMove)\n\t\t}\n\t}, [editor, currentTool, ownerDocument])\n\n\treturn events\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["events"]
|
|
7
7
|
}
|
|
@@ -39,7 +39,7 @@ const UnlicensedWatermark = memo(function UnlicensedWatermark2({
|
|
|
39
39
|
const events = useCanvasEvents();
|
|
40
40
|
const ref = useRef(null);
|
|
41
41
|
usePassThroughWheelEvents(ref);
|
|
42
|
-
const url = "https://tldraw.dev/pricing?utm_source=
|
|
42
|
+
const url = "https://tldraw.dev/pricing?utm_source=sdk&utm_medium=organic&utm_campaign=watermark";
|
|
43
43
|
return /* @__PURE__ */ jsx(
|
|
44
44
|
"div",
|
|
45
45
|
{
|
|
@@ -61,7 +61,9 @@ const UnlicensedWatermark = memo(function UnlicensedWatermark2({
|
|
|
61
61
|
preventDefault(e);
|
|
62
62
|
},
|
|
63
63
|
title: "The tldraw SDK requires a license key to work in production. You can get a free 100-day trial license at tldraw.dev/pricing.",
|
|
64
|
-
onClick: () =>
|
|
64
|
+
onClick: () => {
|
|
65
|
+
runtime.openWindow(url, "_blank", true);
|
|
66
|
+
},
|
|
65
67
|
children: "Get a license for production"
|
|
66
68
|
}
|
|
67
69
|
)
|
|
@@ -81,7 +83,7 @@ const WatermarkInner = memo(function WatermarkInner2({
|
|
|
81
83
|
const ref = useRef(null);
|
|
82
84
|
usePassThroughWheelEvents(ref);
|
|
83
85
|
const maskCss = `url('${src}') center 100% / 100% no-repeat`;
|
|
84
|
-
const url = "https://tldraw.dev/?utm_source=
|
|
86
|
+
const url = "https://tldraw.dev/?utm_source=sdk&utm_medium=organic&utm_campaign=watermark";
|
|
85
87
|
if (isUnlicensed) {
|
|
86
88
|
return /* @__PURE__ */ jsx(UnlicensedWatermark, { isDebugMode, isMobile });
|
|
87
89
|
}
|
|
@@ -105,7 +107,9 @@ const WatermarkInner = memo(function WatermarkInner2({
|
|
|
105
107
|
preventDefault(e);
|
|
106
108
|
},
|
|
107
109
|
title: "Build infinite canvas applications with the tldraw SDK. Learn more at https://tldraw.dev.",
|
|
108
|
-
onClick: () =>
|
|
110
|
+
onClick: () => {
|
|
111
|
+
runtime.openWindow(url, "_blank");
|
|
112
|
+
},
|
|
109
113
|
style: { mask: maskCss, WebkitMask: maskCss }
|
|
110
114
|
}
|
|
111
115
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/license/Watermark.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { memo, useRef } from 'react'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { usePassThroughWheelEvents } from '../hooks/usePassThroughWheelEvents'\nimport { preventDefault } from '../utils/dom'\nimport { runtime } from '../utils/runtime'\nimport { watermarkDesktopSvg, watermarkMobileSvg } from '../watermarks'\nimport { LicenseManager } from './LicenseManager'\nimport { useLicenseContext } from './LicenseProvider'\nimport { useLicenseManagerState } from './useLicenseManagerState'\n\nconst WATERMARK_DESKTOP_LOCAL_SRC = `data:image/svg+xml;utf8,${encodeURIComponent(watermarkDesktopSvg)}`\nconst WATERMARK_MOBILE_LOCAL_SRC = `data:image/svg+xml;utf8,${encodeURIComponent(watermarkMobileSvg)}`\n\n/** @internal */\nexport const Watermark = memo(function Watermark() {\n\tconst licenseManager = useLicenseContext()\n\tconst editor = useEditor()\n\tconst isMobile = useValue('is mobile', () => editor.getViewportScreenBounds().width < 700, [\n\t\teditor,\n\t])\n\n\tconst licenseManagerState = useLicenseManagerState(licenseManager)\n\n\tif (!['licensed-with-watermark', 'unlicensed'].includes(licenseManagerState)) return null\n\n\treturn (\n\t\t<>\n\t\t\t<LicenseStyles />\n\t\t\t<WatermarkInner\n\t\t\t\tsrc={isMobile ? WATERMARK_MOBILE_LOCAL_SRC : WATERMARK_DESKTOP_LOCAL_SRC}\n\t\t\t\tisUnlicensed={licenseManagerState === 'unlicensed'}\n\t\t\t/>\n\t\t</>\n\t)\n})\n\nconst UnlicensedWatermark = memo(function UnlicensedWatermark({\n\tisDebugMode,\n\tisMobile,\n}: {\n\tisDebugMode: boolean\n\tisMobile: boolean\n}) {\n\tconst editor = useEditor()\n\tconst events = useCanvasEvents()\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\n\tconst url
|
|
5
|
-
"mappings": "AA4BE,mBACC,KADD;AA5BF,SAAS,gBAAgB;AACzB,SAAS,MAAM,cAAc;AAC7B,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B,SAAS,iCAAiC;AAC1C,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AACxB,SAAS,qBAAqB,0BAA0B;AACxD,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,8BAA8B;AAEvC,MAAM,8BAA8B,2BAA2B,mBAAmB,mBAAmB,CAAC;AACtG,MAAM,6BAA6B,2BAA2B,mBAAmB,kBAAkB,CAAC;AAG7F,MAAM,YAAY,KAAK,SAASA,aAAY;AAClD,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,SAAS,aAAa,MAAM,OAAO,wBAAwB,EAAE,QAAQ,KAAK;AAAA,IAC1F;AAAA,EACD,CAAC;AAED,QAAM,sBAAsB,uBAAuB,cAAc;AAEjE,MAAI,CAAC,CAAC,2BAA2B,YAAY,EAAE,SAAS,mBAAmB,EAAG,QAAO;AAErF,SACC,iCACC;AAAA,wBAAC,iBAAc;AAAA,IACf;AAAA,MAAC;AAAA;AAAA,QACA,KAAK,WAAW,6BAA6B;AAAA,QAC7C,cAAc,wBAAwB;AAAA;AAAA,IACvC;AAAA,KACD;AAEF,CAAC;AAED,MAAM,sBAAsB,KAAK,SAASC,qBAAoB;AAAA,EAC7D;AAAA,EACA;AACD,GAGG;AACF,QAAM,SAAS,UAAU;AACzB,QAAM,SAAS,gBAAgB;AAC/B,QAAM,MAAM,OAAuB,IAAI;AACvC,4BAA0B,GAAG;AAE7B,QAAM,
|
|
4
|
+
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { memo, useRef } from 'react'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { usePassThroughWheelEvents } from '../hooks/usePassThroughWheelEvents'\nimport { preventDefault } from '../utils/dom'\nimport { runtime } from '../utils/runtime'\nimport { watermarkDesktopSvg, watermarkMobileSvg } from '../watermarks'\nimport { LicenseManager } from './LicenseManager'\nimport { useLicenseContext } from './LicenseProvider'\nimport { useLicenseManagerState } from './useLicenseManagerState'\n\nconst WATERMARK_DESKTOP_LOCAL_SRC = `data:image/svg+xml;utf8,${encodeURIComponent(watermarkDesktopSvg)}`\nconst WATERMARK_MOBILE_LOCAL_SRC = `data:image/svg+xml;utf8,${encodeURIComponent(watermarkMobileSvg)}`\n\n/** @internal */\nexport const Watermark = memo(function Watermark() {\n\tconst licenseManager = useLicenseContext()\n\tconst editor = useEditor()\n\tconst isMobile = useValue('is mobile', () => editor.getViewportScreenBounds().width < 700, [\n\t\teditor,\n\t])\n\n\tconst licenseManagerState = useLicenseManagerState(licenseManager)\n\n\tif (!['licensed-with-watermark', 'unlicensed'].includes(licenseManagerState)) return null\n\n\treturn (\n\t\t<>\n\t\t\t<LicenseStyles />\n\t\t\t<WatermarkInner\n\t\t\t\tsrc={isMobile ? WATERMARK_MOBILE_LOCAL_SRC : WATERMARK_DESKTOP_LOCAL_SRC}\n\t\t\t\tisUnlicensed={licenseManagerState === 'unlicensed'}\n\t\t\t/>\n\t\t</>\n\t)\n})\n\nconst UnlicensedWatermark = memo(function UnlicensedWatermark({\n\tisDebugMode,\n\tisMobile,\n}: {\n\tisDebugMode: boolean\n\tisMobile: boolean\n}) {\n\tconst editor = useEditor()\n\tconst events = useCanvasEvents()\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\n\tconst url = 'https://tldraw.dev/pricing?utm_source=sdk&utm_medium=organic&utm_campaign=watermark'\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName={LicenseManager.className}\n\t\t\tdata-debug={isDebugMode}\n\t\t\tdata-mobile={isMobile}\n\t\t\tdata-unlicensed={true}\n\t\t\tdata-testid=\"tl-watermark-unlicensed\"\n\t\t\tdraggable={false}\n\t\t\t{...events}\n\t\t>\n\t\t\t<button\n\t\t\t\tdraggable={false}\n\t\t\t\trole=\"button\"\n\t\t\t\tonPointerDown={(e) => {\n\t\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t}}\n\t\t\t\ttitle=\"The tldraw SDK requires a license key to work in production. You can get a free 100-day trial license at tldraw.dev/pricing.\"\n\t\t\t\tonClick={() => {\n\t\t\t\t\truntime.openWindow(url, '_blank', true)\n\t\t\t\t}} // allow referrer\n\t\t\t>\n\t\t\t\tGet a license for production\n\t\t\t</button>\n\t\t</div>\n\t)\n})\n\nconst WatermarkInner = memo(function WatermarkInner({\n\tsrc,\n\tisUnlicensed,\n}: {\n\tsrc: string\n\tisUnlicensed: boolean\n}) {\n\tconst editor = useEditor()\n\tconst isDebugMode = useValue('debug mode', () => editor.getInstanceState().isDebugMode, [editor])\n\tconst isMobile = useValue('is mobile', () => editor.getViewportScreenBounds().width < 700, [\n\t\teditor,\n\t])\n\tconst events = useCanvasEvents()\n\n\tconst ref = useRef<HTMLDivElement>(null)\n\tusePassThroughWheelEvents(ref)\n\n\tconst maskCss = `url('${src}') center 100% / 100% no-repeat`\n\tconst url = 'https://tldraw.dev/?utm_source=sdk&utm_medium=organic&utm_campaign=watermark'\n\n\tif (isUnlicensed) {\n\t\treturn <UnlicensedWatermark isDebugMode={isDebugMode} isMobile={isMobile} />\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName={LicenseManager.className}\n\t\t\tdata-debug={isDebugMode}\n\t\t\tdata-mobile={isMobile}\n\t\t\tdata-testid=\"tl-watermark-licensed\"\n\t\t\tdraggable={false}\n\t\t\t{...events}\n\t\t>\n\t\t\t<button\n\t\t\t\tdraggable={false}\n\t\t\t\trole=\"button\"\n\t\t\t\tonPointerDown={(e) => {\n\t\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t}}\n\t\t\t\ttitle=\"Build infinite canvas applications with the tldraw SDK. Learn more at https://tldraw.dev.\"\n\t\t\t\tonClick={() => {\n\t\t\t\t\truntime.openWindow(url, '_blank')\n\t\t\t\t}}\n\t\t\t\tstyle={{ mask: maskCss, WebkitMask: maskCss }}\n\t\t\t/>\n\t\t</div>\n\t)\n})\n\nconst LicenseStyles = memo(function LicenseStyles() {\n\tconst editor = useEditor()\n\tconst className = LicenseManager.className\n\n\tconst CSS = `\n/* ------------------- SEE LICENSE -------------------\nThe tldraw watermark is part of tldraw's license. It is shown for unlicensed\nor \"licensed-with-watermark\" users. By using this library, you agree to\npreserve the watermark's behavior, keeping it visible, unobscured, and\navailable to user-interaction.\n\nTo remove the watermark, please purchase a license at tldraw.dev.\n*/\n\n.${className} {\n\tposition: absolute;\n\tbottom: max(var(--tl-space-2), env(safe-area-inset-bottom));\n\tright: max(var(--tl-space-2), env(safe-area-inset-right));\n\twidth: 96px;\n\theight: 32px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tz-index: var(--tl-layer-watermark) !important;\n\tbackground-color: color-mix(in srgb, var(--tl-color-background) 62%, transparent);\n\topacity: 1;\n\tborder-radius: 5px;\n\tpointer-events: all;\n\tpadding: 2px;\n\tbox-sizing: content-box;\n}\n\n.${className} > button {\n\tposition: absolute;\n\twidth: 96px;\n\theight: 32px;\n\tpointer-events: all;\n\tcursor: inherit;\n\tcolor: var(--tl-color-text);\n\topacity: .38;\n\tborder: 0;\n\tpadding: 0;\n\tbackground-color: currentColor;\n}\n\n.${className}[data-debug='true'] {\n\tbottom: max(46px, env(safe-area-inset-bottom));\n}\n\n.${className}[data-mobile='true'] {\n\tborder-radius: 4px 0px 0px 4px;\n\tright: max(-2px, calc(env(safe-area-inset-right) - 2px));\n\twidth: 8px;\n\theight: 48px;\n}\n\n.${className}[data-mobile='true'] > button {\n\twidth: 8px;\n\theight: 32px;\n}\n\n.${className}[data-unlicensed='true'] > button {\n\tfont-size: 100px;\n\tposition: absolute;\n\tpointer-events: all;\n\tcursor: pointer;\n\tcolor: var(--tl-color-text);\n\topacity: 0.8;\n\tborder: 0;\n\tpadding: 0;\n\tbackground-color: transparent;\n\tfont-size: 11px;\n\tfont-weight: 600;\n\ttext-align: center;\n}\n\n.${className}[data-mobile='true'][data-unlicensed='true'] > button {\n\tdisplay: none;\n}\n\n@media (hover: hover) {\n\t.${className} > button {\n\t\tpointer-events: none;\n\t}\n\n\t.${className}:hover {\n\t\tbackground-color: var(--tl-color-background);\n\t\ttransition: background-color 0.2s ease-in-out;\n\t\ttransition-delay: 0.32s;\n\t}\n\n\t.${className}:hover > button {\n\t\tanimation: ${className}_delayed_link 0.2s forwards ease-in-out;\n\t\tanimation-delay: 0.32s;\n\t}\n\n\t.${className} > button:focus-visible {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes ${className}_delayed_link {\n\t0% {\n\t\tcursor: inherit;\n\t\topacity: .38;\n\t\tpointer-events: none;\n\t}\n\t100% {\n\t\tcursor: pointer;\n\t\topacity: 1;\n\t\tpointer-events: all;\n\t}\n}`\n\n\treturn <style nonce={editor.options.nonce}>{CSS}</style>\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["Watermark", "UnlicensedWatermark", "WatermarkInner", "LicenseStyles"]
|
|
7
7
|
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "4.
|
|
1
|
+
const version = "4.3.0-canary.d8da2a99f394";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2025-09-18T14:39:22.803Z",
|
|
4
|
-
minor: "2025-
|
|
5
|
-
patch: "2025-
|
|
4
|
+
minor: "2025-11-19T12:05:56.836Z",
|
|
5
|
+
patch: "2025-11-19T12:05:56.836Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
package/dist-esm/version.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.3.0-canary.d8da2a99f394'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2025-11-19T12:05:56.836Z',\n\tpatch: '2025-11-19T12:05:56.836Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (editor).",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.0-canary.d8da2a99f394",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"@tiptap/core": "3.6.2",
|
|
51
51
|
"@tiptap/pm": "3.6.2",
|
|
52
52
|
"@tiptap/react": "3.6.2",
|
|
53
|
-
"@tldraw/state": "4.
|
|
54
|
-
"@tldraw/state-react": "4.
|
|
55
|
-
"@tldraw/store": "4.
|
|
56
|
-
"@tldraw/tlschema": "4.
|
|
57
|
-
"@tldraw/utils": "4.
|
|
58
|
-
"@tldraw/validate": "4.
|
|
53
|
+
"@tldraw/state": "4.3.0-canary.d8da2a99f394",
|
|
54
|
+
"@tldraw/state-react": "4.3.0-canary.d8da2a99f394",
|
|
55
|
+
"@tldraw/store": "4.3.0-canary.d8da2a99f394",
|
|
56
|
+
"@tldraw/tlschema": "4.3.0-canary.d8da2a99f394",
|
|
57
|
+
"@tldraw/utils": "4.3.0-canary.d8da2a99f394",
|
|
58
|
+
"@tldraw/validate": "4.3.0-canary.d8da2a99f394",
|
|
59
59
|
"@types/core-js": "^2.5.8",
|
|
60
60
|
"@use-gesture/react": "^10.3.1",
|
|
61
61
|
"classnames": "^2.5.1",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
createTLStore,
|
|
12
12
|
} from '../..'
|
|
13
13
|
import { Editor } from './Editor'
|
|
14
|
+
import { StateNode } from './tools/StateNode'
|
|
14
15
|
|
|
15
16
|
type ICustomShape = TLBaseShape<
|
|
16
17
|
'my-custom-shape',
|
|
@@ -923,3 +924,270 @@ describe('replaceExternalContent', () => {
|
|
|
923
924
|
expect(mockHandler).toHaveBeenCalledWith(info)
|
|
924
925
|
})
|
|
925
926
|
})
|
|
927
|
+
|
|
928
|
+
describe('setTool', () => {
|
|
929
|
+
class CustomToolA extends StateNode {
|
|
930
|
+
static override id = 'custom-tool-a'
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
class CustomToolB extends StateNode {
|
|
934
|
+
static override id = 'custom-tool-b'
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
class CustomToolC extends StateNode {
|
|
938
|
+
static override id = 'custom-tool-c'
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
class ParentTool extends StateNode {
|
|
942
|
+
static override id = 'parent-tool'
|
|
943
|
+
static override initial = 'child-tool-1'
|
|
944
|
+
static override children() {
|
|
945
|
+
return [ChildTool1]
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
class ChildTool1 extends StateNode {
|
|
950
|
+
static override id = 'child-tool-1'
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
class ChildTool2 extends StateNode {
|
|
954
|
+
static override id = 'child-tool-2'
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
let toolEditor: Editor
|
|
958
|
+
|
|
959
|
+
beforeEach(() => {
|
|
960
|
+
toolEditor = new Editor({
|
|
961
|
+
shapeUtils: [],
|
|
962
|
+
bindingUtils: [],
|
|
963
|
+
tools: [CustomToolA, ParentTool],
|
|
964
|
+
store: createTLStore({ shapeUtils: [], bindingUtils: [] }),
|
|
965
|
+
getContainer: () => document.body,
|
|
966
|
+
})
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
it('should add a tool to the root state', () => {
|
|
970
|
+
// Initially CustomToolB should not exist
|
|
971
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeUndefined()
|
|
972
|
+
|
|
973
|
+
// Add CustomToolB
|
|
974
|
+
toolEditor.setTool(CustomToolB)
|
|
975
|
+
|
|
976
|
+
// CustomToolB should now exist in root
|
|
977
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeDefined()
|
|
978
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeInstanceOf(CustomToolB)
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
it('should add a tool to a specific parent state', () => {
|
|
982
|
+
const parentTool = toolEditor.root.children!['parent-tool'] as ParentTool
|
|
983
|
+
|
|
984
|
+
// Initially should only have child-tool-1
|
|
985
|
+
expect(Object.keys(parentTool.children!)).toHaveLength(1)
|
|
986
|
+
expect(parentTool.children!['child-tool-1']).toBeDefined()
|
|
987
|
+
expect(parentTool.children!['child-tool-2']).toBeUndefined()
|
|
988
|
+
|
|
989
|
+
// Add ChildTool2 to ParentTool
|
|
990
|
+
toolEditor.setTool(ChildTool2, parentTool)
|
|
991
|
+
|
|
992
|
+
// Should now have both children
|
|
993
|
+
expect(Object.keys(parentTool.children!)).toHaveLength(2)
|
|
994
|
+
expect(parentTool.children!['child-tool-1']).toBeDefined()
|
|
995
|
+
expect(parentTool.children!['child-tool-2']).toBeDefined()
|
|
996
|
+
expect(parentTool.children!['child-tool-2']).toBeInstanceOf(ChildTool2)
|
|
997
|
+
})
|
|
998
|
+
|
|
999
|
+
it('should throw an error when trying to override an existing tool', () => {
|
|
1000
|
+
// CustomToolA is already in the root (added in beforeEach)
|
|
1001
|
+
expect(toolEditor.root.children!['custom-tool-a']).toBeDefined()
|
|
1002
|
+
|
|
1003
|
+
// Should throw error when trying to add another tool with the same ID
|
|
1004
|
+
expect(() => {
|
|
1005
|
+
toolEditor.setTool(CustomToolA)
|
|
1006
|
+
}).toThrow('Can\'t override tool with id "custom-tool-a"')
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
it('should allow transitioning to a newly added tool', () => {
|
|
1010
|
+
// Add CustomToolB
|
|
1011
|
+
toolEditor.setTool(CustomToolB)
|
|
1012
|
+
|
|
1013
|
+
// Should be able to transition to the new tool
|
|
1014
|
+
expect(() => {
|
|
1015
|
+
toolEditor.setCurrentTool('custom-tool-b')
|
|
1016
|
+
}).not.toThrow()
|
|
1017
|
+
|
|
1018
|
+
// Should now be on the new tool
|
|
1019
|
+
expect(toolEditor.getCurrentToolId()).toBe('custom-tool-b')
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
it('should create the tool with the correct editor and parent', () => {
|
|
1023
|
+
// Add CustomToolB to root
|
|
1024
|
+
toolEditor.setTool(CustomToolB)
|
|
1025
|
+
|
|
1026
|
+
const customToolB = toolEditor.root.children!['custom-tool-b'] as CustomToolB
|
|
1027
|
+
|
|
1028
|
+
expect(customToolB.editor).toBe(toolEditor)
|
|
1029
|
+
expect(customToolB.parent).toBe(toolEditor.root)
|
|
1030
|
+
})
|
|
1031
|
+
|
|
1032
|
+
it('should maintain existing tools when adding new ones', () => {
|
|
1033
|
+
const originalTool = toolEditor.root.children!['custom-tool-a']
|
|
1034
|
+
|
|
1035
|
+
// Add CustomToolB
|
|
1036
|
+
toolEditor.setTool(CustomToolB)
|
|
1037
|
+
|
|
1038
|
+
// Original tool should still exist
|
|
1039
|
+
expect(toolEditor.root.children!['custom-tool-a']).toBe(originalTool)
|
|
1040
|
+
expect(toolEditor.root.children!['custom-tool-a']).toBeInstanceOf(CustomToolA)
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
it('should allow adding multiple tools', () => {
|
|
1044
|
+
// Add multiple tools
|
|
1045
|
+
toolEditor.setTool(CustomToolB)
|
|
1046
|
+
toolEditor.setTool(CustomToolC)
|
|
1047
|
+
|
|
1048
|
+
// All tools should exist
|
|
1049
|
+
expect(toolEditor.root.children!['custom-tool-a']).toBeDefined()
|
|
1050
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeDefined()
|
|
1051
|
+
expect(toolEditor.root.children!['custom-tool-c']).toBeDefined()
|
|
1052
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeInstanceOf(CustomToolB)
|
|
1053
|
+
expect(toolEditor.root.children!['custom-tool-c']).toBeInstanceOf(CustomToolC)
|
|
1054
|
+
})
|
|
1055
|
+
})
|
|
1056
|
+
|
|
1057
|
+
describe('removeTool', () => {
|
|
1058
|
+
class CustomToolA extends StateNode {
|
|
1059
|
+
static override id = 'custom-tool-a'
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
class CustomToolB extends StateNode {
|
|
1063
|
+
static override id = 'custom-tool-b'
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
class CustomToolC extends StateNode {
|
|
1067
|
+
static override id = 'custom-tool-c'
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
class ParentTool extends StateNode {
|
|
1071
|
+
static override id = 'parent-tool'
|
|
1072
|
+
static override initial = 'child-tool-1'
|
|
1073
|
+
static override children() {
|
|
1074
|
+
return [ChildTool1, ChildTool2]
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
class ChildTool1 extends StateNode {
|
|
1079
|
+
static override id = 'child-tool-1'
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
class ChildTool2 extends StateNode {
|
|
1083
|
+
static override id = 'child-tool-2'
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
let toolEditor: Editor
|
|
1087
|
+
|
|
1088
|
+
beforeEach(() => {
|
|
1089
|
+
toolEditor = new Editor({
|
|
1090
|
+
shapeUtils: [],
|
|
1091
|
+
bindingUtils: [],
|
|
1092
|
+
tools: [CustomToolA, CustomToolB, CustomToolC, ParentTool],
|
|
1093
|
+
store: createTLStore({ shapeUtils: [], bindingUtils: [] }),
|
|
1094
|
+
getContainer: () => document.body,
|
|
1095
|
+
})
|
|
1096
|
+
})
|
|
1097
|
+
|
|
1098
|
+
it('should remove a tool from the root state', () => {
|
|
1099
|
+
// CustomToolB should exist initially
|
|
1100
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeDefined()
|
|
1101
|
+
|
|
1102
|
+
// Remove CustomToolB
|
|
1103
|
+
toolEditor.removeTool(CustomToolB)
|
|
1104
|
+
|
|
1105
|
+
// CustomToolB should no longer exist
|
|
1106
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeUndefined()
|
|
1107
|
+
})
|
|
1108
|
+
|
|
1109
|
+
it('should remove a tool from a specific parent state', () => {
|
|
1110
|
+
const parentTool = toolEditor.root.children!['parent-tool'] as ParentTool
|
|
1111
|
+
|
|
1112
|
+
// Initially should have both children
|
|
1113
|
+
expect(Object.keys(parentTool.children!)).toHaveLength(2)
|
|
1114
|
+
expect(parentTool.children!['child-tool-1']).toBeDefined()
|
|
1115
|
+
expect(parentTool.children!['child-tool-2']).toBeDefined()
|
|
1116
|
+
|
|
1117
|
+
// Remove ChildTool2 from ParentTool
|
|
1118
|
+
toolEditor.removeTool(ChildTool2, parentTool)
|
|
1119
|
+
|
|
1120
|
+
// Should now only have child-tool-1
|
|
1121
|
+
expect(Object.keys(parentTool.children!)).toHaveLength(1)
|
|
1122
|
+
expect(parentTool.children!['child-tool-1']).toBeDefined()
|
|
1123
|
+
expect(parentTool.children!['child-tool-2']).toBeUndefined()
|
|
1124
|
+
})
|
|
1125
|
+
|
|
1126
|
+
it('should not throw an error when trying to remove a non-existent tool', () => {
|
|
1127
|
+
// First remove CustomToolB
|
|
1128
|
+
toolEditor.removeTool(CustomToolB)
|
|
1129
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeUndefined()
|
|
1130
|
+
|
|
1131
|
+
// Trying to remove it again should not throw
|
|
1132
|
+
expect(() => {
|
|
1133
|
+
toolEditor.removeTool(CustomToolB)
|
|
1134
|
+
}).not.toThrow()
|
|
1135
|
+
})
|
|
1136
|
+
|
|
1137
|
+
it('should maintain other tools when removing one', () => {
|
|
1138
|
+
const originalToolA = toolEditor.root.children!['custom-tool-a']
|
|
1139
|
+
const originalToolC = toolEditor.root.children!['custom-tool-c']
|
|
1140
|
+
|
|
1141
|
+
// Remove CustomToolB
|
|
1142
|
+
toolEditor.removeTool(CustomToolB)
|
|
1143
|
+
|
|
1144
|
+
// Other tools should still exist
|
|
1145
|
+
expect(toolEditor.root.children!['custom-tool-a']).toBe(originalToolA)
|
|
1146
|
+
expect(toolEditor.root.children!['custom-tool-c']).toBe(originalToolC)
|
|
1147
|
+
expect(toolEditor.root.children!['custom-tool-a']).toBeInstanceOf(CustomToolA)
|
|
1148
|
+
expect(toolEditor.root.children!['custom-tool-c']).toBeInstanceOf(CustomToolC)
|
|
1149
|
+
})
|
|
1150
|
+
|
|
1151
|
+
it('should not be able to transition to a removed tool', () => {
|
|
1152
|
+
// Remove CustomToolB
|
|
1153
|
+
toolEditor.removeTool(CustomToolB)
|
|
1154
|
+
|
|
1155
|
+
// Should throw when trying to transition to removed tool
|
|
1156
|
+
expect(() => {
|
|
1157
|
+
toolEditor.setCurrentTool('custom-tool-b')
|
|
1158
|
+
}).toThrow()
|
|
1159
|
+
})
|
|
1160
|
+
|
|
1161
|
+
it('should allow removing multiple tools', () => {
|
|
1162
|
+
// Remove multiple tools
|
|
1163
|
+
toolEditor.removeTool(CustomToolB)
|
|
1164
|
+
toolEditor.removeTool(CustomToolC)
|
|
1165
|
+
|
|
1166
|
+
// Removed tools should not exist
|
|
1167
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeUndefined()
|
|
1168
|
+
expect(toolEditor.root.children!['custom-tool-c']).toBeUndefined()
|
|
1169
|
+
|
|
1170
|
+
// Other tools should still exist
|
|
1171
|
+
expect(toolEditor.root.children!['custom-tool-a']).toBeDefined()
|
|
1172
|
+
expect(toolEditor.root.children!['parent-tool']).toBeDefined()
|
|
1173
|
+
})
|
|
1174
|
+
|
|
1175
|
+
it('should allow re-adding a tool after removing it', () => {
|
|
1176
|
+
// Remove CustomToolB
|
|
1177
|
+
toolEditor.removeTool(CustomToolB)
|
|
1178
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeUndefined()
|
|
1179
|
+
|
|
1180
|
+
// Re-add CustomToolB
|
|
1181
|
+
toolEditor.setTool(CustomToolB)
|
|
1182
|
+
|
|
1183
|
+
// CustomToolB should exist again
|
|
1184
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeDefined()
|
|
1185
|
+
expect(toolEditor.root.children!['custom-tool-b']).toBeInstanceOf(CustomToolB)
|
|
1186
|
+
|
|
1187
|
+
// Should be able to transition to it
|
|
1188
|
+
expect(() => {
|
|
1189
|
+
toolEditor.setCurrentTool('custom-tool-b')
|
|
1190
|
+
}).not.toThrow()
|
|
1191
|
+
expect(toolEditor.getCurrentToolId()).toBe('custom-tool-b')
|
|
1192
|
+
})
|
|
1193
|
+
})
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -842,14 +842,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
842
842
|
* after the editor has already been initialized.
|
|
843
843
|
*
|
|
844
844
|
* @param Tool - The tool to set.
|
|
845
|
+
* @param parent - The parent state node to set the tool on.
|
|
845
846
|
*
|
|
846
847
|
* @public
|
|
847
848
|
*/
|
|
848
|
-
setTool(Tool: TLStateNodeConstructor) {
|
|
849
|
-
|
|
849
|
+
setTool(Tool: TLStateNodeConstructor, parent?: StateNode) {
|
|
850
|
+
parent ??= this.root
|
|
851
|
+
if (hasOwnProperty(parent.children!, Tool.id)) {
|
|
850
852
|
throw Error(`Can't override tool with id "${Tool.id}"`)
|
|
851
853
|
}
|
|
852
|
-
|
|
854
|
+
parent.children![Tool.id] = new Tool(this, parent)
|
|
853
855
|
}
|
|
854
856
|
|
|
855
857
|
/**
|
|
@@ -857,12 +859,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
857
859
|
* after the editor has already been initialized.
|
|
858
860
|
*
|
|
859
861
|
* @param Tool - The tool to delete.
|
|
862
|
+
* @param parent - The parent state node to remove the tool from.
|
|
860
863
|
*
|
|
861
864
|
* @public
|
|
862
865
|
*/
|
|
863
|
-
removeTool(Tool: TLStateNodeConstructor) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
+
removeTool(Tool: TLStateNodeConstructor, parent?: StateNode) {
|
|
867
|
+
parent ??= this.root
|
|
868
|
+
if (hasOwnProperty(parent.children!, Tool.id)) {
|
|
869
|
+
delete parent.children![Tool.id]
|
|
866
870
|
}
|
|
867
871
|
}
|
|
868
872
|
|
|
@@ -8,6 +8,7 @@ import { useEditor } from './useEditor'
|
|
|
8
8
|
|
|
9
9
|
export function useCanvasEvents() {
|
|
10
10
|
const editor = useEditor()
|
|
11
|
+
const ownerDocument = editor.getContainer().ownerDocument
|
|
11
12
|
const currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])
|
|
12
13
|
|
|
13
14
|
const events = useMemo(
|
|
@@ -180,11 +181,11 @@ export function useCanvasEvents() {
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
|
|
184
|
+
ownerDocument.body.addEventListener('pointermove', onPointerMove)
|
|
184
185
|
return () => {
|
|
185
|
-
|
|
186
|
+
ownerDocument.body.removeEventListener('pointermove', onPointerMove)
|
|
186
187
|
}
|
|
187
|
-
}, [editor, currentTool])
|
|
188
|
+
}, [editor, currentTool, ownerDocument])
|
|
188
189
|
|
|
189
190
|
return events
|
|
190
191
|
}
|
|
@@ -48,8 +48,7 @@ const UnlicensedWatermark = memo(function UnlicensedWatermark({
|
|
|
48
48
|
const ref = useRef<HTMLDivElement>(null)
|
|
49
49
|
usePassThroughWheelEvents(ref)
|
|
50
50
|
|
|
51
|
-
const url =
|
|
52
|
-
'https://tldraw.dev/pricing?utm_source=dotcom&utm_medium=organic&utm_campaign=watermark'
|
|
51
|
+
const url = 'https://tldraw.dev/pricing?utm_source=sdk&utm_medium=organic&utm_campaign=watermark'
|
|
53
52
|
|
|
54
53
|
return (
|
|
55
54
|
<div
|
|
@@ -70,7 +69,9 @@ const UnlicensedWatermark = memo(function UnlicensedWatermark({
|
|
|
70
69
|
preventDefault(e)
|
|
71
70
|
}}
|
|
72
71
|
title="The tldraw SDK requires a license key to work in production. You can get a free 100-day trial license at tldraw.dev/pricing."
|
|
73
|
-
onClick={() =>
|
|
72
|
+
onClick={() => {
|
|
73
|
+
runtime.openWindow(url, '_blank', true)
|
|
74
|
+
}} // allow referrer
|
|
74
75
|
>
|
|
75
76
|
Get a license for production
|
|
76
77
|
</button>
|
|
@@ -96,7 +97,7 @@ const WatermarkInner = memo(function WatermarkInner({
|
|
|
96
97
|
usePassThroughWheelEvents(ref)
|
|
97
98
|
|
|
98
99
|
const maskCss = `url('${src}') center 100% / 100% no-repeat`
|
|
99
|
-
const url = 'https://tldraw.dev/?utm_source=
|
|
100
|
+
const url = 'https://tldraw.dev/?utm_source=sdk&utm_medium=organic&utm_campaign=watermark'
|
|
100
101
|
|
|
101
102
|
if (isUnlicensed) {
|
|
102
103
|
return <UnlicensedWatermark isDebugMode={isDebugMode} isMobile={isMobile} />
|
|
@@ -120,7 +121,9 @@ const WatermarkInner = memo(function WatermarkInner({
|
|
|
120
121
|
preventDefault(e)
|
|
121
122
|
}}
|
|
122
123
|
title="Build infinite canvas applications with the tldraw SDK. Learn more at https://tldraw.dev."
|
|
123
|
-
onClick={() =>
|
|
124
|
+
onClick={() => {
|
|
125
|
+
runtime.openWindow(url, '_blank')
|
|
126
|
+
}}
|
|
124
127
|
style={{ mask: maskCss, WebkitMask: maskCss }}
|
|
125
128
|
/>
|
|
126
129
|
</div>
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '4.
|
|
4
|
+
export const version = '4.3.0-canary.d8da2a99f394'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-11-19T12:05:56.836Z',
|
|
8
|
+
patch: '2025-11-19T12:05:56.836Z',
|
|
9
9
|
}
|