[fix] text editing in vscode (#683)
* fix text editing events in vscode * fix outline in vscode
This commit is contained in:
parent
b8dfc9895a
commit
543757984b
5 changed files with 107 additions and 106 deletions
|
@ -1,9 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as React from 'react'
|
||||
import { Utils, HTMLContainer, TLBounds } from '@tldraw/core'
|
||||
import { defaultTextStyle } from '../shared/shape-styles'
|
||||
import { AlignStyle, StickyShape, TDMeta, TDShapeType, TransformInfo } from '~types'
|
||||
import { getBoundsRectangle, TextAreaUtils } from '../shared'
|
||||
import { defaultTextStyle, getBoundsRectangle, TextAreaUtils } from '../shared'
|
||||
import { TDShapeUtil } from '../TDShapeUtil'
|
||||
import { getStickyFontStyle, getStickyShapeStyle } from '../shared/shape-styles'
|
||||
import { styled } from '~styles'
|
||||
|
@ -59,24 +58,28 @@ export class StickyUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
const rText = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
const rTextContent = React.useRef(shape.text)
|
||||
|
||||
const rIsMounted = React.useRef(false)
|
||||
|
||||
const handlePointerDown = React.useCallback((e: React.PointerEvent) => {
|
||||
e.stopPropagation()
|
||||
}, [])
|
||||
|
||||
const handleTextChange = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
rTextContent.current = TLDR.normalizeText(e.currentTarget.value)
|
||||
const onChange = React.useCallback(
|
||||
(text: string) => {
|
||||
onShapeChange?.({
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
text: rTextContent.current,
|
||||
text: TLDR.normalizeText(text),
|
||||
})
|
||||
},
|
||||
[onShapeChange]
|
||||
[shape.id]
|
||||
)
|
||||
|
||||
const handleTextChange = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
onChange(e.currentTarget.value)
|
||||
},
|
||||
[onShapeChange, onChange]
|
||||
)
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
|
@ -88,15 +91,6 @@ export class StickyUtil extends TDShapeUtil<T, E> {
|
|||
return
|
||||
}
|
||||
|
||||
// If this keydown was just the meta key or a shortcut
|
||||
// that includes holding the meta key like (Command+V)
|
||||
// then leave the event untouched. We also have to explicitly
|
||||
// Implement undo/redo for some reason in order to get this working
|
||||
// in the vscode extension. Without the below code the following doesn't work
|
||||
//
|
||||
// - You can't cut/copy/paste when when text-editing/focused
|
||||
// - You can't undo/redo when when text-editing/focused
|
||||
// - You can't use Command+A to select all the text, when when text-editing/focused
|
||||
if (!(e.key === 'Meta' || e.metaKey)) {
|
||||
e.stopPropagation()
|
||||
} else if (e.key === 'z' && e.metaKey) {
|
||||
|
@ -118,8 +112,7 @@ export class StickyUtil extends TDShapeUtil<T, E> {
|
|||
TextAreaUtils.indent(e.currentTarget)
|
||||
}
|
||||
|
||||
rTextContent.current = TLDR.normalizeText(e.currentTarget.value)
|
||||
onShapeChange?.({ ...shape, text: rTextContent.current })
|
||||
onShapeChange?.({ ...shape, text: TLDR.normalizeText(e.currentTarget.value) })
|
||||
}
|
||||
},
|
||||
[shape, onShapeChange]
|
||||
|
@ -142,7 +135,6 @@ export class StickyUtil extends TDShapeUtil<T, E> {
|
|||
// Focus when editing changes to true
|
||||
React.useEffect(() => {
|
||||
if (isEditing) {
|
||||
rTextContent.current = shape.text
|
||||
rIsMounted.current = true
|
||||
const elm = rTextArea.current!
|
||||
elm.focus()
|
||||
|
@ -210,13 +202,13 @@ export class StickyUtil extends TDShapeUtil<T, E> {
|
|||
/>
|
||||
)}
|
||||
<StyledText ref={rText} isEditing={isEditing} alignment={shape.style.textAlign}>
|
||||
{isEditing ? rTextContent.current : shape.text}​
|
||||
{shape.text}​
|
||||
</StyledText>
|
||||
{isEditing && (
|
||||
<StyledTextArea
|
||||
ref={rTextArea}
|
||||
onPointerDown={handlePointerDown}
|
||||
value={isEditing ? rTextContent.current : shape.text}
|
||||
value={shape.text}
|
||||
onChange={handleTextChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={handleFocus}
|
||||
|
@ -408,4 +400,8 @@ const StyledTextArea = styled('textarea', {
|
|||
},
|
||||
},
|
||||
},
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as React from 'react'
|
||||
import { Utils, HTMLContainer, TLBounds } from '@tldraw/core'
|
||||
import { defaultTextStyle, getShapeStyle, getFontStyle } from '../shared/shape-styles'
|
||||
import { TextShape, TDMeta, TDShapeType, TransformInfo, AlignStyle } from '~types'
|
||||
import { BINDING_DISTANCE, GHOSTED_OPACITY, LETTER_SPACING } from '~constants'
|
||||
import { TDShapeUtil } from '../TDShapeUtil'
|
||||
import { styled } from '~styles'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
import { TLDR } from '~state/TLDR'
|
||||
import { getTextAlign } from '../shared/getTextAlign'
|
||||
import { getTextSvgElement } from '../shared/getTextSvgElement'
|
||||
import { stopPropagation } from '~components/stopPropagation'
|
||||
import { useTextKeyboardEvents } from '../shared/useTextKeyboardEvents'
|
||||
import { preventEvent } from '~components/preventEvent'
|
||||
import {
|
||||
getTextSvgElement,
|
||||
TextAreaUtils,
|
||||
defaultTextStyle,
|
||||
getShapeStyle,
|
||||
getFontStyle,
|
||||
getTextAlign,
|
||||
} from '../shared'
|
||||
|
||||
type T = TextShape
|
||||
type E = HTMLDivElement
|
||||
|
@ -90,15 +93,41 @@ export class TextUtil extends TDShapeUtil<T, E> {
|
|||
[shape.id, shape.point]
|
||||
)
|
||||
|
||||
const onChange = React.useCallback(
|
||||
(text: string) => {
|
||||
this.texts.set(shape.id, TLDR.normalizeText(text))
|
||||
onShapeChange?.({ id: shape.id, text: this.texts.get(shape.id)! })
|
||||
},
|
||||
[shape.id]
|
||||
)
|
||||
const handleKeyDown = React.useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Escape') return
|
||||
|
||||
const handleKeyDown = useTextKeyboardEvents(onChange)
|
||||
if (e.key === 'Tab' && shape.text.length === 0) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (!(e.key === 'Meta' || e.metaKey)) {
|
||||
e.stopPropagation()
|
||||
} else if (e.key === 'z' && e.metaKey) {
|
||||
if (e.shiftKey) {
|
||||
document.execCommand('redo', false)
|
||||
} else {
|
||||
document.execCommand('undo', false)
|
||||
}
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
TextAreaUtils.unindent(e.currentTarget)
|
||||
} else {
|
||||
TextAreaUtils.indent(e.currentTarget)
|
||||
}
|
||||
|
||||
onShapeChange?.({ ...shape, text: TLDR.normalizeText(e.currentTarget.value) })
|
||||
}
|
||||
},
|
||||
[shape, onShapeChange]
|
||||
)
|
||||
|
||||
const handleBlur = React.useCallback((e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||
e.currentTarget.setSelectionRange(0, 0)
|
||||
|
@ -434,4 +463,8 @@ const TextArea = styled('textarea', {
|
|||
userSelect: 'text',
|
||||
WebkitUserSelect: 'text',
|
||||
...commonTextWrapping,
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -4,7 +4,7 @@ import { GHOSTED_OPACITY, LETTER_SPACING } from '~constants'
|
|||
import { TLDR } from '~state/TLDR'
|
||||
import { styled } from '~styles'
|
||||
import { getTextLabelSize } from './getTextSize'
|
||||
import { useTextKeyboardEvents } from './useTextKeyboardEvents'
|
||||
import { TextAreaUtils } from './TextAreaUtils'
|
||||
|
||||
export interface TextLabelProps {
|
||||
font: string
|
||||
|
@ -32,17 +32,47 @@ export const TextLabel = React.memo(function TextLabel({
|
|||
const rInput = React.useRef<HTMLTextAreaElement>(null)
|
||||
const rIsMounted = React.useRef(false)
|
||||
|
||||
const rTextContent = React.useRef(text)
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
rTextContent.current = TLDR.normalizeText(e.currentTarget.value)
|
||||
onChange(rTextContent.current)
|
||||
onChange(TLDR.normalizeText(e.currentTarget.value))
|
||||
},
|
||||
[onChange]
|
||||
)
|
||||
const handleKeyDown = React.useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Escape') return
|
||||
|
||||
const handleKeyDown = useTextKeyboardEvents(onChange)
|
||||
if (e.key === 'Tab' && text.length === 0) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (!(e.key === 'Meta' || e.metaKey)) {
|
||||
e.stopPropagation()
|
||||
} else if (e.key === 'z' && e.metaKey) {
|
||||
if (e.shiftKey) {
|
||||
document.execCommand('redo', false)
|
||||
} else {
|
||||
document.execCommand('undo', false)
|
||||
}
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
TextAreaUtils.unindent(e.currentTarget)
|
||||
} else {
|
||||
TextAreaUtils.indent(e.currentTarget)
|
||||
}
|
||||
|
||||
onChange?.(TLDR.normalizeText(e.currentTarget.value))
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
)
|
||||
|
||||
const handleBlur = React.useCallback(
|
||||
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||
|
@ -75,7 +105,6 @@ export const TextLabel = React.memo(function TextLabel({
|
|||
|
||||
React.useEffect(() => {
|
||||
if (isEditing) {
|
||||
rTextContent.current = text
|
||||
requestAnimationFrame(() => {
|
||||
rIsMounted.current = true
|
||||
const elm = rInput.current
|
||||
|
@ -130,7 +159,7 @@ export const TextLabel = React.memo(function TextLabel({
|
|||
wrap="off"
|
||||
dir="auto"
|
||||
datatype="wysiwyg"
|
||||
defaultValue={rTextContent.current}
|
||||
defaultValue={text}
|
||||
color={color}
|
||||
onFocus={handleFocus}
|
||||
onChange={handleChange}
|
||||
|
@ -238,4 +267,8 @@ const TextArea = styled('textarea', {
|
|||
WebkitFontSmoothing: 'subpixel-antialiased',
|
||||
MozOsxFontSmoothing: 'auto',
|
||||
...commonTextWrapping,
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -5,3 +5,4 @@ export * from './TextAreaUtils'
|
|||
export * from './shape-styles'
|
||||
export * from './getTextAlign'
|
||||
export * from './TextLabel'
|
||||
export * from './getTextSvgElement'
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import { TLDR } from '~state/TLDR'
|
||||
import { TextAreaUtils } from '.'
|
||||
|
||||
export function useTextKeyboardEvents(onChange: (text: string) => void) {
|
||||
const handleKeyDown = React.useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// If this keydown was just the meta key or a shortcut
|
||||
// that includes holding the meta key like (Command+V)
|
||||
// then leave the event untouched. We also have to explicitly
|
||||
// Implement undo/redo for some reason in order to get this working
|
||||
// in the vscode extension. Without the below code the following doesn't work
|
||||
//
|
||||
// - You can't cut/copy/paste when when text-editing/focused
|
||||
// - You can't undo/redo when when text-editing/focused
|
||||
// - You can't use Command+A to select all the text, when when text-editing/focused
|
||||
if (e.metaKey) e.stopPropagation()
|
||||
|
||||
switch (e.key) {
|
||||
case 'Meta': {
|
||||
e.stopPropagation()
|
||||
break
|
||||
}
|
||||
case 'z': {
|
||||
if (e.metaKey) {
|
||||
if (e.shiftKey) {
|
||||
document.execCommand('redo', false)
|
||||
} else {
|
||||
document.execCommand('undo', false)
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Escape': {
|
||||
e.currentTarget.blur()
|
||||
break
|
||||
}
|
||||
case 'Enter': {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.currentTarget.blur()
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Tab': {
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
TextAreaUtils.unindent(e.currentTarget)
|
||||
} else {
|
||||
TextAreaUtils.indent(e.currentTarget)
|
||||
}
|
||||
|
||||
onChange(TLDR.normalizeText(e.currentTarget.value))
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
)
|
||||
|
||||
return handleKeyDown
|
||||
}
|
Loading…
Reference in a new issue