0

in our chat component, many people don't know they can find the emojis menu by right clicking on the text area and choosing Emojis from the menu. I'd like to Put a button in the UI that would open the emoji menu directly. This way people who don't know about right click and the Context Menu can just click the emoji button and get the built in emojis.

I tried adding a button next to the text input (so not a child of the TextInput) and sending the contextmenu like this:

const showCtxMenu = () => {
    var element = document.getElementById("chatti");
    var ev2 = new MouseEvent("contextmenu", {
      bubbles: true,
      cancelable: false,
      view: window,
      button: 2,
      buttons: 0,
      clientX: element.getBoundingClientRect().x,
      clientY: element.getBoundingClientRect().y
    });
    element.dispatchEvent(ev2);
  }

and the TextInput object is like this in the same functional component:

return (
    <View
      style={[
        style.inputView,
        {borderColor: primaryColor, height: 40}
      ]}>
      <div id="chatti">
      <TextInput
        value={message}
        // onContentSizeChange={(event) => {
        // causes infinite react state update on ctrl+A -> delete
        // setHeight(event.nativeEvent.contentSize.height);
        // }}
        onChangeText={(text) => onChangeMessage(text)}
        style={{

When the button clicks, I'm sending the event but to the parent div, so nothing happens. I don't know how to send the event directly to the TextInput because I don't know how to find it.


I have set a code example in React how you can invoke the contextmenu event from a button click. You will need DOM reference to both TextInput and the context menu container where you have the context menu.

Working sample: https://stackblitz.com/edit/react-727dpd?file=src/App.js

In my example context menu shows up when you right-click the text area or the button. The point you need to focus on is how I have added the ref to textarea.

import React, { useRef, useEffect } from 'react';
import './style.css';

export default function App() {
  const contextMenuRef = useRef();
  const chatBoxRef = useRef();

  useEffect(() => {
    const contextMenu = contextMenuRef.current;
    const scope = chatBoxRef.current;

    const normalizePozition = (mouseX, mouseY) => {
      // ? compute what is the mouse position relative to the container element (scope)
      let { left: scopeOffsetX, top: scopeOffsetY } =
        scope.getBoundingClientRect();

      scopeOffsetX = scopeOffsetX < 0 ? 0 : scopeOffsetX;
      scopeOffsetY = scopeOffsetY < 0 ? 0 : scopeOffsetY;

      const scopeX = mouseX - scopeOffsetX;
      const scopeY = mouseY - scopeOffsetY;

      // ? check if the element will go out of bounds
      const outOfBoundsOnX =
        scopeX + contextMenu.clientWidth > scope.clientWidth;

      const outOfBoundsOnY =
        scopeY + contextMenu.clientHeight > scope.clientHeight;

      let normalizedX = mouseX;
      let normalizedY = mouseY;

      // ? normalize on X
      if (outOfBoundsOnX) {
        normalizedX =
          scopeOffsetX + scope.clientWidth - contextMenu.clientWidth;
      }

      // ? normalize on Y
      if (outOfBoundsOnY) {
        normalizedY =
          scopeOffsetY + scope.clientHeight - contextMenu.clientHeight;
      }

      return { normalizedX, normalizedY };
    };

    scope.addEventListener('contextmenu', (event) => {
      console.log(event);
      event.preventDefault();

      const { clientX: mouseX, clientY: mouseY } = event;

      const { normalizedX, normalizedY } = normalizePozition(mouseX, mouseY);

      contextMenu.classList.remove('visible');

      contextMenu.style.top = `${normalizedY}px`;
      contextMenu.style.left = `${normalizedX}px`;

      setTimeout(() => {
        contextMenu.classList.add('visible');
      });
    });

    scope.addEventListener('click', (e) => {
      // ? close the menu if the user clicks outside of it
      if (e.target.offsetParent != contextMenu) {
        contextMenu.classList.remove('visible');
      }
    });
  }, []);

  const showCtxMenu = (e) => {
    //e.preventDefault();
    var element = chatBoxRef.current;
    var ev2 = new MouseEvent('contextmenu', {
      bubbles: true,
      cancelable: false,
      view: window,
      button: 2,
      buttons: 0,
      clientX: element.getBoundingClientRect().x,
      clientY: element.getBoundingClientRect().y,
    });
    element.dispatchEvent(ev2);
  };

  return (
    <>
      <div style={{ height: '300px' }}></div>
      <textarea ref={chatBoxRef} col={10}></textarea>
      <div ref={contextMenuRef} id="context-menu">
        <div class="item">Option 1</div>
        <div class="item">Option 2</div>
        <div class="item">Option 3</div>
        <div class="item">Option 4</div>
        <div class="item">Option 5</div>
      </div>
      <button onClick={showCtxMenu}>Press Context</button>
    </>
  );
}
4

1 回答 1

1

I have set a code example in React how you can invoke the contextmenu event from a button click. You will need DOM reference to both TextInput and the context menu container where you have the context menu.

Working sample: https://stackblitz.com/edit/react-727dpd?file=src/App.js

In my example context menu shows up when you right-click the text area or the button. The point you need to focus on is how I have added the ref to textarea.

import React, { useRef, useEffect } from 'react';
import './style.css';

export default function App() {
  const contextMenuRef = useRef();
  const chatBoxRef = useRef();

  useEffect(() => {
    const contextMenu = contextMenuRef.current;
    const scope = chatBoxRef.current;

    const normalizePozition = (mouseX, mouseY) => {
      // ? compute what is the mouse position relative to the container element (scope)
      let { left: scopeOffsetX, top: scopeOffsetY } =
        scope.getBoundingClientRect();

      scopeOffsetX = scopeOffsetX < 0 ? 0 : scopeOffsetX;
      scopeOffsetY = scopeOffsetY < 0 ? 0 : scopeOffsetY;

      const scopeX = mouseX - scopeOffsetX;
      const scopeY = mouseY - scopeOffsetY;

      // ? check if the element will go out of bounds
      const outOfBoundsOnX =
        scopeX + contextMenu.clientWidth > scope.clientWidth;

      const outOfBoundsOnY =
        scopeY + contextMenu.clientHeight > scope.clientHeight;

      let normalizedX = mouseX;
      let normalizedY = mouseY;

      // ? normalize on X
      if (outOfBoundsOnX) {
        normalizedX =
          scopeOffsetX + scope.clientWidth - contextMenu.clientWidth;
      }

      // ? normalize on Y
      if (outOfBoundsOnY) {
        normalizedY =
          scopeOffsetY + scope.clientHeight - contextMenu.clientHeight;
      }

      return { normalizedX, normalizedY };
    };

    scope.addEventListener('contextmenu', (event) => {
      console.log(event);
      event.preventDefault();

      const { clientX: mouseX, clientY: mouseY } = event;

      const { normalizedX, normalizedY } = normalizePozition(mouseX, mouseY);

      contextMenu.classList.remove('visible');

      contextMenu.style.top = `${normalizedY}px`;
      contextMenu.style.left = `${normalizedX}px`;

      setTimeout(() => {
        contextMenu.classList.add('visible');
      });
    });

    scope.addEventListener('click', (e) => {
      // ? close the menu if the user clicks outside of it
      if (e.target.offsetParent != contextMenu) {
        contextMenu.classList.remove('visible');
      }
    });
  }, []);

  const showCtxMenu = (e) => {
    //e.preventDefault();
    var element = chatBoxRef.current;
    var ev2 = new MouseEvent('contextmenu', {
      bubbles: true,
      cancelable: false,
      view: window,
      button: 2,
      buttons: 0,
      clientX: element.getBoundingClientRect().x,
      clientY: element.getBoundingClientRect().y,
    });
    element.dispatchEvent(ev2);
  };

  return (
    <>
      <div style={{ height: '300px' }}></div>
      <textarea ref={chatBoxRef} col={10}></textarea>
      <div ref={contextMenuRef} id="context-menu">
        <div class="item">Option 1</div>
        <div class="item">Option 2</div>
        <div class="item">Option 3</div>
        <div class="item">Option 4</div>
        <div class="item">Option 5</div>
      </div>
      <button onClick={showCtxMenu}>Press Context</button>
    </>
  );
}
于 2021-09-18T02:11:25.200 回答