diff --git a/src/widget.js b/src/widget.js
index 7eee3781..906436e3 100644
--- a/src/widget.js
+++ b/src/widget.js
@@ -164,7 +164,8 @@ export default class Widget extends Plugin {
*/
_onKeydown( eventInfo, domEventData ) {
const keyCode = domEventData.keyCode;
- const isForward = keyCode == keyCodes.arrowdown || keyCode == keyCodes.arrowright;
+ const isLtrContent = this.editor.locale.contentLanguageDirection === 'ltr';
+ const isForward = keyCode == keyCodes.arrowdown || keyCode == keyCodes[ isLtrContent ? 'arrowright' : 'arrowleft' ];
let wasHandled = false;
// Checks if the keys were handled and then prevents the default event behaviour and stops
diff --git a/tests/manual/keyboard.html b/tests/manual/keyboard.html
new file mode 100644
index 00000000..c0d9902e
--- /dev/null
+++ b/tests/manual/keyboard.html
@@ -0,0 +1,46 @@
+
+
+
+
+LTR content
+
+
+
Heading 1
+
Parainline widgetgraph
+
+
Paragraph
+
+
+RTL content
+
+
+
مرحبا
+
مرحباinline widgetمرحبا
+
+
مرحبا
+
diff --git a/tests/manual/keyboard.js b/tests/manual/keyboard.js
new file mode 100644
index 00000000..7e56d893
--- /dev/null
+++ b/tests/manual/keyboard.js
@@ -0,0 +1,130 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import Widget from '../../src/widget';
+import { toWidget, viewToModelPositionOutsideModelElement } from '../../src/utils';
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+
+function BlockWidget( editor ) {
+ editor.model.schema.register( 'div', {
+ allowIn: [ '$root' ],
+ isObject: true
+ } );
+
+ editor.conversion.for( 'downcast' ).elementToElement( {
+ model: 'div',
+ view: ( modelElement, writer ) => {
+ return toWidget(
+ writer.createContainerElement( 'div', {
+ class: 'widget'
+ } ),
+ writer,
+ { hasSelectionHandler: true }
+ );
+ }
+ } );
+
+ editor.conversion.for( 'upcast' ).elementToElement( {
+ model: 'div',
+ view: 'div'
+ } );
+}
+
+class InlineWidget extends Plugin {
+ constructor( editor ) {
+ super( editor );
+
+ editor.model.schema.register( 'placeholder', {
+ allowWhere: '$text',
+ isObject: true,
+ isInline: true
+ } );
+
+ editor.conversion.for( 'editingDowncast' ).elementToElement( {
+ model: 'placeholder',
+ view: ( modelItem, viewWriter ) => {
+ const widgetElement = createPlaceholderView( modelItem, viewWriter );
+
+ return toWidget( widgetElement, viewWriter );
+ }
+ } );
+
+ editor.conversion.for( 'dataDowncast' ).elementToElement( {
+ model: 'placeholder',
+ view: createPlaceholderView
+ } );
+
+ editor.conversion.for( 'upcast' ).elementToElement( {
+ view: 'placeholder',
+ model: 'placeholder'
+ } );
+
+ editor.editing.mapper.on(
+ 'viewToModelPosition',
+ viewToModelPositionOutsideModelElement( editor.model, viewElement => viewElement.name == 'placeholder' )
+ );
+
+ function createPlaceholderView( modelItem, viewWriter ) {
+ const widgetElement = viewWriter.createContainerElement( 'placeholder' );
+ const viewText = viewWriter.createText( '{placeholder}' );
+
+ viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), viewText );
+
+ return widgetElement;
+ }
+ }
+}
+
+const config = {
+ plugins: [ ArticlePluginSet, Widget, InlineWidget, BlockWidget ],
+ toolbar: [
+ 'heading',
+ '|',
+ 'bold',
+ 'italic',
+ 'link',
+ 'bulletedList',
+ 'numberedList',
+ 'blockQuote',
+ 'insertTable',
+ 'mediaEmbed',
+ 'undo',
+ 'redo'
+ ],
+ image: {
+ toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
+ },
+ table: {
+ contentToolbar: [
+ 'tableColumn',
+ 'tableRow',
+ 'mergeTableCells'
+ ]
+ }
+};
+
+ClassicEditor
+ .create( document.querySelector( '#editor-ltr' ), config )
+ .then( editor => {
+ window.editorLtr = editor;
+ } )
+ .catch( err => {
+ console.error( err.stack );
+ } );
+
+ClassicEditor
+ .create( document.querySelector( '#editor-rtl' ), Object.assign( {}, config, {
+ language: 'ar'
+ } ) )
+ .then( editor => {
+ window.editorRtl = editor;
+ } )
+ .catch( err => {
+ console.error( err.stack );
+ } );
diff --git a/tests/manual/keyboard.md b/tests/manual/keyboard.md
new file mode 100644
index 00000000..1756954e
--- /dev/null
+++ b/tests/manual/keyboard.md
@@ -0,0 +1,20 @@
+# Keyboard navigation across widgets
+
+1. Put a selection at the beginning of the document.
+2. Use the **right arrow** key to navigate forwards.
+3. Go through inline and block widgets.
+4. The navigation should
+ * be uninterrupted,
+ * go always forward,
+ * select widgets on its way.
+5. Reach the end of the document.
+6. Go backwards using the **left arrow** key and repeat the entire scenario.
+
+## RTL (right–to–left) content navigation
+
+In this scenario the content is written in Arabic.
+
+1. Repeat the scenario from the previous section but note that the content is mirrored, i.e. the beginning is on the right–hand side.
+2. Forwards navigation is done by pressing the **left arrow** key.
+3. To go backwards, use the **right arrow** key.
+4. Just like with the LTR content, the navigation should be seamless, always in the same direction.
diff --git a/tests/widget.js b/tests/widget.js
index a4912a0e..bd1f7486 100644
--- a/tests/widget.js
+++ b/tests/widget.js
@@ -657,6 +657,44 @@ describe( 'Widget', () => {
'[]' +
'foo'
);
+
+ describe( 'RTL (right-to-left) content', () => {
+ test(
+ 'should move selection forward from selected object - left arrow',
+ '[]foo',
+ keyCodes.arrowleft,
+ '[]foo',
+ null,
+ 'rtl'
+ );
+
+ test(
+ 'should move selection backward from selected object - right arrow',
+ 'foo[]',
+ keyCodes.arrowright,
+ 'foo[]',
+ null,
+ 'rtl'
+ );
+
+ test(
+ 'should move selection to next widget - left arrow',
+ '[]',
+ keyCodes.arrowleft,
+ '[]',
+ null,
+ 'rtl'
+ );
+
+ test(
+ 'should move selection to previous widget - right arrow',
+ '[]',
+ keyCodes.arrowright,
+ '[]',
+ null,
+ 'rtl'
+ );
+ } );
} );
describe( 'Ctrl+A', () => {
@@ -782,8 +820,10 @@ describe( 'Widget', () => {
);
} );
- function test( name, data, keyCodeOrMock, expected, expectedView ) {
+ function test( name, data, keyCodeOrMock, expected, expectedView, contentLanguageDirection = 'ltr' ) {
it( name, () => {
+ testUtils.sinon.stub( editor.locale, 'contentLanguageDirection' ).value( contentLanguageDirection );
+
const domEventDataMock = ( typeof keyCodeOrMock == 'object' ) ? keyCodeOrMock : {
keyCode: keyCodeOrMock
};