fix stuck pointer during text editing / palm hits (#316)

This commit is contained in:
Steve Ruiz 2021-11-20 00:05:48 +00:00 committed by GitHub
parent d5d999e86d
commit 62803443ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 688 additions and 676 deletions

View file

@ -25,7 +25,7 @@
"build:core": "lerna run build:core", "build:core": "lerna run build:core",
"build:packages": "lerna run build:packages --stream", "build:packages": "lerna run build:packages --stream",
"build:apps": "lerna run build:apps", "build:apps": "lerna run build:apps",
"start": "lerna run start --stream --parallel", "start": "yarn build:packages && lerna run start --stream --parallel",
"start:core": "lerna run start:core --stream --parallel", "start:core": "lerna run start:core --stream --parallel",
"start:www": "yarn build:packages && lerna run start --parallel & cd apps/www && yarn dev", "start:www": "yarn build:packages && lerna run start --parallel & cd apps/www && yarn dev",
"start:electron": "lerna run start:electron --stream --parallel", "start:electron": "lerna run start:electron --stream --parallel",

View file

@ -2,11 +2,11 @@
import * as React from 'react' import * as React from 'react'
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import type { TLShape, TLBounds, TLComponentProps } from '~types' import type { TLShape, TLBounds, TLComponentProps } from '../types'
import { TLShapeUtil } from './TLShapeUtil' import { TLShapeUtil } from './TLShapeUtil'
import { render } from '@testing-library/react' import { render } from '@testing-library/react'
import { SVGContainer } from '~components' import { SVGContainer } from '~components'
import Utils from '~utils' import Utils from '../utils'
export interface BoxShape extends TLShape { export interface BoxShape extends TLShape {
type: 'box' type: 'box'

View file

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'react' import * as React from 'react'
import Utils from '~utils' import Utils from '../utils'
import { intersectPolylineBounds } from '@tldraw/intersect' import { intersectPolylineBounds } from '@tldraw/intersect'
import type { TLBounds, TLComponentProps, TLForwardedRef, TLShape, TLUser } from '~types' import type { TLBounds, TLComponentProps, TLForwardedRef, TLShape, TLUser } from '../types'
export abstract class TLShapeUtil<T extends TLShape, E extends Element = any, M = any> { export abstract class TLShapeUtil<T extends TLShape, E extends Element = any, M = any> {
refMap = new Map<string, React.RefObject<E>>() refMap = new Map<string, React.RefObject<E>>()

View file

@ -1,4 +1,4 @@
import type { TLShape } from '~types' import type { TLShape } from '../types'
import type { TLShapeUtil } from './TLShapeUtil' import type { TLShapeUtil } from './TLShapeUtil'
export type TLShapeUtilsMap<T extends TLShape> = { export type TLShapeUtilsMap<T extends TLShape> = {

View file

@ -1,7 +1,7 @@
import * as React from 'react' import * as React from 'react'
import { renderWithContext } from '~test' import { renderWithContext } from '~test'
import { Handles } from './handles' import { Handles } from './handles'
import { boxShape } from '~shape-utils/TLShapeUtil.spec' import { boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
import { screen } from '@testing-library/react' import { screen } from '@testing-library/react'
describe('handles', () => { describe('handles', () => {

View file

@ -7,7 +7,7 @@ import { BoundsBg } from '~components/bounds/bounds-bg'
import { Handles } from '~components/handles' import { Handles } from '~components/handles'
import { ShapeNode } from '~components/shape' import { ShapeNode } from '~components/shape'
import { ShapeIndicator } from '~components/shape-indicator' import { ShapeIndicator } from '~components/shape-indicator'
import type { TLShapeUtil } from '~shape-utils' import type { TLShapeUtil } from '~TLShapeUtil'
interface PageProps<T extends TLShape, M extends Record<string, unknown>> { interface PageProps<T extends TLShape, M extends Record<string, unknown>> {
page: TLPage<T, TLBinding> page: TLPage<T, TLBinding>

View file

@ -8,12 +8,13 @@ import type {
TLTheme, TLTheme,
TLBounds, TLBounds,
TLBinding, TLBinding,
TLSnapLine,
TLUsers,
} from '../../types' } from '../../types'
import { Canvas } from '../canvas' import { Canvas } from '../canvas'
import { Inputs } from '../../inputs' import { Inputs } from '../../inputs'
import { useTLTheme, TLContext, TLContextType } from '../../hooks' import { useTLTheme, TLContext, TLContextType } from '../../hooks'
import type { TLSnapLine, TLUsers } from '~index' import type { TLShapeUtilsMap } from '../../TLShapeUtil'
import type { TLShapeUtilsMap } from '~shape-utils'
export interface RendererProps<T extends TLShape, M = any> extends Partial<TLCallbacks<T>> { export interface RendererProps<T extends TLShape, M = any> extends Partial<TLCallbacks<T>> {
/** /**

View file

@ -1,7 +1,7 @@
import * as React from 'react' import * as React from 'react'
import { renderWithSvg } from '~test' import { renderWithSvg } from '~test'
import { ShapeIndicator } from './shape-indicator' import { ShapeIndicator } from './shape-indicator'
import { boxShape } from '~shape-utils/TLShapeUtil.spec' import { boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
describe('shape indicator', () => { describe('shape indicator', () => {
test('mounts component without crashing', () => { test('mounts component without crashing', () => {

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as React from 'react' import * as React from 'react'
import type { TLComponentProps, TLShape } from '~types' import type { TLComponentProps, TLShape } from '~types'
import type { TLShapeUtil } from '~shape-utils' import type { TLShapeUtil } from '~TLShapeUtil'
interface RenderedShapeProps<T extends TLShape, E extends Element, M> interface RenderedShapeProps<T extends TLShape, E extends Element, M>
extends TLComponentProps<T, E, M> { extends TLComponentProps<T, E, M> {

View file

@ -1,7 +1,7 @@
import * as React from 'react' import * as React from 'react'
import type { IShapeTreeNode, TLShape } from '~types' import type { IShapeTreeNode, TLShape } from '~types'
import { Shape } from './shape' import { Shape } from './shape'
import type { TLShapeUtilsMap } from '~shape-utils' import type { TLShapeUtilsMap } from '~TLShapeUtil'
interface ShapeNodeProps<T extends TLShape> extends IShapeTreeNode<T> { interface ShapeNodeProps<T extends TLShape> extends IShapeTreeNode<T> {
utils: TLShapeUtilsMap<TLShape> utils: TLShapeUtilsMap<TLShape>

View file

@ -1,8 +1,8 @@
import * as React from 'react' import * as React from 'react'
import { renderWithContext } from '~test' import { renderWithContext } from '~test'
import { Shape } from './shape' import { Shape } from './shape'
import { BoxUtil, boxShape } from '~shape-utils/TLShapeUtil.spec' import { BoxUtil, boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
import type { TLShapeUtil } from '~shape-utils' import type { TLShapeUtil } from '~TLShapeUtil'
import type { TLShape } from '~types' import type { TLShape } from '~types'
describe('shape', () => { describe('shape', () => {

View file

@ -7,7 +7,7 @@ import { RenderedShape } from './rendered-shape'
import { Container } from '~components/container' import { Container } from '~components/container'
import { useTLContext } from '~hooks' import { useTLContext } from '~hooks'
import { useForceUpdate } from '~hooks/useForceUpdate' import { useForceUpdate } from '~hooks/useForceUpdate'
import type { TLShapeUtil } from '~shape-utils' import type { TLShapeUtil } from '~TLShapeUtil'
interface ShapeProps<T extends TLShape, M> extends IShapeTreeNode<T, M> { interface ShapeProps<T extends TLShape, M> extends IShapeTreeNode<T, M> {
utils: TLShapeUtil<T> utils: TLShapeUtil<T>

View file

@ -28,6 +28,9 @@ export function useBoundsEvents() {
const onPointerUp = React.useCallback( const onPointerUp = React.useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
if (e.button !== 0) return if (e.button !== 0) return
inputs.activePointer = undefined
if (!inputs.pointerIsValid(e)) return if (!inputs.pointerIsValid(e)) return
e.stopPropagation() e.stopPropagation()
const isDoubleClick = inputs.isDoubleClick() const isDoubleClick = inputs.isDoubleClick()

View file

@ -1,5 +1,5 @@
import * as React from 'react' import * as React from 'react'
import type { TLBoundsEdge, TLBoundsCorner } from '~types' import type { TLBoundsEdge, TLBoundsCorner } from '../types'
import { useTLContext } from './useTLContext' import { useTLContext } from './useTLContext'
export function useBoundsHandleEvents( export function useBoundsHandleEvents(

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as React from 'react' import * as React from 'react'
import type { TLPageState } from '~types' import type { TLPageState } from '../types'
export function useCameraCss( export function useCameraCss(
layerRef: React.RefObject<HTMLDivElement>, layerRef: React.RefObject<HTMLDivElement>,

View file

@ -37,6 +37,9 @@ export function useCanvasEvents() {
const onPointerUp = React.useCallback( const onPointerUp = React.useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
if (e.button !== 0) return if (e.button !== 0) return
inputs.activePointer = undefined
if (!inputs.pointerIsValid(e)) return if (!inputs.pointerIsValid(e)) return
const isDoubleClick = inputs.isDoubleClick() const isDoubleClick = inputs.isDoubleClick()
const info = inputs.pointerUp(e, 'canvas') const info = inputs.pointerUp(e, 'canvas')

View file

@ -1,4 +1,4 @@
import type { TLBinding, TLPage, TLPageState, TLShape } from '~types' import type { TLBinding, TLPage, TLPageState, TLShape } from '../types'
export function useHandles<T extends TLShape>(page: TLPage<T, TLBinding>, pageState: TLPageState) { export function useHandles<T extends TLShape>(page: TLPage<T, TLBinding>, pageState: TLPageState) {
const { selectedIds } = pageState const { selectedIds } = pageState

View file

@ -1,4 +1,4 @@
import { useTLContext } from '~hooks' import { useTLContext } from '../hooks'
import * as React from 'react' import * as React from 'react'
export function useKeyEvents() { export function useKeyEvents() {

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as React from 'react' import * as React from 'react'
import type { TLBounds } from '~types' import type { TLBounds } from '../types'
export function usePosition(bounds: TLBounds, rotation = 0) { export function usePosition(bounds: TLBounds, rotation = 0) {
const rBounds = React.useRef<HTMLDivElement>(null) const rBounds = React.useRef<HTMLDivElement>(null)

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import * as React from 'react' import * as React from 'react'
import { useTLContext } from '~hooks' import { useTLContext } from './useTLContext'
export function usePreventNavigation(rCanvas: React.RefObject<HTMLDivElement>): void { export function usePreventNavigation(rCanvas: React.RefObject<HTMLDivElement>): void {
const { bounds } = useTLContext() const { bounds } = useTLContext()

View file

@ -1,7 +1,7 @@
import { useTLContext } from '~hooks' import { useTLContext } from '../hooks'
import * as React from 'react' import * as React from 'react'
import { Utils } from '~utils' import { Utils } from '../utils'
import type { TLBounds } from '~types' import type { TLBounds } from '../types'
export function useResizeObserver<T extends Element>( export function useResizeObserver<T extends Element>(
ref: React.RefObject<T>, ref: React.RefObject<T>,

View file

@ -1,5 +1,5 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import Utils from '~utils' import Utils from '../utils'
import { useTLContext } from './useTLContext' import { useTLContext } from './useTLContext'
// Send event on iOS when a user presses the "Done" key while editing a text element. // Send event on iOS when a user presses the "Done" key while editing a text element.

View file

@ -1,8 +1,8 @@
import * as React from 'react' import * as React from 'react'
import type { TLPage, TLPageState, TLShape, TLBounds, TLBinding } from '~types' import type { TLPage, TLPageState, TLShape, TLBounds, TLBinding } from '../types'
import Utils from '~utils' import Utils from '../utils'
import { useTLContext } from '~hooks' import { useTLContext } from './useTLContext'
import type { TLShapeUtil, TLShapeUtilsMap } from '~shape-utils' import type { TLShapeUtil, TLShapeUtilsMap } from '../TLShapeUtil'
function canvasToScreen(point: number[], camera: TLPageState['camera']): number[] { function canvasToScreen(point: number[], camera: TLPageState['camera']): number[] {
return [(point[0] + camera.point[0]) * camera.zoom, (point[1] + camera.point[1]) * camera.zoom] return [(point[0] + camera.point[0]) * camera.zoom, (point[1] + camera.point[1]) * camera.zoom]

View file

@ -43,6 +43,9 @@ export function useShapeEvents(id: string) {
const onPointerUp = React.useCallback( const onPointerUp = React.useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
if (e.button !== 0) return if (e.button !== 0) return
inputs.activePointer = undefined
if (!inputs.pointerIsValid(e)) return if (!inputs.pointerIsValid(e)) return
e.stopPropagation() e.stopPropagation()
const isDoubleClick = inputs.isDoubleClick() const isDoubleClick = inputs.isDoubleClick()

View file

@ -1,7 +1,7 @@
import * as React from 'react' import * as React from 'react'
import type { Inputs } from '~inputs' import type { Inputs } from '~inputs'
import type { TLCallbacks, TLShape, TLBounds, TLPageState } from '~types' import type { TLCallbacks, TLShape, TLBounds, TLPageState } from '~types'
import type { TLShapeUtilsMap } from '~shape-utils' import type { TLShapeUtilsMap } from '~TLShapeUtil'
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface TLContextType<T extends TLShape> { export interface TLContextType<T extends TLShape> {

View file

@ -2,4 +2,4 @@ export * from './components'
export * from './types' export * from './types'
export * from './utils' export * from './utils'
export * from './inputs' export * from './inputs'
export * from './shape-utils' export * from './TLShapeUtil'

View file

@ -1,8 +1,7 @@
import type React from 'react' import type React from 'react'
import type { TLKeyboardInfo, TLPointerInfo } from './types' import type { TLBounds, TLKeyboardInfo, TLPointerInfo } from './types'
import { Utils } from './utils' import { Utils } from './utils'
import { Vec } from '@tldraw/vec' import { Vec } from '@tldraw/vec'
import type { TLBounds } from '~index'
const DOUBLE_CLICK_DURATION = 250 const DOUBLE_CLICK_DURATION = 250
@ -32,12 +31,16 @@ export class Inputs {
pointerIsValid(e: TouchEvent | React.TouchEvent | PointerEvent | React.PointerEvent) { pointerIsValid(e: TouchEvent | React.TouchEvent | PointerEvent | React.PointerEvent) {
if ('pointerId' in e) { if ('pointerId' in e) {
if (this.activePointer && this.activePointer !== e.pointerId) return false if (this.activePointer && this.activePointer !== e.pointerId) {
return false
}
} }
if ('touches' in e) { if ('touches' in e) {
const touch = e.changedTouches[0] const touch = e.changedTouches[0]
if (this.activePointer && this.activePointer !== touch.identifier) return false if (this.activePointer && this.activePointer !== touch.identifier) {
return false
}
} }
return true return true

View file

@ -5,7 +5,7 @@ import { mockUtils } from './mockUtils'
import { useTLTheme, TLContext, TLContextType } from '../hooks' import { useTLTheme, TLContext, TLContextType } from '../hooks'
import { Inputs } from '~inputs' import { Inputs } from '~inputs'
import type { TLShape } from '~index' import type { TLShape } from '~index'
import type { BoxShape } from '~shape-utils/TLShapeUtil.spec' import type { BoxShape } from '~TLShapeUtil/TLShapeUtil.spec'
export const ContextWrapper: React.FC = ({ children }) => { export const ContextWrapper: React.FC = ({ children }) => {
useTLTheme() useTLTheme()

View file

@ -1,4 +1,4 @@
import type { BoxShape } from '~shape-utils/TLShapeUtil.spec' import type { BoxShape } from '~TLShapeUtil/TLShapeUtil.spec'
import type { TLBinding, TLPage, TLPageState } from '~types' import type { TLBinding, TLPage, TLPageState } from '~types'
export const mockDocument: { page: TLPage<BoxShape, TLBinding>; pageState: TLPageState } = { export const mockDocument: { page: TLPage<BoxShape, TLBinding>; pageState: TLPageState } = {

View file

@ -1,4 +1,4 @@
import { BoxUtil } from '~shape-utils/TLShapeUtil.spec' import { BoxUtil } from '~TLShapeUtil/TLShapeUtil.spec'
export const mockUtils = { export const mockUtils = {
box: new BoxUtil(), box: new BoxUtil(),

View file

@ -75,9 +75,9 @@ export interface TLComponentProps<T extends TLShape, E = any, M = any> {
isSelected: boolean isSelected: boolean
isGhost?: boolean isGhost?: boolean
isChildOfSelected?: boolean isChildOfSelected?: boolean
meta: M extends any ? M : never meta: M
onShapeChange?: TLCallbacks<T>['onShapeChange'] onShapeChange?: TLShapeChangeHandler<T, any>
onShapeBlur?: TLCallbacks<T>['onShapeBlur'] onShapeBlur?: TLShapeBlurHandler<any>
events: { events: {
onPointerDown: (e: React.PointerEvent<E>) => void onPointerDown: (e: React.PointerEvent<E>) => void
onPointerUp: (e: React.PointerEvent<E>) => void onPointerUp: (e: React.PointerEvent<E>) => void

View file

@ -49,7 +49,7 @@
"@radix-ui/react-radio-group": "^0.1.1", "@radix-ui/react-radio-group": "^0.1.1",
"@radix-ui/react-tooltip": "^0.1.1", "@radix-ui/react-tooltip": "^0.1.1",
"@stitches/react": "^1.2.5", "@stitches/react": "^1.2.5",
"@tldraw/core": "latest", "@tldraw/core": "^1.0.4",
"@tldraw/intersect": "latest", "@tldraw/intersect": "latest",
"@tldraw/vec": "latest", "@tldraw/vec": "latest",
"perfect-freehand": "^1.0.16", "perfect-freehand": "^1.0.16",

View file

@ -14,6 +14,7 @@ import { getEllipseIndicatorPathTDSnapshot, getEllipsePath } from './ellipseHelp
type T = EllipseShape type T = EllipseShape
type E = SVGSVGElement type E = SVGSVGElement
type M = TDMeta
export class EllipseUtil extends TDShapeUtil<T, E> { export class EllipseUtil extends TDShapeUtil<T, E> {
type = TDShapeType.Ellipse as const type = TDShapeType.Ellipse as const
@ -128,7 +129,7 @@ export class EllipseUtil extends TDShapeUtil<T, E> {
} }
) )
Indicator = TDShapeUtil.Indicator<T>(({ shape }) => { Indicator = TDShapeUtil.Indicator<T, M>(({ shape }) => {
return <path d={getEllipseIndicatorPathTDSnapshot(shape, this.getCenter(shape))} /> return <path d={getEllipseIndicatorPathTDSnapshot(shape, this.getCenter(shape))} />
}) })

1258
yarn.lock

File diff suppressed because it is too large Load diff