@ckeditor/ckeditor5-ui 41.3.0-alpha.3 → 41.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.
Files changed (324) hide show
  1. package/ckeditor5-metadata.json +1 -1
  2. package/lang/contexts.json +12 -2
  3. package/lang/translations/af.po +210 -0
  4. package/lang/translations/ar.po +44 -4
  5. package/lang/translations/ast.po +44 -4
  6. package/lang/translations/az.po +44 -4
  7. package/lang/translations/bg.po +44 -4
  8. package/lang/translations/bn.po +44 -4
  9. package/lang/translations/bs.po +210 -0
  10. package/lang/translations/ca.po +44 -4
  11. package/lang/translations/cs.po +44 -4
  12. package/lang/translations/da.po +44 -4
  13. package/lang/translations/de-ch.po +44 -4
  14. package/lang/translations/de.po +44 -4
  15. package/lang/translations/el.po +44 -4
  16. package/lang/translations/en-au.po +44 -4
  17. package/lang/translations/en-gb.po +44 -4
  18. package/lang/translations/en.po +44 -4
  19. package/lang/translations/eo.po +44 -4
  20. package/lang/translations/es-co.po +210 -0
  21. package/lang/translations/es.po +44 -4
  22. package/lang/translations/et.po +44 -4
  23. package/lang/translations/eu.po +44 -4
  24. package/lang/translations/fa.po +44 -4
  25. package/lang/translations/fi.po +44 -4
  26. package/lang/translations/fr.po +44 -4
  27. package/lang/translations/gl.po +44 -4
  28. package/lang/translations/he.po +44 -4
  29. package/lang/translations/hi.po +44 -4
  30. package/lang/translations/hr.po +44 -4
  31. package/lang/translations/hu.po +44 -4
  32. package/lang/translations/id.po +44 -4
  33. package/lang/translations/it.po +44 -4
  34. package/lang/translations/ja.po +44 -4
  35. package/lang/translations/jv.po +210 -0
  36. package/lang/translations/km.po +44 -4
  37. package/lang/translations/kn.po +44 -4
  38. package/lang/translations/ko.po +44 -4
  39. package/lang/translations/ku.po +44 -4
  40. package/lang/translations/lt.po +44 -4
  41. package/lang/translations/lv.po +44 -4
  42. package/lang/translations/ms.po +44 -4
  43. package/lang/translations/nb.po +44 -4
  44. package/lang/translations/ne.po +44 -4
  45. package/lang/translations/nl.po +44 -4
  46. package/lang/translations/no.po +44 -4
  47. package/lang/translations/pl.po +44 -4
  48. package/lang/translations/pt-br.po +44 -4
  49. package/lang/translations/pt.po +44 -4
  50. package/lang/translations/ro.po +44 -4
  51. package/lang/translations/ru.po +44 -4
  52. package/lang/translations/sk.po +44 -4
  53. package/lang/translations/sl.po +44 -4
  54. package/lang/translations/sq.po +44 -4
  55. package/lang/translations/sr-latn.po +44 -4
  56. package/lang/translations/sr.po +44 -4
  57. package/lang/translations/sv.po +44 -4
  58. package/lang/translations/th.po +44 -4
  59. package/lang/translations/tk.po +44 -4
  60. package/lang/translations/tr.po +44 -4
  61. package/lang/translations/tt.po +44 -4
  62. package/lang/translations/ug.po +44 -4
  63. package/lang/translations/uk.po +44 -4
  64. package/lang/translations/ur.po +44 -4
  65. package/lang/translations/uz.po +44 -4
  66. package/lang/translations/vi.po +44 -4
  67. package/lang/translations/zh-cn.po +44 -4
  68. package/lang/translations/zh.po +44 -4
  69. package/package.json +3 -4
  70. package/src/button/button.d.ts +6 -0
  71. package/src/button/buttonview.d.ts +4 -0
  72. package/src/button/buttonview.js +1 -0
  73. package/src/button/filedialogbuttonview.d.ts +80 -0
  74. package/src/button/filedialogbuttonview.js +103 -0
  75. package/src/dropdown/utils.js +1 -5
  76. package/src/editorui/accessibilityhelp/accessibilityhelp.d.ts +4 -0
  77. package/src/editorui/accessibilityhelp/accessibilityhelp.js +25 -9
  78. package/src/index.d.ts +12 -3
  79. package/src/index.js +10 -2
  80. package/src/menubar/menubarmenubuttonview.d.ts +35 -0
  81. package/src/menubar/menubarmenubuttonview.js +64 -0
  82. package/src/menubar/menubarmenulistitembuttonview.d.ts +21 -0
  83. package/src/menubar/menubarmenulistitembuttonview.js +30 -0
  84. package/src/menubar/menubarmenulistitemfiledialogbuttonview.d.ts +23 -0
  85. package/src/menubar/menubarmenulistitemfiledialogbuttonview.js +32 -0
  86. package/src/menubar/menubarmenulistitemview.d.ts +25 -0
  87. package/src/menubar/menubarmenulistitemview.js +34 -0
  88. package/src/menubar/menubarmenulistview.d.ts +24 -0
  89. package/src/menubar/menubarmenulistview.js +23 -0
  90. package/src/menubar/menubarmenupanelview.d.ts +53 -0
  91. package/src/menubar/menubarmenupanelview.js +60 -0
  92. package/src/menubar/menubarmenuview.d.ts +109 -0
  93. package/src/menubar/menubarmenuview.js +159 -0
  94. package/src/menubar/menubarview.d.ts +164 -0
  95. package/src/menubar/menubarview.js +254 -0
  96. package/src/menubar/utils.d.ts +432 -0
  97. package/src/menubar/utils.js +1320 -0
  98. package/src/toolbar/toolbarview.js +5 -3
  99. package/src/tooltipmanager.d.ts +11 -0
  100. package/src/tooltipmanager.js +37 -6
  101. package/theme/components/menubar/menubar.css +10 -0
  102. package/theme/components/menubar/menubarmenu.css +9 -0
  103. package/theme/components/menubar/menubarmenubutton.css +11 -0
  104. package/theme/components/menubar/menubarmenulistitem.css +10 -0
  105. package/theme/components/menubar/menubarmenulistitembutton.css +10 -0
  106. package/theme/components/menubar/menubarmenupanel.css +62 -0
  107. package/theme/components/tooltip/tooltip.css +0 -3
  108. package/theme/globals/_reset.css +13 -0
  109. package/theme/globals/globals.css +1 -0
  110. package/dist/content-index.css +0 -4
  111. package/dist/editor-index.css +0 -445
  112. package/dist/index.css +0 -844
  113. package/dist/index.css.map +0 -1
  114. package/dist/translations/ar.d.ts +0 -8
  115. package/dist/translations/ar.js +0 -5
  116. package/dist/translations/ast.d.ts +0 -8
  117. package/dist/translations/ast.js +0 -5
  118. package/dist/translations/az.d.ts +0 -8
  119. package/dist/translations/az.js +0 -5
  120. package/dist/translations/bg.d.ts +0 -8
  121. package/dist/translations/bg.js +0 -5
  122. package/dist/translations/bn.d.ts +0 -8
  123. package/dist/translations/bn.js +0 -5
  124. package/dist/translations/ca.d.ts +0 -8
  125. package/dist/translations/ca.js +0 -5
  126. package/dist/translations/cs.d.ts +0 -8
  127. package/dist/translations/cs.js +0 -5
  128. package/dist/translations/da.d.ts +0 -8
  129. package/dist/translations/da.js +0 -5
  130. package/dist/translations/de-ch.d.ts +0 -8
  131. package/dist/translations/de-ch.js +0 -5
  132. package/dist/translations/de.d.ts +0 -8
  133. package/dist/translations/de.js +0 -5
  134. package/dist/translations/el.d.ts +0 -8
  135. package/dist/translations/el.js +0 -5
  136. package/dist/translations/en-au.d.ts +0 -8
  137. package/dist/translations/en-au.js +0 -5
  138. package/dist/translations/en-gb.d.ts +0 -8
  139. package/dist/translations/en-gb.js +0 -5
  140. package/dist/translations/en.d.ts +0 -8
  141. package/dist/translations/en.js +0 -5
  142. package/dist/translations/eo.d.ts +0 -8
  143. package/dist/translations/eo.js +0 -5
  144. package/dist/translations/es.d.ts +0 -8
  145. package/dist/translations/es.js +0 -5
  146. package/dist/translations/et.d.ts +0 -8
  147. package/dist/translations/et.js +0 -5
  148. package/dist/translations/eu.d.ts +0 -8
  149. package/dist/translations/eu.js +0 -5
  150. package/dist/translations/fa.d.ts +0 -8
  151. package/dist/translations/fa.js +0 -5
  152. package/dist/translations/fi.d.ts +0 -8
  153. package/dist/translations/fi.js +0 -5
  154. package/dist/translations/fr.d.ts +0 -8
  155. package/dist/translations/fr.js +0 -5
  156. package/dist/translations/gl.d.ts +0 -8
  157. package/dist/translations/gl.js +0 -5
  158. package/dist/translations/he.d.ts +0 -8
  159. package/dist/translations/he.js +0 -5
  160. package/dist/translations/hi.d.ts +0 -8
  161. package/dist/translations/hi.js +0 -5
  162. package/dist/translations/hr.d.ts +0 -8
  163. package/dist/translations/hr.js +0 -5
  164. package/dist/translations/hu.d.ts +0 -8
  165. package/dist/translations/hu.js +0 -5
  166. package/dist/translations/id.d.ts +0 -8
  167. package/dist/translations/id.js +0 -5
  168. package/dist/translations/it.d.ts +0 -8
  169. package/dist/translations/it.js +0 -5
  170. package/dist/translations/ja.d.ts +0 -8
  171. package/dist/translations/ja.js +0 -5
  172. package/dist/translations/km.d.ts +0 -8
  173. package/dist/translations/km.js +0 -5
  174. package/dist/translations/kn.d.ts +0 -8
  175. package/dist/translations/kn.js +0 -5
  176. package/dist/translations/ko.d.ts +0 -8
  177. package/dist/translations/ko.js +0 -5
  178. package/dist/translations/ku.d.ts +0 -8
  179. package/dist/translations/ku.js +0 -5
  180. package/dist/translations/lt.d.ts +0 -8
  181. package/dist/translations/lt.js +0 -5
  182. package/dist/translations/lv.d.ts +0 -8
  183. package/dist/translations/lv.js +0 -5
  184. package/dist/translations/ms.d.ts +0 -8
  185. package/dist/translations/ms.js +0 -5
  186. package/dist/translations/nb.d.ts +0 -8
  187. package/dist/translations/nb.js +0 -5
  188. package/dist/translations/ne.d.ts +0 -8
  189. package/dist/translations/ne.js +0 -5
  190. package/dist/translations/nl.d.ts +0 -8
  191. package/dist/translations/nl.js +0 -5
  192. package/dist/translations/no.d.ts +0 -8
  193. package/dist/translations/no.js +0 -5
  194. package/dist/translations/pl.d.ts +0 -8
  195. package/dist/translations/pl.js +0 -5
  196. package/dist/translations/pt-br.d.ts +0 -8
  197. package/dist/translations/pt-br.js +0 -5
  198. package/dist/translations/pt.d.ts +0 -8
  199. package/dist/translations/pt.js +0 -5
  200. package/dist/translations/ro.d.ts +0 -8
  201. package/dist/translations/ro.js +0 -5
  202. package/dist/translations/ru.d.ts +0 -8
  203. package/dist/translations/ru.js +0 -5
  204. package/dist/translations/sk.d.ts +0 -8
  205. package/dist/translations/sk.js +0 -5
  206. package/dist/translations/sl.d.ts +0 -8
  207. package/dist/translations/sl.js +0 -5
  208. package/dist/translations/sq.d.ts +0 -8
  209. package/dist/translations/sq.js +0 -5
  210. package/dist/translations/sr-latn.d.ts +0 -8
  211. package/dist/translations/sr-latn.js +0 -5
  212. package/dist/translations/sr.d.ts +0 -8
  213. package/dist/translations/sr.js +0 -5
  214. package/dist/translations/sv.d.ts +0 -8
  215. package/dist/translations/sv.js +0 -5
  216. package/dist/translations/th.d.ts +0 -8
  217. package/dist/translations/th.js +0 -5
  218. package/dist/translations/tk.d.ts +0 -8
  219. package/dist/translations/tk.js +0 -5
  220. package/dist/translations/tr.d.ts +0 -8
  221. package/dist/translations/tr.js +0 -5
  222. package/dist/translations/tt.d.ts +0 -8
  223. package/dist/translations/tt.js +0 -5
  224. package/dist/translations/ug.d.ts +0 -8
  225. package/dist/translations/ug.js +0 -5
  226. package/dist/translations/uk.d.ts +0 -8
  227. package/dist/translations/uk.js +0 -5
  228. package/dist/translations/ur.d.ts +0 -8
  229. package/dist/translations/ur.js +0 -5
  230. package/dist/translations/uz.d.ts +0 -8
  231. package/dist/translations/uz.js +0 -5
  232. package/dist/translations/vi.d.ts +0 -8
  233. package/dist/translations/vi.js +0 -5
  234. package/dist/translations/zh-cn.d.ts +0 -8
  235. package/dist/translations/zh-cn.js +0 -5
  236. package/dist/translations/zh.d.ts +0 -8
  237. package/dist/translations/zh.js +0 -5
  238. package/dist/types/arialiveannouncer.d.ts +0 -102
  239. package/dist/types/augmentation.d.ts +0 -92
  240. package/dist/types/autocomplete/autocompleteview.d.ts +0 -85
  241. package/dist/types/bindings/addkeyboardhandlingforgrid.d.ts +0 -31
  242. package/dist/types/bindings/clickoutsidehandler.d.ts +0 -32
  243. package/dist/types/bindings/csstransitiondisablermixin.d.ts +0 -44
  244. package/dist/types/bindings/draggableviewmixin.d.ts +0 -50
  245. package/dist/types/bindings/injectcsstransitiondisabler.d.ts +0 -63
  246. package/dist/types/bindings/preventdefault.d.ts +0 -37
  247. package/dist/types/bindings/submithandler.d.ts +0 -61
  248. package/dist/types/button/button.d.ts +0 -179
  249. package/dist/types/button/buttonlabel.d.ts +0 -38
  250. package/dist/types/button/buttonlabelview.d.ts +0 -35
  251. package/dist/types/button/buttonview.d.ts +0 -185
  252. package/dist/types/button/switchbuttonview.d.ts +0 -49
  253. package/dist/types/collapsible/collapsibleview.d.ts +0 -74
  254. package/dist/types/colorgrid/colorgridview.d.ts +0 -136
  255. package/dist/types/colorgrid/colortileview.d.ts +0 -32
  256. package/dist/types/colorgrid/utils.d.ts +0 -51
  257. package/dist/types/colorpicker/colorpickerview.d.ts +0 -146
  258. package/dist/types/colorpicker/utils.d.ts +0 -52
  259. package/dist/types/colorselector/colorgridsfragmentview.d.ts +0 -199
  260. package/dist/types/colorselector/colorpickerfragmentview.d.ts +0 -133
  261. package/dist/types/colorselector/colorselectorview.d.ts +0 -246
  262. package/dist/types/colorselector/documentcolorcollection.d.ts +0 -74
  263. package/dist/types/componentfactory.d.ts +0 -85
  264. package/dist/types/dialog/dialog.d.ts +0 -277
  265. package/dist/types/dialog/dialogactionsview.d.ts +0 -73
  266. package/dist/types/dialog/dialogcontentview.d.ts +0 -31
  267. package/dist/types/dialog/dialogview.d.ts +0 -260
  268. package/dist/types/dropdown/button/dropdownbutton.d.ts +0 -29
  269. package/dist/types/dropdown/button/dropdownbuttonview.d.ts +0 -52
  270. package/dist/types/dropdown/button/splitbuttonview.d.ts +0 -166
  271. package/dist/types/dropdown/dropdownpanelfocusable.d.ts +0 -25
  272. package/dist/types/dropdown/dropdownpanelview.d.ts +0 -66
  273. package/dist/types/dropdown/dropdownview.d.ts +0 -319
  274. package/dist/types/dropdown/utils.d.ts +0 -239
  275. package/dist/types/editableui/editableuiview.d.ts +0 -76
  276. package/dist/types/editableui/inline/inlineeditableuiview.d.ts +0 -44
  277. package/dist/types/editorui/accessibilityhelp/accessibilityhelp.d.ts +0 -51
  278. package/dist/types/editorui/accessibilityhelp/accessibilityhelpcontentview.d.ts +0 -39
  279. package/dist/types/editorui/bodycollection.d.ts +0 -59
  280. package/dist/types/editorui/boxed/boxededitoruiview.d.ts +0 -44
  281. package/dist/types/editorui/editorui.d.ts +0 -292
  282. package/dist/types/editorui/editoruiview.d.ts +0 -43
  283. package/dist/types/editorui/poweredby.d.ts +0 -75
  284. package/dist/types/focuscycler.d.ts +0 -249
  285. package/dist/types/formheader/formheaderview.d.ts +0 -63
  286. package/dist/types/highlightedtext/highlightedtextview.d.ts +0 -42
  287. package/dist/types/icon/iconview.d.ts +0 -92
  288. package/dist/types/iframe/iframeview.d.ts +0 -54
  289. package/dist/types/index.d.ts +0 -78
  290. package/dist/types/input/inputbase.d.ts +0 -123
  291. package/dist/types/input/inputview.d.ts +0 -40
  292. package/dist/types/inputnumber/inputnumberview.d.ts +0 -53
  293. package/dist/types/inputtext/inputtextview.d.ts +0 -22
  294. package/dist/types/label/labelview.d.ts +0 -40
  295. package/dist/types/labeledfield/labeledfieldview.d.ts +0 -191
  296. package/dist/types/labeledfield/utils.d.ts +0 -127
  297. package/dist/types/labeledinput/labeledinputview.d.ts +0 -129
  298. package/dist/types/list/listitemgroupview.d.ts +0 -63
  299. package/dist/types/list/listitemview.d.ts +0 -40
  300. package/dist/types/list/listseparatorview.d.ts +0 -22
  301. package/dist/types/list/listview.d.ts +0 -128
  302. package/dist/types/model.d.ts +0 -26
  303. package/dist/types/notification/notification.d.ts +0 -215
  304. package/dist/types/panel/balloon/balloonpanelview.d.ts +0 -689
  305. package/dist/types/panel/balloon/contextualballoon.d.ts +0 -303
  306. package/dist/types/panel/sticky/stickypanelview.d.ts +0 -160
  307. package/dist/types/search/filteredview.d.ts +0 -35
  308. package/dist/types/search/searchinfoview.d.ts +0 -49
  309. package/dist/types/search/searchresultsview.d.ts +0 -58
  310. package/dist/types/search/text/searchtextqueryview.d.ts +0 -80
  311. package/dist/types/search/text/searchtextview.d.ts +0 -223
  312. package/dist/types/spinner/spinnerview.d.ts +0 -29
  313. package/dist/types/template.d.ts +0 -946
  314. package/dist/types/textarea/textareaview.d.ts +0 -108
  315. package/dist/types/toolbar/balloon/balloontoolbar.d.ts +0 -121
  316. package/dist/types/toolbar/block/blockbuttonview.d.ts +0 -39
  317. package/dist/types/toolbar/block/blocktoolbar.d.ts +0 -157
  318. package/dist/types/toolbar/normalizetoolbarconfig.d.ts +0 -44
  319. package/dist/types/toolbar/toolbarlinebreakview.d.ts +0 -22
  320. package/dist/types/toolbar/toolbarseparatorview.d.ts +0 -22
  321. package/dist/types/toolbar/toolbarview.d.ts +0 -271
  322. package/dist/types/tooltipmanager.d.ts +0 -188
  323. package/dist/types/view.d.ts +0 -426
  324. package/dist/types/viewcollection.d.ts +0 -143
@@ -0,0 +1,1320 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, 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
+ * @module ui/menubar/utils
7
+ */
8
+ import clickOutsideHandler from '../bindings/clickoutsidehandler.js';
9
+ import MenuBarMenuListItemView from './menubarmenulistitemview.js';
10
+ import { cloneDeep } from 'lodash-es';
11
+ import { logWarning } from '@ckeditor/ckeditor5-utils';
12
+ const NESTED_PANEL_HORIZONTAL_OFFSET = 5;
13
+ /**
14
+ * Behaviors of the {@link module:ui/menubar/menubarview~MenuBarView} component.
15
+ */
16
+ export const MenuBarBehaviors = {
17
+ /**
18
+ * When the bar is already open:
19
+ * * Opens the menu when the user hovers over its button.
20
+ * * Closes open menu when another menu's button gets hovered.
21
+ */
22
+ toggleMenusAndFocusItemsOnHover(menuBarView) {
23
+ menuBarView.on('menu:mouseenter', evt => {
24
+ // This works only when the menu bar has already been open and the user hover over the menu bar.
25
+ if (!menuBarView.isOpen) {
26
+ return;
27
+ }
28
+ for (const menuView of menuBarView.menus) {
29
+ // @if CK_DEBUG_MENU_BAR // const wasOpen = menuView.isOpen;
30
+ const pathLeaf = evt.path[0];
31
+ const isListItemContainingMenu = pathLeaf instanceof MenuBarMenuListItemView && pathLeaf.children.first === menuView;
32
+ menuView.isOpen = (evt.path.includes(menuView) || isListItemContainingMenu) && menuView.isEnabled;
33
+ // @if CK_DEBUG_MENU_BAR // if ( wasOpen !== menuView.isOpen ) {
34
+ // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] toggleMenusAndFocusItemsOnHover(): Toggle',
35
+ // @if CK_DEBUG_MENU_BAR // logMenu( menuView ), 'isOpen', menuView.isOpen
36
+ // @if CK_DEBUG_MENU_BAR // );
37
+ // @if CK_DEBUG_MENU_BAR // }
38
+ }
39
+ evt.source.focus();
40
+ });
41
+ },
42
+ /**
43
+ * Moves between top-level menus using the arrow left and right keys.
44
+ *
45
+ * If the menubar has already been open, the arrow keys move focus between top-level menu buttons and open them.
46
+ * If the menubar is closed, the arrow keys only move focus between top-level menu buttons.
47
+ */
48
+ focusCycleMenusOnArrows(menuBarView) {
49
+ const isContentRTL = menuBarView.locale.uiLanguageDirection === 'rtl';
50
+ menuBarView.on('menu:arrowright', evt => {
51
+ cycleTopLevelMenus(evt.source, isContentRTL ? -1 : 1);
52
+ });
53
+ menuBarView.on('menu:arrowleft', evt => {
54
+ cycleTopLevelMenus(evt.source, isContentRTL ? 1 : -1);
55
+ });
56
+ function cycleTopLevelMenus(currentMenuView, step) {
57
+ const currentIndex = menuBarView.children.getIndex(currentMenuView);
58
+ const isCurrentMenuViewOpen = currentMenuView.isOpen;
59
+ const menusCount = menuBarView.children.length;
60
+ const menuViewToOpen = menuBarView.children.get((currentIndex + menusCount + step) % menusCount);
61
+ currentMenuView.isOpen = false;
62
+ if (isCurrentMenuViewOpen) {
63
+ menuViewToOpen.isOpen = true;
64
+ }
65
+ menuViewToOpen.buttonView.focus();
66
+ }
67
+ },
68
+ /**
69
+ * Closes the entire sub-menu structure when the bar is closed. This prevents sub-menus from being open if the user
70
+ * closes the entire bar, and then re-opens some top-level menu.
71
+ */
72
+ closeMenusWhenTheBarCloses(menuBarView) {
73
+ menuBarView.on('change:isOpen', () => {
74
+ if (!menuBarView.isOpen) {
75
+ menuBarView.menus.forEach(menuView => {
76
+ menuView.isOpen = false;
77
+ // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeMenusWhenTheBarCloses(): Closing', logMenu( menuView ) );
78
+ });
79
+ }
80
+ });
81
+ },
82
+ /**
83
+ * Handles the following case:
84
+ * 1. Hover to open a sub-menu (A). The button has focus.
85
+ * 2. Press arrow up/down to move focus to another sub-menu (B) button.
86
+ * 3. Press arrow right to open the sub-menu (B).
87
+ * 4. The sub-menu (A) should close as it would with `toggleMenusAndFocusItemsOnHover()`.
88
+ */
89
+ closeMenuWhenAnotherOnTheSameLevelOpens(menuBarView) {
90
+ menuBarView.on('menu:change:isOpen', (evt, name, isOpen) => {
91
+ if (isOpen) {
92
+ menuBarView.menus
93
+ .filter(menuView => {
94
+ return evt.source.parentMenuView === menuView.parentMenuView &&
95
+ evt.source !== menuView &&
96
+ menuView.isOpen;
97
+ }).forEach(menuView => {
98
+ menuView.isOpen = false;
99
+ // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeMenuWhenAnotherOpens(): Closing', logMenu( menuView ) );
100
+ });
101
+ }
102
+ });
103
+ },
104
+ /**
105
+ * Closes the bar when the user clicked outside of it (page body, editor root, etc.).
106
+ */
107
+ closeOnClickOutside(menuBarView) {
108
+ clickOutsideHandler({
109
+ emitter: menuBarView,
110
+ activator: () => menuBarView.isOpen,
111
+ callback: () => menuBarView.close(),
112
+ contextElements: () => menuBarView.children.map(child => child.element)
113
+ });
114
+ }
115
+ };
116
+ /**
117
+ * Behaviors of the {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} component.
118
+ */
119
+ export const MenuBarMenuBehaviors = {
120
+ /**
121
+ * If the button of the menu is focused, pressing the arrow down key should open the panel and focus it.
122
+ * This is analogous to the {@link module:ui/dropdown/dropdownview~DropdownView}.
123
+ */
124
+ openAndFocusPanelOnArrowDownKey(menuView) {
125
+ menuView.keystrokes.set('arrowdown', (data, cancel) => {
126
+ if (menuView.focusTracker.focusedElement === menuView.buttonView.element) {
127
+ if (!menuView.isOpen) {
128
+ menuView.isOpen = true;
129
+ }
130
+ menuView.panelView.focus();
131
+ cancel();
132
+ }
133
+ });
134
+ },
135
+ /**
136
+ * Open the menu on the right arrow key press. This allows for navigating to sub-menus using the keyboard.
137
+ */
138
+ openOnArrowRightKey(menuView) {
139
+ const keystroke = menuView.locale.uiLanguageDirection === 'rtl' ? 'arrowleft' : 'arrowright';
140
+ menuView.keystrokes.set(keystroke, (data, cancel) => {
141
+ if (menuView.focusTracker.focusedElement !== menuView.buttonView.element || !menuView.isEnabled) {
142
+ return;
143
+ }
144
+ // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] openOnArrowRightKey(): Opening', logMenu( menuView ) );
145
+ if (!menuView.isOpen) {
146
+ menuView.isOpen = true;
147
+ }
148
+ menuView.panelView.focus();
149
+ cancel();
150
+ });
151
+ },
152
+ /**
153
+ * Opens the menu on its button click. Note that this behavior only opens but never closes the menu (unlike
154
+ * {@link module:ui/dropdown/dropdownview~DropdownView}).
155
+ */
156
+ openOnButtonClick(menuView) {
157
+ menuView.buttonView.on('execute', () => {
158
+ menuView.isOpen = true;
159
+ menuView.panelView.focus();
160
+ });
161
+ },
162
+ /**
163
+ * Toggles the menu on its button click. This behavior is analogous to {@link module:ui/dropdown/dropdownview~DropdownView}.
164
+ */
165
+ toggleOnButtonClick(menuView) {
166
+ menuView.buttonView.on('execute', () => {
167
+ menuView.isOpen = !menuView.isOpen;
168
+ if (menuView.isOpen) {
169
+ menuView.panelView.focus();
170
+ }
171
+ });
172
+ },
173
+ /**
174
+ * Closes the menu on the right left key press. This allows for navigating to sub-menus using the keyboard.
175
+ */
176
+ closeOnArrowLeftKey(menuView) {
177
+ const keystroke = menuView.locale.uiLanguageDirection === 'rtl' ? 'arrowright' : 'arrowleft';
178
+ menuView.keystrokes.set(keystroke, (data, cancel) => {
179
+ if (menuView.isOpen) {
180
+ menuView.isOpen = false;
181
+ menuView.focus();
182
+ cancel();
183
+ }
184
+ });
185
+ },
186
+ /**
187
+ * Closes the menu on the esc key press. This allows for navigating to sub-menus using the keyboard.
188
+ */
189
+ closeOnEscKey(menuView) {
190
+ menuView.keystrokes.set('esc', (data, cancel) => {
191
+ if (menuView.isOpen) {
192
+ menuView.isOpen = false;
193
+ menuView.focus();
194
+ cancel();
195
+ }
196
+ });
197
+ },
198
+ /**
199
+ * Closes the menu when its parent menu also closed. This prevents from orphaned open menus when the parent menu re-opens.
200
+ */
201
+ closeOnParentClose(menuView) {
202
+ menuView.parentMenuView.on('change:isOpen', (evt, name, isOpen) => {
203
+ if (!isOpen && evt.source === menuView.parentMenuView) {
204
+ // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeOnParentClose(): Closing', logMenu( menuView ) );
205
+ menuView.isOpen = false;
206
+ }
207
+ });
208
+ }
209
+ };
210
+ // @if CK_DEBUG_MENU_BAR // function logMenu( menuView: MenuBarMenuView ) {
211
+ // @if CK_DEBUG_MENU_BAR // return `"${ menuView.buttonView.label }"`;
212
+ // @if CK_DEBUG_MENU_BAR // }
213
+ /**
214
+ * Contains every positioning function used by {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} that decides where the
215
+ * {@link module:ui/menubar/menubarmenuview~MenuBarMenuView#panelView} should be placed.
216
+ *
217
+ * Top-level menu positioning functions:
218
+ *
219
+ * ┌──────┐
220
+ * │ │
221
+ * ├──────┴────────┐
222
+ * │ │
223
+ * │ │
224
+ * │ │
225
+ * │ SE │
226
+ * └───────────────┘
227
+ *
228
+ * ┌──────┐
229
+ * │ │
230
+ * ┌────────┴──────┤
231
+ * │ │
232
+ * │ │
233
+ * │ │
234
+ * │ SW │
235
+ * └───────────────┘
236
+ *
237
+ * ┌───────────────┐
238
+ * │ NW │
239
+ * │ │
240
+ * │ │
241
+ * │ │
242
+ * └────────┬──────┤
243
+ * │ │
244
+ * └──────┘
245
+ *
246
+ * ┌───────────────┐
247
+ * │ NE │
248
+ * │ │
249
+ * │ │
250
+ * │ │
251
+ * ├──────┬────────┘
252
+ * │ │
253
+ * └──────┘
254
+ *
255
+ * Sub-menu positioning functions:
256
+ *
257
+ * ┌──────┬───────────────┐
258
+ * │ │ │
259
+ * └──────┤ │
260
+ * │ │
261
+ * │ ES │
262
+ * └───────────────┘
263
+ *
264
+ * ┌───────────────┬──────┐
265
+ * │ │ │
266
+ * │ ├──────┘
267
+ * │ │
268
+ * │ WS │
269
+ * └───────────────┘
270
+ *
271
+ * ┌───────────────┐
272
+ * │ EN │
273
+ * │ │
274
+ * ┌──────┤ │
275
+ * │ │ │
276
+ * └──────┴───────────────┘
277
+ *
278
+ * ┌───────────────┐
279
+ * │ WN │
280
+ * │ │
281
+ * │ ├──────┐
282
+ * │ │ │
283
+ * └───────────────┴──────┘
284
+ */
285
+ export const MenuBarMenuViewPanelPositioningFunctions = {
286
+ southEast: buttonRect => {
287
+ return {
288
+ top: buttonRect.bottom,
289
+ left: buttonRect.left,
290
+ name: 'se'
291
+ };
292
+ },
293
+ southWest: (buttonRect, panelRect) => {
294
+ return {
295
+ top: buttonRect.bottom,
296
+ left: buttonRect.left - panelRect.width + buttonRect.width,
297
+ name: 'sw'
298
+ };
299
+ },
300
+ northEast: (buttonRect, panelRect) => {
301
+ return {
302
+ top: buttonRect.top - panelRect.height,
303
+ left: buttonRect.left,
304
+ name: 'ne'
305
+ };
306
+ },
307
+ northWest: (buttonRect, panelRect) => {
308
+ return {
309
+ top: buttonRect.top - panelRect.height,
310
+ left: buttonRect.left - panelRect.width + buttonRect.width,
311
+ name: 'nw'
312
+ };
313
+ },
314
+ eastSouth: buttonRect => {
315
+ return {
316
+ top: buttonRect.top,
317
+ left: buttonRect.right - NESTED_PANEL_HORIZONTAL_OFFSET,
318
+ name: 'es'
319
+ };
320
+ },
321
+ eastNorth: (buttonRect, panelRect) => {
322
+ return {
323
+ top: buttonRect.top - panelRect.height,
324
+ left: buttonRect.right - NESTED_PANEL_HORIZONTAL_OFFSET,
325
+ name: 'en'
326
+ };
327
+ },
328
+ westSouth: (buttonRect, panelRect) => {
329
+ return {
330
+ top: buttonRect.top,
331
+ left: buttonRect.left - panelRect.width + NESTED_PANEL_HORIZONTAL_OFFSET,
332
+ name: 'ws'
333
+ };
334
+ },
335
+ westNorth: (buttonRect, panelRect) => {
336
+ return {
337
+ top: buttonRect.top - panelRect.height,
338
+ left: buttonRect.left - panelRect.width + NESTED_PANEL_HORIZONTAL_OFFSET,
339
+ name: 'wn'
340
+ };
341
+ }
342
+ };
343
+ /**
344
+ * The default items {@link module:core/editor/editorconfig~EditorConfig#menuBar configuration} of the
345
+ * {@link module:ui/menubar/menubarview~MenuBarView} component. It contains names of all menu bar components
346
+ * registered in the {@link module:ui/componentfactory~ComponentFactory component factory} (available in the project).
347
+ *
348
+ * **Note**: Menu bar component names provided by core editor features are prefixed with `menuBar:` in order to distinguish
349
+ * them from components referenced by the {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}, for instance,
350
+ * `'menuBar:bold'` is a menu bar button but `'bold'` is a toolbar button.
351
+ *
352
+ * Below is the preset menu bar structure (the default value of `config.menuBar.items` property):
353
+ *
354
+ * ```ts
355
+ * [
356
+ * {
357
+ * menuId: 'file',
358
+ * label: 'File',
359
+ * groups: [
360
+ * {
361
+ * groupId: 'export',
362
+ * items: [
363
+ * 'menuBar:exportPdf',
364
+ * 'menuBar:exportWord'
365
+ * ]
366
+ * },
367
+ * {
368
+ * groupId: 'import',
369
+ * items: [
370
+ * 'menuBar:importWord'
371
+ * ]
372
+ * },
373
+ * {
374
+ * groupId: 'revisionHistory',
375
+ * items: [
376
+ * 'menuBar:revisionHistory'
377
+ * ]
378
+ * }
379
+ * ]
380
+ * },
381
+ * {
382
+ * menuId: 'edit',
383
+ * label: 'Edit',
384
+ * groups: [
385
+ * {
386
+ * groupId: 'undo',
387
+ * items: [
388
+ * 'menuBar:undo',
389
+ * 'menuBar:redo'
390
+ * ]
391
+ * },
392
+ * {
393
+ * groupId: 'selectAll',
394
+ * items: [
395
+ * 'menuBar:selectAll'
396
+ * ]
397
+ * },
398
+ * {
399
+ * groupId: 'findAndReplace',
400
+ * items: [
401
+ * 'menuBar:findAndReplace'
402
+ * ]
403
+ * }
404
+ * ]
405
+ * },
406
+ * {
407
+ * menuId: 'view',
408
+ * label: 'View',
409
+ * groups: [
410
+ * {
411
+ * groupId: 'sourceEditing',
412
+ * items: [
413
+ * 'menuBar:sourceEditing'
414
+ * ]
415
+ * },
416
+ * {
417
+ * groupId: 'showBlocks',
418
+ * items: [
419
+ * 'menuBar:showBlocks'
420
+ * ]
421
+ * },
422
+ * {
423
+ * groupId: 'restrictedEditingException',
424
+ * items: [
425
+ * 'menuBar:restrictedEditingException'
426
+ * ]
427
+ * }
428
+ * ]
429
+ * },
430
+ * {
431
+ * menuId: 'insert',
432
+ * label: 'Insert',
433
+ * groups: [
434
+ * {
435
+ * groupId: 'insertMainWidgets',
436
+ * items: [
437
+ * 'menuBar:uploadImage',
438
+ * 'menuBar:ckbox',
439
+ * 'menuBar:ckfinder',
440
+ * 'menuBar:insertTable'
441
+ * ]
442
+ * },
443
+ * {
444
+ * groupId: 'insertInline',
445
+ * items: [
446
+ * 'menuBar:link',
447
+ * 'menuBar:comment'
448
+ * ]
449
+ * },
450
+ * {
451
+ * groupId: 'insertMinorWidgets',
452
+ * items: [
453
+ * 'menuBar:insertTemplate',
454
+ * 'menuBar:blockQuote',
455
+ * 'menuBar:codeBlock',
456
+ * 'menuBar:htmlEmbed'
457
+ * ]
458
+ * },
459
+ * {
460
+ * groupId: 'insertStructureWidgets',
461
+ * items: [
462
+ * 'menuBar:horizontalLine',
463
+ * 'menuBar:pageBreak',
464
+ * 'menuBar:tableOfContents'
465
+ * ]
466
+ * },
467
+ * {
468
+ * groupId: 'restrictedEditing',
469
+ * items: [
470
+ * 'menuBar:restrictedEditing'
471
+ * ]
472
+ * }
473
+ * ]
474
+ * },
475
+ * {
476
+ * menuId: 'format',
477
+ * label: 'Format',
478
+ * groups: [
479
+ * {
480
+ * groupId: 'textAndFont',
481
+ * items: [
482
+ * {
483
+ * menuId: 'text',
484
+ * label: 'Text',
485
+ * groups: [
486
+ * {
487
+ * groupId: 'basicStyles',
488
+ * items: [
489
+ * 'menuBar:bold',
490
+ * 'menuBar:italic',
491
+ * 'menuBar:underline',
492
+ * 'menuBar:strikethrough',
493
+ * 'menuBar:superscript',
494
+ * 'menuBar:subscript',
495
+ * 'menuBar:code'
496
+ * ]
497
+ * },
498
+ * {
499
+ * groupId: 'textPartLanguage',
500
+ * items: [
501
+ * 'menuBar:textPartLanguage'
502
+ * ]
503
+ * }
504
+ * ]
505
+ * },
506
+ * {
507
+ * menuId: 'font',
508
+ * label: 'Font',
509
+ * groups: [
510
+ * {
511
+ * groupId: 'fontProperties',
512
+ * items: [
513
+ * 'menuBar:fontSize',
514
+ * 'menuBar:fontFamily'
515
+ * ]
516
+ * },
517
+ * {
518
+ * groupId: 'fontColors',
519
+ * items: [
520
+ * 'menuBar:fontColor',
521
+ * 'menuBar:fontBackgroundColor'
522
+ * ]
523
+ * },
524
+ * {
525
+ * groupId: 'highlight',
526
+ * items: [
527
+ * 'menuBar:highlight'
528
+ * ]
529
+ * }
530
+ * ]
531
+ * },
532
+ * 'menuBar:heading'
533
+ * ]
534
+ * },
535
+ * {
536
+ * groupId: 'list',
537
+ * items: [
538
+ * 'menuBar:bulletedList',
539
+ * 'menuBar:numberedList',
540
+ * 'menuBar:todoList'
541
+ * ]
542
+ * },
543
+ * {
544
+ * groupId: 'indent',
545
+ * items: [
546
+ * 'menuBar:alignment',
547
+ * 'menuBar:indent',
548
+ * 'menuBar:outdent'
549
+ * ]
550
+ * },
551
+ * {
552
+ * groupId: 'caseChange',
553
+ * items: [
554
+ * 'menuBar:caseChange'
555
+ * ]
556
+ * },
557
+ * {
558
+ * groupId: 'removeFormat',
559
+ * items: [
560
+ * 'menuBar:removeFormat'
561
+ * ]
562
+ * }
563
+ * ]
564
+ * },
565
+ * {
566
+ * menuId: 'tools',
567
+ * label: 'Tools',
568
+ * groups: [
569
+ * {
570
+ * groupId: 'aiTools',
571
+ * items: [
572
+ * 'menuBar:aiAssistant',
573
+ * 'menuBar:aiCommands'
574
+ * ]
575
+ * },
576
+ * {
577
+ * groupId: 'tools',
578
+ * items: [
579
+ * 'menuBar:trackChanges',
580
+ * 'menuBar:commentsArchive'
581
+ * ]
582
+ * }
583
+ * ]
584
+ * },
585
+ * {
586
+ * menuId: 'help',
587
+ * label: 'Help',
588
+ * groups: [
589
+ * {
590
+ * groupId: 'help',
591
+ * items: [
592
+ * 'menuBar:accessibilityHelp'
593
+ * ]
594
+ * }
595
+ * ]
596
+ * }
597
+ * ];
598
+ * ```
599
+ *
600
+ * The menu bar can be customized using the `config.menuBar.removeItems` and `config.menuBar.addItems` properties.
601
+ */
602
+ // **NOTE: Whenever you make changes to this value, reflect it in the documentation above!**
603
+ export const DefaultMenuBarItems = [
604
+ {
605
+ menuId: 'file',
606
+ label: 'File',
607
+ groups: [
608
+ {
609
+ groupId: 'export',
610
+ items: [
611
+ 'menuBar:exportPdf',
612
+ 'menuBar:exportWord'
613
+ ]
614
+ },
615
+ {
616
+ groupId: 'import',
617
+ items: [
618
+ 'menuBar:importWord'
619
+ ]
620
+ },
621
+ {
622
+ groupId: 'revisionHistory',
623
+ items: [
624
+ 'menuBar:revisionHistory'
625
+ ]
626
+ }
627
+ ]
628
+ },
629
+ {
630
+ menuId: 'edit',
631
+ label: 'Edit',
632
+ groups: [
633
+ {
634
+ groupId: 'undo',
635
+ items: [
636
+ 'menuBar:undo',
637
+ 'menuBar:redo'
638
+ ]
639
+ },
640
+ {
641
+ groupId: 'selectAll',
642
+ items: [
643
+ 'menuBar:selectAll'
644
+ ]
645
+ },
646
+ {
647
+ groupId: 'findAndReplace',
648
+ items: [
649
+ 'menuBar:findAndReplace'
650
+ ]
651
+ }
652
+ ]
653
+ },
654
+ {
655
+ menuId: 'view',
656
+ label: 'View',
657
+ groups: [
658
+ {
659
+ groupId: 'sourceEditing',
660
+ items: [
661
+ 'menuBar:sourceEditing'
662
+ ]
663
+ },
664
+ {
665
+ groupId: 'showBlocks',
666
+ items: [
667
+ 'menuBar:showBlocks'
668
+ ]
669
+ },
670
+ {
671
+ groupId: 'restrictedEditingException',
672
+ items: [
673
+ 'menuBar:restrictedEditingException'
674
+ ]
675
+ }
676
+ ]
677
+ },
678
+ {
679
+ menuId: 'insert',
680
+ label: 'Insert',
681
+ groups: [
682
+ {
683
+ groupId: 'insertMainWidgets',
684
+ items: [
685
+ 'menuBar:uploadImage',
686
+ 'menuBar:ckbox',
687
+ 'menuBar:ckfinder',
688
+ 'menuBar:insertTable'
689
+ ]
690
+ },
691
+ {
692
+ groupId: 'insertInline',
693
+ items: [
694
+ 'menuBar:link',
695
+ 'menuBar:comment'
696
+ ]
697
+ },
698
+ {
699
+ groupId: 'insertMinorWidgets',
700
+ items: [
701
+ 'menuBar:insertTemplate',
702
+ 'menuBar:blockQuote',
703
+ 'menuBar:codeBlock',
704
+ 'menuBar:htmlEmbed'
705
+ ]
706
+ },
707
+ {
708
+ groupId: 'insertStructureWidgets',
709
+ items: [
710
+ 'menuBar:horizontalLine',
711
+ 'menuBar:pageBreak',
712
+ 'menuBar:tableOfContents'
713
+ ]
714
+ },
715
+ {
716
+ groupId: 'restrictedEditing',
717
+ items: [
718
+ 'menuBar:restrictedEditing'
719
+ ]
720
+ }
721
+ ]
722
+ },
723
+ {
724
+ menuId: 'format',
725
+ label: 'Format',
726
+ groups: [
727
+ {
728
+ groupId: 'textAndFont',
729
+ items: [
730
+ {
731
+ menuId: 'text',
732
+ label: 'Text',
733
+ groups: [
734
+ {
735
+ groupId: 'basicStyles',
736
+ items: [
737
+ 'menuBar:bold',
738
+ 'menuBar:italic',
739
+ 'menuBar:underline',
740
+ 'menuBar:strikethrough',
741
+ 'menuBar:superscript',
742
+ 'menuBar:subscript',
743
+ 'menuBar:code'
744
+ ]
745
+ },
746
+ {
747
+ groupId: 'textPartLanguage',
748
+ items: [
749
+ 'menuBar:textPartLanguage'
750
+ ]
751
+ }
752
+ ]
753
+ },
754
+ {
755
+ menuId: 'font',
756
+ label: 'Font',
757
+ groups: [
758
+ {
759
+ groupId: 'fontProperties',
760
+ items: [
761
+ 'menuBar:fontSize',
762
+ 'menuBar:fontFamily'
763
+ ]
764
+ },
765
+ {
766
+ groupId: 'fontColors',
767
+ items: [
768
+ 'menuBar:fontColor',
769
+ 'menuBar:fontBackgroundColor'
770
+ ]
771
+ },
772
+ {
773
+ groupId: 'highlight',
774
+ items: [
775
+ 'menuBar:highlight'
776
+ ]
777
+ }
778
+ ]
779
+ },
780
+ 'menuBar:heading'
781
+ ]
782
+ },
783
+ {
784
+ groupId: 'list',
785
+ items: [
786
+ 'menuBar:bulletedList',
787
+ 'menuBar:numberedList',
788
+ 'menuBar:todoList'
789
+ ]
790
+ },
791
+ {
792
+ groupId: 'indent',
793
+ items: [
794
+ 'menuBar:alignment',
795
+ 'menuBar:indent',
796
+ 'menuBar:outdent'
797
+ ]
798
+ },
799
+ {
800
+ groupId: 'caseChange',
801
+ items: [
802
+ 'menuBar:caseChange'
803
+ ]
804
+ },
805
+ {
806
+ groupId: 'removeFormat',
807
+ items: [
808
+ 'menuBar:removeFormat'
809
+ ]
810
+ }
811
+ ]
812
+ },
813
+ {
814
+ menuId: 'tools',
815
+ label: 'Tools',
816
+ groups: [
817
+ {
818
+ groupId: 'aiTools',
819
+ items: [
820
+ 'menuBar:aiAssistant',
821
+ 'menuBar:aiCommands'
822
+ ]
823
+ },
824
+ {
825
+ groupId: 'tools',
826
+ items: [
827
+ 'menuBar:trackChanges',
828
+ 'menuBar:commentsArchive'
829
+ ]
830
+ }
831
+ ]
832
+ },
833
+ {
834
+ menuId: 'help',
835
+ label: 'Help',
836
+ groups: [
837
+ {
838
+ groupId: 'help',
839
+ items: [
840
+ 'menuBar:accessibilityHelp'
841
+ ]
842
+ }
843
+ ]
844
+ }
845
+ ];
846
+ /**
847
+ * Performs a cleanup and normalization of the menu bar configuration.
848
+ */
849
+ export function normalizeMenuBarConfig(config) {
850
+ let configObject;
851
+ // The integrator specified the config as an object but without items. Let's give them defaults but respect their
852
+ // additions and removals.
853
+ if (!('items' in config) || !config.items) {
854
+ configObject = {
855
+ items: cloneDeep(DefaultMenuBarItems),
856
+ addItems: [],
857
+ removeItems: [],
858
+ isVisible: true,
859
+ isUsingDefaultConfig: true,
860
+ ...config
861
+ };
862
+ }
863
+ // The integrator specified the config as an object and there are items there. Let's take it as it is.
864
+ else {
865
+ configObject = {
866
+ items: config.items,
867
+ removeItems: [],
868
+ addItems: [],
869
+ isVisible: true,
870
+ isUsingDefaultConfig: false,
871
+ ...config
872
+ };
873
+ }
874
+ return configObject;
875
+ }
876
+ /**
877
+ * Processes a normalized menu bar config and returns a config clone with the following modifications:
878
+ *
879
+ * * Removed components that are not available in the component factory,
880
+ * * Removed obsolete separators,
881
+ * * Purged empty menus,
882
+ * * Localized top-level menu labels.
883
+ */
884
+ export function processMenuBarConfig({ normalizedConfig, locale, componentFactory }) {
885
+ const configClone = cloneDeep(normalizedConfig);
886
+ handleRemovals(normalizedConfig, configClone);
887
+ handleAdditions(normalizedConfig, configClone);
888
+ purgeUnavailableComponents(normalizedConfig, configClone, componentFactory);
889
+ purgeEmptyMenus(normalizedConfig, configClone);
890
+ localizeMenuLabels(configClone, locale);
891
+ return configClone;
892
+ }
893
+ /**
894
+ * Removes items from the menu bar config based on user `removeItems` configuration. Users can remove
895
+ * individual items, groups, or entire menus. For each removed item, a warning is logged if the item
896
+ * was not found in the configuration.
897
+ */
898
+ function handleRemovals(originalConfig, config) {
899
+ const itemsToBeRemoved = config.removeItems;
900
+ const successfullyRemovedItems = [];
901
+ // Remove top-level menus.
902
+ config.items = config.items.filter(({ menuId }) => {
903
+ if (itemsToBeRemoved.includes(menuId)) {
904
+ successfullyRemovedItems.push(menuId);
905
+ return false;
906
+ }
907
+ return true;
908
+ });
909
+ walkConfigMenus(config.items, menuDefinition => {
910
+ // Remove groups from menus.
911
+ menuDefinition.groups = menuDefinition.groups.filter(({ groupId }) => {
912
+ if (itemsToBeRemoved.includes(groupId)) {
913
+ successfullyRemovedItems.push(groupId);
914
+ return false;
915
+ }
916
+ return true;
917
+ });
918
+ // Remove sub-menus and items from groups.
919
+ for (const groupDefinition of menuDefinition.groups) {
920
+ groupDefinition.items = groupDefinition.items.filter(item => {
921
+ const itemId = getIdFromGroupItem(item);
922
+ if (itemsToBeRemoved.includes(itemId)) {
923
+ successfullyRemovedItems.push(itemId);
924
+ return false;
925
+ }
926
+ return true;
927
+ });
928
+ }
929
+ });
930
+ for (const itemName of itemsToBeRemoved) {
931
+ if (!successfullyRemovedItems.includes(itemName)) {
932
+ /**
933
+ * There was a problem processing the configuration of the menu bar. The item with the given
934
+ * name does could not be removed from the menu bar configuration.
935
+ *
936
+ * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
937
+ * to provide a menu bar item has not been loaded or there is a typo in the
938
+ * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.
939
+ *
940
+ * @error menu-bar-item-could-not-be-removed
941
+ * @param menuBarConfig The full configuration of the menu bar.
942
+ * @param itemName The name of the item that was not removed from the menu bar.
943
+ */
944
+ logWarning('menu-bar-item-could-not-be-removed', {
945
+ menuBarConfig: originalConfig,
946
+ itemName
947
+ });
948
+ }
949
+ }
950
+ }
951
+ /**
952
+ * Handles the `config.menuBar.addItems` configuration. It allows for adding menus, groups, and items at arbitrary
953
+ * positions in the menu bar. If the position does not exist, a warning is logged.
954
+ */
955
+ function handleAdditions(originalConfig, config) {
956
+ const itemsToBeAdded = config.addItems;
957
+ const successFullyAddedItems = [];
958
+ for (const itemToAdd of itemsToBeAdded) {
959
+ const relation = getRelationFromPosition(itemToAdd.position);
960
+ const relativeId = getRelativeIdFromPosition(itemToAdd.position);
961
+ // Adding a menu.
962
+ if (isMenuBarMenuAddition(itemToAdd)) {
963
+ if (!relativeId) {
964
+ // Adding a top-level menu at the beginning of the menu bar.
965
+ if (relation === 'start') {
966
+ config.items.unshift(itemToAdd.menu);
967
+ successFullyAddedItems.push(itemToAdd);
968
+ }
969
+ // Adding a top-level menu at the end of the menu bar.
970
+ else if (relation === 'end') {
971
+ config.items.push(itemToAdd.menu);
972
+ successFullyAddedItems.push(itemToAdd);
973
+ }
974
+ }
975
+ else {
976
+ const topLevelMenuDefinitionIndex = config.items.findIndex(menuDefinition => menuDefinition.menuId === relativeId);
977
+ // Adding a top-level menu somewhere between existing menu bar menus.
978
+ if (topLevelMenuDefinitionIndex != -1) {
979
+ if (relation === 'before') {
980
+ config.items.splice(topLevelMenuDefinitionIndex, 0, itemToAdd.menu);
981
+ successFullyAddedItems.push(itemToAdd);
982
+ }
983
+ else if (relation === 'after') {
984
+ config.items.splice(topLevelMenuDefinitionIndex + 1, 0, itemToAdd.menu);
985
+ successFullyAddedItems.push(itemToAdd);
986
+ }
987
+ }
988
+ // Adding a sub-menu to an existing items group.
989
+ else {
990
+ const wasAdded = addMenuOrItemToGroup(config, itemToAdd.menu, relativeId, relation);
991
+ if (wasAdded) {
992
+ successFullyAddedItems.push(itemToAdd);
993
+ }
994
+ }
995
+ }
996
+ }
997
+ // Adding a group.
998
+ else if (isMenuBarMenuGroupAddition(itemToAdd)) {
999
+ walkConfigMenus(config.items, menuDefinition => {
1000
+ if (menuDefinition.menuId === relativeId) {
1001
+ // Add a group at the start of a menu.
1002
+ if (relation === 'start') {
1003
+ menuDefinition.groups.unshift(itemToAdd.group);
1004
+ successFullyAddedItems.push(itemToAdd);
1005
+ }
1006
+ // Add a group at the end of a menu.
1007
+ else if (relation === 'end') {
1008
+ menuDefinition.groups.push(itemToAdd.group);
1009
+ successFullyAddedItems.push(itemToAdd);
1010
+ }
1011
+ }
1012
+ else {
1013
+ const relativeGroupIndex = menuDefinition.groups.findIndex(group => group.groupId === relativeId);
1014
+ if (relativeGroupIndex !== -1) {
1015
+ // Add a group before an existing group in a menu.
1016
+ if (relation === 'before') {
1017
+ menuDefinition.groups.splice(relativeGroupIndex, 0, itemToAdd.group);
1018
+ successFullyAddedItems.push(itemToAdd);
1019
+ }
1020
+ // Add a group after an existing group in a menu.
1021
+ else if (relation === 'after') {
1022
+ menuDefinition.groups.splice(relativeGroupIndex + 1, 0, itemToAdd.group);
1023
+ successFullyAddedItems.push(itemToAdd);
1024
+ }
1025
+ }
1026
+ }
1027
+ });
1028
+ }
1029
+ // Adding an item to an existing items group.
1030
+ else {
1031
+ const wasAdded = addMenuOrItemToGroup(config, itemToAdd.item, relativeId, relation);
1032
+ if (wasAdded) {
1033
+ successFullyAddedItems.push(itemToAdd);
1034
+ }
1035
+ }
1036
+ }
1037
+ for (const addedItemConfig of itemsToBeAdded) {
1038
+ if (!successFullyAddedItems.includes(addedItemConfig)) {
1039
+ /**
1040
+ * There was a problem processing the configuration of the menu bar. The configured item could not be added
1041
+ * because the position it was supposed to be added to does not exist.
1042
+ *
1043
+ * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
1044
+ * to provide a menu bar item has not been loaded or there is a typo in the
1045
+ * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.
1046
+ *
1047
+ * @error menu-bar-item-could-not-be-removed
1048
+ * @param menuBarConfig The full configuration of the menu bar.
1049
+ * @param itemName The name of the item that was not removed from the menu bar.
1050
+ */
1051
+ logWarning('menu-bar-item-could-not-be-added', {
1052
+ menuBarConfig: originalConfig,
1053
+ addedItemConfig
1054
+ });
1055
+ }
1056
+ }
1057
+ }
1058
+ /**
1059
+ * Handles adding a sub-menu or an item into a group. The logic is the same for both cases.
1060
+ */
1061
+ function addMenuOrItemToGroup(config, itemOrMenuToAdd, relativeId, relation) {
1062
+ let wasAdded = false;
1063
+ walkConfigMenus(config.items, menuDefinition => {
1064
+ for (const { groupId, items: groupItems } of menuDefinition.groups) {
1065
+ // Avoid infinite loops.
1066
+ if (wasAdded) {
1067
+ return;
1068
+ }
1069
+ if (groupId === relativeId) {
1070
+ // Adding an item/menu at the beginning of a group.
1071
+ if (relation === 'start') {
1072
+ groupItems.unshift(itemOrMenuToAdd);
1073
+ wasAdded = true;
1074
+ }
1075
+ // Adding an item/menu at the end of a group.
1076
+ else if (relation === 'end') {
1077
+ groupItems.push(itemOrMenuToAdd);
1078
+ wasAdded = true;
1079
+ }
1080
+ }
1081
+ else {
1082
+ // Adding an item/menu relative to an existing item/menu.
1083
+ const relativeItemIndex = groupItems.findIndex(groupItem => {
1084
+ return getIdFromGroupItem(groupItem) === relativeId;
1085
+ });
1086
+ if (relativeItemIndex !== -1) {
1087
+ if (relation === 'before') {
1088
+ groupItems.splice(relativeItemIndex, 0, itemOrMenuToAdd);
1089
+ wasAdded = true;
1090
+ }
1091
+ else if (relation === 'after') {
1092
+ groupItems.splice(relativeItemIndex + 1, 0, itemOrMenuToAdd);
1093
+ wasAdded = true;
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ });
1099
+ return wasAdded;
1100
+ }
1101
+ /**
1102
+ * Removes components from the menu bar configuration that are not available in the factory and would
1103
+ * not be instantiated. Warns about missing components if the menu bar configuration was specified by the user.
1104
+ */
1105
+ function purgeUnavailableComponents(originalConfig, config, componentFactory) {
1106
+ walkConfigMenus(config.items, menuDefinition => {
1107
+ for (const groupDefinition of menuDefinition.groups) {
1108
+ groupDefinition.items = groupDefinition.items.filter(item => {
1109
+ const isItemUnavailable = typeof item === 'string' && !componentFactory.has(item);
1110
+ // The default configuration contains all possible editor features. But integrators' editors rarely load
1111
+ // every possible feature. This is why we do not want to log warnings about unavailable items for the default config
1112
+ // because they would show up in almost every integration. If the configuration has been provided by
1113
+ // the integrator, on the other hand, then these warnings bring value.
1114
+ if (isItemUnavailable && !config.isUsingDefaultConfig) {
1115
+ /**
1116
+ * There was a problem processing the configuration of the menu bar. The item with the given
1117
+ * name does not exist so it was omitted when rendering the menu bar.
1118
+ *
1119
+ * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
1120
+ * to provide a menu bar item has not been loaded or there is a typo in the
1121
+ * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.
1122
+ *
1123
+ * Make sure the plugin responsible for this menu bar item is loaded and the menu bar configuration
1124
+ * is correct, e.g. {@link module:basic-styles/bold/boldui~BoldUI} is loaded for the `'menuBar:bold'`
1125
+ * menu bar item.
1126
+ *
1127
+ * @error menu-bar-item-unavailable
1128
+ * @param menuBarConfig The full configuration of the menu bar.
1129
+ * @param parentMenuConfig The config of the menu the unavailable component was defined in.
1130
+ * @param componentName The name of the unavailable component.
1131
+ */
1132
+ logWarning('menu-bar-item-unavailable', {
1133
+ menuBarConfig: originalConfig,
1134
+ parentMenuConfig: cloneDeep(menuDefinition),
1135
+ componentName: item
1136
+ });
1137
+ }
1138
+ return !isItemUnavailable;
1139
+ });
1140
+ }
1141
+ });
1142
+ }
1143
+ /**
1144
+ * Removes empty menus from the menu bar configuration to improve the visual UX. Such menus can occur
1145
+ * when some plugins responsible for providing menu bar items have not been loaded and some part of
1146
+ * the configuration populated menus using these components exclusively.
1147
+ */
1148
+ function purgeEmptyMenus(originalConfig, config) {
1149
+ const isUsingDefaultConfig = config.isUsingDefaultConfig;
1150
+ let wasSubMenuPurged = false;
1151
+ // Purge top-level menus.
1152
+ config.items = config.items.filter(menuDefinition => {
1153
+ if (!menuDefinition.groups.length) {
1154
+ warnAboutEmptyMenu(originalConfig, menuDefinition, isUsingDefaultConfig);
1155
+ return false;
1156
+ }
1157
+ return true;
1158
+ });
1159
+ // Warn if there were no top-level menus left in the menu bar after purging.
1160
+ if (!config.items.length) {
1161
+ warnAboutEmptyMenu(originalConfig, originalConfig, isUsingDefaultConfig);
1162
+ return;
1163
+ }
1164
+ // Purge sub-menus and groups.
1165
+ walkConfigMenus(config.items, menuDefinition => {
1166
+ // Get rid of empty groups.
1167
+ menuDefinition.groups = menuDefinition.groups.filter(groupDefinition => {
1168
+ if (!groupDefinition.items.length) {
1169
+ wasSubMenuPurged = true;
1170
+ return false;
1171
+ }
1172
+ return true;
1173
+ });
1174
+ // Get rid of empty sub-menus.
1175
+ for (const groupDefinition of menuDefinition.groups) {
1176
+ groupDefinition.items = groupDefinition.items.filter(item => {
1177
+ // If no groups were left after removing empty ones.
1178
+ if (isMenuDefinition(item) && !item.groups.length) {
1179
+ warnAboutEmptyMenu(originalConfig, item, isUsingDefaultConfig);
1180
+ wasSubMenuPurged = true;
1181
+ return false;
1182
+ }
1183
+ return true;
1184
+ });
1185
+ }
1186
+ });
1187
+ if (wasSubMenuPurged) {
1188
+ // The config is walked from the root to the leaves so if anything gets removed, we need to re-run the
1189
+ // whole process because it could've affected parents.
1190
+ purgeEmptyMenus(originalConfig, config);
1191
+ }
1192
+ }
1193
+ function warnAboutEmptyMenu(originalConfig, emptyMenuConfig, isUsingDefaultConfig) {
1194
+ if (isUsingDefaultConfig) {
1195
+ return;
1196
+ }
1197
+ /**
1198
+ * There was a problem processing the configuration of the menu bar. One of the menus
1199
+ * is empty so it was omitted when rendering the menu bar.
1200
+ *
1201
+ * This warning usually shows up when some {@link module:core/plugin~Plugin plugins} responsible for
1202
+ * providing menu bar items have not been loaded and the
1203
+ * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration} was not updated.
1204
+ *
1205
+ * Make sure all necessary editor plugins are loaded and/or update the menu bar configuration
1206
+ * to account for the missing menu items.
1207
+ *
1208
+ * @error menu-bar-menu-empty
1209
+ * @param menuBarConfig The full configuration of the menu bar.
1210
+ * @param emptyMenuConfig The definition of the menu that has no child items.
1211
+ */
1212
+ logWarning('menu-bar-menu-empty', {
1213
+ menuBarConfig: originalConfig,
1214
+ emptyMenuConfig
1215
+ });
1216
+ }
1217
+ /**
1218
+ * Localizes the user-config using pre-defined localized category labels.
1219
+ */
1220
+ function localizeMenuLabels(config, locale) {
1221
+ const t = locale.t;
1222
+ const localizedCategoryLabels = {
1223
+ // Top-level categories.
1224
+ 'File': t({
1225
+ string: 'File',
1226
+ id: 'MENU_BAR_MENU_FILE'
1227
+ }),
1228
+ 'Edit': t({
1229
+ string: 'Edit',
1230
+ id: 'MENU_BAR_MENU_EDIT'
1231
+ }),
1232
+ 'View': t({
1233
+ string: 'View',
1234
+ id: 'MENU_BAR_MENU_VIEW'
1235
+ }),
1236
+ 'Insert': t({
1237
+ string: 'Insert',
1238
+ id: 'MENU_BAR_MENU_INSERT'
1239
+ }),
1240
+ 'Format': t({
1241
+ string: 'Format',
1242
+ id: 'MENU_BAR_MENU_FORMAT'
1243
+ }),
1244
+ 'Tools': t({
1245
+ string: 'Tools',
1246
+ id: 'MENU_BAR_MENU_TOOLS'
1247
+ }),
1248
+ 'Help': t({
1249
+ string: 'Help',
1250
+ id: 'MENU_BAR_MENU_HELP'
1251
+ }),
1252
+ // Sub-menus.
1253
+ 'Text': t({
1254
+ string: 'Text',
1255
+ id: 'MENU_BAR_MENU_TEXT'
1256
+ }),
1257
+ 'Font': t({
1258
+ string: 'Font',
1259
+ id: 'MENU_BAR_MENU_FONT'
1260
+ })
1261
+ };
1262
+ walkConfigMenus(config.items, definition => {
1263
+ if (definition.label in localizedCategoryLabels) {
1264
+ definition.label = localizedCategoryLabels[definition.label];
1265
+ }
1266
+ });
1267
+ }
1268
+ /**
1269
+ * Recursively visits all menu definitions in the config and calls the callback for each of them.
1270
+ */
1271
+ function walkConfigMenus(definition, callback) {
1272
+ if (Array.isArray(definition)) {
1273
+ for (const topLevelMenuDefinition of definition) {
1274
+ walk(topLevelMenuDefinition);
1275
+ }
1276
+ }
1277
+ function walk(menuDefinition) {
1278
+ callback(menuDefinition);
1279
+ for (const groupDefinition of menuDefinition.groups) {
1280
+ for (const groupItem of groupDefinition.items) {
1281
+ if (isMenuDefinition(groupItem)) {
1282
+ walk(groupItem);
1283
+ }
1284
+ }
1285
+ }
1286
+ }
1287
+ }
1288
+ function isMenuBarMenuAddition(definition) {
1289
+ return typeof definition === 'object' && 'menu' in definition;
1290
+ }
1291
+ function isMenuBarMenuGroupAddition(definition) {
1292
+ return typeof definition === 'object' && 'group' in definition;
1293
+ }
1294
+ function getRelationFromPosition(position) {
1295
+ if (position.startsWith('start')) {
1296
+ return 'start';
1297
+ }
1298
+ else if (position.startsWith('end')) {
1299
+ return 'end';
1300
+ }
1301
+ else if (position.startsWith('after')) {
1302
+ return 'after';
1303
+ }
1304
+ else {
1305
+ return 'before';
1306
+ }
1307
+ }
1308
+ function getRelativeIdFromPosition(position) {
1309
+ const match = position.match(/^[^:]+:(.+)/);
1310
+ if (match) {
1311
+ return match[1];
1312
+ }
1313
+ return null;
1314
+ }
1315
+ function getIdFromGroupItem(item) {
1316
+ return typeof item === 'string' ? item : item.menuId;
1317
+ }
1318
+ function isMenuDefinition(definition) {
1319
+ return typeof definition === 'object' && 'menuId' in definition;
1320
+ }