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-07-17 14:32:59

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2019 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6           12 : 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           12 : export const ContextMenu = ({ parentId, children } : {
      20              :     parentId: string,
      21              :     children?: React.ReactNode,
      22           12 : }) => {
      23           12 :     const [visible, setVisible] = React.useState(false);
      24           12 :     const [event, setEvent] = React.useState<MouseEvent | null>(null);
      25           12 :     const root = React.useRef<HTMLDivElement>(null);
      26              : 
      27           12 :     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           12 :         const parent = document.getElementById(parentId)!;
      48           12 :         parent.addEventListener('contextmenu', _handleContextMenu);
      49           12 :         document.addEventListener('click', _handleClick);
      50              : 
      51            1 :         return () => {
      52            1 :             parent.removeEventListener('contextmenu', _handleContextMenu);
      53            1 :             document.removeEventListener('click', _handleClick);
      54            1 :         };
      55           12 :     }, [parentId]);
      56              : 
      57           12 :     React.useEffect(() => {
      58            2 :         if (!event || !root.current)
      59           12 :             return;
      60              : 
      61            2 :         const clickX = event.clientX;
      62            2 :         const clickY = event.clientY;
      63            2 :         const screenW = window.innerWidth;
      64            2 :         const screenH = window.innerHeight;
      65            2 :         const rootW = root.current.offsetWidth;
      66            2 :         const rootH = root.current.offsetHeight;
      67              : 
      68            2 :         const right = (screenW - clickX) > rootW;
      69            2 :         const left = !right;
      70            2 :         const top = (screenH - clickY) > rootH;
      71            2 :         const bottom = !top;
      72              : 
      73            2 :         if (right) {
      74            2 :             root.current.style.left = `${clickX + 5}px`;
      75            2 :         }
      76              : 
      77            2 :         if (left) {
      78            2 :             root.current.style.left = `${clickX - rootW - 5}px`;
      79            2 :         }
      80              : 
      81            2 :         if (top) {
      82            2 :             root.current.style.top = `${clickY + 5}px`;
      83            2 :         }
      84              : 
      85            2 :         if (bottom) {
      86            2 :             root.current.style.top = `${clickY - rootH - 5}px`;
      87            2 :         }
      88           12 :     }, [event]);
      89              : 
      90           12 :     return visible &&
      91            2 :         <Menu ref={root} className="contextMenu">
      92            2 :             <MenuContent ref={root}>
      93            2 :                 {children}
      94            2 :             </MenuContent>
      95            2 :         </Menu>;
      96           12 : };
        

Generated by: LCOV version 2.0-1