UNPKG

@ckeditor/ckeditor5-table

Version:

Table feature for CKEditor 5.

249 lines (248 loc) • 9.32 kB
/** * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ /** * @module table/ui/colorinputview */ import { View, InputTextView, ButtonView, createDropdown, ColorGridView, FocusCycler, ViewCollection } from 'ckeditor5/src/ui'; import { icons } from 'ckeditor5/src/core'; import { FocusTracker, KeystrokeHandler } from 'ckeditor5/src/utils'; import '../../theme/colorinput.css'; /** * The color input view class. It allows the user to type in a color (hex, rgb, etc.) * or choose it from the configurable color palette with a preview. * * @internal */ export default class ColorInputView extends View { /** * Creates an instance of the color input view. * * @param locale The locale instance. * @param options The input options. * @param options.colorDefinitions The colors to be displayed in the palette inside the input's dropdown. * @param options.columns The number of columns in which the colors will be displayed. * @param options.defaultColorValue If specified, the color input view will replace the "Remove color" button with * the "Restore default" button. Instead of clearing the input field, the default color value will be set. */ constructor(locale, options) { super(locale); this.set('value', ''); this.set('isReadOnly', false); this.set('isFocused', false); this.set('isEmpty', true); this.options = options; this.focusTracker = new FocusTracker(); this._focusables = new ViewCollection(); this.dropdownView = this._createDropdownView(); this.inputView = this._createInputTextView(); this.keystrokes = new KeystrokeHandler(); this._stillTyping = false; this._focusCycler = new FocusCycler({ focusables: this._focusables, focusTracker: this.focusTracker, keystrokeHandler: this.keystrokes, actions: { // Navigate items backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke. focusPrevious: 'shift + tab', // Navigate items forwards using the <kbd>Tab</kbd> key. focusNext: 'tab' } }); this.setTemplate({ tag: 'div', attributes: { class: [ 'ck', 'ck-input-color' ] }, children: [ this.dropdownView, this.inputView ] }); this.on('change:value', (evt, name, inputValue) => this._setInputValue(inputValue)); } /** * @inheritDoc */ render() { super.render(); // Start listening for the keystrokes coming from the dropdown panel view. this.keystrokes.listenTo(this.dropdownView.panelView.element); } /** * Focuses the input. */ focus() { this.inputView.focus(); } /** * @inheritDoc */ destroy() { super.destroy(); this.focusTracker.destroy(); this.keystrokes.destroy(); } /** * Creates and configures the {@link #dropdownView}. */ _createDropdownView() { const locale = this.locale; const t = locale.t; const bind = this.bindTemplate; const colorGrid = this._createColorGrid(locale); const dropdown = createDropdown(locale); const colorPreview = new View(); const removeColorButton = this._createRemoveColorButton(); colorPreview.setTemplate({ tag: 'span', attributes: { class: [ 'ck', 'ck-input-color__button__preview' ], style: { backgroundColor: bind.to('value') } }, children: [{ tag: 'span', attributes: { class: [ 'ck', 'ck-input-color__button__preview__no-color-indicator', bind.if('value', 'ck-hidden', value => value != '') ] } }] }); dropdown.buttonView.extendTemplate({ attributes: { class: 'ck-input-color__button' } }); dropdown.buttonView.children.add(colorPreview); dropdown.buttonView.label = t('Color picker'); dropdown.buttonView.tooltip = true; dropdown.panelPosition = locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw'; dropdown.panelView.children.add(removeColorButton); dropdown.panelView.children.add(colorGrid); dropdown.bind('isEnabled').to(this, 'isReadOnly', value => !value); this._focusables.add(removeColorButton); this._focusables.add(colorGrid); this.focusTracker.add(removeColorButton.element); this.focusTracker.add(colorGrid.element); return dropdown; } /** * Creates and configures an instance of {@link module:ui/inputtext/inputtextview~InputTextView}. * * @returns A configured instance to be set as {@link #inputView}. */ _createInputTextView() { const locale = this.locale; const inputView = new InputTextView(locale); inputView.extendTemplate({ on: { blur: inputView.bindTemplate.to('blur') } }); inputView.value = this.value; inputView.bind('isReadOnly', 'hasError').to(this); this.bind('isFocused', 'isEmpty').to(inputView); inputView.on('input', () => { const inputValue = inputView.element.value; // Check if the value matches one of our defined colors' label. const mappedColor = this.options.colorDefinitions.find(def => inputValue === def.label); this._stillTyping = true; this.value = mappedColor && mappedColor.color || inputValue; }); inputView.on('blur', () => { this._stillTyping = false; this._setInputValue(inputView.element.value); }); inputView.delegate('input').to(this); return inputView; } /** * Creates and configures the button that clears the color. */ _createRemoveColorButton() { const locale = this.locale; const t = locale.t; const removeColorButton = new ButtonView(locale); const defaultColor = this.options.defaultColorValue || ''; const removeColorButtonLabel = defaultColor ? t('Restore default') : t('Remove color'); removeColorButton.class = 'ck-input-color__remove-color'; removeColorButton.withText = true; removeColorButton.icon = icons.eraser; removeColorButton.label = removeColorButtonLabel; removeColorButton.on('execute', () => { this.value = defaultColor; this.dropdownView.isOpen = false; this.fire('input'); }); return removeColorButton; } /** * Creates and configures the color grid inside the {@link #dropdownView}. */ _createColorGrid(locale) { const colorGrid = new ColorGridView(locale, { colorDefinitions: this.options.colorDefinitions, columns: this.options.columns }); colorGrid.on('execute', (evtData, data) => { this.value = data.value; this.dropdownView.isOpen = false; this.fire('input'); }); colorGrid.bind('selectedColor').to(this, 'value'); return colorGrid; } /** * Sets {@link #inputView}'s value property to the color value or color label, * if there is one and the user is not typing. * * Handles cases like: * * * Someone picks the color in the grid. * * The color is set from the plugin level. * * @param inputValue Color value to be set. */ _setInputValue(inputValue) { if (!this._stillTyping) { const normalizedInputValue = normalizeColor(inputValue); // Check if the value matches one of our defined colors. const mappedColor = this.options.colorDefinitions.find(def => normalizedInputValue === normalizeColor(def.color)); if (mappedColor) { this.inputView.value = mappedColor.label; } else { this.inputView.value = inputValue || ''; } } } } /** * Normalizes color value, by stripping extensive whitespace. * For example., transforms: * * ` rgb( 25 50 0 )` to `rgb(25 50 0)`, * * "\t rgb( 25 , 50,0 ) " to `rgb(25 50 0)`. * * @param colorString The value to be normalized. */ function normalizeColor(colorString) { return colorString // Remove any whitespace right after `(` or `,`. .replace(/([(,])\s+/g, '$1') // Remove any whitespace at the beginning or right before the end, `)`, `,`, or another whitespace. .replace(/^\s+|\s+(?=[),\s]|$)/g, '') // Then, replace `,` or whitespace with a single space. .replace(/,|\s/g, ' '); }