Fix context menu, fix blur issue
This commit is contained in:
parent
31a1a8b5ae
commit
b1697b2ca7
12 changed files with 244 additions and 351 deletions
|
@ -4,85 +4,54 @@ import { useTLContext } from './useTLContext'
|
|||
export function useBoundsEvents() {
|
||||
const { callbacks, inputs } = useTLContext()
|
||||
|
||||
const onPointerDown = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
|
||||
if (e.button === 2) {
|
||||
callbacks.onRightPointBounds?.(inputs.pointerDown(e, 'bounds'), e)
|
||||
return
|
||||
}
|
||||
|
||||
if (e.button !== 0) return
|
||||
|
||||
e.stopPropagation()
|
||||
e.currentTarget?.setPointerCapture(e.pointerId)
|
||||
const info = inputs.pointerDown(e, 'bounds')
|
||||
|
||||
callbacks.onPointBounds?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
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()
|
||||
const info = inputs.pointerUp(e, 'bounds')
|
||||
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget?.releasePointerCapture(e.pointerId)
|
||||
}
|
||||
|
||||
if (isDoubleClick && !(info.altKey || info.metaKey)) {
|
||||
callbacks.onDoubleClickBounds?.(info, e)
|
||||
}
|
||||
|
||||
callbacks.onReleaseBounds?.(info, e)
|
||||
callbacks.onPointerUp?.(info, e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
const onPointerMove = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
callbacks.onDragBounds?.(inputs.pointerMove(e, 'bounds'), e)
|
||||
}
|
||||
const info = inputs.pointerMove(e, 'bounds')
|
||||
callbacks.onPointerMove?.(info, e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
const onPointerEnter = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
callbacks.onHoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
const onPointerLeave = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
callbacks.onUnhoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
return {
|
||||
onPointerDown,
|
||||
onPointerUp,
|
||||
onPointerEnter,
|
||||
onPointerMove,
|
||||
onPointerLeave,
|
||||
}
|
||||
return React.useMemo(() => {
|
||||
return {
|
||||
onPointerDown: (e: React.PointerEvent) => {
|
||||
if ((e as any).dead) return
|
||||
else (e as any).dead = true
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
if (e.button === 2) {
|
||||
callbacks.onRightPointBounds?.(inputs.pointerDown(e, 'bounds'), e)
|
||||
return
|
||||
}
|
||||
if (e.button !== 0) return
|
||||
e.currentTarget?.setPointerCapture(e.pointerId)
|
||||
const info = inputs.pointerDown(e, 'bounds')
|
||||
callbacks.onPointBounds?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
},
|
||||
onPointerUp: (e: React.PointerEvent) => {
|
||||
if (e.button !== 0) return
|
||||
inputs.activePointer = undefined
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
const info = inputs.pointerUp(e, 'bounds')
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget?.releasePointerCapture(e.pointerId)
|
||||
}
|
||||
if (isDoubleClick && !(info.altKey || info.metaKey)) {
|
||||
callbacks.onDoubleClickBounds?.(info, e)
|
||||
}
|
||||
callbacks.onReleaseBounds?.(info, e)
|
||||
callbacks.onPointerUp?.(info, e)
|
||||
},
|
||||
onPointerMove: (e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
callbacks.onDragBounds?.(inputs.pointerMove(e, 'bounds'), e)
|
||||
}
|
||||
const info = inputs.pointerMove(e, 'bounds')
|
||||
callbacks.onPointerMove?.(info, e)
|
||||
},
|
||||
onPointerEnter: (e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
callbacks.onHoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
||||
},
|
||||
onPointerLeave: (e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
callbacks.onUnhoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
||||
},
|
||||
}
|
||||
}, [inputs, callbacks])
|
||||
}
|
||||
|
|
|
@ -4,64 +4,46 @@ import { useTLContext } from './useTLContext'
|
|||
export function useCanvasEvents() {
|
||||
const { callbacks, inputs } = useTLContext()
|
||||
|
||||
const onPointerDown = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (e.button !== 0 && e.button !== 1) return
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
|
||||
const info = inputs.pointerDown(e, 'canvas')
|
||||
|
||||
if (e.button === 0 || e.button === 1) {
|
||||
callbacks.onPointCanvas?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
}
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
const onPointerMove = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const info = inputs.pointerMove(e, 'canvas')
|
||||
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
callbacks.onDragCanvas?.(info, e)
|
||||
}
|
||||
|
||||
callbacks.onPointerMove?.(info, e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
const onPointerUp = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (e.button !== 0 && e.button !== 1) return
|
||||
|
||||
inputs.activePointer = undefined
|
||||
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
const info = inputs.pointerUp(e, 'canvas')
|
||||
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget?.releasePointerCapture(e.pointerId)
|
||||
}
|
||||
if (isDoubleClick && !(info.altKey || info.metaKey)) {
|
||||
callbacks.onDoubleClickCanvas?.(info, e)
|
||||
}
|
||||
|
||||
callbacks.onReleaseCanvas?.(info, e)
|
||||
callbacks.onPointerUp?.(info, e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
)
|
||||
|
||||
return {
|
||||
onPointerDown,
|
||||
onPointerMove,
|
||||
onPointerUp,
|
||||
onDrop: callbacks.onDrop,
|
||||
onDragOver: callbacks.onDragOver,
|
||||
}
|
||||
return React.useMemo(() => {
|
||||
return {
|
||||
onPointerDown: (e: React.PointerEvent) => {
|
||||
if ((e as any).dead) return
|
||||
else (e as any).dead = true
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
if (e.button !== 0 && e.button !== 1) return
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
const info = inputs.pointerDown(e, 'canvas')
|
||||
if (e.button === 0 || e.button === 1) {
|
||||
callbacks.onPointCanvas?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
}
|
||||
},
|
||||
onPointerMove: (e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const info = inputs.pointerMove(e, 'canvas')
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
callbacks.onDragCanvas?.(info, e)
|
||||
}
|
||||
callbacks.onPointerMove?.(info, e)
|
||||
},
|
||||
onPointerUp: (e: React.PointerEvent) => {
|
||||
if (e.button !== 0 && e.button !== 1) return
|
||||
inputs.activePointer = undefined
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
const info = inputs.pointerUp(e, 'canvas')
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget?.releasePointerCapture(e.pointerId)
|
||||
}
|
||||
if (isDoubleClick && !(info.altKey || info.metaKey)) {
|
||||
callbacks.onDoubleClickCanvas?.(info, e)
|
||||
}
|
||||
callbacks.onReleaseCanvas?.(info, e)
|
||||
callbacks.onPointerUp?.(info, e)
|
||||
},
|
||||
onDrop: callbacks.onDrop,
|
||||
onDragOver: callbacks.onDragOver,
|
||||
}
|
||||
}, [callbacks, inputs])
|
||||
}
|
||||
|
|
|
@ -90,10 +90,7 @@ export function useCursorAnimation(ref: any, point: number[]) {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(rTimeoutId.current)
|
||||
}
|
||||
return () => clearTimeout(rTimeoutId.current)
|
||||
}, [point, spline])
|
||||
}
|
||||
|
||||
|
|
|
@ -4,78 +4,53 @@ import { useTLContext } from './useTLContext'
|
|||
export function useHandleEvents(id: string) {
|
||||
const { inputs, callbacks } = useTLContext()
|
||||
|
||||
const onPointerDown = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (e.button !== 0) return
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
e.currentTarget?.setPointerCapture(e.pointerId)
|
||||
|
||||
const info = inputs.pointerDown(e, id)
|
||||
callbacks.onPointHandle?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
},
|
||||
[inputs, callbacks, id]
|
||||
)
|
||||
|
||||
const onPointerUp = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (e.button !== 0) return
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
const info = inputs.pointerUp(e, id)
|
||||
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget?.releasePointerCapture(e.pointerId)
|
||||
|
||||
if (isDoubleClick && !(info.altKey || info.metaKey)) {
|
||||
callbacks.onDoubleClickHandle?.(info, e)
|
||||
return React.useMemo(() => {
|
||||
return {
|
||||
onPointerDown: (e: React.PointerEvent) => {
|
||||
if ((e as any).dead) return
|
||||
else (e as any).dead = true
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
if (e.button !== 0) return
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.currentTarget?.setPointerCapture(e.pointerId)
|
||||
const info = inputs.pointerDown(e, id)
|
||||
callbacks.onPointHandle?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
},
|
||||
onPointerUp: (e: React.PointerEvent) => {
|
||||
if (e.button !== 0) return
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
const isDoubleClick = inputs.isDoubleClick()
|
||||
const info = inputs.pointerUp(e, id)
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget?.releasePointerCapture(e.pointerId)
|
||||
if (isDoubleClick && !(info.altKey || info.metaKey)) {
|
||||
callbacks.onDoubleClickHandle?.(info, e)
|
||||
}
|
||||
callbacks.onReleaseHandle?.(info, e)
|
||||
}
|
||||
|
||||
callbacks.onReleaseHandle?.(info, e)
|
||||
}
|
||||
callbacks.onPointerUp?.(info, e)
|
||||
},
|
||||
[inputs, callbacks]
|
||||
)
|
||||
|
||||
const onPointerMove = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
const info = inputs.pointerMove(e, id)
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
callbacks.onDragHandle?.(info, e)
|
||||
}
|
||||
callbacks.onPointerMove?.(info, e)
|
||||
},
|
||||
[inputs, callbacks, id]
|
||||
)
|
||||
|
||||
const onPointerEnter = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const info = inputs.pointerEnter(e, id)
|
||||
callbacks.onHoverHandle?.(info, e)
|
||||
},
|
||||
[inputs, callbacks, id]
|
||||
)
|
||||
|
||||
const onPointerLeave = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const info = inputs.pointerEnter(e, id)
|
||||
callbacks.onUnhoverHandle?.(info, e)
|
||||
},
|
||||
[inputs, callbacks, id]
|
||||
)
|
||||
|
||||
return {
|
||||
onPointerDown,
|
||||
onPointerUp,
|
||||
onPointerEnter,
|
||||
onPointerMove,
|
||||
onPointerLeave,
|
||||
}
|
||||
callbacks.onPointerUp?.(info, e)
|
||||
},
|
||||
onPointerMove: (e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
e.stopPropagation()
|
||||
const info = inputs.pointerMove(e, id)
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
callbacks.onDragHandle?.(info, e)
|
||||
}
|
||||
callbacks.onPointerMove?.(info, e)
|
||||
},
|
||||
onPointerEnter: (e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const info = inputs.pointerEnter(e, id)
|
||||
callbacks.onHoverHandle?.(info, e)
|
||||
},
|
||||
onPointerLeave: (e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const info = inputs.pointerEnter(e, id)
|
||||
callbacks.onUnhoverHandle?.(info, e)
|
||||
},
|
||||
}
|
||||
}, [inputs, callbacks, id])
|
||||
}
|
||||
|
|
|
@ -8,15 +8,12 @@ export function useKeyEvents() {
|
|||
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)
|
||||
|
|
|
@ -10,27 +10,21 @@ export function usePosition(bounds: TLBounds, rotation = 0) {
|
|||
React.useLayoutEffect(() => {
|
||||
return autorun(() => {
|
||||
const elm = rBounds.current!
|
||||
|
||||
const transform = `
|
||||
translate(
|
||||
calc(${bounds.minX}px - var(--tl-padding)),
|
||||
calc(${bounds.minY}px - var(--tl-padding))
|
||||
)
|
||||
rotate(${rotation + (bounds.rotation || 0)}rad)`
|
||||
|
||||
elm.style.setProperty('transform', transform)
|
||||
|
||||
elm.style.setProperty(
|
||||
'width',
|
||||
`calc(${Math.floor(bounds.width)}px + (var(--tl-padding) * 2))`
|
||||
)
|
||||
|
||||
elm.style.setProperty(
|
||||
'height',
|
||||
`calc(${Math.floor(bounds.height)}px + (var(--tl-padding) * 2))`
|
||||
)
|
||||
|
||||
// elm.style.setProperty('z-index', zIndex + '')
|
||||
})
|
||||
}, [bounds, rotation])
|
||||
|
||||
|
|
|
@ -28,23 +28,15 @@ export function useSelection<T extends TLShape>(
|
|||
|
||||
if (selectedIds.length === 1) {
|
||||
const id = selectedIds[0]
|
||||
|
||||
const shape = page.shapes[id]
|
||||
|
||||
rotation = shape.rotation || 0
|
||||
|
||||
isLocked = shape.isLocked || false
|
||||
|
||||
const utils = getShapeUtils(shapeUtils, shape)
|
||||
|
||||
bounds = utils.hideBounds ? undefined : utils.getBounds(shape)
|
||||
} else if (selectedIds.length > 1) {
|
||||
const selectedShapes = selectedIds.map((id) => page.shapes[id])
|
||||
|
||||
rotation = 0
|
||||
|
||||
isLocked = selectedShapes.every((shape) => shape.isLocked)
|
||||
|
||||
bounds = selectedShapes.reduce((acc, shape, i) => {
|
||||
if (i === 0) {
|
||||
return getShapeUtils(shapeUtils, shape).getRotatedBounds(shape)
|
||||
|
@ -56,11 +48,9 @@ export function useSelection<T extends TLShape>(
|
|||
if (bounds) {
|
||||
const [minX, minY] = canvasToScreen([bounds.minX, bounds.minY], pageState.camera)
|
||||
const [maxX, maxY] = canvasToScreen([bounds.maxX, bounds.maxY], pageState.camera)
|
||||
|
||||
isLinked = !!Object.values(page.bindings).find(
|
||||
(binding) => selectedIds.includes(binding.toId) || selectedIds.includes(binding.fromId)
|
||||
)
|
||||
|
||||
rSelectionBounds.current = {
|
||||
minX,
|
||||
minY,
|
||||
|
|
|
@ -8,18 +8,16 @@ export function useShapeEvents(id: string) {
|
|||
return React.useMemo(
|
||||
() => ({
|
||||
onPointerDown: (e: React.PointerEvent) => {
|
||||
if ((e as any).dead) return
|
||||
else (e as any).dead = true
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
|
||||
if (e.button === 2) {
|
||||
callbacks.onRightPointShape?.(inputs.pointerDown(e, id), e)
|
||||
return
|
||||
}
|
||||
|
||||
if (e.button !== 0) return
|
||||
|
||||
const info = inputs.pointerDown(e, id)
|
||||
|
||||
e.stopPropagation()
|
||||
e.currentTarget?.setPointerCapture(e.pointerId)
|
||||
|
||||
// If we click "through" the selection bounding box to hit a shape that isn't selected,
|
||||
|
@ -35,7 +33,6 @@ export function useShapeEvents(id: string) {
|
|||
callbacks.onPointerDown?.(info, e)
|
||||
return
|
||||
}
|
||||
|
||||
callbacks.onPointShape?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
},
|
||||
|
|
|
@ -14,9 +14,7 @@ export function useZoomEvents<T extends HTMLElement>(zoom: number, ref: React.Re
|
|||
const { inputs, bounds, callbacks } = useTLContext()
|
||||
|
||||
React.useEffect(() => {
|
||||
const preventGesture = (event: TouchEvent) => {
|
||||
event.preventDefault()
|
||||
}
|
||||
const preventGesture = (event: TouchEvent) => event.preventDefault()
|
||||
|
||||
// @ts-ignore
|
||||
document.addEventListener('gesturestart', preventGesture)
|
||||
|
|
|
@ -417,18 +417,6 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||
return {}
|
||||
}, [settings.isDarkMode])
|
||||
|
||||
// When the context menu is blurred, close the menu by sending pointer events
|
||||
// to the context menu's ref. This is a hack around the fact that certain shapes
|
||||
// stop event propagation, which causes the menu to stay open even when blurred.
|
||||
const handleMenuBlur = React.useCallback<React.FocusEventHandler>((e) => {
|
||||
const elm = rWrapper.current
|
||||
if (!elm) return
|
||||
if (!elm.contains(e.relatedTarget)) return
|
||||
const event = new Event('mousedown', { bubbles: true }) as any
|
||||
elm.dispatchEvent(event)
|
||||
elm.dispatchEvent(event)
|
||||
}, [])
|
||||
|
||||
const isInSession = app.session !== undefined
|
||||
|
||||
// Hide bounds when not using the select tool, or when the only selected shape has handles
|
||||
|
@ -452,7 +440,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||
<StyledLayout ref={rWrapper} tabIndex={-0} className={settings.isDarkMode ? dark : ''}>
|
||||
<Loading />
|
||||
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
||||
<ContextMenu onBlur={handleMenuBlur}>
|
||||
<ContextMenu>
|
||||
<Renderer
|
||||
id={id}
|
||||
containerRef={rWrapper}
|
||||
|
@ -543,7 +531,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||
showSponsorLink={showSponsorLink}
|
||||
/>
|
||||
<StyledSpacer />
|
||||
{showTools && !readOnly && <ToolsPanel onBlur={handleMenuBlur} />}
|
||||
{showTools && !readOnly && <ToolsPanel />}
|
||||
</>
|
||||
)}
|
||||
</StyledUI>
|
||||
|
|
|
@ -37,11 +37,24 @@ const hasGroupSelectedSelector = (s: TDSnapshot) => {
|
|||
const preventDefault = (e: Event) => e.stopPropagation()
|
||||
|
||||
interface ContextMenuProps {
|
||||
onBlur: React.FocusEventHandler
|
||||
onBlur?: React.FocusEventHandler
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const ContextMenu = ({ onBlur, children }: ContextMenuProps): JSX.Element => {
|
||||
return (
|
||||
<RadixContextMenu.Root dir="ltr">
|
||||
<RadixContextMenu.Trigger dir="ltr">{children}</RadixContextMenu.Trigger>
|
||||
<InnerMenu onBlur={onBlur} />
|
||||
</RadixContextMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
interface InnerContextMenuProps {
|
||||
onBlur?: React.FocusEventHandler
|
||||
}
|
||||
|
||||
const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) {
|
||||
const app = useTldrawApp()
|
||||
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
||||
const isDebugMode = app.useStore(isDebugModeSelector)
|
||||
|
@ -122,101 +135,95 @@ export const ContextMenu = ({ onBlur, children }: ContextMenuProps): JSX.Element
|
|||
const hasThreeOrMore = numberOfSelectedIds > 2
|
||||
|
||||
return (
|
||||
<RadixContextMenu.Root dir="ltr">
|
||||
<RadixContextMenu.Trigger dir="ltr">{children}</RadixContextMenu.Trigger>
|
||||
<RadixContextMenu.Content
|
||||
dir="ltr"
|
||||
ref={rContent}
|
||||
onEscapeKeyDown={preventDefault}
|
||||
asChild
|
||||
tabIndex={-1}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
<MenuContent>
|
||||
{hasSelection ? (
|
||||
<>
|
||||
<CMRowButton onClick={handleDuplicate} kbd="#D">
|
||||
Duplicate
|
||||
<RadixContextMenu.Content
|
||||
dir="ltr"
|
||||
ref={rContent}
|
||||
onEscapeKeyDown={preventDefault}
|
||||
asChild
|
||||
tabIndex={-1}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
<MenuContent>
|
||||
{hasSelection ? (
|
||||
<>
|
||||
<CMRowButton onClick={handleDuplicate} kbd="#D">
|
||||
Duplicate
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleFlipHorizontal} kbd="⇧H">
|
||||
Flip Horizontal
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleFlipVertical} kbd="⇧V">
|
||||
Flip Vertical
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleLock} kbd="#⇧L">
|
||||
Lock / Unlock
|
||||
</CMRowButton>
|
||||
{(hasTwoOrMore || hasGroupSelected) && <Divider />}
|
||||
{hasTwoOrMore && (
|
||||
<CMRowButton onClick={handleGroup} kbd="#G">
|
||||
Group
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleFlipHorizontal} kbd="⇧H">
|
||||
Flip Horizontal
|
||||
)}
|
||||
{hasGroupSelected && (
|
||||
<CMRowButton onClick={handleGroup} kbd="#G">
|
||||
Ungroup
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleFlipVertical} kbd="⇧V">
|
||||
Flip Vertical
|
||||
)}
|
||||
<Divider />
|
||||
<ContextMenuSubMenu label="Move">
|
||||
<CMRowButton onClick={handleMoveToFront} kbd="⇧]">
|
||||
To Front
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleLock} kbd="#⇧L">
|
||||
Lock / Unlock
|
||||
<CMRowButton onClick={handleMoveForward} kbd="]">
|
||||
Forward
|
||||
</CMRowButton>
|
||||
{(hasTwoOrMore || hasGroupSelected) && <Divider />}
|
||||
{hasTwoOrMore && (
|
||||
<CMRowButton onClick={handleGroup} kbd="#G">
|
||||
Group
|
||||
</CMRowButton>
|
||||
)}
|
||||
{hasGroupSelected && (
|
||||
<CMRowButton onClick={handleGroup} kbd="#G">
|
||||
Ungroup
|
||||
</CMRowButton>
|
||||
)}
|
||||
<Divider />
|
||||
<ContextMenuSubMenu label="Move">
|
||||
<CMRowButton onClick={handleMoveToFront} kbd="⇧]">
|
||||
To Front
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleMoveForward} kbd="]">
|
||||
Forward
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleMoveBackward} kbd="[">
|
||||
Backward
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleMoveToBack} kbd="⇧[">
|
||||
To Back
|
||||
</CMRowButton>
|
||||
</ContextMenuSubMenu>
|
||||
<MoveToPageMenu />
|
||||
{hasTwoOrMore && (
|
||||
<AlignDistributeSubMenu
|
||||
hasTwoOrMore={hasTwoOrMore}
|
||||
hasThreeOrMore={hasThreeOrMore}
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
<CMRowButton onClick={handleCut} kbd="#X">
|
||||
Cut
|
||||
<CMRowButton onClick={handleMoveBackward} kbd="[">
|
||||
Backward
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleCopy} kbd="#C">
|
||||
Copy
|
||||
<CMRowButton onClick={handleMoveToBack} kbd="⇧[">
|
||||
To Back
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleCopySvg} kbd="#⇧C">
|
||||
Copy as SVG
|
||||
</CMRowButton>
|
||||
{isDebugMode && <CMRowButton onClick={handleCopyJson}>Copy as JSON</CMRowButton>}
|
||||
<CMRowButton onClick={handlePaste} kbd="#V">
|
||||
Paste
|
||||
</CMRowButton>
|
||||
<Divider />
|
||||
<CMRowButton onClick={handleDelete} kbd="⌫">
|
||||
Delete
|
||||
</CMRowButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CMRowButton onClick={handlePaste} kbd="#V">
|
||||
Paste
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleUndo} kbd="#Z">
|
||||
Undo
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleRedo} kbd="#⇧Z">
|
||||
Redo
|
||||
</CMRowButton>
|
||||
</>
|
||||
)}
|
||||
</MenuContent>
|
||||
</RadixContextMenu.Content>
|
||||
</RadixContextMenu.Root>
|
||||
</ContextMenuSubMenu>
|
||||
<MoveToPageMenu />
|
||||
{hasTwoOrMore && (
|
||||
<AlignDistributeSubMenu hasTwoOrMore={hasTwoOrMore} hasThreeOrMore={hasThreeOrMore} />
|
||||
)}
|
||||
<Divider />
|
||||
<CMRowButton onClick={handleCut} kbd="#X">
|
||||
Cut
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleCopy} kbd="#C">
|
||||
Copy
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleCopySvg} kbd="#⇧C">
|
||||
Copy as SVG
|
||||
</CMRowButton>
|
||||
{isDebugMode && <CMRowButton onClick={handleCopyJson}>Copy as JSON</CMRowButton>}
|
||||
<CMRowButton onClick={handlePaste} kbd="#V">
|
||||
Paste
|
||||
</CMRowButton>
|
||||
<Divider />
|
||||
<CMRowButton onClick={handleDelete} kbd="⌫">
|
||||
Delete
|
||||
</CMRowButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CMRowButton onClick={handlePaste} kbd="#V">
|
||||
Paste
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleUndo} kbd="#Z">
|
||||
Undo
|
||||
</CMRowButton>
|
||||
<CMRowButton onClick={handleRedo} kbd="#⇧Z">
|
||||
Redo
|
||||
</CMRowButton>
|
||||
</>
|
||||
)}
|
||||
</MenuContent>
|
||||
</RadixContextMenu.Content>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
/* ---------- Align and Distribute Sub Menu --------- */
|
||||
|
||||
|
|
|
@ -11,12 +11,11 @@ import { DeleteButton } from './DeleteButton'
|
|||
const isDebugModeSelector = (s: TDSnapshot) => s.settings.isDebugMode
|
||||
|
||||
interface ToolsPanelProps {
|
||||
onBlur: React.FocusEventHandler
|
||||
onBlur?: React.FocusEventHandler
|
||||
}
|
||||
|
||||
export const ToolsPanel = React.memo(function ToolsPanel({ onBlur }: ToolsPanelProps): JSX.Element {
|
||||
const app = useTldrawApp()
|
||||
|
||||
const isDebugMode = app.useStore(isDebugModeSelector)
|
||||
|
||||
return (
|
||||
|
|
Loading…
Reference in a new issue