fix stuck pointer during text editing / palm hits (#316)
This commit is contained in:
parent
d5d999e86d
commit
62803443ef
35 changed files with 688 additions and 676 deletions
|
@ -25,7 +25,7 @@
|
|||
"build:core": "lerna run build:core",
|
||||
"build:packages": "lerna run build:packages --stream",
|
||||
"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:www": "yarn build:packages && lerna run start --parallel & cd apps/www && yarn dev",
|
||||
"start:electron": "lerna run start:electron --stream --parallel",
|
||||
|
|
|
@ -81,4 +81,4 @@
|
|||
"\\~(.*)": "<rootDir>/src/$1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,11 +2,11 @@
|
|||
import * as React from 'react'
|
||||
|
||||
/* 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 { render } from '@testing-library/react'
|
||||
import { SVGContainer } from '~components'
|
||||
import Utils from '~utils'
|
||||
import Utils from '../utils'
|
||||
|
||||
export interface BoxShape extends TLShape {
|
||||
type: 'box'
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import * as React from 'react'
|
||||
import Utils from '~utils'
|
||||
import Utils from '../utils'
|
||||
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> {
|
||||
refMap = new Map<string, React.RefObject<E>>()
|
|
@ -1,4 +1,4 @@
|
|||
import type { TLShape } from '~types'
|
||||
import type { TLShape } from '../types'
|
||||
import type { TLShapeUtil } from './TLShapeUtil'
|
||||
|
||||
export type TLShapeUtilsMap<T extends TLShape> = {
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import { renderWithContext } from '~test'
|
||||
import { Handles } from './handles'
|
||||
import { boxShape } from '~shape-utils/TLShapeUtil.spec'
|
||||
import { boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
|
||||
import { screen } from '@testing-library/react'
|
||||
|
||||
describe('handles', () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { BoundsBg } from '~components/bounds/bounds-bg'
|
|||
import { Handles } from '~components/handles'
|
||||
import { ShapeNode } from '~components/shape'
|
||||
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>> {
|
||||
page: TLPage<T, TLBinding>
|
||||
|
|
|
@ -8,12 +8,13 @@ import type {
|
|||
TLTheme,
|
||||
TLBounds,
|
||||
TLBinding,
|
||||
TLSnapLine,
|
||||
TLUsers,
|
||||
} from '../../types'
|
||||
import { Canvas } from '../canvas'
|
||||
import { Inputs } from '../../inputs'
|
||||
import { useTLTheme, TLContext, TLContextType } from '../../hooks'
|
||||
import type { TLSnapLine, TLUsers } from '~index'
|
||||
import type { TLShapeUtilsMap } from '~shape-utils'
|
||||
import type { TLShapeUtilsMap } from '../../TLShapeUtil'
|
||||
|
||||
export interface RendererProps<T extends TLShape, M = any> extends Partial<TLCallbacks<T>> {
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import { renderWithSvg } from '~test'
|
||||
import { ShapeIndicator } from './shape-indicator'
|
||||
import { boxShape } from '~shape-utils/TLShapeUtil.spec'
|
||||
import { boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
|
||||
|
||||
describe('shape indicator', () => {
|
||||
test('mounts component without crashing', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as React from 'react'
|
||||
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>
|
||||
extends TLComponentProps<T, E, M> {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import type { IShapeTreeNode, TLShape } from '~types'
|
||||
import { Shape } from './shape'
|
||||
import type { TLShapeUtilsMap } from '~shape-utils'
|
||||
import type { TLShapeUtilsMap } from '~TLShapeUtil'
|
||||
|
||||
interface ShapeNodeProps<T extends TLShape> extends IShapeTreeNode<T> {
|
||||
utils: TLShapeUtilsMap<TLShape>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from 'react'
|
||||
import { renderWithContext } from '~test'
|
||||
import { Shape } from './shape'
|
||||
import { BoxUtil, boxShape } from '~shape-utils/TLShapeUtil.spec'
|
||||
import type { TLShapeUtil } from '~shape-utils'
|
||||
import { BoxUtil, boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
|
||||
import type { TLShapeUtil } from '~TLShapeUtil'
|
||||
import type { TLShape } from '~types'
|
||||
|
||||
describe('shape', () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { RenderedShape } from './rendered-shape'
|
|||
import { Container } from '~components/container'
|
||||
import { useTLContext } from '~hooks'
|
||||
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> {
|
||||
utils: TLShapeUtil<T>
|
||||
|
|
|
@ -28,6 +28,9 @@ export function useBoundsEvents() {
|
|||
const onPointerUp = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (e.button !== 0) return
|
||||
|
||||
inputs.activePointer = undefined
|
||||
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import type { TLBoundsEdge, TLBoundsCorner } from '~types'
|
||||
import type { TLBoundsEdge, TLBoundsCorner } from '../types'
|
||||
import { useTLContext } from './useTLContext'
|
||||
|
||||
export function useBoundsHandleEvents(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as React from 'react'
|
||||
import type { TLPageState } from '~types'
|
||||
import type { TLPageState } from '../types'
|
||||
|
||||
export function useCameraCss(
|
||||
layerRef: React.RefObject<HTMLDivElement>,
|
||||
|
|
|
@ -37,6 +37,9 @@ export function useCanvasEvents() {
|
|||
const onPointerUp = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (e.button !== 0) return
|
||||
|
||||
inputs.activePointer = undefined
|
||||
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
const info = inputs.pointerUp(e, 'canvas')
|
||||
|
|
|
@ -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) {
|
||||
const { selectedIds } = pageState
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useTLContext } from '~hooks'
|
||||
import { useTLContext } from '../hooks'
|
||||
import * as React from 'react'
|
||||
|
||||
export function useKeyEvents() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as React from 'react'
|
||||
import type { TLBounds } from '~types'
|
||||
import type { TLBounds } from '../types'
|
||||
|
||||
export function usePosition(bounds: TLBounds, rotation = 0) {
|
||||
const rBounds = React.useRef<HTMLDivElement>(null)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import * as React from 'react'
|
||||
import { useTLContext } from '~hooks'
|
||||
import { useTLContext } from './useTLContext'
|
||||
|
||||
export function usePreventNavigation(rCanvas: React.RefObject<HTMLDivElement>): void {
|
||||
const { bounds } = useTLContext()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useTLContext } from '~hooks'
|
||||
import { useTLContext } from '../hooks'
|
||||
import * as React from 'react'
|
||||
import { Utils } from '~utils'
|
||||
import type { TLBounds } from '~types'
|
||||
import { Utils } from '../utils'
|
||||
import type { TLBounds } from '../types'
|
||||
|
||||
export function useResizeObserver<T extends Element>(
|
||||
ref: React.RefObject<T>,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect } from 'react'
|
||||
import Utils from '~utils'
|
||||
import Utils from '../utils'
|
||||
import { useTLContext } from './useTLContext'
|
||||
|
||||
// Send event on iOS when a user presses the "Done" key while editing a text element.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from 'react'
|
||||
import type { TLPage, TLPageState, TLShape, TLBounds, TLBinding } from '~types'
|
||||
import Utils from '~utils'
|
||||
import { useTLContext } from '~hooks'
|
||||
import type { TLShapeUtil, TLShapeUtilsMap } from '~shape-utils'
|
||||
import type { TLPage, TLPageState, TLShape, TLBounds, TLBinding } from '../types'
|
||||
import Utils from '../utils'
|
||||
import { useTLContext } from './useTLContext'
|
||||
import type { TLShapeUtil, TLShapeUtilsMap } from '../TLShapeUtil'
|
||||
|
||||
function canvasToScreen(point: number[], camera: TLPageState['camera']): number[] {
|
||||
return [(point[0] + camera.point[0]) * camera.zoom, (point[1] + camera.point[1]) * camera.zoom]
|
||||
|
|
|
@ -43,6 +43,9 @@ export function useShapeEvents(id: string) {
|
|||
const onPointerUp = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (e.button !== 0) return
|
||||
|
||||
inputs.activePointer = undefined
|
||||
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import type { Inputs } from '~inputs'
|
||||
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
|
||||
export interface TLContextType<T extends TLShape> {
|
||||
|
|
|
@ -2,4 +2,4 @@ export * from './components'
|
|||
export * from './types'
|
||||
export * from './utils'
|
||||
export * from './inputs'
|
||||
export * from './shape-utils'
|
||||
export * from './TLShapeUtil'
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import type React from 'react'
|
||||
import type { TLKeyboardInfo, TLPointerInfo } from './types'
|
||||
import type { TLBounds, TLKeyboardInfo, TLPointerInfo } from './types'
|
||||
import { Utils } from './utils'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
import type { TLBounds } from '~index'
|
||||
|
||||
const DOUBLE_CLICK_DURATION = 250
|
||||
|
||||
|
@ -32,12 +31,16 @@ export class Inputs {
|
|||
|
||||
pointerIsValid(e: TouchEvent | React.TouchEvent | PointerEvent | React.PointerEvent) {
|
||||
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) {
|
||||
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
|
||||
|
|
|
@ -5,7 +5,7 @@ import { mockUtils } from './mockUtils'
|
|||
import { useTLTheme, TLContext, TLContextType } from '../hooks'
|
||||
import { Inputs } from '~inputs'
|
||||
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 }) => {
|
||||
useTLTheme()
|
||||
|
|
|
@ -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'
|
||||
|
||||
export const mockDocument: { page: TLPage<BoxShape, TLBinding>; pageState: TLPageState } = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BoxUtil } from '~shape-utils/TLShapeUtil.spec'
|
||||
import { BoxUtil } from '~TLShapeUtil/TLShapeUtil.spec'
|
||||
|
||||
export const mockUtils = {
|
||||
box: new BoxUtil(),
|
||||
|
|
|
@ -75,9 +75,9 @@ export interface TLComponentProps<T extends TLShape, E = any, M = any> {
|
|||
isSelected: boolean
|
||||
isGhost?: boolean
|
||||
isChildOfSelected?: boolean
|
||||
meta: M extends any ? M : never
|
||||
onShapeChange?: TLCallbacks<T>['onShapeChange']
|
||||
onShapeBlur?: TLCallbacks<T>['onShapeBlur']
|
||||
meta: M
|
||||
onShapeChange?: TLShapeChangeHandler<T, any>
|
||||
onShapeBlur?: TLShapeBlurHandler<any>
|
||||
events: {
|
||||
onPointerDown: (e: React.PointerEvent<E>) => void
|
||||
onPointerUp: (e: React.PointerEvent<E>) => void
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
"@radix-ui/react-radio-group": "^0.1.1",
|
||||
"@radix-ui/react-tooltip": "^0.1.1",
|
||||
"@stitches/react": "^1.2.5",
|
||||
"@tldraw/core": "latest",
|
||||
"@tldraw/core": "^1.0.4",
|
||||
"@tldraw/intersect": "latest",
|
||||
"@tldraw/vec": "latest",
|
||||
"perfect-freehand": "^1.0.16",
|
||||
|
@ -90,4 +90,4 @@
|
|||
}
|
||||
},
|
||||
"gitHead": "325008ff82bd27b63d625ad1b760f8871fb71af9"
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import { getEllipseIndicatorPathTDSnapshot, getEllipsePath } from './ellipseHelp
|
|||
|
||||
type T = EllipseShape
|
||||
type E = SVGSVGElement
|
||||
type M = TDMeta
|
||||
|
||||
export class EllipseUtil extends TDShapeUtil<T, E> {
|
||||
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))} />
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue