[fix] Text on Safari (#232)
* Fix editing bug on safari text * Fix text behavior when blurring * Update SelectTool.ts
This commit is contained in:
parent
0b5a516b57
commit
6592608a09
6 changed files with 62 additions and 47 deletions
|
@ -60,4 +60,4 @@
|
||||||
"tsconfig-replace-paths": "^0.0.5"
|
"tsconfig-replace-paths": "^0.0.5"
|
||||||
},
|
},
|
||||||
"gitHead": "083b36e167b6911927a6b58cbbb830b11b33f00a"
|
"gitHead": "083b36e167b6911927a6b58cbbb830b11b33f00a"
|
||||||
}
|
}
|
|
@ -15,7 +15,6 @@ import {
|
||||||
TLWheelEventHandler,
|
TLWheelEventHandler,
|
||||||
Utils,
|
Utils,
|
||||||
TLBounds,
|
TLBounds,
|
||||||
Inputs,
|
|
||||||
} from '@tldraw/core'
|
} from '@tldraw/core'
|
||||||
import {
|
import {
|
||||||
FlipType,
|
FlipType,
|
||||||
|
@ -134,6 +133,8 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
|
||||||
|
|
||||||
currentTool: BaseTool = this.tools.select
|
currentTool: BaseTool = this.tools.select
|
||||||
|
|
||||||
|
editingStartTime = -1
|
||||||
|
|
||||||
private isCreating = false
|
private isCreating = false
|
||||||
|
|
||||||
// The editor's bounding client rect
|
// The editor's bounding client rect
|
||||||
|
@ -486,6 +487,7 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
|
||||||
* @param id [string]
|
* @param id [string]
|
||||||
*/
|
*/
|
||||||
setEditingId = (id?: string) => {
|
setEditingId = (id?: string) => {
|
||||||
|
this.editingStartTime = Date.now()
|
||||||
this.patchState(
|
this.patchState(
|
||||||
{
|
{
|
||||||
document: {
|
document: {
|
||||||
|
@ -2527,14 +2529,15 @@ export class TLDrawState extends StateManager<TLDrawSnapshot> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onShapeBlur = () => {
|
onShapeBlur = () => {
|
||||||
|
// This prevents an auto-blur event from Safari
|
||||||
|
if (Date.now() - this.editingStartTime < 50) return
|
||||||
|
|
||||||
const { editingId } = this.pageState
|
const { editingId } = this.pageState
|
||||||
|
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
// If we're editing text, then delete the text if it's empty
|
// If we're editing text, then delete the text if it's empty
|
||||||
const shape = this.getShape(editingId)
|
const shape = this.getShape(editingId)
|
||||||
|
|
||||||
this.setEditingId()
|
this.setEditingId()
|
||||||
|
|
||||||
if (shape.type === TLDrawShapeType.Text) {
|
if (shape.type === TLDrawShapeType.Text) {
|
||||||
if (shape.text.trim().length <= 0) {
|
if (shape.text.trim().length <= 0) {
|
||||||
this.setState(Commands.deleteShapes(this.state, [editingId]), 'delete_empty_text')
|
this.setState(Commands.deleteShapes(this.state, [editingId]), 'delete_empty_text')
|
||||||
|
|
|
@ -12,7 +12,11 @@ import {
|
||||||
} from '~types'
|
} from '~types'
|
||||||
import { BINDING_DISTANCE } from '~constants'
|
import { BINDING_DISTANCE } from '~constants'
|
||||||
import { TLDrawShapeUtil } from '../TLDrawShapeUtil'
|
import { TLDrawShapeUtil } from '../TLDrawShapeUtil'
|
||||||
import { intersectLineSegmentEllipse, intersectRayEllipse } from '@tldraw/intersect'
|
import {
|
||||||
|
intersectEllipseBounds,
|
||||||
|
intersectLineSegmentEllipse,
|
||||||
|
intersectRayEllipse,
|
||||||
|
} from '@tldraw/intersect'
|
||||||
import { getEllipseIndicatorPathTLDrawSnapshot, getEllipsePath } from './ellipseHelpers'
|
import { getEllipseIndicatorPathTLDrawSnapshot, getEllipsePath } from './ellipseHelpers'
|
||||||
|
|
||||||
type T = EllipseShape
|
type T = EllipseShape
|
||||||
|
@ -156,12 +160,27 @@ export class EllipseUtil extends TLDrawShapeUtil<T, E> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hitTestBounds = (shape: T, bounds: TLBounds): boolean => {
|
||||||
|
const shapeBounds = this.getBounds(shape)
|
||||||
|
|
||||||
|
return (
|
||||||
|
Utils.boundsContained(shapeBounds, bounds) ||
|
||||||
|
intersectEllipseBounds(
|
||||||
|
this.getCenter(shape),
|
||||||
|
shape.radius[0],
|
||||||
|
shape.radius[1],
|
||||||
|
shape.rotation || 0,
|
||||||
|
bounds
|
||||||
|
).length > 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
shouldRender = (prev: T, next: T): boolean => {
|
shouldRender = (prev: T, next: T): boolean => {
|
||||||
return next.radius !== prev.radius || next.style !== prev.style
|
return next.radius !== prev.radius || next.style !== prev.style
|
||||||
}
|
}
|
||||||
|
|
||||||
getCenter = (shape: T): number[] => {
|
getCenter = (shape: T): number[] => {
|
||||||
return [shape.point[0] + shape.radius[0], shape.point[1] + shape.radius[1]]
|
return Vec.add(shape.point, shape.radius)
|
||||||
}
|
}
|
||||||
|
|
||||||
getBindingPoint = <K extends TLDrawShape>(
|
getBindingPoint = <K extends TLDrawShape>(
|
||||||
|
|
|
@ -17,6 +17,8 @@ export class StickyUtil extends TLDrawShapeUtil<T, E> {
|
||||||
|
|
||||||
canBind = true
|
canBind = true
|
||||||
|
|
||||||
|
canEdit = true
|
||||||
|
|
||||||
getShape = (props: Partial<T>): T => {
|
getShape = (props: Partial<T>): T => {
|
||||||
return Utils.deepMerge<T>(
|
return Utils.deepMerge<T>(
|
||||||
{
|
{
|
||||||
|
@ -89,25 +91,16 @@ export class StickyUtil extends TLDrawShapeUtil<T, E> {
|
||||||
[shape, onShapeChange]
|
[shape, onShapeChange]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleBlur = React.useCallback(
|
const handleBlur = React.useCallback((e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
e.currentTarget.setSelectionRange(0, 0)
|
||||||
if (!isEditing) return
|
onShapeBlur?.()
|
||||||
if (rIsMounted.current) {
|
}, [])
|
||||||
e.currentTarget.setSelectionRange(0, 0)
|
|
||||||
onShapeBlur?.()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isEditing]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleFocus = React.useCallback(
|
const handleFocus = React.useCallback(
|
||||||
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
if (!isEditing) return
|
if (!isEditing) return
|
||||||
if (!rIsMounted.current) return
|
if (!rIsMounted.current) return
|
||||||
|
e.currentTarget.select()
|
||||||
if (document.activeElement === e.currentTarget) {
|
|
||||||
e.currentTarget.select()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[isEditing]
|
[isEditing]
|
||||||
)
|
)
|
||||||
|
@ -115,14 +108,10 @@ export class StickyUtil extends TLDrawShapeUtil<T, E> {
|
||||||
// Focus when editing changes to true
|
// Focus when editing changes to true
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
if (document.activeElement !== rText.current) {
|
rIsMounted.current = true
|
||||||
requestAnimationFrame(() => {
|
const elm = rTextArea.current!
|
||||||
rIsMounted.current = true
|
elm.focus()
|
||||||
const elm = rTextArea.current!
|
elm.select()
|
||||||
elm.focus()
|
|
||||||
elm.select()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [isEditing])
|
}, [isEditing])
|
||||||
|
|
||||||
|
@ -151,6 +140,9 @@ export class StickyUtil extends TLDrawShapeUtil<T, E> {
|
||||||
onShapeChange?.({ id: shape.id, size: [size[0], MIN_CONTAINER_HEIGHT] })
|
onShapeChange?.({ id: shape.id, size: [size[0], MIN_CONTAINER_HEIGHT] })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const textarea = rTextArea.current
|
||||||
|
textarea?.focus()
|
||||||
}, [shape.text, shape.size[1], shape.style])
|
}, [shape.text, shape.size[1], shape.style])
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
|
@ -180,10 +172,13 @@ export class StickyUtil extends TLDrawShapeUtil<T, E> {
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
autoCapitalize="off"
|
tabIndex={-1}
|
||||||
autoComplete="off"
|
autoComplete="false"
|
||||||
spellCheck={false}
|
autoCapitalize="false"
|
||||||
|
autoCorrect="false"
|
||||||
|
autoSave="false"
|
||||||
autoFocus
|
autoFocus
|
||||||
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledStickyContainer>
|
</StyledStickyContainer>
|
||||||
|
|
|
@ -77,16 +77,10 @@ export class TextUtil extends TLDrawShapeUtil<T, E> {
|
||||||
[shape, onShapeChange]
|
[shape, onShapeChange]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleBlur = React.useCallback(
|
const handleBlur = React.useCallback((e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
e.currentTarget.setSelectionRange(0, 0)
|
||||||
if (!isEditing) return
|
onShapeBlur?.()
|
||||||
if (rIsMounted.current) {
|
}, [])
|
||||||
e.currentTarget.setSelectionRange(0, 0)
|
|
||||||
onShapeBlur?.()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isEditing]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleFocus = React.useCallback(
|
const handleFocus = React.useCallback(
|
||||||
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
|
@ -117,6 +111,8 @@ export class TextUtil extends TLDrawShapeUtil<T, E> {
|
||||||
elm.focus()
|
elm.focus()
|
||||||
elm.select()
|
elm.select()
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
onShapeBlur?.()
|
||||||
}
|
}
|
||||||
}, [isEditing])
|
}, [isEditing])
|
||||||
|
|
||||||
|
@ -156,14 +152,14 @@ export class TextUtil extends TLDrawShapeUtil<T, E> {
|
||||||
autoCapitalize="false"
|
autoCapitalize="false"
|
||||||
autoCorrect="false"
|
autoCorrect="false"
|
||||||
autoSave="false"
|
autoSave="false"
|
||||||
|
autoFocus
|
||||||
placeholder=""
|
placeholder=""
|
||||||
color={styles.stroke}
|
color={styles.stroke}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onBlur={handleBlur}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
onBlur={handleBlur}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
autoFocus
|
|
||||||
wrap="off"
|
wrap="off"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
datatype="wysiwyg"
|
datatype="wysiwyg"
|
||||||
|
|
|
@ -429,9 +429,7 @@ export class SelectTool extends BaseTool<Status> {
|
||||||
|
|
||||||
// Unless the user is holding shift or meta, clear the current selection
|
// Unless the user is holding shift or meta, clear the current selection
|
||||||
if (!info.shiftKey) {
|
if (!info.shiftKey) {
|
||||||
if (this.state.pageState.editingId) {
|
this.state.onShapeBlur()
|
||||||
this.state.setEditingId()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.altKey && this.state.selectedIds.length > 0) {
|
if (info.altKey && this.state.selectedIds.length > 0) {
|
||||||
this.state.duplicate(this.state.selectedIds, this.state.getPagePoint(info.point))
|
this.state.duplicate(this.state.selectedIds, this.state.getPagePoint(info.point))
|
||||||
|
@ -459,7 +457,11 @@ export class SelectTool extends BaseTool<Status> {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hoveredId } = this.state.pageState
|
const { editingId, hoveredId } = this.state.pageState
|
||||||
|
|
||||||
|
if (editingId && info.target !== editingId) {
|
||||||
|
this.state.onShapeBlur()
|
||||||
|
}
|
||||||
|
|
||||||
// While holding command and shift, select or deselect
|
// While holding command and shift, select or deselect
|
||||||
// the shape, ignoring any group that may contain it. Yikes!
|
// the shape, ignoring any group that may contain it. Yikes!
|
||||||
|
|
Loading…
Reference in a new issue