transfer-out: transfer out
This commit is contained in:
parent
ec84f64e63
commit
29ed921c67
1056 changed files with 154507 additions and 0 deletions
270
packages/editor/src/lib/hooks/useDocumentEvents.ts
Normal file
270
packages/editor/src/lib/hooks/useDocumentEvents.ts
Normal file
|
@ -0,0 +1,270 @@
|
|||
import { useEffect } from 'react'
|
||||
import { useValue } from 'signia-react'
|
||||
import { TLKeyboardEventInfo, TLPointerEventInfo } from '../app/types/event-types'
|
||||
import { preventDefault } from '../utils/dom'
|
||||
import { useApp } from './useApp'
|
||||
import { useContainer } from './useContainer'
|
||||
|
||||
export function useDocumentEvents() {
|
||||
const app = useApp()
|
||||
const container = useContainer()
|
||||
|
||||
const isAppFocused = useValue('isFocused', () => app.isFocused, [app])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAppFocused) return
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (
|
||||
e.altKey &&
|
||||
(app.isIn('zoom') || !app.root.path.value.endsWith('.idle')) &&
|
||||
!isFocusingInput()
|
||||
) {
|
||||
// On windows the alt key opens the menu bar.
|
||||
// We want to prevent that if the user is doing something else,
|
||||
// e.g. resizing a shape
|
||||
preventDefault(e)
|
||||
}
|
||||
|
||||
if ((e as any).isKilled) return
|
||||
;(e as any).isKilled = true
|
||||
|
||||
switch (e.key) {
|
||||
case '=': {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
preventDefault(e)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case '-': {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
preventDefault(e)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case '0': {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
preventDefault(e)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Tab': {
|
||||
if (isFocusingInput() || app.isMenuOpen) {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
case ',': {
|
||||
if (!isFocusingInput()) {
|
||||
preventDefault(e)
|
||||
if (!app.inputs.keys.has('Comma')) {
|
||||
const { x, y, z } = app.inputs.currentScreenPoint
|
||||
const {
|
||||
pageState: { hoveredId },
|
||||
} = app
|
||||
app.inputs.keys.add('Comma')
|
||||
|
||||
const info: TLPointerEventInfo = {
|
||||
type: 'pointer',
|
||||
name: 'pointer_down',
|
||||
point: { x, y, z },
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
ctrlKey: e.metaKey || e.ctrlKey,
|
||||
pointerId: 0,
|
||||
button: 0,
|
||||
isPen: app.isPenMode,
|
||||
...(hoveredId
|
||||
? {
|
||||
target: 'shape',
|
||||
shape: app.getShapeById(hoveredId)!,
|
||||
}
|
||||
: {
|
||||
target: 'canvas',
|
||||
}),
|
||||
}
|
||||
|
||||
app.dispatch(info)
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Escape': {
|
||||
if (!app.inputs.keys.has('Escape')) {
|
||||
app.inputs.keys.add('Escape')
|
||||
|
||||
app.cancel()
|
||||
// Pressing escape will focus the document.body,
|
||||
// which will cause the app to lose focus, which
|
||||
// will break additional shortcuts. We need to
|
||||
// refocus the container in order to keep these
|
||||
// shortcuts working.
|
||||
container.focus()
|
||||
}
|
||||
return
|
||||
}
|
||||
default: {
|
||||
if (isFocusingInput() || app.isMenuOpen) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const info: TLKeyboardEventInfo = {
|
||||
type: 'keyboard',
|
||||
name: app.inputs.keys.has(e.code) ? 'key_repeat' : 'key_down',
|
||||
key: e.key,
|
||||
code: e.code,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
ctrlKey: e.metaKey || e.ctrlKey,
|
||||
}
|
||||
|
||||
app.dispatch(info)
|
||||
}
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if ((e as any).isKilled) return
|
||||
;(e as any).isKilled = true
|
||||
|
||||
if (isFocusingInput() || app.isMenuOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
// Use the , key to send pointer events
|
||||
if (e.key === ',') {
|
||||
if (document.activeElement?.ELEMENT_NODE) preventDefault(e)
|
||||
if (app.inputs.keys.has(e.code)) {
|
||||
const { x, y, z } = app.inputs.currentScreenPoint
|
||||
const {
|
||||
pageState: { hoveredId },
|
||||
} = app
|
||||
|
||||
app.inputs.keys.delete(e.code)
|
||||
|
||||
const info: TLPointerEventInfo = {
|
||||
type: 'pointer',
|
||||
name: 'pointer_up',
|
||||
point: { x, y, z },
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
ctrlKey: e.metaKey || e.ctrlKey,
|
||||
pointerId: 0,
|
||||
button: 0,
|
||||
isPen: app.isPenMode,
|
||||
...(hoveredId
|
||||
? {
|
||||
target: 'shape',
|
||||
shape: app.getShapeById(hoveredId)!,
|
||||
}
|
||||
: {
|
||||
target: 'canvas',
|
||||
}),
|
||||
}
|
||||
app.dispatch(info)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const info: TLKeyboardEventInfo = {
|
||||
type: 'keyboard',
|
||||
name: 'key_up',
|
||||
key: e.key,
|
||||
code: e.code,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
ctrlKey: e.metaKey || e.ctrlKey,
|
||||
}
|
||||
|
||||
app.dispatch(info)
|
||||
}
|
||||
|
||||
function handleTouchStart(e: TouchEvent) {
|
||||
if (container.contains(e.target as Node)) {
|
||||
// Center point of the touch area
|
||||
const touchXPosition = e.touches[0].pageX
|
||||
// Size of the touch area
|
||||
const touchXRadius = e.touches[0].radiusX || 0
|
||||
|
||||
// We set a threshold (10px) on both sizes of the screen,
|
||||
// if the touch area overlaps with the screen edges
|
||||
// it's likely to trigger the navigation. We prevent the
|
||||
// touchstart event in that case.
|
||||
if (
|
||||
touchXPosition - touchXRadius < 10 ||
|
||||
touchXPosition + touchXRadius > app.viewportScreenBounds.width - 10
|
||||
) {
|
||||
if ((e.target as HTMLElement)?.tagName === 'BUTTON') {
|
||||
// Force a click before bailing
|
||||
;(e.target as HTMLButtonElement)?.click()
|
||||
}
|
||||
|
||||
preventDefault(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent wheel events that occur inside of the container
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
if (container.contains(e.target as Node) && (e.ctrlKey || e.metaKey)) {
|
||||
preventDefault(e)
|
||||
}
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
app.complete()
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
app.updateViewportScreenBounds()
|
||||
}
|
||||
|
||||
container.addEventListener('touchstart', handleTouchStart, { passive: false })
|
||||
|
||||
document.addEventListener('wheel', handleWheel, { passive: false })
|
||||
document.addEventListener('gesturestart', preventDefault)
|
||||
document.addEventListener('gesturechange', preventDefault)
|
||||
document.addEventListener('gestureend', preventDefault)
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
document.addEventListener('keyup', handleKeyUp)
|
||||
|
||||
window.addEventListener('blur', handleBlur)
|
||||
window.addEventListener('focus', handleFocus)
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('touchstart', handleTouchStart)
|
||||
|
||||
document.removeEventListener('wheel', handleWheel)
|
||||
document.removeEventListener('gesturestart', preventDefault)
|
||||
document.removeEventListener('gesturechange', preventDefault)
|
||||
document.removeEventListener('gestureend', preventDefault)
|
||||
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
document.removeEventListener('keyup', handleKeyUp)
|
||||
|
||||
window.removeEventListener('blur', handleBlur)
|
||||
window.removeEventListener('focus', handleFocus)
|
||||
}
|
||||
}, [app, container, isAppFocused])
|
||||
}
|
||||
|
||||
const INPUTS = ['input', 'select', 'button', 'textarea']
|
||||
|
||||
function isFocusingInput() {
|
||||
const { activeElement } = document
|
||||
|
||||
if (
|
||||
activeElement &&
|
||||
(activeElement.getAttribute('contenteditble') ||
|
||||
INPUTS.indexOf(activeElement.tagName.toLowerCase()) > -1)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue