8
0
Kuba Niegowski 5 лет назад
Родитель
Сommit
4bd587776c

+ 56 - 8
packages/ckeditor5-widget/src/verticalnavigation.js

@@ -10,6 +10,12 @@ import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
  * @module widget/verticalnavigationhandler
  */
 
+/**
+ * Returns 'keydown' handler for up/down arrow keys that modifies the caret movement if it's in a text line next to an object.
+ *
+ * @param {module:engine/controller/editingcontroller~EditingController} editing The editing controller.
+ * @returns {Function}
+ */
 export default function verticalNavigationHandler( editing ) {
 	const model = editing.model;
 
@@ -23,13 +29,18 @@ export default function verticalNavigationHandler( editing ) {
 			return;
 		}
 
-		const range = findTextRangeFromSelection( model, editing.mapper, selection, arrowDownPressed );
+		// Find a range between selection and closest limit element.
+		const range = findTextRangeFromSelection( editing, selection, arrowDownPressed );
 
 		if ( !range || range.start.isTouching( range.end ) ) {
 			return;
 		}
 
-		if ( isSingleLineRange( model, editing, range, arrowDownPressed ) ) {
+		// If the range is a single line (there is no word wrapping) then move the selection to the position closest to the limit element.
+		//
+		// We can't move the selection directly to the isObject element (eg. table cell) because of dual position at the end/beginning
+		// of wrapped line (it's at the same time at the end of one line and at the start of the next line).
+		if ( isSingleLineRange( editing, range, arrowDownPressed ) ) {
 			model.change( writer => {
 				const newPosition = arrowDownPressed ? range.end : range.start;
 
@@ -50,15 +61,25 @@ export default function verticalNavigationHandler( editing ) {
 	};
 }
 
-function findTextRangeFromSelection( model, mapper, selection, isForward ) {
-	const schema = model.schema;
+// Finds the range between selection and closest limit element (in the direction of navigation).
+// The position next to limit element is adjusted to the closest allowed `$text` position.
+//
+// Returns `null` if, according to the schema, the resulting range cannot contain a `$text` element.
+//
+// @param {module:engine/controller/editingcontroller~EditingController} editing The editing controller.
+// @param {module:engine/model/selection~Selection} selection The current selection.
+// @param {Boolean} isForward The expected navigation direction.
+// @returns {module:engine/model/range~Range|null}
+//
+function findTextRangeFromSelection( editing, selection, isForward ) {
+	const model = editing.model;
 
 	if ( isForward ) {
 		const startPosition = selection.isCollapsed ? selection.focus : selection.getLastPosition();
 		const endPosition = getNearestNonInlineLimit( model, startPosition, 'forward' );
 
 		const range = model.createRange( startPosition, endPosition );
-		const lastRangePosition = getNearestVisibleTextPosition( schema, mapper, range, 'backward' );
+		const lastRangePosition = getNearestVisibleTextPosition( editing, range, 'backward' );
 
 		if ( lastRangePosition && startPosition.isBefore( lastRangePosition ) ) {
 			return model.createRange( startPosition, lastRangePosition );
@@ -70,7 +91,7 @@ function findTextRangeFromSelection( model, mapper, selection, isForward ) {
 		const startPosition = getNearestNonInlineLimit( model, endPosition, 'backward' );
 
 		const range = model.createRange( startPosition, endPosition );
-		const firstRangePosition = getNearestVisibleTextPosition( schema, mapper, range, 'forward' );
+		const firstRangePosition = getNearestVisibleTextPosition( editing, range, 'forward' );
 
 		if ( firstRangePosition && endPosition.isAfter( firstRangePosition ) ) {
 			return model.createRange( firstRangePosition, endPosition );
@@ -80,6 +101,13 @@ function findTextRangeFromSelection( model, mapper, selection, isForward ) {
 	}
 }
 
+// Finds the limit element position that is closest to startPosition.
+//
+// @param {module:engine/model/model~Model} model
+// @param {<module:engine/model/position~Position>} startPosition
+// @param {'forward'|'backward'} direction Search direction.
+// @returns {<module:engine/model/position~Position>}
+//
 function getNearestNonInlineLimit( model, startPosition, direction ) {
 	const schema = model.schema;
 	const range = model.createRangeIn( startPosition.root );
@@ -93,7 +121,18 @@ function getNearestNonInlineLimit( model, startPosition, direction ) {
 	return direction == 'forward' ? range.end : range.start;
 }
 
-function getNearestVisibleTextPosition( schema, mapper, range, direction ) {
+// Basing on the provided range, finds the first or last (depending on `direction`) position inside the range
+// that can contain `$text` (according to schema) and is visible in the view.
+//
+// @param {module:engine/controller/editingcontroller~EditingController} editing The editing controller.
+// @param {module:engine/model/range~Range} range The range to find the position in.
+// @param {'forward'|'backward'} direction Search direction.
+// @returns {module:engine/model/position~Position} The nearest selection range.
+//
+function getNearestVisibleTextPosition( editing, range, direction ) {
+	const schema = editing.model.schema;
+	const mapper = editing.mapper;
+
 	const position = direction == 'backward' ? range.end : range.start;
 
 	if ( schema.checkChild( position, '$text' ) ) {
@@ -111,7 +150,16 @@ function getNearestVisibleTextPosition( schema, mapper, range, direction ) {
 	}
 }
 
-function isSingleLineRange( model, editing, modelRange, isForward ) {
+// Checks if the DOM range corresponding to the provided model range renders as a single line by analyzing DOMRects
+// (verifying if they visually wrap content to the next line).
+//
+// @param {module:engine/controller/editingcontroller~EditingController} editing The editing controller.
+// @param {module:engine/model/range~Range} modelRange The current table cell content range.
+// @param {Boolean} isForward The expected navigation direction.
+// @returns {Boolean}
+//
+function isSingleLineRange( editing, modelRange, isForward ) {
+	const model = editing.model;
 	const domConverter = editing.view.domConverter;
 
 	// Wrapped lines contain exactly the same position at the end of current line

+ 1 - 1
packages/ckeditor5-widget/tests/widget.js

@@ -827,7 +827,7 @@ describe( 'Widget', () => {
 				for ( const action of actions ) {
 					viewDocument.fire( 'keydown', new DomEventData(
 						viewDocument,
-						{ target: document.createElement( 'div' ), preventDefault() {} },
+						{ target: document.createElement( 'div' ), preventDefault() {}, stopPropagation() {} },
 						action
 					) );
 				}