@ckeditor/ckeditor5-ui 43.0.0 → 43.1.0-alpha.1

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 (274) hide show
  1. package/dist/bindings/clickoutsidehandler.d.ts +5 -3
  2. package/dist/dropdown/dropdownview.d.ts +21 -8
  3. package/dist/dropdown/menu/dropdownmenubehaviors.d.ts +48 -0
  4. package/dist/dropdown/menu/dropdownmenubuttonview.d.ts +40 -0
  5. package/dist/dropdown/menu/dropdownmenulistitembuttonview.d.ts +21 -0
  6. package/dist/dropdown/menu/dropdownmenulistitemview.d.ts +26 -0
  7. package/dist/dropdown/menu/dropdownmenulistview.d.ts +28 -0
  8. package/dist/dropdown/menu/dropdownmenunestedmenupanelview.d.ts +38 -0
  9. package/dist/dropdown/menu/dropdownmenunestedmenuview.d.ts +135 -0
  10. package/dist/dropdown/menu/dropdownmenurootlistview.d.ts +144 -0
  11. package/dist/dropdown/menu/utils.d.ts +128 -0
  12. package/dist/dropdown/utils.d.ts +58 -0
  13. package/dist/editableui/editableuiview.d.ts +1 -1
  14. package/dist/editableui/inline/inlineeditableuiview.d.ts +13 -9
  15. package/dist/editorui/editorui.d.ts +4 -0
  16. package/dist/index-editor.css +16 -0
  17. package/dist/index.css +31 -0
  18. package/dist/index.css.map +1 -1
  19. package/dist/index.d.ts +7 -1
  20. package/dist/index.js +4717 -3869
  21. package/dist/index.js.map +1 -1
  22. package/dist/menubar/utils.d.ts +5 -5
  23. package/dist/panel/balloon/contextualballoon.d.ts +12 -6
  24. package/dist/toolbar/balloon/balloontoolbar.d.ts +5 -0
  25. package/dist/translations/af.js +1 -1
  26. package/dist/translations/af.umd.js +1 -1
  27. package/dist/translations/ar.js +1 -1
  28. package/dist/translations/ar.umd.js +1 -1
  29. package/dist/translations/ast.js +1 -1
  30. package/dist/translations/ast.umd.js +1 -1
  31. package/dist/translations/az.js +1 -1
  32. package/dist/translations/az.umd.js +1 -1
  33. package/dist/translations/bg.js +1 -1
  34. package/dist/translations/bg.umd.js +1 -1
  35. package/dist/translations/bn.js +1 -1
  36. package/dist/translations/bn.umd.js +1 -1
  37. package/dist/translations/bs.js +1 -1
  38. package/dist/translations/bs.umd.js +1 -1
  39. package/dist/translations/ca.js +1 -1
  40. package/dist/translations/ca.umd.js +1 -1
  41. package/dist/translations/cs.js +1 -1
  42. package/dist/translations/cs.umd.js +1 -1
  43. package/dist/translations/da.js +1 -1
  44. package/dist/translations/da.umd.js +1 -1
  45. package/dist/translations/de-ch.js +1 -1
  46. package/dist/translations/de-ch.umd.js +1 -1
  47. package/dist/translations/de.js +1 -1
  48. package/dist/translations/de.umd.js +1 -1
  49. package/dist/translations/el.js +1 -1
  50. package/dist/translations/el.umd.js +1 -1
  51. package/dist/translations/en-au.js +1 -1
  52. package/dist/translations/en-au.umd.js +1 -1
  53. package/dist/translations/en-gb.js +1 -1
  54. package/dist/translations/en-gb.umd.js +1 -1
  55. package/dist/translations/en.js +1 -1
  56. package/dist/translations/en.umd.js +1 -1
  57. package/dist/translations/eo.js +1 -1
  58. package/dist/translations/eo.umd.js +1 -1
  59. package/dist/translations/es-co.js +1 -1
  60. package/dist/translations/es-co.umd.js +1 -1
  61. package/dist/translations/es.js +1 -1
  62. package/dist/translations/es.umd.js +1 -1
  63. package/dist/translations/et.js +1 -1
  64. package/dist/translations/et.umd.js +1 -1
  65. package/dist/translations/eu.js +1 -1
  66. package/dist/translations/eu.umd.js +1 -1
  67. package/dist/translations/fa.js +1 -1
  68. package/dist/translations/fa.umd.js +1 -1
  69. package/dist/translations/fi.js +1 -1
  70. package/dist/translations/fi.umd.js +1 -1
  71. package/dist/translations/fr.js +1 -1
  72. package/dist/translations/fr.umd.js +1 -1
  73. package/dist/translations/gl.js +1 -1
  74. package/dist/translations/gl.umd.js +1 -1
  75. package/dist/translations/he.js +1 -1
  76. package/dist/translations/he.umd.js +1 -1
  77. package/dist/translations/hi.js +1 -1
  78. package/dist/translations/hi.umd.js +1 -1
  79. package/dist/translations/hr.js +1 -1
  80. package/dist/translations/hr.umd.js +1 -1
  81. package/dist/translations/hu.js +1 -1
  82. package/dist/translations/hu.umd.js +1 -1
  83. package/dist/translations/id.js +1 -1
  84. package/dist/translations/id.umd.js +1 -1
  85. package/dist/translations/it.js +1 -1
  86. package/dist/translations/it.umd.js +1 -1
  87. package/dist/translations/ja.js +1 -1
  88. package/dist/translations/ja.umd.js +1 -1
  89. package/dist/translations/jv.js +1 -1
  90. package/dist/translations/jv.umd.js +1 -1
  91. package/dist/translations/km.js +1 -1
  92. package/dist/translations/km.umd.js +1 -1
  93. package/dist/translations/kn.js +1 -1
  94. package/dist/translations/kn.umd.js +1 -1
  95. package/dist/translations/ko.js +1 -1
  96. package/dist/translations/ko.umd.js +1 -1
  97. package/dist/translations/ku.js +1 -1
  98. package/dist/translations/ku.umd.js +1 -1
  99. package/dist/translations/lt.js +1 -1
  100. package/dist/translations/lt.umd.js +1 -1
  101. package/dist/translations/lv.js +1 -1
  102. package/dist/translations/lv.umd.js +1 -1
  103. package/dist/translations/ms.js +1 -1
  104. package/dist/translations/ms.umd.js +1 -1
  105. package/dist/translations/nb.js +1 -1
  106. package/dist/translations/nb.umd.js +1 -1
  107. package/dist/translations/ne.js +1 -1
  108. package/dist/translations/ne.umd.js +1 -1
  109. package/dist/translations/nl.js +1 -1
  110. package/dist/translations/nl.umd.js +1 -1
  111. package/dist/translations/no.js +1 -1
  112. package/dist/translations/no.umd.js +1 -1
  113. package/dist/translations/pl.js +1 -1
  114. package/dist/translations/pl.umd.js +1 -1
  115. package/dist/translations/pt-br.js +1 -1
  116. package/dist/translations/pt-br.umd.js +1 -1
  117. package/dist/translations/pt.js +1 -1
  118. package/dist/translations/pt.umd.js +1 -1
  119. package/dist/translations/ro.js +1 -1
  120. package/dist/translations/ro.umd.js +1 -1
  121. package/dist/translations/ru.js +1 -1
  122. package/dist/translations/ru.umd.js +1 -1
  123. package/dist/translations/sk.js +1 -1
  124. package/dist/translations/sk.umd.js +1 -1
  125. package/dist/translations/sl.js +1 -1
  126. package/dist/translations/sl.umd.js +1 -1
  127. package/dist/translations/sq.js +1 -1
  128. package/dist/translations/sq.umd.js +1 -1
  129. package/dist/translations/sr-latn.js +1 -1
  130. package/dist/translations/sr-latn.umd.js +1 -1
  131. package/dist/translations/sr.js +1 -1
  132. package/dist/translations/sr.umd.js +1 -1
  133. package/dist/translations/sv.js +1 -1
  134. package/dist/translations/sv.umd.js +1 -1
  135. package/dist/translations/th.js +1 -1
  136. package/dist/translations/th.umd.js +1 -1
  137. package/dist/translations/ti.js +1 -1
  138. package/dist/translations/ti.umd.js +1 -1
  139. package/dist/translations/tk.js +1 -1
  140. package/dist/translations/tk.umd.js +1 -1
  141. package/dist/translations/tr.js +1 -1
  142. package/dist/translations/tr.umd.js +1 -1
  143. package/dist/translations/tt.js +1 -1
  144. package/dist/translations/tt.umd.js +1 -1
  145. package/dist/translations/ug.js +1 -1
  146. package/dist/translations/ug.umd.js +1 -1
  147. package/dist/translations/uk.js +1 -1
  148. package/dist/translations/uk.umd.js +1 -1
  149. package/dist/translations/ur.js +1 -1
  150. package/dist/translations/ur.umd.js +1 -1
  151. package/dist/translations/uz.js +1 -1
  152. package/dist/translations/uz.umd.js +1 -1
  153. package/dist/translations/vi.js +1 -1
  154. package/dist/translations/vi.umd.js +1 -1
  155. package/dist/translations/zh-cn.js +1 -1
  156. package/dist/translations/zh-cn.umd.js +1 -1
  157. package/dist/translations/zh.js +1 -1
  158. package/dist/translations/zh.umd.js +1 -1
  159. package/lang/contexts.json +1 -1
  160. package/lang/translations/af.po +4 -4
  161. package/lang/translations/ar.po +4 -4
  162. package/lang/translations/ast.po +4 -4
  163. package/lang/translations/az.po +4 -4
  164. package/lang/translations/bg.po +4 -4
  165. package/lang/translations/bn.po +4 -4
  166. package/lang/translations/bs.po +4 -4
  167. package/lang/translations/ca.po +4 -4
  168. package/lang/translations/cs.po +4 -4
  169. package/lang/translations/da.po +4 -4
  170. package/lang/translations/de-ch.po +4 -4
  171. package/lang/translations/de.po +4 -4
  172. package/lang/translations/el.po +4 -4
  173. package/lang/translations/en-au.po +4 -4
  174. package/lang/translations/en-gb.po +4 -4
  175. package/lang/translations/en.po +4 -4
  176. package/lang/translations/eo.po +4 -4
  177. package/lang/translations/es-co.po +4 -4
  178. package/lang/translations/es.po +4 -4
  179. package/lang/translations/et.po +4 -4
  180. package/lang/translations/eu.po +4 -4
  181. package/lang/translations/fa.po +4 -4
  182. package/lang/translations/fi.po +4 -4
  183. package/lang/translations/fr.po +4 -4
  184. package/lang/translations/gl.po +4 -4
  185. package/lang/translations/he.po +4 -4
  186. package/lang/translations/hi.po +4 -4
  187. package/lang/translations/hr.po +4 -4
  188. package/lang/translations/hu.po +4 -4
  189. package/lang/translations/id.po +4 -4
  190. package/lang/translations/it.po +4 -4
  191. package/lang/translations/ja.po +4 -4
  192. package/lang/translations/jv.po +4 -4
  193. package/lang/translations/km.po +4 -4
  194. package/lang/translations/kn.po +4 -4
  195. package/lang/translations/ko.po +4 -4
  196. package/lang/translations/ku.po +4 -4
  197. package/lang/translations/lt.po +4 -4
  198. package/lang/translations/lv.po +4 -4
  199. package/lang/translations/ms.po +4 -4
  200. package/lang/translations/nb.po +4 -4
  201. package/lang/translations/ne.po +4 -4
  202. package/lang/translations/nl.po +4 -4
  203. package/lang/translations/no.po +4 -4
  204. package/lang/translations/pl.po +4 -4
  205. package/lang/translations/pt-br.po +4 -4
  206. package/lang/translations/pt.po +4 -4
  207. package/lang/translations/ro.po +4 -4
  208. package/lang/translations/ru.po +4 -4
  209. package/lang/translations/sk.po +4 -4
  210. package/lang/translations/sl.po +4 -4
  211. package/lang/translations/sq.po +4 -4
  212. package/lang/translations/sr-latn.po +4 -4
  213. package/lang/translations/sr.po +4 -4
  214. package/lang/translations/sv.po +4 -4
  215. package/lang/translations/th.po +4 -4
  216. package/lang/translations/ti.po +4 -4
  217. package/lang/translations/tk.po +4 -4
  218. package/lang/translations/tr.po +4 -4
  219. package/lang/translations/tt.po +4 -4
  220. package/lang/translations/ug.po +4 -4
  221. package/lang/translations/uk.po +4 -4
  222. package/lang/translations/ur.po +4 -4
  223. package/lang/translations/uz.po +4 -4
  224. package/lang/translations/vi.po +4 -4
  225. package/lang/translations/zh-cn.po +4 -4
  226. package/lang/translations/zh.po +4 -4
  227. package/package.json +4 -3
  228. package/src/bindings/clickoutsidehandler.d.ts +5 -3
  229. package/src/bindings/clickoutsidehandler.js +3 -2
  230. package/src/dialog/dialog.js +0 -1
  231. package/src/dropdown/dropdownview.d.ts +21 -8
  232. package/src/dropdown/menu/dropdownmenubehaviors.d.ts +44 -0
  233. package/src/dropdown/menu/dropdownmenubehaviors.js +119 -0
  234. package/src/dropdown/menu/dropdownmenubuttonview.d.ts +36 -0
  235. package/src/dropdown/menu/dropdownmenubuttonview.js +65 -0
  236. package/src/dropdown/menu/dropdownmenulistitembuttonview.d.ts +17 -0
  237. package/src/dropdown/menu/dropdownmenulistitembuttonview.js +29 -0
  238. package/src/dropdown/menu/dropdownmenulistitemview.d.ts +22 -0
  239. package/src/dropdown/menu/dropdownmenulistitemview.js +34 -0
  240. package/src/dropdown/menu/dropdownmenulistview.d.ts +24 -0
  241. package/src/dropdown/menu/dropdownmenulistview.js +29 -0
  242. package/src/dropdown/menu/dropdownmenunestedmenupanelview.d.ts +34 -0
  243. package/src/dropdown/menu/dropdownmenunestedmenupanelview.js +63 -0
  244. package/src/dropdown/menu/dropdownmenunestedmenuview.d.ts +131 -0
  245. package/src/dropdown/menu/dropdownmenunestedmenuview.js +191 -0
  246. package/src/dropdown/menu/dropdownmenurootlistview.d.ts +140 -0
  247. package/src/dropdown/menu/dropdownmenurootlistview.js +160 -0
  248. package/src/dropdown/menu/utils.d.ts +124 -0
  249. package/src/dropdown/menu/utils.js +61 -0
  250. package/src/dropdown/utils.d.ts +58 -0
  251. package/src/dropdown/utils.js +101 -18
  252. package/src/editableui/editableuiview.d.ts +1 -1
  253. package/src/editableui/inline/inlineeditableuiview.d.ts +13 -9
  254. package/src/editableui/inline/inlineeditableuiview.js +29 -6
  255. package/src/editorui/accessibilityhelp/accessibilityhelp.js +3 -1
  256. package/src/editorui/editorui.d.ts +4 -0
  257. package/src/editorui/editorui.js +16 -1
  258. package/src/editorui/poweredby.js +0 -1
  259. package/src/index.d.ts +7 -1
  260. package/src/index.js +6 -0
  261. package/src/menubar/menubarmenuview.js +1 -0
  262. package/src/menubar/utils.d.ts +5 -5
  263. package/src/menubar/utils.js +21 -12
  264. package/src/panel/balloon/balloonpanelview.js +19 -12
  265. package/src/panel/balloon/contextualballoon.d.ts +12 -6
  266. package/src/panel/balloon/contextualballoon.js +24 -24
  267. package/src/toolbar/balloon/balloontoolbar.d.ts +5 -0
  268. package/src/toolbar/balloon/balloontoolbar.js +26 -5
  269. package/src/toolbar/block/blocktoolbar.js +0 -2
  270. package/theme/components/dropdown/menu/dropdownmenu.css +8 -0
  271. package/theme/components/dropdown/menu/dropdownmenubutton.css +9 -0
  272. package/theme/components/dropdown/menu/dropdownmenulistitem.css +10 -0
  273. package/theme/components/dropdown/menu/dropdownmenulistitembutton.css +10 -0
  274. package/theme/components/dropdown/menu/dropdownmenupanel.css +11 -0
@@ -8,6 +8,7 @@
8
8
  import DropdownPanelView from './dropdownpanelview.js';
9
9
  import DropdownView from './dropdownview.js';
10
10
  import DropdownButtonView from './button/dropdownbuttonview.js';
11
+ import DropdownMenuRootListView from './menu/dropdownmenurootlistview.js';
11
12
  import ToolbarView from '../toolbar/toolbarview.js';
12
13
  import ListView from '../list/listview.js';
13
14
  import ListItemView from '../list/listitemview.js';
@@ -86,6 +87,8 @@ import ListItemButtonView from '../button/listitembuttonview.js';
86
87
  *
87
88
  * @param locale The locale instance.
88
89
  * @param ButtonClassOrInstance The dropdown button view class. Needs to implement the
90
+ * @param behaviorOptions Attributes for the default behavior of the dropdown.
91
+ *
89
92
  * {@link module:ui/dropdown/button/dropdownbutton~DropdownButton} interface.
90
93
  * @returns The dropdown view instance.
91
94
  */
@@ -100,9 +103,90 @@ export function createDropdown(locale, ButtonClassOrInstance = DropdownButtonVie
100
103
  else {
101
104
  buttonView.bind('isOn').to(dropdownView, 'isOpen');
102
105
  }
103
- addDefaultBehavior(dropdownView);
106
+ addDefaultBehaviors(dropdownView);
104
107
  return dropdownView;
105
108
  }
109
+ /**
110
+ * Adds a menu UI component to a dropdown and sets all common behaviors and interactions between the dropdown and the menu.
111
+ *
112
+ * Use this helper to create multi-level dropdown menus that are displayed in a toolbar.
113
+ *
114
+ * Internally, it creates an instance of {@link module:ui/dropdown/menu/dropdownmenurootlistview~DropdownMenuRootListView}.
115
+ *
116
+ * Example:
117
+ *
118
+ * ```ts
119
+ * const definitions = [
120
+ * {
121
+ * id: 'menu_1',
122
+ * menu: 'Menu 1',
123
+ * children: [
124
+ * {
125
+ * id: 'menu_1_a',
126
+ * label: 'Item A'
127
+ * },
128
+ * {
129
+ * id: 'menu_1_b',
130
+ * label: 'Item B'
131
+ * }
132
+ * ]
133
+ * },
134
+ * {
135
+ * id: 'top_a',
136
+ * label: 'Top Item A'
137
+ * },
138
+ * {
139
+ * id: 'top_b',
140
+ * label: 'Top Item B'
141
+ * }
142
+ * ];
143
+ *
144
+ * const dropdownView = createDropdown( editor.locale );
145
+ *
146
+ * addMenuToDropdown( dropdownView, editor.ui.view.body, definitions );
147
+ * ```
148
+ *
149
+ * After using this helper, the `dropdown` will fire {@link module:ui/dropdown/dropdownview~DropdownViewEvent `execute`} event when
150
+ * a nested menu button is pressed.
151
+ *
152
+ * The helper will make sure that the `dropdownMenuRootListView` is lazy loaded, i.e., the menu component structure will be initialized
153
+ * and rendered only after the `dropdown` is opened for the first time.
154
+ *
155
+ * @param dropdownView A dropdown instance to which the menu component will be added.
156
+ * @param body Body collection to which floating menu panels will be added.
157
+ * @param definition The menu component definition.
158
+ * @param options.ariaLabel Label used by assistive technologies to describe the top-level menu.
159
+ */
160
+ export function addMenuToDropdown(dropdownView, body, definition, options = {}) {
161
+ dropdownView.menuView = new DropdownMenuRootListView(dropdownView.locale, body, definition);
162
+ if (dropdownView.isOpen) {
163
+ addMenuToOpenDropdown(dropdownView, options);
164
+ }
165
+ else {
166
+ // Load the UI elements only after the dropdown is opened for the first time - lazy loading.
167
+ dropdownView.once('change:isOpen', () => {
168
+ addMenuToOpenDropdown(dropdownView, options);
169
+ }, { priority: 'highest' });
170
+ }
171
+ }
172
+ function addMenuToOpenDropdown(dropdownView, options) {
173
+ const dropdownMenuRootListView = dropdownView.menuView;
174
+ const t = dropdownView.locale.t;
175
+ dropdownMenuRootListView.delegate('menu:execute').to(dropdownView, 'execute');
176
+ dropdownMenuRootListView.listenTo(dropdownView, 'change:isOpen', (evt, name, isOpen) => {
177
+ if (!isOpen) {
178
+ dropdownMenuRootListView.closeMenus();
179
+ }
180
+ }, { priority: 'low' }); // Make sure this is fired after `focusDropdownButtonOnClose` behavior.
181
+ // When `dropdownMenuRootListView` is added as a `panelView` child, it becomes rendered (`panelView` is rendered at this point).
182
+ dropdownView.panelView.children.add(dropdownMenuRootListView);
183
+ // Nested menu panels are added to body collection, so they are not children of the `dropdownView` from DOM perspective.
184
+ // Add these panels to `dropdownView` focus tracker, so they are treated like part of the `dropdownView` for focus-related purposes.
185
+ for (const menu of dropdownMenuRootListView.menus) {
186
+ dropdownView.focusTracker.add(menu.panelView.element);
187
+ }
188
+ dropdownMenuRootListView.ariaLabel = options.ariaLabel || t('Dropdown menu');
189
+ }
106
190
  /**
107
191
  * Adds an instance of {@link module:ui/toolbar/toolbarview~ToolbarView} to a dropdown.
108
192
  *
@@ -315,7 +399,7 @@ export function focusChildOnDropdownOpen(dropdownView, childSelectorCallback) {
315
399
  /**
316
400
  * Add a set of default behaviors to dropdown view.
317
401
  */
318
- function addDefaultBehavior(dropdownView) {
402
+ function addDefaultBehaviors(dropdownView) {
319
403
  closeDropdownOnClickOutside(dropdownView);
320
404
  closeDropdownOnExecute(dropdownView);
321
405
  closeDropdownOnBlur(dropdownView);
@@ -327,18 +411,16 @@ function addDefaultBehavior(dropdownView) {
327
411
  * Adds a behavior to a dropdownView that closes opened dropdown when user clicks outside the dropdown.
328
412
  */
329
413
  function closeDropdownOnClickOutside(dropdownView) {
330
- dropdownView.on('render', () => {
331
- clickOutsideHandler({
332
- emitter: dropdownView,
333
- activator: () => dropdownView.isOpen,
334
- callback: () => {
335
- dropdownView.isOpen = false;
336
- },
337
- contextElements: () => [
338
- dropdownView.element,
339
- ...dropdownView.focusTracker._elements
340
- ]
341
- });
414
+ clickOutsideHandler({
415
+ emitter: dropdownView,
416
+ activator: () => dropdownView.isRendered && dropdownView.isOpen,
417
+ callback: () => {
418
+ dropdownView.isOpen = false;
419
+ },
420
+ contextElements: () => [
421
+ dropdownView.element,
422
+ ...dropdownView.focusTracker.elements
423
+ ]
342
424
  });
343
425
  }
344
426
  /**
@@ -359,9 +441,10 @@ function closeDropdownOnExecute(dropdownView) {
359
441
  */
360
442
  function closeDropdownOnBlur(dropdownView) {
361
443
  dropdownView.focusTracker.on('change:isFocused', (evt, name, isFocused) => {
362
- if (dropdownView.isOpen && !isFocused) {
363
- dropdownView.isOpen = false;
444
+ if (isFocused || !dropdownView.isOpen) {
445
+ return;
364
446
  }
447
+ dropdownView.isOpen = false;
365
448
  });
366
449
  }
367
450
  /**
@@ -392,11 +475,11 @@ function focusDropdownButtonOnClose(dropdownView) {
392
475
  if (isOpen) {
393
476
  return;
394
477
  }
395
- const element = dropdownView.panelView.element;
478
+ const elements = dropdownView.focusTracker.elements;
396
479
  // If the dropdown was closed, move the focus back to the button (#12125).
397
480
  // Don't touch the focus, if it moved somewhere else (e.g. moved to the editing root on #execute) (#12178).
398
481
  // Note: Don't use the state of the DropdownView#focusTracker here. It fires #blur with the timeout.
399
- if (element && element.contains(global.document.activeElement)) {
482
+ if (elements.some(element => element.contains(global.document.activeElement))) {
400
483
  dropdownView.buttonView.focus();
401
484
  }
402
485
  });
@@ -34,7 +34,7 @@ export default class EditableUIView extends View {
34
34
  /**
35
35
  * The element which is the main editable element (usually the one with `contentEditable="true"`).
36
36
  */
37
- private _editableElement;
37
+ protected _editableElement: HTMLElement | null | undefined;
38
38
  /**
39
39
  * Whether an external {@link #_editableElement} was passed into the constructor, which also means
40
40
  * the view will not render its {@link #template}.
@@ -13,10 +13,9 @@ import type { Locale } from '@ckeditor/ckeditor5-utils';
13
13
  */
14
14
  export default class InlineEditableUIView extends EditableUIView {
15
15
  /**
16
- * A function that gets called with the instance of this view as an argument and should return a string that
17
- * represents the label of the editable for assistive technologies.
16
+ * The cached options object passed to the constructor.
18
17
  */
19
- private readonly _generateLabel;
18
+ private readonly _options;
20
19
  /**
21
20
  * Creates an instance of the InlineEditableUIView class.
22
21
  *
@@ -26,15 +25,20 @@ export default class InlineEditableUIView extends EditableUIView {
26
25
  * {@link module:ui/editableui/editableuiview~EditableUIView}
27
26
  * will create it. Otherwise, the existing element will be used.
28
27
  * @param options Additional configuration of the view.
29
- * @param options.label A function that gets called with the instance of this view as an argument
30
- * and should return a string that represents the label of the editable for assistive technologies. If not provided,
31
- * a default label generator is used.
28
+ * @param options.label The label of the editable for assistive technologies. If not provided, a default label is used or,
29
+ * the existing `aria-label` attribute value from the specified `editableElement` is preserved.
32
30
  */
33
- constructor(locale: Locale, editingView: EditingView, editableElement?: HTMLElement, options?: {
34
- label?: (view: InlineEditableUIView) => string;
35
- });
31
+ constructor(locale: Locale, editingView: EditingView, editableElement?: HTMLElement, options?: InlineEditableUIViewOptions);
36
32
  /**
37
33
  * @inheritDoc
38
34
  */
39
35
  render(): void;
36
+ /**
37
+ * Returns a normalized label for the editable view based on the environment.
38
+ */
39
+ getEditableAriaLabel(): string;
40
40
  }
41
+ type InlineEditableUIViewOptions = {
42
+ label?: ((view: InlineEditableUIView) => string) | string | Record<string, string>;
43
+ };
44
+ export {};
@@ -19,20 +19,18 @@ export default class InlineEditableUIView extends EditableUIView {
19
19
  * {@link module:ui/editableui/editableuiview~EditableUIView}
20
20
  * will create it. Otherwise, the existing element will be used.
21
21
  * @param options Additional configuration of the view.
22
- * @param options.label A function that gets called with the instance of this view as an argument
23
- * and should return a string that represents the label of the editable for assistive technologies. If not provided,
24
- * a default label generator is used.
22
+ * @param options.label The label of the editable for assistive technologies. If not provided, a default label is used or,
23
+ * the existing `aria-label` attribute value from the specified `editableElement` is preserved.
25
24
  */
26
25
  constructor(locale, editingView, editableElement, options = {}) {
27
26
  super(locale, editingView, editableElement);
28
- const t = locale.t;
27
+ this._options = options;
29
28
  this.extendTemplate({
30
29
  attributes: {
31
30
  role: 'textbox',
32
31
  class: 'ck-editor__editable_inline'
33
32
  }
34
33
  });
35
- this._generateLabel = options.label || (() => t('Editor editing area: %0', this.name));
36
34
  }
37
35
  /**
38
36
  * @inheritDoc
@@ -42,7 +40,32 @@ export default class InlineEditableUIView extends EditableUIView {
42
40
  const editingView = this._editingView;
43
41
  editingView.change(writer => {
44
42
  const viewRoot = editingView.document.getRoot(this.name);
45
- writer.setAttribute('aria-label', this._generateLabel(this), viewRoot);
43
+ writer.setAttribute('aria-label', this.getEditableAriaLabel(), viewRoot);
46
44
  });
47
45
  }
46
+ /**
47
+ * Returns a normalized label for the editable view based on the environment.
48
+ */
49
+ getEditableAriaLabel() {
50
+ const t = this.locale.t;
51
+ const label = this._options.label;
52
+ const editableElement = this._editableElement;
53
+ const editableName = this.name;
54
+ if (typeof label == 'string') {
55
+ return label;
56
+ }
57
+ else if (typeof label === 'object') {
58
+ return label[editableName];
59
+ }
60
+ else if (typeof label === 'function') {
61
+ return label(this);
62
+ }
63
+ else if (editableElement) {
64
+ const existingLabel = editableElement.getAttribute('aria-label');
65
+ if (existingLabel) {
66
+ return existingLabel;
67
+ }
68
+ }
69
+ return t('Rich Text Editor. Editing area: %0', editableName);
70
+ }
48
71
  }
@@ -106,7 +106,9 @@ export default class AccessibilityHelp extends Plugin {
106
106
  });
107
107
  function addAriaLabelTextToRoot(writer, viewRoot) {
108
108
  const currentAriaLabel = viewRoot.getAttribute('aria-label');
109
- const newAriaLabel = `${currentAriaLabel}. ${t('Press %0 for help.', [getEnvKeystrokeText('Alt+0')])}`;
109
+ const newAriaLabel = [currentAriaLabel, t('Press %0 for help.', [getEnvKeystrokeText('Alt+0')])]
110
+ .filter(segment => segment)
111
+ .join('. ');
110
112
  writer.setAttribute('aria-label', newAriaLabel, viewRoot);
111
113
  }
112
114
  }
@@ -282,6 +282,10 @@ export default abstract class EditorUI extends /* #__PURE__ */ EditorUI_base {
282
282
  * @param data The payload carried by the `scrollToTheSelection` event.
283
283
  */
284
284
  private _handleScrollToTheSelection;
285
+ /**
286
+ * Ensures that the focus tracker is aware of all views' DOM elements in the body collection.
287
+ */
288
+ private _bindBodyCollectionWithFocusTracker;
285
289
  }
286
290
  /**
287
291
  * Fired when the editor UI is ready.
@@ -55,6 +55,7 @@ export default class EditorUI extends /* #__PURE__ */ ObservableMixin() {
55
55
  this.ariaLiveAnnouncer = new AriaLiveAnnouncer(editor);
56
56
  this.set('viewportOffset', this._readViewportOffsetFromConfig());
57
57
  this.once('ready', () => {
58
+ this._bindBodyCollectionWithFocusTracker();
58
59
  this.isReady = true;
59
60
  });
60
61
  // Informs UI components that should be refreshed after layout change.
@@ -332,7 +333,6 @@ export default class EditorUI extends /* #__PURE__ */ ObservableMixin() {
332
333
  */
333
334
  _initFocusTracking() {
334
335
  const editor = this.editor;
335
- const editingView = editor.editing.view;
336
336
  let candidateDefinitions;
337
337
  // Focus the next focusable toolbar on <kbd>Alt</kbd> + <kbd>F10</kbd>.
338
338
  editor.keystrokes.set('Alt+F10', (data, cancel) => {
@@ -480,6 +480,21 @@ export default class EditorUI extends /* #__PURE__ */ ObservableMixin() {
480
480
  data.viewportOffset.left += configuredViewportOffset.left;
481
481
  data.viewportOffset.right += configuredViewportOffset.right;
482
482
  }
483
+ /**
484
+ * Ensures that the focus tracker is aware of all views' DOM elements in the body collection.
485
+ */
486
+ _bindBodyCollectionWithFocusTracker() {
487
+ const body = this.view.body;
488
+ for (const view of body) {
489
+ this.focusTracker.add(view.element);
490
+ }
491
+ body.on('add', (evt, view) => {
492
+ this.focusTracker.add(view.element);
493
+ });
494
+ body.on('remove', (evt, view) => {
495
+ this.focusTracker.remove(view.element);
496
+ });
497
+ }
483
498
  }
484
499
  /**
485
500
  * Returns a number (weight) for a toolbar definition. Visible toolbars have a higher priority and so do
@@ -97,7 +97,6 @@ export default class PoweredBy extends /* #__PURE__ */ DomEmitterMixin() {
97
97
  class: 'ck-powered-by-balloon'
98
98
  });
99
99
  editor.ui.view.body.add(balloon);
100
- editor.ui.focusTracker.add(balloon.element);
101
100
  this._balloonView = balloon;
102
101
  }
103
102
  /**
package/src/index.d.ts CHANGED
@@ -35,6 +35,12 @@ export { default as DropdownPanelView } from './dropdown/dropdownpanelview.js';
35
35
  export { default as DropdownButtonView } from './dropdown/button/dropdownbuttonview.js';
36
36
  export { default as SplitButtonView } from './dropdown/button/splitbuttonview.js';
37
37
  export * from './dropdown/utils.js';
38
+ export * from './dropdown/menu/utils.js';
39
+ export { default as DropdownMenuNestedMenuView } from './dropdown/menu/dropdownmenunestedmenuview.js';
40
+ export { default as DropdownMenuRootListView } from './dropdown/menu/dropdownmenurootlistview.js';
41
+ export { default as DropdownMenuListView } from './dropdown/menu/dropdownmenulistview.js';
42
+ export { default as DropdownMenuListItemView } from './dropdown/menu/dropdownmenulistitemview.js';
43
+ export { default as DropdownMenuListItemButtonView } from './dropdown/menu/dropdownmenulistitembuttonview.js';
38
44
  export { default as EditorUI, type EditorUIReadyEvent, type EditorUIUpdateEvent } from './editorui/editorui.js';
39
45
  export { default as EditorUIView } from './editorui/editoruiview.js';
40
46
  export { default as BoxedEditorUIView } from './editorui/boxed/boxededitoruiview.js';
@@ -58,7 +64,7 @@ export { default as filterGroupAndItemNames } from './search/filtergroupanditemn
58
64
  export { default as Notification } from './notification/notification.js';
59
65
  export { default as ViewModel } from './model.js';
60
66
  export { default as BalloonPanelView } from './panel/balloon/balloonpanelview.js';
61
- export { default as ContextualBalloon } from './panel/balloon/contextualballoon.js';
67
+ export { default as ContextualBalloon, type ContextualBalloonGetPositionOptionsEvent } from './panel/balloon/contextualballoon.js';
62
68
  export { default as StickyPanelView } from './panel/sticky/stickypanelview.js';
63
69
  export { default as AutocompleteView, type AutocompleteViewConfig, type AutocompleteResultsView } from './autocomplete/autocompleteview.js';
64
70
  export { default as SearchTextView, type SearchTextViewSearchEvent, type SearchTextViewConfig } from './search/text/searchtextview.js';
package/src/index.js CHANGED
@@ -33,6 +33,12 @@ export { default as DropdownPanelView } from './dropdown/dropdownpanelview.js';
33
33
  export { default as DropdownButtonView } from './dropdown/button/dropdownbuttonview.js';
34
34
  export { default as SplitButtonView } from './dropdown/button/splitbuttonview.js';
35
35
  export * from './dropdown/utils.js';
36
+ export * from './dropdown/menu/utils.js';
37
+ export { default as DropdownMenuNestedMenuView } from './dropdown/menu/dropdownmenunestedmenuview.js';
38
+ export { default as DropdownMenuRootListView } from './dropdown/menu/dropdownmenurootlistview.js';
39
+ export { default as DropdownMenuListView } from './dropdown/menu/dropdownmenulistview.js';
40
+ export { default as DropdownMenuListItemView } from './dropdown/menu/dropdownmenulistitemview.js';
41
+ export { default as DropdownMenuListItemButtonView } from './dropdown/menu/dropdownmenulistitembuttonview.js';
36
42
  export { default as EditorUI } from './editorui/editorui.js';
37
43
  export { default as EditorUIView } from './editorui/editoruiview.js';
38
44
  export { default as BoxedEditorUIView } from './editorui/boxed/boxededitoruiview.js';
@@ -85,6 +85,7 @@ class MenuBarMenuView extends View {
85
85
  MenuBarMenuBehaviors.openOnButtonClick(this);
86
86
  MenuBarMenuBehaviors.openOnArrowRightKey(this);
87
87
  MenuBarMenuBehaviors.closeOnArrowLeftKey(this);
88
+ MenuBarMenuBehaviors.openAndFocusOnEnterKeyPress(this);
88
89
  MenuBarMenuBehaviors.closeOnParentClose(this);
89
90
  }
90
91
  }
@@ -6,9 +6,6 @@ import type MenuBarMenuView from './menubarmenuview.js';
6
6
  import type { default as MenuBarView, MenuBarConfig, MenuBarConfigObject, MenuBarConfigAddedGroup, MenuBarConfigAddedMenu, NormalizedMenuBarConfigObject, MenuBarConfigAddedItem } from './menubarview.js';
7
7
  import type ComponentFactory from '../componentfactory.js';
8
8
  import { type Locale, type PositioningFunction } from '@ckeditor/ckeditor5-utils';
9
- type DeepReadonly<T> = Readonly<{
10
- [K in keyof T]: T[K] extends string ? Readonly<T[K]> : T[K] extends Array<infer A> ? Readonly<Array<DeepReadonly<A>>> : DeepReadonly<T[K]>;
11
- }>;
12
9
  /**
13
10
  * Behaviors of the {@link module:ui/menubar/menubarview~MenuBarView} component.
14
11
  */
@@ -71,6 +68,10 @@ export declare const MenuBarMenuBehaviors: {
71
68
  * Toggles the menu on its button click. This behavior is analogous to {@link module:ui/dropdown/dropdownview~DropdownView}.
72
69
  */
73
70
  toggleOnButtonClick(menuView: MenuBarMenuView): void;
71
+ /**
72
+ * Opens the menu and focuses the panel content upon pressing the Enter key.
73
+ */
74
+ openAndFocusOnEnterKeyPress(menuView: MenuBarMenuView): void;
74
75
  /**
75
76
  * Closes the menu on the right left key press. This allows for navigating to sub-menus using the keyboard.
76
77
  */
@@ -425,7 +426,7 @@ export declare const MenuBarMenuViewPanelPositioningFunctions: Record<string, Po
425
426
  *
426
427
  * The menu bar can be customized using the `config.menuBar.removeItems` and `config.menuBar.addItems` properties.
427
428
  */
428
- export declare const DefaultMenuBarItems: DeepReadonly<MenuBarConfigObject['items']>;
429
+ export declare const DefaultMenuBarItems: MenuBarConfigObject['items'];
429
430
  /**
430
431
  * Performs a cleanup and normalization of the menu bar configuration.
431
432
  */
@@ -444,4 +445,3 @@ export declare function processMenuBarConfig({ normalizedConfig, locale, compone
444
445
  componentFactory: ComponentFactory;
445
446
  extraItems: Array<MenuBarConfigAddedItem | MenuBarConfigAddedGroup | MenuBarConfigAddedMenu>;
446
447
  }): NormalizedMenuBarConfigObject;
447
- export {};
@@ -124,20 +124,17 @@ export const MenuBarBehaviors = {
124
124
  let isKeyPressed = false;
125
125
  menuBarView.on('change:isOpen', (_, evt, isOpen) => {
126
126
  if (!isOpen) {
127
- menuBarView.isFocusBorderEnabled = false;
127
+ // Keep the focus border if the menu bar was closed by a keyboard interaction (Esc key).
128
+ // The user remains in the keyboard navigation mode and can traverse the main categories.
129
+ // See https://github.com/ckeditor/ckeditor5/issues/16719.
130
+ if (!isKeyPressed) {
131
+ menuBarView.isFocusBorderEnabled = false;
132
+ }
128
133
  // Reset the flag when the menu bar is closed, menu items tend to intercept `keyup` event
129
134
  // and sometimes, after pressing `enter` on focused item, `isKeyPressed` stuck in `true` state.
130
135
  isKeyPressed = false;
131
136
  }
132
137
  });
133
- // After clicking menu bar list item the focus is moved to the newly opened submenu.
134
- // We need to enable focus border for the submenu items because after pressing arrow down it will
135
- // focus second item instead of first which is not super intuitive.
136
- menuBarView.listenTo(menuBarView.element, 'click', () => {
137
- if (menuBarView.isOpen && menuBarView.element.matches(':focus-within')) {
138
- menuBarView.isFocusBorderEnabled = true;
139
- }
140
- }, { useCapture: true });
141
138
  menuBarView.listenTo(menuBarView.element, 'keydown', () => {
142
139
  isKeyPressed = true;
143
140
  }, { useCapture: true });
@@ -194,9 +191,6 @@ export const MenuBarMenuBehaviors = {
194
191
  openOnButtonClick(menuView) {
195
192
  menuView.buttonView.on('execute', () => {
196
193
  menuView.isOpen = true;
197
- if (menuView.parentMenuView) {
198
- menuView.panelView.focus();
199
- }
200
194
  });
201
195
  },
202
196
  /**
@@ -207,6 +201,21 @@ export const MenuBarMenuBehaviors = {
207
201
  menuView.isOpen = !menuView.isOpen;
208
202
  });
209
203
  },
204
+ /**
205
+ * Opens the menu and focuses the panel content upon pressing the Enter key.
206
+ */
207
+ openAndFocusOnEnterKeyPress(menuView) {
208
+ menuView.keystrokes.set('enter', (data, cancel) => {
209
+ // Engage only for Enter key press when the button is focused. The panel can contain
210
+ // other UI components and features that rely on the Enter key press.
211
+ if (menuView.focusTracker.focusedElement !== menuView.buttonView.element) {
212
+ return;
213
+ }
214
+ menuView.isOpen = true;
215
+ menuView.panelView.focus();
216
+ cancel();
217
+ });
218
+ },
210
219
  /**
211
220
  * Closes the menu on the right left key press. This allows for navigating to sub-menus using the keyboard.
212
221
  */
@@ -6,7 +6,7 @@
6
6
  * @module ui/panel/balloon/balloonpanelview
7
7
  */
8
8
  import View from '../../view.js';
9
- import { getOptimalPosition, global, isRange, toUnit, isVisible, ResizeObserver } from '@ckeditor/ckeditor5-utils';
9
+ import { getOptimalPosition, global, isRange, toUnit, isVisible, isText, ResizeObserver } from '@ckeditor/ckeditor5-utils';
10
10
  import { isElement } from 'lodash-es';
11
11
  import '../../../theme/components/panel/balloonpanel.css';
12
12
  const toPx = /* #__PURE__ */ toUnit('px');
@@ -273,7 +273,7 @@ class BalloonPanelView extends View {
273
273
  if (!this.attachTo(options)) {
274
274
  return false;
275
275
  }
276
- const targetElement = getDomElement(options.target);
276
+ let targetElement = getDomElement(options.target);
277
277
  const limiterElement = options.limiter ? getDomElement(options.limiter) : global.document.body;
278
278
  // Then we need to listen on scroll event of eny element in the document.
279
279
  this.listenTo(global.document, 'scroll', (evt, domEvt) => {
@@ -293,16 +293,23 @@ class BalloonPanelView extends View {
293
293
  this.attachTo(options);
294
294
  });
295
295
  // Hide the panel if the target element is no longer visible.
296
- if (targetElement && !this._resizeObserver) {
297
- const checkVisibility = () => {
298
- // If the target element is no longer visible, hide the panel.
299
- if (!isVisible(targetElement)) {
300
- this.unpin();
301
- }
302
- };
303
- // Element is being resized to 0x0 after it's parent became hidden,
304
- // so we need to check size in order to determine if it's visible or not.
305
- this._resizeObserver = new ResizeObserver(targetElement, checkVisibility);
296
+ if (!this._resizeObserver) {
297
+ // If the target element is a text node, we need to check the parent element.
298
+ // It's because `ResizeObserver` accept only elements, not text nodes.
299
+ if (targetElement && isText(targetElement)) {
300
+ targetElement = targetElement.parentElement;
301
+ }
302
+ if (targetElement) {
303
+ const checkVisibility = () => {
304
+ // If the target element is no longer visible, hide the panel.
305
+ if (!isVisible(targetElement)) {
306
+ this.unpin();
307
+ }
308
+ };
309
+ // Element is being resized to 0x0 after it's parent became hidden,
310
+ // so we need to check size in order to determine if it's visible or not.
311
+ this._resizeObserver = new ResizeObserver(targetElement, checkVisibility);
312
+ }
306
313
  }
307
314
  return true;
308
315
  }
@@ -10,7 +10,7 @@ import View from '../../view.js';
10
10
  import ButtonView from '../../button/buttonview.js';
11
11
  import type ViewCollection from '../../viewcollection.js';
12
12
  import { Plugin, type Editor } from '@ckeditor/ckeditor5-core';
13
- import { FocusTracker, type Locale, type PositionOptions } from '@ckeditor/ckeditor5-utils';
13
+ import { FocusTracker, type Locale, type PositionOptions, type DecoratedMethodEvent } from '@ckeditor/ckeditor5-utils';
14
14
  import '../../../theme/components/panel/balloonrotator.css';
15
15
  import '../../../theme/components/panel/fakepanel.css';
16
16
  /**
@@ -152,6 +152,11 @@ export default class ContextualBalloon extends Plugin {
152
152
  * @param position Position options.
153
153
  */
154
154
  updatePosition(position?: Partial<PositionOptions>): void;
155
+ /**
156
+ * Returns position options of the last view in the stack.
157
+ * This keeps the balloon in the same position when the view is changed.
158
+ */
159
+ getPositionOptions(): Partial<PositionOptions> | undefined;
155
160
  /**
156
161
  * Shows the last view from the stack of a given ID.
157
162
  */
@@ -194,12 +199,13 @@ export default class ContextualBalloon extends Plugin {
194
199
  * @param data.withArrow Whether the {@link #view balloon} should be rendered with an arrow.
195
200
  */
196
201
  private _showView;
197
- /**
198
- * Returns position options of the last view in the stack.
199
- * This keeps the balloon in the same position when the view is changed.
200
- */
201
- private _getBalloonPosition;
202
202
  }
203
+ /**
204
+ * An event fired when the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon} is about to get the position of the balloon.
205
+ *
206
+ * @eventName ~ContextualBalloon#getPositionOptions
207
+ */
208
+ export type ContextualBalloonGetPositionOptionsEvent = DecoratedMethodEvent<ContextualBalloon, 'getPositionOptions'>;
203
209
  /**
204
210
  * The configuration of the view.
205
211
  */