Kaynağa Gözat

Other: The toolbar should support a vertical offset from the top of the web page. Closes #23.

Aleksander Nowodzinski 8 yıl önce
ebeveyn
işleme
6955460ecd

+ 17 - 1
packages/ckeditor5-editor-inline/src/inlineeditorui.js

@@ -10,6 +10,7 @@
 import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
 import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
 import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
+import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
 
 /**
  * The inline editor UI class.
@@ -44,9 +45,21 @@ export default class InlineEditorUI {
 		 */
 		this.focusTracker = new FocusTracker();
 
+		/**
+		 * A normalized `config.toolbar` object.
+		 *
+		 * @type {Object}
+		 * @private
+		 */
+		this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
+
 		// Set–up the view#panel.
 		view.panel.bind( 'isVisible' ).to( this.focusTracker, 'isFocused' );
 
+		if ( this._toolbarConfig ) {
+			view.viewportTopOffset = this._toolbarConfig.viewportTopOffset;
+		}
+
 		// https://github.com/ckeditor/ckeditor5-editor-inline/issues/4
 		view.listenTo( editor.editing.view, 'render', () => {
 			// Don't pin if the panel is not already visible. It prevents the panel
@@ -78,7 +91,10 @@ export default class InlineEditorUI {
 		const editor = this.editor;
 
 		this.view.init();
-		this.view.toolbar.fillFromConfig( editor.config.get( 'toolbar' ), this.componentFactory );
+
+		if ( this._toolbarConfig ) {
+			this.view.toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+		}
 
 		enableToolbarKeyboardFocus( {
 			origin: editor.editing.view,

+ 104 - 86
packages/ckeditor5-editor-inline/src/inlineeditoruiview.js

@@ -13,28 +13,6 @@ import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpa
 import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
 import Template from '@ckeditor/ckeditor5-ui/src/template';
 
-// A set of positioning functions used by the
-// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panel}.
-//
-// @private
-// @type {module:utils/dom/position~Options#positions}
-const panelPositions = [
-	( editableRect, panelRect ) => {
-		return {
-			top: getPanelPositionTop( editableRect, panelRect ),
-			left: editableRect.left,
-			name: 'toolbar_west'
-		};
-	},
-	( editableRect, panelRect ) => {
-		return {
-			top: getPanelPositionTop( editableRect, panelRect ),
-			left: editableRect.left + editableRect.width - panelRect.width,
-			name: 'toolbar_east'
-		};
-	}
-];
-
 /**
  * Inline editor UI view. Uses inline editable and floating toolbar.
  *
@@ -57,6 +35,21 @@ export default class InlineEditorUIView extends EditorUIView {
 		 */
 		this.toolbar = new ToolbarView( locale );
 
+		/**
+		 * The offset from the top edge of the web browser's viewport which makes the
+		 * UI become sticky. The default value is `0`, which means the UI becomes
+		 * sticky when it's upper edge touches the top of the page viewport.
+		 *
+		 * This attribute is useful when the web page has UI elements positioned to the top
+		 * either using `position: fixed` or `position: sticky`, which would cover the
+		 * UI or vice–versa (depending on the `z-index` hierarchy).
+		 *
+		 * @readonly
+		 * @observable
+		 * @member {Number} #viewportTopOffset
+		 */
+		this.set( 'viewportTopOffset', 0 );
+
 		Template.extend( this.toolbar.template, {
 			attributes: {
 				class: [
@@ -78,6 +71,53 @@ export default class InlineEditorUIView extends EditorUIView {
 
 		this.panel.withArrow = false;
 
+		/**
+		 * A set of positioning functions used by the {@link #panel} to float around
+		 * {@link #editableElement}.
+		 *
+		 * The positioning functions are as follows:
+		 *
+		 * * West:
+		 *
+		 *		[ Panel ]
+		 *		+------------------+
+		 *		| #editableElement |
+		 *		+------------------+
+		 *
+		 *		+------------------+
+		 *		| #editableElement |
+		 *		|[ Panel ]         |
+		 *		|                  |
+		 *		+------------------+
+		 *
+		 *		+------------------+
+		 *		| #editableElement |
+		 *		+------------------+
+		 *		[ Panel ]
+		 *
+		 * * East:
+		 *
+		 *		           [ Panel ]
+		 *		+------------------+
+		 *		| #editableElement |
+		 *		+------------------+
+		 *
+		 *		+------------------+
+		 *		| #editableElement |
+		 *		|         [ Panel ]|
+		 *		|                  |
+		 *		+------------------+
+		 *
+		 *		+------------------+
+		 *		| #editableElement |
+		 *		+------------------+
+		 *		           [ Panel ]
+		 *
+		 * @readonly
+		 * @type {module:utils/dom/position~Options#positions}
+		 */
+		this.panelPositions = this._getPanelPositions();
+
 		Template.extend( this.panel.template, {
 			attributes: {
 				class: 'ck-toolbar-container'
@@ -113,73 +153,51 @@ export default class InlineEditorUIView extends EditorUIView {
 	}
 
 	/**
-	 * A set of positioning functions used by the {@link #panel} to float around
-	 * {@link #editableElement}.
-	 *
-	 * The positioning functions are as follows:
-	 *
-	 * * West:
-	 *
-	 *		[ Panel ]
-	 *		+------------------+
-	 *		| #editableElement |
-	 *		+------------------+
-	 *
-	 *  	+------------------+
-	 *		| #editableElement |
-	 *		|[ Panel ]         |
-	 *		|                  |
-	 *		+------------------+
-	 *
-	 *		+------------------+
-	 *		| #editableElement |
-	 *		+------------------+
-	 *		[ Panel ]
-	 *
-	 * * East:
-	 *
-	 *		           [ Panel ]
-	 *		+------------------+
-	 *		| #editableElement |
-	 *		+------------------+
+	 * Determines panel top position of the {@link #panel} in {@link #panelPositions}.
 	 *
-	 *  	+------------------+
-	 *		| #editableElement |
-	 *		|         [ Panel ]|
-	 *		|                  |
-	 *		+------------------+
-	 *
-	 *		+------------------+
-	 *		| #editableElement |
-	 *		+------------------+
-	 *		           [ Panel ]
-	 *
-	 * @readonly
-	 * @type {module:utils/dom/position~Options#positions}
+	 * @private
+	 * @param {module:utils/dom/rect~Rect} editableRect Rect of the
+	 * {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#editableElement}.
+	 * @param {module:utils/dom/rect~Rect} panelRect Rect of the
+	 * {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panel}.
 	 */
-	get panelPositions() {
-		return panelPositions;
+	_getPanelPositionTop( editableRect, panelRect ) {
+		let top;
+
+		if ( editableRect.top > panelRect.height + this.viewportTopOffset ) {
+			top = editableRect.top - panelRect.height;
+		} else if ( editableRect.bottom > panelRect.height + this.viewportTopOffset + 50 ) {
+			top = this.viewportTopOffset;
+		} else {
+			top = editableRect.bottom;
+		}
+
+		return top;
 	}
-}
 
-// Determines panel top position for
-// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panelPositions}
-//
-// @private
-// @param {module:utils/dom/rect~Rect} editableRect Rect of the
-// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#editableElement}.
-// @param {module:utils/dom/rect~Rect} panelRect Rect of the
-// {@link module:editor-inline/inlineeditoruiview~InlineEditableUIView#panel}.
-function getPanelPositionTop( editableRect, panelRect ) {
-	let top;
-
-	if ( editableRect.top > panelRect.height ) {
-		top = editableRect.top - panelRect.height;
-	} else if ( editableRect.bottom > panelRect.height + 50 ) {
-		top = 0;
-	} else {
-		top = editableRect.bottom;
+	/**
+	 * Returns the positions for {@link #panelPositions}.
+	 *
+	 * @private
+	 * @returns module:utils/dom/position~Options#positions
+	 */
+	_getPanelPositions() {
+		return [
+			( editableRect, panelRect ) => {
+				return {
+					top: this._getPanelPositionTop( editableRect, panelRect ),
+					left: editableRect.left,
+					name: 'toolbar_west'
+				};
+			},
+			( editableRect, panelRect ) => {
+				return {
+					top: this._getPanelPositionTop( editableRect, panelRect ),
+					left: editableRect.left + editableRect.width - panelRect.width,
+					name: 'toolbar_east'
+				};
+			}
+		];
 	}
-
-	return top;
 }
+

+ 73 - 11
packages/ckeditor5-editor-inline/tests/inlineeditorui.js

@@ -27,16 +27,24 @@ describe( 'InlineEditorUI', () => {
 		editorElement = document.createElement( 'div' );
 		document.body.appendChild( editorElement );
 
-		editor = new ClassicTestEditor( editorElement, {
+		return ClassicTestEditor.create( editorElement, {
 			toolbar: [ 'foo', 'bar' ]
-		} );
+		} )
+		.then( newEditor => {
+			editor = newEditor;
+
+			view = new InlineEditorUIView( editor.locale );
+			ui = new InlineEditorUI( editor, view );
+			editable = editor.editing.view.getRoot();
 
-		view = new InlineEditorUIView( editor.locale );
-		ui = new InlineEditorUI( editor, view );
-		editable = editor.editing.view.getRoot();
+			ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
+			ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );
+		} );
+	} );
 
-		ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
-		ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );
+	afterEach( () => {
+		editorElement.remove();
+		editor.destroy();
 	} );
 
 	describe( 'constructor()', () => {
@@ -65,6 +73,31 @@ describe( 'InlineEditorUI', () => {
 				expect( view.panel.isVisible ).to.be.true;
 			} );
 
+			it( 'sets view#viewportTopOffset', () => {
+				editorElement = document.createElement( 'div' );
+				document.body.appendChild( editorElement );
+
+				return ClassicTestEditor.create( editorElement, {
+					toolbar: {
+						items: [ 'foo', 'bar' ],
+						viewportTopOffset: 100
+					}
+				} )
+				.then( editor => {
+					view = new InlineEditorUIView( editor.locale );
+					ui = new InlineEditorUI( editor, view );
+					editable = editor.editing.view.getRoot();
+
+					ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
+					ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );
+
+					expect( view.viewportTopOffset ).to.equal( 100 );
+
+					editorElement.remove();
+					return editor.destroy();
+				} );
+			} );
+
 			// https://github.com/ckeditor/ckeditor5-editor-inline/issues/4
 			it( 'pin() is called on editor.editable.view#render', () => {
 				const spy = sinon.spy( view.panel, 'pin' );
@@ -133,11 +166,40 @@ describe( 'InlineEditorUI', () => {
 			sinon.assert.calledOnce( spy );
 		} );
 
-		it( 'fills view.toolbar#items with editor config', () => {
-			const spy = testUtils.sinon.spy( view.toolbar, 'fillFromConfig' );
+		describe( 'view.toolbar#items', () => {
+			it( 'are filled with the config.toolbar (specified as an Array)', () => {
+				const spy = testUtils.sinon.spy( view.toolbar, 'fillFromConfig' );
 
-			ui.init();
-			sinon.assert.calledWithExactly( spy, editor.config.get( 'toolbar' ), ui.componentFactory );
+				ui.init();
+				sinon.assert.calledWithExactly( spy, editor.config.get( 'toolbar' ), ui.componentFactory );
+			} );
+
+			it( 'are filled with the config.toolbar (specified as an Object)', () => {
+				editorElement = document.createElement( 'div' );
+				document.body.appendChild( editorElement );
+
+				return ClassicTestEditor.create( editorElement, {
+					toolbar: {
+						items: [ 'foo', 'bar' ],
+						viewportTopOffset: 100
+					}
+				} )
+				.then( editor => {
+					view = new InlineEditorUIView( editor.locale );
+					ui = new InlineEditorUI( editor, view );
+
+					ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
+					ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );
+
+					const spy = testUtils.sinon.spy( view.toolbar, 'fillFromConfig' );
+
+					ui.init();
+					sinon.assert.calledWithExactly( spy,
+						editor.config.get( 'toolbar.items' ),
+						ui.componentFactory
+					);
+				} );
+			} );
 		} );
 
 		it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {

+ 48 - 0
packages/ckeditor5-editor-inline/tests/inlineeditoruiview.js

@@ -226,6 +226,54 @@ describe( 'InlineEditorUIView', () => {
 				expect( top ).to.equal( 150 );
 				expect( left ).to.equal( expectedLeft );
 			} );
+
+			describe( 'view#viewportTopOffset', () => {
+				it( 'sticks the panel to the offset when there\'s not enough space above', () => {
+					view.viewportTopOffset = 50;
+
+					const position = view.panelPositions[ positionIndex ];
+					const editableRect = {
+						top: 0, // !
+						bottom: 200,
+						left: 100,
+						right: 100,
+						width: 100,
+						height: 200
+					};
+					const panelRect = {
+						width: 50,
+						height: 50
+					};
+
+					const { top, left } = position( editableRect, panelRect );
+
+					expect( top ).to.equal( 50 );
+					expect( left ).to.equal( expectedLeft );
+				} );
+
+				it( 'positions the panel below the editable when there\'s not enough space above/over', () => {
+					view.viewportTopOffset = 50;
+
+					const position = view.panelPositions[ positionIndex ];
+					const editableRect = {
+						top: 100,
+						bottom: 150,
+						left: 100,
+						right: 100,
+						width: 100,
+						height: 50
+					};
+					const panelRect = {
+						width: 50,
+						height: 80
+					};
+
+					const { top, left } = position( editableRect, panelRect );
+
+					expect( top ).to.equal( 150 );
+					expect( left ).to.equal( expectedLeft );
+				} );
+			} );
 		}
 	} );
 } );