@ckeditor/ckeditor5-core 34.2.0 → 35.1.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +311 -0
  2. package/LICENSE.md +4 -0
  3. package/lang/contexts.json +2 -1
  4. package/lang/translations/af.po +4 -0
  5. package/lang/translations/ar.po +8 -4
  6. package/lang/translations/ast.po +4 -0
  7. package/lang/translations/az.po +4 -0
  8. package/lang/translations/bg.po +9 -5
  9. package/lang/translations/bn.po +49 -0
  10. package/lang/translations/bs.po +4 -0
  11. package/lang/translations/ca.po +8 -4
  12. package/lang/translations/cs.po +4 -0
  13. package/lang/translations/da.po +4 -0
  14. package/lang/translations/de-ch.po +4 -0
  15. package/lang/translations/de.po +4 -0
  16. package/lang/translations/el.po +4 -0
  17. package/lang/translations/en-au.po +4 -0
  18. package/lang/translations/en-gb.po +4 -0
  19. package/lang/translations/en.po +4 -0
  20. package/lang/translations/eo.po +4 -0
  21. package/lang/translations/es.po +4 -0
  22. package/lang/translations/et.po +5 -1
  23. package/lang/translations/eu.po +4 -0
  24. package/lang/translations/fa.po +4 -0
  25. package/lang/translations/fi.po +6 -2
  26. package/lang/translations/fr.po +5 -1
  27. package/lang/translations/gl.po +4 -0
  28. package/lang/translations/gu.po +4 -0
  29. package/lang/translations/he.po +7 -3
  30. package/lang/translations/hi.po +5 -1
  31. package/lang/translations/hr.po +4 -0
  32. package/lang/translations/hu.po +4 -0
  33. package/lang/translations/id.po +6 -2
  34. package/lang/translations/it.po +4 -0
  35. package/lang/translations/ja.po +7 -3
  36. package/lang/translations/jv.po +4 -0
  37. package/lang/translations/km.po +4 -0
  38. package/lang/translations/kn.po +4 -0
  39. package/lang/translations/ko.po +5 -1
  40. package/lang/translations/ku.po +4 -0
  41. package/lang/translations/lt.po +7 -3
  42. package/lang/translations/lv.po +4 -0
  43. package/lang/translations/ms.po +10 -6
  44. package/lang/translations/nb.po +4 -0
  45. package/lang/translations/ne.po +4 -0
  46. package/lang/translations/nl.po +4 -0
  47. package/lang/translations/no.po +4 -0
  48. package/lang/translations/oc.po +4 -0
  49. package/lang/translations/pl.po +4 -0
  50. package/lang/translations/pt-br.po +4 -0
  51. package/lang/translations/pt.po +8 -4
  52. package/lang/translations/ro.po +4 -0
  53. package/lang/translations/ru.po +4 -0
  54. package/lang/translations/si.po +4 -0
  55. package/lang/translations/sk.po +4 -0
  56. package/lang/translations/sl.po +4 -0
  57. package/lang/translations/sq.po +4 -0
  58. package/lang/translations/sr-latn.po +4 -0
  59. package/lang/translations/sr.po +4 -0
  60. package/lang/translations/sv.po +8 -4
  61. package/lang/translations/th.po +7 -3
  62. package/lang/translations/tk.po +4 -0
  63. package/lang/translations/tr.po +5 -1
  64. package/lang/translations/tt.po +5 -1
  65. package/lang/translations/ug.po +4 -0
  66. package/lang/translations/uk.po +5 -1
  67. package/lang/translations/ur.po +4 -0
  68. package/lang/translations/uz.po +4 -0
  69. package/lang/translations/vi.po +5 -1
  70. package/lang/translations/zh-cn.po +4 -0
  71. package/lang/translations/zh.po +4 -0
  72. package/package.json +19 -18
  73. package/src/editor/editor.js +1 -1
  74. package/src/editor/editorconfig.jsdoc +18 -5
  75. package/src/editor/editorui.js +309 -2
  76. package/src/editor/utils/elementapimixin.js +19 -2
  77. package/src/plugincollection.js +1 -1
@@ -26,7 +26,7 @@ msgstr "Видалити колір"
26
26
 
27
27
  msgctxt "The label used by a button next to the color palette in the color picker that restores the default value if the default table properties are specified."
28
28
  msgid "Restore default"
29
- msgstr ""
29
+ msgstr "Відновити за замовчуванням"
30
30
 
31
31
  msgctxt "Label for the Save button."
32
32
  msgid "Save"
@@ -43,3 +43,7 @@ msgstr "%0 із %1"
43
43
  msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
44
44
  msgid "Cannot upload file:"
45
45
  msgstr "Неможливо завантажити файл:"
46
+
47
+ msgctxt "Accessible label of the specific editing area of the editor acting as a root of the entire application."
48
+ msgid "Rich Text Editor. Editing area: %0"
49
+ msgstr "Редактор Rich Text. Область редагування: %0"
@@ -43,3 +43,7 @@ msgstr "0% میں سے 1%"
43
43
  msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
44
44
  msgid "Cannot upload file:"
45
45
  msgstr "فائل اپلوڈ نہیں ہو سکی:"
46
+
47
+ msgctxt "Accessible label of the specific editing area of the editor acting as a root of the entire application."
48
+ msgid "Rich Text Editor. Editing area: %0"
49
+ msgstr "خانۂ ترمیم۔ علاقۂ ترمیم 0%"
@@ -43,3 +43,7 @@ msgstr ""
43
43
  msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
44
44
  msgid "Cannot upload file:"
45
45
  msgstr ""
46
+
47
+ msgctxt "Accessible label of the specific editing area of the editor acting as a root of the entire application."
48
+ msgid "Rich Text Editor. Editing area: %0"
49
+ msgstr ""
@@ -26,7 +26,7 @@ msgstr "Xóa màu"
26
26
 
27
27
  msgctxt "The label used by a button next to the color palette in the color picker that restores the default value if the default table properties are specified."
28
28
  msgid "Restore default"
29
- msgstr ""
29
+ msgstr "Khôi phục giá trị mặc định"
30
30
 
31
31
  msgctxt "Label for the Save button."
32
32
  msgid "Save"
@@ -43,3 +43,7 @@ msgstr "%0 đến %1"
43
43
  msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
44
44
  msgid "Cannot upload file:"
45
45
  msgstr "Không thể tải file:"
46
+
47
+ msgctxt "Accessible label of the specific editing area of the editor acting as a root of the entire application."
48
+ msgid "Rich Text Editor. Editing area: %0"
49
+ msgstr "Trình chỉnh sửa Văn bản dạng RTF. Vùng chỉnh sửa: %0"
@@ -43,3 +43,7 @@ msgstr "第 %0 步,共 %1 步"
43
43
  msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
44
44
  msgid "Cannot upload file:"
45
45
  msgstr "无法上传的文件:"
46
+
47
+ msgctxt "Accessible label of the specific editing area of the editor acting as a root of the entire application."
48
+ msgid "Rich Text Editor. Editing area: %0"
49
+ msgstr "富文本编辑器。编辑区域:%0"
@@ -43,3 +43,7 @@ msgstr "%0/%1"
43
43
  msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
44
44
  msgid "Cannot upload file:"
45
45
  msgstr "無法上傳檔案:"
46
+
47
+ msgctxt "Accessible label of the specific editing area of the editor acting as a root of the entire application."
48
+ msgid "Rich Text Editor. Editing area: %0"
49
+ msgstr "RTF 編輯器。編輯區:%0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-core",
3
- "version": "34.2.0",
3
+ "version": "35.1.0",
4
4
  "description": "The core architecture of CKEditor 5 – the best browser-based rich text editor.",
5
5
  "keywords": [
6
6
  "wysiwyg",
@@ -23,25 +23,25 @@
23
23
  ],
24
24
  "main": "src/index.js",
25
25
  "dependencies": {
26
- "@ckeditor/ckeditor5-engine": "^34.2.0",
27
- "@ckeditor/ckeditor5-ui": "^34.2.0",
28
- "@ckeditor/ckeditor5-utils": "^34.2.0",
26
+ "@ckeditor/ckeditor5-engine": "^35.1.0",
27
+ "@ckeditor/ckeditor5-ui": "^35.1.0",
28
+ "@ckeditor/ckeditor5-utils": "^35.1.0",
29
29
  "lodash-es": "^4.17.15"
30
30
  },
31
31
  "devDependencies": {
32
- "@ckeditor/ckeditor5-autoformat": "^34.2.0",
33
- "@ckeditor/ckeditor5-basic-styles": "^34.2.0",
34
- "@ckeditor/ckeditor5-block-quote": "^34.2.0",
35
- "@ckeditor/ckeditor5-editor-classic": "^34.2.0",
36
- "@ckeditor/ckeditor5-essentials": "^34.2.0",
37
- "@ckeditor/ckeditor5-heading": "^34.2.0",
38
- "@ckeditor/ckeditor5-image": "^34.2.0",
39
- "@ckeditor/ckeditor5-indent": "^34.2.0",
40
- "@ckeditor/ckeditor5-link": "^34.2.0",
41
- "@ckeditor/ckeditor5-list": "^34.2.0",
42
- "@ckeditor/ckeditor5-media-embed": "^34.2.0",
43
- "@ckeditor/ckeditor5-paragraph": "^34.2.0",
44
- "@ckeditor/ckeditor5-table": "^34.2.0"
32
+ "@ckeditor/ckeditor5-autoformat": "^35.1.0",
33
+ "@ckeditor/ckeditor5-basic-styles": "^35.1.0",
34
+ "@ckeditor/ckeditor5-block-quote": "^35.1.0",
35
+ "@ckeditor/ckeditor5-editor-classic": "^35.1.0",
36
+ "@ckeditor/ckeditor5-essentials": "^35.1.0",
37
+ "@ckeditor/ckeditor5-heading": "^35.1.0",
38
+ "@ckeditor/ckeditor5-image": "^35.1.0",
39
+ "@ckeditor/ckeditor5-indent": "^35.1.0",
40
+ "@ckeditor/ckeditor5-link": "^35.1.0",
41
+ "@ckeditor/ckeditor5-list": "^35.1.0",
42
+ "@ckeditor/ckeditor5-media-embed": "^35.1.0",
43
+ "@ckeditor/ckeditor5-paragraph": "^35.1.0",
44
+ "@ckeditor/ckeditor5-table": "^35.1.0"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=14.0.0",
@@ -60,6 +60,7 @@
60
60
  "lang",
61
61
  "src",
62
62
  "theme",
63
- "ckeditor5-metadata.json"
63
+ "ckeditor5-metadata.json",
64
+ "CHANGELOG.md"
64
65
  ]
65
66
  }
@@ -481,7 +481,7 @@ mix( Editor, ObservableMixin );
481
481
  * This error is thrown when trying to pass a `<textarea>` element to a `create()` function of an editor class.
482
482
  *
483
483
  * The only editor type which can be initialized on `<textarea>` elements is
484
- * the {@glink installation/advanced/alternative-setups/predefined-builds#classic-editor classic editor}.
484
+ * the {@glink installation/getting-started/predefined-builds#classic-editor classic editor}.
485
485
  * This editor hides the passed element and inserts its own UI next to it. Other types of editors reuse the passed element as their root
486
486
  * editable element and therefore `<textarea>` is not appropriate for them. Use a `<div>` or another text container instead:
487
487
  *
@@ -24,7 +24,7 @@
24
24
  * .then( ... )
25
25
  * .catch( ... );
26
26
  *
27
- * Check the {@glink installation/advanced/alternative-setups/predefined-builds Configuration guide} for more information
27
+ * Check the {@glink installation/getting-started/predefined-builds Configuration guide} for more information
28
28
  * about setting configuration options.
29
29
  *
30
30
  * @interface EditorConfig
@@ -66,7 +66,7 @@
66
66
  /**
67
67
  * The list of plugins to load.
68
68
  *
69
- * If you use an {@glink installation/advanced/alternative-setups/predefined-builds editor build} you can define the list of plugins to load
69
+ * If you use an {@glink installation/getting-started/predefined-builds editor build} you can define the list of plugins to load
70
70
  * using the names of plugins that are available:
71
71
  *
72
72
  * const config = {
@@ -98,7 +98,7 @@
98
98
 
99
99
  /**
100
100
  * The list of additional plugins to load along those already available in the
101
- * {@glink installation/advanced/alternative-setups/predefined-builds editor build}. It extends the {@link #plugins `plugins`} configuration.
101
+ * {@glink installation/getting-started/predefined-builds editor build}. It extends the {@link #plugins `plugins`} configuration.
102
102
  *
103
103
  * function MyPlugin( editor ) {
104
104
  * // ...
@@ -110,7 +110,7 @@
110
110
  *
111
111
  * **Note:** This configuration works only for simple plugins which utilize the
112
112
  * {@link module:core/plugin~PluginInterface plugin interface} and have no dependencies. To extend a
113
- * build with complex features, create a {@glink installation/getting-started/quick-start#creating-custom-builds-with-online-builder custom build}.
113
+ * build with complex features, create a {@glink installation/getting-started/quick-start-other#creating-custom-builds-with-online-builder custom build}.
114
114
  *
115
115
  * **Note:** Make sure you include the new features in you toolbar configuration. Learn more
116
116
  * about the {@glink features/toolbar/toolbar toolbar setup}.
@@ -119,7 +119,7 @@
119
119
  */
120
120
 
121
121
  /**
122
- * The list of plugins which should not be loaded despite being available in an {@glink installation/advanced/alternative-setups/predefined-builds editor build}.
122
+ * The list of plugins which should not be loaded despite being available in an {@glink installation/getting-started/predefined-builds editor build}.
123
123
  *
124
124
  * const config = {
125
125
  * removePlugins: [ 'Bold', 'Italic' ]
@@ -327,3 +327,16 @@
327
327
  *
328
328
  * @member {Object} module:core/editor/editorconfig~EditorConfig#ui
329
329
  */
330
+
331
+ /**
332
+ * Enables updating the source element after the editor destroy.
333
+ *
334
+ * Enabling this option might have some security implications, as the editor doesn't have control over all data
335
+ * in the output.
336
+ *
337
+ * Be careful, especially while using
338
+ * {@glink features/markdown Markdown}, {@glink features/general-html-support General HTML Support} or
339
+ * {@glink features/html-embed HTML embed} features.
340
+ *
341
+ * @member {Boolean} module:core/editor/editorconfig~EditorConfig#updateSourceElementOnDestroy
342
+ */
@@ -11,9 +11,11 @@
11
11
 
12
12
  import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
13
13
  import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
14
+ import TooltipManager from '@ckeditor/ckeditor5-ui/src/tooltipmanager';
14
15
 
15
16
  import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
16
17
  import mix from '@ckeditor/ckeditor5-utils/src/mix';
18
+ import isVisible from '@ckeditor/ckeditor5-utils/src/dom/isvisible';
17
19
 
18
20
  /**
19
21
  * A class providing the minimal interface that is required to successfully bootstrap any editor UI.
@@ -53,6 +55,14 @@ export default class EditorUI {
53
55
  */
54
56
  this.focusTracker = new FocusTracker();
55
57
 
58
+ /**
59
+ * Manages the tooltips displayed on mouseover and focus across the UI.
60
+ *
61
+ * @readonly
62
+ * @member {module:ui/tooltipmanager~TooltipManager}
63
+ */
64
+ this.tooltipManager = new TooltipManager( editor );
65
+
56
66
  /**
57
67
  * Stores viewport offsets from every direction.
58
68
  *
@@ -86,6 +96,18 @@ export default class EditorUI {
86
96
  */
87
97
  this.set( 'viewportOffset', this._readViewportOffsetFromConfig() );
88
98
 
99
+ /**
100
+ * Indicates the UI is ready. Set `true` after {@link #event:ready} event is fired.
101
+ *
102
+ * @readonly
103
+ * @default false
104
+ * @member {Boolean} #isReady
105
+ */
106
+ this.isReady = false;
107
+ this.once( 'ready', () => {
108
+ this.isReady = true;
109
+ } );
110
+
89
111
  /**
90
112
  * Stores all editable elements used by the editor instance.
91
113
  *
@@ -94,8 +116,18 @@ export default class EditorUI {
94
116
  */
95
117
  this._editableElementsMap = new Map();
96
118
 
119
+ /**
120
+ * All available & focusable toolbars.
121
+ *
122
+ * @private
123
+ * @type {Array.<module:core/editor/editorui~FocusableToolbarDefinition>}
124
+ */
125
+ this._focusableToolbarDefinitions = [];
126
+
97
127
  // Informs UI components that should be refreshed after layout change.
98
128
  this.listenTo( editor.editing.view.document, 'layoutChanged', () => this.update() );
129
+
130
+ this._initFocusTracking();
99
131
  }
100
132
 
101
133
  /**
@@ -134,6 +166,7 @@ export default class EditorUI {
134
166
  this.stopListening();
135
167
 
136
168
  this.focusTracker.destroy();
169
+ this.tooltipManager.destroy( this.editor );
137
170
 
138
171
  // Clean–up the references to the CKEditor instance stored in the native editable DOM elements.
139
172
  for ( const domElement of this._editableElementsMap.values() ) {
@@ -141,11 +174,14 @@ export default class EditorUI {
141
174
  }
142
175
 
143
176
  this._editableElementsMap = new Map();
177
+ this._focusableToolbarDefinitions = [];
144
178
  }
145
179
 
146
180
  /**
147
- * Store the native DOM editable element used by the editor under
148
- * a unique name.
181
+ * Stores the native DOM editable element used by the editor under a unique name.
182
+ *
183
+ * Also, registers the element in the editor to maintain the accessibility of the UI. When the user is editing text in a focusable
184
+ * editable area, they can use the <kbd>Alt</kbd> + <kbd>F10</kbd> keystroke to navigate over editor toolbars. See {@link #addToolbar}.
149
185
  *
150
186
  * @param {String} rootName The unique name of the editable element.
151
187
  * @param {HTMLElement} domElement The native DOM editable element.
@@ -160,6 +196,28 @@ export default class EditorUI {
160
196
  if ( !domElement.ckeditorInstance ) {
161
197
  domElement.ckeditorInstance = this.editor;
162
198
  }
199
+
200
+ // Register the element so it becomes available for Alt+F10 and Esc navigation.
201
+ this.focusTracker.add( domElement );
202
+
203
+ const setUpKeystrokeHandler = () => {
204
+ // The editing view of the editor is already listening to keystrokes from DOM roots (see: KeyObserver).
205
+ // Do not duplicate listeners.
206
+ if ( this.editor.editing.view.getDomRoot( rootName ) ) {
207
+ return;
208
+ }
209
+
210
+ this.editor.keystrokes.listenTo( domElement );
211
+ };
212
+
213
+ // For editable elements set by features after EditorUI is ready (e.g. source editing).
214
+ if ( this.isReady ) {
215
+ setUpKeystrokeHandler();
216
+ }
217
+ // For editable elements set while the editor is being created (e.g. DOM roots).
218
+ else {
219
+ this.once( 'ready', setUpKeystrokeHandler );
220
+ }
163
221
  }
164
222
 
165
223
  /**
@@ -181,6 +239,35 @@ export default class EditorUI {
181
239
  return this._editableElementsMap.keys();
182
240
  }
183
241
 
242
+ /**
243
+ * Adds a toolbar to the editor UI. Used primarily to maintain the accessibility of the UI.
244
+ *
245
+ * Focusable toolbars can be accessed (focused) by users by pressing the <kbd>Alt</kbd> + <kbd>F10</kbd> keystroke.
246
+ * Successive keystroke presses navigate over available toolbars.
247
+ *
248
+ * @param {module:ui/toolbar/toolbarview~ToolbarView} toolbarView A instance of the toolbar to be registered.
249
+ * @param {Object} [options]
250
+ * @param {Boolean} [options.isContextual] Set `true` if the toolbar is attached to the content of the editor. Such toolbar takes
251
+ * a precedence over other toolbars when a user pressed <kbd>Alt</kbd> + <kbd>F10</kbd>.
252
+ * @param {Function} [options.beforeFocus] Specify a callback executed before the toolbar instance DOM element gains focus
253
+ * upon the <kbd>Alt</kbd> + <kbd>F10</kbd> keystroke.
254
+ * @param {Function} [options.afterBlur] Specify a callback executed after the toolbar instance DOM element loses focus upon
255
+ * <kbd>Esc</kbd> keystroke but before the focus goes back to the {@link #setEditableElement editable element}.
256
+ */
257
+ addToolbar( toolbarView, options = {} ) {
258
+ if ( toolbarView.isRendered ) {
259
+ this.focusTracker.add( toolbarView.element );
260
+ this.editor.keystrokes.listenTo( toolbarView.element );
261
+ } else {
262
+ toolbarView.once( 'render', () => {
263
+ this.focusTracker.add( toolbarView.element );
264
+ this.editor.keystrokes.listenTo( toolbarView.element );
265
+ } );
266
+ }
267
+
268
+ this._focusableToolbarDefinitions.push( { toolbarView, options } );
269
+ }
270
+
184
271
  /**
185
272
  * Stores all editable elements used by the editor instance.
186
273
  *
@@ -254,6 +341,177 @@ export default class EditorUI {
254
341
  return { top: 0 };
255
342
  }
256
343
 
344
+ /**
345
+ * Starts listening for <kbd>Alt</kbd> + <kbd>F10</kbd> and <kbd>Esc</kbd> keystrokes in the context of focusable
346
+ * {@link #setEditableElement editable elements} and {@link #addToolbar toolbars}
347
+ * to allow users navigate across the UI.
348
+ *
349
+ * @private
350
+ */
351
+ _initFocusTracking() {
352
+ const editor = this.editor;
353
+ const editingView = editor.editing.view;
354
+
355
+ let lastFocusedForeignElement;
356
+ let candidateDefinitions;
357
+
358
+ // Focus the next focusable toolbar on <kbd>Alt</kbd> + <kbd>F10</kbd>.
359
+ editor.keystrokes.set( 'Alt+F10', ( data, cancel ) => {
360
+ const focusedElement = this.focusTracker.focusedElement;
361
+
362
+ // Focus moved out of a DOM element that
363
+ // * is not a toolbar,
364
+ // * does not belong to the editing view (e.g. source editing).
365
+ if (
366
+ Array.from( this._editableElementsMap.values() ).includes( focusedElement ) &&
367
+ !Array.from( editingView.domRoots.values() ).includes( focusedElement )
368
+ ) {
369
+ lastFocusedForeignElement = focusedElement;
370
+ }
371
+
372
+ const currentFocusedToolbarDefinition = this._getCurrentFocusedToolbarDefinition();
373
+
374
+ // * When focusing a toolbar for the first time, set the array of definitions for successive presses of Alt+F10.
375
+ // This ensures, the navigation works always the same and no pair of toolbars takes over
376
+ // (e.g. image and table toolbars when a selected image is inside a cell).
377
+ // * It could be that the focus went to the toolbar by clicking a toolbar item (e.g. a dropdown). In this case,
378
+ // there were no candidates so they must be obtained (#12339).
379
+ if ( !currentFocusedToolbarDefinition || !candidateDefinitions ) {
380
+ candidateDefinitions = this._getFocusableCandidateToolbarDefinitions( currentFocusedToolbarDefinition );
381
+ }
382
+
383
+ // In a single Alt+F10 press, check all candidates but if none were focused, don't go any further.
384
+ // This prevents an infinite loop.
385
+ for ( let i = 0; i < candidateDefinitions.length; i++ ) {
386
+ const candidateDefinition = candidateDefinitions.shift();
387
+
388
+ // Put the first definition to the back of the array. This allows circular navigation over all toolbars
389
+ // on successive presses of Alt+F10.
390
+ candidateDefinitions.push( candidateDefinition );
391
+
392
+ // Don't focus the same toolbar again. If you did, this would move focus from the nth focused toolbar item back to the
393
+ // first item as per ToolbarView#focus() if the user navigated inside the toolbar.
394
+ if (
395
+ candidateDefinition !== currentFocusedToolbarDefinition &&
396
+ this._focusFocusableCandidateToolbar( candidateDefinition )
397
+ ) {
398
+ // Clean up after a current visible toolbar when switching to the next one.
399
+ if ( currentFocusedToolbarDefinition && currentFocusedToolbarDefinition.options.afterBlur ) {
400
+ currentFocusedToolbarDefinition.options.afterBlur();
401
+ }
402
+
403
+ break;
404
+ }
405
+ }
406
+
407
+ cancel();
408
+ } );
409
+
410
+ // Blur the focused toolbar on <kbd>Esc</kbd> and bring the focus back to its origin.
411
+ editor.keystrokes.set( 'Esc', ( data, cancel ) => {
412
+ const focusedToolbarDef = this._getCurrentFocusedToolbarDefinition();
413
+
414
+ if ( !focusedToolbarDef ) {
415
+ return;
416
+ }
417
+
418
+ // Bring focus back to where it came from before focusing the toolbar:
419
+ // 1. If it came from outside the engine view (e.g. source editing), move it there.
420
+ if ( lastFocusedForeignElement ) {
421
+ lastFocusedForeignElement.focus();
422
+ lastFocusedForeignElement = null;
423
+ }
424
+ // 2. There are two possibilities left:
425
+ // 2.1. It could be that the focus went from an editable element in the view (root or nested).
426
+ // 2.2. It could be the focus went straight to the toolbar before even focusing the editing area.
427
+ // In either case, just focus the view editing. The focus will land where it belongs.
428
+ else {
429
+ editor.editing.view.focus();
430
+ }
431
+
432
+ // Clean up after the toolbar if there is anything to do there.
433
+ if ( focusedToolbarDef.options.afterBlur ) {
434
+ focusedToolbarDef.options.afterBlur();
435
+ }
436
+
437
+ cancel();
438
+ } );
439
+ }
440
+
441
+ /**
442
+ * Returns definitions of toolbars that could potentially be focused, sorted by their importance for the user.
443
+ *
444
+ * Focusable toolbars candidates are either:
445
+ * * already visible,
446
+ * * have `beforeFocus()` set in their {@link module:core/editor/editorui~FocusableToolbarDefinition definition} that suggests that
447
+ * they might show up when called. Keep in mind that determining whether a toolbar will show up (and become focusable) is impossible
448
+ * at this stage because it depends on its implementation, that in turn depends on the editing context (selection).
449
+ *
450
+ * **Note**: Contextual toolbars take precedence over regular toolbars.
451
+ *
452
+ * @private
453
+ * @returns {Array.<module:core/editor/editorui~FocusableToolbarDefinition>}
454
+ */
455
+ _getFocusableCandidateToolbarDefinitions() {
456
+ const definitions = [];
457
+
458
+ for ( const toolbarDef of this._focusableToolbarDefinitions ) {
459
+ const { toolbarView, options } = toolbarDef;
460
+
461
+ if ( isVisible( toolbarView.element ) || options.beforeFocus ) {
462
+ definitions.push( toolbarDef );
463
+ }
464
+ }
465
+
466
+ // Contextual and already visible toolbars have higher priority. If both are true, the toolbar will always focus first.
467
+ // For instance, a selected widget toolbar vs inline editor toolbar: both are visible but the widget toolbar is contextual.
468
+ definitions.sort( ( defA, defB ) => getToolbarDefinitionWeight( defA ) - getToolbarDefinitionWeight( defB ) );
469
+
470
+ return definitions;
471
+ }
472
+
473
+ /**
474
+ * Returns a definition of the toolbar that is currently visible and focused (one of its children has focus).
475
+ *
476
+ * `null` is returned when no toolbar is currently focused.
477
+ *
478
+ * @private
479
+ * @returns {module:core/editor/editorui~FocusableToolbarDefinition|null}
480
+ */
481
+ _getCurrentFocusedToolbarDefinition() {
482
+ for ( const definition of this._focusableToolbarDefinitions ) {
483
+ if ( definition.toolbarView.element && definition.toolbarView.element.contains( this.focusTracker.focusedElement ) ) {
484
+ return definition;
485
+ }
486
+ }
487
+
488
+ return null;
489
+ }
490
+
491
+ /**
492
+ * Focuses a focusable toolbar candidate using its definition.
493
+ *
494
+ * @private
495
+ * @param {module:core/editor/editorui~FocusableToolbarDefinition} candidateToolbarDefinition A definition of the toolbar to focus.
496
+ * @returns {Boolean} `true` when the toolbar candidate was focused. `false` otherwise.
497
+ */
498
+ _focusFocusableCandidateToolbar( candidateToolbarDefinition ) {
499
+ const { toolbarView, options: { beforeFocus } } = candidateToolbarDefinition;
500
+
501
+ if ( beforeFocus ) {
502
+ beforeFocus();
503
+ }
504
+
505
+ // If it didn't show up after beforeFocus(), it's not focusable at all.
506
+ if ( !isVisible( toolbarView.element ) ) {
507
+ return false;
508
+ }
509
+
510
+ toolbarView.focus();
511
+
512
+ return true;
513
+ }
514
+
257
515
  /**
258
516
  * Fired when the editor UI is ready.
259
517
  *
@@ -273,3 +531,52 @@ export default class EditorUI {
273
531
  }
274
532
 
275
533
  mix( EditorUI, ObservableMixin );
534
+
535
+ /**
536
+ * A definition of a focusable toolbar. Used by {@link module:core/editor/editorui~EditorUI#addToolbar}.
537
+ *
538
+ * @private
539
+ * @interface module:core/editor/editorui~FocusableToolbarDefinition
540
+ */
541
+
542
+ /**
543
+ * An instance of a focusable toolbar view.
544
+ *
545
+ * @member {module:ui/toolbar/toolbarview~ToolbarView} #toolbarView
546
+ */
547
+
548
+ /**
549
+ * Options of a focusable toolbar view:
550
+ *
551
+ * * `isContextual`: Marks the higher priority toolbar. For example when there are 2 visible toolbars,
552
+ * it allows to distinguish which toolbar should be focused first after the `alt+f10` keystroke
553
+ * * `beforeFocus`: A callback executed before the `ToolbarView` gains focus upon the `Alt+F10` keystroke.
554
+ * * `afterBlur`: A callback executed after `ToolbarView` loses focus upon `Esc` keystroke but before the focus goes back to the `origin`.
555
+ *
556
+ * @member {Object} #options
557
+ */
558
+
559
+ // Returns a number (weight) for a toolbar definition. Visible toolbars have a higher priority and so do
560
+ // contextual toolbars (displayed in the context of a content, for instance, an image toolbar).
561
+ //
562
+ // A standard invisible toolbar is the heaviest. A visible contextual toolbar is the lightest.
563
+ //
564
+ // @private
565
+ // @param {module:core/editor/editorui~FocusableToolbarDefinition} toolbarDef A toolbar definition to be weighted.
566
+ // @returns {Number}
567
+ function getToolbarDefinitionWeight( toolbarDef ) {
568
+ const { toolbarView, options } = toolbarDef;
569
+ let weight = 10;
570
+
571
+ // Prioritize already visible toolbars. They should get focused first.
572
+ if ( isVisible( toolbarView.element ) ) {
573
+ weight--;
574
+ }
575
+
576
+ // Prioritize contextual toolbars. They are displayed at the selection.
577
+ if ( options.isContextual ) {
578
+ weight--;
579
+ }
580
+
581
+ return weight;
582
+ }
@@ -3,6 +3,8 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
 
6
+ /* globals HTMLTextAreaElement */
7
+
6
8
  import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
7
9
  import setDataInElement from '@ckeditor/ckeditor5-utils/src/dom/setdatainelement';
8
10
 
@@ -20,7 +22,7 @@ const ElementApiMixin = {
20
22
  /**
21
23
  * @inheritDoc
22
24
  */
23
- updateSourceElement() {
25
+ updateSourceElement( data = this.data.get() ) {
24
26
  if ( !this.sourceElement ) {
25
27
  /**
26
28
  * Cannot update the source element of a detached editor.
@@ -36,7 +38,20 @@ const ElementApiMixin = {
36
38
  );
37
39
  }
38
40
 
39
- setDataInElement( this.sourceElement, this.data.get() );
41
+ const shouldUpdateSourceElement = this.config.get( 'updateSourceElementOnDestroy' );
42
+ const isSourceElementTextArea = this.sourceElement instanceof HTMLTextAreaElement;
43
+
44
+ // The data returned by the editor might be unsafe, so we want to prevent rendering
45
+ // unsafe content inside the source element different than <textarea>, which is considered
46
+ // secure. This behaviour could be changed by setting the `updateSourceElementOnDestroy`
47
+ // configuration option to `true`.
48
+ if ( !shouldUpdateSourceElement && !isSourceElementTextArea ) {
49
+ setDataInElement( this.sourceElement, '' );
50
+
51
+ return;
52
+ }
53
+
54
+ setDataInElement( this.sourceElement, data );
40
55
  }
41
56
  };
42
57
 
@@ -62,4 +77,6 @@ export default ElementApiMixin;
62
77
  * Updates the {@link #sourceElement editor source element}'s content with the data.
63
78
  *
64
79
  * @method #updateSourceElement
80
+ * @param {String} data The data that should be used to update the source element.
81
+ * By default, it is taken directly from the existing editor instance.
65
82
  */
@@ -344,7 +344,7 @@ export default class PluginCollection {
344
344
  * This is usually done in CKEditor 5 builds by setting the {@link module:core/editor/editor~Editor.builtinPlugins}
345
345
  * property.
346
346
  *
347
- * **If you see this warning when using one of the {@glink installation/advanced/alternative-setups/predefined-builds
347
+ * **If you see this warning when using one of the {@glink installation/getting-started/predefined-builds
348
348
  * CKEditor 5 Builds}**,
349
349
  * it means that you try to enable a plugin which was not included in that build. This may be due to a typo
350
350
  * in the plugin name or simply because that plugin is not a part of this build. In the latter scenario,