@tldraw/editor 5.4.0-next.ef99ab47e5ce → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dist-cjs/index.d.ts +114 -47
  2. package/dist-cjs/index.js +4 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +7 -2
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
  9. package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
  12. package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
  13. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  14. package/dist-cjs/lib/config/createTLStore.js +1 -1
  15. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  16. package/dist-cjs/lib/editor/Editor.js +181 -294
  17. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  18. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +7 -3
  19. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
  21. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
  23. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  24. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
  25. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
  27. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
  29. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  30. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
  31. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  32. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
  33. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  34. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
  35. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  36. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
  37. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  38. package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
  39. package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
  40. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
  41. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
  42. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
  43. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
  44. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
  45. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  46. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
  47. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  48. package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
  49. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  50. package/dist-cjs/lib/globals/editors.js +57 -0
  51. package/dist-cjs/lib/globals/editors.js.map +7 -0
  52. package/dist-cjs/lib/globals/menus.js +9 -4
  53. package/dist-cjs/lib/globals/menus.js.map +2 -2
  54. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
  55. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  56. package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
  57. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  58. package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
  59. package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
  60. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
  61. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  62. package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
  63. package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
  64. package/dist-cjs/lib/license/LicenseManager.js +9 -2
  65. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  66. package/dist-cjs/lib/license/LicenseProvider.js +16 -3
  67. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  68. package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
  69. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  70. package/dist-cjs/lib/primitives/Box.js +2 -1
  71. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  72. package/dist-cjs/lib/primitives/Vec.js +4 -3
  73. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  74. package/dist-cjs/lib/primitives/geometry/Group2d.js +63 -21
  75. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  76. package/dist-cjs/lib/primitives/utils.js +2 -1
  77. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  78. package/dist-cjs/lib/utils/deepLinks.js +5 -1
  79. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  80. package/dist-cjs/lib/utils/normalizeWheel.js +2 -0
  81. package/dist-cjs/lib/utils/normalizeWheel.js.map +2 -2
  82. package/dist-cjs/lib/utils/rotation.js +1 -9
  83. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  84. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
  85. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  86. package/dist-cjs/lib/utils/sync/alerts.js +1 -1
  87. package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
  88. package/dist-cjs/version.js +3 -3
  89. package/dist-cjs/version.js.map +1 -1
  90. package/dist-esm/index.d.mts +114 -47
  91. package/dist-esm/index.mjs +9 -2
  92. package/dist-esm/index.mjs.map +2 -2
  93. package/dist-esm/lib/TldrawEditor.mjs +9 -4
  94. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  95. package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
  96. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  97. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
  98. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
  99. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  100. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
  101. package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
  102. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  103. package/dist-esm/lib/config/createTLStore.mjs +1 -1
  104. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  105. package/dist-esm/lib/editor/Editor.mjs +189 -295
  106. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  107. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +7 -3
  108. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
  109. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
  110. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  111. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
  112. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  113. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
  114. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  115. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
  116. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  117. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
  118. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  119. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
  120. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  121. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
  122. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  123. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
  124. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  125. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
  126. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  127. package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
  128. package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
  129. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
  130. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
  131. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
  132. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
  133. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
  134. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  135. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
  136. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  137. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
  138. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  139. package/dist-esm/lib/globals/editors.mjs +37 -0
  140. package/dist-esm/lib/globals/editors.mjs.map +7 -0
  141. package/dist-esm/lib/globals/menus.mjs +9 -4
  142. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  143. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
  144. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  145. package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
  146. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  147. package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
  148. package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
  149. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
  150. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  151. package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
  152. package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
  153. package/dist-esm/lib/license/LicenseManager.mjs +9 -2
  154. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  155. package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
  156. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  157. package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
  158. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  159. package/dist-esm/lib/primitives/Box.mjs +2 -1
  160. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  161. package/dist-esm/lib/primitives/Vec.mjs +4 -3
  162. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  163. package/dist-esm/lib/primitives/geometry/Group2d.mjs +63 -21
  164. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  165. package/dist-esm/lib/primitives/utils.mjs +2 -1
  166. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  167. package/dist-esm/lib/utils/deepLinks.mjs +5 -1
  168. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  169. package/dist-esm/lib/utils/normalizeWheel.mjs +2 -0
  170. package/dist-esm/lib/utils/normalizeWheel.mjs.map +2 -2
  171. package/dist-esm/lib/utils/rotation.mjs +1 -9
  172. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  173. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
  174. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  175. package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
  176. package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
  177. package/dist-esm/version.mjs +3 -3
  178. package/dist-esm/version.mjs.map +1 -1
  179. package/package.json +13 -7
  180. package/src/index.ts +6 -1
  181. package/src/lib/TldrawEditor.tsx +16 -4
  182. package/src/lib/components/MenuClickCapture.tsx +19 -18
  183. package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
  184. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  185. package/src/lib/config/TLUserPreferences.test.ts +18 -1
  186. package/src/lib/config/TLUserPreferences.ts +8 -4
  187. package/src/lib/config/createTLStore.ts +1 -1
  188. package/src/lib/editor/Editor.ts +266 -383
  189. package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +7 -3
  190. package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
  191. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
  192. package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
  193. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
  194. package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
  195. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
  196. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
  197. package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
  198. package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
  199. package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
  200. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
  201. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
  202. package/src/lib/editor/overlays/OverlayManager.ts +3 -7
  203. package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
  204. package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
  205. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
  206. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
  207. package/src/lib/editor/tools/StateNode.test.ts +64 -0
  208. package/src/lib/editor/tools/StateNode.ts +12 -2
  209. package/src/lib/globals/editors.test.ts +76 -0
  210. package/src/lib/globals/editors.ts +53 -0
  211. package/src/lib/globals/menus.test.ts +33 -0
  212. package/src/lib/globals/menus.ts +11 -4
  213. package/src/lib/hooks/useCanvasEvents.ts +2 -0
  214. package/src/lib/hooks/useDocumentEvents.ts +60 -16
  215. package/src/lib/hooks/useLocalStore.ts +16 -8
  216. package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
  217. package/src/lib/hooks/usePeerIds.ts +2 -1
  218. package/src/lib/license/LicenseManager.test.ts +160 -0
  219. package/src/lib/license/LicenseManager.ts +13 -4
  220. package/src/lib/license/LicenseProvider.tsx +24 -3
  221. package/src/lib/license/useLicenseManagerState.ts +8 -6
  222. package/src/lib/primitives/Box.test.ts +9 -1
  223. package/src/lib/primitives/Box.ts +3 -1
  224. package/src/lib/primitives/Vec.test.ts +10 -0
  225. package/src/lib/primitives/Vec.ts +4 -4
  226. package/src/lib/primitives/geometry/Arc2d.test.ts +226 -3
  227. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +173 -3
  228. package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
  229. package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
  230. package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
  231. package/src/lib/primitives/geometry/Group2d.test.ts +398 -3
  232. package/src/lib/primitives/geometry/Group2d.ts +63 -27
  233. package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
  234. package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
  235. package/src/lib/primitives/utils.test.ts +10 -1
  236. package/src/lib/primitives/utils.ts +4 -1
  237. package/src/lib/test/useCanvasEvents.test.tsx +74 -0
  238. package/src/lib/test/useDocumentEvents.test.tsx +107 -0
  239. package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
  240. package/src/lib/utils/deepLinks.test.ts +15 -0
  241. package/src/lib/utils/deepLinks.ts +7 -1
  242. package/src/lib/utils/normalizeWheel.test.ts +39 -0
  243. package/src/lib/utils/normalizeWheel.ts +6 -0
  244. package/src/lib/utils/rotation.ts +4 -12
  245. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
  246. package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
  247. package/src/lib/utils/sync/alerts.ts +1 -1
  248. package/src/version.ts +3 -3
  249. package/src/lib/primitives/geometry/Polyline.test.ts +0 -5
@@ -73,9 +73,15 @@ export function parseDeepLinkString(deepLinkString: string): TLDeepLink {
73
73
  }
74
74
  case 'v': {
75
75
  const [x, y, w, h, pageId] = deepLinkString.slice(1).split('.')
76
+ const bounds = new Box(Number(x), Number(y), Number(w), Number(h))
77
+ // Number('abc') is NaN rather than an error, so without this check a malformed
78
+ // viewport link would bypass the caller's fallback and park the camera at the origin
79
+ if (![bounds.x, bounds.y, bounds.w, bounds.h].every(Number.isFinite)) {
80
+ throw Error('Invalid deep link string')
81
+ }
76
82
  return {
77
83
  type: 'viewport',
78
- bounds: new Box(Number(x), Number(y), Number(w), Number(h)),
84
+ bounds,
79
85
  pageId: pageId ? PageRecordType.createId(decodeURIComponent(pageId)) : undefined,
80
86
  }
81
87
  }
@@ -0,0 +1,39 @@
1
+ import { normalizeWheel } from './normalizeWheel'
2
+
3
+ function wheelEvent(props: Partial<WheelEvent>): WheelEvent {
4
+ return {
5
+ deltaX: 0,
6
+ deltaY: 0,
7
+ ctrlKey: false,
8
+ altKey: false,
9
+ metaKey: false,
10
+ shiftKey: false,
11
+ ...props,
12
+ } as WheelEvent
13
+ }
14
+
15
+ describe('normalizeWheel', () => {
16
+ it('negates finite deltas for panning', () => {
17
+ const { x, y } = normalizeWheel(wheelEvent({ deltaX: 5, deltaY: 10 }))
18
+ expect(x).toBe(-5)
19
+ expect(y).toBe(-10)
20
+ })
21
+
22
+ it('coerces a non-finite deltaX to zero movement', () => {
23
+ const { x, y } = normalizeWheel(wheelEvent({ deltaX: NaN, deltaY: 10 }))
24
+ expect(x === 0).toBe(true)
25
+ expect(y).toBe(-10)
26
+ })
27
+
28
+ it('coerces a non-finite deltaY to zero movement', () => {
29
+ const { x, y } = normalizeWheel(wheelEvent({ deltaX: 5, deltaY: Infinity }))
30
+ expect(x).toBe(-5)
31
+ expect(y === 0).toBe(true)
32
+ })
33
+
34
+ it('does not produce a non-finite zoom delta when wheeling with a bad deltaY', () => {
35
+ const { z } = normalizeWheel(wheelEvent({ deltaY: NaN, ctrlKey: true }))
36
+ expect(Number.isFinite(z)).toBe(true)
37
+ expect(z === 0).toBe(true)
38
+ })
39
+ })
@@ -9,6 +9,12 @@ const MAX_ZOOM_STEP = 10
9
9
  * @internal */
10
10
  export function normalizeWheel(event: WheelEvent | React.WheelEvent<HTMLElement>) {
11
11
  let { deltaY, deltaX } = event
12
+
13
+ // Coerce non-finite deltas to no movement so they can't propagate into the camera and
14
+ // crash validation. Also covers deltaZ below, which is derived from deltaY.
15
+ if (!Number.isFinite(deltaX)) deltaX = 0
16
+ if (!Number.isFinite(deltaY)) deltaY = 0
17
+
12
18
  let deltaZ = 0
13
19
 
14
20
  // wheeling
@@ -1,7 +1,6 @@
1
- import { isShapeId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'
1
+ import { TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'
2
2
  import { compact } from '@tldraw/utils'
3
3
  import type { Editor } from '../editor/Editor'
4
- import { Mat } from '../primitives/Mat'
5
4
  import { canonicalizeRotation } from '../primitives/utils'
6
5
  import { Vec, VecLike } from '../primitives/Vec'
7
6
 
@@ -75,18 +74,11 @@ export function applyRotationToSnapshotShapes({
75
74
  // We need to both rotate each shape individually and rotate the shapes
76
75
  // around the pivot point (the average center of all rotating shapes.)
77
76
 
78
- const parentTransform = isShapeId(shape.parentId)
79
- ? editor.getShapePageTransform(shape.parentId)!
80
- : Mat.Identity()
81
-
82
77
  const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta)
83
78
 
84
- const newLocalPoint = Mat.applyToPoint(
85
- // use the current parent transform in case it has moved/resized since the start
86
- // (e.g. if rotating a shape at the edge of a group)
87
- Mat.Inverse(parentTransform),
88
- newPagePoint
89
- )
79
+ // uses the current parent transform in case it has moved/resized since the start
80
+ // (e.g. if rotating a shape at the edge of a group)
81
+ const newLocalPoint = editor.getPointInParentSpace(shape.id, newPagePoint)
90
82
  const newRotation = canonicalizeRotation(shape.rotation + delta)
91
83
 
92
84
  return {
@@ -172,3 +172,59 @@ test('writes that come in during a persist operation will get persisted afterwar
172
172
  await tick()
173
173
  expect(client.db.storeChanges).toHaveBeenCalledTimes(1)
174
174
  })
175
+
176
+ test('pagehide flushes pending changes without waiting for the throttle', async () => {
177
+ const { client, tick } = testClient()
178
+ await tick()
179
+ client.store.put([PageRecordType.create({ name: 'test', index: 'a0' as IndexKey })])
180
+ await tick()
181
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
182
+
183
+ client.store.put([PageRecordType.create({ name: 'test2', index: 'a1' as IndexKey })])
184
+ expect(client.db.storeChanges).not.toHaveBeenCalled()
185
+
186
+ window.dispatchEvent(new Event('pagehide'))
187
+ expect(client.db.storeChanges).toHaveBeenCalledTimes(1)
188
+ })
189
+
190
+ test('hiding the tab flushes pending changes without waiting for the throttle', async () => {
191
+ const { client, tick } = testClient()
192
+ await tick()
193
+ client.store.put([PageRecordType.create({ name: 'test', index: 'a0' as IndexKey })])
194
+ await tick()
195
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
196
+
197
+ client.store.put([PageRecordType.create({ name: 'test2', index: 'a1' as IndexKey })])
198
+ expect(client.db.storeChanges).not.toHaveBeenCalled()
199
+
200
+ const visibilityState = vi.spyOn(document, 'visibilityState', 'get')
201
+ try {
202
+ visibilityState.mockReturnValue('hidden')
203
+ document.dispatchEvent(new Event('visibilitychange'))
204
+ expect(client.db.storeChanges).toHaveBeenCalledTimes(1)
205
+ } finally {
206
+ visibilityState.mockRestore()
207
+ }
208
+ })
209
+
210
+ test('pagehide does not write before the initial load has completed', async () => {
211
+ const { client } = testClient()
212
+ window.dispatchEvent(new Event('pagehide'))
213
+ expect(client.db.storeSnapshot).not.toHaveBeenCalled()
214
+ expect(client.db.storeChanges).not.toHaveBeenCalled()
215
+ })
216
+
217
+ test('pagehide and visibilitychange listeners are removed when the client closes', async () => {
218
+ const removeWindowListener = vi.spyOn(window, 'removeEventListener')
219
+ const removeDocumentListener = vi.spyOn(document, 'removeEventListener')
220
+ try {
221
+ const { client, tick } = testClient()
222
+ await tick()
223
+ client.close()
224
+ expect(removeWindowListener).toHaveBeenCalledWith('pagehide', expect.any(Function))
225
+ expect(removeDocumentListener).toHaveBeenCalledWith('visibilitychange', expect.any(Function))
226
+ } finally {
227
+ removeWindowListener.mockRestore()
228
+ removeDocumentListener.mockRestore()
229
+ }
230
+ })
@@ -68,6 +68,7 @@ export class TLLocalSyncClient {
68
68
  private disposables = new Set<() => void>()
69
69
  private diffQueue: Array<RecordsDiff<UnknownRecord> | typeof UPDATE_INSTANCE_STATE> = []
70
70
  private didDispose = false
71
+ private didLoad = false
71
72
  private shouldDoFullDBWrite = true
72
73
  private isReloading = false
73
74
  readonly persistenceKey: string
@@ -143,6 +144,28 @@ export class TLLocalSyncClient {
143
144
  )
144
145
  )
145
146
 
147
+ if (typeof window !== 'undefined') {
148
+ // Persists are throttled, so without this the last PERSIST_THROTTLE_MS of edits are lost
149
+ // whenever the tab is closed or reloaded. `pagehide` is the last reliable event before
150
+ // unload on desktop; on mobile the page can be discarded without it firing, so we also
151
+ // flush when the tab is hidden.
152
+ const flush = () => {
153
+ // Before the initial load completes a full write would overwrite the saved
154
+ // document with an empty store.
155
+ if (!this.didLoad) return
156
+ this.persistIfNeeded()
157
+ }
158
+ const onVisibilityChange = () => {
159
+ if (document.visibilityState === 'hidden') flush()
160
+ }
161
+ window.addEventListener('pagehide', flush)
162
+ document.addEventListener('visibilitychange', onVisibilityChange)
163
+ this.disposables.add(() => {
164
+ window.removeEventListener('pagehide', flush)
165
+ document.removeEventListener('visibilitychange', onVisibilityChange)
166
+ })
167
+ }
168
+
146
169
  this.connect(onLoad, onLoadError)
147
170
 
148
171
  this.documentTypes = new Set(
@@ -249,6 +272,7 @@ export class TLLocalSyncClient {
249
272
  this.disposables.add(() => {
250
273
  this.channel.close()
251
274
  })
275
+ this.didLoad = true
252
276
  onLoad(this)
253
277
  } catch (e: any) {
254
278
  this.debug('error loading data from store', e)
@@ -12,7 +12,7 @@ Keep seeing this message?
12
12
  /** @internal */
13
13
  export function showCantReadFromIndexDbAlert() {
14
14
  window.alert(
15
- `Oops! We could not access your browser's storage—and the app won't work correctly without that. We now need to reload the page and try again.
15
+ `Oops! We could not access your browser's storage—and the app won't work correctly without that. Reload the page to try again.
16
16
 
17
17
  Keep seeing this message?
18
18
  • If you're using tldraw in a private or "incognito" window, try loading tldraw in a regular window or in a different browser.`
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '5.4.0-next.ef99ab47e5ce'
4
+ export const version = '5.4.0'
5
5
  export const publishDates = {
6
6
  major: '2026-05-06T16:28:18.473Z',
7
- minor: '2026-08-05T11:52:36.664Z',
8
- patch: '2026-08-05T11:52:36.664Z',
7
+ minor: '2026-09-02T11:04:02.901Z',
8
+ patch: '2026-09-02T11:04:02.901Z',
9
9
  }
@@ -1,5 +0,0 @@
1
- describe('Polyline2d', () => {
2
- it('should be tested', () => {
3
- expect(true).toBe(true)
4
- })
5
- })