ソースを参照

Handle content deletion before composition start to simplify mutations.

Krzysztof Krztoń 7 年 前
コミット
0b9b771926
1 ファイル変更75 行追加2 行削除
  1. 75 2
      packages/ckeditor5-typing/src/input.js

+ 75 - 2
packages/ckeditor5-typing/src/input.js

@@ -8,6 +8,7 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import Selection from '@ckeditor/ckeditor5-engine/src/model/selection';
 import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
@@ -40,12 +41,22 @@ export default class Input extends Plugin {
 
 		// TODO The above default configuration value should be defined using editor.config.define() once it's fixed.
 
+		this._latestCompositionSelection = null;
+
 		editor.commands.add( 'input', inputCommand );
 
 		this.listenTo( editingView.document, 'keydown', ( evt, data ) => {
 			this._handleKeydown( data, inputCommand );
 		}, { priority: 'lowest' } );
 
+		this.listenTo( editingView.document, 'compositionstart', () => {
+			this._handleCompositionstart( inputCommand );
+		}, { priority: 'lowest' } );
+
+		this.listenTo( editingView.document, 'compositionend', () => {
+			this._handleCompositionend();
+		}, { priority: 'lowest' } );
+
 		this.listenTo( editingView.document, 'mutations', ( evt, mutations, viewSelection ) => {
 			this._handleMutations( mutations, viewSelection );
 		} );
@@ -71,6 +82,11 @@ export default class Input extends Plugin {
 		const model = this.editor.model;
 		const doc = model.document;
 		const buffer = inputCommand.buffer;
+		const isComposing = this.editor.editing.view.document.isComposing;
+		const isSelectionUnchanged = this._latestCompositionSelection && this._latestCompositionSelection.isEqual( doc.selection );
+
+		// Reset stored composition selection.
+		this._latestCompositionSelection = null;
 
 		// By relying on the state of the input command we allow disabling the entire input easily
 		// by just disabling the input command. We could’ve used here the delete command but that
@@ -85,6 +101,52 @@ export default class Input extends Plugin {
 			return;
 		}
 
+		// If not during composition we should also delete content on '229' key code (composition start).
+		// While composing, deletion should be prevented as it may remove composed sequence (#83).
+		if ( isComposing && evtData.keyCode === 229 ) {
+			return;
+		}
+
+		// If there is a `keydown` event fired with '229' keycode it might be related to recent composition.
+		// Check if selection is the same as upon ending recent composition, if so do not remove selected content
+		// as it will remove composed sequence (#83).
+		if ( !isComposing && evtData.keyCode === 229 && isSelectionUnchanged ) {
+			return;
+		}
+
+		buffer.lock();
+
+		model.enqueueChange( buffer.batch, () => {
+			this.editor.model.deleteContent( doc.selection );
+		} );
+
+		buffer.unlock();
+	}
+
+	/**
+	 * Handles the `compositionstart` event. It is used only in special cases to remove the contents
+	 * of a non-collapsed selection so composition itself does not result in complex mutations.
+	 *
+	 * The special case mentioned above is a situation in which the `keydown` event is fired after
+	 * `compositionstart` event. In such cases {@link #_handleKeydown} cannot clear current selection
+	 * contents (because it is too late and will break the composition) so the composition handler takes care of it.
+	 *
+	 * @private
+	 * @param {module:typing/inputcommand~InputCommand} inputCommand
+	 */
+	_handleCompositionstart( inputCommand ) {
+		const model = this.editor.model;
+		const doc = model.document;
+		const buffer = inputCommand.buffer;
+		const isFlatSelection = doc.selection.rangeCount === 1 ? doc.selection.getFirstRange().isFlat : true;
+
+		// If on `compositionstart` there is a non-collapsed selection containing more than one element
+		// it means `_handleKeydown()` method didn't removed its content. It happens usually because of
+		// events different order (`compositionstart` before `keydown`) - observed only in Safari (#83).
+		if ( doc.selection.isCollapsed || isFlatSelection ) {
+			return;
+		}
+
 		buffer.lock();
 
 		model.enqueueChange( buffer.batch, () => {
@@ -95,6 +157,18 @@ export default class Input extends Plugin {
 	}
 
 	/**
+	 * Handles `compositionend` event. It is used to store latest composition selection.
+	 *
+	 * @private
+	 */
+	_handleCompositionend() {
+		// Store current selection on `compositionend`. It is then used in `_handleKeydown()` method
+		// for browsers (Safari) which fire `keydown` event after (and not before) `compositionend`
+		// to detect if selection content should be removed (#83).
+		this._latestCompositionSelection = new Selection( this.editor.model.document.selection );
+	}
+
+	/**
 	 * Handles DOM mutations.
 	 *
 	 * @private
@@ -346,8 +420,7 @@ const safeKeycodes = [
 	33, // PageUp
 	34, // PageDown
 	35, // Home
-	36, // End
-	229 // Composition start key
+	36 // End
 ];
 
 // Function keys.