Makes keyboard events work again (#114)
This commit is contained in:
parent
48f784c322
commit
fc1dde724e
9 changed files with 108 additions and 39 deletions
|
@ -6,6 +6,7 @@ import {
|
|||
useSafariFocusOutFix,
|
||||
useCanvasEvents,
|
||||
useCameraCss,
|
||||
useKeyEvents,
|
||||
} from '+hooks'
|
||||
import type { TLBinding, TLPage, TLPageState, TLShape } from '+types'
|
||||
import { ErrorFallback } from '+components/error-fallback'
|
||||
|
@ -57,6 +58,8 @@ export function Canvas<T extends TLShape, M extends Record<string, unknown>>({
|
|||
e.currentTarget.scrollTo(0, 0)
|
||||
}, [])
|
||||
|
||||
useKeyEvents()
|
||||
|
||||
return (
|
||||
<div id={id} className="tl-container" ref={rContainer}>
|
||||
<div
|
||||
|
|
|
@ -14,3 +14,4 @@ export * from './useHandles'
|
|||
export * from './usePreventNavigation'
|
||||
export * from './useBoundsEvents'
|
||||
export * from './usePosition'
|
||||
export * from './useKeyEvents'
|
||||
|
|
25
packages/core/src/hooks/useKeyEvents.ts
Normal file
25
packages/core/src/hooks/useKeyEvents.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { useTLContext } from '+hooks'
|
||||
import * as React from 'react'
|
||||
|
||||
export function useKeyEvents() {
|
||||
const { inputs, callbacks } = useTLContext()
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
callbacks.onKeyDown?.(e.key, inputs.keydown(e), e)
|
||||
}
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
inputs.keyup(e)
|
||||
callbacks.onKeyUp?.(e.key, inputs.keyup(e), e)
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
}
|
||||
}, [inputs, callbacks])
|
||||
}
|
|
@ -133,6 +133,8 @@ export type TLShapeChangeHandler<T, K = any> = (
|
|||
|
||||
export type TLShapeBlurHandler<K = any> = (info?: K) => void
|
||||
|
||||
export type TLKeyboardEventHandler = (key: string, info: TLKeyboardInfo, e: KeyboardEvent) => void
|
||||
|
||||
export type TLPointerEventHandler = (info: TLPointerInfo<string>, e: React.PointerEvent) => void
|
||||
|
||||
export type TLCanvasEventHandler = (info: TLPointerInfo<'canvas'>, e: React.PointerEvent) => void
|
||||
|
@ -206,6 +208,10 @@ export interface TLCallbacks<T extends TLShape> {
|
|||
onRenderCountChange: (ids: string[]) => void
|
||||
onError: (error: Error) => void
|
||||
onBoundsChange: (bounds: TLBounds) => void
|
||||
|
||||
// Keyboard event handlers
|
||||
onKeyDown: TLKeyboardEventHandler
|
||||
onKeyUp: TLKeyboardEventHandler
|
||||
}
|
||||
|
||||
export interface TLBounds {
|
||||
|
|
|
@ -240,6 +240,8 @@ function InnerTldraw({
|
|||
onShapeChange={tlstate.onShapeChange}
|
||||
onShapeBlur={tlstate.onShapeBlur}
|
||||
onBoundsChange={tlstate.updateBounds}
|
||||
onKeyDown={tlstate.onKeyDown}
|
||||
onKeyUp={tlstate.onKeyUp}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{isFocusMode ? (
|
||||
|
|
|
@ -11,24 +11,6 @@ export function useKeyboardShortcuts(ref: React.RefObject<HTMLDivElement>) {
|
|||
return elm && (document.activeElement === elm || elm.contains(document.activeElement))
|
||||
}, [ref])
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (canHandleEvent()) tlstate.onKeyDown(e.key)
|
||||
}
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if (canHandleEvent()) tlstate.onKeyUp(e.key)
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
}
|
||||
}, [tlstate])
|
||||
|
||||
/* ---------------------- Tools --------------------- */
|
||||
|
||||
useHotkeys(
|
||||
|
|
|
@ -61,6 +61,53 @@ describe('Rotate session', () => {
|
|||
|
||||
it.todo('rotates handles only on shapes with handles')
|
||||
|
||||
describe('when rotating a single shape while pressing shift', () => {
|
||||
it('Clamps rotation to 15 degrees', () => {
|
||||
const tlstate = new TLDrawState()
|
||||
.loadDocument(mockDocument)
|
||||
.select('rect1')
|
||||
.startTransformSession([0, 0], 'rotate')
|
||||
.updateTransformSession([20, 10], true)
|
||||
.completeSession()
|
||||
|
||||
expect(Math.round((tlstate.getShape('rect1').rotation || 0) * (180 / Math.PI)) % 15).toEqual(
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
it('Clamps rotation to 15 degrees when starting from a rotation', () => {
|
||||
// Rect 1 is a little rotated
|
||||
const tlstate = new TLDrawState()
|
||||
.loadDocument(mockDocument)
|
||||
.select('rect1')
|
||||
.startTransformSession([0, 0], 'rotate')
|
||||
.updateTransformSession([5, 5])
|
||||
.completeSession()
|
||||
|
||||
// Rect 1 clamp rotated, starting from a little rotation
|
||||
tlstate
|
||||
.select('rect1')
|
||||
.startTransformSession([0, 0], 'rotate')
|
||||
.updateTransformSession([100, 200], true)
|
||||
.completeSession()
|
||||
|
||||
expect(Math.round((tlstate.getShape('rect1').rotation || 0) * (180 / Math.PI)) % 15).toEqual(
|
||||
0
|
||||
)
|
||||
|
||||
// Try again, too.
|
||||
tlstate
|
||||
.select('rect1')
|
||||
.startTransformSession([0, 0], 'rotate')
|
||||
.updateTransformSession([-100, 5000], true)
|
||||
.completeSession()
|
||||
|
||||
expect(Math.round((tlstate.getShape('rect1').rotation || 0) * (180 / Math.PI)) % 15).toEqual(
|
||||
0
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when rotating multiple shapes', () => {
|
||||
it('keeps the center', () => {
|
||||
tlstate.loadDocument(mockDocument).select('rect1', 'rect2')
|
||||
|
|
|
@ -30,19 +30,28 @@ export class RotateSession implements Session {
|
|||
|
||||
const shapes: Record<string, Partial<TLDrawShape>> = {}
|
||||
|
||||
const nextDirection = Vec.angle(commonBoundsCenter, point) - this.initialAngle
|
||||
|
||||
let nextBoundsRotation = this.snapshot.boundsRotation + nextDirection
|
||||
let directionDelta = Vec.angle(commonBoundsCenter, point) - this.initialAngle
|
||||
|
||||
if (isLocked) {
|
||||
nextBoundsRotation = Utils.snapAngleToSegments(nextBoundsRotation, 24)
|
||||
directionDelta = Utils.snapAngleToSegments(directionDelta, 24) // 15 degrees
|
||||
}
|
||||
|
||||
const delta = nextBoundsRotation - this.snapshot.boundsRotation
|
||||
|
||||
// Update the shapes
|
||||
initialShapes.forEach(({ id, center, shape }) => {
|
||||
const change = TLDR.getRotatedShapeMutation(shape, center, commonBoundsCenter, delta)
|
||||
const { rotation = 0 } = shape
|
||||
let shapeDelta = 0
|
||||
|
||||
if (isLocked) {
|
||||
const snappedRotation = Utils.snapAngleToSegments(rotation, 24)
|
||||
shapeDelta = snappedRotation - rotation
|
||||
}
|
||||
|
||||
const change = TLDR.getRotatedShapeMutation(
|
||||
shape,
|
||||
center,
|
||||
commonBoundsCenter,
|
||||
isLocked ? directionDelta + shapeDelta : directionDelta
|
||||
)
|
||||
|
||||
if (change) {
|
||||
shapes[id] = change
|
||||
|
@ -51,6 +60,8 @@ export class RotateSession implements Session {
|
|||
|
||||
this.changes = shapes
|
||||
|
||||
const nextBoundsRotation = this.snapshot.boundsRotation + directionDelta
|
||||
|
||||
return {
|
||||
document: {
|
||||
pages: {
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
TLBoundsEdge,
|
||||
TLBoundsEventHandler,
|
||||
TLBoundsHandleEventHandler,
|
||||
TLKeyboardEventHandler,
|
||||
TLCanvasEventHandler,
|
||||
TLPageState,
|
||||
TLPinchEventHandler,
|
||||
|
@ -361,10 +362,6 @@ export class TLDrawState extends StateManager<Data> {
|
|||
)
|
||||
}
|
||||
|
||||
handleMount = (inputs: Inputs): void => {
|
||||
this.inputs = inputs
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the bounding box when the renderer's bounds change.
|
||||
* @param bounds
|
||||
|
@ -2261,16 +2258,14 @@ export class TLDrawState extends StateManager<Data> {
|
|||
|
||||
/* ----------------- Keyboard Events ---------------- */
|
||||
|
||||
onKeyDown = (key: string) => {
|
||||
const info = this.inputs?.pointer
|
||||
if (!info) return
|
||||
|
||||
onKeyDown: TLKeyboardEventHandler = (key, info) => {
|
||||
if (key === 'Escape') {
|
||||
this.cancel()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!info) return
|
||||
|
||||
switch (this.appState.status.current) {
|
||||
case TLDrawStatus.Idle: {
|
||||
break
|
||||
|
@ -2296,9 +2291,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
case TLDrawStatus.Transforming: {
|
||||
if (key === 'Escape') {
|
||||
this.cancelSession(this.getPagePoint(info.point))
|
||||
}
|
||||
|
||||
if (key === 'Shift' || key === 'Alt') {
|
||||
} else if (key === 'Shift' || key === 'Alt') {
|
||||
this.updateTransformSession(this.getPagePoint(info.point), info.shiftKey, info.altKey)
|
||||
}
|
||||
break
|
||||
|
@ -2321,8 +2314,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
}
|
||||
|
||||
onKeyUp = (key: string) => {
|
||||
const info = this.inputs?.pointer
|
||||
onKeyUp: TLKeyboardEventHandler = (key, info) => {
|
||||
if (!info) return
|
||||
|
||||
switch (this.appState.status.current) {
|
||||
|
|
Loading…
Reference in a new issue