tldraw 4.3.0-canary.c5efe11c58e0 → 4.3.0-canary.cf5673a789a1
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/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 +144 -77
- 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-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/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 +144 -78
- 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/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +2 -2
- package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +188 -95
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -2
- package/src/lib/ui/version.ts +3 -3
- package/src/test/TldrawEditor.test.tsx +3 -2
- package/src/test/commands/putContent.test.ts +79 -1
|
@@ -6,7 +6,6 @@ import React, {
|
|
|
6
6
|
ReactNode,
|
|
7
7
|
useContext,
|
|
8
8
|
useEffect,
|
|
9
|
-
useLayoutEffect,
|
|
10
9
|
useRef,
|
|
11
10
|
useState,
|
|
12
11
|
} from 'react'
|
|
@@ -25,7 +24,7 @@ export interface TldrawUiTooltipProps {
|
|
|
25
24
|
delayDuration?: number
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
interface
|
|
27
|
+
interface TooltipData {
|
|
29
28
|
id: string
|
|
30
29
|
content: ReactNode
|
|
31
30
|
side: 'top' | 'right' | 'bottom' | 'left'
|
|
@@ -35,11 +34,25 @@ interface CurrentTooltip {
|
|
|
35
34
|
delayDuration: number
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
//
|
|
37
|
+
// State machine states
|
|
38
|
+
type TooltipState =
|
|
39
|
+
| { name: 'idle' }
|
|
40
|
+
| { name: 'pointer_down' }
|
|
41
|
+
| { name: 'showing'; tooltip: TooltipData }
|
|
42
|
+
| { name: 'waiting_to_hide'; tooltip: TooltipData; timeoutId: number }
|
|
43
|
+
|
|
44
|
+
// State machine events
|
|
45
|
+
type TooltipEvent =
|
|
46
|
+
| { type: 'pointer_down' }
|
|
47
|
+
| { type: 'pointer_up' }
|
|
48
|
+
| { type: 'show'; tooltip: TooltipData }
|
|
49
|
+
| { type: 'hide'; tooltipId: string; editor: Editor | null; instant: boolean }
|
|
50
|
+
| { type: 'hide_all' }
|
|
51
|
+
|
|
52
|
+
// Singleton tooltip manager using explicit state machine
|
|
39
53
|
class TooltipManager {
|
|
40
54
|
private static instance: TooltipManager | null = null
|
|
41
|
-
private
|
|
42
|
-
private destroyTimeoutId: number | null = null
|
|
55
|
+
private state = atom<TooltipState>('tooltip state', { name: 'idle' })
|
|
43
56
|
|
|
44
57
|
static getInstance(): TooltipManager {
|
|
45
58
|
if (!TooltipManager.instance) {
|
|
@@ -48,69 +61,108 @@ class TooltipManager {
|
|
|
48
61
|
return TooltipManager.instance
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
content: string | React.ReactNode,
|
|
54
|
-
targetElement: HTMLElement,
|
|
55
|
-
side: 'top' | 'right' | 'bottom' | 'left',
|
|
56
|
-
sideOffset: number,
|
|
57
|
-
showOnMobile: boolean,
|
|
58
|
-
delayDuration: number
|
|
59
|
-
) {
|
|
60
|
-
// Clear any existing destroy timeout
|
|
61
|
-
if (this.destroyTimeoutId) {
|
|
62
|
-
clearTimeout(this.destroyTimeoutId)
|
|
63
|
-
this.destroyTimeoutId = null
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Update current tooltip
|
|
67
|
-
this.currentTooltip.set({
|
|
68
|
-
id: tooltipId,
|
|
69
|
-
content,
|
|
70
|
-
side,
|
|
71
|
-
sideOffset,
|
|
72
|
-
showOnMobile,
|
|
73
|
-
targetElement,
|
|
74
|
-
delayDuration,
|
|
75
|
-
})
|
|
64
|
+
hideAllTooltips() {
|
|
65
|
+
this.handleEvent({ type: 'hide_all' })
|
|
76
66
|
}
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
this.
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
handleEvent(event: TooltipEvent) {
|
|
69
|
+
const currentState = this.state.get()
|
|
70
|
+
|
|
71
|
+
switch (event.type) {
|
|
72
|
+
case 'pointer_down': {
|
|
73
|
+
// Transition to pointer_down from any state
|
|
74
|
+
if (currentState.name === 'waiting_to_hide') {
|
|
75
|
+
clearTimeout(currentState.timeoutId)
|
|
76
|
+
}
|
|
77
|
+
this.state.set({ name: 'pointer_down' })
|
|
78
|
+
break
|
|
82
79
|
}
|
|
83
|
-
return tooltip
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
case 'pointer_up': {
|
|
82
|
+
// Only transition from pointer_down to idle
|
|
83
|
+
if (currentState.name === 'pointer_down') {
|
|
84
|
+
this.state.set({ name: 'idle' })
|
|
85
|
+
}
|
|
86
|
+
break
|
|
93
87
|
}
|
|
94
|
-
}
|
|
95
88
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
89
|
+
case 'show': {
|
|
90
|
+
// Don't show tooltips while pointer is down
|
|
91
|
+
if (currentState.name === 'pointer_down') {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Clear any existing timeout if transitioning from waiting_to_hide
|
|
96
|
+
if (currentState.name === 'waiting_to_hide') {
|
|
97
|
+
clearTimeout(currentState.timeoutId)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Transition to showing state
|
|
101
|
+
this.state.set({ name: 'showing', tooltip: event.tooltip })
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case 'hide': {
|
|
106
|
+
const { tooltipId, editor, instant } = event
|
|
107
|
+
|
|
108
|
+
// Only hide if the tooltip matches
|
|
109
|
+
if (currentState.name === 'showing' && currentState.tooltip.id === tooltipId) {
|
|
110
|
+
if (editor && !instant) {
|
|
111
|
+
// Transition to waiting_to_hide state
|
|
112
|
+
const timeoutId = editor.timers.setTimeout(() => {
|
|
113
|
+
const state = this.state.get()
|
|
114
|
+
if (state.name === 'waiting_to_hide' && state.tooltip.id === tooltipId) {
|
|
115
|
+
this.state.set({ name: 'idle' })
|
|
116
|
+
}
|
|
117
|
+
}, 300)
|
|
118
|
+
this.state.set({
|
|
119
|
+
name: 'waiting_to_hide',
|
|
120
|
+
tooltip: currentState.tooltip,
|
|
121
|
+
timeoutId,
|
|
122
|
+
})
|
|
123
|
+
} else {
|
|
124
|
+
this.state.set({ name: 'idle' })
|
|
125
|
+
}
|
|
126
|
+
} else if (
|
|
127
|
+
currentState.name === 'waiting_to_hide' &&
|
|
128
|
+
currentState.tooltip.id === tooltipId
|
|
129
|
+
) {
|
|
130
|
+
// Already waiting to hide, make it instant if requested
|
|
131
|
+
if (instant) {
|
|
132
|
+
clearTimeout(currentState.timeoutId)
|
|
133
|
+
this.state.set({ name: 'idle' })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case 'hide_all': {
|
|
140
|
+
if (currentState.name === 'waiting_to_hide') {
|
|
141
|
+
clearTimeout(currentState.timeoutId)
|
|
142
|
+
}
|
|
143
|
+
// Preserve pointer_down state if that's the current state
|
|
144
|
+
if (currentState.name === 'pointer_down') {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
this.state.set({ name: 'idle' })
|
|
148
|
+
break
|
|
149
|
+
}
|
|
101
150
|
}
|
|
102
151
|
}
|
|
103
152
|
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
}
|
|
153
|
+
getCurrentTooltipData(): TooltipData | null {
|
|
154
|
+
const currentState = this.state.get()
|
|
155
|
+
let tooltip: TooltipData | null = null
|
|
108
156
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
157
|
+
if (currentState.name === 'showing') {
|
|
158
|
+
tooltip = currentState.tooltip
|
|
159
|
+
} else if (currentState.name === 'waiting_to_hide') {
|
|
160
|
+
tooltip = currentState.tooltip
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!tooltip) return null
|
|
164
|
+
if (!this.supportsHover() && !tooltip.showOnMobile) return null
|
|
165
|
+
return tooltip
|
|
114
166
|
}
|
|
115
167
|
|
|
116
168
|
private supportsHoverAtom: Atom<boolean> | null = null
|
|
@@ -127,7 +179,12 @@ class TooltipManager {
|
|
|
127
179
|
}
|
|
128
180
|
}
|
|
129
181
|
|
|
130
|
-
|
|
182
|
+
const tooltipManager = TooltipManager.getInstance()
|
|
183
|
+
|
|
184
|
+
/** @public */
|
|
185
|
+
export function hideAllTooltips() {
|
|
186
|
+
tooltipManager.hideAllTooltips()
|
|
187
|
+
}
|
|
131
188
|
|
|
132
189
|
// Context for the tooltip singleton
|
|
133
190
|
const TooltipSingletonContext = createContext<boolean>(false)
|
|
@@ -167,14 +224,19 @@ function TooltipSingleton() {
|
|
|
167
224
|
// Hide tooltip when camera is moving (panning/zooming)
|
|
168
225
|
useEffect(() => {
|
|
169
226
|
if (cameraState === 'moving' && isOpen && currentTooltip) {
|
|
170
|
-
tooltipManager.
|
|
227
|
+
tooltipManager.handleEvent({
|
|
228
|
+
type: 'hide',
|
|
229
|
+
tooltipId: currentTooltip.id,
|
|
230
|
+
editor,
|
|
231
|
+
instant: true,
|
|
232
|
+
})
|
|
171
233
|
}
|
|
172
234
|
}, [cameraState, isOpen, currentTooltip, editor])
|
|
173
235
|
|
|
174
236
|
useEffect(() => {
|
|
175
237
|
function handleKeyDown(event: KeyboardEvent) {
|
|
176
238
|
if (event.key === 'Escape' && currentTooltip && isOpen) {
|
|
177
|
-
|
|
239
|
+
hideAllTooltips()
|
|
178
240
|
event.stopPropagation()
|
|
179
241
|
}
|
|
180
242
|
}
|
|
@@ -183,7 +245,29 @@ function TooltipSingleton() {
|
|
|
183
245
|
return () => {
|
|
184
246
|
document.removeEventListener('keydown', handleKeyDown, { capture: true })
|
|
185
247
|
}
|
|
186
|
-
}, [
|
|
248
|
+
}, [currentTooltip, isOpen])
|
|
249
|
+
|
|
250
|
+
// Hide tooltip and prevent new ones from opening while pointer is down
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
function handlePointerDown() {
|
|
253
|
+
tooltipManager.handleEvent({ type: 'pointer_down' })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function handlePointerUp() {
|
|
257
|
+
tooltipManager.handleEvent({ type: 'pointer_up' })
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
document.addEventListener('pointerdown', handlePointerDown, { capture: true })
|
|
261
|
+
document.addEventListener('pointerup', handlePointerUp, { capture: true })
|
|
262
|
+
document.addEventListener('pointercancel', handlePointerUp, { capture: true })
|
|
263
|
+
return () => {
|
|
264
|
+
document.removeEventListener('pointerdown', handlePointerDown, { capture: true })
|
|
265
|
+
document.removeEventListener('pointerup', handlePointerUp, { capture: true })
|
|
266
|
+
document.removeEventListener('pointercancel', handlePointerUp, { capture: true })
|
|
267
|
+
// Reset pointer state on unmount to prevent stuck state
|
|
268
|
+
tooltipManager.handleEvent({ type: 'pointer_up' })
|
|
269
|
+
}
|
|
270
|
+
}, [])
|
|
187
271
|
|
|
188
272
|
// Update open state and trigger position
|
|
189
273
|
useEffect(() => {
|
|
@@ -280,23 +364,16 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
280
364
|
const currentTooltipId = tooltipId.current
|
|
281
365
|
return () => {
|
|
282
366
|
if (hasProvider) {
|
|
283
|
-
tooltipManager.
|
|
367
|
+
tooltipManager.handleEvent({
|
|
368
|
+
type: 'hide',
|
|
369
|
+
tooltipId: currentTooltipId,
|
|
370
|
+
editor,
|
|
371
|
+
instant: true,
|
|
372
|
+
})
|
|
284
373
|
}
|
|
285
374
|
}
|
|
286
375
|
}, [editor, hasProvider])
|
|
287
376
|
|
|
288
|
-
useLayoutEffect(() => {
|
|
289
|
-
if (hasProvider && tooltipManager.getCurrentTooltipData()?.id === tooltipId.current) {
|
|
290
|
-
tooltipManager.updateCurrentTooltip(tooltipId.current, (tooltip) => ({
|
|
291
|
-
...tooltip,
|
|
292
|
-
content,
|
|
293
|
-
side: sideToUse,
|
|
294
|
-
sideOffset,
|
|
295
|
-
showOnMobile,
|
|
296
|
-
}))
|
|
297
|
-
}
|
|
298
|
-
}, [content, sideToUse, sideOffset, showOnMobile, hasProvider])
|
|
299
|
-
|
|
300
377
|
// Don't show tooltip if disabled, no content, or enhanced accessibility mode is disabled
|
|
301
378
|
if (disabled || !content) {
|
|
302
379
|
return <>{children}</>
|
|
@@ -340,38 +417,54 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
340
417
|
|
|
341
418
|
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
|
|
342
419
|
child.props.onMouseEnter?.(event)
|
|
343
|
-
tooltipManager.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
420
|
+
tooltipManager.handleEvent({
|
|
421
|
+
type: 'show',
|
|
422
|
+
tooltip: {
|
|
423
|
+
id: tooltipId.current,
|
|
424
|
+
content,
|
|
425
|
+
targetElement: event.currentTarget as HTMLElement,
|
|
426
|
+
side: sideToUse,
|
|
427
|
+
sideOffset,
|
|
428
|
+
showOnMobile,
|
|
429
|
+
delayDuration: delayDurationToUse,
|
|
430
|
+
},
|
|
431
|
+
})
|
|
352
432
|
}
|
|
353
433
|
|
|
354
434
|
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
|
|
355
435
|
child.props.onMouseLeave?.(event)
|
|
356
|
-
tooltipManager.
|
|
436
|
+
tooltipManager.handleEvent({
|
|
437
|
+
type: 'hide',
|
|
438
|
+
tooltipId: tooltipId.current,
|
|
439
|
+
editor,
|
|
440
|
+
instant: false,
|
|
441
|
+
})
|
|
357
442
|
}
|
|
358
443
|
|
|
359
444
|
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
|
360
445
|
child.props.onFocus?.(event)
|
|
361
|
-
tooltipManager.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
446
|
+
tooltipManager.handleEvent({
|
|
447
|
+
type: 'show',
|
|
448
|
+
tooltip: {
|
|
449
|
+
id: tooltipId.current,
|
|
450
|
+
content,
|
|
451
|
+
targetElement: event.currentTarget as HTMLElement,
|
|
452
|
+
side: sideToUse,
|
|
453
|
+
sideOffset,
|
|
454
|
+
showOnMobile,
|
|
455
|
+
delayDuration: delayDurationToUse,
|
|
456
|
+
},
|
|
457
|
+
})
|
|
370
458
|
}
|
|
371
459
|
|
|
372
460
|
const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
|
373
461
|
child.props.onBlur?.(event)
|
|
374
|
-
tooltipManager.
|
|
462
|
+
tooltipManager.handleEvent({
|
|
463
|
+
type: 'hide',
|
|
464
|
+
tooltipId: tooltipId.current,
|
|
465
|
+
editor,
|
|
466
|
+
instant: false,
|
|
467
|
+
})
|
|
375
468
|
}
|
|
376
469
|
|
|
377
470
|
const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
|
|
@@ -24,7 +24,7 @@ import { TldrawUiDropdownMenuItem } from '../TldrawUiDropdownMenu'
|
|
|
24
24
|
import { TLUiIconJsx } from '../TldrawUiIcon'
|
|
25
25
|
import { TldrawUiKbd } from '../TldrawUiKbd'
|
|
26
26
|
import { TldrawUiToolbarButton } from '../TldrawUiToolbar'
|
|
27
|
-
import {
|
|
27
|
+
import { hideAllTooltips } from '../TldrawUiTooltip'
|
|
28
28
|
import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
|
|
29
29
|
|
|
30
30
|
/** @public */
|
|
@@ -350,7 +350,7 @@ function useDraggableEvents(
|
|
|
350
350
|
point: screenSpaceStart,
|
|
351
351
|
})
|
|
352
352
|
|
|
353
|
-
|
|
353
|
+
hideAllTooltips()
|
|
354
354
|
editor.getContainer().focus()
|
|
355
355
|
})
|
|
356
356
|
}
|
package/src/lib/ui/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '4.3.0-canary.
|
|
4
|
+
export const version = '4.3.0-canary.cf5673a789a1'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-12-05T09:37:06.859Z',
|
|
8
|
+
patch: '2025-12-05T09:37:06.859Z',
|
|
9
9
|
}
|
|
@@ -285,8 +285,9 @@ describe('<TldrawEditor />', () => {
|
|
|
285
285
|
|
|
286
286
|
// we should only get one editor instance
|
|
287
287
|
expect(editorInstances.size).toBe(1)
|
|
288
|
-
//
|
|
289
|
-
|
|
288
|
+
// strict mode may cause onMount to be called twice, but the important
|
|
289
|
+
// thing is that we always get the same editor instance
|
|
290
|
+
expect(onMount).toHaveBeenCalled()
|
|
290
291
|
})
|
|
291
292
|
|
|
292
293
|
it('allows updating camera options without re-creating the editor', async () => {
|
|
@@ -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
|
+
})
|