tldraw 5.3.2 → 5.4.0-canary.042da660fa66

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.
Files changed (93) hide show
  1. package/DOCS.md +2822 -3224
  2. package/RELEASE_NOTES.md +11 -39
  3. package/dist-cjs/index.d.ts +264 -275
  4. package/dist-cjs/index.js +1 -1
  5. package/dist-cjs/lib/defaultExternalContentHandlers.js +2 -2
  6. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  7. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js +2 -2
  8. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js.map +2 -2
  9. package/dist-cjs/lib/shapes/frame/frameHelpers.js +3 -0
  10. package/dist-cjs/lib/shapes/frame/frameHelpers.js.map +2 -2
  11. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +3 -2
  12. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +2 -2
  13. package/dist-cjs/lib/ui/TldrawUi.js +2 -3
  14. package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
  15. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +0 -3
  16. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
  17. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js +1 -1
  18. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js.map +2 -2
  19. package/dist-cjs/lib/ui/components/Dialogs.js +8 -0
  20. package/dist-cjs/lib/ui/components/Dialogs.js.map +2 -2
  21. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js +1 -2
  22. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js.map +2 -2
  23. package/dist-cjs/lib/ui/context/actions.js +0 -1
  24. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  25. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +2 -0
  26. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +2 -2
  27. package/dist-cjs/lib/ui/hooks/useFlatten.js +42 -23
  28. package/dist-cjs/lib/ui/hooks/useFlatten.js.map +2 -2
  29. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js +5 -17
  30. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js.map +2 -2
  31. package/dist-cjs/lib/ui/kbd-utils.js +22 -3
  32. package/dist-cjs/lib/ui/kbd-utils.js.map +2 -2
  33. package/dist-cjs/lib/ui/version.js +3 -3
  34. package/dist-cjs/lib/ui/version.js.map +1 -1
  35. package/dist-esm/index.d.mts +264 -275
  36. package/dist-esm/index.mjs +1 -1
  37. package/dist-esm/lib/defaultExternalContentHandlers.mjs +2 -2
  38. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  39. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs +2 -2
  40. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs.map +2 -2
  41. package/dist-esm/lib/shapes/frame/frameHelpers.mjs +3 -0
  42. package/dist-esm/lib/shapes/frame/frameHelpers.mjs.map +2 -2
  43. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +3 -2
  44. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +2 -2
  45. package/dist-esm/lib/ui/TldrawUi.mjs +2 -3
  46. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  47. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +2 -5
  48. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
  49. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs +1 -1
  50. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs.map +2 -2
  51. package/dist-esm/lib/ui/components/Dialogs.mjs +9 -1
  52. package/dist-esm/lib/ui/components/Dialogs.mjs.map +2 -2
  53. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs +1 -2
  54. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs.map +2 -2
  55. package/dist-esm/lib/ui/context/actions.mjs +0 -1
  56. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  57. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +2 -0
  58. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +2 -2
  59. package/dist-esm/lib/ui/hooks/useFlatten.mjs +42 -23
  60. package/dist-esm/lib/ui/hooks/useFlatten.mjs.map +2 -2
  61. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs +5 -17
  62. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs.map +2 -2
  63. package/dist-esm/lib/ui/kbd-utils.mjs +22 -3
  64. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  65. package/dist-esm/lib/ui/version.mjs +3 -3
  66. package/dist-esm/lib/ui/version.mjs.map +1 -1
  67. package/package.json +6 -6
  68. package/src/lib/defaultExternalContentHandlers.ts +2 -2
  69. package/src/lib/shapes/draw/DrawShapeTool.test.ts +19 -0
  70. package/src/lib/shapes/draw/toolStates/Drawing.ts +2 -2
  71. package/src/lib/shapes/frame/frameHelpers.ts +10 -1
  72. package/src/lib/shapes/video/VideoShapeUtil.tsx +4 -2
  73. package/src/lib/ui/TldrawUi.tsx +1 -2
  74. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +2 -6
  75. package/src/lib/ui/components/ContextMenu/DefaultContextMenuContent.tsx +5 -1
  76. package/src/lib/ui/components/Dialogs.tsx +17 -1
  77. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx +2 -3
  78. package/src/lib/ui/context/actions.tsx +0 -1
  79. package/src/lib/ui/hooks/useClipboardEvents.ts +2 -0
  80. package/src/lib/ui/hooks/useFlatten.ts +56 -23
  81. package/src/lib/ui/hooks/useKeyboardShortcuts.ts +5 -23
  82. package/src/lib/ui/kbd-utils.test.ts +11 -0
  83. package/src/lib/ui/kbd-utils.ts +27 -4
  84. package/src/lib/ui/version.ts +3 -3
  85. package/src/lib/ui.css +7 -16
  86. package/src/test/TldrawEditor.test.tsx +3 -0
  87. package/src/test/VideoShapeUtil.test.tsx +126 -0
  88. package/src/test/bookmark-shapes.test.ts +110 -0
  89. package/src/test/commands/alignShapes.test.tsx +19 -10
  90. package/src/test/commands/clipboard.test.ts +36 -1
  91. package/src/test/flatten.test.ts +135 -0
  92. package/src/test/modifiers.test.ts +104 -0
  93. package/tldraw.css +7 -16
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/ui/hooks/useClipboardEvents.ts"],
4
- "sourcesContent": ["import {\n\tEditor,\n\tFileHelpers,\n\tTLClipboardWriteInfo,\n\tTLExternalContentSource,\n\tVecLike,\n\tactiveElementShouldCaptureKeys,\n\tassert,\n\tcompact,\n\tgetGlobalDocument,\n\tisDefined,\n\tpreventDefault,\n\tuniq,\n\tuseEditor,\n\tuseMaybeEditor,\n\tuseValue,\n} from '@tldraw/editor'\nimport lz from 'lz-string'\nimport { useCallback, useEffect } from 'react'\nimport { defaultHandleExternalTextContent } from '../../defaultExternalContentHandlers'\nimport { TLDRAW_CUSTOM_PNG_MIME_TYPE, getCanonicalClipboardReadType } from '../../utils/clipboard'\nimport { TLUiEventSource, useUiEvents } from '../context/events'\nimport { pasteFiles } from './clipboard/pasteFiles'\nimport { pasteUrl } from './clipboard/pasteUrl'\n\n/**\n * Resolves paste modifier keys into plain-text and position behavior.\n * Alt/Option inverts the paste-at-cursor user preference.\n *\n * @param isShift - Whether the Shift key is pressed (indicates plain text paste)\n * @param isAlt - Whether the Alt/Option key is pressed (inverts paste position preference)\n * @param pasteAtCursorPref - The user's preference for pasting at the cursor (true) or center (false)\n *\n * @internal\n */\nexport function resolvePasteModifiers(\n\tisShift: boolean,\n\tisAlt: boolean,\n\tpasteAtCursorPref: boolean\n) {\n\treturn {\n\t\tisPlainText: isShift,\n\t\tpasteAtCursor: isAlt ? !pasteAtCursorPref : pasteAtCursorPref,\n\t}\n}\n\n// Expected paste mime types. The earlier in this array they appear, the higher preference we give\n// them. For example, we prefer the `web image/png+tldraw` type to plain `image/png` as it does not\n// strip some of the extra metadata we write into it.\nconst expectedPasteFileMimeTypes = [\n\tTLDRAW_CUSTOM_PNG_MIME_TYPE,\n\t'image/png',\n\t'image/jpeg',\n\t'image/webp',\n\t'image/svg+xml',\n] satisfies string[]\n\n/**\n * Strip HTML tags from a string.\n * @param html - The HTML to strip.\n * @internal\n */\nfunction stripHtml(html: string) {\n\t// See <https://github.com/developit/preact-markup/blob/4788b8d61b4e24f83688710746ee36e7464f7bbc/src/parse-markup.js#L60-L69>\n\tconst doc = getGlobalDocument().implementation.createHTMLDocument('')\n\tdoc.documentElement.innerHTML = html.trim()\n\treturn doc.body.textContent || doc.body.innerText || ''\n}\n\n/**\n * Extract iframe src and dimensions from an HTML string containing an iframe element.\n * Tries width/height HTML attributes first, then falls back to pixel values in the\n * style attribute, then to sensible defaults.\n * Returns null if no valid iframe is found.\n * @internal\n */\nexport function extractIframeFromHtml(\n\thtml: string\n): { src: string; width: number; height: number } | null {\n\tif (!html.includes('<iframe')) return null\n\tconst doc = new DOMParser().parseFromString(html, 'text/html')\n\tconst iframe = doc.querySelector('iframe')\n\tif (!iframe) return null\n\tconst src = iframe.getAttribute('src')\n\tif (!src || !isValidHttpURL(src)) return null\n\n\tconst attrWidth = parseInt(iframe.getAttribute('width') || '', 10)\n\tconst attrHeight = parseInt(iframe.getAttribute('height') || '', 10)\n\n\tlet styleWidth = NaN\n\tlet styleHeight = NaN\n\tconst style = iframe.getAttribute('style')\n\tif (style) {\n\t\tconst wMatch = style.match(/\\bwidth:\\s*(\\d+)px/)\n\t\tconst hMatch = style.match(/\\bheight:\\s*(\\d+)px/)\n\t\tif (wMatch) styleWidth = parseInt(wMatch[1], 10)\n\t\tif (hMatch) styleHeight = parseInt(hMatch[1], 10)\n\t}\n\n\tconst width = attrWidth || styleWidth || 425\n\tconst height = attrHeight || styleHeight || 350\n\treturn { src, width, height }\n}\n\n/** @public */\nexport function isValidHttpURL(url: string) {\n\ttry {\n\t\tconst u = new URL(url)\n\t\treturn u.protocol === 'http:' || u.protocol === 'https:'\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/** @public */\nconst getValidHttpURLList = (url: string) => {\n\tconst urls = url.split(/[\\n\\s]/)\n\tfor (const url of urls) {\n\t\ttry {\n\t\t\tconst u = new URL(url)\n\t\t\tif (!(u.protocol === 'http:' || u.protocol === 'https:')) {\n\t\t\t\treturn\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t}\n\treturn uniq(urls)\n}\n\n/** @public */\nconst isSvgText = (text: string) => {\n\treturn /^<svg/.test(text)\n}\n\n/**\n * Get whether to disallow clipboard events.\n *\n * @internal\n */\nfunction areShortcutsDisabled(editor: Editor) {\n\treturn (\n\t\teditor.menus.hasAnyOpenMenus() ||\n\t\tactiveElementShouldCaptureKeys(false, editor.getContainerDocument())\n\t)\n}\n\nimport { putPastedExternalContent } from './clipboard/putPastedContent'\nexport { putPastedExternalContent } from './clipboard/putPastedContent'\n\n/**\n * Handle text pasted into the editor.\n * @param editor - The editor instance.\n * @param data - The text to paste.\n * @param point - The point at which to paste the text.\n * @internal\n */\nconst handleText = (\n\teditor: Editor,\n\tdata: string,\n\tpoint?: VecLike,\n\tsources?: TLExternalContentSource[],\n\tclipboardPasteSource: 'native-event' | 'clipboard-read' = 'native-event'\n) => {\n\tconst validUrlList = getValidHttpURLList(data)\n\tif (validUrlList) {\n\t\tfor (const url of validUrlList) {\n\t\t\tpasteUrl(editor, url, point, sources, clipboardPasteSource)\n\t\t}\n\t} else if (isValidHttpURL(data)) {\n\t\tpasteUrl(editor, data, point, sources, clipboardPasteSource)\n\t} else if (isSvgText(data)) {\n\t\teditor.markHistoryStoppingPoint('paste')\n\t\tputPastedExternalContent(\n\t\t\teditor,\n\t\t\t{\n\t\t\t\ttype: 'svg-text',\n\t\t\t\ttext: data,\n\t\t\t\tpoint,\n\t\t\t\tsources,\n\t\t\t},\n\t\t\t{ source: clipboardPasteSource, point }\n\t\t)\n\t} else {\n\t\teditor.markHistoryStoppingPoint('paste')\n\t\tputPastedExternalContent(\n\t\t\teditor,\n\t\t\t{\n\t\t\t\ttype: 'text',\n\t\t\t\ttext: data,\n\t\t\t\tpoint,\n\t\t\t\tsources,\n\t\t\t},\n\t\t\t{ source: clipboardPasteSource, point }\n\t\t)\n\t}\n}\n\n/**\n * Something found on the clipboard, either through the event's clipboard data or the browser's clipboard API.\n * @internal\n */\ntype ClipboardThing =\n\t| {\n\t\t\ttype: 'file'\n\t\t\tsource: Promise<File | null>\n\t }\n\t| {\n\t\t\ttype: 'blob'\n\t\t\tsource: Promise<Blob | null>\n\t }\n\t| {\n\t\t\ttype: 'url'\n\t\t\tsource: Promise<string>\n\t }\n\t| {\n\t\t\ttype: 'html'\n\t\t\tsource: Promise<string>\n\t }\n\t| {\n\t\t\ttype: 'text'\n\t\t\tsource: Promise<string>\n\t }\n\t| {\n\t\t\ttype: string\n\t\t\tsource: Promise<string>\n\t }\n\n/**\n * Handle a paste using event clipboard data. This is the \"original\"\n * paste method that uses the clipboard data from the paste event.\n * https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent/clipboardData\n *\n * @param editor - The editor\n * @param clipboardData - The clipboard data\n * @param point - The point to paste at\n * @internal\n */\nexport async function handlePasteFromEventClipboardData(\n\teditor: Editor,\n\tclipboardData: DataTransfer,\n\tpoint?: VecLike\n) {\n\t// Do not paste while in any editing state\n\tif (editor.getEditingShapeId() !== null) return\n\n\tif (!clipboardData) {\n\t\tthrow Error('No clipboard data')\n\t}\n\n\tconst things: ClipboardThing[] = []\n\n\tfor (const item of Object.values(clipboardData.items)) {\n\t\tswitch (item.kind) {\n\t\t\tcase 'file': {\n\t\t\t\t// files are always blobs\n\t\t\t\tthings.push({\n\t\t\t\t\ttype: 'file',\n\t\t\t\t\tsource: new Promise((r) => r(item.getAsFile())) as Promise<File | null>,\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'string': {\n\t\t\t\t// strings can be text or html\n\t\t\t\tif (item.type === 'text/html') {\n\t\t\t\t\tthings.push({\n\t\t\t\t\t\ttype: 'html',\n\t\t\t\t\t\tsource: new Promise((r) => item.getAsString(r)) as Promise<string>,\n\t\t\t\t\t})\n\t\t\t\t} else if (item.type === 'text/plain') {\n\t\t\t\t\tthings.push({\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\tsource: new Promise((r) => item.getAsString(r)) as Promise<string>,\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tthings.push({ type: item.type, source: new Promise((r) => item.getAsString(r)) })\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn await handleClipboardThings(editor, things, point, 'native-event')\n}\n\n/**\n * Handle a paste using items retrieved from the Clipboard API.\n * https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem\n *\n * @param editor - The editor\n * @param clipboardItems - The clipboard items to handle\n * @param point - The point to paste at\n * @internal\n */\nexport async function handlePasteFromClipboardApi({\n\teditor,\n\tclipboardItems,\n\tpoint,\n\tfallbackFiles,\n\tclipboardPasteSource,\n}: {\n\teditor: Editor\n\tclipboardItems: ClipboardItem[]\n\tpoint?: VecLike\n\tfallbackFiles?: File[]\n\tclipboardPasteSource: 'native-event' | 'clipboard-read'\n}) {\n\t// We need to populate the array of clipboard things\n\t// based on the ClipboardItems from the Clipboard API.\n\t// This is done in a different way than when using\n\t// the clipboard data from the paste event.\n\n\tconst things: ClipboardThing[] = []\n\n\tfor (const item of clipboardItems) {\n\t\tconst matchingTypes = expectedPasteFileMimeTypes.filter((t) => item.types.includes(t))\n\t\tif (matchingTypes.length > 0) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'blob',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tfor (const type of matchingTypes) {\n\t\t\t\t\t\tconst blob = await item.getType(type)\n\t\t\t\t\t\t// Chrome 147 stable regression: web custom-format blobs come back as\n\t\t\t\t\t\t// 0 bytes when clipboard.read() runs inside a paste event. Fixed in\n\t\t\t\t\t\t// Chrome Canary; expected to ship to stable in a later release. Until\n\t\t\t\t\t\t// then, skip empty payloads and fall back to the next preferred type\n\t\t\t\t\t\t// (usually image/png, which means Cmd+V paste of a tldraw-copied PNG\n\t\t\t\t\t\t// loses the pHYs DPI chunk and pastes at 2x size on affected Chrome\n\t\t\t\t\t\t// stable versions). Right-click Paste continues to work at 1x because\n\t\t\t\t\t\t// that path calls clipboard.read() from a click handler, not a paste\n\t\t\t\t\t\t// event. Remove this workaround when the fix ships to stable.\n\t\t\t\t\t\t// https://issues.chromium.org/issues/505045934\n\t\t\t\t\t\tif (blob.size === 0) continue\n\t\t\t\t\t\treturn FileHelpers.rewriteMimeType(blob, getCanonicalClipboardReadType(type))\n\t\t\t\t\t}\n\t\t\t\t\treturn null\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\n\t\tif (item.types.includes('text/html')) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'html',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tconst blob = await item.getType('text/html')\n\t\t\t\t\treturn await FileHelpers.blobToText(blob)\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\n\t\tif (item.types.includes('text/uri-list')) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'url',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tconst blob = await item.getType('text/uri-list')\n\t\t\t\t\treturn await FileHelpers.blobToText(blob)\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\n\t\tif (item.types.includes('text/plain')) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'text',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tconst blob = await item.getType('text/plain')\n\t\t\t\t\treturn await FileHelpers.blobToText(blob)\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\t}\n\n\tif (fallbackFiles?.length && things.length === 1 && things[0].type === 'text') {\n\t\tthings.pop()\n\t\tthings.push(\n\t\t\t...fallbackFiles.map((f): ClipboardThing => ({ type: 'file', source: Promise.resolve(f) }))\n\t\t)\n\t} else if (fallbackFiles?.length && things.length === 0) {\n\t\t// Files pasted in Safari from your computer don't have types, so we need to use the fallback files directly\n\t\t// if they're available. This only works if pasted keyboard shortcuts. Pasting from the menu in Safari seems to never\n\t\t// let you access files that are copied from your computer.\n\t\tthings.push(\n\t\t\t...fallbackFiles.map((f): ClipboardThing => ({ type: 'file', source: Promise.resolve(f) }))\n\t\t)\n\t}\n\n\treturn await handleClipboardThings(editor, things, point, clipboardPasteSource)\n}\n\nasync function handleClipboardThings(\n\teditor: Editor,\n\tthings: ClipboardThing[],\n\tpoint: VecLike | undefined,\n\tclipboardPasteSource: 'native-event' | 'clipboard-read'\n) {\n\t// 1. Generate clipboard results for non-file things\n\t//\n\t// Getting the source from the items is async, however they must be accessed syncronously;\n\t// we can't await them in a loop. So we'll map them to promises and await them all at once,\n\t// then make decisions based on what we find.\n\n\tconst results = await Promise.all<TLExternalContentSource>(\n\t\tthings\n\t\t\t.filter((t) => t.type !== 'file' && t.type !== 'blob')\n\t\t\t.map(\n\t\t\t\t(t) =>\n\t\t\t\t\tnew Promise((r) => {\n\t\t\t\t\t\tconst thing = t as Exclude<ClipboardThing, { type: 'file' } | { type: 'blob' }>\n\n\t\t\t\t\t\tconst parsed = thing.source.then((text) => {\n\t\t\t\t\t\t\t// first, see if we can find tldraw content, which is JSON inside of an html comment\n\t\t\t\t\t\t\tconst tldrawHtmlComment = text.match(/<div data-tldraw[^>]*>(.*)<\\/div>/)?.[1]\n\n\t\t\t\t\t\t\tif (tldrawHtmlComment) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t// First try parsing as plain JSON (version 2/3 formats)\n\t\t\t\t\t\t\t\t\tlet json\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tjson = JSON.parse(tldrawHtmlComment)\n\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\t// Fall back to LZ decompression (legacy format)\n\t\t\t\t\t\t\t\t\t\tconst jsonComment = lz.decompressFromBase64(tldrawHtmlComment)\n\t\t\t\t\t\t\t\t\t\tif (jsonComment === null) {\n\t\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\t\t\t\t\t\t\treason: `found tldraw data comment but could not parse`,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tjson = JSON.parse(jsonComment)\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif (json.type !== 'application/tldraw') {\n\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\tdata: json,\n\t\t\t\t\t\t\t\t\t\t\treason: `found tldraw data comment but JSON was of a different type: ${json.type}`,\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Handle versioned clipboard format\n\t\t\t\t\t\t\t\t\tif (json.version === 3) {\n\t\t\t\t\t\t\t\t\t\t// Version 3: Assets are plain, decompress only other data\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tconst otherData = JSON.parse(\n\t\t\t\t\t\t\t\t\t\t\t\tlz.decompressFromBase64(json.data.otherCompressed) || '{}'\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\tconst reconstructedData = {\n\t\t\t\t\t\t\t\t\t\t\t\tassets: json.data.assets || [],\n\t\t\t\t\t\t\t\t\t\t\t\t...otherData,\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tr({ type: 'tldraw', data: reconstructedData })\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\t\tdata: json,\n\t\t\t\t\t\t\t\t\t\t\t\treason: `failed to decompress version 2 clipboard data: ${error}`,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (json.version === 2) {\n\t\t\t\t\t\t\t\t\t\t// Version 2: Everything is plain, this had issues with encoding... :-/\n\t\t\t\t\t\t\t\t\t\t// TODO: nix this support after some time.\n\t\t\t\t\t\t\t\t\t\tr({ type: 'tldraw', data: json.data })\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t// Version 1 or no version: Legacy format\n\t\t\t\t\t\t\t\t\t\tif (typeof json.data === 'string') {\n\t\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\t\tdata: json,\n\t\t\t\t\t\t\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\t\t\t\t\t\t'found tldraw json but data was a string instead of a TLClipboardModel object',\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tr({ type: 'tldraw', data: json.data })\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\tdata: tldrawHtmlComment,\n\t\t\t\t\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\t\t\t\t'found tldraw json but data was a string instead of a TLClipboardModel object',\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (thing.type === 'html') {\n\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'html' })\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (thing.type === 'url') {\n\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'url' })\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// if we have not found a tldraw comment, Otherwise, try to parse the text as JSON directly.\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst json = JSON.parse(text)\n\t\t\t\t\t\t\t\t\tif (json.type === 'excalidraw/clipboard') {\n\t\t\t\t\t\t\t\t\t\t// If the clipboard contains content copied from excalidraw, then paste that\n\t\t\t\t\t\t\t\t\t\tr({ type: 'excalidraw', data: json })\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'json' })\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t// If we could not parse the text as JSON, then it's just text\n\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'text' })\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tr({ type: 'error', data: text, reason: 'unhandled case' })\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\t// If we can't read one of the clipboard items (e.g. the browser rejects a\n\t\t\t\t\t\t// getType call), or the handler above throws, resolve this thing to an\n\t\t\t\t\t\t// error source rather than hanging the whole paste.\n\t\t\t\t\t\tparsed.catch((error) => {\n\t\t\t\t\t\t\tr({ type: 'error', data: null, reason: `error reading clipboard data: ${error}` })\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t)\n\t)\n\n\t// 2. Handle files\n\t//\n\t// We need to handle files separately because if we want them to\n\t// be placed next to each other, we need to create them all at once.\n\t// We pass along the other clipboard sources (e.g. text and html found\n\t// on the clipboard next to an image) so that external content handlers\n\t// can make use of them.\n\n\tconst files = things.filter((t) => t.type === 'file' || t.type === 'blob') as Extract<\n\t\tClipboardThing,\n\t\t{ type: 'file' } | { type: 'blob' }\n\t>[]\n\n\t// Paste the files, carrying the other sources along with them\n\tif (files.length) {\n\t\tif (files.length > editor.options.maxFilesAtOnce) {\n\t\t\tthrow Error('Too many files')\n\t\t}\n\t\tconst fileBlobs = compact(await Promise.all(files.map((t) => t.source)))\n\t\treturn await pasteFiles(editor, fileBlobs, point, results, clipboardPasteSource)\n\t}\n\n\t// 3.\n\t//\n\t// Now that we know what kind of stuff we're dealing with, we can actual create some content.\n\t// There are priorities here, so order matters: we've already handled images and files, which\n\t// take first priority; then we want to handle tldraw content, then excalidraw content, then\n\t// html content, then links, and finally text content.\n\n\t// Try to paste tldraw content\n\tfor (const result of results) {\n\t\tif (result.type === 'tldraw') {\n\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\tputPastedExternalContent(\n\t\t\t\teditor,\n\t\t\t\t{ type: 'tldraw', content: result.data, point },\n\t\t\t\t{ source: clipboardPasteSource, point }\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Try to paste excalidraw content\n\tfor (const result of results) {\n\t\tif (result.type === 'excalidraw') {\n\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\tputPastedExternalContent(\n\t\t\t\teditor,\n\t\t\t\t{ type: 'excalidraw', content: result.data, point },\n\t\t\t\t{ source: clipboardPasteSource, point }\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Try to paste html content\n\tfor (const result of results) {\n\t\tif (result.type === 'text' && result.subtype === 'html') {\n\t\t\tconst rootNode = new DOMParser().parseFromString(result.data, 'text/html')\n\t\t\tconst bodyNode = rootNode.querySelector('body')\n\n\t\t\t// Check for iframe embeds in HTML before stripping content\n\t\t\tconst iframeInfo = extractIframeFromHtml(result.data)\n\t\t\tif (iframeInfo) {\n\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\teditor.putExternalContent({\n\t\t\t\t\ttype: 'embed',\n\t\t\t\t\turl: iframeInfo.src,\n\t\t\t\t\tpoint,\n\t\t\t\t\tembed: {\n\t\t\t\t\t\twidth: iframeInfo.width,\n\t\t\t\t\t\theight: iframeInfo.height,\n\t\t\t\t\t\tdoesResize: true,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// try to find a link\n\n\t\t\t// Edge on Windows 11 home appears to paste a link as a single <a/> in\n\t\t\t// the HTML document. If we're pasting a single like tag we'll just\n\t\t\t// assume the user meant to paste the URL.\n\t\t\tconst isHtmlSingleLink =\n\t\t\t\tbodyNode &&\n\t\t\t\tArray.from(bodyNode.children).filter((el) => el.nodeType === 1).length === 1 &&\n\t\t\t\tbodyNode.firstElementChild &&\n\t\t\t\tbodyNode.firstElementChild.tagName === 'A' &&\n\t\t\t\tbodyNode.firstElementChild.hasAttribute('href') &&\n\t\t\t\tbodyNode.firstElementChild.getAttribute('href') !== ''\n\n\t\t\tif (isHtmlSingleLink) {\n\t\t\t\tconst href = bodyNode.firstElementChild.getAttribute('href')!\n\t\t\t\thandleText(editor, href, point, results, clipboardPasteSource)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If the html is NOT a link, and we have NO OTHER texty content, then paste the html as text\n\t\t\tif (!results.some((r) => r.type === 'text' && r.subtype !== 'html') && result.data.trim()) {\n\t\t\t\tconst html = stripHtml(result.data) ?? ''\n\t\t\t\tif (html) {\n\t\t\t\t\thandleText(editor, stripHtml(result.data), point, results, clipboardPasteSource)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If the html is NOT a link, and we have other texty content, then paste the html as a text shape\n\t\t\tif (results.some((r) => r.type === 'text' && r.subtype !== 'html')) {\n\t\t\t\tconst html = stripHtml(result.data) ?? ''\n\t\t\t\tif (html) {\n\t\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\t\tputPastedExternalContent(\n\t\t\t\t\t\teditor,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t\thtml: result.data,\n\t\t\t\t\t\t\tpoint,\n\t\t\t\t\t\t\tsources: results,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ source: clipboardPasteSource, point }\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Allow pasting any <iframe> embed code onto the canvas.\n\t\t// Extracts the iframe src and dimensions, then creates an embed shape.\n\t\tif (result.type === 'text' && result.subtype === 'text') {\n\t\t\tconst iframeInfo = extractIframeFromHtml(result.data)\n\t\t\tif (iframeInfo) {\n\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\teditor.putExternalContent({\n\t\t\t\t\ttype: 'embed',\n\t\t\t\t\turl: iframeInfo.src,\n\t\t\t\t\tpoint,\n\t\t\t\t\tembed: {\n\t\t\t\t\t\twidth: iframeInfo.width,\n\t\t\t\t\t\theight: iframeInfo.height,\n\t\t\t\t\t\tdoesResize: true,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// Try to paste a link\n\tfor (const result of results) {\n\t\tif (result.type === 'text' && result.subtype === 'url') {\n\t\t\tpasteUrl(editor, result.data, point, results, clipboardPasteSource)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Finally, if we haven't bailed on anything yet, we can paste text content\n\tfor (const result of results) {\n\t\tif (result.type === 'text' && result.subtype === 'text' && result.data.trim()) {\n\t\t\t// The clipboard may include multiple text items, but we only want to paste the first one\n\t\t\thandleText(editor, result.data, point, results, clipboardPasteSource)\n\t\t\treturn\n\t\t}\n\t}\n}\n\n/**\n * When the user copies or cuts, write the contents to the clipboard.\n *\n * @public\n */\nexport async function handleNativeOrMenuCopy(\n\teditor: Editor,\n\tcontext: TLClipboardWriteInfo = { operation: 'copy', source: 'menu' }\n): Promise<boolean> {\n\tconst nav = editor.getContainerWindow().navigator\n\tlet content = await editor.resolveAssetsInContent(\n\t\teditor.getContentFromCurrentPage(editor.getSelectedShapeIds())\n\t)\n\tif (!content) {\n\t\tnav?.clipboard?.writeText?.('')\n\t\treturn true\n\t}\n\n\tif (editor.options.onBeforeCopyToClipboard) {\n\t\tconst result = await editor.options.onBeforeCopyToClipboard({ editor, content, ...context })\n\t\tif (result === false) return false\n\t\tif (result != null) content = result\n\t}\n\n\t// Use versioned clipboard format for better compression\n\t// Version 3: Don't compress assets, only compress other data\n\tconst { assets, ...otherData } = content\n\tconst clipboardData = {\n\t\ttype: 'application/tldraw',\n\t\tkind: 'content',\n\t\tversion: 3,\n\t\tdata: {\n\t\t\tassets: assets || [], // Plain JSON, no compression\n\t\t\totherCompressed: lz.compressToBase64(JSON.stringify(otherData)), // Only compress non-asset data\n\t\t},\n\t}\n\n\t// Don't compress the final structure - just use plain JSON\n\tconst stringifiedClipboard = JSON.stringify(clipboardData)\n\n\tif (typeof nav === 'undefined') {\n\t\treturn true\n\t}\n\n\t// Extract the text from the clipboard\n\tconst textItems = content.shapes\n\t\t.map((shape) => {\n\t\t\tconst util = editor.getShapeUtil(shape)\n\t\t\treturn util.getText(shape)\n\t\t})\n\t\t.filter(isDefined)\n\n\tif (nav.clipboard?.write) {\n\t\tconst htmlBlob = new Blob([`<div data-tldraw>${stringifiedClipboard}</div>`], {\n\t\t\ttype: 'text/html',\n\t\t})\n\n\t\tlet textContent = textItems.join(' ')\n\n\t\t// This is a bug in chrome android where it won't paste content if\n\t\t// the text/plain content is \"\" so we need to always add an empty\n\t\t// space \uD83E\uDD2C\n\t\tif (textContent === '') {\n\t\t\ttextContent = ' '\n\t\t}\n\n\t\tconst CBI = editor.getContainerWindow().ClipboardItem\n\t\tnav.clipboard.write([\n\t\t\tnew CBI({\n\t\t\t\t'text/html': htmlBlob,\n\t\t\t\t// What is this second blob used for?\n\t\t\t\t'text/plain': new Blob([textContent], { type: 'text/plain' }),\n\t\t\t}),\n\t\t])\n\t} else if (nav.clipboard?.writeText) {\n\t\tnav.clipboard.writeText(`<div data-tldraw>${stringifiedClipboard}</div>`)\n\t}\n\treturn true\n}\n\n/** @public */\nexport function useMenuClipboardEvents() {\n\tconst editor = useMaybeEditor()\n\tconst trackEvent = useUiEvents()\n\n\tconst copy = useCallback(\n\t\tasync function onCopy(source: TLUiEventSource) {\n\t\t\tassert(editor, 'editor is required for copy')\n\t\t\tif (editor.getSelectedShapeIds().length === 0) return\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'copy', source: 'menu' })\n\t\t\tif (didCopy) {\n\t\t\t\ttrackEvent('copy', { source })\n\t\t\t}\n\t\t},\n\t\t[editor, trackEvent]\n\t)\n\n\tconst cut = useCallback(\n\t\tasync function onCut(source: TLUiEventSource) {\n\t\t\tif (!editor) return\n\t\t\tif (editor.getSelectedShapeIds().length === 0) return\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'cut', source: 'menu' })\n\t\t\tif (didCopy) {\n\t\t\t\teditor.deleteShapes(editor.getSelectedShapeIds())\n\t\t\t\ttrackEvent('cut', { source })\n\t\t\t}\n\t\t},\n\t\t[editor, trackEvent]\n\t)\n\n\tconst paste = useCallback(\n\t\tasync function onPaste(\n\t\t\tdata: DataTransfer | ClipboardItem[],\n\t\t\tsource: TLUiEventSource,\n\t\t\tpoint?: VecLike\n\t\t) {\n\t\t\tif (!editor) return\n\t\t\t// If we're editing a shape, or we are focusing an editable input, then\n\t\t\t// we would want the user's paste interaction to go to that element or\n\t\t\t// input instead; e.g. when pasting text into a text shape's content\n\t\t\tif (editor.getEditingShapeId() !== null) return\n\n\t\t\tconst win = editor.getContainerWindow()\n\t\t\tif (Array.isArray(data) && data[0] instanceof win.ClipboardItem) {\n\t\t\t\tif (\n\t\t\t\t\teditor.options.onClipboardPasteRaw?.({\n\t\t\t\t\t\teditor,\n\t\t\t\t\t\tsource: 'clipboard-read',\n\t\t\t\t\t\tclipboardItems: data,\n\t\t\t\t\t\tpoint,\n\t\t\t\t\t}) === false\n\t\t\t\t) {\n\t\t\t\t\ttrackEvent('paste', { source: 'menu' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thandlePasteFromClipboardApi({\n\t\t\t\t\teditor,\n\t\t\t\t\tclipboardItems: data,\n\t\t\t\t\tpoint,\n\t\t\t\t\tclipboardPasteSource: 'clipboard-read',\n\t\t\t\t})\n\t\t\t\ttrackEvent('paste', { source: 'menu' })\n\t\t\t} else {\n\t\t\t\tconst nav = win.navigator\n\t\t\t\tnav.clipboard.read().then((clipboardItems) => {\n\t\t\t\t\tpaste(clipboardItems, source, point)\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\t[editor, trackEvent]\n\t)\n\n\treturn {\n\t\tcopy,\n\t\tcut,\n\t\tpaste,\n\t}\n}\n\n/** @public */\nexport function useNativeClipboardEvents() {\n\tconst editor = useEditor()\n\tconst ownerDocument = editor.getContainerDocument()\n\tconst trackEvent = useUiEvents()\n\n\tconst appIsFocused = useValue('editor.isFocused', () => editor.getInstanceState().isFocused, [\n\t\teditor,\n\t])\n\n\tuseEffect(() => {\n\t\tif (!appIsFocused) return\n\t\tconst copy = async (e: ClipboardEvent) => {\n\t\t\tif (\n\t\t\t\teditor.getSelectedShapeIds().length === 0 ||\n\t\t\t\teditor.getEditingShapeId() !== null ||\n\t\t\t\tareShortcutsDisabled(editor)\n\t\t\t) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpreventDefault(e)\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'copy', source: 'native' })\n\t\t\tif (didCopy) {\n\t\t\t\ttrackEvent('copy', { source: 'kbd' })\n\t\t\t}\n\t\t}\n\n\t\tasync function cut(e: ClipboardEvent) {\n\t\t\tif (\n\t\t\t\teditor.getSelectedShapeIds().length === 0 ||\n\t\t\t\teditor.getEditingShapeId() !== null ||\n\t\t\t\tareShortcutsDisabled(editor)\n\t\t\t) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpreventDefault(e)\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'cut', source: 'native' })\n\t\t\tif (didCopy) {\n\t\t\t\teditor.deleteShapes(editor.getSelectedShapeIds())\n\t\t\t\ttrackEvent('cut', { source: 'kbd' })\n\t\t\t}\n\t\t}\n\n\t\tlet disablingMiddleClickPaste = false\n\t\tconst pointerUpHandler = (e: PointerEvent) => {\n\t\t\tif (e.button === 1) {\n\t\t\t\t// middle mouse button\n\t\t\t\tdisablingMiddleClickPaste = true\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\tdisablingMiddleClickPaste = false\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Track native modifier state from the most recent keydown. We use this\n\t\t// instead of editor.inputs.getShiftKey() because the editor applies a\n\t\t// 150ms delay on modifier release (to prevent physical race conditions\n\t\t// with pointer events), which can cause false positives here.\n\t\tlet nativeShiftKey = false\n\t\tconst trackModifiers = (e: KeyboardEvent) => {\n\t\t\tnativeShiftKey = e.shiftKey\n\t\t}\n\n\t\tconst paste = (e: ClipboardEvent) => {\n\t\t\tif (disablingMiddleClickPaste) {\n\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If we're editing a shape, or we are focusing an editable input, then\n\t\t\t// we would want the user's paste interaction to go to that element or\n\t\t\t// input instead; e.g. when pasting text into a text shape's content\n\t\t\tif (editor.getEditingShapeId() !== null || areShortcutsDisabled(editor)) return\n\n\t\t\t// Cmd+Shift+V / Ctrl+Shift+V = paste as plain text (no formatting).\n\t\t\t// If there's no plain text on the clipboard (e.g., a copied PNG), fall\n\t\t\t// through to the normal paste handler so the file still gets pasted.\n\t\t\tif (nativeShiftKey) {\n\t\t\t\tconst text = e.clipboardData?.getData('text/plain')\n\t\t\t\tif (text?.trim()) {\n\t\t\t\t\tconst point = editor.user.getIsPasteAtCursorMode()\n\t\t\t\t\t\t? editor.inputs.getCurrentPagePoint()\n\t\t\t\t\t\t: editor.getViewportPageBounds().center\n\t\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\t\tdefaultHandleExternalTextContent(editor, { text, point })\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t\ttrackEvent('paste', { source: 'kbd' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Cmd+V: paste at center by default, or at cursor when the preference is on.\n\t\t\t// (Cmd+Option+V and Cmd+Shift+Option+V are handled as actions in actions.tsx\n\t\t\t// because the browser only fires paste events for Cmd+V and Cmd+Shift+V.)\n\t\t\tconst point = editor.user.getIsPasteAtCursorMode()\n\t\t\t\t? editor.inputs.getCurrentPagePoint()\n\t\t\t\t: undefined\n\n\t\t\tif (\n\t\t\t\teditor.options.onClipboardPasteRaw?.({\n\t\t\t\t\teditor,\n\t\t\t\t\tsource: 'native-event',\n\t\t\t\t\tevent: e,\n\t\t\t\t\tclipboardData: e.clipboardData,\n\t\t\t\t\tpoint,\n\t\t\t\t}) === false\n\t\t\t) {\n\t\t\t\tpreventDefault(e)\n\t\t\t\ttrackEvent('paste', { source: 'kbd' })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst pasteFromEvent = () => {\n\t\t\t\tif (e.clipboardData) {\n\t\t\t\t\thandlePasteFromEventClipboardData(editor, e.clipboardData, point)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// if we can read from the clipboard API, we want to try using that first. that allows\n\t\t\t// us to access most things, and doesn't strip out metadata added to tldraw's own\n\t\t\t// copy-as-png features - so copied shapes come back in at the correct size.\n\t\t\tconst win = editor.getContainerWindow()\n\t\t\tconst nav = win.navigator\n\t\t\tif (nav.clipboard?.read) {\n\t\t\t\t// We can't read files from the filesystem using the clipboard API though - they'll\n\t\t\t\t// just come in as the file names instead. So we'll use the clipboard event's files\n\t\t\t\t// as a fallback - if we only got text, but do have files, we use those instead.\n\t\t\t\tconst fallbackFiles = Array.from(e.clipboardData?.files || [])\n\t\t\t\tnav.clipboard.read().then(\n\t\t\t\t\t(clipboardItems) => {\n\t\t\t\t\t\tif (Array.isArray(clipboardItems) && clipboardItems[0] instanceof win.ClipboardItem) {\n\t\t\t\t\t\t\thandlePasteFromClipboardApi({\n\t\t\t\t\t\t\t\teditor,\n\t\t\t\t\t\t\t\tclipboardItems,\n\t\t\t\t\t\t\t\tpoint,\n\t\t\t\t\t\t\t\tfallbackFiles,\n\t\t\t\t\t\t\t\tclipboardPasteSource: 'native-event',\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\t// if reading from the clipboard fails, try to use the event clipboard data\n\t\t\t\t\t\tpasteFromEvent()\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tpasteFromEvent()\n\t\t\t}\n\n\t\t\tpreventDefault(e)\n\t\t\ttrackEvent('paste', { source: 'kbd' })\n\t\t}\n\n\t\townerDocument?.addEventListener('copy', copy)\n\t\townerDocument?.addEventListener('cut', cut)\n\t\townerDocument?.addEventListener('paste', paste)\n\t\townerDocument?.addEventListener('pointerup', pointerUpHandler)\n\t\townerDocument?.addEventListener('keydown', trackModifiers, true)\n\t\townerDocument?.addEventListener('keyup', trackModifiers, true)\n\n\t\treturn () => {\n\t\t\townerDocument?.removeEventListener('copy', copy)\n\t\t\townerDocument?.removeEventListener('cut', cut)\n\t\t\townerDocument?.removeEventListener('paste', paste)\n\t\t\townerDocument?.removeEventListener('pointerup', pointerUpHandler)\n\t\t\townerDocument?.removeEventListener('keydown', trackModifiers, true)\n\t\t\townerDocument?.removeEventListener('keyup', trackModifiers, true)\n\t\t}\n\t}, [editor, trackEvent, appIsFocused, ownerDocument])\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n\tEditor,\n\tFileHelpers,\n\tTLClipboardWriteInfo,\n\tTLExternalContentSource,\n\tVecLike,\n\tactiveElementShouldCaptureKeys,\n\tassert,\n\tcompact,\n\tgetGlobalDocument,\n\tisDefined,\n\tpreventDefault,\n\tuniq,\n\tuseEditor,\n\tuseMaybeEditor,\n\tuseValue,\n} from '@tldraw/editor'\nimport lz from 'lz-string'\nimport { useCallback, useEffect } from 'react'\nimport { defaultHandleExternalTextContent } from '../../defaultExternalContentHandlers'\nimport { TLDRAW_CUSTOM_PNG_MIME_TYPE, getCanonicalClipboardReadType } from '../../utils/clipboard'\nimport { TLUiEventSource, useUiEvents } from '../context/events'\nimport { pasteFiles } from './clipboard/pasteFiles'\nimport { pasteUrl } from './clipboard/pasteUrl'\n\n/**\n * Resolves paste modifier keys into plain-text and position behavior.\n * Alt/Option inverts the paste-at-cursor user preference.\n *\n * @param isShift - Whether the Shift key is pressed (indicates plain text paste)\n * @param isAlt - Whether the Alt/Option key is pressed (inverts paste position preference)\n * @param pasteAtCursorPref - The user's preference for pasting at the cursor (true) or center (false)\n *\n * @internal\n */\nexport function resolvePasteModifiers(\n\tisShift: boolean,\n\tisAlt: boolean,\n\tpasteAtCursorPref: boolean\n) {\n\treturn {\n\t\tisPlainText: isShift,\n\t\tpasteAtCursor: isAlt ? !pasteAtCursorPref : pasteAtCursorPref,\n\t}\n}\n\n// Expected paste mime types. The earlier in this array they appear, the higher preference we give\n// them. For example, we prefer the `web image/png+tldraw` type to plain `image/png` as it does not\n// strip some of the extra metadata we write into it.\nconst expectedPasteFileMimeTypes = [\n\tTLDRAW_CUSTOM_PNG_MIME_TYPE,\n\t'image/png',\n\t'image/jpeg',\n\t'image/webp',\n\t'image/svg+xml',\n] satisfies string[]\n\n/**\n * Strip HTML tags from a string.\n * @param html - The HTML to strip.\n * @internal\n */\nfunction stripHtml(html: string) {\n\t// See <https://github.com/developit/preact-markup/blob/4788b8d61b4e24f83688710746ee36e7464f7bbc/src/parse-markup.js#L60-L69>\n\tconst doc = getGlobalDocument().implementation.createHTMLDocument('')\n\tdoc.documentElement.innerHTML = html.trim()\n\treturn doc.body.textContent || doc.body.innerText || ''\n}\n\n/**\n * Extract iframe src and dimensions from an HTML string containing an iframe element.\n * Tries width/height HTML attributes first, then falls back to pixel values in the\n * style attribute, then to sensible defaults.\n * Returns null if no valid iframe is found.\n * @internal\n */\nexport function extractIframeFromHtml(\n\thtml: string\n): { src: string; width: number; height: number } | null {\n\tif (!html.includes('<iframe')) return null\n\tconst doc = new DOMParser().parseFromString(html, 'text/html')\n\tconst iframe = doc.querySelector('iframe')\n\tif (!iframe) return null\n\tconst src = iframe.getAttribute('src')\n\tif (!src || !isValidHttpURL(src)) return null\n\n\tconst attrWidth = parseInt(iframe.getAttribute('width') || '', 10)\n\tconst attrHeight = parseInt(iframe.getAttribute('height') || '', 10)\n\n\tlet styleWidth = NaN\n\tlet styleHeight = NaN\n\tconst style = iframe.getAttribute('style')\n\tif (style) {\n\t\tconst wMatch = style.match(/\\bwidth:\\s*(\\d+)px/)\n\t\tconst hMatch = style.match(/\\bheight:\\s*(\\d+)px/)\n\t\tif (wMatch) styleWidth = parseInt(wMatch[1], 10)\n\t\tif (hMatch) styleHeight = parseInt(hMatch[1], 10)\n\t}\n\n\tconst width = attrWidth || styleWidth || 425\n\tconst height = attrHeight || styleHeight || 350\n\treturn { src, width, height }\n}\n\n/** @public */\nexport function isValidHttpURL(url: string) {\n\ttry {\n\t\tconst u = new URL(url)\n\t\treturn u.protocol === 'http:' || u.protocol === 'https:'\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/** @public */\nconst getValidHttpURLList = (url: string) => {\n\tconst urls = url.split(/[\\n\\s]/)\n\tfor (const url of urls) {\n\t\ttry {\n\t\t\tconst u = new URL(url)\n\t\t\tif (!(u.protocol === 'http:' || u.protocol === 'https:')) {\n\t\t\t\treturn\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t}\n\treturn uniq(urls)\n}\n\n/** @public */\nconst isSvgText = (text: string) => {\n\treturn /^<svg/.test(text)\n}\n\n/**\n * Get whether to disallow clipboard events.\n *\n * @internal\n */\nfunction areShortcutsDisabled(editor: Editor) {\n\treturn (\n\t\teditor.menus.hasAnyOpenMenus() ||\n\t\tactiveElementShouldCaptureKeys(false, editor.getContainerDocument())\n\t)\n}\n\nimport { putPastedExternalContent } from './clipboard/putPastedContent'\nexport { putPastedExternalContent } from './clipboard/putPastedContent'\n\n/**\n * Handle text pasted into the editor.\n * @param editor - The editor instance.\n * @param data - The text to paste.\n * @param point - The point at which to paste the text.\n * @internal\n */\nconst handleText = (\n\teditor: Editor,\n\tdata: string,\n\tpoint?: VecLike,\n\tsources?: TLExternalContentSource[],\n\tclipboardPasteSource: 'native-event' | 'clipboard-read' = 'native-event'\n) => {\n\tconst validUrlList = getValidHttpURLList(data)\n\tif (validUrlList) {\n\t\tfor (const url of validUrlList) {\n\t\t\tpasteUrl(editor, url, point, sources, clipboardPasteSource)\n\t\t}\n\t} else if (isValidHttpURL(data)) {\n\t\tpasteUrl(editor, data, point, sources, clipboardPasteSource)\n\t} else if (isSvgText(data)) {\n\t\teditor.markHistoryStoppingPoint('paste')\n\t\tputPastedExternalContent(\n\t\t\teditor,\n\t\t\t{\n\t\t\t\ttype: 'svg-text',\n\t\t\t\ttext: data,\n\t\t\t\tpoint,\n\t\t\t\tsources,\n\t\t\t},\n\t\t\t{ source: clipboardPasteSource, point }\n\t\t)\n\t} else {\n\t\teditor.markHistoryStoppingPoint('paste')\n\t\tputPastedExternalContent(\n\t\t\teditor,\n\t\t\t{\n\t\t\t\ttype: 'text',\n\t\t\t\ttext: data,\n\t\t\t\tpoint,\n\t\t\t\tsources,\n\t\t\t},\n\t\t\t{ source: clipboardPasteSource, point }\n\t\t)\n\t}\n}\n\n/**\n * Something found on the clipboard, either through the event's clipboard data or the browser's clipboard API.\n * @internal\n */\ntype ClipboardThing =\n\t| {\n\t\t\ttype: 'file'\n\t\t\tsource: Promise<File | null>\n\t }\n\t| {\n\t\t\ttype: 'blob'\n\t\t\tsource: Promise<Blob | null>\n\t }\n\t| {\n\t\t\ttype: 'url'\n\t\t\tsource: Promise<string>\n\t }\n\t| {\n\t\t\ttype: 'html'\n\t\t\tsource: Promise<string>\n\t }\n\t| {\n\t\t\ttype: 'text'\n\t\t\tsource: Promise<string>\n\t }\n\t| {\n\t\t\ttype: string\n\t\t\tsource: Promise<string>\n\t }\n\n/**\n * Handle a paste using event clipboard data. This is the \"original\"\n * paste method that uses the clipboard data from the paste event.\n * https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent/clipboardData\n *\n * @param editor - The editor\n * @param clipboardData - The clipboard data\n * @param point - The point to paste at\n * @internal\n */\nexport async function handlePasteFromEventClipboardData(\n\teditor: Editor,\n\tclipboardData: DataTransfer,\n\tpoint?: VecLike\n) {\n\t// Do not paste while in any editing state\n\tif (editor.getEditingShapeId() !== null) return\n\n\tif (!clipboardData) {\n\t\tthrow Error('No clipboard data')\n\t}\n\n\tconst things: ClipboardThing[] = []\n\n\tfor (const item of Object.values(clipboardData.items)) {\n\t\tswitch (item.kind) {\n\t\t\tcase 'file': {\n\t\t\t\t// files are always blobs\n\t\t\t\tthings.push({\n\t\t\t\t\ttype: 'file',\n\t\t\t\t\tsource: new Promise((r) => r(item.getAsFile())) as Promise<File | null>,\n\t\t\t\t})\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'string': {\n\t\t\t\t// strings can be text or html\n\t\t\t\tif (item.type === 'text/html') {\n\t\t\t\t\tthings.push({\n\t\t\t\t\t\ttype: 'html',\n\t\t\t\t\t\tsource: new Promise((r) => item.getAsString(r)) as Promise<string>,\n\t\t\t\t\t})\n\t\t\t\t} else if (item.type === 'text/plain') {\n\t\t\t\t\tthings.push({\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\tsource: new Promise((r) => item.getAsString(r)) as Promise<string>,\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tthings.push({ type: item.type, source: new Promise((r) => item.getAsString(r)) })\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn await handleClipboardThings(editor, things, point, 'native-event')\n}\n\n/**\n * Handle a paste using items retrieved from the Clipboard API.\n * https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem\n *\n * @param editor - The editor\n * @param clipboardItems - The clipboard items to handle\n * @param point - The point to paste at\n * @internal\n */\nexport async function handlePasteFromClipboardApi({\n\teditor,\n\tclipboardItems,\n\tpoint,\n\tfallbackFiles,\n\tclipboardPasteSource,\n}: {\n\teditor: Editor\n\tclipboardItems: ClipboardItem[]\n\tpoint?: VecLike\n\tfallbackFiles?: File[]\n\tclipboardPasteSource: 'native-event' | 'clipboard-read'\n}) {\n\t// We need to populate the array of clipboard things\n\t// based on the ClipboardItems from the Clipboard API.\n\t// This is done in a different way than when using\n\t// the clipboard data from the paste event.\n\n\tconst things: ClipboardThing[] = []\n\n\tfor (const item of clipboardItems) {\n\t\tconst matchingTypes = expectedPasteFileMimeTypes.filter((t) => item.types.includes(t))\n\t\tif (matchingTypes.length > 0) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'blob',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tfor (const type of matchingTypes) {\n\t\t\t\t\t\tconst blob = await item.getType(type)\n\t\t\t\t\t\t// Chrome 147 stable regression: web custom-format blobs come back as\n\t\t\t\t\t\t// 0 bytes when clipboard.read() runs inside a paste event. Fixed in\n\t\t\t\t\t\t// Chrome Canary; expected to ship to stable in a later release. Until\n\t\t\t\t\t\t// then, skip empty payloads and fall back to the next preferred type\n\t\t\t\t\t\t// (usually image/png, which means Cmd+V paste of a tldraw-copied PNG\n\t\t\t\t\t\t// loses the pHYs DPI chunk and pastes at 2x size on affected Chrome\n\t\t\t\t\t\t// stable versions). Right-click Paste continues to work at 1x because\n\t\t\t\t\t\t// that path calls clipboard.read() from a click handler, not a paste\n\t\t\t\t\t\t// event. Remove this workaround when the fix ships to stable.\n\t\t\t\t\t\t// https://issues.chromium.org/issues/505045934\n\t\t\t\t\t\tif (blob.size === 0) continue\n\t\t\t\t\t\treturn FileHelpers.rewriteMimeType(blob, getCanonicalClipboardReadType(type))\n\t\t\t\t\t}\n\t\t\t\t\treturn null\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\n\t\tif (item.types.includes('text/html')) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'html',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tconst blob = await item.getType('text/html')\n\t\t\t\t\treturn await FileHelpers.blobToText(blob)\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\n\t\tif (item.types.includes('text/uri-list')) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'url',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tconst blob = await item.getType('text/uri-list')\n\t\t\t\t\treturn await FileHelpers.blobToText(blob)\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\n\t\tif (item.types.includes('text/plain')) {\n\t\t\tthings.push({\n\t\t\t\ttype: 'text',\n\t\t\t\tsource: (async () => {\n\t\t\t\t\tconst blob = await item.getType('text/plain')\n\t\t\t\t\treturn await FileHelpers.blobToText(blob)\n\t\t\t\t})(),\n\t\t\t})\n\t\t}\n\t}\n\n\tif (fallbackFiles?.length && things.length === 1 && things[0].type === 'text') {\n\t\tthings.pop()\n\t\tthings.push(\n\t\t\t...fallbackFiles.map((f): ClipboardThing => ({ type: 'file', source: Promise.resolve(f) }))\n\t\t)\n\t} else if (fallbackFiles?.length && things.length === 0) {\n\t\t// Files pasted in Safari from your computer don't have types, so we need to use the fallback files directly\n\t\t// if they're available. This only works if pasted keyboard shortcuts. Pasting from the menu in Safari seems to never\n\t\t// let you access files that are copied from your computer.\n\t\tthings.push(\n\t\t\t...fallbackFiles.map((f): ClipboardThing => ({ type: 'file', source: Promise.resolve(f) }))\n\t\t)\n\t}\n\n\treturn await handleClipboardThings(editor, things, point, clipboardPasteSource)\n}\n\nasync function handleClipboardThings(\n\teditor: Editor,\n\tthings: ClipboardThing[],\n\tpoint: VecLike | undefined,\n\tclipboardPasteSource: 'native-event' | 'clipboard-read'\n) {\n\t// 1. Generate clipboard results for non-file things\n\t//\n\t// Getting the source from the items is async, however they must be accessed syncronously;\n\t// we can't await them in a loop. So we'll map them to promises and await them all at once,\n\t// then make decisions based on what we find.\n\n\tconst results = await Promise.all<TLExternalContentSource>(\n\t\tthings\n\t\t\t.filter((t) => t.type !== 'file' && t.type !== 'blob')\n\t\t\t.map(\n\t\t\t\t(t) =>\n\t\t\t\t\tnew Promise((r) => {\n\t\t\t\t\t\tconst thing = t as Exclude<ClipboardThing, { type: 'file' } | { type: 'blob' }>\n\n\t\t\t\t\t\tconst parsed = thing.source.then((text) => {\n\t\t\t\t\t\t\t// first, see if we can find tldraw content, which is JSON inside of an html comment\n\t\t\t\t\t\t\tconst tldrawHtmlComment = text.match(/<div data-tldraw[^>]*>(.*)<\\/div>/)?.[1]\n\n\t\t\t\t\t\t\tif (tldrawHtmlComment) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t// First try parsing as plain JSON (version 2/3 formats)\n\t\t\t\t\t\t\t\t\tlet json\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tjson = JSON.parse(tldrawHtmlComment)\n\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\t// Fall back to LZ decompression (legacy format)\n\t\t\t\t\t\t\t\t\t\tconst jsonComment = lz.decompressFromBase64(tldrawHtmlComment)\n\t\t\t\t\t\t\t\t\t\tif (jsonComment === null) {\n\t\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\t\t\t\t\t\t\treason: `found tldraw data comment but could not parse`,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tjson = JSON.parse(jsonComment)\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif (json.type !== 'application/tldraw') {\n\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\tdata: json,\n\t\t\t\t\t\t\t\t\t\t\treason: `found tldraw data comment but JSON was of a different type: ${json.type}`,\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Handle versioned clipboard format\n\t\t\t\t\t\t\t\t\tif (json.version === 3) {\n\t\t\t\t\t\t\t\t\t\t// Version 3: Assets are plain, decompress only other data\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tconst otherData = JSON.parse(\n\t\t\t\t\t\t\t\t\t\t\t\tlz.decompressFromBase64(json.data.otherCompressed) || '{}'\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\tconst reconstructedData = {\n\t\t\t\t\t\t\t\t\t\t\t\tassets: json.data.assets || [],\n\t\t\t\t\t\t\t\t\t\t\t\t...otherData,\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tr({ type: 'tldraw', data: reconstructedData })\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\t\tdata: json,\n\t\t\t\t\t\t\t\t\t\t\t\treason: `failed to decompress version 2 clipboard data: ${error}`,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (json.version === 2) {\n\t\t\t\t\t\t\t\t\t\t// Version 2: Everything is plain, this had issues with encoding... :-/\n\t\t\t\t\t\t\t\t\t\t// TODO: nix this support after some time.\n\t\t\t\t\t\t\t\t\t\tr({ type: 'tldraw', data: json.data })\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t// Version 1 or no version: Legacy format\n\t\t\t\t\t\t\t\t\t\tif (typeof json.data === 'string') {\n\t\t\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\t\t\tdata: json,\n\t\t\t\t\t\t\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\t\t\t\t\t\t'found tldraw json but data was a string instead of a TLClipboardModel object',\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tr({ type: 'tldraw', data: json.data })\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tr({\n\t\t\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\t\t\tdata: tldrawHtmlComment,\n\t\t\t\t\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\t\t\t\t'found tldraw json but data was a string instead of a TLClipboardModel object',\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (thing.type === 'html') {\n\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'html' })\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (thing.type === 'url') {\n\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'url' })\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// if we have not found a tldraw comment, Otherwise, try to parse the text as JSON directly.\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst json = JSON.parse(text)\n\t\t\t\t\t\t\t\t\tif (json.type === 'excalidraw/clipboard') {\n\t\t\t\t\t\t\t\t\t\t// If the clipboard contains content copied from excalidraw, then paste that\n\t\t\t\t\t\t\t\t\t\tr({ type: 'excalidraw', data: json })\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'json' })\n\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t// If we could not parse the text as JSON, then it's just text\n\t\t\t\t\t\t\t\t\tr({ type: 'text', data: text, subtype: 'text' })\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tr({ type: 'error', data: text, reason: 'unhandled case' })\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\t// If we can't read one of the clipboard items (e.g. the browser rejects a\n\t\t\t\t\t\t// getType call), or the handler above throws, resolve this thing to an\n\t\t\t\t\t\t// error source rather than hanging the whole paste.\n\t\t\t\t\t\tparsed.catch((error) => {\n\t\t\t\t\t\t\tr({ type: 'error', data: null, reason: `error reading clipboard data: ${error}` })\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t)\n\t)\n\n\t// 2. Handle files\n\t//\n\t// We need to handle files separately because if we want them to\n\t// be placed next to each other, we need to create them all at once.\n\t// We pass along the other clipboard sources (e.g. text and html found\n\t// on the clipboard next to an image) so that external content handlers\n\t// can make use of them.\n\n\tconst files = things.filter((t) => t.type === 'file' || t.type === 'blob') as Extract<\n\t\tClipboardThing,\n\t\t{ type: 'file' } | { type: 'blob' }\n\t>[]\n\n\t// Paste the files, carrying the other sources along with them\n\tif (files.length) {\n\t\tif (files.length > editor.options.maxFilesAtOnce) {\n\t\t\tthrow Error('Too many files')\n\t\t}\n\t\tconst fileBlobs = compact(await Promise.all(files.map((t) => t.source)))\n\t\treturn await pasteFiles(editor, fileBlobs, point, results, clipboardPasteSource)\n\t}\n\n\t// 3.\n\t//\n\t// Now that we know what kind of stuff we're dealing with, we can actual create some content.\n\t// There are priorities here, so order matters: we've already handled images and files, which\n\t// take first priority; then we want to handle tldraw content, then excalidraw content, then\n\t// html content, then links, and finally text content.\n\n\t// Try to paste tldraw content\n\tfor (const result of results) {\n\t\tif (result.type === 'tldraw') {\n\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\tputPastedExternalContent(\n\t\t\t\teditor,\n\t\t\t\t{ type: 'tldraw', content: result.data, point },\n\t\t\t\t{ source: clipboardPasteSource, point }\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Try to paste excalidraw content\n\tfor (const result of results) {\n\t\tif (result.type === 'excalidraw') {\n\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\tputPastedExternalContent(\n\t\t\t\teditor,\n\t\t\t\t{ type: 'excalidraw', content: result.data, point },\n\t\t\t\t{ source: clipboardPasteSource, point }\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Try to paste html content\n\tfor (const result of results) {\n\t\tif (result.type === 'text' && result.subtype === 'html') {\n\t\t\tconst rootNode = new DOMParser().parseFromString(result.data, 'text/html')\n\t\t\tconst bodyNode = rootNode.querySelector('body')\n\n\t\t\t// Check for iframe embeds in HTML before stripping content\n\t\t\tconst iframeInfo = extractIframeFromHtml(result.data)\n\t\t\tif (iframeInfo) {\n\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\teditor.putExternalContent({\n\t\t\t\t\ttype: 'embed',\n\t\t\t\t\turl: iframeInfo.src,\n\t\t\t\t\tpoint,\n\t\t\t\t\tembed: {\n\t\t\t\t\t\twidth: iframeInfo.width,\n\t\t\t\t\t\theight: iframeInfo.height,\n\t\t\t\t\t\tdoesResize: true,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// try to find a link\n\n\t\t\t// Edge on Windows 11 home appears to paste a link as a single <a/> in\n\t\t\t// the HTML document. If we're pasting a single like tag we'll just\n\t\t\t// assume the user meant to paste the URL.\n\t\t\tconst isHtmlSingleLink =\n\t\t\t\tbodyNode &&\n\t\t\t\tArray.from(bodyNode.children).filter((el) => el.nodeType === 1).length === 1 &&\n\t\t\t\tbodyNode.firstElementChild &&\n\t\t\t\tbodyNode.firstElementChild.tagName === 'A' &&\n\t\t\t\tbodyNode.firstElementChild.hasAttribute('href') &&\n\t\t\t\tbodyNode.firstElementChild.getAttribute('href') !== ''\n\n\t\t\tif (isHtmlSingleLink) {\n\t\t\t\tconst href = bodyNode.firstElementChild.getAttribute('href')!\n\t\t\t\thandleText(editor, href, point, results, clipboardPasteSource)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If the html is NOT a link, and we have NO OTHER texty content, then paste the html as text\n\t\t\tif (!results.some((r) => r.type === 'text' && r.subtype !== 'html') && result.data.trim()) {\n\t\t\t\tconst html = stripHtml(result.data) ?? ''\n\t\t\t\tif (html) {\n\t\t\t\t\thandleText(editor, stripHtml(result.data), point, results, clipboardPasteSource)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If the html is NOT a link, and we have other texty content, then paste the html as a text shape\n\t\t\tif (results.some((r) => r.type === 'text' && r.subtype !== 'html')) {\n\t\t\t\tconst html = stripHtml(result.data) ?? ''\n\t\t\t\tif (html) {\n\t\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\t\tputPastedExternalContent(\n\t\t\t\t\t\teditor,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t\thtml: result.data,\n\t\t\t\t\t\t\tpoint,\n\t\t\t\t\t\t\tsources: results,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ source: clipboardPasteSource, point }\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Allow pasting any <iframe> embed code onto the canvas.\n\t\t// Extracts the iframe src and dimensions, then creates an embed shape.\n\t\tif (result.type === 'text' && result.subtype === 'text') {\n\t\t\tconst iframeInfo = extractIframeFromHtml(result.data)\n\t\t\tif (iframeInfo) {\n\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\teditor.putExternalContent({\n\t\t\t\t\ttype: 'embed',\n\t\t\t\t\turl: iframeInfo.src,\n\t\t\t\t\tpoint,\n\t\t\t\t\tembed: {\n\t\t\t\t\t\twidth: iframeInfo.width,\n\t\t\t\t\t\theight: iframeInfo.height,\n\t\t\t\t\t\tdoesResize: true,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// Try to paste a link\n\tfor (const result of results) {\n\t\tif (result.type === 'text' && result.subtype === 'url') {\n\t\t\tpasteUrl(editor, result.data, point, results, clipboardPasteSource)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Finally, if we haven't bailed on anything yet, we can paste text content\n\tfor (const result of results) {\n\t\tif (result.type === 'text' && result.subtype === 'text' && result.data.trim()) {\n\t\t\t// The clipboard may include multiple text items, but we only want to paste the first one\n\t\t\thandleText(editor, result.data, point, results, clipboardPasteSource)\n\t\t\treturn\n\t\t}\n\t}\n}\n\n/**\n * When the user copies or cuts, write the contents to the clipboard.\n *\n * @public\n */\nexport async function handleNativeOrMenuCopy(\n\teditor: Editor,\n\tcontext: TLClipboardWriteInfo = { operation: 'copy', source: 'menu' }\n): Promise<boolean> {\n\tconst nav = editor.getContainerWindow().navigator\n\tlet content = await editor.resolveAssetsInContent(\n\t\teditor.getContentFromCurrentPage(editor.getSelectedShapeIds())\n\t)\n\tif (!content) {\n\t\tnav?.clipboard?.writeText?.('')\n\t\treturn true\n\t}\n\n\tif (editor.options.onBeforeCopyToClipboard) {\n\t\tconst result = await editor.options.onBeforeCopyToClipboard({ editor, content, ...context })\n\t\tif (result === false) return false\n\t\tif (result != null) content = result\n\t}\n\n\t// Use versioned clipboard format for better compression\n\t// Version 3: Don't compress assets, only compress other data\n\tconst { assets, ...otherData } = content\n\tconst clipboardData = {\n\t\ttype: 'application/tldraw',\n\t\tkind: 'content',\n\t\tversion: 3,\n\t\tdata: {\n\t\t\tassets: assets || [], // Plain JSON, no compression\n\t\t\totherCompressed: lz.compressToBase64(JSON.stringify(otherData)), // Only compress non-asset data\n\t\t},\n\t}\n\n\t// Don't compress the final structure - just use plain JSON\n\tconst stringifiedClipboard = JSON.stringify(clipboardData)\n\n\tif (typeof nav === 'undefined') {\n\t\treturn true\n\t}\n\n\t// Extract the text from the clipboard\n\tconst textItems = content.shapes\n\t\t.map((shape) => {\n\t\t\tconst util = editor.getShapeUtil(shape)\n\t\t\treturn util.getText(shape)\n\t\t})\n\t\t.filter(isDefined)\n\n\tif (nav.clipboard?.write) {\n\t\tconst htmlBlob = new Blob([`<div data-tldraw>${stringifiedClipboard}</div>`], {\n\t\t\ttype: 'text/html',\n\t\t})\n\n\t\tlet textContent = textItems.join(' ')\n\n\t\t// This is a bug in chrome android where it won't paste content if\n\t\t// the text/plain content is \"\" so we need to always add an empty\n\t\t// space \uD83E\uDD2C\n\t\tif (textContent === '') {\n\t\t\ttextContent = ' '\n\t\t}\n\n\t\tconst CBI = editor.getContainerWindow().ClipboardItem\n\t\tnav.clipboard.write([\n\t\t\tnew CBI({\n\t\t\t\t'text/html': htmlBlob,\n\t\t\t\t// What is this second blob used for?\n\t\t\t\t'text/plain': new Blob([textContent], { type: 'text/plain' }),\n\t\t\t}),\n\t\t])\n\t} else if (nav.clipboard?.writeText) {\n\t\tnav.clipboard.writeText(`<div data-tldraw>${stringifiedClipboard}</div>`)\n\t}\n\treturn true\n}\n\n/** @public */\nexport function useMenuClipboardEvents() {\n\tconst editor = useMaybeEditor()\n\tconst trackEvent = useUiEvents()\n\n\tconst copy = useCallback(\n\t\tasync function onCopy(source: TLUiEventSource) {\n\t\t\tassert(editor, 'editor is required for copy')\n\t\t\tif (editor.getSelectedShapeIds().length === 0) return\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'copy', source: 'menu' })\n\t\t\tif (didCopy) {\n\t\t\t\ttrackEvent('copy', { source })\n\t\t\t}\n\t\t},\n\t\t[editor, trackEvent]\n\t)\n\n\tconst cut = useCallback(\n\t\tasync function onCut(source: TLUiEventSource) {\n\t\t\tif (!editor) return\n\t\t\tif (editor.getSelectedShapeIds().length === 0) return\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'cut', source: 'menu' })\n\t\t\tif (didCopy) {\n\t\t\t\teditor.markHistoryStoppingPoint('cut')\n\t\t\t\teditor.deleteShapes(editor.getSelectedShapeIds())\n\t\t\t\ttrackEvent('cut', { source })\n\t\t\t}\n\t\t},\n\t\t[editor, trackEvent]\n\t)\n\n\tconst paste = useCallback(\n\t\tasync function onPaste(\n\t\t\tdata: DataTransfer | ClipboardItem[],\n\t\t\tsource: TLUiEventSource,\n\t\t\tpoint?: VecLike\n\t\t) {\n\t\t\tif (!editor) return\n\t\t\t// If we're editing a shape, or we are focusing an editable input, then\n\t\t\t// we would want the user's paste interaction to go to that element or\n\t\t\t// input instead; e.g. when pasting text into a text shape's content\n\t\t\tif (editor.getEditingShapeId() !== null) return\n\n\t\t\tconst win = editor.getContainerWindow()\n\t\t\tif (Array.isArray(data) && data[0] instanceof win.ClipboardItem) {\n\t\t\t\tif (\n\t\t\t\t\teditor.options.onClipboardPasteRaw?.({\n\t\t\t\t\t\teditor,\n\t\t\t\t\t\tsource: 'clipboard-read',\n\t\t\t\t\t\tclipboardItems: data,\n\t\t\t\t\t\tpoint,\n\t\t\t\t\t}) === false\n\t\t\t\t) {\n\t\t\t\t\ttrackEvent('paste', { source: 'menu' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thandlePasteFromClipboardApi({\n\t\t\t\t\teditor,\n\t\t\t\t\tclipboardItems: data,\n\t\t\t\t\tpoint,\n\t\t\t\t\tclipboardPasteSource: 'clipboard-read',\n\t\t\t\t})\n\t\t\t\ttrackEvent('paste', { source: 'menu' })\n\t\t\t} else {\n\t\t\t\tconst nav = win.navigator\n\t\t\t\tnav.clipboard.read().then((clipboardItems) => {\n\t\t\t\t\tpaste(clipboardItems, source, point)\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\t[editor, trackEvent]\n\t)\n\n\treturn {\n\t\tcopy,\n\t\tcut,\n\t\tpaste,\n\t}\n}\n\n/** @public */\nexport function useNativeClipboardEvents() {\n\tconst editor = useEditor()\n\tconst ownerDocument = editor.getContainerDocument()\n\tconst trackEvent = useUiEvents()\n\n\tconst appIsFocused = useValue('editor.isFocused', () => editor.getInstanceState().isFocused, [\n\t\teditor,\n\t])\n\n\tuseEffect(() => {\n\t\tif (!appIsFocused) return\n\t\tconst copy = async (e: ClipboardEvent) => {\n\t\t\tif (\n\t\t\t\teditor.getSelectedShapeIds().length === 0 ||\n\t\t\t\teditor.getEditingShapeId() !== null ||\n\t\t\t\tareShortcutsDisabled(editor)\n\t\t\t) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpreventDefault(e)\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'copy', source: 'native' })\n\t\t\tif (didCopy) {\n\t\t\t\ttrackEvent('copy', { source: 'kbd' })\n\t\t\t}\n\t\t}\n\n\t\tasync function cut(e: ClipboardEvent) {\n\t\t\tif (\n\t\t\t\teditor.getSelectedShapeIds().length === 0 ||\n\t\t\t\teditor.getEditingShapeId() !== null ||\n\t\t\t\tareShortcutsDisabled(editor)\n\t\t\t) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpreventDefault(e)\n\n\t\t\tconst didCopy = await handleNativeOrMenuCopy(editor, { operation: 'cut', source: 'native' })\n\t\t\tif (didCopy) {\n\t\t\t\teditor.markHistoryStoppingPoint('cut')\n\t\t\t\teditor.deleteShapes(editor.getSelectedShapeIds())\n\t\t\t\ttrackEvent('cut', { source: 'kbd' })\n\t\t\t}\n\t\t}\n\n\t\tlet disablingMiddleClickPaste = false\n\t\tconst pointerUpHandler = (e: PointerEvent) => {\n\t\t\tif (e.button === 1) {\n\t\t\t\t// middle mouse button\n\t\t\t\tdisablingMiddleClickPaste = true\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\tdisablingMiddleClickPaste = false\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Track native modifier state from the most recent keydown. We use this\n\t\t// instead of editor.inputs.getShiftKey() because the editor applies a\n\t\t// 150ms delay on modifier release (to prevent physical race conditions\n\t\t// with pointer events), which can cause false positives here.\n\t\tlet nativeShiftKey = false\n\t\tconst trackModifiers = (e: KeyboardEvent) => {\n\t\t\tnativeShiftKey = e.shiftKey\n\t\t}\n\n\t\tconst paste = (e: ClipboardEvent) => {\n\t\t\tif (disablingMiddleClickPaste) {\n\t\t\t\teditor.markEventAsHandled(e)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If we're editing a shape, or we are focusing an editable input, then\n\t\t\t// we would want the user's paste interaction to go to that element or\n\t\t\t// input instead; e.g. when pasting text into a text shape's content\n\t\t\tif (editor.getEditingShapeId() !== null || areShortcutsDisabled(editor)) return\n\n\t\t\t// Cmd+Shift+V / Ctrl+Shift+V = paste as plain text (no formatting).\n\t\t\t// If there's no plain text on the clipboard (e.g., a copied PNG), fall\n\t\t\t// through to the normal paste handler so the file still gets pasted.\n\t\t\tif (nativeShiftKey) {\n\t\t\t\tconst text = e.clipboardData?.getData('text/plain')\n\t\t\t\tif (text?.trim()) {\n\t\t\t\t\tconst point = editor.user.getIsPasteAtCursorMode()\n\t\t\t\t\t\t? editor.inputs.getCurrentPagePoint()\n\t\t\t\t\t\t: editor.getViewportPageBounds().center\n\t\t\t\t\teditor.markHistoryStoppingPoint('paste')\n\t\t\t\t\tdefaultHandleExternalTextContent(editor, { text, point })\n\t\t\t\t\tpreventDefault(e)\n\t\t\t\t\ttrackEvent('paste', { source: 'kbd' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Cmd+V: paste at center by default, or at cursor when the preference is on.\n\t\t\t// (Cmd+Option+V and Cmd+Shift+Option+V are handled as actions in actions.tsx\n\t\t\t// because the browser only fires paste events for Cmd+V and Cmd+Shift+V.)\n\t\t\tconst point = editor.user.getIsPasteAtCursorMode()\n\t\t\t\t? editor.inputs.getCurrentPagePoint()\n\t\t\t\t: undefined\n\n\t\t\tif (\n\t\t\t\teditor.options.onClipboardPasteRaw?.({\n\t\t\t\t\teditor,\n\t\t\t\t\tsource: 'native-event',\n\t\t\t\t\tevent: e,\n\t\t\t\t\tclipboardData: e.clipboardData,\n\t\t\t\t\tpoint,\n\t\t\t\t}) === false\n\t\t\t) {\n\t\t\t\tpreventDefault(e)\n\t\t\t\ttrackEvent('paste', { source: 'kbd' })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst pasteFromEvent = () => {\n\t\t\t\tif (e.clipboardData) {\n\t\t\t\t\thandlePasteFromEventClipboardData(editor, e.clipboardData, point)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// if we can read from the clipboard API, we want to try using that first. that allows\n\t\t\t// us to access most things, and doesn't strip out metadata added to tldraw's own\n\t\t\t// copy-as-png features - so copied shapes come back in at the correct size.\n\t\t\tconst win = editor.getContainerWindow()\n\t\t\tconst nav = win.navigator\n\t\t\tif (nav.clipboard?.read) {\n\t\t\t\t// We can't read files from the filesystem using the clipboard API though - they'll\n\t\t\t\t// just come in as the file names instead. So we'll use the clipboard event's files\n\t\t\t\t// as a fallback - if we only got text, but do have files, we use those instead.\n\t\t\t\tconst fallbackFiles = Array.from(e.clipboardData?.files || [])\n\t\t\t\tnav.clipboard.read().then(\n\t\t\t\t\t(clipboardItems) => {\n\t\t\t\t\t\tif (Array.isArray(clipboardItems) && clipboardItems[0] instanceof win.ClipboardItem) {\n\t\t\t\t\t\t\thandlePasteFromClipboardApi({\n\t\t\t\t\t\t\t\teditor,\n\t\t\t\t\t\t\t\tclipboardItems,\n\t\t\t\t\t\t\t\tpoint,\n\t\t\t\t\t\t\t\tfallbackFiles,\n\t\t\t\t\t\t\t\tclipboardPasteSource: 'native-event',\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\t// if reading from the clipboard fails, try to use the event clipboard data\n\t\t\t\t\t\tpasteFromEvent()\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tpasteFromEvent()\n\t\t\t}\n\n\t\t\tpreventDefault(e)\n\t\t\ttrackEvent('paste', { source: 'kbd' })\n\t\t}\n\n\t\townerDocument?.addEventListener('copy', copy)\n\t\townerDocument?.addEventListener('cut', cut)\n\t\townerDocument?.addEventListener('paste', paste)\n\t\townerDocument?.addEventListener('pointerup', pointerUpHandler)\n\t\townerDocument?.addEventListener('keydown', trackModifiers, true)\n\t\townerDocument?.addEventListener('keyup', trackModifiers, true)\n\n\t\treturn () => {\n\t\t\townerDocument?.removeEventListener('copy', copy)\n\t\t\townerDocument?.removeEventListener('cut', cut)\n\t\t\townerDocument?.removeEventListener('paste', paste)\n\t\t\townerDocument?.removeEventListener('pointerup', pointerUpHandler)\n\t\t\townerDocument?.removeEventListener('keydown', trackModifiers, true)\n\t\t\townerDocument?.removeEventListener('keyup', trackModifiers, true)\n\t\t}\n\t}, [editor, trackEvent, appIsFocused, ownerDocument])\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["url", "putPastedExternalContent", "point"]
7
7
  }
@@ -32,31 +32,53 @@ async function flattenShapesToImages(editor, shapeIds, flattenImageBoundsExpand)
32
32
  bounds: editor.getShapeMaskedPageBounds(shape).clone().expandBy(flattenImageBoundsExpand)
33
33
  };
34
34
  });
35
- for (let i = 0; i < expandedBounds.length; i++) {
36
- const item = expandedBounds[i];
37
- if (i === 0) {
38
- groups[0] = {
39
- shapes: [item.shape],
40
- bounds: item.bounds
41
- };
42
- continue;
35
+ const parents = expandedBounds.map((_, i) => i);
36
+ const find = (i) => {
37
+ let root = i;
38
+ while (parents[root] !== root) root = parents[root];
39
+ while (parents[i] !== root) {
40
+ const next = parents[i];
41
+ parents[i] = root;
42
+ i = next;
43
43
  }
44
- let didLand = false;
45
- for (const group of groups) {
46
- if (group.bounds.includes(item.bounds)) {
47
- group.shapes.push(item.shape);
48
- group.bounds.expand(item.bounds);
49
- didLand = true;
50
- break;
44
+ return root;
45
+ };
46
+ const union = (i, j) => {
47
+ const ri = find(i);
48
+ const rj = find(j);
49
+ if (ri !== rj) parents[ri] = rj;
50
+ };
51
+ const order = expandedBounds.map((_, i) => i).sort((a, b) => {
52
+ return expandedBounds[a].bounds.minX - expandedBounds[b].bounds.minX;
53
+ });
54
+ for (let a = 0; a < order.length; a++) {
55
+ const i = order[a];
56
+ const boundsI = expandedBounds[i].bounds;
57
+ for (let b = a + 1; b < order.length; b++) {
58
+ const j = order[b];
59
+ if (expandedBounds[j].bounds.minX > boundsI.maxX) break;
60
+ if (find(i) === find(j)) continue;
61
+ if (boundsI.includes(expandedBounds[j].bounds)) {
62
+ union(i, j);
51
63
  }
52
64
  }
53
- if (!didLand) {
54
- groups.push({
65
+ }
66
+ const groupsByRoot = /* @__PURE__ */ new Map();
67
+ for (let i = 0; i < expandedBounds.length; i++) {
68
+ const root = find(i);
69
+ const item = expandedBounds[i];
70
+ const existing = groupsByRoot.get(root);
71
+ if (existing) {
72
+ existing.shapes.push(item.shape);
73
+ existing.bounds.expand(item.bounds);
74
+ } else {
75
+ groupsByRoot.set(root, {
55
76
  shapes: [item.shape],
56
- bounds: item.bounds
77
+ bounds: item.bounds.clone()
57
78
  });
58
79
  }
59
80
  }
81
+ groups.push(...groupsByRoot.values());
60
82
  } else {
61
83
  const bounds = Box.Common(shapes.map((shape) => editor.getShapeMaskedPageBounds(shape)));
62
84
  groups.push({
@@ -90,11 +112,8 @@ async function flattenShapesToImages(editor, shapeIds, flattenImageBoundsExpand)
90
112
  if (!commonAncestorId) continue;
91
113
  let index = "a1";
92
114
  for (const shape of shapes2) {
93
- if (shape.parentId === commonAncestorId) {
94
- if (shape.index > index) {
95
- index = shape.index;
96
- }
97
- break;
115
+ if (shape.parentId === commonAncestorId && shape.index > index) {
116
+ index = shape.index;
98
117
  }
99
118
  }
100
119
  let x;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/ui/hooks/useFlatten.ts"],
4
- "sourcesContent": ["import {\n\tBox,\n\tEditor,\n\tIndexKey,\n\tTLImageAsset,\n\tTLShape,\n\tTLShapeId,\n\tVec,\n\tcompact,\n\tcreateShapeId,\n\tisShapeId,\n\ttransact,\n\tuseEditor,\n} from '@tldraw/editor'\nimport { useCallback } from 'react'\n\nexport async function flattenShapesToImages(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\tflattenImageBoundsExpand?: number\n) {\n\tconst shapes = compact(\n\t\tshapeIds.map((id) => {\n\t\t\tconst shape = editor.getShape(id)\n\t\t\tif (!shape) return\n\t\t\tconst util = editor.getShapeUtil(shape.type)\n\t\t\t// skip shapes that don't have a toSvg method\n\t\t\tif (util.toSvg === undefined) return\n\t\t\treturn shape\n\t\t})\n\t)\n\n\tif (shapes.length === 0) return\n\n\t// Don't flatten if it's just one image\n\tif (shapes.length === 1) {\n\t\tconst shape = shapes[0]\n\t\tif (!shape) return\n\t\tif (editor.isShapeOfType(shape, 'image')) return\n\t}\n\n\tconst groups: { shapes: TLShape[]; bounds: Box; asset?: TLImageAsset }[] = []\n\n\tif (flattenImageBoundsExpand !== undefined) {\n\t\tconst expandedBounds = shapes.map((shape) => {\n\t\t\treturn {\n\t\t\t\tshape,\n\t\t\t\tbounds: editor.getShapeMaskedPageBounds(shape)!.clone().expandBy(flattenImageBoundsExpand),\n\t\t\t}\n\t\t})\n\n\t\tfor (let i = 0; i < expandedBounds.length; i++) {\n\t\t\tconst item = expandedBounds[i]\n\t\t\tif (i === 0) {\n\t\t\t\tgroups[0] = {\n\t\t\t\t\tshapes: [item.shape],\n\t\t\t\t\tbounds: item.bounds,\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlet didLand = false\n\n\t\t\tfor (const group of groups) {\n\t\t\t\tif (group.bounds.includes(item.bounds)) {\n\t\t\t\t\tgroup.shapes.push(item.shape)\n\t\t\t\t\tgroup.bounds.expand(item.bounds)\n\t\t\t\t\tdidLand = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!didLand) {\n\t\t\t\tgroups.push({\n\t\t\t\t\tshapes: [item.shape],\n\t\t\t\t\tbounds: item.bounds,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else {\n\t\tconst bounds = Box.Common(shapes.map((shape) => editor.getShapeMaskedPageBounds(shape)!))\n\t\tgroups.push({\n\t\t\tshapes,\n\t\t\tbounds,\n\t\t})\n\t}\n\n\tconst padding = editor.options.flattenImageBoundsPadding\n\n\tfor (const group of groups) {\n\t\tif (flattenImageBoundsExpand !== undefined) {\n\t\t\t// shrink the bounds again, removing the expanded area\n\t\t\tgroup.bounds.expandBy(-flattenImageBoundsExpand)\n\t\t}\n\n\t\t// get an image for the shapes\n\t\tconst svgResult = await editor.getSvgString(group.shapes, {\n\t\t\tpadding,\n\t\t\tbackground: false,\n\t\t})\n\t\tif (!svgResult?.svg) continue\n\n\t\t// get an image asset for the image\n\t\tconst asset = (await editor.getAssetForExternalContent({\n\t\t\ttype: 'file',\n\t\t\tfile: new File([svgResult.svg], 'asset.svg', { type: 'image/svg+xml' }),\n\t\t})) as TLImageAsset\n\t\tif (!asset) continue\n\n\t\t// add it to the group\n\t\tgroup.asset = asset\n\t}\n\n\tconst createdShapeIds: TLShapeId[] = []\n\n\ttransact(() => {\n\t\tfor (const group of groups) {\n\t\t\tconst { asset, bounds, shapes } = group\n\t\t\tif (!asset) continue\n\n\t\t\tconst commonAncestorId = editor.findCommonAncestor(shapes) ?? editor.getCurrentPageId()\n\t\t\tif (!commonAncestorId) continue\n\n\t\t\tlet index: IndexKey = 'a1' as IndexKey\n\t\t\tfor (const shape of shapes) {\n\t\t\t\tif (shape.parentId === commonAncestorId) {\n\t\t\t\t\tif (shape.index > index) {\n\t\t\t\t\t\tindex = shape.index\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet x: number\n\t\t\tlet y: number\n\t\t\tlet rotation: number\n\n\t\t\tif (isShapeId(commonAncestorId)) {\n\t\t\t\tconst commonAncestor = editor.getShape(commonAncestorId)\n\t\t\t\tif (!commonAncestor) continue\n\t\t\t\t// put the point in the parent's space\n\t\t\t\tconst point = editor.getPointInShapeSpace(commonAncestor, {\n\t\t\t\t\tx: bounds.x,\n\t\t\t\t\ty: bounds.y,\n\t\t\t\t})\n\t\t\t\t// get the parent's rotation\n\t\t\t\trotation = editor.getShapePageTransform(commonAncestorId).rotation()\n\t\t\t\t// rotate the point against the parent's rotation\n\t\t\t\tpoint.sub(new Vec(padding, padding).rot(-rotation))\n\t\t\t\tx = point.x\n\t\t\t\ty = point.y\n\t\t\t} else {\n\t\t\t\t// if the common ancestor is the page, then just adjust for the padding\n\t\t\t\tx = bounds.x - padding\n\t\t\t\ty = bounds.y - padding\n\t\t\t\trotation = 0\n\t\t\t}\n\n\t\t\t// delete the shapes\n\t\t\teditor.deleteShapes(shapes)\n\n\t\t\t// create the asset\n\t\t\teditor.createAssets([{ ...asset, id: asset.id }])\n\n\t\t\tconst shapeId = createShapeId()\n\n\t\t\t// create an image shape in the same place as the shapes\n\t\t\teditor.createShape({\n\t\t\t\tid: shapeId,\n\t\t\t\ttype: 'image',\n\t\t\t\tindex,\n\t\t\t\tparentId: commonAncestorId,\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\trotation: -rotation,\n\t\t\t\tprops: {\n\t\t\t\t\tassetId: asset.id,\n\t\t\t\t\tw: bounds.w + padding * 2,\n\t\t\t\t\th: bounds.h + padding * 2,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tcreatedShapeIds.push(shapeId)\n\t\t}\n\t})\n\n\treturn createdShapeIds\n}\n\nexport function useFlatten() {\n\tconst editor = useEditor()\n\treturn useCallback(\n\t\t(ids: TLShapeId[]) => {\n\t\t\treturn flattenShapesToImages(editor, ids)\n\t\t},\n\t\t[editor]\n\t)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n\tBox,\n\tEditor,\n\tIndexKey,\n\tTLImageAsset,\n\tTLShape,\n\tTLShapeId,\n\tVec,\n\tcompact,\n\tcreateShapeId,\n\tisShapeId,\n\ttransact,\n\tuseEditor,\n} from '@tldraw/editor'\nimport { useCallback } from 'react'\n\nexport async function flattenShapesToImages(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\tflattenImageBoundsExpand?: number\n) {\n\tconst shapes = compact(\n\t\tshapeIds.map((id) => {\n\t\t\tconst shape = editor.getShape(id)\n\t\t\tif (!shape) return\n\t\t\tconst util = editor.getShapeUtil(shape.type)\n\t\t\t// skip shapes that don't have a toSvg method\n\t\t\tif (util.toSvg === undefined) return\n\t\t\treturn shape\n\t\t})\n\t)\n\n\tif (shapes.length === 0) return\n\n\t// Don't flatten if it's just one image\n\tif (shapes.length === 1) {\n\t\tconst shape = shapes[0]\n\t\tif (!shape) return\n\t\tif (editor.isShapeOfType(shape, 'image')) return\n\t}\n\n\tconst groups: { shapes: TLShape[]; bounds: Box; asset?: TLImageAsset }[] = []\n\n\tif (flattenImageBoundsExpand !== undefined) {\n\t\tconst expandedBounds = shapes.map((shape) => {\n\t\t\treturn {\n\t\t\t\tshape,\n\t\t\t\tbounds: editor.getShapeMaskedPageBounds(shape)!.clone().expandBy(flattenImageBoundsExpand),\n\t\t\t}\n\t\t})\n\n\t\t// Cluster shapes by connected components: any two shapes whose expanded\n\t\t// bounds intersect end up in the same group. Using union-find here\n\t\t// instead of a greedy first-fit pass means the result is independent of\n\t\t// the order shapes appear in `shapeIds`, and a shape that bridges two\n\t\t// otherwise-disjoint clusters merges them in one pass.\n\t\tconst parents = expandedBounds.map((_, i) => i)\n\t\tconst find = (i: number): number => {\n\t\t\tlet root = i\n\t\t\twhile (parents[root] !== root) root = parents[root]\n\t\t\twhile (parents[i] !== root) {\n\t\t\t\tconst next = parents[i]\n\t\t\t\tparents[i] = root\n\t\t\t\ti = next\n\t\t\t}\n\t\t\treturn root\n\t\t}\n\t\tconst union = (i: number, j: number) => {\n\t\t\tconst ri = find(i)\n\t\t\tconst rj = find(j)\n\t\t\tif (ri !== rj) parents[ri] = rj\n\t\t}\n\n\t\t// Broad-phase sweep: sort by minX and, for each box, only compare against\n\t\t// later boxes until one starts past the current box's maxX. Boxes that\n\t\t// don't overlap on the x-axis can't intersect, so this skips the bulk of\n\t\t// the pairwise checks for spread-out selections while still finding every\n\t\t// intersecting pair (Box.includes is symmetric, so each pair is tested once).\n\t\tconst order = expandedBounds\n\t\t\t.map((_, i) => i)\n\t\t\t.sort((a, b) => {\n\t\t\t\treturn expandedBounds[a].bounds.minX - expandedBounds[b].bounds.minX\n\t\t\t})\n\n\t\tfor (let a = 0; a < order.length; a++) {\n\t\t\tconst i = order[a]\n\t\t\tconst boundsI = expandedBounds[i].bounds\n\t\t\tfor (let b = a + 1; b < order.length; b++) {\n\t\t\t\tconst j = order[b]\n\t\t\t\t// sorted by minX, so once a box starts past i's maxX no later box can overlap i\n\t\t\t\tif (expandedBounds[j].bounds.minX > boundsI.maxX) break\n\t\t\t\tif (find(i) === find(j)) continue\n\t\t\t\tif (boundsI.includes(expandedBounds[j].bounds)) {\n\t\t\t\t\tunion(i, j)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst groupsByRoot = new Map<number, { shapes: TLShape[]; bounds: Box }>()\n\t\tfor (let i = 0; i < expandedBounds.length; i++) {\n\t\t\tconst root = find(i)\n\t\t\tconst item = expandedBounds[i]\n\t\t\tconst existing = groupsByRoot.get(root)\n\t\t\tif (existing) {\n\t\t\t\texisting.shapes.push(item.shape)\n\t\t\t\texisting.bounds.expand(item.bounds)\n\t\t\t} else {\n\t\t\t\tgroupsByRoot.set(root, {\n\t\t\t\t\tshapes: [item.shape],\n\t\t\t\t\tbounds: item.bounds.clone(),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tgroups.push(...groupsByRoot.values())\n\t} else {\n\t\tconst bounds = Box.Common(shapes.map((shape) => editor.getShapeMaskedPageBounds(shape)!))\n\t\tgroups.push({\n\t\t\tshapes,\n\t\t\tbounds,\n\t\t})\n\t}\n\n\tconst padding = editor.options.flattenImageBoundsPadding\n\n\tfor (const group of groups) {\n\t\tif (flattenImageBoundsExpand !== undefined) {\n\t\t\t// shrink the bounds again, removing the expanded area\n\t\t\tgroup.bounds.expandBy(-flattenImageBoundsExpand)\n\t\t}\n\n\t\t// get an image for the shapes\n\t\tconst svgResult = await editor.getSvgString(group.shapes, {\n\t\t\tpadding,\n\t\t\tbackground: false,\n\t\t})\n\t\tif (!svgResult?.svg) continue\n\n\t\t// get an image asset for the image\n\t\tconst asset = (await editor.getAssetForExternalContent({\n\t\t\ttype: 'file',\n\t\t\tfile: new File([svgResult.svg], 'asset.svg', { type: 'image/svg+xml' }),\n\t\t})) as TLImageAsset\n\t\tif (!asset) continue\n\n\t\t// add it to the group\n\t\tgroup.asset = asset\n\t}\n\n\tconst createdShapeIds: TLShapeId[] = []\n\n\ttransact(() => {\n\t\tfor (const group of groups) {\n\t\t\tconst { asset, bounds, shapes } = group\n\t\t\tif (!asset) continue\n\n\t\t\tconst commonAncestorId = editor.findCommonAncestor(shapes) ?? editor.getCurrentPageId()\n\t\t\tif (!commonAncestorId) continue\n\n\t\t\tlet index: IndexKey = 'a1' as IndexKey\n\t\t\tfor (const shape of shapes) {\n\t\t\t\tif (shape.parentId === commonAncestorId && shape.index > index) {\n\t\t\t\t\tindex = shape.index\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet x: number\n\t\t\tlet y: number\n\t\t\tlet rotation: number\n\n\t\t\tif (isShapeId(commonAncestorId)) {\n\t\t\t\tconst commonAncestor = editor.getShape(commonAncestorId)\n\t\t\t\tif (!commonAncestor) continue\n\t\t\t\t// put the point in the parent's space\n\t\t\t\tconst point = editor.getPointInShapeSpace(commonAncestor, {\n\t\t\t\t\tx: bounds.x,\n\t\t\t\t\ty: bounds.y,\n\t\t\t\t})\n\t\t\t\t// get the parent's rotation\n\t\t\t\trotation = editor.getShapePageTransform(commonAncestorId).rotation()\n\t\t\t\t// rotate the point against the parent's rotation\n\t\t\t\tpoint.sub(new Vec(padding, padding).rot(-rotation))\n\t\t\t\tx = point.x\n\t\t\t\ty = point.y\n\t\t\t} else {\n\t\t\t\t// if the common ancestor is the page, then just adjust for the padding\n\t\t\t\tx = bounds.x - padding\n\t\t\t\ty = bounds.y - padding\n\t\t\t\trotation = 0\n\t\t\t}\n\n\t\t\t// delete the shapes\n\t\t\teditor.deleteShapes(shapes)\n\n\t\t\t// create the asset\n\t\t\teditor.createAssets([{ ...asset, id: asset.id }])\n\n\t\t\tconst shapeId = createShapeId()\n\n\t\t\t// create an image shape in the same place as the shapes\n\t\t\teditor.createShape({\n\t\t\t\tid: shapeId,\n\t\t\t\ttype: 'image',\n\t\t\t\tindex,\n\t\t\t\tparentId: commonAncestorId,\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\trotation: -rotation,\n\t\t\t\tprops: {\n\t\t\t\t\tassetId: asset.id,\n\t\t\t\t\tw: bounds.w + padding * 2,\n\t\t\t\t\th: bounds.h + padding * 2,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tcreatedShapeIds.push(shapeId)\n\t\t}\n\t})\n\n\treturn createdShapeIds\n}\n\nexport function useFlatten() {\n\tconst editor = useEditor()\n\treturn useCallback(\n\t\t(ids: TLShapeId[]) => {\n\t\t\treturn flattenShapesToImages(editor, ids)\n\t\t},\n\t\t[editor]\n\t)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["shapes"]
7
7
  }
@@ -1,7 +1,6 @@
1
1
  /*!
2
- * The kbd-string splitter (`getKeys`) and the form-input filter pattern in `shouldSkipEvent`
3
- * (including its list of non-text INPUT types) are adapted from hotkeys-js, which this hook
4
- * previously depended on.
2
+ * The form-input filter pattern in `shouldSkipEvent` (including its list of non-text INPUT
3
+ * types) is adapted from hotkeys-js, which this hook previously depended on.
5
4
  *
6
5
  * MIT License: https://github.com/jaywcjlove/hotkeys-js/blob/master/LICENSE
7
6
  * Copyright (c) 2015-present, Kenny Wong
@@ -16,6 +15,7 @@ import {
16
15
  } from "@tldraw/editor";
17
16
  import { useEffect } from "react";
18
17
  import { useActions } from "../context/actions.mjs";
18
+ import { splitKbd } from "../kbd-utils.mjs";
19
19
  import { useCommentingEnabled } from "./useCommentingEnabled.mjs";
20
20
  import { useReadonly } from "./useReadonly.mjs";
21
21
  import { useTools } from "./useTools.mjs";
@@ -261,7 +261,7 @@ const PHYSICAL_KEY_MAP = {
261
261
  };
262
262
  function parseKbd(kbd) {
263
263
  const out = [];
264
- for (const shortcut of getKeys(kbd)) {
264
+ for (const shortcut of splitKbd(kbd.replace(/\s/g, ""))) {
265
265
  const parsed = parseShortcut(shortcut);
266
266
  if (parsed) out.push(parsed);
267
267
  }
@@ -337,7 +337,7 @@ function shouldSkipEvent(e) {
337
337
  return false;
338
338
  }
339
339
  function getHotkeysStringFromKbd(kbd) {
340
- return getKeys(kbd).map((kbd2) => {
340
+ return splitKbd(kbd.replace(/\s/g, "")).map((kbd2) => {
341
341
  let str = "";
342
342
  const shift = kbd2.includes("!");
343
343
  const alt = kbd2.includes("?");
@@ -363,18 +363,6 @@ function getHotkeysStringFromKbd(kbd) {
363
363
  return str;
364
364
  }).join(",");
365
365
  }
366
- function getKeys(key) {
367
- if (typeof key !== "string") key = "";
368
- key = key.replace(/\s/g, "");
369
- const keys = key.split(",");
370
- let index = keys.lastIndexOf("");
371
- for (; index >= 0; ) {
372
- keys[index - 1] += ",";
373
- keys.splice(index, 1);
374
- index = keys.lastIndexOf("");
375
- }
376
- return keys;
377
- }
378
366
  export {
379
367
  areShortcutsDisabled,
380
368
  getHotkeysStringFromKbd,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/ui/hooks/useKeyboardShortcuts.ts"],
4
- "sourcesContent": ["/*!\n * The kbd-string splitter (`getKeys`) and the form-input filter pattern in `shouldSkipEvent`\n * (including its list of non-text INPUT types) are adapted from hotkeys-js, which this hook\n * previously depended on.\n *\n * MIT License: https://github.com/jaywcjlove/hotkeys-js/blob/master/LICENSE\n * Copyright (c) 2015-present, Kenny Wong\n * Copyright (c) 2011-2013 Thomas Fuchs (https://github.com/madrobby/keymaster)\n * Source: https://github.com/jaywcjlove/hotkeys-js\n */\n\nimport {\n\tEditor,\n\tTLPointerEventInfo,\n\tisAccelKey,\n\tpreventDefault,\n\tuseEditor,\n\tuseValue,\n} from '@tldraw/editor'\nimport { useEffect } from 'react'\nimport { useActions } from '../context/actions'\nimport { useCommentingEnabled } from './useCommentingEnabled'\nimport { useReadonly } from './useReadonly'\nimport { useTools } from './useTools'\n\nconst SKIP_KBDS = [\n\t// we set these in useNativeClipboardEvents instead\n\t'copy',\n\t'cut',\n\t'paste',\n\t// There's also an upload asset action, so we don't want to set the kbd twice\n\t'asset',\n]\n\n/** @public */\nexport function useKeyboardShortcuts() {\n\tconst editor = useEditor()\n\n\tconst isReadonlyMode = useReadonly()\n\tconst actions = useActions()\n\tconst tools = useTools()\n\t// The comment tool's shortcut is gated by the same license as its toolbar button.\n\tconst commentingEnabled = useCommentingEnabled()\n\tconst isFocused = useValue('is focused', () => editor.getInstanceState().isFocused, [editor])\n\tuseEffect(() => {\n\t\tif (!isFocused) return\n\n\t\tconst registry: Registration[] = []\n\n\t\tconst register = (kbd: string, onKeyDown?: KbdHandler, onKeyUp?: KbdHandler) => {\n\t\t\tconst parsed = parseKbd(kbd)\n\t\t\tif (parsed.length === 0) return\n\t\t\tregistry.push({ parsed, onKeyDown, onKeyUp })\n\t\t}\n\n\t\t// Add hotkeys for actions and tools.\n\t\t// Except those that in SKIP_KBDS!\n\t\tfor (const action of Object.values(actions)) {\n\t\t\tif (!action.kbd) continue\n\t\t\tif (isReadonlyMode && !action.readonlyOk) continue\n\t\t\tif (SKIP_KBDS.includes(action.id)) continue\n\n\t\t\tregister(getHotkeysStringFromKbd(action.kbd), (event) => {\n\t\t\t\tif (areShortcutsDisabled(editor) && !action.isRequiredA11yAction) return\n\t\t\t\tpreventDefault(event)\n\t\t\t\taction.onSelect('kbd')\n\t\t\t})\n\t\t}\n\n\t\tfor (const tool of Object.values(tools)) {\n\t\t\tif (!tool.kbd || (!tool.readonlyOk && editor.getIsReadonly())) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (SKIP_KBDS.includes(tool.id)) continue\n\n\t\t\t// The comment tool is a licensed feature; skip its shortcut when commenting isn't enabled.\n\t\t\tif (tool.id === 'comment' && !commentingEnabled) continue\n\n\t\t\tregister(getHotkeysStringFromKbd(tool.kbd), (event) => {\n\t\t\t\tif (areShortcutsDisabled(editor)) return\n\t\t\t\tpreventDefault(event)\n\t\t\t\ttool.onSelect('kbd')\n\t\t\t})\n\t\t}\n\n\t\tregister(\n\t\t\t',',\n\t\t\t(e) => {\n\t\t\t\t// Skip if shortcuts are disabled\n\t\t\t\tif (areShortcutsDisabled(editor)) return\n\n\t\t\t\t// Don't press again if already pressed\n\t\t\t\tif (editor.inputs.keys.has('Comma')) return\n\n\t\t\t\tpreventDefault(e) // prevent whatever would normally happen\n\t\t\t\teditor.focus() // Focus if not already focused\n\n\t\t\t\teditor.inputs.keys.add('Comma')\n\n\t\t\t\tconst { x, y, z } = editor.inputs.getCurrentPagePoint()\n\t\t\t\tconst screenpoints = editor.pageToScreen({ x, y })\n\n\t\t\t\tconst info: TLPointerEventInfo = {\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\tpoint: { x: screenpoints.x, y: screenpoints.y, z },\n\t\t\t\t\tshiftKey: e.shiftKey,\n\t\t\t\t\taltKey: e.altKey,\n\t\t\t\t\tctrlKey: e.metaKey || e.ctrlKey,\n\t\t\t\t\tmetaKey: e.metaKey,\n\t\t\t\t\taccelKey: isAccelKey(e),\n\t\t\t\t\tpointerId: 0,\n\t\t\t\t\tbutton: 0,\n\t\t\t\t\tisPen: editor.getInstanceState().isPenMode,\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t}\n\n\t\t\t\teditor.dispatch(info)\n\t\t\t},\n\t\t\t(e) => {\n\t\t\t\tif (areShortcutsDisabled(editor)) return\n\t\t\t\tif (!editor.inputs.keys.has('Comma')) return\n\n\t\t\t\teditor.inputs.keys.delete('Comma')\n\n\t\t\t\tconst { x, y, z } = editor.inputs.getCurrentPagePoint()\n\t\t\t\tconst screenPoint = editor.pageToScreen({ x, y })\n\t\t\t\tconst info: TLPointerEventInfo = {\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\tname: 'pointer_up',\n\t\t\t\t\tpoint: { x: screenPoint.x, y: screenPoint.y, z },\n\t\t\t\t\tshiftKey: e.shiftKey,\n\t\t\t\t\taltKey: e.altKey,\n\t\t\t\t\tctrlKey: e.metaKey || e.ctrlKey,\n\t\t\t\t\tmetaKey: e.metaKey,\n\t\t\t\t\taccelKey: isAccelKey(e),\n\t\t\t\t\tpointerId: 0,\n\t\t\t\t\tbutton: 0,\n\t\t\t\t\tisPen: editor.getInstanceState().isPenMode,\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t}\n\n\t\t\t\teditor.dispatch(info)\n\t\t\t}\n\t\t)\n\n\t\tconst body = editor.getContainerDocument().body\n\n\t\t// Track which registration each physically-held key first triggered, keyed by\n\t\t// `event.code`. While a key is held down, releasing a modifier should not let the\n\t\t// auto-repeat keydown events trigger an adjacent shortcut (e.g. releasing shift while\n\t\t// still holding shift+q shouldn't start firing the plain `q` shortcut). The same\n\t\t// registration is still allowed to repeat (e.g. holding `=` to keep zooming).\n\t\tconst heldKeyRegistrations = new Map<string, Registration>()\n\n\t\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (shouldSkipEvent(e)) return\n\t\t\tconst code = e.code\n\t\t\tfor (const reg of registry) {\n\t\t\t\tif (!reg.onKeyDown) continue\n\t\t\t\tfor (const p of reg.parsed) {\n\t\t\t\t\tif (matchesEvent(e, p)) {\n\t\t\t\t\t\tif (code) {\n\t\t\t\t\t\t\t// We only guard auto-repeat events. A fresh keypress (`e.repeat` is\n\t\t\t\t\t\t\t// false) is always free to trigger whatever it matches, even on the same\n\t\t\t\t\t\t\t// physical key \u2014 e.g. cmd+z (undo) then cmd+shift+z (redo), where macOS\n\t\t\t\t\t\t\t// swallows the `z` keyup while cmd stays held, so we can't rely on keyup\n\t\t\t\t\t\t\t// to clear the previous registration.\n\t\t\t\t\t\t\tif (e.repeat) {\n\t\t\t\t\t\t\t\tconst prev = heldKeyRegistrations.get(code)\n\t\t\t\t\t\t\t\t// The held key already triggered a different shortcut; don't fall back\n\t\t\t\t\t\t\t\t// to this one (or anything else) just because a modifier was released.\n\t\t\t\t\t\t\t\tif (prev && prev !== reg) return\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\theldKeyRegistrations.set(code, reg)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treg.onKeyDown(e)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst handleKeyUp = (e: KeyboardEvent) => {\n\t\t\t// Always release the held-key tracking, even for events we'd otherwise skip (e.g. the\n\t\t\t// key was released after focus moved into a text input), so a stale entry can't block\n\t\t\t// later shortcuts.\n\t\t\tif (e.code) heldKeyRegistrations.delete(e.code)\n\t\t\tif (shouldSkipEvent(e)) return\n\t\t\tfor (const reg of registry) {\n\t\t\t\tif (!reg.onKeyUp) continue\n\t\t\t\tfor (const p of reg.parsed) {\n\t\t\t\t\tif (matchesEvent(e, p)) {\n\t\t\t\t\t\treg.onKeyUp(e)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbody.addEventListener('keydown', handleKeyDown)\n\t\tbody.addEventListener('keyup', handleKeyUp)\n\n\t\treturn () => {\n\t\t\tbody.removeEventListener('keydown', handleKeyDown)\n\t\t\tbody.removeEventListener('keyup', handleKeyUp)\n\t\t}\n\t}, [actions, tools, isReadonlyMode, editor, isFocused, commentingEnabled])\n}\n\nexport function areShortcutsDisabled(editor: Editor) {\n\treturn (\n\t\teditor.menus.hasAnyOpenMenus() ||\n\t\teditor.getEditingShapeId() !== null ||\n\t\teditor.getCrashingError() ||\n\t\t!editor.user.getAreKeyboardShortcutsEnabled()\n\t)\n}\n\n// kbd parsing & native event matching\n// -----------------------------------\n// We deliberately do NOT use `event.code` (physical key position) for primary matching.\n// Doing so breaks alternative Latin keyboard layouts (Dvorak, Colemak, AZERTY) because\n// `event.code` always reflects the US-QWERTY position regardless of what the user typed.\n// Instead we match the typed character (`event.key`) and only fall back to `event.code` for\n// non-Latin layouts (Cyrillic, Greek, etc.) and macOS Option-letter dead-key combinations,\n// where `event.key` is a non-ASCII glyph.\n\n/**\n * @internal\n */\nexport interface ParsedKbd {\n\tkey: string\n\tshift: boolean\n\talt: boolean\n\tctrl: boolean\n\tmeta: boolean\n}\n\ntype KbdHandler = (event: KeyboardEvent) => void\n\ninterface Registration {\n\tparsed: ParsedKbd[]\n\tonKeyDown?: KbdHandler\n\tonKeyUp?: KbdHandler\n}\n\nconst MODIFIER_ALIASES: Record<string, 'shift' | 'alt' | 'ctrl' | 'meta'> = {\n\tshift: 'shift',\n\t'\u21E7': 'shift',\n\talt: 'alt',\n\toption: 'alt',\n\t'\u2325': 'alt',\n\tctrl: 'ctrl',\n\tcontrol: 'ctrl',\n\t'\u2303': 'ctrl',\n\tcmd: 'meta',\n\tcommand: 'meta',\n\tmeta: 'meta',\n\t'\u2318': 'meta',\n}\n\n// Aliases for named keys. Values match the lowercased `event.key` for that key.\nconst KEY_ALIASES: Record<string, string> = {\n\t'\u232B': 'backspace',\n\t'\u21A9': 'enter',\n\treturn: 'enter',\n\tesc: 'escape',\n\tdel: 'delete',\n\tins: 'insert',\n\tleft: 'arrowleft',\n\tright: 'arrowright',\n\tup: 'arrowup',\n\tdown: 'arrowdown',\n\tspace: ' ',\n}\n\n// When shift is held, `event.key` reports the shifted character (e.g. '<' for shift+,).\n// Map those back so a `shift+,` shortcut definition matches.\nconst SHIFT_KEY_TO_BASE: Record<string, string> = {\n\t'<': ',',\n\t'>': '.',\n\t'?': '/',\n\t':': ';',\n\t'\"': \"'\",\n\t'{': '[',\n\t'}': ']',\n\t'|': '\\\\',\n\t_: '-',\n\t'+': '=',\n\t'~': '`',\n\t'!': '1',\n\t'@': '2',\n\t'#': '3',\n\t$: '4',\n\t'%': '5',\n\t'^': '6',\n\t'&': '7',\n\t'*': '8',\n\t'(': '9',\n\t')': '0',\n}\n\n// Physical key code -> US-QWERTY-equivalent character. Used as a fallback when `event.key`\n// is non-ASCII (Cyrillic, Greek, macOS Option dead-keys, etc.).\nconst PHYSICAL_KEY_MAP: Record<string, string> = {\n\tKeyA: 'a',\n\tKeyB: 'b',\n\tKeyC: 'c',\n\tKeyD: 'd',\n\tKeyE: 'e',\n\tKeyF: 'f',\n\tKeyG: 'g',\n\tKeyH: 'h',\n\tKeyI: 'i',\n\tKeyJ: 'j',\n\tKeyK: 'k',\n\tKeyL: 'l',\n\tKeyM: 'm',\n\tKeyN: 'n',\n\tKeyO: 'o',\n\tKeyP: 'p',\n\tKeyQ: 'q',\n\tKeyR: 'r',\n\tKeyS: 's',\n\tKeyT: 't',\n\tKeyU: 'u',\n\tKeyV: 'v',\n\tKeyW: 'w',\n\tKeyX: 'x',\n\tKeyY: 'y',\n\tKeyZ: 'z',\n\tDigit0: '0',\n\tDigit1: '1',\n\tDigit2: '2',\n\tDigit3: '3',\n\tDigit4: '4',\n\tDigit5: '5',\n\tDigit6: '6',\n\tDigit7: '7',\n\tDigit8: '8',\n\tDigit9: '9',\n\tComma: ',',\n\tPeriod: '.',\n\tSlash: '/',\n\tSemicolon: ';',\n\tQuote: \"'\",\n\tBracketLeft: '[',\n\tBracketRight: ']',\n\tBackslash: '\\\\',\n\tMinus: '-',\n\tEqual: '=',\n\tBackquote: '`',\n}\n\n/**\n * @internal\n */\nexport function parseKbd(kbd: string): ParsedKbd[] {\n\tconst out: ParsedKbd[] = []\n\tfor (const shortcut of getKeys(kbd)) {\n\t\tconst parsed = parseShortcut(shortcut)\n\t\tif (parsed) out.push(parsed)\n\t}\n\treturn out\n}\n\nfunction parseShortcut(shortcut: string): ParsedKbd | null {\n\tconst parts = shortcut.split('+')\n\tconst result: ParsedKbd = {\n\t\tkey: '',\n\t\tshift: false,\n\t\talt: false,\n\t\tctrl: false,\n\t\tmeta: false,\n\t}\n\n\tlet keyPart = ''\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i]\n\t\tconst isLast = i === parts.length - 1\n\t\tif (!isLast) {\n\t\t\tconst modAlias = MODIFIER_ALIASES[part.toLowerCase()]\n\t\t\tif (modAlias) result[modAlias] = true\n\t\t\t// silently drop unknown leading parts\n\t\t} else {\n\t\t\tkeyPart = part\n\t\t}\n\t}\n\n\tif (!keyPart) return null\n\n\tlet key = keyPart.toLowerCase()\n\tif (KEY_ALIASES[key]) key = KEY_ALIASES[key]\n\tresult.key = key\n\treturn result\n}\n\nfunction getEventKey(e: KeyboardEvent): string {\n\tlet key = e.key.toLowerCase()\n\tif (e.shiftKey && SHIFT_KEY_TO_BASE[key]) {\n\t\tkey = SHIFT_KEY_TO_BASE[key]\n\t}\n\treturn key\n}\n\nfunction matchesEvent(e: KeyboardEvent, parsed: ParsedKbd): boolean {\n\tif (e.shiftKey !== parsed.shift) return false\n\tif (e.altKey !== parsed.alt) return false\n\tif (e.ctrlKey !== parsed.ctrl) return false\n\tif (e.metaKey !== parsed.meta) return false\n\n\t// With shift held, match number-row digits by physical position rather than the typed glyph: the\n\t// digit keys sit in the same place on every Latin layout, but their shifted glyph varies\n\t// (shift+2 is '@' on US, '\"' on British PC and German), so glyph matching misses them. Returning\n\t// here also stops a digit press from matching a symbol shortcut that shares its glyph \u2014 on\n\t// German, shift+0 types '=', which must run zoom-to-100, not the '=' zoom-in shortcut it would\n\t// otherwise alias to. Unshifted presses keep glyph matching, because layouts put real symbol\n\t// shortcuts on the number row: AZERTY types '-' on Digit6, and that must still zoom out.\n\tif (e.shiftKey) {\n\t\tconst digitCode = /^Digit([0-9])$/.exec(e.code)\n\t\tif (digitCode) return parsed.key === digitCode[1]\n\t}\n\n\tconst eventKey = getEventKey(e)\n\tif (eventKey === parsed.key) return true\n\n\t// Fallback for non-Latin layouts (Cyrillic, Greek, etc.) and macOS Option dead-keys,\n\t// where event.key is a non-ASCII glyph that wouldn't match any of our shortcut keys.\n\t// We re-derive the intended key from event.code's US-QWERTY equivalent. Importantly,\n\t// we only use this fallback when event.key is non-ASCII so that Dvorak/Colemak/AZERTY\n\t// users \u2014 whose event.key IS the Latin character they typed \u2014 keep getting the right match.\n\tif (eventKey.length === 1 && /^[\\x20-\\x7e]$/.test(eventKey)) return false\n\tconst codeKey = PHYSICAL_KEY_MAP[e.code]\n\treturn codeKey === parsed.key\n}\n\nfunction shouldSkipEvent(e: KeyboardEvent): boolean {\n\tif (e.isComposing) return true\n\tconst target = e.target as HTMLElement | null\n\tif (!target) return false\n\tif (target.isContentEditable) return true\n\tconst tagName = target.tagName\n\tif (tagName === 'SELECT') return true\n\tif (tagName === 'TEXTAREA') {\n\t\treturn !(target as HTMLTextAreaElement).readOnly\n\t}\n\tif (tagName === 'INPUT') {\n\t\tconst input = target as HTMLInputElement\n\t\t// Form inputs that don't accept text input should not block shortcuts.\n\t\tif (\n\t\t\t['checkbox', 'radio', 'range', 'button', 'file', 'reset', 'submit', 'color'].includes(\n\t\t\t\tinput.type\n\t\t\t)\n\t\t) {\n\t\t\treturn false\n\t\t}\n\t\treturn !input.readOnly\n\t}\n\treturn false\n}\n\n/**\n * The \"raw\" kbd here will look something like \"a\" or a combination of keys\n * \"del,backspace\". We need to first split them up by comma, then parse each\n * key to ensure backwards compatibility with the old kbd format. We used to\n * have symbols to denote cmd/alt/shift, using ! for shift, $ for cmd, and ?\n * for alt.\n *\n * @internal\n */\nexport function getHotkeysStringFromKbd(kbd: string) {\n\treturn getKeys(kbd)\n\t\t.map((kbd) => {\n\t\t\tlet str = ''\n\n\t\t\tconst shift = kbd.includes('!')\n\t\t\tconst alt = kbd.includes('?')\n\t\t\tconst cmd = kbd.includes('$')\n\n\t\t\t// remove the modifiers; the remaining string are the actual key\n\t\t\tconst k = kbd.replace(/[!?$]/g, '')\n\n\t\t\tif (shift && alt && cmd) {\n\t\t\t\tstr = `cmd+shift+alt+${k},ctrl+shift+alt+${k}`\n\t\t\t} else if (shift && cmd) {\n\t\t\t\tstr = `cmd+shift+${k},ctrl+shift+${k}`\n\t\t\t} else if (alt && cmd) {\n\t\t\t\tstr = `cmd+alt+${k},ctrl+alt+${k}`\n\t\t\t} else if (alt && shift) {\n\t\t\t\tstr = `shift+alt+${k}`\n\t\t\t} else if (shift) {\n\t\t\t\tstr = `shift+${k}`\n\t\t\t} else if (alt) {\n\t\t\t\tstr = `alt+${k}`\n\t\t\t} else if (cmd) {\n\t\t\t\tstr = `cmd+${k},ctrl+${k}`\n\t\t\t} else {\n\t\t\t\tstr = k\n\t\t\t}\n\n\t\t\treturn str\n\t\t})\n\t\t.join(',')\n}\n\n// Split a kbd string on commas, treating an empty entry produced by \"x,,\" as a literal\n// trailing comma on the previous entry. Verbatim port of the splitter from hotkeys-js\n// (MIT, see top-of-file attribution).\nfunction getKeys(key: string) {\n\tif (typeof key !== 'string') key = ''\n\tkey = key.replace(/\\s/g, '')\n\tconst keys = key.split(',')\n\tlet index = keys.lastIndexOf('')\n\n\tfor (; index >= 0; ) {\n\t\tkeys[index - 1] += ','\n\t\tkeys.splice(index, 1)\n\t\tindex = keys.lastIndexOf('')\n\t}\n\n\treturn keys\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/*!\n * The form-input filter pattern in `shouldSkipEvent` (including its list of non-text INPUT\n * types) is adapted from hotkeys-js, which this hook previously depended on.\n *\n * MIT License: https://github.com/jaywcjlove/hotkeys-js/blob/master/LICENSE\n * Copyright (c) 2015-present, Kenny Wong\n * Copyright (c) 2011-2013 Thomas Fuchs (https://github.com/madrobby/keymaster)\n * Source: https://github.com/jaywcjlove/hotkeys-js\n */\n\nimport {\n\tEditor,\n\tTLPointerEventInfo,\n\tisAccelKey,\n\tpreventDefault,\n\tuseEditor,\n\tuseValue,\n} from '@tldraw/editor'\nimport { useEffect } from 'react'\nimport { useActions } from '../context/actions'\nimport { splitKbd } from '../kbd-utils'\nimport { useCommentingEnabled } from './useCommentingEnabled'\nimport { useReadonly } from './useReadonly'\nimport { useTools } from './useTools'\n\nconst SKIP_KBDS = [\n\t// we set these in useNativeClipboardEvents instead\n\t'copy',\n\t'cut',\n\t'paste',\n\t// There's also an upload asset action, so we don't want to set the kbd twice\n\t'asset',\n]\n\n/** @public */\nexport function useKeyboardShortcuts() {\n\tconst editor = useEditor()\n\n\tconst isReadonlyMode = useReadonly()\n\tconst actions = useActions()\n\tconst tools = useTools()\n\t// The comment tool's shortcut is gated by the same license as its toolbar button.\n\tconst commentingEnabled = useCommentingEnabled()\n\tconst isFocused = useValue('is focused', () => editor.getInstanceState().isFocused, [editor])\n\tuseEffect(() => {\n\t\tif (!isFocused) return\n\n\t\tconst registry: Registration[] = []\n\n\t\tconst register = (kbd: string, onKeyDown?: KbdHandler, onKeyUp?: KbdHandler) => {\n\t\t\tconst parsed = parseKbd(kbd)\n\t\t\tif (parsed.length === 0) return\n\t\t\tregistry.push({ parsed, onKeyDown, onKeyUp })\n\t\t}\n\n\t\t// Add hotkeys for actions and tools.\n\t\t// Except those that in SKIP_KBDS!\n\t\tfor (const action of Object.values(actions)) {\n\t\t\tif (!action.kbd) continue\n\t\t\tif (isReadonlyMode && !action.readonlyOk) continue\n\t\t\tif (SKIP_KBDS.includes(action.id)) continue\n\n\t\t\tregister(getHotkeysStringFromKbd(action.kbd), (event) => {\n\t\t\t\tif (areShortcutsDisabled(editor) && !action.isRequiredA11yAction) return\n\t\t\t\tpreventDefault(event)\n\t\t\t\taction.onSelect('kbd')\n\t\t\t})\n\t\t}\n\n\t\tfor (const tool of Object.values(tools)) {\n\t\t\tif (!tool.kbd || (!tool.readonlyOk && editor.getIsReadonly())) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (SKIP_KBDS.includes(tool.id)) continue\n\n\t\t\t// The comment tool is a licensed feature; skip its shortcut when commenting isn't enabled.\n\t\t\tif (tool.id === 'comment' && !commentingEnabled) continue\n\n\t\t\tregister(getHotkeysStringFromKbd(tool.kbd), (event) => {\n\t\t\t\tif (areShortcutsDisabled(editor)) return\n\t\t\t\tpreventDefault(event)\n\t\t\t\ttool.onSelect('kbd')\n\t\t\t})\n\t\t}\n\n\t\tregister(\n\t\t\t',',\n\t\t\t(e) => {\n\t\t\t\t// Skip if shortcuts are disabled\n\t\t\t\tif (areShortcutsDisabled(editor)) return\n\n\t\t\t\t// Don't press again if already pressed\n\t\t\t\tif (editor.inputs.keys.has('Comma')) return\n\n\t\t\t\tpreventDefault(e) // prevent whatever would normally happen\n\t\t\t\teditor.focus() // Focus if not already focused\n\n\t\t\t\teditor.inputs.keys.add('Comma')\n\n\t\t\t\tconst { x, y, z } = editor.inputs.getCurrentPagePoint()\n\t\t\t\tconst screenpoints = editor.pageToScreen({ x, y })\n\n\t\t\t\tconst info: TLPointerEventInfo = {\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\tpoint: { x: screenpoints.x, y: screenpoints.y, z },\n\t\t\t\t\tshiftKey: e.shiftKey,\n\t\t\t\t\taltKey: e.altKey,\n\t\t\t\t\tctrlKey: e.metaKey || e.ctrlKey,\n\t\t\t\t\tmetaKey: e.metaKey,\n\t\t\t\t\taccelKey: isAccelKey(e),\n\t\t\t\t\tpointerId: 0,\n\t\t\t\t\tbutton: 0,\n\t\t\t\t\tisPen: editor.getInstanceState().isPenMode,\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t}\n\n\t\t\t\teditor.dispatch(info)\n\t\t\t},\n\t\t\t(e) => {\n\t\t\t\tif (areShortcutsDisabled(editor)) return\n\t\t\t\tif (!editor.inputs.keys.has('Comma')) return\n\n\t\t\t\teditor.inputs.keys.delete('Comma')\n\n\t\t\t\tconst { x, y, z } = editor.inputs.getCurrentPagePoint()\n\t\t\t\tconst screenPoint = editor.pageToScreen({ x, y })\n\t\t\t\tconst info: TLPointerEventInfo = {\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\tname: 'pointer_up',\n\t\t\t\t\tpoint: { x: screenPoint.x, y: screenPoint.y, z },\n\t\t\t\t\tshiftKey: e.shiftKey,\n\t\t\t\t\taltKey: e.altKey,\n\t\t\t\t\tctrlKey: e.metaKey || e.ctrlKey,\n\t\t\t\t\tmetaKey: e.metaKey,\n\t\t\t\t\taccelKey: isAccelKey(e),\n\t\t\t\t\tpointerId: 0,\n\t\t\t\t\tbutton: 0,\n\t\t\t\t\tisPen: editor.getInstanceState().isPenMode,\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t}\n\n\t\t\t\teditor.dispatch(info)\n\t\t\t}\n\t\t)\n\n\t\tconst body = editor.getContainerDocument().body\n\n\t\t// Track which registration each physically-held key first triggered, keyed by\n\t\t// `event.code`. While a key is held down, releasing a modifier should not let the\n\t\t// auto-repeat keydown events trigger an adjacent shortcut (e.g. releasing shift while\n\t\t// still holding shift+q shouldn't start firing the plain `q` shortcut). The same\n\t\t// registration is still allowed to repeat (e.g. holding `=` to keep zooming).\n\t\tconst heldKeyRegistrations = new Map<string, Registration>()\n\n\t\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (shouldSkipEvent(e)) return\n\t\t\tconst code = e.code\n\t\t\tfor (const reg of registry) {\n\t\t\t\tif (!reg.onKeyDown) continue\n\t\t\t\tfor (const p of reg.parsed) {\n\t\t\t\t\tif (matchesEvent(e, p)) {\n\t\t\t\t\t\tif (code) {\n\t\t\t\t\t\t\t// We only guard auto-repeat events. A fresh keypress (`e.repeat` is\n\t\t\t\t\t\t\t// false) is always free to trigger whatever it matches, even on the same\n\t\t\t\t\t\t\t// physical key \u2014 e.g. cmd+z (undo) then cmd+shift+z (redo), where macOS\n\t\t\t\t\t\t\t// swallows the `z` keyup while cmd stays held, so we can't rely on keyup\n\t\t\t\t\t\t\t// to clear the previous registration.\n\t\t\t\t\t\t\tif (e.repeat) {\n\t\t\t\t\t\t\t\tconst prev = heldKeyRegistrations.get(code)\n\t\t\t\t\t\t\t\t// The held key already triggered a different shortcut; don't fall back\n\t\t\t\t\t\t\t\t// to this one (or anything else) just because a modifier was released.\n\t\t\t\t\t\t\t\tif (prev && prev !== reg) return\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\theldKeyRegistrations.set(code, reg)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treg.onKeyDown(e)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst handleKeyUp = (e: KeyboardEvent) => {\n\t\t\t// Always release the held-key tracking, even for events we'd otherwise skip (e.g. the\n\t\t\t// key was released after focus moved into a text input), so a stale entry can't block\n\t\t\t// later shortcuts.\n\t\t\tif (e.code) heldKeyRegistrations.delete(e.code)\n\t\t\tif (shouldSkipEvent(e)) return\n\t\t\tfor (const reg of registry) {\n\t\t\t\tif (!reg.onKeyUp) continue\n\t\t\t\tfor (const p of reg.parsed) {\n\t\t\t\t\tif (matchesEvent(e, p)) {\n\t\t\t\t\t\treg.onKeyUp(e)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbody.addEventListener('keydown', handleKeyDown)\n\t\tbody.addEventListener('keyup', handleKeyUp)\n\n\t\treturn () => {\n\t\t\tbody.removeEventListener('keydown', handleKeyDown)\n\t\t\tbody.removeEventListener('keyup', handleKeyUp)\n\t\t}\n\t}, [actions, tools, isReadonlyMode, editor, isFocused, commentingEnabled])\n}\n\nexport function areShortcutsDisabled(editor: Editor) {\n\treturn (\n\t\teditor.menus.hasAnyOpenMenus() ||\n\t\teditor.getEditingShapeId() !== null ||\n\t\teditor.getCrashingError() ||\n\t\t!editor.user.getAreKeyboardShortcutsEnabled()\n\t)\n}\n\n// kbd parsing & native event matching\n// -----------------------------------\n// We deliberately do NOT use `event.code` (physical key position) for primary matching.\n// Doing so breaks alternative Latin keyboard layouts (Dvorak, Colemak, AZERTY) because\n// `event.code` always reflects the US-QWERTY position regardless of what the user typed.\n// Instead we match the typed character (`event.key`) and only fall back to `event.code` for\n// non-Latin layouts (Cyrillic, Greek, etc.) and macOS Option-letter dead-key combinations,\n// where `event.key` is a non-ASCII glyph.\n\n/**\n * @internal\n */\nexport interface ParsedKbd {\n\tkey: string\n\tshift: boolean\n\talt: boolean\n\tctrl: boolean\n\tmeta: boolean\n}\n\ntype KbdHandler = (event: KeyboardEvent) => void\n\ninterface Registration {\n\tparsed: ParsedKbd[]\n\tonKeyDown?: KbdHandler\n\tonKeyUp?: KbdHandler\n}\n\nconst MODIFIER_ALIASES: Record<string, 'shift' | 'alt' | 'ctrl' | 'meta'> = {\n\tshift: 'shift',\n\t'\u21E7': 'shift',\n\talt: 'alt',\n\toption: 'alt',\n\t'\u2325': 'alt',\n\tctrl: 'ctrl',\n\tcontrol: 'ctrl',\n\t'\u2303': 'ctrl',\n\tcmd: 'meta',\n\tcommand: 'meta',\n\tmeta: 'meta',\n\t'\u2318': 'meta',\n}\n\n// Aliases for named keys. Values match the lowercased `event.key` for that key.\nconst KEY_ALIASES: Record<string, string> = {\n\t'\u232B': 'backspace',\n\t'\u21A9': 'enter',\n\treturn: 'enter',\n\tesc: 'escape',\n\tdel: 'delete',\n\tins: 'insert',\n\tleft: 'arrowleft',\n\tright: 'arrowright',\n\tup: 'arrowup',\n\tdown: 'arrowdown',\n\tspace: ' ',\n}\n\n// When shift is held, `event.key` reports the shifted character (e.g. '<' for shift+,).\n// Map those back so a `shift+,` shortcut definition matches.\nconst SHIFT_KEY_TO_BASE: Record<string, string> = {\n\t'<': ',',\n\t'>': '.',\n\t'?': '/',\n\t':': ';',\n\t'\"': \"'\",\n\t'{': '[',\n\t'}': ']',\n\t'|': '\\\\',\n\t_: '-',\n\t'+': '=',\n\t'~': '`',\n\t'!': '1',\n\t'@': '2',\n\t'#': '3',\n\t$: '4',\n\t'%': '5',\n\t'^': '6',\n\t'&': '7',\n\t'*': '8',\n\t'(': '9',\n\t')': '0',\n}\n\n// Physical key code -> US-QWERTY-equivalent character. Used as a fallback when `event.key`\n// is non-ASCII (Cyrillic, Greek, macOS Option dead-keys, etc.).\nconst PHYSICAL_KEY_MAP: Record<string, string> = {\n\tKeyA: 'a',\n\tKeyB: 'b',\n\tKeyC: 'c',\n\tKeyD: 'd',\n\tKeyE: 'e',\n\tKeyF: 'f',\n\tKeyG: 'g',\n\tKeyH: 'h',\n\tKeyI: 'i',\n\tKeyJ: 'j',\n\tKeyK: 'k',\n\tKeyL: 'l',\n\tKeyM: 'm',\n\tKeyN: 'n',\n\tKeyO: 'o',\n\tKeyP: 'p',\n\tKeyQ: 'q',\n\tKeyR: 'r',\n\tKeyS: 's',\n\tKeyT: 't',\n\tKeyU: 'u',\n\tKeyV: 'v',\n\tKeyW: 'w',\n\tKeyX: 'x',\n\tKeyY: 'y',\n\tKeyZ: 'z',\n\tDigit0: '0',\n\tDigit1: '1',\n\tDigit2: '2',\n\tDigit3: '3',\n\tDigit4: '4',\n\tDigit5: '5',\n\tDigit6: '6',\n\tDigit7: '7',\n\tDigit8: '8',\n\tDigit9: '9',\n\tComma: ',',\n\tPeriod: '.',\n\tSlash: '/',\n\tSemicolon: ';',\n\tQuote: \"'\",\n\tBracketLeft: '[',\n\tBracketRight: ']',\n\tBackslash: '\\\\',\n\tMinus: '-',\n\tEqual: '=',\n\tBackquote: '`',\n}\n\n/**\n * @internal\n */\nexport function parseKbd(kbd: string): ParsedKbd[] {\n\tconst out: ParsedKbd[] = []\n\tfor (const shortcut of splitKbd(kbd.replace(/\\s/g, ''))) {\n\t\tconst parsed = parseShortcut(shortcut)\n\t\tif (parsed) out.push(parsed)\n\t}\n\treturn out\n}\n\nfunction parseShortcut(shortcut: string): ParsedKbd | null {\n\tconst parts = shortcut.split('+')\n\tconst result: ParsedKbd = {\n\t\tkey: '',\n\t\tshift: false,\n\t\talt: false,\n\t\tctrl: false,\n\t\tmeta: false,\n\t}\n\n\tlet keyPart = ''\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i]\n\t\tconst isLast = i === parts.length - 1\n\t\tif (!isLast) {\n\t\t\tconst modAlias = MODIFIER_ALIASES[part.toLowerCase()]\n\t\t\tif (modAlias) result[modAlias] = true\n\t\t\t// silently drop unknown leading parts\n\t\t} else {\n\t\t\tkeyPart = part\n\t\t}\n\t}\n\n\tif (!keyPart) return null\n\n\tlet key = keyPart.toLowerCase()\n\tif (KEY_ALIASES[key]) key = KEY_ALIASES[key]\n\tresult.key = key\n\treturn result\n}\n\nfunction getEventKey(e: KeyboardEvent): string {\n\tlet key = e.key.toLowerCase()\n\tif (e.shiftKey && SHIFT_KEY_TO_BASE[key]) {\n\t\tkey = SHIFT_KEY_TO_BASE[key]\n\t}\n\treturn key\n}\n\nfunction matchesEvent(e: KeyboardEvent, parsed: ParsedKbd): boolean {\n\tif (e.shiftKey !== parsed.shift) return false\n\tif (e.altKey !== parsed.alt) return false\n\tif (e.ctrlKey !== parsed.ctrl) return false\n\tif (e.metaKey !== parsed.meta) return false\n\n\t// With shift held, match number-row digits by physical position rather than the typed glyph: the\n\t// digit keys sit in the same place on every Latin layout, but their shifted glyph varies\n\t// (shift+2 is '@' on US, '\"' on British PC and German), so glyph matching misses them. Returning\n\t// here also stops a digit press from matching a symbol shortcut that shares its glyph \u2014 on\n\t// German, shift+0 types '=', which must run zoom-to-100, not the '=' zoom-in shortcut it would\n\t// otherwise alias to. Unshifted presses keep glyph matching, because layouts put real symbol\n\t// shortcuts on the number row: AZERTY types '-' on Digit6, and that must still zoom out.\n\tif (e.shiftKey) {\n\t\tconst digitCode = /^Digit([0-9])$/.exec(e.code)\n\t\tif (digitCode) return parsed.key === digitCode[1]\n\t}\n\n\tconst eventKey = getEventKey(e)\n\tif (eventKey === parsed.key) return true\n\n\t// Fallback for non-Latin layouts (Cyrillic, Greek, etc.) and macOS Option dead-keys,\n\t// where event.key is a non-ASCII glyph that wouldn't match any of our shortcut keys.\n\t// We re-derive the intended key from event.code's US-QWERTY equivalent. Importantly,\n\t// we only use this fallback when event.key is non-ASCII so that Dvorak/Colemak/AZERTY\n\t// users \u2014 whose event.key IS the Latin character they typed \u2014 keep getting the right match.\n\tif (eventKey.length === 1 && /^[\\x20-\\x7e]$/.test(eventKey)) return false\n\tconst codeKey = PHYSICAL_KEY_MAP[e.code]\n\treturn codeKey === parsed.key\n}\n\nfunction shouldSkipEvent(e: KeyboardEvent): boolean {\n\tif (e.isComposing) return true\n\tconst target = e.target as HTMLElement | null\n\tif (!target) return false\n\tif (target.isContentEditable) return true\n\tconst tagName = target.tagName\n\tif (tagName === 'SELECT') return true\n\tif (tagName === 'TEXTAREA') {\n\t\treturn !(target as HTMLTextAreaElement).readOnly\n\t}\n\tif (tagName === 'INPUT') {\n\t\tconst input = target as HTMLInputElement\n\t\t// Form inputs that don't accept text input should not block shortcuts.\n\t\tif (\n\t\t\t['checkbox', 'radio', 'range', 'button', 'file', 'reset', 'submit', 'color'].includes(\n\t\t\t\tinput.type\n\t\t\t)\n\t\t) {\n\t\t\treturn false\n\t\t}\n\t\treturn !input.readOnly\n\t}\n\treturn false\n}\n\n/**\n * The \"raw\" kbd here will look something like \"a\" or a combination of keys\n * \"del,backspace\". We need to first split them up by comma, then parse each\n * key to ensure backwards compatibility with the old kbd format. We used to\n * have symbols to denote cmd/alt/shift, using ! for shift, $ for cmd, and ?\n * for alt.\n *\n * @internal\n */\nexport function getHotkeysStringFromKbd(kbd: string) {\n\treturn splitKbd(kbd.replace(/\\s/g, ''))\n\t\t.map((kbd) => {\n\t\t\tlet str = ''\n\n\t\t\tconst shift = kbd.includes('!')\n\t\t\tconst alt = kbd.includes('?')\n\t\t\tconst cmd = kbd.includes('$')\n\n\t\t\t// remove the modifiers; the remaining string are the actual key\n\t\t\tconst k = kbd.replace(/[!?$]/g, '')\n\n\t\t\tif (shift && alt && cmd) {\n\t\t\t\tstr = `cmd+shift+alt+${k},ctrl+shift+alt+${k}`\n\t\t\t} else if (shift && cmd) {\n\t\t\t\tstr = `cmd+shift+${k},ctrl+shift+${k}`\n\t\t\t} else if (alt && cmd) {\n\t\t\t\tstr = `cmd+alt+${k},ctrl+alt+${k}`\n\t\t\t} else if (alt && shift) {\n\t\t\t\tstr = `shift+alt+${k}`\n\t\t\t} else if (shift) {\n\t\t\t\tstr = `shift+${k}`\n\t\t\t} else if (alt) {\n\t\t\t\tstr = `alt+${k}`\n\t\t\t} else if (cmd) {\n\t\t\t\tstr = `cmd+${k},ctrl+${k}`\n\t\t\t} else {\n\t\t\t\tstr = k\n\t\t\t}\n\n\t\t\treturn str\n\t\t})\n\t\t.join(',')\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["kbd"]
7
7
  }