@tldraw/editor 3.16.0-canary.2e83e38fb91b → 3.16.0-canary.344cec0354f3

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 (95) hide show
  1. package/dist-cjs/index.d.ts +35 -32
  2. package/dist-cjs/index.js +2 -4
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +0 -2
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +4 -4
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  8. package/dist-cjs/lib/config/TLUserPreferences.js +15 -4
  9. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  10. package/dist-cjs/lib/editor/Editor.js +35 -1
  11. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  12. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
  13. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  14. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +11 -6
  15. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  16. package/dist-cjs/lib/hooks/useCanvasEvents.js +17 -17
  17. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  18. package/dist-cjs/lib/hooks/useDocumentEvents.js +4 -4
  19. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  20. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -1
  21. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  22. package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
  23. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  24. package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
  25. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  26. package/dist-cjs/lib/license/LicenseManager.js +3 -0
  27. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  28. package/dist-cjs/lib/license/Watermark.js +97 -90
  29. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  30. package/dist-cjs/lib/utils/dom.js +1 -12
  31. package/dist-cjs/lib/utils/dom.js.map +2 -2
  32. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  33. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  34. package/dist-cjs/lib/utils/reparenting.js +5 -1
  35. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  36. package/dist-cjs/version.js +3 -3
  37. package/dist-cjs/version.js.map +1 -1
  38. package/dist-esm/index.d.mts +35 -32
  39. package/dist-esm/index.mjs +3 -7
  40. package/dist-esm/index.mjs.map +2 -2
  41. package/dist-esm/lib/TldrawEditor.mjs +0 -2
  42. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  43. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -5
  44. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  45. package/dist-esm/lib/config/TLUserPreferences.mjs +15 -4
  46. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  47. package/dist-esm/lib/editor/Editor.mjs +35 -1
  48. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  49. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  50. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  51. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +11 -6
  52. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  53. package/dist-esm/lib/hooks/useCanvasEvents.mjs +18 -24
  54. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  55. package/dist-esm/lib/hooks/useDocumentEvents.mjs +5 -10
  56. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  57. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +2 -2
  58. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  59. package/dist-esm/lib/hooks/useHandleEvents.mjs +7 -12
  60. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  61. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -15
  62. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  63. package/dist-esm/lib/license/LicenseManager.mjs +3 -0
  64. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  65. package/dist-esm/lib/license/Watermark.mjs +98 -91
  66. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  67. package/dist-esm/lib/utils/dom.mjs +1 -12
  68. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  69. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  70. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  71. package/dist-esm/lib/utils/reparenting.mjs +5 -1
  72. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  73. package/dist-esm/version.mjs +3 -3
  74. package/dist-esm/version.mjs.map +1 -1
  75. package/package.json +7 -7
  76. package/src/index.ts +0 -2
  77. package/src/lib/TldrawEditor.tsx +0 -2
  78. package/src/lib/components/default-components/DefaultCanvas.tsx +5 -5
  79. package/src/lib/config/TLUserPreferences.ts +16 -3
  80. package/src/lib/editor/Editor.ts +41 -1
  81. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  82. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +30 -8
  83. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +10 -3
  84. package/src/lib/hooks/useCanvasEvents.ts +18 -24
  85. package/src/lib/hooks/useDocumentEvents.ts +5 -10
  86. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +2 -2
  87. package/src/lib/hooks/useHandleEvents.ts +7 -12
  88. package/src/lib/hooks/useSelectionEvents.ts +9 -15
  89. package/src/lib/license/LicenseManager.ts +3 -0
  90. package/src/lib/license/Watermark.tsx +100 -92
  91. package/src/lib/utils/dom.test.ts +33 -24
  92. package/src/lib/utils/dom.ts +1 -31
  93. package/src/lib/utils/getPointerInfo.ts +3 -3
  94. package/src/lib/utils/reparenting.ts +7 -1
  95. package/src/version.ts +3 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/getPointerInfo.ts"],
4
- "sourcesContent": ["import { markEventAsHandled } from './dom'\nimport { isAccelKey } from './keyboard'\n\n/** @public */\nexport function getPointerInfo(e: React.PointerEvent | PointerEvent) {\n\tmarkEventAsHandled(e)\n\n\treturn {\n\t\tpoint: {\n\t\t\tx: e.clientX,\n\t\t\ty: e.clientY,\n\t\t\tz: e.pressure,\n\t\t},\n\t\tshiftKey: e.shiftKey,\n\t\taltKey: e.altKey,\n\t\tctrlKey: e.metaKey || e.ctrlKey,\n\t\tmetaKey: e.metaKey,\n\t\taccelKey: isAccelKey(e),\n\t\tpointerId: e.pointerId,\n\t\tbutton: e.button,\n\t\tisPen: e.pointerType === 'pen',\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAS,0BAA0B;AACnC,SAAS,kBAAkB;AAGpB,SAAS,eAAe,GAAsC;AACpE,qBAAmB,CAAC;AAEpB,SAAO;AAAA,IACN,OAAO;AAAA,MACN,GAAG,EAAE;AAAA,MACL,GAAG,EAAE;AAAA,MACL,GAAG,EAAE;AAAA,IACN;AAAA,IACA,UAAU,EAAE;AAAA,IACZ,QAAQ,EAAE;AAAA,IACV,SAAS,EAAE,WAAW,EAAE;AAAA,IACxB,SAAS,EAAE;AAAA,IACX,UAAU,WAAW,CAAC;AAAA,IACtB,WAAW,EAAE;AAAA,IACb,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE,gBAAgB;AAAA,EAC1B;AACD;",
4
+ "sourcesContent": ["import { Editor } from '../editor/Editor'\nimport { isAccelKey } from './keyboard'\n\n/** @public */\nexport function getPointerInfo(editor: Editor, e: React.PointerEvent | PointerEvent) {\n\teditor.markEventAsHandled(e)\n\n\treturn {\n\t\tpoint: {\n\t\t\tx: e.clientX,\n\t\t\ty: e.clientY,\n\t\t\tz: e.pressure,\n\t\t},\n\t\tshiftKey: e.shiftKey,\n\t\taltKey: e.altKey,\n\t\tctrlKey: e.metaKey || e.ctrlKey,\n\t\tmetaKey: e.metaKey,\n\t\taccelKey: isAccelKey(e),\n\t\tpointerId: e.pointerId,\n\t\tbutton: e.button,\n\t\tisPen: e.pointerType === 'pen',\n\t}\n}\n"],
5
+ "mappings": "AACA,SAAS,kBAAkB;AAGpB,SAAS,eAAe,QAAgB,GAAsC;AACpF,SAAO,mBAAmB,CAAC;AAE3B,SAAO;AAAA,IACN,OAAO;AAAA,MACN,GAAG,EAAE;AAAA,MACL,GAAG,EAAE;AAAA,MACL,GAAG,EAAE;AAAA,IACN;AAAA,IACA,UAAU,EAAE;AAAA,IACZ,QAAQ,EAAE;AAAA,IACV,SAAS,EAAE,WAAW,EAAE;AAAA,IACxB,SAAS,EAAE;AAAA,IACX,UAAU,WAAW,CAAC;AAAA,IACtB,WAAW,EAAE;AAAA,IACb,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE,gBAAgB;AAAA,EAC1B;AACD;",
6
6
  "names": []
7
7
  }
@@ -88,7 +88,11 @@ function getOverlappingShapes(editor, shape, otherShapes) {
88
88
  const parentGeometry = editor.getShapeGeometry(shape);
89
89
  const parentPageTransform = editor.getShapePageTransform(shape);
90
90
  const parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices);
91
- const parentPageMaskVertices = editor.getShapeMask(shape);
91
+ const _shape = editor.getShape(shape);
92
+ if (!_shape) return EMPTY_ARRAY;
93
+ const pageTransform = editor.getShapePageTransform(shape);
94
+ const clipPath = editor.getShapeUtil(_shape.type).getClipPath?.(_shape);
95
+ const parentPageMaskVertices = clipPath ? pageTransform.applyToPoints(clipPath) : void 0;
92
96
  const parentPagePolygon = parentPageMaskVertices ? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners) : parentPageCorners;
93
97
  if (!parentPagePolygon) return EMPTY_ARRAY;
94
98
  return otherShapes.filter((childId) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/reparenting.ts"],
4
- "sourcesContent": ["import { EMPTY_ARRAY } from '@tldraw/state'\nimport { TLGroupShape, TLParentId, TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndexAbove, getIndexBetween } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { intersectPolygonPolygon } from '../primitives/intersect'\n\n/**\n * Reparents shapes that are no longer contained within their parent shapes.\n * todo: rename me to something more descriptive, like `reparentOccludedShapes` or `reparentAutoDroppedShapes`\n *\n * @param editor - The editor instance.\n * @param shapeIds - The IDs of the shapes to reparent.\n * @param opts - Optional options, including a callback to filter out certain parents, such as when removing a frame.\n *\n * @public\n */\nexport function kickoutOccludedShapes(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\topts?: { filter?(parent: TLShape): boolean }\n) {\n\tconst parentsToCheck = new Set<TLShape>()\n\n\tfor (const id of shapeIds) {\n\t\tconst shape = editor.getShape(id)\n\n\t\tif (!shape) continue\n\t\tparentsToCheck.add(shape)\n\n\t\tconst parent = editor.getShape(shape.parentId)\n\t\tif (!parent) continue\n\t\tparentsToCheck.add(parent)\n\t}\n\n\t// Check all of the parents and gather up parents who have lost children\n\tconst parentsToLostChildren = new Map<TLShape, TLShapeId[]>()\n\n\tfor (const parent of parentsToCheck) {\n\t\tconst childIds = editor.getSortedChildIdsForParent(parent)\n\t\tif (opts?.filter && !opts.filter(parent)) {\n\t\t\t// If the shape is filtered out, we kick out all of its children\n\t\t\tparentsToLostChildren.set(parent, childIds)\n\t\t} else {\n\t\t\tconst overlappingChildren = getOverlappingShapes(editor, parent.id, childIds)\n\t\t\tif (overlappingChildren.length < childIds.length) {\n\t\t\t\tparentsToLostChildren.set(\n\t\t\t\t\tparent,\n\t\t\t\t\tchildIds.filter((id) => !overlappingChildren.includes(id))\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get all of the shapes on the current page, sorted by their index\n\tconst sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id)\n\n\tconst parentsToNewChildren: Record<\n\t\tTLParentId,\n\t\t{ parentId: TLParentId; shapeIds: TLShapeId[]; index?: IndexKey }\n\t> = {}\n\n\tfor (const [prevParent, lostChildrenIds] of parentsToLostChildren) {\n\t\tconst lostChildren = compact(lostChildrenIds.map((id) => editor.getShape(id)))\n\n\t\t// Don't fall \"up\" into frames in front of the shape\n\t\t// if (pageShapes.indexOf(shape) < frameSortPosition) continue shapeCheck\n\n\t\t// Otherwise, we have no next dropping shape under the cursor, so go find\n\t\t// all the frames on the page where the moving shapes will fall into\n\t\tconst { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(\n\t\t\teditor,\n\t\t\tlostChildren,\n\t\t\t(shape, maybeNewParent) => {\n\t\t\t\t// If we're filtering out a potential parent, don't reparent shapes to the filtered out shape\n\t\t\t\tif (opts?.filter && !opts.filter(maybeNewParent)) return false\n\t\t\t\treturn (\n\t\t\t\t\tmaybeNewParent.id !== prevParent.id &&\n\t\t\t\t\tsortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id)\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\n\t\treparenting.forEach((childrenToReparent, newParentId) => {\n\t\t\tif (childrenToReparent.length === 0) return\n\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\tshapeIds: [],\n\t\t\t\t}\n\t\t\t}\n\t\t\tparentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id))\n\t\t})\n\n\t\t// Reparent the rest to the page (or containing group)\n\t\tif (remainingShapesToReparent.size > 0) {\n\t\t\t// The remaining shapes are going to be reparented to the old parent's containing group, if there was one, or else to the page\n\t\t\tconst newParentId =\n\t\t\t\teditor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType<TLGroupShape>(s, 'group'))\n\t\t\t\t\t?.id ?? editor.getCurrentPageId()\n\n\t\t\tremainingShapesToReparent.forEach((shape) => {\n\t\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\t\tlet insertIndexKey: IndexKey | undefined\n\n\t\t\t\t\tconst oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId)\n\t\t\t\t\tconst oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id)\n\t\t\t\t\tif (oldParentIndex > -1) {\n\t\t\t\t\t\t// If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.\n\t\t\t\t\t\tconst siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]\n\t\t\t\t\t\tconst indexKeyAbove = siblingsIndexAbove\n\t\t\t\t\t\t\t? editor.getShape(siblingsIndexAbove)!.index\n\t\t\t\t\t\t\t: getIndexAbove(prevParent.index)\n\t\t\t\t\t\tinsertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the old parent is not a direct child of the new parent, then we'll add them to the \"top\" of the new parent's children.\n\t\t\t\t\t\t// This is done automatically if we leave the index undefined, so let's do that.\n\t\t\t\t\t}\n\n\t\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\t\tshapeIds: [],\n\t\t\t\t\t\tindex: insertIndexKey,\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tparentsToNewChildren[newParentId].shapeIds.push(shape.id)\n\t\t\t})\n\t\t}\n\t}\n\n\teditor.run(() => {\n\t\tObject.values(parentsToNewChildren).forEach(({ parentId, shapeIds, index }) => {\n\t\t\tif (shapeIds.length === 0) return\n\t\t\t// Before we reparent, sort the new shape ids by their place in the original absolute order on the page\n\t\t\tshapeIds.sort((a, b) => (sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1))\n\t\t\teditor.reparentShapes(shapeIds, parentId, index)\n\t\t})\n\t})\n}\n\n/**\n * Get the shapes that overlap with a given shape.\n *\n * @param editor - The editor instance.\n * @param shape - The shapes or shape IDs to check against.\n * @param otherShapes - The shapes or shape IDs to check for overlap.\n * @returns An array of shapes or shape IDs that overlap with the given shape.\n */\nfunction getOverlappingShapes<T extends TLShape[] | TLShapeId[]>(\n\teditor: Editor,\n\tshape: T[number],\n\totherShapes: T\n) {\n\tif (otherShapes.length === 0) {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\tconst parentPageBounds = editor.getShapePageBounds(shape)\n\tif (!parentPageBounds) return EMPTY_ARRAY\n\n\tconst parentGeometry = editor.getShapeGeometry(shape)\n\tconst parentPageTransform = editor.getShapePageTransform(shape)\n\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\n\tconst parentPageMaskVertices = editor.getShapeMask(shape)\n\tconst parentPagePolygon = parentPageMaskVertices\n\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t: parentPageCorners\n\n\tif (!parentPagePolygon) return EMPTY_ARRAY\n\n\treturn otherShapes.filter((childId) => {\n\t\tconst shapePageBounds = editor.getShapePageBounds(childId)\n\t\tif (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false\n\n\t\tconst parentPolygonInShapeShape = editor\n\t\t\t.getShapePageTransform(childId)\n\t\t\t.clone()\n\t\t\t.invert()\n\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\tconst geometry = editor.getShapeGeometry(childId)\n\n\t\treturn geometry.overlapsPolygon(parentPolygonInShapeShape)\n\t})\n}\n\n/**\n * Get the shapes that will be reparented to new parents when the shapes are dropped.\n *\n * @param editor - The editor instance.\n * @param shapes - The shapes to check.\n * @param cb - A callback to filter out certain shapes.\n * @returns An object with the shapes that will be reparented to new parents and the shapes that will be reparented to the page or their ancestral group.\n *\n * @public\n */\nexport function getDroppedShapesToNewParents(\n\teditor: Editor,\n\tshapes: Set<TLShape> | TLShape[],\n\tcb?: (shape: TLShape, parent: TLShape) => boolean\n) {\n\tconst shapesToActuallyCheck = new Set<TLShape>(shapes)\n\tconst movingGroups = new Set<TLGroupShape>()\n\n\tfor (const shape of shapes) {\n\t\tconst parent = editor.getShapeParent(shape)\n\t\tif (parent && editor.isShapeOfType<TLGroupShape>(parent, 'group')) {\n\t\t\tif (!movingGroups.has(parent)) {\n\t\t\t\tmovingGroups.add(parent)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If all of a group's children are moving, then move the group instead\n\tfor (const movingGroup of movingGroups) {\n\t\tconst children = compact(\n\t\t\teditor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))\n\t\t)\n\t\tfor (const child of children) {\n\t\t\tshapesToActuallyCheck.delete(child)\n\t\t}\n\t\tshapesToActuallyCheck.add(movingGroup)\n\t}\n\n\t// this could be cached and passed in\n\tconst shapeGroupIds = new Map<TLShapeId, TLShapeId | undefined>()\n\n\tconst reparenting = new Map<TLShapeId, TLShape[]>()\n\n\tconst remainingShapesToReparent = new Set(shapesToActuallyCheck)\n\n\tconst potentialParentShapes = editor\n\t\t.getCurrentPageShapesSorted()\n\t\t// filter out any shapes that aren't frames or that are included among the provided shapes\n\t\t.filter(\n\t\t\t(s) =>\n\t\t\t\teditor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) &&\n\t\t\t\t!remainingShapesToReparent.has(s)\n\t\t)\n\n\tparentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {\n\t\tconst parentShape = potentialParentShapes[i]\n\t\tconst parentShapeContainingGroupId = editor.findShapeAncestor(parentShape, (s) =>\n\t\t\teditor.isShapeOfType<TLGroupShape>(s, 'group')\n\t\t)?.id\n\n\t\tconst parentGeometry = editor.getShapeGeometry(parentShape)\n\t\tconst parentPageTransform = editor.getShapePageTransform(parentShape)\n\t\tconst parentPageMaskVertices = editor.getShapeMask(parentShape)\n\t\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\t\tconst parentPagePolygon = parentPageMaskVertices\n\t\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t\t: parentPageCorners\n\n\t\tif (!parentPagePolygon) continue parentCheck\n\n\t\tconst childrenToReparent = []\n\n\t\t// For each of the dropping shapes...\n\t\tshapeCheck: for (const shape of remainingShapesToReparent) {\n\t\t\t// Don't reparent a frame to itself\n\t\t\tif (parentShape.id === shape.id) continue shapeCheck\n\n\t\t\t// Use the callback to filter out certain shapes\n\t\t\tif (cb && !cb(shape, parentShape)) continue shapeCheck\n\n\t\t\tif (!shapeGroupIds.has(shape.id)) {\n\t\t\t\tshapeGroupIds.set(\n\t\t\t\t\tshape.id,\n\t\t\t\t\teditor.findShapeAncestor(shape, (s) => editor.isShapeOfType<TLGroupShape>(s, 'group'))?.id\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst shapeGroupId = shapeGroupIds.get(shape.id)\n\n\t\t\t// Are the shape and the parent part of different groups?\n\t\t\tif (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck\n\n\t\t\t// Is the shape is actually the ancestor of the parent?\n\t\t\tif (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck\n\n\t\t\t// Convert the parent polygon to the shape's space\n\t\t\tconst parentPolygonInShapeSpace = editor\n\t\t\t\t.getShapePageTransform(shape)\n\t\t\t\t.clone()\n\t\t\t\t.invert()\n\t\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\t\t// If the shape overlaps the parent polygon, reparent it to that parent\n\t\t\tif (editor.getShapeGeometry(shape).overlapsPolygon(parentPolygonInShapeSpace)) {\n\t\t\t\t// Use the util to check if the shape can be reparented to the parent\n\t\t\t\tif (\n\t\t\t\t\t!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type)\n\t\t\t\t)\n\t\t\t\t\tcontinue shapeCheck\n\n\t\t\t\tif (shape.parentId !== parentShape.id) {\n\t\t\t\t\tchildrenToReparent.push(shape)\n\t\t\t\t}\n\t\t\t\tremainingShapesToReparent.delete(shape)\n\t\t\t\tcontinue shapeCheck\n\t\t\t}\n\t\t}\n\n\t\tif (childrenToReparent.length) {\n\t\t\treparenting.set(parentShape.id, childrenToReparent)\n\t\t}\n\t}\n\n\treturn {\n\t\t// these are the shapes that will be reparented to new parents\n\t\treparenting,\n\t\t// these are the shapes that will be reparented to the page or their ancestral group\n\t\tremainingShapesToReparent,\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { EMPTY_ARRAY } from '@tldraw/state'\nimport { TLGroupShape, TLParentId, TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndexAbove, getIndexBetween } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { intersectPolygonPolygon } from '../primitives/intersect'\n\n/**\n * Reparents shapes that are no longer contained within their parent shapes.\n * todo: rename me to something more descriptive, like `reparentOccludedShapes` or `reparentAutoDroppedShapes`\n *\n * @param editor - The editor instance.\n * @param shapeIds - The IDs of the shapes to reparent.\n * @param opts - Optional options, including a callback to filter out certain parents, such as when removing a frame.\n *\n * @public\n */\nexport function kickoutOccludedShapes(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\topts?: { filter?(parent: TLShape): boolean }\n) {\n\tconst parentsToCheck = new Set<TLShape>()\n\n\tfor (const id of shapeIds) {\n\t\tconst shape = editor.getShape(id)\n\n\t\tif (!shape) continue\n\t\tparentsToCheck.add(shape)\n\n\t\tconst parent = editor.getShape(shape.parentId)\n\t\tif (!parent) continue\n\t\tparentsToCheck.add(parent)\n\t}\n\n\t// Check all of the parents and gather up parents who have lost children\n\tconst parentsToLostChildren = new Map<TLShape, TLShapeId[]>()\n\n\tfor (const parent of parentsToCheck) {\n\t\tconst childIds = editor.getSortedChildIdsForParent(parent)\n\t\tif (opts?.filter && !opts.filter(parent)) {\n\t\t\t// If the shape is filtered out, we kick out all of its children\n\t\t\tparentsToLostChildren.set(parent, childIds)\n\t\t} else {\n\t\t\tconst overlappingChildren = getOverlappingShapes(editor, parent.id, childIds)\n\t\t\tif (overlappingChildren.length < childIds.length) {\n\t\t\t\tparentsToLostChildren.set(\n\t\t\t\t\tparent,\n\t\t\t\t\tchildIds.filter((id) => !overlappingChildren.includes(id))\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get all of the shapes on the current page, sorted by their index\n\tconst sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id)\n\n\tconst parentsToNewChildren: Record<\n\t\tTLParentId,\n\t\t{ parentId: TLParentId; shapeIds: TLShapeId[]; index?: IndexKey }\n\t> = {}\n\n\tfor (const [prevParent, lostChildrenIds] of parentsToLostChildren) {\n\t\tconst lostChildren = compact(lostChildrenIds.map((id) => editor.getShape(id)))\n\n\t\t// Don't fall \"up\" into frames in front of the shape\n\t\t// if (pageShapes.indexOf(shape) < frameSortPosition) continue shapeCheck\n\n\t\t// Otherwise, we have no next dropping shape under the cursor, so go find\n\t\t// all the frames on the page where the moving shapes will fall into\n\t\tconst { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(\n\t\t\teditor,\n\t\t\tlostChildren,\n\t\t\t(shape, maybeNewParent) => {\n\t\t\t\t// If we're filtering out a potential parent, don't reparent shapes to the filtered out shape\n\t\t\t\tif (opts?.filter && !opts.filter(maybeNewParent)) return false\n\t\t\t\treturn (\n\t\t\t\t\tmaybeNewParent.id !== prevParent.id &&\n\t\t\t\t\tsortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id)\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\n\t\treparenting.forEach((childrenToReparent, newParentId) => {\n\t\t\tif (childrenToReparent.length === 0) return\n\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\tshapeIds: [],\n\t\t\t\t}\n\t\t\t}\n\t\t\tparentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id))\n\t\t})\n\n\t\t// Reparent the rest to the page (or containing group)\n\t\tif (remainingShapesToReparent.size > 0) {\n\t\t\t// The remaining shapes are going to be reparented to the old parent's containing group, if there was one, or else to the page\n\t\t\tconst newParentId =\n\t\t\t\teditor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType<TLGroupShape>(s, 'group'))\n\t\t\t\t\t?.id ?? editor.getCurrentPageId()\n\n\t\t\tremainingShapesToReparent.forEach((shape) => {\n\t\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\t\tlet insertIndexKey: IndexKey | undefined\n\n\t\t\t\t\tconst oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId)\n\t\t\t\t\tconst oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id)\n\t\t\t\t\tif (oldParentIndex > -1) {\n\t\t\t\t\t\t// If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.\n\t\t\t\t\t\tconst siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]\n\t\t\t\t\t\tconst indexKeyAbove = siblingsIndexAbove\n\t\t\t\t\t\t\t? editor.getShape(siblingsIndexAbove)!.index\n\t\t\t\t\t\t\t: getIndexAbove(prevParent.index)\n\t\t\t\t\t\tinsertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the old parent is not a direct child of the new parent, then we'll add them to the \"top\" of the new parent's children.\n\t\t\t\t\t\t// This is done automatically if we leave the index undefined, so let's do that.\n\t\t\t\t\t}\n\n\t\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\t\tshapeIds: [],\n\t\t\t\t\t\tindex: insertIndexKey,\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tparentsToNewChildren[newParentId].shapeIds.push(shape.id)\n\t\t\t})\n\t\t}\n\t}\n\n\teditor.run(() => {\n\t\tObject.values(parentsToNewChildren).forEach(({ parentId, shapeIds, index }) => {\n\t\t\tif (shapeIds.length === 0) return\n\t\t\t// Before we reparent, sort the new shape ids by their place in the original absolute order on the page\n\t\t\tshapeIds.sort((a, b) => (sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1))\n\t\t\teditor.reparentShapes(shapeIds, parentId, index)\n\t\t})\n\t})\n}\n\n/**\n * Get the shapes that overlap with a given shape.\n *\n * @param editor - The editor instance.\n * @param shape - The shapes or shape IDs to check against.\n * @param otherShapes - The shapes or shape IDs to check for overlap.\n * @returns An array of shapes or shape IDs that overlap with the given shape.\n */\nfunction getOverlappingShapes<T extends TLShape[] | TLShapeId[]>(\n\teditor: Editor,\n\tshape: T[number],\n\totherShapes: T\n) {\n\tif (otherShapes.length === 0) {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\tconst parentPageBounds = editor.getShapePageBounds(shape)\n\tif (!parentPageBounds) return EMPTY_ARRAY\n\n\tconst parentGeometry = editor.getShapeGeometry(shape)\n\tconst parentPageTransform = editor.getShapePageTransform(shape)\n\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\n\tconst _shape = editor.getShape(shape)\n\tif (!_shape) return EMPTY_ARRAY\n\n\tconst pageTransform = editor.getShapePageTransform(shape)\n\tconst clipPath = editor.getShapeUtil(_shape.type).getClipPath?.(_shape)\n\n\tconst parentPageMaskVertices = clipPath ? pageTransform.applyToPoints(clipPath) : undefined\n\tconst parentPagePolygon = parentPageMaskVertices\n\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t: parentPageCorners\n\n\tif (!parentPagePolygon) return EMPTY_ARRAY\n\n\treturn otherShapes.filter((childId) => {\n\t\tconst shapePageBounds = editor.getShapePageBounds(childId)\n\t\tif (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false\n\n\t\tconst parentPolygonInShapeShape = editor\n\t\t\t.getShapePageTransform(childId)\n\t\t\t.clone()\n\t\t\t.invert()\n\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\tconst geometry = editor.getShapeGeometry(childId)\n\n\t\treturn geometry.overlapsPolygon(parentPolygonInShapeShape)\n\t})\n}\n\n/**\n * Get the shapes that will be reparented to new parents when the shapes are dropped.\n *\n * @param editor - The editor instance.\n * @param shapes - The shapes to check.\n * @param cb - A callback to filter out certain shapes.\n * @returns An object with the shapes that will be reparented to new parents and the shapes that will be reparented to the page or their ancestral group.\n *\n * @public\n */\nexport function getDroppedShapesToNewParents(\n\teditor: Editor,\n\tshapes: Set<TLShape> | TLShape[],\n\tcb?: (shape: TLShape, parent: TLShape) => boolean\n) {\n\tconst shapesToActuallyCheck = new Set<TLShape>(shapes)\n\tconst movingGroups = new Set<TLGroupShape>()\n\n\tfor (const shape of shapes) {\n\t\tconst parent = editor.getShapeParent(shape)\n\t\tif (parent && editor.isShapeOfType<TLGroupShape>(parent, 'group')) {\n\t\t\tif (!movingGroups.has(parent)) {\n\t\t\t\tmovingGroups.add(parent)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If all of a group's children are moving, then move the group instead\n\tfor (const movingGroup of movingGroups) {\n\t\tconst children = compact(\n\t\t\teditor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))\n\t\t)\n\t\tfor (const child of children) {\n\t\t\tshapesToActuallyCheck.delete(child)\n\t\t}\n\t\tshapesToActuallyCheck.add(movingGroup)\n\t}\n\n\t// this could be cached and passed in\n\tconst shapeGroupIds = new Map<TLShapeId, TLShapeId | undefined>()\n\n\tconst reparenting = new Map<TLShapeId, TLShape[]>()\n\n\tconst remainingShapesToReparent = new Set(shapesToActuallyCheck)\n\n\tconst potentialParentShapes = editor\n\t\t.getCurrentPageShapesSorted()\n\t\t// filter out any shapes that aren't frames or that are included among the provided shapes\n\t\t.filter(\n\t\t\t(s) =>\n\t\t\t\teditor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) &&\n\t\t\t\t!remainingShapesToReparent.has(s)\n\t\t)\n\n\tparentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {\n\t\tconst parentShape = potentialParentShapes[i]\n\t\tconst parentShapeContainingGroupId = editor.findShapeAncestor(parentShape, (s) =>\n\t\t\teditor.isShapeOfType<TLGroupShape>(s, 'group')\n\t\t)?.id\n\n\t\tconst parentGeometry = editor.getShapeGeometry(parentShape)\n\t\tconst parentPageTransform = editor.getShapePageTransform(parentShape)\n\t\tconst parentPageMaskVertices = editor.getShapeMask(parentShape)\n\t\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\t\tconst parentPagePolygon = parentPageMaskVertices\n\t\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t\t: parentPageCorners\n\n\t\tif (!parentPagePolygon) continue parentCheck\n\n\t\tconst childrenToReparent = []\n\n\t\t// For each of the dropping shapes...\n\t\tshapeCheck: for (const shape of remainingShapesToReparent) {\n\t\t\t// Don't reparent a frame to itself\n\t\t\tif (parentShape.id === shape.id) continue shapeCheck\n\n\t\t\t// Use the callback to filter out certain shapes\n\t\t\tif (cb && !cb(shape, parentShape)) continue shapeCheck\n\n\t\t\tif (!shapeGroupIds.has(shape.id)) {\n\t\t\t\tshapeGroupIds.set(\n\t\t\t\t\tshape.id,\n\t\t\t\t\teditor.findShapeAncestor(shape, (s) => editor.isShapeOfType<TLGroupShape>(s, 'group'))?.id\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst shapeGroupId = shapeGroupIds.get(shape.id)\n\n\t\t\t// Are the shape and the parent part of different groups?\n\t\t\tif (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck\n\n\t\t\t// Is the shape is actually the ancestor of the parent?\n\t\t\tif (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck\n\n\t\t\t// Convert the parent polygon to the shape's space\n\t\t\tconst parentPolygonInShapeSpace = editor\n\t\t\t\t.getShapePageTransform(shape)\n\t\t\t\t.clone()\n\t\t\t\t.invert()\n\t\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\t\t// If the shape overlaps the parent polygon, reparent it to that parent\n\t\t\tif (editor.getShapeGeometry(shape).overlapsPolygon(parentPolygonInShapeSpace)) {\n\t\t\t\t// Use the util to check if the shape can be reparented to the parent\n\t\t\t\tif (\n\t\t\t\t\t!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type)\n\t\t\t\t)\n\t\t\t\t\tcontinue shapeCheck\n\n\t\t\t\tif (shape.parentId !== parentShape.id) {\n\t\t\t\t\tchildrenToReparent.push(shape)\n\t\t\t\t}\n\t\t\t\tremainingShapesToReparent.delete(shape)\n\t\t\t\tcontinue shapeCheck\n\t\t\t}\n\t\t}\n\n\t\tif (childrenToReparent.length) {\n\t\t\treparenting.set(parentShape.id, childrenToReparent)\n\t\t}\n\t}\n\n\treturn {\n\t\t// these are the shapes that will be reparented to new parents\n\t\treparenting,\n\t\t// these are the shapes that will be reparented to the page or their ancestral group\n\t\tremainingShapesToReparent,\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["shapeIds"]
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "3.16.0-canary.2e83e38fb91b";
1
+ const version = "3.16.0-canary.344cec0354f3";
2
2
  const publishDates = {
3
3
  major: "2024-09-13T14:36:29.063Z",
4
- minor: "2025-09-15T17:14:42.089Z",
5
- patch: "2025-09-15T17:14:42.089Z"
4
+ minor: "2025-09-18T14:24:07.314Z",
5
+ patch: "2025-09-18T14:24:07.314Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.16.0-canary.2e83e38fb91b'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-09-15T17:14:42.089Z',\n\tpatch: '2025-09-15T17:14:42.089Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.16.0-canary.344cec0354f3'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-09-18T14:24:07.314Z',\n\tpatch: '2025-09-18T14:24:07.314Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "tldraw infinite canvas SDK (editor).",
4
- "version": "3.16.0-canary.2e83e38fb91b",
4
+ "version": "3.16.0-canary.344cec0354f3",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -50,12 +50,12 @@
50
50
  "@tiptap/core": "^2.9.1",
51
51
  "@tiptap/pm": "^2.9.1",
52
52
  "@tiptap/react": "^2.9.1",
53
- "@tldraw/state": "3.16.0-canary.2e83e38fb91b",
54
- "@tldraw/state-react": "3.16.0-canary.2e83e38fb91b",
55
- "@tldraw/store": "3.16.0-canary.2e83e38fb91b",
56
- "@tldraw/tlschema": "3.16.0-canary.2e83e38fb91b",
57
- "@tldraw/utils": "3.16.0-canary.2e83e38fb91b",
58
- "@tldraw/validate": "3.16.0-canary.2e83e38fb91b",
53
+ "@tldraw/state": "3.16.0-canary.344cec0354f3",
54
+ "@tldraw/state-react": "3.16.0-canary.344cec0354f3",
55
+ "@tldraw/store": "3.16.0-canary.344cec0354f3",
56
+ "@tldraw/tlschema": "3.16.0-canary.344cec0354f3",
57
+ "@tldraw/utils": "3.16.0-canary.344cec0354f3",
58
+ "@tldraw/validate": "3.16.0-canary.344cec0354f3",
59
59
  "@types/core-js": "^2.5.8",
60
60
  "@use-gesture/react": "^10.3.1",
61
61
  "classnames": "^2.5.1",
package/src/index.ts CHANGED
@@ -447,12 +447,10 @@ export {
447
447
  export {
448
448
  activeElementShouldCaptureKeys,
449
449
  loopToHtmlElement,
450
- markEventAsHandled,
451
450
  preventDefault,
452
451
  releasePointerCapture,
453
452
  setPointerCapture,
454
453
  stopEventPropagation,
455
- wasEventAlreadyHandled,
456
454
  } from './lib/utils/dom'
457
455
  export { EditorAtom } from './lib/utils/EditorAtom'
458
456
  export { getIncrementedName } from './lib/utils/getIncrementedName'
@@ -44,7 +44,6 @@ import { LicenseProvider } from './license/LicenseProvider'
44
44
  import { Watermark } from './license/Watermark'
45
45
  import { TldrawOptions } from './options'
46
46
  import { TLDeepLinkOptions } from './utils/deepLinks'
47
- import { markEventAsHandled } from './utils/dom'
48
47
  import { TLTextOptions } from './utils/richText'
49
48
  import { TLStoreWithStatus } from './utils/sync/StoreWithStatus'
50
49
 
@@ -275,7 +274,6 @@ export const TldrawEditor = memo(function TldrawEditor({
275
274
  data-tldraw={version}
276
275
  draggable={false}
277
276
  className={classNames(`${TL_CONTAINER_CLASS} tl-theme__light`, className)}
278
- onPointerDown={markEventAsHandled}
279
277
  tabIndex={-1}
280
278
  role="application"
281
279
  aria-label={_options?.branding ?? 'tldraw'}
@@ -21,7 +21,7 @@ import { Mat } from '../../primitives/Mat'
21
21
  import { Vec } from '../../primitives/Vec'
22
22
  import { toDomPrecision } from '../../primitives/utils'
23
23
  import { debugFlags } from '../../utils/debug-flags'
24
- import { markEventAsHandled, setStyleProperty } from '../../utils/dom'
24
+ import { setStyleProperty } from '../../utils/dom'
25
25
  import { GeometryDebuggingView } from '../GeometryDebuggingView'
26
26
  import { LiveCollaborators } from '../LiveCollaborators'
27
27
  import { MenuClickCapture } from '../MenuClickCapture'
@@ -174,10 +174,10 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
174
174
  </div>
175
175
  <div
176
176
  className="tl-canvas__in-front"
177
- onPointerDown={markEventAsHandled}
178
- onPointerUp={markEventAsHandled}
179
- onTouchStart={markEventAsHandled}
180
- onTouchEnd={markEventAsHandled}
177
+ onPointerDown={editor.markEventAsHandled}
178
+ onPointerUp={editor.markEventAsHandled}
179
+ onTouchStart={editor.markEventAsHandled}
180
+ onTouchEnd={editor.markEventAsHandled}
181
181
  >
182
182
  <InFrontOfTheCanvasWrapper />
183
183
  </div>
@@ -24,7 +24,8 @@ export interface TLUserPreferences {
24
24
  isWrapMode?: boolean | null
25
25
  isDynamicSizeMode?: boolean | null
26
26
  isPasteAtCursorMode?: boolean | null
27
- showUiLabels?: boolean | null
27
+ enhancedA11yMode?: boolean | null
28
+ inputMode?: 'trackpad' | 'mouse' | null
28
29
  }
29
30
 
30
31
  interface UserDataSnapshot {
@@ -53,7 +54,8 @@ export const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUser
53
54
  isWrapMode: T.boolean.nullable().optional(),
54
55
  isDynamicSizeMode: T.boolean.nullable().optional(),
55
56
  isPasteAtCursorMode: T.boolean.nullable().optional(),
56
- showUiLabels: T.boolean.nullable().optional(),
57
+ enhancedA11yMode: T.boolean.nullable().optional(),
58
+ inputMode: T.literalEnum('trackpad', 'mouse').nullable().optional(),
57
59
  })
58
60
 
59
61
  const Versions = {
@@ -67,6 +69,8 @@ const Versions = {
67
69
  AddPasteAtCursor: 8,
68
70
  AddKeyboardShortcuts: 9,
69
71
  AddShowUiLabels: 10,
72
+ AddPointerPeripheral: 11,
73
+ RenameShowUiLabelsToEnhancedA11yMode: 12,
70
74
  } as const
71
75
 
72
76
  const CURRENT_VERSION = Math.max(...Object.values(Versions))
@@ -108,6 +112,14 @@ function migrateSnapshot(data: { version: number; user: any }) {
108
112
  if (data.version < Versions.AddShowUiLabels) {
109
113
  data.user.showUiLabels = false
110
114
  }
115
+ if (data.version < Versions.RenameShowUiLabelsToEnhancedA11yMode) {
116
+ data.user.enhancedA11yMode = data.user.showUiLabels
117
+ delete data.user.showUiLabels
118
+ }
119
+
120
+ if (data.version < Versions.AddPointerPeripheral) {
121
+ data.user.inputMode = null
122
+ }
111
123
 
112
124
  // finally
113
125
  data.version = CURRENT_VERSION
@@ -156,8 +168,9 @@ export const defaultUserPreferences = Object.freeze({
156
168
  isWrapMode: false,
157
169
  isDynamicSizeMode: false,
158
170
  isPasteAtCursorMode: false,
159
- showUiLabels: false,
171
+ enhancedA11yMode: false,
160
172
  colorScheme: 'light',
173
+ inputMode: null,
161
174
  }) satisfies Readonly<Omit<TLUserPreferences, 'id'>>
162
175
 
163
176
  /** @public */
@@ -343,6 +343,8 @@ export class Editor extends EventEmitter<TLEventMap> {
343
343
  this.root = new NewRoot(this)
344
344
  this.root.children = {}
345
345
 
346
+ this.markEventAsHandled = this.markEventAsHandled.bind(this)
347
+
346
348
  const allShapeUtils = checkShapesAndAddCore(shapeUtils)
347
349
 
348
350
  const _shapeUtils = {} as Record<string, ShapeUtil<any>>
@@ -10097,6 +10099,37 @@ export class Editor extends EventEmitter<TLEventMap> {
10097
10099
  /** @internal */
10098
10100
  private performanceTrackerTimeout = -1 as any
10099
10101
 
10102
+ /** @internal */
10103
+ private handledEvents = new WeakSet<Event>()
10104
+
10105
+ /**
10106
+ * In tldraw, events are sometimes handled by multiple components. For example, the shapes might
10107
+ * have events, but the canvas handles events too. The way that the canvas handles events can
10108
+ * interfere with the with the shapes event handlers - for example, it calls `.preventDefault()`
10109
+ * on `pointerDown`, which also prevents `click` events from firing on the shapes.
10110
+ *
10111
+ * You can use `.stopPropagation()` to prevent the event from propagating to the rest of the
10112
+ * DOM, but that can impact non-tldraw event handlers set up elsewhere. By using
10113
+ * `markEventAsHandled`, you'll stop other parts of tldraw from handling the event without
10114
+ * impacting other, non-tldraw event handlers. See also {@link Editor.wasEventAlreadyHandled}.
10115
+ *
10116
+ * @public
10117
+ */
10118
+ markEventAsHandled(e: Event | { nativeEvent: Event }) {
10119
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10120
+ this.handledEvents.add(nativeEvent)
10121
+ }
10122
+
10123
+ /**
10124
+ * Checks if an event has already been handled. See {@link Editor.markEventAsHandled}.
10125
+ *
10126
+ * @public
10127
+ */
10128
+ wasEventAlreadyHandled(e: Event | { nativeEvent: Event }) {
10129
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10130
+ return this.handledEvents.has(nativeEvent)
10131
+ }
10132
+
10100
10133
  /**
10101
10134
  * Dispatch an event to the editor.
10102
10135
  *
@@ -10301,7 +10334,14 @@ export class Editor extends EventEmitter<TLEventMap> {
10301
10334
 
10302
10335
  this._updateInputsFromEvent(info)
10303
10336
 
10304
- const { panSpeed, zoomSpeed, wheelBehavior } = cameraOptions
10337
+ const { panSpeed, zoomSpeed } = cameraOptions
10338
+ let wheelBehavior = cameraOptions.wheelBehavior
10339
+ const inputMode = this.user.getUserPreferences().inputMode
10340
+
10341
+ // If the user has set their input mode preference, then use that to determine the wheel behavior
10342
+ if (inputMode !== null) {
10343
+ wheelBehavior = inputMode === 'trackpad' ? 'pan' : 'zoom'
10344
+ }
10305
10345
 
10306
10346
  if (wheelBehavior !== 'none') {
10307
10347
  // Stop any camera animation
@@ -58,8 +58,12 @@ export class FocusManager {
58
58
 
59
59
  private handleKeyDown(keyEvent: KeyboardEvent) {
60
60
  const container = this.editor.getContainer()
61
- if (this.editor.isIn('select.editing_shape')) return
62
- if (document.activeElement === container && this.editor.getSelectedShapeIds().length > 0) return
61
+ const activeEl = document.activeElement
62
+ // Edit mode should remove the focus ring, however if the active element's
63
+ // parent is the contextual toolbar, then allow it.
64
+ if (this.editor.isIn('select.editing_shape') && !activeEl?.closest('.tlui-contextual-toolbar'))
65
+ return
66
+ if (activeEl === container && this.editor.getSelectedShapeIds().length > 0) return
63
67
  if (['Tab', 'ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
64
68
  container.classList.remove('tl-container__no-focus-ring')
65
69
  }
@@ -23,13 +23,14 @@ describe('UserPreferencesManager', () => {
23
23
  locale: 'en',
24
24
  animationSpeed: 1,
25
25
  areKeyboardShortcutsEnabled: true,
26
- showUiLabels: false,
26
+ enhancedA11yMode: false,
27
27
  edgeScrollSpeed: 1,
28
28
  colorScheme: 'light',
29
29
  isSnapMode: false,
30
30
  isWrapMode: false,
31
31
  isDynamicSizeMode: false,
32
32
  isPasteAtCursorMode: false,
33
+ inputMode: null,
33
34
  ...overrides,
34
35
  })
35
36
 
@@ -227,12 +228,13 @@ describe('UserPreferencesManager', () => {
227
228
  color: mockUserPreferences.color,
228
229
  animationSpeed: mockUserPreferences.animationSpeed,
229
230
  areKeyboardShortcutsEnabled: mockUserPreferences.areKeyboardShortcutsEnabled,
230
- showUiLabels: mockUserPreferences.showUiLabels,
231
+ enhancedA11yMode: mockUserPreferences.enhancedA11yMode,
231
232
  isSnapMode: mockUserPreferences.isSnapMode,
232
233
  colorScheme: mockUserPreferences.colorScheme,
233
234
  isDarkMode: false, // light mode
234
235
  isWrapMode: mockUserPreferences.isWrapMode,
235
236
  isDynamicResizeMode: mockUserPreferences.isDynamicSizeMode,
237
+ inputMode: mockUserPreferences.inputMode,
236
238
  })
237
239
  })
238
240
 
@@ -376,14 +378,18 @@ describe('UserPreferencesManager', () => {
376
378
  })
377
379
  })
378
380
 
379
- describe('getShowUiLabels', () => {
380
- it('should return user show ui labels setting', () => {
381
- expect(userPreferencesManager.getShowUiLabels()).toBe(mockUserPreferences.showUiLabels)
381
+ describe('getEnhancedA11yMode', () => {
382
+ it('should return user enhanced a11y mode setting', () => {
383
+ expect(userPreferencesManager.getEnhancedA11yMode()).toBe(
384
+ mockUserPreferences.enhancedA11yMode
385
+ )
382
386
  })
383
387
 
384
- it('should return default show ui labels when null', () => {
385
- userPreferencesAtom.set({ ...mockUserPreferences, showUiLabels: null })
386
- expect(userPreferencesManager.getShowUiLabels()).toBe(defaultUserPreferences.showUiLabels)
388
+ it('should return default enhanced a11y mode when null', () => {
389
+ userPreferencesAtom.set({ ...mockUserPreferences, enhancedA11yMode: null })
390
+ expect(userPreferencesManager.getEnhancedA11yMode()).toBe(
391
+ defaultUserPreferences.enhancedA11yMode
392
+ )
387
393
  })
388
394
  })
389
395
 
@@ -453,6 +459,22 @@ describe('UserPreferencesManager', () => {
453
459
  )
454
460
  })
455
461
  })
462
+
463
+ describe('getInputMode', () => {
464
+ it('should return user input mode setting', () => {
465
+ expect(userPreferencesManager.getInputMode()).toBe(null)
466
+ })
467
+
468
+ it('should return trackpad if input mode is trackpad', () => {
469
+ userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'trackpad' })
470
+ expect(userPreferencesManager.getInputMode()).toBe('trackpad')
471
+ })
472
+
473
+ it('should return mouse if input mode is mouse', () => {
474
+ userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'mouse' })
475
+ expect(userPreferencesManager.getInputMode()).toBe('mouse')
476
+ })
477
+ })
456
478
  })
457
479
 
458
480
  describe('reactive behavior', () => {
@@ -49,7 +49,8 @@ export class UserPreferencesManager {
49
49
  isDarkMode: this.getIsDarkMode(),
50
50
  isWrapMode: this.getIsWrapMode(),
51
51
  isDynamicResizeMode: this.getIsDynamicResizeMode(),
52
- showUiLabels: this.getShowUiLabels(),
52
+ enhancedA11yMode: this.getEnhancedA11yMode(),
53
+ inputMode: this.getInputMode(),
53
54
  }
54
55
  }
55
56
 
@@ -121,7 +122,13 @@ export class UserPreferencesManager {
121
122
  )
122
123
  }
123
124
 
124
- @computed getShowUiLabels() {
125
- return this.user.userPreferences.get().showUiLabels ?? defaultUserPreferences.showUiLabels
125
+ @computed getEnhancedA11yMode() {
126
+ return (
127
+ this.user.userPreferences.get().enhancedA11yMode ?? defaultUserPreferences.enhancedA11yMode
128
+ )
129
+ }
130
+
131
+ @computed getInputMode() {
132
+ return this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode
126
133
  }
127
134
  }
@@ -1,13 +1,7 @@
1
1
  import { useValue } from '@tldraw/state-react'
2
2
  import React, { useEffect, useMemo } from 'react'
3
3
  import { RIGHT_MOUSE_BUTTON } from '../constants'
4
- import {
5
- markEventAsHandled,
6
- preventDefault,
7
- releasePointerCapture,
8
- setPointerCapture,
9
- wasEventAlreadyHandled,
10
- } from '../utils/dom'
4
+ import { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom'
11
5
  import { getPointerInfo } from '../utils/getPointerInfo'
12
6
  import { useEditor } from './useEditor'
13
7
 
@@ -18,14 +12,14 @@ export function useCanvasEvents() {
18
12
  const events = useMemo(
19
13
  function canvasEvents() {
20
14
  function onPointerDown(e: React.PointerEvent) {
21
- if (wasEventAlreadyHandled(e)) return
15
+ if (editor.wasEventAlreadyHandled(e)) return
22
16
 
23
17
  if (e.button === RIGHT_MOUSE_BUTTON) {
24
18
  editor.dispatch({
25
19
  type: 'pointer',
26
20
  target: 'canvas',
27
21
  name: 'right_click',
28
- ...getPointerInfo(e),
22
+ ...getPointerInfo(editor, e),
29
23
  })
30
24
  return
31
25
  }
@@ -38,12 +32,12 @@ export function useCanvasEvents() {
38
32
  type: 'pointer',
39
33
  target: 'canvas',
40
34
  name: 'pointer_down',
41
- ...getPointerInfo(e),
35
+ ...getPointerInfo(editor, e),
42
36
  })
43
37
  }
44
38
 
45
39
  function onPointerUp(e: React.PointerEvent) {
46
- if (wasEventAlreadyHandled(e)) return
40
+ if (editor.wasEventAlreadyHandled(e)) return
47
41
  if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
48
42
 
49
43
  releasePointerCapture(e.currentTarget, e)
@@ -52,33 +46,33 @@ export function useCanvasEvents() {
52
46
  type: 'pointer',
53
47
  target: 'canvas',
54
48
  name: 'pointer_up',
55
- ...getPointerInfo(e),
49
+ ...getPointerInfo(editor, e),
56
50
  })
57
51
  }
58
52
 
59
53
  function onPointerEnter(e: React.PointerEvent) {
60
- if (wasEventAlreadyHandled(e)) return
54
+ if (editor.wasEventAlreadyHandled(e)) return
61
55
  if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
62
56
  const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
63
57
  editor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })
64
58
  }
65
59
 
66
60
  function onPointerLeave(e: React.PointerEvent) {
67
- if (wasEventAlreadyHandled(e)) return
61
+ if (editor.wasEventAlreadyHandled(e)) return
68
62
  if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
69
63
  const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
70
64
  editor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })
71
65
  }
72
66
 
73
67
  function onTouchStart(e: React.TouchEvent) {
74
- if (wasEventAlreadyHandled(e)) return
75
- markEventAsHandled(e)
68
+ if (editor.wasEventAlreadyHandled(e)) return
69
+ editor.markEventAsHandled(e)
76
70
  preventDefault(e)
77
71
  }
78
72
 
79
73
  function onTouchEnd(e: React.TouchEvent) {
80
- if (wasEventAlreadyHandled(e)) return
81
- markEventAsHandled(e)
74
+ if (editor.wasEventAlreadyHandled(e)) return
75
+ editor.markEventAsHandled(e)
82
76
  // check that e.target is an HTMLElement
83
77
  if (!(e.target instanceof HTMLElement)) return
84
78
 
@@ -97,12 +91,12 @@ export function useCanvasEvents() {
97
91
  }
98
92
 
99
93
  function onDragOver(e: React.DragEvent<Element>) {
100
- if (wasEventAlreadyHandled(e)) return
94
+ if (editor.wasEventAlreadyHandled(e)) return
101
95
  preventDefault(e)
102
96
  }
103
97
 
104
98
  async function onDrop(e: React.DragEvent<Element>) {
105
- if (wasEventAlreadyHandled(e)) return
99
+ if (editor.wasEventAlreadyHandled(e)) return
106
100
  preventDefault(e)
107
101
  e.stopPropagation()
108
102
 
@@ -129,7 +123,7 @@ export function useCanvasEvents() {
129
123
  }
130
124
 
131
125
  function onClick(e: React.MouseEvent) {
132
- if (wasEventAlreadyHandled(e)) return
126
+ if (editor.wasEventAlreadyHandled(e)) return
133
127
  e.stopPropagation()
134
128
  }
135
129
 
@@ -157,8 +151,8 @@ export function useCanvasEvents() {
157
151
  let lastX: number, lastY: number
158
152
 
159
153
  function onPointerMove(e: PointerEvent) {
160
- if (wasEventAlreadyHandled(e)) return
161
- markEventAsHandled(e)
154
+ if (editor.wasEventAlreadyHandled(e)) return
155
+ editor.markEventAsHandled(e)
162
156
 
163
157
  if (e.clientX === lastX && e.clientY === lastY) return
164
158
  lastX = e.clientX
@@ -174,7 +168,7 @@ export function useCanvasEvents() {
174
168
  type: 'pointer',
175
169
  target: 'canvas',
176
170
  name: 'pointer_move',
177
- ...getPointerInfo(singleEvent),
171
+ ...getPointerInfo(editor, singleEvent),
178
172
  })
179
173
  }
180
174
  }
@@ -2,12 +2,7 @@ import { useValue } from '@tldraw/state-react'
2
2
  import { useEffect } from 'react'
3
3
  import { Editor } from '../editor/Editor'
4
4
  import { TLKeyboardEventInfo } from '../editor/types/event-types'
5
- import {
6
- activeElementShouldCaptureKeys,
7
- markEventAsHandled,
8
- preventDefault,
9
- wasEventAlreadyHandled,
10
- } from '../utils/dom'
5
+ import { activeElementShouldCaptureKeys, preventDefault } from '../utils/dom'
11
6
  import { isAccelKey } from '../utils/keyboard'
12
7
  import { useContainer } from './useContainer'
13
8
  import { useEditor } from './useEditor'
@@ -108,8 +103,8 @@ export function useDocumentEvents() {
108
103
  preventDefault(e)
109
104
  }
110
105
 
111
- if (wasEventAlreadyHandled(e)) return
112
- markEventAsHandled(e)
106
+ if (editor.wasEventAlreadyHandled(e)) return
107
+ editor.markEventAsHandled(e)
113
108
  const hasSelectedShapes = !!editor.getSelectedShapeIds().length
114
109
 
115
110
  switch (e.key) {
@@ -216,8 +211,8 @@ export function useDocumentEvents() {
216
211
  }
217
212
 
218
213
  const handleKeyUp = (e: KeyboardEvent) => {
219
- if (wasEventAlreadyHandled(e)) return
220
- markEventAsHandled(e)
214
+ if (editor.wasEventAlreadyHandled(e)) return
215
+ editor.markEventAsHandled(e)
221
216
 
222
217
  if (areShortcutsDisabled(editor)) {
223
218
  return
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from 'react'
2
- import { markEventAsHandled, preventDefault } from '../utils/dom'
2
+ import { preventDefault } from '../utils/dom'
3
3
  import { useEditor } from './useEditor'
4
4
 
5
5
  const IGNORED_TAGS = ['textarea', 'input']
@@ -19,7 +19,7 @@ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLE
19
19
 
20
20
  const handleEvent = (e: PointerEvent | TouchEvent) => {
21
21
  if (e instanceof PointerEvent && e.pointerType === 'pen') {
22
- markEventAsHandled(e)
22
+ editor.markEventAsHandled(e)
23
23
  const { target } = e
24
24
 
25
25
  // Allow events to propagate if the app is editing a shape, or if the event is occurring in a text area or input