Skip to content

Commit 9e000da

Browse files
authored
Revert "Permit drag out of toolbar" (tldraw#6551)
Reverts tldraw#4793
1 parent 056cb7f commit 9e000da

11 files changed

Lines changed: 61 additions & 501 deletions

File tree

apps/examples/e2e/tests/fixtures/menus/Toolbar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export class Toolbar {
1717
this.tools = {
1818
select: this.page.getByTestId('tools.select'),
1919
draw: this.page.getByTestId('tools.draw'),
20-
text: this.page.getByTestId('tools.text'),
2120
arrow: this.page.getByTestId('tools.arrow'),
2221
cloud: this.page.getByTestId('tools.cloud'),
2322
eraser: this.page.getByTestId('tools.eraser'),
2423
rectangle: this.page.getByTestId('tools.rectangle'),
2524
hand: this.page.getByTestId('tools.hand'),
25+
text: this.page.getByTestId('tools.text'),
2626
}
2727
this.popOverTools = {
2828
popoverCloud: this.page.getByTestId('tools.more.cloud'),
Lines changed: 1 addition & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { expect } from '@playwright/test'
2-
import { sleep } from 'tldraw'
3-
import { getAllShapeTypes, setup } from '../shared-e2e'
2+
import { setup } from '../shared-e2e'
43
import test from './fixtures/fixtures'
54

65
test.describe('when selecting a tool from the toolbar', () => {
@@ -37,106 +36,3 @@ test.describe('when selecting a tool from the toolbar', () => {
3736
})
3837
})
3938
})
40-
41-
test.describe('when dragging a tool from the toolbar', () => {
42-
test.beforeEach(setup)
43-
44-
test('dragging from main toolbar creates and positions shapes', async ({
45-
page,
46-
toolbar,
47-
isMobile,
48-
}) => {
49-
if (isMobile) return
50-
51-
const { rectangle, arrow, text } = toolbar.tools
52-
53-
await test.step('dragging rectangle tool creates a rectangle', async () => {
54-
const startPoint = { x: 100, y: 100 }
55-
const endPoint = { x: 200, y: 200 }
56-
57-
// Start dragging from the rectangle tool
58-
await rectangle.hover()
59-
await page.mouse.down()
60-
await page.mouse.move(startPoint.x, startPoint.y)
61-
await page.mouse.move(endPoint.x, endPoint.y)
62-
await page.mouse.up()
63-
64-
// Verify a rectangle was created
65-
const shapes = await getAllShapeTypes(page)
66-
expect(shapes).toHaveLength(1)
67-
expect(shapes).toContain('geo')
68-
})
69-
70-
await sleep(100)
71-
72-
await test.step('dragging arrow tool creates an arrow', async () => {
73-
const startPoint = { x: 300, y: 100 }
74-
const endPoint = { x: 400, y: 200 }
75-
76-
// Start dragging from the arrow tool
77-
await arrow.hover()
78-
await page.mouse.down()
79-
await page.mouse.move(startPoint.x, startPoint.y)
80-
await page.mouse.move(endPoint.x, endPoint.y)
81-
await page.mouse.up()
82-
83-
// Verify an arrow was created
84-
const shapes = await getAllShapeTypes(page)
85-
expect(shapes).toHaveLength(2)
86-
expect(shapes).toContain('geo')
87-
expect(shapes).toContain('arrow')
88-
})
89-
90-
await sleep(100)
91-
92-
await test.step('dragging text tool creates editable text', async () => {
93-
const startPoint = { x: 500, y: 100 }
94-
const endPoint = { x: 600, y: 200 }
95-
96-
// Start dragging from the text tool
97-
await text.hover()
98-
await page.mouse.down()
99-
await page.mouse.move(startPoint.x, startPoint.y)
100-
await page.mouse.move(endPoint.x, endPoint.y)
101-
await page.mouse.up()
102-
103-
// Verify a text shape was created
104-
const shapes = await getAllShapeTypes(page)
105-
expect(shapes).toHaveLength(3)
106-
expect(shapes).toContain('geo')
107-
expect(shapes).toContain('arrow')
108-
expect(shapes).toContain('text')
109-
110-
// Verify text is in edit mode
111-
const hasTextInputFocused = await page.evaluate(() => {
112-
return document.activeElement?.classList.contains('tiptap')
113-
})
114-
expect(hasTextInputFocused).toBe(true)
115-
})
116-
})
117-
118-
test('dragging from overflow toolbar creates shapes', async ({ page, toolbar }) => {
119-
// Open the overflow menu
120-
await toolbar.moreToolsButton.click()
121-
await expect(toolbar.moreToolsPopover).toBeVisible()
122-
123-
const { popoverCloud } = toolbar.popOverTools
124-
125-
await test.step('dragging cloud tool from overflow creates a cloud', async () => {
126-
const startPoint = { x: 100, y: 100 }
127-
const endPoint = { x: 200, y: 200 }
128-
129-
// Start dragging from the cloud tool in overflow
130-
await popoverCloud.hover()
131-
await page.mouse.down()
132-
await page.mouse.move(startPoint.x, startPoint.y)
133-
await page.mouse.move(endPoint.x, endPoint.y)
134-
await page.mouse.up()
135-
136-
// Verify a cloud shape was created
137-
const shapes = await getAllShapeTypes(page)
138-
expect(shapes).toHaveLength(1)
139-
expect(shapes).toContain('geo')
140-
})
141-
})
142-
})

packages/editor/src/lib/TldrawEditor.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { MigrationSequence, Store } from '@tldraw/store'
22
import { TLShape, TLStore, TLStoreSnapshot } from '@tldraw/tlschema'
3-
import { annotateError, Required } from '@tldraw/utils'
3+
import { Required, annotateError } from '@tldraw/utils'
44
import React, {
5-
memo,
65
ReactNode,
6+
memo,
77
useCallback,
88
useEffect,
99
useLayoutEffect,
@@ -15,13 +15,13 @@ import React, {
1515

1616
import classNames from 'classnames'
1717
import { version } from '../version'
18-
import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback'
1918
import { OptionalErrorBoundary } from './components/ErrorBoundary'
19+
import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback'
20+
import { TLEditorSnapshot } from './config/TLEditorSnapshot'
2021
import { TLStoreBaseOptions } from './config/createTLStore'
21-
import { createTLUser, TLUser } from './config/createTLUser'
22+
import { TLUser, createTLUser } from './config/createTLUser'
2223
import { TLAnyBindingUtilConstructor } from './config/defaultBindings'
2324
import { TLAnyShapeUtilConstructor } from './config/defaultShapes'
24-
import { TLEditorSnapshot } from './config/TLEditorSnapshot'
2525
import { Editor } from './editor/Editor'
2626
import { TLStateNodeConstructor } from './editor/tools/StateNode'
2727
import { TLCameraOptions } from './editor/types/misc-types'

packages/editor/src/lib/components/MenuClickCapture.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ export function MenuClickCapture() {
5050
// Do nothing unless we're pointing
5151
if (!rPointerState.current.isDown) return
5252

53+
// If we're already dragging, pass on the event as it is
54+
if (rPointerState.current.isDragging) {
55+
canvasEvents.onPointerMove?.(e)
56+
return
57+
}
58+
5359
if (
5460
// We're pointing, but are we dragging?
5561
Vec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >
@@ -69,6 +75,8 @@ export function MenuClickCapture() {
6975
clientY: y,
7076
button: 0,
7177
})
78+
// call the pointer move with the current pointer position
79+
canvasEvents.onPointerMove?.(e)
7280
}
7381
},
7482
[canvasEvents, editor]

packages/editor/src/lib/hooks/useCanvasEvents.ts

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useValue } from '@tldraw/state-react'
2-
import React, { useEffect, useMemo } from 'react'
2+
import React, { useMemo } from 'react'
33
import { RIGHT_MOUSE_BUTTON } from '../constants'
44
import {
55
preventDefault,
@@ -16,6 +16,9 @@ export function useCanvasEvents() {
1616

1717
const events = useMemo(
1818
function canvasEvents() {
19+
// Track the last screen point
20+
let lastX: number, lastY: number
21+
1922
function onPointerDown(e: React.PointerEvent) {
2023
if ((e as any).isKilled) return
2124

@@ -41,9 +44,35 @@ export function useCanvasEvents() {
4144
})
4245
}
4346

47+
function onPointerMove(e: React.PointerEvent) {
48+
if ((e as any).isKilled) return
49+
50+
if (e.clientX === lastX && e.clientY === lastY) return
51+
lastX = e.clientX
52+
lastY = e.clientY
53+
54+
// For tools that benefit from a higher fidelity of events,
55+
// we dispatch the coalesced events.
56+
// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.
57+
const events =
58+
currentTool.useCoalescedEvents && e.nativeEvent.getCoalescedEvents
59+
? e.nativeEvent.getCoalescedEvents()
60+
: [e]
61+
for (const singleEvent of events) {
62+
editor.dispatch({
63+
type: 'pointer',
64+
target: 'canvas',
65+
name: 'pointer_move',
66+
...getPointerInfo(singleEvent),
67+
})
68+
}
69+
}
70+
4471
function onPointerUp(e: React.PointerEvent) {
4572
if ((e as any).isKilled) return
4673
if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
74+
lastX = e.clientX
75+
lastY = e.clientY
4776

4877
releasePointerCapture(e.currentTarget, e)
4978

@@ -129,6 +158,7 @@ export function useCanvasEvents() {
129158

130159
return {
131160
onPointerDown,
161+
onPointerMove,
132162
onPointerUp,
133163
onPointerEnter,
134164
onPointerLeave,
@@ -139,45 +169,8 @@ export function useCanvasEvents() {
139169
onClick,
140170
}
141171
},
142-
[editor]
172+
[editor, currentTool]
143173
)
144174

145-
// onPointerMove is special: where we're only interested in the other events when they're
146-
// happening _on_ the canvas (as opposed to outside of it, or on UI floating over it), we want
147-
// the pointer position to be up to date regardless of whether it's over the tldraw canvas or
148-
// not. So instead of returning a listener to be attached to the canvas, we directly attach a
149-
// listener to the whole document instead.
150-
useEffect(() => {
151-
let lastX: number, lastY: number
152-
153-
function onPointerMove(e: PointerEvent) {
154-
if ((e as any).isKilled) return
155-
;(e as any).isKilled = true
156-
157-
if (e.clientX === lastX && e.clientY === lastY) return
158-
lastX = e.clientX
159-
lastY = e.clientY
160-
161-
// For tools that benefit from a higher fidelity of events,
162-
// we dispatch the coalesced events.
163-
// N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.
164-
const events =
165-
currentTool.useCoalescedEvents && e.getCoalescedEvents ? e.getCoalescedEvents() : [e]
166-
for (const singleEvent of events) {
167-
editor.dispatch({
168-
type: 'pointer',
169-
target: 'canvas',
170-
name: 'pointer_move',
171-
...getPointerInfo(singleEvent),
172-
})
173-
}
174-
}
175-
176-
document.body.addEventListener('pointermove', onPointerMove)
177-
return () => {
178-
document.body.removeEventListener('pointermove', onPointerMove)
179-
}
180-
}, [editor, currentTool])
181-
182175
return events
183176
}

packages/tldraw/api-report.api.md

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,15 +2147,6 @@ export function NoteToolbarItem(): JSX_2.Element;
21472147
// @public (undocumented)
21482148
export function OfflineIndicator(): JSX_2.Element;
21492149

2150-
// @public
2151-
export function onDragFromToolbarToCreateShape(editor: Editor, info: TLPointerEventInfo, opts: OnDragFromToolbarToCreateShapesOpts): void;
2152-
2153-
// @public
2154-
export interface OnDragFromToolbarToCreateShapesOpts {
2155-
createShape(id: TLShapeId): void;
2156-
onDragEnd?(id: TLShapeId): void;
2157-
}
2158-
21592150
// @public (undocumented)
21602151
export function OpacitySlider(): JSX_2.Element | null;
21612152

@@ -3058,7 +3049,7 @@ export function TldrawUiMenuContextProvider({ type, sourceId, children, }: TLUiM
30583049
export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGroupProps): boolean | JSX_2.Element | Iterable<ReactNode> | null | number | string | undefined;
30593050

30603051
// @public (undocumented)
3061-
export function TldrawUiMenuItem<TranslationKey extends string = string, IconType extends string = string>({ disabled, spinner, readonlyOk, id, kbd, label, icon, iconLeft, onSelect, noClose, isSelected, onDragStart, }: TLUiMenuItemProps<TranslationKey, IconType>): JSX_2.Element | null;
3052+
export function TldrawUiMenuItem<TranslationKey extends string = string, IconType extends string = string>({ disabled, spinner, readonlyOk, id, kbd, label, icon, iconLeft, onSelect, noClose, isSelected, }: TLUiMenuItemProps<TranslationKey, IconType>): JSX_2.Element | null;
30623053

30633054
// @public (undocumented)
30643055
export function TldrawUiMenuSubmenu<Translation extends string = string>({ id, disabled, label, size, children, }: TLUiMenuSubmenuProps<Translation>): boolean | JSX_2.Element | Iterable<ReactNode> | null | number | string | undefined;
@@ -3655,10 +3646,6 @@ export interface TLUiEventMap {
36553646
// (undocumented)
36563647
'download-original': null;
36573648
// (undocumented)
3658-
'drag-tool': {
3659-
id: string;
3660-
};
3661-
// (undocumented)
36623649
'duplicate-page': null;
36633650
// (undocumented)
36643651
'duplicate-shapes': null;
@@ -4033,7 +4020,6 @@ export interface TLUiMenuItemProps<TranslationKey extends string = string, IconT
40334020
[key: string]: TranslationKey;
40344021
} | TranslationKey;
40354022
noClose?: boolean;
4036-
onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void;
40374023
onSelect(source: TLUiEventSource): Promise<void> | void;
40384024
readonlyOk?: boolean;
40394025
spinner?: boolean;
@@ -4281,8 +4267,6 @@ export interface TLUiToolItem<TranslationKey extends string = string, IconType e
42814267
[key: string]: any;
42824268
};
42834269
// (undocumented)
4284-
onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void;
4285-
// (undocumented)
42864270
onSelect(source: TLUiEventSource): void;
42874271
// (undocumented)
42884272
readonlyOk?: boolean;

packages/tldraw/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,7 @@ export { useMenuIsOpen } from './lib/ui/hooks/useMenuIsOpen'
590590
export { useReadonly } from './lib/ui/hooks/useReadonly'
591591
export { useRelevantStyles } from './lib/ui/hooks/useRelevantStyles'
592592
export {
593-
onDragFromToolbarToCreateShape,
594593
useTools,
595-
type OnDragFromToolbarToCreateShapesOpts,
596594
type TLUiToolItem,
597595
type TLUiToolsContextType,
598596
type TLUiToolsProviderProps,

packages/tldraw/src/lib/tools/SelectTool/childStates/Translating.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type TranslatingInfo = TLPointerEventInfo & {
2828
isCreating?: boolean
2929
creatingMarkId?: string
3030
onCreate?(): void
31+
didStartInPit?: boolean
3132
onInteractionEnd?: string
3233
}
3334

0 commit comments

Comments
 (0)