@jupyterlab/shortcuts-extension 5.4.0-alpha.3 → 5.4.0-alpha.5

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.
Files changed (38) hide show
  1. package/lib/components/NewShortcutItem.d.ts +44 -0
  2. package/lib/components/NewShortcutItem.js +107 -0
  3. package/lib/components/NewShortcutItem.js.map +1 -0
  4. package/lib/components/ShortcutCustomOptions.d.ts +20 -0
  5. package/lib/components/ShortcutCustomOptions.js +173 -0
  6. package/lib/components/ShortcutCustomOptions.js.map +1 -0
  7. package/lib/components/ShortcutItem.d.ts +9 -2
  8. package/lib/components/ShortcutItem.js +104 -30
  9. package/lib/components/ShortcutItem.js.map +1 -1
  10. package/lib/components/ShortcutList.d.ts +1 -0
  11. package/lib/components/ShortcutList.js +1 -1
  12. package/lib/components/ShortcutList.js.map +1 -1
  13. package/lib/components/ShortcutUI.d.ts +18 -1
  14. package/lib/components/ShortcutUI.js +80 -118
  15. package/lib/components/ShortcutUI.js.map +1 -1
  16. package/lib/components/TopNav.d.ts +2 -0
  17. package/lib/components/TopNav.js +6 -23
  18. package/lib/components/TopNav.js.map +1 -1
  19. package/lib/index.js +12 -7
  20. package/lib/index.js.map +1 -1
  21. package/lib/registry.d.ts +9 -3
  22. package/lib/registry.js +173 -24
  23. package/lib/registry.js.map +1 -1
  24. package/lib/types.d.ts +49 -3
  25. package/lib/types.js.map +1 -1
  26. package/package.json +10 -6
  27. package/src/components/NewShortcutItem.tsx +154 -0
  28. package/src/components/ShortcutCustomOptions.tsx +235 -0
  29. package/src/components/ShortcutItem.tsx +176 -31
  30. package/src/components/ShortcutList.tsx +2 -0
  31. package/src/components/ShortcutUI.tsx +109 -162
  32. package/src/components/TopNav.tsx +17 -33
  33. package/src/index.ts +19 -8
  34. package/src/registry.ts +215 -29
  35. package/src/types.ts +58 -3
  36. package/style/base.css +103 -1
  37. package/style/index.css +3 -0
  38. package/style/index.js +3 -0
@@ -0,0 +1,44 @@
1
+ import type { ISignal } from '@lumino/signaling';
2
+ import type { ICustomOptions, IKeybinding, IShortcutRegistry, IShortcutTarget, IShortcutUI } from '../types';
3
+ import type { IShortcutItemProps } from './ShortcutItem';
4
+ /** Props for ShortcutItem component */
5
+ export interface INewShortcutItemProps {
6
+ findConflictsFor: IShortcutRegistry['findConflictsFor'];
7
+ showSelectors: boolean;
8
+ external: IShortcutUI.IExternalBundle;
9
+ }
10
+ export declare class NewShortcutItem implements IShortcutItemProps {
11
+ constructor(options: INewShortcutItemProps);
12
+ showSelectors: boolean;
13
+ external: IShortcutUI.IExternalBundle;
14
+ get shortcut(): IShortcutTarget;
15
+ get changed(): ISignal<NewShortcutItem, void>;
16
+ /**
17
+ * Reset the shortcut.
18
+ */
19
+ reset: () => void;
20
+ /**
21
+ * Add a new keybinding.
22
+ */
23
+ addKeybinding: (target: IShortcutTarget, keys: string[]) => Promise<void>;
24
+ /**
25
+ * Replace the given keybinding with a new keybinding as defined by given keys.
26
+ */
27
+ replaceKeybinding: (target: IShortcutTarget, keybinding: IKeybinding, keys: string[]) => Promise<void>;
28
+ /**
29
+ * Delete a single keybinding for given shortcut target.
30
+ */
31
+ deleteKeybinding: (target: IShortcutTarget, keybinding: IKeybinding) => Promise<void>;
32
+ /**
33
+ * Reset keybindings for given target to defaults.
34
+ */
35
+ resetKeybindings: (target: IShortcutTarget) => Promise<void>;
36
+ /**
37
+ * Update the selector and args for a user defined shortcut.
38
+ */
39
+ setCustomOptions: (target: IShortcutTarget, options: ICustomOptions) => Promise<boolean>;
40
+ updateCommand: (command: string, category: string) => void;
41
+ private _shortcut;
42
+ findConflictsFor: (keys: string[], selector: string) => IShortcutTarget[];
43
+ private _changed;
44
+ }
@@ -0,0 +1,107 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { Signal } from '@lumino/signaling';
6
+ export class NewShortcutItem {
7
+ constructor(options) {
8
+ /**
9
+ * Reset the shortcut.
10
+ */
11
+ this.reset = () => {
12
+ this._shortcut = {
13
+ id: 'new shortcut',
14
+ command: '',
15
+ keybindings: [],
16
+ selector: 'body',
17
+ category: '',
18
+ args: {}
19
+ };
20
+ this._changed.emit();
21
+ };
22
+ /**
23
+ * Add a new keybinding.
24
+ */
25
+ this.addKeybinding = async (target, keys) => {
26
+ this._shortcut.keybindings.push({ keys, isDefault: false });
27
+ this._changed.emit();
28
+ };
29
+ /**
30
+ * Replace the given keybinding with a new keybinding as defined by given keys.
31
+ */
32
+ this.replaceKeybinding = async (target, keybinding, keys) => {
33
+ //Remove empty keys and delete keybindings if there is no new key.
34
+ keys = keys.filter(key => key !== '');
35
+ if (!keys.length) {
36
+ return this.deleteKeybinding(target, keybinding);
37
+ }
38
+ const index = this._shortcut.keybindings.findIndex(binding => binding.keys === keybinding.keys);
39
+ if (index > -1) {
40
+ this._shortcut.keybindings[index] = { keys, isDefault: false };
41
+ }
42
+ else {
43
+ void this.addKeybinding(target, keys);
44
+ }
45
+ this._changed.emit();
46
+ };
47
+ /**
48
+ * Delete a single keybinding for given shortcut target.
49
+ */
50
+ this.deleteKeybinding = async (target, keybinding) => {
51
+ const index = this._shortcut.keybindings.findIndex(binding => binding.keys === keybinding.keys);
52
+ if (index > -1) {
53
+ this._shortcut.keybindings.splice(index, 1);
54
+ this._changed.emit();
55
+ }
56
+ };
57
+ /**
58
+ * Reset keybindings for given target to defaults.
59
+ */
60
+ this.resetKeybindings = async (target) => {
61
+ this._shortcut = {
62
+ ...this._shortcut,
63
+ keybindings: []
64
+ };
65
+ this._changed.emit();
66
+ };
67
+ /**
68
+ * Update the selector and args for a user defined shortcut.
69
+ */
70
+ this.setCustomOptions = async (target, options) => {
71
+ this._shortcut = {
72
+ ...this._shortcut,
73
+ selector: options.selector,
74
+ args: options.args
75
+ };
76
+ this._changed.emit();
77
+ return true;
78
+ };
79
+ this.updateCommand = (command, category) => {
80
+ this._shortcut = {
81
+ ...this._shortcut,
82
+ command,
83
+ category
84
+ };
85
+ this._changed.emit();
86
+ };
87
+ this._changed = new Signal(this);
88
+ this._shortcut = {
89
+ id: 'new shortcut',
90
+ command: '',
91
+ keybindings: [],
92
+ selector: 'body',
93
+ category: '',
94
+ args: {}
95
+ };
96
+ this.findConflictsFor = options.findConflictsFor;
97
+ this.showSelectors = options.showSelectors;
98
+ this.external = options.external;
99
+ }
100
+ get shortcut() {
101
+ return this._shortcut;
102
+ }
103
+ get changed() {
104
+ return this._changed;
105
+ }
106
+ }
107
+ //# sourceMappingURL=NewShortcutItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NewShortcutItem.js","sourceRoot":"","sources":["../../src/components/NewShortcutItem.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { Dialog } from '@jupyterlab/apputils';
2
+ import { CodeEditor } from '@jupyterlab/codeeditor';
3
+ import type { ITranslator } from '@jupyterlab/translation';
4
+ import type { ICustomOptions, IShortcutTarget } from '../types';
5
+ export declare class CustomOptionsDialog extends Dialog<ICustomOptions> {
6
+ constructor(options: {
7
+ shortcut: IShortcutTarget;
8
+ editorFactory: CodeEditor.Factory;
9
+ translator: ITranslator;
10
+ readOnly: boolean;
11
+ });
12
+ /**
13
+ * Overwrite the keyboard event to prevent the dialog from submitting when
14
+ * pressing Enter in the JSON editor.
15
+ *
16
+ * For all other keys (and Enter outside the JSON editor), delegate to the
17
+ * base dialog handler to preserve standard keyboard behavior.
18
+ */
19
+ protected _evtKeydown(event: KeyboardEvent): void;
20
+ }
@@ -0,0 +1,173 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { Dialog } from '@jupyterlab/apputils';
6
+ import { CodeEditor } from '@jupyterlab/codeeditor';
7
+ import { Widget } from '@lumino/widgets';
8
+ export class CustomOptionsDialog extends Dialog {
9
+ constructor(options) {
10
+ const { shortcut, translator, editorFactory } = options;
11
+ const trans = translator.load('jupyterlab');
12
+ const body = new CustomOptionsDialogBody(shortcut, translator, editorFactory, () => this._checkValidation(), options.readOnly);
13
+ const buttons = [];
14
+ if (options.readOnly) {
15
+ buttons.push(Dialog.cancelButton({ label: trans.__('OK') }));
16
+ }
17
+ else {
18
+ buttons.push(Dialog.cancelButton({ label: trans.__('Cancel') }));
19
+ buttons.push(Dialog.okButton({ label: trans.__('Apply') }));
20
+ }
21
+ super({
22
+ title: options.readOnly
23
+ ? trans.__('Shortcut Options')
24
+ : trans.__('Custom Shortcut Options'),
25
+ body,
26
+ buttons
27
+ });
28
+ }
29
+ /**
30
+ * Overwrite the keyboard event to prevent the dialog from submitting when
31
+ * pressing Enter in the JSON editor.
32
+ *
33
+ * For all other keys (and Enter outside the JSON editor), delegate to the
34
+ * base dialog handler to preserve standard keyboard behavior.
35
+ */
36
+ _evtKeydown(event) {
37
+ if (event.key === 'Enter') {
38
+ // If focus is inside the JSON editor, prevent the dialog from treating
39
+ // Enter as a submit/activate-default-button key.
40
+ const jsonEditorNode = this.node.querySelector('.jp-JSONEditor');
41
+ const activeElement = document.activeElement;
42
+ if (jsonEditorNode instanceof HTMLElement &&
43
+ activeElement instanceof HTMLElement &&
44
+ jsonEditorNode.contains(activeElement)) {
45
+ event.stopPropagation();
46
+ return;
47
+ }
48
+ }
49
+ // Fallback to the default dialog behavior.
50
+ super._evtKeydown(event);
51
+ }
52
+ }
53
+ /**
54
+ * Body widget for the custom options dialog.
55
+ */
56
+ class CustomOptionsDialogBody extends Widget {
57
+ constructor(shortcut, translator, editorFactory, checkValidation, readOnly) {
58
+ var _a, _b;
59
+ super();
60
+ const trans = translator.load('jupyterlab');
61
+ this.addClass('jp-Shortcuts-CustomOptionsDialogBody');
62
+ // Create main container
63
+ const container = document.createElement('div');
64
+ container.className = 'jp-Shortcuts-CustomOptionDialog-container';
65
+ // Selector section
66
+ const selectorLabel = document.createElement('label');
67
+ selectorLabel.textContent = trans.__('Selector:');
68
+ selectorLabel.className = 'jp-Dialog-label';
69
+ this._selectorInput = document.createElement('input');
70
+ this._selectorInput.type = 'text';
71
+ this._selectorInput.disabled = readOnly !== null && readOnly !== void 0 ? readOnly : false;
72
+ this._selectorInput.value = shortcut.selector;
73
+ this._selectorInput.className = 'jp-mod-styled';
74
+ container.appendChild(selectorLabel);
75
+ container.appendChild(this._selectorInput);
76
+ // Args section
77
+ const argsLabel = document.createElement('label');
78
+ argsLabel.textContent = trans.__('Arguments:');
79
+ argsLabel.className = 'jp-Dialog-label';
80
+ container.appendChild(argsLabel);
81
+ if (readOnly) {
82
+ const argsPre = document.createElement('pre');
83
+ argsPre.textContent = JSON.stringify((_a = shortcut.args) !== null && _a !== void 0 ? _a : {}, undefined, 2);
84
+ container.appendChild(argsPre);
85
+ }
86
+ else {
87
+ // Create dedicated JSON editor widget
88
+ this._editorWidget = new JSONEditorWidget((_b = shortcut.args) !== null && _b !== void 0 ? _b : {}, editorFactory, checkValidation);
89
+ container.appendChild(this._editorWidget.node);
90
+ }
91
+ this.node.appendChild(container);
92
+ }
93
+ /**
94
+ * Get the value from the dialog body.
95
+ */
96
+ getValue() {
97
+ var _a, _b;
98
+ const argsText = (_b = (_a = this._editorWidget) === null || _a === void 0 ? void 0 : _a.getJSON()) !== null && _b !== void 0 ? _b : '{}';
99
+ let args;
100
+ try {
101
+ args = JSON.parse(argsText);
102
+ }
103
+ catch (error) {
104
+ console.error(`Invalid JSON in arguments: ${error instanceof Error ? error.message : String(error)}`);
105
+ args = {};
106
+ }
107
+ return {
108
+ selector: this._selectorInput.value,
109
+ args
110
+ };
111
+ }
112
+ /**
113
+ * Dispose resources.
114
+ */
115
+ dispose() {
116
+ var _a;
117
+ (_a = this._editorWidget) === null || _a === void 0 ? void 0 : _a.dispose();
118
+ super.dispose();
119
+ }
120
+ }
121
+ /**
122
+ * A dedicated widget for JSON editing with validation.
123
+ */
124
+ class JSONEditorWidget extends Widget {
125
+ constructor(initialArgs, editorFactory, checkValidation) {
126
+ super();
127
+ /**
128
+ * Validate the JSON sources.
129
+ */
130
+ this._validateJSON = () => {
131
+ try {
132
+ const text = this._editor.model.sharedModel.getSource();
133
+ JSON.parse(text);
134
+ this.node.classList.remove('jp-mod-error');
135
+ }
136
+ catch (error) {
137
+ this.node.classList.add('jp-mod-error');
138
+ }
139
+ this._checkValidation();
140
+ };
141
+ this.addClass('jp-JSONEditor');
142
+ this._checkValidation = checkValidation;
143
+ const editorHostNode = document.createElement('div');
144
+ editorHostNode.className = 'jp-JSONEditor-host';
145
+ const model = new CodeEditor.Model({ mimeType: 'application/json' });
146
+ const content = JSON.stringify(initialArgs, null, 2);
147
+ model.sharedModel.setSource(content);
148
+ this._editor = editorFactory({
149
+ host: editorHostNode,
150
+ model,
151
+ config: {
152
+ readOnly: false
153
+ }
154
+ });
155
+ this.node.appendChild(editorHostNode);
156
+ this._editor.model.sharedModel.changed.connect(this._validateJSON, this);
157
+ }
158
+ /**
159
+ * Get the current JSON text.
160
+ */
161
+ getJSON() {
162
+ return this._editor.model.sharedModel.getSource();
163
+ }
164
+ /**
165
+ * Dispose of the resources.
166
+ */
167
+ dispose() {
168
+ this._editor.model.sharedModel.changed.disconnect(this._validateJSON, this);
169
+ this._editor.dispose();
170
+ super.dispose();
171
+ }
172
+ }
173
+ //# sourceMappingURL=ShortcutCustomOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShortcutCustomOptions.js","sourceRoot":"","sources":["../../src/components/ShortcutCustomOptions.tsx"],"names":[],"mappings":"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"}
@@ -9,8 +9,14 @@ export interface IShortcutItemProps {
9
9
  resetKeybindings: IShortcutUI['resetKeybindings'];
10
10
  deleteKeybinding: IShortcutUI['deleteKeybinding'];
11
11
  findConflictsFor: IShortcutRegistry['findConflictsFor'];
12
+ setCustomOptions: IShortcutUI['setCustomOptions'];
12
13
  showSelectors: boolean;
13
14
  external: IShortcutUI.IExternalBundle;
15
+ newShortcutUtils?: {
16
+ searchQuery: string;
17
+ updateCommand: (command: string, category: string) => void;
18
+ saveShortcut: () => Promise<void>;
19
+ };
14
20
  }
15
21
  /** State for ShortcutItem component */
16
22
  export interface IShortcutItemState {
@@ -26,7 +32,7 @@ export declare class ShortcutItem extends React.Component<IShortcutItemProps, IS
26
32
  private _onActionRequested;
27
33
  /** Toggle display state of input box */
28
34
  private toggleInputNew;
29
- /** Transform special key names into unicode characters */
35
+ /** Transform special key names into unicode characters for Mac */
30
36
  toSymbols: (value: string) => string;
31
37
  getCategoryCell(): JSX.Element;
32
38
  getLabelCell(): JSX.Element;
@@ -36,7 +42,7 @@ export declare class ShortcutItem extends React.Component<IShortcutItemProps, IS
36
42
  getClassNameForShortCuts(nonEmptyBindings: IKeybinding[]): string;
37
43
  toggleInputReplaceMethod(location: number): void;
38
44
  getDisplayReplaceInput(location: number): boolean;
39
- getOrDiplayIfNeeded(force: boolean): JSX.Element;
45
+ getOrDisplayIfNeeded(force: boolean): JSX.Element;
40
46
  getShortCutAsInput(binding: IKeybinding, location: number): JSX.Element;
41
47
  getShortCutForDisplayOnly(binding: IKeybinding): JSX.Element[];
42
48
  isLocationBeingEdited(location: number): boolean;
@@ -53,6 +59,7 @@ export declare class ShortcutItem extends React.Component<IShortcutItemProps, IS
53
59
  * Create a unique conflict identifier.
54
60
  */
55
61
  private _conflictId;
62
+ private _getFilteredCommands;
56
63
  private get _nonEmptyBindings();
57
64
  render(): JSX.Element;
58
65
  private _trans;
@@ -2,9 +2,19 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ import { Button } from '@jupyter/react-components';
6
+ import { checkIcon, editIcon, HTMLSelect, infoIcon } from '@jupyterlab/ui-components';
5
7
  import { Platform } from '@lumino/domutils';
6
8
  import * as React from 'react';
9
+ import { CustomOptionsDialog } from './ShortcutCustomOptions';
7
10
  import { CONFLICT_CONTAINER_CLASS, ShortcutInput } from './ShortcutInput';
11
+ import { ShortcutRegistry } from '../registry';
12
+ const MAC_SYMBOLS = {
13
+ Ctrl: '⌃',
14
+ Alt: '⌥',
15
+ Shift: '⇧',
16
+ Accel: '⌘'
17
+ };
8
18
  /** React component for each command shortcut item */
9
19
  export class ShortcutItem extends React.Component {
10
20
  constructor(props) {
@@ -17,28 +27,18 @@ export class ShortcutItem extends React.Component {
17
27
  conflicts: new Map()
18
28
  });
19
29
  };
20
- /** Transform special key names into unicode characters */
30
+ /** Transform special key names into unicode characters for Mac */
21
31
  this.toSymbols = (value) => {
22
- return value.split(' ').reduce((result, key) => {
23
- if (key === 'Ctrl') {
24
- return (result + ' ').trim();
25
- }
26
- else if (key === 'Alt') {
27
- return (result + ' ⌥').trim();
28
- }
29
- else if (key === 'Shift') {
30
- return (result + ' ⇧').trim();
31
- }
32
- else if (key === 'Accel' && Platform.IS_MAC) {
33
- return (result + ' ⌘').trim();
34
- }
35
- else if (key === 'Accel') {
36
- return (result + ' ⌃').trim();
37
- }
38
- else {
39
- return (result + ' ' + key).trim();
40
- }
41
- }, '');
32
+ if (!Platform.IS_MAC) {
33
+ return value
34
+ .split(' ')
35
+ .map(key => (key === 'Accel' ? 'Ctrl' : key))
36
+ .join(' ');
37
+ }
38
+ return value
39
+ .split(' ')
40
+ .map(key => { var _a; return (_a = MAC_SYMBOLS[key]) !== null && _a !== void 0 ? _a : key; })
41
+ .join(' ');
42
42
  };
43
43
  this._trans = this.props.external.translator.load('jupyterlab');
44
44
  this.state = {
@@ -75,17 +75,62 @@ export class ShortcutItem extends React.Component {
75
75
  }
76
76
  getLabelCell() {
77
77
  var _a;
78
- return (React.createElement("div", { className: "jp-Shortcuts-Cell" },
79
- React.createElement("div", { className: "jp-label" }, (_a = this.props.shortcut.label) !== null && _a !== void 0 ? _a : this._trans.__('(Command label missing)'))));
78
+ if (this.props.newShortcutUtils) {
79
+ const filteredShortcuts = this._getFilteredCommands();
80
+ return (React.createElement("div", { className: "jp-Shortcuts-Cell" },
81
+ React.createElement(HTMLSelect, { value: this.props.shortcut.command, options: [
82
+ { value: '', label: this._trans.__('Select a command') },
83
+ ...filteredShortcuts.map(shortcut => ({
84
+ value: shortcut.command,
85
+ label: `${shortcut.category}: ${shortcut.label}`
86
+ }))
87
+ ], onChange: e => {
88
+ var _a, _b, _c;
89
+ const shortcut = filteredShortcuts.find(shortcut => shortcut.command === e.target.value);
90
+ (_a = this.props.newShortcutUtils) === null || _a === void 0 ? void 0 : _a.updateCommand((_b = shortcut === null || shortcut === void 0 ? void 0 : shortcut.command) !== null && _b !== void 0 ? _b : '', (_c = shortcut === null || shortcut === void 0 ? void 0 : shortcut.category) !== null && _c !== void 0 ? _c : '');
91
+ } })));
92
+ }
93
+ else {
94
+ return (React.createElement("div", { className: "jp-Shortcuts-Cell" },
95
+ React.createElement("div", { className: "jp-label" }, (_a = this.props.shortcut.label) !== null && _a !== void 0 ? _a : this._trans.__('(Command label missing)'))));
96
+ }
80
97
  }
81
98
  getResetShortCutLink() {
82
- return (React.createElement("a", { className: "jp-Shortcuts-Reset", onClick: () => this.props.resetKeybindings(this.props.shortcut) }, this._trans.__('Reset')));
99
+ return (React.createElement("a", { className: "jp-Shortcuts-Reset", onClick: () => this.props.resetKeybindings(this.props.shortcut) }, this.props.shortcut.userDefined
100
+ ? this._trans.__('Delete')
101
+ : this._trans.__('Reset')));
83
102
  }
84
103
  getSourceCell() {
104
+ var _a;
85
105
  const allDefault = this.props.shortcut.keybindings.every(binding => binding.isDefault);
106
+ const editable = this.props.shortcut.userDefined || !!this.props.newShortcutUtils;
107
+ const showOptionsButtonTitle = editable
108
+ ? this._trans.__('Custom options')
109
+ : this._trans.__('Show options');
86
110
  return (React.createElement("div", { className: "jp-Shortcuts-Cell" },
87
- React.createElement("div", { className: "jp-Shortcuts-SourceCell" }, allDefault ? this._trans.__('Default') : this._trans.__('Custom')),
88
- !allDefault ? this.getResetShortCutLink() : ''));
111
+ !this.props.newShortcutUtils && (React.createElement("div", { className: "jp-Shortcuts-SourceCell" }, allDefault ? this._trans.__('Default') : this._trans.__('Custom'))),
112
+ !allDefault ? this.getResetShortCutLink() : '',
113
+ this.props.external.editorFactory && (React.createElement(Button, { className: "jp-mod-styled jp-mod-reject jp-Shortcuts-CustomOptions", onClick: async () => {
114
+ if (!this.props.external.editorFactory) {
115
+ console.error('Cannot build the custom options form');
116
+ return;
117
+ }
118
+ const dialog = new CustomOptionsDialog({
119
+ shortcut: this.props.shortcut,
120
+ translator: this.props.external.translator,
121
+ editorFactory: this.props.external.editorFactory,
122
+ readOnly: !editable
123
+ });
124
+ const result = await dialog.launch();
125
+ if (result.button.accept && editable && result.value) {
126
+ await this.props.setCustomOptions(this.props.shortcut, result.value);
127
+ }
128
+ }, title: showOptionsButtonTitle, "aria-label": showOptionsButtonTitle, appearance: 'neutral' }, editable ? (React.createElement(editIcon.react, { tag: null })) : (React.createElement(infoIcon.react, { tag: null })))),
129
+ !!this.props.newShortcutUtils && (React.createElement(React.Fragment, null,
130
+ React.createElement(Button, { className: "jp-mod-styled jp-mod-accept jp-Shortcuts-SaveNew", onClick: (_a = this.props.newShortcutUtils) === null || _a === void 0 ? void 0 : _a.saveShortcut, title: this._trans.__('Save shortcut'), "aria-label": this._trans.__('Save shortcut'), appearance: "neutral", disabled: !this.props.shortcut.command ||
131
+ !this.props.shortcut.keybindings.length ||
132
+ this.props.shortcut.keybindings.every(binding => !binding.keys || binding.keys.length === 0) },
133
+ React.createElement(checkIcon.react, { tag: null }))))));
89
134
  }
90
135
  getOptionalSelectorCell() {
91
136
  return this.props.showSelectors ? (React.createElement("div", { className: "jp-Shortcuts-Cell" },
@@ -117,7 +162,7 @@ export class ShortcutItem extends React.Component {
117
162
  getDisplayReplaceInput(location) {
118
163
  return this.state.displayReplaceInput[location];
119
164
  }
120
- getOrDiplayIfNeeded(force) {
165
+ getOrDisplayIfNeeded(force) {
121
166
  const classes = ['jp-Shortcuts-Or'];
122
167
  if (force || this.state.displayNewInput) {
123
168
  classes.push('jp-Shortcuts-Or-Forced');
@@ -133,7 +178,11 @@ export class ShortcutItem extends React.Component {
133
178
  }
134
179
  getShortCutForDisplayOnly(binding) {
135
180
  return binding.keys.map((keyboardKey, index) => (React.createElement("div", { className: "jp-Shortcuts-ShortcutKeysContainer", key: index },
136
- React.createElement("div", { className: "jp-Shortcuts-ShortcutKeys" }, this.toSymbols(keyboardKey)),
181
+ React.createElement("div", { className: "jp-Shortcuts-ShortcutKeys" }, this.toSymbols(keyboardKey)
182
+ .split(' ')
183
+ .map((keyPart, keyPartIndex, keyParts) => (React.createElement(React.Fragment, { key: `${index}-${keyPart}-${keyPartIndex}` },
184
+ React.createElement("span", { className: "jp-ContextualShortcut-Key" }, keyPart),
185
+ keyPartIndex + 1 < keyParts.length ? ' + ' : null)))),
137
186
  index + 1 < binding.keys.length ? (React.createElement("div", { className: "jp-Shortcuts-Comma" }, ",")) : null)));
138
187
  }
139
188
  isLocationBeingEdited(location) {
@@ -146,7 +195,7 @@ export class ShortcutItem extends React.Component {
146
195
  : this.getShortCutForDisplayOnly(binding),
147
196
  !(index === this._nonEmptyBindings.length - 1 &&
148
197
  Object.values(this.state.displayReplaceInput).some(Boolean)) &&
149
- this.getOrDiplayIfNeeded(index < this._nonEmptyBindings.length - 1)));
198
+ this.getOrDisplayIfNeeded(index < this._nonEmptyBindings.length - 1)));
150
199
  }
151
200
  getAddLink() {
152
201
  return (React.createElement("a", { className: !this.state.displayNewInput ? 'jp-Shortcuts-Plus' : '', onClick: () => {
@@ -220,12 +269,37 @@ export class ShortcutItem extends React.Component {
220
269
  '_' +
221
270
  conflict.conflictsWith.map(target => target.id).join(''));
222
271
  }
272
+ _getFilteredCommands() {
273
+ var _a, _b;
274
+ const registry = new ShortcutRegistry({
275
+ commandRegistry: this.props.external.commandRegistry,
276
+ allCommands: true
277
+ });
278
+ const filteredShortcuts = ShortcutRegistry.matchItems(registry, (_b = (_a = this.props.newShortcutUtils) === null || _a === void 0 ? void 0 : _a.searchQuery) !== null && _b !== void 0 ? _b : '')
279
+ .map((item) => item.item)
280
+ .filter(target => !target.command.startsWith('__internal:'));
281
+ filteredShortcuts.sort((a, b) => {
282
+ var _a, _b;
283
+ const compareA = a.category;
284
+ const compareB = b.category;
285
+ const compareResult = compareA.localeCompare(compareB);
286
+ if (compareResult) {
287
+ return compareResult;
288
+ }
289
+ else {
290
+ const aLabel = (_a = a['label']) !== null && _a !== void 0 ? _a : '';
291
+ const bLabel = (_b = b['label']) !== null && _b !== void 0 ? _b : '';
292
+ return aLabel.localeCompare(bLabel);
293
+ }
294
+ });
295
+ return filteredShortcuts;
296
+ }
223
297
  get _nonEmptyBindings() {
224
298
  return this.props.shortcut.keybindings.filter(binding => binding.keys.filter(k => k != '').length !== 0);
225
299
  }
226
300
  render() {
227
301
  return (React.createElement(React.Fragment, null,
228
- React.createElement("div", { className: "jp-Shortcuts-Row", "data-shortcut": this.props.shortcut.id },
302
+ React.createElement("div", { className: `jp-Shortcuts-Row${this.props.newShortcutUtils ? ' jp-Shortcuts-Row-newShortcut' : ''}`, "data-shortcut": this.props.shortcut.id },
229
303
  this.getCategoryCell(),
230
304
  this.getLabelCell(),
231
305
  this.getShortCutsCell(this._nonEmptyBindings),