@tldraw/editor 3.14.0-canary.dbe17bbcfb3d → 3.14.0-canary.df0f81d497d2

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 (218) hide show
  1. package/dist-cjs/index.d.ts +114 -104
  2. package/dist-cjs/index.js +8 -8
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
  5. package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
  6. package/dist-cjs/lib/editor/Editor.js +75 -76
  7. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  8. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  9. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +22 -22
  10. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  11. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +16 -16
  12. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  13. package/dist-cjs/lib/editor/managers/{ClickManager.js → ClickManager/ClickManager.js} +1 -1
  14. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +7 -0
  15. package/dist-cjs/lib/editor/managers/{EdgeScrollManager.js → EdgeScrollManager/EdgeScrollManager.js} +2 -2
  16. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +7 -0
  17. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +7 -0
  18. package/dist-cjs/lib/editor/managers/{FontManager.js → FontManager/FontManager.js} +4 -1
  19. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +7 -0
  20. package/dist-cjs/lib/editor/managers/{HistoryManager.js → HistoryManager/HistoryManager.js} +64 -6
  21. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +7 -0
  22. package/dist-cjs/lib/editor/managers/{ScribbleManager.js → ScribbleManager/ScribbleManager.js} +1 -1
  23. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +7 -0
  24. package/dist-cjs/lib/editor/managers/{TextManager.js → TextManager/TextManager.js} +72 -42
  25. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +7 -0
  26. package/dist-cjs/lib/editor/managers/{TickManager.js → TickManager/TickManager.js} +1 -1
  27. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +7 -0
  28. package/dist-cjs/lib/editor/managers/{UserPreferencesManager.js → UserPreferencesManager/UserPreferencesManager.js} +1 -1
  29. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +7 -0
  30. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +1 -1
  31. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  32. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +1 -1
  33. package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
  34. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  35. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  36. package/dist-cjs/lib/exports/getSvgJsx.js.map +1 -1
  37. package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
  38. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  39. package/dist-cjs/lib/primitives/Box.js +33 -33
  40. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  41. package/dist-cjs/lib/primitives/Vec.js +13 -8
  42. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  43. package/dist-cjs/lib/primitives/geometry/Arc2d.js +41 -21
  44. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  45. package/dist-cjs/lib/primitives/geometry/Circle2d.js +11 -11
  46. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  47. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +13 -16
  48. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  49. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +4 -4
  50. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  51. package/dist-cjs/lib/primitives/geometry/Edge2d.js +14 -17
  52. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  53. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +10 -10
  54. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  55. package/dist-cjs/lib/primitives/geometry/Point2d.js +6 -6
  56. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  57. package/dist-cjs/lib/primitives/geometry/Polygon2d.js +3 -0
  58. package/dist-cjs/lib/primitives/geometry/Polygon2d.js.map +2 -2
  59. package/dist-cjs/lib/primitives/geometry/Polyline2d.js +8 -5
  60. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  61. package/dist-cjs/lib/primitives/geometry/Rectangle2d.js +22 -11
  62. package/dist-cjs/lib/primitives/geometry/Rectangle2d.js.map +2 -2
  63. package/dist-cjs/lib/primitives/geometry/Stadium2d.js +22 -22
  64. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  65. package/dist-cjs/lib/utils/reorderShapes.js +11 -10
  66. package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
  67. package/dist-cjs/lib/utils/richText.js +7 -2
  68. package/dist-cjs/lib/utils/richText.js.map +2 -2
  69. package/dist-cjs/version.js +3 -3
  70. package/dist-cjs/version.js.map +1 -1
  71. package/dist-esm/index.d.mts +114 -104
  72. package/dist-esm/index.mjs +12 -8
  73. package/dist-esm/index.mjs.map +2 -2
  74. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
  75. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  76. package/dist-esm/lib/editor/Editor.mjs +75 -76
  77. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  78. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  79. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +22 -22
  80. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  81. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +16 -16
  82. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  83. package/dist-esm/lib/editor/managers/{ClickManager.mjs → ClickManager/ClickManager.mjs} +1 -1
  84. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +7 -0
  85. package/dist-esm/lib/editor/managers/{EdgeScrollManager.mjs → EdgeScrollManager/EdgeScrollManager.mjs} +2 -2
  86. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +7 -0
  87. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +7 -0
  88. package/dist-esm/lib/editor/managers/{FontManager.mjs → FontManager/FontManager.mjs} +4 -1
  89. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +7 -0
  90. package/dist-esm/lib/editor/managers/{HistoryManager.mjs → HistoryManager/HistoryManager.mjs} +60 -2
  91. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +7 -0
  92. package/dist-esm/lib/editor/managers/{ScribbleManager.mjs → ScribbleManager/ScribbleManager.mjs} +1 -1
  93. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +7 -0
  94. package/dist-esm/lib/editor/managers/{TextManager.mjs → TextManager/TextManager.mjs} +72 -42
  95. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +7 -0
  96. package/dist-esm/lib/editor/managers/{TickManager.mjs → TickManager/TickManager.mjs} +1 -1
  97. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +7 -0
  98. package/dist-esm/lib/editor/managers/{UserPreferencesManager.mjs → UserPreferencesManager/UserPreferencesManager.mjs} +1 -1
  99. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +7 -0
  100. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +1 -1
  101. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  102. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
  103. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
  104. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  105. package/dist-esm/lib/exports/getSvgJsx.mjs.map +1 -1
  106. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
  107. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  108. package/dist-esm/lib/primitives/Box.mjs +33 -33
  109. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  110. package/dist-esm/lib/primitives/Vec.mjs +13 -8
  111. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  112. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +41 -21
  113. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  114. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +11 -11
  115. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  116. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +13 -16
  117. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  118. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +4 -4
  119. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  120. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +14 -17
  121. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  122. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +11 -11
  123. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  124. package/dist-esm/lib/primitives/geometry/Point2d.mjs +6 -6
  125. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  126. package/dist-esm/lib/primitives/geometry/Polygon2d.mjs +3 -0
  127. package/dist-esm/lib/primitives/geometry/Polygon2d.mjs.map +2 -2
  128. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +8 -5
  129. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  130. package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs +22 -11
  131. package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs.map +2 -2
  132. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +22 -22
  133. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  134. package/dist-esm/lib/utils/reorderShapes.mjs +11 -10
  135. package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
  136. package/dist-esm/lib/utils/richText.mjs +8 -3
  137. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  138. package/dist-esm/version.mjs +3 -3
  139. package/dist-esm/version.mjs.map +1 -1
  140. package/editor.css +433 -482
  141. package/package.json +8 -9
  142. package/src/index.ts +15 -7
  143. package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
  144. package/src/lib/editor/Editor.test.ts +252 -3
  145. package/src/lib/editor/Editor.ts +76 -74
  146. package/src/lib/editor/bindings/BindingUtil.ts +6 -0
  147. package/src/lib/editor/derivations/bindingsIndex.ts +27 -26
  148. package/src/lib/editor/derivations/parentsToChildren.ts +28 -25
  149. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +442 -0
  150. package/src/lib/editor/managers/{ClickManager.ts → ClickManager/ClickManager.ts} +3 -3
  151. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +374 -0
  152. package/src/lib/editor/managers/{EdgeScrollManager.ts → EdgeScrollManager/EdgeScrollManager.ts} +3 -3
  153. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +455 -0
  154. package/src/lib/editor/managers/{FocusManager.ts → FocusManager/FocusManager.ts} +1 -1
  155. package/src/lib/editor/managers/FontManager/FontManager.test.ts +263 -0
  156. package/src/lib/editor/managers/{FontManager.ts → FontManager/FontManager.ts} +5 -2
  157. package/src/lib/editor/managers/{HistoryManager.test.ts → HistoryManager/HistoryManager.test.ts} +388 -1
  158. package/src/lib/editor/managers/{HistoryManager.ts → HistoryManager/HistoryManager.ts} +73 -2
  159. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +624 -0
  160. package/src/lib/editor/managers/{ScribbleManager.ts → ScribbleManager/ScribbleManager.ts} +2 -2
  161. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +485 -0
  162. package/src/lib/editor/managers/TextManager/TextManager.test.ts +407 -0
  163. package/src/lib/editor/managers/{TextManager.ts → TextManager/TextManager.ts} +117 -87
  164. package/src/lib/editor/managers/TickManager/TickManager.test.ts +314 -0
  165. package/src/lib/editor/managers/{TickManager.ts → TickManager/TickManager.ts} +2 -2
  166. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +591 -0
  167. package/src/lib/editor/managers/{UserPreferencesManager.ts → UserPreferencesManager/UserPreferencesManager.ts} +2 -2
  168. package/src/lib/editor/shapes/ShapeUtil.ts +1 -1
  169. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  170. package/src/lib/editor/tools/StateNode.ts +3 -3
  171. package/src/lib/editor/types/external-content.ts +11 -2
  172. package/src/lib/exports/getSvgJsx.tsx +1 -1
  173. package/src/lib/hooks/useCanvasEvents.ts +0 -1
  174. package/src/lib/primitives/Box.test.ts +588 -7
  175. package/src/lib/primitives/Box.ts +33 -33
  176. package/src/lib/primitives/Vec.test.ts +2 -2
  177. package/src/lib/primitives/Vec.ts +13 -8
  178. package/src/lib/primitives/geometry/Arc2d.ts +42 -23
  179. package/src/lib/primitives/geometry/Circle2d.ts +12 -12
  180. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +5 -0
  181. package/src/lib/primitives/geometry/CubicBezier2d.ts +13 -17
  182. package/src/lib/primitives/geometry/CubicSpline2d.ts +5 -5
  183. package/src/lib/primitives/geometry/Edge2d.ts +14 -18
  184. package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
  185. package/src/lib/primitives/geometry/Point2d.ts +6 -6
  186. package/src/lib/primitives/geometry/Polygon2d.ts +4 -0
  187. package/src/lib/primitives/geometry/Polyline2d.ts +10 -7
  188. package/src/lib/primitives/geometry/Rectangle2d.ts +24 -11
  189. package/src/lib/primitives/geometry/Stadium2d.ts +22 -23
  190. package/src/lib/utils/reorderShapes.ts +10 -13
  191. package/src/lib/utils/richText.ts +10 -4
  192. package/src/version.ts +3 -3
  193. package/dist-cjs/lib/editor/managers/ClickManager.js.map +0 -7
  194. package/dist-cjs/lib/editor/managers/EdgeScrollManager.js.map +0 -7
  195. package/dist-cjs/lib/editor/managers/FocusManager.js.map +0 -7
  196. package/dist-cjs/lib/editor/managers/FontManager.js.map +0 -7
  197. package/dist-cjs/lib/editor/managers/HistoryManager.js.map +0 -7
  198. package/dist-cjs/lib/editor/managers/ScribbleManager.js.map +0 -7
  199. package/dist-cjs/lib/editor/managers/Stack.js +0 -82
  200. package/dist-cjs/lib/editor/managers/Stack.js.map +0 -7
  201. package/dist-cjs/lib/editor/managers/TextManager.js.map +0 -7
  202. package/dist-cjs/lib/editor/managers/TickManager.js.map +0 -7
  203. package/dist-cjs/lib/editor/managers/UserPreferencesManager.js.map +0 -7
  204. package/dist-esm/lib/editor/managers/ClickManager.mjs.map +0 -7
  205. package/dist-esm/lib/editor/managers/EdgeScrollManager.mjs.map +0 -7
  206. package/dist-esm/lib/editor/managers/FocusManager.mjs.map +0 -7
  207. package/dist-esm/lib/editor/managers/FontManager.mjs.map +0 -7
  208. package/dist-esm/lib/editor/managers/HistoryManager.mjs.map +0 -7
  209. package/dist-esm/lib/editor/managers/ScribbleManager.mjs.map +0 -7
  210. package/dist-esm/lib/editor/managers/Stack.mjs +0 -62
  211. package/dist-esm/lib/editor/managers/Stack.mjs.map +0 -7
  212. package/dist-esm/lib/editor/managers/TextManager.mjs.map +0 -7
  213. package/dist-esm/lib/editor/managers/TickManager.mjs.map +0 -7
  214. package/dist-esm/lib/editor/managers/UserPreferencesManager.mjs.map +0 -7
  215. package/src/lib/editor/managers/ScribbleManager.test.ts +0 -32
  216. package/src/lib/editor/managers/Stack.ts +0 -71
  217. /package/dist-cjs/lib/editor/managers/{FocusManager.js → FocusManager/FocusManager.js} +0 -0
  218. /package/dist-esm/lib/editor/managers/{FocusManager.mjs → FocusManager/FocusManager.mjs} +0 -0
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.14.0-canary.dbe17bbcfb3d",
4
+ "version": "3.14.0-canary.df0f81d497d2",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -48,20 +48,19 @@
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.14.0-canary.dbe17bbcfb3d",
52
- "@tldraw/state-react": "3.14.0-canary.dbe17bbcfb3d",
53
- "@tldraw/store": "3.14.0-canary.dbe17bbcfb3d",
54
- "@tldraw/tlschema": "3.14.0-canary.dbe17bbcfb3d",
55
- "@tldraw/utils": "3.14.0-canary.dbe17bbcfb3d",
56
- "@tldraw/validate": "3.14.0-canary.dbe17bbcfb3d",
51
+ "@tldraw/state": "3.14.0-canary.df0f81d497d2",
52
+ "@tldraw/state-react": "3.14.0-canary.df0f81d497d2",
53
+ "@tldraw/store": "3.14.0-canary.df0f81d497d2",
54
+ "@tldraw/tlschema": "3.14.0-canary.df0f81d497d2",
55
+ "@tldraw/utils": "3.14.0-canary.df0f81d497d2",
56
+ "@tldraw/validate": "3.14.0-canary.df0f81d497d2",
57
57
  "@types/core-js": "^2.5.8",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
60
60
  "core-js": "^3.40.0",
61
61
  "eventemitter3": "^4.0.7",
62
62
  "idb": "^7.1.1",
63
- "is-plain-object": "^5.0.0",
64
- "lodash.isequal": "^4.5.0"
63
+ "is-plain-object": "^5.0.0"
65
64
  },
66
65
  "peerDependencies": {
67
66
  "react": "^18.2.0 || ^19.0.0",
package/src/index.ts CHANGED
@@ -147,15 +147,18 @@ export {
147
147
  type BindingOnShapeIsolateOptions,
148
148
  type TLBindingUtilConstructor,
149
149
  } from './lib/editor/bindings/BindingUtil'
150
- export { ClickManager, type TLClickState } from './lib/editor/managers/ClickManager'
151
- export { EdgeScrollManager } from './lib/editor/managers/EdgeScrollManager'
150
+ export { ClickManager, type TLClickState } from './lib/editor/managers/ClickManager/ClickManager'
151
+ export { EdgeScrollManager } from './lib/editor/managers/EdgeScrollManager/EdgeScrollManager'
152
152
  export {
153
153
  FontManager,
154
154
  type TLFontFace,
155
155
  type TLFontFaceSource,
156
- } from './lib/editor/managers/FontManager'
157
- export { HistoryManager } from './lib/editor/managers/HistoryManager'
158
- export { ScribbleManager, type ScribbleItem } from './lib/editor/managers/ScribbleManager'
156
+ } from './lib/editor/managers/FontManager/FontManager'
157
+ export { HistoryManager } from './lib/editor/managers/HistoryManager/HistoryManager'
158
+ export {
159
+ ScribbleManager,
160
+ type ScribbleItem,
161
+ } from './lib/editor/managers/ScribbleManager/ScribbleManager'
159
162
  export {
160
163
  BoundsSnaps,
161
164
  type BoundsSnapGeometry,
@@ -169,8 +172,12 @@ export {
169
172
  type SnapData,
170
173
  type SnapIndicator,
171
174
  } from './lib/editor/managers/SnapManager/SnapManager'
172
- export { TextManager, type TLMeasureTextSpanOpts } from './lib/editor/managers/TextManager'
173
- export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager'
175
+ export {
176
+ TextManager,
177
+ type TLMeasureTextOpts,
178
+ type TLMeasureTextSpanOpts,
179
+ } from './lib/editor/managers/TextManager/TextManager'
180
+ export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
174
181
  export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseBoxShapeUtil'
175
182
  export {
176
183
  ShapeUtil,
@@ -246,6 +253,7 @@ export {
246
253
  type TLExternalContent,
247
254
  type TLExternalContentSource,
248
255
  type TLFileExternalAsset,
256
+ type TLFileReplaceExternalContent,
249
257
  type TLFilesExternalContent,
250
258
  type TLSvgTextExternalContent,
251
259
  type TLTextExternalContent,
@@ -14,12 +14,12 @@ import {
14
14
  import {
15
15
  deleteFromSessionStorage,
16
16
  getFromSessionStorage,
17
+ isEqual,
17
18
  setInSessionStorage,
18
19
  structuredClone,
19
20
  uniqueId,
20
21
  } from '@tldraw/utils'
21
22
  import { T } from '@tldraw/validate'
22
- import isEqual from 'lodash.isequal'
23
23
  import { tlenv } from '../globals/environment'
24
24
 
25
25
  const tabIdKey = 'TLDRAW_TAB_ID_v2' as const
@@ -17,6 +17,7 @@ type ICustomShape = TLBaseShape<
17
17
  w: number
18
18
  h: number
19
19
  text: string | undefined
20
+ isFilled: boolean
20
21
  }
21
22
  >
22
23
 
@@ -26,19 +27,21 @@ class CustomShape extends ShapeUtil<ICustomShape> {
26
27
  w: T.number,
27
28
  h: T.number,
28
29
  text: T.string.optional(),
30
+ isFilled: T.boolean,
29
31
  }
30
32
  getDefaultProps(): ICustomShape['props'] {
31
33
  return {
32
34
  w: 200,
33
35
  h: 200,
34
36
  text: '',
37
+ isFilled: false,
35
38
  }
36
39
  }
37
40
  getGeometry(shape: ICustomShape): Geometry2d {
38
41
  return new Rectangle2d({
39
42
  width: shape.props.w,
40
43
  height: shape.props.h,
41
- isFilled: true,
44
+ isFilled: shape.props.isFilled,
42
45
  })
43
46
  }
44
47
  indicator() {}
@@ -81,11 +84,11 @@ describe('updateShape', () => {
81
84
  props: { w: 100, h: 100, text: 'Hello' },
82
85
  })
83
86
  const shape = editor.getShape(id) as ICustomShape
84
- expect(shape.props).toEqual({ w: 100, h: 100, text: 'Hello' })
87
+ expect(shape.props).toEqual({ w: 100, h: 100, text: 'Hello', isFilled: false })
85
88
 
86
89
  editor.updateShape({ ...shape, props: { ...shape.props, text: undefined } })
87
90
  const updatedShape = editor.getShape(id) as ICustomShape
88
- expect(updatedShape.props).toEqual({ w: 100, h: 100, text: undefined })
91
+ expect(updatedShape.props).toEqual({ w: 100, h: 100, text: undefined, isFilled: false })
89
92
  })
90
93
  })
91
94
 
@@ -176,3 +179,249 @@ describe('zoomToBounds', () => {
176
179
  expect(editor.setCamera).toHaveBeenCalled()
177
180
  })
178
181
  })
182
+
183
+ describe('getShapesAtPoint', () => {
184
+ const ids = {
185
+ shape1: createShapeId('shape1'),
186
+ shape2: createShapeId('shape2'),
187
+ shape3: createShapeId('shape3'),
188
+ shape4: createShapeId('shape4'),
189
+ shape5: createShapeId('shape5'),
190
+ overlap1: createShapeId('overlap1'),
191
+ overlap2: createShapeId('overlap2'),
192
+ filledShape: createShapeId('filledShape'),
193
+ hollowShape: createShapeId('hollowShape'),
194
+ hiddenShape: createShapeId('hiddenShape'),
195
+ }
196
+
197
+ beforeEach(() => {
198
+ // Create test shapes with different z-index positions
199
+ // Shape 1: Bottom layer, large square
200
+ editor.createShape({
201
+ id: ids.shape1,
202
+ type: 'my-custom-shape',
203
+ x: 0,
204
+ y: 0,
205
+ props: { w: 200, h: 200, text: 'Bottom' },
206
+ })
207
+
208
+ // Shape 2: Middle layer, overlapping square
209
+ editor.createShape({
210
+ id: ids.shape2,
211
+ type: 'my-custom-shape',
212
+ x: 100,
213
+ y: 0,
214
+ props: { w: 200, h: 200, text: 'Middle' },
215
+ })
216
+
217
+ // Shape 3: Top layer, small square
218
+ editor.createShape({
219
+ id: ids.shape3,
220
+ type: 'my-custom-shape',
221
+ x: 50,
222
+ y: 50,
223
+ props: { w: 100, h: 100, text: 'Top' },
224
+ })
225
+
226
+ // Shape 4: Separate area, no overlap
227
+ editor.createShape({
228
+ id: ids.shape4,
229
+ type: 'my-custom-shape',
230
+ x: 50,
231
+ y: 100,
232
+ props: { w: 100, h: 100, text: 'Separate' },
233
+ })
234
+ })
235
+
236
+ it('returns shapes at a point in reverse z-index order', () => {
237
+ // Point at (50, 50) should hit shape3's edge (since it's at 50,50 with size 100x100)
238
+ // This point is exactly at the top-left corner of shape3
239
+ const shapes = editor.getShapesAtPoint({ x: 50, y: 50 })
240
+ const shapeIds = shapes.map((s) => s.id)
241
+
242
+ expect(shapeIds).toEqual([ids.shape3])
243
+ expect(shapes).toHaveLength(1)
244
+ })
245
+
246
+ it('returns empty array when no shapes at point', () => {
247
+ const shapes = editor.getShapesAtPoint({ x: 1000, y: 1000 })
248
+ expect(shapes).toEqual([])
249
+ })
250
+
251
+ it('returns single shape when point hits only one shape', () => {
252
+ // Point at right edge of shape2 where it doesn't overlap with other shapes
253
+ // Shape2 is at (100,0) with size 200x200, so right edge is at x=300
254
+ const shapes = editor.getShapesAtPoint({ x: 300, y: 100 })
255
+ expect(shapes).toHaveLength(1)
256
+ expect(shapes[0].id).toBe(ids.shape2)
257
+ })
258
+
259
+ it('returns shapes on edge when point is exactly on boundary', () => {
260
+ // Point at exact edge of shape1
261
+ const shapes = editor.getShapesAtPoint({ x: 0, y: 0 })
262
+ expect(shapes).toHaveLength(1)
263
+ expect(shapes[0].id).toBe(ids.shape1)
264
+ })
265
+
266
+ it('respects hitInside option when false (default)', () => {
267
+ // Point inside shape1 (at 0,0 with size 200x200) but with hitInside false should not hit
268
+ const shapes = editor.getShapesAtPoint({ x: 25, y: 25 }, { hitInside: false })
269
+ expect(shapes).toEqual([])
270
+ })
271
+
272
+ it('respects hitInside option when true', () => {
273
+ // Point inside shape1 (at 0,0 with size 200x200) with hitInside true should hit
274
+ const shapes = editor.getShapesAtPoint({ x: 25, y: 25 }, { hitInside: true })
275
+ expect(shapes).toHaveLength(1)
276
+ expect(shapes[0].id).toBe(ids.shape1)
277
+ })
278
+
279
+ it('respects margin option', () => {
280
+ // Point slightly outside shape1 at bottom edge but within margin should hit only shape1
281
+ // Shape1 is at (0,0) with size 200x200, shape2 goes to (300,200) so avoid overlap at (200,200)
282
+ const shapes = editor.getShapesAtPoint({ x: 205, y: 100 }, { margin: 10 })
283
+ expect(shapes).toHaveLength(1)
284
+ expect(shapes[0].id).toBe(ids.shape1)
285
+ })
286
+
287
+ it('filters out hidden shapes', () => {
288
+ // Create a spy to mock isShapeHidden
289
+ const isShapeHiddenSpy = jest.spyOn(editor, 'isShapeHidden')
290
+ isShapeHiddenSpy.mockImplementation((shape) => {
291
+ return typeof shape === 'string' ? shape === ids.shape3 : shape.id === ids.shape3
292
+ })
293
+
294
+ const shapes = editor.getShapesAtPoint({ x: 50, y: 50 })
295
+ const shapeIds = shapes.map((s) => s.id)
296
+
297
+ // Should not include shape3 since it's hidden, and no other shapes are at this point
298
+ expect(shapeIds).toEqual([])
299
+ expect(shapes).toHaveLength(0)
300
+
301
+ isShapeHiddenSpy.mockRestore()
302
+ })
303
+
304
+ it('handles point exactly at shape corner', () => {
305
+ // Point at bottom-left corner of shape1 where it doesn't overlap with other shapes
306
+ const shapes = editor.getShapesAtPoint({ x: 0, y: 200 })
307
+ expect(shapes).toHaveLength(1)
308
+ expect(shapes[0].id).toBe(ids.shape1)
309
+ })
310
+
311
+ it('handles overlapping shapes with different hit areas', () => {
312
+ // Point that hits both shape1 and shape2 edges (they overlap at x=100,y=0)
313
+ const shapes = editor.getShapesAtPoint({ x: 100, y: 0 })
314
+ const shapeIds = shapes.map((s) => s.id)
315
+
316
+ // Both shapes should be detected at this overlapping point (reversed order - top-most first)
317
+ expect(shapeIds).toEqual([ids.shape2, ids.shape1])
318
+ expect(shapes).toHaveLength(2)
319
+ })
320
+
321
+ it('maintains reverse shape order and responds to z-index changes', () => {
322
+ // Create filled shape that overlaps with shape2
323
+ editor.createShape({
324
+ id: ids.shape5,
325
+ type: 'my-custom-shape',
326
+ x: 110,
327
+ y: 110,
328
+ props: { w: 200, h: 200, isFilled: true, text: 'Shape5' },
329
+ })
330
+
331
+ // Test with hitInside to detect multiple shapes
332
+ // Point (120,120) will hit shape1, shape2, shape3, shape4, and shape5 with hitInside: true
333
+ const shapes = editor.getShapesAtPoint({ x: 120, y: 120 }, { hitInside: true })
334
+ const shapeIds = shapes.map((s) => s.id)
335
+
336
+ // All shapes that contain this point should be returned in reverse z-index order (top-most first)
337
+ expect(shapeIds).toEqual([ids.shape5, ids.shape4, ids.shape3, ids.shape2, ids.shape1])
338
+
339
+ // After bringing shape2 to front, order should change (shape2 becomes top-most)
340
+ editor.bringToFront([ids.shape2])
341
+ const shapes2 = editor.getShapesAtPoint({ x: 120, y: 120 }, { hitInside: true })
342
+ const shapeIds2 = shapes2.map((s) => s.id)
343
+ expect(shapeIds2).toEqual([ids.shape2, ids.shape5, ids.shape4, ids.shape3, ids.shape1])
344
+ })
345
+
346
+ it('combines hitInside and margin options', () => {
347
+ // Point inside shape1 (at 0,0 with size 200x200) with hitInside and margin
348
+ const shapes = editor.getShapesAtPoint({ x: 25, y: 25 }, { hitInside: true, margin: 5 })
349
+ expect(shapes).toHaveLength(1)
350
+ expect(shapes[0].id).toBe(ids.shape1)
351
+ })
352
+
353
+ it('returns empty array when all shapes are hidden', () => {
354
+ // Mock all shapes as hidden
355
+ const isShapeHiddenSpy = jest.spyOn(editor, 'isShapeHidden')
356
+ isShapeHiddenSpy.mockReturnValue(true)
357
+
358
+ const shapes = editor.getShapesAtPoint({ x: 50, y: 50 })
359
+ expect(shapes).toEqual([])
360
+
361
+ isShapeHiddenSpy.mockRestore()
362
+ })
363
+
364
+ it('returns multiple shapes at same point in reverse z-index order', () => {
365
+ // Create two shapes at exactly the same position (away from existing shapes)
366
+ editor.createShape({
367
+ id: ids.overlap1,
368
+ type: 'my-custom-shape',
369
+ x: 600,
370
+ y: 600,
371
+ props: { w: 100, h: 100, text: 'First' },
372
+ })
373
+
374
+ editor.createShape({
375
+ id: ids.overlap2,
376
+ type: 'my-custom-shape',
377
+ x: 600,
378
+ y: 600,
379
+ props: { w: 100, h: 100, text: 'Second' },
380
+ })
381
+
382
+ // Test at corner where both shapes' edges meet
383
+ const shapes = editor.getShapesAtPoint({ x: 600, y: 600 })
384
+ const shapeIds = shapes.map((s) => s.id)
385
+
386
+ // Should return both shapes in reverse z-index order (top-most first)
387
+ expect(shapeIds).toEqual([ids.overlap2, ids.overlap1])
388
+ expect(shapes).toHaveLength(2)
389
+ })
390
+
391
+ it('respects isFilled property for hit detection', () => {
392
+ // Create a filled shape
393
+ editor.createShape({
394
+ id: ids.filledShape,
395
+ type: 'my-custom-shape',
396
+ x: 300,
397
+ y: 300,
398
+ props: { w: 100, h: 100, isFilled: true, text: 'Filled' },
399
+ })
400
+
401
+ // Create a hollow shape at the same position
402
+ editor.createShape({
403
+ id: ids.hollowShape,
404
+ type: 'my-custom-shape',
405
+ x: 400,
406
+ y: 300,
407
+ props: { w: 100, h: 100, isFilled: false, text: 'Hollow' },
408
+ })
409
+
410
+ // Test point inside filled shape - should hit without hitInside option
411
+ const filledShapes = editor.getShapesAtPoint({ x: 350, y: 350 })
412
+ expect(filledShapes).toHaveLength(1)
413
+ expect(filledShapes[0].id).toBe(ids.filledShape)
414
+
415
+ // Test point inside hollow shape - should not hit without hitInside option
416
+ const hollowShapes = editor.getShapesAtPoint({ x: 450, y: 350 })
417
+ expect(hollowShapes).toHaveLength(0)
418
+
419
+ // Test point inside hollow shape with hitInside - should hit
420
+ const hollowShapesWithHitInside = editor.getShapesAtPoint(
421
+ { x: 450, y: 350 },
422
+ { hitInside: true }
423
+ )
424
+ expect(hollowShapesWithHitInside).toHaveLength(1)
425
+ expect(hollowShapesWithHitInside[0].id).toBe(ids.hollowShape)
426
+ })
427
+ })
@@ -148,16 +148,16 @@ import { bindingsIndex } from './derivations/bindingsIndex'
148
148
  import { notVisibleShapes } from './derivations/notVisibleShapes'
149
149
  import { parentsToChildren } from './derivations/parentsToChildren'
150
150
  import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage'
151
- import { ClickManager } from './managers/ClickManager'
152
- import { EdgeScrollManager } from './managers/EdgeScrollManager'
153
- import { FocusManager } from './managers/FocusManager'
154
- import { FontManager } from './managers/FontManager'
155
- import { HistoryManager } from './managers/HistoryManager'
156
- import { ScribbleManager } from './managers/ScribbleManager'
151
+ import { ClickManager } from './managers/ClickManager/ClickManager'
152
+ import { EdgeScrollManager } from './managers/EdgeScrollManager/EdgeScrollManager'
153
+ import { FocusManager } from './managers/FocusManager/FocusManager'
154
+ import { FontManager } from './managers/FontManager/FontManager'
155
+ import { HistoryManager } from './managers/HistoryManager/HistoryManager'
156
+ import { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'
157
157
  import { SnapManager } from './managers/SnapManager/SnapManager'
158
- import { TextManager } from './managers/TextManager'
159
- import { TickManager } from './managers/TickManager'
160
- import { UserPreferencesManager } from './managers/UserPreferencesManager'
158
+ import { TextManager } from './managers/TextManager/TextManager'
159
+ import { TickManager } from './managers/TickManager/TickManager'
160
+ import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
161
161
  import { ShapeUtil, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
162
162
  import { RootState } from './tools/RootState'
163
163
  import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
@@ -328,7 +328,7 @@ export class Editor extends EventEmitter<TLEventMap> {
328
328
  this.store = store
329
329
  this.history = new HistoryManager<TLRecord>({
330
330
  store,
331
- annotateError: (error) => {
331
+ annotateError: (error: any) => {
332
332
  this.annotateError(error, { origin: 'history.batch', willCrashApp: true })
333
333
  this.crash(error)
334
334
  },
@@ -348,6 +348,8 @@ export class Editor extends EventEmitter<TLEventMap> {
348
348
  this.getContainer = getContainer
349
349
 
350
350
  this.textMeasure = new TextManager(this)
351
+ this.disposables.add(() => this.textMeasure.dispose())
352
+
351
353
  this.fonts = new FontManager(this, fontAssetUrls)
352
354
 
353
355
  this._tickManager = new TickManager(this)
@@ -512,6 +514,7 @@ export class Editor extends EventEmitter<TLEventMap> {
512
514
  binding,
513
515
  shapeBefore,
514
516
  shapeAfter,
517
+ reason: 'self',
515
518
  })
516
519
  }
517
520
  if (binding.toId === shapeAfter.id) {
@@ -519,6 +522,7 @@ export class Editor extends EventEmitter<TLEventMap> {
519
522
  binding,
520
523
  shapeBefore,
521
524
  shapeAfter,
525
+ reason: 'self',
522
526
  })
523
527
  }
524
528
  }
@@ -537,6 +541,7 @@ export class Editor extends EventEmitter<TLEventMap> {
537
541
  binding,
538
542
  shapeBefore: descendantShape,
539
543
  shapeAfter: descendantShape,
544
+ reason: 'ancestry',
540
545
  })
541
546
  }
542
547
  if (binding.toId === descendantShape.id) {
@@ -544,6 +549,7 @@ export class Editor extends EventEmitter<TLEventMap> {
544
549
  binding,
545
550
  shapeBefore: descendantShape,
546
551
  shapeAfter: descendantShape,
552
+ reason: 'ancestry',
547
553
  })
548
554
  }
549
555
  }
@@ -2116,6 +2122,20 @@ export class Editor extends EventEmitter<TLEventMap> {
2116
2122
  return this.getShapesPageBounds(this.getSelectedShapeIds())
2117
2123
  }
2118
2124
 
2125
+ /**
2126
+ * The bounds of the selection bounding box in the current page space.
2127
+ *
2128
+ * @readonly
2129
+ * @public
2130
+ */
2131
+ getSelectionScreenBounds(): Box | undefined {
2132
+ const bounds = this.getSelectionPageBounds()
2133
+ if (!bounds) return undefined
2134
+ const { x, y } = this.pageToScreen(bounds.point)
2135
+ const zoom = this.getZoomLevel()
2136
+ return new Box(x, y, bounds.width * zoom, bounds.height * zoom)
2137
+ }
2138
+
2119
2139
  /**
2120
2140
  * @internal
2121
2141
  */
@@ -3715,10 +3735,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3715
3735
  */
3716
3736
  @computed getViewportScreenCenter() {
3717
3737
  const viewportScreenBounds = this.getViewportScreenBounds()
3718
- return new Vec(
3719
- viewportScreenBounds.midX - viewportScreenBounds.minX,
3720
- viewportScreenBounds.midY - viewportScreenBounds.minY
3721
- )
3738
+ return new Vec(viewportScreenBounds.w / 2, viewportScreenBounds.h / 2)
3722
3739
  }
3723
3740
 
3724
3741
  /**
@@ -4644,44 +4661,6 @@ export class Editor extends EventEmitter<TLEventMap> {
4644
4661
  )! as T
4645
4662
  }
4646
4663
 
4647
- private _shapePageGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
4648
-
4649
- /**
4650
- * Get the geometry of a shape in page-space.
4651
- *
4652
- * @example
4653
- * ```ts
4654
- * editor.getShapePageGeometry(myShape)
4655
- * editor.getShapePageGeometry(myShapeId)
4656
- * editor.getShapePageGeometry(myShapeId, { context: "arrow" })
4657
- * ```
4658
- *
4659
- * @param shape - The shape (or shape id) to get the geometry for.
4660
- * @param opts - Additional options about the request for geometry. Passed to {@link ShapeUtil.getGeometry}.
4661
- *
4662
- * @public
4663
- */
4664
- getShapePageGeometry<T extends Geometry2d>(shape: TLShape | TLShapeId, opts?: TLGeometryOpts): T {
4665
- const context = opts?.context ?? 'none'
4666
- if (!this._shapePageGeometryCaches[context]) {
4667
- this._shapePageGeometryCaches[context] = this.store.createComputedCache(
4668
- 'bounds',
4669
- (shape) => {
4670
- const geometry = this.getShapeGeometry(shape.id, opts)
4671
- const pageTransform = this.getShapePageTransform(shape.id)
4672
- return geometry.transform(pageTransform)
4673
- },
4674
- {
4675
- // we only depend directly on the shape id, and changing geometry/transform will update us anyway
4676
- areRecordsEqual: () => true,
4677
- }
4678
- )
4679
- }
4680
- return this._shapePageGeometryCaches[context].get(
4681
- typeof shape === 'string' ? shape : shape.id
4682
- )! as T
4683
- }
4684
-
4685
4664
  /** @internal */
4686
4665
  @computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
4687
4666
  return this.store.createComputedCache(
@@ -4794,7 +4773,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4794
4773
  /** @internal */
4795
4774
  @computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
4796
4775
  return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
4797
- return this.getShapePageGeometry(shape).bounds
4776
+ const pageTransform = this.getShapePageTransform(shape)
4777
+ if (!pageTransform) return undefined
4778
+ const geometry = this.getShapeGeometry(shape)
4779
+ return Box.FromPoints(pageTransform.applyToPoints(geometry.vertices))
4798
4780
  })
4799
4781
  }
4800
4782
 
@@ -4868,11 +4850,12 @@ export class Editor extends EventEmitter<TLEventMap> {
4868
4850
  if (frameAncestors.length === 0) return undefined
4869
4851
 
4870
4852
  const pageMask = frameAncestors
4871
- .map<Vec[] | undefined>(
4872
- (s) =>
4873
- // Apply the frame transform to the frame outline to get the frame outline in the current page space
4874
- this.getShapePageGeometry(s.id).vertices
4875
- )
4853
+ .map<Vec[] | undefined>((s) => {
4854
+ // Apply the frame transform to the frame outline to get the frame outline in the current page space
4855
+ const geometry = this.getShapeGeometry(s.id)
4856
+ const pageTransform = this.getShapePageTransform(s.id)
4857
+ return pageTransform.applyToPoints(geometry.vertices)
4858
+ })
4876
4859
  .reduce((acc, b) => {
4877
4860
  if (!(b && acc)) return undefined
4878
4861
  const intersection = intersectPolygonPolygon(acc, b)
@@ -5070,28 +5053,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5070
5053
  *
5071
5054
  * @public
5072
5055
  */
5073
- isShapeOrAncestorLocked(shape?: TLShape): boolean
5074
- isShapeOrAncestorLocked(id?: TLShapeId): boolean
5075
- isShapeOrAncestorLocked(arg?: TLShape | TLShapeId): boolean {
5076
- const shape = typeof arg === 'string' ? this.getShape(arg) : arg
5077
- if (shape === undefined) return false
5078
- if (shape.isLocked) return true
5079
- return this.isShapeOrAncestorLocked(this.getShapeParent(shape))
5056
+ isShapeOrAncestorLocked(shape?: TLShape | TLShapeId): boolean {
5057
+ const _shape = shape && this.getShape(shape)
5058
+ if (_shape === undefined) return false
5059
+ if (_shape.isLocked) return true
5060
+ return this.isShapeOrAncestorLocked(this.getShapeParent(_shape))
5080
5061
  }
5081
5062
 
5063
+ /**
5064
+ * Get shapes that are outside of the viewport.
5065
+ *
5066
+ * @public
5067
+ */
5082
5068
  @computed
5083
- private _notVisibleShapes() {
5084
- return notVisibleShapes(this)
5069
+ getNotVisibleShapes() {
5070
+ return this._notVisibleShapes.get()
5085
5071
  }
5086
5072
 
5073
+ private _notVisibleShapes = notVisibleShapes(this)
5074
+
5087
5075
  /**
5088
- * Get culled shapes.
5076
+ * Get culled shapes (those that should not render), taking into account which shapes are selected or editing.
5089
5077
  *
5090
5078
  * @public
5091
5079
  */
5092
5080
  @computed
5093
5081
  getCulledShapes() {
5094
- const notVisibleShapes = this._notVisibleShapes().get()
5082
+ const notVisibleShapes = this.getNotVisibleShapes()
5095
5083
  const selectedShapeIds = this.getSelectedShapeIds()
5096
5084
  const editingId = this.getEditingShapeId()
5097
5085
  const culledShapes = new Set<TLShapeId>(notVisibleShapes)
@@ -5340,21 +5328,23 @@ export class Editor extends EventEmitter<TLEventMap> {
5340
5328
  * @example
5341
5329
  * ```ts
5342
5330
  * editor.getShapesAtPoint({ x: 100, y: 100 })
5343
- * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, exact: true })
5331
+ * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, margin: 8 })
5344
5332
  * ```
5345
5333
  *
5346
5334
  * @param point - The page point to test.
5347
5335
  * @param opts - The options for the hit point testing.
5348
5336
  *
5337
+ * @returns An array of shapes at the given point, sorted in reverse order of their absolute z-index (top-most shape first).
5338
+ *
5349
5339
  * @public
5350
5340
  */
5351
5341
  getShapesAtPoint(
5352
5342
  point: VecLike,
5353
5343
  opts = {} as { margin?: number; hitInside?: boolean }
5354
5344
  ): TLShape[] {
5355
- return this.getCurrentPageShapes().filter(
5356
- (shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts)
5357
- )
5345
+ return this.getCurrentPageShapesSorted()
5346
+ .filter((shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts))
5347
+ .reverse()
5358
5348
  }
5359
5349
 
5360
5350
  /**
@@ -6221,11 +6211,12 @@ export class Editor extends EventEmitter<TLEventMap> {
6221
6211
  */
6222
6212
  duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
6223
6213
  this.run(() => {
6224
- const ids =
6214
+ const _ids =
6225
6215
  typeof shapes[0] === 'string'
6226
6216
  ? (shapes as TLShapeId[])
6227
6217
  : (shapes as TLShape[]).map((s) => s.id)
6228
6218
 
6219
+ const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
6229
6220
  if (ids.length <= 0) return this
6230
6221
 
6231
6222
  const initialIds = new Set(ids)
@@ -8837,6 +8828,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8837
8828
  } = {
8838
8829
  text: null,
8839
8830
  files: null,
8831
+ 'file-replace': null,
8840
8832
  embed: null,
8841
8833
  'svg-text': null,
8842
8834
  url: null,
@@ -8886,6 +8878,15 @@ export class Editor extends EventEmitter<TLEventMap> {
8886
8878
  return this.externalContentHandlers[info.type]?.(info as any)
8887
8879
  }
8888
8880
 
8881
+ /**
8882
+ * Handle replacing external content.
8883
+ *
8884
+ * @param info - Info about the external content.
8885
+ */
8886
+ async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8887
+ return this.externalContentHandlers[info.type]?.(info as any)
8888
+ }
8889
+
8889
8890
  /**
8890
8891
  * Get content that can be exported for the given shape ids.
8891
8892
  *
@@ -9303,6 +9304,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9303
9304
  if (rootShapes.length === 1) {
9304
9305
  const onlyRoot = rootShapes[0] as TLFrameShape
9305
9306
  // If the old bounds are in the viewport...
9307
+ // todo: replace frame references with shapes that can accept children
9306
9308
  if (this.isShapeOfType<TLFrameShape>(onlyRoot, 'frame')) {
9307
9309
  while (
9308
9310
  this.getShapesAtPoint(point).some(