@tldraw/editor 3.14.0-canary.ee1f3f027485 → 3.14.0-canary.ee7f3d43f08d
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 +122 -104
- package/dist-cjs/index.js +8 -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 +76 -78
- 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} +64 -6
- 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} +72 -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 -0
- 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/StateNode.js +3 -3
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- 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/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/reorderShapes.js +11 -10
- package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
- 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 +122 -104
- package/dist-esm/index.mjs +12 -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 +76 -78
- 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} +60 -2
- 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} +72 -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 -0
- 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/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/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/reorderShapes.mjs +11 -10
- package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
- 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 +433 -482
- package/package.json +8 -9
- package/src/index.ts +15 -7
- package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
- package/src/lib/editor/Editor.test.ts +252 -3
- package/src/lib/editor/Editor.ts +77 -76
- 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} +73 -2
- 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} +117 -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 +10 -1
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +8 -0
- package/src/lib/editor/tools/StateNode.ts +3 -3
- 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/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/reorderShapes.ts +10 -13
- 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
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { TLFrameShape, TLGroupShape, TLPageId, TLShapeId, createShapeId } from '@tldraw/tlschema'
|
|
2
|
+
import { Box } from '../../../primitives/Box'
|
|
3
|
+
import { Vec } from '../../../primitives/Vec'
|
|
4
|
+
import { Editor } from '../../Editor'
|
|
5
|
+
import { BoundsSnaps } from './BoundsSnaps'
|
|
6
|
+
import { HandleSnaps } from './HandleSnaps'
|
|
7
|
+
import { GapsSnapIndicator, PointsSnapIndicator, SnapManager } from './SnapManager'
|
|
8
|
+
|
|
9
|
+
// Mock the Editor class
|
|
10
|
+
jest.mock('../../Editor')
|
|
11
|
+
jest.mock('./BoundsSnaps')
|
|
12
|
+
jest.mock('./HandleSnaps')
|
|
13
|
+
|
|
14
|
+
describe('SnapManager', () => {
|
|
15
|
+
let editor: jest.Mocked<Editor>
|
|
16
|
+
let snapManager: SnapManager
|
|
17
|
+
|
|
18
|
+
const createMockShape = (
|
|
19
|
+
id: TLShapeId,
|
|
20
|
+
type: string = 'geo',
|
|
21
|
+
parentId: TLShapeId | string = 'page:page'
|
|
22
|
+
) => ({
|
|
23
|
+
id,
|
|
24
|
+
type,
|
|
25
|
+
parentId,
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 0,
|
|
28
|
+
rotation: 0,
|
|
29
|
+
index: 'a1' as const,
|
|
30
|
+
opacity: 1,
|
|
31
|
+
isLocked: false,
|
|
32
|
+
meta: {},
|
|
33
|
+
props: {},
|
|
34
|
+
typeName: 'shape' as const,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const createMockFrameShape = (id: TLShapeId): TLFrameShape =>
|
|
38
|
+
({
|
|
39
|
+
...createMockShape(id, 'frame'),
|
|
40
|
+
type: 'frame',
|
|
41
|
+
props: {
|
|
42
|
+
w: 100,
|
|
43
|
+
h: 100,
|
|
44
|
+
name: '',
|
|
45
|
+
},
|
|
46
|
+
}) as TLFrameShape
|
|
47
|
+
|
|
48
|
+
const createMockGroupShape = (id: TLShapeId): TLGroupShape =>
|
|
49
|
+
({
|
|
50
|
+
...createMockShape(id, 'group'),
|
|
51
|
+
type: 'group',
|
|
52
|
+
props: {},
|
|
53
|
+
}) as TLGroupShape
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
editor = {
|
|
57
|
+
getZoomLevel: jest.fn(() => 1),
|
|
58
|
+
getViewportPageBounds: jest.fn(() => new Box(0, 0, 1000, 1000)),
|
|
59
|
+
getSelectedShapeIds: jest.fn(() => []),
|
|
60
|
+
getSelectedShapes: jest.fn(() => []),
|
|
61
|
+
findCommonAncestor: jest.fn(() => createShapeId('page')),
|
|
62
|
+
getCurrentPageId: jest.fn(() => 'page:page' as TLPageId),
|
|
63
|
+
getSortedChildIdsForParent: jest.fn(() => []),
|
|
64
|
+
getShape: jest.fn(),
|
|
65
|
+
getShapeUtil: jest.fn(() => ({
|
|
66
|
+
canSnap: jest.fn(() => true),
|
|
67
|
+
})),
|
|
68
|
+
getShapePageBounds: jest.fn(),
|
|
69
|
+
isShapeOfType: jest.fn(),
|
|
70
|
+
} as any
|
|
71
|
+
|
|
72
|
+
snapManager = new SnapManager(editor)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
jest.clearAllMocks()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('constructor and initialization', () => {
|
|
80
|
+
it('should initialize with editor reference', () => {
|
|
81
|
+
expect(snapManager.editor).toBe(editor)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should create BoundsSnaps instance', () => {
|
|
85
|
+
expect(BoundsSnaps).toHaveBeenCalledWith(snapManager)
|
|
86
|
+
expect(snapManager.shapeBounds).toBeInstanceOf(BoundsSnaps)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should create HandleSnaps instance', () => {
|
|
90
|
+
expect(HandleSnaps).toHaveBeenCalledWith(snapManager)
|
|
91
|
+
expect(snapManager.handles).toBeInstanceOf(HandleSnaps)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should initialize snap indicators as undefined', () => {
|
|
95
|
+
expect(snapManager.getIndicators()).toEqual([])
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('indicator management', () => {
|
|
100
|
+
describe('getIndicators', () => {
|
|
101
|
+
it('should return empty array when no indicators are set', () => {
|
|
102
|
+
expect(snapManager.getIndicators()).toEqual([])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should return set indicators', () => {
|
|
106
|
+
const indicators: PointsSnapIndicator[] = [
|
|
107
|
+
{
|
|
108
|
+
id: 'test-indicator',
|
|
109
|
+
type: 'points',
|
|
110
|
+
points: [
|
|
111
|
+
{ x: 10, y: 20 },
|
|
112
|
+
{ x: 30, y: 40 },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
snapManager.setIndicators(indicators)
|
|
118
|
+
expect(snapManager.getIndicators()).toEqual(indicators)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('setIndicators', () => {
|
|
123
|
+
it('should set points indicators', () => {
|
|
124
|
+
const indicators: PointsSnapIndicator[] = [
|
|
125
|
+
{
|
|
126
|
+
id: 'points-indicator',
|
|
127
|
+
type: 'points',
|
|
128
|
+
points: [new Vec(10, 20), new Vec(30, 40)],
|
|
129
|
+
},
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
snapManager.setIndicators(indicators)
|
|
133
|
+
expect(snapManager.getIndicators()).toEqual(indicators)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('should set gaps indicators', () => {
|
|
137
|
+
const indicators: GapsSnapIndicator[] = [
|
|
138
|
+
{
|
|
139
|
+
id: 'gaps-indicator',
|
|
140
|
+
type: 'gaps',
|
|
141
|
+
direction: 'horizontal',
|
|
142
|
+
gaps: [
|
|
143
|
+
{
|
|
144
|
+
startEdge: [
|
|
145
|
+
{ x: 0, y: 0 },
|
|
146
|
+
{ x: 10, y: 0 },
|
|
147
|
+
],
|
|
148
|
+
endEdge: [
|
|
149
|
+
{ x: 20, y: 0 },
|
|
150
|
+
{ x: 30, y: 0 },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
snapManager.setIndicators(indicators)
|
|
158
|
+
expect(snapManager.getIndicators()).toEqual(indicators)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should set mixed indicator types', () => {
|
|
162
|
+
const indicators = [
|
|
163
|
+
{
|
|
164
|
+
id: 'points-indicator',
|
|
165
|
+
type: 'points' as const,
|
|
166
|
+
points: [{ x: 10, y: 20 }],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'gaps-indicator',
|
|
170
|
+
type: 'gaps' as const,
|
|
171
|
+
direction: 'vertical' as const,
|
|
172
|
+
gaps: [
|
|
173
|
+
{
|
|
174
|
+
startEdge: [
|
|
175
|
+
{ x: 0, y: 0 },
|
|
176
|
+
{ x: 0, y: 10 },
|
|
177
|
+
] as [Vec, Vec],
|
|
178
|
+
endEdge: [
|
|
179
|
+
{ x: 0, y: 20 },
|
|
180
|
+
{ x: 0, y: 30 },
|
|
181
|
+
] as [Vec, Vec],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
snapManager.setIndicators(indicators)
|
|
188
|
+
expect(snapManager.getIndicators()).toEqual(indicators)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('clearIndicators', () => {
|
|
193
|
+
it('should clear indicators when they exist', () => {
|
|
194
|
+
const indicators: PointsSnapIndicator[] = [
|
|
195
|
+
{
|
|
196
|
+
id: 'test-indicator',
|
|
197
|
+
type: 'points',
|
|
198
|
+
points: [{ x: 10, y: 20 }],
|
|
199
|
+
},
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
snapManager.setIndicators(indicators)
|
|
203
|
+
expect(snapManager.getIndicators()).toHaveLength(1)
|
|
204
|
+
|
|
205
|
+
snapManager.clearIndicators()
|
|
206
|
+
expect(snapManager.getIndicators()).toEqual([])
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should not throw when clearing empty indicators', () => {
|
|
210
|
+
expect(() => snapManager.clearIndicators()).not.toThrow()
|
|
211
|
+
expect(snapManager.getIndicators()).toEqual([])
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('getSnapThreshold', () => {
|
|
217
|
+
it('should calculate threshold based on zoom level', () => {
|
|
218
|
+
editor.getZoomLevel.mockReturnValue(1)
|
|
219
|
+
expect(snapManager.getSnapThreshold()).toBe(8)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should adjust threshold for different zoom levels', () => {
|
|
223
|
+
// Create a new SnapManager for each zoom level to avoid computed value caching
|
|
224
|
+
editor.getZoomLevel.mockReturnValue(2)
|
|
225
|
+
const snapManager2 = new SnapManager(editor)
|
|
226
|
+
expect(snapManager2.getSnapThreshold()).toBe(4)
|
|
227
|
+
|
|
228
|
+
editor.getZoomLevel.mockReturnValue(0.5)
|
|
229
|
+
const snapManager3 = new SnapManager(editor)
|
|
230
|
+
expect(snapManager3.getSnapThreshold()).toBe(16)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('should handle very small zoom levels', () => {
|
|
234
|
+
editor.getZoomLevel.mockReturnValue(0.1)
|
|
235
|
+
expect(snapManager.getSnapThreshold()).toBe(80)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should handle very large zoom levels', () => {
|
|
239
|
+
editor.getZoomLevel.mockReturnValue(10)
|
|
240
|
+
expect(snapManager.getSnapThreshold()).toBe(0.8)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
describe('getCurrentCommonAncestor', () => {
|
|
245
|
+
it('should return common ancestor of selected shapes', () => {
|
|
246
|
+
const shapeId = createShapeId('shape1')
|
|
247
|
+
const selectedShapes = [createMockShape(shapeId)]
|
|
248
|
+
|
|
249
|
+
editor.getSelectedShapes.mockReturnValue(selectedShapes as any)
|
|
250
|
+
editor.findCommonAncestor.mockReturnValue(shapeId)
|
|
251
|
+
|
|
252
|
+
expect(snapManager.getCurrentCommonAncestor()).toBe(shapeId)
|
|
253
|
+
expect(editor.findCommonAncestor).toHaveBeenCalledWith(selectedShapes)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should return null when no common ancestor found', () => {
|
|
257
|
+
editor.getSelectedShapes.mockReturnValue([])
|
|
258
|
+
editor.findCommonAncestor.mockReturnValue(undefined)
|
|
259
|
+
|
|
260
|
+
expect(snapManager.getCurrentCommonAncestor()).toBeUndefined()
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
describe('getSnappableShapes', () => {
|
|
265
|
+
it('should return empty set when no shapes in viewport', () => {
|
|
266
|
+
editor.getSortedChildIdsForParent.mockReturnValue([])
|
|
267
|
+
|
|
268
|
+
const result = snapManager.getSnappableShapes()
|
|
269
|
+
expect(result).toBeInstanceOf(Set)
|
|
270
|
+
expect(result.size).toBe(0)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('should include shapes that can snap and are in viewport', () => {
|
|
274
|
+
const shapeId = createShapeId('shape1')
|
|
275
|
+
const shape = createMockShape(shapeId)
|
|
276
|
+
|
|
277
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId])
|
|
278
|
+
editor.getShape.mockReturnValue(shape as any)
|
|
279
|
+
editor.getShapePageBounds.mockReturnValue(new Box(10, 10, 50, 50))
|
|
280
|
+
editor.getViewportPageBounds.mockReturnValue(new Box(0, 0, 100, 100))
|
|
281
|
+
|
|
282
|
+
const result = snapManager.getSnappableShapes()
|
|
283
|
+
expect(result.has(shapeId)).toBe(true)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('should exclude selected shapes', () => {
|
|
287
|
+
const selectedId = createShapeId('selected')
|
|
288
|
+
const unselectedId = createShapeId('unselected')
|
|
289
|
+
|
|
290
|
+
editor.getSelectedShapeIds.mockReturnValue([selectedId])
|
|
291
|
+
editor.getSortedChildIdsForParent.mockReturnValue([selectedId, unselectedId])
|
|
292
|
+
editor.getShape.mockReturnValue(createMockShape(unselectedId) as any)
|
|
293
|
+
editor.getShapePageBounds.mockReturnValue(new Box(10, 10, 50, 50))
|
|
294
|
+
|
|
295
|
+
const result = snapManager.getSnappableShapes()
|
|
296
|
+
expect(result.has(selectedId)).toBe(false)
|
|
297
|
+
expect(result.has(unselectedId)).toBe(true)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('should exclude shapes that cannot snap', () => {
|
|
301
|
+
const shapeId = createShapeId('shape1')
|
|
302
|
+
const shape = createMockShape(shapeId)
|
|
303
|
+
|
|
304
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId])
|
|
305
|
+
editor.getShape.mockReturnValue(shape as any)
|
|
306
|
+
editor.getShapeUtil.mockReturnValue({
|
|
307
|
+
canSnap: jest.fn(() => false),
|
|
308
|
+
} as any)
|
|
309
|
+
|
|
310
|
+
const result = snapManager.getSnappableShapes()
|
|
311
|
+
expect(result.has(shapeId)).toBe(false)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('should exclude shapes outside viewport bounds', () => {
|
|
315
|
+
const shapeId = createShapeId('shape1')
|
|
316
|
+
const shape = createMockShape(shapeId)
|
|
317
|
+
|
|
318
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId])
|
|
319
|
+
editor.getShape.mockReturnValue(shape as any)
|
|
320
|
+
editor.getShapePageBounds.mockReturnValue(new Box(200, 200, 50, 50))
|
|
321
|
+
editor.getViewportPageBounds.mockReturnValue(new Box(0, 0, 100, 100))
|
|
322
|
+
|
|
323
|
+
const result = snapManager.getSnappableShapes()
|
|
324
|
+
expect(result.has(shapeId)).toBe(false)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('should include frame shapes as snappable', () => {
|
|
328
|
+
const frameId = createShapeId('frame1')
|
|
329
|
+
const frameShape = createMockFrameShape(frameId)
|
|
330
|
+
|
|
331
|
+
editor.getSortedChildIdsForParent.mockReturnValue([frameId])
|
|
332
|
+
editor.getShape.mockReturnValue(frameShape as any)
|
|
333
|
+
editor.isShapeOfType.mockImplementation((_shape, type) => type === 'frame')
|
|
334
|
+
editor.getShapePageBounds.mockReturnValue(new Box(10, 10, 50, 50))
|
|
335
|
+
|
|
336
|
+
const result = snapManager.getSnappableShapes()
|
|
337
|
+
expect(result.has(frameId)).toBe(true)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('should recurse into group shapes but not include group itself', () => {
|
|
341
|
+
const groupId = createShapeId('group1')
|
|
342
|
+
const childId = createShapeId('child1')
|
|
343
|
+
const groupShape = createMockGroupShape(groupId)
|
|
344
|
+
const childShape = createMockShape(childId)
|
|
345
|
+
|
|
346
|
+
editor.getSortedChildIdsForParent
|
|
347
|
+
.mockReturnValueOnce([groupId]) // Root level
|
|
348
|
+
.mockReturnValueOnce([childId]) // Inside group
|
|
349
|
+
|
|
350
|
+
editor.getShape.mockImplementation((id) => {
|
|
351
|
+
if (id === groupId) return groupShape as any
|
|
352
|
+
if (id === childId) return childShape as any
|
|
353
|
+
return undefined
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
editor.isShapeOfType.mockImplementation(
|
|
357
|
+
(shape, type) => shape && (shape as any).type === type
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
editor.getShapePageBounds.mockReturnValue(new Box(10, 10, 50, 50))
|
|
361
|
+
|
|
362
|
+
const result = snapManager.getSnappableShapes()
|
|
363
|
+
expect(result.has(groupId)).toBe(false) // Group itself not included
|
|
364
|
+
expect(result.has(childId)).toBe(true) // Child is included
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('should handle nested frame structures', () => {
|
|
368
|
+
const parentFrameId = createShapeId('parent-frame')
|
|
369
|
+
const childFrameId = createShapeId('child-frame')
|
|
370
|
+
const parentFrame = createMockFrameShape(parentFrameId)
|
|
371
|
+
const childFrame = createMockFrameShape(childFrameId)
|
|
372
|
+
|
|
373
|
+
// Override the getCurrentCommonAncestor mock for this specific test
|
|
374
|
+
const originalGetCurrentCommonAncestor = snapManager.getCurrentCommonAncestor
|
|
375
|
+
jest.spyOn(snapManager, 'getCurrentCommonAncestor').mockReturnValue(parentFrameId)
|
|
376
|
+
|
|
377
|
+
editor.getSortedChildIdsForParent.mockReturnValueOnce([childFrameId]) // Children of parent frame
|
|
378
|
+
|
|
379
|
+
editor.getShape.mockImplementation((id) => {
|
|
380
|
+
if (id === parentFrameId) return parentFrame as any
|
|
381
|
+
if (id === childFrameId) return childFrame as any
|
|
382
|
+
return undefined
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
editor.isShapeOfType.mockImplementation((shape, type) => type === 'frame')
|
|
386
|
+
editor.getShapePageBounds.mockReturnValue(new Box(10, 10, 50, 50))
|
|
387
|
+
|
|
388
|
+
const result = snapManager.getSnappableShapes()
|
|
389
|
+
expect(result.has(childFrameId)).toBe(true)
|
|
390
|
+
|
|
391
|
+
// Restore original method
|
|
392
|
+
jest
|
|
393
|
+
.spyOn(snapManager, 'getCurrentCommonAncestor')
|
|
394
|
+
.mockImplementation(originalGetCurrentCommonAncestor)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should handle missing shape bounds gracefully', () => {
|
|
398
|
+
const shapeId = createShapeId('shape1')
|
|
399
|
+
const shape = createMockShape(shapeId)
|
|
400
|
+
|
|
401
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId])
|
|
402
|
+
editor.getShape.mockReturnValue(shape as any)
|
|
403
|
+
editor.getShapePageBounds.mockReturnValue(undefined)
|
|
404
|
+
|
|
405
|
+
const result = snapManager.getSnappableShapes()
|
|
406
|
+
expect(result.has(shapeId)).toBe(false)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('should handle missing shapes gracefully', () => {
|
|
410
|
+
const shapeId = createShapeId('shape1')
|
|
411
|
+
|
|
412
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId])
|
|
413
|
+
editor.getShape.mockReturnValue(undefined)
|
|
414
|
+
|
|
415
|
+
const result = snapManager.getSnappableShapes()
|
|
416
|
+
expect(result.has(shapeId)).toBe(false)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('should use current page as fallback when no common ancestor', () => {
|
|
420
|
+
const shapeId = createShapeId('shape1')
|
|
421
|
+
const shape = createMockShape(shapeId)
|
|
422
|
+
|
|
423
|
+
// Override the getCurrentCommonAncestor mock for this specific test
|
|
424
|
+
const originalGetCurrentCommonAncestor = snapManager.getCurrentCommonAncestor
|
|
425
|
+
jest.spyOn(snapManager, 'getCurrentCommonAncestor').mockReturnValue(undefined)
|
|
426
|
+
|
|
427
|
+
editor.getCurrentPageId.mockReturnValue('page:current' as TLPageId)
|
|
428
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId])
|
|
429
|
+
editor.getShape.mockReturnValue(shape as any)
|
|
430
|
+
editor.getShapePageBounds.mockReturnValue(new Box(10, 10, 50, 50))
|
|
431
|
+
|
|
432
|
+
snapManager.getSnappableShapes()
|
|
433
|
+
expect(editor.getSortedChildIdsForParent).toHaveBeenCalledWith('page:current')
|
|
434
|
+
|
|
435
|
+
// Restore original method
|
|
436
|
+
jest
|
|
437
|
+
.spyOn(snapManager, 'getCurrentCommonAncestor')
|
|
438
|
+
.mockImplementation(originalGetCurrentCommonAncestor)
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
describe('computed properties behavior', () => {
|
|
443
|
+
it('should calculate threshold based on current zoom level', () => {
|
|
444
|
+
// Test with different SnapManager instances to verify the computation works
|
|
445
|
+
editor.getZoomLevel.mockReturnValue(1)
|
|
446
|
+
const snapManager1 = new SnapManager(editor)
|
|
447
|
+
const threshold1 = snapManager1.getSnapThreshold()
|
|
448
|
+
|
|
449
|
+
editor.getZoomLevel.mockReturnValue(2)
|
|
450
|
+
const snapManager2 = new SnapManager(editor)
|
|
451
|
+
const threshold2 = snapManager2.getSnapThreshold()
|
|
452
|
+
|
|
453
|
+
expect(threshold1).toBe(8)
|
|
454
|
+
expect(threshold2).toBe(4)
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should calculate snappable shapes based on current editor state', () => {
|
|
458
|
+
const shapeId1 = createShapeId('shape1')
|
|
459
|
+
const shapeId2 = createShapeId('shape2')
|
|
460
|
+
|
|
461
|
+
// Test with first set of shapes
|
|
462
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId1])
|
|
463
|
+
editor.getShape.mockReturnValue(createMockShape(shapeId1) as any)
|
|
464
|
+
editor.getShapePageBounds.mockReturnValue(new Box(10, 10, 50, 50))
|
|
465
|
+
|
|
466
|
+
const snapManager1 = new SnapManager(editor)
|
|
467
|
+
const result1 = snapManager1.getSnappableShapes()
|
|
468
|
+
expect(result1.has(shapeId1)).toBe(true)
|
|
469
|
+
expect(result1.has(shapeId2)).toBe(false)
|
|
470
|
+
|
|
471
|
+
// Test with second set of shapes
|
|
472
|
+
editor.getSortedChildIdsForParent.mockReturnValue([shapeId1, shapeId2])
|
|
473
|
+
editor.getShape.mockImplementation((id) => {
|
|
474
|
+
if (id === shapeId1) return createMockShape(shapeId1) as any
|
|
475
|
+
if (id === shapeId2) return createMockShape(shapeId2) as any
|
|
476
|
+
return undefined
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
const snapManager2 = new SnapManager(editor)
|
|
480
|
+
const result2 = snapManager2.getSnappableShapes()
|
|
481
|
+
expect(result2.has(shapeId1)).toBe(true)
|
|
482
|
+
expect(result2.has(shapeId2)).toBe(true)
|
|
483
|
+
})
|
|
484
|
+
})
|
|
485
|
+
})
|