@tldraw/editor 4.3.0-canary.c5efe11c58e0 → 4.3.0-canary.cb6779b4f066

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 (178) hide show
  1. package/README.md +1 -1
  2. package/dist-cjs/index.d.ts +448 -120
  3. package/dist-cjs/index.js +8 -1
  4. package/dist-cjs/index.js.map +2 -2
  5. package/dist-cjs/lib/components/ErrorBoundary.js.map +1 -1
  6. package/dist-cjs/lib/components/GeometryDebuggingView.js +1 -17
  7. package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +4 -5
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/constants.js +1 -3
  11. package/dist-cjs/lib/constants.js.map +2 -2
  12. package/dist-cjs/lib/editor/Editor.js +346 -280
  13. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  14. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -23
  15. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
  16. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +12 -3
  17. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  18. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +1 -1
  19. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +5 -6
  21. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +591 -0
  23. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +7 -0
  24. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +1 -1
  25. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js +144 -0
  27. package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js.map +7 -0
  28. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +181 -0
  29. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +7 -0
  30. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +1 -22
  31. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  32. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +31 -23
  33. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  34. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js +1 -1
  35. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js.map +2 -2
  36. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -3
  37. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  38. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  39. package/dist-cjs/lib/exports/parseCss.js +1 -1
  40. package/dist-cjs/lib/exports/parseCss.js.map +2 -2
  41. package/dist-cjs/lib/globals/environment.js +45 -9
  42. package/dist-cjs/lib/globals/environment.js.map +2 -2
  43. package/dist-cjs/lib/hooks/useCoarsePointer.js +14 -29
  44. package/dist-cjs/lib/hooks/useCoarsePointer.js.map +2 -2
  45. package/dist-cjs/lib/hooks/useEvent.js +1 -1
  46. package/dist-cjs/lib/hooks/useEvent.js.map +2 -2
  47. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  48. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  49. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  50. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  51. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  52. package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
  53. package/dist-cjs/lib/hooks/useStateAttribute.js +4 -1
  54. package/dist-cjs/lib/hooks/useStateAttribute.js.map +2 -2
  55. package/dist-cjs/lib/hooks/useTransform.js.map +1 -1
  56. package/dist-cjs/lib/hooks/useZoomCss.js +4 -8
  57. package/dist-cjs/lib/hooks/useZoomCss.js.map +2 -2
  58. package/dist-cjs/lib/options.js +6 -1
  59. package/dist-cjs/lib/options.js.map +2 -2
  60. package/dist-cjs/lib/primitives/Box.js +3 -0
  61. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  62. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +1 -0
  63. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  64. package/dist-cjs/lib/utils/rotation.js +1 -1
  65. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  66. package/dist-cjs/version.js +3 -3
  67. package/dist-cjs/version.js.map +1 -1
  68. package/dist-esm/index.d.mts +448 -120
  69. package/dist-esm/index.mjs +9 -2
  70. package/dist-esm/index.mjs.map +2 -2
  71. package/dist-esm/lib/components/ErrorBoundary.mjs.map +1 -1
  72. package/dist-esm/lib/components/GeometryDebuggingView.mjs +1 -17
  73. package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
  74. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +4 -5
  75. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  76. package/dist-esm/lib/constants.mjs +1 -3
  77. package/dist-esm/lib/constants.mjs.map +2 -2
  78. package/dist-esm/lib/editor/Editor.mjs +347 -283
  79. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  80. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -23
  81. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
  82. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +13 -4
  83. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  84. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +1 -1
  85. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +5 -6
  87. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +573 -0
  89. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +7 -0
  90. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +1 -1
  91. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  92. package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs +114 -0
  93. package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs.map +7 -0
  94. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +161 -0
  95. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +7 -0
  96. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +1 -22
  97. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  98. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +31 -23
  99. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  100. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs +1 -1
  101. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs.map +2 -2
  102. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -3
  103. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  104. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  105. package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
  106. package/dist-esm/lib/globals/environment.mjs +45 -9
  107. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  108. package/dist-esm/lib/hooks/useCoarsePointer.mjs +15 -30
  109. package/dist-esm/lib/hooks/useCoarsePointer.mjs.map +2 -2
  110. package/dist-esm/lib/hooks/useEvent.mjs +1 -1
  111. package/dist-esm/lib/hooks/useEvent.mjs.map +2 -2
  112. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  113. package/dist-esm/lib/hooks/useGestureEvents.mjs +1 -1
  114. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  115. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  116. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  117. package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
  118. package/dist-esm/lib/hooks/useStateAttribute.mjs +4 -1
  119. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +2 -2
  120. package/dist-esm/lib/hooks/useTransform.mjs.map +1 -1
  121. package/dist-esm/lib/hooks/useZoomCss.mjs +4 -8
  122. package/dist-esm/lib/hooks/useZoomCss.mjs.map +2 -2
  123. package/dist-esm/lib/options.mjs +6 -1
  124. package/dist-esm/lib/options.mjs.map +2 -2
  125. package/dist-esm/lib/primitives/Box.mjs +3 -0
  126. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  127. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +1 -0
  128. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  129. package/dist-esm/lib/utils/rotation.mjs +1 -1
  130. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  131. package/dist-esm/version.mjs +3 -3
  132. package/dist-esm/version.mjs.map +1 -1
  133. package/editor.css +14 -12
  134. package/package.json +21 -17
  135. package/src/index.ts +5 -1
  136. package/src/lib/components/ErrorBoundary.tsx +1 -1
  137. package/src/lib/components/GeometryDebuggingView.tsx +1 -19
  138. package/src/lib/components/default-components/DefaultCanvas.tsx +5 -8
  139. package/src/lib/config/TLUserPreferences.test.ts +40 -0
  140. package/src/lib/constants.ts +0 -2
  141. package/src/lib/editor/Editor.test.ts +140 -0
  142. package/src/lib/editor/Editor.ts +452 -326
  143. package/src/lib/editor/derivations/notVisibleShapes.ts +21 -33
  144. package/src/lib/editor/derivations/parentsToChildren.ts +18 -7
  145. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +17 -31
  146. package/src/lib/editor/managers/ClickManager/ClickManager.ts +1 -1
  147. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +129 -79
  148. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +10 -6
  149. package/src/lib/editor/managers/InputsManager/InputsManager.ts +566 -0
  150. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -4
  151. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +12 -0
  152. package/src/lib/editor/managers/SnapManager/SnapManager.ts +1 -1
  153. package/src/lib/editor/managers/SpatialIndexManager/RBushIndex.ts +144 -0
  154. package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +215 -0
  155. package/src/lib/editor/managers/TickManager/TickManager.test.ts +40 -107
  156. package/src/lib/editor/managers/TickManager/TickManager.ts +2 -32
  157. package/src/lib/editor/shapes/ShapeUtil.ts +67 -24
  158. package/src/lib/editor/shapes/group/DashedOutlineBox.tsx +1 -1
  159. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +3 -3
  160. package/src/lib/editor/types/emit-types.ts +3 -1
  161. package/src/lib/exports/parseCss.test.ts +1 -0
  162. package/src/lib/exports/parseCss.ts +1 -1
  163. package/src/lib/globals/environment.ts +65 -10
  164. package/src/lib/hooks/useCoarsePointer.ts +16 -59
  165. package/src/lib/hooks/useEvent.tsx +1 -1
  166. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  167. package/src/lib/hooks/useGestureEvents.ts +2 -2
  168. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +1 -1
  169. package/src/lib/hooks/usePassThroughWheelEvents.ts +1 -1
  170. package/src/lib/hooks/useScreenBounds.ts +1 -1
  171. package/src/lib/hooks/useStateAttribute.ts +4 -1
  172. package/src/lib/hooks/useTransform.ts +1 -1
  173. package/src/lib/hooks/useZoomCss.ts +3 -8
  174. package/src/lib/options.ts +32 -0
  175. package/src/lib/primitives/Box.ts +9 -0
  176. package/src/lib/primitives/geometry/Geometry2d.ts +1 -0
  177. package/src/lib/utils/rotation.ts +1 -1
  178. package/src/version.ts +3 -3
@@ -16,7 +16,7 @@ function getRotationSnapshot({
16
16
  const initialPageCenter = rotatedPageBounds.center.clone().rotWith(rotatedPageBounds.point, rotation);
17
17
  return {
18
18
  initialPageCenter,
19
- initialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),
19
+ initialCursorAngle: initialPageCenter.angle(editor.inputs.getOriginPagePoint()),
20
20
  initialShapesRotation: rotation,
21
21
  shapeSnapshots: shapes.map((shape) => ({
22
22
  shape,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/rotation.ts"],
4
- "sourcesContent": ["import { isShapeId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { compact } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Mat } from '../primitives/Mat'\nimport { canonicalizeRotation } from '../primitives/utils'\nimport { Vec, VecLike } from '../primitives/Vec'\n\n/** @internal */\nexport function getRotationSnapshot({\n\teditor,\n\tids,\n}: {\n\teditor: Editor\n\tids: TLShapeId[]\n}): TLRotationSnapshot | null {\n\tconst shapes = compact(ids.map((id) => editor.getShape(id)))\n\tconst rotation = editor.getShapesSharedRotation(ids)\n\tconst rotatedPageBounds = editor.getShapesRotatedPageBounds(ids)\n\n\t// todo: this assumes we're rotating the selected shapes\n\t// if we try to rotate shapes that aren't selected, this\n\t// will produce the wrong results\n\n\t// Return null if there are no selected shapes\n\tif (!rotatedPageBounds) {\n\t\treturn null\n\t}\n\n\tconst initialPageCenter = rotatedPageBounds.center\n\t\t.clone()\n\t\t.rotWith(rotatedPageBounds.point, rotation)\n\n\treturn {\n\t\tinitialPageCenter,\n\t\tinitialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),\n\t\tinitialShapesRotation: rotation,\n\t\tshapeSnapshots: shapes.map((shape) => ({\n\t\t\tshape,\n\t\t\tinitialPagePoint: editor.getShapePageTransform(shape.id)!.point(),\n\t\t})),\n\t}\n}\n\n/**\n * @internal\n **/\nexport interface TLRotationSnapshot {\n\tinitialPageCenter: Vec\n\tinitialCursorAngle: number\n\tinitialShapesRotation: number\n\tshapeSnapshots: {\n\t\tshape: TLShape\n\t\tinitialPagePoint: Vec\n\t}[]\n}\n\n/** @internal */\nexport function applyRotationToSnapshotShapes({\n\tdelta,\n\teditor,\n\tsnapshot,\n\tstage,\n\tcenterOverride,\n}: {\n\tdelta: number\n\tsnapshot: TLRotationSnapshot\n\teditor: Editor\n\tstage: 'start' | 'update' | 'end' | 'one-off'\n\tcenterOverride?: VecLike\n}) {\n\tconst { initialPageCenter, shapeSnapshots } = snapshot\n\n\teditor.updateShapes(\n\t\tshapeSnapshots.map(({ shape, initialPagePoint }) => {\n\t\t\t// We need to both rotate each shape individually and rotate the shapes\n\t\t\t// around the pivot point (the average center of all rotating shapes.)\n\n\t\t\tconst parentTransform = isShapeId(shape.parentId)\n\t\t\t\t? editor.getShapePageTransform(shape.parentId)!\n\t\t\t\t: Mat.Identity()\n\n\t\t\tconst newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta)\n\n\t\t\tconst newLocalPoint = Mat.applyToPoint(\n\t\t\t\t// use the current parent transform in case it has moved/resized since the start\n\t\t\t\t// (e.g. if rotating a shape at the edge of a group)\n\t\t\t\tMat.Inverse(parentTransform),\n\t\t\t\tnewPagePoint\n\t\t\t)\n\t\t\tconst newRotation = canonicalizeRotation(shape.rotation + delta)\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx: newLocalPoint.x,\n\t\t\t\ty: newLocalPoint.y,\n\t\t\t\trotation: newRotation,\n\t\t\t}\n\t\t})\n\t)\n\n\t// Handle change\n\n\tconst changes: TLShapePartial[] = []\n\n\tshapeSnapshots.forEach(({ shape }) => {\n\t\tconst current = editor.getShape(shape.id)\n\t\tif (!current) return\n\t\tconst util = editor.getShapeUtil(shape)\n\n\t\tif (stage === 'start' || stage === 'one-off') {\n\t\t\tconst changeStart = util.onRotateStart?.(shape)\n\t\t\tif (changeStart) changes.push(changeStart)\n\t\t}\n\n\t\tconst changeUpdate = util.onRotate?.(shape, current)\n\t\tif (changeUpdate) changes.push(changeUpdate)\n\n\t\tif (stage === 'end' || stage === 'one-off') {\n\t\t\tconst changeEnd = util.onRotateEnd?.(shape, current)\n\t\t\tif (changeEnd) changes.push(changeEnd)\n\t\t}\n\t})\n\n\tif (changes.length > 0) {\n\t\teditor.updateShapes(changes)\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAS,iBAAqD;AAC9D,SAAS,eAAe;AAExB,SAAS,WAAW;AACpB,SAAS,4BAA4B;AACrC,SAAS,WAAoB;AAGtB,SAAS,oBAAoB;AAAA,EACnC;AAAA,EACA;AACD,GAG8B;AAC7B,QAAM,SAAS,QAAQ,IAAI,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC;AAC3D,QAAM,WAAW,OAAO,wBAAwB,GAAG;AACnD,QAAM,oBAAoB,OAAO,2BAA2B,GAAG;AAO/D,MAAI,CAAC,mBAAmB;AACvB,WAAO;AAAA,EACR;AAEA,QAAM,oBAAoB,kBAAkB,OAC1C,MAAM,EACN,QAAQ,kBAAkB,OAAO,QAAQ;AAE3C,SAAO;AAAA,IACN;AAAA,IACA,oBAAoB,kBAAkB,MAAM,OAAO,OAAO,eAAe;AAAA,IACzE,uBAAuB;AAAA,IACvB,gBAAgB,OAAO,IAAI,CAAC,WAAW;AAAA,MACtC;AAAA,MACA,kBAAkB,OAAO,sBAAsB,MAAM,EAAE,EAAG,MAAM;AAAA,IACjE,EAAE;AAAA,EACH;AACD;AAgBO,SAAS,8BAA8B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,GAMG;AACF,QAAM,EAAE,mBAAmB,eAAe,IAAI;AAE9C,SAAO;AAAA,IACN,eAAe,IAAI,CAAC,EAAE,OAAO,iBAAiB,MAAM;AAInD,YAAM,kBAAkB,UAAU,MAAM,QAAQ,IAC7C,OAAO,sBAAsB,MAAM,QAAQ,IAC3C,IAAI,SAAS;AAEhB,YAAM,eAAe,IAAI,QAAQ,kBAAkB,kBAAkB,mBAAmB,KAAK;AAE7F,YAAM,gBAAgB,IAAI;AAAA;AAAA;AAAA,QAGzB,IAAI,QAAQ,eAAe;AAAA,QAC3B;AAAA,MACD;AACA,YAAM,cAAc,qBAAqB,MAAM,WAAW,KAAK;AAE/D,aAAO;AAAA,QACN,IAAI,MAAM;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,GAAG,cAAc;AAAA,QACjB,GAAG,cAAc;AAAA,QACjB,UAAU;AAAA,MACX;AAAA,IACD,CAAC;AAAA,EACF;AAIA,QAAM,UAA4B,CAAC;AAEnC,iBAAe,QAAQ,CAAC,EAAE,MAAM,MAAM;AACrC,UAAM,UAAU,OAAO,SAAS,MAAM,EAAE;AACxC,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,OAAO,aAAa,KAAK;AAEtC,QAAI,UAAU,WAAW,UAAU,WAAW;AAC7C,YAAM,cAAc,KAAK,gBAAgB,KAAK;AAC9C,UAAI,YAAa,SAAQ,KAAK,WAAW;AAAA,IAC1C;AAEA,UAAM,eAAe,KAAK,WAAW,OAAO,OAAO;AACnD,QAAI,aAAc,SAAQ,KAAK,YAAY;AAE3C,QAAI,UAAU,SAAS,UAAU,WAAW;AAC3C,YAAM,YAAY,KAAK,cAAc,OAAO,OAAO;AACnD,UAAI,UAAW,SAAQ,KAAK,SAAS;AAAA,IACtC;AAAA,EACD,CAAC;AAED,MAAI,QAAQ,SAAS,GAAG;AACvB,WAAO,aAAa,OAAO;AAAA,EAC5B;AACD;",
4
+ "sourcesContent": ["import { isShapeId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { compact } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Mat } from '../primitives/Mat'\nimport { canonicalizeRotation } from '../primitives/utils'\nimport { Vec, VecLike } from '../primitives/Vec'\n\n/** @internal */\nexport function getRotationSnapshot({\n\teditor,\n\tids,\n}: {\n\teditor: Editor\n\tids: TLShapeId[]\n}): TLRotationSnapshot | null {\n\tconst shapes = compact(ids.map((id) => editor.getShape(id)))\n\tconst rotation = editor.getShapesSharedRotation(ids)\n\tconst rotatedPageBounds = editor.getShapesRotatedPageBounds(ids)\n\n\t// todo: this assumes we're rotating the selected shapes\n\t// if we try to rotate shapes that aren't selected, this\n\t// will produce the wrong results\n\n\t// Return null if there are no selected shapes\n\tif (!rotatedPageBounds) {\n\t\treturn null\n\t}\n\n\tconst initialPageCenter = rotatedPageBounds.center\n\t\t.clone()\n\t\t.rotWith(rotatedPageBounds.point, rotation)\n\n\treturn {\n\t\tinitialPageCenter,\n\t\tinitialCursorAngle: initialPageCenter.angle(editor.inputs.getOriginPagePoint()),\n\t\tinitialShapesRotation: rotation,\n\t\tshapeSnapshots: shapes.map((shape) => ({\n\t\t\tshape,\n\t\t\tinitialPagePoint: editor.getShapePageTransform(shape.id)!.point(),\n\t\t})),\n\t}\n}\n\n/**\n * @internal\n **/\nexport interface TLRotationSnapshot {\n\tinitialPageCenter: Vec\n\tinitialCursorAngle: number\n\tinitialShapesRotation: number\n\tshapeSnapshots: {\n\t\tshape: TLShape\n\t\tinitialPagePoint: Vec\n\t}[]\n}\n\n/** @internal */\nexport function applyRotationToSnapshotShapes({\n\tdelta,\n\teditor,\n\tsnapshot,\n\tstage,\n\tcenterOverride,\n}: {\n\tdelta: number\n\tsnapshot: TLRotationSnapshot\n\teditor: Editor\n\tstage: 'start' | 'update' | 'end' | 'one-off'\n\tcenterOverride?: VecLike\n}) {\n\tconst { initialPageCenter, shapeSnapshots } = snapshot\n\n\teditor.updateShapes(\n\t\tshapeSnapshots.map(({ shape, initialPagePoint }) => {\n\t\t\t// We need to both rotate each shape individually and rotate the shapes\n\t\t\t// around the pivot point (the average center of all rotating shapes.)\n\n\t\t\tconst parentTransform = isShapeId(shape.parentId)\n\t\t\t\t? editor.getShapePageTransform(shape.parentId)!\n\t\t\t\t: Mat.Identity()\n\n\t\t\tconst newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta)\n\n\t\t\tconst newLocalPoint = Mat.applyToPoint(\n\t\t\t\t// use the current parent transform in case it has moved/resized since the start\n\t\t\t\t// (e.g. if rotating a shape at the edge of a group)\n\t\t\t\tMat.Inverse(parentTransform),\n\t\t\t\tnewPagePoint\n\t\t\t)\n\t\t\tconst newRotation = canonicalizeRotation(shape.rotation + delta)\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx: newLocalPoint.x,\n\t\t\t\ty: newLocalPoint.y,\n\t\t\t\trotation: newRotation,\n\t\t\t}\n\t\t})\n\t)\n\n\t// Handle change\n\n\tconst changes: TLShapePartial[] = []\n\n\tshapeSnapshots.forEach(({ shape }) => {\n\t\tconst current = editor.getShape(shape.id)\n\t\tif (!current) return\n\t\tconst util = editor.getShapeUtil(shape)\n\n\t\tif (stage === 'start' || stage === 'one-off') {\n\t\t\tconst changeStart = util.onRotateStart?.(shape)\n\t\t\tif (changeStart) changes.push(changeStart)\n\t\t}\n\n\t\tconst changeUpdate = util.onRotate?.(shape, current)\n\t\tif (changeUpdate) changes.push(changeUpdate)\n\n\t\tif (stage === 'end' || stage === 'one-off') {\n\t\t\tconst changeEnd = util.onRotateEnd?.(shape, current)\n\t\t\tif (changeEnd) changes.push(changeEnd)\n\t\t}\n\t})\n\n\tif (changes.length > 0) {\n\t\teditor.updateShapes(changes)\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAqD;AAC9D,SAAS,eAAe;AAExB,SAAS,WAAW;AACpB,SAAS,4BAA4B;AACrC,SAAS,WAAoB;AAGtB,SAAS,oBAAoB;AAAA,EACnC;AAAA,EACA;AACD,GAG8B;AAC7B,QAAM,SAAS,QAAQ,IAAI,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC;AAC3D,QAAM,WAAW,OAAO,wBAAwB,GAAG;AACnD,QAAM,oBAAoB,OAAO,2BAA2B,GAAG;AAO/D,MAAI,CAAC,mBAAmB;AACvB,WAAO;AAAA,EACR;AAEA,QAAM,oBAAoB,kBAAkB,OAC1C,MAAM,EACN,QAAQ,kBAAkB,OAAO,QAAQ;AAE3C,SAAO;AAAA,IACN;AAAA,IACA,oBAAoB,kBAAkB,MAAM,OAAO,OAAO,mBAAmB,CAAC;AAAA,IAC9E,uBAAuB;AAAA,IACvB,gBAAgB,OAAO,IAAI,CAAC,WAAW;AAAA,MACtC;AAAA,MACA,kBAAkB,OAAO,sBAAsB,MAAM,EAAE,EAAG,MAAM;AAAA,IACjE,EAAE;AAAA,EACH;AACD;AAgBO,SAAS,8BAA8B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,GAMG;AACF,QAAM,EAAE,mBAAmB,eAAe,IAAI;AAE9C,SAAO;AAAA,IACN,eAAe,IAAI,CAAC,EAAE,OAAO,iBAAiB,MAAM;AAInD,YAAM,kBAAkB,UAAU,MAAM,QAAQ,IAC7C,OAAO,sBAAsB,MAAM,QAAQ,IAC3C,IAAI,SAAS;AAEhB,YAAM,eAAe,IAAI,QAAQ,kBAAkB,kBAAkB,mBAAmB,KAAK;AAE7F,YAAM,gBAAgB,IAAI;AAAA;AAAA;AAAA,QAGzB,IAAI,QAAQ,eAAe;AAAA,QAC3B;AAAA,MACD;AACA,YAAM,cAAc,qBAAqB,MAAM,WAAW,KAAK;AAE/D,aAAO;AAAA,QACN,IAAI,MAAM;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,GAAG,cAAc;AAAA,QACjB,GAAG,cAAc;AAAA,QACjB,UAAU;AAAA,MACX;AAAA,IACD,CAAC;AAAA,EACF;AAIA,QAAM,UAA4B,CAAC;AAEnC,iBAAe,QAAQ,CAAC,EAAE,MAAM,MAAM;AACrC,UAAM,UAAU,OAAO,SAAS,MAAM,EAAE;AACxC,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,OAAO,aAAa,KAAK;AAEtC,QAAI,UAAU,WAAW,UAAU,WAAW;AAC7C,YAAM,cAAc,KAAK,gBAAgB,KAAK;AAC9C,UAAI,YAAa,SAAQ,KAAK,WAAW;AAAA,IAC1C;AAEA,UAAM,eAAe,KAAK,WAAW,OAAO,OAAO;AACnD,QAAI,aAAc,SAAQ,KAAK,YAAY;AAE3C,QAAI,UAAU,SAAS,UAAU,WAAW;AAC3C,YAAM,YAAY,KAAK,cAAc,OAAO,OAAO;AACnD,UAAI,UAAW,SAAQ,KAAK,SAAS;AAAA,IACtC;AAAA,EACD,CAAC;AAED,MAAI,QAAQ,SAAS,GAAG;AACvB,WAAO,aAAa,OAAO;AAAA,EAC5B;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "4.3.0-canary.c5efe11c58e0";
1
+ const version = "4.3.0-canary.cb6779b4f066";
2
2
  const publishDates = {
3
3
  major: "2025-09-18T14:39:22.803Z",
4
- minor: "2025-11-28T08:48:30.162Z",
5
- patch: "2025-11-28T08:48:30.162Z"
4
+ minor: "2026-01-18T00:37:30.564Z",
5
+ patch: "2026-01-18T00:37:30.564Z"
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 = '4.3.0-canary.c5efe11c58e0'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2025-11-28T08:48:30.162Z',\n\tpatch: '2025-11-28T08:48:30.162Z',\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 = '4.3.0-canary.cb6779b4f066'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-01-18T00:37:30.564Z',\n\tpatch: '2026-01-18T00:37:30.564Z',\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
@@ -607,7 +607,6 @@ input,
607
607
  pointer-events: all;
608
608
  white-space: pre-wrap;
609
609
  overflow-wrap: break-word;
610
- text-shadow: var(--tl-text-outline);
611
610
  }
612
611
 
613
612
  .tl-text-wrapper[data-font='draw'] {
@@ -709,7 +708,7 @@ input,
709
708
  resize: none;
710
709
  border: none;
711
710
  user-select: none;
712
- contain: style paint;
711
+ contain: layout style paint;
713
712
  /* N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers") is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs. */
714
713
  unicode-bidi: plaintext;
715
714
  -webkit-user-select: none;
@@ -770,7 +769,6 @@ input,
770
769
  justify-content: center;
771
770
  align-items: center;
772
771
  color: var(--tl-color-text);
773
- text-shadow: var(--tl-text-outline);
774
772
  line-height: inherit;
775
773
  position: absolute;
776
774
  inset: 0px;
@@ -970,6 +968,14 @@ input,
970
968
  display: block;
971
969
  }
972
970
 
971
+ .tl-text__outline {
972
+ text-shadow: var(--tl-text-outline);
973
+ }
974
+
975
+ .tl-text__no-outline {
976
+ text-shadow: none;
977
+ }
978
+
973
979
  /* --------------------- Loading -------------------- */
974
980
 
975
981
  .tl-loading {
@@ -1134,14 +1140,12 @@ input,
1134
1140
  fill: none;
1135
1141
  }
1136
1142
 
1137
- @media (pointer: coarse) {
1138
- .tl-handle__bg:active {
1139
- fill: var(--tl-color-selection-fill);
1140
- }
1143
+ .tl-container[data-coarse='true'] .tl-handle__bg:active {
1144
+ fill: var(--tl-color-selection-fill);
1145
+ }
1141
1146
 
1142
- .tl-handle__create {
1143
- opacity: 1;
1144
- }
1147
+ .tl-container[data-coarse='true'] .tl-handle__create {
1148
+ opacity: 1;
1145
1149
  }
1146
1150
 
1147
1151
  .tl-rotate-corner:not(:hover),
@@ -1217,7 +1221,6 @@ input,
1217
1221
  align-items: center;
1218
1222
  text-align: center;
1219
1223
  color: var(--tl-color-text);
1220
- text-shadow: var(--tl-text-outline);
1221
1224
  }
1222
1225
 
1223
1226
  .tl-shape[data-shape-type='arrow'] .tl-text-label__inner {
@@ -1446,7 +1449,6 @@ input,
1446
1449
  }
1447
1450
 
1448
1451
  .tl-note__container > .tl-text-label {
1449
- text-shadow: none;
1450
1452
  color: currentColor;
1451
1453
  }
1452
1454
 
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": "4.3.0-canary.c5efe11c58e0",
4
+ "version": "4.3.0-canary.cb6779b4f066",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -47,38 +47,42 @@
47
47
  "context": "yarn run -T tsx ../../internal/scripts/context.ts"
48
48
  },
49
49
  "dependencies": {
50
- "@tiptap/core": "^3.6.2",
51
- "@tiptap/pm": "^3.6.2",
52
- "@tiptap/react": "^3.6.2",
53
- "@tldraw/state": "4.3.0-canary.c5efe11c58e0",
54
- "@tldraw/state-react": "4.3.0-canary.c5efe11c58e0",
55
- "@tldraw/store": "4.3.0-canary.c5efe11c58e0",
56
- "@tldraw/tlschema": "4.3.0-canary.c5efe11c58e0",
57
- "@tldraw/utils": "4.3.0-canary.c5efe11c58e0",
58
- "@tldraw/validate": "4.3.0-canary.c5efe11c58e0",
50
+ "@tiptap/core": "^3.12.1",
51
+ "@tiptap/pm": "^3.12.1",
52
+ "@tiptap/react": "^3.12.1",
53
+ "@tldraw/state": "4.3.0-canary.cb6779b4f066",
54
+ "@tldraw/state-react": "4.3.0-canary.cb6779b4f066",
55
+ "@tldraw/store": "4.3.0-canary.cb6779b4f066",
56
+ "@tldraw/tlschema": "4.3.0-canary.cb6779b4f066",
57
+ "@tldraw/utils": "4.3.0-canary.cb6779b4f066",
58
+ "@tldraw/validate": "4.3.0-canary.cb6779b4f066",
59
59
  "@types/core-js": "^2.5.8",
60
60
  "@use-gesture/react": "^10.3.1",
61
61
  "classnames": "^2.5.1",
62
62
  "core-js": "^3.40.0",
63
63
  "eventemitter3": "^4.0.7",
64
64
  "idb": "^7.1.1",
65
- "is-plain-object": "^5.0.0"
65
+ "is-plain-object": "^5.0.0",
66
+ "rbush": "^4.0.1"
66
67
  },
67
68
  "peerDependencies": {
68
- "react": "^18.2.0 || ^19.0.0",
69
- "react-dom": "^18.2.0 || ^19.0.0"
69
+ "react": "^18.2.0 || ^19.2.1",
70
+ "react-dom": "^18.2.0 || ^19.2.1"
70
71
  },
71
72
  "devDependencies": {
72
73
  "@peculiar/webcrypto": "^1.5.0",
73
- "@testing-library/react": "^15.0.7",
74
+ "@testing-library/dom": "^10.0.0",
75
+ "@testing-library/react": "^16.0.0",
74
76
  "@types/benchmark": "^2.1.5",
75
- "@types/react": "^18.3.18",
77
+ "@types/rbush": "^4.0.0",
78
+ "@types/react": "^19.2.7",
79
+ "@types/react-dom": "^19.2.3",
76
80
  "@types/wicg-file-system-access": "^2020.9.8",
77
81
  "benchmark": "^2.1.4",
78
82
  "fake-indexeddb": "^4.0.2",
79
83
  "lazyrepo": "0.0.0-alpha.27",
80
- "react": "^18.3.1",
81
- "react-dom": "^18.3.1",
84
+ "react": "^19.2.1",
85
+ "react-dom": "^19.2.1",
82
86
  "resize-observer-polyfill": "^1.5.1",
83
87
  "vitest": "^3.2.4"
84
88
  },
package/src/index.ts CHANGED
@@ -146,6 +146,7 @@ export {
146
146
  type TLFontFaceSource,
147
147
  } from './lib/editor/managers/FontManager/FontManager'
148
148
  export { HistoryManager } from './lib/editor/managers/HistoryManager/HistoryManager'
149
+ export { InputsManager } from './lib/editor/managers/InputsManager/InputsManager'
149
150
  export {
150
151
  ScribbleManager,
151
152
  type ScribbleItem,
@@ -163,11 +164,13 @@ export {
163
164
  type SnapData,
164
165
  type SnapIndicator,
165
166
  } from './lib/editor/managers/SnapManager/SnapManager'
167
+ export { SpatialIndexManager } from './lib/editor/managers/SpatialIndexManager/SpatialIndexManager'
166
168
  export {
167
169
  TextManager,
168
170
  type TLMeasureTextOpts,
169
171
  type TLMeasureTextSpanOpts,
170
172
  } from './lib/editor/managers/TextManager/TextManager'
173
+ export { TickManager } from './lib/editor/managers/TickManager/TickManager'
171
174
  export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
172
175
  export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseBoxShapeUtil'
173
176
  export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
@@ -178,6 +181,7 @@ export {
178
181
  type TLDragShapesOutInfo,
179
182
  type TLDragShapesOverInfo,
180
183
  type TLDropShapesOverInfo,
184
+ type TLEditStartInfo,
181
185
  type TLGeometryOpts,
182
186
  type TLHandleDragInfo,
183
187
  type TLResizeInfo,
@@ -282,7 +286,7 @@ export {
282
286
  type SvgExportDef,
283
287
  } from './lib/editor/types/SvgExportContext'
284
288
  export { getSvgAsImage } from './lib/exports/getSvgAsImage'
285
- export { tlenv } from './lib/globals/environment'
289
+ export { tlenv, tlenvReactive } from './lib/globals/environment'
286
290
  export { tlmenus } from './lib/globals/menus'
287
291
  export { tltime } from './lib/globals/time'
288
292
  export {
@@ -12,7 +12,7 @@ const initialState = { error: null }
12
12
 
13
13
  /** @public */
14
14
  export class ErrorBoundary extends React.Component<
15
- React.PropsWithRef<React.PropsWithChildren<TLErrorBoundaryProps>>,
15
+ React.PropsWithChildren<TLErrorBoundaryProps>,
16
16
  { error: Error | null }
17
17
  > {
18
18
  static getDerivedStateFromError(error: Error) {
@@ -1,23 +1,9 @@
1
1
  import { track } from '@tldraw/state-react'
2
2
  import { modulate } from '@tldraw/utils'
3
- import { useEffect, useState } from 'react'
4
3
  import { useEditor } from '../hooks/useEditor'
5
4
  import { Geometry2d } from '../primitives/geometry/Geometry2d'
6
5
  import { Group2d } from '../primitives/geometry/Group2d'
7
6
 
8
- function useTick(isEnabled = true) {
9
- const [_, setTick] = useState(0)
10
- const editor = useEditor()
11
- useEffect(() => {
12
- if (!isEnabled) return
13
- const update = () => setTick((tick) => tick + 1)
14
- editor.on('tick', update)
15
- return () => {
16
- editor.off('tick', update)
17
- }
18
- }, [editor, isEnabled])
19
- }
20
-
21
7
  export const GeometryDebuggingView = track(function GeometryDebuggingView({
22
8
  showStroke = true,
23
9
  showVertices = true,
@@ -29,13 +15,9 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({
29
15
  }) {
30
16
  const editor = useEditor()
31
17
 
32
- useTick(showClosestPointOnOutline)
33
-
34
18
  const zoomLevel = editor.getZoomLevel()
35
19
  const renderingShapes = editor.getRenderingShapes()
36
- const {
37
- inputs: { currentPagePoint },
38
- } = editor
20
+ const currentPagePoint = editor.inputs.getCurrentPagePoint()
39
21
 
40
22
  return (
41
23
  <svg
@@ -86,6 +86,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
86
86
  const transform = `scale(${toDomPrecision(z)}) translate(${toDomPrecision(
87
87
  x + offset
88
88
  )}px,${toDomPrecision(y + offset)}px)`
89
+
89
90
  setStyleProperty(rHtmlLayer.current, 'transform', transform)
90
91
  setStyleProperty(rHtmlLayer2.current, 'transform', transform)
91
92
  },
@@ -209,7 +210,7 @@ function GridWrapper() {
209
210
  function ScribbleWrapper() {
210
211
  const editor = useEditor()
211
212
  const scribbles = useValue('scribbles', () => editor.getInstanceState().scribbles, [editor])
212
- const zoomLevel = useValue('zoomLevel', () => editor.getZoomLevel(), [editor])
213
+ const zoomLevel = useValue('zoomLevel', () => editor.getEfficientZoomLevel(), [editor])
213
214
  const { Scribble } = useEditorComponents()
214
215
 
215
216
  if (!(Scribble && scribbles.length)) return null
@@ -242,7 +243,7 @@ function ZoomBrushWrapper() {
242
243
  function SnapIndicatorWrapper() {
243
244
  const editor = useEditor()
244
245
  const lines = useValue('snapLines', () => editor.snaps.getIndicators(), [editor])
245
- const zoomLevel = useValue('zoomLevel', () => editor.getZoomLevel(), [editor])
246
+ const zoomLevel = useValue('zoomLevel', () => editor.getEfficientZoomLevel(), [editor])
246
247
  const { SnapIndicator } = useEditorComponents()
247
248
 
248
249
  if (!(SnapIndicator && lines.length > 0)) return null
@@ -283,7 +284,7 @@ function HandlesWrapperInner({ shapeId }: { shapeId: TLShapeId }) {
283
284
  const editor = useEditor()
284
285
  const { Handles } = useEditorComponents()
285
286
 
286
- const zoomLevel = useValue('zoomLevel', () => editor.getZoomLevel(), [editor])
287
+ const zoomLevel = useValue('zoomLevel', () => editor.getEfficientZoomLevel(), [editor])
287
288
 
288
289
  const isCoarse = useValue('coarse pointer', () => editor.getInstanceState().isCoarsePointer, [
289
290
  editor,
@@ -411,11 +412,7 @@ function ReflowIfNeeded() {
411
412
  'reflow for culled shapes',
412
413
  () => {
413
414
  const culledShapes = editor.getCulledShapes()
414
- if (
415
- culledShapesRef.current.size === culledShapes.size &&
416
- [...culledShapes].every((id) => culledShapesRef.current.has(id))
417
- )
418
- return
415
+ if (culledShapesRef.current === culledShapes) return
419
416
 
420
417
  culledShapesRef.current = culledShapes
421
418
  const canvas = document.getElementsByClassName('tl-canvas')
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { defaultUserPreferences, userTypeValidator } from './TLUserPreferences'
3
+
4
+ describe('TLUserPreferences consistency', () => {
5
+ // When adding a new user preference, add it to this list AND update:
6
+ // 1. TLUserPreferences interface
7
+ // 2. userTypeValidator
8
+ // 3. defaultUserPreferences
9
+ // 4. Versions enum and migrateSnapshot()
10
+ const interfaceKeys = [
11
+ 'name',
12
+ 'color',
13
+ 'locale',
14
+ 'animationSpeed',
15
+ 'areKeyboardShortcutsEnabled',
16
+ 'edgeScrollSpeed',
17
+ 'colorScheme',
18
+ 'isSnapMode',
19
+ 'isWrapMode',
20
+ 'isDynamicSizeMode',
21
+ 'isPasteAtCursorMode',
22
+ 'enhancedA11yMode',
23
+ 'inputMode',
24
+ ] as const
25
+
26
+ it('defaultUserPreferences contains all TLUserPreferences keys (except id)', () => {
27
+ const defaultKeys = Object.keys(defaultUserPreferences).sort()
28
+ const expected = [...interfaceKeys].sort()
29
+
30
+ expect(defaultKeys).toEqual(expected)
31
+ })
32
+
33
+ it('userTypeValidator validates all TLUserPreferences keys', () => {
34
+ // Access the internal config property to check which keys the validator covers
35
+ const validatorKeys = Object.keys((userTypeValidator as any).config).sort()
36
+ const expected = ['id', ...interfaceKeys].sort()
37
+
38
+ expect(validatorKeys).toEqual(expected)
39
+ })
40
+ })
@@ -31,5 +31,3 @@ export const LEFT_MOUSE_BUTTON = 0
31
31
  export const RIGHT_MOUSE_BUTTON = 2
32
32
  export const MIDDLE_MOUSE_BUTTON = 1
33
33
  export const STYLUS_ERASER_BUTTON = 5
34
-
35
- export const ZOOM_TO_FIT_PADDING = 128
@@ -925,6 +925,146 @@ describe('replaceExternalContent', () => {
925
925
  })
926
926
  })
927
927
 
928
+ describe('dispatch event emission', () => {
929
+ let testEditor: Editor
930
+
931
+ beforeEach(() => {
932
+ testEditor = new Editor({
933
+ shapeUtils: [CustomShape],
934
+ bindingUtils: [],
935
+ tools: [],
936
+ store: createTLStore({ shapeUtils: [CustomShape], bindingUtils: [] }),
937
+ getContainer: () => document.body,
938
+ })
939
+ // Ensure camera is unlocked so events are processed
940
+ testEditor.setCameraOptions({ isLocked: false })
941
+ })
942
+
943
+ it('emits wheel events through the event emitter', () => {
944
+ const eventHandler = vi.fn()
945
+ testEditor.on('event', eventHandler)
946
+
947
+ const wheelEvent = {
948
+ type: 'wheel' as const,
949
+ name: 'wheel' as const,
950
+ delta: { x: 0, y: 10, z: 0 },
951
+ point: { x: 100, y: 100, z: 1 },
952
+ shiftKey: false,
953
+ altKey: false,
954
+ ctrlKey: false,
955
+ metaKey: false,
956
+ accelKey: false,
957
+ }
958
+
959
+ testEditor.dispatch(wheelEvent)
960
+ // Wheel events are batched for the next tick, so emit a tick to flush them
961
+ testEditor.emit('tick', 16)
962
+
963
+ expect(eventHandler).toHaveBeenCalledWith(wheelEvent)
964
+ })
965
+
966
+ it('emits pinch_start events through the event emitter', () => {
967
+ const eventHandler = vi.fn()
968
+ testEditor.on('event', eventHandler)
969
+
970
+ const pinchStartEvent = {
971
+ type: 'pinch' as const,
972
+ name: 'pinch_start' as const,
973
+ point: { x: 100, y: 100, z: 1 },
974
+ delta: { x: 0, y: 0, z: 0 },
975
+ shiftKey: false,
976
+ altKey: false,
977
+ ctrlKey: false,
978
+ metaKey: false,
979
+ accelKey: false,
980
+ }
981
+
982
+ testEditor.dispatch(pinchStartEvent)
983
+ // Pinch events are batched for the next tick, so emit a tick to flush them
984
+ testEditor.emit('tick', 16)
985
+
986
+ expect(eventHandler).toHaveBeenCalledWith(pinchStartEvent)
987
+ })
988
+
989
+ it('emits pinch events through the event emitter', () => {
990
+ const eventHandler = vi.fn()
991
+ testEditor.on('event', eventHandler)
992
+
993
+ // First dispatch pinch_start to set isPinching
994
+ const pinchStartEvent = {
995
+ type: 'pinch' as const,
996
+ name: 'pinch_start' as const,
997
+ point: { x: 100, y: 100, z: 1 },
998
+ delta: { x: 0, y: 0, z: 0 },
999
+ shiftKey: false,
1000
+ altKey: false,
1001
+ ctrlKey: false,
1002
+ metaKey: false,
1003
+ accelKey: false,
1004
+ }
1005
+ testEditor.dispatch(pinchStartEvent)
1006
+ testEditor.emit('tick', 16)
1007
+
1008
+ eventHandler.mockClear()
1009
+
1010
+ const pinchEvent = {
1011
+ type: 'pinch' as const,
1012
+ name: 'pinch' as const,
1013
+ point: { x: 100, y: 100, z: 1.5 },
1014
+ delta: { x: 10, y: 10, z: 0 },
1015
+ shiftKey: false,
1016
+ altKey: false,
1017
+ ctrlKey: false,
1018
+ metaKey: false,
1019
+ accelKey: false,
1020
+ }
1021
+
1022
+ testEditor.dispatch(pinchEvent)
1023
+ testEditor.emit('tick', 16)
1024
+
1025
+ expect(eventHandler).toHaveBeenCalledWith(pinchEvent)
1026
+ })
1027
+
1028
+ it('emits pinch_end events through the event emitter', () => {
1029
+ const eventHandler = vi.fn()
1030
+ testEditor.on('event', eventHandler)
1031
+
1032
+ // First dispatch pinch_start to set isPinching
1033
+ const pinchStartEvent = {
1034
+ type: 'pinch' as const,
1035
+ name: 'pinch_start' as const,
1036
+ point: { x: 100, y: 100, z: 1 },
1037
+ delta: { x: 0, y: 0, z: 0 },
1038
+ shiftKey: false,
1039
+ altKey: false,
1040
+ ctrlKey: false,
1041
+ metaKey: false,
1042
+ accelKey: false,
1043
+ }
1044
+ testEditor.dispatch(pinchStartEvent)
1045
+ testEditor.emit('tick', 16)
1046
+
1047
+ eventHandler.mockClear()
1048
+
1049
+ const pinchEndEvent = {
1050
+ type: 'pinch' as const,
1051
+ name: 'pinch_end' as const,
1052
+ point: { x: 100, y: 100, z: 1.5 },
1053
+ delta: { x: 0, y: 0, z: 0 },
1054
+ shiftKey: false,
1055
+ altKey: false,
1056
+ ctrlKey: false,
1057
+ metaKey: false,
1058
+ accelKey: false,
1059
+ }
1060
+
1061
+ testEditor.dispatch(pinchEndEvent)
1062
+ testEditor.emit('tick', 16)
1063
+
1064
+ expect(eventHandler).toHaveBeenCalledWith(pinchEndEvent)
1065
+ })
1066
+ })
1067
+
928
1068
  describe('setTool', () => {
929
1069
  class CustomToolA extends StateNode {
930
1070
  static override id = 'custom-tool-a'