forked from Acode-Foundation/Acode
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathindex.js
More file actions
144 lines (126 loc) · 3.44 KB
/
index.js
File metadata and controls
144 lines (126 loc) · 3.44 KB
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
import "./style.scss";
import actionStack from "lib/actionStack";
/**
* @typedef {object} ContextMenuObj
* @extends HTMLElement
* @property {function():void} hide hides the menu
* @property {function():void} show shows the page
* @property {function():void} destroy destroys the menu
*/
/**
* @typedef {object} ContextMenuOptions
* @property {number} left
* @property {number} top
* @property {number} bottom
* @property {number} right
* @property {string} transformOrigin
* @property {HTMLElement} toggler
* @property {function} onshow
* @property {function} onhide
* @property {Array<[string, string]>} items Array of [text, action] pairs
* @property {(this:HTMLElement, event:MouseEvent)=>void} onclick Called when an item is clicked
* @property {(item:string) => void} onselect Called when an item is selected
* @property {(this:HTMLElement) => string} innerHTML Called when the menu is shown
*/
/**
* Create a context menu
* @param {string|ContextMenuOptions} content Context menu content or options
* @param {ContextMenuOptions} [options] Options
* @returns {ContextMenuObj}
*/
export default function Contextmenu(content, options) {
if (!options && typeof content === "object") {
options = content;
content = null;
} else if (!options) {
options = {};
}
const $el = tag("ul", {
className: "context-menu scroll",
innerHTML: content || "",
onclick(e) {
if (options.onclick) options.onclick.call(this, e);
if (options.onselect) {
const $target = e.target;
const { action } = $target.dataset;
if (!action) return;
hide();
options.onselect.call(this, action);
}
},
style: {
top: options.top || "auto",
left: options.left || "auto",
right: options.right || "auto",
bottom: options.bottom || "auto",
transformOrigin: options.transformOrigin,
},
});
const $mask = tag("span", {
className: "mask",
ontouchstart: hide,
onmousedown: hide,
});
if (Array.isArray(options.items)) {
for (const [text, action] of options.items) {
$el.append(<li data-action={action}>{text}</li>);
}
}
if (!options.innerHTML) addTabindex();
function show() {
actionStack.push({
id: "main-menu",
action: hide,
});
$el.onshow();
$el.classList.remove("hide");
if (options.innerHTML) {
$el.innerHTML = options.innerHTML.call($el);
addTabindex();
}
if (options.toggler) {
const client = options.toggler.getBoundingClientRect();
if (!options.top && !options.bottom) {
$el.style.top = `${client.top}px`;
}
if (!options.left && !options.right) {
$el.style.right = `${innerWidth - client.right}px`;
}
}
app.append($el, $mask);
const $firstChild = $el.firstChild;
if ($firstChild?.focus) $firstChild.focus();
}
function hide() {
actionStack.remove("main-menu");
$el.onhide();
$el.classList.add("hide");
setTimeout(() => {
$mask.remove();
$el.remove();
}, 100);
}
function toggle() {
if ($el.parentElement) return hide();
show();
}
function addTabindex() {
/**@type {Array<HTMLLIElement>} */
const children = [...$el.children];
for (const $el of children) $el.tabIndex = "0";
}
function destroy() {
$el.remove();
$mask.remove();
options.toggler?.removeEventListener("click", toggle);
}
if (options.toggler) {
options.toggler.addEventListener("click", toggle);
}
$el.hide = hide;
$el.show = show;
$el.destroy = destroy;
$el.onshow = options.onshow || (() => {});
$el.onhide = options.onhide || (() => {});
return $el;
}