Kaynağa Gözat

Implemented Keyboard util.

Piotrek Koszuliński 9 yıl önce
ebeveyn
işleme
ad63a258ad

+ 152 - 0
packages/ckeditor5-utils/src/keyboard.js

@@ -0,0 +1,152 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import CKEditorError from './ckeditorerror.js';
+
+/**
+ * Set of utils related to keyboard support.
+ *
+ * @namespace utils.keyboard
+ */
+
+/**
+ * Object with `keyName => keyCode` pairs.
+ *
+ * Contains:
+ *
+ * * `a-z`,
+ * * `0-9`,
+ * * `arrow(left|up|right|bottom)`,
+ * * `backspace`, `delete`, `enter`,
+ * * `ctrl`, `cmd`, `shift`, `alt`.
+ *
+ * @member {Object} utils.keyboard.knownKeyNames
+ */
+export const knownKeyNames = generateKnownKeyNames();
+
+/**
+ * Converts a key name or a {@link utils.keyboard.KeystrokeInfo keystroke info} into a key code.
+ *
+ * Note: Key names are matched with {@link utils.keyboard.knownKeyNames} in a case-insensitive way.
+ *
+ * @method utils.keyboard.getCode
+ * @param {String|utils.keyboard.KeystrokeInfo} Key name (see {@link utils.keyboard.knownKeyNames})
+ * or a keystroke data object.
+ * @returns {Number} Key or keystroke code.
+ */
+export function getCode( key ) {
+	let keyCode;
+
+	if ( typeof key == 'string' ) {
+		keyCode = knownKeyNames[ key.toLowerCase() ];
+
+		if ( !keyCode ) {
+			throw new CKEditorError( 'keyboard-unknown-key: Unknown key name.', { key } );
+		}
+	} else {
+		keyCode = key.keyCode +
+			( key.altKey ? knownKeyNames.alt : 0 ) +
+			( key.ctrlKey ? knownKeyNames.ctrl : 0 ) +
+			( key.shiftKey ? knownKeyNames.shift : 0 );
+	}
+
+	return keyCode;
+}
+
+/**
+ * Parses keystroke and returns a keystroke code that will match the code returned by
+ * link {@link utils.keyboard.getCode} for a corresponding {@link utils.keyboard.KeystrokeInfo keystroke info}.
+ *
+ * The keystroke can be passed in two formats:
+ *
+ * * as a single string – e.g. `ctrl + A`,
+ * * as an array of {@link utils.keyboard.knownKeyNames known key names} and key codes – e.g.:
+ *   * `[ 'ctrl', 32 ]` (ctrl + space),
+ *   * `[ 'ctrl', 'a' ]` (ctrl + A).
+ *
+ * Note: Key names are matched with {@link utils.keyboard.knownKeyNames} in a case-insensitive way.
+ *
+ * Note: Only keystrokes with a single non-modifier key are supported (e.g. `ctrl+A` is OK, but `ctrl+A+B` is not).
+ *
+ * @method utils.keyboard.parseKeystroke
+ * @param {String|Array.<Number|String>} keystroke Keystroke definition.
+ * @returns {Number} Keystroke code.
+ */
+export function parseKeystroke( keystroke ) {
+	if ( typeof keystroke == 'string' ) {
+		keystroke = keystroke.split( /\s*\+\s*/ );
+	}
+
+	return keystroke
+		.map( key => ( typeof key == 'string' ) ? getCode( key ) : key )
+		.reduce( ( key, sum ) => sum + key, 0 );
+}
+
+function generateKnownKeyNames() {
+	const knownKeyNames = {
+		arrowleft: 37,
+		arrowup: 38,
+		arrowright: 39,
+		arrowdown: 40,
+		backspace: 8,
+		delete: 46,
+		enter: 13,
+
+		// The idea about these numbers is that they do not collide with any real key codes, so we can use them
+		// like bit masks.
+		ctrl: 0x110000,
+		// Has the same code as ctrl, because their behaviour should be unified across the editor.
+		// See http://ckeditor.github.io/editor-recommendations/general-policies#ctrl-vs-cmd
+		cmd: 0x110000,
+		shift: 0x220000,
+		alt: 0x440000
+	};
+
+	// a-z
+	for ( let code = 65; code <= 90; code++ ) {
+		const letter = String.fromCharCode( code );
+
+		knownKeyNames[ letter.toLowerCase() ] = code;
+	}
+
+	// 0-9
+	for ( let code = 48; code <= 57; code++ ) {
+		knownKeyNames[ code - 48 ] = code;
+	}
+
+	return knownKeyNames;
+}
+
+/**
+ * Information about a keystroke.
+ *
+ * @interface utils.keyboard.KeystrokeInfo
+ */
+
+/**
+ * The [key code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode).
+ *
+ * @member {Number} utils.keyboard.KeystrokeInfo#keyCode
+ */
+
+/**
+ * Whether the <kbd>Alt</kbd> modifier was pressed.
+ *
+ * @member {Bolean} utils.keyboard.KeystrokeInfo#altKey
+ */
+
+/**
+ * Whether the <kbd>Ctrl</kbd> or <kbd>Cmd</kbd> modifier was pressed.
+ *
+ * @member {Bolean} utils.keyboard.KeystrokeInfo#ctrlKey
+ */
+
+/**
+ * Whether the <kbd>Shift</kbd> modifier was pressed.
+ *
+ * @member {Bolean} utils.keyboard.KeystrokeInfo#shiftKey
+ */

+ 92 - 0
packages/ckeditor5-utils/tests/keyboard.js

@@ -0,0 +1,92 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import { knownKeyNames, getCode, parseKeystroke } from '/ckeditor5/utils/keyboard.js';
+import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
+
+describe( 'Keyboard', () => {
+	describe( 'knownKeyNames', () => {
+		it( 'contains numbers', () => {
+			expect( knownKeyNames[ '0' ] ).to.equal( 48 );
+			expect( knownKeyNames[ '9' ] ).to.equal( 57 );
+		} );
+
+		it( 'contains letters', () => {
+			expect( knownKeyNames.a ).to.equal( 65 );
+			expect( knownKeyNames.z ).to.equal( 90 );
+		} );
+
+		it( 'modifiers and other keys', () => {
+			expect( knownKeyNames.delete ).to.equal( 46 );
+			expect( knownKeyNames.ctrl ).to.equal( 0x110000 );
+			expect( knownKeyNames.cmd ).to.equal( 0x110000 );
+		} );
+	} );
+
+	describe( 'getCode', () => {
+		it( 'gets code of a number', () => {
+			expect( getCode( '0' ) ).to.equal( 48 );
+		} );
+
+		it( 'gets code of a letter', () => {
+			expect( getCode( 'a' ) ).to.equal( 65 );
+		} );
+
+		it( 'is case insensitive', () => {
+			expect( getCode( 'A' ) ).to.equal( 65 );
+			expect( getCode( 'Ctrl' ) ).to.equal( 0x110000 );
+			expect( getCode( 'ENTER' ) ).to.equal( 13 );
+		} );
+
+		it( 'throws when passed unknown key name', () => {
+			expect( () => {
+				getCode( 'foo' );
+			} ).to.throw( CKEditorError, /^keyboard-unknown-key:/ );
+		} );
+
+		it( 'gets code of a keystroke info', () => {
+			expect( getCode( { keyCode: 48 } ) ).to.equal( 48 );
+		} );
+
+		it( 'adds modifiers to the keystroke code', () => {
+			expect( getCode( { keyCode: 48, altKey: true, ctrlKey: true, shiftKey: true } ) )
+				.to.equal( 48 + 0x110000 + 0x220000 + 0x440000 );
+		} );
+	} );
+
+	describe( 'parseKeystroke', () => {
+		it( 'parses string', () => {
+			expect( parseKeystroke( 'ctrl+a' ) ).to.equal( 0x110000 + 65 );
+		} );
+
+		it( 'allows spacing', () => {
+			expect( parseKeystroke( 'ctrl +   a' ) ).to.equal( 0x110000 + 65 );
+		} );
+
+		it( 'is case-insensitive', () => {
+			expect( parseKeystroke( 'Ctrl+A' ) ).to.equal( 0x110000 + 65 );
+		} );
+
+		it( 'works with an array', () => {
+			expect( parseKeystroke( [ 'ctrl', 'a' ] ) ).to.equal( 0x110000 + 65 );
+		} );
+
+		it( 'works with an array which contains numbers', () => {
+			expect( parseKeystroke( [ 'shift', 33 ] ) ).to.equal( 0x220000 + 33 );
+		} );
+
+		it( 'works with two modifiers', () => {
+			expect( parseKeystroke( 'ctrl+shift+a' ) ).to.equal( 0x110000 + 0x220000 + 65 );
+		} );
+
+		it( 'throws on unknown name', () => {
+			expect( () => {
+				parseKeystroke( 'foo' );
+			} ).to.throw( CKEditorError, /^keyboard-unknown-key:/ );
+		} );
+	} );
+} );