@ckeditor/ckeditor5-ui 41.1.0 → 41.2.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 (82) hide show
  1. package/ckeditor5-metadata.json +14 -0
  2. package/lang/contexts.json +7 -1
  3. package/lang/translations/ar.po +24 -0
  4. package/lang/translations/ast.po +24 -0
  5. package/lang/translations/az.po +24 -0
  6. package/lang/translations/bg.po +24 -0
  7. package/lang/translations/bn.po +24 -0
  8. package/lang/translations/ca.po +24 -0
  9. package/lang/translations/cs.po +24 -0
  10. package/lang/translations/da.po +24 -0
  11. package/lang/translations/de-ch.po +24 -0
  12. package/lang/translations/de.po +24 -0
  13. package/lang/translations/el.po +24 -0
  14. package/lang/translations/en-au.po +24 -0
  15. package/lang/translations/en-gb.po +24 -0
  16. package/lang/translations/en.po +24 -0
  17. package/lang/translations/eo.po +24 -0
  18. package/lang/translations/es.po +24 -0
  19. package/lang/translations/et.po +24 -0
  20. package/lang/translations/eu.po +24 -0
  21. package/lang/translations/fa.po +24 -0
  22. package/lang/translations/fi.po +24 -0
  23. package/lang/translations/fr.po +24 -0
  24. package/lang/translations/gl.po +24 -0
  25. package/lang/translations/he.po +25 -1
  26. package/lang/translations/hi.po +24 -0
  27. package/lang/translations/hr.po +24 -0
  28. package/lang/translations/hu.po +24 -0
  29. package/lang/translations/id.po +24 -0
  30. package/lang/translations/it.po +24 -0
  31. package/lang/translations/ja.po +24 -0
  32. package/lang/translations/km.po +24 -0
  33. package/lang/translations/kn.po +24 -0
  34. package/lang/translations/ko.po +24 -0
  35. package/lang/translations/ku.po +24 -0
  36. package/lang/translations/lt.po +24 -0
  37. package/lang/translations/lv.po +24 -0
  38. package/lang/translations/ms.po +24 -0
  39. package/lang/translations/nb.po +24 -0
  40. package/lang/translations/ne.po +24 -0
  41. package/lang/translations/nl.po +24 -0
  42. package/lang/translations/no.po +24 -0
  43. package/lang/translations/pl.po +24 -0
  44. package/lang/translations/pt-br.po +24 -0
  45. package/lang/translations/pt.po +25 -1
  46. package/lang/translations/ro.po +24 -0
  47. package/lang/translations/ru.po +24 -0
  48. package/lang/translations/sk.po +24 -0
  49. package/lang/translations/sl.po +24 -0
  50. package/lang/translations/sq.po +24 -0
  51. package/lang/translations/sr-latn.po +24 -0
  52. package/lang/translations/sr.po +24 -0
  53. package/lang/translations/sv.po +24 -0
  54. package/lang/translations/th.po +24 -0
  55. package/lang/translations/tk.po +24 -0
  56. package/lang/translations/tr.po +24 -0
  57. package/lang/translations/tt.po +24 -0
  58. package/lang/translations/ug.po +24 -0
  59. package/lang/translations/uk.po +24 -0
  60. package/lang/translations/ur.po +24 -0
  61. package/lang/translations/uz.po +24 -0
  62. package/lang/translations/vi.po +24 -0
  63. package/lang/translations/zh-cn.po +24 -0
  64. package/lang/translations/zh.po +24 -0
  65. package/package.json +3 -3
  66. package/src/augmentation.d.ts +2 -1
  67. package/src/dialog/dialog.js +10 -0
  68. package/src/editorui/accessibilityhelp/accessibilityhelp.d.ts +47 -0
  69. package/src/editorui/accessibilityhelp/accessibilityhelp.js +111 -0
  70. package/src/editorui/accessibilityhelp/accessibilityhelpcontentview.d.ts +35 -0
  71. package/src/editorui/accessibilityhelp/accessibilityhelpcontentview.js +112 -0
  72. package/src/formheader/formheaderview.js +2 -1
  73. package/src/index.d.ts +1 -0
  74. package/src/index.js +1 -0
  75. package/src/toolbar/balloon/balloontoolbar.d.ts +0 -5
  76. package/src/toolbar/balloon/balloontoolbar.js +5 -8
  77. package/src/toolbar/block/blocktoolbar.d.ts +0 -8
  78. package/src/toolbar/block/blocktoolbar.js +9 -14
  79. package/src/tooltipmanager.d.ts +5 -1
  80. package/src/tooltipmanager.js +32 -4
  81. package/theme/components/editorui/accessibilityhelp.css +10 -0
  82. package/theme/icons/accessibility.svg +1 -0
@@ -0,0 +1,47 @@
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/editorui/accessibilityhelp/accessibilityhelp
7
+ */
8
+ import { Plugin } from '@ckeditor/ckeditor5-core';
9
+ import { Dialog } from '../../index.js';
10
+ import AccessibilityHelpContentView from './accessibilityhelpcontentview.js';
11
+ import '../../../theme/components/editorui/accessibilityhelp.css';
12
+ /**
13
+ * A plugin that brings the accessibility help dialog to the editor available under the <kbd>Alt</kbd>+<kbd>0</kbd>
14
+ * keystroke and via the "Accessibility help" toolbar button. The dialog displays a list of keystrokes that can be used
15
+ * by the user to perform various actions in the editor.
16
+ *
17
+ * Keystroke information is loaded from {@link module:core/accessibility~Accessibility#keystrokeInfos}. New entries can be
18
+ * added using the API provided by the {@link module:core/accessibility~Accessibility} class.
19
+ */
20
+ export default class AccessibilityHelp extends Plugin {
21
+ /**
22
+ * The view that displays the dialog content (list of keystrokes).
23
+ * Created when the dialog is opened for the first time.
24
+ */
25
+ contentView: AccessibilityHelpContentView | null;
26
+ /**
27
+ * @inheritDoc
28
+ */
29
+ static get requires(): readonly [typeof Dialog];
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ static get pluginName(): "AccessibilityHelp";
34
+ /**
35
+ * @inheritDoc
36
+ */
37
+ init(): void;
38
+ /**
39
+ * Injects a help text into each editing root's `aria-label` attribute allowing assistive technology users
40
+ * to discover the availability of the Accessibility help dialog.
41
+ */
42
+ private _setupRootLabels;
43
+ /**
44
+ * Shows the accessibility help dialog. Also, creates {@link #contentView} on demand.
45
+ */
46
+ private _showDialog;
47
+ }
@@ -0,0 +1,111 @@
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/editorui/accessibilityhelp/accessibilityhelp
7
+ */
8
+ import { Plugin } from '@ckeditor/ckeditor5-core';
9
+ import { ButtonView, Dialog } from '../../index.js';
10
+ import AccessibilityHelpContentView from './accessibilityhelpcontentview.js';
11
+ import { getEnvKeystrokeText } from '@ckeditor/ckeditor5-utils';
12
+ import accessibilityIcon from '../../../theme/icons/accessibility.svg';
13
+ import '../../../theme/components/editorui/accessibilityhelp.css';
14
+ /**
15
+ * A plugin that brings the accessibility help dialog to the editor available under the <kbd>Alt</kbd>+<kbd>0</kbd>
16
+ * keystroke and via the "Accessibility help" toolbar button. The dialog displays a list of keystrokes that can be used
17
+ * by the user to perform various actions in the editor.
18
+ *
19
+ * Keystroke information is loaded from {@link module:core/accessibility~Accessibility#keystrokeInfos}. New entries can be
20
+ * added using the API provided by the {@link module:core/accessibility~Accessibility} class.
21
+ */
22
+ export default class AccessibilityHelp extends Plugin {
23
+ constructor() {
24
+ super(...arguments);
25
+ /**
26
+ * The view that displays the dialog content (list of keystrokes).
27
+ * Created when the dialog is opened for the first time.
28
+ */
29
+ this.contentView = null;
30
+ }
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ static get requires() {
35
+ return [Dialog];
36
+ }
37
+ /**
38
+ * @inheritDoc
39
+ */
40
+ static get pluginName() {
41
+ return 'AccessibilityHelp';
42
+ }
43
+ /**
44
+ * @inheritDoc
45
+ */
46
+ init() {
47
+ const editor = this.editor;
48
+ const t = editor.locale.t;
49
+ editor.ui.componentFactory.add('accessibilityHelp', locale => {
50
+ const buttonView = new ButtonView(locale);
51
+ buttonView.set({
52
+ label: t('Accessibility help'),
53
+ tooltip: true,
54
+ withText: false,
55
+ keystroke: 'Alt+0',
56
+ icon: accessibilityIcon
57
+ });
58
+ buttonView.on('execute', () => this._showDialog());
59
+ return buttonView;
60
+ });
61
+ editor.keystrokes.set('Alt+0', (evt, cancel) => {
62
+ this._showDialog();
63
+ cancel();
64
+ });
65
+ this._setupRootLabels();
66
+ }
67
+ /**
68
+ * Injects a help text into each editing root's `aria-label` attribute allowing assistive technology users
69
+ * to discover the availability of the Accessibility help dialog.
70
+ */
71
+ _setupRootLabels() {
72
+ const editor = this.editor;
73
+ const editingView = editor.editing.view;
74
+ const t = editor.t;
75
+ editor.ui.on('ready', () => {
76
+ editingView.change(writer => {
77
+ for (const root of editingView.document.roots) {
78
+ addAriaLabelTextToRoot(writer, root);
79
+ }
80
+ });
81
+ editor.on('addRoot', (evt, modelRoot) => {
82
+ const viewRoot = editor.editing.view.document.getRoot(modelRoot.rootName);
83
+ editingView.change(writer => addAriaLabelTextToRoot(writer, viewRoot));
84
+ }, { priority: 'low' });
85
+ });
86
+ function addAriaLabelTextToRoot(writer, viewRoot) {
87
+ const currentAriaLabel = viewRoot.getAttribute('aria-label');
88
+ const newAriaLabel = `${currentAriaLabel}. ${t('Press %0 for help.', [getEnvKeystrokeText('Alt+0')])}`;
89
+ writer.setAttribute('aria-label', newAriaLabel, viewRoot);
90
+ }
91
+ }
92
+ /**
93
+ * Shows the accessibility help dialog. Also, creates {@link #contentView} on demand.
94
+ */
95
+ _showDialog() {
96
+ const editor = this.editor;
97
+ const dialog = editor.plugins.get('Dialog');
98
+ const t = editor.locale.t;
99
+ if (!this.contentView) {
100
+ this.contentView = new AccessibilityHelpContentView(editor.locale, editor.accessibility.keystrokeInfos);
101
+ }
102
+ dialog.show({
103
+ id: 'accessibilityHelp',
104
+ className: 'ck-accessibility-help-dialog',
105
+ title: t('Accessibility help'),
106
+ icon: accessibilityIcon,
107
+ hasCloseButton: true,
108
+ content: this.contentView
109
+ });
110
+ }
111
+ }
@@ -0,0 +1,35 @@
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/editorui/accessibilityhelp/accessibilityhelpcontentview
7
+ */
8
+ import { type Locale } from '@ckeditor/ckeditor5-utils';
9
+ import View from '../../view.js';
10
+ import type { KeystrokeInfoDefinitions } from '@ckeditor/ckeditor5-core';
11
+ /**
12
+ * The view displaying keystrokes in the Accessibility help dialog.
13
+ */
14
+ export default class AccessibilityHelpContentView extends View<HTMLDivElement> {
15
+ /**
16
+ * @inheritDoc
17
+ */
18
+ constructor(locale: Locale, keystrokes: KeystrokeInfoDefinitions);
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ focus(): void;
23
+ /**
24
+ * Creates `<section><h3>Category label</h3>...</section>` elements for each category of keystrokes.
25
+ */
26
+ private _createCategories;
27
+ /**
28
+ * Creates `[<h4>Optional label</h4>]<dl>...</dl>` elements for each group of keystrokes in a category.
29
+ */
30
+ private _createGroup;
31
+ /**
32
+ * Creates `<dt>Keystroke label</dt><dd>Keystroke definition</dd>` elements for each keystroke in a group.
33
+ */
34
+ private _createGroupRow;
35
+ }
@@ -0,0 +1,112 @@
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/editorui/accessibilityhelp/accessibilityhelpcontentview
7
+ */
8
+ import { createElement, env, getEnvKeystrokeText } from '@ckeditor/ckeditor5-utils';
9
+ import View from '../../view.js';
10
+ import LabelView from '../../label/labelview.js';
11
+ /**
12
+ * The view displaying keystrokes in the Accessibility help dialog.
13
+ */
14
+ export default class AccessibilityHelpContentView extends View {
15
+ /**
16
+ * @inheritDoc
17
+ */
18
+ constructor(locale, keystrokes) {
19
+ super(locale);
20
+ const t = locale.t;
21
+ const helpLabel = new LabelView();
22
+ helpLabel.text = t('Help Contents. To close this dialog press ESC.');
23
+ this.setTemplate({
24
+ tag: 'div',
25
+ attributes: {
26
+ class: ['ck', 'ck-accessibility-help-dialog__content'],
27
+ 'aria-labelledby': helpLabel.id,
28
+ role: 'document',
29
+ tabindex: -1
30
+ },
31
+ children: [
32
+ createElement(document, 'p', {}, t('Below, you can find a list of keyboard shortcuts that can be used in the editor.')),
33
+ ...this._createCategories(Array.from(keystrokes.values())),
34
+ helpLabel
35
+ ]
36
+ });
37
+ }
38
+ /**
39
+ * @inheritDoc
40
+ */
41
+ focus() {
42
+ this.element.focus();
43
+ }
44
+ /**
45
+ * Creates `<section><h3>Category label</h3>...</section>` elements for each category of keystrokes.
46
+ */
47
+ _createCategories(categories) {
48
+ return categories.map(categoryDefinition => {
49
+ const elements = [
50
+ // Category header.
51
+ createElement(document, 'h3', {}, categoryDefinition.label),
52
+ // Category definitions (<dl>) and their optional headers (<h4>).
53
+ ...Array.from(categoryDefinition.groups.values())
54
+ .map(groupDefinition => this._createGroup(groupDefinition))
55
+ .flat()
56
+ ];
57
+ // Category description (<p>).
58
+ if (categoryDefinition.description) {
59
+ elements.splice(1, 0, createElement(document, 'p', {}, categoryDefinition.description));
60
+ }
61
+ return createElement(document, 'section', {}, elements);
62
+ });
63
+ }
64
+ /**
65
+ * Creates `[<h4>Optional label</h4>]<dl>...</dl>` elements for each group of keystrokes in a category.
66
+ */
67
+ _createGroup(groupDefinition) {
68
+ const definitionAndDescriptionElements = groupDefinition.keystrokes
69
+ .sort((a, b) => a.label.localeCompare(b.label))
70
+ .map(keystrokeDefinition => this._createGroupRow(keystrokeDefinition))
71
+ .flat();
72
+ const elements = [
73
+ createElement(document, 'dl', {}, definitionAndDescriptionElements)
74
+ ];
75
+ if (groupDefinition.label) {
76
+ elements.unshift(createElement(document, 'h4', {}, groupDefinition.label));
77
+ }
78
+ return elements;
79
+ }
80
+ /**
81
+ * Creates `<dt>Keystroke label</dt><dd>Keystroke definition</dd>` elements for each keystroke in a group.
82
+ */
83
+ _createGroupRow(keystrokeDefinition) {
84
+ const t = this.locale.t;
85
+ const dt = createElement(document, 'dt');
86
+ const dd = createElement(document, 'dd');
87
+ const normalizedKeystrokeDefinition = normalizeKeystrokeDefinition(keystrokeDefinition.keystroke);
88
+ const keystrokeAlternativeHTMLs = [];
89
+ for (const keystrokeAlternative of normalizedKeystrokeDefinition) {
90
+ keystrokeAlternativeHTMLs.push(keystrokeAlternative.map(keystrokeToEnvKbd).join(''));
91
+ }
92
+ dt.innerHTML = keystrokeDefinition.label;
93
+ dd.innerHTML = keystrokeAlternativeHTMLs.join(', ') +
94
+ (keystrokeDefinition.mayRequireFn && env.isMac ? ` ${t('(may require <kbd>Fn</kbd>)')}` : '');
95
+ return [dt, dd];
96
+ }
97
+ }
98
+ function keystrokeToEnvKbd(keystroke) {
99
+ return getEnvKeystrokeText(keystroke)
100
+ .split('+')
101
+ .map(part => `<kbd>${part}</kbd>`)
102
+ .join('+');
103
+ }
104
+ function normalizeKeystrokeDefinition(definition) {
105
+ if (typeof definition === 'string') {
106
+ return [[definition]];
107
+ }
108
+ if (typeof definition[0] === 'string') {
109
+ return [definition];
110
+ }
111
+ return definition;
112
+ }
@@ -58,7 +58,8 @@ export default class FormHeaderView extends View {
58
58
  class: [
59
59
  'ck',
60
60
  'ck-form__header__label'
61
- ]
61
+ ],
62
+ role: 'presentation'
62
63
  },
63
64
  children: [
64
65
  { text: bind.to('label') }
package/src/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { default as injectCssTransitionDisabler } from './bindings/injectcsstran
10
10
  export { default as CssTransitionDisablerMixin, type ViewWithCssTransitionDisabler } from './bindings/csstransitiondisablermixin.js';
11
11
  export { default as submitHandler } from './bindings/submithandler.js';
12
12
  export { default as addKeyboardHandlingForGrid } from './bindings/addkeyboardhandlingforgrid.js';
13
+ export { default as AccessibilityHelp } from './editorui/accessibilityhelp/accessibilityhelp.js';
13
14
  export { default as BodyCollection } from './editorui/bodycollection.js';
14
15
  export { type ButtonExecuteEvent } from './button/button.js';
15
16
  export type { default as ButtonLabel } from './button/buttonlabel.js';
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ export { default as injectCssTransitionDisabler } from './bindings/injectcsstran
10
10
  export { default as CssTransitionDisablerMixin } from './bindings/csstransitiondisablermixin.js';
11
11
  export { default as submitHandler } from './bindings/submithandler.js';
12
12
  export { default as addKeyboardHandlingForGrid } from './bindings/addkeyboardhandlingforgrid.js';
13
+ export { default as AccessibilityHelp } from './editorui/accessibilityhelp/accessibilityhelp.js';
13
14
  export { default as BodyCollection } from './editorui/bodycollection.js';
14
15
  export { default as ButtonView } from './button/buttonview.js';
15
16
  export { default as ButtonLabelView } from './button/buttonlabelview.js';
@@ -68,11 +68,6 @@ export default class BalloonToolbar extends Plugin {
68
68
  * @inheritDoc
69
69
  */
70
70
  init(): void;
71
- /**
72
- * Creates toolbar components based on given configuration.
73
- * This needs to be done when all plugins are ready.
74
- */
75
- afterInit(): void;
76
71
  /**
77
72
  * Creates the toolbar view instance.
78
73
  */
@@ -117,14 +117,11 @@ export default class BalloonToolbar extends Plugin {
117
117
  this.listenTo(this.toolbarView, 'groupedItemsUpdate', () => {
118
118
  this._updatePosition();
119
119
  });
120
- }
121
- /**
122
- * Creates toolbar components based on given configuration.
123
- * This needs to be done when all plugins are ready.
124
- */
125
- afterInit() {
126
- const factory = this.editor.ui.componentFactory;
127
- this.toolbarView.fillFromConfig(this._balloonConfig, factory);
120
+ // Creates toolbar components based on given configuration.
121
+ // This needs to be done when all plugins are ready.
122
+ editor.ui.once('ready', () => {
123
+ this.toolbarView.fillFromConfig(this._balloonConfig, this.editor.ui.componentFactory);
124
+ });
128
125
  }
129
126
  /**
130
127
  * Creates the toolbar view instance.
@@ -75,8 +75,6 @@ export default class BlockToolbar extends Plugin {
75
75
  *
76
76
  * **Note**: Used only when `shouldNotGroupWhenFull` was **not** set in the
77
77
  * {@link module:core/editor/editorconfig~EditorConfig#blockToolbar configuration}.
78
- *
79
- * **Note:** Created in {@link #afterInit}.
80
78
  */
81
79
  private _resizeObserver;
82
80
  /**
@@ -95,12 +93,6 @@ export default class BlockToolbar extends Plugin {
95
93
  * @inheritDoc
96
94
  */
97
95
  init(): void;
98
- /**
99
- * Fills the toolbar with its items based on the configuration.
100
- *
101
- * **Note:** This needs to be done after all plugins are ready.
102
- */
103
- afterInit(): void;
104
96
  /**
105
97
  * @inheritDoc
106
98
  */
@@ -79,8 +79,6 @@ export default class BlockToolbar extends Plugin {
79
79
  *
80
80
  * **Note**: Used only when `shouldNotGroupWhenFull` was **not** set in the
81
81
  * {@link module:core/editor/editorconfig~EditorConfig#blockToolbar configuration}.
82
- *
83
- * **Note:** Created in {@link #afterInit}.
84
82
  */
85
83
  this._resizeObserver = null;
86
84
  this._blockToolbarConfig = normalizeToolbarConfig(this.editor.config.get('blockToolbar'));
@@ -138,18 +136,15 @@ export default class BlockToolbar extends Plugin {
138
136
  beforeFocus: () => this._showPanel(),
139
137
  afterBlur: () => this._hidePanel()
140
138
  });
141
- }
142
- /**
143
- * Fills the toolbar with its items based on the configuration.
144
- *
145
- * **Note:** This needs to be done after all plugins are ready.
146
- */
147
- afterInit() {
148
- this.toolbarView.fillFromConfig(this._blockToolbarConfig, this.editor.ui.componentFactory);
149
- // Hide panel before executing each button in the panel.
150
- for (const item of this.toolbarView.items) {
151
- item.on('execute', () => this._hidePanel(true), { priority: 'high' });
152
- }
139
+ // Fills the toolbar with its items based on the configuration.
140
+ // This needs to be done after all plugins are ready.
141
+ editor.ui.once('ready', () => {
142
+ this.toolbarView.fillFromConfig(this._blockToolbarConfig, this.editor.ui.componentFactory);
143
+ // Hide panel before executing each button in the panel.
144
+ for (const item of this.toolbarView.items) {
145
+ item.on('execute', () => this._hidePanel(true), { priority: 'high' });
146
+ }
147
+ });
153
148
  }
154
149
  /**
155
150
  * @inheritDoc
@@ -100,6 +100,10 @@ export default class TooltipManager extends TooltipManager_base {
100
100
  * {@link module:core/editor/editorconfig~EditorConfig#balloonToolbar configuration}.
101
101
  */
102
102
  private _resizeObserver;
103
+ /**
104
+ * An instance of the mutation observer that keeps track on target element attributes changes.
105
+ */
106
+ private _mutationObserver;
103
107
  /**
104
108
  * A debounced version of {@link #_pinTooltip}. Tooltips show with a delay to avoid flashing and
105
109
  * to improve the UX.
@@ -172,7 +176,7 @@ export default class TooltipManager extends TooltipManager_base {
172
176
  /**
173
177
  * Updates the position of the tooltip so it stays in sync with the element it is pinned to.
174
178
  *
175
- * Hides the tooltip when the element is no longer visible in DOM.
179
+ * Hides the tooltip when the element is no longer visible in DOM or the tooltip text was removed.
176
180
  */
177
181
  private _updateTooltipPosition;
178
182
  }
@@ -87,6 +87,10 @@ class TooltipManager extends DomEmitterMixin() {
87
87
  * {@link module:core/editor/editorconfig~EditorConfig#balloonToolbar configuration}.
88
88
  */
89
89
  this._resizeObserver = null;
90
+ /**
91
+ * An instance of the mutation observer that keeps track on target element attributes changes.
92
+ */
93
+ this._mutationObserver = null;
90
94
  TooltipManager._editors.add(editor);
91
95
  // TooltipManager must be a singleton. Multiple instances would mean multiple tooltips attached
92
96
  // to the same DOM element with data-cke-tooltip-* attributes.
@@ -113,6 +117,9 @@ class TooltipManager extends DomEmitterMixin() {
113
117
  this.balloonPanelView = new BalloonPanelView(editor.locale);
114
118
  this.balloonPanelView.class = BALLOON_CLASS;
115
119
  this.balloonPanelView.content.add(this.tooltipTextView);
120
+ this._mutationObserver = createMutationObserver(() => {
121
+ this._updateTooltipPosition();
122
+ });
116
123
  this._pinTooltipDebounced = debounce(this._pinTooltip, 600);
117
124
  this.listenTo(global.document, 'mouseenter', this._onEnterOrFocus.bind(this), { useCapture: true });
118
125
  this.listenTo(global.document, 'mouseleave', this._onLeaveOrBlur.bind(this), { useCapture: true });
@@ -275,6 +282,7 @@ class TooltipManager extends DomEmitterMixin() {
275
282
  this._unpinTooltip();
276
283
  }
277
284
  });
285
+ this._mutationObserver.attach(targetDomElement);
278
286
  this.balloonPanelView.class = [BALLOON_CLASS, cssClass]
279
287
  .filter(className => className)
280
288
  .join(' ');
@@ -301,22 +309,24 @@ class TooltipManager extends DomEmitterMixin() {
301
309
  if (this._resizeObserver) {
302
310
  this._resizeObserver.destroy();
303
311
  }
312
+ this._mutationObserver.detach();
304
313
  }
305
314
  /**
306
315
  * Updates the position of the tooltip so it stays in sync with the element it is pinned to.
307
316
  *
308
- * Hides the tooltip when the element is no longer visible in DOM.
317
+ * Hides the tooltip when the element is no longer visible in DOM or the tooltip text was removed.
309
318
  */
310
319
  _updateTooltipPosition() {
320
+ const tooltipData = getTooltipData(this._currentElementWithTooltip);
311
321
  // This could happen if the tooltip was attached somewhere in a contextual content toolbar and the toolbar
312
- // disappeared (e.g. removed an image).
313
- if (!isVisible(this._currentElementWithTooltip)) {
322
+ // disappeared (e.g. removed an image), or the tooltip text was removed.
323
+ if (!isVisible(this._currentElementWithTooltip) || !tooltipData.text) {
314
324
  this._unpinTooltip();
315
325
  return;
316
326
  }
317
327
  this.balloonPanelView.pin({
318
328
  target: this._currentElementWithTooltip,
319
- positions: TooltipManager.getPositioningFunctions(this._currentTooltipPosition)
329
+ positions: TooltipManager.getPositioningFunctions(tooltipData.position)
320
330
  });
321
331
  }
322
332
  }
@@ -352,3 +362,21 @@ function getTooltipData(element) {
352
362
  cssClass: element.dataset.ckeTooltipClass || ''
353
363
  };
354
364
  }
365
+ // Creates a simple `MutationObserver` instance wrapper that observes changes in the tooltip-related attributes of the given element.
366
+ // Used instead of the `MutationObserver` from the engine for simplicity.
367
+ function createMutationObserver(callback) {
368
+ const mutationObserver = new MutationObserver(() => {
369
+ callback();
370
+ });
371
+ return {
372
+ attach(element) {
373
+ mutationObserver.observe(element, {
374
+ attributes: true,
375
+ attributeFilter: ['data-cke-tooltip-text', 'data-cke-tooltip-position']
376
+ });
377
+ },
378
+ detach() {
379
+ mutationObserver.disconnect();
380
+ }
381
+ };
382
+ }
@@ -0,0 +1,10 @@
1
+ /*
2
+ * 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
+ /*
7
+ * Note: This file should contain the wireframe styles only. But since there are no such styles,
8
+ * it acts as a message to the builder telling that it should look for the corresponding styles
9
+ * **in the theme** when compiling the editor.
10
+ */
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 6.628a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/><path d="M8.5 9.125a.3.3 0 0 0-.253-.296L5.11 8.327a.75.75 0 1 1 .388-1.449l4.04.716c.267.072.624.08.893.009l4.066-.724a.75.75 0 1 1 .388 1.45l-3.132.5a.3.3 0 0 0-.253.296v1.357a.3.3 0 0 0 .018.102l1.615 4.438a.75.75 0 0 1-1.41.513l-1.35-3.71a.3.3 0 0 0-.281-.197h-.209a.3.3 0 0 0-.282.198l-1.35 3.711a.75.75 0 0 1-1.41-.513l1.64-4.509a.3.3 0 0 0 .019-.103V9.125Z"/><path clip-rule="evenodd" d="M10 18.5a8.5 8.5 0 1 1 0-17 8.5 8.5 0 0 1 0 17Zm0 1.5c5.523 0 10-4.477 10-10S15.523 0 10 0 0 4.477 0 10s4.477 10 10 10Z"/></svg>