kopia lustrzana https://github.com/Tldraw/Tldraw
Fix weird text complete (#3342)
This PR fixes a weird issue where editing text notes would call blur / complete, preventing certain interactions. Before: 1. Select a sticky note 2. Start editing the note 3. pointer down on the canvas 4. Instead of being in pointing_canvas, you'll be in idle After: 4. You'll be in pointing_canvas ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `bugfix` — Bug fixpull/3345/head
rodzic
30e605ef7e
commit
e60f8d56a0
|
@ -795,6 +795,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
undo(): this {
|
||||
this._flushEventsForTick(0)
|
||||
this.history.undo()
|
||||
return this
|
||||
}
|
||||
|
@ -819,6 +820,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
redo(): this {
|
||||
this._flushEventsForTick(0)
|
||||
this.history.redo()
|
||||
return this
|
||||
}
|
||||
|
@ -8180,7 +8182,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const sx = info.point.x - screenBounds.x
|
||||
const sy = info.point.y - screenBounds.y
|
||||
const sz = info.point.z
|
||||
const sz = info.point.z ?? 0.5
|
||||
|
||||
previousScreenPoint.setTo(currentScreenPoint)
|
||||
previousPagePoint.setTo(currentPagePoint)
|
||||
|
@ -8190,7 +8192,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// it will be 0,0 when its actual screen position is equal
|
||||
// to screenBounds.point. This is confusing!
|
||||
currentScreenPoint.set(sx, sy)
|
||||
currentPagePoint.set(sx / cz - cx, sy / cz - cy, sz ?? 0.5)
|
||||
currentPagePoint.set(sx / cz - cx, sy / cz - cy, sz)
|
||||
|
||||
this.inputs.isPen = info.type === 'pointer' && info.isPen
|
||||
|
||||
|
@ -8367,6 +8369,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
dispatch = (info: TLEventInfo): this => {
|
||||
this._pendingEventsForNextTick.push(info)
|
||||
if (!(info.type === 'pointer' || info.type === 'wheel' || info.type === 'pinch')) {
|
||||
this._flushEventsForTick(0)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { Vec } from '@tldraw/editor'
|
||||
import { Box, Vec } from '@tldraw/editor'
|
||||
import { TestEditor } from '../../../test/TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new TestEditor()
|
||||
// We don't want the camera to move when the shape gets created off screen
|
||||
editor.updateViewportScreenBounds(new Box(0, 0, 2000, 2000))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
editor?.dispose()
|
||||
})
|
||||
|
@ -24,8 +27,14 @@ function testCloneHandles(x: number, y: number, rotation: number) {
|
|||
)
|
||||
|
||||
handles.forEach((handle, i) => {
|
||||
const handleInPageSpace = editor.getShapePageTransform(shape).applyToPoint(handle)
|
||||
editor.select(shape.id)
|
||||
editor.pointerDown(handle.x, handle.y, {
|
||||
editor.pointerMove(handleInPageSpace.x, handleInPageSpace.y)
|
||||
expect(editor.inputs.currentPagePoint).toMatchObject({
|
||||
x: handleInPageSpace.x,
|
||||
y: handleInPageSpace.y,
|
||||
})
|
||||
editor.pointerDown(handleInPageSpace.x, handleInPageSpace.y, {
|
||||
target: 'handle',
|
||||
shape,
|
||||
handle,
|
||||
|
@ -50,25 +59,21 @@ function testCloneHandles(x: number, y: number, rotation: number) {
|
|||
|
||||
editor.expectToBeIn('select.editing_shape')
|
||||
|
||||
editor.cancel().undo()
|
||||
editor.cancel().undo().forceTick()
|
||||
})
|
||||
}
|
||||
|
||||
describe('Note clone handles', () => {
|
||||
it('Creates a new sticky note using handles', () => {
|
||||
testCloneHandles(0, 0, 0)
|
||||
})
|
||||
|
||||
it('Creates a new sticky note when translated', () => {
|
||||
testCloneHandles(100, 100, 0)
|
||||
testCloneHandles(1000, 1000, 0)
|
||||
})
|
||||
|
||||
it('Creates a new sticky when rotated', () => {
|
||||
testCloneHandles(0, 0, Math.PI / 2)
|
||||
testCloneHandles(1000, 1000, Math.PI / 2)
|
||||
})
|
||||
|
||||
it('Creates a new sticky when translated and rotated', () => {
|
||||
testCloneHandles(100, 100, Math.PI / 2)
|
||||
testCloneHandles(1000, 1000, Math.PI / 2)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -82,9 +87,10 @@ function testDragCloneHandles(x: number, y: number, rotation: number) {
|
|||
const handles = editor.getShapeHandles(shape.id)!
|
||||
|
||||
handles.forEach((handle) => {
|
||||
const handleInPageSpace = editor.getShapePageTransform(shape).applyToPoint(handle)
|
||||
editor.select(shape.id)
|
||||
editor.pointerMove(handle.x, handle.y)
|
||||
editor.pointerDown(handle.x, handle.y, {
|
||||
editor.pointerMove(handleInPageSpace.x, handleInPageSpace.y)
|
||||
editor.pointerDown(handleInPageSpace.x, handleInPageSpace.y, {
|
||||
target: 'handle',
|
||||
shape,
|
||||
handle,
|
||||
|
@ -92,7 +98,7 @@ function testDragCloneHandles(x: number, y: number, rotation: number) {
|
|||
|
||||
editor.expectToBeIn('select.pointing_handle')
|
||||
|
||||
editor.pointerMove(handle.x + 30, handle.y + 30)
|
||||
editor.pointerMove(handleInPageSpace.x + 30, handleInPageSpace.y + 30)
|
||||
|
||||
editor.expectToBeIn('select.translating')
|
||||
|
||||
|
@ -105,8 +111,8 @@ function testDragCloneHandles(x: number, y: number, rotation: number) {
|
|||
editor.expectShapeToMatch({
|
||||
id: newShape.id,
|
||||
type: 'note',
|
||||
x: handle.x + 30 - offset.x,
|
||||
y: handle.y + 30 - offset.y,
|
||||
x: handleInPageSpace.x + 30 - offset.x,
|
||||
y: handleInPageSpace.y + 30 - offset.y,
|
||||
})
|
||||
|
||||
editor.pointerUp()
|
||||
|
@ -119,27 +125,23 @@ function testDragCloneHandles(x: number, y: number, rotation: number) {
|
|||
|
||||
describe('Dragging clone handles', () => {
|
||||
it('Creates a new sticky note using handles', () => {
|
||||
testDragCloneHandles(0, 0, 0)
|
||||
})
|
||||
|
||||
it('Creates a new sticky note when translated', () => {
|
||||
testDragCloneHandles(100, 100, 0)
|
||||
testDragCloneHandles(1000, 1000, 0)
|
||||
})
|
||||
|
||||
it('Creates a new sticky when rotated', () => {
|
||||
testDragCloneHandles(0, 0, Math.PI / 2)
|
||||
testDragCloneHandles(1000, 1000, Math.PI / 2)
|
||||
})
|
||||
|
||||
it('Creates a new sticky when translated and rotated', () => {
|
||||
testDragCloneHandles(100, 100, Math.PI / 2)
|
||||
testDragCloneHandles(1000, 1000, Math.PI / 2)
|
||||
})
|
||||
})
|
||||
|
||||
it('Selects an adjacent note when clicking the clone handle', () => {
|
||||
editor.createShape({ type: 'note', x: 220, y: 0 })
|
||||
editor.createShape({ type: 'note', x: 1220, y: 1000 })
|
||||
const shapeA = editor.getLastCreatedShape()!
|
||||
|
||||
editor.createShape({ type: 'note', x: 0, y: 0 })
|
||||
editor.createShape({ type: 'note', x: 1000, y: 1000 })
|
||||
const shapeB = editor.getLastCreatedShape()!
|
||||
|
||||
editor.select(shapeB.id)
|
||||
|
@ -171,10 +173,10 @@ it('Selects an adjacent note when clicking the clone handle', () => {
|
|||
})
|
||||
|
||||
it('Creates an adjacent note when dragging the clone handle', () => {
|
||||
editor.createShape({ type: 'note', x: 220, y: 0 })
|
||||
editor.createShape({ type: 'note', x: 1220, y: 1000 })
|
||||
const shapeA = editor.getLastCreatedShape()!
|
||||
|
||||
editor.createShape({ type: 'note', x: 0, y: 0 })
|
||||
editor.createShape({ type: 'note', x: 1000, y: 1000 })
|
||||
const shapeB = editor.getLastCreatedShape()!
|
||||
|
||||
editor.select(shapeB.id)
|
||||
|
@ -214,10 +216,10 @@ it('Creates an adjacent note when dragging the clone handle', () => {
|
|||
})
|
||||
|
||||
it('Does not put the new shape into a frame if its center is not in the frame', () => {
|
||||
editor.createShape({ type: 'frame', x: 321, y: 100 }) // one pixel too far...
|
||||
editor.createShape({ type: 'frame', x: 1321, y: 1000 }) // one pixel too far...
|
||||
const frameA = editor.getLastCreatedShape()!
|
||||
// center no longer in the frame
|
||||
editor.createShape({ type: 'note', x: 0, y: 0 })
|
||||
editor.createShape({ type: 'note', x: 1000, y: 1000 })
|
||||
const shapeA = editor.getLastCreatedShape()!
|
||||
// to the right
|
||||
const handle = editor.getShapeHandles(shapeA.id)![1]
|
||||
|
@ -237,10 +239,10 @@ it('Does not put the new shape into a frame if its center is not in the frame',
|
|||
})
|
||||
|
||||
it('Puts the new shape into a frame based on its center', () => {
|
||||
editor.createShape({ type: 'frame', x: 320, y: 100 })
|
||||
editor.createShape({ type: 'frame', x: 1320, y: 1100 })
|
||||
const frameA = editor.getLastCreatedShape()!
|
||||
// top left won't be in the frame, but the center will (barely but yes)
|
||||
editor.createShape({ type: 'note', x: 0, y: 0 })
|
||||
editor.createShape({ type: 'note', x: 1000, y: 1000 })
|
||||
const shapeA = editor.getLastCreatedShape()!
|
||||
// to the right
|
||||
const handle = editor.getShapeHandles(shapeA.id)![1]
|
||||
|
@ -260,10 +262,10 @@ it('Puts the new shape into a frame based on its center', () => {
|
|||
})
|
||||
|
||||
function testNoteShapeFrameRotations(sourceRotation: number, rotation: number) {
|
||||
editor.createShape({ type: 'frame', x: 220, y: 0, rotation: rotation })
|
||||
editor.createShape({ type: 'frame', x: 1220, y: 1000, rotation: rotation })
|
||||
const frameA = editor.getLastCreatedShape()!
|
||||
// top left won't be in the frame, but the center will (barely but yes)
|
||||
editor.createShape({ type: 'note', x: 0, y: 0, rotation: sourceRotation })
|
||||
editor.createShape({ type: 'note', x: 1000, y: 1000, rotation: sourceRotation })
|
||||
const shapeA = editor.getLastCreatedShape()!
|
||||
// to the right
|
||||
const handle = editor.getShapeHandles(shapeA.id)![1]
|
||||
|
|
|
@ -172,15 +172,7 @@ export function getNoteShapeForAdjacentPosition(
|
|||
nextNote = editor.getShape(id)!
|
||||
}
|
||||
|
||||
// Animate to the next sticky if it would be off screen
|
||||
const selectionPageBounds = editor.getSelectionPageBounds()
|
||||
const viewportPageBounds = editor.getViewportPageBounds()
|
||||
if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) {
|
||||
editor.centerOnPoint(selectionPageBounds.center, {
|
||||
duration: ANIMATION_MEDIUM_MS,
|
||||
})
|
||||
}
|
||||
|
||||
zoomToShapeIfOffscreen(editor)
|
||||
return nextNote
|
||||
}
|
||||
|
||||
|
@ -197,9 +189,14 @@ export function startEditingNoteShape(editor: Editor, shape: TLShape) {
|
|||
// Select any text that's in the newly selected sticky
|
||||
;(document.getElementById(`text-input-${shape.id}`) as HTMLTextAreaElement)?.select()
|
||||
|
||||
zoomToShapeIfOffscreen(editor)
|
||||
}
|
||||
|
||||
function zoomToShapeIfOffscreen(editor: Editor) {
|
||||
const selectionPageBounds = editor.getSelectionPageBounds()
|
||||
const viewportPageBounds = editor.getViewportPageBounds()
|
||||
if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) {
|
||||
console.log('centering on point', viewportPageBounds, selectionPageBounds)
|
||||
editor.centerOnPoint(selectionPageBounds.center, {
|
||||
duration: ANIMATION_MEDIUM_MS,
|
||||
})
|
||||
|
|
|
@ -74,32 +74,27 @@ export function useEditableText(id: TLShapeId, type: string, text: string) {
|
|||
const elm = rInput.current
|
||||
const editingShapeId = editor.getEditingShapeId()
|
||||
// Did we move to a different shape?
|
||||
if (editingShapeId) {
|
||||
// important! these ^v are two different things
|
||||
// is that shape OUR shape?
|
||||
if (elm && editingShapeId === id) {
|
||||
if (ranges) {
|
||||
if (!ranges.length) {
|
||||
// If we don't have any ranges, restore selection
|
||||
// and select all of the text
|
||||
elm.focus()
|
||||
} else {
|
||||
// Otherwise, skip the select-all-on-focus behavior
|
||||
// and restore the selection
|
||||
rSkipSelectOnFocus.current = true
|
||||
elm.focus()
|
||||
const selection = window.getSelection()
|
||||
if (selection) {
|
||||
ranges.forEach((range) => selection.addRange(range))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// important! these ^v are two different things
|
||||
// is that shape OUR shape?
|
||||
if (elm && editingShapeId === id) {
|
||||
if (ranges) {
|
||||
if (!ranges.length) {
|
||||
// If we don't have any ranges, restore selection
|
||||
// and select all of the text
|
||||
elm.focus()
|
||||
} else {
|
||||
// Otherwise, skip the select-all-on-focus behavior
|
||||
// and restore the selection
|
||||
rSkipSelectOnFocus.current = true
|
||||
elm.focus()
|
||||
const selection = window.getSelection()
|
||||
if (selection) {
|
||||
ranges.forEach((range) => selection.addRange(range))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
elm.focus()
|
||||
}
|
||||
} else {
|
||||
window.getSelection()?.removeAllRanges()
|
||||
editor.complete()
|
||||
}
|
||||
})
|
||||
}, [editor, id])
|
||||
|
|
Ładowanie…
Reference in New Issue