LCOV - code coverage report
Current view: top level - pkg/lib - cockpit-components-context-menu.tsx Coverage Total Hit
Test: cockpit Lines: 85.2 % 61 52
Test Date: 2026-06-17 06:28:00

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2019 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6            6 : import React from "react";
       7              : 
       8              : import { Menu, MenuContent } from "@patternfly/react-core/dist/esm/components/Menu";
       9              : 
      10              : import "context-menu.scss";
      11              : 
      12              : /*
      13              :  * A context menu component
      14              :  *
      15              :  * It has two properties:
      16              :  *  - parentId (required), area in which it listens to left button click
      17              :  *  - children (optional), a MenuList to be rendered in the context menu
      18              :  */
      19            6 : export const ContextMenu = ({ parentId, children } : {
      20              :     parentId: string,
      21              :     children?: React.ReactNode,
      22            6 : }) => {
      23            6 :     const [visible, setVisible] = React.useState(false);
      24            6 :     const [event, setEvent] = React.useState<MouseEvent | null>(null);
      25            6 :     const root = React.useRef<HTMLDivElement>(null);
      26              : 
      27            6 :     React.useEffect(() => {
      28            0 :         const _handleContextMenu = (event: MouseEvent) => {
      29              :             /* In Firefox they explicitly prevent us from interrupting when holding shift while
      30              :              * right-clicking for context. Lets make it default for all browsers so they can inspect et. al.
      31              :              */
      32            0 :             if (event.shiftKey) {
      33            0 :                 setVisible(false);
      34            0 :                 return;
      35            0 :             }
      36            0 :             event.preventDefault();
      37              : 
      38            0 :             setVisible(true);
      39            0 :             setEvent(event);
      40            0 :         };
      41              : 
      42            3 :         const _handleClick = (event: MouseEvent) => {
      43            3 :             if (event.button === 0)
      44            3 :                 setVisible(false);
      45            3 :         };
      46              : 
      47            6 :         const parent = document.getElementById(parentId)!;
      48            6 :         parent.addEventListener('contextmenu', _handleContextMenu);
      49            6 :         document.addEventListener('click', _handleClick);
      50              : 
      51            1 :         return () => {
      52            1 :             parent.removeEventListener('contextmenu', _handleContextMenu);
      53            1 :             document.removeEventListener('click', _handleClick);
      54            1 :         };
      55            6 :     }, [parentId]);
      56              : 
      57            6 :     React.useEffect(() => {
      58            1 :         if (!event || !root.current)
      59            6 :             return;
      60              : 
      61            1 :         const clickX = event.clientX;
      62            1 :         const clickY = event.clientY;
      63            1 :         const screenW = window.innerWidth;
      64            1 :         const screenH = window.innerHeight;
      65            1 :         const rootW = root.current.offsetWidth;
      66            1 :         const rootH = root.current.offsetHeight;
      67              : 
      68            1 :         const right = (screenW - clickX) > rootW;
      69            1 :         const left = !right;
      70            1 :         const top = (screenH - clickY) > rootH;
      71            1 :         const bottom = !top;
      72              : 
      73            1 :         if (right) {
      74            1 :             root.current.style.left = `${clickX + 5}px`;
      75            1 :         }
      76              : 
      77            1 :         if (left) {
      78            1 :             root.current.style.left = `${clickX - rootW - 5}px`;
      79            1 :         }
      80              : 
      81            1 :         if (top) {
      82            1 :             root.current.style.top = `${clickY + 5}px`;
      83            1 :         }
      84              : 
      85            1 :         if (bottom) {
      86            1 :             root.current.style.top = `${clickY - rootH - 5}px`;
      87            1 :         }
      88            6 :     }, [event]);
      89              : 
      90            6 :     return visible &&
      91            1 :         <Menu ref={root} className="contextMenu">
      92            1 :             <MenuContent ref={root}>
      93            1 :                 {children}
      94            1 :             </MenuContent>
      95            1 :         </Menu>;
      96            6 : };
        

Generated by: LCOV version 2.0-1