UNPKG

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