chrome-devtools-frontend
Version:
Chrome DevTools UI
645 lines (588 loc) • 25.6 kB
text/typescript
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/no-imperative-dom-api */
import '../../ui/components/cards/cards.js';
import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as IconButton from '../../ui/components/icon_button/icon_button.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import keybindsSettingsTabStyles from './keybindsSettingsTab.css.js';
import settingsScreenStyles from './settingsScreen.css.js';
const UIStrings = {
/**
*@description Text for keyboard shortcuts
*/
shortcuts: 'Shortcuts',
/**
*@description Text appearing before a select control offering users their choice of keyboard shortcut presets.
*/
matchShortcutsFromPreset: 'Shortcut preset',
/**
*@description Screen reader label for list of keyboard shortcuts in settings
*/
keyboardShortcutsList: 'Keyboard shortcuts list',
/**
*@description Screen reader label for an icon denoting a shortcut that has been changed from its default
*/
shortcutModified: 'Shortcut modified',
/**
*@description Screen reader label for an empty shortcut cell in custom shortcuts settings tab
*/
noShortcutForAction: 'No shortcut for action',
/**
*@description Link text in the settings pane to add another shortcut for an action
*/
addAShortcut: 'Add a shortcut',
/**
*@description Label for a button in the settings pane that confirms changes to a keyboard shortcut
*/
confirmChanges: 'Confirm changes',
/**
*@description Label for a button in the settings pane that discards changes to the shortcut being edited
*/
discardChanges: 'Discard changes',
/**
*@description Label for a button in the settings pane that removes a keyboard shortcut.
*/
removeShortcut: 'Remove shortcut',
/**
*@description Label for a button in the settings pane that edits a keyboard shortcut
*/
editShortcut: 'Edit shortcut',
/**
*@description Message shown in settings when the user inputs a modifier-only shortcut such as Ctrl+Shift.
*/
shortcutsCannotContainOnly: 'Shortcuts cannot contain only modifier keys.',
/**
*@description Messages shown in shortcuts settings when the user inputs a shortcut that is already in use.
*@example {Performance} PH1
*@example {Start/stop recording} PH2
*/
thisShortcutIsInUseByS: 'This shortcut is in use by {PH1}: {PH2}.',
/**
*@description Message shown in settings when to restore default shortcuts.
*/
RestoreDefaultShortcuts: 'Restore default shortcuts',
/**
*@description Message shown in settings to show the full list of keyboard shortcuts.
*/
FullListOfDevtoolsKeyboard: 'Full list of DevTools keyboard shortcuts and gestures',
/**
*@description Label for a button in the shortcut editor that resets all shortcuts for the current action.
*/
ResetShortcutsForAction: 'Reset shortcuts for action',
/**
*@description Screen reader announcement for shortcut removed
*@example {Start/stop recording} PH1
*/
shortcutRemoved: '{PH1} Shortcut removed',
/**
*@description Screen reader announcment for shortcut restored to default
*/
shortcutChangesRestored: 'Changes to shortcut restored to default',
/**
*@description Screen reader announcment for applied short cut changes
*/
shortcutChangesApplied: 'Changes to shortcut applied',
/**
*@description Screen reader announcment for discarded short cut changes
*/
shortcutChangesDiscared: 'Changes to shortcut discarded',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/settings/KeybindsSettingsTab.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class KeybindsSettingsTab extends UI.Widget.VBox implements UI.ListControl.ListDelegate<KeybindsItem> {
private readonly items: UI.ListModel.ListModel<KeybindsItem>;
private list: UI.ListControl.ListControl<string|UI.ActionRegistration.Action>;
private editingItem: UI.ActionRegistration.Action|null;
private editingRow: ShortcutListItem|null;
constructor() {
super(true);
this.registerRequiredCSS(keybindsSettingsTabStyles, settingsScreenStyles);
this.element.setAttribute('jslog', `${VisualLogging.pane('keybinds')}`);
const settingsContent =
this.contentElement.createChild('div', 'settings-card-container-wrapper').createChild('div');
settingsContent.classList.add('settings-card-container');
const keybindsSetSetting = Common.Settings.Settings.instance().moduleSetting('active-keybind-set');
const userShortcutsSetting = Common.Settings.Settings.instance().moduleSetting('user-shortcuts');
keybindsSetSetting.addChangeListener(this.update, this);
const keybindsSetSelect =
UI.SettingsUI.createControlForSetting(keybindsSetSetting, i18nString(UIStrings.matchShortcutsFromPreset));
const card = settingsContent.createChild('devtools-card');
card.heading = i18nString(UIStrings.shortcuts);
if (keybindsSetSelect) {
keybindsSetSelect.classList.add('keybinds-set-select');
}
this.items = new UI.ListModel.ListModel();
this.list = new UI.ListControl.ListControl(this.items, this, UI.ListControl.ListMode.NonViewport);
this.list.element.classList.add('shortcut-list');
this.items.replaceAll(this.createListItems());
UI.ARIAUtils.markAsList(this.list.element);
UI.ARIAUtils.setLabel(this.list.element, i18nString(UIStrings.keyboardShortcutsList));
const footer = document.createElement('div');
footer.classList.add('keybinds-footer');
const docsLink = UI.XLink.XLink.create(
'https://developer.chrome.com/docs/devtools/shortcuts/', i18nString(UIStrings.FullListOfDevtoolsKeyboard),
undefined, undefined, 'learn-more');
docsLink.classList.add('docs-link');
footer.appendChild(docsLink);
const restoreDefaultShortcutsButton =
UI.UIUtils.createTextButton(i18nString(UIStrings.RestoreDefaultShortcuts), () => {
userShortcutsSetting.set([]);
keybindsSetSetting.set(UI.ShortcutRegistry.DefaultShortcutSetting);
}, {jslogContext: 'restore-default-shortcuts'});
footer.appendChild(restoreDefaultShortcutsButton);
this.editingItem = null;
this.editingRow = null;
if (keybindsSetSelect) {
card.append(keybindsSetSelect);
}
card.append(this.list.element, footer);
this.update();
}
createElementForItem(item: KeybindsItem): Element {
const element = document.createElement('div');
let itemContent;
if (typeof item === 'string') {
itemContent = element;
itemContent.classList.add('keybinds-category-header');
itemContent.textContent = UI.ActionRegistration.getLocalizedActionCategory(item);
UI.ARIAUtils.setLevel(itemContent, 1);
} else {
const listItem = new ShortcutListItem(item, this, item === this.editingItem);
itemContent = listItem.element;
UI.ARIAUtils.setLevel(itemContent, 2);
if (item === this.editingItem) {
this.editingRow = listItem;
}
itemContent.classList.add('keybinds-list-item');
element.classList.add('keybinds-list-item-wrapper');
element.appendChild(itemContent);
}
UI.ARIAUtils.markAsListitem(itemContent);
itemContent.tabIndex = item === this.list.selectedItem() && item !== this.editingItem ? 0 : -1;
return element;
}
commitChanges(
item: UI.ActionRegistration.Action,
editedShortcuts: Map<UI.KeyboardShortcut.KeyboardShortcut, UI.KeyboardShortcut.Descriptor[]|null>): void {
for (const [originalShortcut, newDescriptors] of editedShortcuts) {
if (originalShortcut.type !== UI.KeyboardShortcut.Type.UNSET_SHORTCUT) {
UI.ShortcutRegistry.ShortcutRegistry.instance().removeShortcut(originalShortcut);
if (!newDescriptors) {
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ShortcutRemoved);
}
}
if (newDescriptors) {
UI.ShortcutRegistry.ShortcutRegistry.instance().registerUserShortcut(
originalShortcut.changeKeys(newDescriptors).changeType(UI.KeyboardShortcut.Type.USER_SHORTCUT));
if (originalShortcut.type === UI.KeyboardShortcut.Type.UNSET_SHORTCUT) {
Host.userMetrics.actionTaken(Host.UserMetrics.Action.UserShortcutAdded);
} else {
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ShortcutModified);
}
}
}
this.stopEditing(item);
}
/**
* This method will never be called.
*/
heightForItem(_item: KeybindsItem): number {
return 0;
}
isItemSelectable(_item: KeybindsItem): boolean {
return true;
}
selectedItemChanged(
_from: KeybindsItem|null, to: KeybindsItem|null, fromElement: HTMLElement|null,
toElement: HTMLElement|null): void {
if (fromElement) {
fromElement.tabIndex = -1;
}
if (toElement) {
if (to === this.editingItem && this.editingRow) {
this.editingRow.focus();
} else {
toElement.tabIndex = 0;
if (this.list.element.hasFocus()) {
toElement.focus();
}
}
this.setDefaultFocusedElement(toElement);
}
}
updateSelectedItemARIA(_fromElement: Element|null, _toElement: Element|null): boolean {
return true;
}
startEditing(action: UI.ActionRegistration.Action): void {
this.list.selectItem(action);
if (this.editingItem) {
this.stopEditing(this.editingItem);
}
UI.UIUtils.markBeingEdited(this.list.element, true);
this.editingItem = action;
this.list.refreshItem(action);
}
stopEditing(action: UI.ActionRegistration.Action): void {
UI.UIUtils.markBeingEdited(this.list.element, false);
this.editingItem = null;
this.editingRow = null;
this.list.refreshItem(action);
this.focus();
}
private createListItems(): KeybindsItem[] {
const actions = UI.ActionRegistry.ActionRegistry.instance().actions().sort((actionA, actionB) => {
if (actionA.category() < actionB.category()) {
return -1;
}
if (actionA.category() > actionB.category()) {
return 1;
}
if (actionA.id() < actionB.id()) {
return -1;
}
if (actionA.id() > actionB.id()) {
return 1;
}
return 0;
});
const items: KeybindsItem[] = [];
let currentCategory: UI.ActionRegistration.ActionCategory;
actions.forEach(action => {
if (action.id() === 'elements.toggle-element-search') {
return;
}
if (currentCategory !== action.category()) {
items.push(action.category());
}
items.push(action);
currentCategory = action.category();
});
return items;
}
onEscapeKeyPressed(event: Event): void {
const deepActiveElement = Platform.DOMUtilities.deepActiveElement(document);
if (this.editingRow && deepActiveElement && deepActiveElement.nodeName === 'INPUT') {
this.editingRow.onEscapeKeyPressed(event);
}
}
update(): void {
if (this.editingItem) {
this.stopEditing(this.editingItem);
}
this.list.refreshAllItems();
if (!this.list.selectedItem()) {
this.list.selectItem(this.items.at(0));
}
}
override willHide(): void {
super.willHide();
if (this.editingItem) {
this.stopEditing(this.editingItem);
}
}
}
export class ShortcutListItem {
private isEditing: boolean;
private settingsTab: KeybindsSettingsTab;
private item: UI.ActionRegistration.Action;
element: HTMLDivElement;
private editedShortcuts: Map<UI.KeyboardShortcut.KeyboardShortcut, UI.KeyboardShortcut.Descriptor[]|null>;
private readonly shortcutInputs: Map<UI.KeyboardShortcut.KeyboardShortcut, Element>;
private readonly shortcuts: UI.KeyboardShortcut.KeyboardShortcut[];
private elementToFocus: HTMLElement|null;
private confirmButton: Buttons.Button.Button|null;
private addShortcutLinkContainer: Element|null;
private errorMessageElement: Element|null;
private secondKeyTimeout: number|null;
constructor(item: UI.ActionRegistration.Action, settingsTab: KeybindsSettingsTab, isEditing?: boolean) {
this.isEditing = Boolean(isEditing);
this.settingsTab = settingsTab;
this.item = item;
this.element = document.createElement('div');
this.element.setAttribute('jslog', `${VisualLogging.item().context(item.id()).track({keydown: 'Escape'})}`);
this.editedShortcuts = new Map();
this.shortcutInputs = new Map();
this.shortcuts = UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutsForAction(item.id());
this.elementToFocus = null;
this.confirmButton = null;
this.addShortcutLinkContainer = null;
this.errorMessageElement = null;
this.secondKeyTimeout = null;
this.update();
}
focus(): void {
if (this.elementToFocus) {
this.elementToFocus.focus();
}
}
private update(): void {
this.element.removeChildren();
this.elementToFocus = null;
this.shortcutInputs.clear();
this.element.classList.toggle('keybinds-editing', this.isEditing);
this.element.createChild('div', 'keybinds-action-name keybinds-list-text').textContent = this.item.title();
this.shortcuts.forEach(this.createShortcutRow, this);
if (this.shortcuts.length === 0) {
this.createEmptyInfo();
}
if (this.isEditing) {
this.setupEditor();
}
}
private createEmptyInfo(): void {
if (UI.ShortcutRegistry.ShortcutRegistry.instance().actionHasDefaultShortcut(this.item.id())) {
const icon = IconButton.Icon.create('keyboard-pen', 'keybinds-modified');
UI.ARIAUtils.setLabel(icon, i18nString(UIStrings.shortcutModified));
this.element.appendChild(icon);
}
if (!this.isEditing) {
const emptyElement = this.element.createChild('div', 'keybinds-shortcut keybinds-list-text');
UI.ARIAUtils.setLabel(emptyElement, i18nString(UIStrings.noShortcutForAction));
this.element.appendChild(this.createEditButton());
}
}
private setupEditor(): void {
this.addShortcutLinkContainer = this.element.createChild('div', 'keybinds-shortcut');
const addShortcutButton = UI.UIUtils.createTextButton(
i18nString(UIStrings.addAShortcut), this.addShortcut.bind(this), {jslogContext: 'add-shortcut'});
this.addShortcutLinkContainer.appendChild(addShortcutButton);
if (!this.elementToFocus) {
this.elementToFocus = addShortcutButton;
}
this.errorMessageElement = this.element.createChild('div', 'keybinds-info keybinds-error hidden');
UI.ARIAUtils.markAsAlert(this.errorMessageElement);
this.element.appendChild(this.createIconButton(
i18nString(UIStrings.ResetShortcutsForAction), 'undo', '', 'undo', this.resetShortcutsToDefaults.bind(this)));
this.confirmButton = this.createIconButton(
i18nString(UIStrings.confirmChanges), 'checkmark', 'keybinds-confirm-button', 'confirm', () => {
this.settingsTab.commitChanges(this.item, this.editedShortcuts);
UI.ARIAUtils.alert(i18nString(UIStrings.shortcutChangesApplied, {PH1: this.item.title()}));
});
this.element.appendChild(this.confirmButton);
this.element.appendChild(
this.createIconButton(i18nString(UIStrings.discardChanges), 'cross', 'keybinds-cancel-button', 'cancel', () => {
this.settingsTab.stopEditing(this.item);
UI.ARIAUtils.alert(i18nString(UIStrings.shortcutChangesDiscared));
}));
this.element.addEventListener('keydown', event => {
if (Platform.KeyboardUtilities.isEscKey(event)) {
this.settingsTab.stopEditing(this.item);
event.consume(true);
}
});
}
private addShortcut(): void {
const shortcut =
new UI.KeyboardShortcut.KeyboardShortcut([], this.item.id(), UI.KeyboardShortcut.Type.UNSET_SHORTCUT);
this.shortcuts.push(shortcut);
this.update();
const shortcutInput = this.shortcutInputs.get(shortcut) as HTMLElement;
if (shortcutInput) {
shortcutInput.focus();
}
}
private createShortcutRow(shortcut: UI.KeyboardShortcut.KeyboardShortcut, index?: number): void {
if (this.editedShortcuts.has(shortcut) && !this.editedShortcuts.get(shortcut)) {
return;
}
let icon: IconButton.Icon.Icon;
if (shortcut.type !== UI.KeyboardShortcut.Type.UNSET_SHORTCUT && !shortcut.isDefault()) {
icon = IconButton.Icon.create('keyboard-pen', 'keybinds-modified');
UI.ARIAUtils.setLabel(icon, i18nString(UIStrings.shortcutModified));
this.element.appendChild(icon);
}
const shortcutElement = this.element.createChild('div', 'keybinds-shortcut keybinds-list-text');
if (this.isEditing) {
const shortcutInput = shortcutElement.createChild('input', 'harmony-input');
shortcutInput.setAttribute('jslog', `${VisualLogging.textField().track({change: true})}`);
shortcutInput.spellcheck = false;
shortcutInput.maxLength = 0;
this.shortcutInputs.set(shortcut, shortcutInput);
if (!this.elementToFocus) {
this.elementToFocus = shortcutInput;
}
shortcutInput.value = shortcut.title();
const userDescriptors = this.editedShortcuts.get(shortcut);
if (userDescriptors) {
shortcutInput.value = this.shortcutInputTextForDescriptors(userDescriptors);
}
shortcutInput.addEventListener('keydown', this.onShortcutInputKeyDown.bind(this, shortcut, shortcutInput));
shortcutInput.addEventListener('blur', () => {
if (this.secondKeyTimeout !== null) {
clearTimeout(this.secondKeyTimeout);
this.secondKeyTimeout = null;
}
});
shortcutElement.appendChild(
this.createIconButton(i18nString(UIStrings.removeShortcut), 'bin', 'keybinds-delete-button', 'delete', () => {
const index = this.shortcuts.indexOf(shortcut);
if (!shortcut.isDefault()) {
this.shortcuts.splice(index, 1);
}
this.editedShortcuts.set(shortcut, null);
this.update();
this.focus();
this.validateInputs();
UI.ARIAUtils.alert(i18nString(UIStrings.shortcutRemoved, {PH1: this.item.title()}));
}));
} else {
const separator = Host.Platform.isMac() ? '\u2004' : ' + ';
const keys = shortcut.descriptors.flatMap(descriptor => descriptor.name.split(separator));
keys.forEach(key => {
shortcutElement.createChild('div', 'keybinds-key').createChild('span').textContent = key;
});
if (index === 0) {
this.element.appendChild(this.createEditButton());
}
}
}
private createEditButton(): HTMLElement {
return this.createIconButton(
i18nString(UIStrings.editShortcut), 'edit', 'keybinds-edit-button', 'edit',
() => this.settingsTab.startEditing(this.item));
}
private createIconButton(
label: string, iconName: string, className: string, jslogContext: string,
listener: () => void): Buttons.Button.Button {
const button = new Buttons.Button.Button();
button.data = {variant: Buttons.Button.Variant.ICON, iconName, jslogContext, title: label};
button.addEventListener('click', listener);
UI.ARIAUtils.setLabel(button, label);
if (className) {
button.classList.add(className);
}
return button;
}
private onShortcutInputKeyDown(
shortcut: UI.KeyboardShortcut.KeyboardShortcut, shortcutInput: HTMLInputElement, event: Event): void {
if ((event as KeyboardEvent).key !== 'Tab') {
const eventDescriptor = this.descriptorForEvent(event as KeyboardEvent);
const userDescriptors = this.editedShortcuts.get(shortcut) || [];
this.editedShortcuts.set(shortcut, userDescriptors);
const isLastKeyOfShortcut =
userDescriptors.length === 2 && UI.KeyboardShortcut.KeyboardShortcut.isModifier(userDescriptors[1].key);
const shouldClearOldShortcut = userDescriptors.length === 2 && !isLastKeyOfShortcut;
if (shouldClearOldShortcut) {
userDescriptors.splice(0, 2);
}
if (this.secondKeyTimeout) {
clearTimeout(this.secondKeyTimeout);
this.secondKeyTimeout = null;
userDescriptors.push(eventDescriptor);
} else if (isLastKeyOfShortcut) {
userDescriptors[1] = eventDescriptor;
} else if (!UI.KeyboardShortcut.KeyboardShortcut.isModifier(eventDescriptor.key)) {
userDescriptors[0] = eventDescriptor;
this.secondKeyTimeout = window.setTimeout(() => {
this.secondKeyTimeout = null;
}, UI.ShortcutRegistry.KeyTimeout);
} else {
userDescriptors[0] = eventDescriptor;
}
shortcutInput.value = this.shortcutInputTextForDescriptors(userDescriptors);
this.validateInputs();
event.consume(true);
}
}
private descriptorForEvent(event: KeyboardEvent): UI.KeyboardShortcut.Descriptor {
const userKey = UI.KeyboardShortcut.KeyboardShortcut.makeKeyFromEvent(event);
const codeAndModifiers = UI.KeyboardShortcut.KeyboardShortcut.keyCodeAndModifiersFromKey(userKey);
let key: UI.KeyboardShortcut.Key|string =
UI.KeyboardShortcut.Keys[event.key] || UI.KeyboardShortcut.KeyBindings[event.key];
if (!key && !/^[a-z]$/i.test(event.key)) {
const keyCode = event.code;
// if we still don't have a key name, let's try the code before falling back to the raw key
key = UI.KeyboardShortcut.Keys[keyCode] || UI.KeyboardShortcut.KeyBindings[keyCode];
if (keyCode.startsWith('Digit')) {
key = keyCode.slice(5);
} else if (keyCode.startsWith('Key')) {
key = keyCode.slice(3);
}
}
return UI.KeyboardShortcut.KeyboardShortcut.makeDescriptor(key || event.key, codeAndModifiers.modifiers);
}
private shortcutInputTextForDescriptors(descriptors: UI.KeyboardShortcut.Descriptor[]): string {
return descriptors.map(descriptor => descriptor.name).join(' ');
}
private resetShortcutsToDefaults(): void {
this.editedShortcuts.clear();
for (const shortcut of this.shortcuts) {
if (shortcut.type === UI.KeyboardShortcut.Type.UNSET_SHORTCUT) {
const index = this.shortcuts.indexOf(shortcut);
this.shortcuts.splice(index, 1);
} else if (shortcut.type === UI.KeyboardShortcut.Type.USER_SHORTCUT) {
this.editedShortcuts.set(shortcut, null);
}
}
const disabledDefaults = UI.ShortcutRegistry.ShortcutRegistry.instance().disabledDefaultsForAction(this.item.id());
disabledDefaults.forEach(shortcut => {
if (this.shortcuts.includes(shortcut)) {
return;
}
this.shortcuts.push(shortcut);
this.editedShortcuts.set(shortcut, shortcut.descriptors);
});
this.update();
this.focus();
UI.ARIAUtils.alert(i18nString(UIStrings.shortcutChangesRestored, {PH1: this.item.title()}));
}
onEscapeKeyPressed(event: Event): void {
const activeElement = Platform.DOMUtilities.deepActiveElement(document);
for (const [shortcut, shortcutInput] of this.shortcutInputs.entries()) {
if (activeElement === shortcutInput) {
this.onShortcutInputKeyDown(shortcut, shortcutInput as HTMLInputElement, event as KeyboardEvent);
}
}
}
private validateInputs(): void {
const confirmButton = this.confirmButton;
const errorMessageElement = this.errorMessageElement;
if (!confirmButton || !errorMessageElement) {
return;
}
confirmButton.disabled = false;
errorMessageElement.classList.add('hidden');
this.shortcutInputs.forEach((shortcutInput, shortcut) => {
const userDescriptors = this.editedShortcuts.get(shortcut);
if (!userDescriptors) {
return;
}
if (userDescriptors.some(descriptor => UI.KeyboardShortcut.KeyboardShortcut.isModifier(descriptor.key))) {
confirmButton.disabled = true;
shortcutInput.classList.add('error-input');
UI.ARIAUtils.setInvalid(shortcutInput, true);
errorMessageElement.classList.remove('hidden');
errorMessageElement.textContent = i18nString(UIStrings.shortcutsCannotContainOnly);
return;
}
const conflicts = UI.ShortcutRegistry.ShortcutRegistry.instance()
.actionsForDescriptors(userDescriptors)
.filter(actionId => actionId !== this.item.id());
if (conflicts.length) {
confirmButton.disabled = true;
shortcutInput.classList.add('error-input');
UI.ARIAUtils.setInvalid(shortcutInput, true);
errorMessageElement.classList.remove('hidden');
if (!UI.ActionRegistry.ActionRegistry.instance().hasAction(conflicts[0])) {
return;
}
const action = UI.ActionRegistry.ActionRegistry.instance().getAction(conflicts[0]);
const actionTitle = action.title();
const actionCategory = action.category();
errorMessageElement.textContent =
i18nString(UIStrings.thisShortcutIsInUseByS, {PH1: actionCategory, PH2: actionTitle});
return;
}
shortcutInput.classList.remove('error-input');
UI.ARIAUtils.setInvalid(shortcutInput, false);
});
}
}
export type KeybindsItem = UI.ActionRegistration.ActionCategory|UI.ActionRegistration.Action;