Kaynağa Gözat

Code refactoring: Split the Widget keydown listener into two separate listeners for more flexibility when integrating with other features (TableKeyboard, WidgetTypeAround).

Aleksander Nowodzinski 5 yıl önce
ebeveyn
işleme
73c22728aa

+ 1 - 35
packages/ckeditor5-table/src/tablekeyboard.js

@@ -57,7 +57,7 @@ export default class TableKeyboard extends Plugin {
 		// (like Widgets), which take over the keydown events with the "high" priority. Table navigation takes precedence
 		// (like Widgets), which take over the keydown events with the "high" priority. Table navigation takes precedence
 		// over Widgets in that matter (widget arrow handler stops propagation of event if object element was selected
 		// over Widgets in that matter (widget arrow handler stops propagation of event if object element was selected
 		// but getNearestSelectionRange didn't returned any range).
 		// but getNearestSelectionRange didn't returned any range).
-		this.listenTo( viewDocument, 'keydown', ( ...args ) => this._onKeydown( ...args ), { priority: priorities.get( 'high' ) + 1 } );
+		this.listenTo( viewDocument, 'keydown', ( ...args ) => this._onKeydown( ...args ), { priority: priorities.get( 'high' ) - 10 } );
 	}
 	}
 
 
 	/**
 	/**
@@ -230,19 +230,6 @@ export default class TableKeyboard extends Plugin {
 			return true;
 			return true;
 		}
 		}
 
 
-		// If this is an object selected and it's not at the start or the end of cell content
-		// then let's allow widget handler to take care of it.
-		const objectElement = selection.getSelectedElement();
-
-		if ( objectElement && model.schema.isObject( objectElement ) ) {
-			return false;
-		}
-
-		// If next to the selection there is an object then this is not the cell boundary (widget handler should handle this).
-		if ( this._isObjectElementNextToSelection( selection, isForward ) ) {
-			return false;
-		}
-
 		// If there isn't any $text position between cell edge and selection then we shall move the selection to next cell.
 		// If there isn't any $text position between cell edge and selection then we shall move the selection to next cell.
 		const textRange = this._findTextRangeFromSelection( cellRange, selection, isForward );
 		const textRange = this._findTextRangeFromSelection( cellRange, selection, isForward );
 
 
@@ -307,27 +294,6 @@ export default class TableKeyboard extends Plugin {
 		return focus.isEqual( probe.focus );
 		return focus.isEqual( probe.focus );
 	}
 	}
 
 
-	/**
-	 * Checks if there is an {@link module:engine/model/element~Element element} next to the current
-	 * {@link module:engine/model/selection~Selection model selection} marked in the
-	 * {@link module:engine/model/schema~Schema schema} as an `object`.
-	 *
-	 * @private
-	 * @param {module:engine/model/selection~Selection} modelSelection The selection.
-	 * @param {Boolean} isForward The direction of checking.
-	 * @returns {Boolean}
-	 */
-	_isObjectElementNextToSelection( modelSelection, isForward ) {
-		const model = this.editor.model;
-		const schema = model.schema;
-
-		const probe = model.createSelection( modelSelection );
-		model.modifySelection( probe, { direction: isForward ? 'forward' : 'backward' } );
-		const objectElement = isForward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
-
-		return objectElement && schema.isObject( objectElement );
-	}
-
 	/**
 	/**
 	 * Truncates the range so that it spans from the last selection position
 	 * Truncates the range so that it spans from the last selection position
 	 * to the last allowed `$text` position (mirrored if `isForward` is false).
 	 * to the last allowed `$text` position (mirrored if `isForward` is false).

+ 93 - 52
packages/ckeditor5-widget/src/widget.js

@@ -18,6 +18,7 @@ import {
 import env from '@ckeditor/ckeditor5-utils/src/env';
 import env from '@ckeditor/ckeditor5-utils/src/env';
 
 
 import '../theme/widget.css';
 import '../theme/widget.css';
+import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
 
 
 /**
 /**
  * The widget plugin. It enables base support for widgets.
  * The widget plugin. It enables base support for widgets.
@@ -99,8 +100,24 @@ export default class Widget extends Plugin {
 		view.addObserver( MouseObserver );
 		view.addObserver( MouseObserver );
 		this.listenTo( viewDocument, 'mousedown', ( ...args ) => this._onMousedown( ...args ) );
 		this.listenTo( viewDocument, 'mousedown', ( ...args ) => this._onMousedown( ...args ) );
 
 
-		// Handle custom keydown behaviour.
-		this.listenTo( viewDocument, 'keydown', ( ...args ) => this._onKeydown( ...args ), { priority: 'high' } );
+		// There are two keydown listeners working on different priorities. This allows other
+		// features such as WidgetTypeAround or TableKeyboard to attach their listeners in between
+		// and customize the behavior even further in different content/selection scenarios.
+		//
+		// * The first listener handles changing the selection on arrow key press
+		// if the widget is selected or if the selection is next to a widget and the widget
+		// should become selected upon the arrow key press.
+		//
+		// * The second (late) listener makes sure the default browser action on arrow key press is
+		// prevented when a widget is selected. This prevents the selection from being moved
+		// from a fake selection container.
+		this.listenTo( viewDocument, 'keydown', ( ...args ) => {
+			this._handleSelectionChangeOnArrowKeyPress( ...args );
+		}, { priority: 'high' } );
+
+		this.listenTo( viewDocument, 'keydown', ( ...args ) => {
+			this._preventDefaultOnArrowKeyPress( ...args );
+		}, { priority: priorities.get( 'high' ) - 20 } );
 
 
 		// Handle custom delete behaviour.
 		// Handle custom delete behaviour.
 		this.listenTo( viewDocument, 'delete', ( evt, data ) => {
 		this.listenTo( viewDocument, 'delete', ( evt, data ) => {
@@ -165,25 +182,92 @@ export default class Widget extends Plugin {
 	}
 	}
 
 
 	/**
 	/**
-	 * Handles {@link module:engine/view/document~Document#event:keydown keydown} events.
+	 * Handles {@link module:engine/view/document~Document#event:keydown keydown} events and changes
+	 * the model selection when:
+	 *
+	 * * arrow key is pressed when the widget is selected,
+	 * * the selection is next to a widget and the widget should become selected upon the arrow key press.
+	 *
+	 * See {@link #_preventDefaultOnArrowKeyPress}.
 	 *
 	 *
 	 * @private
 	 * @private
 	 * @param {module:utils/eventinfo~EventInfo} eventInfo
 	 * @param {module:utils/eventinfo~EventInfo} eventInfo
 	 * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
 	 * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
 	 */
 	 */
-	_onKeydown( eventInfo, domEventData ) {
+	_handleSelectionChangeOnArrowKeyPress( eventInfo, domEventData ) {
 		const keyCode = domEventData.keyCode;
 		const keyCode = domEventData.keyCode;
-		let wasHandled = false;
 
 
 		// Checks if the keys were handled and then prevents the default event behaviour and stops
 		// Checks if the keys were handled and then prevents the default event behaviour and stops
 		// the propagation.
 		// the propagation.
-		if ( isArrowKeyCode( keyCode ) ) {
-			const isForward = isForwardArrowKeyCode( keyCode, this.editor.locale.contentLanguageDirection );
+		if ( !isArrowKeyCode( keyCode ) ) {
+			return;
+		}
+
+		const model = this.editor.model;
+		const schema = model.schema;
+		const modelSelection = model.document.selection;
+		const objectElement = modelSelection.getSelectedElement();
+		const isForward = isForwardArrowKeyCode( keyCode, this.editor.locale.contentLanguageDirection );
+
+		// If object element is selected.
+		if ( objectElement && schema.isObject( objectElement ) ) {
+			const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
+			const newRange = schema.getNearestSelectionRange( position, isForward ? 'forward' : 'backward' );
+
+			if ( newRange ) {
+				model.change( writer => {
+					writer.setSelection( newRange );
+				} );
+
+				domEventData.preventDefault();
+				eventInfo.stop();
+
+				return;
+			}
+		}
+
+		// If selection is next to object element.
+		// Return if not collapsed.
+		if ( !modelSelection.isCollapsed ) {
+			return;
+		}
+
+		const objectElement2 = this._getObjectElementNextToSelection( isForward );
+
+		if ( !!objectElement2 && schema.isObject( objectElement2 ) ) {
+			this._setSelectionOverElement( objectElement2 );
+
+			domEventData.preventDefault();
+			eventInfo.stop();
+		}
+	}
+
+	/**
+	 * Handles {@link module:engine/view/document~Document#event:keydown keydown} events and prevents
+	 * the default browser behavior to make sure the fake selection is not being moved from a fake selection
+	 * container.
+	 *
+	 * See {@link #_handleSelectionChangeOnArrowKeyPress}.
+	 *
+	 * @private
+	 * @param {module:utils/eventinfo~EventInfo} eventInfo
+	 * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
+	 */
+	_preventDefaultOnArrowKeyPress( eventInfo, domEventData ) {
+		const keyCode = domEventData.keyCode;
 
 
-			wasHandled = this._handleArrowKeys( isForward );
+		// Checks if the keys were handled and then prevents the default event behaviour and stops
+		// the propagation.
+		if ( !isArrowKeyCode( keyCode ) ) {
+			return;
 		}
 		}
 
 
-		if ( wasHandled ) {
+		const model = this.editor.model;
+		const schema = model.schema;
+		const objectElement = model.document.selection.getSelectedElement();
+
+		// If object element is selected.
+		if ( objectElement && schema.isObject( objectElement ) ) {
 			domEventData.preventDefault();
 			domEventData.preventDefault();
 			eventInfo.stop();
 			eventInfo.stop();
 		}
 		}
@@ -231,49 +315,6 @@ export default class Widget extends Plugin {
 		}
 		}
 	}
 	}
 
 
-	/**
-	 * Handles arrow keys.
-	 *
-	 * @private
-	 * @param {Boolean} isForward Set to true if arrow key should be handled in forward direction.
-	 * @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
-	 */
-	_handleArrowKeys( isForward ) {
-		const model = this.editor.model;
-		const schema = model.schema;
-		const modelDocument = model.document;
-		const modelSelection = modelDocument.selection;
-		const objectElement = modelSelection.getSelectedElement();
-
-		// If object element is selected.
-		if ( objectElement && schema.isObject( objectElement ) ) {
-			const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
-			const newRange = schema.getNearestSelectionRange( position, isForward ? 'forward' : 'backward' );
-
-			if ( newRange ) {
-				model.change( writer => {
-					writer.setSelection( newRange );
-				} );
-			}
-
-			return true;
-		}
-
-		// If selection is next to object element.
-		// Return if not collapsed.
-		if ( !modelSelection.isCollapsed ) {
-			return;
-		}
-
-		const objectElement2 = this._getObjectElementNextToSelection( isForward );
-
-		if ( !!objectElement2 && schema.isObject( objectElement2 ) ) {
-			this._setSelectionOverElement( objectElement2 );
-
-			return true;
-		}
-	}
-
 	/**
 	/**
 	 * Sets {@link module:engine/model/selection~Selection document's selection} over given element.
 	 * Sets {@link module:engine/model/selection~Selection document's selection} over given element.
 	 *
 	 *