@alekstar79/context-menu 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +284 -0
- package/lib/components/ContextMenu.vue.d.ts +31 -0
- package/lib/context-menu-vue.d.ts +2 -0
- package/lib/context-menu-vue.js +74 -0
- package/lib/context-menu-vue.js.map +1 -0
- package/lib/context-menu.d.ts +2 -0
- package/lib/context-menu.js +709 -0
- package/lib/context-menu.js.map +1 -0
- package/lib/core/animate.d.ts +4 -0
- package/lib/core/easing.d.ts +5 -0
- package/lib/core/index.d.ts +6 -0
- package/lib/core/matrix.d.ts +62 -0
- package/lib/core/parse.d.ts +2 -0
- package/lib/core/svg.d.ts +54 -0
- package/lib/core/transform.d.ts +5 -0
- package/lib/index.d.ts +2 -0
- package/lib/menu/builder.d.ts +37 -0
- package/lib/menu/config.d.ts +39 -0
- package/lib/menu/manager.d.ts +9 -0
- package/lib/styles.css +1 -0
- package/lib/styles.d.ts +1 -0
- package/lib/utils/text-metrics.d.ts +17 -0
- package/lib/vue.d.ts +3 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alekstar79
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Context Menu TS (Vanilla/Vue3)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@alekstar79/context-menu)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/alekstar79/context-menu)
|
|
6
|
+
[](https://www.typescriptlang.org)
|
|
7
|
+
[](https://github.com/alekstar79/comparison-slider)
|
|
8
|
+
|
|
9
|
+
> A beautiful and customizable radial context menu written in TypeScript.
|
|
10
|
+
> It can be used both as a plain JavaScript/TypeScript module and as a Vue component.
|
|
11
|
+
> The project includes full TypeScript support (types are exported) and provides a simple API for controlling the menu (show/hide, event subscription).
|
|
12
|
+
> The library is lightweight, has no required dependencies (Vue 3 is optional), and can be easily integrated into existing projects.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
**[View Live Demo](https://alekstar79.github.io/context-menu)**
|
|
17
|
+
|
|
18
|
+
<!-- TOC -->
|
|
19
|
+
* [Context Menu TS (Vanilla/Vue3)](#context-menu-ts-vanillavue3)
|
|
20
|
+
* [✨ Features](#-features)
|
|
21
|
+
* [📦 Installation](#-installation)
|
|
22
|
+
* [🚀 Usage](#-usage)
|
|
23
|
+
* [⚙️ Configuration](#-configuration)
|
|
24
|
+
* [🧠 API](#-api)
|
|
25
|
+
* [Methods](#methods)
|
|
26
|
+
* [Events](#events)
|
|
27
|
+
* [Vue component CircularMenu](#vue-component-circularmenu)
|
|
28
|
+
* [💅 Styling](#-styling)
|
|
29
|
+
* [🛠️ Development](#-development)
|
|
30
|
+
* [Scripts](#scripts)
|
|
31
|
+
* [📄 License](#-license)
|
|
32
|
+
<!-- TOC -->
|
|
33
|
+
|
|
34
|
+
## ✨ Features
|
|
35
|
+
|
|
36
|
+
🎨 Customizable appearance (colors, radii, opacity)
|
|
37
|
+
🖼️ SVG icon support via sprites
|
|
38
|
+
💬 Hints with optional background
|
|
39
|
+
🖱️ Hover and wheel animations
|
|
40
|
+
🧩 Optional central button
|
|
41
|
+
📦 Two usage modes: vanilla JS and Vue 3 component
|
|
42
|
+
⚡ Full TypeScript support (types included)
|
|
43
|
+
|
|
44
|
+
## 📦 Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install context-menu
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The library has an optional peer dependency on vue. If you are using the Vue component, install Vue 3 as well:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install vue@^3
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 🚀 Usage
|
|
57
|
+
1. Vanilla JS / TypeScript
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { defineConfig, Manager } from 'context-menu-ts'
|
|
61
|
+
|
|
62
|
+
const config = defineConfig({
|
|
63
|
+
sprite: '/path/to/icons.svg', // path to SVG sprite
|
|
64
|
+
innerRadius: 50,
|
|
65
|
+
outerRadius: 150,
|
|
66
|
+
opacity: 0.7,
|
|
67
|
+
color: '#1976D2',
|
|
68
|
+
sectors: [
|
|
69
|
+
{ icon: 'new', hint: 'New' },
|
|
70
|
+
{ icon: 'open', hint: 'Open' },
|
|
71
|
+
{ icon: 'save', hint: 'Save' }
|
|
72
|
+
],
|
|
73
|
+
centralButton: {
|
|
74
|
+
icon: 'home',
|
|
75
|
+
hint: 'Home',
|
|
76
|
+
hintPosition: 'top'
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// container element where the menu will be placed
|
|
81
|
+
const container = document.getElementById('menu-container')
|
|
82
|
+
const menu = new Manager(container, config)
|
|
83
|
+
|
|
84
|
+
// subscribe to events
|
|
85
|
+
menu.on('click', (data) => {
|
|
86
|
+
console.log(`Selected: ${data.hint}`)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// show menu at cursor position (e.g., on contextmenu)
|
|
90
|
+
window.addEventListener('contextmenu', (e) => {
|
|
91
|
+
e.preventDefault()
|
|
92
|
+
menu.show(e)
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
2. Vue Component
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<template>
|
|
100
|
+
<CircularMenu
|
|
101
|
+
ref="menuRef"
|
|
102
|
+
:sprite="sprite"
|
|
103
|
+
:inner-radius="75"
|
|
104
|
+
:outer-radius="150"
|
|
105
|
+
:sectors="sectors"
|
|
106
|
+
:central-button="centralButton"
|
|
107
|
+
color="#42B883"
|
|
108
|
+
@click="onMenuItemClick"
|
|
109
|
+
/>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<script setup lang="ts">
|
|
113
|
+
import { ref } from 'vue'
|
|
114
|
+
import ContextMenu from 'context-menu/vue'
|
|
115
|
+
import type { ISector } from 'context-menu'
|
|
116
|
+
|
|
117
|
+
const sprite = '/icons.svg'
|
|
118
|
+
const sectors: ISector[] = [
|
|
119
|
+
{ icon: 'new', hint: 'New' },
|
|
120
|
+
{ icon: 'open', hint: 'Open' },
|
|
121
|
+
{ icon: 'save', hint: 'Save' }
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const centralButton = {
|
|
125
|
+
icon: 'home',
|
|
126
|
+
hint: 'Home',
|
|
127
|
+
hintPosition: 'bottom'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const menuRef = ref()
|
|
131
|
+
|
|
132
|
+
const onMenuItemClick = (data: { icon: string; hint: string }) => {
|
|
133
|
+
console.log('Selected:', data.hint)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Example of manually showing the menu
|
|
137
|
+
const showMenu = (e: PointerEvent) => {
|
|
138
|
+
menuRef.value?.show(e)
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## ⚙️ Configuration
|
|
144
|
+
`defineConfig(options: Partial<IConfig>): IConfig`
|
|
145
|
+
|
|
146
|
+
Creates a configuration object with default values.
|
|
147
|
+
|
|
148
|
+
`IConfig` interface
|
|
149
|
+
|
|
150
|
+
| Property | Type | Default | Description |
|
|
151
|
+
|---------------------|----------------|----------------|----------------------------------------------------------------------------------------------|
|
|
152
|
+
| sprite | string | `../icons.svg` | Path to the SVG sprite containing icons |
|
|
153
|
+
| innerRadius | number | – | Inner radius of sectors (the smaller ring radius) |
|
|
154
|
+
| outerRadius | number | – | Outer radius of sectors |
|
|
155
|
+
| opacity | number | `0.7` | Opacity of sectors and hint backgrounds (if any) |
|
|
156
|
+
| color | string | – | Main color for sectors and hint backgrounds (default '#1976D2' if omitted) |
|
|
157
|
+
| hintPadding | number | – | Padding around the hint text (pixels). If not set, the hint is rendered without a background |
|
|
158
|
+
| iconScale | number | – | Global scale factor for all icons (can be overridden per sector) |
|
|
159
|
+
| iconRadius | number | – | Global radius at which icons are placed (can be overridden per sector) |
|
|
160
|
+
| sectors | ISector[] | `[]` | Array of menu sectors |
|
|
161
|
+
| centralButton | ICentralButton | – | Configuration for the central button |
|
|
162
|
+
| autoBindContextMenu | boolean | `true` | Automatically bind the contextmenu event listener to window |
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
`ISector` interface
|
|
166
|
+
|
|
167
|
+
| Property | Type | Default | Description |
|
|
168
|
+
|-------------|------------|---------|-------------------------------------------|
|
|
169
|
+
| icon | string | – | Icon identifier (without #) |
|
|
170
|
+
| hint | string | – | Hint text |
|
|
171
|
+
| onclick | () => void | – | Click handler for the sector |
|
|
172
|
+
| rotate | number | `0` | Additional rotation of the icon (degrees) |
|
|
173
|
+
| iconScale | number | global | Icon scale for this specific sector |
|
|
174
|
+
| iconRadius | number | global | Icon placement radius for this sector |
|
|
175
|
+
| hintPadding | number | global | Hint padding for this sector |
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
`ICentralButton` interface
|
|
179
|
+
|
|
180
|
+
| Property | Type | Default | Description |
|
|
181
|
+
|----------------|------------------|---------|-----------------------------------------------------------------|
|
|
182
|
+
| icon | string | – | Icon identifier |
|
|
183
|
+
| hint | string | – | Hint text |
|
|
184
|
+
| onclick | () => void | – | Click handler |
|
|
185
|
+
| iconScale | number | global | Icon scale |
|
|
186
|
+
| iconRadius | number | global | Icon radius (effectively the button size) |
|
|
187
|
+
| hintPosition | `top \| bottom` | `top` | Position of the hint relative to the button |
|
|
188
|
+
| hintSpan | number | `180` | Angular span of the hint arc (degrees) |
|
|
189
|
+
| hintDistance | number | `8` | Distance from button edge to the hint text (when no background) |
|
|
190
|
+
| hintOffset | number | – | Absolute offset of the hint (overrides hintDistance) |
|
|
191
|
+
| hintPadding | number | global | Hint padding for the central button |
|
|
192
|
+
| hintStartAngle | number | – | Start angle of the arc (must be set together with hintEndAngle) |
|
|
193
|
+
| hintEndAngle | number | – | End angle of the arc |
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## 🧠 API
|
|
197
|
+
|
|
198
|
+
`Manager` class (vanilla)
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const menu = new Manager(container: HTMLElement, config: IConfig)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Methods
|
|
205
|
+
|
|
206
|
+
- `show(event: MouseEvent | PointerEvent)` – Shows the menu at the cursor position.
|
|
207
|
+
- `hide()` – Hides the menu.
|
|
208
|
+
- `on(event: string, callback: Function)` – Subscribes to events.
|
|
209
|
+
|
|
210
|
+
### Events
|
|
211
|
+
|
|
212
|
+
- `click` – Emitted when a sector or the central button is clicked. The callback receives `{ icon: string; hint: string }`.
|
|
213
|
+
|
|
214
|
+
### Vue component CircularMenu
|
|
215
|
+
|
|
216
|
+
**Props**
|
|
217
|
+
|
|
218
|
+
All properties from `IConfig` except `sectors` and `centralButton` (they are passed separately).
|
|
219
|
+
Additional props:
|
|
220
|
+
|
|
221
|
+
- `autoBindContextMenu` – Automatically bind contextmenu to window (default `true`).
|
|
222
|
+
|
|
223
|
+
**Events**
|
|
224
|
+
|
|
225
|
+
- `click` – Same as the vanilla `click` event.
|
|
226
|
+
|
|
227
|
+
**Exposed methods (via `ref`)**
|
|
228
|
+
|
|
229
|
+
- `show(event: PointerEvent)` – Shows the menu.
|
|
230
|
+
- `hide()` – Hides the menu.
|
|
231
|
+
|
|
232
|
+
## 💅 Styling
|
|
233
|
+
|
|
234
|
+
> The library comes with basic styles that are automatically included. You can override them using the following CSS classes:
|
|
235
|
+
|
|
236
|
+
- `.context` – Root menu container.
|
|
237
|
+
- `.radial-menu-svg` – The SVG element.
|
|
238
|
+
- `.radial-sector` – A sector.
|
|
239
|
+
- `.radial-icon` – An icon.
|
|
240
|
+
- `.radial-hint` – Hint text.
|
|
241
|
+
- `.radial-hint-bg` – Hint background.
|
|
242
|
+
- `.central-sector`, `.central-icon`, `.central-hint` – Central button elements.
|
|
243
|
+
- `.active` – Class added to hints on hover.
|
|
244
|
+
|
|
245
|
+
Example of customization:
|
|
246
|
+
|
|
247
|
+
```scss
|
|
248
|
+
.radial-hint {
|
|
249
|
+
font-size: 14px;
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
fill: #000;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## 🛠️ Development
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
git clone <repo>
|
|
259
|
+
cd context-menu
|
|
260
|
+
npm install
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Scripts
|
|
264
|
+
|
|
265
|
+
- `npm run dev` – Start dev server with demo.
|
|
266
|
+
- `npm run build` – Build the library.
|
|
267
|
+
- `npm run build:demo` – Build the demo app.
|
|
268
|
+
- `npm run test` – Run tests.
|
|
269
|
+
- `npm run test:ui` – Run tests with UI.
|
|
270
|
+
- `npm run test:coverage` – Run tests with coverage.
|
|
271
|
+
|
|
272
|
+
Project structure:
|
|
273
|
+
|
|
274
|
+
- `src/` – Source code.
|
|
275
|
+
- `src/menu/` – Core menu implementation.
|
|
276
|
+
- `src/components/` – Vue component.
|
|
277
|
+
- `src/core/` – Utilities and SVG wrapper.
|
|
278
|
+
- `public/` – Static assets for the demo.
|
|
279
|
+
|
|
280
|
+
## 📄 License
|
|
281
|
+
|
|
282
|
+
MIT © [alekstar79](https://github.com/alekstar79)
|
|
283
|
+
|
|
284
|
+
For more examples and a live demo, visit the repository. Feel free to open issues for questions or suggestions.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { IConfig, ISector } from '../menu/config';
|
|
2
|
+
interface Props {
|
|
3
|
+
sprite: string;
|
|
4
|
+
innerRadius: number;
|
|
5
|
+
outerRadius: number;
|
|
6
|
+
opacity?: number;
|
|
7
|
+
iconScale?: number;
|
|
8
|
+
iconRadius?: number;
|
|
9
|
+
sectors: ISector[];
|
|
10
|
+
color?: string;
|
|
11
|
+
hintPadding?: number;
|
|
12
|
+
centralButton?: IConfig['centralButton'];
|
|
13
|
+
autoBindContextMenu?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare const _default: import('vue').DefineComponent<Props, {
|
|
16
|
+
show: (event: PointerEvent) => void | undefined;
|
|
17
|
+
hide: () => void | undefined;
|
|
18
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
19
|
+
click: (data: {
|
|
20
|
+
icon: string;
|
|
21
|
+
hint: string;
|
|
22
|
+
}) => any;
|
|
23
|
+
}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
|
|
24
|
+
onClick?: ((data: {
|
|
25
|
+
icon: string;
|
|
26
|
+
hint: string;
|
|
27
|
+
}) => any) | undefined;
|
|
28
|
+
}>, {
|
|
29
|
+
autoBindContextMenu: boolean;
|
|
30
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
31
|
+
export default _default;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Manager as m } from "./context-menu.js";
|
|
2
|
+
import { defineConfig as y } from "./context-menu.js";
|
|
3
|
+
import { defineComponent as f, onMounted as h, onUnmounted as C, watch as R, getCurrentInstance as x } from "vue";
|
|
4
|
+
const B = /* @__PURE__ */ f({
|
|
5
|
+
__name: "ContextMenu",
|
|
6
|
+
props: {
|
|
7
|
+
sprite: {},
|
|
8
|
+
innerRadius: {},
|
|
9
|
+
outerRadius: {},
|
|
10
|
+
opacity: {},
|
|
11
|
+
iconScale: {},
|
|
12
|
+
iconRadius: {},
|
|
13
|
+
sectors: {},
|
|
14
|
+
color: {},
|
|
15
|
+
hintPadding: {},
|
|
16
|
+
centralButton: {},
|
|
17
|
+
autoBindContextMenu: { type: Boolean, default: !0 }
|
|
18
|
+
},
|
|
19
|
+
emits: ["click"],
|
|
20
|
+
setup(s, { expose: d, emit: l }) {
|
|
21
|
+
const e = s, p = l;
|
|
22
|
+
let i = null, t = null;
|
|
23
|
+
function c() {
|
|
24
|
+
const n = {
|
|
25
|
+
autoBindContextMenu: e.autoBindContextMenu,
|
|
26
|
+
sprite: e.sprite,
|
|
27
|
+
innerRadius: e.innerRadius,
|
|
28
|
+
outerRadius: e.outerRadius,
|
|
29
|
+
opacity: e.opacity ?? 0.7,
|
|
30
|
+
iconScale: e.iconScale,
|
|
31
|
+
iconRadius: e.iconRadius,
|
|
32
|
+
sectors: e.sectors,
|
|
33
|
+
centralButton: e.centralButton,
|
|
34
|
+
color: e.color,
|
|
35
|
+
hintPadding: e.hintPadding
|
|
36
|
+
}, o = document.createElement("div");
|
|
37
|
+
return i = new m(o, n), i.on("click", (r) => p("click", r)), t = o.firstChild, o.removeChild(t), t;
|
|
38
|
+
}
|
|
39
|
+
function a(n) {
|
|
40
|
+
const o = x(), r = o?.vnode.el, u = r?.parentNode;
|
|
41
|
+
u && r && n && (u.replaceChild(n, r), o.vnode.el = n);
|
|
42
|
+
}
|
|
43
|
+
return h(() => {
|
|
44
|
+
a(c());
|
|
45
|
+
}), C(() => {
|
|
46
|
+
t && t.parentNode && t.parentNode.removeChild(t), i = null;
|
|
47
|
+
}), R(() => [
|
|
48
|
+
e.sprite,
|
|
49
|
+
e.innerRadius,
|
|
50
|
+
e.outerRadius,
|
|
51
|
+
e.opacity,
|
|
52
|
+
e.iconScale,
|
|
53
|
+
e.iconRadius,
|
|
54
|
+
e.sectors,
|
|
55
|
+
e.centralButton,
|
|
56
|
+
e.color,
|
|
57
|
+
e.hintPadding,
|
|
58
|
+
e.autoBindContextMenu
|
|
59
|
+
], () => {
|
|
60
|
+
if (!t) return;
|
|
61
|
+
const n = c();
|
|
62
|
+
a(n), t = n;
|
|
63
|
+
}, { deep: !0 }), d({
|
|
64
|
+
show: (n) => i?.show(n),
|
|
65
|
+
hide: () => i?.hide()
|
|
66
|
+
}), (n, o) => null;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
export {
|
|
70
|
+
B as ContextMenu,
|
|
71
|
+
m as Manager,
|
|
72
|
+
y as defineConfig
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=context-menu-vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-menu-vue.js","sources":["../src/components/ContextMenu.vue"],"sourcesContent":["<template></template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, getCurrentInstance } from 'vue'\nimport type { IConfig, ISector } from '@/menu/config'\nimport { Manager } from '@/menu/manager'\n\ninterface Props {\n sprite: string;\n innerRadius: number;\n outerRadius: number;\n opacity?: number;\n iconScale?: number;\n iconRadius?: number;\n sectors: ISector[];\n color?: string;\n hintPadding?: number;\n centralButton?: IConfig['centralButton'];\n autoBindContextMenu?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n autoBindContextMenu: true\n})\n\nconst emit = defineEmits<{\n (e: 'click', data: { icon: string; hint: string }): void;\n}>();\n\nlet menuManager: Manager | null = null\nlet menuElement: HTMLElement | null = null\n\nfunction createMenu() {\n const config: IConfig = {\n autoBindContextMenu: props.autoBindContextMenu,\n sprite: props.sprite,\n innerRadius: props.innerRadius,\n outerRadius: props.outerRadius,\n opacity: props.opacity ?? 0.7,\n iconScale: props.iconScale,\n iconRadius: props.iconRadius,\n sectors: props.sectors,\n centralButton: props.centralButton,\n color: props.color,\n hintPadding: props.hintPadding,\n }\n\n const tempContainer = document.createElement('div')\n\n menuManager = new Manager(tempContainer, config)\n menuManager.on('click', (data: any) => emit('click', data))\n menuElement = tempContainer.firstChild as HTMLElement\n tempContainer.removeChild(menuElement)\n\n return menuElement\n}\n\nfunction replaceRoot(newElement: HTMLElement) {\n const instance = getCurrentInstance()\n const oldEl = instance?.vnode.el\n const parent = oldEl?.parentNode\n\n if (parent && oldEl && newElement) {\n parent.replaceChild(newElement, oldEl)\n instance.vnode.el = newElement\n }\n}\n\nonMounted(() => {\n replaceRoot(createMenu())\n})\n\nonUnmounted(() => {\n if (menuElement && menuElement.parentNode) {\n menuElement.parentNode.removeChild(menuElement)\n }\n\n menuManager = null\n})\n\nwatch(() => [\n props.sprite,\n props.innerRadius,\n props.outerRadius,\n props.opacity,\n props.iconScale,\n props.iconRadius,\n props.sectors,\n props.centralButton,\n props.color,\n props.hintPadding,\n props.autoBindContextMenu,\n], () => {\n if (!menuElement) return\n\n const newEl = createMenu()\n replaceRoot(newEl)\n menuElement = newEl\n}, { deep: true })\n\ndefineExpose({\n show: (event: PointerEvent) => menuManager?.show(event),\n hide: () => menuManager?.hide()\n})\n</script>\n"],"names":["props","__props","emit","__emit","menuManager","menuElement","createMenu","config","tempContainer","Manager","data","replaceRoot","newElement","instance","getCurrentInstance","oldEl","parent","onMounted","onUnmounted","watch","newEl","__expose","event"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,UAAMA,IAAQC,GAIRC,IAAOC;AAIb,QAAIC,IAA8B,MAC9BC,IAAkC;AAEtC,aAASC,IAAa;AACpB,YAAMC,IAAkB;AAAA,QACtB,qBAAqBP,EAAM;AAAA,QAC3B,QAAQA,EAAM;AAAA,QACd,aAAaA,EAAM;AAAA,QACnB,aAAaA,EAAM;AAAA,QACnB,SAASA,EAAM,WAAW;AAAA,QAC1B,WAAWA,EAAM;AAAA,QACjB,YAAYA,EAAM;AAAA,QAClB,SAASA,EAAM;AAAA,QACf,eAAeA,EAAM;AAAA,QACrB,OAAOA,EAAM;AAAA,QACb,aAAaA,EAAM;AAAA,MAAA,GAGfQ,IAAgB,SAAS,cAAc,KAAK;AAElD,aAAAJ,IAAc,IAAIK,EAAQD,GAAeD,CAAM,GAC/CH,EAAY,GAAG,SAAS,CAACM,MAAcR,EAAK,SAASQ,CAAI,CAAC,GAC1DL,IAAcG,EAAc,YAC5BA,EAAc,YAAYH,CAAW,GAE9BA;AAAA,IACT;AAEA,aAASM,EAAYC,GAAyB;AAC5C,YAAMC,IAAWC,EAAA,GACXC,IAAQF,GAAU,MAAM,IACxBG,IAASD,GAAO;AAEtB,MAAIC,KAAUD,KAASH,MACrBI,EAAO,aAAaJ,GAAYG,CAAK,GACrCF,EAAS,MAAM,KAAKD;AAAA,IAExB;AAEA,WAAAK,EAAU,MAAM;AACd,MAAAN,EAAYL,GAAY;AAAA,IAC1B,CAAC,GAEDY,EAAY,MAAM;AAChB,MAAIb,KAAeA,EAAY,cAC7BA,EAAY,WAAW,YAAYA,CAAW,GAGhDD,IAAc;AAAA,IAChB,CAAC,GAEDe,EAAM,MAAM;AAAA,MACVnB,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,IAAA,GACL,MAAM;AACP,UAAI,CAACK,EAAa;AAElB,YAAMe,IAAQd,EAAA;AACd,MAAAK,EAAYS,CAAK,GACjBf,IAAce;AAAA,IAChB,GAAG,EAAE,MAAM,IAAM,GAEjBC,EAAa;AAAA,MACX,MAAM,CAACC,MAAwBlB,GAAa,KAAKkB,CAAK;AAAA,MACtD,MAAM,MAAMlB,GAAa,KAAA;AAAA,IAAK,CAC/B;;;"}
|