@ckeditor/ckeditor5-typing 35.2.1 → 35.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,100 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
-
6
- /**
7
- * @module typing/inputcommand
8
- */
9
-
10
- import Command from '@ckeditor/ckeditor5-core/src/command';
11
-
12
- import ChangeBuffer from './utils/changebuffer';
13
-
14
- /**
15
- * The input command. Used by the {@link module:typing/input~Input input feature} to handle typing.
16
- *
17
- * @extends module:core/command~Command
18
- */
19
- export default class InputCommand extends Command {
20
- /**
21
- * Creates an instance of the command.
22
- *
23
- * @param {module:core/editor/editor~Editor} editor
24
- * @param {Number} undoStepSize The maximum number of atomic changes
25
- * which can be contained in one batch in the command buffer.
26
- */
27
- constructor( editor, undoStepSize ) {
28
- super( editor );
29
-
30
- /**
31
- * Typing's change buffer used to group subsequent changes into batches.
32
- *
33
- * @readonly
34
- * @private
35
- * @member {module:typing/utils/changebuffer~ChangeBuffer} #_buffer
36
- */
37
- this._buffer = new ChangeBuffer( editor.model, undoStepSize );
38
- }
39
-
40
- /**
41
- * The current change buffer.
42
- *
43
- * @type {module:typing/utils/changebuffer~ChangeBuffer}
44
- */
45
- get buffer() {
46
- return this._buffer;
47
- }
48
-
49
- /**
50
- * @inheritDoc
51
- */
52
- destroy() {
53
- super.destroy();
54
-
55
- this._buffer.destroy();
56
- }
57
-
58
- /**
59
- * Executes the input command. It replaces the content within the given range with the given text.
60
- * Replacing is a two step process, first the content within the range is removed and then the new text is inserted
61
- * at the beginning of the range (which after the removal is a collapsed range).
62
- *
63
- * @fires execute
64
- * @param {Object} [options] The command options.
65
- * @param {String} [options.text=''] The text to be inserted.
66
- * @param {module:engine/model/range~Range} [options.range] The range in which the text is inserted. Defaults
67
- * to the first range in the current selection.
68
- * @param {module:engine/model/range~Range} [options.resultRange] The range where the selection
69
- * should be placed after the insertion. If not specified, the selection will be placed right after
70
- * the inserted text.
71
- */
72
- execute( options = {} ) {
73
- const model = this.editor.model;
74
- const doc = model.document;
75
- const text = options.text || '';
76
- const textInsertions = text.length;
77
- const selection = options.range ? model.createSelection( options.range ) : doc.selection;
78
- const resultRange = options.resultRange;
79
-
80
- model.enqueueChange( this._buffer.batch, writer => {
81
- this._buffer.lock();
82
-
83
- model.deleteContent( selection );
84
-
85
- if ( text ) {
86
- model.insertContent( writer.createText( text, doc.selection.getAttributes() ), selection );
87
- }
88
-
89
- if ( resultRange ) {
90
- writer.setSelection( resultRange );
91
- } else if ( !selection.is( 'documentSelection' ) ) {
92
- writer.setSelection( selection );
93
- }
94
-
95
- this._buffer.unlock();
96
-
97
- this._buffer.input( textInsertions );
98
- } );
99
- }
100
- }
@@ -1,331 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
-
6
- /**
7
- * @module typing/utils/injecttypingmutationshandling
8
- */
9
-
10
- import diff from '@ckeditor/ckeditor5-utils/src/diff';
11
- import DomConverter from '@ckeditor/ckeditor5-engine/src/view/domconverter';
12
-
13
- import { getSingleTextNodeChange, containerChildrenMutated } from './utils';
14
-
15
- /**
16
- * Handles mutations caused by normal typing.
17
- *
18
- * @param {module:core/editor/editor~Editor} editor The editor instance.
19
- */
20
- export default function injectTypingMutationsHandling( editor ) {
21
- editor.editing.view.document.on( 'mutations', ( evt, mutations, viewSelection ) => {
22
- new MutationHandler( editor ).handle( mutations, viewSelection );
23
- } );
24
- }
25
-
26
- /**
27
- * Helper class for translating DOM mutations into model changes.
28
- *
29
- * @private
30
- */
31
- class MutationHandler {
32
- /**
33
- * Creates an instance of the mutation handler.
34
- *
35
- * @param {module:core/editor/editor~Editor} editor
36
- */
37
- constructor( editor ) {
38
- /**
39
- * Editor instance for which mutations are handled.
40
- *
41
- * @readonly
42
- * @member {module:core/editor/editor~Editor} #editor
43
- */
44
- this.editor = editor;
45
-
46
- /**
47
- * The editing controller.
48
- *
49
- * @readonly
50
- * @member {module:engine/controller/editingcontroller~EditingController} #editing
51
- */
52
- this.editing = this.editor.editing;
53
- }
54
-
55
- /**
56
- * Handles given mutations.
57
- *
58
- * @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
59
- * module:engine/view/observer/mutationobserver~MutatedChildren>} mutations
60
- * @param {module:engine/view/selection~Selection|null} viewSelection
61
- */
62
- handle( mutations, viewSelection ) {
63
- if ( containerChildrenMutated( mutations ) ) {
64
- this._handleContainerChildrenMutations( mutations, viewSelection );
65
- } else {
66
- for ( const mutation of mutations ) {
67
- // Fortunately it will never be both.
68
- this._handleTextMutation( mutation, viewSelection );
69
- this._handleTextNodeInsertion( mutation );
70
- }
71
- }
72
- }
73
-
74
- /**
75
- * Handles situations when container's children mutated during input. This can happen when
76
- * the browser is trying to "fix" DOM in certain situations. For example, when the user starts to type
77
- * in `<p><a href=""><i>Link{}</i></a></p>`, the browser might change the order of elements
78
- * to `<p><i><a href="">Link</a>x{}</i></p>`. A similar situation happens when the spell checker
79
- * replaces a word wrapped with `<strong>` with a word wrapped with a `<b>` element.
80
- *
81
- * To handle such situations, the common DOM ancestor of all mutations is converted to the model representation
82
- * and then compared with the current model to calculate the proper text change.
83
- *
84
- * Note: Single text node insertion is handled in {@link #_handleTextNodeInsertion} and text node mutation is handled
85
- * in {@link #_handleTextMutation}).
86
- *
87
- * @private
88
- * @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
89
- * module:engine/view/observer/mutationobserver~MutatedChildren>} mutations
90
- * @param {module:engine/view/selection~Selection|null} viewSelection
91
- */
92
- _handleContainerChildrenMutations( mutations, viewSelection ) {
93
- // Get common ancestor of all mutations.
94
- const mutationsCommonAncestor = getMutationsContainer( mutations );
95
-
96
- // Quit if there is no common ancestor.
97
- if ( !mutationsCommonAncestor ) {
98
- return;
99
- }
100
-
101
- const domConverter = this.editor.editing.view.domConverter;
102
-
103
- // Get common ancestor in DOM.
104
- const domMutationCommonAncestor = domConverter.mapViewToDom( mutationsCommonAncestor );
105
-
106
- // Create fresh DomConverter so it will not use existing mapping and convert current DOM to model.
107
- // This wouldn't be needed if DomConverter would allow to create fresh view without checking any mappings.
108
- const freshDomConverter = new DomConverter( this.editor.editing.view.document );
109
- const modelFromCurrentDom = this.editor.data.toModel(
110
- freshDomConverter.domToView( domMutationCommonAncestor )
111
- ).getChild( 0 );
112
-
113
- // Current model.
114
- const currentModel = this.editor.editing.mapper.toModelElement( mutationsCommonAncestor );
115
-
116
- // If common ancestor is not mapped, do not do anything. It probably is a parent of another view element.
117
- // That means that we would need to diff model elements (see `if` below). Better return early instead of
118
- // trying to get a reasonable model ancestor. It will fell into the `if` below anyway.
119
- // This situation happens for example for lists. If `<ul>` is a common ancestor, `currentModel` is `undefined`
120
- // because `<ul>` is not mapped (`<li>`s are).
121
- // See https://github.com/ckeditor/ckeditor5/issues/718.
122
- if ( !currentModel ) {
123
- return;
124
- }
125
-
126
- // Get children from both ancestors.
127
- const modelFromDomChildren = Array.from( modelFromCurrentDom.getChildren() );
128
- const currentModelChildren = Array.from( currentModel.getChildren() );
129
-
130
- // Remove the last `<softBreak>` from the end of `modelFromDomChildren` if there is no `<softBreak>` in current model.
131
- // If the described scenario happened, it means that this is a bogus `<br />` added by a browser.
132
- const lastDomChild = modelFromDomChildren[ modelFromDomChildren.length - 1 ];
133
- const lastCurrentChild = currentModelChildren[ currentModelChildren.length - 1 ];
134
-
135
- const isLastDomChildSoftBreak = lastDomChild && lastDomChild.is( 'element', 'softBreak' );
136
- const isLastCurrentChildSoftBreak = lastCurrentChild && !lastCurrentChild.is( 'element', 'softBreak' );
137
-
138
- if ( isLastDomChildSoftBreak && isLastCurrentChildSoftBreak ) {
139
- modelFromDomChildren.pop();
140
- }
141
-
142
- const schema = this.editor.model.schema;
143
-
144
- // Skip situations when common ancestor has any container elements.
145
- if ( !isSafeForTextMutation( modelFromDomChildren, schema ) || !isSafeForTextMutation( currentModelChildren, schema ) ) {
146
- return;
147
- }
148
-
149
- // Replace &nbsp; inserted by the browser with normal space. See comment in `_handleTextMutation`.
150
- // Replace non-texts with any character. This is potentially dangerous but passes in manual tests. The thing is
151
- // that we need to take care of proper indexes so we cannot simply remove non-text elements from the content.
152
- // By inserting a character we keep all the real texts on their indexes.
153
- const newText = modelFromDomChildren.map( item => item.is( '$text' ) ? item.data : '@' ).join( '' ).replace( /\u00A0/g, ' ' );
154
- const oldText = currentModelChildren.map( item => item.is( '$text' ) ? item.data : '@' ).join( '' ).replace( /\u00A0/g, ' ' );
155
-
156
- // Do nothing if mutations created same text.
157
- if ( oldText === newText ) {
158
- return;
159
- }
160
-
161
- const diffResult = diff( oldText, newText );
162
-
163
- const { firstChangeAt, insertions, deletions } = calculateChanges( diffResult );
164
-
165
- // Try setting new model selection according to passed view selection.
166
- let modelSelectionRange = null;
167
-
168
- if ( viewSelection ) {
169
- modelSelectionRange = this.editing.mapper.toModelRange( viewSelection.getFirstRange() );
170
- }
171
-
172
- const insertText = newText.substr( firstChangeAt, insertions );
173
- const removeRange = this.editor.model.createRange(
174
- this.editor.model.createPositionAt( currentModel, firstChangeAt ),
175
- this.editor.model.createPositionAt( currentModel, firstChangeAt + deletions )
176
- );
177
-
178
- this.editor.execute( 'input', {
179
- text: insertText,
180
- range: removeRange,
181
- resultRange: modelSelectionRange
182
- } );
183
- }
184
-
185
- /**
186
- * @private
187
- */
188
- _handleTextMutation( mutation, viewSelection ) {
189
- if ( mutation.type != 'text' ) {
190
- return;
191
- }
192
-
193
- // Replace &nbsp; inserted by the browser with normal space.
194
- // We want only normal spaces in the model and in the view. Renderer and DOM Converter will be then responsible
195
- // for rendering consecutive spaces using &nbsp;, but the model and the view has to be clear.
196
- // Other feature may introduce inserting non-breakable space on specific key stroke (for example shift + space).
197
- // However then it will be handled outside of mutations, like enter key is.
198
- // The replacing is here because it has to be done before `diff` and `diffToChanges` functions, as they
199
- // take `newText` and compare it to (cleaned up) view.
200
- // It could also be done in mutation observer too, however if any outside plugin would like to
201
- // introduce additional events for mutations, they would get already cleaned up version (this may be good or not).
202
- const newText = mutation.newText.replace( /\u00A0/g, ' ' );
203
- // To have correct `diffResult`, we also compare view node text data with &nbsp; replaced by space.
204
- const oldText = mutation.oldText.replace( /\u00A0/g, ' ' );
205
-
206
- // Do nothing if mutations created same text.
207
- if ( oldText === newText ) {
208
- return;
209
- }
210
-
211
- const diffResult = diff( oldText, newText );
212
-
213
- const { firstChangeAt, insertions, deletions } = calculateChanges( diffResult );
214
-
215
- // Try setting new model selection according to passed view selection.
216
- let modelSelectionRange = null;
217
-
218
- if ( viewSelection ) {
219
- modelSelectionRange = this.editing.mapper.toModelRange( viewSelection.getFirstRange() );
220
- }
221
-
222
- // Get the position in view and model where the changes will happen.
223
- const viewPos = this.editing.view.createPositionAt( mutation.node, firstChangeAt );
224
- const modelPos = this.editing.mapper.toModelPosition( viewPos );
225
- const removeRange = this.editor.model.createRange( modelPos, modelPos.getShiftedBy( deletions ) );
226
- const insertText = newText.substr( firstChangeAt, insertions );
227
-
228
- this.editor.execute( 'input', {
229
- text: insertText,
230
- range: removeRange,
231
- resultRange: modelSelectionRange
232
- } );
233
- }
234
-
235
- /**
236
- * @private
237
- */
238
- _handleTextNodeInsertion( mutation ) {
239
- if ( mutation.type != 'children' ) {
240
- return;
241
- }
242
-
243
- const change = getSingleTextNodeChange( mutation );
244
- const viewPos = this.editing.view.createPositionAt( mutation.node, change.index );
245
- const modelPos = this.editing.mapper.toModelPosition( viewPos );
246
- const insertedText = change.values[ 0 ].data;
247
-
248
- this.editor.execute( 'input', {
249
- // Replace &nbsp; inserted by the browser with normal space.
250
- // See comment in `_handleTextMutation`.
251
- // In this case we don't need to do this before `diff` because we diff whole nodes.
252
- // Just change &nbsp; in case there are some.
253
- text: insertedText.replace( /\u00A0/g, ' ' ),
254
- range: this.editor.model.createRange( modelPos )
255
- } );
256
- }
257
- }
258
-
259
- // Returns first common ancestor of all mutations that is either {@link module:engine/view/containerelement~ContainerElement}
260
- // or {@link module:engine/view/rootelement~RootElement}.
261
- //
262
- // @private
263
- // @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
264
- // module:engine/view/observer/mutationobserver~MutatedChildren>} mutations
265
- // @returns {module:engine/view/containerelement~ContainerElement|engine/view/rootelement~RootElement|undefined}
266
- function getMutationsContainer( mutations ) {
267
- const lca = mutations
268
- .map( mutation => mutation.node )
269
- .reduce( ( commonAncestor, node ) => {
270
- return commonAncestor.getCommonAncestor( node, { includeSelf: true } );
271
- } );
272
-
273
- if ( !lca ) {
274
- return;
275
- }
276
-
277
- // We need to look for container and root elements only, so check all LCA's
278
- // ancestors (starting from itself).
279
- return lca.getAncestors( { includeSelf: true, parentFirst: true } )
280
- .find( element => element.is( 'containerElement' ) || element.is( 'rootElement' ) );
281
- }
282
-
283
- // Returns true if provided array contains content that won't be problematic during diffing and text mutation handling.
284
- //
285
- // @param {Array.<module:engine/model/node~Node>} children
286
- // @param {module:engine/model/schema~Schema} schema
287
- // @returns {Boolean}
288
- function isSafeForTextMutation( children, schema ) {
289
- return children.every( child => schema.isInline( child ) );
290
- }
291
-
292
- // Calculates first change index and number of characters that should be inserted and deleted starting from that index.
293
- //
294
- // @private
295
- // @param diffResult
296
- // @returns {{insertions: number, deletions: number, firstChangeAt: *}}
297
- function calculateChanges( diffResult ) {
298
- // Index where the first change happens. Used to set the position from which nodes will be removed and where will be inserted.
299
- let firstChangeAt = null;
300
- // Index where the last change happens. Used to properly count how many characters have to be removed and inserted.
301
- let lastChangeAt = null;
302
-
303
- // Get `firstChangeAt` and `lastChangeAt`.
304
- for ( let i = 0; i < diffResult.length; i++ ) {
305
- const change = diffResult[ i ];
306
-
307
- if ( change != 'equal' ) {
308
- firstChangeAt = firstChangeAt === null ? i : firstChangeAt;
309
- lastChangeAt = i;
310
- }
311
- }
312
-
313
- // How many characters, starting from `firstChangeAt`, should be removed.
314
- let deletions = 0;
315
- // How many characters, starting from `firstChangeAt`, should be inserted.
316
- let insertions = 0;
317
-
318
- for ( let i = firstChangeAt; i <= lastChangeAt; i++ ) {
319
- // If there is no change (equal) or delete, the character is existing in `oldText`. We count it for removing.
320
- if ( diffResult[ i ] != 'insert' ) {
321
- deletions++;
322
- }
323
-
324
- // If there is no change (equal) or insert, the character is existing in `newText`. We count it for inserting.
325
- if ( diffResult[ i ] != 'delete' ) {
326
- insertions++;
327
- }
328
- }
329
-
330
- return { insertions, deletions, firstChangeAt };
331
- }
@@ -1,189 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
-
6
- /**
7
- * @module typing/utils/injectunsafekeystrokeshandling
8
- */
9
-
10
- import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
11
- import env from '@ckeditor/ckeditor5-utils/src/env';
12
- import { isShiftDeleteOnNonCollapsedSelection } from './utils';
13
-
14
- /**
15
- * Handles keystrokes which are unsafe for typing. This handler's logic is explained
16
- * in https://github.com/ckeditor/ckeditor5-typing/issues/83#issuecomment-398690251.
17
- *
18
- * @param {module:core/editor/editor~Editor} editor The editor instance.
19
- */
20
- export default function injectUnsafeKeystrokesHandling( editor ) {
21
- let latestCompositionSelection = null;
22
-
23
- const model = editor.model;
24
- const view = editor.editing.view;
25
- const inputCommand = editor.commands.get( 'input' );
26
-
27
- // For Android, we want to handle keystrokes on `beforeinput` to be sure that code in `DeleteObserver` already had a chance to be fired.
28
- if ( env.isAndroid ) {
29
- view.document.on( 'beforeinput', ( evt, evtData ) => handleUnsafeKeystroke( evtData ), { priority: 'lowest' } );
30
- } else {
31
- view.document.on( 'keydown', ( evt, evtData ) => handleUnsafeKeystroke( evtData ), { priority: 'lowest' } );
32
- }
33
-
34
- view.document.on( 'compositionstart', handleCompositionStart, { priority: 'lowest' } );
35
-
36
- view.document.on( 'compositionend', () => {
37
- latestCompositionSelection = model.createSelection( model.document.selection );
38
- }, { priority: 'lowest' } );
39
-
40
- // Handles the keydown event. We need to guess whether such keystroke is going to result
41
- // in typing. If so, then before character insertion happens, any selected content needs
42
- // to be deleted. Otherwise the default browser deletion mechanism would be
43
- // triggered, resulting in:
44
- //
45
- // * Hundreds of mutations which could not be handled.
46
- // * But most importantly, loss of control over how the content is being deleted.
47
- //
48
- // The method is used in a low-priority listener, hence allowing other listeners (e.g. delete or enter features)
49
- // to handle the event.
50
- //
51
- // @param {module:engine/view/observer/keyobserver~KeyEventData} evtData
52
- function handleUnsafeKeystroke( evtData ) {
53
- // Do not delete the content, if Shift + Delete key combination was pressed on a non-collapsed selection on Windows.
54
- //
55
- // The Shift + Delete key combination should work in the same way as the `cut` event on a non-collapsed selection on Windows.
56
- // In fact, the native `cut` event is actually emitted in this case, but with lower priority. Therefore, in order to handle the
57
- // Shift + Delete key combination correctly, it is enough to prevent the content deletion here.
58
- if ( env.isWindows && isShiftDeleteOnNonCollapsedSelection( evtData, view.document ) ) {
59
- return;
60
- }
61
-
62
- const doc = model.document;
63
- const isComposing = view.document.isComposing;
64
- const isSelectionUnchanged = latestCompositionSelection && latestCompositionSelection.isEqual( doc.selection );
65
-
66
- // Reset stored composition selection.
67
- latestCompositionSelection = null;
68
-
69
- // By relying on the state of the input command we allow disabling the entire input easily
70
- // by just disabling the input command. We could’ve used here the delete command but that
71
- // would mean requiring the delete feature which would block loading one without the other.
72
- // We could also check the editor.isReadOnly property, but that wouldn't allow to block
73
- // the input without blocking other features.
74
- if ( !inputCommand.isEnabled ) {
75
- return;
76
- }
77
-
78
- if ( isNonTypingKeystroke( evtData ) || doc.selection.isCollapsed ) {
79
- return;
80
- }
81
-
82
- // If during composition, deletion should be prevented as it may remove composed sequence (#83).
83
- if ( isComposing && evtData.keyCode === 229 ) {
84
- return;
85
- }
86
-
87
- // If there is a `keydown` event fired with '229' keycode it might be related
88
- // to recent composition. Check if selection is the same as upon ending recent composition,
89
- // if so do not remove selected content as it will remove composed sequence (#83).
90
- if ( !isComposing && evtData.keyCode === 229 && isSelectionUnchanged ) {
91
- return;
92
- }
93
-
94
- deleteSelectionContent();
95
- }
96
-
97
- // Handles the `compositionstart` event. It is used only in special cases to remove the contents
98
- // of a non-collapsed selection so composition itself does not result in complex mutations.
99
- //
100
- // The special case mentioned above is a situation in which the `keydown` event is fired after
101
- // `compositionstart` event. In such cases {@link #handleKeydown} cannot clear current selection
102
- // contents (because it is too late and will break the composition) so the composition handler takes care of it.
103
- function handleCompositionStart() {
104
- const doc = model.document;
105
- const isFlatSelection = doc.selection.rangeCount === 1 ? doc.selection.getFirstRange().isFlat : true;
106
-
107
- // If on `compositionstart` there is a non-collapsed selection which start and end have different parents
108
- // it means the `handleKeydown()` method did not remove its contents. It happens usually because
109
- // of different order of events (`compositionstart` before `keydown` - in Safari). In such cases
110
- // we need to remove selection contents on composition start (#83).
111
- if ( doc.selection.isCollapsed || isFlatSelection ) {
112
- return;
113
- }
114
-
115
- deleteSelectionContent();
116
- }
117
-
118
- function deleteSelectionContent() {
119
- const buffer = inputCommand.buffer;
120
-
121
- buffer.lock();
122
-
123
- const batch = buffer.batch;
124
-
125
- model.enqueueChange( batch, () => {
126
- model.deleteContent( model.document.selection );
127
- } );
128
-
129
- buffer.unlock();
130
- }
131
- }
132
-
133
- const safeKeycodes = [
134
- getCode( 'arrowUp' ),
135
- getCode( 'arrowRight' ),
136
- getCode( 'arrowDown' ),
137
- getCode( 'arrowLeft' ),
138
- 9, // Tab
139
- 16, // Shift
140
- 17, // Ctrl
141
- 18, // Alt
142
- 19, // Pause
143
- 20, // CapsLock
144
- 27, // Escape
145
- 33, // PageUp
146
- 34, // PageDown
147
- 35, // Home
148
- 36, // End,
149
- 45, // Insert,
150
- 91, // Windows,
151
- 93, // Menu key,
152
- 144, // NumLock
153
- 145, // ScrollLock,
154
- 173, // Mute/Unmute
155
- 174, // Volume up
156
- 175, // Volume down,
157
- 176, // Next song,
158
- 177, // Previous song,
159
- 178, // Stop,
160
- 179, // Play/Pause,
161
- 255 // Display brightness (increase and decrease)
162
- ];
163
-
164
- // Function keys.
165
- for ( let code = 112; code <= 135; code++ ) {
166
- safeKeycodes.push( code );
167
- }
168
-
169
- /**
170
- * Returns `true` if a keystroke will **not** result in "typing".
171
- *
172
- * For instance, keystrokes that result in typing are letters "a-zA-Z", numbers "0-9", delete, backspace, etc.
173
- *
174
- * Keystrokes that do not cause typing are, for instance, Fn keys (F5, F8, etc.), arrow keys (←, →, ↑, ↓),
175
- * Tab (↹), "Windows logo key" (⊞ Win), etc.
176
- *
177
- * Note: This implementation is very simple and will need to be refined with time.
178
- *
179
- * @param {module:engine/view/observer/keyobserver~KeyEventData} keyData
180
- * @returns {Boolean}
181
- */
182
- export function isNonTypingKeystroke( keyData ) {
183
- // Keystrokes which contain Ctrl or Cmd don't represent typing.
184
- if ( keyData.ctrlKey || keyData.metaKey ) {
185
- return true;
186
- }
187
-
188
- return safeKeycodes.includes( keyData.keyCode );
189
- }