import classNames from "classnames" ; import * as React from "react" ; import * as ReactDOM from "react-dom" ; import { polyfill } from "react-lifecycles-compat" ; import { AbstractPureComponent2 , Classes , Position } from "../../common" ; import { IOverlayLifecycleProps } from "../overlay/overlay" ; import { Popover } from "../popover/popover" ; import { PopperModifiers } from "../popover/popoverSharedProps" ; export interface IOffset { left : number ; top : number ; } interface IContextMenuState { isOpen : boolean ; isDarkTheme : boolean ; menu ? : JSX . Element ; offset ? : IOffset ; onClose ? : ( ) => void ; } const POPPER_MODIFIERS = { preventOverflow : { boundariesElement : "viewport" } , } ; const TRANSITION_DURATION = 100 ; type IContextMenuProps = IOverlayLifecycleProps ; deprecated @ polyfill class ContextMenu extends AbstractPureComponent2 < IContextMenuProps , IContextMenuState > { public state : IContextMenuState = { isDarkTheme : false , isOpen : false , } ; public render ( ) { const content = < div onContextMenu = { this . cancelContextMenu } > { this . state . menu } < /div>; const popoverClassName = classNames ( { [ Classes . DARK ] : this . state . isDarkTheme } ) ; const key = this . state . offset === undefined ? "" : `${ this . state . offset . left }x${ this . state . offset . top }` ; return ( < div className = { Classes . CONTEXT_MENU_POPOVER_TARGET } style = { this . state . offset } > < Popover { ... this . props } backdropProps = { { onContextMenu : this . handleBackdropContextMenu } } content = { content } enforceFocus = { false } key = { key } hasBackdrop = { true } isOpen = { this . state . isOpen } minimal = { true } modifiers = { POPPER_MODIFIERS } onInteraction = { this . handlePopoverInteraction } position = { Position . RIGHT_TOP } popoverClassName = { popoverClassName } target = { < div / > } transitionDuration = { TRANSITION_DURATION } / > < /div>); } public show ( menu , offset , onClose ? , isDarkTheme = false ) { this . setState ( { isOpen : true , menu , offset , onClose , isDarkTheme } ) ; } public hide ( ) { this . state . onClose ?. ( ) ; this . setState ( { isOpen : false , onClose : undefined } ) ; } private cancelContextMenu = ( e ) => e . preventDefault ( ) ; private handleBackdropContextMenu = ( e ) => { e . persist ( ) ; e . preventDefault ( ) ; this . setTimeout ( ( ) => { const newTarget = document . elementFromPoint ( e . clientX , e . clientY ) ; const { view , ... newEventInit } = e ; newTarget ?. dispatchEvent ( new MouseEvent ( "contextmenu" , newEventInit ) ) ; } , TRANSITION_DURATION ) ; } ; private handlePopoverInteraction = ( nextOpenState ) => { if ( ! nextOpenState ) { this . requestAnimationFrame ( ( ) => this . hide ( ) ) ; } } ; } let contextMenuElement ; let contextMenu ; /**
 * Show the given menu element at the given offset from the top-left corner of the viewport.
 * The menu will appear below-right of this point and will flip to below-left if there is not enough
 * room onscreen. The optional callback will be invoked when this menu closes.
 */ export function show ( menu , offset , onClose ? , isDarkTheme ? ) { if ( contextMenuElement === undefined ) { contextMenuElement = document . createElement ( "div" ) ; contextMenuElement . classList . add ( Classes . CONTEXT_MENU ) ; document . body . appendChild ( contextMenuElement ) ; contextMenu = ReactDOM . render < IContextMenuProps > ( < ContextMenu onClosed = { remove } / > , contextMenuElement ) as ContextMenu ; } contextMenu ! . show ( menu , offset , onClose , isDarkTheme ) ; } /** Hide the open context menu. */ export function hide ( ) { contextMenu ?. hide ( ) ; } /** Return whether a context menu is currently open. */ export function isOpen ( ) { return contextMenu != null && contextMenu . state . isOpen ; } function remove ( ) { if ( contextMenuElement != null ) { ReactDOM . unmountComponentAtNode ( contextMenuElement ) ; contextMenuElement . remove ( ) ; contextMenuElement = undefined ; contextMenu = undefined ; } }