@vaadin/context-menu 24.3.0-alpha1 → 24.3.0-alpha2
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/package.json +11 -11
- package/src/vaadin-context-menu-mixin.d.ts +81 -0
- package/src/vaadin-context-menu-mixin.js +494 -0
- package/src/vaadin-context-menu.d.ts +3 -69
- package/src/vaadin-context-menu.js +6 -479
- package/src/vaadin-contextmenu-items-mixin.js +14 -15
- package/web-types.json +24 -24
- package/web-types.lit.json +9 -9
|
@@ -4,18 +4,17 @@
|
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import './vaadin-contextmenu-event.js';
|
|
7
|
+
import './vaadin-context-menu-item.js';
|
|
8
|
+
import './vaadin-context-menu-list-box.js';
|
|
7
9
|
import './vaadin-context-menu-overlay.js';
|
|
8
10
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
9
|
-
import { isTouch } from '@vaadin/component-base/src/browser-utils.js';
|
|
10
11
|
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
11
12
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
12
13
|
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
13
|
-
import { addListener, gestures, removeListener } from '@vaadin/component-base/src/gestures.js';
|
|
14
|
-
import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
|
|
15
14
|
import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
|
|
16
15
|
import { processTemplates } from '@vaadin/component-base/src/templates.js';
|
|
17
16
|
import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
|
|
18
|
-
import {
|
|
17
|
+
import { ContextMenuMixin } from './vaadin-context-menu-mixin.js';
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* `<vaadin-context-menu>` is a Web Component for creating context menus.
|
|
@@ -202,13 +201,13 @@ import { ItemsMixin } from './vaadin-contextmenu-items-mixin.js';
|
|
|
202
201
|
* @customElement
|
|
203
202
|
* @extends HTMLElement
|
|
204
203
|
* @mixes ElementMixin
|
|
204
|
+
* @mixes ContextMenuMixin
|
|
205
205
|
* @mixes ControllerMixin
|
|
206
206
|
* @mixes OverlayClassMixin
|
|
207
207
|
* @mixes ThemePropertyMixin
|
|
208
|
-
* @mixes ItemsMixin
|
|
209
208
|
*/
|
|
210
|
-
class ContextMenu extends
|
|
211
|
-
ControllerMixin(ElementMixin(ThemePropertyMixin(
|
|
209
|
+
class ContextMenu extends ContextMenuMixin(
|
|
210
|
+
OverlayClassMixin(ControllerMixin(ElementMixin(ThemePropertyMixin(PolymerElement)))),
|
|
212
211
|
) {
|
|
213
212
|
static get template() {
|
|
214
213
|
return html`
|
|
@@ -241,485 +240,13 @@ class ContextMenu extends OverlayClassMixin(
|
|
|
241
240
|
return 'vaadin-context-menu';
|
|
242
241
|
}
|
|
243
242
|
|
|
244
|
-
static get properties() {
|
|
245
|
-
return {
|
|
246
|
-
/**
|
|
247
|
-
* CSS selector that can be used to target any child element
|
|
248
|
-
* of the context menu to listen for `openOn` events.
|
|
249
|
-
*/
|
|
250
|
-
selector: {
|
|
251
|
-
type: String,
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* True if the overlay is currently displayed.
|
|
256
|
-
* @type {boolean}
|
|
257
|
-
*/
|
|
258
|
-
opened: {
|
|
259
|
-
type: Boolean,
|
|
260
|
-
value: false,
|
|
261
|
-
notify: true,
|
|
262
|
-
readOnly: true,
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Event name to listen for opening the context menu.
|
|
267
|
-
* @attr {string} open-on
|
|
268
|
-
* @type {string}
|
|
269
|
-
*/
|
|
270
|
-
openOn: {
|
|
271
|
-
type: String,
|
|
272
|
-
value: 'vaadin-contextmenu',
|
|
273
|
-
},
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* The target element that's listened to for context menu opening events.
|
|
277
|
-
* By default the vaadin-context-menu listens to the target's `vaadin-contextmenu`
|
|
278
|
-
* events.
|
|
279
|
-
* @type {!HTMLElement}
|
|
280
|
-
* @default self
|
|
281
|
-
*/
|
|
282
|
-
listenOn: {
|
|
283
|
-
type: Object,
|
|
284
|
-
value() {
|
|
285
|
-
return this;
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Event name to listen for closing the context menu.
|
|
291
|
-
* @attr {string} close-on
|
|
292
|
-
* @type {string}
|
|
293
|
-
*/
|
|
294
|
-
closeOn: {
|
|
295
|
-
type: String,
|
|
296
|
-
value: 'click',
|
|
297
|
-
observer: '_closeOnChanged',
|
|
298
|
-
},
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Custom function for rendering the content of the menu overlay.
|
|
302
|
-
* Receives three arguments:
|
|
303
|
-
*
|
|
304
|
-
* - `root` The root container DOM element. Append your content to it.
|
|
305
|
-
* - `contextMenu` The reference to the `<vaadin-context-menu>` element.
|
|
306
|
-
* - `context` The object with the menu context, contains:
|
|
307
|
-
* - `context.target` the target of the menu opening event,
|
|
308
|
-
* - `context.detail` the menu opening event detail.
|
|
309
|
-
* @type {ContextMenuRenderer | undefined}
|
|
310
|
-
*/
|
|
311
|
-
renderer: {
|
|
312
|
-
type: Function,
|
|
313
|
-
},
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* When true, the menu overlay is modeless.
|
|
317
|
-
* @protected
|
|
318
|
-
*/
|
|
319
|
-
_modeless: {
|
|
320
|
-
type: Boolean,
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
/** @private */
|
|
324
|
-
_context: Object,
|
|
325
|
-
|
|
326
|
-
/** @private */
|
|
327
|
-
_phone: {
|
|
328
|
-
type: Boolean,
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
/** @private */
|
|
332
|
-
_touch: {
|
|
333
|
-
type: Boolean,
|
|
334
|
-
value: isTouch,
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
/** @private */
|
|
338
|
-
_wide: {
|
|
339
|
-
type: Boolean,
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
/** @private */
|
|
343
|
-
_wideMediaQuery: {
|
|
344
|
-
type: String,
|
|
345
|
-
value: '(min-device-width: 750px)',
|
|
346
|
-
},
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
static get observers() {
|
|
351
|
-
return [
|
|
352
|
-
'_openedChanged(opened)',
|
|
353
|
-
'_targetOrOpenOnChanged(listenOn, openOn)',
|
|
354
|
-
'_rendererChanged(renderer, items)',
|
|
355
|
-
'_touchOrWideChanged(_touch, _wide)',
|
|
356
|
-
];
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
constructor() {
|
|
360
|
-
super();
|
|
361
|
-
this._boundOpen = this.open.bind(this);
|
|
362
|
-
this._boundClose = this.close.bind(this);
|
|
363
|
-
this._boundPreventDefault = this._preventDefault.bind(this);
|
|
364
|
-
this._boundOnGlobalContextMenu = this._onGlobalContextMenu.bind(this);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/** @protected */
|
|
368
|
-
connectedCallback() {
|
|
369
|
-
super.connectedCallback();
|
|
370
|
-
|
|
371
|
-
this.__boundOnScroll = this.__onScroll.bind(this);
|
|
372
|
-
window.addEventListener('scroll', this.__boundOnScroll, true);
|
|
373
|
-
|
|
374
|
-
// Restore opened state if overlay was opened when disconnecting
|
|
375
|
-
if (this.__restoreOpened) {
|
|
376
|
-
this._setOpened(true);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/** @protected */
|
|
381
|
-
disconnectedCallback() {
|
|
382
|
-
super.disconnectedCallback();
|
|
383
|
-
|
|
384
|
-
window.removeEventListener('scroll', this.__boundOnScroll, true);
|
|
385
|
-
|
|
386
|
-
// Close overlay and memorize opened state
|
|
387
|
-
this.__restoreOpened = this.opened;
|
|
388
|
-
this.close();
|
|
389
|
-
}
|
|
390
|
-
|
|
391
243
|
/** @protected */
|
|
392
244
|
ready() {
|
|
393
245
|
super.ready();
|
|
394
246
|
|
|
395
|
-
this._overlayElement = this.$.overlay;
|
|
396
|
-
|
|
397
|
-
this.addController(
|
|
398
|
-
new MediaQueryController(this._wideMediaQuery, (matches) => {
|
|
399
|
-
this._wide = matches;
|
|
400
|
-
}),
|
|
401
|
-
);
|
|
402
|
-
|
|
403
247
|
processTemplates(this);
|
|
404
248
|
}
|
|
405
249
|
|
|
406
|
-
/**
|
|
407
|
-
* Runs before overlay is fully rendered
|
|
408
|
-
* @private
|
|
409
|
-
*/
|
|
410
|
-
_onOverlayOpened(e) {
|
|
411
|
-
this._setOpened(e.detail.value);
|
|
412
|
-
this.__alignOverlayPosition();
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Runs after overlay is fully rendered
|
|
417
|
-
* @private
|
|
418
|
-
*/
|
|
419
|
-
_onVaadinOverlayOpen() {
|
|
420
|
-
this.__alignOverlayPosition();
|
|
421
|
-
this.$.overlay.style.opacity = '';
|
|
422
|
-
this.__forwardFocus();
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/** @private */
|
|
426
|
-
_targetOrOpenOnChanged(listenOn, openOn) {
|
|
427
|
-
if (this._oldListenOn && this._oldOpenOn) {
|
|
428
|
-
this._unlisten(this._oldListenOn, this._oldOpenOn, this._boundOpen);
|
|
429
|
-
|
|
430
|
-
this._oldListenOn.style.webkitTouchCallout = '';
|
|
431
|
-
this._oldListenOn.style.webkitUserSelect = '';
|
|
432
|
-
this._oldListenOn.style.userSelect = '';
|
|
433
|
-
|
|
434
|
-
this._oldListenOn = null;
|
|
435
|
-
this._oldOpenOn = null;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (listenOn && openOn) {
|
|
439
|
-
this._listen(listenOn, openOn, this._boundOpen);
|
|
440
|
-
|
|
441
|
-
this._oldListenOn = listenOn;
|
|
442
|
-
this._oldOpenOn = openOn;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/** @private */
|
|
447
|
-
_touchOrWideChanged(touch, wide) {
|
|
448
|
-
this._phone = !wide && touch;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/** @private */
|
|
452
|
-
_setListenOnUserSelect(value) {
|
|
453
|
-
// Note: these styles don't seem to work in Firefox on iOS.
|
|
454
|
-
this.listenOn.style.webkitTouchCallout = value;
|
|
455
|
-
this.listenOn.style.webkitUserSelect = value; // Chrome, Safari, Firefox
|
|
456
|
-
this.listenOn.style.userSelect = value;
|
|
457
|
-
|
|
458
|
-
// Note: because user-selection is disabled on the overlay
|
|
459
|
-
// before opening the menu the text could be already selected
|
|
460
|
-
// so we need to clear that selection
|
|
461
|
-
document.getSelection().removeAllRanges();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/** @private */
|
|
465
|
-
_closeOnChanged(closeOn, oldCloseOn) {
|
|
466
|
-
// Outside click event from overlay
|
|
467
|
-
const evtOverlay = 'vaadin-overlay-outside-click';
|
|
468
|
-
|
|
469
|
-
const overlay = this.$.overlay;
|
|
470
|
-
|
|
471
|
-
if (oldCloseOn) {
|
|
472
|
-
this._unlisten(overlay, oldCloseOn, this._boundClose);
|
|
473
|
-
}
|
|
474
|
-
if (closeOn) {
|
|
475
|
-
this._listen(overlay, closeOn, this._boundClose);
|
|
476
|
-
overlay.removeEventListener(evtOverlay, this._boundPreventDefault);
|
|
477
|
-
} else {
|
|
478
|
-
overlay.addEventListener(evtOverlay, this._boundPreventDefault);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/** @private */
|
|
483
|
-
_preventDefault(e) {
|
|
484
|
-
e.preventDefault();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/** @private */
|
|
488
|
-
_openedChanged(opened) {
|
|
489
|
-
if (opened) {
|
|
490
|
-
document.documentElement.addEventListener('contextmenu', this._boundOnGlobalContextMenu, true);
|
|
491
|
-
this._setListenOnUserSelect('none');
|
|
492
|
-
} else {
|
|
493
|
-
document.documentElement.removeEventListener('contextmenu', this._boundOnGlobalContextMenu, true);
|
|
494
|
-
this._setListenOnUserSelect('');
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Has to be set after instance has been created
|
|
498
|
-
this.$.overlay.opened = opened;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Requests an update for the content of the menu overlay.
|
|
503
|
-
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
504
|
-
*
|
|
505
|
-
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
506
|
-
*/
|
|
507
|
-
requestContentUpdate() {
|
|
508
|
-
if (!this._overlayElement || !this.renderer) {
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
this._overlayElement.requestContentUpdate();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/** @private */
|
|
516
|
-
_rendererChanged(renderer, items) {
|
|
517
|
-
if (items) {
|
|
518
|
-
if (renderer) {
|
|
519
|
-
throw new Error('The items API cannot be used together with a renderer');
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (this.closeOn === 'click') {
|
|
523
|
-
this.closeOn = '';
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
renderer = this.__itemsRenderer;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
this.$.overlay.setProperties({ owner: this, renderer });
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Closes the overlay.
|
|
534
|
-
*/
|
|
535
|
-
close() {
|
|
536
|
-
this._setOpened(false);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/** @private */
|
|
540
|
-
_contextTarget(e) {
|
|
541
|
-
if (this.selector) {
|
|
542
|
-
const targets = this.listenOn.querySelectorAll(this.selector);
|
|
543
|
-
|
|
544
|
-
return Array.prototype.filter.call(targets, (el) => {
|
|
545
|
-
return e.composedPath().indexOf(el) > -1;
|
|
546
|
-
})[0];
|
|
547
|
-
}
|
|
548
|
-
return e.target;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Opens the overlay.
|
|
553
|
-
* @param {!Event | undefined} e used as the context for the menu. Overlay coordinates are taken from this event.
|
|
554
|
-
*/
|
|
555
|
-
open(e) {
|
|
556
|
-
if (e && !this.opened) {
|
|
557
|
-
this._context = {
|
|
558
|
-
detail: e.detail,
|
|
559
|
-
target: this._contextTarget(e),
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
if (this._context.target) {
|
|
563
|
-
e.preventDefault();
|
|
564
|
-
e.stopPropagation();
|
|
565
|
-
|
|
566
|
-
// Used in alignment which is delayed until overlay is rendered
|
|
567
|
-
this.__x = this._getEventCoordinate(e, 'x');
|
|
568
|
-
this.__pageXOffset = window.pageXOffset;
|
|
569
|
-
|
|
570
|
-
this.__y = this._getEventCoordinate(e, 'y');
|
|
571
|
-
this.__pageYOffset = window.pageYOffset;
|
|
572
|
-
|
|
573
|
-
this.$.overlay.style.opacity = '0';
|
|
574
|
-
this._setOpened(true);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/** @private */
|
|
580
|
-
__onScroll() {
|
|
581
|
-
if (!this.opened) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const yDiff = window.pageYOffset - this.__pageYOffset;
|
|
586
|
-
const xDiff = window.pageXOffset - this.__pageXOffset;
|
|
587
|
-
|
|
588
|
-
this.__adjustPosition('left', -xDiff);
|
|
589
|
-
this.__adjustPosition('right', xDiff);
|
|
590
|
-
|
|
591
|
-
this.__adjustPosition('top', -yDiff);
|
|
592
|
-
this.__adjustPosition('bottom', yDiff);
|
|
593
|
-
|
|
594
|
-
this.__pageYOffset += yDiff;
|
|
595
|
-
this.__pageXOffset += xDiff;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/** @private */
|
|
599
|
-
__adjustPosition(coord, diff) {
|
|
600
|
-
const overlay = this.$.overlay;
|
|
601
|
-
const style = overlay.style;
|
|
602
|
-
|
|
603
|
-
style[coord] = `${(parseInt(style[coord]) || 0) + diff}px`;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/** @private */
|
|
607
|
-
__alignOverlayPosition() {
|
|
608
|
-
const overlay = this.$.overlay;
|
|
609
|
-
|
|
610
|
-
if (overlay.positionTarget) {
|
|
611
|
-
// The overlay is positioned relative to another node, for example, a
|
|
612
|
-
// menu item in a nested submenu structure where this overlay lists
|
|
613
|
-
// the items for another submenu.
|
|
614
|
-
// It means that the overlay positioning is controlled by
|
|
615
|
-
// vaadin-overlay-position-mixin so no manual alignment is needed.
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const style = overlay.style;
|
|
620
|
-
|
|
621
|
-
// Reset all properties before measuring
|
|
622
|
-
['top', 'right', 'bottom', 'left'].forEach((prop) => style.removeProperty(prop));
|
|
623
|
-
['right-aligned', 'end-aligned', 'bottom-aligned'].forEach((attr) => overlay.removeAttribute(attr));
|
|
624
|
-
|
|
625
|
-
// Maximum x and y values are imposed by content size and overlay limits.
|
|
626
|
-
const { xMax, xMin, yMax } = overlay.getBoundaries();
|
|
627
|
-
// Reuse saved x and y event values, in order to this method be used async
|
|
628
|
-
// in the `vaadin-overlay-change` which guarantees that overlay is ready.
|
|
629
|
-
// The valus represent an anchor position on the page where the contextmenu
|
|
630
|
-
// event took place.
|
|
631
|
-
const x = this.__x;
|
|
632
|
-
const y = this.__y;
|
|
633
|
-
|
|
634
|
-
// Select one overlay corner and move to the event x/y position.
|
|
635
|
-
// Then set styling attrs for flex-aligning the content appropriately.
|
|
636
|
-
const wdthVport = document.documentElement.clientWidth;
|
|
637
|
-
const hghtVport = document.documentElement.clientHeight;
|
|
638
|
-
|
|
639
|
-
if (!this.__isRTL) {
|
|
640
|
-
if (x < wdthVport / 2 || x < xMax) {
|
|
641
|
-
// Menu is displayed in the right side of the anchor
|
|
642
|
-
style.left = `${x}px`;
|
|
643
|
-
} else {
|
|
644
|
-
// Menu is displayed in the left side of the anchor
|
|
645
|
-
style.right = `${Math.max(0, wdthVport - x)}px`;
|
|
646
|
-
this._setEndAligned(overlay);
|
|
647
|
-
}
|
|
648
|
-
} else if (x > wdthVport / 2 || x > xMin) {
|
|
649
|
-
// Menu is displayed in the right side of the anchor
|
|
650
|
-
style.right = `${Math.max(0, wdthVport - x)}px`;
|
|
651
|
-
} else {
|
|
652
|
-
// Menu is displayed in the left side of the anchor
|
|
653
|
-
style.left = `${x}px`;
|
|
654
|
-
this._setEndAligned(overlay);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (y < hghtVport / 2 || y < yMax) {
|
|
658
|
-
style.top = `${y}px`;
|
|
659
|
-
} else {
|
|
660
|
-
style.bottom = `${Math.max(0, hghtVport - y)}px`;
|
|
661
|
-
overlay.setAttribute('bottom-aligned', '');
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/** @private */
|
|
666
|
-
_setEndAligned(element) {
|
|
667
|
-
element.setAttribute('end-aligned', '');
|
|
668
|
-
if (!this.__isRTL) {
|
|
669
|
-
element.setAttribute('right-aligned', '');
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/** @private */
|
|
674
|
-
_getEventCoordinate(event, coord) {
|
|
675
|
-
if (event.detail instanceof Object) {
|
|
676
|
-
if (event.detail[coord]) {
|
|
677
|
-
// Polymer gesture events, get coordinate from detail
|
|
678
|
-
return event.detail[coord];
|
|
679
|
-
} else if (event.detail.sourceEvent) {
|
|
680
|
-
// Unwrap detailed event
|
|
681
|
-
return this._getEventCoordinate(event.detail.sourceEvent, coord);
|
|
682
|
-
}
|
|
683
|
-
} else {
|
|
684
|
-
const prop = `client${coord.toUpperCase()}`;
|
|
685
|
-
const position = event.changedTouches ? event.changedTouches[0][prop] : event[prop];
|
|
686
|
-
|
|
687
|
-
if (position === 0) {
|
|
688
|
-
// Native keyboard event
|
|
689
|
-
const rect = event.target.getBoundingClientRect();
|
|
690
|
-
return coord === 'x' ? rect.left : rect.top + rect.height;
|
|
691
|
-
}
|
|
692
|
-
// Native mouse or touch event
|
|
693
|
-
return position;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/** @private */
|
|
698
|
-
_listen(node, evType, handler) {
|
|
699
|
-
if (gestures[evType]) {
|
|
700
|
-
addListener(node, evType, handler);
|
|
701
|
-
} else {
|
|
702
|
-
node.addEventListener(evType, handler);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
/** @private */
|
|
707
|
-
_unlisten(node, evType, handler) {
|
|
708
|
-
if (gestures[evType]) {
|
|
709
|
-
removeListener(node, evType, handler);
|
|
710
|
-
} else {
|
|
711
|
-
node.removeEventListener(evType, handler);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/** @private */
|
|
716
|
-
_onGlobalContextMenu(e) {
|
|
717
|
-
if (!e.shiftKey) {
|
|
718
|
-
e.preventDefault();
|
|
719
|
-
this.close();
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
250
|
/**
|
|
724
251
|
* Fired when an item is selected when the context menu is populated using the `items` API.
|
|
725
252
|
*
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
* Copyright (c) 2016 - 2023 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import './vaadin-context-menu-item.js';
|
|
7
|
-
import './vaadin-context-menu-list-box.js';
|
|
8
6
|
import { isTouch } from '@vaadin/component-base/src/browser-utils.js';
|
|
9
7
|
|
|
10
8
|
/**
|
|
@@ -71,18 +69,8 @@ export const ItemsMixin = (superClass) =>
|
|
|
71
69
|
};
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* @protected
|
|
77
|
-
* @return {string}
|
|
78
|
-
*/
|
|
79
|
-
get _tagNamePrefix() {
|
|
80
|
-
return 'vaadin-context-menu';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** @protected */
|
|
84
|
-
ready() {
|
|
85
|
-
super.ready();
|
|
72
|
+
constructor() {
|
|
73
|
+
super();
|
|
86
74
|
|
|
87
75
|
// Overlay's outside click listener doesn't work with modeless
|
|
88
76
|
// overlays (submenus) so we need additional logic for it
|
|
@@ -91,7 +79,18 @@ export const ItemsMixin = (superClass) =>
|
|
|
91
79
|
this.dispatchEvent(new CustomEvent('items-outside-click'));
|
|
92
80
|
}
|
|
93
81
|
};
|
|
94
|
-
this.addEventListener('items-outside-click', () =>
|
|
82
|
+
this.addEventListener('items-outside-click', () => {
|
|
83
|
+
this.items && this.close();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Tag name prefix used by overlay, list-box and items.
|
|
89
|
+
* @protected
|
|
90
|
+
* @return {string}
|
|
91
|
+
*/
|
|
92
|
+
get _tagNamePrefix() {
|
|
93
|
+
return 'vaadin-context-menu';
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
/** @protected */
|
package/web-types.json
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/web-types",
|
|
3
3
|
"name": "@vaadin/context-menu",
|
|
4
|
-
"version": "24.3.0-
|
|
4
|
+
"version": "24.3.0-alpha2",
|
|
5
5
|
"description-markup": "markdown",
|
|
6
6
|
"contributions": {
|
|
7
7
|
"html": {
|
|
8
8
|
"elements": [
|
|
9
9
|
{
|
|
10
10
|
"name": "vaadin-context-menu",
|
|
11
|
-
"description": "`<vaadin-context-menu>` is a Web Component for creating context menus.\n\n### Items\n\nItems is a higher level convenience API for defining a (hierarchical) menu structure for the component.\nIf a menu item has a non-empty `children` set, a sub-menu with the child items is opened\nnext to the parent menu on mouseover, tap or a right arrow keypress.\n\nWhen an item is selected, `<vaadin-context-menu>` dispatches an \"item-selected\" event\nwith the selected item as `event.detail.value` property.\nIf item does not have `keepOpen` property the menu will be closed.\n\n```javascript\ncontextMenu.items = [\n { text: 'Menu Item 1', theme: 'primary', children:\n [\n { text: 'Menu Item 1-1', checked: true, keepOpen: true },\n { text: 'Menu Item 1-2' }\n ]\n },\n { component: 'hr' },\n { text: 'Menu Item 2', children:\n [\n { text: 'Menu Item 2-1' },\n { text: 'Menu Item 2-2', disabled: true }\n ]\n },\n { text: 'Menu Item 3', disabled: true }\n];\n\ncontextMenu.addEventListener('item-selected', e => {\n const item = e.detail.value;\n console.log(`${item.text} selected`);\n});\n```\n\n**NOTE:** when the `items` array is defined, the renderer cannot be used.\n\n### Rendering\n\nThe content of the menu can be populated by using the renderer callback function.\n\nThe renderer function provides `root`, `contextMenu`, `model` arguments when applicable.\nGenerate DOM content by using `model` object properties if needed, append it to the `root`\nelement and control the state of the host element by accessing `contextMenu`. Before generating\nnew content, the renderer function should check if there is already content in `root` for reusing it.\n\n```html\n<vaadin-context-menu id=\"contextMenu\">\n <p>This paragraph has a context menu.</p>\n</vaadin-context-menu>\n```\n```js\nconst contextMenu = document.querySelector('#contextMenu');\ncontextMenu.renderer = (root, contextMenu, context) => {\n let listBox = root.firstElementChild;\n if (!listBox) {\n listBox = document.createElement('vaadin-list-box');\n root.appendChild(listBox);\n }\n\n let item = listBox.querySelector('vaadin-item');\n if (!item) {\n item = document.createElement('vaadin-item');\n listBox.appendChild(item);\n }\n item.textContent = 'Content of the selector: ' + context.target.textContent;\n};\n```\n\nYou can access the menu context inside the renderer using\n`context.target` and `context.detail`.\n\nRenderer is called on the opening of the context-menu and each time the related context is updated.\nDOM generated during the renderer call can be reused\nin the next renderer call and will be provided with the `root` argument.\nOn first call it will be empty.\n\n### `vaadin-contextmenu` Gesture Event\n\n`vaadin-contextmenu` is a gesture event (a custom event),\nwhich is dispatched after either `contextmenu` or long touch events.\nThis enables support for both mouse and touch environments in a uniform way.\n\n`<vaadin-context-menu>` opens the menu overlay on the `vaadin-contextmenu`\nevent by default.\n\n### Menu Listener\n\nBy default, the `<vaadin-context-menu>` element listens for the menu opening\nevent on itself. In case if you do not want to wrap the target, you can listen for\nevents on an element outside the `<vaadin-context-menu>` by setting the\n`listenOn` property:\n\n```html\n<vaadin-context-menu id=\"contextMenu\"></vaadin-context-menu>\n\n<div id=\"menuListener\">The element that listens for the contextmenu event.</div>\n```\n```javascript\nconst contextMenu = document.querySelector('#contextMenu');\ncontextMenu.listenOn = document.querySelector('#menuListener');\n```\n\n### Filtering Menu Targets\n\nBy default, the listener element and all its descendants open the context\nmenu. You can filter the menu targets to a smaller set of elements inside\nthe listener element by setting the `selector` property.\n\nIn the following example, only the elements matching `.has-menu` will open the context menu:\n\n```html\n<vaadin-context-menu selector=\".has-menu\">\n <p class=\"has-menu\">This paragraph opens the context menu</p>\n <p>This paragraph does not open the context menu</p>\n</vaadin-context-menu>\n```\n\n### Menu Context\n\nThe following properties are available in the `context` argument:\n\n- `target` is the menu opening event target, which is the element that\nthe user has called the context menu for\n- `detail` is the menu opening event detail\n\nIn the following example, the menu item text is composed with the contents\nof the element that opened the menu:\n\n```html\n<vaadin-context-menu selector=\"li\" id=\"contextMenu\">\n <ul>\n <li>Foo</li>\n <li>Bar</li>\n <li>Baz</li>\n </ul>\n</vaadin-context-menu>\n```\n```js\nconst contextMenu = document.querySelector('#contextMenu');\ncontextMenu.renderer = (root, contextMenu, context) => {\n let listBox = root.firstElementChild;\n if (!listBox) {\n listBox = document.createElement('vaadin-list-box');\n root.appendChild(listBox);\n }\n\n let item = listBox.querySelector('vaadin-item');\n if (!item) {\n item = document.createElement('vaadin-item');\n listBox.appendChild(item);\n }\n item.textContent = 'The menu target: ' + context.target.textContent;\n};\n```\n\n### Styling\n\n`<vaadin-context-menu>` uses `<vaadin-context-menu-overlay>` internal\nthemable component as the actual visible context menu overlay.\n\nSee [`<vaadin-overlay>`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-
|
|
11
|
+
"description": "`<vaadin-context-menu>` is a Web Component for creating context menus.\n\n### Items\n\nItems is a higher level convenience API for defining a (hierarchical) menu structure for the component.\nIf a menu item has a non-empty `children` set, a sub-menu with the child items is opened\nnext to the parent menu on mouseover, tap or a right arrow keypress.\n\nWhen an item is selected, `<vaadin-context-menu>` dispatches an \"item-selected\" event\nwith the selected item as `event.detail.value` property.\nIf item does not have `keepOpen` property the menu will be closed.\n\n```javascript\ncontextMenu.items = [\n { text: 'Menu Item 1', theme: 'primary', children:\n [\n { text: 'Menu Item 1-1', checked: true, keepOpen: true },\n { text: 'Menu Item 1-2' }\n ]\n },\n { component: 'hr' },\n { text: 'Menu Item 2', children:\n [\n { text: 'Menu Item 2-1' },\n { text: 'Menu Item 2-2', disabled: true }\n ]\n },\n { text: 'Menu Item 3', disabled: true }\n];\n\ncontextMenu.addEventListener('item-selected', e => {\n const item = e.detail.value;\n console.log(`${item.text} selected`);\n});\n```\n\n**NOTE:** when the `items` array is defined, the renderer cannot be used.\n\n### Rendering\n\nThe content of the menu can be populated by using the renderer callback function.\n\nThe renderer function provides `root`, `contextMenu`, `model` arguments when applicable.\nGenerate DOM content by using `model` object properties if needed, append it to the `root`\nelement and control the state of the host element by accessing `contextMenu`. Before generating\nnew content, the renderer function should check if there is already content in `root` for reusing it.\n\n```html\n<vaadin-context-menu id=\"contextMenu\">\n <p>This paragraph has a context menu.</p>\n</vaadin-context-menu>\n```\n```js\nconst contextMenu = document.querySelector('#contextMenu');\ncontextMenu.renderer = (root, contextMenu, context) => {\n let listBox = root.firstElementChild;\n if (!listBox) {\n listBox = document.createElement('vaadin-list-box');\n root.appendChild(listBox);\n }\n\n let item = listBox.querySelector('vaadin-item');\n if (!item) {\n item = document.createElement('vaadin-item');\n listBox.appendChild(item);\n }\n item.textContent = 'Content of the selector: ' + context.target.textContent;\n};\n```\n\nYou can access the menu context inside the renderer using\n`context.target` and `context.detail`.\n\nRenderer is called on the opening of the context-menu and each time the related context is updated.\nDOM generated during the renderer call can be reused\nin the next renderer call and will be provided with the `root` argument.\nOn first call it will be empty.\n\n### `vaadin-contextmenu` Gesture Event\n\n`vaadin-contextmenu` is a gesture event (a custom event),\nwhich is dispatched after either `contextmenu` or long touch events.\nThis enables support for both mouse and touch environments in a uniform way.\n\n`<vaadin-context-menu>` opens the menu overlay on the `vaadin-contextmenu`\nevent by default.\n\n### Menu Listener\n\nBy default, the `<vaadin-context-menu>` element listens for the menu opening\nevent on itself. In case if you do not want to wrap the target, you can listen for\nevents on an element outside the `<vaadin-context-menu>` by setting the\n`listenOn` property:\n\n```html\n<vaadin-context-menu id=\"contextMenu\"></vaadin-context-menu>\n\n<div id=\"menuListener\">The element that listens for the contextmenu event.</div>\n```\n```javascript\nconst contextMenu = document.querySelector('#contextMenu');\ncontextMenu.listenOn = document.querySelector('#menuListener');\n```\n\n### Filtering Menu Targets\n\nBy default, the listener element and all its descendants open the context\nmenu. You can filter the menu targets to a smaller set of elements inside\nthe listener element by setting the `selector` property.\n\nIn the following example, only the elements matching `.has-menu` will open the context menu:\n\n```html\n<vaadin-context-menu selector=\".has-menu\">\n <p class=\"has-menu\">This paragraph opens the context menu</p>\n <p>This paragraph does not open the context menu</p>\n</vaadin-context-menu>\n```\n\n### Menu Context\n\nThe following properties are available in the `context` argument:\n\n- `target` is the menu opening event target, which is the element that\nthe user has called the context menu for\n- `detail` is the menu opening event detail\n\nIn the following example, the menu item text is composed with the contents\nof the element that opened the menu:\n\n```html\n<vaadin-context-menu selector=\"li\" id=\"contextMenu\">\n <ul>\n <li>Foo</li>\n <li>Bar</li>\n <li>Baz</li>\n </ul>\n</vaadin-context-menu>\n```\n```js\nconst contextMenu = document.querySelector('#contextMenu');\ncontextMenu.renderer = (root, contextMenu, context) => {\n let listBox = root.firstElementChild;\n if (!listBox) {\n listBox = document.createElement('vaadin-list-box');\n root.appendChild(listBox);\n }\n\n let item = listBox.querySelector('vaadin-item');\n if (!item) {\n item = document.createElement('vaadin-item');\n listBox.appendChild(item);\n }\n item.textContent = 'The menu target: ' + context.target.textContent;\n};\n```\n\n### Styling\n\n`<vaadin-context-menu>` uses `<vaadin-context-menu-overlay>` internal\nthemable component as the actual visible context menu overlay.\n\nSee [`<vaadin-overlay>`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-overlay)\ndocumentation for `<vaadin-context-menu-overlay>` stylable parts.\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.\n\n### Internal components\n\nWhen using `items` API, in addition `<vaadin-context-menu-overlay>`, the following\ninternal components are themable:\n\n- `<vaadin-context-menu-item>` - has the same API as [`<vaadin-item>`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-item).\n- `<vaadin-context-menu-list-box>` - has the same API as [`<vaadin-list-box>`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-list-box).\n\nNote: the `theme` attribute value set on `<vaadin-context-menu>` is\npropagated to the internal components listed above.",
|
|
12
12
|
"attributes": [
|
|
13
|
-
{
|
|
14
|
-
"name": "overlay-class",
|
|
15
|
-
"description": "A space-delimited list of CSS class names to set on the overlay element.\nThis property does not affect other CSS class names set manually via JS.\n\nNote, if the CSS class name was set with this property, clearing it will\nremove it from the overlay, even if the same class name was also added\nmanually, e.g. by using `classList.add()` in the `renderer` function.",
|
|
16
|
-
"value": {
|
|
17
|
-
"type": [
|
|
18
|
-
"string",
|
|
19
|
-
"null",
|
|
20
|
-
"undefined"
|
|
21
|
-
]
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
13
|
{
|
|
25
14
|
"name": "selector",
|
|
26
15
|
"description": "CSS selector that can be used to target any child element\nof the context menu to listen for `openOn` events.",
|
|
@@ -50,6 +39,17 @@
|
|
|
50
39
|
]
|
|
51
40
|
}
|
|
52
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"name": "overlay-class",
|
|
44
|
+
"description": "A space-delimited list of CSS class names to set on the overlay element.\nThis property does not affect other CSS class names set manually via JS.\n\nNote, if the CSS class name was set with this property, clearing it will\nremove it from the overlay, even if the same class name was also added\nmanually, e.g. by using `classList.add()` in the `renderer` function.",
|
|
45
|
+
"value": {
|
|
46
|
+
"type": [
|
|
47
|
+
"string",
|
|
48
|
+
"null",
|
|
49
|
+
"undefined"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
53
|
{
|
|
54
54
|
"name": "theme",
|
|
55
55
|
"description": "The theme variants to apply to the component.",
|
|
@@ -64,17 +64,6 @@
|
|
|
64
64
|
],
|
|
65
65
|
"js": {
|
|
66
66
|
"properties": [
|
|
67
|
-
{
|
|
68
|
-
"name": "overlayClass",
|
|
69
|
-
"description": "A space-delimited list of CSS class names to set on the overlay element.\nThis property does not affect other CSS class names set manually via JS.\n\nNote, if the CSS class name was set with this property, clearing it will\nremove it from the overlay, even if the same class name was also added\nmanually, e.g. by using `classList.add()` in the `renderer` function.",
|
|
70
|
-
"value": {
|
|
71
|
-
"type": [
|
|
72
|
-
"string",
|
|
73
|
-
"null",
|
|
74
|
-
"undefined"
|
|
75
|
-
]
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
67
|
{
|
|
79
68
|
"name": "items",
|
|
80
69
|
"description": "Defines a (hierarchical) menu structure for the component.\nIf a menu item has a non-empty `children` set, a sub-menu with the child items is opened\nnext to the parent menu on mouseover, tap or a right arrow keypress.\n\nThe items API can't be used together with a renderer!\n\n#### Example\n\n```javascript\ncontextMenu.items = [\n { text: 'Menu Item 1', theme: 'primary', children:\n [\n { text: 'Menu Item 1-1', checked: true, keepOpen: true },\n { text: 'Menu Item 1-2' }\n ]\n },\n { component: 'hr' },\n { text: 'Menu Item 2', children:\n [\n { text: 'Menu Item 2-1' },\n { text: 'Menu Item 2-2', disabled: true }\n ]\n },\n { text: 'Menu Item 3', disabled: true }\n];\n```",
|
|
@@ -132,6 +121,17 @@
|
|
|
132
121
|
"undefined"
|
|
133
122
|
]
|
|
134
123
|
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "overlayClass",
|
|
127
|
+
"description": "A space-delimited list of CSS class names to set on the overlay element.\nThis property does not affect other CSS class names set manually via JS.\n\nNote, if the CSS class name was set with this property, clearing it will\nremove it from the overlay, even if the same class name was also added\nmanually, e.g. by using `classList.add()` in the `renderer` function.",
|
|
128
|
+
"value": {
|
|
129
|
+
"type": [
|
|
130
|
+
"string",
|
|
131
|
+
"null",
|
|
132
|
+
"undefined"
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
135
|
}
|
|
136
136
|
],
|
|
137
137
|
"events": [
|