tldraw 4.3.0-canary.ef37ae623ce8 → 4.3.0-canary.ef709265bb13
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 +3 -0
- package/dist-cjs/index.js +2 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/canvas/TldrawSelectionForeground.js +2 -2
- package/dist-cjs/lib/canvas/TldrawSelectionForeground.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +5 -10
- package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +3 -3
- package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +3 -4
- package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +4 -5
- package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/HyperlinkButton.js +2 -1
- package/dist-cjs/lib/shapes/shared/HyperlinkButton.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/ShapeFill.js +2 -2
- package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/{useForceSolid.js → useEfficientZoomThreshold.js} +10 -7
- package/dist-cjs/lib/shapes/shared/useEfficientZoomThreshold.js.map +7 -0
- package/dist-cjs/lib/shapes/shared/useImageOrVideoAsset.js +1 -1
- package/dist-cjs/lib/shapes/shared/useImageOrVideoAsset.js.map +2 -2
- package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +2 -2
- package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.js +3 -9
- package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/ZoomMenu/DefaultZoomMenu.js +1 -1
- package/dist-cjs/lib/ui/components/ZoomMenu/DefaultZoomMenu.js.map +2 -2
- package/dist-cjs/lib/ui/components/menu-items.js +3 -1
- package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +143 -88
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +1 -1
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/version.js +3 -3
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-cjs/lib/utils/text/richText.js +7 -17
- package/dist-cjs/lib/utils/text/richText.js.map +3 -3
- package/dist-esm/index.d.mts +3 -0
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/canvas/TldrawSelectionForeground.mjs +2 -2
- package/dist-esm/lib/canvas/TldrawSelectionForeground.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +6 -12
- package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +3 -3
- package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +1 -1
- package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +3 -4
- package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +1 -1
- package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +4 -5
- package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs +3 -2
- package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/ShapeFill.mjs +2 -2
- package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/useEfficientZoomThreshold.mjs +12 -0
- package/dist-esm/lib/shapes/shared/useEfficientZoomThreshold.mjs.map +7 -0
- package/dist-esm/lib/shapes/shared/useImageOrVideoAsset.mjs +1 -1
- package/dist-esm/lib/shapes/shared/useImageOrVideoAsset.mjs.map +2 -2
- package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +1 -1
- package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.mjs +2 -8
- package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/ZoomMenu/DefaultZoomMenu.mjs +1 -1
- package/dist-esm/lib/ui/components/ZoomMenu/DefaultZoomMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/components/menu-items.mjs +3 -1
- package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +151 -90
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/version.mjs +3 -3
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/dist-esm/lib/utils/text/richText.mjs +3 -3
- package/dist-esm/lib/utils/text/richText.mjs.map +2 -2
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/lib/canvas/TldrawSelectionForeground.tsx +2 -2
- package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +6 -11
- package/src/lib/shapes/draw/DrawShapeUtil.tsx +3 -3
- package/src/lib/shapes/frame/FrameShapeUtil.tsx +1 -1
- package/src/lib/shapes/geo/GeoShapeUtil.tsx +3 -4
- package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +1 -1
- package/src/lib/shapes/note/NoteShapeUtil.tsx +6 -8
- package/src/lib/shapes/shared/HyperlinkButton.tsx +3 -2
- package/src/lib/shapes/shared/ShapeFill.tsx +2 -2
- package/src/lib/shapes/shared/useEfficientZoomThreshold.ts +10 -0
- package/src/lib/shapes/shared/useImageOrVideoAsset.ts +1 -1
- package/src/lib/shapes/video/VideoShapeUtil.tsx +2 -1
- package/src/lib/ui/components/ActionsMenu/DefaultActionsMenuContent.tsx +1 -9
- package/src/lib/ui/components/ZoomMenu/DefaultZoomMenu.tsx +1 -1
- package/src/lib/ui/components/menu-items.tsx +3 -1
- package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +2 -2
- package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +196 -108
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -2
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/utils/text/richText.ts +3 -3
- package/src/test/commands/cameraState.test.ts +299 -0
- package/src/test/commands/putContent.test.ts +79 -1
- package/dist-cjs/lib/shapes/shared/useForceSolid.js.map +0 -7
- package/dist-esm/lib/shapes/shared/useForceSolid.mjs +0 -9
- package/dist-esm/lib/shapes/shared/useForceSolid.mjs.map +0 -7
- package/src/lib/shapes/shared/useForceSolid.ts +0 -6
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { Box } from '@tldraw/editor'
|
|
2
|
+
import { TestEditor } from '../TestEditor'
|
|
3
|
+
|
|
4
|
+
let editor: TestEditor
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
editor = new TestEditor()
|
|
8
|
+
editor.updateViewportScreenBounds(new Box(0, 0, 1600, 900))
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('getCameraState', () => {
|
|
12
|
+
it('starts as idle', () => {
|
|
13
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('becomes moving when the camera changes via setCamera', () => {
|
|
17
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
18
|
+
editor.setCamera({ x: 100, y: 100, z: 1 })
|
|
19
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('becomes moving when the camera changes via pan', () => {
|
|
23
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
24
|
+
editor.pan({ x: 100, y: 100 })
|
|
25
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('becomes moving when the camera changes via zoomIn', () => {
|
|
29
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
30
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
31
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('returns to idle after the timeout elapses', () => {
|
|
35
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
36
|
+
editor.setCamera({ x: 100, y: 100, z: 1 })
|
|
37
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
38
|
+
|
|
39
|
+
// The default timeout is 64ms (options.cameraMovingTimeoutMs)
|
|
40
|
+
// Each tick is 16ms, so we need ~4 ticks to elapse
|
|
41
|
+
editor.forceTick(5)
|
|
42
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('stays moving while camera continues to change', () => {
|
|
46
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
47
|
+
editor.setCamera({ x: 100, y: 100, z: 1 })
|
|
48
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
49
|
+
|
|
50
|
+
// Move again before timeout elapses
|
|
51
|
+
editor.forceTick(2)
|
|
52
|
+
editor.setCamera({ x: 200, y: 200, z: 1 })
|
|
53
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
54
|
+
|
|
55
|
+
// Move again
|
|
56
|
+
editor.forceTick(2)
|
|
57
|
+
editor.setCamera({ x: 300, y: 300, z: 1 })
|
|
58
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
59
|
+
|
|
60
|
+
// Now let it settle
|
|
61
|
+
editor.forceTick(5)
|
|
62
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('stays idle when camera position does not actually change', () => {
|
|
66
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
67
|
+
|
|
68
|
+
// Setting the same camera position should not trigger moving state
|
|
69
|
+
const currentCamera = editor.getCamera()
|
|
70
|
+
editor.setCamera({ x: currentCamera.x, y: currentCamera.y, z: currentCamera.z })
|
|
71
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('does not add multiple tick listeners when camera changes rapidly', () => {
|
|
75
|
+
// This test verifies the fix: we should not have redundant listeners
|
|
76
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
77
|
+
|
|
78
|
+
// Change camera multiple times rapidly
|
|
79
|
+
editor.setCamera({ x: 100, y: 100, z: 1 })
|
|
80
|
+
editor.setCamera({ x: 200, y: 200, z: 1 })
|
|
81
|
+
editor.setCamera({ x: 300, y: 300, z: 1 })
|
|
82
|
+
|
|
83
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
84
|
+
|
|
85
|
+
// After timeout, should return to idle exactly once
|
|
86
|
+
// If there were multiple listeners, the state might behave unexpectedly
|
|
87
|
+
editor.forceTick(5)
|
|
88
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('resets timeout when camera changes while already moving', () => {
|
|
92
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
93
|
+
editor.setCamera({ x: 100, y: 100, z: 1 })
|
|
94
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
95
|
+
|
|
96
|
+
// Wait almost until timeout
|
|
97
|
+
editor.forceTick(3)
|
|
98
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
99
|
+
|
|
100
|
+
// Change camera again - should reset timeout
|
|
101
|
+
editor.setCamera({ x: 200, y: 200, z: 1 })
|
|
102
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
103
|
+
|
|
104
|
+
// Wait 3 more ticks - would have been idle if timeout wasn't reset
|
|
105
|
+
editor.forceTick(3)
|
|
106
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
107
|
+
|
|
108
|
+
// Now let it fully settle
|
|
109
|
+
editor.forceTick(3)
|
|
110
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('camera state with zoom', () => {
|
|
115
|
+
it('becomes moving on zoomOut', () => {
|
|
116
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
117
|
+
editor.zoomOut(undefined, { immediate: true })
|
|
118
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('becomes moving on centerOnPoint', () => {
|
|
122
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
123
|
+
editor.centerOnPoint({ x: 500, y: 500 })
|
|
124
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('becomes moving on zoomToFit', () => {
|
|
128
|
+
// Create a shape so zoomToFit has something to fit
|
|
129
|
+
editor.createShape({ type: 'geo', x: 100, y: 100, props: { w: 200, h: 200 } })
|
|
130
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
131
|
+
editor.zoomToFit({ immediate: true })
|
|
132
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('getDebouncedZoomLevel', () => {
|
|
137
|
+
it('returns the current zoom level when camera is idle', () => {
|
|
138
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
139
|
+
expect(editor.getDebouncedZoomLevel()).toBe(editor.getZoomLevel())
|
|
140
|
+
|
|
141
|
+
// Change zoom and let it settle
|
|
142
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
143
|
+
editor.forceTick(5)
|
|
144
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
145
|
+
expect(editor.getDebouncedZoomLevel()).toBe(editor.getZoomLevel())
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('captures zoom when camera starts moving', () => {
|
|
149
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
150
|
+
|
|
151
|
+
// Start zooming - the debounced zoom is captured when movement starts
|
|
152
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
153
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
154
|
+
|
|
155
|
+
// The debounced zoom is captured at the moment movement starts (after first change)
|
|
156
|
+
const capturedZoom = editor.getDebouncedZoomLevel()
|
|
157
|
+
expect(capturedZoom).toBe(editor.getZoomLevel())
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('keeps captured zoom during continued camera movement', () => {
|
|
161
|
+
// Start zooming
|
|
162
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
163
|
+
const capturedZoom = editor.getDebouncedZoomLevel()
|
|
164
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
165
|
+
|
|
166
|
+
// Zoom again while still moving - debounced value should stay the same
|
|
167
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
168
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
169
|
+
expect(editor.getDebouncedZoomLevel()).toBe(capturedZoom)
|
|
170
|
+
|
|
171
|
+
// But current zoom should have changed
|
|
172
|
+
expect(editor.getZoomLevel()).not.toBe(capturedZoom)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('updates debounced zoom when camera becomes idle again', () => {
|
|
176
|
+
// Start zooming
|
|
177
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
178
|
+
const capturedZoom = editor.getDebouncedZoomLevel()
|
|
179
|
+
|
|
180
|
+
// Zoom again while moving to change the current zoom
|
|
181
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
182
|
+
expect(editor.getDebouncedZoomLevel()).toBe(capturedZoom)
|
|
183
|
+
|
|
184
|
+
// Let camera settle
|
|
185
|
+
editor.forceTick(5)
|
|
186
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
187
|
+
|
|
188
|
+
// Debounced zoom should now match current zoom
|
|
189
|
+
expect(editor.getDebouncedZoomLevel()).toBe(editor.getZoomLevel())
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('captures new zoom at the start of each new movement', () => {
|
|
193
|
+
// First zoom and settle
|
|
194
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
195
|
+
const firstCapturedZoom = editor.getDebouncedZoomLevel()
|
|
196
|
+
editor.forceTick(5)
|
|
197
|
+
expect(editor.getCameraState()).toBe('idle')
|
|
198
|
+
|
|
199
|
+
// Second zoom - should capture new zoom level
|
|
200
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
201
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
202
|
+
// The captured zoom should be different from the first capture
|
|
203
|
+
expect(editor.getDebouncedZoomLevel()).not.toBe(firstCapturedZoom)
|
|
204
|
+
// And it should match the current zoom (since we just started moving)
|
|
205
|
+
expect(editor.getDebouncedZoomLevel()).toBe(editor.getZoomLevel())
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('with debouncedZoom option disabled', () => {
|
|
209
|
+
let editorWithoutDebouncedZoom: TestEditor
|
|
210
|
+
|
|
211
|
+
beforeEach(() => {
|
|
212
|
+
editorWithoutDebouncedZoom = new TestEditor({
|
|
213
|
+
options: { debouncedZoom: false },
|
|
214
|
+
})
|
|
215
|
+
editorWithoutDebouncedZoom.updateViewportScreenBounds(new Box(0, 0, 1600, 900))
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('always returns the current zoom level even when camera is moving', () => {
|
|
219
|
+
const initialZoom = editorWithoutDebouncedZoom.getZoomLevel()
|
|
220
|
+
|
|
221
|
+
editorWithoutDebouncedZoom.zoomIn(undefined, { immediate: true })
|
|
222
|
+
expect(editorWithoutDebouncedZoom.getCameraState()).toBe('moving')
|
|
223
|
+
|
|
224
|
+
// Should return the current zoom, not the captured one
|
|
225
|
+
expect(editorWithoutDebouncedZoom.getDebouncedZoomLevel()).toBe(
|
|
226
|
+
editorWithoutDebouncedZoom.getZoomLevel()
|
|
227
|
+
)
|
|
228
|
+
expect(editorWithoutDebouncedZoom.getDebouncedZoomLevel()).not.toBe(initialZoom)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('getEfficientZoomLevel', () => {
|
|
234
|
+
it('returns current zoom level when below shape threshold', () => {
|
|
235
|
+
// Default threshold is 500 shapes, we have 0
|
|
236
|
+
expect(editor.getZoomLevel()).toBe(editor.getEfficientZoomLevel())
|
|
237
|
+
|
|
238
|
+
// Add a few shapes - still below threshold
|
|
239
|
+
for (let i = 0; i < 10; i++) {
|
|
240
|
+
editor.createShape({ type: 'geo', x: i * 100, y: 0, props: { w: 50, h: 50 } })
|
|
241
|
+
}
|
|
242
|
+
expect(editor.getCurrentPageShapeIds().size).toBe(10)
|
|
243
|
+
|
|
244
|
+
// Start zooming
|
|
245
|
+
editor.zoomIn(undefined, { immediate: true })
|
|
246
|
+
expect(editor.getCameraState()).toBe('moving')
|
|
247
|
+
|
|
248
|
+
// Should still return current zoom because we're below threshold
|
|
249
|
+
expect(editor.getEfficientZoomLevel()).toBe(editor.getZoomLevel())
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
describe('with many shapes above threshold', () => {
|
|
253
|
+
let editorWithManyShapes: TestEditor
|
|
254
|
+
|
|
255
|
+
beforeEach(() => {
|
|
256
|
+
// Use a lower threshold for testing
|
|
257
|
+
editorWithManyShapes = new TestEditor({
|
|
258
|
+
options: { debouncedZoomThreshold: 5 },
|
|
259
|
+
})
|
|
260
|
+
editorWithManyShapes.updateViewportScreenBounds(new Box(0, 0, 1600, 900))
|
|
261
|
+
|
|
262
|
+
// Add shapes above the threshold
|
|
263
|
+
for (let i = 0; i < 10; i++) {
|
|
264
|
+
editorWithManyShapes.createShape({
|
|
265
|
+
type: 'geo',
|
|
266
|
+
x: i * 100,
|
|
267
|
+
y: 0,
|
|
268
|
+
props: { w: 50, h: 50 },
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('returns debounced zoom level when above shape threshold and camera is moving', () => {
|
|
274
|
+
// First zoom to capture a debounced value
|
|
275
|
+
editorWithManyShapes.zoomIn(undefined, { immediate: true })
|
|
276
|
+
const capturedZoom = editorWithManyShapes.getEfficientZoomLevel()
|
|
277
|
+
expect(editorWithManyShapes.getCameraState()).toBe('moving')
|
|
278
|
+
|
|
279
|
+
// Zoom again while still moving
|
|
280
|
+
editorWithManyShapes.zoomIn(undefined, { immediate: true })
|
|
281
|
+
expect(editorWithManyShapes.getCameraState()).toBe('moving')
|
|
282
|
+
|
|
283
|
+
// Should return the captured zoom, not the current zoom
|
|
284
|
+
expect(editorWithManyShapes.getEfficientZoomLevel()).toBe(capturedZoom)
|
|
285
|
+
expect(editorWithManyShapes.getEfficientZoomLevel()).not.toBe(
|
|
286
|
+
editorWithManyShapes.getZoomLevel()
|
|
287
|
+
)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('returns current zoom level when above threshold but camera is idle', () => {
|
|
291
|
+
editorWithManyShapes.zoomIn(undefined, { immediate: true })
|
|
292
|
+
editorWithManyShapes.forceTick(5)
|
|
293
|
+
expect(editorWithManyShapes.getCameraState()).toBe('idle')
|
|
294
|
+
|
|
295
|
+
// Should return current zoom because camera is idle
|
|
296
|
+
expect(editorWithManyShapes.getEfficientZoomLevel()).toBe(editorWithManyShapes.getZoomLevel())
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TLContent, structuredClone } from '@tldraw/editor'
|
|
1
|
+
import { TLContent, createShapeId, structuredClone } from '@tldraw/editor'
|
|
2
2
|
import { TestEditor } from '../TestEditor'
|
|
3
3
|
|
|
4
4
|
let editor: TestEditor
|
|
@@ -38,3 +38,81 @@ describe('Migrations', () => {
|
|
|
38
38
|
expect(() => editor.putContentOntoCurrentPage(withInvalidShapeModel)).toThrow()
|
|
39
39
|
})
|
|
40
40
|
})
|
|
41
|
+
|
|
42
|
+
describe('Paste parent selection with explicit point', () => {
|
|
43
|
+
it('falls back to the page when the cursor is outside the original parent', () => {
|
|
44
|
+
const frameId = createShapeId('frame')
|
|
45
|
+
const childId = createShapeId('child')
|
|
46
|
+
|
|
47
|
+
editor.createShapes([
|
|
48
|
+
{
|
|
49
|
+
id: frameId,
|
|
50
|
+
type: 'frame',
|
|
51
|
+
x: 0,
|
|
52
|
+
y: 0,
|
|
53
|
+
props: { w: 200, h: 200 },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: childId,
|
|
57
|
+
type: 'geo',
|
|
58
|
+
parentId: frameId,
|
|
59
|
+
x: 40,
|
|
60
|
+
y: 40,
|
|
61
|
+
props: { w: 60, h: 60 },
|
|
62
|
+
},
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
editor.select(childId)
|
|
66
|
+
editor.copy()
|
|
67
|
+
|
|
68
|
+
editor.putContentOntoCurrentPage(editor.clipboard!, {
|
|
69
|
+
point: { x: 500, y: 500 },
|
|
70
|
+
select: true,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const [pastedId] = editor.getSelectedShapeIds()
|
|
74
|
+
expect(editor.getShape(pastedId)?.parentId).toBe(editor.getCurrentPageId())
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('uses the parent under the cursor when it can accept the pasted shapes', () => {
|
|
78
|
+
const frameAId = createShapeId('frameA')
|
|
79
|
+
const frameBId = createShapeId('frameB')
|
|
80
|
+
const childId = createShapeId('child')
|
|
81
|
+
|
|
82
|
+
editor.createShapes([
|
|
83
|
+
{
|
|
84
|
+
id: frameAId,
|
|
85
|
+
type: 'frame',
|
|
86
|
+
x: 0,
|
|
87
|
+
y: 0,
|
|
88
|
+
props: { w: 200, h: 200 },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: frameBId,
|
|
92
|
+
type: 'frame',
|
|
93
|
+
x: 400,
|
|
94
|
+
y: 0,
|
|
95
|
+
props: { w: 200, h: 200 },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: childId,
|
|
99
|
+
type: 'geo',
|
|
100
|
+
parentId: frameAId,
|
|
101
|
+
x: 40,
|
|
102
|
+
y: 40,
|
|
103
|
+
props: { w: 60, h: 60 },
|
|
104
|
+
},
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
editor.select(childId)
|
|
108
|
+
editor.copy()
|
|
109
|
+
|
|
110
|
+
editor.putContentOntoCurrentPage(editor.clipboard!, {
|
|
111
|
+
point: { x: 450, y: 50 },
|
|
112
|
+
select: true,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const [pastedId] = editor.getSelectedShapeIds()
|
|
116
|
+
expect(editor.getShape(pastedId)?.parentId).toBe(frameBId)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/lib/shapes/shared/useForceSolid.ts"],
|
|
4
|
-
"sourcesContent": ["import { useEditor, useValue } from '@tldraw/editor'\n\nexport function useForceSolid() {\n\tconst editor = useEditor()\n\treturn useValue('zoom', () => editor.getZoomLevel() < 0.35, [editor])\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAoC;AAE7B,SAAS,gBAAgB;AAC/B,QAAM,aAAS,yBAAU;AACzB,aAAO,wBAAS,QAAQ,MAAM,OAAO,aAAa,IAAI,MAAM,CAAC,MAAM,CAAC;AACrE;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { useEditor, useValue } from "@tldraw/editor";
|
|
2
|
-
function useForceSolid() {
|
|
3
|
-
const editor = useEditor();
|
|
4
|
-
return useValue("zoom", () => editor.getZoomLevel() < 0.35, [editor]);
|
|
5
|
-
}
|
|
6
|
-
export {
|
|
7
|
-
useForceSolid
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=useForceSolid.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/lib/shapes/shared/useForceSolid.ts"],
|
|
4
|
-
"sourcesContent": ["import { useEditor, useValue } from '@tldraw/editor'\n\nexport function useForceSolid() {\n\tconst editor = useEditor()\n\treturn useValue('zoom', () => editor.getZoomLevel() < 0.35, [editor])\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,WAAW,gBAAgB;AAE7B,SAAS,gBAAgB;AAC/B,QAAM,SAAS,UAAU;AACzB,SAAO,SAAS,QAAQ,MAAM,OAAO,aAAa,IAAI,MAAM,CAAC,MAAM,CAAC;AACrE;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|