@tldraw/editor 5.4.0-next.ef99ab47e5ce → 5.4.1

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/TextManager/TextManager.ts"],
4
- "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { objectMapKeys } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { EditorManager } from '../EditorManager'\n\n/**\n * The whole-pixel line-height for a given font size and tldraw's unitless line-height\n * multiplier. tldraw's theme stores line-height as a multiplier (e.g. 1.35); resolving it\n * to a whole pixel keeps line spacing identical across rendering engines, which otherwise\n * disagree on fractional line boxes (WebKit snaps them to whole pixels, Blink keeps the\n * fraction) and let multi-line text drift apart. Apply it everywhere line-height is used \u2014\n * measurement, on-canvas render, and export \u2014 so geometry and rendering agree.\n * See https://github.com/tldraw/tldraw/issues/8970.\n *\n * @public\n */\nexport function resolveLineHeightPx(fontSize: number, lineHeight: number): number {\n\treturn Math.round(fontSize * lineHeight)\n}\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\ninterface PoolItem {\n\tel: HTMLDivElement\n\thtml: string\n\tappliedStyleKeys: string[]\n}\n\n/** @public */\nexport interface BatchMeasurementRequest {\n\thtml: string\n\topts: TLMeasureTextOpts\n}\n\n/** @public */\nexport type TLMeasuredTextSize = BoxModel & {\n\tscrollWidth: number\n}\n\n/** @public */\nexport interface TLMeasureTextOpts {\n\tfontStyle: string\n\tfontWeight: string\n\tfontFamily: string\n\tfontSize: number\n\t/** This must be a number, e.g. 1.35, not a pixel value. */\n\tlineHeight: number\n\t/**\n\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t * space are preserved.\n\t */\n\tmaxWidth: null | number\n\tminWidth?: null | number\n\t// todo: make this a number so that it is consistent with other TLMeasureTextSpanOpts\n\tpadding: string\n\totherStyles?: Record<string, string>\n\tdisableOverflowWrapBreaking?: boolean\n\tmeasureScrollWidth?: boolean\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n\totherStyles?: Record<string, string>\n\tmeasureScrollWidth?: boolean\n}\n\nconst spaceCharacterRegex = /\\s/\n\n// Iterate by grapheme cluster (e.g. emoji sequences, flags, accented letters)\n// rather than by code point, so a single glyph is measured as one unit.\nconst graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n\nconst initialDefaultStyles = Object.freeze({\n\t'overflow-wrap': 'break-word',\n\t'word-break': 'auto',\n\twidth: null,\n\theight: null,\n\t'max-width': null,\n\t'min-width': null,\n})\n\n/** @public */\nexport class TextManager extends EditorManager {\n\tprivate elm: HTMLDivElement\n\tprivate poolElms: PoolItem[] = []\n\n\tconstructor(editor: Editor) {\n\t\tsuper(editor)\n\t\tthis.elm = this.createMeasurementEl()\n\t\tthis.editor.getContainer().appendChild(this.elm)\n\t\tthis.register(() => {\n\t\t\tthis.elm.remove()\n\t\t\tfor (const { el } of this.poolElms) {\n\t\t\t\tel.remove()\n\t\t\t}\n\t\t\tthis.poolElms.length = 0\n\t\t})\n\t}\n\n\tprivate createMeasurementEl(): HTMLDivElement {\n\t\tconst elm = this.editor.getContainerDocument().createElement('div')\n\t\telm.classList.add('tl-text')\n\t\telm.classList.add('tl-text-measure')\n\t\telm.setAttribute('dir', 'auto')\n\t\telm.tabIndex = -1\n\t\tfor (const key of objectMapKeys(initialDefaultStyles)) {\n\t\t\telm.style.setProperty(key, initialDefaultStyles[key])\n\t\t}\n\n\t\treturn elm\n\t}\n\n\tprivate resetElementStyles(el: HTMLElement, appliedStyleKeys: string[]) {\n\t\tfor (const key of appliedStyleKeys) {\n\t\t\tif (key in initialDefaultStyles) {\n\t\t\t\tel.style.setProperty(key, initialDefaultStyles[key as keyof typeof initialDefaultStyles])\n\t\t\t} else {\n\t\t\t\tel.style.removeProperty(key)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate setElementStyles(el: HTMLElement, styles: Record<string, string | undefined | null>) {\n\t\ttype StyleValue = string | null\n\t\ttype RestoreEntry = [prop: string, value: StyleValue]\n\n\t\tconst restore: RestoreEntry[] = []\n\n\t\tfor (const [key, nextValue] of Object.entries(styles)) {\n\t\t\tconst oldValue = el.style.getPropertyValue(key)\n\n\t\t\tif (typeof nextValue === 'string') {\n\t\t\t\tif (oldValue === nextValue) continue\n\t\t\t\trestore.push([key, oldValue || null])\n\t\t\t\tel.style.setProperty(key, nextValue)\n\t\t\t} else {\n\t\t\t\tif (!oldValue) continue\n\t\t\t\trestore.push([key, oldValue])\n\t\t\t\tel.style.removeProperty(key)\n\t\t\t}\n\t\t}\n\n\t\treturn () => {\n\t\t\tfor (const [key, value] of restore) {\n\t\t\t\tif (value === null || value === '') el.style.removeProperty(key)\n\t\t\t\telse el.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getMeasureStyles(opts: TLMeasureTextOpts): Record<string, string | undefined> {\n\t\treturn {\n\t\t\t'font-family': opts.fontFamily,\n\t\t\t'font-style': opts.fontStyle,\n\t\t\t'font-weight': opts.fontWeight,\n\t\t\t'font-size': opts.fontSize + 'px',\n\t\t\t'line-height': `${resolveLineHeightPx(opts.fontSize, opts.lineHeight)}px`,\n\t\t\t// Unitless multiplier consumed by the .tl-rich-text h1\u2013h6 rule (see editor.css),\n\t\t\t// so heading measurement matches on-canvas rendering.\n\t\t\t'--tl-rich-text-heading-line-height': `${opts.lineHeight}`,\n\t\t\tpadding: opts.padding,\n\t\t\t'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,\n\t\t\t'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,\n\t\t\t'overflow-wrap': opts.disableOverflowWrapBreaking ? 'normal' : 'break-word',\n\t\t\t...opts.otherStyles,\n\t\t}\n\t}\n\n\tprivate ensurePoolSize(size: number) {\n\t\tif (this.poolElms.length >= size) return\n\n\t\tconst fragment = this.editor.getContainerDocument().createDocumentFragment()\n\t\twhile (this.poolElms.length < size) {\n\t\t\tconst el = this.createMeasurementEl()\n\t\t\tthis.poolElms.push({ el, html: '', appliedStyleKeys: [] })\n\t\t\tfragment.appendChild(el)\n\t\t}\n\t\tthis.editor.getContainer().appendChild(fragment)\n\t}\n\n\tprivate getPoolItem(index: number): PoolItem {\n\t\tthis.ensurePoolSize(index + 1)\n\t\treturn this.poolElms[index]\n\t}\n\n\tmeasureHtmlBatch(requests: BatchMeasurementRequest[]): TLMeasuredTextSize[] {\n\t\tif (requests.length === 0) return []\n\n\t\twhile (this.poolElms.length > requests.length) {\n\t\t\tconst { el } = this.poolElms.pop()!\n\t\t\tel.remove()\n\t\t}\n\n\t\tfor (let i = 0; i < requests.length; i++) {\n\t\t\tconst { html, opts } = requests[i]\n\t\t\tconst poolItem = this.getPoolItem(i)\n\n\t\t\tconst { el } = poolItem\n\t\t\tthis.resetElementStyles(el, poolItem.appliedStyleKeys)\n\t\t\tconst styles = this.getMeasureStyles(opts)\n\t\t\tthis.setElementStyles(el, styles)\n\t\t\tpoolItem.appliedStyleKeys = Object.keys(styles)\n\t\t\t// Skip innerHTML parsing if the content hasn't changed\n\t\t\tif (poolItem.html !== html) {\n\t\t\t\tel.innerHTML = html\n\t\t\t\tpoolItem.html = html\n\t\t\t}\n\t\t}\n\n\t\tconst results: TLMeasuredTextSize[] = []\n\t\tfor (let i = 0; i < requests.length; i++) {\n\t\t\tconst el = this.getPoolItem(i).el\n\t\t\tconst scrollWidth = requests[i].opts.measureScrollWidth ? el.scrollWidth : 0\n\t\t\tconst rect = el.getBoundingClientRect()\n\t\t\tresults.push({\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tw: rect.width,\n\t\t\t\th: rect.height,\n\t\t\t\tscrollWidth,\n\t\t\t})\n\t\t}\n\n\t\treturn results\n\t}\n\n\tmeasureText(textToMeasure: string, opts: TLMeasureTextOpts): TLMeasuredTextSize {\n\t\tconst div = this.editor.getContainerDocument().createElement('div')\n\t\tdiv.textContent = normalizeTextForDom(textToMeasure)\n\t\treturn this.measureHtml(div.innerHTML, opts)\n\t}\n\n\tmeasureHtml(html: string, opts: TLMeasureTextOpts): TLMeasuredTextSize {\n\t\tconst { elm } = this\n\n\t\tconst restoreStyles = this.setElementStyles(elm, this.getMeasureStyles(opts))\n\n\t\ttry {\n\t\t\telm.innerHTML = html\n\n\t\t\tconst scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0\n\t\t\tconst rect = elm.getBoundingClientRect()\n\n\t\t\treturn {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tw: rect.width,\n\t\t\t\th: rect.height,\n\t\t\t\tscrollWidth,\n\t\t\t}\n\t\t} finally {\n\t\t\trestoreStyles()\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const { segment } of graphemeSegmenter.segment(childNode.textContent ?? '')) {\n\t\t\t\t// place the range around the grapheme we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + segment.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]\n\n\t\t\t\t// some graphemes produce no layout rectangle (e.g. zero-width\n\t\t\t\t// spaces or newlines). skip them rather than crashing, but keep\n\t\t\t\t// advancing the index so later graphemes stay aligned.\n\t\t\t\t// See https://github.com/tldraw/tldraw/issues/9112.\n\t\t\t\tif (!rect) {\n\t\t\t\t\tidx += segment.length\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(segment)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: segment,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += segment\n\t\t\t\t}\n\n\t\t\t\tif (segment === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += segment.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst { elm } = this\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\tconst newStyles = {\n\t\t\t'font-family': opts.fontFamily,\n\t\t\t'font-style': opts.fontStyle,\n\t\t\t'font-weight': opts.fontWeight,\n\t\t\t'font-size': opts.fontSize + 'px',\n\t\t\t'line-height': `${resolveLineHeightPx(opts.fontSize, opts.lineHeight)}px`,\n\t\t\twidth: `${elementWidth}px`,\n\t\t\theight: 'min-content',\n\t\t\t'text-align': textAlignmentsForLtr[opts.textAlign],\n\t\t\t'overflow-wrap': shouldTruncateToFirstLine ? 'anywhere' : 'break-word',\n\t\t\t'word-break': shouldTruncateToFirstLine ? 'break-all' : 'normal',\n\t\t\t...opts.otherStyles,\n\t\t}\n\t\tconst restoreStyles = this.setElementStyles(elm, newStyles)\n\n\t\ttry {\n\t\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t\t// Render the text into the measurement element:\n\t\t\telm.textContent = normalizedText\n\n\t\t\t// actually measure the text:\n\t\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine,\n\t\t\t})\n\n\t\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\t\telm.textContent = '\u2026'\n\t\t\t\tconst ellipsisSpan = this.measureElementTextNodeSpans(elm).spans[0]\n\t\t\t\tconst ellipsisWidth = ellipsisSpan ? Math.ceil(ellipsisSpan.box.w) : 0\n\n\t\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\t\telm.textContent = normalizedText\n\t\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t\t}).spans\n\n\t\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t\t// browser.\n\t\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]\n\t\t\t\t// If nothing measurable remained, return the (possibly empty)\n\t\t\t\t// spans as-is rather than dereferencing a missing last span.\n\t\t\t\tif (!lastSpan) {\n\t\t\t\t\treturn truncatedSpans\n\t\t\t\t}\n\t\t\t\ttruncatedSpans.push({\n\t\t\t\t\ttext: '\u2026',\n\t\t\t\t\tbox: {\n\t\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\treturn truncatedSpans\n\t\t\t}\n\n\t\t\treturn spans\n\t\t} finally {\n\t\t\trestoreStyles()\n\t\t}\n\t}\n}\n"],
5
- "mappings": "AACA,SAAS,qBAAqB;AAE9B,SAAS,qBAAqB;AAavB,SAAS,oBAAoB,UAAkB,YAA4B;AACjF,SAAO,KAAK,MAAM,WAAW,UAAU;AACxC;AAEA,MAAM,cAAc;AAEpB,SAAS,oBAAoB,MAAc;AAC1C,SAAO,KACL,QAAQ,aAAa,IAAI,EACzB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,KAAK,GAAG,EACnB,KAAK,IAAI;AACZ;AAEA,MAAM,uBAAuB;AAAA,EAC5B,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,KAAK;AAAA,EACL,cAAc;AACf;AAyDA,MAAM,sBAAsB;AAI5B,MAAM,oBAAoB,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAEnF,MAAM,uBAAuB,OAAO,OAAO;AAAA,EAC1C,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,aAAa;AACd,CAAC;AAGM,MAAM,oBAAoB,cAAc;AAAA,EACtC;AAAA,EACA,WAAuB,CAAC;AAAA,EAEhC,YAAY,QAAgB;AAC3B,UAAM,MAAM;AACZ,SAAK,MAAM,KAAK,oBAAoB;AACpC,SAAK,OAAO,aAAa,EAAE,YAAY,KAAK,GAAG;AAC/C,SAAK,SAAS,MAAM;AACnB,WAAK,IAAI,OAAO;AAChB,iBAAW,EAAE,GAAG,KAAK,KAAK,UAAU;AACnC,WAAG,OAAO;AAAA,MACX;AACA,WAAK,SAAS,SAAS;AAAA,IACxB,CAAC;AAAA,EACF;AAAA,EAEQ,sBAAsC;AAC7C,UAAM,MAAM,KAAK,OAAO,qBAAqB,EAAE,cAAc,KAAK;AAClE,QAAI,UAAU,IAAI,SAAS;AAC3B,QAAI,UAAU,IAAI,iBAAiB;AACnC,QAAI,aAAa,OAAO,MAAM;AAC9B,QAAI,WAAW;AACf,eAAW,OAAO,cAAc,oBAAoB,GAAG;AACtD,UAAI,MAAM,YAAY,KAAK,qBAAqB,GAAG,CAAC;AAAA,IACrD;AAEA,WAAO;AAAA,EACR;AAAA,EAEQ,mBAAmB,IAAiB,kBAA4B;AACvE,eAAW,OAAO,kBAAkB;AACnC,UAAI,OAAO,sBAAsB;AAChC,WAAG,MAAM,YAAY,KAAK,qBAAqB,GAAwC,CAAC;AAAA,MACzF,OAAO;AACN,WAAG,MAAM,eAAe,GAAG;AAAA,MAC5B;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,iBAAiB,IAAiB,QAAmD;AAI5F,UAAM,UAA0B,CAAC;AAEjC,eAAW,CAAC,KAAK,SAAS,KAAK,OAAO,QAAQ,MAAM,GAAG;AACtD,YAAM,WAAW,GAAG,MAAM,iBAAiB,GAAG;AAE9C,UAAI,OAAO,cAAc,UAAU;AAClC,YAAI,aAAa,UAAW;AAC5B,gBAAQ,KAAK,CAAC,KAAK,YAAY,IAAI,CAAC;AACpC,WAAG,MAAM,YAAY,KAAK,SAAS;AAAA,MACpC,OAAO;AACN,YAAI,CAAC,SAAU;AACf,gBAAQ,KAAK,CAAC,KAAK,QAAQ,CAAC;AAC5B,WAAG,MAAM,eAAe,GAAG;AAAA,MAC5B;AAAA,IACD;AAEA,WAAO,MAAM;AACZ,iBAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AACnC,YAAI,UAAU,QAAQ,UAAU,GAAI,IAAG,MAAM,eAAe,GAAG;AAAA,YAC1D,IAAG,MAAM,YAAY,KAAK,KAAK;AAAA,MACrC;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,iBAAiB,MAA6D;AACrF,WAAO;AAAA,MACN,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,aAAa,KAAK,WAAW;AAAA,MAC7B,eAAe,GAAG,oBAAoB,KAAK,UAAU,KAAK,UAAU,CAAC;AAAA;AAAA;AAAA,MAGrE,sCAAsC,GAAG,KAAK,UAAU;AAAA,MACxD,SAAS,KAAK;AAAA,MACd,aAAa,KAAK,WAAW,KAAK,WAAW,OAAO;AAAA,MACpD,aAAa,KAAK,WAAW,KAAK,WAAW,OAAO;AAAA,MACpD,iBAAiB,KAAK,8BAA8B,WAAW;AAAA,MAC/D,GAAG,KAAK;AAAA,IACT;AAAA,EACD;AAAA,EAEQ,eAAe,MAAc;AACpC,QAAI,KAAK,SAAS,UAAU,KAAM;AAElC,UAAM,WAAW,KAAK,OAAO,qBAAqB,EAAE,uBAAuB;AAC3E,WAAO,KAAK,SAAS,SAAS,MAAM;AACnC,YAAM,KAAK,KAAK,oBAAoB;AACpC,WAAK,SAAS,KAAK,EAAE,IAAI,MAAM,IAAI,kBAAkB,CAAC,EAAE,CAAC;AACzD,eAAS,YAAY,EAAE;AAAA,IACxB;AACA,SAAK,OAAO,aAAa,EAAE,YAAY,QAAQ;AAAA,EAChD;AAAA,EAEQ,YAAY,OAAyB;AAC5C,SAAK,eAAe,QAAQ,CAAC;AAC7B,WAAO,KAAK,SAAS,KAAK;AAAA,EAC3B;AAAA,EAEA,iBAAiB,UAA2D;AAC3E,QAAI,SAAS,WAAW,EAAG,QAAO,CAAC;AAEnC,WAAO,KAAK,SAAS,SAAS,SAAS,QAAQ;AAC9C,YAAM,EAAE,GAAG,IAAI,KAAK,SAAS,IAAI;AACjC,SAAG,OAAO;AAAA,IACX;AAEA,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACzC,YAAM,EAAE,MAAM,KAAK,IAAI,SAAS,CAAC;AACjC,YAAM,WAAW,KAAK,YAAY,CAAC;AAEnC,YAAM,EAAE,GAAG,IAAI;AACf,WAAK,mBAAmB,IAAI,SAAS,gBAAgB;AACrD,YAAM,SAAS,KAAK,iBAAiB,IAAI;AACzC,WAAK,iBAAiB,IAAI,MAAM;AAChC,eAAS,mBAAmB,OAAO,KAAK,MAAM;AAE9C,UAAI,SAAS,SAAS,MAAM;AAC3B,WAAG,YAAY;AACf,iBAAS,OAAO;AAAA,MACjB;AAAA,IACD;AAEA,UAAM,UAAgC,CAAC;AACvC,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACzC,YAAM,KAAK,KAAK,YAAY,CAAC,EAAE;AAC/B,YAAM,cAAc,SAAS,CAAC,EAAE,KAAK,qBAAqB,GAAG,cAAc;AAC3E,YAAM,OAAO,GAAG,sBAAsB;AACtC,cAAQ,KAAK;AAAA,QACZ,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG,KAAK;AAAA,QACR,GAAG,KAAK;AAAA,QACR;AAAA,MACD,CAAC;AAAA,IACF;AAEA,WAAO;AAAA,EACR;AAAA,EAEA,YAAY,eAAuB,MAA6C;AAC/E,UAAM,MAAM,KAAK,OAAO,qBAAqB,EAAE,cAAc,KAAK;AAClE,QAAI,cAAc,oBAAoB,aAAa;AACnD,WAAO,KAAK,YAAY,IAAI,WAAW,IAAI;AAAA,EAC5C;AAAA,EAEA,YAAY,MAAc,MAA6C;AACtE,UAAM,EAAE,IAAI,IAAI;AAEhB,UAAM,gBAAgB,KAAK,iBAAiB,KAAK,KAAK,iBAAiB,IAAI,CAAC;AAE5E,QAAI;AACH,UAAI,YAAY;AAEhB,YAAM,cAAc,KAAK,qBAAqB,IAAI,cAAc;AAChE,YAAM,OAAO,IAAI,sBAAsB;AAEvC,aAAO;AAAA,QACN,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG,KAAK;AAAA,QACR,GAAG,KAAK;AAAA,QACR;AAAA,MACD;AAAA,IACD,UAAE;AACD,oBAAc;AAAA,IACf;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,4BACC,SACA,EAAE,4BAA4B,MAAM,IAA6C,CAAC,GACb;AACrE,UAAM,QAAQ,CAAC;AAGf,UAAM,YAAY,QAAQ,sBAAsB;AAChD,UAAM,UAAU,CAAC,UAAU;AAC3B,UAAM,UAAU,CAAC,UAAU;AAG3B,UAAM,QAAQ,IAAI,MAAM;AACxB,UAAM,WAAW,QAAQ,WAAW,CAAC;AACrC,QAAI,MAAM;AAEV,QAAI,cAAc;AAClB,QAAI,4BAA4B;AAChC,QAAI,cAAc;AAClB,QAAI,yBAAyB;AAC7B,QAAI,cAAc;AAClB,eAAW,aAAa,QAAQ,YAAY;AAC3C,UAAI,UAAU,aAAa,KAAK,UAAW;AAE3C,iBAAW,EAAE,QAAQ,KAAK,kBAAkB,QAAQ,UAAU,eAAe,EAAE,GAAG;AAEjF,cAAM,SAAS,UAAU,GAAG;AAC5B,cAAM,OAAO,UAAU,MAAM,QAAQ,MAAM;AAI3C,cAAM,QAAQ,MAAM,eAAe;AACnC,cAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AAMnC,YAAI,CAAC,MAAM;AACV,iBAAO,QAAQ;AACf;AAAA,QACD;AAGA,cAAM,MAAM,KAAK,MAAM;AACvB,cAAM,OAAO,KAAK,OAAO;AACzB,cAAM,QAAQ,KAAK,QAAQ;AAC3B,cAAM,QAAQ,OAAO;AAErB,cAAM,mBAAmB,oBAAoB,KAAK,OAAO;AACzD;AAAA;AAAA,UAEC,qBAAqB;AAAA,UAErB,QAAQ;AAAA,UAER,CAAC;AAAA,UACA;AAGD,cAAI,aAAa;AAEhB,gBAAI,6BAA6B,QAAQ,aAAa;AACrD,4BAAc;AACd;AAAA,YACD;AAEA,kBAAM,KAAK,WAAW;AAAA,UACvB;AAGA,wBAAc;AAAA,YACb,KAAK,EAAE,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO;AAAA,YACtD,MAAM;AAAA,UACP;AACA,mCAAyB;AAAA,QAC1B,OAAO;AAEN,cAAI,OAAO;AACV,wBAAY,IAAI,IAAI;AAAA,UACrB;AAGA,sBAAY,IAAI,IAAI,QAAQ,YAAY,IAAI,IAAI,KAAK,QAAQ,QAAQ,YAAY,IAAI;AACrF,sBAAY,QAAQ;AAAA,QACrB;AAEA,YAAI,YAAY,MAAM;AACrB,mCAAyB;AAAA,QAC1B;AAEA,oCAA4B;AAC5B,sBAAc;AACd,eAAO,QAAQ;AAAA,MAChB;AAAA,IACD;AAGA,QAAI,aAAa;AAChB,YAAM,KAAK,WAAW;AAAA,IACvB;AAEA,WAAO,EAAE,OAAO,YAAY;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iBACC,eACA,MACoC;AACpC,QAAI,kBAAkB,GAAI,QAAO,CAAC;AAElC,UAAM,EAAE,IAAI,IAAI;AAEhB,UAAM,4BACL,KAAK,aAAa,uBAAuB,KAAK,aAAa;AAC5D,UAAM,eAAe,KAAK,KAAK,KAAK,QAAQ,KAAK,UAAU,CAAC;AAC5D,UAAM,YAAY;AAAA,MACjB,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,aAAa,KAAK,WAAW;AAAA,MAC7B,eAAe,GAAG,oBAAoB,KAAK,UAAU,KAAK,UAAU,CAAC;AAAA,MACrE,OAAO,GAAG,YAAY;AAAA,MACtB,QAAQ;AAAA,MACR,cAAc,qBAAqB,KAAK,SAAS;AAAA,MACjD,iBAAiB,4BAA4B,aAAa;AAAA,MAC1D,cAAc,4BAA4B,cAAc;AAAA,MACxD,GAAG,KAAK;AAAA,IACT;AACA,UAAM,gBAAgB,KAAK,iBAAiB,KAAK,SAAS;AAE1D,QAAI;AACH,YAAM,iBAAiB,oBAAoB,aAAa;AAGxD,UAAI,cAAc;AAGlB,YAAM,EAAE,OAAO,YAAY,IAAI,KAAK,4BAA4B,KAAK;AAAA,QACpE;AAAA,MACD,CAAC;AAED,UAAI,KAAK,aAAa,uBAAuB,aAAa;AAEzD,YAAI,cAAc;AAClB,cAAM,eAAe,KAAK,4BAA4B,GAAG,EAAE,MAAM,CAAC;AAClE,cAAM,gBAAgB,eAAe,KAAK,KAAK,aAAa,IAAI,CAAC,IAAI;AAGrE,YAAI,MAAM,YAAY,SAAS,GAAG,eAAe,aAAa,IAAI;AAClE,YAAI,cAAc;AAClB,cAAM,iBAAiB,KAAK,4BAA4B,KAAK;AAAA,UAC5D,2BAA2B;AAAA,QAC5B,CAAC,EAAE;AAMH,cAAM,WAAW,eAAe,eAAe,SAAS,CAAC;AAGzD,YAAI,CAAC,UAAU;AACd,iBAAO;AAAA,QACR;AACA,uBAAe,KAAK;AAAA,UACnB,MAAM;AAAA,UACN,KAAK;AAAA,YACJ,GAAG,KAAK,IAAI,SAAS,IAAI,IAAI,SAAS,IAAI,GAAG,KAAK,QAAQ,KAAK,UAAU,aAAa;AAAA,YACtF,GAAG,SAAS,IAAI;AAAA,YAChB,GAAG;AAAA,YACH,GAAG,SAAS,IAAI;AAAA,UACjB;AAAA,QACD,CAAC;AAED,eAAO;AAAA,MACR;AAEA,aAAO;AAAA,IACR,UAAE;AACD,oBAAc;AAAA,IACf;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { iterateGraphemes, objectMapKeys } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { EditorManager } from '../EditorManager'\n\n/**\n * The whole-pixel line-height for a given font size and tldraw's unitless line-height\n * multiplier. tldraw's theme stores line-height as a multiplier (e.g. 1.35); resolving it\n * to a whole pixel keeps line spacing identical across rendering engines, which otherwise\n * disagree on fractional line boxes (WebKit snaps them to whole pixels, Blink keeps the\n * fraction) and let multi-line text drift apart. Apply it everywhere line-height is used \u2014\n * measurement, on-canvas render, and export \u2014 so geometry and rendering agree.\n * See https://github.com/tldraw/tldraw/issues/8970.\n *\n * @public\n */\nexport function resolveLineHeightPx(fontSize: number, lineHeight: number): number {\n\treturn Math.round(fontSize * lineHeight)\n}\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\ninterface PoolItem {\n\tel: HTMLDivElement\n\thtml: string\n\tappliedStyleKeys: string[]\n}\n\n/** @public */\nexport interface BatchMeasurementRequest {\n\thtml: string\n\topts: TLMeasureTextOpts\n}\n\n/** @public */\nexport type TLMeasuredTextSize = BoxModel & {\n\tscrollWidth: number\n}\n\n/** @public */\nexport interface TLMeasureTextOpts {\n\tfontStyle: string\n\tfontWeight: string\n\tfontFamily: string\n\tfontSize: number\n\t/** This must be a number, e.g. 1.35, not a pixel value. */\n\tlineHeight: number\n\t/**\n\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t * space are preserved.\n\t */\n\tmaxWidth: null | number\n\tminWidth?: null | number\n\t// todo: make this a number so that it is consistent with other TLMeasureTextSpanOpts\n\tpadding: string\n\totherStyles?: Record<string, string>\n\tdisableOverflowWrapBreaking?: boolean\n\tmeasureScrollWidth?: boolean\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n\totherStyles?: Record<string, string>\n\tmeasureScrollWidth?: boolean\n}\n\nconst spaceCharacterRegex = /\\s/\n\nconst initialDefaultStyles = Object.freeze({\n\t'overflow-wrap': 'break-word',\n\t'word-break': 'auto',\n\twidth: null,\n\theight: null,\n\t'max-width': null,\n\t'min-width': null,\n})\n\n/** @public */\nexport class TextManager extends EditorManager {\n\tprivate elm: HTMLDivElement\n\tprivate poolElms: PoolItem[] = []\n\n\tconstructor(editor: Editor) {\n\t\tsuper(editor)\n\t\tthis.elm = this.createMeasurementEl()\n\t\tthis.editor.getContainer().appendChild(this.elm)\n\t\tthis.register(() => {\n\t\t\tthis.elm.remove()\n\t\t\tfor (const { el } of this.poolElms) {\n\t\t\t\tel.remove()\n\t\t\t}\n\t\t\tthis.poolElms.length = 0\n\t\t})\n\t}\n\n\tprivate createMeasurementEl(): HTMLDivElement {\n\t\tconst elm = this.editor.getContainerDocument().createElement('div')\n\t\telm.classList.add('tl-text')\n\t\telm.classList.add('tl-text-measure')\n\t\telm.setAttribute('dir', 'auto')\n\t\telm.tabIndex = -1\n\t\tfor (const key of objectMapKeys(initialDefaultStyles)) {\n\t\t\telm.style.setProperty(key, initialDefaultStyles[key])\n\t\t}\n\n\t\treturn elm\n\t}\n\n\tprivate resetElementStyles(el: HTMLElement, appliedStyleKeys: string[]) {\n\t\tfor (const key of appliedStyleKeys) {\n\t\t\tif (key in initialDefaultStyles) {\n\t\t\t\tel.style.setProperty(key, initialDefaultStyles[key as keyof typeof initialDefaultStyles])\n\t\t\t} else {\n\t\t\t\tel.style.removeProperty(key)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate setElementStyles(el: HTMLElement, styles: Record<string, string | undefined | null>) {\n\t\ttype StyleValue = string | null\n\t\ttype RestoreEntry = [prop: string, value: StyleValue]\n\n\t\tconst restore: RestoreEntry[] = []\n\n\t\tfor (const [key, nextValue] of Object.entries(styles)) {\n\t\t\tconst oldValue = el.style.getPropertyValue(key)\n\n\t\t\tif (typeof nextValue === 'string') {\n\t\t\t\tif (oldValue === nextValue) continue\n\t\t\t\trestore.push([key, oldValue || null])\n\t\t\t\tel.style.setProperty(key, nextValue)\n\t\t\t} else {\n\t\t\t\tif (!oldValue) continue\n\t\t\t\trestore.push([key, oldValue])\n\t\t\t\tel.style.removeProperty(key)\n\t\t\t}\n\t\t}\n\n\t\treturn () => {\n\t\t\tfor (const [key, value] of restore) {\n\t\t\t\tif (value === null || value === '') el.style.removeProperty(key)\n\t\t\t\telse el.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getMeasureStyles(opts: TLMeasureTextOpts): Record<string, string | undefined> {\n\t\treturn {\n\t\t\t'font-family': opts.fontFamily,\n\t\t\t'font-style': opts.fontStyle,\n\t\t\t'font-weight': opts.fontWeight,\n\t\t\t'font-size': opts.fontSize + 'px',\n\t\t\t'line-height': `${resolveLineHeightPx(opts.fontSize, opts.lineHeight)}px`,\n\t\t\t// Unitless multiplier consumed by the .tl-rich-text h1\u2013h6 rule (see editor.css),\n\t\t\t// so heading measurement matches on-canvas rendering.\n\t\t\t'--tl-rich-text-heading-line-height': `${opts.lineHeight}`,\n\t\t\tpadding: opts.padding,\n\t\t\t'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,\n\t\t\t'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,\n\t\t\t'overflow-wrap': opts.disableOverflowWrapBreaking ? 'normal' : 'break-word',\n\t\t\t...opts.otherStyles,\n\t\t}\n\t}\n\n\tprivate ensurePoolSize(size: number) {\n\t\tif (this.poolElms.length >= size) return\n\n\t\tconst fragment = this.editor.getContainerDocument().createDocumentFragment()\n\t\twhile (this.poolElms.length < size) {\n\t\t\tconst el = this.createMeasurementEl()\n\t\t\tthis.poolElms.push({ el, html: '', appliedStyleKeys: [] })\n\t\t\tfragment.appendChild(el)\n\t\t}\n\t\tthis.editor.getContainer().appendChild(fragment)\n\t}\n\n\tprivate getPoolItem(index: number): PoolItem {\n\t\tthis.ensurePoolSize(index + 1)\n\t\treturn this.poolElms[index]\n\t}\n\n\tmeasureHtmlBatch(requests: BatchMeasurementRequest[]): TLMeasuredTextSize[] {\n\t\tif (requests.length === 0) return []\n\n\t\twhile (this.poolElms.length > requests.length) {\n\t\t\tconst { el } = this.poolElms.pop()!\n\t\t\tel.remove()\n\t\t}\n\n\t\tfor (let i = 0; i < requests.length; i++) {\n\t\t\tconst { html, opts } = requests[i]\n\t\t\tconst poolItem = this.getPoolItem(i)\n\n\t\t\tconst { el } = poolItem\n\t\t\tthis.resetElementStyles(el, poolItem.appliedStyleKeys)\n\t\t\tconst styles = this.getMeasureStyles(opts)\n\t\t\tthis.setElementStyles(el, styles)\n\t\t\tpoolItem.appliedStyleKeys = Object.keys(styles)\n\t\t\t// Skip innerHTML parsing if the content hasn't changed\n\t\t\tif (poolItem.html !== html) {\n\t\t\t\tel.innerHTML = html\n\t\t\t\tpoolItem.html = html\n\t\t\t}\n\t\t}\n\n\t\tconst results: TLMeasuredTextSize[] = []\n\t\tfor (let i = 0; i < requests.length; i++) {\n\t\t\tconst el = this.getPoolItem(i).el\n\t\t\tconst scrollWidth = requests[i].opts.measureScrollWidth ? el.scrollWidth : 0\n\t\t\tconst rect = el.getBoundingClientRect()\n\t\t\tresults.push({\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tw: rect.width,\n\t\t\t\th: rect.height,\n\t\t\t\tscrollWidth,\n\t\t\t})\n\t\t}\n\n\t\treturn results\n\t}\n\n\tmeasureText(textToMeasure: string, opts: TLMeasureTextOpts): TLMeasuredTextSize {\n\t\tconst div = this.editor.getContainerDocument().createElement('div')\n\t\tdiv.textContent = normalizeTextForDom(textToMeasure)\n\t\treturn this.measureHtml(div.innerHTML, opts)\n\t}\n\n\tmeasureHtml(html: string, opts: TLMeasureTextOpts): TLMeasuredTextSize {\n\t\tconst { elm } = this\n\n\t\tconst restoreStyles = this.setElementStyles(elm, this.getMeasureStyles(opts))\n\n\t\ttry {\n\t\t\telm.innerHTML = html\n\n\t\t\tconst scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0\n\t\t\tconst rect = elm.getBoundingClientRect()\n\n\t\t\treturn {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tw: rect.width,\n\t\t\t\th: rect.height,\n\t\t\t\tscrollWidth,\n\t\t\t}\n\t\t} finally {\n\t\t\trestoreStyles()\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const segment of iterateGraphemes(childNode.textContent ?? '')) {\n\t\t\t\t// place the range around the grapheme we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + segment.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]\n\n\t\t\t\t// some graphemes produce no layout rectangle (e.g. zero-width\n\t\t\t\t// spaces or newlines). skip them rather than crashing, but keep\n\t\t\t\t// advancing the index so later graphemes stay aligned.\n\t\t\t\t// See https://github.com/tldraw/tldraw/issues/9112.\n\t\t\t\tif (!rect) {\n\t\t\t\t\tidx += segment.length\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(segment)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: segment,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += segment\n\t\t\t\t}\n\n\t\t\t\tif (segment === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += segment.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst { elm } = this\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\tconst newStyles = {\n\t\t\t'font-family': opts.fontFamily,\n\t\t\t'font-style': opts.fontStyle,\n\t\t\t'font-weight': opts.fontWeight,\n\t\t\t'font-size': opts.fontSize + 'px',\n\t\t\t'line-height': `${resolveLineHeightPx(opts.fontSize, opts.lineHeight)}px`,\n\t\t\twidth: `${elementWidth}px`,\n\t\t\theight: 'min-content',\n\t\t\t'text-align': textAlignmentsForLtr[opts.textAlign],\n\t\t\t'overflow-wrap': shouldTruncateToFirstLine ? 'anywhere' : 'break-word',\n\t\t\t'word-break': shouldTruncateToFirstLine ? 'break-all' : 'normal',\n\t\t\t...opts.otherStyles,\n\t\t}\n\t\tconst restoreStyles = this.setElementStyles(elm, newStyles)\n\n\t\ttry {\n\t\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t\t// Render the text into the measurement element:\n\t\t\telm.textContent = normalizedText\n\n\t\t\t// actually measure the text:\n\t\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine,\n\t\t\t})\n\n\t\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\t\telm.textContent = '\u2026'\n\t\t\t\tconst ellipsisSpan = this.measureElementTextNodeSpans(elm).spans[0]\n\t\t\t\tconst ellipsisWidth = ellipsisSpan ? Math.ceil(ellipsisSpan.box.w) : 0\n\n\t\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\t\telm.textContent = normalizedText\n\t\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t\t}).spans\n\n\t\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t\t// browser.\n\t\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]\n\t\t\t\t// If nothing measurable remained, return the (possibly empty)\n\t\t\t\t// spans as-is rather than dereferencing a missing last span.\n\t\t\t\tif (!lastSpan) {\n\t\t\t\t\treturn truncatedSpans\n\t\t\t\t}\n\t\t\t\ttruncatedSpans.push({\n\t\t\t\t\ttext: '\u2026',\n\t\t\t\t\tbox: {\n\t\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\treturn truncatedSpans\n\t\t\t}\n\n\t\t\treturn spans\n\t\t} finally {\n\t\t\trestoreStyles()\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -39,9 +39,12 @@ class TickManager extends EditorManager {
39
39
  const now = Date.now();
40
40
  const elapsed = now - this.now;
41
41
  this.now = now;
42
- this.editor.emit("frame", elapsed);
43
- this.editor.emit("tick", elapsed);
44
- this.cancelRaf = throttleToNextFrame(this.tick);
42
+ try {
43
+ this.editor.emit("frame", elapsed);
44
+ this.editor.emit("tick", elapsed);
45
+ } finally {
46
+ this.cancelRaf = throttleToNextFrame(this.tick);
47
+ }
45
48
  }
46
49
  dispose() {
47
50
  this.isPaused = true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/TickManager/TickManager.ts"],
4
- "sourcesContent": ["import { throttleToNextFrame as _throttleToNextFrame, bind } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { EditorManager } from '../EditorManager'\n\nconst throttleToNextFrame =\n\ttypeof process !== 'undefined' && process.env.NODE_ENV === 'test'\n\t\t? // At test time we should use actual raf and not throttle, because throttle was set up to evaluate immediately during tests, which causes stack overflow\n\t\t\t// for the tick manager since it sets up a raf loop.\n\t\t\tfunction mockThrottle(cb: any) {\n\t\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\t\tconst frame = requestAnimationFrame(cb)\n\t\t\t\treturn () => cancelAnimationFrame(frame)\n\t\t\t}\n\t\t: _throttleToNextFrame\n\n/** @internal */\nexport class TickManager extends EditorManager {\n\tconstructor(editor: Editor) {\n\t\tsuper(editor)\n\t\tthis.start()\n\t}\n\n\tcancelRaf?: null | (() => void)\n\tisPaused = true\n\tnow = 0\n\n\tstart() {\n\t\tthis.isPaused = false\n\t\tthis.cancelRaf?.()\n\t\tthis.cancelRaf = throttleToNextFrame(this.tick)\n\t\tthis.now = Date.now()\n\t}\n\n\t@bind\n\ttick() {\n\t\tif (this.isPaused) {\n\t\t\treturn\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst elapsed = now - this.now\n\t\tthis.now = now\n\n\t\tthis.editor.emit('frame', elapsed)\n\t\tthis.editor.emit('tick', elapsed)\n\t\tthis.cancelRaf = throttleToNextFrame(this.tick)\n\t}\n\n\tdispose() {\n\t\tthis.isPaused = true\n\t\tthis.cancelRaf?.()\n\t\tsuper.dispose()\n\t}\n}\n"],
5
- "mappings": ";;;;;;;;;;AAAA,SAAS,uBAAuB,sBAAsB,YAAY;AAElE,SAAS,qBAAqB;AAE9B,MAAM,sBACL,OAAO,YAAY,eAAe,QAAQ,IAAI,aAAa;AAAA;AAAA;AAAA,EAGzD,SAAS,aAAa,IAAS;AAE9B,UAAM,QAAQ,sBAAsB,EAAE;AACtC,WAAO,MAAM,qBAAqB,KAAK;AAAA,EACxC;AAAA,IACC;AAGG,MAAM,oBAAoB,cAAc;AAAA,EAC9C,YAAY,QAAgB;AAC3B,UAAM,MAAM;AACZ,SAAK,MAAM;AAAA,EACZ;AAAA,EAEA;AAAA,EACA,WAAW;AAAA,EACX,MAAM;AAAA,EAEN,QAAQ;AACP,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,YAAY,oBAAoB,KAAK,IAAI;AAC9C,SAAK,MAAM,KAAK,IAAI;AAAA,EACrB;AAAA,EAGA,OAAO;AACN,QAAI,KAAK,UAAU;AAClB;AAAA,IACD;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,UAAU,MAAM,KAAK;AAC3B,SAAK,MAAM;AAEX,SAAK,OAAO,KAAK,SAAS,OAAO;AACjC,SAAK,OAAO,KAAK,QAAQ,OAAO;AAChC,SAAK,YAAY,oBAAoB,KAAK,IAAI;AAAA,EAC/C;AAAA,EAEA,UAAU;AACT,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,UAAM,QAAQ;AAAA,EACf;AACD;AAnBC;AAAA,EADC;AAAA,GAjBW,YAkBZ;",
4
+ "sourcesContent": ["import { throttleToNextFrame as _throttleToNextFrame, bind } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { EditorManager } from '../EditorManager'\n\nconst throttleToNextFrame =\n\ttypeof process !== 'undefined' && process.env.NODE_ENV === 'test'\n\t\t? // At test time we should use actual raf and not throttle, because throttle was set up to evaluate immediately during tests, which causes stack overflow\n\t\t\t// for the tick manager since it sets up a raf loop.\n\t\t\tfunction mockThrottle(cb: any) {\n\t\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\t\tconst frame = requestAnimationFrame(cb)\n\t\t\t\treturn () => cancelAnimationFrame(frame)\n\t\t\t}\n\t\t: _throttleToNextFrame\n\n/** @internal */\nexport class TickManager extends EditorManager {\n\tconstructor(editor: Editor) {\n\t\tsuper(editor)\n\t\tthis.start()\n\t}\n\n\tcancelRaf?: null | (() => void)\n\tisPaused = true\n\tnow = 0\n\n\tstart() {\n\t\tthis.isPaused = false\n\t\tthis.cancelRaf?.()\n\t\tthis.cancelRaf = throttleToNextFrame(this.tick)\n\t\tthis.now = Date.now()\n\t}\n\n\t@bind\n\ttick() {\n\t\tif (this.isPaused) {\n\t\t\treturn\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst elapsed = now - this.now\n\t\tthis.now = now\n\n\t\t// Schedule the next frame even if a listener throws. Otherwise a single throwing listener\n\t\t// ends the loop for good, which silently stops every frame-driven behavior \u2014 camera\n\t\t// animations, edge scrolling, pointer velocity, following \u2014 instead of failing once.\n\t\ttry {\n\t\t\tthis.editor.emit('frame', elapsed)\n\t\t\tthis.editor.emit('tick', elapsed)\n\t\t} finally {\n\t\t\tthis.cancelRaf = throttleToNextFrame(this.tick)\n\t\t}\n\t}\n\n\tdispose() {\n\t\tthis.isPaused = true\n\t\tthis.cancelRaf?.()\n\t\tsuper.dispose()\n\t}\n}\n"],
5
+ "mappings": ";;;;;;;;;;AAAA,SAAS,uBAAuB,sBAAsB,YAAY;AAElE,SAAS,qBAAqB;AAE9B,MAAM,sBACL,OAAO,YAAY,eAAe,QAAQ,IAAI,aAAa;AAAA;AAAA;AAAA,EAGzD,SAAS,aAAa,IAAS;AAE9B,UAAM,QAAQ,sBAAsB,EAAE;AACtC,WAAO,MAAM,qBAAqB,KAAK;AAAA,EACxC;AAAA,IACC;AAGG,MAAM,oBAAoB,cAAc;AAAA,EAC9C,YAAY,QAAgB;AAC3B,UAAM,MAAM;AACZ,SAAK,MAAM;AAAA,EACZ;AAAA,EAEA;AAAA,EACA,WAAW;AAAA,EACX,MAAM;AAAA,EAEN,QAAQ;AACP,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,YAAY,oBAAoB,KAAK,IAAI;AAC9C,SAAK,MAAM,KAAK,IAAI;AAAA,EACrB;AAAA,EAGA,OAAO;AACN,QAAI,KAAK,UAAU;AAClB;AAAA,IACD;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,UAAU,MAAM,KAAK;AAC3B,SAAK,MAAM;AAKX,QAAI;AACH,WAAK,OAAO,KAAK,SAAS,OAAO;AACjC,WAAK,OAAO,KAAK,QAAQ,OAAO;AAAA,IACjC,UAAE;AACD,WAAK,YAAY,oBAAoB,KAAK,IAAI;AAAA,IAC/C;AAAA,EACD;AAAA,EAEA,UAAU;AACT,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,UAAM,QAAQ;AAAA,EACf;AACD;AAzBC;AAAA,EADC;AAAA,GAjBW,YAkBZ;",
6
6
  "names": []
7
7
  }
@@ -30,13 +30,24 @@ class UserPreferencesManager {
30
30
  };
31
31
  darkModeMediaQuery?.addEventListener("change", handleChange);
32
32
  this.disposables.add(() => darkModeMediaQuery?.removeEventListener("change", handleChange));
33
+ const reducedMotionMediaQuery = getGlobalWindow().matchMedia("(prefers-reduced-motion: reduce)");
34
+ this.systemPrefersReducedMotion.set(!!reducedMotionMediaQuery?.matches);
35
+ const handleReducedMotionChange = (e) => {
36
+ this.systemPrefersReducedMotion.set(e.matches);
37
+ };
38
+ reducedMotionMediaQuery?.addEventListener("change", handleReducedMotionChange);
39
+ this.disposables.add(
40
+ () => reducedMotionMediaQuery?.removeEventListener("change", handleReducedMotionChange)
41
+ );
33
42
  }
34
43
  user;
35
44
  colorScheme;
36
45
  systemColorScheme = atom("systemColorScheme", "light");
46
+ systemPrefersReducedMotion = atom("systemPrefersReducedMotion", false);
37
47
  disposables = /* @__PURE__ */ new Set();
38
48
  dispose() {
39
49
  this.disposables.forEach((d) => d());
50
+ this.disposables.clear();
40
51
  }
41
52
  updateUserPreferences(userPreferences) {
42
53
  this.user.setUserPreferences({
@@ -80,7 +91,7 @@ class UserPreferencesManager {
80
91
  return this.user.userPreferences.get().edgeScrollSpeed ?? defaultUserPreferences.edgeScrollSpeed;
81
92
  }
82
93
  getAnimationSpeed() {
83
- return this.user.userPreferences.get().animationSpeed ?? defaultUserPreferences.animationSpeed;
94
+ return this.user.userPreferences.get().animationSpeed ?? (this.systemPrefersReducedMotion.get() ? 0 : 1);
84
95
  }
85
96
  getAreKeyboardShortcutsEnabled() {
86
97
  return this.user.userPreferences.get().areKeyboardShortcutsEnabled ?? defaultUserPreferences.areKeyboardShortcutsEnabled;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts"],
4
- "sourcesContent": ["import { atom, computed } from '@tldraw/state'\nimport { createUserId, TLUserId } from '@tldraw/tlschema'\nimport { TLCurrentUser } from '../../../config/createTLCurrentUser'\nimport { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'\nimport { getGlobalWindow } from '../../../utils/dom'\n\n/** @public */\nexport class UserPreferencesManager {\n\tsystemColorScheme = atom<'dark' | 'light'>('systemColorScheme', 'light')\n\tdisposables = new Set<() => void>()\n\tdispose() {\n\t\tthis.disposables.forEach((d) => d())\n\t}\n\tconstructor(\n\t\tprivate readonly user: TLCurrentUser,\n\t\tprivate readonly colorScheme: 'light' | 'dark' | 'system'\n\t) {\n\t\tif (typeof window === 'undefined' || !getGlobalWindow().matchMedia) return\n\n\t\tconst darkModeMediaQuery = getGlobalWindow().matchMedia('(prefers-color-scheme: dark)')\n\t\tif (darkModeMediaQuery?.matches) {\n\t\t\tthis.systemColorScheme.set('dark')\n\t\t}\n\t\tconst handleChange = (e: MediaQueryListEvent) => {\n\t\t\tif (e.matches) {\n\t\t\t\tthis.systemColorScheme.set('dark')\n\t\t\t} else {\n\t\t\t\tthis.systemColorScheme.set('light')\n\t\t\t}\n\t\t}\n\t\tdarkModeMediaQuery?.addEventListener('change', handleChange)\n\t\tthis.disposables.add(() => darkModeMediaQuery?.removeEventListener('change', handleChange))\n\t}\n\n\tupdateUserPreferences(userPreferences: Partial<TLUserPreferences>) {\n\t\tthis.user.setUserPreferences({\n\t\t\t...this.user.userPreferences.get(),\n\t\t\t...userPreferences,\n\t\t})\n\t}\n\t@computed getUserPreferences() {\n\t\treturn {\n\t\t\tid: this.getExternalId(),\n\t\t\tname: this.getName(),\n\t\t\tlocale: this.getLocale(),\n\t\t\tcolor: this.getColor(),\n\t\t\tanimationSpeed: this.getAnimationSpeed(),\n\t\t\tareKeyboardShortcutsEnabled: this.getAreKeyboardShortcutsEnabled(),\n\t\t\tisSnapMode: this.getIsSnapMode(),\n\t\t\tcolorScheme: this.user.userPreferences.get().colorScheme,\n\t\t\tisDarkMode: this.getIsDarkMode(),\n\t\t\tisWrapMode: this.getIsWrapMode(),\n\t\t\tisDynamicResizeMode: this.getIsDynamicResizeMode(),\n\t\t\tenhancedA11yMode: this.getEnhancedA11yMode(),\n\t\t\tinputMode: this.getInputMode(),\n\t\t\tisZoomDirectionInverted: this.getIsZoomDirectionInverted(),\n\t\t}\n\t}\n\n\t@computed getIsDarkMode() {\n\t\tconst userColorScheme = this.user.userPreferences.get().colorScheme\n\t\tconst scheme = userColorScheme ?? this.colorScheme\n\t\tswitch (scheme) {\n\t\t\tcase 'dark':\n\t\t\t\treturn true\n\t\t\tcase 'light':\n\t\t\t\treturn false\n\t\t\tcase 'system':\n\t\t\t\treturn this.systemColorScheme.get() === 'dark'\n\t\t\tdefault:\n\t\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * The speed at which the user can scroll by dragging toward the edge of the screen.\n\t */\n\t@computed getEdgeScrollSpeed() {\n\t\treturn this.user.userPreferences.get().edgeScrollSpeed ?? defaultUserPreferences.edgeScrollSpeed\n\t}\n\n\t@computed getAnimationSpeed() {\n\t\treturn this.user.userPreferences.get().animationSpeed ?? defaultUserPreferences.animationSpeed\n\t}\n\n\t@computed getAreKeyboardShortcutsEnabled() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().areKeyboardShortcutsEnabled ??\n\t\t\tdefaultUserPreferences.areKeyboardShortcutsEnabled\n\t\t)\n\t}\n\n\t/**\n\t * The current user's raw, app-provided id \u2014 the value set in the user's\n\t * {@link @tldraw/editor#TLUserPreferences}. Use this when you need the id your application\n\t * assigned to the user. To compare against or look up store records, use\n\t * {@link UserPreferencesManager.getRecordId} instead.\n\t */\n\t@computed getExternalId(): string {\n\t\treturn this.user.userPreferences.get().id\n\t}\n\n\t/**\n\t * @deprecated Use {@link UserPreferencesManager.getExternalId} for the raw app-provided id, or\n\t * {@link UserPreferencesManager.getRecordId} for the prefixed `TLUserId` record id.\n\t */\n\t@computed getId() {\n\t\treturn this.getExternalId()\n\t}\n\n\t/**\n\t * The current user's id as a tldraw {@link @tldraw/tlschema#TLUserId} record id (prefixed\n\t * with `user:`). Use this when comparing against or looking up store records, such as a\n\t * presence record's `userId` or `followingUserId`. For the raw, app-provided id, use\n\t * {@link UserPreferencesManager.getExternalId}.\n\t */\n\t@computed getRecordId(): TLUserId {\n\t\treturn createUserId(this.getExternalId())\n\t}\n\n\t@computed getName() {\n\t\treturn this.user.userPreferences.get().name?.trim() ?? defaultUserPreferences.name\n\t}\n\n\t@computed getLocale() {\n\t\treturn this.user.userPreferences.get().locale ?? defaultUserPreferences.locale\n\t}\n\n\t@computed getColor() {\n\t\treturn this.user.userPreferences.get().color ?? defaultUserPreferences.color\n\t}\n\n\t@computed getIsSnapMode() {\n\t\treturn this.user.userPreferences.get().isSnapMode ?? defaultUserPreferences.isSnapMode\n\t}\n\n\t@computed getIsWrapMode() {\n\t\treturn this.user.userPreferences.get().isWrapMode ?? defaultUserPreferences.isWrapMode\n\t}\n\n\t@computed getIsDynamicResizeMode() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().isDynamicSizeMode ?? defaultUserPreferences.isDynamicSizeMode\n\t\t)\n\t}\n\n\t@computed getIsPasteAtCursorMode() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().isPasteAtCursorMode ??\n\t\t\tdefaultUserPreferences.isPasteAtCursorMode\n\t\t)\n\t}\n\n\t@computed getEnhancedA11yMode() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().enhancedA11yMode ?? defaultUserPreferences.enhancedA11yMode\n\t\t)\n\t}\n\n\t@computed getInputMode() {\n\t\treturn this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode\n\t}\n\n\t@computed getIsZoomDirectionInverted() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().isZoomDirectionInverted ??\n\t\t\tdefaultUserPreferences.isZoomDirectionInverted\n\t\t)\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { atom, computed } from '@tldraw/state'\nimport { createUserId, TLUserId } from '@tldraw/tlschema'\nimport { TLCurrentUser } from '../../../config/createTLCurrentUser'\nimport { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'\nimport { getGlobalWindow } from '../../../utils/dom'\n\n/** @public */\nexport class UserPreferencesManager {\n\tsystemColorScheme = atom<'dark' | 'light'>('systemColorScheme', 'light')\n\tprivate readonly systemPrefersReducedMotion = atom('systemPrefersReducedMotion', false)\n\tdisposables = new Set<() => void>()\n\tdispose() {\n\t\tthis.disposables.forEach((d) => d())\n\t\tthis.disposables.clear()\n\t}\n\tconstructor(\n\t\tprivate readonly user: TLCurrentUser,\n\t\tprivate readonly colorScheme: 'light' | 'dark' | 'system'\n\t) {\n\t\tif (typeof window === 'undefined' || !getGlobalWindow().matchMedia) return\n\n\t\tconst darkModeMediaQuery = getGlobalWindow().matchMedia('(prefers-color-scheme: dark)')\n\t\tif (darkModeMediaQuery?.matches) {\n\t\t\tthis.systemColorScheme.set('dark')\n\t\t}\n\t\tconst handleChange = (e: MediaQueryListEvent) => {\n\t\t\tif (e.matches) {\n\t\t\t\tthis.systemColorScheme.set('dark')\n\t\t\t} else {\n\t\t\t\tthis.systemColorScheme.set('light')\n\t\t\t}\n\t\t}\n\t\tdarkModeMediaQuery?.addEventListener('change', handleChange)\n\t\tthis.disposables.add(() => darkModeMediaQuery?.removeEventListener('change', handleChange))\n\n\t\tconst reducedMotionMediaQuery = getGlobalWindow().matchMedia('(prefers-reduced-motion: reduce)')\n\t\tthis.systemPrefersReducedMotion.set(!!reducedMotionMediaQuery?.matches)\n\t\tconst handleReducedMotionChange = (e: MediaQueryListEvent) => {\n\t\t\tthis.systemPrefersReducedMotion.set(e.matches)\n\t\t}\n\t\treducedMotionMediaQuery?.addEventListener('change', handleReducedMotionChange)\n\t\tthis.disposables.add(() =>\n\t\t\treducedMotionMediaQuery?.removeEventListener('change', handleReducedMotionChange)\n\t\t)\n\t}\n\n\tupdateUserPreferences(userPreferences: Partial<TLUserPreferences>) {\n\t\tthis.user.setUserPreferences({\n\t\t\t...this.user.userPreferences.get(),\n\t\t\t...userPreferences,\n\t\t})\n\t}\n\t@computed getUserPreferences() {\n\t\treturn {\n\t\t\tid: this.getExternalId(),\n\t\t\tname: this.getName(),\n\t\t\tlocale: this.getLocale(),\n\t\t\tcolor: this.getColor(),\n\t\t\tanimationSpeed: this.getAnimationSpeed(),\n\t\t\tareKeyboardShortcutsEnabled: this.getAreKeyboardShortcutsEnabled(),\n\t\t\tisSnapMode: this.getIsSnapMode(),\n\t\t\tcolorScheme: this.user.userPreferences.get().colorScheme,\n\t\t\tisDarkMode: this.getIsDarkMode(),\n\t\t\tisWrapMode: this.getIsWrapMode(),\n\t\t\tisDynamicResizeMode: this.getIsDynamicResizeMode(),\n\t\t\tenhancedA11yMode: this.getEnhancedA11yMode(),\n\t\t\tinputMode: this.getInputMode(),\n\t\t\tisZoomDirectionInverted: this.getIsZoomDirectionInverted(),\n\t\t}\n\t}\n\n\t@computed getIsDarkMode() {\n\t\tconst userColorScheme = this.user.userPreferences.get().colorScheme\n\t\tconst scheme = userColorScheme ?? this.colorScheme\n\t\tswitch (scheme) {\n\t\t\tcase 'dark':\n\t\t\t\treturn true\n\t\t\tcase 'light':\n\t\t\t\treturn false\n\t\t\tcase 'system':\n\t\t\t\treturn this.systemColorScheme.get() === 'dark'\n\t\t\tdefault:\n\t\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * The speed at which the user can scroll by dragging toward the edge of the screen.\n\t */\n\t@computed getEdgeScrollSpeed() {\n\t\treturn this.user.userPreferences.get().edgeScrollSpeed ?? defaultUserPreferences.edgeScrollSpeed\n\t}\n\n\t@computed getAnimationSpeed() {\n\t\t// Until the user explicitly sets a speed, follow the OS reduce-motion setting live rather\n\t\t// than the value `defaultUserPreferences` captured at module load.\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().animationSpeed ??\n\t\t\t(this.systemPrefersReducedMotion.get() ? 0 : 1)\n\t\t)\n\t}\n\n\t@computed getAreKeyboardShortcutsEnabled() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().areKeyboardShortcutsEnabled ??\n\t\t\tdefaultUserPreferences.areKeyboardShortcutsEnabled\n\t\t)\n\t}\n\n\t/**\n\t * The current user's raw, app-provided id \u2014 the value set in the user's\n\t * {@link @tldraw/editor#TLUserPreferences}. Use this when you need the id your application\n\t * assigned to the user. To compare against or look up store records, use\n\t * {@link UserPreferencesManager.getRecordId} instead.\n\t */\n\t@computed getExternalId(): string {\n\t\treturn this.user.userPreferences.get().id\n\t}\n\n\t/**\n\t * @deprecated Use {@link UserPreferencesManager.getExternalId} for the raw app-provided id, or\n\t * {@link UserPreferencesManager.getRecordId} for the prefixed `TLUserId` record id.\n\t */\n\t@computed getId() {\n\t\treturn this.getExternalId()\n\t}\n\n\t/**\n\t * The current user's id as a tldraw {@link @tldraw/tlschema#TLUserId} record id (prefixed\n\t * with `user:`). Use this when comparing against or looking up store records, such as a\n\t * presence record's `userId` or `followingUserId`. For the raw, app-provided id, use\n\t * {@link UserPreferencesManager.getExternalId}.\n\t */\n\t@computed getRecordId(): TLUserId {\n\t\treturn createUserId(this.getExternalId())\n\t}\n\n\t@computed getName() {\n\t\treturn this.user.userPreferences.get().name?.trim() ?? defaultUserPreferences.name\n\t}\n\n\t@computed getLocale() {\n\t\treturn this.user.userPreferences.get().locale ?? defaultUserPreferences.locale\n\t}\n\n\t@computed getColor() {\n\t\treturn this.user.userPreferences.get().color ?? defaultUserPreferences.color\n\t}\n\n\t@computed getIsSnapMode() {\n\t\treturn this.user.userPreferences.get().isSnapMode ?? defaultUserPreferences.isSnapMode\n\t}\n\n\t@computed getIsWrapMode() {\n\t\treturn this.user.userPreferences.get().isWrapMode ?? defaultUserPreferences.isWrapMode\n\t}\n\n\t@computed getIsDynamicResizeMode() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().isDynamicSizeMode ?? defaultUserPreferences.isDynamicSizeMode\n\t\t)\n\t}\n\n\t@computed getIsPasteAtCursorMode() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().isPasteAtCursorMode ??\n\t\t\tdefaultUserPreferences.isPasteAtCursorMode\n\t\t)\n\t}\n\n\t@computed getEnhancedA11yMode() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().enhancedA11yMode ?? defaultUserPreferences.enhancedA11yMode\n\t\t)\n\t}\n\n\t@computed getInputMode() {\n\t\treturn this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode\n\t}\n\n\t@computed getIsZoomDirectionInverted() {\n\t\treturn (\n\t\t\tthis.user.userPreferences.get().isZoomDirectionInverted ??\n\t\t\tdefaultUserPreferences.isZoomDirectionInverted\n\t\t)\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -9,6 +9,7 @@ var __decorateClass = (decorators, target, key, kind) => {
9
9
  return result;
10
10
  };
11
11
  import { atom, computed } from "@tldraw/state";
12
+ import { WeakCache } from "@tldraw/utils";
12
13
  class OverlayManager {
13
14
  constructor(editor) {
14
15
  this.editor = editor;
@@ -64,7 +65,7 @@ class OverlayManager {
64
65
  // while getActiveOverlayEntries() keeps returning the same overlay
65
66
  // instances; when its reactive deps change, getOverlays() emits fresh
66
67
  // objects and stale entries fall out by GC.
67
- _geometryCache = /* @__PURE__ */ new WeakMap();
68
+ _geometryCache = new WeakCache();
68
69
  /**
69
70
  * Get hit-test geometry for an overlay, cached by overlay identity. Lets
70
71
  * hit-testing on a pointermove storm skip the per-overlay geometry
@@ -74,12 +75,7 @@ class OverlayManager {
74
75
  * @public
75
76
  */
76
77
  getOverlayGeometry(overlay) {
77
- const cached = this._geometryCache.get(overlay);
78
- if (cached !== void 0) return cached;
79
- const util = this.getOverlayUtil(overlay);
80
- const geometry = util.getGeometry(overlay);
81
- this._geometryCache.set(overlay, geometry);
82
- return geometry;
78
+ return this._geometryCache.get(overlay, (o) => this.getOverlayUtil(o).getGeometry(o));
83
79
  }
84
80
  /**
85
81
  * The currently hovered overlay id.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/overlays/OverlayManager.ts"],
4
- "sourcesContent": ["import { atom, computed } from '@tldraw/state'\nimport { Geometry2d } from '../../primitives/geometry/Geometry2d'\nimport { VecLike } from '../../primitives/Vec'\nimport type { Editor } from '../Editor'\nimport { OverlayUtil, TLOverlay } from './OverlayUtil'\n\n/**\n * An active overlay util paired with the overlays it produced for the current\n * editor state. Returned by {@link OverlayManager.getActiveOverlayEntries} so\n * hit-test, render, and debug paths share a single scan per reactive tick.\n *\n * @public\n */\nexport interface TLOverlayEntry {\n\tutil: OverlayUtil\n\toverlays: TLOverlay[]\n}\n\n/** @public */\nexport class OverlayManager {\n\tconstructor(public readonly editor: Editor) {}\n\n\t/** @internal */\n\treadonly _overlayUtils = new Map<string, OverlayUtil>()\n\n\t/**\n\t * Register an overlay util instance. Called during editor construction.\n\t * @internal\n\t */\n\tregisterUtil(util: OverlayUtil) {\n\t\tconst type = (util.constructor as typeof OverlayUtil).type\n\t\tif (!type) {\n\t\t\tthrow new Error(`Overlay util ${util.constructor.name} is missing a static 'type' property.`)\n\t\t}\n\t\tif (this._overlayUtils.has(type)) {\n\t\t\tthrow new Error(`Duplicate overlay util type: \"${type}\"`)\n\t\t}\n\t\tthis._overlayUtils.set(type, util)\n\t}\n\n\tdispose() {\n\t\tfor (const util of this._overlayUtils.values()) {\n\t\t\tutil.dispose()\n\t\t}\n\t}\n\n\t/**\n\t * Get an overlay util by type string, overlay instance, or by passing\n\t * a util class as a generic parameter for type-safe lookup.\n\t *\n\t * @example\n\t * ```ts\n\t * const util = editor.overlays.getOverlayUtil('brush')\n\t * const util = editor.overlays.getOverlayUtil<BrushOverlayUtil>('brush')\n\t * const util = editor.overlays.getOverlayUtil(myOverlay)\n\t * ```\n\t *\n\t * @public\n\t */\n\tgetOverlayUtil<T extends OverlayUtil>(\n\t\ttype: T extends OverlayUtil<infer O> ? O['type'] : string\n\t): T\n\tgetOverlayUtil<O extends TLOverlay>(overlay: O): OverlayUtil<O>\n\tgetOverlayUtil(arg: string | TLOverlay): OverlayUtil {\n\t\tconst type = typeof arg === 'string' ? arg : arg.type\n\t\tconst util = this._overlayUtils.get(type)\n\t\tif (!util) throw new Error(`No overlay util found for type: \"${type}\"`)\n\t\treturn util\n\t}\n\n\t/**\n\t * Returns all registered overlay utils in paint order (ascending zIndex).\n\t * Utils with the same zIndex preserve their registration order.\n\t *\n\t * @public\n\t */\n\t@computed getOverlayUtilsInZOrder(): OverlayUtil[] {\n\t\tconst utils = Array.from(this._overlayUtils.values())\n\t\t// Stable sort by zIndex (registration order breaks ties).\n\t\treturn utils\n\t\t\t.map((util, i) => ({ util, i, z: util.options.zIndex ?? 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.i - b.i)\n\t\t\t.map((entry) => entry.util)\n\t}\n\n\t/**\n\t * Reactive list of active overlay utils paired with the overlays they\n\t * produced for the current editor state, in paint order (ascending\n\t * zIndex). Both the hit-test and render paths read from this single\n\t * cached scan instead of each re-deriving the active set. Active utils\n\t * are included even when their `getOverlays()` returns an empty array,\n\t * since `render()` may still draw non-interactive UI (e.g. the selection\n\t * bounding box during brushing).\n\t *\n\t * @public\n\t */\n\t@computed getActiveOverlayEntries(): TLOverlayEntry[] {\n\t\tconst entries: TLOverlayEntry[] = []\n\t\tfor (const util of this.getOverlayUtilsInZOrder()) {\n\t\t\tif (!util.isActive()) continue\n\t\t\tentries.push({ util, overlays: util.getOverlays() })\n\t\t}\n\t\treturn entries\n\t}\n\n\t/**\n\t * Reactively computed list of all currently active overlays, in paint order.\n\t * @public\n\t */\n\t@computed getCurrentOverlays(): TLOverlay[] {\n\t\tconst all: TLOverlay[] = []\n\t\tfor (const { overlays } of this.getActiveOverlayEntries()) {\n\t\t\tall.push(...overlays)\n\t\t}\n\t\treturn all\n\t}\n\n\t// Hit-test geometry cache keyed by overlay identity. Entries remain valid\n\t// while getActiveOverlayEntries() keeps returning the same overlay\n\t// instances; when its reactive deps change, getOverlays() emits fresh\n\t// objects and stale entries fall out by GC.\n\tprivate _geometryCache = new WeakMap<TLOverlay, Geometry2d | null>()\n\n\t/**\n\t * Get hit-test geometry for an overlay, cached by overlay identity. Lets\n\t * hit-testing on a pointermove storm skip the per-overlay geometry\n\t * allocation that {@link OverlayUtil.getGeometry} would otherwise do on\n\t * every call.\n\t *\n\t * @public\n\t */\n\tgetOverlayGeometry(overlay: TLOverlay): Geometry2d | null {\n\t\tconst cached = this._geometryCache.get(overlay)\n\t\tif (cached !== undefined) return cached\n\t\tconst util = this.getOverlayUtil(overlay)\n\t\tconst geometry = util.getGeometry(overlay)\n\t\tthis._geometryCache.set(overlay, geometry)\n\t\treturn geometry\n\t}\n\n\t/**\n\t * The currently hovered overlay id.\n\t * @public\n\t */\n\tprivate _hoveredOverlayId = atom<string | null>('hoveredOverlayId', null)\n\n\tgetHoveredOverlayId(): string | null {\n\t\treturn this._hoveredOverlayId.get()\n\t}\n\n\tgetHoveredOverlay(): TLOverlay | null {\n\t\tconst id = this._hoveredOverlayId.get()\n\t\tif (!id) return null\n\t\treturn this.getCurrentOverlays().find((o) => o.id === id) ?? null\n\t}\n\n\tsetHoveredOverlay(id: string | null) {\n\t\tif (id === this._hoveredOverlayId.get()) return\n\t\tthis._hoveredOverlayId.set(id)\n\t}\n\n\t/**\n\t * Hit test all active overlays at a given page point.\n\t * Returns the topmost overlay whose geometry contains the point, or null.\n\t * Utils are walked from highest zIndex to lowest so the overlay painted on\n\t * top also wins the hit test. Within a util, overlays are walked in\n\t * array order: the first overlay whose geometry contains the point wins,\n\t * so utils should place highest-priority overlays first in `getOverlays`.\n\t * Interactive overlays (those with geometry) are checked; non-interactive are skipped.\n\t *\n\t * @param point - Point in page coordinates\n\t * @param margin - Hit test margin\n\t * @public\n\t */\n\tgetOverlayAtPoint(point: VecLike, margin = 0): TLOverlay | null {\n\t\tconst entries = this.getActiveOverlayEntries()\n\t\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\t\tconst { overlays } = entries[i]\n\t\t\tfor (const overlay of overlays) {\n\t\t\t\tconst geometry = this.getOverlayGeometry(overlay)\n\t\t\t\tif (!geometry) continue\n\t\t\t\tif (geometry.hitTestPoint(point, geometry.isFilled ? 0 : margin, true)) {\n\t\t\t\t\treturn overlay\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { atom, computed } from '@tldraw/state'\nimport { WeakCache } from '@tldraw/utils'\nimport { Geometry2d } from '../../primitives/geometry/Geometry2d'\nimport { VecLike } from '../../primitives/Vec'\nimport type { Editor } from '../Editor'\nimport { OverlayUtil, TLOverlay } from './OverlayUtil'\n\n/**\n * An active overlay util paired with the overlays it produced for the current\n * editor state. Returned by {@link OverlayManager.getActiveOverlayEntries} so\n * hit-test, render, and debug paths share a single scan per reactive tick.\n *\n * @public\n */\nexport interface TLOverlayEntry {\n\tutil: OverlayUtil\n\toverlays: TLOverlay[]\n}\n\n/** @public */\nexport class OverlayManager {\n\tconstructor(public readonly editor: Editor) {}\n\n\t/** @internal */\n\treadonly _overlayUtils = new Map<string, OverlayUtil>()\n\n\t/**\n\t * Register an overlay util instance. Called during editor construction.\n\t * @internal\n\t */\n\tregisterUtil(util: OverlayUtil) {\n\t\tconst type = (util.constructor as typeof OverlayUtil).type\n\t\tif (!type) {\n\t\t\tthrow new Error(`Overlay util ${util.constructor.name} is missing a static 'type' property.`)\n\t\t}\n\t\tif (this._overlayUtils.has(type)) {\n\t\t\tthrow new Error(`Duplicate overlay util type: \"${type}\"`)\n\t\t}\n\t\tthis._overlayUtils.set(type, util)\n\t}\n\n\tdispose() {\n\t\tfor (const util of this._overlayUtils.values()) {\n\t\t\tutil.dispose()\n\t\t}\n\t}\n\n\t/**\n\t * Get an overlay util by type string, overlay instance, or by passing\n\t * a util class as a generic parameter for type-safe lookup.\n\t *\n\t * @example\n\t * ```ts\n\t * const util = editor.overlays.getOverlayUtil('brush')\n\t * const util = editor.overlays.getOverlayUtil<BrushOverlayUtil>('brush')\n\t * const util = editor.overlays.getOverlayUtil(myOverlay)\n\t * ```\n\t *\n\t * @public\n\t */\n\tgetOverlayUtil<T extends OverlayUtil>(\n\t\ttype: T extends OverlayUtil<infer O> ? O['type'] : string\n\t): T\n\tgetOverlayUtil<O extends TLOverlay>(overlay: O): OverlayUtil<O>\n\tgetOverlayUtil(arg: string | TLOverlay): OverlayUtil {\n\t\tconst type = typeof arg === 'string' ? arg : arg.type\n\t\tconst util = this._overlayUtils.get(type)\n\t\tif (!util) throw new Error(`No overlay util found for type: \"${type}\"`)\n\t\treturn util\n\t}\n\n\t/**\n\t * Returns all registered overlay utils in paint order (ascending zIndex).\n\t * Utils with the same zIndex preserve their registration order.\n\t *\n\t * @public\n\t */\n\t@computed getOverlayUtilsInZOrder(): OverlayUtil[] {\n\t\tconst utils = Array.from(this._overlayUtils.values())\n\t\t// Stable sort by zIndex (registration order breaks ties).\n\t\treturn utils\n\t\t\t.map((util, i) => ({ util, i, z: util.options.zIndex ?? 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.i - b.i)\n\t\t\t.map((entry) => entry.util)\n\t}\n\n\t/**\n\t * Reactive list of active overlay utils paired with the overlays they\n\t * produced for the current editor state, in paint order (ascending\n\t * zIndex). Both the hit-test and render paths read from this single\n\t * cached scan instead of each re-deriving the active set. Active utils\n\t * are included even when their `getOverlays()` returns an empty array,\n\t * since `render()` may still draw non-interactive UI (e.g. the selection\n\t * bounding box during brushing).\n\t *\n\t * @public\n\t */\n\t@computed getActiveOverlayEntries(): TLOverlayEntry[] {\n\t\tconst entries: TLOverlayEntry[] = []\n\t\tfor (const util of this.getOverlayUtilsInZOrder()) {\n\t\t\tif (!util.isActive()) continue\n\t\t\tentries.push({ util, overlays: util.getOverlays() })\n\t\t}\n\t\treturn entries\n\t}\n\n\t/**\n\t * Reactively computed list of all currently active overlays, in paint order.\n\t * @public\n\t */\n\t@computed getCurrentOverlays(): TLOverlay[] {\n\t\tconst all: TLOverlay[] = []\n\t\tfor (const { overlays } of this.getActiveOverlayEntries()) {\n\t\t\tall.push(...overlays)\n\t\t}\n\t\treturn all\n\t}\n\n\t// Hit-test geometry cache keyed by overlay identity. Entries remain valid\n\t// while getActiveOverlayEntries() keeps returning the same overlay\n\t// instances; when its reactive deps change, getOverlays() emits fresh\n\t// objects and stale entries fall out by GC.\n\tprivate _geometryCache = new WeakCache<TLOverlay, Geometry2d | null>()\n\n\t/**\n\t * Get hit-test geometry for an overlay, cached by overlay identity. Lets\n\t * hit-testing on a pointermove storm skip the per-overlay geometry\n\t * allocation that {@link OverlayUtil.getGeometry} would otherwise do on\n\t * every call.\n\t *\n\t * @public\n\t */\n\tgetOverlayGeometry(overlay: TLOverlay): Geometry2d | null {\n\t\treturn this._geometryCache.get(overlay, (o) => this.getOverlayUtil(o).getGeometry(o))\n\t}\n\n\t/**\n\t * The currently hovered overlay id.\n\t * @public\n\t */\n\tprivate _hoveredOverlayId = atom<string | null>('hoveredOverlayId', null)\n\n\tgetHoveredOverlayId(): string | null {\n\t\treturn this._hoveredOverlayId.get()\n\t}\n\n\tgetHoveredOverlay(): TLOverlay | null {\n\t\tconst id = this._hoveredOverlayId.get()\n\t\tif (!id) return null\n\t\treturn this.getCurrentOverlays().find((o) => o.id === id) ?? null\n\t}\n\n\tsetHoveredOverlay(id: string | null) {\n\t\tif (id === this._hoveredOverlayId.get()) return\n\t\tthis._hoveredOverlayId.set(id)\n\t}\n\n\t/**\n\t * Hit test all active overlays at a given page point.\n\t * Returns the topmost overlay whose geometry contains the point, or null.\n\t * Utils are walked from highest zIndex to lowest so the overlay painted on\n\t * top also wins the hit test. Within a util, overlays are walked in\n\t * array order: the first overlay whose geometry contains the point wins,\n\t * so utils should place highest-priority overlays first in `getOverlays`.\n\t * Interactive overlays (those with geometry) are checked; non-interactive are skipped.\n\t *\n\t * @param point - Point in page coordinates\n\t * @param margin - Hit test margin\n\t * @public\n\t */\n\tgetOverlayAtPoint(point: VecLike, margin = 0): TLOverlay | null {\n\t\tconst entries = this.getActiveOverlayEntries()\n\t\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\t\tconst { overlays } = entries[i]\n\t\t\tfor (const overlay of overlays) {\n\t\t\t\tconst geometry = this.getOverlayGeometry(overlay)\n\t\t\t\tif (!geometry) continue\n\t\t\t\tif (geometry.hitTestPoint(point, geometry.isFilled ? 0 : margin, true)) {\n\t\t\t\t\treturn overlay\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -18,7 +18,6 @@ function strokeShapeIndicators(editor, ctx, shapeIds) {
18
18
  const shape = editor.getShape(shapeId);
19
19
  if (!shape || shape.isLocked) continue;
20
20
  const pageTransform = editor.getShapePageTransform(shape);
21
- if (!pageTransform) continue;
22
21
  const indicatorPath = indicatorPathCache.get(editor, shape.id);
23
22
  if (!indicatorPath) continue;
24
23
  if (indicatorPath instanceof Path2D) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/overlays/strokeShapeIndicators.ts"],
4
- "sourcesContent": ["import { createComputedCache } from '@tldraw/store'\nimport { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport type { Editor } from '../Editor'\n\nconst indicatorPathCache = createComputedCache(\n\t'shapeIndicatorPath',\n\t(editor: Editor, shape: TLShape) => {\n\t\tconst util = editor.getShapeUtil(shape)\n\t\treturn util.getIndicatorPath(shape)\n\t},\n\t{\n\t\tareRecordsEqual(a, b) {\n\t\t\treturn a.props === b.props\n\t\t},\n\t}\n)\n\n/**\n * Combine every batchable shape indicator into a single page-space `Path2D` and\n * emit one stroke call. Shapes whose indicator needs an evenodd clip (e.g.\n * arrows with labels or complex arrowheads) can't be batched \u2014 they still\n * stroke individually inside a save/restore with `ctx.clip` applied.\n *\n * Shared by any overlay util that paints shape indicators (e.g. collaborator\n * selections).\n *\n * @public\n */\nexport function strokeShapeIndicators(\n\teditor: Editor,\n\tctx: CanvasRenderingContext2D,\n\tshapeIds: TLShapeId[]\n): void {\n\tif (shapeIds.length === 0) return\n\n\tconst batched = new Path2D()\n\n\tfor (const shapeId of shapeIds) {\n\t\tconst shape = editor.getShape(shapeId)\n\t\tif (!shape || shape.isLocked) continue\n\n\t\tconst pageTransform = editor.getShapePageTransform(shape)\n\t\tif (!pageTransform) continue\n\n\t\tconst indicatorPath = indicatorPathCache.get(editor, shape.id)\n\t\tif (!indicatorPath) continue\n\n\t\tif (indicatorPath instanceof Path2D) {\n\t\t\tbatched.addPath(indicatorPath, pageTransform)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst { path, clipPath, additionalPaths } = indicatorPath\n\n\t\tif (!clipPath) {\n\t\t\tbatched.addPath(path, pageTransform)\n\t\t\tif (additionalPaths) {\n\t\t\t\tfor (const p of additionalPaths) batched.addPath(p, pageTransform)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Clipped case: fall back to an individual stroke. Rare (arrows with\n\t\t// labels / complex arrowheads), so the extra save/restore/stroke\n\t\t// pair per such shape isn't worth batching away.\n\t\tctx.save()\n\t\tctx.transform(\n\t\t\tpageTransform.a,\n\t\t\tpageTransform.b,\n\t\t\tpageTransform.c,\n\t\t\tpageTransform.d,\n\t\t\tpageTransform.e,\n\t\t\tpageTransform.f\n\t\t)\n\t\tctx.save()\n\t\tctx.clip(clipPath, 'evenodd')\n\t\tctx.stroke(path)\n\t\tctx.restore()\n\t\tif (additionalPaths) {\n\t\t\tfor (const p of additionalPaths) ctx.stroke(p)\n\t\t}\n\t\tctx.restore()\n\t}\n\n\tctx.stroke(batched)\n}\n"],
5
- "mappings": "AAAA,SAAS,2BAA2B;AAIpC,MAAM,qBAAqB;AAAA,EAC1B;AAAA,EACA,CAAC,QAAgB,UAAmB;AACnC,UAAM,OAAO,OAAO,aAAa,KAAK;AACtC,WAAO,KAAK,iBAAiB,KAAK;AAAA,EACnC;AAAA,EACA;AAAA,IACC,gBAAgB,GAAG,GAAG;AACrB,aAAO,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,EACD;AACD;AAaO,SAAS,sBACf,QACA,KACA,UACO;AACP,MAAI,SAAS,WAAW,EAAG;AAE3B,QAAM,UAAU,IAAI,OAAO;AAE3B,aAAW,WAAW,UAAU;AAC/B,UAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,QAAI,CAAC,SAAS,MAAM,SAAU;AAE9B,UAAM,gBAAgB,OAAO,sBAAsB,KAAK;AACxD,QAAI,CAAC,cAAe;AAEpB,UAAM,gBAAgB,mBAAmB,IAAI,QAAQ,MAAM,EAAE;AAC7D,QAAI,CAAC,cAAe;AAEpB,QAAI,yBAAyB,QAAQ;AACpC,cAAQ,QAAQ,eAAe,aAAa;AAC5C;AAAA,IACD;AAEA,UAAM,EAAE,MAAM,UAAU,gBAAgB,IAAI;AAE5C,QAAI,CAAC,UAAU;AACd,cAAQ,QAAQ,MAAM,aAAa;AACnC,UAAI,iBAAiB;AACpB,mBAAW,KAAK,gBAAiB,SAAQ,QAAQ,GAAG,aAAa;AAAA,MAClE;AACA;AAAA,IACD;AAKA,QAAI,KAAK;AACT,QAAI;AAAA,MACH,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,IACf;AACA,QAAI,KAAK;AACT,QAAI,KAAK,UAAU,SAAS;AAC5B,QAAI,OAAO,IAAI;AACf,QAAI,QAAQ;AACZ,QAAI,iBAAiB;AACpB,iBAAW,KAAK,gBAAiB,KAAI,OAAO,CAAC;AAAA,IAC9C;AACA,QAAI,QAAQ;AAAA,EACb;AAEA,MAAI,OAAO,OAAO;AACnB;",
4
+ "sourcesContent": ["import { createComputedCache } from '@tldraw/store'\nimport { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport type { Editor } from '../Editor'\n\nconst indicatorPathCache = createComputedCache(\n\t'shapeIndicatorPath',\n\t(editor: Editor, shape: TLShape) => {\n\t\tconst util = editor.getShapeUtil(shape)\n\t\treturn util.getIndicatorPath(shape)\n\t},\n\t{\n\t\tareRecordsEqual(a, b) {\n\t\t\treturn a.props === b.props\n\t\t},\n\t}\n)\n\n/**\n * Combine every batchable shape indicator into a single page-space `Path2D` and\n * emit one stroke call. Shapes whose indicator needs an evenodd clip (e.g.\n * arrows with labels or complex arrowheads) can't be batched \u2014 they still\n * stroke individually inside a save/restore with `ctx.clip` applied.\n *\n * Shared by any overlay util that paints shape indicators (e.g. collaborator\n * selections).\n *\n * @public\n */\nexport function strokeShapeIndicators(\n\teditor: Editor,\n\tctx: CanvasRenderingContext2D,\n\tshapeIds: TLShapeId[]\n): void {\n\tif (shapeIds.length === 0) return\n\n\tconst batched = new Path2D()\n\n\tfor (const shapeId of shapeIds) {\n\t\tconst shape = editor.getShape(shapeId)\n\t\tif (!shape || shape.isLocked) continue\n\n\t\tconst pageTransform = editor.getShapePageTransform(shape)\n\n\t\tconst indicatorPath = indicatorPathCache.get(editor, shape.id)\n\t\tif (!indicatorPath) continue\n\n\t\tif (indicatorPath instanceof Path2D) {\n\t\t\tbatched.addPath(indicatorPath, pageTransform)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst { path, clipPath, additionalPaths } = indicatorPath\n\n\t\tif (!clipPath) {\n\t\t\tbatched.addPath(path, pageTransform)\n\t\t\tif (additionalPaths) {\n\t\t\t\tfor (const p of additionalPaths) batched.addPath(p, pageTransform)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Clipped case: fall back to an individual stroke. Rare (arrows with\n\t\t// labels / complex arrowheads), so the extra save/restore/stroke\n\t\t// pair per such shape isn't worth batching away.\n\t\tctx.save()\n\t\tctx.transform(\n\t\t\tpageTransform.a,\n\t\t\tpageTransform.b,\n\t\t\tpageTransform.c,\n\t\t\tpageTransform.d,\n\t\t\tpageTransform.e,\n\t\t\tpageTransform.f\n\t\t)\n\t\tctx.save()\n\t\tctx.clip(clipPath, 'evenodd')\n\t\tctx.stroke(path)\n\t\tctx.restore()\n\t\tif (additionalPaths) {\n\t\t\tfor (const p of additionalPaths) ctx.stroke(p)\n\t\t}\n\t\tctx.restore()\n\t}\n\n\tctx.stroke(batched)\n}\n"],
5
+ "mappings": "AAAA,SAAS,2BAA2B;AAIpC,MAAM,qBAAqB;AAAA,EAC1B;AAAA,EACA,CAAC,QAAgB,UAAmB;AACnC,UAAM,OAAO,OAAO,aAAa,KAAK;AACtC,WAAO,KAAK,iBAAiB,KAAK;AAAA,EACnC;AAAA,EACA;AAAA,IACC,gBAAgB,GAAG,GAAG;AACrB,aAAO,EAAE,UAAU,EAAE;AAAA,IACtB;AAAA,EACD;AACD;AAaO,SAAS,sBACf,QACA,KACA,UACO;AACP,MAAI,SAAS,WAAW,EAAG;AAE3B,QAAM,UAAU,IAAI,OAAO;AAE3B,aAAW,WAAW,UAAU;AAC/B,UAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,QAAI,CAAC,SAAS,MAAM,SAAU;AAE9B,UAAM,gBAAgB,OAAO,sBAAsB,KAAK;AAExD,UAAM,gBAAgB,mBAAmB,IAAI,QAAQ,MAAM,EAAE;AAC7D,QAAI,CAAC,cAAe;AAEpB,QAAI,yBAAyB,QAAQ;AACpC,cAAQ,QAAQ,eAAe,aAAa;AAC5C;AAAA,IACD;AAEA,UAAM,EAAE,MAAM,UAAU,gBAAgB,IAAI;AAE5C,QAAI,CAAC,UAAU;AACd,cAAQ,QAAQ,MAAM,aAAa;AACnC,UAAI,iBAAiB;AACpB,mBAAW,KAAK,gBAAiB,SAAQ,QAAQ,GAAG,aAAa;AAAA,MAClE;AACA;AAAA,IACD;AAKA,QAAI,KAAK;AACT,QAAI;AAAA,MACH,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,IACf;AACA,QAAI,KAAK;AACT,QAAI,KAAK,UAAU,SAAS;AAC5B,QAAI,OAAO,IAAI;AACf,QAAI,QAAQ;AACZ,QAAI,iBAAiB;AACpB,iBAAW,KAAK,gBAAiB,KAAI,OAAO,CAAC;AAAA,IAC9C;AACA,QAAI,QAAQ;AAAA,EACb;AAEA,MAAI,OAAO,OAAO;AACnB;",
6
6
  "names": []
7
7
  }
@@ -30,7 +30,9 @@ class BaseFrameLikeShapeUtil extends BaseBoxShapeUtil {
30
30
  const currentChildren = compact(
31
31
  editor.getSortedChildIdsForParent(shape).map((id) => editor.getShape(id))
32
32
  );
33
- if (previousChildren.every((s) => !currentChildren.find((c) => c.index === s.index))) {
33
+ if (previousChildren.every(
34
+ (s) => !currentChildren.find((c) => c.index === initialIndices.get(s.id))
35
+ )) {
34
36
  canRestoreOriginalIndices = true;
35
37
  }
36
38
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx"],
4
- "sourcesContent": ["import { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact } from '@tldraw/utils'\nimport { Vec } from '../../primitives/Vec'\nimport { BaseBoxShapeUtil, TLBaseBoxShape } from './BaseBoxShapeUtil'\nimport { TLDragShapesInInfo, TLDragShapesOutInfo } from './ShapeUtil'\n\n/**\n * A base class for frame-like shapes \u2014 containers that clip their children except arrows,\n * require full-brush selection, block erasure from inside, and support\n * drag-and-drop reparenting.\n *\n * Extending this class is the easiest way to create a custom frame-like shape.\n * It provides sensible defaults for all frame-like behaviors:\n *\n * - `isFrameLike()` returns `true`\n * - `providesBackgroundForChildren()` returns `true`\n * - `canReceiveNewChildrenOfType()` returns `true` unless the container is locked\n * - `canRemoveChildrenOfType()` returns `true` unless the container is locked\n * - `getClipPath()` returns the shape geometry's vertices\n * - `shouldClipChild()` clips all children except arrows\n * - `onDragShapesIn()` reparents shapes into the frame (with index restoration)\n * - `onDragShapesOut()` reparents shapes back to the page\n *\n * All methods can be overridden for custom behavior.\n *\n * @example\n * ```ts\n * class MyContainerUtil extends BaseFrameLikeShapeUtil<MyContainerShape> {\n * static override type = 'my-container' as const\n * static override props = myContainerShapeProps\n *\n * override getDefaultProps() {\n * return { w: 300, h: 200 }\n * }\n *\n * override component(shape: MyContainerShape) {\n * return <SVGContainer>...</SVGContainer>\n * }\n *\n * override getIndicatorPath(shape: MyContainerShape) {\n * const path = new Path2D()\n * path.rect(0, 0, shape.props.w, shape.props.h)\n * return path\n * }\n * }\n * ```\n *\n * @public\n */\nexport abstract class BaseFrameLikeShapeUtil<\n\tShape extends TLBaseBoxShape,\n> extends BaseBoxShapeUtil<Shape> {\n\toverride isFrameLike(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\toverride providesBackgroundForChildren(): boolean {\n\t\treturn true\n\t}\n\n\toverride canReceiveNewChildrenOfType(shape: Shape, _type: TLShape['type']): boolean {\n\t\treturn !shape.isLocked\n\t}\n\n\toverride canRemoveChildrenOfType(shape: Shape, _type: TLShape['type']): boolean {\n\t\treturn !shape.isLocked\n\t}\n\n\toverride getClipPath(shape: Shape): Vec[] | undefined {\n\t\treturn this.editor.getShapeGeometry(shape.id).vertices\n\t}\n\n\toverride shouldClipChild(child: TLShape): boolean {\n\t\treturn child.type !== 'arrow'\n\t}\n\n\toverride onDragShapesIn(\n\t\tshape: Shape,\n\t\tdraggingShapes: TLShape[],\n\t\t{ initialParentIds, initialIndices }: TLDragShapesInInfo\n\t): void {\n\t\tconst { editor } = this\n\n\t\tif (draggingShapes.every((s) => s.parentId === shape.id)) return\n\n\t\t// Check to see whether any of the shapes can have their old index restored\n\t\tlet canRestoreOriginalIndices = false\n\t\tconst previousChildren = draggingShapes.filter(\n\t\t\t(s) => shape.id === (initialParentIds.get(s.id) as TLShapeId)\n\t\t)\n\n\t\tif (previousChildren.length > 0) {\n\t\t\tconst currentChildren = compact(\n\t\t\t\teditor.getSortedChildIdsForParent(shape).map((id) => editor.getShape(id))\n\t\t\t)\n\t\t\tif (previousChildren.every((s) => !currentChildren.find((c) => c.index === s.index))) {\n\t\t\t\tcanRestoreOriginalIndices = true\n\t\t\t}\n\t\t}\n\n\t\t// If any of the children are the ancestor of the frame, quit here\n\t\tif (draggingShapes.some((s) => editor.hasAncestor(shape, s.id))) return\n\n\t\teditor.reparentShapes(draggingShapes, shape.id)\n\n\t\tif (canRestoreOriginalIndices) {\n\t\t\tfor (const s of previousChildren) {\n\t\t\t\teditor.updateShape({\n\t\t\t\t\tid: s.id,\n\t\t\t\t\ttype: s.type,\n\t\t\t\t\tindex: initialIndices.get(s.id) as IndexKey,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\toverride onDragShapesOut(\n\t\tshape: Shape,\n\t\tdraggingShapes: TLShape[],\n\t\tinfo: TLDragShapesOutInfo\n\t): void {\n\t\tconst { editor } = this\n\t\t// When a user drags shapes out and we're not dragging into a new shape,\n\t\t// reparent the dragging shapes onto the current page instead\n\t\tif (!info.nextDraggingOverShapeId) {\n\t\t\t// Locked shapes are already filtered out upstream by DragAndDropManager.\n\t\t\teditor.reparentShapes(\n\t\t\t\tdraggingShapes.filter((s) => s.parentId === shape.id),\n\t\t\t\teditor.getCurrentPageId()\n\t\t\t)\n\t\t}\n\t}\n}\n"],
5
- "mappings": "AACA,SAAmB,eAAe;AAElC,SAAS,wBAAwC;AA8C1C,MAAe,+BAEZ,iBAAwB;AAAA,EACxB,YAAY,QAAwB;AAC5C,WAAO;AAAA,EACR;AAAA,EAES,gCAAyC;AACjD,WAAO;AAAA,EACR;AAAA,EAES,4BAA4B,OAAc,OAAiC;AACnF,WAAO,CAAC,MAAM;AAAA,EACf;AAAA,EAES,wBAAwB,OAAc,OAAiC;AAC/E,WAAO,CAAC,MAAM;AAAA,EACf;AAAA,EAES,YAAY,OAAiC;AACrD,WAAO,KAAK,OAAO,iBAAiB,MAAM,EAAE,EAAE;AAAA,EAC/C;AAAA,EAES,gBAAgB,OAAyB;AACjD,WAAO,MAAM,SAAS;AAAA,EACvB;AAAA,EAES,eACR,OACA,gBACA,EAAE,kBAAkB,eAAe,GAC5B;AACP,UAAM,EAAE,OAAO,IAAI;AAEnB,QAAI,eAAe,MAAM,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE,EAAG;AAG1D,QAAI,4BAA4B;AAChC,UAAM,mBAAmB,eAAe;AAAA,MACvC,CAAC,MAAM,MAAM,OAAQ,iBAAiB,IAAI,EAAE,EAAE;AAAA,IAC/C;AAEA,QAAI,iBAAiB,SAAS,GAAG;AAChC,YAAM,kBAAkB;AAAA,QACvB,OAAO,2BAA2B,KAAK,EAAE,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC;AAAA,MACzE;AACA,UAAI,iBAAiB,MAAM,CAAC,MAAM,CAAC,gBAAgB,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG;AACrF,oCAA4B;AAAA,MAC7B;AAAA,IACD;AAGA,QAAI,eAAe,KAAK,CAAC,MAAM,OAAO,YAAY,OAAO,EAAE,EAAE,CAAC,EAAG;AAEjE,WAAO,eAAe,gBAAgB,MAAM,EAAE;AAE9C,QAAI,2BAA2B;AAC9B,iBAAW,KAAK,kBAAkB;AACjC,eAAO,YAAY;AAAA,UAClB,IAAI,EAAE;AAAA,UACN,MAAM,EAAE;AAAA,UACR,OAAO,eAAe,IAAI,EAAE,EAAE;AAAA,QAC/B,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAAA,EAES,gBACR,OACA,gBACA,MACO;AACP,UAAM,EAAE,OAAO,IAAI;AAGnB,QAAI,CAAC,KAAK,yBAAyB;AAElC,aAAO;AAAA,QACN,eAAe,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAAA,QACpD,OAAO,iBAAiB;AAAA,MACzB;AAAA,IACD;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["import { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact } from '@tldraw/utils'\nimport { Vec } from '../../primitives/Vec'\nimport { BaseBoxShapeUtil, TLBaseBoxShape } from './BaseBoxShapeUtil'\nimport { TLDragShapesInInfo, TLDragShapesOutInfo } from './ShapeUtil'\n\n/**\n * A base class for frame-like shapes \u2014 containers that clip their children except arrows,\n * require full-brush selection, block erasure from inside, and support\n * drag-and-drop reparenting.\n *\n * Extending this class is the easiest way to create a custom frame-like shape.\n * It provides sensible defaults for all frame-like behaviors:\n *\n * - `isFrameLike()` returns `true`\n * - `providesBackgroundForChildren()` returns `true`\n * - `canReceiveNewChildrenOfType()` returns `true` unless the container is locked\n * - `canRemoveChildrenOfType()` returns `true` unless the container is locked\n * - `getClipPath()` returns the shape geometry's vertices\n * - `shouldClipChild()` clips all children except arrows\n * - `onDragShapesIn()` reparents shapes into the frame (with index restoration)\n * - `onDragShapesOut()` reparents shapes back to the page\n *\n * All methods can be overridden for custom behavior.\n *\n * @example\n * ```ts\n * class MyContainerUtil extends BaseFrameLikeShapeUtil<MyContainerShape> {\n * static override type = 'my-container' as const\n * static override props = myContainerShapeProps\n *\n * override getDefaultProps() {\n * return { w: 300, h: 200 }\n * }\n *\n * override component(shape: MyContainerShape) {\n * return <SVGContainer>...</SVGContainer>\n * }\n *\n * override getIndicatorPath(shape: MyContainerShape) {\n * const path = new Path2D()\n * path.rect(0, 0, shape.props.w, shape.props.h)\n * return path\n * }\n * }\n * ```\n *\n * @public\n */\nexport abstract class BaseFrameLikeShapeUtil<\n\tShape extends TLBaseBoxShape,\n> extends BaseBoxShapeUtil<Shape> {\n\toverride isFrameLike(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\toverride providesBackgroundForChildren(): boolean {\n\t\treturn true\n\t}\n\n\toverride canReceiveNewChildrenOfType(shape: Shape, _type: TLShape['type']): boolean {\n\t\treturn !shape.isLocked\n\t}\n\n\toverride canRemoveChildrenOfType(shape: Shape, _type: TLShape['type']): boolean {\n\t\treturn !shape.isLocked\n\t}\n\n\toverride getClipPath(shape: Shape): Vec[] | undefined {\n\t\treturn this.editor.getShapeGeometry(shape.id).vertices\n\t}\n\n\toverride shouldClipChild(child: TLShape): boolean {\n\t\treturn child.type !== 'arrow'\n\t}\n\n\toverride onDragShapesIn(\n\t\tshape: Shape,\n\t\tdraggingShapes: TLShape[],\n\t\t{ initialParentIds, initialIndices }: TLDragShapesInInfo\n\t): void {\n\t\tconst { editor } = this\n\n\t\tif (draggingShapes.every((s) => s.parentId === shape.id)) return\n\n\t\t// Check to see whether any of the shapes can have their old index restored\n\t\tlet canRestoreOriginalIndices = false\n\t\tconst previousChildren = draggingShapes.filter(\n\t\t\t(s) => shape.id === (initialParentIds.get(s.id) as TLShapeId)\n\t\t)\n\n\t\tif (previousChildren.length > 0) {\n\t\t\tconst currentChildren = compact(\n\t\t\t\teditor.getSortedChildIdsForParent(shape).map((id) => editor.getShape(id))\n\t\t\t)\n\t\t\tif (\n\t\t\t\tpreviousChildren.every(\n\t\t\t\t\t(s) => !currentChildren.find((c) => c.index === initialIndices.get(s.id))\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcanRestoreOriginalIndices = true\n\t\t\t}\n\t\t}\n\n\t\t// If any of the children are the ancestor of the frame, quit here\n\t\tif (draggingShapes.some((s) => editor.hasAncestor(shape, s.id))) return\n\n\t\teditor.reparentShapes(draggingShapes, shape.id)\n\n\t\tif (canRestoreOriginalIndices) {\n\t\t\tfor (const s of previousChildren) {\n\t\t\t\teditor.updateShape({\n\t\t\t\t\tid: s.id,\n\t\t\t\t\ttype: s.type,\n\t\t\t\t\tindex: initialIndices.get(s.id) as IndexKey,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\toverride onDragShapesOut(\n\t\tshape: Shape,\n\t\tdraggingShapes: TLShape[],\n\t\tinfo: TLDragShapesOutInfo\n\t): void {\n\t\tconst { editor } = this\n\t\t// When a user drags shapes out and we're not dragging into a new shape,\n\t\t// reparent the dragging shapes onto the current page instead\n\t\tif (!info.nextDraggingOverShapeId) {\n\t\t\t// Locked shapes are already filtered out upstream by DragAndDropManager.\n\t\t\teditor.reparentShapes(\n\t\t\t\tdraggingShapes.filter((s) => s.parentId === shape.id),\n\t\t\t\teditor.getCurrentPageId()\n\t\t\t)\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "AACA,SAAmB,eAAe;AAElC,SAAS,wBAAwC;AA8C1C,MAAe,+BAEZ,iBAAwB;AAAA,EACxB,YAAY,QAAwB;AAC5C,WAAO;AAAA,EACR;AAAA,EAES,gCAAyC;AACjD,WAAO;AAAA,EACR;AAAA,EAES,4BAA4B,OAAc,OAAiC;AACnF,WAAO,CAAC,MAAM;AAAA,EACf;AAAA,EAES,wBAAwB,OAAc,OAAiC;AAC/E,WAAO,CAAC,MAAM;AAAA,EACf;AAAA,EAES,YAAY,OAAiC;AACrD,WAAO,KAAK,OAAO,iBAAiB,MAAM,EAAE,EAAE;AAAA,EAC/C;AAAA,EAES,gBAAgB,OAAyB;AACjD,WAAO,MAAM,SAAS;AAAA,EACvB;AAAA,EAES,eACR,OACA,gBACA,EAAE,kBAAkB,eAAe,GAC5B;AACP,UAAM,EAAE,OAAO,IAAI;AAEnB,QAAI,eAAe,MAAM,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE,EAAG;AAG1D,QAAI,4BAA4B;AAChC,UAAM,mBAAmB,eAAe;AAAA,MACvC,CAAC,MAAM,MAAM,OAAQ,iBAAiB,IAAI,EAAE,EAAE;AAAA,IAC/C;AAEA,QAAI,iBAAiB,SAAS,GAAG;AAChC,YAAM,kBAAkB;AAAA,QACvB,OAAO,2BAA2B,KAAK,EAAE,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC;AAAA,MACzE;AACA,UACC,iBAAiB;AAAA,QAChB,CAAC,MAAM,CAAC,gBAAgB,KAAK,CAAC,MAAM,EAAE,UAAU,eAAe,IAAI,EAAE,EAAE,CAAC;AAAA,MACzE,GACC;AACD,oCAA4B;AAAA,MAC7B;AAAA,IACD;AAGA,QAAI,eAAe,KAAK,CAAC,MAAM,OAAO,YAAY,OAAO,EAAE,EAAE,CAAC,EAAG;AAEjE,WAAO,eAAe,gBAAgB,MAAM,EAAE;AAE9C,QAAI,2BAA2B;AAC9B,iBAAW,KAAK,kBAAkB;AACjC,eAAO,YAAY;AAAA,UAClB,IAAI,EAAE;AAAA,UACN,MAAM,EAAE;AAAA,UACR,OAAO,eAAe,IAAI,EAAE,EAAE;AAAA,QAC/B,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAAA,EAES,gBACR,OACA,gBACA,MACO;AACP,UAAM,EAAE,OAAO,IAAI;AAGnB,QAAI,CAAC,KAAK,yBAAyB;AAElC,aAAO;AAAA,QACN,eAAe,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAAA,QACpD,OAAO,iBAAiB;AAAA,MACzB;AAAA,IACD;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -59,6 +59,7 @@ function getPerfectDashProps(totalLength, strokeWidth, opts = {}) {
59
59
  }
60
60
  dashCount = Math.floor(totalLength / dashLength / (2 * ratio));
61
61
  dashCount -= dashCount % snap;
62
+ if (!(dashCount >= 1)) dashCount = 1;
62
63
  if (dashCount < 3 && style === "dashed") {
63
64
  if (totalLength / strokeWidth < 4) {
64
65
  dashLength = totalLength;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/shapes/shared/getPerfectDashProps.ts"],
4
- "sourcesContent": ["import { TLDefaultDashStyle } from '@tldraw/tlschema'\n\n/** @public */\nexport type PerfectDashTerminal = 'skip' | 'outset' | 'none'\n\n/** @public */\nexport function getPerfectDashProps(\n\ttotalLength: number,\n\tstrokeWidth: number,\n\topts: {\n\t\tstyle?: TLDefaultDashStyle\n\t\tsnap?: number\n\t\tend?: PerfectDashTerminal\n\t\tstart?: PerfectDashTerminal\n\t\tlengthRatio?: number\n\t\tclosed?: boolean\n\t\tforceSolid?: boolean\n\t} = {}\n): {\n\tstrokeDasharray: string\n\tstrokeDashoffset: string\n} {\n\tconst {\n\t\tclosed = false,\n\t\tsnap = 1,\n\t\tstart = 'outset',\n\t\tend = 'outset',\n\t\tlengthRatio = 2,\n\t\tstyle = 'dashed',\n\t\tforceSolid = false,\n\t} = opts\n\n\tlet dashLength = 0\n\tlet dashCount = 0\n\tlet ratio = 1\n\tlet gapLength = 0\n\tlet strokeDashoffset = 0\n\n\tif (forceSolid) {\n\t\treturn {\n\t\t\tstrokeDasharray: 'none',\n\t\t\tstrokeDashoffset: 'none',\n\t\t}\n\t}\n\n\tif (style === 'none') {\n\t\treturn {\n\t\t\tstrokeDasharray: 'none',\n\t\t\tstrokeDashoffset: 'none',\n\t\t}\n\t}\n\n\tswitch (style) {\n\t\tcase 'dashed': {\n\t\t\tratio = 1\n\t\t\tdashLength = Math.min(strokeWidth * lengthRatio, totalLength / 4)\n\t\t\tbreak\n\t\t}\n\t\tcase 'dotted': {\n\t\t\tratio = 100\n\t\t\tdashLength = strokeWidth / ratio\n\t\t\tbreak\n\t\t}\n\t\tdefault: {\n\t\t\treturn {\n\t\t\t\tstrokeDasharray: 'none',\n\t\t\t\tstrokeDashoffset: 'none',\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!closed) {\n\t\tif (start === 'outset') {\n\t\t\ttotalLength += dashLength / 2\n\t\t\tstrokeDashoffset += dashLength / 2\n\t\t} else if (start === 'skip') {\n\t\t\ttotalLength -= dashLength\n\t\t\tstrokeDashoffset -= dashLength\n\t\t}\n\n\t\tif (end === 'outset') {\n\t\t\ttotalLength += dashLength / 2\n\t\t} else if (end === 'skip') {\n\t\t\ttotalLength -= dashLength\n\t\t}\n\t}\n\n\tdashCount = Math.floor(totalLength / dashLength / (2 * ratio))\n\tdashCount -= dashCount % snap\n\n\tif (dashCount < 3 && style === 'dashed') {\n\t\tif (totalLength / strokeWidth < 4) {\n\t\t\tdashLength = totalLength\n\t\t\tdashCount = 1\n\t\t\tgapLength = 0\n\t\t} else {\n\t\t\tdashLength = totalLength * (1 / 3)\n\t\t\tgapLength = totalLength * (1 / 3)\n\t\t}\n\t} else {\n\t\tdashLength = totalLength / dashCount / (2 * ratio)\n\n\t\tif (closed) {\n\t\t\tstrokeDashoffset = dashLength / 2\n\t\t\tgapLength = (totalLength - dashCount * dashLength) / dashCount\n\t\t} else {\n\t\t\tgapLength = (totalLength - dashCount * dashLength) / Math.max(1, dashCount - 1)\n\t\t}\n\t}\n\n\treturn {\n\t\tstrokeDasharray: [dashLength, gapLength].join(' '),\n\t\tstrokeDashoffset: strokeDashoffset.toString(),\n\t}\n}\n"],
5
- "mappings": "AAMO,SAAS,oBACf,aACA,aACA,OAQI,CAAC,GAIJ;AACD,QAAM;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,aAAa;AAAA,EACd,IAAI;AAEJ,MAAI,aAAa;AACjB,MAAI,YAAY;AAChB,MAAI,QAAQ;AACZ,MAAI,YAAY;AAChB,MAAI,mBAAmB;AAEvB,MAAI,YAAY;AACf,WAAO;AAAA,MACN,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACnB;AAAA,EACD;AAEA,MAAI,UAAU,QAAQ;AACrB,WAAO;AAAA,MACN,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACnB;AAAA,EACD;AAEA,UAAQ,OAAO;AAAA,IACd,KAAK,UAAU;AACd,cAAQ;AACR,mBAAa,KAAK,IAAI,cAAc,aAAa,cAAc,CAAC;AAChE;AAAA,IACD;AAAA,IACA,KAAK,UAAU;AACd,cAAQ;AACR,mBAAa,cAAc;AAC3B;AAAA,IACD;AAAA,IACA,SAAS;AACR,aAAO;AAAA,QACN,iBAAiB;AAAA,QACjB,kBAAkB;AAAA,MACnB;AAAA,IACD;AAAA,EACD;AAEA,MAAI,CAAC,QAAQ;AACZ,QAAI,UAAU,UAAU;AACvB,qBAAe,aAAa;AAC5B,0BAAoB,aAAa;AAAA,IAClC,WAAW,UAAU,QAAQ;AAC5B,qBAAe;AACf,0BAAoB;AAAA,IACrB;AAEA,QAAI,QAAQ,UAAU;AACrB,qBAAe,aAAa;AAAA,IAC7B,WAAW,QAAQ,QAAQ;AAC1B,qBAAe;AAAA,IAChB;AAAA,EACD;AAEA,cAAY,KAAK,MAAM,cAAc,cAAc,IAAI,MAAM;AAC7D,eAAa,YAAY;AAEzB,MAAI,YAAY,KAAK,UAAU,UAAU;AACxC,QAAI,cAAc,cAAc,GAAG;AAClC,mBAAa;AACb,kBAAY;AACZ,kBAAY;AAAA,IACb,OAAO;AACN,mBAAa,eAAe,IAAI;AAChC,kBAAY,eAAe,IAAI;AAAA,IAChC;AAAA,EACD,OAAO;AACN,iBAAa,cAAc,aAAa,IAAI;AAE5C,QAAI,QAAQ;AACX,yBAAmB,aAAa;AAChC,mBAAa,cAAc,YAAY,cAAc;AAAA,IACtD,OAAO;AACN,mBAAa,cAAc,YAAY,cAAc,KAAK,IAAI,GAAG,YAAY,CAAC;AAAA,IAC/E;AAAA,EACD;AAEA,SAAO;AAAA,IACN,iBAAiB,CAAC,YAAY,SAAS,EAAE,KAAK,GAAG;AAAA,IACjD,kBAAkB,iBAAiB,SAAS;AAAA,EAC7C;AACD;",
4
+ "sourcesContent": ["import { TLDefaultDashStyle } from '@tldraw/tlschema'\n\n/** @public */\nexport type PerfectDashTerminal = 'skip' | 'outset' | 'none'\n\n/** @public */\nexport function getPerfectDashProps(\n\ttotalLength: number,\n\tstrokeWidth: number,\n\topts: {\n\t\tstyle?: TLDefaultDashStyle\n\t\tsnap?: number\n\t\tend?: PerfectDashTerminal\n\t\tstart?: PerfectDashTerminal\n\t\tlengthRatio?: number\n\t\tclosed?: boolean\n\t\tforceSolid?: boolean\n\t} = {}\n): {\n\tstrokeDasharray: string\n\tstrokeDashoffset: string\n} {\n\tconst {\n\t\tclosed = false,\n\t\tsnap = 1,\n\t\tstart = 'outset',\n\t\tend = 'outset',\n\t\tlengthRatio = 2,\n\t\tstyle = 'dashed',\n\t\tforceSolid = false,\n\t} = opts\n\n\tlet dashLength = 0\n\tlet dashCount = 0\n\tlet ratio = 1\n\tlet gapLength = 0\n\tlet strokeDashoffset = 0\n\n\tif (forceSolid) {\n\t\treturn {\n\t\t\tstrokeDasharray: 'none',\n\t\t\tstrokeDashoffset: 'none',\n\t\t}\n\t}\n\n\tif (style === 'none') {\n\t\treturn {\n\t\t\tstrokeDasharray: 'none',\n\t\t\tstrokeDashoffset: 'none',\n\t\t}\n\t}\n\n\tswitch (style) {\n\t\tcase 'dashed': {\n\t\t\tratio = 1\n\t\t\tdashLength = Math.min(strokeWidth * lengthRatio, totalLength / 4)\n\t\t\tbreak\n\t\t}\n\t\tcase 'dotted': {\n\t\t\tratio = 100\n\t\t\tdashLength = strokeWidth / ratio\n\t\t\tbreak\n\t\t}\n\t\tdefault: {\n\t\t\treturn {\n\t\t\t\tstrokeDasharray: 'none',\n\t\t\t\tstrokeDashoffset: 'none',\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!closed) {\n\t\tif (start === 'outset') {\n\t\t\ttotalLength += dashLength / 2\n\t\t\tstrokeDashoffset += dashLength / 2\n\t\t} else if (start === 'skip') {\n\t\t\ttotalLength -= dashLength\n\t\t\tstrokeDashoffset -= dashLength\n\t\t}\n\n\t\tif (end === 'outset') {\n\t\t\ttotalLength += dashLength / 2\n\t\t} else if (end === 'skip') {\n\t\t\ttotalLength -= dashLength\n\t\t}\n\t}\n\n\tdashCount = Math.floor(totalLength / dashLength / (2 * ratio))\n\tdashCount -= dashCount % snap\n\t// A path shorter than one dash period (or of zero length, where the count is NaN) would\n\t// otherwise divide by zero below and produce an invalid dasharray; draw a single dash instead\n\tif (!(dashCount >= 1)) dashCount = 1\n\n\tif (dashCount < 3 && style === 'dashed') {\n\t\tif (totalLength / strokeWidth < 4) {\n\t\t\tdashLength = totalLength\n\t\t\tdashCount = 1\n\t\t\tgapLength = 0\n\t\t} else {\n\t\t\tdashLength = totalLength * (1 / 3)\n\t\t\tgapLength = totalLength * (1 / 3)\n\t\t}\n\t} else {\n\t\tdashLength = totalLength / dashCount / (2 * ratio)\n\n\t\tif (closed) {\n\t\t\tstrokeDashoffset = dashLength / 2\n\t\t\tgapLength = (totalLength - dashCount * dashLength) / dashCount\n\t\t} else {\n\t\t\tgapLength = (totalLength - dashCount * dashLength) / Math.max(1, dashCount - 1)\n\t\t}\n\t}\n\n\treturn {\n\t\tstrokeDasharray: [dashLength, gapLength].join(' '),\n\t\tstrokeDashoffset: strokeDashoffset.toString(),\n\t}\n}\n"],
5
+ "mappings": "AAMO,SAAS,oBACf,aACA,aACA,OAQI,CAAC,GAIJ;AACD,QAAM;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,aAAa;AAAA,EACd,IAAI;AAEJ,MAAI,aAAa;AACjB,MAAI,YAAY;AAChB,MAAI,QAAQ;AACZ,MAAI,YAAY;AAChB,MAAI,mBAAmB;AAEvB,MAAI,YAAY;AACf,WAAO;AAAA,MACN,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACnB;AAAA,EACD;AAEA,MAAI,UAAU,QAAQ;AACrB,WAAO;AAAA,MACN,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACnB;AAAA,EACD;AAEA,UAAQ,OAAO;AAAA,IACd,KAAK,UAAU;AACd,cAAQ;AACR,mBAAa,KAAK,IAAI,cAAc,aAAa,cAAc,CAAC;AAChE;AAAA,IACD;AAAA,IACA,KAAK,UAAU;AACd,cAAQ;AACR,mBAAa,cAAc;AAC3B;AAAA,IACD;AAAA,IACA,SAAS;AACR,aAAO;AAAA,QACN,iBAAiB;AAAA,QACjB,kBAAkB;AAAA,MACnB;AAAA,IACD;AAAA,EACD;AAEA,MAAI,CAAC,QAAQ;AACZ,QAAI,UAAU,UAAU;AACvB,qBAAe,aAAa;AAC5B,0BAAoB,aAAa;AAAA,IAClC,WAAW,UAAU,QAAQ;AAC5B,qBAAe;AACf,0BAAoB;AAAA,IACrB;AAEA,QAAI,QAAQ,UAAU;AACrB,qBAAe,aAAa;AAAA,IAC7B,WAAW,QAAQ,QAAQ;AAC1B,qBAAe;AAAA,IAChB;AAAA,EACD;AAEA,cAAY,KAAK,MAAM,cAAc,cAAc,IAAI,MAAM;AAC7D,eAAa,YAAY;AAGzB,MAAI,EAAE,aAAa,GAAI,aAAY;AAEnC,MAAI,YAAY,KAAK,UAAU,UAAU;AACxC,QAAI,cAAc,cAAc,GAAG;AAClC,mBAAa;AACb,kBAAY;AACZ,kBAAY;AAAA,IACb,OAAO;AACN,mBAAa,eAAe,IAAI;AAChC,kBAAY,eAAe,IAAI;AAAA,IAChC;AAAA,EACD,OAAO;AACN,iBAAa,cAAc,aAAa,IAAI;AAE5C,QAAI,QAAQ;AACX,yBAAmB,aAAa;AAChC,mBAAa,cAAc,YAAY,cAAc;AAAA,IACtD,OAAO;AACN,mBAAa,cAAc,YAAY,cAAc,KAAK,IAAI,GAAG,YAAY,CAAC;AAAA,IAC/E;AAAA,EACD;AAEA,SAAO;AAAA,IACN,iBAAiB,CAAC,YAAY,SAAS,EAAE,KAAK,GAAG;AAAA,IACjD,kBAAkB,iBAAiB,SAAS;AAAA,EAC7C;AACD;",
6
6
  "names": []
7
7
  }
@@ -57,7 +57,7 @@ class Pointing extends StateNode {
57
57
  this.cancel();
58
58
  }
59
59
  onComplete() {
60
- this.complete();
60
+ this.cancel();
61
61
  }
62
62
  onInterrupt() {
63
63
  this.cancel();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts"],
4
- "sourcesContent": ["import { TLShape, createShapeId } from '@tldraw/tlschema'\nimport { structuredClone } from '@tldraw/utils'\nimport { Vec } from '../../../../primitives/Vec'\nimport type { Editor } from '../../../Editor'\nimport { TLBaseBoxShape } from '../../../shapes/BaseBoxShapeUtil'\nimport { TLPointerEventInfo } from '../../../types/event-types'\nimport { StateNode } from '../../StateNode'\nimport type { BaseBoxShapeTool } from '../BaseBoxShapeTool'\n\nexport class Pointing extends StateNode {\n\tstatic override id = 'pointing'\n\n\toverride onPointerMove(info: TLPointerEventInfo) {\n\t\tconst { editor } = this\n\t\tif (editor.inputs.getIsDragging()) {\n\t\t\tconst originPagePoint = editor.inputs.getOriginPagePoint()\n\n\t\t\tconst shapeType = (this.parent as BaseBoxShapeTool)!.shapeType\n\n\t\t\tconst id = createShapeId()\n\n\t\t\tconst creatingMarkId = editor.markHistoryStoppingPoint(`creating_box:${id}`)\n\t\t\tconst newPoint = maybeSnapToGrid(originPagePoint, editor)\n\n\t\t\t// Allow this to trigger the max shapes reached alert\n\t\t\tthis.editor.createShapes([\n\t\t\t\t{\n\t\t\t\t\tid,\n\t\t\t\t\ttype: shapeType,\n\t\t\t\t\tx: newPoint.x,\n\t\t\t\t\ty: newPoint.y,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tw: 1,\n\t\t\t\t\t\th: 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t])\n\t\t\tconst shape = editor.getShape(id)\n\t\t\tif (!shape) {\n\t\t\t\tthis.cancel()\n\t\t\t\treturn\n\t\t\t}\n\t\t\teditor.select(id)\n\n\t\t\tconst parent = this.parent as BaseBoxShapeTool\n\t\t\tthis.editor.setCurrentTool(\n\t\t\t\t'select.resizing',\n\t\t\t\t{\n\t\t\t\t\t...info,\n\t\t\t\t\ttarget: 'selection',\n\t\t\t\t\thandle: 'bottom_right',\n\t\t\t\t\tisCreating: true,\n\t\t\t\t\tcreatingMarkId,\n\t\t\t\t\tcreationCursorOffset: { x: 1, y: 1 },\n\t\t\t\t\tonInteractionEnd: this.parent.id,\n\t\t\t\t\tonCreate: parent.onCreate\n\t\t\t\t\t\t? (shape: TLShape | null) => parent.onCreate?.(shape)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t} /** satisfies ResizingInfo, defined in main tldraw package \uD83D\uDE27 */\n\t\t\t)\n\t\t}\n\t}\n\n\toverride onPointerUp() {\n\t\tthis.complete()\n\t}\n\n\toverride onLongPress() {\n\t\t// On a touch (coarse pointer) long-press, cancel the pending shape so it leaves nothing behind.\n\t\tif (this.editor.getInstanceState().isCoarsePointer) this.cancel()\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onComplete() {\n\t\tthis.complete()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\tcomplete() {\n\t\tconst originPagePoint = this.editor.inputs.getOriginPagePoint()\n\n\t\tconst shapeType = (this.parent as BaseBoxShapeTool)!.shapeType as TLBaseBoxShape['type']\n\n\t\tconst id = createShapeId()\n\n\t\tthis.editor.markHistoryStoppingPoint(`creating_box:${id}`)\n\n\t\t// Allow this to trigger the max shapes reached alert\n\t\t// todo: add scale here when dynamic size is enabled (is this still needed?)\n\t\tthis.editor.createShapes([\n\t\t\t{\n\t\t\t\tid,\n\t\t\t\ttype: shapeType,\n\t\t\t\tx: originPagePoint.x,\n\t\t\t\ty: originPagePoint.y,\n\t\t\t},\n\t\t])\n\n\t\tconst shape = this.editor.getShape<TLBaseBoxShape>(id)!\n\t\tif (!shape) {\n\t\t\tthis.cancel()\n\t\t\treturn\n\t\t}\n\n\t\tlet { w, h } = shape.props\n\t\tconst delta = new Vec(w / 2, h / 2)\n\t\tconst parentTransform = this.editor.getShapeParentTransform(shape)\n\t\tif (parentTransform) delta.rot(-parentTransform.rotation())\n\t\tconst scale = this.editor.getResizeScaleFactor()\n\n\t\t// A scale factor of 1 means dynamic sizing is not affecting this shape.\n\t\tif (scale !== 1) {\n\t\t\tw *= scale\n\t\t\th *= scale\n\t\t\tdelta.mul(scale)\n\t\t}\n\n\t\tconst next = structuredClone(shape)\n\t\tconst newPoint = maybeSnapToGrid(new Vec(shape.x - delta.x, shape.y - delta.y), this.editor)\n\t\tnext.x = newPoint.x\n\t\tnext.y = newPoint.y\n\t\tnext.props.w = w\n\t\tnext.props.h = h\n\n\t\tif ('scale' in shape.props) {\n\t\t\t;(next as TLBaseBoxShape & { props: { scale: number } }).props.scale = scale\n\t\t}\n\n\t\tthis.editor.updateShape(next)\n\n\t\tthis.editor.setSelectedShapes([id])\n\n\t\tif (this.editor.getInstanceState().isToolLocked) {\n\t\t\tthis.parent.transition('idle')\n\t\t} else {\n\t\t\tthis.editor.setCurrentTool('select.idle')\n\t\t}\n\t}\n\n\tcancel() {\n\t\tthis.parent.transition('idle')\n\t}\n}\n\n/**\n * Checks if grid mode is enabled and snaps a point to the grid if so\n *\n * @public\n */\nexport function maybeSnapToGrid(point: Vec, editor: Editor): Vec {\n\tconst isGridMode = editor.getInstanceState().isGridMode\n\tconst gridSize = editor.getDocumentSettings().gridSize\n\tif (isGridMode) return point.clone().snapToGrid(gridSize)\n\treturn point.clone()\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { TLShape, createShapeId } from '@tldraw/tlschema'\nimport { structuredClone } from '@tldraw/utils'\nimport { Vec } from '../../../../primitives/Vec'\nimport type { Editor } from '../../../Editor'\nimport { TLBaseBoxShape } from '../../../shapes/BaseBoxShapeUtil'\nimport { TLPointerEventInfo } from '../../../types/event-types'\nimport { StateNode } from '../../StateNode'\nimport type { BaseBoxShapeTool } from '../BaseBoxShapeTool'\n\nexport class Pointing extends StateNode {\n\tstatic override id = 'pointing'\n\n\toverride onPointerMove(info: TLPointerEventInfo) {\n\t\tconst { editor } = this\n\t\tif (editor.inputs.getIsDragging()) {\n\t\t\tconst originPagePoint = editor.inputs.getOriginPagePoint()\n\n\t\t\tconst shapeType = (this.parent as BaseBoxShapeTool)!.shapeType\n\n\t\t\tconst id = createShapeId()\n\n\t\t\tconst creatingMarkId = editor.markHistoryStoppingPoint(`creating_box:${id}`)\n\t\t\tconst newPoint = maybeSnapToGrid(originPagePoint, editor)\n\n\t\t\t// Allow this to trigger the max shapes reached alert\n\t\t\tthis.editor.createShapes([\n\t\t\t\t{\n\t\t\t\t\tid,\n\t\t\t\t\ttype: shapeType,\n\t\t\t\t\tx: newPoint.x,\n\t\t\t\t\ty: newPoint.y,\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tw: 1,\n\t\t\t\t\t\th: 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t])\n\t\t\tconst shape = editor.getShape(id)\n\t\t\tif (!shape) {\n\t\t\t\tthis.cancel()\n\t\t\t\treturn\n\t\t\t}\n\t\t\teditor.select(id)\n\n\t\t\tconst parent = this.parent as BaseBoxShapeTool\n\t\t\tthis.editor.setCurrentTool(\n\t\t\t\t'select.resizing',\n\t\t\t\t{\n\t\t\t\t\t...info,\n\t\t\t\t\ttarget: 'selection',\n\t\t\t\t\thandle: 'bottom_right',\n\t\t\t\t\tisCreating: true,\n\t\t\t\t\tcreatingMarkId,\n\t\t\t\t\tcreationCursorOffset: { x: 1, y: 1 },\n\t\t\t\t\tonInteractionEnd: this.parent.id,\n\t\t\t\t\tonCreate: parent.onCreate\n\t\t\t\t\t\t? (shape: TLShape | null) => parent.onCreate?.(shape)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t} /** satisfies ResizingInfo, defined in main tldraw package \uD83D\uDE27 */\n\t\t\t)\n\t\t}\n\t}\n\n\toverride onPointerUp() {\n\t\tthis.complete()\n\t}\n\n\toverride onLongPress() {\n\t\t// On a touch (coarse pointer) long-press, cancel the pending shape so it leaves nothing behind.\n\t\tif (this.editor.getInstanceState().isCoarsePointer) this.cancel()\n\t}\n\n\toverride onCancel() {\n\t\tthis.cancel()\n\t}\n\n\toverride onComplete() {\n\t\t// `complete` arrives mid-press from undo, menus opening, page changes, and focus loss,\n\t\t// none of which is a release; committing here would drop a default-size shape the user\n\t\t// never asked for. Match the text and arrow tools and leave nothing behind.\n\t\tthis.cancel()\n\t}\n\n\toverride onInterrupt() {\n\t\tthis.cancel()\n\t}\n\n\tcomplete() {\n\t\tconst originPagePoint = this.editor.inputs.getOriginPagePoint()\n\n\t\tconst shapeType = (this.parent as BaseBoxShapeTool)!.shapeType as TLBaseBoxShape['type']\n\n\t\tconst id = createShapeId()\n\n\t\tthis.editor.markHistoryStoppingPoint(`creating_box:${id}`)\n\n\t\t// Allow this to trigger the max shapes reached alert\n\t\t// todo: add scale here when dynamic size is enabled (is this still needed?)\n\t\tthis.editor.createShapes([\n\t\t\t{\n\t\t\t\tid,\n\t\t\t\ttype: shapeType,\n\t\t\t\tx: originPagePoint.x,\n\t\t\t\ty: originPagePoint.y,\n\t\t\t},\n\t\t])\n\n\t\tconst shape = this.editor.getShape<TLBaseBoxShape>(id)!\n\t\tif (!shape) {\n\t\t\tthis.cancel()\n\t\t\treturn\n\t\t}\n\n\t\tlet { w, h } = shape.props\n\t\tconst delta = new Vec(w / 2, h / 2)\n\t\tconst parentTransform = this.editor.getShapeParentTransform(shape)\n\t\tif (parentTransform) delta.rot(-parentTransform.rotation())\n\t\tconst scale = this.editor.getResizeScaleFactor()\n\n\t\t// A scale factor of 1 means dynamic sizing is not affecting this shape.\n\t\tif (scale !== 1) {\n\t\t\tw *= scale\n\t\t\th *= scale\n\t\t\tdelta.mul(scale)\n\t\t}\n\n\t\tconst next = structuredClone(shape)\n\t\tconst newPoint = maybeSnapToGrid(new Vec(shape.x - delta.x, shape.y - delta.y), this.editor)\n\t\tnext.x = newPoint.x\n\t\tnext.y = newPoint.y\n\t\tnext.props.w = w\n\t\tnext.props.h = h\n\n\t\tif ('scale' in shape.props) {\n\t\t\t;(next as TLBaseBoxShape & { props: { scale: number } }).props.scale = scale\n\t\t}\n\n\t\tthis.editor.updateShape(next)\n\n\t\tthis.editor.setSelectedShapes([id])\n\n\t\tif (this.editor.getInstanceState().isToolLocked) {\n\t\t\tthis.parent.transition('idle')\n\t\t} else {\n\t\t\tthis.editor.setCurrentTool('select.idle')\n\t\t}\n\t}\n\n\tcancel() {\n\t\tthis.parent.transition('idle')\n\t}\n}\n\n/**\n * Checks if grid mode is enabled and snaps a point to the grid if so\n *\n * @public\n */\nexport function maybeSnapToGrid(point: Vec, editor: Editor): Vec {\n\tconst isGridMode = editor.getInstanceState().isGridMode\n\tconst gridSize = editor.getDocumentSettings().gridSize\n\tif (isGridMode) return point.clone().snapToGrid(gridSize)\n\treturn point.clone()\n}\n"],
5
+ "mappings": "AAAA,SAAkB,qBAAqB;AACvC,SAAS,uBAAuB;AAChC,SAAS,WAAW;AAIpB,SAAS,iBAAiB;AAGnB,MAAM,iBAAiB,UAAU;AAAA,EACvC,OAAgB,KAAK;AAAA,EAEZ,cAAc,MAA0B;AAChD,UAAM,EAAE,OAAO,IAAI;AACnB,QAAI,OAAO,OAAO,cAAc,GAAG;AAClC,YAAM,kBAAkB,OAAO,OAAO,mBAAmB;AAEzD,YAAM,YAAa,KAAK,OAA6B;AAErD,YAAM,KAAK,cAAc;AAEzB,YAAM,iBAAiB,OAAO,yBAAyB,gBAAgB,EAAE,EAAE;AAC3E,YAAM,WAAW,gBAAgB,iBAAiB,MAAM;AAGxD,WAAK,OAAO,aAAa;AAAA,QACxB;AAAA,UACC;AAAA,UACA,MAAM;AAAA,UACN,GAAG,SAAS;AAAA,UACZ,GAAG,SAAS;AAAA,UACZ,OAAO;AAAA,YACN,GAAG;AAAA,YACH,GAAG;AAAA,UACJ;AAAA,QACD;AAAA,MACD,CAAC;AACD,YAAM,QAAQ,OAAO,SAAS,EAAE;AAChC,UAAI,CAAC,OAAO;AACX,aAAK,OAAO;AACZ;AAAA,MACD;AACA,aAAO,OAAO,EAAE;AAEhB,YAAM,SAAS,KAAK;AACpB,WAAK,OAAO;AAAA,QACX;AAAA,QACA;AAAA,UACC,GAAG;AAAA,UACH,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,YAAY;AAAA,UACZ;AAAA,UACA,sBAAsB,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,UACnC,kBAAkB,KAAK,OAAO;AAAA,UAC9B,UAAU,OAAO,WACd,CAACA,WAA0B,OAAO,WAAWA,MAAK,IAClD;AAAA,QACJ;AAAA;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAES,cAAc;AACtB,SAAK,SAAS;AAAA,EACf;AAAA,EAES,cAAc;AAEtB,QAAI,KAAK,OAAO,iBAAiB,EAAE,gBAAiB,MAAK,OAAO;AAAA,EACjE;AAAA,EAES,WAAW;AACnB,SAAK,OAAO;AAAA,EACb;AAAA,EAES,aAAa;AAIrB,SAAK,OAAO;AAAA,EACb;AAAA,EAES,cAAc;AACtB,SAAK,OAAO;AAAA,EACb;AAAA,EAEA,WAAW;AACV,UAAM,kBAAkB,KAAK,OAAO,OAAO,mBAAmB;AAE9D,UAAM,YAAa,KAAK,OAA6B;AAErD,UAAM,KAAK,cAAc;AAEzB,SAAK,OAAO,yBAAyB,gBAAgB,EAAE,EAAE;AAIzD,SAAK,OAAO,aAAa;AAAA,MACxB;AAAA,QACC;AAAA,QACA,MAAM;AAAA,QACN,GAAG,gBAAgB;AAAA,QACnB,GAAG,gBAAgB;AAAA,MACpB;AAAA,IACD,CAAC;AAED,UAAM,QAAQ,KAAK,OAAO,SAAyB,EAAE;AACrD,QAAI,CAAC,OAAO;AACX,WAAK,OAAO;AACZ;AAAA,IACD;AAEA,QAAI,EAAE,GAAG,EAAE,IAAI,MAAM;AACrB,UAAM,QAAQ,IAAI,IAAI,IAAI,GAAG,IAAI,CAAC;AAClC,UAAM,kBAAkB,KAAK,OAAO,wBAAwB,KAAK;AACjE,QAAI,gBAAiB,OAAM,IAAI,CAAC,gBAAgB,SAAS,CAAC;AAC1D,UAAM,QAAQ,KAAK,OAAO,qBAAqB;AAG/C,QAAI,UAAU,GAAG;AAChB,WAAK;AACL,WAAK;AACL,YAAM,IAAI,KAAK;AAAA,IAChB;AAEA,UAAM,OAAO,gBAAgB,KAAK;AAClC,UAAM,WAAW,gBAAgB,IAAI,IAAI,MAAM,IAAI,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC,GAAG,KAAK,MAAM;AAC3F,SAAK,IAAI,SAAS;AAClB,SAAK,IAAI,SAAS;AAClB,SAAK,MAAM,IAAI;AACf,SAAK,MAAM,IAAI;AAEf,QAAI,WAAW,MAAM,OAAO;AAC3B;AAAC,MAAC,KAAuD,MAAM,QAAQ;AAAA,IACxE;AAEA,SAAK,OAAO,YAAY,IAAI;AAE5B,SAAK,OAAO,kBAAkB,CAAC,EAAE,CAAC;AAElC,QAAI,KAAK,OAAO,iBAAiB,EAAE,cAAc;AAChD,WAAK,OAAO,WAAW,MAAM;AAAA,IAC9B,OAAO;AACN,WAAK,OAAO,eAAe,aAAa;AAAA,IACzC;AAAA,EACD;AAAA,EAEA,SAAS;AACR,SAAK,OAAO,WAAW,MAAM;AAAA,EAC9B;AACD;AAOO,SAAS,gBAAgB,OAAY,QAAqB;AAChE,QAAM,aAAa,OAAO,iBAAiB,EAAE;AAC7C,QAAM,WAAW,OAAO,oBAAoB,EAAE;AAC9C,MAAI,WAAY,QAAO,MAAM,MAAM,EAAE,WAAW,QAAQ;AACxD,SAAO,MAAM,MAAM;AACpB;",
6
6
  "names": ["shape"]
7
7
  }
@@ -110,7 +110,7 @@ class StateNode {
110
110
  throw Error(`${currState.id} - no child state exists with the id ${id2}.`);
111
111
  }
112
112
  if (prevChildState?.id !== nextChildState.id) {
113
- prevChildState?.exit(info, id2);
113
+ if (prevChildState?.getIsActive()) prevChildState.exit(info, id2);
114
114
  currState._current.set(nextChildState);
115
115
  nextChildState.enter(info, prevChildState?.id || "initial");
116
116
  if (!nextChildState.getIsActive()) break;
@@ -136,9 +136,10 @@ class StateNode {
136
136
  }
137
137
  this.editor.performance._notifyInteractionStart(this.id, this.getPath());
138
138
  }
139
+ const currentBeforeEnter = this.getCurrent();
139
140
  this._isActive.set(true);
140
141
  this.onEnter?.(info, from);
141
- if (this.children && this.initial && this.getIsActive()) {
142
+ if (this.children && this.initial && this.getIsActive() && this.getCurrent() === currentBeforeEnter) {
142
143
  const initial = this.children[this.initial];
143
144
  this._current.set(initial);
144
145
  initial.enter(info, from);