@tldraw/editor 3.14.0-canary.d926f92ca8d6 → 3.14.0-canary.db789786fb06

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 (237) hide show
  1. package/dist-cjs/index.d.ts +212 -117
  2. package/dist-cjs/index.js +11 -8
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
  5. package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
  6. package/dist-cjs/lib/editor/Editor.js +131 -99
  7. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  8. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  9. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +22 -22
  10. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  11. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +16 -16
  12. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  13. package/dist-cjs/lib/editor/managers/{ClickManager.js → ClickManager/ClickManager.js} +1 -1
  14. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +7 -0
  15. package/dist-cjs/lib/editor/managers/{EdgeScrollManager.js → EdgeScrollManager/EdgeScrollManager.js} +2 -2
  16. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +7 -0
  17. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +7 -0
  18. package/dist-cjs/lib/editor/managers/{FontManager.js → FontManager/FontManager.js} +4 -1
  19. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +7 -0
  20. package/dist-cjs/lib/editor/managers/{HistoryManager.js → HistoryManager/HistoryManager.js} +67 -7
  21. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +7 -0
  22. package/dist-cjs/lib/editor/managers/{ScribbleManager.js → ScribbleManager/ScribbleManager.js} +1 -1
  23. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +7 -0
  24. package/dist-cjs/lib/editor/managers/{TextManager.js → TextManager/TextManager.js} +73 -42
  25. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +7 -0
  26. package/dist-cjs/lib/editor/managers/{TickManager.js → TickManager/TickManager.js} +1 -1
  27. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +7 -0
  28. package/dist-cjs/lib/editor/managers/{UserPreferencesManager.js → UserPreferencesManager/UserPreferencesManager.js} +1 -1
  29. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +7 -0
  30. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +0 -10
  31. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  32. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  33. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +1 -1
  34. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +10 -6
  35. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
  36. package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
  37. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  38. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  39. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  40. package/dist-cjs/lib/exports/getSvgJsx.js.map +1 -1
  41. package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
  42. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  43. package/dist-cjs/lib/primitives/Box.js +33 -33
  44. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  45. package/dist-cjs/lib/primitives/Vec.js +13 -8
  46. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  47. package/dist-cjs/lib/primitives/geometry/Arc2d.js +41 -21
  48. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  49. package/dist-cjs/lib/primitives/geometry/Circle2d.js +11 -11
  50. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  51. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +13 -16
  52. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  53. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +4 -4
  54. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  55. package/dist-cjs/lib/primitives/geometry/Edge2d.js +14 -17
  56. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  57. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +10 -10
  58. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  59. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
  60. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  61. package/dist-cjs/lib/primitives/geometry/Point2d.js +6 -6
  62. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  63. package/dist-cjs/lib/primitives/geometry/Polygon2d.js +3 -0
  64. package/dist-cjs/lib/primitives/geometry/Polygon2d.js.map +2 -2
  65. package/dist-cjs/lib/primitives/geometry/Polyline2d.js +8 -5
  66. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  67. package/dist-cjs/lib/primitives/geometry/Rectangle2d.js +22 -11
  68. package/dist-cjs/lib/primitives/geometry/Rectangle2d.js.map +2 -2
  69. package/dist-cjs/lib/primitives/geometry/Stadium2d.js +22 -22
  70. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  71. package/dist-cjs/lib/utils/reorderShapes.js +11 -10
  72. package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
  73. package/dist-cjs/lib/utils/reparenting.js +232 -0
  74. package/dist-cjs/lib/utils/reparenting.js.map +7 -0
  75. package/dist-cjs/lib/utils/richText.js +7 -2
  76. package/dist-cjs/lib/utils/richText.js.map +2 -2
  77. package/dist-cjs/version.js +3 -3
  78. package/dist-cjs/version.js.map +1 -1
  79. package/dist-esm/index.d.mts +212 -117
  80. package/dist-esm/index.mjs +15 -8
  81. package/dist-esm/index.mjs.map +2 -2
  82. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
  83. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  84. package/dist-esm/lib/editor/Editor.mjs +131 -99
  85. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  86. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  87. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +22 -22
  88. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  89. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +16 -16
  90. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  91. package/dist-esm/lib/editor/managers/{ClickManager.mjs → ClickManager/ClickManager.mjs} +1 -1
  92. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +7 -0
  93. package/dist-esm/lib/editor/managers/{EdgeScrollManager.mjs → EdgeScrollManager/EdgeScrollManager.mjs} +2 -2
  94. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +7 -0
  95. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +7 -0
  96. package/dist-esm/lib/editor/managers/{FontManager.mjs → FontManager/FontManager.mjs} +4 -1
  97. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +7 -0
  98. package/dist-esm/lib/editor/managers/{HistoryManager.mjs → HistoryManager/HistoryManager.mjs} +63 -3
  99. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +7 -0
  100. package/dist-esm/lib/editor/managers/{ScribbleManager.mjs → ScribbleManager/ScribbleManager.mjs} +1 -1
  101. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +7 -0
  102. package/dist-esm/lib/editor/managers/{TextManager.mjs → TextManager/TextManager.mjs} +73 -42
  103. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +7 -0
  104. package/dist-esm/lib/editor/managers/{TickManager.mjs → TickManager/TickManager.mjs} +1 -1
  105. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +7 -0
  106. package/dist-esm/lib/editor/managers/{UserPreferencesManager.mjs → UserPreferencesManager/UserPreferencesManager.mjs} +1 -1
  107. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +7 -0
  108. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +0 -10
  109. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  110. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  111. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
  112. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +10 -6
  113. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
  114. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
  115. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  116. package/dist-esm/lib/exports/getSvgJsx.mjs.map +1 -1
  117. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
  118. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  119. package/dist-esm/lib/primitives/Box.mjs +33 -33
  120. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  121. package/dist-esm/lib/primitives/Vec.mjs +13 -8
  122. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  123. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +41 -21
  124. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  125. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +11 -11
  126. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  127. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +13 -16
  128. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  129. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +4 -4
  130. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  131. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +14 -17
  132. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  133. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +11 -11
  134. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  135. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
  136. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  137. package/dist-esm/lib/primitives/geometry/Point2d.mjs +6 -6
  138. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  139. package/dist-esm/lib/primitives/geometry/Polygon2d.mjs +3 -0
  140. package/dist-esm/lib/primitives/geometry/Polygon2d.mjs.map +2 -2
  141. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +8 -5
  142. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  143. package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs +22 -11
  144. package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs.map +2 -2
  145. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +22 -22
  146. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  147. package/dist-esm/lib/utils/reorderShapes.mjs +11 -10
  148. package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
  149. package/dist-esm/lib/utils/reparenting.mjs +216 -0
  150. package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
  151. package/dist-esm/lib/utils/richText.mjs +8 -3
  152. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  153. package/dist-esm/version.mjs +3 -3
  154. package/dist-esm/version.mjs.map +1 -1
  155. package/editor.css +442 -492
  156. package/package.json +8 -9
  157. package/src/index.ts +20 -7
  158. package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
  159. package/src/lib/editor/Editor.test.ts +252 -3
  160. package/src/lib/editor/Editor.ts +149 -107
  161. package/src/lib/editor/bindings/BindingUtil.ts +6 -0
  162. package/src/lib/editor/derivations/bindingsIndex.ts +27 -26
  163. package/src/lib/editor/derivations/parentsToChildren.ts +28 -25
  164. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +442 -0
  165. package/src/lib/editor/managers/{ClickManager.ts → ClickManager/ClickManager.ts} +3 -3
  166. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +374 -0
  167. package/src/lib/editor/managers/{EdgeScrollManager.ts → EdgeScrollManager/EdgeScrollManager.ts} +3 -3
  168. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +455 -0
  169. package/src/lib/editor/managers/{FocusManager.ts → FocusManager/FocusManager.ts} +1 -1
  170. package/src/lib/editor/managers/FontManager/FontManager.test.ts +263 -0
  171. package/src/lib/editor/managers/{FontManager.ts → FontManager/FontManager.ts} +5 -2
  172. package/src/lib/editor/managers/{HistoryManager.test.ts → HistoryManager/HistoryManager.test.ts} +388 -1
  173. package/src/lib/editor/managers/{HistoryManager.ts → HistoryManager/HistoryManager.ts} +76 -3
  174. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +624 -0
  175. package/src/lib/editor/managers/{ScribbleManager.ts → ScribbleManager/ScribbleManager.ts} +2 -2
  176. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +485 -0
  177. package/src/lib/editor/managers/TextManager/TextManager.test.ts +407 -0
  178. package/src/lib/editor/managers/{TextManager.ts → TextManager/TextManager.ts} +119 -87
  179. package/src/lib/editor/managers/TickManager/TickManager.test.ts +314 -0
  180. package/src/lib/editor/managers/{TickManager.ts → TickManager/TickManager.ts} +2 -2
  181. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +591 -0
  182. package/src/lib/editor/managers/{UserPreferencesManager.ts → UserPreferencesManager/UserPreferencesManager.ts} +2 -2
  183. package/src/lib/editor/shapes/ShapeUtil.ts +48 -16
  184. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  185. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +22 -17
  186. package/src/lib/editor/tools/StateNode.ts +3 -3
  187. package/src/lib/editor/types/emit-types.ts +4 -0
  188. package/src/lib/editor/types/external-content.ts +11 -2
  189. package/src/lib/exports/getSvgJsx.tsx +1 -1
  190. package/src/lib/hooks/useCanvasEvents.ts +0 -1
  191. package/src/lib/primitives/Box.test.ts +588 -7
  192. package/src/lib/primitives/Box.ts +33 -33
  193. package/src/lib/primitives/Vec.test.ts +2 -2
  194. package/src/lib/primitives/Vec.ts +13 -8
  195. package/src/lib/primitives/geometry/Arc2d.ts +42 -23
  196. package/src/lib/primitives/geometry/Circle2d.ts +12 -12
  197. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +5 -0
  198. package/src/lib/primitives/geometry/CubicBezier2d.ts +13 -17
  199. package/src/lib/primitives/geometry/CubicSpline2d.ts +5 -5
  200. package/src/lib/primitives/geometry/Edge2d.ts +14 -18
  201. package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
  202. package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
  203. package/src/lib/primitives/geometry/Point2d.ts +6 -6
  204. package/src/lib/primitives/geometry/Polygon2d.ts +4 -0
  205. package/src/lib/primitives/geometry/Polyline2d.ts +10 -7
  206. package/src/lib/primitives/geometry/Rectangle2d.ts +24 -11
  207. package/src/lib/primitives/geometry/Stadium2d.ts +22 -23
  208. package/src/lib/utils/reorderShapes.ts +10 -13
  209. package/src/lib/utils/reparenting.ts +383 -0
  210. package/src/lib/utils/richText.ts +10 -4
  211. package/src/version.ts +3 -3
  212. package/dist-cjs/lib/editor/managers/ClickManager.js.map +0 -7
  213. package/dist-cjs/lib/editor/managers/EdgeScrollManager.js.map +0 -7
  214. package/dist-cjs/lib/editor/managers/FocusManager.js.map +0 -7
  215. package/dist-cjs/lib/editor/managers/FontManager.js.map +0 -7
  216. package/dist-cjs/lib/editor/managers/HistoryManager.js.map +0 -7
  217. package/dist-cjs/lib/editor/managers/ScribbleManager.js.map +0 -7
  218. package/dist-cjs/lib/editor/managers/Stack.js +0 -82
  219. package/dist-cjs/lib/editor/managers/Stack.js.map +0 -7
  220. package/dist-cjs/lib/editor/managers/TextManager.js.map +0 -7
  221. package/dist-cjs/lib/editor/managers/TickManager.js.map +0 -7
  222. package/dist-cjs/lib/editor/managers/UserPreferencesManager.js.map +0 -7
  223. package/dist-esm/lib/editor/managers/ClickManager.mjs.map +0 -7
  224. package/dist-esm/lib/editor/managers/EdgeScrollManager.mjs.map +0 -7
  225. package/dist-esm/lib/editor/managers/FocusManager.mjs.map +0 -7
  226. package/dist-esm/lib/editor/managers/FontManager.mjs.map +0 -7
  227. package/dist-esm/lib/editor/managers/HistoryManager.mjs.map +0 -7
  228. package/dist-esm/lib/editor/managers/ScribbleManager.mjs.map +0 -7
  229. package/dist-esm/lib/editor/managers/Stack.mjs +0 -62
  230. package/dist-esm/lib/editor/managers/Stack.mjs.map +0 -7
  231. package/dist-esm/lib/editor/managers/TextManager.mjs.map +0 -7
  232. package/dist-esm/lib/editor/managers/TickManager.mjs.map +0 -7
  233. package/dist-esm/lib/editor/managers/UserPreferencesManager.mjs.map +0 -7
  234. package/src/lib/editor/managers/ScribbleManager.test.ts +0 -32
  235. package/src/lib/editor/managers/Stack.ts +0 -71
  236. /package/dist-cjs/lib/editor/managers/{FocusManager.js → FocusManager/FocusManager.js} +0 -0
  237. /package/dist-esm/lib/editor/managers/{FocusManager.mjs → FocusManager/FocusManager.mjs} +0 -0
@@ -9,8 +9,7 @@ import {
9
9
  squashRecordDiffsMutable,
10
10
  } from '@tldraw/store'
11
11
  import { exhaustiveSwitchError, noop } from '@tldraw/utils'
12
- import { TLHistoryBatchOptions, TLHistoryEntry } from '../types/history-types'
13
- import { stack } from './Stack'
12
+ import { TLHistoryBatchOptions, TLHistoryEntry } from '../../types/history-types'
14
13
 
15
14
  enum HistoryRecorderState {
16
15
  Recording = 'recording',
@@ -242,7 +241,9 @@ export class HistoryManager<R extends UnknownRecord> {
242
241
  }
243
242
 
244
243
  bailToMark(id: string) {
245
- this._undo({ pushToRedoStack: false, toMark: id })
244
+ if (id) {
245
+ this._undo({ pushToRedoStack: false, toMark: id })
246
+ }
246
247
 
247
248
  return this
248
249
  }
@@ -349,3 +350,75 @@ class PendingDiff<R extends UnknownRecord> {
349
350
  return { diff: this.diff, isEmpty: this.isEmpty() }
350
351
  }
351
352
  }
353
+
354
+ import { EMPTY_ARRAY } from '@tldraw/state'
355
+
356
+ export type Stack<T> = StackItem<T> | EmptyStackItem<T>
357
+
358
+ export function stack<T>(items?: Array<T>): Stack<T> {
359
+ if (items) {
360
+ let result = EMPTY_STACK_ITEM as Stack<T>
361
+ while (items.length) {
362
+ result = result.push(items.pop()!)
363
+ }
364
+ return result
365
+ }
366
+ return EMPTY_STACK_ITEM as any
367
+ }
368
+
369
+ class EmptyStackItem<T> implements Iterable<T> {
370
+ readonly length = 0
371
+ readonly head = null
372
+ readonly tail: Stack<T> = this
373
+
374
+ push(head: T): Stack<T> {
375
+ return new StackItem<T>(head, this)
376
+ }
377
+
378
+ toArray() {
379
+ return EMPTY_ARRAY
380
+ }
381
+
382
+ [Symbol.iterator]() {
383
+ return {
384
+ next() {
385
+ return { value: undefined, done: true as const }
386
+ },
387
+ }
388
+ }
389
+ }
390
+
391
+ const EMPTY_STACK_ITEM = new EmptyStackItem()
392
+
393
+ class StackItem<T> implements Iterable<T> {
394
+ length: number
395
+ constructor(
396
+ public readonly head: T,
397
+ public readonly tail: Stack<T>
398
+ ) {
399
+ this.length = tail.length + 1
400
+ }
401
+
402
+ push(head: T): Stack<T> {
403
+ return new StackItem(head, this)
404
+ }
405
+
406
+ toArray() {
407
+ return Array.from(this)
408
+ }
409
+
410
+ [Symbol.iterator]() {
411
+ let stack = this as Stack<T>
412
+ return {
413
+ next() {
414
+ if (stack.length) {
415
+ const value = stack.head!
416
+ stack = stack.tail
417
+ return { value, done: false as const }
418
+ } else {
419
+ return { value: undefined, done: true as const }
420
+ }
421
+ },
422
+ }
423
+ }
424
+ }
@@ -0,0 +1,624 @@
1
+ import { TLScribble } from '@tldraw/tlschema'
2
+ import { Editor } from '../../Editor'
3
+ import { ScribbleItem, ScribbleManager } from './ScribbleManager'
4
+
5
+ // Mock the Editor class
6
+ jest.mock('../../Editor')
7
+ jest.mock('@tldraw/utils', () => ({
8
+ uniqueId: jest.fn(() => 'test-id'),
9
+ }))
10
+
11
+ describe('ScribbleManager', () => {
12
+ let editor: jest.Mocked<Editor>
13
+ let scribbleManager: ScribbleManager
14
+ let mockUniqueId: jest.Mock
15
+
16
+ beforeEach(() => {
17
+ editor = {
18
+ updateInstanceState: jest.fn(),
19
+ run: jest.fn((fn) => fn()),
20
+ } as any
21
+
22
+ const { uniqueId } = jest.requireMock('@tldraw/utils')
23
+ mockUniqueId = uniqueId
24
+ mockUniqueId.mockReturnValue('test-id')
25
+
26
+ scribbleManager = new ScribbleManager(editor)
27
+ })
28
+
29
+ afterEach(() => {
30
+ jest.clearAllMocks()
31
+ })
32
+
33
+ describe('constructor and initialization', () => {
34
+ it('should initialize with empty scribble items and paused state', () => {
35
+ expect(scribbleManager.scribbleItems.size).toBe(0)
36
+ expect(scribbleManager.state).toBe('paused')
37
+ })
38
+
39
+ it('should store reference to editor', () => {
40
+ expect((scribbleManager as any).editor).toBe(editor)
41
+ })
42
+ })
43
+
44
+ describe('addScribble', () => {
45
+ it('should add a new scribble with default values', () => {
46
+ const result = scribbleManager.addScribble({})
47
+
48
+ expect(result).toBeDefined()
49
+ expect(result.id).toBe('test-id')
50
+ expect(result.scribble).toMatchObject({
51
+ id: 'test-id',
52
+ size: 20,
53
+ color: 'accent',
54
+ opacity: 0.8,
55
+ delay: 0,
56
+ points: [],
57
+ shrink: 0.1,
58
+ taper: true,
59
+ state: 'starting',
60
+ })
61
+ expect(result.timeoutMs).toBe(0)
62
+ expect(result.delayRemaining).toBe(0)
63
+ expect(result.prev).toBeNull()
64
+ expect(result.next).toBeNull()
65
+ })
66
+
67
+ it('should add a scribble with custom properties', () => {
68
+ const customScribble: Partial<TLScribble> = {
69
+ size: 30,
70
+ color: 'black',
71
+ opacity: 0.5,
72
+ delay: 1000,
73
+ shrink: 0.2,
74
+ taper: false,
75
+ }
76
+
77
+ const result = scribbleManager.addScribble(customScribble)
78
+
79
+ expect(result.scribble).toMatchObject({
80
+ ...customScribble,
81
+ id: 'test-id',
82
+ points: [],
83
+ state: 'starting',
84
+ })
85
+ expect(result.delayRemaining).toBe(1000)
86
+ })
87
+
88
+ it('should add scribble with custom id', () => {
89
+ const customId = 'custom-scribble-id'
90
+ const result = scribbleManager.addScribble({}, customId)
91
+
92
+ expect(result.id).toBe(customId)
93
+ expect(result.scribble.id).toBe(customId)
94
+ expect(scribbleManager.scribbleItems.has(customId)).toBe(true)
95
+ })
96
+
97
+ it('should store scribble in scribbleItems map', () => {
98
+ const result = scribbleManager.addScribble({})
99
+
100
+ expect(scribbleManager.scribbleItems.size).toBe(1)
101
+ expect(scribbleManager.scribbleItems.get('test-id')).toBe(result)
102
+ })
103
+
104
+ it('should handle multiple scribbles', () => {
105
+ mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2').mockReturnValueOnce('id3')
106
+
107
+ const scribble1 = scribbleManager.addScribble({ color: 'black' })
108
+ const scribble2 = scribbleManager.addScribble({ color: 'white' })
109
+ const scribble3 = scribbleManager.addScribble({ color: 'accent' })
110
+
111
+ expect(scribbleManager.scribbleItems.size).toBe(3)
112
+ expect(scribble1.scribble.color).toBe('black')
113
+ expect(scribble2.scribble.color).toBe('white')
114
+ expect(scribble3.scribble.color).toBe('accent')
115
+ })
116
+ })
117
+
118
+ describe('reset', () => {
119
+ it('should clear all scribble items and update instance state', () => {
120
+ mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2')
121
+ scribbleManager.addScribble({})
122
+ scribbleManager.addScribble({})
123
+ expect(scribbleManager.scribbleItems.size).toBe(2)
124
+
125
+ scribbleManager.reset()
126
+
127
+ expect(scribbleManager.scribbleItems.size).toBe(0)
128
+ expect(editor.updateInstanceState).toHaveBeenCalledWith({ scribbles: [] })
129
+ })
130
+
131
+ it('should work when no scribbles exist', () => {
132
+ expect(() => scribbleManager.reset()).not.toThrow()
133
+ expect(scribbleManager.scribbleItems.size).toBe(0)
134
+ expect(editor.updateInstanceState).toHaveBeenCalledWith({ scribbles: [] })
135
+ })
136
+ })
137
+
138
+ describe('stop', () => {
139
+ it('should stop an existing scribble', () => {
140
+ const item = scribbleManager.addScribble({ delay: 1000 })
141
+ item.delayRemaining = 500
142
+
143
+ const result = scribbleManager.stop(item.id)
144
+
145
+ expect(result).toBe(item)
146
+ expect(result.scribble.state).toBe('stopping')
147
+ expect(result.delayRemaining).toBe(200) // min(500, 200)
148
+ })
149
+
150
+ it('should cap delay at 200ms when stopping', () => {
151
+ const item = scribbleManager.addScribble({ delay: 50 })
152
+ item.delayRemaining = 50
153
+
154
+ scribbleManager.stop(item.id)
155
+
156
+ expect(item.delayRemaining).toBe(50) // min(50, 200)
157
+ })
158
+
159
+ it('should throw error for non-existent scribble', () => {
160
+ expect(() => scribbleManager.stop('non-existent-id')).toThrow(
161
+ 'Scribble with id non-existent-id not found'
162
+ )
163
+ })
164
+
165
+ it('should handle stopping multiple scribbles', () => {
166
+ mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2')
167
+
168
+ const item1 = scribbleManager.addScribble({})
169
+ const item2 = scribbleManager.addScribble({})
170
+
171
+ scribbleManager.stop('id1')
172
+ scribbleManager.stop('id2')
173
+
174
+ expect(item1.scribble.state).toBe('stopping')
175
+ expect(item2.scribble.state).toBe('stopping')
176
+ })
177
+ })
178
+
179
+ describe('addPoint', () => {
180
+ it('should add point to existing scribble', () => {
181
+ const item = scribbleManager.addScribble({})
182
+
183
+ const result = scribbleManager.addPoint(item.id, 10, 20, 0.7)
184
+
185
+ expect(result).toBe(item)
186
+ expect(result.next).toEqual({ x: 10, y: 20, z: 0.7 })
187
+ })
188
+
189
+ it('should use default z value of 0.5', () => {
190
+ const item = scribbleManager.addScribble({})
191
+
192
+ scribbleManager.addPoint(item.id, 10, 20)
193
+
194
+ expect(item.next).toEqual({ x: 10, y: 20, z: 0.5 })
195
+ })
196
+
197
+ it('should only set next if distance from prev is >= 1', () => {
198
+ const item = scribbleManager.addScribble({})
199
+ item.prev = { x: 10, y: 20, z: 0.5 }
200
+
201
+ // Distance < 1 (should not set next)
202
+ scribbleManager.addPoint(item.id, 10.5, 20.3)
203
+ expect(item.next).toBeNull()
204
+
205
+ // Distance >= 1 (should set next)
206
+ scribbleManager.addPoint(item.id, 11, 21)
207
+ expect(item.next).toEqual({ x: 11, y: 21, z: 0.5 })
208
+ })
209
+
210
+ it('should set next when prev is null', () => {
211
+ const item = scribbleManager.addScribble({})
212
+ expect(item.prev).toBeNull()
213
+
214
+ scribbleManager.addPoint(item.id, 5, 5)
215
+
216
+ expect(item.next).toEqual({ x: 5, y: 5, z: 0.5 })
217
+ })
218
+
219
+ it('should throw error for non-existent scribble', () => {
220
+ expect(() => scribbleManager.addPoint('non-existent-id', 10, 20)).toThrow(
221
+ 'Scribble with id non-existent-id not found'
222
+ )
223
+ })
224
+
225
+ it('should handle multiple points', () => {
226
+ const item = scribbleManager.addScribble({})
227
+
228
+ scribbleManager.addPoint(item.id, 0, 0)
229
+ expect(item.next).toEqual({ x: 0, y: 0, z: 0.5 })
230
+
231
+ item.prev = item.next
232
+ scribbleManager.addPoint(item.id, 10, 10)
233
+ expect(item.next).toEqual({ x: 10, y: 10, z: 0.5 })
234
+ })
235
+ })
236
+
237
+ describe('tick', () => {
238
+ it('should return early when no scribble items exist', () => {
239
+ scribbleManager.tick(16)
240
+
241
+ expect(editor.run).not.toHaveBeenCalled()
242
+ })
243
+
244
+ it('should wrap tick operations in editor.run', () => {
245
+ scribbleManager.addScribble({})
246
+
247
+ scribbleManager.tick(16)
248
+
249
+ expect(editor.run).toHaveBeenCalledWith(expect.any(Function))
250
+ })
251
+
252
+ describe('starting state behavior', () => {
253
+ it('should add points to scribble in starting state', () => {
254
+ const item = scribbleManager.addScribble({})
255
+ item.next = { x: 10, y: 20, z: 0.5 }
256
+
257
+ scribbleManager.tick(16)
258
+
259
+ expect(item.prev).toEqual({ x: 10, y: 20, z: 0.5 })
260
+ expect(item.scribble.points).toHaveLength(1)
261
+ expect(item.scribble.points[0]).toEqual({ x: 10, y: 20, z: 0.5 })
262
+ })
263
+
264
+ it('should not add point if next equals prev', () => {
265
+ const item = scribbleManager.addScribble({})
266
+ const point = { x: 10, y: 20, z: 0.5 }
267
+ item.next = point
268
+ item.prev = point
269
+
270
+ scribbleManager.tick(16)
271
+
272
+ expect(item.scribble.points).toHaveLength(0)
273
+ })
274
+
275
+ it('should transition to active after 8 points', () => {
276
+ const item = scribbleManager.addScribble({})
277
+
278
+ // Add 9 points to trigger transition
279
+ for (let i = 0; i < 9; i++) {
280
+ item.next = { x: i, y: i, z: 0.5 }
281
+ item.prev = null // Reset prev to ensure point is added
282
+ scribbleManager.tick(16)
283
+ }
284
+
285
+ expect(item.scribble.state).toBe('active')
286
+ expect(item.scribble.points).toHaveLength(9)
287
+ })
288
+ })
289
+
290
+ describe('active state behavior', () => {
291
+ let item: ScribbleItem
292
+
293
+ beforeEach(() => {
294
+ item = scribbleManager.addScribble({})
295
+ item.scribble.state = 'active'
296
+ })
297
+
298
+ it('should add new points when next differs from prev', () => {
299
+ item.next = { x: 10, y: 20, z: 0.5 }
300
+ item.prev = { x: 0, y: 0, z: 0.5 }
301
+
302
+ scribbleManager.tick(16)
303
+
304
+ expect(item.prev).toEqual({ x: 10, y: 20, z: 0.5 })
305
+ expect(item.scribble.points).toContainEqual({ x: 10, y: 20, z: 0.5 })
306
+ })
307
+
308
+ it('should shrink from start when delay is finished and points > 8', () => {
309
+ // Set up scribble with > 8 points and no delay
310
+ for (let i = 0; i < 10; i++) {
311
+ item.scribble.points.push({ x: i, y: i, z: 0.5 })
312
+ }
313
+ item.delayRemaining = 0
314
+ item.next = { x: 50, y: 50, z: 0.5 }
315
+
316
+ scribbleManager.tick(16)
317
+
318
+ expect(item.scribble.points).toHaveLength(10) // Added one, removed one
319
+ expect(item.scribble.points[0]).toEqual({ x: 1, y: 1, z: 0.5 }) // First was removed
320
+ })
321
+
322
+ it('should shrink when not moving and timeout reached', () => {
323
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
324
+ item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
325
+ item.timeoutMs = 16 // Will reset to 0, triggering shrink
326
+
327
+ scribbleManager.tick(16)
328
+
329
+ expect(item.scribble.points).toHaveLength(1)
330
+ expect(item.scribble.points[0]).toEqual({ x: 2, y: 2, z: 0.5 })
331
+ })
332
+
333
+ it('should reset delay when down to single point while stationary', () => {
334
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
335
+ item.scribble.delay = 500
336
+ item.delayRemaining = 0
337
+ item.timeoutMs = 16
338
+
339
+ scribbleManager.tick(16)
340
+
341
+ expect(item.delayRemaining).toBe(500)
342
+ })
343
+
344
+ it('should update timeout correctly', () => {
345
+ item.timeoutMs = 10
346
+
347
+ scribbleManager.tick(5)
348
+ expect(item.timeoutMs).toBe(15)
349
+
350
+ scribbleManager.tick(2)
351
+ expect(item.timeoutMs).toBe(0) // Reset when >= 16 (15 + 2 = 17)
352
+ })
353
+
354
+ it('should reduce delay remaining', () => {
355
+ item.delayRemaining = 100
356
+
357
+ scribbleManager.tick(30)
358
+
359
+ expect(item.delayRemaining).toBeLessThan(100)
360
+ })
361
+
362
+ it('should not reduce delay below 0', () => {
363
+ item.delayRemaining = 10
364
+
365
+ scribbleManager.tick(30)
366
+
367
+ expect(item.delayRemaining).toBe(0)
368
+ })
369
+ })
370
+
371
+ describe('stopping state behavior', () => {
372
+ let item: ScribbleItem
373
+
374
+ beforeEach(() => {
375
+ item = scribbleManager.addScribble({})
376
+ item.scribble.state = 'stopping'
377
+ })
378
+
379
+ it('should remove points when delay is finished and timeout reached', () => {
380
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
381
+ item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
382
+ item.delayRemaining = 0
383
+ item.timeoutMs = 16
384
+
385
+ scribbleManager.tick(16)
386
+
387
+ expect(item.scribble.points).toHaveLength(1)
388
+ expect(item.scribble.points[0]).toEqual({ x: 2, y: 2, z: 0.5 })
389
+ })
390
+
391
+ it('should shrink scribble size when shrink is enabled', () => {
392
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
393
+ item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
394
+ item.scribble.size = 20
395
+ item.scribble.shrink = 0.1
396
+ item.delayRemaining = 0
397
+ item.timeoutMs = 16
398
+
399
+ scribbleManager.tick(16)
400
+
401
+ expect(item.scribble.size).toBe(18) // 20 * (1 - 0.1)
402
+ })
403
+
404
+ it('should not shrink size below 1', () => {
405
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
406
+ item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
407
+ item.scribble.size = 1.5
408
+ item.scribble.shrink = 0.8
409
+ item.delayRemaining = 0
410
+ item.timeoutMs = 16
411
+
412
+ scribbleManager.tick(16)
413
+
414
+ expect(item.scribble.size).toBe(1) // Math.max(1, 1.5 * 0.2)
415
+ })
416
+
417
+ it('should remove scribble when down to one point', () => {
418
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
419
+ item.delayRemaining = 0
420
+ item.timeoutMs = 16
421
+
422
+ scribbleManager.tick(16)
423
+
424
+ expect(scribbleManager.scribbleItems.has(item.id)).toBe(false)
425
+ })
426
+
427
+ it('should not process when delay remaining > 0', () => {
428
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
429
+ item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
430
+ item.delayRemaining = 100
431
+ item.timeoutMs = 16
432
+
433
+ scribbleManager.tick(16)
434
+
435
+ expect(item.scribble.points).toHaveLength(2) // No change
436
+ })
437
+
438
+ it('should not process when timeout < 16', () => {
439
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
440
+ item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
441
+ item.delayRemaining = 0
442
+ item.timeoutMs = 10
443
+
444
+ scribbleManager.tick(5)
445
+
446
+ expect(item.scribble.points).toHaveLength(2) // No change
447
+ expect(item.timeoutMs).toBe(15)
448
+ })
449
+ })
450
+
451
+ describe('paused state behavior', () => {
452
+ it('should do nothing when scribble is paused', () => {
453
+ const item = scribbleManager.addScribble({})
454
+ item.scribble.state = 'paused'
455
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
456
+ const originalPoints = [...item.scribble.points]
457
+
458
+ scribbleManager.tick(16)
459
+
460
+ expect(item.scribble.points).toEqual(originalPoints)
461
+ })
462
+ })
463
+
464
+ describe('instance state updates', () => {
465
+ it('should update instance state with scribbles', () => {
466
+ mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2')
467
+ scribbleManager.addScribble({ color: 'black' })
468
+ scribbleManager.addScribble({ color: 'white' })
469
+
470
+ scribbleManager.tick(16)
471
+
472
+ expect(editor.updateInstanceState).toHaveBeenCalledWith({
473
+ scribbles: expect.arrayContaining([
474
+ expect.objectContaining({
475
+ color: 'black',
476
+ points: expect.any(Array),
477
+ }),
478
+ expect.objectContaining({
479
+ color: 'white',
480
+ points: expect.any(Array),
481
+ }),
482
+ ]),
483
+ })
484
+ })
485
+
486
+ it('should create copies of scribbles for instance state', () => {
487
+ const item = scribbleManager.addScribble({})
488
+ item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
489
+
490
+ scribbleManager.tick(16)
491
+
492
+ const call = editor.updateInstanceState.mock.calls[0][0]
493
+ const scribbleInState = call.scribbles![0]
494
+
495
+ // Modify the original
496
+ item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
497
+
498
+ // State copy should be unaffected
499
+ expect(scribbleInState.points).toHaveLength(1)
500
+ })
501
+
502
+ it('should limit scribbles to 5 items', () => {
503
+ // Add 7 scribbles
504
+ const colors = ['accent', 'black', 'white', 'laser', 'muted-1', 'accent', 'black'] as const
505
+ for (let i = 0; i < 7; i++) {
506
+ mockUniqueId.mockReturnValueOnce(`id${i}`)
507
+ scribbleManager.addScribble({ color: colors[i] })
508
+ }
509
+
510
+ scribbleManager.tick(16)
511
+
512
+ const call = editor.updateInstanceState.mock.calls[0][0]
513
+ expect(call.scribbles).toHaveLength(5)
514
+ })
515
+ })
516
+ })
517
+
518
+ describe('edge cases and error handling', () => {
519
+ it('should handle Vec.Dist calculation edge cases', () => {
520
+ const item = scribbleManager.addScribble({})
521
+ item.prev = { x: 0, y: 0, z: 0 }
522
+
523
+ // Exactly distance 1
524
+ scribbleManager.addPoint(item.id, 1, 0)
525
+ expect(item.next).toEqual({ x: 1, y: 0, z: 0.5 })
526
+
527
+ // Reset and test just under distance 1
528
+ item.next = null
529
+ scribbleManager.addPoint(item.id, 0.9, 0)
530
+ expect(item.next).toBeNull()
531
+ })
532
+
533
+ it('should handle multiple scribbles in different states', () => {
534
+ mockUniqueId
535
+ .mockReturnValueOnce('starting')
536
+ .mockReturnValueOnce('active')
537
+ .mockReturnValueOnce('stopping')
538
+
539
+ const startingItem = scribbleManager.addScribble({})
540
+ const activeItem = scribbleManager.addScribble({})
541
+ const stoppingItem = scribbleManager.addScribble({})
542
+
543
+ activeItem.scribble.state = 'active'
544
+ stoppingItem.scribble.state = 'stopping'
545
+
546
+ startingItem.next = { x: 1, y: 1, z: 0.5 }
547
+ activeItem.next = { x: 2, y: 2, z: 0.5 }
548
+ stoppingItem.scribble.points.push({ x: 3, y: 3, z: 0.5 })
549
+ stoppingItem.delayRemaining = 0
550
+ stoppingItem.timeoutMs = 16
551
+
552
+ scribbleManager.tick(16)
553
+
554
+ expect(startingItem.scribble.points).toHaveLength(1)
555
+ expect(activeItem.scribble.points).toHaveLength(1)
556
+ expect(scribbleManager.scribbleItems.has('stopping')).toBe(false) // Removed
557
+ })
558
+
559
+ it('should handle tick with 0 elapsed time', () => {
560
+ const item = scribbleManager.addScribble({})
561
+ item.delayRemaining = 100
562
+
563
+ expect(() => scribbleManager.tick(0)).not.toThrow()
564
+ expect(item.delayRemaining).toBe(100) // Should remain unchanged
565
+ })
566
+
567
+ it('should handle negative elapsed time', () => {
568
+ const item = scribbleManager.addScribble({})
569
+ item.delayRemaining = 100
570
+
571
+ scribbleManager.tick(-50)
572
+
573
+ expect(item.delayRemaining).toBe(100) // Should remain unchanged or handle gracefully
574
+ })
575
+
576
+ it('should handle empty points array operations', () => {
577
+ const item = scribbleManager.addScribble({})
578
+ item.scribble.state = 'active'
579
+ item.timeoutMs = 16
580
+
581
+ expect(() => scribbleManager.tick(16)).not.toThrow()
582
+ })
583
+ })
584
+
585
+ describe('integration scenarios', () => {
586
+ it('should handle complete scribble lifecycle', () => {
587
+ const item = scribbleManager.addScribble({ delay: 100 })
588
+
589
+ // Starting state - add points
590
+ for (let i = 0; i < 10; i++) {
591
+ item.next = { x: i, y: i, z: 0.5 }
592
+ item.prev = null
593
+ scribbleManager.tick(16)
594
+ }
595
+
596
+ expect(item.scribble.state).toBe('active')
597
+ expect(item.scribble.points).toHaveLength(10)
598
+
599
+ // Stop the scribble
600
+ scribbleManager.stop(item.id)
601
+ expect(item.scribble.state).toBe('stopping')
602
+
603
+ // Process until removed
604
+ let iterations = 0
605
+ while (scribbleManager.scribbleItems.has(item.id) && iterations < 20) {
606
+ scribbleManager.tick(16)
607
+ iterations++
608
+ }
609
+
610
+ expect(scribbleManager.scribbleItems.has(item.id)).toBe(false)
611
+ })
612
+
613
+ it('should handle rapid point additions', () => {
614
+ const item = scribbleManager.addScribble({})
615
+
616
+ // Add many points rapidly
617
+ for (let i = 0; i < 100; i++) {
618
+ scribbleManager.addPoint(item.id, i * 2, i * 2) // Ensure distance > 1
619
+ }
620
+
621
+ expect(item.next).toEqual({ x: 198, y: 198, z: 0.5 })
622
+ })
623
+ })
624
+ })