Skip to content

Commit

Permalink
Merge pull request #6784 from ckeditor/i/6241-colorinput
Browse files Browse the repository at this point in the history
Other (table): Display a human readable color label in the text input field. Closes #6241.
  • Loading branch information
Reinmar authored May 11, 2020
2 parents 6421767 + 9abb619 commit af7928f
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 22 deletions.
75 changes: 64 additions & 11 deletions packages/ckeditor5-table/src/ui/colorinputview.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,20 @@ export default class ColorInputView extends View {
* An instance of the input allowing the user to type a color value.
*
* @protected
* @member {module:ui/dropdown/dropdown~DropdownView}
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this._inputView = this._createInputTextView( locale );

/**
* The flag that indicates whether the user is still typing.
* If set to true, it means that the text input field ({@link #_inputView}) still has the focus.
* So, we should interrupt the user by replacing the input's value.
*
* @protected
* @member {Boolean}
*/
this._stillTyping = false;

this.setTemplate( {
tag: 'div',
attributes: {
Expand All @@ -122,6 +132,8 @@ export default class ColorInputView extends View {
this._dropdownView
]
} );

this.on( 'change:value', ( evt, name, inputValue ) => this._setInputValue( inputValue ) );
}

/**
Expand Down Expand Up @@ -186,25 +198,42 @@ export default class ColorInputView extends View {
}

/**
* Creates and configures the {@link #_inputView}.
* Creates and configures an instance of {@link module:ui/inputtext/inputtextview~InputTextView}.
*
* @private
* @returns {module:ui/inputtext/inputtextview~InputTextView} A configured instance to be set as {@link #_inputView}.
*/
_createInputTextView() {
const locale = this.locale;
const input = new InputTextView( locale );
const inputView = new InputTextView( locale );

inputView.extendTemplate( {
on: {
blur: inputView.bindTemplate.to( 'blur' )
}
} );

inputView.value = this.value;
inputView.bind( 'isReadOnly' ).to( this );
inputView.bind( 'hasError' ).to( this );

input.bind( 'value' ).to( this );
input.bind( 'isReadOnly' ).to( this );
input.bind( 'hasError' ).to( this );
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 );

input.on( 'input', () => {
this.value = input.element.value;
this._stillTyping = true;
this.value = mappedColor && mappedColor.color || inputValue;
} );

input.delegate( 'input' ).to( this );
inputView.on( 'blur', () => {
this._stillTyping = false;
this._setInputValue( inputView.element.value );
} );

return input;
inputView.delegate( 'input' ).to( this );

return inputView;
}

/**
Expand Down Expand Up @@ -246,9 +275,33 @@ export default class ColorInputView extends View {
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.
*
* @private
* @param {String} inputValue Color value to be set.
*/
_setInputValue( inputValue ) {
if ( !this._stillTyping ) {
// Check if the value matches one of our defined colors.
const mappedColor = this.options.colorDefinitions.find( def => inputValue === def.color );

if ( mappedColor ) {
this._inputView.value = mappedColor.label;
} else {
this._inputView.value = inputValue || '';
}
}
}
}
90 changes: 87 additions & 3 deletions packages/ckeditor5-table/tests/ui/colorinputview.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global Event */

import ColorInputView from '../../src/ui/colorinputview';
import InputTextView from '@ckeditor/ckeditor5-ui/src/inputtext/inputtextview';
import ColorGridView from '@ckeditor/ckeditor5-ui/src/colorgrid/colorgridview';
Expand Down Expand Up @@ -138,23 +140,31 @@ describe( 'ColorInputView', () => {
expect( colorGridView ).to.be.instanceOf( ColorGridView );
} );

it( 'should set #value upon #execute', () => {
it( 'should set ColorInputView#value upon ColorTileView#execute', () => {
expect( view.value ).to.equal( '' );

colorGridView.items.last.fire( 'execute' );

expect( view.value ).to.equal( 'rgb(0,0,255)' );
} );

it( 'should close the dropdown upon #execute', () => {
it( 'should set InputTextView#value to the selected color\'s label upon ColorTileView#execute', () => {
expect( inputView.value ).to.equal( '' );

colorGridView.items.last.fire( 'execute' );

expect( inputView.value ).to.equal( 'Blue' );
} );

it( 'should close the dropdown upon ColorTileView#execute', () => {
view._dropdownView.isOpen = true;

colorGridView.items.last.fire( 'execute' );

expect( view._dropdownView.isOpen ).to.be.false;
} );

it( 'should fire #input upon #execute', () => {
it( 'should fire the ColorInputView#input event upon ColorTileView#execute', () => {
const spy = sinon.spy( view, 'fire' );

colorGridView.items.last.fire( 'execute' );
Expand Down Expand Up @@ -208,6 +218,15 @@ describe( 'ColorInputView', () => {
expect( inputView.value ).to.equal( 'bar' );
} );

it( `when the color input value is set to one of defined colors,
should use its label as the text input value`, () => {
view.value = 'rgb(0,255,0)';
expect( inputView.value ).to.equal( 'Green' );

view.value = 'rgb(255,0,0)';
expect( inputView.value ).to.equal( 'Red' );
} );

it( 'should have #isReadOnly bound to the color input', () => {
view.isReadOnly = true;
expect( inputView.isReadOnly ).to.equal( true );
Expand Down Expand Up @@ -236,6 +255,71 @@ describe( 'ColorInputView', () => {
expect( view.value ).to.equal( 'bar' );
} );

it(
`when any defined color label is given as the text input #value (case-sensitive),
should set the color as #value on #input event`,
() => {
inputView.element.value = 'Red';
inputView.fire( 'input' );

expect( view.value ).to.equal( 'rgb(255,0,0)' );

inputView.element.value = 'Green';
inputView.fire( 'input' );

expect( view.value ).to.equal( 'rgb(0,255,0)' );

inputView.element.value = 'blue';
inputView.fire( 'input' );

expect( view.value ).to.equal( 'blue' );
}
);

it(
`when any defined color label is given as the text input #value (case-sensitive),
then a non-defined value is set to the color input,
the latter value should be set to text input`,
() => {
inputView.element.value = 'Red';
inputView.fire( 'input' );

expect( view.value ).to.equal( 'rgb(255,0,0)' );

view.value = 'rgb(0,0,255)';

expect( view.value ).to.equal( 'rgb(0,0,255)' );
}
);

it(
`when any defined color value is given as the text input #value (case-sensitive),
its value should be set to color and text inputs after input event`,
() => {
inputView.element.value = 'rgb(255,0,0)';
inputView.fire( 'input' );

expect( view.value ).to.equal( 'rgb(255,0,0)' );
expect( inputView.element.value ).to.equal( 'rgb(255,0,0)' );
}
);

it(
`when any defined color value is given as the text input #value (case-sensitive),
its label should be set to text inputs after blur event on input view input element`,
() => {
inputView.element.value = 'rgb(255,0,0)';

inputView.fire( 'input' );

expect( inputView.element.value ).to.equal( 'rgb(255,0,0)' );

inputView.element.dispatchEvent( new Event( 'blur' ) );

expect( inputView.element.value ).to.equal( 'Red' );
}
);

it( 'should have #input event delegated to the color input', () => {
const spy = sinon.spy();
view.on( 'input', spy );
Expand Down
29 changes: 21 additions & 8 deletions packages/ckeditor5-ui/src/colorgrid/colorgridview.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,21 @@ export default class ColorGridView extends View {
colorTile.isOn = colorTile.color === this.selectedColor;
} );

colorDefinitions.forEach( item => {
colorDefinitions.forEach( color => {
const colorTile = new ColorTileView();

colorTile.set( {
color: item.color,
label: item.label,
color: color.color,
label: color.label,
tooltip: true,
hasBorder: item.options.hasBorder
hasBorder: color.options.hasBorder
} );

colorTile.on( 'execute', () => {
this.fire( 'execute', {
value: item.color,
hasBorder: item.options.hasBorder,
label: item.label
value: color.color,
hasBorder: color.options.hasBorder,
label: color.label
} );
} );

Expand Down Expand Up @@ -175,13 +175,26 @@ export default class ColorGridView extends View {
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
}

/**
* Fired when the `ColorTileView` for the picked item is executed.
*
* @event execute
* @param {Object} data Additional information about the event.
* @param {String} data.value The value of the selected color
* ({@link module:ui/colorgrid/colorgrid~ColorDefinition#color `color.color`}).
* @param {Boolean} data.hasBorder The `hasBorder` property of the selected color
* ({@link module:ui/colorgrid/colorgrid~ColorDefinition#options.hasBorder `color.options.hasBorder`}).
* @param {String} data.Label The label of the selected color
* ({@link module:ui/colorgrid/colorgrid~ColorDefinition#label `color.label`})
*/
}

/**
* A color definition used to create a {@link module:ui/colorgrid/colortile~ColorTileView}.
*
* {
* color: hsl(0, 0%, 75%),
* color: 'hsl(0, 0%, 75%)',
* label: 'Light Grey',
* options: {
* hasBorder: true
Expand Down

0 comments on commit af7928f

Please sign in to comment.