Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit dac8ce0

Browse files
authored
Merge pull request #531 from ckeditor/i/5782
Feature: Enabled keystroke preview in `ButtonView`. Implemented the `ButtonView#withKeystroke` property. Closes ckeditor/ckeditor5#5782.
2 parents 95404e3 + c239bc5 commit dac8ce0

File tree

3 files changed

+131
-2
lines changed

3 files changed

+131
-2
lines changed

src/button/button.jsdoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
* (Optional) The keystroke associated with the button, i.e. <kbd>CTRL+B</kbd>,
2828
* in the string format compatible with {@link module:utils/keyboard}.
2929
*
30+
* **Note**: Use {@link module:ui/button/button~Button#withKeystroke} if you want to display
31+
* the keystroke information next to the {@link module:ui/button/button~Button#label label}.
32+
*
3033
* @observable
3134
* @member {Boolean} #keystroke
3235
*/
@@ -111,6 +114,18 @@
111114
* @member {Boolean} #withText
112115
*/
113116

117+
/**
118+
* (Optional) Controls whether the keystroke of the button is displayed next to its
119+
* {@link module:ui/button/button~Button#label label}.
120+
*
121+
* **Note**: This property requires a {@link module:ui/button/button~Button#keystroke keystroke}
122+
* to be defined in the first place.
123+
*
124+
* @observable
125+
* @default false
126+
* @member {Boolean} #withKeystroke
127+
*/
128+
114129
/**
115130
* (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon.
116131
* When defined, an `iconView` should be added to the button.

src/button/buttonview.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export default class ButtonView extends View {
6060
this.set( 'tooltipPosition', 's' );
6161
this.set( 'type', 'button' );
6262
this.set( 'withText', false );
63+
this.set( 'withKeystroke', false );
6364

6465
/**
6566
* Collection of the child views inside of the button {@link #element}.
@@ -100,6 +101,16 @@ export default class ButtonView extends View {
100101
}
101102
} );
102103

104+
/**
105+
* A view displaying the keystroke of the button next to the {@link #labelView label}.
106+
* Added to {@link #children} when the {@link #withKeystroke `withKeystroke` attribute}
107+
* is defined.
108+
*
109+
* @readonly
110+
* @member {module:ui/view/view~View} #keystrokeView
111+
*/
112+
this.keystrokeView = this._createKeystrokeView();
113+
103114
/**
104115
* Tooltip of the button bound to the template.
105116
*
@@ -127,7 +138,8 @@ export default class ButtonView extends View {
127138
bind.if( 'isEnabled', 'ck-disabled', value => !value ),
128139
bind.if( 'isVisible', 'ck-hidden', value => !value ),
129140
bind.to( 'isOn', value => value ? 'ck-on' : 'ck-off' ),
130-
bind.if( 'withText', 'ck-button_with-text' )
141+
bind.if( 'withText', 'ck-button_with-text' ),
142+
bind.if( 'withKeystroke', 'ck-button_with-keystroke' ),
131143
],
132144
type: bind.to( 'type', value => value ? value : 'button' ),
133145
tabindex: bind.to( 'tabindex' ),
@@ -171,6 +183,10 @@ export default class ButtonView extends View {
171183

172184
this.children.add( this.tooltipView );
173185
this.children.add( this.labelView );
186+
187+
if ( this.withKeystroke ) {
188+
this.children.add( this.keystrokeView );
189+
}
174190
}
175191

176192
/**
@@ -229,6 +245,36 @@ export default class ButtonView extends View {
229245
return labelView;
230246
}
231247

248+
/**
249+
* Creates a view that displays a keystroke next to a {@link #labelView label }
250+
* and binds it with button attributes.
251+
*
252+
* @private
253+
* @returns {module:ui/view~View}
254+
*/
255+
_createKeystrokeView() {
256+
const keystrokeView = new View();
257+
258+
keystrokeView.setTemplate( {
259+
tag: 'span',
260+
261+
attributes: {
262+
class: [
263+
'ck',
264+
'ck-button__keystroke'
265+
]
266+
},
267+
268+
children: [
269+
{
270+
text: this.bindTemplate.to( 'keystroke', text => getEnvKeystrokeText( text ) )
271+
}
272+
]
273+
} );
274+
275+
return keystrokeView;
276+
}
277+
232278
/**
233279
* Gets the text for the {@link #tooltipView} from the combination of
234280
* {@link #tooltip}, {@link #label} and {@link #keystroke} attributes.

tests/button/buttonview.js

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import IconView from '../../src/icon/iconview';
1111
import TooltipView from '../../src/tooltip/tooltipview';
1212
import View from '../../src/view';
1313
import ViewCollection from '../../src/viewcollection';
14+
import env from '@ckeditor/ckeditor5-utils/src/env';
1415

1516
describe( 'ButtonView', () => {
1617
let locale, view;
@@ -24,6 +25,10 @@ describe( 'ButtonView', () => {
2425
view.render();
2526
} );
2627

28+
afterEach( () => {
29+
view.destroy();
30+
} );
31+
2732
describe( 'constructor()', () => {
2833
it( 'creates view#children collection', () => {
2934
expect( view.children ).to.be.instanceOf( ViewCollection );
@@ -39,6 +44,10 @@ describe( 'ButtonView', () => {
3944
expect( view.labelView.element.classList.contains( 'ck-button__label' ) ).to.be.true;
4045
} );
4146

47+
it( 'creates #keystrokeView', () => {
48+
expect( view.keystrokeView ).to.be.instanceOf( View );
49+
} );
50+
4251
it( 'creates #iconView', () => {
4352
expect( view.iconView ).to.be.instanceOf( IconView );
4453
} );
@@ -86,6 +95,14 @@ describe( 'ButtonView', () => {
8695
expect( view.element.classList.contains( 'ck-button_with-text' ) ).to.false;
8796
} );
8897

98+
it( 'reacts on view#withKeystroke', () => {
99+
view.withKeystroke = true;
100+
expect( view.element.classList.contains( 'ck-button_with-keystroke' ) ).to.true;
101+
102+
view.withKeystroke = false;
103+
expect( view.element.classList.contains( 'ck-button_with-keystroke' ) ).to.false;
104+
} );
105+
89106
it( 'reacts on view#type', () => {
90107
// Default value.
91108
expect( view.element.getAttribute( 'type' ) ).to.equal( 'button' );
@@ -288,7 +305,7 @@ describe( 'ButtonView', () => {
288305
} );
289306
} );
290307

291-
describe( 'icon', () => {
308+
describe( '#iconView', () => {
292309
it( 'is omited in #children when view#icon is not defined', () => {
293310
view = new ButtonView( locale );
294311
view.render();
@@ -325,6 +342,57 @@ describe( 'ButtonView', () => {
325342
} );
326343
} );
327344

345+
describe( '#keystrokeView', () => {
346+
it( 'is omited in #children when view#icon is not defined', () => {
347+
view = new ButtonView( locale );
348+
view.render();
349+
350+
expect( view.element.childNodes ).to.have.length( 2 );
351+
expect( view.keystrokeView.element ).to.be.null;
352+
} );
353+
354+
it( 'is added to the #children when view#withKeystroke is true', () => {
355+
testUtils.sinon.stub( env, 'isMac' ).value( false );
356+
357+
view = new ButtonView( locale );
358+
view.keystroke = 'Ctrl+A';
359+
view.withKeystroke = true;
360+
view.render();
361+
362+
expect( view.element.childNodes ).to.have.length( 3 );
363+
expect( view.element.childNodes[ 2 ] ).to.equal( view.keystrokeView.element );
364+
365+
expect( view.keystrokeView.element.classList.contains( 'ck' ) ).to.be.true;
366+
expect( view.keystrokeView.element.classList.contains( 'ck-button__keystroke' ) ).to.be.true;
367+
368+
expect( view.keystrokeView ).to.instanceOf( View );
369+
expect( view.keystrokeView.element.textContent ).to.equal( 'Ctrl+A' );
370+
} );
371+
372+
it( 'usese fancy kesytroke preview on Mac', () => {
373+
testUtils.sinon.stub( env, 'isMac' ).value( true );
374+
375+
view = new ButtonView( locale );
376+
view.keystroke = 'Ctrl+A';
377+
view.withKeystroke = true;
378+
view.render();
379+
380+
expect( view.keystrokeView.element.textContent ).to.equal( '⌘A' );
381+
} );
382+
383+
it( 'is destroyed with the view', () => {
384+
view = new ButtonView( locale );
385+
view.keystroke = 'Ctrl+A';
386+
view.withKeystroke = true;
387+
view.render();
388+
389+
const spy = sinon.spy( view.keystrokeView, 'destroy' );
390+
391+
view.destroy();
392+
sinon.assert.calledOnce( spy );
393+
} );
394+
} );
395+
328396
describe( 'focus()', () => {
329397
it( 'focuses the button in DOM', () => {
330398
const spy = sinon.spy( view.element, 'focus' );

0 commit comments

Comments
 (0)