tldraw 4.5.9 → 4.5.12
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.js +1 -1
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js +11 -0
- package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js.map +2 -2
- package/dist-cjs/lib/ui/version.js +2 -2
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs +12 -1
- package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/version.mjs +2 -2
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/package.json +3 -3
- package/src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx +20 -2
- package/src/lib/ui/version.ts +2 -2
package/dist-cjs/index.js
CHANGED
|
@@ -606,7 +606,7 @@ var import_buildFromV1Document = require("./lib/utils/tldr/buildFromV1Document")
|
|
|
606
606
|
var import_file = require("./lib/utils/tldr/file");
|
|
607
607
|
(0, import_editor.registerTldrawLibraryVersion)(
|
|
608
608
|
"tldraw",
|
|
609
|
-
"4.5.
|
|
609
|
+
"4.5.12",
|
|
610
610
|
"cjs"
|
|
611
611
|
);
|
|
612
612
|
//# sourceMappingURL=index.js.map
|
|
@@ -52,6 +52,7 @@ const DefaultContextMenu = (0, import_react.memo)(function DefaultContextMenu2({
|
|
|
52
52
|
});
|
|
53
53
|
};
|
|
54
54
|
}, [preventEscapeFromLosingShapeFocus]);
|
|
55
|
+
const suppressDismissUntilRef = (0, import_react.useRef)(0);
|
|
55
56
|
const cb = (0, import_react.useCallback)(
|
|
56
57
|
(isOpen2) => {
|
|
57
58
|
if (!isOpen2) {
|
|
@@ -69,6 +70,7 @@ const DefaultContextMenu = (0, import_react.memo)(function DefaultContextMenu2({
|
|
|
69
70
|
capture: true
|
|
70
71
|
});
|
|
71
72
|
if (editor.getInstanceState().isCoarsePointer) {
|
|
73
|
+
suppressDismissUntilRef.current = Date.now() + 500;
|
|
72
74
|
const selectedShapes = editor.getSelectedShapes();
|
|
73
75
|
const currentPagePoint = editor.inputs.getCurrentPagePoint();
|
|
74
76
|
const shapesAtPoint = editor.getShapesAtPoint(currentPagePoint);
|
|
@@ -101,6 +103,15 @@ const DefaultContextMenu = (0, import_react.memo)(function DefaultContextMenu2({
|
|
|
101
103
|
alignOffset: -4,
|
|
102
104
|
collisionPadding: 4,
|
|
103
105
|
onContextMenu: import_editor.preventDefault,
|
|
106
|
+
onPointerDownOutside: (e) => {
|
|
107
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault();
|
|
108
|
+
},
|
|
109
|
+
onInteractOutside: (e) => {
|
|
110
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault();
|
|
111
|
+
},
|
|
112
|
+
onFocusOutside: (e) => {
|
|
113
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault();
|
|
114
|
+
},
|
|
104
115
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiMenuContext.TldrawUiMenuContextProvider, { type: "context-menu", sourceId: "context-menu", children: content })
|
|
105
116
|
}
|
|
106
117
|
) })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx"],
|
|
4
|
-
"sourcesContent": ["import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'\nimport { ContextMenu as _ContextMenu } from 'radix-ui'\nimport { ReactNode, memo, useCallback, useEffect } from 'react'\nimport { useMenuIsOpen } from '../../hooks/useMenuIsOpen'\nimport { useTranslation } from '../../hooks/useTranslation/useTranslation'\nimport { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'\nimport { DefaultContextMenuContent } from './DefaultContextMenuContent'\n\n/** @public */\nexport interface TLUiContextMenuProps {\n\tchildren?: ReactNode\n\tdisabled?: boolean\n}\n\n/** @public @react */\nexport const DefaultContextMenu = memo(function DefaultContextMenu({\n\tchildren,\n\tdisabled = false,\n}: TLUiContextMenuProps) {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\n\tconst { Canvas } = useEditorComponents()\n\n\t// When hitting `Escape` while the context menu is open, we want to prevent\n\t// the default behavior of losing focus on the shape. Otherwise,\n\t// it's pretty annoying from an accessibility perspective.\n\tconst preventEscapeFromLosingShapeFocus = useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\te.stopPropagation()\n\t\t\t\teditor.getContainer().focus()\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\t// Cleanup the event listener when the component unmounts.\n\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\tcapture: true,\n\t\t\t})\n\t\t}\n\t}, [preventEscapeFromLosingShapeFocus])\n\n\tconst cb = useCallback(\n\t\t(isOpen: boolean) => {\n\t\t\tif (!isOpen) {\n\t\t\t\tconst onlySelectedShape = editor.getOnlySelectedShape()\n\n\t\t\t\tif (onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape)) {\n\t\t\t\t\teditor.setSelectedShapes([])\n\t\t\t\t}\n\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\t\tcapture: true,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tdocument.body.addEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\tcapture: true,\n\t\t\t\t})\n\n\t\t\t\t// Weird route: selecting locked shapes on long press\n\t\t\t\
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'\nimport { ContextMenu as _ContextMenu } from 'radix-ui'\nimport { ReactNode, memo, useCallback, useEffect, useRef } from 'react'\nimport { useMenuIsOpen } from '../../hooks/useMenuIsOpen'\nimport { useTranslation } from '../../hooks/useTranslation/useTranslation'\nimport { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'\nimport { DefaultContextMenuContent } from './DefaultContextMenuContent'\n\n/** @public */\nexport interface TLUiContextMenuProps {\n\tchildren?: ReactNode\n\tdisabled?: boolean\n}\n\n/** @public @react */\nexport const DefaultContextMenu = memo(function DefaultContextMenu({\n\tchildren,\n\tdisabled = false,\n}: TLUiContextMenuProps) {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\n\tconst { Canvas } = useEditorComponents()\n\n\t// When hitting `Escape` while the context menu is open, we want to prevent\n\t// the default behavior of losing focus on the shape. Otherwise,\n\t// it's pretty annoying from an accessibility perspective.\n\tconst preventEscapeFromLosingShapeFocus = useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\te.stopPropagation()\n\t\t\t\teditor.getContainer().focus()\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\t// Cleanup the event listener when the component unmounts.\n\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\tcapture: true,\n\t\t\t})\n\t\t}\n\t}, [preventEscapeFromLosingShapeFocus])\n\n\t// On touch devices, the same touch that triggers Radix's long-press open is still\n\t// down when the menu mounts. The release fires events the dismissable layer treats\n\t// as an outside interaction and closes the menu. We swallow dismissals during a\n\t// short grace window after open so the menu stays put until the user actually\n\t// interacts again.\n\tconst suppressDismissUntilRef = useRef(0)\n\n\tconst cb = useCallback(\n\t\t(isOpen: boolean) => {\n\t\t\tif (!isOpen) {\n\t\t\t\tconst onlySelectedShape = editor.getOnlySelectedShape()\n\n\t\t\t\tif (onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape)) {\n\t\t\t\t\teditor.setSelectedShapes([])\n\t\t\t\t}\n\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\t\tcapture: true,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tdocument.body.addEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\tcapture: true,\n\t\t\t\t})\n\n\t\t\t\tif (editor.getInstanceState().isCoarsePointer) {\n\t\t\t\t\tsuppressDismissUntilRef.current = Date.now() + 500\n\n\t\t\t\t\t// Weird route: selecting locked shapes on long press\n\t\t\t\t\tconst selectedShapes = editor.getSelectedShapes()\n\t\t\t\t\tconst currentPagePoint = editor.inputs.getCurrentPagePoint()\n\n\t\t\t\t\t// get all of the shapes under the current pointer\n\t\t\t\t\tconst shapesAtPoint = editor.getShapesAtPoint(currentPagePoint)\n\n\t\t\t\t\tif (\n\t\t\t\t\t\t// if there are no selected shapes\n\t\t\t\t\t\t!editor.getSelectedShapes().length ||\n\t\t\t\t\t\t// OR if none of the shapes at the point include the selected shape\n\t\t\t\t\t\t!shapesAtPoint.some((s) => selectedShapes.includes(s))\n\t\t\t\t\t) {\n\t\t\t\t\t\t// then are there any locked shapes under the current pointer?\n\t\t\t\t\t\tconst lockedShapes = shapesAtPoint.filter((s) => editor.isShapeOrAncestorLocked(s))\n\n\t\t\t\t\t\tif (lockedShapes.length) {\n\t\t\t\t\t\t\t// nice, let's select them\n\t\t\t\t\t\t\teditor.select(...lockedShapes.map((s) => s.id))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[editor, preventEscapeFromLosingShapeFocus]\n\t)\n\n\tconst container = useContainer()\n\tconst [isOpen, handleOpenChange] = useMenuIsOpen('context menu', cb)\n\n\t// Get the context menu content, either the default component or the user's\n\t// override. If there's no menu content, then the user has set it to null,\n\t// so skip rendering the menu.\n\tconst content = children ?? <DefaultContextMenuContent />\n\n\treturn (\n\t\t<_ContextMenu.Root dir=\"ltr\" onOpenChange={handleOpenChange} modal={false}>\n\t\t\t<_ContextMenu.Trigger onContextMenu={undefined} dir=\"ltr\" disabled={disabled}>\n\t\t\t\t{Canvas ? <Canvas /> : null}\n\t\t\t</_ContextMenu.Trigger>\n\t\t\t{isOpen && (\n\t\t\t\t<_ContextMenu.Portal container={container}>\n\t\t\t\t\t<_ContextMenu.Content\n\t\t\t\t\t\tclassName=\"tlui-menu tlui-scrollable\"\n\t\t\t\t\t\tdata-testid=\"context-menu\"\n\t\t\t\t\t\taria-label={msg('context-menu.title')}\n\t\t\t\t\t\talignOffset={-4}\n\t\t\t\t\t\tcollisionPadding={4}\n\t\t\t\t\t\tonContextMenu={preventDefault}\n\t\t\t\t\t\tonPointerDownOutside={(e) => {\n\t\t\t\t\t\t\tif (Date.now() < suppressDismissUntilRef.current) e.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonInteractOutside={(e) => {\n\t\t\t\t\t\t\tif (Date.now() < suppressDismissUntilRef.current) e.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonFocusOutside={(e) => {\n\t\t\t\t\t\t\tif (Date.now() < suppressDismissUntilRef.current) e.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<TldrawUiMenuContextProvider type=\"context-menu\" sourceId=\"context-menu\">\n\t\t\t\t\t\t\t{content}\n\t\t\t\t\t\t</TldrawUiMenuContextProvider>\n\t\t\t\t\t</_ContextMenu.Content>\n\t\t\t\t</_ContextMenu.Portal>\n\t\t\t)}\n\t\t</_ContextMenu.Root>\n\t)\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["DefaultContextMenu", "isOpen", "_ContextMenu"]
|
|
7
7
|
}
|
|
@@ -22,10 +22,10 @@ __export(version_exports, {
|
|
|
22
22
|
version: () => version
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(version_exports);
|
|
25
|
-
const version = "4.5.
|
|
25
|
+
const version = "4.5.12";
|
|
26
26
|
const publishDates = {
|
|
27
27
|
major: "2025-09-18T14:39:22.803Z",
|
|
28
28
|
minor: "2026-03-18T11:05:13.340Z",
|
|
29
|
-
patch: "2026-
|
|
29
|
+
patch: "2026-05-13T13:01:54.016Z"
|
|
30
30
|
};
|
|
31
31
|
//# sourceMappingURL=version.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/ui/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.5.
|
|
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.5.12'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-03-18T11:05:13.340Z',\n\tpatch: '2026-05-13T13:01:54.016Z',\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { preventDefault, useContainer, useEditor, useEditorComponents } from "@tldraw/editor";
|
|
3
3
|
import { ContextMenu as _ContextMenu } from "radix-ui";
|
|
4
|
-
import { memo, useCallback, useEffect } from "react";
|
|
4
|
+
import { memo, useCallback, useEffect, useRef } from "react";
|
|
5
5
|
import { useMenuIsOpen } from "../../hooks/useMenuIsOpen.mjs";
|
|
6
6
|
import { useTranslation } from "../../hooks/useTranslation/useTranslation.mjs";
|
|
7
7
|
import { TldrawUiMenuContextProvider } from "../primitives/menus/TldrawUiMenuContext.mjs";
|
|
@@ -29,6 +29,7 @@ const DefaultContextMenu = memo(function DefaultContextMenu2({
|
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
31
|
}, [preventEscapeFromLosingShapeFocus]);
|
|
32
|
+
const suppressDismissUntilRef = useRef(0);
|
|
32
33
|
const cb = useCallback(
|
|
33
34
|
(isOpen2) => {
|
|
34
35
|
if (!isOpen2) {
|
|
@@ -46,6 +47,7 @@ const DefaultContextMenu = memo(function DefaultContextMenu2({
|
|
|
46
47
|
capture: true
|
|
47
48
|
});
|
|
48
49
|
if (editor.getInstanceState().isCoarsePointer) {
|
|
50
|
+
suppressDismissUntilRef.current = Date.now() + 500;
|
|
49
51
|
const selectedShapes = editor.getSelectedShapes();
|
|
50
52
|
const currentPagePoint = editor.inputs.getCurrentPagePoint();
|
|
51
53
|
const shapesAtPoint = editor.getShapesAtPoint(currentPagePoint);
|
|
@@ -78,6 +80,15 @@ const DefaultContextMenu = memo(function DefaultContextMenu2({
|
|
|
78
80
|
alignOffset: -4,
|
|
79
81
|
collisionPadding: 4,
|
|
80
82
|
onContextMenu: preventDefault,
|
|
83
|
+
onPointerDownOutside: (e) => {
|
|
84
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault();
|
|
85
|
+
},
|
|
86
|
+
onInteractOutside: (e) => {
|
|
87
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault();
|
|
88
|
+
},
|
|
89
|
+
onFocusOutside: (e) => {
|
|
90
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault();
|
|
91
|
+
},
|
|
81
92
|
children: /* @__PURE__ */ jsx(TldrawUiMenuContextProvider, { type: "context-menu", sourceId: "context-menu", children: content })
|
|
82
93
|
}
|
|
83
94
|
) })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx"],
|
|
4
|
-
"sourcesContent": ["import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'\nimport { ContextMenu as _ContextMenu } from 'radix-ui'\nimport { ReactNode, memo, useCallback, useEffect } from 'react'\nimport { useMenuIsOpen } from '../../hooks/useMenuIsOpen'\nimport { useTranslation } from '../../hooks/useTranslation/useTranslation'\nimport { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'\nimport { DefaultContextMenuContent } from './DefaultContextMenuContent'\n\n/** @public */\nexport interface TLUiContextMenuProps {\n\tchildren?: ReactNode\n\tdisabled?: boolean\n}\n\n/** @public @react */\nexport const DefaultContextMenu = memo(function DefaultContextMenu({\n\tchildren,\n\tdisabled = false,\n}: TLUiContextMenuProps) {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\n\tconst { Canvas } = useEditorComponents()\n\n\t// When hitting `Escape` while the context menu is open, we want to prevent\n\t// the default behavior of losing focus on the shape. Otherwise,\n\t// it's pretty annoying from an accessibility perspective.\n\tconst preventEscapeFromLosingShapeFocus = useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\te.stopPropagation()\n\t\t\t\teditor.getContainer().focus()\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\t// Cleanup the event listener when the component unmounts.\n\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\tcapture: true,\n\t\t\t})\n\t\t}\n\t}, [preventEscapeFromLosingShapeFocus])\n\n\tconst cb = useCallback(\n\t\t(isOpen: boolean) => {\n\t\t\tif (!isOpen) {\n\t\t\t\tconst onlySelectedShape = editor.getOnlySelectedShape()\n\n\t\t\t\tif (onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape)) {\n\t\t\t\t\teditor.setSelectedShapes([])\n\t\t\t\t}\n\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\t\tcapture: true,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tdocument.body.addEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\tcapture: true,\n\t\t\t\t})\n\n\t\t\t\t// Weird route: selecting locked shapes on long press\n\t\t\t\
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'\nimport { ContextMenu as _ContextMenu } from 'radix-ui'\nimport { ReactNode, memo, useCallback, useEffect, useRef } from 'react'\nimport { useMenuIsOpen } from '../../hooks/useMenuIsOpen'\nimport { useTranslation } from '../../hooks/useTranslation/useTranslation'\nimport { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'\nimport { DefaultContextMenuContent } from './DefaultContextMenuContent'\n\n/** @public */\nexport interface TLUiContextMenuProps {\n\tchildren?: ReactNode\n\tdisabled?: boolean\n}\n\n/** @public @react */\nexport const DefaultContextMenu = memo(function DefaultContextMenu({\n\tchildren,\n\tdisabled = false,\n}: TLUiContextMenuProps) {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\n\tconst { Canvas } = useEditorComponents()\n\n\t// When hitting `Escape` while the context menu is open, we want to prevent\n\t// the default behavior of losing focus on the shape. Otherwise,\n\t// it's pretty annoying from an accessibility perspective.\n\tconst preventEscapeFromLosingShapeFocus = useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\te.stopPropagation()\n\t\t\t\teditor.getContainer().focus()\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\t// Cleanup the event listener when the component unmounts.\n\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\tcapture: true,\n\t\t\t})\n\t\t}\n\t}, [preventEscapeFromLosingShapeFocus])\n\n\t// On touch devices, the same touch that triggers Radix's long-press open is still\n\t// down when the menu mounts. The release fires events the dismissable layer treats\n\t// as an outside interaction and closes the menu. We swallow dismissals during a\n\t// short grace window after open so the menu stays put until the user actually\n\t// interacts again.\n\tconst suppressDismissUntilRef = useRef(0)\n\n\tconst cb = useCallback(\n\t\t(isOpen: boolean) => {\n\t\t\tif (!isOpen) {\n\t\t\t\tconst onlySelectedShape = editor.getOnlySelectedShape()\n\n\t\t\t\tif (onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape)) {\n\t\t\t\t\teditor.setSelectedShapes([])\n\t\t\t\t}\n\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\tdocument.body.removeEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\t\tcapture: true,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tdocument.body.addEventListener('keydown', preventEscapeFromLosingShapeFocus, {\n\t\t\t\t\tcapture: true,\n\t\t\t\t})\n\n\t\t\t\tif (editor.getInstanceState().isCoarsePointer) {\n\t\t\t\t\tsuppressDismissUntilRef.current = Date.now() + 500\n\n\t\t\t\t\t// Weird route: selecting locked shapes on long press\n\t\t\t\t\tconst selectedShapes = editor.getSelectedShapes()\n\t\t\t\t\tconst currentPagePoint = editor.inputs.getCurrentPagePoint()\n\n\t\t\t\t\t// get all of the shapes under the current pointer\n\t\t\t\t\tconst shapesAtPoint = editor.getShapesAtPoint(currentPagePoint)\n\n\t\t\t\t\tif (\n\t\t\t\t\t\t// if there are no selected shapes\n\t\t\t\t\t\t!editor.getSelectedShapes().length ||\n\t\t\t\t\t\t// OR if none of the shapes at the point include the selected shape\n\t\t\t\t\t\t!shapesAtPoint.some((s) => selectedShapes.includes(s))\n\t\t\t\t\t) {\n\t\t\t\t\t\t// then are there any locked shapes under the current pointer?\n\t\t\t\t\t\tconst lockedShapes = shapesAtPoint.filter((s) => editor.isShapeOrAncestorLocked(s))\n\n\t\t\t\t\t\tif (lockedShapes.length) {\n\t\t\t\t\t\t\t// nice, let's select them\n\t\t\t\t\t\t\teditor.select(...lockedShapes.map((s) => s.id))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[editor, preventEscapeFromLosingShapeFocus]\n\t)\n\n\tconst container = useContainer()\n\tconst [isOpen, handleOpenChange] = useMenuIsOpen('context menu', cb)\n\n\t// Get the context menu content, either the default component or the user's\n\t// override. If there's no menu content, then the user has set it to null,\n\t// so skip rendering the menu.\n\tconst content = children ?? <DefaultContextMenuContent />\n\n\treturn (\n\t\t<_ContextMenu.Root dir=\"ltr\" onOpenChange={handleOpenChange} modal={false}>\n\t\t\t<_ContextMenu.Trigger onContextMenu={undefined} dir=\"ltr\" disabled={disabled}>\n\t\t\t\t{Canvas ? <Canvas /> : null}\n\t\t\t</_ContextMenu.Trigger>\n\t\t\t{isOpen && (\n\t\t\t\t<_ContextMenu.Portal container={container}>\n\t\t\t\t\t<_ContextMenu.Content\n\t\t\t\t\t\tclassName=\"tlui-menu tlui-scrollable\"\n\t\t\t\t\t\tdata-testid=\"context-menu\"\n\t\t\t\t\t\taria-label={msg('context-menu.title')}\n\t\t\t\t\t\talignOffset={-4}\n\t\t\t\t\t\tcollisionPadding={4}\n\t\t\t\t\t\tonContextMenu={preventDefault}\n\t\t\t\t\t\tonPointerDownOutside={(e) => {\n\t\t\t\t\t\t\tif (Date.now() < suppressDismissUntilRef.current) e.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonInteractOutside={(e) => {\n\t\t\t\t\t\t\tif (Date.now() < suppressDismissUntilRef.current) e.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonFocusOutside={(e) => {\n\t\t\t\t\t\t\tif (Date.now() < suppressDismissUntilRef.current) e.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<TldrawUiMenuContextProvider type=\"context-menu\" sourceId=\"context-menu\">\n\t\t\t\t\t\t\t{content}\n\t\t\t\t\t\t</TldrawUiMenuContextProvider>\n\t\t\t\t\t</_ContextMenu.Content>\n\t\t\t\t</_ContextMenu.Portal>\n\t\t\t)}\n\t\t</_ContextMenu.Root>\n\t)\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["DefaultContextMenu", "isOpen"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/ui/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.5.
|
|
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.5.12'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-03-18T11:05:13.340Z',\n\tpatch: '2026-05-13T13:01:54.016Z',\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",
|
|
3
3
|
"description": "A tiny little drawing editor.",
|
|
4
|
-
"version": "4.5.
|
|
4
|
+
"version": "4.5.12",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"@tiptap/pm": "^3.12.1",
|
|
62
62
|
"@tiptap/react": "^3.12.1",
|
|
63
63
|
"@tiptap/starter-kit": "^3.12.1",
|
|
64
|
-
"@tldraw/editor": "4.5.
|
|
65
|
-
"@tldraw/store": "4.5.
|
|
64
|
+
"@tldraw/editor": "4.5.12",
|
|
65
|
+
"@tldraw/store": "4.5.12",
|
|
66
66
|
"classnames": "^2.5.1",
|
|
67
67
|
"hotkeys-js": "^3.13.9",
|
|
68
68
|
"idb": "^7.1.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'
|
|
2
2
|
import { ContextMenu as _ContextMenu } from 'radix-ui'
|
|
3
|
-
import { ReactNode, memo, useCallback, useEffect } from 'react'
|
|
3
|
+
import { ReactNode, memo, useCallback, useEffect, useRef } from 'react'
|
|
4
4
|
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
|
|
5
5
|
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
|
6
6
|
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
|
|
@@ -44,6 +44,13 @@ export const DefaultContextMenu = memo(function DefaultContextMenu({
|
|
|
44
44
|
}
|
|
45
45
|
}, [preventEscapeFromLosingShapeFocus])
|
|
46
46
|
|
|
47
|
+
// On touch devices, the same touch that triggers Radix's long-press open is still
|
|
48
|
+
// down when the menu mounts. The release fires events the dismissable layer treats
|
|
49
|
+
// as an outside interaction and closes the menu. We swallow dismissals during a
|
|
50
|
+
// short grace window after open so the menu stays put until the user actually
|
|
51
|
+
// interacts again.
|
|
52
|
+
const suppressDismissUntilRef = useRef(0)
|
|
53
|
+
|
|
47
54
|
const cb = useCallback(
|
|
48
55
|
(isOpen: boolean) => {
|
|
49
56
|
if (!isOpen) {
|
|
@@ -63,8 +70,10 @@ export const DefaultContextMenu = memo(function DefaultContextMenu({
|
|
|
63
70
|
capture: true,
|
|
64
71
|
})
|
|
65
72
|
|
|
66
|
-
// Weird route: selecting locked shapes on long press
|
|
67
73
|
if (editor.getInstanceState().isCoarsePointer) {
|
|
74
|
+
suppressDismissUntilRef.current = Date.now() + 500
|
|
75
|
+
|
|
76
|
+
// Weird route: selecting locked shapes on long press
|
|
68
77
|
const selectedShapes = editor.getSelectedShapes()
|
|
69
78
|
const currentPagePoint = editor.inputs.getCurrentPagePoint()
|
|
70
79
|
|
|
@@ -113,6 +122,15 @@ export const DefaultContextMenu = memo(function DefaultContextMenu({
|
|
|
113
122
|
alignOffset={-4}
|
|
114
123
|
collisionPadding={4}
|
|
115
124
|
onContextMenu={preventDefault}
|
|
125
|
+
onPointerDownOutside={(e) => {
|
|
126
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault()
|
|
127
|
+
}}
|
|
128
|
+
onInteractOutside={(e) => {
|
|
129
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault()
|
|
130
|
+
}}
|
|
131
|
+
onFocusOutside={(e) => {
|
|
132
|
+
if (Date.now() < suppressDismissUntilRef.current) e.preventDefault()
|
|
133
|
+
}}
|
|
116
134
|
>
|
|
117
135
|
<TldrawUiMenuContextProvider type="context-menu" sourceId="context-menu">
|
|
118
136
|
{content}
|
package/src/lib/ui/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.5.
|
|
4
|
+
export const version = '4.5.12'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
7
|
minor: '2026-03-18T11:05:13.340Z',
|
|
8
|
-
patch: '2026-
|
|
8
|
+
patch: '2026-05-13T13:01:54.016Z',
|
|
9
9
|
}
|