@tldraw/editor 3.16.0-canary.ed8bd30c0f28 → 3.16.0-canary.f60032f16651
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 +74 -9
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +1 -1
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +0 -5
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +19 -9
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -20
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/utils/EditorAtom.js +45 -0
- package/dist-cjs/lib/utils/EditorAtom.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +74 -9
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +1 -1
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +19 -9
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +25 -21
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/utils/EditorAtom.mjs +25 -0
- package/dist-esm/lib/utils/EditorAtom.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +2 -0
- package/src/lib/TldrawEditor.tsx +5 -5
- package/src/lib/components/MenuClickCapture.tsx +0 -8
- package/src/lib/editor/Editor.ts +33 -27
- package/src/lib/editor/types/misc-types.ts +54 -1
- package/src/lib/hooks/useCanvasEvents.ts +39 -32
- package/src/lib/utils/EditorAtom.ts +37 -0
- package/src/version.ts +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useValue } from "@tldraw/state-react";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useEffect, useMemo } from "react";
|
|
3
3
|
import { RIGHT_MOUSE_BUTTON } from "../constants.mjs";
|
|
4
4
|
import {
|
|
5
5
|
preventDefault,
|
|
@@ -14,7 +14,6 @@ function useCanvasEvents() {
|
|
|
14
14
|
const currentTool = useValue("current tool", () => editor.getCurrentTool(), [editor]);
|
|
15
15
|
const events = useMemo(
|
|
16
16
|
function canvasEvents() {
|
|
17
|
-
let lastX, lastY;
|
|
18
17
|
function onPointerDown(e) {
|
|
19
18
|
if (e.isKilled) return;
|
|
20
19
|
if (e.button === RIGHT_MOUSE_BUTTON) {
|
|
@@ -35,26 +34,9 @@ function useCanvasEvents() {
|
|
|
35
34
|
...getPointerInfo(e)
|
|
36
35
|
});
|
|
37
36
|
}
|
|
38
|
-
function onPointerMove(e) {
|
|
39
|
-
if (e.isKilled) return;
|
|
40
|
-
if (e.clientX === lastX && e.clientY === lastY) return;
|
|
41
|
-
lastX = e.clientX;
|
|
42
|
-
lastY = e.clientY;
|
|
43
|
-
const events2 = currentTool.useCoalescedEvents && e.nativeEvent.getCoalescedEvents ? e.nativeEvent.getCoalescedEvents() : [e];
|
|
44
|
-
for (const singleEvent of events2) {
|
|
45
|
-
editor.dispatch({
|
|
46
|
-
type: "pointer",
|
|
47
|
-
target: "canvas",
|
|
48
|
-
name: "pointer_move",
|
|
49
|
-
...getPointerInfo(singleEvent)
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
37
|
function onPointerUp(e) {
|
|
54
38
|
if (e.isKilled) return;
|
|
55
39
|
if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return;
|
|
56
|
-
lastX = e.clientX;
|
|
57
|
-
lastY = e.clientY;
|
|
58
40
|
releasePointerCapture(e.currentTarget, e);
|
|
59
41
|
editor.dispatch({
|
|
60
42
|
type: "pointer",
|
|
@@ -122,7 +104,6 @@ function useCanvasEvents() {
|
|
|
122
104
|
}
|
|
123
105
|
return {
|
|
124
106
|
onPointerDown,
|
|
125
|
-
onPointerMove,
|
|
126
107
|
onPointerUp,
|
|
127
108
|
onPointerEnter,
|
|
128
109
|
onPointerLeave,
|
|
@@ -133,8 +114,31 @@ function useCanvasEvents() {
|
|
|
133
114
|
onClick
|
|
134
115
|
};
|
|
135
116
|
},
|
|
136
|
-
[editor
|
|
117
|
+
[editor]
|
|
137
118
|
);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
let lastX, lastY;
|
|
121
|
+
function onPointerMove(e) {
|
|
122
|
+
if (e.isKilled) return;
|
|
123
|
+
e.isKilled = true;
|
|
124
|
+
if (e.clientX === lastX && e.clientY === lastY) return;
|
|
125
|
+
lastX = e.clientX;
|
|
126
|
+
lastY = e.clientY;
|
|
127
|
+
const events2 = currentTool.useCoalescedEvents && e.getCoalescedEvents ? e.getCoalescedEvents() : [e];
|
|
128
|
+
for (const singleEvent of events2) {
|
|
129
|
+
editor.dispatch({
|
|
130
|
+
type: "pointer",
|
|
131
|
+
target: "canvas",
|
|
132
|
+
name: "pointer_move",
|
|
133
|
+
...getPointerInfo(singleEvent)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
document.body.addEventListener("pointermove", onPointerMove);
|
|
138
|
+
return () => {
|
|
139
|
+
document.body.removeEventListener("pointermove", onPointerMove);
|
|
140
|
+
};
|
|
141
|
+
}, [editor, currentTool]);
|
|
138
142
|
return events;
|
|
139
143
|
}
|
|
140
144
|
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, { useMemo } from 'react'\nimport { RIGHT_MOUSE_BUTTON } from '../constants'\nimport {\n\tpreventDefault,\n\treleasePointerCapture,\n\tsetPointerCapture,\n\tstopEventPropagation,\n} 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\
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAgB,eAAe;
|
|
4
|
+
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport React, { useEffect, useMemo } from 'react'\nimport { RIGHT_MOUSE_BUTTON } from '../constants'\nimport {\n\tpreventDefault,\n\treleasePointerCapture,\n\tsetPointerCapture,\n\tstopEventPropagation,\n} 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 ((e as any).isKilled) 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(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(e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerUp(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) 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(e),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tfunction onPointerEnter(e: React.PointerEvent) {\n\t\t\t\tif ((e as any).isKilled) 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 ((e as any).isKilled) 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\t;(e as any).isKilled = true\n\t\t\t\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tfunction onTouchEnd(e: React.TouchEvent) {\n\t\t\t\t;(e as any).isKilled = true\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\tif (\n\t\t\t\t\te.target.tagName !== 'A' &&\n\t\t\t\t\te.target.tagName !== 'TEXTAREA' &&\n\t\t\t\t\t!e.target.isContentEditable &&\n\t\t\t\t\t// When in EditingShape state, we are actually clicking on a 'DIV'\n\t\t\t\t\t// not A/TEXTAREA/contenteditable element yet. So, to preserve cursor position\n\t\t\t\t\t// for edit mode on mobile we need to not preventDefault.\n\t\t\t\t\t// TODO: Find out if we still need this preventDefault in general though.\n\t\t\t\t\t!(editor.getEditingShape() && e.target.className.includes('tl-text-content'))\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\tpreventDefault(e)\n\t\t\t}\n\n\t\t\tasync function onDrop(e: React.DragEvent<Element>) {\n\t\t\t\tpreventDefault(e)\n\t\t\t\tstopEventPropagation(e)\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\tstopEventPropagation(e)\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 ((e as any).isKilled) return\n\t\t\t;(e as any).isKilled = true\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\tconst events =\n\t\t\t\tcurrentTool.useCoalescedEvents && e.getCoalescedEvents ? e.getCoalescedEvents() : [e]\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(singleEvent),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tdocument.body.addEventListener('pointermove', onPointerMove)\n\t\treturn () => {\n\t\t\tdocument.body.removeEventListener('pointermove', onPointerMove)\n\t\t}\n\t}, [editor, currentTool])\n\n\treturn events\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["events"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { atom } from "@tldraw/state";
|
|
2
|
+
import { WeakCache } from "@tldraw/utils";
|
|
3
|
+
class EditorAtom {
|
|
4
|
+
constructor(name, getInitialState) {
|
|
5
|
+
this.name = name;
|
|
6
|
+
this.getInitialState = getInitialState;
|
|
7
|
+
}
|
|
8
|
+
states = new WeakCache();
|
|
9
|
+
getAtom(editor) {
|
|
10
|
+
return this.states.get(editor, () => atom(this.name, this.getInitialState(editor)));
|
|
11
|
+
}
|
|
12
|
+
get(editor) {
|
|
13
|
+
return this.getAtom(editor).get();
|
|
14
|
+
}
|
|
15
|
+
update(editor, update) {
|
|
16
|
+
return this.getAtom(editor).update(update);
|
|
17
|
+
}
|
|
18
|
+
set(editor, state) {
|
|
19
|
+
return this.getAtom(editor).set(state);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
EditorAtom
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=EditorAtom.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/utils/EditorAtom.ts"],
|
|
4
|
+
"sourcesContent": ["import { atom, Atom } from '@tldraw/state'\nimport { WeakCache } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\n\n/**\n * An Atom that is scoped to the lifetime of an Editor.\n *\n * This is useful for storing UI state for tldraw applications. Keeping state scoped to an editor\n * instead of stored in a global atom can prevent issues with state being shared between editors\n * when navigating between pages, or when multiple editor instances are used on the same page.\n *\n * @public\n */\nexport class EditorAtom<T> {\n\tprivate states = new WeakCache<Editor, Atom<T>>()\n\n\tconstructor(\n\t\tprivate name: string,\n\t\tprivate getInitialState: (editor: Editor) => T\n\t) {}\n\n\tgetAtom(editor: Editor): Atom<T> {\n\t\treturn this.states.get(editor, () => atom(this.name, this.getInitialState(editor)))\n\t}\n\n\tget(editor: Editor): T {\n\t\treturn this.getAtom(editor).get()\n\t}\n\n\tupdate(editor: Editor, update: (state: T) => T): T {\n\t\treturn this.getAtom(editor).update(update)\n\t}\n\n\tset(editor: Editor, state: T): T {\n\t\treturn this.getAtom(editor).set(state)\n\t}\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,YAAkB;AAC3B,SAAS,iBAAiB;AAYnB,MAAM,WAAc;AAAA,EAG1B,YACS,MACA,iBACP;AAFO;AACA;AAAA,EACN;AAAA,EALK,SAAS,IAAI,UAA2B;AAAA,EAOhD,QAAQ,QAAyB;AAChC,WAAO,KAAK,OAAO,IAAI,QAAQ,MAAM,KAAK,KAAK,MAAM,KAAK,gBAAgB,MAAM,CAAC,CAAC;AAAA,EACnF;AAAA,EAEA,IAAI,QAAmB;AACtB,WAAO,KAAK,QAAQ,MAAM,EAAE,IAAI;AAAA,EACjC;AAAA,EAEA,OAAO,QAAgB,QAA4B;AAClD,WAAO,KAAK,QAAQ,MAAM,EAAE,OAAO,MAAM;AAAA,EAC1C;AAAA,EAEA,IAAI,QAAgB,OAAa;AAChC,WAAO,KAAK,QAAQ,MAAM,EAAE,IAAI,KAAK;AAAA,EACtC;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "3.16.0-canary.
|
|
1
|
+
const version = "3.16.0-canary.f60032f16651";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-
|
|
5
|
-
patch: "2025-
|
|
4
|
+
minor: "2025-08-06T09:18:23.766Z",
|
|
5
|
+
patch: "2025-08-06T09:18:23.766Z"
|
|
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 = '3.16.0-canary.
|
|
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 = '3.16.0-canary.f60032f16651'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-08-06T09:18:23.766Z',\n\tpatch: '2025-08-06T09:18:23.766Z',\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": "3.16.0-canary.
|
|
4
|
+
"version": "3.16.0-canary.f60032f16651",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"@tiptap/core": "^2.9.1",
|
|
50
50
|
"@tiptap/pm": "^2.9.1",
|
|
51
51
|
"@tiptap/react": "^2.9.1",
|
|
52
|
-
"@tldraw/state": "3.16.0-canary.
|
|
53
|
-
"@tldraw/state-react": "3.16.0-canary.
|
|
54
|
-
"@tldraw/store": "3.16.0-canary.
|
|
55
|
-
"@tldraw/tlschema": "3.16.0-canary.
|
|
56
|
-
"@tldraw/utils": "3.16.0-canary.
|
|
57
|
-
"@tldraw/validate": "3.16.0-canary.
|
|
52
|
+
"@tldraw/state": "3.16.0-canary.f60032f16651",
|
|
53
|
+
"@tldraw/state-react": "3.16.0-canary.f60032f16651",
|
|
54
|
+
"@tldraw/store": "3.16.0-canary.f60032f16651",
|
|
55
|
+
"@tldraw/tlschema": "3.16.0-canary.f60032f16651",
|
|
56
|
+
"@tldraw/utils": "3.16.0-canary.f60032f16651",
|
|
57
|
+
"@tldraw/validate": "3.16.0-canary.f60032f16651",
|
|
58
58
|
"@types/core-js": "^2.5.8",
|
|
59
59
|
"@use-gesture/react": "^10.3.1",
|
|
60
60
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
|
@@ -265,6 +265,7 @@ export {
|
|
|
265
265
|
type TLCameraMoveOptions,
|
|
266
266
|
type TLCameraOptions,
|
|
267
267
|
type TLExportType,
|
|
268
|
+
type TLGetShapeAtPointOptions,
|
|
268
269
|
type TLImageExportOptions,
|
|
269
270
|
type TLSvgExportOptions,
|
|
270
271
|
type TLSvgOptions,
|
|
@@ -450,6 +451,7 @@ export {
|
|
|
450
451
|
setPointerCapture,
|
|
451
452
|
stopEventPropagation,
|
|
452
453
|
} from './lib/utils/dom'
|
|
454
|
+
export { EditorAtom } from './lib/utils/EditorAtom'
|
|
453
455
|
export { getIncrementedName } from './lib/utils/getIncrementedName'
|
|
454
456
|
export { getPointerInfo } from './lib/utils/getPointerInfo'
|
|
455
457
|
export { getSvgPathFromPoints } from './lib/utils/getSvgPathFromPoints'
|
package/src/lib/TldrawEditor.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { MigrationSequence, Store } from '@tldraw/store'
|
|
2
2
|
import { TLShape, TLStore, TLStoreSnapshot } from '@tldraw/tlschema'
|
|
3
|
-
import {
|
|
3
|
+
import { annotateError, Required } from '@tldraw/utils'
|
|
4
4
|
import React, {
|
|
5
|
-
ReactNode,
|
|
6
5
|
memo,
|
|
6
|
+
ReactNode,
|
|
7
7
|
useCallback,
|
|
8
8
|
useEffect,
|
|
9
9
|
useLayoutEffect,
|
|
@@ -15,13 +15,13 @@ import React, {
|
|
|
15
15
|
|
|
16
16
|
import classNames from 'classnames'
|
|
17
17
|
import { version } from '../version'
|
|
18
|
-
import { OptionalErrorBoundary } from './components/ErrorBoundary'
|
|
19
18
|
import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback'
|
|
20
|
-
import {
|
|
19
|
+
import { OptionalErrorBoundary } from './components/ErrorBoundary'
|
|
21
20
|
import { TLStoreBaseOptions } from './config/createTLStore'
|
|
22
|
-
import {
|
|
21
|
+
import { createTLUser, TLUser } from './config/createTLUser'
|
|
23
22
|
import { TLAnyBindingUtilConstructor } from './config/defaultBindings'
|
|
24
23
|
import { TLAnyShapeUtilConstructor } from './config/defaultShapes'
|
|
24
|
+
import { TLEditorSnapshot } from './config/TLEditorSnapshot'
|
|
25
25
|
import { Editor } from './editor/Editor'
|
|
26
26
|
import { TLStateNodeConstructor } from './editor/tools/StateNode'
|
|
27
27
|
import { TLCameraOptions } from './editor/types/misc-types'
|
|
@@ -50,12 +50,6 @@ export function MenuClickCapture() {
|
|
|
50
50
|
// Do nothing unless we're pointing
|
|
51
51
|
if (!rPointerState.current.isDown) return
|
|
52
52
|
|
|
53
|
-
// If we're already dragging, pass on the event as it is
|
|
54
|
-
if (rPointerState.current.isDragging) {
|
|
55
|
-
canvasEvents.onPointerMove?.(e)
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
53
|
if (
|
|
60
54
|
// We're pointing, but are we dragging?
|
|
61
55
|
Vec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >
|
|
@@ -75,8 +69,6 @@ export function MenuClickCapture() {
|
|
|
75
69
|
clientY: y,
|
|
76
70
|
button: 0,
|
|
77
71
|
})
|
|
78
|
-
// call the pointer move with the current pointer position
|
|
79
|
-
canvasEvents.onPointerMove?.(e)
|
|
80
72
|
}
|
|
81
73
|
},
|
|
82
74
|
[canvasEvents, editor]
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -176,6 +176,7 @@ import {
|
|
|
176
176
|
RequiredKeys,
|
|
177
177
|
TLCameraMoveOptions,
|
|
178
178
|
TLCameraOptions,
|
|
179
|
+
TLGetShapeAtPointOptions,
|
|
179
180
|
TLImageExportOptions,
|
|
180
181
|
TLSvgExportOptions,
|
|
181
182
|
TLUpdatePointerOptions,
|
|
@@ -5154,20 +5155,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5154
5155
|
*
|
|
5155
5156
|
* @returns The shape at the given point, or undefined if there is no shape at the point.
|
|
5156
5157
|
*/
|
|
5157
|
-
getShapeAtPoint(
|
|
5158
|
-
point: VecLike,
|
|
5159
|
-
opts = {} as {
|
|
5160
|
-
renderingOnly?: boolean
|
|
5161
|
-
margin?: number
|
|
5162
|
-
hitInside?: boolean
|
|
5163
|
-
hitLocked?: boolean
|
|
5164
|
-
// TODO: we probably need to rename this, we don't quite _always_
|
|
5165
|
-
// respect this esp. in the part below that does "Check labels first"
|
|
5166
|
-
hitLabels?: boolean
|
|
5167
|
-
hitFrameInside?: boolean
|
|
5168
|
-
filter?(shape: TLShape): boolean
|
|
5169
|
-
}
|
|
5170
|
-
): TLShape | undefined {
|
|
5158
|
+
getShapeAtPoint(point: VecLike, opts: TLGetShapeAtPointOptions = {}): TLShape | undefined {
|
|
5171
5159
|
const zoomLevel = this.getZoomLevel()
|
|
5172
5160
|
const viewportPageBounds = this.getViewportPageBounds()
|
|
5173
5161
|
const {
|
|
@@ -5179,6 +5167,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5179
5167
|
hitFrameInside = false,
|
|
5180
5168
|
} = opts
|
|
5181
5169
|
|
|
5170
|
+
const [innerMargin, outerMargin] = Array.isArray(margin) ? margin : [margin, margin]
|
|
5171
|
+
|
|
5182
5172
|
let inHollowSmallestArea = Infinity
|
|
5183
5173
|
let inHollowSmallestAreaHit: TLShape | null = null
|
|
5184
5174
|
|
|
@@ -5198,7 +5188,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5198
5188
|
return false
|
|
5199
5189
|
const pageMask = this.getShapeMask(shape)
|
|
5200
5190
|
if (pageMask && !pointInPolygon(point, pageMask)) return false
|
|
5201
|
-
if (filter
|
|
5191
|
+
if (filter && !filter(shape)) return false
|
|
5202
5192
|
return true
|
|
5203
5193
|
})
|
|
5204
5194
|
|
|
@@ -5224,13 +5214,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5224
5214
|
}
|
|
5225
5215
|
}
|
|
5226
5216
|
|
|
5227
|
-
if (this.isShapeOfType(shape, 'frame')) {
|
|
5217
|
+
if (this.isShapeOfType<TLFrameShape>(shape, 'frame')) {
|
|
5228
5218
|
// On the rare case that we've hit a frame (not its label), test again hitInside to be forced true;
|
|
5229
5219
|
// this prevents clicks from passing through the body of a frame to shapes behind it.
|
|
5230
5220
|
|
|
5231
5221
|
// If the hit is within the frame's outer margin, then select the frame
|
|
5232
|
-
const distance = geometry.distanceToPoint(pointInShapeSpace,
|
|
5233
|
-
if (
|
|
5222
|
+
const distance = geometry.distanceToPoint(pointInShapeSpace, hitFrameInside)
|
|
5223
|
+
if (
|
|
5224
|
+
hitFrameInside
|
|
5225
|
+
? (distance > 0 && distance <= outerMargin) ||
|
|
5226
|
+
(distance <= 0 && distance > -innerMargin)
|
|
5227
|
+
: distance > 0 && distance <= outerMargin
|
|
5228
|
+
) {
|
|
5234
5229
|
return inMarginClosestToEdgeHit || shape
|
|
5235
5230
|
}
|
|
5236
5231
|
|
|
@@ -5269,11 +5264,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5269
5264
|
// If the margin is zero and the geometry has a very small width or height,
|
|
5270
5265
|
// then check the actual distance. This is to prevent a bug where straight
|
|
5271
5266
|
// lines would never pass the broad phase (point-in-bounds) check.
|
|
5272
|
-
if (
|
|
5267
|
+
if (outerMargin === 0 && (geometry.bounds.w < 1 || geometry.bounds.h < 1)) {
|
|
5273
5268
|
distance = geometry.distanceToPoint(pointInShapeSpace, hitInside)
|
|
5274
5269
|
} else {
|
|
5275
5270
|
// Broad phase
|
|
5276
|
-
if (geometry.bounds.containsPoint(pointInShapeSpace,
|
|
5271
|
+
if (geometry.bounds.containsPoint(pointInShapeSpace, outerMargin)) {
|
|
5277
5272
|
// Narrow phase (actual distance)
|
|
5278
5273
|
distance = geometry.distanceToPoint(pointInShapeSpace, hitInside)
|
|
5279
5274
|
} else {
|
|
@@ -5288,7 +5283,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5288
5283
|
// the shape or negative if inside of the shape. If the distance
|
|
5289
5284
|
// is greater than the margin, then it's a miss. Otherwise...
|
|
5290
5285
|
|
|
5291
|
-
|
|
5286
|
+
// Are we close to the shape's edge?
|
|
5287
|
+
if (distance <= outerMargin || (hitInside && distance <= 0 && distance > -innerMargin)) {
|
|
5292
5288
|
if (geometry.isFilled || (isGroup && geometry.children[0].isFilled)) {
|
|
5293
5289
|
// If the shape is filled, then it's a hit. Remember, we're
|
|
5294
5290
|
// starting from the TOP-MOST shape in z-index order, so any
|
|
@@ -5298,11 +5294,21 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5298
5294
|
// If the shape is bigger than the viewport, then skip it.
|
|
5299
5295
|
if (this.getShapePageBounds(shape)!.contains(viewportPageBounds)) continue
|
|
5300
5296
|
|
|
5301
|
-
//
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5297
|
+
// If we're close to the edge of the shape, and if it's the closest edge among
|
|
5298
|
+
// all the edges that we've gotten close to so far, then we will want to hit the
|
|
5299
|
+
// shape unless we hit something else or closer in later iterations.
|
|
5300
|
+
if (
|
|
5301
|
+
hitInside
|
|
5302
|
+
? // On hitInside, the distance will be negative for hits inside
|
|
5303
|
+
// If the distance is positive, check against the outer margin
|
|
5304
|
+
(distance > 0 && distance <= outerMargin) ||
|
|
5305
|
+
// If the distance is negative, check against the inner margin
|
|
5306
|
+
(distance <= 0 && distance > -innerMargin)
|
|
5307
|
+
: // If hitInside is false, then sadly _we do not know_ whether the
|
|
5308
|
+
// point is inside or outside of the shape, so we check against
|
|
5309
|
+
// the max of the two margins
|
|
5310
|
+
Math.abs(distance) <= Math.max(innerMargin, outerMargin)
|
|
5311
|
+
) {
|
|
5306
5312
|
if (Math.abs(distance) < inMarginClosestToEdgeDistance) {
|
|
5307
5313
|
inMarginClosestToEdgeDistance = Math.abs(distance)
|
|
5308
5314
|
inMarginClosestToEdgeHit = shape
|
|
@@ -5324,6 +5330,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5324
5330
|
} else {
|
|
5325
5331
|
// For open shapes (e.g. lines or draw shapes) always use the margin.
|
|
5326
5332
|
// If the distance is less than the margin, return the shape as the hit.
|
|
5333
|
+
// Use the editor's configurable hit test margin.
|
|
5327
5334
|
if (distance < this.options.hitTestMargin / zoomLevel) {
|
|
5328
5335
|
return shape
|
|
5329
5336
|
}
|
|
@@ -7380,7 +7387,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7380
7387
|
if (
|
|
7381
7388
|
!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
|
|
7382
7389
|
type: 'stretch',
|
|
7383
|
-
shapes: shapesToStretchFirstPass,
|
|
7384
7390
|
})
|
|
7385
7391
|
) {
|
|
7386
7392
|
continue
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxModel } from '@tldraw/tlschema'
|
|
1
|
+
import { BoxModel, TLShape } from '@tldraw/tlschema'
|
|
2
2
|
import { Box } from '../../primitives/Box'
|
|
3
3
|
import { VecLike } from '../../primitives/Vec'
|
|
4
4
|
|
|
@@ -206,3 +206,56 @@ export interface TLUpdatePointerOptions {
|
|
|
206
206
|
isPen?: boolean
|
|
207
207
|
button?: number
|
|
208
208
|
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Options to {@link Editor.getShapeAtPoint}.
|
|
212
|
+
*
|
|
213
|
+
* @public
|
|
214
|
+
*/
|
|
215
|
+
export interface TLGetShapeAtPointOptions {
|
|
216
|
+
/**
|
|
217
|
+
* The margin to apply to the shape.
|
|
218
|
+
* If a number, it will be applied to both the inside and outside of the shape.
|
|
219
|
+
* If an array, the first element will be applied to the inside of the shape, and the second element will be applied to the outside.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* // Get the shape at the center of the screen
|
|
224
|
+
* const shape = editor.getShapeAtProps({
|
|
225
|
+
* margin: 10,
|
|
226
|
+
* })
|
|
227
|
+
*
|
|
228
|
+
* // Get the shape at the center of the screen with a 10px inner margin and a 5px outer margin
|
|
229
|
+
* const shape = editor.getShapeAtProps({
|
|
230
|
+
* margin: [10, 5],
|
|
231
|
+
* })
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
margin?: number | [number, number]
|
|
235
|
+
/**
|
|
236
|
+
* Whether to register hits inside of shapes (beyond the margin), such as the inside of a solid shape.
|
|
237
|
+
*/
|
|
238
|
+
hitInside?: boolean
|
|
239
|
+
/**
|
|
240
|
+
* Whether to register hits on locked shapes.
|
|
241
|
+
*/
|
|
242
|
+
hitLocked?: boolean
|
|
243
|
+
/**
|
|
244
|
+
* Whether to register hits on labels.
|
|
245
|
+
*/
|
|
246
|
+
hitLabels?: boolean
|
|
247
|
+
/**
|
|
248
|
+
* Whether to only return hits on shapes that are currently being rendered.
|
|
249
|
+
* todo: rename this to hitCulled or hitNotRendering
|
|
250
|
+
*/
|
|
251
|
+
renderingOnly?: boolean
|
|
252
|
+
/**
|
|
253
|
+
* Whether to register hits on the inside of frame shapes.
|
|
254
|
+
* todo: rename this to hitInsideFrames
|
|
255
|
+
*/
|
|
256
|
+
hitFrameInside?: boolean
|
|
257
|
+
/**
|
|
258
|
+
* A filter function to apply to the shapes.
|
|
259
|
+
*/
|
|
260
|
+
filter?(shape: TLShape): boolean
|
|
261
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
|
-
import React, { useMemo } from 'react'
|
|
2
|
+
import React, { useEffect, useMemo } from 'react'
|
|
3
3
|
import { RIGHT_MOUSE_BUTTON } from '../constants'
|
|
4
4
|
import {
|
|
5
5
|
preventDefault,
|
|
@@ -16,9 +16,6 @@ export function useCanvasEvents() {
|
|
|
16
16
|
|
|
17
17
|
const events = useMemo(
|
|
18
18
|
function canvasEvents() {
|
|
19
|
-
// Track the last screen point
|
|
20
|
-
let lastX: number, lastY: number
|
|
21
|
-
|
|
22
19
|
function onPointerDown(e: React.PointerEvent) {
|
|
23
20
|
if ((e as any).isKilled) return
|
|
24
21
|
|
|
@@ -44,35 +41,9 @@ export function useCanvasEvents() {
|
|
|
44
41
|
})
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
function onPointerMove(e: React.PointerEvent) {
|
|
48
|
-
if ((e as any).isKilled) return
|
|
49
|
-
|
|
50
|
-
if (e.clientX === lastX && e.clientY === lastY) return
|
|
51
|
-
lastX = e.clientX
|
|
52
|
-
lastY = e.clientY
|
|
53
|
-
|
|
54
|
-
// For tools that benefit from a higher fidelity of events,
|
|
55
|
-
// we dispatch the coalesced events.
|
|
56
|
-
// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.
|
|
57
|
-
const events =
|
|
58
|
-
currentTool.useCoalescedEvents && e.nativeEvent.getCoalescedEvents
|
|
59
|
-
? e.nativeEvent.getCoalescedEvents()
|
|
60
|
-
: [e]
|
|
61
|
-
for (const singleEvent of events) {
|
|
62
|
-
editor.dispatch({
|
|
63
|
-
type: 'pointer',
|
|
64
|
-
target: 'canvas',
|
|
65
|
-
name: 'pointer_move',
|
|
66
|
-
...getPointerInfo(singleEvent),
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
44
|
function onPointerUp(e: React.PointerEvent) {
|
|
72
45
|
if ((e as any).isKilled) return
|
|
73
46
|
if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
|
|
74
|
-
lastX = e.clientX
|
|
75
|
-
lastY = e.clientY
|
|
76
47
|
|
|
77
48
|
releasePointerCapture(e.currentTarget, e)
|
|
78
49
|
|
|
@@ -158,7 +129,6 @@ export function useCanvasEvents() {
|
|
|
158
129
|
|
|
159
130
|
return {
|
|
160
131
|
onPointerDown,
|
|
161
|
-
onPointerMove,
|
|
162
132
|
onPointerUp,
|
|
163
133
|
onPointerEnter,
|
|
164
134
|
onPointerLeave,
|
|
@@ -169,8 +139,45 @@ export function useCanvasEvents() {
|
|
|
169
139
|
onClick,
|
|
170
140
|
}
|
|
171
141
|
},
|
|
172
|
-
[editor
|
|
142
|
+
[editor]
|
|
173
143
|
)
|
|
174
144
|
|
|
145
|
+
// onPointerMove is special: where we're only interested in the other events when they're
|
|
146
|
+
// happening _on_ the canvas (as opposed to outside of it, or on UI floating over it), we want
|
|
147
|
+
// the pointer position to be up to date regardless of whether it's over the tldraw canvas or
|
|
148
|
+
// not. So instead of returning a listener to be attached to the canvas, we directly attach a
|
|
149
|
+
// listener to the whole document instead.
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
let lastX: number, lastY: number
|
|
152
|
+
|
|
153
|
+
function onPointerMove(e: PointerEvent) {
|
|
154
|
+
if ((e as any).isKilled) return
|
|
155
|
+
;(e as any).isKilled = true
|
|
156
|
+
|
|
157
|
+
if (e.clientX === lastX && e.clientY === lastY) return
|
|
158
|
+
lastX = e.clientX
|
|
159
|
+
lastY = e.clientY
|
|
160
|
+
|
|
161
|
+
// For tools that benefit from a higher fidelity of events,
|
|
162
|
+
// we dispatch the coalesced events.
|
|
163
|
+
// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.
|
|
164
|
+
const events =
|
|
165
|
+
currentTool.useCoalescedEvents && e.getCoalescedEvents ? e.getCoalescedEvents() : [e]
|
|
166
|
+
for (const singleEvent of events) {
|
|
167
|
+
editor.dispatch({
|
|
168
|
+
type: 'pointer',
|
|
169
|
+
target: 'canvas',
|
|
170
|
+
name: 'pointer_move',
|
|
171
|
+
...getPointerInfo(singleEvent),
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
document.body.addEventListener('pointermove', onPointerMove)
|
|
177
|
+
return () => {
|
|
178
|
+
document.body.removeEventListener('pointermove', onPointerMove)
|
|
179
|
+
}
|
|
180
|
+
}, [editor, currentTool])
|
|
181
|
+
|
|
175
182
|
return events
|
|
176
183
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { atom, Atom } from '@tldraw/state'
|
|
2
|
+
import { WeakCache } from '@tldraw/utils'
|
|
3
|
+
import { Editor } from '../editor/Editor'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An Atom that is scoped to the lifetime of an Editor.
|
|
7
|
+
*
|
|
8
|
+
* This is useful for storing UI state for tldraw applications. Keeping state scoped to an editor
|
|
9
|
+
* instead of stored in a global atom can prevent issues with state being shared between editors
|
|
10
|
+
* when navigating between pages, or when multiple editor instances are used on the same page.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export class EditorAtom<T> {
|
|
15
|
+
private states = new WeakCache<Editor, Atom<T>>()
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private name: string,
|
|
19
|
+
private getInitialState: (editor: Editor) => T
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
getAtom(editor: Editor): Atom<T> {
|
|
23
|
+
return this.states.get(editor, () => atom(this.name, this.getInitialState(editor)))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get(editor: Editor): T {
|
|
27
|
+
return this.getAtom(editor).get()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
update(editor: Editor, update: (state: T) => T): T {
|
|
31
|
+
return this.getAtom(editor).update(update)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set(editor: Editor, state: T): T {
|
|
35
|
+
return this.getAtom(editor).set(state)
|
|
36
|
+
}
|
|
37
|
+
}
|
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 = '3.16.0-canary.
|
|
4
|
+
export const version = '3.16.0-canary.f60032f16651'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-08-06T09:18:23.766Z',
|
|
8
|
+
patch: '2025-08-06T09:18:23.766Z',
|
|
9
9
|
}
|