| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- import * as React from 'react'
- import { TLSelectionHandle, TLTargetType } from '@tldraw/core'
- import { DOUBLE_CLICK_DURATION } from '../constants'
- import type { TLReactCustomEvents } from '../types'
- import { useRendererContext } from './useRendererContext'
- export function useBoundsEvents(handle: TLSelectionHandle) {
- const { callbacks } = useRendererContext()
- const rDoubleClickTimer = React.useRef<number>(-1)
- const events = React.useMemo(() => {
- const onPointerMove: TLReactCustomEvents['pointer'] = e => {
- const { order = 0 } = e
- if (order) return
- callbacks.onPointerMove?.({ type: TLTargetType.Selection, handle, order }, e)
- e.order = order + 1
- }
- const onPointerDown: TLReactCustomEvents['pointer'] = e => {
- const { order = 0 } = e
- if (order) return
- // See note at bottom of the file
- const elm = loopToHtmlElement(e.currentTarget)
- elm.setPointerCapture(e.pointerId)
- elm.addEventListener('pointerup', onPointerUp)
- callbacks.onPointerDown?.({ type: TLTargetType.Selection, handle, order }, e)
- e.order = order + 1
- }
- const onPointerUp = (e: PointerEvent & { order?: number }) => {
- const { order = 0 } = e
- if (order) return
- const elm = e.target as HTMLElement
- elm.removeEventListener('pointerup', onPointerUp)
- elm.releasePointerCapture(e.pointerId)
- callbacks.onPointerUp?.({ type: TLTargetType.Selection, handle, order }, e)
- const now = Date.now()
- const elapsed = now - rDoubleClickTimer.current
- if (elapsed > DOUBLE_CLICK_DURATION) {
- rDoubleClickTimer.current = now
- } else {
- if (elapsed <= DOUBLE_CLICK_DURATION) {
- callbacks.onDoubleClick?.({ type: TLTargetType.Selection, handle, order }, e)
- rDoubleClickTimer.current = -1
- }
- }
- e.order = order + 1
- }
- const onPointerEnter: TLReactCustomEvents['pointer'] = e => {
- const { order = 0 } = e
- if (order) return
- callbacks.onPointerEnter?.({ type: TLTargetType.Selection, handle, order }, e)
- e.order = order + 1
- }
- const onPointerLeave: TLReactCustomEvents['pointer'] = e => {
- const { order = 0 } = e
- if (order) return
- callbacks.onPointerLeave?.({ type: TLTargetType.Selection, handle, order }, e)
- e.order = order + 1
- }
- const onKeyDown: TLReactCustomEvents['keyboard'] = e => {
- callbacks.onKeyDown?.({ type: TLTargetType.Selection, handle, order: -1 }, e)
- }
- const onKeyUp: TLReactCustomEvents['keyboard'] = e => {
- callbacks.onKeyUp?.({ type: TLTargetType.Selection, handle, order: -1 }, e)
- }
- return {
- onPointerDown,
- onPointerMove,
- // onPointerUp,
- onPointerEnter,
- onPointerLeave,
- onKeyDown,
- onKeyUp,
- }
- }, [callbacks])
- return events
- }
- function loopToHtmlElement(elm: Element): HTMLElement {
- if (elm.namespaceURI?.endsWith('svg')) {
- if (elm.parentElement) return loopToHtmlElement(elm.parentElement)
- else throw Error('Could not find a parent element of an HTML type!')
- }
- return elm as HTMLElement
- }
- /*
- There are a few hacks here in facilitate double clicking and pointer
- capture on elements.
- The events in this file are possibly set on individual SVG elements,
- such as handles or corner handles, rather than on HTML elements or
- SVGSVGElements. Raw SVG elements do not support pointerCapture in
- most cases, meaning that in order for pointer capture to work, we
- need to crawl up the DOM tree to find the nearest HTML element. Then,
- in order for that element to also call the `onPointerUp` event from
- this file, we need to manually set that event on that element and
- later remove it when the pointerup occurs. This is a potential leak
- if the user clicks on a handle but the pointerup does not fire for
- whatever reason.
- */
|