@tldraw/editor 3.14.0-canary.fd2ad122b803 → 3.14.0-canary.fdbfe5bf2604
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.
- package/dist-cjs/index.d.ts +220 -117
- package/dist-cjs/index.js +11 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
- package/dist-cjs/lib/editor/Editor.js +133 -102
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js +22 -22
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -20
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js +16 -16
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
- package/dist-cjs/lib/editor/managers/{ClickManager.js → ClickManager/ClickManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{EdgeScrollManager.js → EdgeScrollManager/EdgeScrollManager.js} +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{FontManager.js → FontManager/FontManager.js} +4 -1
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{HistoryManager.js → HistoryManager/HistoryManager.js} +67 -7
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{ScribbleManager.js → ScribbleManager/ScribbleManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{TextManager.js → TextManager/TextManager.js} +73 -42
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{TickManager.js → TickManager/TickManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{UserPreferencesManager.js → UserPreferencesManager/UserPreferencesManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +7 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +8 -10
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +6 -0
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +10 -6
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
- package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/exports/getSvgJsx.js.map +1 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +33 -33
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +13 -8
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +41 -21
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +11 -11
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +13 -16
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +4 -4
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +14 -17
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +10 -10
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +11 -6
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +6 -6
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js +3 -0
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js +8 -5
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js +22 -11
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js +22 -22
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +1 -1
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/reorderShapes.js +11 -10
- package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +232 -0
- package/dist-cjs/lib/utils/reparenting.js.map +7 -0
- package/dist-cjs/lib/utils/richText.js +7 -2
- package/dist-cjs/lib/utils/richText.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +220 -117
- package/dist-esm/index.mjs +15 -8
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +133 -102
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +22 -22
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -20
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +16 -16
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/{ClickManager.mjs → ClickManager/ClickManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{EdgeScrollManager.mjs → EdgeScrollManager/EdgeScrollManager.mjs} +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{FontManager.mjs → FontManager/FontManager.mjs} +4 -1
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{HistoryManager.mjs → HistoryManager/HistoryManager.mjs} +63 -3
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{ScribbleManager.mjs → ScribbleManager/ScribbleManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{TextManager.mjs → TextManager/TextManager.mjs} +73 -42
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{TickManager.mjs → TickManager/TickManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{UserPreferencesManager.mjs → UserPreferencesManager/UserPreferencesManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +7 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +8 -10
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +6 -0
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +10 -6
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
- package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +1 -1
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +33 -33
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +13 -8
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +41 -21
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +13 -16
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +4 -4
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +14 -17
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +11 -6
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +6 -6
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs +3 -0
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +8 -5
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs +22 -11
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +22 -22
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +1 -1
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/reorderShapes.mjs +11 -10
- package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +216 -0
- package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
- package/dist-esm/lib/utils/richText.mjs +8 -3
- package/dist-esm/lib/utils/richText.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +446 -489
- package/package.json +8 -9
- package/src/index.ts +20 -7
- package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
- package/src/lib/editor/Editor.test.ts +252 -3
- package/src/lib/editor/Editor.ts +152 -111
- package/src/lib/editor/bindings/BindingUtil.ts +6 -0
- package/src/lib/editor/derivations/bindingsIndex.ts +27 -26
- package/src/lib/editor/derivations/notVisibleShapes.ts +24 -25
- package/src/lib/editor/derivations/parentsToChildren.ts +28 -25
- package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +442 -0
- package/src/lib/editor/managers/{ClickManager.ts → ClickManager/ClickManager.ts} +3 -3
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +374 -0
- package/src/lib/editor/managers/{EdgeScrollManager.ts → EdgeScrollManager/EdgeScrollManager.ts} +3 -3
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +455 -0
- package/src/lib/editor/managers/{FocusManager.ts → FocusManager/FocusManager.ts} +1 -1
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +263 -0
- package/src/lib/editor/managers/{FontManager.ts → FontManager/FontManager.ts} +5 -2
- package/src/lib/editor/managers/{HistoryManager.test.ts → HistoryManager/HistoryManager.test.ts} +388 -1
- package/src/lib/editor/managers/{HistoryManager.ts → HistoryManager/HistoryManager.ts} +76 -3
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +624 -0
- package/src/lib/editor/managers/{ScribbleManager.ts → ScribbleManager/ScribbleManager.ts} +2 -2
- package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +485 -0
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +407 -0
- package/src/lib/editor/managers/{TextManager.ts → TextManager/TextManager.ts} +119 -87
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +314 -0
- package/src/lib/editor/managers/{TickManager.ts → TickManager/TickManager.ts} +2 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +591 -0
- package/src/lib/editor/managers/{UserPreferencesManager.ts → UserPreferencesManager/UserPreferencesManager.ts} +2 -2
- package/src/lib/editor/shapes/ShapeUtil.ts +57 -16
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +8 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +22 -17
- package/src/lib/editor/tools/StateNode.ts +3 -3
- package/src/lib/editor/types/emit-types.ts +4 -0
- package/src/lib/editor/types/external-content.ts +11 -2
- package/src/lib/exports/getSvgJsx.tsx +1 -1
- package/src/lib/hooks/useCanvasEvents.ts +0 -1
- package/src/lib/primitives/Box.test.ts +588 -7
- package/src/lib/primitives/Box.ts +33 -33
- package/src/lib/primitives/Vec.test.ts +2 -2
- package/src/lib/primitives/Vec.ts +13 -8
- package/src/lib/primitives/geometry/Arc2d.ts +42 -23
- package/src/lib/primitives/geometry/Circle2d.ts +12 -12
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +5 -0
- package/src/lib/primitives/geometry/CubicBezier2d.ts +13 -17
- package/src/lib/primitives/geometry/CubicSpline2d.ts +5 -5
- package/src/lib/primitives/geometry/Edge2d.ts +14 -18
- package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
- package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
- package/src/lib/primitives/geometry/Group2d.ts +11 -5
- package/src/lib/primitives/geometry/Point2d.ts +6 -6
- package/src/lib/primitives/geometry/Polygon2d.ts +4 -0
- package/src/lib/primitives/geometry/Polyline2d.ts +10 -7
- package/src/lib/primitives/geometry/Rectangle2d.ts +24 -11
- package/src/lib/primitives/geometry/Stadium2d.ts +22 -23
- package/src/lib/utils/dom.ts +1 -1
- package/src/lib/utils/reorderShapes.ts +10 -13
- package/src/lib/utils/reparenting.ts +383 -0
- package/src/lib/utils/richText.ts +10 -4
- package/src/version.ts +3 -3
- package/dist-cjs/lib/editor/managers/ClickManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/EdgeScrollManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/FocusManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/FontManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/HistoryManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/ScribbleManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/Stack.js +0 -82
- package/dist-cjs/lib/editor/managers/Stack.js.map +0 -7
- package/dist-cjs/lib/editor/managers/TextManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/TickManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/UserPreferencesManager.js.map +0 -7
- package/dist-esm/lib/editor/managers/ClickManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/EdgeScrollManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/FocusManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/FontManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/HistoryManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/ScribbleManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/Stack.mjs +0 -62
- package/dist-esm/lib/editor/managers/Stack.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/TickManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/UserPreferencesManager.mjs.map +0 -7
- package/src/lib/editor/managers/ScribbleManager.test.ts +0 -32
- package/src/lib/editor/managers/Stack.ts +0 -71
- /package/dist-cjs/lib/editor/managers/{FocusManager.js → FocusManager/FocusManager.js} +0 -0
- /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 '
|
|
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
|
-
|
|
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
|
+
})
|