@ckeditor/ckeditor5-ui 42.0.2 → 43.0.0-alpha.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 (85) hide show
  1. package/CHANGELOG.md +1 -539
  2. package/dist/button/button.d.ts +0 -6
  3. package/dist/button/buttonview.d.ts +16 -4
  4. package/dist/button/filedialogbuttonview.d.ts +42 -15
  5. package/dist/button/listitembuttonview.d.ts +82 -0
  6. package/dist/editorui/accessibilityhelp/accessibilityhelp.d.ts +1 -1
  7. package/dist/editorui/editorui.d.ts +57 -0
  8. package/dist/editorui/editoruiview.d.ts +5 -0
  9. package/dist/focuscycler.d.ts +53 -7
  10. package/dist/formheader/formheaderview.d.ts +1 -2
  11. package/dist/highlightedtext/buttonlabelwithhighlightview.d.ts +34 -0
  12. package/dist/highlightedtext/labelwithhighlightview.d.ts +30 -0
  13. package/dist/index-editor.css +32 -0
  14. package/dist/index.css +42 -0
  15. package/dist/index.css.map +1 -1
  16. package/dist/index.d.ts +6 -2
  17. package/dist/index.js +4934 -4291
  18. package/dist/index.js.map +1 -1
  19. package/dist/menubar/menubarmenubuttonview.d.ts +2 -2
  20. package/dist/menubar/menubarmenulistitembuttonview.d.ts +2 -2
  21. package/dist/menubar/menubarmenulistitemfiledialogbuttonview.d.ts +2 -2
  22. package/dist/menubar/menubarmenulistview.d.ts +5 -0
  23. package/dist/menubar/menubarview.d.ts +10 -1
  24. package/dist/menubar/utils.d.ts +16 -10
  25. package/dist/panel/balloon/balloonpanelview.d.ts +13 -1
  26. package/dist/search/filtergroupanditemnames.d.ts +19 -0
  27. package/dist/toolbar/block/blocktoolbar.d.ts +14 -0
  28. package/dist/tooltipmanager.d.ts +0 -7
  29. package/dist/translations/sr-latn.js +1 -1
  30. package/dist/translations/sr-latn.umd.js +1 -1
  31. package/lang/translations/sr-latn.po +17 -17
  32. package/package.json +3 -3
  33. package/src/arialiveannouncer.js +0 -1
  34. package/src/bindings/draggableviewmixin.js +1 -1
  35. package/src/button/button.d.ts +0 -6
  36. package/src/button/buttonview.d.ts +16 -4
  37. package/src/button/buttonview.js +32 -2
  38. package/src/button/filedialogbuttonview.d.ts +42 -15
  39. package/src/button/filedialogbuttonview.js +69 -27
  40. package/src/button/listitembuttonview.d.ts +78 -0
  41. package/src/button/listitembuttonview.js +129 -0
  42. package/src/colorpicker/colorpickerview.js +5 -0
  43. package/src/colorselector/colorgridsfragmentview.js +9 -5
  44. package/src/dialog/dialog.js +4 -1
  45. package/src/dialog/dialogview.js +1 -14
  46. package/src/dropdown/utils.js +23 -3
  47. package/src/editorui/accessibilityhelp/accessibilityhelp.d.ts +1 -1
  48. package/src/editorui/accessibilityhelp/accessibilityhelp.js +20 -12
  49. package/src/editorui/bodycollection.js +2 -1
  50. package/src/editorui/editorui.d.ts +57 -0
  51. package/src/editorui/editorui.js +104 -12
  52. package/src/editorui/editoruiview.d.ts +5 -0
  53. package/src/focuscycler.d.ts +53 -7
  54. package/src/focuscycler.js +79 -1
  55. package/src/formheader/formheaderview.d.ts +1 -2
  56. package/src/formheader/formheaderview.js +1 -2
  57. package/src/highlightedtext/buttonlabelwithhighlightview.d.ts +30 -0
  58. package/src/highlightedtext/buttonlabelwithhighlightview.js +31 -0
  59. package/src/highlightedtext/labelwithhighlightview.d.ts +26 -0
  60. package/src/highlightedtext/labelwithhighlightview.js +33 -0
  61. package/src/index.d.ts +6 -2
  62. package/src/index.js +6 -2
  63. package/src/menubar/menubarmenubuttonview.d.ts +2 -2
  64. package/src/menubar/menubarmenubuttonview.js +2 -2
  65. package/src/menubar/menubarmenulistitembuttonview.d.ts +2 -2
  66. package/src/menubar/menubarmenulistitembuttonview.js +2 -2
  67. package/src/menubar/menubarmenulistitemfiledialogbuttonview.d.ts +2 -2
  68. package/src/menubar/menubarmenulistitemfiledialogbuttonview.js +2 -2
  69. package/src/menubar/menubarmenulistview.d.ts +5 -0
  70. package/src/menubar/menubarmenulistview.js +49 -0
  71. package/src/menubar/menubarview.d.ts +10 -1
  72. package/src/menubar/menubarview.js +11 -4
  73. package/src/menubar/utils.d.ts +16 -10
  74. package/src/menubar/utils.js +84 -53
  75. package/src/panel/balloon/balloonpanelview.d.ts +13 -1
  76. package/src/panel/balloon/balloonpanelview.js +41 -3
  77. package/src/search/filtergroupanditemnames.d.ts +15 -0
  78. package/src/search/filtergroupanditemnames.js +38 -0
  79. package/src/search/text/searchtextview.js +1 -0
  80. package/src/toolbar/balloon/balloontoolbar.js +1 -1
  81. package/src/toolbar/block/blocktoolbar.d.ts +14 -0
  82. package/src/toolbar/block/blocktoolbar.js +83 -3
  83. package/src/tooltipmanager.d.ts +0 -7
  84. package/src/tooltipmanager.js +1 -18
  85. package/theme/components/button/listitembutton.css +38 -0
@@ -6,14 +6,14 @@
6
6
  * @module ui/menubar/menubarmenulistitemfiledialogbuttonview
7
7
  */
8
8
  import type { Locale } from '@ckeditor/ckeditor5-utils';
9
- import FileDialogButtonView from '../button/filedialogbuttonview.js';
9
+ import { FileDialogListItemButtonView } from '../button/filedialogbuttonview.js';
10
10
  import '../../theme/components/menubar/menubarmenulistitembutton.css';
11
11
  /**
12
12
  * A menu bar list file dialog button view. Buttons like this one execute user actions.
13
13
  *
14
14
  * This component provides a button that opens the native file selection dialog.
15
15
  */
16
- export default class MenuBarMenuListItemFileDialogButtonView extends FileDialogButtonView {
16
+ export default class MenuBarMenuListItemFileDialogButtonView extends FileDialogListItemButtonView {
17
17
  /**
18
18
  * Creates an instance of the menu bar list button view.
19
19
  *
@@ -2,14 +2,14 @@
2
2
  * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
- import FileDialogButtonView from '../button/filedialogbuttonview.js';
5
+ import { FileDialogListItemButtonView } from '../button/filedialogbuttonview.js';
6
6
  import '../../theme/components/menubar/menubarmenulistitembutton.css';
7
7
  /**
8
8
  * A menu bar list file dialog button view. Buttons like this one execute user actions.
9
9
  *
10
10
  * This component provides a button that opens the native file selection dialog.
11
11
  */
12
- export default class MenuBarMenuListItemFileDialogButtonView extends FileDialogButtonView {
12
+ export default class MenuBarMenuListItemFileDialogButtonView extends FileDialogListItemButtonView {
13
13
  /**
14
14
  * Creates an instance of the menu bar list button view.
15
15
  *
@@ -21,4 +21,9 @@ export default class MenuBarMenuListView extends ListView {
21
21
  * @param locale The localization services instance.
22
22
  */
23
23
  constructor(locale: Locale);
24
+ /**
25
+ * This method adds empty space if there is any toggleable item in the list.
26
+ * It makes the list properly aligned.
27
+ */
28
+ private _setItemsCheckSpace;
24
29
  }
@@ -2,7 +2,10 @@
2
2
  * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
+ import ListItemView from '../list/listitemview.js';
5
6
  import ListView from '../list/listview.js';
7
+ import ListItemButtonView from '../button/listitembuttonview.js';
8
+ import ButtonView from '../button/buttonview.js';
6
9
  /**
7
10
  * A list of menu bar items, a child of {@link module:ui/menubar/menubarmenuview~MenuBarMenuView#panelView}.
8
11
  *
@@ -19,5 +22,51 @@ export default class MenuBarMenuListView extends ListView {
19
22
  constructor(locale) {
20
23
  super(locale);
21
24
  this.role = 'menu';
25
+ this.items.on('change', this._setItemsCheckSpace.bind(this));
22
26
  }
27
+ /**
28
+ * This method adds empty space if there is any toggleable item in the list.
29
+ * It makes the list properly aligned.
30
+ */
31
+ _setItemsCheckSpace() {
32
+ const hasAnyToggleableItem = (Array
33
+ .from(this.items)
34
+ .some(item => {
35
+ const listButtonView = pickListButtonMenuViewIfPresent(item);
36
+ return listButtonView && listButtonView.isToggleable;
37
+ }));
38
+ this.items.forEach(item => {
39
+ const listButtonView = pickListButtonMenuViewIfPresent(item);
40
+ if (listButtonView) {
41
+ listButtonView.hasCheckSpace = hasAnyToggleableItem;
42
+ }
43
+ });
44
+ }
45
+ }
46
+ /**
47
+ * Picks the first button menu view from the given item if present.
48
+ *
49
+ * @param item The item to check for a button menu view.
50
+ * @returns The first button menu view found in the item, or `null` if not found.
51
+ */
52
+ function pickListButtonMenuViewIfPresent(item) {
53
+ if (!(item instanceof ListItemView)) {
54
+ return null;
55
+ }
56
+ return item
57
+ .children
58
+ .map(child => isNestedMenuLikeView(child) ? child.buttonView : child)
59
+ .find(item => item instanceof ListItemButtonView);
60
+ }
61
+ /**
62
+ * Checks if the given item is a nested menu-like view. `MenuBarMenuView` imports this file
63
+ * so to avoid circular dependencies, this function is defined in more generic way.
64
+ *
65
+ * @param item The item to check.
66
+ * @returns `true` if the item is a nested menu-like view, `false` otherwise.
67
+ */
68
+ function isNestedMenuLikeView(item) {
69
+ return (typeof item === 'object' &&
70
+ 'buttonView' in item &&
71
+ item.buttonView instanceof ButtonView);
23
72
  }
@@ -28,6 +28,15 @@ export default class MenuBarView extends View implements FocusableView {
28
28
  * @observable
29
29
  */
30
30
  isOpen: boolean;
31
+ /**
32
+ * Indicates whether the menu bar has been interacted with using the keyboard.
33
+ *
34
+ * It is useful for showing focus outlines while hovering over the menu bar when
35
+ * interaction with the keyboard was detected.
36
+ *
37
+ * @observable
38
+ */
39
+ isFocusBorderEnabled: boolean;
31
40
  /**
32
41
  * A list of {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} instances registered in the menu bar.
33
42
  *
@@ -47,7 +56,7 @@ export default class MenuBarView extends View implements FocusableView {
47
56
  * See the {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar} in the editor
48
57
  * configuration reference to learn how to configure the menu bar.
49
58
  */
50
- fillFromConfig(config: NormalizedMenuBarConfigObject, componentFactory: ComponentFactory): void;
59
+ fillFromConfig(config: NormalizedMenuBarConfigObject, componentFactory: ComponentFactory, extraItems?: Array<MenuBarConfigAddedItem | MenuBarConfigAddedGroup | MenuBarConfigAddedMenu>): void;
51
60
  /**
52
61
  * @inheritDoc
53
62
  */
@@ -37,7 +37,11 @@ export default class MenuBarView extends View {
37
37
  */
38
38
  this.menus = [];
39
39
  const t = locale.t;
40
- this.set('isOpen', false);
40
+ const bind = this.bindTemplate;
41
+ this.set({
42
+ isOpen: false,
43
+ isFocusBorderEnabled: false
44
+ });
41
45
  this._setupIsOpenUpdater();
42
46
  this.children = this.createCollection();
43
47
  // @if CK_DEBUG_MENU_BAR // // Logs events in the main event bus of the component.
@@ -49,7 +53,8 @@ export default class MenuBarView extends View {
49
53
  attributes: {
50
54
  class: [
51
55
  'ck',
52
- 'ck-menu-bar'
56
+ 'ck-menu-bar',
57
+ bind.if('isFocusBorderEnabled', 'ck-menu-bar_focus-border-enabled')
53
58
  ],
54
59
  'aria-label': t('Editor menu bar'),
55
60
  role: 'menubar'
@@ -64,12 +69,13 @@ export default class MenuBarView extends View {
64
69
  * See the {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar} in the editor
65
70
  * configuration reference to learn how to configure the menu bar.
66
71
  */
67
- fillFromConfig(config, componentFactory) {
72
+ fillFromConfig(config, componentFactory, extraItems = []) {
68
73
  const locale = this.locale;
69
74
  const processedConfig = processMenuBarConfig({
70
75
  normalizedConfig: config,
71
76
  locale,
72
- componentFactory
77
+ componentFactory,
78
+ extraItems
73
79
  });
74
80
  const topLevelCategoryMenuViews = processedConfig.items.map(menuDefinition => this._createMenu({
75
81
  componentFactory,
@@ -87,6 +93,7 @@ export default class MenuBarView extends View {
87
93
  MenuBarBehaviors.closeMenuWhenAnotherOnTheSameLevelOpens(this);
88
94
  MenuBarBehaviors.focusCycleMenusOnArrows(this);
89
95
  MenuBarBehaviors.closeOnClickOutside(this);
96
+ MenuBarBehaviors.enableFocusHighlightOnInteraction(this);
90
97
  }
91
98
  /**
92
99
  * Focuses the menu bar.
@@ -3,9 +3,8 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
  import type MenuBarMenuView from './menubarmenuview.js';
6
- import type { default as MenuBarView, MenuBarConfig, MenuBarConfigObject, NormalizedMenuBarConfigObject } from './menubarview.js';
6
+ import type { default as MenuBarView, MenuBarConfig, MenuBarConfigObject, MenuBarConfigAddedGroup, MenuBarConfigAddedMenu, NormalizedMenuBarConfigObject, MenuBarConfigAddedItem } from './menubarview.js';
7
7
  import type ComponentFactory from '../componentfactory.js';
8
- import type { Editor } from '@ckeditor/ckeditor5-core';
9
8
  import { type Locale, type PositioningFunction } from '@ckeditor/ckeditor5-utils';
10
9
  type DeepReadonly<T> = Readonly<{
11
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]>;
@@ -44,6 +43,11 @@ export declare const MenuBarBehaviors: {
44
43
  * Closes the bar when the user clicked outside of it (page body, editor root, etc.).
45
44
  */
46
45
  closeOnClickOutside(menuBarView: MenuBarView): void;
46
+ /**
47
+ * Tracks the keyboard focus interaction on the menu bar view. It is used to determine if the nested items
48
+ * of the menu bar should render focus rings after first interaction with the keyboard.
49
+ */
50
+ enableFocusHighlightOnInteraction(menuBarView: MenuBarView): void;
47
51
  };
48
52
  /**
49
53
  * Behaviors of the {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} component.
@@ -233,6 +237,12 @@ export declare const MenuBarMenuViewPanelPositioningFunctions: Record<string, Po
233
237
  * ]
234
238
  * },
235
239
  * {
240
+ * groupId: 'previewMergeFields',
241
+ * items: [
242
+ * 'menuBar:previewMergeFields'
243
+ * ]
244
+ * },
245
+ * {
236
246
  * groupId: 'restrictedEditingException',
237
247
  * items: [
238
248
  * 'menuBar:restrictedEditingException'
@@ -257,7 +267,8 @@ export declare const MenuBarMenuViewPanelPositioningFunctions: Record<string, Po
257
267
  * groupId: 'insertInline',
258
268
  * items: [
259
269
  * 'menuBar:link',
260
- * 'menuBar:comment'
270
+ * 'menuBar:comment',
271
+ * 'menuBar:insertMergeField'
261
272
  * ]
262
273
  * },
263
274
  * {
@@ -427,15 +438,10 @@ export declare function normalizeMenuBarConfig(config: Readonly<MenuBarConfig>):
427
438
  * * Purged empty menus,
428
439
  * * Localized top-level menu labels.
429
440
  */
430
- export declare function processMenuBarConfig({ normalizedConfig, locale, componentFactory }: {
441
+ export declare function processMenuBarConfig({ normalizedConfig, locale, componentFactory, extraItems }: {
431
442
  normalizedConfig: NormalizedMenuBarConfigObject;
432
443
  locale: Locale;
433
444
  componentFactory: ComponentFactory;
445
+ extraItems: Array<MenuBarConfigAddedItem | MenuBarConfigAddedGroup | MenuBarConfigAddedMenu>;
434
446
  }): NormalizedMenuBarConfigObject;
435
- /**
436
- * Initializes menu bar for given editor.
437
- *
438
- * @internal
439
- */
440
- export declare function _initMenuBar(editor: Editor, menuBarView: MenuBarView): void;
441
447
  export {};
@@ -21,20 +21,24 @@ export const MenuBarBehaviors = {
21
21
  */
22
22
  toggleMenusAndFocusItemsOnHover(menuBarView) {
23
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) {
24
+ // This behavior should be activated when one of condition is present:
25
+ // 1. The user opened any submenu of menubar and hover over items in the menu bar.
26
+ // 2. The user focused whole menubar using keyboard interaction and enabled focus borders and hover over items in the menu bar.
27
+ if (!menuBarView.isFocusBorderEnabled && !menuBarView.isOpen) {
26
28
  return;
27
29
  }
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 // }
30
+ if (menuBarView.isOpen) {
31
+ for (const menuView of menuBarView.menus) {
32
+ // @if CK_DEBUG_MENU_BAR // const wasOpen = menuView.isOpen;
33
+ const pathLeaf = evt.path[0];
34
+ const isListItemContainingMenu = pathLeaf instanceof MenuBarMenuListItemView && pathLeaf.children.first === menuView;
35
+ menuView.isOpen = (evt.path.includes(menuView) || isListItemContainingMenu) && menuView.isEnabled;
36
+ // @if CK_DEBUG_MENU_BAR // if ( wasOpen !== menuView.isOpen ) {
37
+ // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] toggleMenusAndFocusItemsOnHover(): Toggle',
38
+ // @if CK_DEBUG_MENU_BAR // logMenu( menuView ), 'isOpen', menuView.isOpen
39
+ // @if CK_DEBUG_MENU_BAR // );
40
+ // @if CK_DEBUG_MENU_BAR // }
41
+ }
38
42
  }
39
43
  evt.source.focus();
40
44
  });
@@ -111,6 +115,40 @@ export const MenuBarBehaviors = {
111
115
  callback: () => menuBarView.close(),
112
116
  contextElements: () => menuBarView.children.map(child => child.element)
113
117
  });
118
+ },
119
+ /**
120
+ * Tracks the keyboard focus interaction on the menu bar view. It is used to determine if the nested items
121
+ * of the menu bar should render focus rings after first interaction with the keyboard.
122
+ */
123
+ enableFocusHighlightOnInteraction(menuBarView) {
124
+ let isKeyPressed = false;
125
+ menuBarView.on('change:isOpen', (_, evt, isOpen) => {
126
+ if (!isOpen) {
127
+ menuBarView.isFocusBorderEnabled = false;
128
+ // Reset the flag when the menu bar is closed, menu items tend to intercept `keyup` event
129
+ // and sometimes, after pressing `enter` on focused item, `isKeyPressed` stuck in `true` state.
130
+ isKeyPressed = false;
131
+ }
132
+ });
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
+ menuBarView.listenTo(menuBarView.element, 'keydown', () => {
142
+ isKeyPressed = true;
143
+ }, { useCapture: true });
144
+ menuBarView.listenTo(menuBarView.element, 'keyup', () => {
145
+ isKeyPressed = false;
146
+ }, { useCapture: true });
147
+ menuBarView.listenTo(menuBarView.element, 'focus', () => {
148
+ if (isKeyPressed) {
149
+ menuBarView.isFocusBorderEnabled = true;
150
+ }
151
+ }, { useCapture: true });
114
152
  }
115
153
  };
116
154
  /**
@@ -156,7 +194,9 @@ export const MenuBarMenuBehaviors = {
156
194
  openOnButtonClick(menuView) {
157
195
  menuView.buttonView.on('execute', () => {
158
196
  menuView.isOpen = true;
159
- menuView.panelView.focus();
197
+ if (menuView.parentMenuView) {
198
+ menuView.panelView.focus();
199
+ }
160
200
  });
161
201
  },
162
202
  /**
@@ -165,9 +205,6 @@ export const MenuBarMenuBehaviors = {
165
205
  toggleOnButtonClick(menuView) {
166
206
  menuView.buttonView.on('execute', () => {
167
207
  menuView.isOpen = !menuView.isOpen;
168
- if (menuView.isOpen) {
169
- menuView.panelView.focus();
170
- }
171
208
  });
172
209
  },
173
210
  /**
@@ -420,6 +457,12 @@ export const MenuBarMenuViewPanelPositioningFunctions = {
420
457
  * ]
421
458
  * },
422
459
  * {
460
+ * groupId: 'previewMergeFields',
461
+ * items: [
462
+ * 'menuBar:previewMergeFields'
463
+ * ]
464
+ * },
465
+ * {
423
466
  * groupId: 'restrictedEditingException',
424
467
  * items: [
425
468
  * 'menuBar:restrictedEditingException'
@@ -444,7 +487,8 @@ export const MenuBarMenuViewPanelPositioningFunctions = {
444
487
  * groupId: 'insertInline',
445
488
  * items: [
446
489
  * 'menuBar:link',
447
- * 'menuBar:comment'
490
+ * 'menuBar:comment',
491
+ * 'menuBar:insertMergeField'
448
492
  * ]
449
493
  * },
450
494
  * {
@@ -670,9 +714,15 @@ export const DefaultMenuBarItems = [
670
714
  ]
671
715
  },
672
716
  {
673
- groupId: 'restrictedEditingException',
717
+ groupId: 'previewMergeFields',
674
718
  items: [
675
- 'menuBar:restrictedEditingException'
719
+ 'menuBar:previewMergeFields'
720
+ ]
721
+ },
722
+ {
723
+ groupId: 'restrictedEditing',
724
+ items: [
725
+ 'menuBar:restrictedEditing'
676
726
  ]
677
727
  }
678
728
  ]
@@ -694,7 +744,8 @@ export const DefaultMenuBarItems = [
694
744
  groupId: 'insertInline',
695
745
  items: [
696
746
  'menuBar:link',
697
- 'menuBar:comment'
747
+ 'menuBar:comment',
748
+ 'menuBar:insertMergeField'
698
749
  ]
699
750
  },
700
751
  {
@@ -702,6 +753,7 @@ export const DefaultMenuBarItems = [
702
753
  items: [
703
754
  'menuBar:mediaEmbed',
704
755
  'menuBar:insertTemplate',
756
+ 'menuBar:specialCharacters',
705
757
  'menuBar:blockQuote',
706
758
  'menuBar:codeBlock',
707
759
  'menuBar:htmlEmbed'
@@ -716,9 +768,9 @@ export const DefaultMenuBarItems = [
716
768
  ]
717
769
  },
718
770
  {
719
- groupId: 'restrictedEditing',
771
+ groupId: 'restrictedEditingException',
720
772
  items: [
721
- 'menuBar:restrictedEditing'
773
+ 'menuBar:restrictedEditingException'
722
774
  ]
723
775
  }
724
776
  ]
@@ -885,10 +937,11 @@ export function normalizeMenuBarConfig(config) {
885
937
  * * Purged empty menus,
886
938
  * * Localized top-level menu labels.
887
939
  */
888
- export function processMenuBarConfig({ normalizedConfig, locale, componentFactory }) {
940
+ export function processMenuBarConfig({ normalizedConfig, locale, componentFactory, extraItems }) {
889
941
  const configClone = cloneDeep(normalizedConfig);
942
+ handleAdditions(normalizedConfig, configClone, extraItems);
890
943
  handleRemovals(normalizedConfig, configClone);
891
- handleAdditions(normalizedConfig, configClone);
944
+ handleAdditions(normalizedConfig, configClone, configClone.addItems);
892
945
  purgeUnavailableComponents(normalizedConfig, configClone, componentFactory);
893
946
  purgeEmptyMenus(normalizedConfig, configClone);
894
947
  localizeMenuLabels(configClone, locale);
@@ -953,13 +1006,15 @@ function handleRemovals(originalConfig, config) {
953
1006
  }
954
1007
  }
955
1008
  /**
956
- * Handles the `config.menuBar.addItems` configuration. It allows for adding menus, groups, and items at arbitrary
1009
+ * Adds provided items to config. It allows for adding menus, groups, and items at arbitrary
957
1010
  * positions in the menu bar. If the position does not exist, a warning is logged.
958
1011
  */
959
- function handleAdditions(originalConfig, config) {
960
- const itemsToBeAdded = config.addItems;
1012
+ function handleAdditions(originalConfig, config, items) {
961
1013
  const successFullyAddedItems = [];
962
- for (const itemToAdd of itemsToBeAdded) {
1014
+ if (items.length == 0) {
1015
+ return;
1016
+ }
1017
+ for (const itemToAdd of items) {
963
1018
  const relation = getRelationFromPosition(itemToAdd.position);
964
1019
  const relativeId = getRelativeIdFromPosition(itemToAdd.position);
965
1020
  // Adding a menu.
@@ -1038,7 +1093,7 @@ function handleAdditions(originalConfig, config) {
1038
1093
  }
1039
1094
  }
1040
1095
  }
1041
- for (const addedItemConfig of itemsToBeAdded) {
1096
+ for (const addedItemConfig of items) {
1042
1097
  if (!successFullyAddedItems.includes(addedItemConfig)) {
1043
1098
  /**
1044
1099
  * There was a problem processing the configuration of the menu bar. The configured item could not be added
@@ -1322,27 +1377,3 @@ function getIdFromGroupItem(item) {
1322
1377
  function isMenuDefinition(definition) {
1323
1378
  return typeof definition === 'object' && 'menuId' in definition;
1324
1379
  }
1325
- /**
1326
- * Initializes menu bar for given editor.
1327
- *
1328
- * @internal
1329
- */
1330
- export function _initMenuBar(editor, menuBarView) {
1331
- const menuBarViewElement = menuBarView.element;
1332
- editor.ui.focusTracker.add(menuBarViewElement);
1333
- editor.keystrokes.listenTo(menuBarViewElement);
1334
- const normalizedMenuBarConfig = normalizeMenuBarConfig(editor.config.get('menuBar') || {});
1335
- menuBarView.fillFromConfig(normalizedMenuBarConfig, editor.ui.componentFactory);
1336
- editor.keystrokes.set('Esc', (data, cancel) => {
1337
- if (menuBarViewElement.contains(editor.ui.focusTracker.focusedElement)) {
1338
- editor.editing.view.focus();
1339
- cancel();
1340
- }
1341
- });
1342
- editor.keystrokes.set('Alt+F9', (data, cancel) => {
1343
- if (!menuBarViewElement.contains(editor.ui.focusTracker.focusedElement)) {
1344
- menuBarView.focus();
1345
- cancel();
1346
- }
1347
- });
1348
- }
@@ -110,10 +110,18 @@ export default class BalloonPanelView extends View {
110
110
  * @private
111
111
  */
112
112
  private _pinWhenIsVisibleCallback;
113
+ /**
114
+ * An instance of resize observer used to detect if target element is still visible.
115
+ */
116
+ private _resizeObserver;
113
117
  /**
114
118
  * @inheritDoc
115
119
  */
116
120
  constructor(locale?: Locale);
121
+ /**
122
+ * @inheritDoc
123
+ */
124
+ destroy(): void;
117
125
  /**
118
126
  * Shows the panel.
119
127
  *
@@ -157,8 +165,10 @@ export default class BalloonPanelView extends View {
157
165
  *
158
166
  * @param options Positioning options compatible with {@link module:utils/dom/position~getOptimalPosition}.
159
167
  * Default `positions` array is {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}.
168
+ * @returns Whether the balloon was shown and successfully attached or not. Attaching can fail if the target
169
+ * provided in the options is invisible (e.g. element detached from DOM).
160
170
  */
161
- attachTo(options: Partial<PositionOptions>): void;
171
+ attachTo(options: Partial<PositionOptions>): boolean;
162
172
  /**
163
173
  * Works the same way as the {@link #attachTo} method except that the position of the panel is
164
174
  * continuously updated when:
@@ -202,6 +212,8 @@ export default class BalloonPanelView extends View {
202
212
  * Starts managing the pinned state of the panel. See {@link #pin}.
203
213
  *
204
214
  * @param options Positioning options compatible with {@link module:utils/dom/position~getOptimalPosition}.
215
+ * @returns Whether the balloon was shown and successfully attached or not. Attaching can fail if the target
216
+ * provided in the options is invisible (e.g. element detached from DOM).
205
217
  */
206
218
  private _startPinning;
207
219
  /**
@@ -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 } from '@ckeditor/ckeditor5-utils';
9
+ import { getOptimalPosition, global, isRange, toUnit, isVisible, 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');
@@ -79,6 +79,7 @@ class BalloonPanelView extends View {
79
79
  this.set('withArrow', true);
80
80
  this.set('class', undefined);
81
81
  this._pinWhenIsVisibleCallback = null;
82
+ this._resizeObserver = null;
82
83
  this.content = this.createCollection();
83
84
  this.setTemplate({
84
85
  tag: 'div',
@@ -99,6 +100,13 @@ class BalloonPanelView extends View {
99
100
  children: this.content
100
101
  });
101
102
  }
103
+ /**
104
+ * @inheritDoc
105
+ */
106
+ destroy() {
107
+ this.hide();
108
+ super.destroy();
109
+ }
102
110
  /**
103
111
  * Shows the panel.
104
112
  *
@@ -146,8 +154,14 @@ class BalloonPanelView extends View {
146
154
  *
147
155
  * @param options Positioning options compatible with {@link module:utils/dom/position~getOptimalPosition}.
148
156
  * Default `positions` array is {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}.
157
+ * @returns Whether the balloon was shown and successfully attached or not. Attaching can fail if the target
158
+ * provided in the options is invisible (e.g. element detached from DOM).
149
159
  */
150
160
  attachTo(options) {
161
+ const target = getDomElement(options.target);
162
+ if (target && !isVisible(target)) {
163
+ return false;
164
+ }
151
165
  this.show();
152
166
  const defaultPositions = BalloonPanelView.defaultPositions;
153
167
  const positionOptions = Object.assign({}, {
@@ -180,6 +194,7 @@ class BalloonPanelView extends View {
180
194
  this.left = left;
181
195
  this.position = position;
182
196
  this.withArrow = withArrow;
197
+ return true;
183
198
  }
184
199
  /**
185
200
  * Works the same way as the {@link #attachTo} method except that the position of the panel is
@@ -217,6 +232,9 @@ class BalloonPanelView extends View {
217
232
  */
218
233
  pin(options) {
219
234
  this.unpin();
235
+ if (!this._startPinning(options)) {
236
+ return;
237
+ }
220
238
  this._pinWhenIsVisibleCallback = () => {
221
239
  if (this.isVisible) {
222
240
  this._startPinning(options);
@@ -225,7 +243,6 @@ class BalloonPanelView extends View {
225
243
  this._stopPinning();
226
244
  }
227
245
  };
228
- this._startPinning(options);
229
246
  // Control the state of the listeners depending on whether the panel is visible
230
247
  // or not.
231
248
  // TODO: Use on() (https://github.com/ckeditor/ckeditor5-utils/issues/144).
@@ -249,9 +266,13 @@ class BalloonPanelView extends View {
249
266
  * Starts managing the pinned state of the panel. See {@link #pin}.
250
267
  *
251
268
  * @param options Positioning options compatible with {@link module:utils/dom/position~getOptimalPosition}.
269
+ * @returns Whether the balloon was shown and successfully attached or not. Attaching can fail if the target
270
+ * provided in the options is invisible (e.g. element detached from DOM).
252
271
  */
253
272
  _startPinning(options) {
254
- this.attachTo(options);
273
+ if (!this.attachTo(options)) {
274
+ return false;
275
+ }
255
276
  const targetElement = getDomElement(options.target);
256
277
  const limiterElement = options.limiter ? getDomElement(options.limiter) : global.document.body;
257
278
  // Then we need to listen on scroll event of eny element in the document.
@@ -271,6 +292,19 @@ class BalloonPanelView extends View {
271
292
  this.listenTo(global.window, 'resize', () => {
272
293
  this.attachTo(options);
273
294
  });
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);
306
+ }
307
+ return true;
274
308
  }
275
309
  /**
276
310
  * Stops managing the pinned state of the panel. See {@link #pin}.
@@ -278,6 +312,10 @@ class BalloonPanelView extends View {
278
312
  _stopPinning() {
279
313
  this.stopListening(global.document, 'scroll');
280
314
  this.stopListening(global.window, 'resize');
315
+ if (this._resizeObserver) {
316
+ this._resizeObserver.destroy();
317
+ this._resizeObserver = null;
318
+ }
281
319
  }
282
320
  /**
283
321
  * Returns available {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
@@ -0,0 +1,15 @@
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
+ import type ViewCollection from '../viewcollection.js';
6
+ import type ListItemGroupView from '../list/listitemgroupview.js';
7
+ import type ListItemView from '../list/listitemview.js';
8
+ import type ListSeparatorView from '../list/listseparatorview.js';
9
+ /**
10
+ * A filter function that returns matching item and group names in the list view.
11
+ */
12
+ export default function filterGroupAndItemNames(regExp: RegExp | null, items: ViewCollection<ListItemGroupView | ListItemView | ListSeparatorView>): {
13
+ resultsCount: number;
14
+ totalItemsCount: number;
15
+ };