Skip to content

Commit

Permalink
Merge branch 't/16804' into major
Browse files Browse the repository at this point in the history
  • Loading branch information
Comandeer committed Feb 28, 2017
2 parents f8e830b + a702759 commit 61a6108
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -21,6 +21,7 @@ Fixed Issues:
* [#16777](https://dev.ckeditor.com/ticket/16777): [Edge] Fixed: [Clipboard](http://ckeditor.com/addon/clipboard) plugin doesn't allow to drop widgets into editor.
* [#14894](https://dev.ckeditor.com/ticket/14894): [Chrome] Fixed: Editor scrolls to top after focusing or when a dialog is opened.
* [#14769](https://dev.ckeditor.com/ticket/14769): Fixed: URLs with '-' in host are not detected by [Autolink](http://ckeditor.com/addon/autolink) plugin.
* [#16804](https://dev.ckeditor.com/ticket/16804): Fixed: Focus is not on the first menu item when user opens [Context Menu](http://ckeditor.com/addon/contextmenu) or combobox from editor's toolbar.

## CKEditor 4.6.2

Expand Down
77 changes: 69 additions & 8 deletions plugins/colorbutton/plugin.js
Expand Up @@ -85,7 +85,8 @@ CKEDITOR.plugins.add( 'colorbutton', {

function addButton( name, type, title, order, options ) {
var style = new CKEDITOR.style( config[ 'colorButton_' + type + 'Style' ] ),
colorBoxId = CKEDITOR.tools.getNextId() + '_colorBox';
colorBoxId = CKEDITOR.tools.getNextId() + '_colorBox',
panelBlock;

options = options || {};

Expand All @@ -105,6 +106,8 @@ CKEDITOR.plugins.add( 'colorbutton', {
},

onBlock: function( panel, block ) {
panelBlock = block;

block.autoSize = true;
block.element.addClass( 'cke_colorblock' );
block.element.setHtml( renderColors( panel, type, colorBoxId ) );
Expand Down Expand Up @@ -135,7 +138,7 @@ CKEDITOR.plugins.add( 'colorbutton', {
var selection = editor.getSelection(),
block = selection && selection.getStartElement(),
path = editor.elementPath( block ),
color;
automaticColor;

if ( !path )
return;
Expand All @@ -145,19 +148,46 @@ CKEDITOR.plugins.add( 'colorbutton', {

// The background color might be transparent. In that case, look up the color in the DOM tree.
do {
color = block && block.getComputedStyle( type == 'back' ? 'background-color' : 'color' ) || 'transparent';
automaticColor = block && block.getComputedStyle( type == 'back' ? 'background-color' : 'color' ) || 'transparent';
}
while ( type == 'back' && color == 'transparent' && block && ( block = block.getParent() ) );
while ( type == 'back' && automaticColor == 'transparent' && block && ( block = block.getParent() ) );

// The box should never be transparent.
if ( !color || color == 'transparent' )
color = '#ffffff';
if ( !automaticColor || automaticColor == 'transparent' )
automaticColor = '#ffffff';

if ( config.colorButton_enableAutomatic !== false ) {
this._.panel._.iframe.getFrameDocument().getById( colorBoxId ).setStyle( 'background-color', color );
this._.panel._.iframe.getFrameDocument().getById( colorBoxId ).setStyle( 'background-color', automaticColor );
}

return color;
var range = selection && selection.getRanges()[ 0 ];

if ( range ) {
var walker = new CKEDITOR.dom.walker( range ),
element = range.collapsed ? range.startContainer : walker.next(),
finalColor = '',
currentColor;

while ( element ) {
if ( element.type === CKEDITOR.NODE_TEXT ) {
element = element.getParent();
}

currentColor = normalizeColor( element.getComputedStyle( type == 'back' ? 'background-color' : 'color' ) );
finalColor = finalColor || currentColor;

if ( finalColor !== currentColor ) {
finalColor = '';
break;
}

element = walker.next();
}

selectColor( panelBlock, finalColor );
}

return automaticColor;
}
} );
}
Expand Down Expand Up @@ -255,6 +285,7 @@ CKEDITOR.plugins.add( 'colorbutton', {
' title="', colorLabel, '"' +
' onclick="CKEDITOR.tools.callFunction(', clickFn, ',\'', colorName, '\',\'', type, '\'); return false;"' +
' href="javascript:void(\'', colorLabel, '\')"' +
' data-value="' + colorCode + '"' +
' role="option" aria-posinset="', ( i + 2 ), '" aria-setsize="', total, '">' +
'<span class="cke_colorbox" style="background-color:#', colorCode, '"></span>' +
'</a>' +
Expand All @@ -281,6 +312,36 @@ CKEDITOR.plugins.add( 'colorbutton', {
function isUnstylable( ele ) {
return ( ele.getAttribute( 'contentEditable' ) == 'false' ) || ele.getAttribute( 'data-nostyle' );
}

/**
* Selects the specified color in the specified panel block.
*
* @param block
* @param color
*/
function selectColor( block, color ) {
var items = block._.getItems();

for ( var i = 0; i < items.count(); i++ ) {
var item = items.getItem( i );

item.removeAttribute( 'aria-selected' );

if ( color && color == normalizeColor( item.getAttribute( 'data-value' ) ) ) {
item.setAttribute( 'aria-selected', true );
}
}
}

/**
* Converts a CSS color value to an easily comparable form.
*
* @param {string} color
* @returns {string}
*/
function normalizeColor( color ) {
return CKEDITOR.tools.convertRgbToHex( color || '' ).replace( /#/, '' ).toLowerCase();
}
}
} );

Expand Down
10 changes: 10 additions & 0 deletions plugins/floatpanel/plugin.js
Expand Up @@ -416,6 +416,16 @@ CKEDITOR.plugins.add( 'floatpanel', {

// We need this get fired manually because of unfired focus() function.
this.allowBlur( true );

// Ensure that the first item is focused (#16804).
if ( CKEDITOR.env.ie ) {
CKEDITOR.tools.setTimeout( function() {
block.markFirstDisplayed ? block.markFirstDisplayed() : block._.markFirstDisplayed();
}, 0 );
} else {
block.markFirstDisplayed ? block.markFirstDisplayed() : block._.markFirstDisplayed();
}

this._.editor.fire( 'panelShow', this );
}, 0, this );
}, CKEDITOR.env.air ? 200 : 0, this );
Expand Down
8 changes: 8 additions & 0 deletions plugins/listblock/plugin.js
Expand Up @@ -180,6 +180,14 @@ CKEDITOR.plugins.add( 'listblock', {
this.onMark && this.onMark( item );
},

markFirstDisplayed: function() {
var context = this;
this._.markFirstDisplayed( function() {
if ( !context.multiSelect )
context.unmarkAll();
} );
},

unmark: function( value ) {
var doc = this.element.getDocument(),
itemId = this._.items[ value ],
Expand Down
55 changes: 55 additions & 0 deletions plugins/panel/plugin.js
Expand Up @@ -307,6 +307,61 @@
item.focus();

this.onMark && this.onMark( item );
},

/**
* Marks the first visible item or the one who's `aria-selected` attribute is set to `true`.
* The latter has priority over the former.
*
* @private
* @param beforeMark function to be executed just before marking.
* Used in cases when any preparatory cleanup(like unmarking all items) would simultaneously
* destroy the information that's needed to determine the focused item.
*/
markFirstDisplayed: function( beforeMark ) {
var notDisplayed = function( element ) {
return element.type == CKEDITOR.NODE_ELEMENT && element.getStyle( 'display' ) == 'none';
},
links = this._.getItems(),
item, focused;

for ( var i = links.count() - 1; i >= 0; i-- ) {
item = links.getItem( i );

if ( !item.getAscendant( notDisplayed ) ) {
focused = item;
this._.focusIndex = i;
}

if ( item.getAttribute( 'aria-selected' ) == 'true' ) {
focused = item;
this._.focusIndex = i;
break;
}
}

if ( !focused ) {
return;
}

if ( beforeMark ) {
beforeMark();
}

if ( CKEDITOR.env.webkit )
focused.getDocument().getWindow().focus();
focused.focus();

this.onMark && this.onMark( focused );
},

/**
* Returns a `CKEDITOR.dom.nodeList` of block items.
*
* @returns {*|CKEDITOR.dom.nodeList}
*/
getItems: function() {
return this.element.getElementsByTag( 'a' );
}
},

Expand Down
15 changes: 0 additions & 15 deletions plugins/richcombo/plugin.js
Expand Up @@ -189,15 +189,6 @@ CKEDITOR.plugins.add( 'richcombo', {

var keystroke = ev.getKeystroke();

// ARROW-DOWN
// This call is duplicated in plugins/toolbar/plugin.js in itemKeystroke().
// Move focus to the first element after drop down was opened by the arrow down key.
if ( keystroke == 40 ) {
editor.once( 'panelShow', function( evt ) {
evt.data._.panel._.currentBlock.onKeyDown( 40 );
} );
}

switch ( keystroke ) {
case 13: // ENTER
case 32: // SPACE
Expand Down Expand Up @@ -266,12 +257,6 @@ CKEDITOR.plugins.add( 'richcombo', {

if ( me.onOpen )
me.onOpen();

// The "panelShow" event is fired assinchronously, after the
// onShow method call.
editor.once( 'panelShow', function() {
list.focus( !list.multiSelect && me.getValue() );
} );
};

panel.onHide = function( preventOnClose ) {
Expand Down
4 changes: 0 additions & 4 deletions plugins/toolbar/plugin.js
Expand Up @@ -118,10 +118,6 @@
return false;
case 40: // DOWN-ARROW
if ( item.button && item.button.hasArrow ) {
// Note: code is duplicated in plugins\richcombo\plugin.js in keyDownFn().
editor.once( 'panelShow', function( evt ) {
evt.data._.panel._.currentBlock.onKeyDown( 40 );
} );
item.execute();
} else {
// Send left arrow key.
Expand Down
83 changes: 83 additions & 0 deletions tests/plugins/panel/focusonopen.js
@@ -0,0 +1,83 @@
/* bender-tags: editor,unit,panel */
/* bender-ckeditor-plugins: pagebreak,toolbar,clipboard,panel */

( function() {
'use strict';

bender.editor = {
creator: 'replace'
};

function createBlock() {
var parent = new CKEDITOR.dom.element( 'div' );
CKEDITOR.document.getBody().append( parent );

var block = new CKEDITOR.ui.panel.block( parent, { attributes: {} } );

for ( var i = 0; i < 4; i++ ) {
var item = new CKEDITOR.dom.element( 'p' );
var link = new CKEDITOR.dom.element( 'a' );
item.append( link );
block.element.append( item );
}
block.show();

return block;
}

bender.test( {
'test mark first panel block item': function() {
var block = createBlock(),
timeout = CKEDITOR.tools.setTimeout;

block.onMark = sinon.spy();

CKEDITOR.tools.setTimeout = function( callback ) {
callback();
};

block._.markFirstDisplayed();

CKEDITOR.tools.setTimeout = timeout;

assert.isTrue( block.onMark.calledWith( block.element.getElementsByTag( 'a' ).getItem( 0 ) ) );
},

'test mark first displayed panel block item': function() {
var block = createBlock(),
timeout = CKEDITOR.tools.setTimeout;

block.onMark = sinon.spy();
block.element.getElementsByTag( 'p' ).getItem( 0 ).setStyle( 'display', 'none' );

CKEDITOR.tools.setTimeout = function( callback ) {
callback();
};

block._.markFirstDisplayed();

CKEDITOR.tools.setTimeout = timeout;

assert.isTrue( block.onMark.calledWith( block.element.getElementsByTag( 'a' ).getItem( 1 ) ) );
},

'test mark first displayed item with aria-selected': function() {
var block = createBlock(),
itemIndex = 2,
timeout = CKEDITOR.tools.setTimeout;

block.onMark = sinon.spy();
block.element.getElementsByTag( 'a' ).getItem( itemIndex ).setAttribute( 'aria-selected', true );

CKEDITOR.tools.setTimeout = function( callback ) {
callback();
};

block._.markFirstDisplayed();

CKEDITOR.tools.setTimeout = timeout;

assert.isTrue( block.onMark.calledWith( block.element.getElementsByTag( 'a' ).getItem( itemIndex ) ) );
}
} );
} )();
12 changes: 12 additions & 0 deletions tests/plugins/panel/manual/contextmenua11y.html
@@ -0,0 +1,12 @@
<div id="editor">
<table>
<tr>
<td>Check the cell's properties</td>
<td></td>
</tr>
</table>
</div>

<script>
CKEDITOR.replace( 'editor' );
</script>
16 changes: 16 additions & 0 deletions tests/plugins/panel/manual/contextmenua11y.md
@@ -0,0 +1,16 @@
@bender-tags: 4.7.0, tc, 16804
@bender-ui: collapsed
@bender-ckeditor-plugins: wysiwygarea,toolbar,elementspath,floatpanel,panel,menu,contextmenu,clipboard,a11yhelp,table,tabletools

### Test Scenario

1. Focus the editor and press `Ctrl + Shift + F10` to open the context menu.
2. Navigate through the menu.

#### Expected

In each opened menu or submenu the first active element is focused.

#### Unexpected

The first element is not focused.
6 changes: 6 additions & 0 deletions tests/plugins/panel/manual/toolbara11y.html
@@ -0,0 +1,6 @@
<div id="editor">
</div>

<script>
CKEDITOR.replace( 'editor' );
</script>

0 comments on commit 61a6108

Please sign in to comment.