textfields: bring shape into view when editing (#3362)

This actually is a problem in production anyway, for any shape, not
particular to new auto-editing or stickies.
Case in point:
- shape is selected
- move the shape offscreen
- hit Enter
- you'll be editing the shape but it won't be visible to you.

This change consolidates some of the duplicate logic in `Idle.ts` and in
`noteHelpers.ts`.

Two questions
- `Idle.ts` didn't have the `select()` call but `noteHelpers.ts` did -
is this really important?
- `noteHelpers` didn't have the `mark()` call but `Idle.ts` did - seems
like it was missing in noteHelpers, but lemme know if that was intended
to be left out.

### Change Type

<!--  Please select a 'Scope' label ️ -->

- [x] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [ ] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [x] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know


### Release Notes

- Textfields: bring shape to view that's being edited.

---------

Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
pull/3370/head
Mime Čuvalo 2024-04-05 10:49:54 +01:00 zatwierdzone przez GitHub
rodzic 7671e6291e
commit c5059f15bb
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
5 zmienionych plików z 67 dodań i 53 usunięć

Wyświetl plik

@ -36,6 +36,7 @@ import {
} from '../shared/default-shape-constants'
import { getFontDefForExport } from '../shared/defaultStyleDefs'
import { startEditingShapeWithLabel } from '../shared/TextHelpers'
import { useForceSolid } from '../shared/useForceSolid'
import {
ADJACENT_NOTE_MARGIN,
@ -43,7 +44,6 @@ import {
NOTE_CENTER_OFFSET,
NOTE_SIZE,
getNoteShapeForAdjacentPosition,
startEditingNoteShape,
} from './noteHelpers'
/** @public */
@ -450,7 +450,7 @@ function useNoteKeydownHandler(id: TLShapeId) {
const newNote = getNoteShapeForAdjacentPosition(editor, shape, adjacentCenter, pageRotation)
if (newNote) {
startEditingNoteShape(editor, newNote)
startEditingShapeWithLabel(editor, newNote, true /* selectAll */)
}
}
},

Wyświetl plik

@ -1,12 +1,5 @@
import {
ANIMATION_MEDIUM_MS,
Editor,
TLNoteShape,
TLShape,
Vec,
compact,
createShapeId,
} from '@tldraw/editor'
import { Editor, TLNoteShape, TLShape, Vec, compact, createShapeId } from '@tldraw/editor'
import { zoomToShapeIfOffscreen } from '../shared/TextHelpers'
/** @internal */
export const ADJACENT_NOTE_MARGIN = 20
@ -173,29 +166,3 @@ export function getNoteShapeForAdjacentPosition(
zoomToShapeIfOffscreen(editor)
return nextNote
}
/** @internal */
export function startEditingNoteShape(editor: Editor, shape: TLShape) {
// Finish this sticky and start editing the next one
editor.select(shape)
editor.setEditingShape(shape)
editor.setCurrentTool('select.editing_shape', {
target: 'shape',
shape: shape,
})
// 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)) {
editor.centerOnPoint(selectionPageBounds.center, {
duration: ANIMATION_MEDIUM_MS,
})
}
}

Wyświetl plik

@ -4,6 +4,8 @@
* Copyright (c) Federico Brigante <opensource@bfred.it> (bfred.it)
*/
import { ANIMATION_MEDIUM_MS, Editor, TLShape } from '@tldraw/editor'
// TODO: Most of this file can be moved into a DOM utils library.
/** @internal */
@ -290,3 +292,50 @@ function getCaretIndex(element: HTMLElement) {
}
return position
}
/** @internal */
export function startEditingShapeWithLabel(
editor: Editor,
shape: TLShape,
shouldSelectAll?: boolean
) {
// Finish this shape and start editing the next one
editor.select(shape)
editor.mark('editing shape')
editor.setEditingShape(shape)
editor.setCurrentTool('select.editing_shape', {
target: 'shape',
shape: shape,
})
if (shouldSelectAll) {
// Select any text that's in the newly selected sticky
;(document.getElementById(`text-input-${shape.id}`) as HTMLTextAreaElement)?.select()
}
zoomToShapeIfOffscreen(editor)
}
const ZOOM_TO_SHAPE_PADDING = 16
export function zoomToShapeIfOffscreen(editor: Editor) {
const selectionPageBounds = editor.getSelectionPageBounds()
const viewportPageBounds = editor.getViewportPageBounds()
if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) {
const eb = selectionPageBounds
.clone()
// Expand the bounds by the padding
.expandBy(ZOOM_TO_SHAPE_PADDING / editor.getZoomLevel())
// then expand the bounds to include the viewport bounds
.expand(viewportPageBounds)
// then use the difference between the centers to calculate the offset
const nextBounds = viewportPageBounds.clone().translate({
x: (eb.center.x - viewportPageBounds.center.x) * 2,
y: (eb.center.y - viewportPageBounds.center.y) * 2,
})
editor.zoomToBounds(nextBounds, {
duration: ANIMATION_MEDIUM_MS,
inset: 0,
})
}
}

Wyświetl plik

@ -15,6 +15,7 @@ import {
createShapeId,
pointInPolygon,
} from '@tldraw/editor'
import { startEditingShapeWithLabel } from '../../../shapes/shared/TextHelpers'
import { getHitShapeOnCanvasPointerDown } from '../../selection-logic/getHitShapeOnCanvasPointerDown'
import { getShouldEnterCropMode } from '../../selection-logic/getShouldEnterCropModeOnPointerDown'
import { selectOnCanvasPointerUp } from '../../selection-logic/selectOnCanvasPointerUp'
@ -446,11 +447,15 @@ export class Idle extends StateNode {
this.shouldStartEditingShape(onlySelectedShape) &&
this.editor.getShapeUtil(onlySelectedShape).doesAutoEditOnKeyStroke(onlySelectedShape)
) {
this.startEditingShape(onlySelectedShape, {
...info,
target: 'shape',
shape: onlySelectedShape,
})
this.startEditingShape(
onlySelectedShape,
{
...info,
target: 'shape',
shape: onlySelectedShape,
},
true /* select all */
)
return
}
}
@ -522,15 +527,8 @@ export class Idle extends StateNode {
shouldSelectAll?: boolean
) {
if (this.editor.isShapeOrAncestorLocked(shape) && shape.type !== 'embed') return
this.editor.mark('editing shape')
this.editor.setEditingShape(shape.id)
startEditingShapeWithLabel(this.editor, shape, shouldSelectAll)
this.parent.transition('editing_shape', info)
if (shouldSelectAll) {
// XXX this is a hack to select the text in the textarea when we hit enter.
// Open to other ideas! I don't see how else to currently do this in the codebase.
;(document.getElementById(`text-input-${shape.id}`) as HTMLTextAreaElement)?.select()
}
}
isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1

Wyświetl plik

@ -12,8 +12,8 @@ import {
NOTE_CENTER_OFFSET,
getNoteAdjacentPositions,
getNoteShapeForAdjacentPosition,
startEditingNoteShape,
} from '../../../shapes/note/noteHelpers'
import { startEditingShapeWithLabel } from '../../../shapes/shared/TextHelpers'
export class PointingHandle extends StateNode {
static override id = 'pointing_handle'
@ -53,7 +53,7 @@ export class PointingHandle extends StateNode {
const { editor } = this
const nextNote = getNoteForPit(editor, shape, handle, false)
if (nextNote) {
startEditingNoteShape(editor, nextNote)
startEditingShapeWithLabel(editor, nextNote, true /* selectAll */)
return
}
}
@ -89,7 +89,7 @@ export class PointingHandle extends StateNode {
isCreating: true,
onCreate: () => {
// When we're done, start editing it
startEditingNoteShape(editor, nextNote)
startEditingShapeWithLabel(editor, nextNote, true /* selectAll */)
},
})
return