/
DefaultContextMenu.tsx
90 lines (79 loc) · 2.86 KB
/
DefaultContextMenu.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import * as _ContextMenu from '@radix-ui/react-context-menu'
import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'
import { ReactNode, memo, useCallback } from 'react'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
import { DefaultContextMenuContent } from './DefaultContextMenuContent'
/** @public */
export interface TLUiContextMenuProps {
children?: ReactNode
}
/** @public */
export const DefaultContextMenu = memo(function DefaultContextMenu({
children,
}: TLUiContextMenuProps) {
const editor = useEditor()
const { Canvas } = useEditorComponents()
const cb = useCallback(
(isOpen: boolean) => {
if (!isOpen) {
const onlySelectedShape = editor.getOnlySelectedShape()
if (onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape)) {
editor.setSelectedShapes([])
}
} else {
// Weird route: selecting locked shapes on long press
if (editor.getInstanceState().isCoarsePointer) {
const selectedShapes = editor.getSelectedShapes()
const {
inputs: { currentPagePoint },
} = editor
// get all of the shapes under the current pointer
const shapesAtPoint = editor.getShapesAtPoint(currentPagePoint)
if (
// if there are no selected shapes
!editor.getSelectedShapes().length ||
// OR if none of the shapes at the point include the selected shape
!shapesAtPoint.some((s) => selectedShapes.includes(s))
) {
// then are there any locked shapes under the current pointer?
const lockedShapes = shapesAtPoint.filter((s) => editor.isShapeOrAncestorLocked(s))
if (lockedShapes.length) {
// nice, let's select them
editor.select(...lockedShapes.map((s) => s.id))
}
}
}
}
},
[editor]
)
const container = useContainer()
const [isOpen, handleOpenChange] = useMenuIsOpen('context menu', cb)
// Get the context menu content, either the default component or the user's
// override. If there's no menu content, then the user has set it to null,
// so skip rendering the menu.
const content = children ?? <DefaultContextMenuContent />
return (
<_ContextMenu.Root dir="ltr" onOpenChange={handleOpenChange} modal={false}>
<_ContextMenu.Trigger onContextMenu={undefined} dir="ltr">
{Canvas ? <Canvas /> : null}
</_ContextMenu.Trigger>
{isOpen && (
<_ContextMenu.Portal container={container}>
<_ContextMenu.Content
className="tlui-menu scrollable"
data-testid="context-menu"
alignOffset={-4}
collisionPadding={4}
onContextMenu={preventDefault}
>
<TldrawUiMenuContextProvider type="context-menu" sourceId="context-menu">
{content}
</TldrawUiMenuContextProvider>
</_ContextMenu.Content>
</_ContextMenu.Portal>
)}
</_ContextMenu.Root>
)
})