@tldraw/editor 3.13.0-canary.ae83a75c91b6 → 3.13.0-canary.b29c8448ae7b

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 (180) hide show
  1. package/dist-cjs/index.d.ts +114 -112
  2. package/dist-cjs/index.js +7 -22
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +2 -1
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/Shape.js +12 -8
  7. package/dist-cjs/lib/components/Shape.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +36 -7
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +14 -12
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +17 -11
  13. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultSpinner.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
  16. package/dist-cjs/lib/editor/Editor.js +40 -15
  17. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  18. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/TextManager.js +10 -0
  20. package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +1 -1
  22. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  23. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  24. package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
  25. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  26. package/dist-cjs/lib/hooks/useDocumentEvents.js +3 -2
  27. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  28. package/dist-cjs/lib/hooks/useEditorComponents.js +16 -16
  29. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  30. package/dist-cjs/lib/license/LicenseManager.js +8 -1
  31. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  32. package/dist-cjs/lib/options.js.map +2 -2
  33. package/dist-cjs/lib/primitives/Box.js +16 -0
  34. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  35. package/dist-cjs/lib/primitives/Mat.js +1 -1
  36. package/dist-cjs/lib/primitives/Mat.js.map +2 -2
  37. package/dist-cjs/lib/primitives/Vec.js +20 -0
  38. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  39. package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
  40. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  41. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  42. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  43. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  44. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  45. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  46. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  47. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  48. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  49. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +91 -20
  50. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  51. package/dist-cjs/lib/primitives/geometry/Group2d.js +55 -2
  52. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  53. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  54. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  55. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  56. package/dist-cjs/lib/utils/areShapesContentEqual.js +25 -0
  57. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
  58. package/dist-cjs/lib/utils/debug-flags.js +5 -2
  59. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  60. package/dist-cjs/lib/utils/dom.js +3 -3
  61. package/dist-cjs/lib/utils/dom.js.map +2 -2
  62. package/dist-cjs/lib/utils/nearestMultiple.js +34 -0
  63. package/dist-cjs/lib/utils/nearestMultiple.js.map +7 -0
  64. package/dist-cjs/lib/utils/rotation.js +5 -5
  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 +114 -112
  69. package/dist-esm/index.mjs +9 -41
  70. package/dist-esm/index.mjs.map +2 -2
  71. package/dist-esm/lib/TldrawEditor.mjs +2 -1
  72. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  73. package/dist-esm/lib/components/Shape.mjs +12 -8
  74. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  75. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +36 -7
  76. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  77. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +14 -12
  78. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  79. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
  80. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  81. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +1 -1
  82. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  83. package/dist-esm/lib/editor/Editor.mjs +40 -15
  84. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  85. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/TextManager.mjs +10 -0
  87. package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +1 -1
  89. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  90. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  91. package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
  92. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  93. package/dist-esm/lib/hooks/useDocumentEvents.mjs +3 -2
  94. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  95. package/dist-esm/lib/hooks/useEditorComponents.mjs +16 -18
  96. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  97. package/dist-esm/lib/license/LicenseManager.mjs +8 -1
  98. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  99. package/dist-esm/lib/options.mjs.map +2 -2
  100. package/dist-esm/lib/primitives/Box.mjs +16 -0
  101. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  102. package/dist-esm/lib/primitives/Mat.mjs +1 -1
  103. package/dist-esm/lib/primitives/Mat.mjs.map +2 -2
  104. package/dist-esm/lib/primitives/Vec.mjs +20 -0
  105. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  106. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  107. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  108. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -1
  109. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  110. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  111. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  112. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  113. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  114. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  115. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  116. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +92 -21
  117. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  118. package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -2
  119. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  120. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  121. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  122. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  123. package/dist-esm/lib/utils/areShapesContentEqual.mjs +5 -0
  124. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
  125. package/dist-esm/lib/utils/debug-flags.mjs +5 -2
  126. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  127. package/dist-esm/lib/utils/dom.mjs +3 -3
  128. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  129. package/dist-esm/lib/utils/nearestMultiple.mjs +14 -0
  130. package/dist-esm/lib/utils/nearestMultiple.mjs.map +7 -0
  131. package/dist-esm/lib/utils/rotation.mjs +5 -5
  132. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  133. package/dist-esm/version.mjs +3 -3
  134. package/dist-esm/version.mjs.map +1 -1
  135. package/editor.css +42 -4
  136. package/package.json +7 -7
  137. package/src/index.ts +16 -31
  138. package/src/lib/TldrawEditor.tsx +6 -1
  139. package/src/lib/components/Shape.tsx +14 -10
  140. package/src/lib/components/default-components/DefaultCanvas.tsx +41 -7
  141. package/src/lib/components/default-components/DefaultErrorFallback.tsx +25 -14
  142. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
  143. package/src/lib/components/default-components/DefaultSpinner.tsx +1 -1
  144. package/src/lib/editor/Editor.test.ts +1 -1
  145. package/src/lib/editor/Editor.ts +40 -15
  146. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +0 -1
  147. package/src/lib/editor/managers/TextManager.ts +12 -0
  148. package/src/lib/editor/shapes/ShapeUtil.ts +23 -3
  149. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +9 -9
  150. package/src/lib/exports/getSvgJsx.tsx +16 -7
  151. package/src/lib/hooks/useDocumentEvents.ts +7 -2
  152. package/src/lib/hooks/useEditorComponents.tsx +33 -32
  153. package/src/lib/license/LicenseManager.test.ts +40 -0
  154. package/src/lib/license/LicenseManager.ts +13 -1
  155. package/src/lib/options.ts +4 -0
  156. package/src/lib/primitives/Box.ts +20 -0
  157. package/src/lib/primitives/Mat.ts +5 -4
  158. package/src/lib/primitives/Vec.ts +23 -0
  159. package/src/lib/primitives/geometry/Arc2d.ts +5 -5
  160. package/src/lib/primitives/geometry/Circle2d.ts +4 -4
  161. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -4
  162. package/src/lib/primitives/geometry/CubicSpline2d.ts +3 -3
  163. package/src/lib/primitives/geometry/Edge2d.ts +3 -3
  164. package/src/lib/primitives/geometry/Ellipse2d.ts +3 -3
  165. package/src/lib/primitives/geometry/Geometry2d.test.ts +42 -0
  166. package/src/lib/primitives/geometry/Geometry2d.ts +123 -35
  167. package/src/lib/primitives/geometry/Group2d.ts +70 -7
  168. package/src/lib/primitives/geometry/Point2d.ts +2 -2
  169. package/src/lib/primitives/geometry/Polyline2d.ts +3 -3
  170. package/src/lib/primitives/geometry/Stadium2d.ts +3 -3
  171. package/src/lib/test/currentToolIdMask.test.ts +1 -1
  172. package/src/lib/test/user.test.ts +1 -1
  173. package/src/lib/utils/areShapesContentEqual.ts +4 -0
  174. package/src/lib/utils/debug-flags.ts +7 -2
  175. package/src/lib/utils/dom.ts +4 -4
  176. package/src/lib/utils/nearestMultiple.ts +13 -0
  177. package/src/lib/utils/rotation.ts +8 -6
  178. package/src/lib/utils/sync/LocalIndexedDb.test.ts +1 -1
  179. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
  180. package/src/version.ts +3 -3
package/editor.css CHANGED
@@ -44,6 +44,7 @@
44
44
  /* User handles need to be above selection edges / corners, matters for sticky note clone handles */
45
45
  --layer-overlays-user-handles: 105;
46
46
  --layer-overlays-user-indicator-hint: 110;
47
+ --layer-overlays-custom: 115;
47
48
  --layer-overlays-collaborator-cursor-hint: 120;
48
49
  --layer-overlays-collaborator-cursor: 130;
49
50
 
@@ -156,6 +157,7 @@
156
157
  --color-panel-contrast: hsl(0, 0%, 100%);
157
158
  --color-panel-overlay: hsl(0, 0%, 100%, 82%);
158
159
  --color-panel: hsl(0, 0%, 99%);
160
+ --color-panel-transparent: hsla(0, 0%, 99%, 0%);
159
161
  --color-focus: hsl(219, 65%, 50%);
160
162
  --color-selected: hsl(214, 84%, 56%);
161
163
  --color-selected-contrast: hsl(0, 0%, 100%);
@@ -207,6 +209,7 @@
207
209
  --color-panel-contrast: hsl(245, 12%, 23%);
208
210
  --color-panel: hsl(235, 6.8%, 13.5%);
209
211
  --color-panel-overlay: hsl(210, 10%, 24%, 82%);
212
+ --color-panel-transparent: hsla(235, 6.8%, 13.5%, 0%);
210
213
  --color-focus: hsl(217, 76%, 80%);
211
214
  --color-selected: hsl(217, 89%, 61%);
212
215
  --color-selected-contrast: hsl(0, 0%, 100%);
@@ -472,6 +475,10 @@ input,
472
475
  stroke-width: calc(2.5px * var(--tl-scale));
473
476
  }
474
477
 
478
+ .tl-custom-overlays {
479
+ z-index: var(--layer-overlays-custom);
480
+ }
481
+
475
482
  /* behind collaborator cursor */
476
483
  .tl-collaborator__cursor-hint {
477
484
  z-index: var(--layer-overlays-collaborator-cursor-hint);
@@ -595,6 +602,31 @@ input,
595
602
  }
596
603
  }
597
604
 
605
+ /* --------------------- Arrow Hints -------------------- */
606
+
607
+ .tl-arrow-hint-handle {
608
+ fill: var(--color-selected-contrast);
609
+ stroke: var(--color-selection-stroke);
610
+ stroke-width: calc(1.5px * var(--tl-scale));
611
+ r: calc(4px * var(--tl-scale));
612
+ }
613
+
614
+ .tl-arrow-hint-snap {
615
+ stroke: transparent;
616
+ fill: var(--color-selection-fill);
617
+ r: calc(12px * var(--tl-scale));
618
+ }
619
+
620
+ .tl-arrow-hint-snap__none,
621
+ .tl-arrow-hint-snap__center,
622
+ .tl-arrow-hint-snap__axis {
623
+ display: none;
624
+ }
625
+
626
+ .tl-arrow-hint-snap__edge {
627
+ r: calc(8px * var(--tl-scale));
628
+ }
629
+
598
630
  /* ------------------ Bounds Detail ----------------- */
599
631
 
600
632
  .tl-image,
@@ -964,6 +996,8 @@ input,
964
996
 
965
997
  .tl-rich-text p {
966
998
  margin: 0;
999
+ /* Depending on the extensions, <p> tags can be empty, without a <br />. */
1000
+ min-height: 1lh;
967
1001
  }
968
1002
 
969
1003
  .tl-rich-text ul,
@@ -971,6 +1005,8 @@ input,
971
1005
  text-align: left;
972
1006
  margin: 0;
973
1007
  padding-left: 3.25ch;
1008
+ /* Some resets, like Tailwind, nix the list styling. */
1009
+ list-style: revert;
974
1010
  }
975
1011
 
976
1012
  .tl-rich-text ol:has(> li:nth-child(10)) {
@@ -1348,10 +1384,6 @@ input,
1348
1384
  opacity: 0;
1349
1385
  }
1350
1386
 
1351
- .tl-arrow-label[data-isediting='true'] > .tl-arrow-label__inner {
1352
- background-color: var(--color-background);
1353
- }
1354
-
1355
1387
  .tl-arrow-label__inner {
1356
1388
  border-radius: var(--radius-1);
1357
1389
  box-sizing: content-box;
@@ -1710,6 +1742,12 @@ it from receiving any pointer events or affecting the cursor. */
1710
1742
  background-color: var(--color-primary);
1711
1743
  color: var(--color-selected-contrast);
1712
1744
  }
1745
+ .tl-container__focused:not(.tl-container__no-focus-ring)
1746
+ .tlui-button.tl-error-boundary__refresh:focus-visible {
1747
+ border-radius: 8px;
1748
+ outline-offset: 0;
1749
+ }
1750
+
1713
1751
  /* --------------------- Coarse --------------------- */
1714
1752
 
1715
1753
  .tl-hidden {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "A tiny little drawing app (editor).",
4
- "version": "3.13.0-canary.ae83a75c91b6",
4
+ "version": "3.13.0-canary.b29c8448ae7b",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -48,12 +48,12 @@
48
48
  "@tiptap/core": "^2.9.1",
49
49
  "@tiptap/pm": "^2.9.1",
50
50
  "@tiptap/react": "^2.9.1",
51
- "@tldraw/state": "3.13.0-canary.ae83a75c91b6",
52
- "@tldraw/state-react": "3.13.0-canary.ae83a75c91b6",
53
- "@tldraw/store": "3.13.0-canary.ae83a75c91b6",
54
- "@tldraw/tlschema": "3.13.0-canary.ae83a75c91b6",
55
- "@tldraw/utils": "3.13.0-canary.ae83a75c91b6",
56
- "@tldraw/validate": "3.13.0-canary.ae83a75c91b6",
51
+ "@tldraw/state": "3.13.0-canary.b29c8448ae7b",
52
+ "@tldraw/state-react": "3.13.0-canary.b29c8448ae7b",
53
+ "@tldraw/store": "3.13.0-canary.b29c8448ae7b",
54
+ "@tldraw/tlschema": "3.13.0-canary.b29c8448ae7b",
55
+ "@tldraw/utils": "3.13.0-canary.b29c8448ae7b",
56
+ "@tldraw/validate": "3.13.0-canary.b29c8448ae7b",
57
57
  "@types/core-js": "^2.5.8",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
package/src/index.ts CHANGED
@@ -4,37 +4,11 @@ import 'core-js/stable/array/flat-map.js'
4
4
  import 'core-js/stable/array/flat.js'
5
5
  import 'core-js/stable/string/at.js'
6
6
  import 'core-js/stable/string/replace-all.js'
7
- export {
8
- EMPTY_ARRAY,
9
- EffectScheduler,
10
- atom,
11
- computed,
12
- react,
13
- transact,
14
- transaction,
15
- whyAmIRunning,
16
- type Atom,
17
- type Signal,
18
- } from '@tldraw/state'
19
- export {
20
- track,
21
- useAtom,
22
- useComputed,
23
- useQuickReactor,
24
- useReactor,
25
- useStateTracking,
26
- useValue,
27
- } from '@tldraw/state-react'
28
- export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
29
- export {
30
- getFontsFromRichText,
31
- type RichTextFontVisitor,
32
- type RichTextFontVisitorState,
33
- type TLTextOptions,
34
- type TiptapEditor,
35
- type TiptapNode,
36
- } from './lib/utils/richText'
37
- export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
7
+
8
+ // eslint-disable-next-line local/no-export-star
9
+ export * from '@tldraw/state'
10
+ // eslint-disable-next-line local/no-export-star
11
+ export * from '@tldraw/state-react'
38
12
  // eslint-disable-next-line local/no-export-star
39
13
  export * from '@tldraw/store'
40
14
  // eslint-disable-next-line local/no-export-star
@@ -43,6 +17,7 @@ export * from '@tldraw/tlschema'
43
17
  export * from '@tldraw/utils'
44
18
  // eslint-disable-next-line local/no-export-star
45
19
  export * from '@tldraw/validate'
20
+
46
21
  export {
47
22
  ErrorScreen,
48
23
  LoadingScreen,
@@ -212,6 +187,7 @@ export {
212
187
  export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
213
188
  export { getPerfectDashProps } from './lib/editor/shapes/shared/getPerfectDashProps'
214
189
  export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox'
190
+ export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
215
191
  export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
216
192
  export { maybeSnapToGrid } from './lib/editor/tools/BaseBoxShapeTool/children/Pointing'
217
193
  export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode'
@@ -459,12 +435,21 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
459
435
  export { isAccelKey } from './lib/utils/keyboard'
460
436
  export { normalizeWheel } from './lib/utils/normalizeWheel'
461
437
  export { refreshPage } from './lib/utils/refreshPage'
438
+ export {
439
+ getFontsFromRichText,
440
+ type RichTextFontVisitor,
441
+ type RichTextFontVisitorState,
442
+ type TLTextOptions,
443
+ type TiptapEditor,
444
+ type TiptapNode,
445
+ } from './lib/utils/richText'
462
446
  export {
463
447
  applyRotationToSnapshotShapes,
464
448
  getRotationSnapshot,
465
449
  type TLRotationSnapshot,
466
450
  } from './lib/utils/rotation'
467
451
  export { runtime, setRuntimeOverrides } from './lib/utils/runtime'
452
+ export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
468
453
  export { type TLStoreWithStatus } from './lib/utils/sync/StoreWithStatus'
469
454
  export { hardReset } from './lib/utils/sync/hardReset'
470
455
  export { uniq } from './lib/utils/uniq'
@@ -285,6 +285,7 @@ export const TldrawEditor = memo(function TldrawEditor({
285
285
  onPointerDown={stopEventPropagation}
286
286
  tabIndex={-1}
287
287
  role="application"
288
+ aria-label={_options?.branding ?? 'tldraw'}
288
289
  >
289
290
  <OptionalErrorBoundary
290
291
  fallback={ErrorFallback}
@@ -669,7 +670,11 @@ export interface LoadingScreenProps {
669
670
 
670
671
  /** @public @react */
671
672
  export function LoadingScreen({ children }: LoadingScreenProps) {
672
- return <div className="tl-loading">{children}</div>
673
+ return (
674
+ <div className="tl-loading" aria-busy="true" tabIndex={0}>
675
+ {children}
676
+ </div>
677
+ )
673
678
  }
674
679
 
675
680
  /** @public @react */
@@ -6,6 +6,7 @@ import { ShapeUtil } from '../editor/shapes/ShapeUtil'
6
6
  import { useEditor } from '../hooks/useEditor'
7
7
  import { useEditorComponents } from '../hooks/useEditorComponents'
8
8
  import { Mat } from '../primitives/Mat'
9
+ import { areShapesContentEqual } from '../utils/areShapesContentEqual'
9
10
  import { setStyleProperty } from '../utils/dom'
10
11
  import { OptionalErrorBoundary } from './ErrorBoundary'
11
12
 
@@ -27,6 +28,7 @@ export const Shape = memo(function Shape({
27
28
  index,
28
29
  backgroundIndex,
29
30
  opacity,
31
+ dprMultiple,
30
32
  }: {
31
33
  id: TLShapeId
32
34
  shape: TLShape
@@ -34,6 +36,7 @@ export const Shape = memo(function Shape({
34
36
  index: number
35
37
  backgroundIndex: number
36
38
  opacity: number
39
+ dprMultiple: number
37
40
  }) {
38
41
  const editor = useEditor()
39
42
 
@@ -88,14 +91,18 @@ export const Shape = memo(function Shape({
88
91
  }
89
92
 
90
93
  // Width / Height
91
- const width = Math.max(bounds.width, 1)
92
- const height = Math.max(bounds.height, 1)
94
+ // We round the shape width and height up to the nearest multiple of dprMultiple
95
+ // to avoid the browser making miscalculations when applying the transform.
96
+ const widthRemainder = bounds.w % dprMultiple
97
+ const heightRemainder = bounds.h % dprMultiple
98
+ const width = widthRemainder === 0 ? bounds.w : bounds.w + (dprMultiple - widthRemainder)
99
+ const height = heightRemainder === 0 ? bounds.h : bounds.h + (dprMultiple - heightRemainder)
93
100
 
94
101
  if (width !== prev.width || height !== prev.height) {
95
- setStyleProperty(containerRef.current, 'width', width + 'px')
96
- setStyleProperty(containerRef.current, 'height', height + 'px')
97
- setStyleProperty(bgContainerRef.current, 'width', width + 'px')
98
- setStyleProperty(bgContainerRef.current, 'height', height + 'px')
102
+ setStyleProperty(containerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
103
+ setStyleProperty(containerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
104
+ setStyleProperty(bgContainerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
105
+ setStyleProperty(bgContainerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
99
106
  prev.width = width
100
107
  prev.height = height
101
108
  }
@@ -184,10 +191,7 @@ export const InnerShape = memo(
184
191
  [util, shape.id]
185
192
  )
186
193
  },
187
- (prev, next) =>
188
- prev.shape.props === next.shape.props &&
189
- prev.shape.meta === next.shape.meta &&
190
- prev.util === next.util
194
+ (prev, next) => areShapesContentEqual(prev.shape, next.shape) && prev.util === next.util
191
195
  )
192
196
 
193
197
  export const InnerShapeBackground = memo(
@@ -22,6 +22,7 @@ import { Vec } from '../../primitives/Vec'
22
22
  import { toDomPrecision } from '../../primitives/utils'
23
23
  import { debugFlags } from '../../utils/debug-flags'
24
24
  import { setStyleProperty } from '../../utils/dom'
25
+ import { nearestMultiple } from '../../utils/nearestMultiple'
25
26
  import { GeometryDebuggingView } from '../GeometryDebuggingView'
26
27
  import { LiveCollaborators } from '../LiveCollaborators'
27
28
  import { MenuClickCapture } from '../MenuClickCapture'
@@ -36,7 +37,7 @@ export interface TLCanvasComponentProps {
36
37
  export function DefaultCanvas({ className }: TLCanvasComponentProps) {
37
38
  const editor = useEditor()
38
39
 
39
- const { Background, SvgDefs, ShapeIndicators } = useEditorComponents()
40
+ const { SelectionBackground, Background, SvgDefs, ShapeIndicators } = useEditorComponents()
40
41
 
41
42
  const rCanvas = useRef<HTMLDivElement>(null)
42
43
  const rHtmlLayer = useRef<HTMLDivElement>(null)
@@ -154,7 +155,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
154
155
  <GridWrapper />
155
156
  <div ref={rHtmlLayer} className="tl-html-layer tl-shapes" draggable={false}>
156
157
  <OnTheCanvasWrapper />
157
- <SelectionBackgroundWrapper />
158
+ {SelectionBackground && <SelectionBackgroundWrapper />}
158
159
  {hideShapes ? null : debugSvg ? <ShapesWithSVGs /> : <ShapesToDisplay />}
159
160
  </div>
160
161
  <div className="tl-overlays">
@@ -168,6 +169,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
168
169
  <SnapIndicatorWrapper />
169
170
  <SelectionForegroundWrapper />
170
171
  <HandlesWrapper />
172
+ <OverlaysWrapper />
171
173
  <LiveCollaborators />
172
174
  </div>
173
175
  </div>
@@ -372,14 +374,33 @@ function HandleWrapper({
372
374
  )
373
375
  }
374
376
 
377
+ function OverlaysWrapper() {
378
+ const { Overlays } = useEditorComponents()
379
+ if (!Overlays) return null
380
+ return (
381
+ <div className="tl-custom-overlays tl-overlays__item">
382
+ <Overlays />
383
+ </div>
384
+ )
385
+ }
386
+
375
387
  function ShapesWithSVGs() {
376
388
  const editor = useEditor()
377
389
 
378
390
  const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
379
391
 
392
+ const dprMultiple = useValue(
393
+ 'dpr multiple',
394
+ () =>
395
+ // dprMultiple is the smallest number we can multiply dpr by to get an integer
396
+ // it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
397
+ nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
398
+ [editor]
399
+ )
400
+
380
401
  return renderingShapes.map((result) => (
381
402
  <Fragment key={result.id + '_fragment'}>
382
- <Shape {...result} />
403
+ <Shape {...result} dprMultiple={dprMultiple} />
383
404
  <DebugSvgCopy id={result.id} mode="iframe" />
384
405
  </Fragment>
385
406
  ))
@@ -414,10 +435,19 @@ function ShapesToDisplay() {
414
435
 
415
436
  const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
416
437
 
438
+ const dprMultiple = useValue(
439
+ 'dpr multiple',
440
+ () =>
441
+ // dprMultiple is the smallest number we can multiply dpr by to get an integer
442
+ // it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
443
+ nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
444
+ [editor]
445
+ )
446
+
417
447
  return (
418
448
  <>
419
449
  {renderingShapes.map((result) => (
420
- <Shape key={result.id + '_shape'} {...result} />
450
+ <Shape key={result.id + '_shape'} {...result} dprMultiple={dprMultiple} />
421
451
  ))}
422
452
  {tlenv.isSafari && <ReflowIfNeeded />}
423
453
  </>
@@ -545,9 +575,13 @@ function DebugSvgCopy({ id, mode }: { id: TLShapeId; mode: 'img' | 'iframe' }) {
545
575
 
546
576
  function SelectionForegroundWrapper() {
547
577
  const editor = useEditor()
548
- const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
549
- editor,
550
- ])
578
+ const selectionRotation = useValue(
579
+ 'selection rotation',
580
+ function getSelectionRotation() {
581
+ return editor.getSelectionRotation()
582
+ },
583
+ [editor]
584
+ )
551
585
  const selectionBounds = useValue(
552
586
  'selection bounds',
553
587
  () => editor.getSelectionRotatedPageBounds(),
@@ -157,8 +157,10 @@ My browser: ${navigator.userAgent}`
157
157
  <h2>Are you sure?</h2>
158
158
  <p>Resetting your data will delete your drawing and cannot be undone.</p>
159
159
  <div className="tl-error-boundary__content__actions">
160
- <button onClick={() => setShouldShowResetConfirmation(false)}>Cancel</button>
161
- <button className="tl-error-boundary__reset" onClick={resetLocalState}>
160
+ <button className="tlui-button" onClick={() => setShouldShowResetConfirmation(false)}>
161
+ Cancel
162
+ </button>
163
+ <button className="tlui-button tl-error-boundary__reset" onClick={resetLocalState}>
162
164
  Reset data
163
165
  </button>
164
166
  </div>
@@ -166,16 +168,23 @@ My browser: ${navigator.userAgent}`
166
168
  ) : (
167
169
  <>
168
170
  <h2>Something went wrong</h2>
169
- <p>Please refresh the page to continue.</p>
171
+ <p>Please refresh your browser.</p>
172
+ <p>
173
+ If the issue continues after refreshing, you may need to reset the tldraw data stored
174
+ on your device.
175
+ </p>
170
176
  <p>
171
- If you keep seeing this screen, you can create a{' '}
172
- <a href={url.toString()}>GitHub issue</a> or ask for help on{' '}
173
- <a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
174
- Discord
175
- </a>
176
- . If you are still stuck, you can reset the tldraw data on your machine. This may
177
- erase the project you were working on, so try to get help first.
177
+ <strong>Note:</strong> Resetting will erase your current project and any unsaved work.
178
178
  </p>
179
+ {process.env.NODE_ENV !== 'production' && (
180
+ <p>
181
+ If you&apos;re developing with the SDK and need help, join us on{' '}
182
+ <a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
183
+ Discord
184
+ </a>
185
+ .
186
+ </p>
187
+ )}
179
188
  {shouldShowError && (
180
189
  <>
181
190
  Message:
@@ -187,22 +196,24 @@ My browser: ${navigator.userAgent}`
187
196
  <pre>
188
197
  <code>{errorStack ?? errorMessage}</code>
189
198
  </pre>
190
- <button onClick={copyError}>{didCopy ? 'Copied!' : 'Copy'}</button>
199
+ <button className="tlui-button" onClick={copyError}>
200
+ {didCopy ? 'Copied!' : 'Copy'}
201
+ </button>
191
202
  </div>
192
203
  </>
193
204
  )}
194
205
  <div className="tl-error-boundary__content__actions">
195
- <button onClick={() => setShouldShowError(!shouldShowError)}>
206
+ <button className="tlui-button" onClick={() => setShouldShowError(!shouldShowError)}>
196
207
  {shouldShowError ? 'Hide details' : 'Show details'}
197
208
  </button>
198
209
  <div className="tl-error-boundary__content__actions__group">
199
210
  <button
200
- className="tl-error-boundary__reset"
211
+ className="tlui-button tl-error-boundary__reset"
201
212
  onClick={() => setShouldShowResetConfirmation(true)}
202
213
  >
203
214
  Reset data
204
215
  </button>
205
- <button className="tl-error-boundary__refresh" onClick={refresh}>
216
+ <button className="tlui-button tl-error-boundary__refresh" onClick={refresh}>
206
217
  Refresh Page
207
218
  </button>
208
219
  </div>
@@ -9,13 +9,21 @@ import { useEditorComponents } from '../../hooks/useEditorComponents'
9
9
  import { OptionalErrorBoundary } from '../ErrorBoundary'
10
10
 
11
11
  // need an extra layer of indirection here to allow hooks to be used inside the indicator render
12
- const EvenInnererIndicator = memo(({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
13
- return useStateTracking('Indicator: ' + shape.type, () =>
14
- // always fetch the latest shape from the store even if the props/meta have not changed, to avoid
15
- // calling the render method with stale data.
16
- util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
17
- )
18
- })
12
+ const EvenInnererIndicator = memo(
13
+ ({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
14
+ return useStateTracking('Indicator: ' + shape.type, () =>
15
+ // always fetch the latest shape from the store even if the props/meta have not changed, to avoid
16
+ // calling the render method with stale data.
17
+ util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
18
+ )
19
+ },
20
+ (prevProps, nextProps) => {
21
+ return (
22
+ prevProps.shape.props === nextProps.shape.props &&
23
+ prevProps.shape.meta === nextProps.shape.meta
24
+ )
25
+ }
26
+ )
19
27
 
20
28
  const InnerIndicator = memo(({ editor, id }: { editor: Editor; id: TLShapeId }) => {
21
29
  const shape = useValue('shape for indicator', () => editor.store.get(id), [editor, id])
@@ -61,13 +69,14 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
61
69
  useQuickReactor(
62
70
  'indicator transform',
63
71
  () => {
72
+ if (hidden) return
64
73
  const elm = rIndicator.current
65
74
  if (!elm) return
66
75
  const pageTransform = editor.getShapePageTransform(shapeId)
67
76
  if (!pageTransform) return
68
77
  elm.style.setProperty('transform', pageTransform.toCssString())
69
78
  },
70
- [editor, shapeId]
79
+ [editor, shapeId, hidden]
71
80
  )
72
81
 
73
82
  useLayoutEffect(() => {
@@ -1,7 +1,7 @@
1
1
  /** @public @react */
2
2
  export function DefaultSpinner() {
3
3
  return (
4
- <svg width={16} height={16} viewBox="0 0 16 16">
4
+ <svg width={16} height={16} viewBox="0 0 16 16" aria-hidden="false">
5
5
  <g strokeWidth={2} fill="none" fillRule="evenodd">
6
6
  <circle strokeOpacity={0.25} cx={8} cy={8} r={7} stroke="currentColor" />
7
7
  <path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor">
@@ -52,7 +52,7 @@ beforeEach(() => {
52
52
  shapeUtils: [CustomShape],
53
53
  bindingUtils: [],
54
54
  tools: [],
55
- store: createTLStore({ shapeUtils: [CustomShape] }),
55
+ store: createTLStore({ shapeUtils: [CustomShape], bindingUtils: [] }),
56
56
  getContainer: () => document.body,
57
57
  })
58
58
  editor.setCameraOptions({ isLocked: true })
@@ -129,6 +129,7 @@ import { Group2d } from '../primitives/geometry/Group2d'
129
129
  import { intersectPolygonPolygon } from '../primitives/intersect'
130
130
  import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'
131
131
  import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
132
+ import { areShapesContentEqual } from '../utils/areShapesContentEqual'
132
133
  import { dataUrlToFile } from '../utils/assets'
133
134
  import { debugFlags } from '../utils/debug-flags'
134
135
  import {
@@ -325,7 +326,6 @@ export class Editor extends EventEmitter<TLEventMap> {
325
326
  this.options = { ...defaultTldrawOptions, ...options }
326
327
 
327
328
  this.store = store
328
- this.disposables.add(this.store.dispose.bind(this.store))
329
329
  this.history = new HistoryManager<TLRecord>({
330
330
  store,
331
331
  annotateError: (error) => {
@@ -955,6 +955,7 @@ export class Editor extends EventEmitter<TLEventMap> {
955
955
  dispose() {
956
956
  this.disposables.forEach((dispose) => dispose())
957
957
  this.disposables.clear()
958
+ this.store.dispose()
958
959
  this.isDisposed = true
959
960
  }
960
961
 
@@ -2275,13 +2276,21 @@ export class Editor extends EventEmitter<TLEventMap> {
2275
2276
  setEditingShape(shape: TLShapeId | TLShape | null): this {
2276
2277
  const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2277
2278
  this.setRichTextEditor(null)
2278
- if (id !== this.getEditingShapeId()) {
2279
+ const prevEditingShapeId = this.getEditingShapeId()
2280
+ if (id !== prevEditingShapeId) {
2279
2281
  if (id) {
2280
2282
  const shape = this.getShape(id)
2281
2283
  if (shape && this.getShapeUtil(shape).canEdit(shape)) {
2282
2284
  this.run(
2283
2285
  () => {
2284
2286
  this._updateCurrentPageState({ editingShapeId: id })
2287
+ if (prevEditingShapeId) {
2288
+ const prevEditingShape = this.getShape(prevEditingShapeId)
2289
+ if (prevEditingShape) {
2290
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2291
+ }
2292
+ }
2293
+ this.getShapeUtil(shape).onEditStart?.(shape)
2285
2294
  },
2286
2295
  { history: 'ignore' }
2287
2296
  )
@@ -2294,6 +2303,12 @@ export class Editor extends EventEmitter<TLEventMap> {
2294
2303
  () => {
2295
2304
  this._updateCurrentPageState({ editingShapeId: null })
2296
2305
  this._currentRichTextEditor.set(null)
2306
+ if (prevEditingShapeId) {
2307
+ const prevEditingShape = this.getShape(prevEditingShapeId)
2308
+ if (prevEditingShape) {
2309
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2310
+ }
2311
+ }
2297
2312
  },
2298
2313
  { history: 'ignore' }
2299
2314
  )
@@ -4574,7 +4589,7 @@ export class Editor extends EventEmitter<TLEventMap> {
4574
4589
  this.fonts.trackFontsForShape(shape)
4575
4590
  return this.getShapeUtil(shape).getGeometry(shape, opts)
4576
4591
  },
4577
- { areRecordsEqual: (a, b) => a.props === b.props }
4592
+ { areRecordsEqual: areShapesContentEqual }
4578
4593
  )
4579
4594
  }
4580
4595
  return this._shapeGeometryCaches[context].get(
@@ -4622,9 +4637,15 @@ export class Editor extends EventEmitter<TLEventMap> {
4622
4637
 
4623
4638
  /** @internal */
4624
4639
  @computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
4625
- return this.store.createComputedCache('handles', (shape) => {
4626
- return this.getShapeUtil(shape).getHandles?.(shape)
4627
- })
4640
+ return this.store.createComputedCache(
4641
+ 'handles',
4642
+ (shape) => {
4643
+ return this.getShapeUtil(shape).getHandles?.(shape)
4644
+ },
4645
+ {
4646
+ areRecordsEqual: areShapesContentEqual,
4647
+ }
4648
+ )
4628
4649
  }
4629
4650
 
4630
4651
  /**
@@ -5845,9 +5866,15 @@ export class Editor extends EventEmitter<TLEventMap> {
5845
5866
  @computed
5846
5867
  private _getBindingsIndexCache() {
5847
5868
  const index = bindingsIndex(this)
5848
- return this.store.createComputedCache<TLBinding[], TLShape>('bindingsIndex', (shape) => {
5849
- return index.get().get(shape.id)
5850
- })
5869
+ return this.store.createComputedCache<TLBinding[], TLShape>(
5870
+ 'bindingsIndex',
5871
+ (shape) => {
5872
+ return index.get().get(shape.id)
5873
+ },
5874
+ // we can ignore the shape equality check here because the index is
5875
+ // computed incrementally based on what bindings are in the store
5876
+ { areRecordsEqual: () => true }
5877
+ )
5851
5878
  }
5852
5879
 
5853
5880
  /**
@@ -10214,7 +10241,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10214
10241
 
10215
10242
  // If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
10216
10243
  // If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
10217
- if (inputs.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
10244
+ if (info.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
10218
10245
 
10219
10246
  switch (behavior) {
10220
10247
  case 'zoom': {
@@ -10330,12 +10357,10 @@ export class Editor extends EventEmitter<TLEventMap> {
10330
10357
  if (this.inputs.isPanning && this.inputs.isPointing) {
10331
10358
  // Handle spacebar / middle mouse button panning
10332
10359
  const { currentScreenPoint, previousScreenPoint } = this.inputs
10333
- const { panSpeed } = cameraOptions
10334
10360
  const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
10335
- this.setCamera(
10336
- new Vec(cx + (offset.x * panSpeed) / cz, cy + (offset.y * panSpeed) / cz, cz),
10337
- { immediate: true }
10338
- )
10361
+ this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
10362
+ immediate: true,
10363
+ })
10339
10364
  this.maybeTrackPerformance('Panning')
10340
10365
  return
10341
10366
  }