commandpalette.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /* -----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. import { Token } from '@lumino/coreutils';
  6. import { IDisposable } from '@lumino/disposable';
  7. import { Message } from '@lumino/messaging';
  8. import { CommandPalette, Panel, Widget } from '@lumino/widgets';
  9. import { searchIcon } from '@jupyterlab/ui-components';
  10. /* tslint:disable */
  11. /**
  12. * The command palette token.
  13. */
  14. export const ICommandPalette = new Token<ICommandPalette>(
  15. '@jupyterlab/apputils:ICommandPalette'
  16. );
  17. /* tslint:enable */
  18. /**
  19. * The options for creating a command palette item.
  20. */
  21. export interface IPaletteItem extends CommandPalette.IItemOptions {}
  22. /**
  23. * The interface for a Jupyter Lab command palette.
  24. */
  25. export interface ICommandPalette {
  26. /**
  27. * The placeholder text of the command palette's search input.
  28. */
  29. placeholder: string;
  30. /**
  31. * Activate the command palette for user input.
  32. */
  33. activate(): void;
  34. /**
  35. * Add a command item to the command palette.
  36. *
  37. * @param options - The options for creating the command item.
  38. *
  39. * @returns A disposable that will remove the item from the palette.
  40. */
  41. addItem(options: IPaletteItem): IDisposable;
  42. }
  43. /**
  44. * Class name identifying the input group with search icon.
  45. */
  46. const SEARCH_ICON_GROUP_CLASS = 'jp-SearchIconGroup';
  47. /**
  48. * Wrap the command palette in a modal to make it more usable.
  49. */
  50. export class ModalCommandPalette extends Panel {
  51. constructor(options: ModalCommandPalette.IOptions) {
  52. super();
  53. this.addClass('jp-ModalCommandPalette');
  54. this.id = 'modal-command-palette';
  55. this.palette = options.commandPalette;
  56. this._commandPalette.commands.commandExecuted.connect(() => {
  57. if (this.isAttached && this.isVisible) {
  58. this.hideAndReset();
  59. }
  60. });
  61. // required to properly receive blur and focus events;
  62. // selection of items with mouse may not work without this.
  63. this.node.tabIndex = 0;
  64. }
  65. get palette(): CommandPalette {
  66. return this._commandPalette;
  67. }
  68. set palette(value: CommandPalette) {
  69. this._commandPalette = value;
  70. if (!this.searchIconGroup) {
  71. this._commandPalette.inputNode.insertAdjacentElement(
  72. 'afterend',
  73. this.createSearchIconGroup()
  74. );
  75. }
  76. this.addWidget(value);
  77. this.hideAndReset();
  78. }
  79. attach(): void {
  80. Widget.attach(this, document.body);
  81. }
  82. detach(): void {
  83. Widget.detach(this);
  84. }
  85. /**
  86. * Hide the modal command palette and reset its search.
  87. */
  88. hideAndReset(): void {
  89. this.hide();
  90. this._commandPalette.inputNode.value = '';
  91. this._commandPalette.refresh();
  92. }
  93. /**
  94. * Handle incoming events.
  95. */
  96. handleEvent(event: Event): void {
  97. switch (event.type) {
  98. case 'keydown':
  99. this._evtKeydown(event as KeyboardEvent);
  100. break;
  101. case 'blur': {
  102. // if the focus shifted outside of this DOM element, hide and reset.
  103. if (
  104. // focus went away from child element
  105. this.node.contains(event.target as HTMLElement) &&
  106. // and it did NOT go to another child element but someplace else
  107. !this.node.contains(
  108. (event as MouseEvent).relatedTarget as HTMLElement
  109. )
  110. ) {
  111. event.stopPropagation();
  112. this.hideAndReset();
  113. }
  114. break;
  115. }
  116. case 'contextmenu':
  117. event.preventDefault();
  118. event.stopPropagation();
  119. break;
  120. default:
  121. break;
  122. }
  123. }
  124. /**
  125. * Find the element with search icon group.
  126. */
  127. protected get searchIconGroup(): HTMLDivElement | undefined {
  128. return this._commandPalette.node.getElementsByClassName(
  129. SEARCH_ICON_GROUP_CLASS
  130. )[0] as HTMLDivElement;
  131. }
  132. /**
  133. * Create element with search icon group.
  134. */
  135. protected createSearchIconGroup(): HTMLDivElement {
  136. const inputGroup = document.createElement('div');
  137. inputGroup.classList.add(SEARCH_ICON_GROUP_CLASS);
  138. searchIcon.render(inputGroup);
  139. return inputGroup;
  140. }
  141. /**
  142. * A message handler invoked on an `'after-attach'` message.
  143. */
  144. protected onAfterAttach(msg: Message): void {
  145. this.node.addEventListener('keydown', this, true);
  146. this.node.addEventListener('contextmenu', this, true);
  147. }
  148. /**
  149. * A message handler invoked on an `'after-detach'` message.
  150. */
  151. protected onAfterDetach(msg: Message): void {
  152. this.node.removeEventListener('keydown', this, true);
  153. this.node.removeEventListener('contextmenu', this, true);
  154. }
  155. protected onBeforeHide(msg: Message): void {
  156. document.removeEventListener('blur', this, true);
  157. }
  158. protected onAfterShow(msg: Message): void {
  159. document.addEventListener('blur', this, true);
  160. }
  161. /**
  162. * A message handler invoked on an `'activate-request'` message.
  163. */
  164. protected onActivateRequest(msg: Message): void {
  165. if (this.isAttached) {
  166. this.show();
  167. this._commandPalette.activate();
  168. }
  169. }
  170. /**
  171. * Handle the `'keydown'` event for the widget.
  172. */
  173. protected _evtKeydown(event: KeyboardEvent): void {
  174. // Check for escape key
  175. switch (event.keyCode) {
  176. case 27: // Escape.
  177. event.stopPropagation();
  178. event.preventDefault();
  179. this.hideAndReset();
  180. break;
  181. default:
  182. break;
  183. }
  184. }
  185. private _commandPalette: CommandPalette;
  186. }
  187. export namespace ModalCommandPalette {
  188. export interface IOptions {
  189. commandPalette: CommandPalette;
  190. }
  191. }