@lemonadejs/contextmenu 5.2.1 → 5.2.4
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/dist/index.js +138 -75
- package/dist/style.css +11 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -12,6 +12,36 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
12
12
|
global.Contextmenu = factory();
|
|
13
13
|
}(this, (function () {
|
|
14
14
|
|
|
15
|
+
class CustomEvents extends Event {
|
|
16
|
+
constructor(type, props, options) {
|
|
17
|
+
super(type, {
|
|
18
|
+
bubbles: true,
|
|
19
|
+
composed: true,
|
|
20
|
+
...options,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (props) {
|
|
24
|
+
for (const key in props) {
|
|
25
|
+
// Avoid assigning if property already exists anywhere on `this`
|
|
26
|
+
if (! (key in this)) {
|
|
27
|
+
this[key] = props[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Dispatcher
|
|
35
|
+
const Dispatch = function(method, type, options) {
|
|
36
|
+
// Try calling the method directly if provided
|
|
37
|
+
if (typeof method === 'function') {
|
|
38
|
+
let a = Object.values(options);
|
|
39
|
+
return method(...a);
|
|
40
|
+
} else if (this.tagName) {
|
|
41
|
+
this.dispatchEvent(new CustomEvents(type, options));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
15
45
|
// Get the coordinates of the action
|
|
16
46
|
const getCoords = function(e) {
|
|
17
47
|
let x;
|
|
@@ -25,11 +55,6 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
25
55
|
y = e.clientY;
|
|
26
56
|
}
|
|
27
57
|
|
|
28
|
-
// Adjust for any scrollable parent element
|
|
29
|
-
let b = document.body;
|
|
30
|
-
x -= b.scrollLeft;
|
|
31
|
-
y -= b.scrollTop;
|
|
32
|
-
|
|
33
58
|
return [x,y];
|
|
34
59
|
}
|
|
35
60
|
|
|
@@ -42,12 +67,15 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
42
67
|
}
|
|
43
68
|
}
|
|
44
69
|
|
|
70
|
+
// Initialize expanded state
|
|
71
|
+
self.expanded = false;
|
|
72
|
+
|
|
45
73
|
if (self.type === 'line') {
|
|
46
|
-
return `<hr />`;
|
|
74
|
+
return `<hr role="separator" />`;
|
|
47
75
|
} else if (self.type === 'inline') {
|
|
48
76
|
return `<div></div>`;
|
|
49
77
|
} else {
|
|
50
|
-
return `<div class="lm-menu-item" data-disabled="{{self.disabled}}" data-cursor="{{self.cursor}}" data-icon="{{self.icon}}" title="{{self.tooltip}}" data-submenu="${!!self.submenu}" onmouseup="self.parent.mouseUp" onmouseenter="self.parent.mouseEnter" onmouseleave="self.parent.mouseLeave">
|
|
78
|
+
return `<div class="lm-menu-item" role="menuitem" data-disabled="{{self.disabled}}" data-cursor="{{self.cursor}}" data-icon="{{self.icon}}" title="{{self.tooltip}}" data-submenu="${!!self.submenu}" aria-haspopup="${!!self.submenu}" aria-expanded="{{self.expanded}}" aria-label="{{self.title}}" tabindex="-1" onmouseup="self.parent.mouseUp" onmouseenter="self.parent.mouseEnter" onmouseleave="self.parent.mouseLeave">
|
|
51
79
|
<a>{{self.title}}</a> <div>{{self.shortcut}}</div>
|
|
52
80
|
</div>`;
|
|
53
81
|
}
|
|
@@ -100,7 +128,7 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
100
128
|
let current = self.parent.modals[index+1];
|
|
101
129
|
// Do not exist yet, create it.
|
|
102
130
|
if (! current) {
|
|
103
|
-
// Modal
|
|
131
|
+
// Modal needs to be created
|
|
104
132
|
current = self.parent.create();
|
|
105
133
|
}
|
|
106
134
|
// Get the parent from this one
|
|
@@ -112,13 +140,17 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
112
140
|
// Close other modals
|
|
113
141
|
self.parent.close(index+1);
|
|
114
142
|
}
|
|
115
|
-
// Update selected modal
|
|
143
|
+
// Update the selected modal
|
|
116
144
|
self.parent.modalIndex = index+1;
|
|
117
145
|
let rect = parent.modal.el.getBoundingClientRect();
|
|
118
146
|
// Update modal
|
|
119
|
-
current.modal.
|
|
147
|
+
current.modal.open();
|
|
148
|
+
// Aria indication
|
|
120
149
|
current.modal.top = rect.y + s.el.offsetTop + 2;
|
|
121
150
|
current.modal.left = rect.x + 248;
|
|
151
|
+
// Keep current item for each modal
|
|
152
|
+
current.item = s;
|
|
153
|
+
s.expanded = true;
|
|
122
154
|
|
|
123
155
|
// Activate the cursor
|
|
124
156
|
if (cursor === true) {
|
|
@@ -161,7 +193,7 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
161
193
|
}
|
|
162
194
|
|
|
163
195
|
let template = `<lm-modal :overflow="true" :closed="true" :ref="self.modal" :responsive="false" :auto-adjust="true" :focus="false" :layers="false" :onopen="self.onopen" :onclose="self.onclose">
|
|
164
|
-
<div class="lm-menu-submenu">
|
|
196
|
+
<div class="lm-menu-submenu" role="menu" aria-orientation="vertical">
|
|
165
197
|
<Item :loop="self.options" />
|
|
166
198
|
</div>
|
|
167
199
|
</lm-modal>`;
|
|
@@ -169,46 +201,71 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
169
201
|
return lemonade.element(template, self, { Item: Item });
|
|
170
202
|
}
|
|
171
203
|
|
|
204
|
+
const findNextEnabledCursor = function(startIndex, direction) {
|
|
205
|
+
if (!this.options || this.options.length === 0) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let cursor = startIndex;
|
|
210
|
+
let attempts = 0;
|
|
211
|
+
const maxAttempts = this.options.length;
|
|
212
|
+
|
|
213
|
+
while (attempts < maxAttempts) {
|
|
214
|
+
if (direction) {
|
|
215
|
+
// Down
|
|
216
|
+
if (cursor >= this.options.length) {
|
|
217
|
+
cursor = 0;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Up
|
|
221
|
+
if (cursor < 0) {
|
|
222
|
+
cursor = this.options.length - 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let item = this.options[cursor];
|
|
227
|
+
if (item && !item.disabled && item.type !== 'line') {
|
|
228
|
+
return cursor;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
cursor = direction ? cursor + 1 : cursor - 1;
|
|
232
|
+
attempts++;
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
};
|
|
236
|
+
|
|
172
237
|
const setCursor = function(direction) {
|
|
173
238
|
let cursor = null;
|
|
174
239
|
|
|
175
240
|
if (typeof(this.cursor) !== 'undefined') {
|
|
176
241
|
if (! direction) {
|
|
177
242
|
// Up
|
|
178
|
-
cursor = this.cursor - 1;
|
|
179
|
-
if (cursor < 0) {
|
|
180
|
-
cursor = this.options.length - 1;
|
|
181
|
-
}
|
|
243
|
+
cursor = findNextEnabledCursor.call(this, this.cursor - 1, false);
|
|
182
244
|
} else {
|
|
183
245
|
// Down
|
|
184
|
-
cursor = this.cursor + 1;
|
|
185
|
-
if (cursor >= this.options.length) {
|
|
186
|
-
cursor = 0;
|
|
187
|
-
}
|
|
246
|
+
cursor = findNextEnabledCursor.call(this, this.cursor + 1, true);
|
|
188
247
|
}
|
|
189
248
|
}
|
|
190
249
|
|
|
191
250
|
// Remove the cursor
|
|
192
251
|
if (cursor === null) {
|
|
193
252
|
if (direction) {
|
|
194
|
-
cursor = 0;
|
|
253
|
+
cursor = findNextEnabledCursor.call(this, 0, true);
|
|
195
254
|
} else {
|
|
196
|
-
cursor = this.options.length - 1;
|
|
255
|
+
cursor = findNextEnabledCursor.call(this, this.options.length - 1, false);
|
|
197
256
|
}
|
|
198
|
-
} else {
|
|
257
|
+
} else if (typeof(this.cursor) !== 'undefined') {
|
|
199
258
|
this.options[this.cursor].cursor = false;
|
|
200
259
|
}
|
|
201
260
|
|
|
202
|
-
// Add the cursor
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (this.options[cursor].type === 'line') {
|
|
208
|
-
setCursor.call(this, direction);
|
|
261
|
+
// Add the cursor if found
|
|
262
|
+
if (cursor !== null) {
|
|
263
|
+
this.options[cursor].cursor = true;
|
|
264
|
+
this.cursor = cursor;
|
|
265
|
+
return true;
|
|
209
266
|
}
|
|
210
267
|
|
|
211
|
-
return
|
|
268
|
+
return false;
|
|
212
269
|
}
|
|
213
270
|
|
|
214
271
|
/**
|
|
@@ -260,68 +317,73 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
260
317
|
return s;
|
|
261
318
|
}
|
|
262
319
|
|
|
320
|
+
self.isClosed = function() {
|
|
321
|
+
return self.modals[0].modal.closed === true;
|
|
322
|
+
}
|
|
323
|
+
|
|
263
324
|
self.open = function(options, x, y, e) {
|
|
264
325
|
// Get the main modal
|
|
265
326
|
let menu = self.modals[0];
|
|
266
327
|
// Reset cursor
|
|
267
328
|
resetCursor.call(menu);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
329
|
+
// Open
|
|
330
|
+
menu.modal.open();
|
|
331
|
+
// If the modal is open and the content is different from what is shown. Close modals with higher level
|
|
332
|
+
self.close(1);
|
|
333
|
+
// Update the data
|
|
334
|
+
if (options && menu.options !== options) {
|
|
335
|
+
// Refresh content
|
|
336
|
+
menu.options = options;
|
|
274
337
|
}
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
self.close(1);
|
|
279
|
-
// Update the data
|
|
280
|
-
if (menu.options !== options) {
|
|
281
|
-
// Refresh content
|
|
282
|
-
menu.options = options;
|
|
283
|
-
}
|
|
284
|
-
// Define new position
|
|
285
|
-
menu.modal.top = y;
|
|
286
|
-
menu.modal.left = x;
|
|
338
|
+
// Define new position
|
|
339
|
+
menu.modal.top = y;
|
|
340
|
+
menu.modal.left = x;
|
|
287
341
|
|
|
288
|
-
|
|
289
|
-
|
|
342
|
+
onopen(self, options);
|
|
343
|
+
|
|
344
|
+
// Focus
|
|
345
|
+
self.el.classList.add('lm-menu-focus');
|
|
346
|
+
// Focus on the contextmenu
|
|
347
|
+
self.el.focus();
|
|
290
348
|
}
|
|
291
349
|
|
|
292
350
|
self.close = function(level) {
|
|
293
351
|
// Close all modals from the level specified
|
|
294
352
|
self.modals.forEach(function(menu, k) {
|
|
295
353
|
if (k >= level) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
354
|
+
if (menu.item) {
|
|
355
|
+
menu.item.expanded = false;
|
|
356
|
+
menu.item = null;
|
|
357
|
+
}
|
|
358
|
+
menu.modal.close();
|
|
300
359
|
}
|
|
301
360
|
});
|
|
302
361
|
// Keep the index of the modal that is opened
|
|
303
362
|
self.modalIndex = level ? level - 1 : 0;
|
|
304
|
-
}
|
|
305
363
|
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
self.
|
|
309
|
-
}
|
|
364
|
+
// Close event
|
|
365
|
+
if (level === 0) {
|
|
366
|
+
self.el.classList.remove('lm-menu-focus');
|
|
310
367
|
|
|
311
|
-
|
|
368
|
+
Dispatch.call(self, self.onclose, 'close', {
|
|
369
|
+
instance: self,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
312
373
|
|
|
374
|
+
onload(() => {
|
|
313
375
|
// Create first menu
|
|
314
376
|
self.create();
|
|
315
377
|
|
|
316
|
-
//
|
|
317
|
-
self.
|
|
318
|
-
if (e.
|
|
378
|
+
// Create event for focus out
|
|
379
|
+
self.el.addEventListener("focusout", (e) => {
|
|
380
|
+
if (! (e.relatedTarget && (self.el.contains(e.relatedTarget) || self.root?.contains(e.relatedTarget)))) {
|
|
319
381
|
self.close(0);
|
|
320
382
|
}
|
|
321
383
|
});
|
|
322
384
|
|
|
323
385
|
// Keyboard event
|
|
324
|
-
self.
|
|
386
|
+
self.el.addEventListener("keydown", function(e) {
|
|
325
387
|
// Menu object
|
|
326
388
|
let menu = self.modals[self.modalIndex];
|
|
327
389
|
// Modal must be opened
|
|
@@ -375,6 +437,8 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
375
437
|
ret = true;
|
|
376
438
|
}
|
|
377
439
|
}
|
|
440
|
+
} else if (e.key === 'Escape') {
|
|
441
|
+
self.close(0);
|
|
378
442
|
}
|
|
379
443
|
|
|
380
444
|
// Something important happen so block any progression
|
|
@@ -385,23 +449,22 @@ if (! Modal && typeof (require) === 'function') {
|
|
|
385
449
|
}
|
|
386
450
|
});
|
|
387
451
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
self.close(0);
|
|
392
|
-
}
|
|
393
|
-
});
|
|
452
|
+
if (! self.root) {
|
|
453
|
+
self.root = self.el.parentNode;
|
|
454
|
+
}
|
|
394
455
|
|
|
395
456
|
// Parent
|
|
396
457
|
self.root.addEventListener("contextmenu", function(e) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
458
|
+
if (Array.isArray(self.options) && self.options.length) {
|
|
459
|
+
let [x, y] = getCoords(e);
|
|
460
|
+
self.open(self.options, x, y, e);
|
|
461
|
+
e.preventDefault();
|
|
462
|
+
e.stopImmediatePropagation();
|
|
463
|
+
}
|
|
401
464
|
});
|
|
402
465
|
});
|
|
403
466
|
|
|
404
|
-
return `<div class="lm-menu"></div>`;
|
|
467
|
+
return `<div class="lm-menu" role="menu" aria-orientation="vertical" tabindex="0"></div>`;
|
|
405
468
|
}
|
|
406
469
|
|
|
407
470
|
lemonade.setComponents({ Contextmenu: Contextmenu });
|
package/dist/style.css
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
.lm-menu {
|
|
2
|
+
display: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.lm-menu-focus {
|
|
6
|
+
display: unset;
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
.lm-menu .lm-modal {
|
|
2
10
|
color: #555;
|
|
3
11
|
user-select: none;
|
|
4
12
|
border: 1px solid var(--lm-border-color-light, #e9e9e9);
|
|
5
13
|
border-radius: 4px;
|
|
6
14
|
box-shadow: 0 2px 4px 2px rgba(60,64,67,.2);
|
|
7
|
-
max-height:
|
|
15
|
+
max-height: 600px;
|
|
8
16
|
width: initial;
|
|
9
17
|
height: initial;
|
|
10
18
|
min-width: 250px;
|
|
@@ -29,6 +37,7 @@
|
|
|
29
37
|
font-family:sans-serif;
|
|
30
38
|
text-align: left;
|
|
31
39
|
align-items: center;
|
|
40
|
+
position: relative;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
.lm-menu-submenu > div.lm-menu-item a {
|
|
@@ -76,6 +85,7 @@
|
|
|
76
85
|
line-height: 24px;
|
|
77
86
|
position: absolute;
|
|
78
87
|
left: 11px;
|
|
88
|
+
transform: rotate(0.03deg);
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
.lm-dark-mode .lm-menu .lm-modal {
|
package/package.json
CHANGED