@tldraw/editor 5.2.0-next.ee0fa4d6244f → 5.2.0

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 (189) hide show
  1. package/README.md +7 -1
  2. package/dist-cjs/index.d.ts +117 -60
  3. package/dist-cjs/index.js +7 -8
  4. package/dist-cjs/index.js.map +2 -2
  5. package/dist-cjs/lib/components/MenuClickCapture.js +8 -5
  6. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  7. package/dist-cjs/lib/components/Shape.js +47 -1
  8. package/dist-cjs/lib/components/Shape.js.map +3 -3
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -16
  10. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  11. package/dist-cjs/lib/editor/Editor.js +131 -57
  12. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  13. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +8 -58
  14. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  15. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +3 -3
  16. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  17. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +1 -2
  18. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +28 -4
  20. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +3 -3
  21. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +24 -2
  22. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +14 -3
  24. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +4 -2
  26. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +7 -3
  28. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  29. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +0 -1
  30. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +15 -2
  32. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  33. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +79 -0
  34. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +7 -0
  35. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  36. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +4 -0
  37. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  38. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  39. package/dist-cjs/lib/{hooks/useTransform.js → editor/tools/cancelShapeCreationOnLongPress.js} +8 -23
  40. package/dist-cjs/lib/editor/tools/cancelShapeCreationOnLongPress.js.map +7 -0
  41. package/dist-cjs/lib/editor/types/event-types.js +0 -2
  42. package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
  43. package/dist-cjs/lib/globals/environment.js +6 -1
  44. package/dist-cjs/lib/globals/environment.js.map +2 -2
  45. package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
  46. package/dist-cjs/lib/hooks/useCanvasEvents.js +17 -8
  47. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  48. package/dist-cjs/lib/hooks/useEditorComponents.js +0 -1
  49. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  50. package/dist-cjs/lib/hooks/usePresence.js.map +2 -2
  51. package/dist-cjs/lib/hooks/useScreenBounds.js +1 -0
  52. package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
  53. package/dist-cjs/lib/license/LicenseManager.js +5 -1
  54. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  55. package/dist-cjs/lib/primitives/Vec.js +3 -2
  56. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  57. package/dist-cjs/lib/utils/dom.js +12 -0
  58. package/dist-cjs/lib/utils/dom.js.map +2 -2
  59. package/dist-cjs/lib/utils/getPointerInfo.js +2 -1
  60. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  61. package/dist-cjs/lib/utils/pointer.js +37 -0
  62. package/dist-cjs/lib/utils/pointer.js.map +7 -0
  63. package/dist-cjs/version.js +3 -3
  64. package/dist-cjs/version.js.map +1 -1
  65. package/dist-esm/index.d.mts +117 -60
  66. package/dist-esm/index.mjs +9 -13
  67. package/dist-esm/index.mjs.map +2 -2
  68. package/dist-esm/lib/components/MenuClickCapture.mjs +8 -5
  69. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  70. package/dist-esm/lib/components/Shape.mjs +48 -2
  71. package/dist-esm/lib/components/Shape.mjs.map +3 -3
  72. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -16
  73. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  74. package/dist-esm/lib/editor/Editor.mjs +131 -57
  75. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  76. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +8 -58
  77. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  78. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +3 -3
  79. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  80. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +1 -2
  81. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  82. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +28 -4
  83. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +3 -3
  84. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +24 -2
  85. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +14 -3
  87. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +4 -2
  89. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +2 -2
  90. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +7 -3
  91. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  92. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +0 -1
  93. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  94. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +15 -2
  95. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  96. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +59 -0
  97. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +7 -0
  98. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  99. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +4 -0
  100. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  101. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  102. package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs +8 -0
  103. package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs.map +7 -0
  104. package/dist-esm/lib/editor/types/event-types.mjs +0 -2
  105. package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
  106. package/dist-esm/lib/globals/environment.mjs +6 -1
  107. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  108. package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
  109. package/dist-esm/lib/hooks/useCanvasEvents.mjs +17 -8
  110. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  111. package/dist-esm/lib/hooks/useEditorComponents.mjs +0 -1
  112. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  113. package/dist-esm/lib/hooks/usePresence.mjs.map +2 -2
  114. package/dist-esm/lib/hooks/useScreenBounds.mjs +1 -0
  115. package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
  116. package/dist-esm/lib/license/LicenseManager.mjs +5 -1
  117. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  118. package/dist-esm/lib/primitives/Vec.mjs +3 -2
  119. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  120. package/dist-esm/lib/utils/dom.mjs +12 -0
  121. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  122. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -1
  123. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  124. package/dist-esm/lib/utils/pointer.mjs +17 -0
  125. package/dist-esm/lib/utils/pointer.mjs.map +7 -0
  126. package/dist-esm/version.mjs +3 -3
  127. package/dist-esm/version.mjs.map +1 -1
  128. package/editor.css +15 -3
  129. package/package.json +11 -8
  130. package/src/index.ts +4 -10
  131. package/src/lib/components/MenuClickCapture.tsx +8 -4
  132. package/src/lib/components/Shape.tsx +88 -1
  133. package/src/lib/components/default-components/DefaultCanvas.tsx +1 -17
  134. package/src/lib/editor/Editor.test.ts +36 -0
  135. package/src/lib/editor/Editor.ts +204 -75
  136. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +54 -74
  137. package/src/lib/editor/managers/ClickManager/ClickManager.ts +15 -65
  138. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.test.ts +43 -16
  139. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +8 -5
  140. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +4 -4
  141. package/src/lib/editor/managers/FocusManager/FocusManager.ts +1 -2
  142. package/src/lib/editor/managers/FontManager/FontManager.test.ts +37 -10
  143. package/src/lib/editor/managers/FontManager/FontManager.ts +46 -1
  144. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +32 -0
  145. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +34 -4
  146. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +61 -0
  147. package/src/lib/editor/managers/InputsManager/InputsManager.ts +16 -4
  148. package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +9 -2
  149. package/src/lib/editor/managers/TextManager/TextManager.test.ts +16 -14
  150. package/src/lib/editor/managers/TextManager/TextManager.ts +17 -2
  151. package/src/lib/editor/managers/TickManager/TickManager.test.ts +0 -40
  152. package/src/lib/editor/managers/TickManager/TickManager.ts +0 -1
  153. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +12 -2
  154. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +27 -2
  155. package/src/lib/editor/overlays/strokeShapeIndicators.ts +86 -0
  156. package/src/lib/editor/shapes/ShapeUtil.ts +41 -0
  157. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +5 -0
  158. package/src/lib/editor/tools/StateNode.ts +0 -2
  159. package/src/lib/editor/tools/cancelShapeCreationOnLongPress.ts +31 -0
  160. package/src/lib/editor/types/event-types.ts +9 -6
  161. package/src/lib/globals/environment.ts +7 -0
  162. package/src/lib/hooks/EditorComponentsContext.tsx +0 -2
  163. package/src/lib/hooks/useCanvasEvents.ts +24 -12
  164. package/src/lib/hooks/useEditorComponents.tsx +0 -1
  165. package/src/lib/hooks/usePresence.ts +2 -2
  166. package/src/lib/hooks/useScreenBounds.ts +3 -0
  167. package/src/lib/license/LicenseManager.test.ts +24 -16
  168. package/src/lib/license/LicenseManager.ts +8 -2
  169. package/src/lib/primitives/Vec.ts +5 -2
  170. package/src/lib/test/ContentElementSlot.test.tsx +201 -0
  171. package/src/lib/utils/dom.ts +34 -0
  172. package/src/lib/utils/getPointerInfo.ts +2 -1
  173. package/src/lib/utils/pointer.test.ts +76 -0
  174. package/src/lib/utils/pointer.ts +42 -0
  175. package/src/version.ts +3 -3
  176. package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js +0 -49
  177. package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js.map +0 -7
  178. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +0 -161
  179. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +0 -7
  180. package/dist-cjs/lib/hooks/useTransform.js.map +0 -7
  181. package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs +0 -19
  182. package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs.map +0 -7
  183. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +0 -141
  184. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +0 -7
  185. package/dist-esm/lib/hooks/useTransform.mjs +0 -23
  186. package/dist-esm/lib/hooks/useTransform.mjs.map +0 -7
  187. package/src/lib/components/default-components/DefaultSelectionBackground.tsx +0 -25
  188. package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +0 -216
  189. package/src/lib/hooks/useTransform.ts +0 -30
@@ -0,0 +1,17 @@
1
+ import { tlenv } from "../globals/environment.mjs";
2
+ function isDirectDisplayPen(e) {
3
+ if (e.pointerType !== "pen") return false;
4
+ return tlenv.isTouchDevice;
5
+ }
6
+ function isSecondaryClickEvent(e) {
7
+ return e.button === 2 || tlenv.isDarwin && e.button === 0 && e.ctrlKey && !e.metaKey;
8
+ }
9
+ function getPointerEventButton(e) {
10
+ return isSecondaryClickEvent(e) ? 2 : e.button;
11
+ }
12
+ export {
13
+ getPointerEventButton,
14
+ isDirectDisplayPen,
15
+ isSecondaryClickEvent
16
+ };
17
+ //# sourceMappingURL=pointer.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/utils/pointer.ts"],
4
+ "sourcesContent": ["import type React from 'react'\nimport { tlenv } from '../globals/environment'\n\n/**\n * Decide whether a pen pointer event looks like direct manipulation on the display (e.g. Apple\n * Pencil on an iPad or a Surface Pen on a touchscreen) rather than indirect input from a desktop\n * graphics tablet (e.g. a Wacom Intuos).\n *\n * We can't tell the two apart from the pointer event itself: both report `pointerType: 'pen'`, and\n * implicit pointer capture \u2014 which in theory distinguishes direct-manipulation pointers \u2014 isn't\n * reliably observable across browsers (notably WebKit/iPad). Instead we key off the device: a\n * direct-display pen draws on a touch-capable screen, while an indirect graphics tablet is used on\n * a non-touch desktop alongside a mouse. A device with no touch input therefore can't host a\n * direct-display pen.\n *\n * Note this uses {@link tlenv.isTouchDevice} \u2014 the device's fixed touch capability \u2014 not the\n * editor's dynamic `isCoarsePointer` state, which a pen `pointerdown` flips to coarse regardless\n * of device.\n *\n * @internal\n */\nexport function isDirectDisplayPen(e: React.PointerEvent | PointerEvent): boolean {\n\tif (e.pointerType !== 'pen') return false\n\treturn tlenv.isTouchDevice\n}\n\n/** @internal */\ninterface PointerLike {\n\tbutton: number\n\tctrlKey: boolean\n\tmetaKey: boolean\n}\n\n/** @internal */\nexport function isSecondaryClickEvent(e: PointerLike) {\n\treturn e.button === 2 || (tlenv.isDarwin && e.button === 0 && e.ctrlKey && !e.metaKey)\n}\n\n/** @internal */\nexport function getPointerEventButton(e: PointerLike) {\n\treturn isSecondaryClickEvent(e) ? 2 : e.button\n}\n"],
5
+ "mappings": "AACA,SAAS,aAAa;AAoBf,SAAS,mBAAmB,GAA+C;AACjF,MAAI,EAAE,gBAAgB,MAAO,QAAO;AACpC,SAAO,MAAM;AACd;AAUO,SAAS,sBAAsB,GAAgB;AACrD,SAAO,EAAE,WAAW,KAAM,MAAM,YAAY,EAAE,WAAW,KAAK,EAAE,WAAW,CAAC,EAAE;AAC/E;AAGO,SAAS,sBAAsB,GAAgB;AACrD,SAAO,sBAAsB,CAAC,IAAI,IAAI,EAAE;AACzC;",
6
+ "names": []
7
+ }
@@ -1,8 +1,8 @@
1
- const version = "5.2.0-next.ee0fa4d6244f";
1
+ const version = "5.2.0";
2
2
  const publishDates = {
3
3
  major: "2026-05-06T16:28:18.473Z",
4
- minor: "2026-06-04T16:42:40.803Z",
5
- patch: "2026-06-04T16:42:40.803Z"
4
+ minor: "2026-07-01T09:20:01.735Z",
5
+ patch: "2026-07-01T09:20:01.735Z"
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 = '5.2.0-next.ee0fa4d6244f'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-06-04T16:42:40.803Z',\n\tpatch: '2026-06-04T16:42:40.803Z',\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 = '5.2.0'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-07-01T09:20:01.735Z',\n\tpatch: '2026-07-01T09:20:01.735Z',\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/editor.css CHANGED
@@ -182,6 +182,7 @@
182
182
  --tl-color-text-0: hsl(0, 0%, 11%);
183
183
  --tl-color-text-1: hsl(0, 0%, 18%);
184
184
  --tl-color-text-3: hsl(204, 4%, 45%);
185
+ --tl-color-text-disabled: hsl(204, 4%, 55%);
185
186
  --tl-color-text-shadow: hsl(0, 0%, 100%);
186
187
  --tl-color-text-highlight: hsl(52, 100%, 50%);
187
188
  --tl-color-text-highlight-p3: color(display-p3 0.972 0.8205 0.05);
@@ -236,6 +237,7 @@
236
237
  --tl-color-text-0: hsl(0, 9%, 94%);
237
238
  --tl-color-text-1: hsl(0, 0%, 85%);
238
239
  --tl-color-text-3: hsl(204, 4%, 75%);
240
+ --tl-color-text-disabled: hsl(204, 4%, 50%);
239
241
  --tl-color-text-shadow: hsl(210, 13%, 18%);
240
242
  --tl-color-text-highlight: hsl(52, 100%, 41%);
241
243
  --tl-color-text-highlight-p3: color(display-p3 0.8078 0.6225 0.0312);
@@ -762,17 +764,17 @@ input,
762
764
  .tl-rich-text ol {
763
765
  text-align: left;
764
766
  margin: 0;
765
- padding-left: 3.25ch;
767
+ padding-left: 1.625ch;
766
768
  /* Some resets, like Tailwind, nix the list styling. */
767
769
  list-style: revert;
768
770
  }
769
771
 
770
772
  .tl-rich-text ol:has(> li:nth-child(10)) {
771
- padding-left: 4.25ch;
773
+ padding-left: 2.625ch;
772
774
  }
773
775
 
774
776
  .tl-rich-text ol:has(> li:nth-child(100)) {
775
- padding-left: 5.25ch;
777
+ padding-left: 3.625ch;
776
778
  }
777
779
 
778
780
  .tl-rich-text h1,
@@ -811,6 +813,7 @@ input,
811
813
  .tl-theme__dark .tl-rich-text mark {
812
814
  background-color: var(--tl-color-text-highlight);
813
815
  color: currentColor;
816
+ text-shadow: none;
814
817
  }
815
818
 
816
819
  @supports (color: color(display-p3 1 1 1)) {
@@ -983,6 +986,15 @@ input,
983
986
  color: var(--tl-color-text-1);
984
987
  }
985
988
 
989
+ .tl-content-slot {
990
+ position: absolute;
991
+ inset: 0px;
992
+ height: 100%;
993
+ width: 100%;
994
+ pointer-events: none;
995
+ transform-origin: top left;
996
+ }
997
+
986
998
  /* -------------------- Group shape ------------------ */
987
999
 
988
1000
  .tl-group {
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": "5.2.0-next.ee0fa4d6244f",
4
+ "version": "5.2.0",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -49,12 +49,12 @@
49
49
  "@tiptap/core": "^3.12.1",
50
50
  "@tiptap/pm": "^3.12.1",
51
51
  "@tiptap/react": "^3.12.1",
52
- "@tldraw/state": "5.2.0-next.ee0fa4d6244f",
53
- "@tldraw/state-react": "5.2.0-next.ee0fa4d6244f",
54
- "@tldraw/store": "5.2.0-next.ee0fa4d6244f",
55
- "@tldraw/tlschema": "5.2.0-next.ee0fa4d6244f",
56
- "@tldraw/utils": "5.2.0-next.ee0fa4d6244f",
57
- "@tldraw/validate": "5.2.0-next.ee0fa4d6244f",
52
+ "@tldraw/state": "5.2.0",
53
+ "@tldraw/state-react": "5.2.0",
54
+ "@tldraw/store": "5.2.0",
55
+ "@tldraw/tlschema": "5.2.0",
56
+ "@tldraw/utils": "5.2.0",
57
+ "@tldraw/validate": "5.2.0",
58
58
  "classnames": "^2.5.1",
59
59
  "eventemitter3": "^4.0.7",
60
60
  "idb": "^7.1.1",
@@ -80,7 +80,10 @@
80
80
  "react": "^19.2.1",
81
81
  "react-dom": "^19.2.1",
82
82
  "resize-observer-polyfill": "^1.5.1",
83
- "vitest": "^3.2.4"
83
+ "vitest": "^4.1.7"
84
+ },
85
+ "engines": {
86
+ "node": ">=22.12.0"
84
87
  },
85
88
  "module": "dist-esm/index.mjs",
86
89
  "source": "src/index.ts",
package/src/index.ts CHANGED
@@ -23,10 +23,6 @@ export {
23
23
  type TLErrorFallbackComponent,
24
24
  } from './lib/components/default-components/DefaultErrorFallback'
25
25
  export { DefaultGrid, type TLGridProps } from './lib/components/default-components/DefaultGrid'
26
- export {
27
- DefaultSelectionBackground,
28
- type TLSelectionBackgroundProps,
29
- } from './lib/components/default-components/DefaultSelectionBackground'
30
26
  export { type TLShapeErrorFallbackComponent } from './lib/components/default-components/DefaultShapeErrorFallback'
31
27
  export {
32
28
  DefaultShapeWrapper,
@@ -128,6 +124,7 @@ export {
128
124
  export { SpatialIndexManager } from './lib/editor/managers/SpatialIndexManager/SpatialIndexManager'
129
125
  export {
130
126
  TextManager,
127
+ resolveLineHeightPx,
131
128
  type BatchMeasurementRequest,
132
129
  type TLMeasuredTextSize,
133
130
  type TLMeasureTextOpts,
@@ -185,11 +182,7 @@ export {
185
182
  type TLOverlay,
186
183
  type TLOverlayUtilConstructor,
187
184
  } from './lib/editor/overlays/OverlayUtil'
188
- export {
189
- ShapeIndicatorOverlayUtil,
190
- strokeShapeIndicators,
191
- type TLShapeIndicatorOverlay,
192
- } from './lib/editor/overlays/ShapeIndicatorOverlayUtil'
185
+ export { strokeShapeIndicators } from './lib/editor/overlays/strokeShapeIndicators'
193
186
  export {
194
187
  getPerfectDashProps,
195
188
  type PerfectDashTerminal,
@@ -198,6 +191,7 @@ export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/res
198
191
  export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
199
192
  export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
200
193
  export { maybeSnapToGrid } from './lib/editor/tools/BaseBoxShapeTool/children/Pointing'
194
+ export { cancelShapeCreationOnLongPress } from './lib/editor/tools/cancelShapeCreationOnLongPress'
201
195
  export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode'
202
196
  export { type TLContent } from './lib/editor/types/clipboard-types'
203
197
  export { type TLEventMap, type TLEventMapHandler } from './lib/editor/types/emit-types'
@@ -325,7 +319,6 @@ export {
325
319
  type SafeId,
326
320
  } from './lib/hooks/useSafeId'
327
321
  export { useTLSchemaFromUtils, useTLStore } from './lib/hooks/useTLStore'
328
- export { useTransform } from './lib/hooks/useTransform'
329
322
  export { useViewportHeight } from './lib/hooks/useViewportHeight'
330
323
  export {
331
324
  LicenseManager,
@@ -458,6 +451,7 @@ export {
458
451
  getGlobalDocument,
459
452
  getGlobalWindow,
460
453
  loopToHtmlElement,
454
+ moveElementInto,
461
455
  preventDefault,
462
456
  releasePointerCapture,
463
457
  setPointerCapture,
@@ -6,6 +6,7 @@ import { useEditor } from '../hooks/useEditor'
6
6
  import { Vec } from '../primitives/Vec'
7
7
  import { releasePointerCapture, setPointerCapture } from '../utils/dom'
8
8
  import { getPointerInfo } from '../utils/getPointerInfo'
9
+ import { getPointerEventButton } from '../utils/pointer'
9
10
 
10
11
  /**
11
12
  * When a menu is open, this component prevents the user from interacting with the canvas.
@@ -68,18 +69,19 @@ export function MenuClickCapture() {
68
69
 
69
70
  const handlePointerDown = useCallback(
70
71
  (e: PointerEvent) => {
71
- if (e.button !== 0 && e.button !== 2) return
72
+ const button = getPointerEventButton(e)
73
+ if (button !== 0 && button !== 2) return
72
74
 
73
75
  flushSync(() => setIsPointing(true))
74
76
  setPointerCapture(e.currentTarget, e)
75
77
  rPointerState.current = {
76
78
  isDown: true,
77
79
  isDragging: false,
78
- button: e.button,
80
+ button,
79
81
  start: new Vec(e.clientX, e.clientY),
80
82
  }
81
83
 
82
- if (e.button === 2) {
84
+ if (button === 2) {
83
85
  if (!editor.options.rightClickPanning) {
84
86
  // Right-click panning off: close the open menu and swallow the native
85
87
  // contextmenu that would otherwise briefly open a new one (causing a flash).
@@ -141,13 +143,15 @@ export function MenuClickCapture() {
141
143
 
142
144
  const handlePointerUp = useCallback(
143
145
  (e: PointerEvent) => {
144
- const isStaticRightClick = e.button === 2 && !rPointerState.current.isDragging
146
+ const isStaticRightClick =
147
+ rPointerState.current.button === 2 && !rPointerState.current.isDragging
145
148
 
146
149
  editor.dispatch({
147
150
  type: 'pointer',
148
151
  target: 'canvas',
149
152
  name: 'pointer_up',
150
153
  ...getPointerInfo(editor, e),
154
+ button: rPointerState.current.button === 2 ? 2 : getPointerEventButton(e),
151
155
  })
152
156
 
153
157
  if (isStaticRightClick && editor.options.rightClickPanning) {
@@ -1,14 +1,16 @@
1
1
  import { react } from '@tldraw/state'
2
2
  import { useQuickReactor, useStateTracking } from '@tldraw/state-react'
3
3
  import { TLShape, TLShapeId } from '@tldraw/tlschema'
4
+ import { warnOnce } from '@tldraw/utils'
4
5
  import { memo, useCallback, useEffect, useLayoutEffect, useRef } from 'react'
6
+ import { Editor } from '../editor/Editor'
5
7
  import { ShapeUtil } from '../editor/shapes/ShapeUtil'
6
8
  import { useEditorComponents } from '../hooks/EditorComponentsContext'
7
9
  import { useEditor } from '../hooks/useEditor'
8
10
  import { useShapeCulling } from '../hooks/useShapeCulling'
9
11
  import { Mat } from '../primitives/Mat'
10
12
  import { areShapesContentEqual } from '../utils/areShapesContentEqual'
11
- import { setStyleProperty } from '../utils/dom'
13
+ import { moveElementInto, setStyleProperty } from '../utils/dom'
12
14
  import { OptionalErrorBoundary } from './ErrorBoundary'
13
15
 
14
16
  /*
@@ -153,11 +155,96 @@ export const Shape = memo(function Shape({
153
155
  <OptionalErrorBoundary fallback={ShapeErrorFallback as any} onError={annotateError}>
154
156
  <InnerShape shape={shape} util={util} />
155
157
  </OptionalErrorBoundary>
158
+ {util.getAppOwnedElement && <ContentElementSlot id={id} util={util} />}
156
159
  </ShapeWrapper>
157
160
  </>
158
161
  )
159
162
  })
160
163
 
164
+ /*
165
+ The content element slot hosts an app-owned element provided by the shape util's
166
+ getAppOwnedElement method. The contract: while the shape is mounted, tldraw never
167
+ unmounts, recreates, or relocates the element (the canvas renders shapes in stable
168
+ id-sorted DOM order, so reorders and reparenting never move DOM nodes), and before
169
+ the slot is destroyed — shape deletion, page change, error teardown, or the whole
170
+ editor unmounting — onReleaseAppOwnedElement is called while the slot is still
171
+ connected to the document, so the app can move the element to a new parent with
172
+ Node.moveBefore without resetting its state. That last guarantee relies on this
173
+ being a layout effect: React runs layout effect cleanups before it detaches the
174
+ host nodes of a deleted subtree, which is not true of passive effects.
175
+ */
176
+ const ContentElementSlot = memo(function ContentElementSlot({
177
+ id,
178
+ util,
179
+ }: {
180
+ id: TLShapeId
181
+ util: ShapeUtil
182
+ }) {
183
+ const editor = useEditor()
184
+ const slotRef = useRef<HTMLDivElement>(null)
185
+
186
+ useLayoutEffect(() => {
187
+ const slot = slotRef.current
188
+ if (!slot) return
189
+
190
+ const shape = editor.getShape(id)
191
+ if (!shape) return
192
+
193
+ // getAppOwnedElement is called once per shape mount (the effect deps below are
194
+ // [editor, id, util], none of which change on a prop edit), so the adopted element is
195
+ // not refreshed when the shape's props change. A util whose element identity depends on
196
+ // props must mutate the existing element in place rather than return a new one. Likewise,
197
+ // if this returns null now and an element only later, the element is never adopted for
198
+ // this mount — return a stable element up front and populate it asynchronously instead.
199
+ const element = util.getAppOwnedElement?.(shape)
200
+ if (!element) return
201
+
202
+ // An element that was connected before adoption (e.g. parked off-canvas by the
203
+ // app between editor sessions) should survive the move without reloading.
204
+ const wasConnectedBeforeAdoption = element.isConnected
205
+
206
+ if (element.parentNode !== slot) {
207
+ moveElementInto(slot, element)
208
+ }
209
+
210
+ if (process.env.NODE_ENV !== 'production' && wasConnectedBeforeAdoption) {
211
+ warnIfContentElementReloads(editor, slot, element)
212
+ }
213
+
214
+ return () => {
215
+ // Note: under React StrictMode (dev only) this effect runs mount → cleanup → mount,
216
+ // so onReleaseAppOwnedElement fires once even though the shape was not removed, and on
217
+ // the appendChild fallback path the element is detached and re-adopted (reloading it).
218
+ // onReleaseAppOwnedElement must therefore be safe to call when the shape still exists;
219
+ // don't treat it as a definitive "shape removed" signal (check the store if you need
220
+ // to know). With Node.moveBefore the re-adoption preserves state, so this is benign.
221
+ const latestShape = editor.getShape(id) ?? shape
222
+ util.onReleaseAppOwnedElement?.(latestShape, element)
223
+ if (process.env.NODE_ENV !== 'production' && element.parentNode === slot) {
224
+ warnOnce(
225
+ `The content element for shape "${id}" was not reclaimed by onReleaseAppOwnedElement and will be destroyed along with its slot, losing any state it holds.`
226
+ )
227
+ }
228
+ }
229
+ }, [editor, id, util])
230
+
231
+ return <div ref={slotRef} className="tl-content-slot" draggable={false} />
232
+ })
233
+
234
+ // Dev-mode assertion that adoption kept the platform's state-preservation promise: a
235
+ // load event firing on a previously-connected adopted element means the embed reloaded.
236
+ // load doesn't bubble, but capture-phase listeners on ancestors still observe it.
237
+ function warnIfContentElementReloads(editor: Editor, slot: HTMLElement, element: HTMLElement) {
238
+ const onLoad = (event: Event) => {
239
+ if (event.target !== element && !element.contains(event.target as Node)) return
240
+ warnOnce(
241
+ 'A load event fired on an adopted content element. The element reloaded when it was moved into the shape, losing its state. State-preserving moves need Node.moveBefore (Chromium 133+, Firefox 144+) and a continuously connected element.'
242
+ )
243
+ }
244
+ slot.addEventListener('load', onLoad, { capture: true })
245
+ editor.timers.setTimeout(() => slot.removeEventListener('load', onLoad, { capture: true }), 1000)
246
+ }
247
+
161
248
  export const InnerShape = memo(
162
249
  function InnerShape<T extends TLShape>({ shape, util }: { shape: T; util: ShapeUtil<T> }) {
163
250
  return useStateTracking(
@@ -32,7 +32,7 @@ export interface TLCanvasComponentProps {
32
32
  export function DefaultCanvas({ className }: TLCanvasComponentProps) {
33
33
  const editor = useEditor()
34
34
 
35
- const { SelectionBackground, Background, SvgDefs } = useEditorComponents()
35
+ const { Background, SvgDefs } = useEditorComponents()
36
36
 
37
37
  const rCanvas = useRef<HTMLDivElement>(null)
38
38
  const rHtmlLayer = useRef<HTMLDivElement>(null)
@@ -160,7 +160,6 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
160
160
  {isGridMode && Grid && <GridWrapper />}
161
161
  <div ref={rHtmlLayer} className="tl-html-layer tl-shapes" draggable={false}>
162
162
  <OnTheCanvasWrapper />
163
- {SelectionBackground && <SelectionBackgroundWrapper />}
164
163
  {hideShapes ? null : <ShapesLayer canvasRef={rCanvas} />}
165
164
  </div>
166
165
  <CanvasOverlays />
@@ -345,21 +344,6 @@ function DebugSvgCopy({ id, mode }: { id: TLShapeId; mode: 'img' | 'iframe' }) {
345
344
  )
346
345
  }
347
346
 
348
- function SelectionBackgroundWrapper() {
349
- const editor = useEditor()
350
- const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
351
- editor,
352
- ])
353
- const selectionBounds = useValue(
354
- 'selection bounds',
355
- () => editor.getSelectionRotatedPageBounds(),
356
- [editor]
357
- )
358
- const { SelectionBackground } = useEditorComponents()
359
- if (!selectionBounds || !SelectionBackground) return null
360
- return <SelectionBackground bounds={selectionBounds} rotation={selectionRotation} />
361
- }
362
-
363
347
  function OnTheCanvasWrapper() {
364
348
  const { OnTheCanvas } = useEditorComponents()
365
349
  if (!OnTheCanvas) return null
@@ -96,6 +96,42 @@ describe('updateShape', () => {
96
96
  })
97
97
  })
98
98
 
99
+ describe('updateViewportScreenBounds', () => {
100
+ it('bails without crashing or updating bounds when the container document has no body', () => {
101
+ const documentSpy = vi
102
+ .spyOn(editor, 'getContainerDocument')
103
+ .mockReturnValue({ body: null } as unknown as Document)
104
+
105
+ const prevScreenBounds = editor.getInstanceState().screenBounds
106
+ const prevInsets = editor.getInstanceState().insets
107
+
108
+ try {
109
+ expect(() => editor.updateViewportScreenBounds(new Box(10, 20, 100, 200))).not.toThrow()
110
+ // A torn-down document is not measurable, so the bounds are left untouched.
111
+ expect(editor.getInstanceState().screenBounds).toEqual(prevScreenBounds)
112
+ expect(editor.getInstanceState().insets).toEqual(prevInsets)
113
+ } finally {
114
+ documentSpy.mockRestore()
115
+ }
116
+ })
117
+
118
+ it('uses the container document body to calculate right and bottom insets', () => {
119
+ const documentSpy = vi.spyOn(editor, 'getContainerDocument').mockReturnValue({
120
+ body: {
121
+ scrollWidth: 500,
122
+ scrollHeight: 400,
123
+ },
124
+ } as unknown as Document)
125
+
126
+ try {
127
+ editor.updateViewportScreenBounds(new Box(10, 20, 100, 200))
128
+ expect(editor.getInstanceState().insets).toEqual([true, true, true, true])
129
+ } finally {
130
+ documentSpy.mockRestore()
131
+ }
132
+ })
133
+ })
134
+
99
135
  describe('zoomToFit', () => {
100
136
  it('no-op when isLocked is set', () => {
101
137
  editor.getCurrentPageShapeIds = vi.fn(() => new Set([createShapeId('box1')]))