UNPKG

debug-server-next

Version:

Dev server for hippy-core.

353 lines (352 loc) 13.1 kB
// Copyright 2015 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_underscored_properties */ import * as i18n from '../../core/i18n/i18n.js'; import * as ARIAUtils from './ARIAUtils.js'; import { Toolbar, ToolbarButton } from './Toolbar.js'; import { Tooltip } from './Tooltip.js'; import { createInput, createTextButton, ElementFocusRestorer } from './UIUtils.js'; import { VBox } from './Widget.js'; const UIStrings = { /** *@description Text on a button to start editing text */ editString: 'Edit', /** *@description Label for an item to remove something */ removeString: 'Remove', /** *@description Text to save something */ saveString: 'Save', /** *@description Text to add something */ addString: 'Add', /** *@description Text to cancel something */ cancelString: 'Cancel', }; const str_ = i18n.i18n.registerUIStrings('ui/legacy/ListWidget.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class ListWidget extends VBox { _delegate; _list; _lastSeparator; _focusRestorer; _items; _editable; _elements; _editor; _editItem; _editElement; _emptyPlaceholder; constructor(delegate, delegatesFocus = true) { super(true, delegatesFocus); this.registerRequiredCSS('ui/legacy/listWidget.css'); this._delegate = delegate; this._list = this.contentElement.createChild('div', 'list'); this._lastSeparator = false; this._focusRestorer = null; this._items = []; this._editable = []; this._elements = []; this._editor = null; this._editItem = null; this._editElement = null; this._emptyPlaceholder = null; this._updatePlaceholder(); } clear() { this._items = []; this._editable = []; this._elements = []; this._lastSeparator = false; this._list.removeChildren(); this._updatePlaceholder(); this._stopEditing(); } appendItem(item, editable) { if (this._lastSeparator && this._items.length) { const element = document.createElement('div'); element.classList.add('list-separator'); this._list.appendChild(element); } this._lastSeparator = false; this._items.push(item); this._editable.push(editable); const element = this._list.createChild('div', 'list-item'); element.appendChild(this._delegate.renderItem(item, editable)); if (editable) { element.classList.add('editable'); element.tabIndex = 0; element.appendChild(this._createControls(item, element)); } this._elements.push(element); this._updatePlaceholder(); } appendSeparator() { this._lastSeparator = true; } removeItem(index) { if (this._editItem === this._items[index]) { this._stopEditing(); } const element = this._elements[index]; const previous = element.previousElementSibling; const previousIsSeparator = previous && previous.classList.contains('list-separator'); const next = element.nextElementSibling; const nextIsSeparator = next && next.classList.contains('list-separator'); if (previousIsSeparator && (nextIsSeparator || !next)) { previous.remove(); } if (nextIsSeparator && !previous) { next.remove(); } element.remove(); this._elements.splice(index, 1); this._items.splice(index, 1); this._editable.splice(index, 1); this._updatePlaceholder(); } addNewItem(index, item) { this._startEditing(item, null, this._elements[index] || null); } setEmptyPlaceholder(element) { this._emptyPlaceholder = element; this._updatePlaceholder(); } _createControls(item, element) { const controls = document.createElement('div'); controls.classList.add('controls-container'); controls.classList.add('fill'); controls.createChild('div', 'controls-gradient'); const buttons = controls.createChild('div', 'controls-buttons'); const toolbar = new Toolbar('', buttons); const editButton = new ToolbarButton(i18nString(UIStrings.editString), 'largeicon-edit'); editButton.addEventListener(ToolbarButton.Events.Click, onEditClicked.bind(this)); toolbar.appendToolbarItem(editButton); const removeButton = new ToolbarButton(i18nString(UIStrings.removeString), 'largeicon-trash-bin'); removeButton.addEventListener(ToolbarButton.Events.Click, onRemoveClicked.bind(this)); toolbar.appendToolbarItem(removeButton); return controls; function onEditClicked() { const index = this._elements.indexOf(element); const insertionPoint = this._elements[index + 1] || null; this._startEditing(item, element, insertionPoint); } function onRemoveClicked() { const index = this._elements.indexOf(element); this.element.focus(); this._delegate.removeItemRequested(this._items[index], index); } } wasShown() { super.wasShown(); this._stopEditing(); } _updatePlaceholder() { if (!this._emptyPlaceholder) { return; } if (!this._elements.length && !this._editor) { this._list.appendChild(this._emptyPlaceholder); } else { this._emptyPlaceholder.remove(); } } _startEditing(item, element, insertionPoint) { if (element && this._editElement === element) { return; } this._stopEditing(); this._focusRestorer = new ElementFocusRestorer(this.element); this._list.classList.add('list-editing'); this._editItem = item; this._editElement = element; if (element) { element.classList.add('hidden'); } const index = element ? this._elements.indexOf(element) : -1; this._editor = this._delegate.beginEdit(item); this._updatePlaceholder(); this._list.insertBefore(this._editor.element, insertionPoint); this._editor.beginEdit(item, index, element ? i18nString(UIStrings.saveString) : i18nString(UIStrings.addString), this._commitEditing.bind(this), this._stopEditing.bind(this)); } _commitEditing() { const editItem = this._editItem; const isNew = !this._editElement; const editor = this._editor; this._stopEditing(); if (editItem) { this._delegate.commitEdit(editItem, editor, isNew); } } _stopEditing() { this._list.classList.remove('list-editing'); if (this._focusRestorer) { this._focusRestorer.restore(); } if (this._editElement) { this._editElement.classList.remove('hidden'); } if (this._editor && this._editor.element.parentElement) { this._editor.element.remove(); } this._editor = null; this._editItem = null; this._editElement = null; this._updatePlaceholder(); } } export class Editor { element; _contentElement; _commitButton; _cancelButton; _errorMessageContainer; _controls; _controlByName; _validators; _commit; _cancel; _item; _index; constructor() { this.element = document.createElement('div'); this.element.classList.add('editor-container'); this.element.addEventListener('keydown', onKeyDown.bind(null, isEscKey, this._cancelClicked.bind(this)), false); this.element.addEventListener('keydown', onKeyDown.bind(null, event => event.key === 'Enter', this._commitClicked.bind(this)), false); this._contentElement = this.element.createChild('div', 'editor-content'); const buttonsRow = this.element.createChild('div', 'editor-buttons'); this._commitButton = createTextButton('', this._commitClicked.bind(this), '', true /* primary */); buttonsRow.appendChild(this._commitButton); this._cancelButton = createTextButton(i18nString(UIStrings.cancelString), this._cancelClicked.bind(this), '', true /* primary */, 'mousedown'); this._cancelButton.addEventListener('keydown', onKeyDown.bind(null, event => event.key === 'Enter', this._cancelClicked.bind(this)), false); buttonsRow.appendChild(this._cancelButton); this._errorMessageContainer = this.element.createChild('div', 'list-widget-input-validation-error'); ARIAUtils.markAsAlert(this._errorMessageContainer); function onKeyDown(predicate, callback, event) { if (predicate(event)) { event.consume(true); callback(); } } this._controls = []; this._controlByName = new Map(); this._validators = []; this._commit = null; this._cancel = null; this._item = null; this._index = -1; } contentElement() { return this._contentElement; } createInput(name, type, title, validator) { const input = createInput('', type); input.placeholder = title; input.addEventListener('input', this._validateControls.bind(this, false), false); input.addEventListener('blur', this._validateControls.bind(this, false), false); ARIAUtils.setAccessibleName(input, title); this._controlByName.set(name, input); this._controls.push(input); this._validators.push(validator); return input; } createSelect(name, options, validator, title) { const select = document.createElement('select'); select.classList.add('chrome-select'); for (let index = 0; index < options.length; ++index) { const option = select.createChild('option'); option.value = options[index]; option.textContent = options[index]; } if (title) { Tooltip.install(select, title); ARIAUtils.setAccessibleName(select, title); } select.addEventListener('input', this._validateControls.bind(this, false), false); select.addEventListener('blur', this._validateControls.bind(this, false), false); this._controlByName.set(name, select); this._controls.push(select); this._validators.push(validator); return select; } createCustomControl(name, ctor, validator) { const control = new ctor(); this._controlByName.set(name, control); this._controls.push(control); this._validators.push(validator); return control; } control(name) { const control = this._controlByName.get(name); if (!control) { throw new Error(`Control with name ${name} does not exist, please verify.`); } return control; } _validateControls(forceValid) { let allValid = true; this._errorMessageContainer.textContent = ''; for (let index = 0; index < this._controls.length; ++index) { const input = this._controls[index]; const { valid, errorMessage } = this._validators[index].call(null, this._item, this._index, input); input.classList.toggle('error-input', !valid && !forceValid); if (valid || forceValid) { ARIAUtils.setInvalid(input, false); } else { ARIAUtils.setInvalid(input, true); } if (!forceValid && errorMessage && !this._errorMessageContainer.textContent) { this._errorMessageContainer.textContent = errorMessage; } allValid = allValid && valid; } this._commitButton.disabled = !allValid; } requestValidation() { this._validateControls(false); } beginEdit(item, index, commitButtonTitle, commit, cancel) { this._commit = commit; this._cancel = cancel; this._item = item; this._index = index; this._commitButton.textContent = commitButtonTitle; this.element.scrollIntoViewIfNeeded(false); if (this._controls.length) { this._controls[0].focus(); } this._validateControls(true); } _commitClicked() { if (this._commitButton.disabled) { return; } const commit = this._commit; this._commit = null; this._cancel = null; this._item = null; this._index = -1; if (commit) { commit(); } } _cancelClicked() { const cancel = this._cancel; this._commit = null; this._cancel = null; this._item = null; this._index = -1; if (cancel) { cancel(); } } }