[fix] Text on Safari (#232)

* Fix editing bug on safari text

* Fix text behavior when blurring

* Update SelectTool.ts
This commit is contained in:
Steve Ruiz 2021-11-09 14:26:41 +00:00 committed by GitHub
parent 0b5a516b57
commit 6592608a09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 47 deletions

View file

@ -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')

View file

@ -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>(

View file

@ -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>) => {
if (!isEditing) return
if (rIsMounted.current) {
e.currentTarget.setSelectionRange(0, 0) e.currentTarget.setSelectionRange(0, 0)
onShapeBlur?.() 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
if (document.activeElement === e.currentTarget) {
e.currentTarget.select() 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) {
requestAnimationFrame(() => {
rIsMounted.current = true rIsMounted.current = true
const elm = rTextArea.current! const elm = rTextArea.current!
elm.focus() elm.focus()
elm.select() 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>

View file

@ -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>) => {
if (!isEditing) return
if (rIsMounted.current) {
e.currentTarget.setSelectionRange(0, 0) e.currentTarget.setSelectionRange(0, 0)
onShapeBlur?.() 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"

View file

@ -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!