zs
2025-05-05 25750ab29ce4e243f4e4e17a796167f3a0f0697b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import { defineComponent, toRefs } from "vue";
import type { VNode } from "vue";
import type { MenuOptions } from "./ContextMenuDefine";
import { MenuConstOptions } from "./ContextMenuDefine";
 
/**
 * Get absolute y position of HTMLElement
 * @param e Element
 * @param stopNode Specify the node for recursive termination, default to body
 * @returns
 */
export function getTop(e: HTMLElement, stopNode?: HTMLElement): number {
  let offset = e.offsetTop;
  if (e.offsetParent != null && e.offsetParent != stopNode) {
    offset -= e.offsetParent.scrollTop;
    offset += getTop(e.offsetParent as HTMLElement, stopNode);
  }
  return offset;
}
/**
 * Get absolute x position of HTMLElement
 * @param e Element
 * @param stopNode Specify the node for recursive termination, default to body
 * @returns
 */
export function getLeft(e: HTMLElement, stopNode?: HTMLElement): number {
  let offset = e.offsetLeft;
  if (e.offsetParent != null && e.offsetParent != stopNode) {
    offset -= e.offsetParent.scrollLeft;
    offset += getLeft(e.offsetParent as HTMLElement, stopNode);
  }
  return offset;
}
 
/**
 * If your `body` element is in a scaled state (e.g. `transform: scale(0.5)`), 
 * this may lead to the wrong position of the menu display. 
 * You can use this function to transform the menu display position:
 * 
 * ```ts
 * 
  import ContextMenu from '@imengyu/vue3-context-menu'
 
  function onContextMenu(e: MouseEvent) {
    const scaledPosition = ContextMenu.transformMenuPosition(e.target as HTMLElement, e.offsetX, e.offsetY);
    //Full code of menuData is in `/examples/views/InScaledBody.vue`
    menuData.x = scaledPosition.x;
    menuData.y = scaledPosition.y;
    //show menu
    ContextMenu.showContextMenu(menuData);
  }
  * ```
  * @param e Current click element
  * @param offsetX MouseEvent.offsetX
  * @param offsetY MouseEvent.offsetY
  */
export function transformMenuPosition(
  e: HTMLElement,
  offsetX: number,
  offsetY: number,
  container?: HTMLElement
): {
  x: number;
  y: number;
} {
  return {
    x: getLeft(e, container) + offsetX,
    y: getTop(e, container) + offsetY,
  };
}
 
const DEFAULT_CONTAINER_ID = "mx-menu-default-container";
const GEN_CONTAINER_ID = "mx-menu-container-";
let containerId = 0;
 
export function removeContainer(container: HTMLElement): void {
  container.parentNode?.removeChild(container);
}
export function genContainer(options: MenuOptions): {
  eleId: string;
  container: HTMLElement;
  isNew: boolean;
} {
  const { getContainer, zIndex } = options;
 
  if (getContainer) {
    const container =
      typeof getContainer === "function" ? getContainer() : getContainer;
    if (container) {
      let eleId = container.getAttribute("id");
      if (!eleId) {
        eleId = GEN_CONTAINER_ID + containerId++;
        container.setAttribute("id", eleId);
      }
      return {
        eleId,
        container,
        isNew: false,
      };
    }
  }
 
  let container = document.getElementById(DEFAULT_CONTAINER_ID);
  if (!container) {
    container = document.createElement("div");
    container.setAttribute("id", DEFAULT_CONTAINER_ID);
    container.setAttribute(
      "class",
      "mx-menu-ghost-host information_full_screen"
    );
    document.body.appendChild(container);
  }
  container.style.zIndex =
    zIndex?.toString() || MenuConstOptions.defaultZindex.toString();
  return {
    eleId: DEFAULT_CONTAINER_ID,
    container,
    isNew: true,
  };
}
 
/**
 * Number to px string
 * @param value
 * @returns
 */
export function solveNumberOrStringSize(value: string | number): string {
  return typeof value === "number" ? `${value}px` : value;
}
 
/**
 * Render a VNode
 */
export const VNodeRender = defineComponent({
  props: {
    /**
     * Can be VNode or (data: unknown) => VNode
     */
    vnode: {
      type: null,
    },
    /**
     * If vnode is a callback, this data will be passed to the callback first parameter.
     * @default null
     */
    data: {
      type: null,
      default: null,
    },
  },
  setup(props) {
    const { vnode, data } = toRefs(props);
    return () =>
      typeof vnode.value === "function"
        ? (vnode.value as unknown as (data: unknown) => VNode)(data.value)
        : (vnode.value as unknown as VNode);
  },
});