Przeglądaj źródła

Merge pull request #531 from ckeditor/i/5782

Feature: Enabled keystroke preview in `ButtonView`. Implemented the `ButtonView#withKeystroke` property. Closes ckeditor/ckeditor5#5782.
Piotrek Koszuliński 6 lat temu
rodzic
commit
2c7bab5372

+ 15 - 0
packages/ckeditor5-ui/src/button/button.jsdoc

@@ -27,6 +27,9 @@
  * (Optional) The keystroke associated with the button, i.e. <kbd>CTRL+B</kbd>,
  * in the string format compatible with {@link module:utils/keyboard}.
  *
+ * **Note**: Use {@link module:ui/button/button~Button#withKeystroke} if you want to display
+ * the keystroke information next to the {@link module:ui/button/button~Button#label label}.
+ *
  * @observable
  * @member {Boolean} #keystroke
  */
@@ -111,6 +114,18 @@
  * @member {Boolean} #withText
  */
 
+/**
+ * (Optional) Controls whether the keystroke of the button is displayed next to its
+ * {@link module:ui/button/button~Button#label label}.
+ *
+ * **Note**: This property requires a {@link module:ui/button/button~Button#keystroke keystroke}
+ * to be defined in the first place.
+ *
+ * @observable
+ * @default false
+ * @member {Boolean} #withKeystroke
+ */
+
 /**
  * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon.
  * When defined, an `iconView` should be added to the button.

+ 47 - 1
packages/ckeditor5-ui/src/button/buttonview.js

@@ -60,6 +60,7 @@ export default class ButtonView extends View {
 		this.set( 'tooltipPosition', 's' );
 		this.set( 'type', 'button' );
 		this.set( 'withText', false );
+		this.set( 'withKeystroke', false );
 
 		/**
 		 * Collection of the child views inside of the button {@link #element}.
@@ -100,6 +101,16 @@ export default class ButtonView extends View {
 			}
 		} );
 
+		/**
+		 * A view displaying the keystroke of the button next to the {@link #labelView label}.
+		 * Added to {@link #children} when the {@link #withKeystroke `withKeystroke` attribute}
+		 * is defined.
+		 *
+		 * @readonly
+		 * @member {module:ui/view/view~View} #keystrokeView
+		 */
+		this.keystrokeView = this._createKeystrokeView();
+
 		/**
 		 * Tooltip of the button bound to the template.
 		 *
@@ -127,7 +138,8 @@ export default class ButtonView extends View {
 					bind.if( 'isEnabled', 'ck-disabled', value => !value ),
 					bind.if( 'isVisible', 'ck-hidden', value => !value ),
 					bind.to( 'isOn', value => value ? 'ck-on' : 'ck-off' ),
-					bind.if( 'withText', 'ck-button_with-text' )
+					bind.if( 'withText', 'ck-button_with-text' ),
+					bind.if( 'withKeystroke', 'ck-button_with-keystroke' ),
 				],
 				type: bind.to( 'type', value => value ? value : 'button' ),
 				tabindex: bind.to( 'tabindex' ),
@@ -171,6 +183,10 @@ export default class ButtonView extends View {
 
 		this.children.add( this.tooltipView );
 		this.children.add( this.labelView );
+
+		if ( this.withKeystroke ) {
+			this.children.add( this.keystrokeView );
+		}
 	}
 
 	/**
@@ -229,6 +245,36 @@ export default class ButtonView extends View {
 		return labelView;
 	}
 
+	/**
+	 * Creates a view that displays a keystroke next to a {@link #labelView label }
+	 * and binds it with button attributes.
+	 *
+	 * @private
+	 * @returns {module:ui/view~View}
+	 */
+	_createKeystrokeView() {
+		const keystrokeView = new View();
+
+		keystrokeView.setTemplate( {
+			tag: 'span',
+
+			attributes: {
+				class: [
+					'ck',
+					'ck-button__keystroke'
+				]
+			},
+
+			children: [
+				{
+					text: this.bindTemplate.to( 'keystroke', text => getEnvKeystrokeText( text ) )
+				}
+			]
+		} );
+
+		return keystrokeView;
+	}
+
 	/**
 	 * Gets the text for the {@link #tooltipView} from the combination of
 	 * {@link #tooltip}, {@link #label} and {@link #keystroke} attributes.

+ 69 - 1
packages/ckeditor5-ui/tests/button/buttonview.js

@@ -11,6 +11,7 @@ import IconView from '../../src/icon/iconview';
 import TooltipView from '../../src/tooltip/tooltipview';
 import View from '../../src/view';
 import ViewCollection from '../../src/viewcollection';
+import env from '@ckeditor/ckeditor5-utils/src/env';
 
 describe( 'ButtonView', () => {
 	let locale, view;
@@ -24,6 +25,10 @@ describe( 'ButtonView', () => {
 		view.render();
 	} );
 
+	afterEach( () => {
+		view.destroy();
+	} );
+
 	describe( 'constructor()', () => {
 		it( 'creates view#children collection', () => {
 			expect( view.children ).to.be.instanceOf( ViewCollection );
@@ -39,6 +44,10 @@ describe( 'ButtonView', () => {
 			expect( view.labelView.element.classList.contains( 'ck-button__label' ) ).to.be.true;
 		} );
 
+		it( 'creates #keystrokeView', () => {
+			expect( view.keystrokeView ).to.be.instanceOf( View );
+		} );
+
 		it( 'creates #iconView', () => {
 			expect( view.iconView ).to.be.instanceOf( IconView );
 		} );
@@ -86,6 +95,14 @@ describe( 'ButtonView', () => {
 				expect( view.element.classList.contains( 'ck-button_with-text' ) ).to.false;
 			} );
 
+			it( 'reacts on view#withKeystroke', () => {
+				view.withKeystroke = true;
+				expect( view.element.classList.contains( 'ck-button_with-keystroke' ) ).to.true;
+
+				view.withKeystroke = false;
+				expect( view.element.classList.contains( 'ck-button_with-keystroke' ) ).to.false;
+			} );
+
 			it( 'reacts on view#type', () => {
 				// Default value.
 				expect( view.element.getAttribute( 'type' ) ).to.equal( 'button' );
@@ -288,7 +305,7 @@ describe( 'ButtonView', () => {
 		} );
 	} );
 
-	describe( 'icon', () => {
+	describe( '#iconView', () => {
 		it( 'is omited in #children when view#icon is not defined', () => {
 			view = new ButtonView( locale );
 			view.render();
@@ -325,6 +342,57 @@ describe( 'ButtonView', () => {
 		} );
 	} );
 
+	describe( '#keystrokeView', () => {
+		it( 'is omited in #children when view#icon is not defined', () => {
+			view = new ButtonView( locale );
+			view.render();
+
+			expect( view.element.childNodes ).to.have.length( 2 );
+			expect( view.keystrokeView.element ).to.be.null;
+		} );
+
+		it( 'is added to the #children when view#withKeystroke is true', () => {
+			testUtils.sinon.stub( env, 'isMac' ).value( false );
+
+			view = new ButtonView( locale );
+			view.keystroke = 'Ctrl+A';
+			view.withKeystroke = true;
+			view.render();
+
+			expect( view.element.childNodes ).to.have.length( 3 );
+			expect( view.element.childNodes[ 2 ] ).to.equal( view.keystrokeView.element );
+
+			expect( view.keystrokeView.element.classList.contains( 'ck' ) ).to.be.true;
+			expect( view.keystrokeView.element.classList.contains( 'ck-button__keystroke' ) ).to.be.true;
+
+			expect( view.keystrokeView ).to.instanceOf( View );
+			expect( view.keystrokeView.element.textContent ).to.equal( 'Ctrl+A' );
+		} );
+
+		it( 'usese fancy kesytroke preview on Mac', () => {
+			testUtils.sinon.stub( env, 'isMac' ).value( true );
+
+			view = new ButtonView( locale );
+			view.keystroke = 'Ctrl+A';
+			view.withKeystroke = true;
+			view.render();
+
+			expect( view.keystrokeView.element.textContent ).to.equal( '⌘A' );
+		} );
+
+		it( 'is destroyed with the view', () => {
+			view = new ButtonView( locale );
+			view.keystroke = 'Ctrl+A';
+			view.withKeystroke = true;
+			view.render();
+
+			const spy = sinon.spy( view.keystrokeView, 'destroy' );
+
+			view.destroy();
+			sinon.assert.calledOnce( spy );
+		} );
+	} );
+
 	describe( 'focus()', () => {
 		it( 'focuses the button in DOM', () => {
 			const spy = sinon.spy( view.element, 'focus' );