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

Commit

Permalink
Merge pull request #520 from ckeditor/t/ckeditor5/1151
Browse files Browse the repository at this point in the history
Feature: Brought support for right–to–left (RTL) languages to various UI components. See ckeditor/ckeditor5#1151.
  • Loading branch information
Reinmar authored Aug 12, 2019
2 parents e89ad60 + 31881a8 commit d6c7f55
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 104 deletions.
8 changes: 8 additions & 0 deletions docs/features/blocktoolbar.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ To adjust the position of the block toolbar button to match the styles of your w
}
```

If you plan to run the editor in a right–to–left (RTL) language, keep in mind the button will be attached to the **right** boundary of the editable area. In that case, make sure the CSS position adjustment works properly by adding the following styles:

```css
.ck[dir="rtl"] .ck-block-toolbar-button {
transform: translateX( 10px );
}
```

## Installation

<info-box hint>
Expand Down
37 changes: 28 additions & 9 deletions src/dropdown/dropdownview.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,18 +253,11 @@ export default class DropdownView extends View {
// If "auto", find the best position of the panel to fit into the viewport.
// Otherwise, simply assign the static position.
if ( this.panelPosition === 'auto' ) {
const defaultPanelPositions = DropdownView.defaultPanelPositions;

this.panelView.position = getOptimalPosition( {
this.panelView.position = DropdownView._getOptimalPosition( {
element: this.panelView.element,
target: this.buttonView.element,
fitInViewport: true,
positions: [
defaultPanelPositions.southEast,
defaultPanelPositions.southWest,
defaultPanelPositions.northEast,
defaultPanelPositions.northWest
]
positions: this._panelPositions
} ).name;
} else {
this.panelView.position = this.panelPosition;
Expand Down Expand Up @@ -312,6 +305,24 @@ export default class DropdownView extends View {
focus() {
this.buttonView.focus();
}

/**
* Returns {@link #panelView panel} positions to be used by the
* {@link module:utils/dom/position~getOptimalPosition `getOptimalPosition()`}
* utility considering the direction of the language the UI of the editor is displayed in.
*
* @type {module:utils/dom/position~Options#positions}
* @private
*/
get _panelPositions() {
const { southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;

if ( this.locale.uiLanguageDirection === 'ltr' ) {
return [ southEast, southWest, northEast, northWest ];
} else {
return [ southWest, southEast, northWest, northEast ];
}
}
}

/**
Expand Down Expand Up @@ -392,3 +403,11 @@ DropdownView.defaultPanelPositions = {
};
}
};

/**
* A function used to calculate the optimal position for the dropdown panel.
*
* @protected
* @member {Function} module:ui/dropdown/dropdownview~DropdownView._getOptimalPosition
*/
DropdownView._getOptimalPosition = getOptimalPosition;
4 changes: 3 additions & 1 deletion src/editableui/editableuiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export default class EditableUIView extends View {
'ck-content',
'ck-editor__editable',
'ck-rounded-corners'
]
],
lang: locale.contentLanguage,
dir: locale.contentLanguageDirection
}
} );

Expand Down
4 changes: 2 additions & 2 deletions src/editorui/boxed/boxededitoruiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export default class BoxedEditorUIView extends EditorUIView {
'ck-rounded-corners'
],
role: 'application',
dir: 'ltr',
lang: locale.language,
dir: locale.uiLanguageDirection,
lang: locale.uiLanguage,
'aria-labelledby': `ck-editor__aria-label_${ ariaLabelUid }`
},

Expand Down
4 changes: 3 additions & 1 deletion src/editorui/editoruiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default class EditorUIView extends View {
* @private
*/
_renderBodyCollection() {
const locale = this.locale;
const bodyElement = this._bodyCollectionContainer = new Template( {
tag: 'div',
attributes: {
Expand All @@ -77,7 +78,8 @@ export default class EditorUIView extends View {
'ck-reset_all',
'ck-body',
'ck-rounded-corners'
]
],
dir: locale.uiLanguageDirection,
},
children: this.body
} ).render();
Expand Down
20 changes: 18 additions & 2 deletions src/toolbar/block/blocktoolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ import iconPilcrow from '@ckeditor/ckeditor5-core/theme/icons/pilcrow.svg';
* | block of content that the button is
* | attached to.
*
* **Note**: If you plan to run the editor in a right–to–left (RTL) language, keep in mind the button
* will be attached to the **right** boundary of the editable area. In that case, make sure the
* CSS position adjustment works properly by adding the following styles:
*
* .ck[dir="rtl"] .ck-block-toolbar-button {
* transform: translateX( 10px );
* }
*
* @extends module:core/plugin~Plugin
*/
export default class BlockToolbar extends Plugin {
Expand Down Expand Up @@ -361,9 +369,17 @@ export default class BlockToolbar extends Plugin {
target: targetElement,
positions: [
( contentRect, buttonRect ) => {
let left;

if ( this.editor.locale.uiLanguageDirection === 'ltr' ) {
left = editableRect.left - buttonRect.width;
} else {
left = editableRect.right;
}

return {
top: contentRect.top + contentPaddingTop + ( ( contentLineHeight - buttonRect.height ) / 2 ),
left: editableRect.left - buttonRect.width
top: contentRect.top + contentPaddingTop + ( contentLineHeight - buttonRect.height ) / 2,
left
};
}
]
Expand Down
170 changes: 88 additions & 82 deletions tests/dropdown/dropdownview.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
import ButtonView from '../../src/button/buttonview';
import DropdownPanelView from '../../src/dropdown/dropdownpanelview';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';

describe( 'DropdownView', () => {
Expand All @@ -19,7 +18,10 @@ describe( 'DropdownView', () => {
testUtils.createSinonSandbox();

beforeEach( () => {
locale = { t() {} };
locale = {
uiLanguageDirection: 'ltr',
t() {}
};

buttonView = new ButtonView( locale );
panelView = new DropdownPanelView( locale );
Expand Down Expand Up @@ -116,7 +118,7 @@ describe( 'DropdownView', () => {
} );

describe( 'view.panelView#position to view#panelPosition', () => {
it( 'does not update until the dropdown is opened', () => {
it( 'does not update until the dropdown is open', () => {
view.isOpen = false;
view.panelPosition = 'nw';

Expand All @@ -128,86 +130,37 @@ describe( 'DropdownView', () => {
} );

describe( 'in "auto" mode', () => {
beforeEach( () => {
// Bloat the panel a little to give the positioning algorithm something to
// work with. If the panel was empty, any smart positioning is pointless.
// Placing an empty element in the viewport isn't that hard, right?
panelView.element.style.width = '200px';
panelView.element.style.height = '200px';
} );

it( 'defaults to "south-east" when there is a plenty of space around', () => {
const windowRect = new Rect( global.window );

// "Put" the dropdown in the middle of the viewport.
stubElementClientRect( view.buttonView.element, {
top: windowRect.height / 2,
left: windowRect.width / 2,
width: 10,
height: 10
} );

view.isOpen = true;

expect( panelView.position ).to.equal( 'se' );
} );

it( 'when the dropdown in the north-west corner of the viewport', () => {
stubElementClientRect( view.buttonView.element, {
top: 0,
left: 0,
width: 100,
height: 10
} );
it( 'uses _getOptimalPosition() and a dedicated set of positions (LTR)', () => {
const spy = testUtils.sinon.spy( DropdownView, '_getOptimalPosition' );
const { southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;

view.isOpen = true;

expect( panelView.position ).to.equal( 'se' );
sinon.assert.calledWithExactly( spy, sinon.match( {
element: panelView.element,
target: buttonView.element,
positions: [
southEast, southWest, northEast, northWest
],
fitInViewport: true
} ) );
} );

it( 'when the dropdown in the north-east corner of the viewport', () => {
const windowRect = new Rect( global.window );

stubElementClientRect( view.buttonView.element, {
top: 0,
left: windowRect.right - 100,
width: 100,
height: 10
} );
it( 'uses _getOptimalPosition() and a dedicated set of positions (RTL)', () => {
const spy = testUtils.sinon.spy( DropdownView, '_getOptimalPosition' );
const { southEast, southWest, northEast, northWest } = DropdownView.defaultPanelPositions;

view.locale.uiLanguageDirection = 'rtl';
view.isOpen = true;

expect( panelView.position ).to.equal( 'sw' );
} );

it( 'when the dropdown in the south-west corner of the viewport', () => {
const windowRect = new Rect( global.window );

stubElementClientRect( view.buttonView.element, {
top: windowRect.bottom - 10,
left: 0,
width: 100,
height: 10
} );

view.isOpen = true;

expect( panelView.position ).to.equal( 'ne' );
} );

it( 'when the dropdown in the south-east corner of the viewport', () => {
const windowRect = new Rect( global.window );

stubElementClientRect( view.buttonView.element, {
top: windowRect.bottom - 10,
left: windowRect.right - 100,
width: 100,
height: 10
} );

view.isOpen = true;

expect( panelView.position ).to.equal( 'nw' );
sinon.assert.calledWithExactly( spy, sinon.match( {
element: panelView.element,
target: buttonView.element,
positions: [
southWest, southEast, northWest, northEast
],
fitInViewport: true
} ) );
} );
} );
} );
Expand Down Expand Up @@ -372,13 +325,66 @@ describe( 'DropdownView', () => {
sinon.assert.calledOnce( spy );
} );
} );
} );

function stubElementClientRect( element, data ) {
const clientRect = Object.assign( {}, data );
describe( 'DropdownView.defaultPanelPositions', () => {
let positions, buttonRect, panelRect;

beforeEach( () => {
positions = DropdownView.defaultPanelPositions;

buttonRect = {
top: 100,
bottom: 200,
left: 100,
right: 200,
width: 100,
height: 100
};

panelRect = {
top: 0,
bottom: 0,
left: 0,
right: 0,
width: 50,
height: 50
};
} );

it( 'should have a proper length', () => {
expect( Object.keys( positions ) ).to.have.length( 4 );
} );

it( 'should define the "southEast" position', () => {
expect( positions.southEast( buttonRect, panelRect ) ).to.deep.equal( {
top: 200,
left: 100,
name: 'se'
} );
} );

it( 'should define the "southWest" position', () => {
expect( positions.southWest( buttonRect, panelRect ) ).to.deep.equal( {
top: 200,
left: 150,
name: 'sw'
} );
} );

clientRect.right = clientRect.left + clientRect.width;
clientRect.bottom = clientRect.top + clientRect.height;
it( 'should define the "northEast" position', () => {
expect( positions.northEast( buttonRect, panelRect ) ).to.deep.equal( {
top: 50,
left: 100,
name: 'ne'
} );
} );

testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( clientRect );
}
it( 'should define the "northWest" position', () => {
expect( positions.northWest( buttonRect, panelRect ) ).to.deep.equal( {
top: 150,
left: 150,
name: 'nw'
} );
} );
} );
} );
Loading

0 comments on commit d6c7f55

Please sign in to comment.