You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

useContextMenu.web.ts 2.3KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { useCallback, useRef, useState } from 'react';
  2. import { findAncestorByClass } from '../functions.web';
  3. type RaiseContext<T> = {
  4. /**
  5. * The entity for which the menu is context menu is raised.
  6. */
  7. entity?: T;
  8. /**
  9. * Target elements against which positioning calculations are made.
  10. */
  11. offsetTarget?: HTMLElement | null;
  12. };
  13. const initialState = Object.freeze({});
  14. const useContextMenu = <T>(): [(force?: boolean | Object) => void,
  15. (entity: T, target: HTMLElement | null) => void,
  16. (entity: T) => (e?: MouseEvent) => void,
  17. () => void,
  18. () => void,
  19. RaiseContext<T>] => {
  20. const [ raiseContext, setRaiseContext ] = useState < RaiseContext<T> >(initialState);
  21. const isMouseOverMenu = useRef(false);
  22. const lowerMenu = useCallback((force: boolean | Object = false) => {
  23. /**
  24. * We are tracking mouse movement over the active participant item and
  25. * the context menu. Due to the order of enter/leave events, we need to
  26. * defer checking if the mouse is over the context menu with
  27. * queueMicrotask.
  28. */
  29. window.queueMicrotask(() => {
  30. if (isMouseOverMenu.current && !(force === true)) {
  31. return;
  32. }
  33. if (raiseContext !== initialState || force) {
  34. setRaiseContext(initialState);
  35. }
  36. });
  37. }, [ raiseContext ]);
  38. const raiseMenu = useCallback((entity: T, target: HTMLElement | null) => {
  39. setRaiseContext({
  40. entity,
  41. offsetTarget: findAncestorByClass(target, 'list-item-container')
  42. });
  43. }, [ raiseContext ]);
  44. const toggleMenu = useCallback((entity: T) => (e?: MouseEvent) => {
  45. e?.stopPropagation();
  46. const { entity: raisedEntity } = raiseContext;
  47. if (raisedEntity && raisedEntity === entity) {
  48. lowerMenu();
  49. } else {
  50. raiseMenu(entity, e?.target as HTMLElement);
  51. }
  52. }, [ raiseContext ]);
  53. const menuEnter = useCallback(() => {
  54. isMouseOverMenu.current = true;
  55. }, []);
  56. const menuLeave = useCallback(() => {
  57. isMouseOverMenu.current = false;
  58. }, [ lowerMenu ]);
  59. return [ lowerMenu, raiseMenu, toggleMenu, menuEnter, menuLeave, raiseContext ];
  60. };
  61. export default useContextMenu;