0c8a71add2a31103bfd32b73dc408749430d0a7324ed68434a4765ca4566893ad42f7f7bd25d1c8615ab658a87651c1164a10fb3376128ffd418b294707450 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { $, addDisposableListener, append, EventHelper, EventType } from '../../dom.js';
  6. import { StandardKeyboardEvent } from '../../keyboardEvent.js';
  7. import { EventType as GestureEventType, Gesture } from '../../touch.js';
  8. import { ActionRunner } from '../../../common/actions.js';
  9. import { Emitter } from '../../../common/event.js';
  10. import './dropdown.css';
  11. export class BaseDropdown extends ActionRunner {
  12. constructor(container, options) {
  13. super();
  14. this._onDidChangeVisibility = this._register(new Emitter());
  15. this.onDidChangeVisibility = this._onDidChangeVisibility.event;
  16. this._element = append(container, $('.monaco-dropdown'));
  17. this._label = append(this._element, $('.dropdown-label'));
  18. let labelRenderer = options.labelRenderer;
  19. if (!labelRenderer) {
  20. labelRenderer = (container) => {
  21. container.textContent = options.label || '';
  22. return null;
  23. };
  24. }
  25. for (const event of [EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap]) {
  26. this._register(addDisposableListener(this.element, event, e => EventHelper.stop(e, true))); // prevent default click behaviour to trigger
  27. }
  28. for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) {
  29. this._register(addDisposableListener(this._label, event, e => {
  30. if (e instanceof MouseEvent && (e.detail > 1 || e.button !== 0)) {
  31. // prevent right click trigger to allow separate context menu (https://github.com/microsoft/vscode/issues/151064)
  32. // prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363)
  33. return;
  34. }
  35. if (this.visible) {
  36. this.hide();
  37. }
  38. else {
  39. this.show();
  40. }
  41. }));
  42. }
  43. this._register(addDisposableListener(this._label, EventType.KEY_UP, e => {
  44. const event = new StandardKeyboardEvent(e);
  45. if (event.equals(3 /* KeyCode.Enter */) || event.equals(10 /* KeyCode.Space */)) {
  46. EventHelper.stop(e, true); // https://github.com/microsoft/vscode/issues/57997
  47. if (this.visible) {
  48. this.hide();
  49. }
  50. else {
  51. this.show();
  52. }
  53. }
  54. }));
  55. const cleanupFn = labelRenderer(this._label);
  56. if (cleanupFn) {
  57. this._register(cleanupFn);
  58. }
  59. this._register(Gesture.addTarget(this._label));
  60. }
  61. get element() {
  62. return this._element;
  63. }
  64. show() {
  65. if (!this.visible) {
  66. this.visible = true;
  67. this._onDidChangeVisibility.fire(true);
  68. }
  69. }
  70. hide() {
  71. if (this.visible) {
  72. this.visible = false;
  73. this._onDidChangeVisibility.fire(false);
  74. }
  75. }
  76. dispose() {
  77. super.dispose();
  78. this.hide();
  79. if (this.boxContainer) {
  80. this.boxContainer.remove();
  81. this.boxContainer = undefined;
  82. }
  83. if (this.contents) {
  84. this.contents.remove();
  85. this.contents = undefined;
  86. }
  87. if (this._label) {
  88. this._label.remove();
  89. this._label = undefined;
  90. }
  91. }
  92. }
  93. export class DropdownMenu extends BaseDropdown {
  94. constructor(container, options) {
  95. super(container, options);
  96. this._actions = [];
  97. this._contextMenuProvider = options.contextMenuProvider;
  98. this.actions = options.actions || [];
  99. this.actionProvider = options.actionProvider;
  100. this.menuClassName = options.menuClassName || '';
  101. this.menuAsChild = !!options.menuAsChild;
  102. }
  103. set menuOptions(options) {
  104. this._menuOptions = options;
  105. }
  106. get menuOptions() {
  107. return this._menuOptions;
  108. }
  109. get actions() {
  110. if (this.actionProvider) {
  111. return this.actionProvider.getActions();
  112. }
  113. return this._actions;
  114. }
  115. set actions(actions) {
  116. this._actions = actions;
  117. }
  118. show() {
  119. super.show();
  120. this.element.classList.add('active');
  121. this._contextMenuProvider.showContextMenu({
  122. getAnchor: () => this.element,
  123. getActions: () => this.actions,
  124. getActionsContext: () => this.menuOptions ? this.menuOptions.context : null,
  125. getActionViewItem: action => this.menuOptions && this.menuOptions.actionViewItemProvider ? this.menuOptions.actionViewItemProvider(action) : undefined,
  126. getKeyBinding: action => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : undefined,
  127. getMenuClassName: () => this.menuClassName,
  128. onHide: () => this.onHide(),
  129. actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
  130. anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : 0 /* AnchorAlignment.LEFT */,
  131. domForShadowRoot: this.menuAsChild ? this.element : undefined
  132. });
  133. }
  134. hide() {
  135. super.hide();
  136. }
  137. onHide() {
  138. this.hide();
  139. this.element.classList.remove('active');
  140. }
  141. }