Fix context menu, fix blur issue

This commit is contained in:
Steve Ruiz 2022-01-06 14:00:23 +00:00
parent 31a1a8b5ae
commit b1697b2ca7
12 changed files with 244 additions and 351 deletions

View file

@ -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])
}

View file

@ -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])
}

View file

@ -90,10 +90,7 @@ export function useCursorAnimation(ref: any, point: number[]) {
break
}
}
return () => {
clearTimeout(rTimeoutId.current)
}
return () => clearTimeout(rTimeoutId.current)
}, [point, spline])
}

View file

@ -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])
}

View file

@ -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)

View file

@ -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])

View file

@ -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,

View file

@ -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)
},

View file

@ -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)

View file

@ -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>

View file

@ -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 --------- */

View file

@ -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 (