@vaadin/context-menu 24.2.0-beta2 → 24.2.0-beta3

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.
@@ -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 { ItemsMixin } from './vaadin-contextmenu-items-mixin.js';
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 OverlayClassMixin(
211
- ControllerMixin(ElementMixin(ThemePropertyMixin(ItemsMixin(PolymerElement)))),
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
- * Tag name prefix used by overlay, list-box and items.
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', () => this.items && this.close());
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.2.0-beta2",
4
+ "version": "24.2.0-beta3",
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.2.0-beta2/#/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.2.0-beta2/#/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.2.0-beta2/#/elements/vaadin-list-box).\n\nNote: the `theme` attribute value set on `<vaadin-context-menu>` is\npropagated to the internal components listed above.",
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.2.0-beta3/#/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.2.0-beta3/#/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.2.0-beta3/#/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": [