@ckeditor/ckeditor5-ui 40.0.0 → 40.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/LICENSE.md +3 -3
  2. package/lang/translations/pt-br.po +1 -1
  3. package/lang/translations/ug.po +26 -26
  4. package/package.json +3 -3
  5. package/src/arialiveannouncer.d.ts +94 -0
  6. package/src/arialiveannouncer.js +113 -0
  7. package/src/augmentation.d.ts +86 -86
  8. package/src/augmentation.js +5 -5
  9. package/src/autocomplete/autocompleteview.d.ts +81 -81
  10. package/src/autocomplete/autocompleteview.js +153 -146
  11. package/src/bindings/addkeyboardhandlingforgrid.d.ts +27 -27
  12. package/src/bindings/addkeyboardhandlingforgrid.js +107 -107
  13. package/src/bindings/clickoutsidehandler.d.ts +28 -28
  14. package/src/bindings/clickoutsidehandler.js +36 -36
  15. package/src/bindings/csstransitiondisablermixin.d.ts +40 -40
  16. package/src/bindings/csstransitiondisablermixin.js +55 -55
  17. package/src/bindings/injectcsstransitiondisabler.d.ts +59 -59
  18. package/src/bindings/injectcsstransitiondisabler.js +71 -71
  19. package/src/bindings/preventdefault.d.ts +33 -33
  20. package/src/bindings/preventdefault.js +34 -34
  21. package/src/bindings/submithandler.d.ts +57 -57
  22. package/src/bindings/submithandler.js +47 -47
  23. package/src/button/button.d.ts +172 -178
  24. package/src/button/button.js +5 -5
  25. package/src/button/buttonlabel.d.ts +34 -34
  26. package/src/button/buttonlabel.js +5 -5
  27. package/src/button/buttonlabelview.d.ts +31 -31
  28. package/src/button/buttonlabelview.js +42 -42
  29. package/src/button/buttonview.d.ts +181 -185
  30. package/src/button/buttonview.js +217 -219
  31. package/src/button/switchbuttonview.d.ts +45 -45
  32. package/src/button/switchbuttonview.js +75 -75
  33. package/src/colorgrid/colorgridview.d.ts +132 -132
  34. package/src/colorgrid/colorgridview.js +124 -124
  35. package/src/colorgrid/colortileview.d.ts +28 -28
  36. package/src/colorgrid/colortileview.js +40 -40
  37. package/src/colorgrid/utils.d.ts +47 -47
  38. package/src/colorgrid/utils.js +84 -84
  39. package/src/colorpicker/colorpickerview.d.ts +137 -137
  40. package/src/colorpicker/colorpickerview.js +270 -270
  41. package/src/colorpicker/utils.d.ts +43 -43
  42. package/src/colorpicker/utils.js +99 -99
  43. package/src/colorselector/colorgridsfragmentview.d.ts +194 -194
  44. package/src/colorselector/colorgridsfragmentview.js +289 -289
  45. package/src/colorselector/colorpickerfragmentview.d.ts +128 -128
  46. package/src/colorselector/colorpickerfragmentview.js +205 -205
  47. package/src/colorselector/colorselectorview.d.ts +242 -242
  48. package/src/colorselector/colorselectorview.js +256 -256
  49. package/src/colorselector/documentcolorcollection.d.ts +70 -70
  50. package/src/colorselector/documentcolorcollection.js +42 -42
  51. package/src/componentfactory.d.ts +81 -81
  52. package/src/componentfactory.js +104 -104
  53. package/src/dropdown/button/dropdownbutton.d.ts +25 -25
  54. package/src/dropdown/button/dropdownbutton.js +5 -5
  55. package/src/dropdown/button/dropdownbuttonview.d.ts +48 -48
  56. package/src/dropdown/button/dropdownbuttonview.js +66 -66
  57. package/src/dropdown/button/splitbuttonview.d.ts +161 -161
  58. package/src/dropdown/button/splitbuttonview.js +152 -152
  59. package/src/dropdown/dropdownpanelfocusable.d.ts +21 -21
  60. package/src/dropdown/dropdownpanelfocusable.js +5 -5
  61. package/src/dropdown/dropdownpanelview.d.ts +62 -62
  62. package/src/dropdown/dropdownpanelview.js +97 -97
  63. package/src/dropdown/dropdownview.d.ts +315 -315
  64. package/src/dropdown/dropdownview.js +379 -379
  65. package/src/dropdown/utils.d.ts +235 -235
  66. package/src/dropdown/utils.js +463 -458
  67. package/src/editableui/editableuiview.d.ts +72 -72
  68. package/src/editableui/editableuiview.js +112 -112
  69. package/src/editableui/inline/inlineeditableuiview.d.ts +40 -40
  70. package/src/editableui/inline/inlineeditableuiview.js +48 -48
  71. package/src/editorui/bodycollection.d.ts +55 -55
  72. package/src/editorui/bodycollection.js +84 -84
  73. package/src/editorui/boxed/boxededitoruiview.d.ts +40 -40
  74. package/src/editorui/boxed/boxededitoruiview.js +81 -81
  75. package/src/editorui/editorui.d.ts +288 -282
  76. package/src/editorui/editorui.js +412 -410
  77. package/src/editorui/editoruiview.d.ts +39 -39
  78. package/src/editorui/editoruiview.js +38 -38
  79. package/src/editorui/poweredby.d.ts +71 -71
  80. package/src/editorui/poweredby.js +276 -276
  81. package/src/focuscycler.d.ts +226 -226
  82. package/src/focuscycler.js +245 -245
  83. package/src/formheader/formheaderview.d.ts +59 -59
  84. package/src/formheader/formheaderview.js +69 -69
  85. package/src/highlightedtext/highlightedtextview.d.ts +38 -38
  86. package/src/highlightedtext/highlightedtextview.js +102 -102
  87. package/src/icon/iconview.d.ts +85 -85
  88. package/src/icon/iconview.js +114 -114
  89. package/src/iframe/iframeview.d.ts +50 -50
  90. package/src/iframe/iframeview.js +63 -63
  91. package/src/index.d.ts +73 -73
  92. package/src/index.js +70 -70
  93. package/src/input/inputbase.d.ts +107 -107
  94. package/src/input/inputbase.js +110 -110
  95. package/src/input/inputview.d.ts +36 -36
  96. package/src/input/inputview.js +24 -24
  97. package/src/inputnumber/inputnumberview.d.ts +49 -49
  98. package/src/inputnumber/inputnumberview.js +40 -40
  99. package/src/inputtext/inputtextview.d.ts +18 -18
  100. package/src/inputtext/inputtextview.js +27 -27
  101. package/src/label/labelview.d.ts +36 -36
  102. package/src/label/labelview.js +41 -41
  103. package/src/labeledfield/labeledfieldview.d.ts +187 -187
  104. package/src/labeledfield/labeledfieldview.js +157 -157
  105. package/src/labeledfield/utils.d.ts +123 -123
  106. package/src/labeledfield/utils.js +176 -176
  107. package/src/labeledinput/labeledinputview.d.ts +125 -125
  108. package/src/labeledinput/labeledinputview.js +125 -125
  109. package/src/list/listitemgroupview.d.ts +59 -51
  110. package/src/list/listitemgroupview.js +63 -75
  111. package/src/list/listitemview.d.ts +36 -36
  112. package/src/list/listitemview.js +42 -42
  113. package/src/list/listseparatorview.d.ts +18 -18
  114. package/src/list/listseparatorview.js +28 -28
  115. package/src/list/listview.d.ts +122 -122
  116. package/src/list/listview.js +187 -187
  117. package/src/model.d.ts +22 -22
  118. package/src/model.js +31 -31
  119. package/src/notification/notification.d.ts +211 -211
  120. package/src/notification/notification.js +187 -187
  121. package/src/panel/balloon/balloonpanelview.d.ts +685 -685
  122. package/src/panel/balloon/balloonpanelview.js +1010 -1010
  123. package/src/panel/balloon/contextualballoon.d.ts +299 -299
  124. package/src/panel/balloon/contextualballoon.js +572 -572
  125. package/src/panel/sticky/stickypanelview.d.ts +156 -156
  126. package/src/panel/sticky/stickypanelview.js +234 -234
  127. package/src/search/filteredview.d.ts +31 -31
  128. package/src/search/filteredview.js +5 -5
  129. package/src/search/searchinfoview.d.ts +45 -45
  130. package/src/search/searchinfoview.js +59 -59
  131. package/src/search/searchresultsview.d.ts +54 -54
  132. package/src/search/searchresultsview.js +65 -65
  133. package/src/search/text/searchtextqueryview.d.ts +76 -76
  134. package/src/search/text/searchtextqueryview.js +75 -75
  135. package/src/search/text/searchtextview.d.ts +219 -219
  136. package/src/search/text/searchtextview.js +201 -201
  137. package/src/spinner/spinnerview.d.ts +25 -25
  138. package/src/spinner/spinnerview.js +38 -38
  139. package/src/template.d.ts +942 -942
  140. package/src/template.js +1294 -1294
  141. package/src/textarea/textareaview.d.ts +88 -88
  142. package/src/textarea/textareaview.js +142 -140
  143. package/src/toolbar/balloon/balloontoolbar.d.ts +122 -122
  144. package/src/toolbar/balloon/balloontoolbar.js +300 -300
  145. package/src/toolbar/block/blockbuttonview.d.ts +35 -35
  146. package/src/toolbar/block/blockbuttonview.js +41 -41
  147. package/src/toolbar/block/blocktoolbar.d.ts +161 -161
  148. package/src/toolbar/block/blocktoolbar.js +395 -395
  149. package/src/toolbar/normalizetoolbarconfig.d.ts +40 -40
  150. package/src/toolbar/normalizetoolbarconfig.js +52 -51
  151. package/src/toolbar/toolbarlinebreakview.d.ts +18 -18
  152. package/src/toolbar/toolbarlinebreakview.js +28 -28
  153. package/src/toolbar/toolbarseparatorview.d.ts +18 -18
  154. package/src/toolbar/toolbarseparatorview.js +28 -28
  155. package/src/toolbar/toolbarview.d.ts +266 -266
  156. package/src/toolbar/toolbarview.js +719 -719
  157. package/src/tooltipmanager.d.ts +180 -180
  158. package/src/tooltipmanager.js +353 -353
  159. package/src/view.d.ts +422 -422
  160. package/src/view.js +396 -396
  161. package/src/viewcollection.d.ts +139 -139
  162. package/src/viewcollection.js +206 -206
  163. package/theme/components/arialiveannouncer/arialiveannouncer.css +10 -0
  164. package/theme/components/button/button.css +9 -1
  165. package/theme/components/formheader/formheader.css +0 -4
@@ -1,276 +1,276 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, 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 { DomEmitterMixin, Rect, verifyLicense } from '@ckeditor/ckeditor5-utils';
6
- import BalloonPanelView from '../panel/balloon/balloonpanelview';
7
- import IconView from '../icon/iconview';
8
- import View from '../view';
9
- import { throttle } from 'lodash-es';
10
- import poweredByIcon from '../../theme/icons/project-logo.svg';
11
- const ICON_WIDTH = 53;
12
- const ICON_HEIGHT = 10;
13
- // ⚠ Note, whenever changing the threshold, make sure to update the docs/support/managing-ckeditor-logo.md docs
14
- // as this information is also mentioned there ⚠.
15
- const NARROW_ROOT_HEIGHT_THRESHOLD = 50;
16
- const NARROW_ROOT_WIDTH_THRESHOLD = 350;
17
- const DEFAULT_LABEL = 'Powered by';
18
- /**
19
- * A helper that enables the "powered by" feature in the editor and renders a link to the project's
20
- * webpage next to the bottom of the editable element (editor root, source editing area, etc.) when the editor is focused.
21
- *
22
- * @private
23
- */
24
- export default class PoweredBy extends DomEmitterMixin() {
25
- /**
26
- * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready
27
- * event.
28
- *
29
- * @param editor
30
- */
31
- constructor(editor) {
32
- super();
33
- this.editor = editor;
34
- this._balloonView = null;
35
- this._lastFocusedEditableElement = null;
36
- this._showBalloonThrottled = throttle(this._showBalloon.bind(this), 50, { leading: true });
37
- editor.on('ready', this._handleEditorReady.bind(this));
38
- }
39
- /**
40
- * Destroys the "powered by" helper along with its view.
41
- */
42
- destroy() {
43
- const balloon = this._balloonView;
44
- if (balloon) {
45
- // Balloon gets destroyed by the body collection.
46
- // The powered by view gets destroyed by the balloon.
47
- balloon.unpin();
48
- this._balloonView = null;
49
- }
50
- this._showBalloonThrottled.cancel();
51
- this.stopListening();
52
- }
53
- /**
54
- * Enables "powered by" label once the editor (ui) is ready.
55
- */
56
- _handleEditorReady() {
57
- const editor = this.editor;
58
- const forceVisible = !!editor.config.get('ui.poweredBy.forceVisible');
59
- /* istanbul ignore next -- @preserve */
60
- if (!forceVisible && verifyLicense(editor.config.get('licenseKey')) === 'VALID') {
61
- return;
62
- }
63
- // No view means no body collection to append the powered by balloon to.
64
- if (!editor.ui.view) {
65
- return;
66
- }
67
- editor.ui.focusTracker.on('change:isFocused', (evt, data, isFocused) => {
68
- this._updateLastFocusedEditableElement();
69
- if (isFocused) {
70
- this._showBalloon();
71
- }
72
- else {
73
- this._hideBalloon();
74
- }
75
- });
76
- editor.ui.focusTracker.on('change:focusedElement', (evt, data, focusedElement) => {
77
- this._updateLastFocusedEditableElement();
78
- if (focusedElement) {
79
- this._showBalloon();
80
- }
81
- });
82
- editor.ui.on('update', () => {
83
- this._showBalloonThrottled();
84
- });
85
- }
86
- /**
87
- * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel}
88
- * with the "powered by" view inside ready for positioning.
89
- */
90
- _createBalloonView() {
91
- const editor = this.editor;
92
- const balloon = this._balloonView = new BalloonPanelView();
93
- const poweredByConfig = getNormalizedConfig(editor);
94
- const view = new PoweredByView(editor.locale, poweredByConfig.label);
95
- balloon.content.add(view);
96
- balloon.set({
97
- class: 'ck-powered-by-balloon'
98
- });
99
- editor.ui.view.body.add(balloon);
100
- editor.ui.focusTracker.add(balloon.element);
101
- this._balloonView = balloon;
102
- }
103
- /**
104
- * Attempts to display the balloon with the "powered by" view.
105
- */
106
- _showBalloon() {
107
- if (!this._lastFocusedEditableElement) {
108
- return;
109
- }
110
- const attachOptions = getBalloonAttachOptions(this.editor, this._lastFocusedEditableElement);
111
- if (attachOptions) {
112
- if (!this._balloonView) {
113
- this._createBalloonView();
114
- }
115
- this._balloonView.pin(attachOptions);
116
- }
117
- }
118
- /**
119
- * Hides the "powered by" balloon if already visible.
120
- */
121
- _hideBalloon() {
122
- if (this._balloonView) {
123
- this._balloonView.unpin();
124
- }
125
- }
126
- /**
127
- * Updates the {@link #_lastFocusedEditableElement} based on the state of the global focus tracker.
128
- */
129
- _updateLastFocusedEditableElement() {
130
- const editor = this.editor;
131
- const isFocused = editor.ui.focusTracker.isFocused;
132
- const focusedElement = editor.ui.focusTracker.focusedElement;
133
- if (!isFocused || !focusedElement) {
134
- this._lastFocusedEditableElement = null;
135
- return;
136
- }
137
- const editableEditorElements = Array.from(editor.ui.getEditableElementsNames()).map(name => {
138
- return editor.ui.getEditableElement(name);
139
- });
140
- if (editableEditorElements.includes(focusedElement)) {
141
- this._lastFocusedEditableElement = focusedElement;
142
- }
143
- else {
144
- // If it's none of the editable element, then the focus is somewhere in the UI. Let's display powered by
145
- // over the first element then.
146
- this._lastFocusedEditableElement = editableEditorElements[0];
147
- }
148
- }
149
- }
150
- /**
151
- * A view displaying a "powered by" label and project logo wrapped in a link.
152
- */
153
- class PoweredByView extends View {
154
- /**
155
- * Created an instance of the "powered by" view.
156
- *
157
- * @param locale The localization services instance.
158
- * @param label The label text.
159
- */
160
- constructor(locale, label) {
161
- super(locale);
162
- const iconView = new IconView();
163
- const bind = this.bindTemplate;
164
- iconView.set({
165
- content: poweredByIcon,
166
- isColorInherited: false
167
- });
168
- iconView.extendTemplate({
169
- attributes: {
170
- style: {
171
- width: ICON_WIDTH + 'px',
172
- height: ICON_HEIGHT + 'px'
173
- }
174
- }
175
- });
176
- this.setTemplate({
177
- tag: 'div',
178
- attributes: {
179
- class: ['ck', 'ck-powered-by'],
180
- 'aria-hidden': true
181
- },
182
- children: [
183
- {
184
- tag: 'a',
185
- attributes: {
186
- href: 'https://ckeditor.com/?utm_source=ckeditor&' +
187
- 'utm_medium=referral&utm_campaign=701Dn000000hVgmIAE_powered_by_ckeditor_logo',
188
- target: '_blank',
189
- tabindex: '-1'
190
- },
191
- children: [
192
- ...label ? [
193
- {
194
- tag: 'span',
195
- attributes: {
196
- class: ['ck', 'ck-powered-by__label']
197
- },
198
- children: [label]
199
- }
200
- ] : [],
201
- iconView
202
- ],
203
- on: {
204
- dragstart: bind.to(evt => evt.preventDefault())
205
- }
206
- }
207
- ]
208
- });
209
- }
210
- }
211
- function getBalloonAttachOptions(editor, focusedEditableElement) {
212
- const poweredByConfig = getNormalizedConfig(editor);
213
- const positioningFunction = poweredByConfig.side === 'right' ?
214
- getLowerRightCornerPosition(focusedEditableElement, poweredByConfig) :
215
- getLowerLeftCornerPosition(focusedEditableElement, poweredByConfig);
216
- return {
217
- target: focusedEditableElement,
218
- positions: [positioningFunction]
219
- };
220
- }
221
- function getLowerRightCornerPosition(focusedEditableElement, config) {
222
- return getLowerCornerPosition(focusedEditableElement, config, (rootRect, balloonRect) => {
223
- return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset;
224
- });
225
- }
226
- function getLowerLeftCornerPosition(focusedEditableElement, config) {
227
- return getLowerCornerPosition(focusedEditableElement, config, rootRect => rootRect.left + config.horizontalOffset);
228
- }
229
- function getLowerCornerPosition(focusedEditableElement, config, getBalloonLeft) {
230
- return (visibleEditableElementRect, balloonRect) => {
231
- const editableElementRect = new Rect(focusedEditableElement);
232
- if (editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD) {
233
- return null;
234
- }
235
- let balloonTop;
236
- if (config.position === 'inside') {
237
- balloonTop = editableElementRect.bottom - balloonRect.height;
238
- }
239
- else {
240
- balloonTop = editableElementRect.bottom - balloonRect.height / 2;
241
- }
242
- balloonTop -= config.verticalOffset;
243
- const balloonLeft = getBalloonLeft(editableElementRect, balloonRect);
244
- // Clone the editable element rect and place it where the balloon would be placed.
245
- // This will allow getVisible() to work from editable element's perspective (rect source).
246
- // and yield a result as if the balloon was on the same (scrollable) layer as the editable element.
247
- const newBalloonPositionRect = visibleEditableElementRect
248
- .clone()
249
- .moveTo(balloonLeft, balloonTop)
250
- .getIntersection(balloonRect.clone().moveTo(balloonLeft, balloonTop));
251
- const newBalloonPositionVisibleRect = newBalloonPositionRect.getVisible();
252
- if (!newBalloonPositionVisibleRect || newBalloonPositionVisibleRect.getArea() < balloonRect.getArea()) {
253
- return null;
254
- }
255
- return {
256
- top: balloonTop,
257
- left: balloonLeft,
258
- name: `position_${config.position}-side_${config.side}`,
259
- config: {
260
- withArrow: false
261
- }
262
- };
263
- };
264
- }
265
- function getNormalizedConfig(editor) {
266
- const userConfig = editor.config.get('ui.poweredBy');
267
- const position = userConfig && userConfig.position || 'border';
268
- return {
269
- position,
270
- label: DEFAULT_LABEL,
271
- verticalOffset: position === 'inside' ? 5 : 0,
272
- horizontalOffset: 5,
273
- side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left',
274
- ...userConfig
275
- };
276
- }
1
+ /**
2
+ * @license Copyright (c) 2003-2023, 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 { DomEmitterMixin, Rect, verifyLicense } from '@ckeditor/ckeditor5-utils';
6
+ import BalloonPanelView from '../panel/balloon/balloonpanelview';
7
+ import IconView from '../icon/iconview';
8
+ import View from '../view';
9
+ import { throttle } from 'lodash-es';
10
+ import poweredByIcon from '../../theme/icons/project-logo.svg';
11
+ const ICON_WIDTH = 53;
12
+ const ICON_HEIGHT = 10;
13
+ // ⚠ Note, whenever changing the threshold, make sure to update the docs/support/managing-ckeditor-logo.md docs
14
+ // as this information is also mentioned there ⚠.
15
+ const NARROW_ROOT_HEIGHT_THRESHOLD = 50;
16
+ const NARROW_ROOT_WIDTH_THRESHOLD = 350;
17
+ const DEFAULT_LABEL = 'Powered by';
18
+ /**
19
+ * A helper that enables the "powered by" feature in the editor and renders a link to the project's
20
+ * webpage next to the bottom of the editable element (editor root, source editing area, etc.) when the editor is focused.
21
+ *
22
+ * @private
23
+ */
24
+ export default class PoweredBy extends DomEmitterMixin() {
25
+ /**
26
+ * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready
27
+ * event.
28
+ *
29
+ * @param editor
30
+ */
31
+ constructor(editor) {
32
+ super();
33
+ this.editor = editor;
34
+ this._balloonView = null;
35
+ this._lastFocusedEditableElement = null;
36
+ this._showBalloonThrottled = throttle(this._showBalloon.bind(this), 50, { leading: true });
37
+ editor.on('ready', this._handleEditorReady.bind(this));
38
+ }
39
+ /**
40
+ * Destroys the "powered by" helper along with its view.
41
+ */
42
+ destroy() {
43
+ const balloon = this._balloonView;
44
+ if (balloon) {
45
+ // Balloon gets destroyed by the body collection.
46
+ // The powered by view gets destroyed by the balloon.
47
+ balloon.unpin();
48
+ this._balloonView = null;
49
+ }
50
+ this._showBalloonThrottled.cancel();
51
+ this.stopListening();
52
+ }
53
+ /**
54
+ * Enables "powered by" label once the editor (ui) is ready.
55
+ */
56
+ _handleEditorReady() {
57
+ const editor = this.editor;
58
+ const forceVisible = !!editor.config.get('ui.poweredBy.forceVisible');
59
+ /* istanbul ignore next -- @preserve */
60
+ if (!forceVisible && verifyLicense(editor.config.get('licenseKey')) === 'VALID') {
61
+ return;
62
+ }
63
+ // No view means no body collection to append the powered by balloon to.
64
+ if (!editor.ui.view) {
65
+ return;
66
+ }
67
+ editor.ui.focusTracker.on('change:isFocused', (evt, data, isFocused) => {
68
+ this._updateLastFocusedEditableElement();
69
+ if (isFocused) {
70
+ this._showBalloon();
71
+ }
72
+ else {
73
+ this._hideBalloon();
74
+ }
75
+ });
76
+ editor.ui.focusTracker.on('change:focusedElement', (evt, data, focusedElement) => {
77
+ this._updateLastFocusedEditableElement();
78
+ if (focusedElement) {
79
+ this._showBalloon();
80
+ }
81
+ });
82
+ editor.ui.on('update', () => {
83
+ this._showBalloonThrottled();
84
+ });
85
+ }
86
+ /**
87
+ * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel}
88
+ * with the "powered by" view inside ready for positioning.
89
+ */
90
+ _createBalloonView() {
91
+ const editor = this.editor;
92
+ const balloon = this._balloonView = new BalloonPanelView();
93
+ const poweredByConfig = getNormalizedConfig(editor);
94
+ const view = new PoweredByView(editor.locale, poweredByConfig.label);
95
+ balloon.content.add(view);
96
+ balloon.set({
97
+ class: 'ck-powered-by-balloon'
98
+ });
99
+ editor.ui.view.body.add(balloon);
100
+ editor.ui.focusTracker.add(balloon.element);
101
+ this._balloonView = balloon;
102
+ }
103
+ /**
104
+ * Attempts to display the balloon with the "powered by" view.
105
+ */
106
+ _showBalloon() {
107
+ if (!this._lastFocusedEditableElement) {
108
+ return;
109
+ }
110
+ const attachOptions = getBalloonAttachOptions(this.editor, this._lastFocusedEditableElement);
111
+ if (attachOptions) {
112
+ if (!this._balloonView) {
113
+ this._createBalloonView();
114
+ }
115
+ this._balloonView.pin(attachOptions);
116
+ }
117
+ }
118
+ /**
119
+ * Hides the "powered by" balloon if already visible.
120
+ */
121
+ _hideBalloon() {
122
+ if (this._balloonView) {
123
+ this._balloonView.unpin();
124
+ }
125
+ }
126
+ /**
127
+ * Updates the {@link #_lastFocusedEditableElement} based on the state of the global focus tracker.
128
+ */
129
+ _updateLastFocusedEditableElement() {
130
+ const editor = this.editor;
131
+ const isFocused = editor.ui.focusTracker.isFocused;
132
+ const focusedElement = editor.ui.focusTracker.focusedElement;
133
+ if (!isFocused || !focusedElement) {
134
+ this._lastFocusedEditableElement = null;
135
+ return;
136
+ }
137
+ const editableEditorElements = Array.from(editor.ui.getEditableElementsNames()).map(name => {
138
+ return editor.ui.getEditableElement(name);
139
+ });
140
+ if (editableEditorElements.includes(focusedElement)) {
141
+ this._lastFocusedEditableElement = focusedElement;
142
+ }
143
+ else {
144
+ // If it's none of the editable element, then the focus is somewhere in the UI. Let's display powered by
145
+ // over the first element then.
146
+ this._lastFocusedEditableElement = editableEditorElements[0];
147
+ }
148
+ }
149
+ }
150
+ /**
151
+ * A view displaying a "powered by" label and project logo wrapped in a link.
152
+ */
153
+ class PoweredByView extends View {
154
+ /**
155
+ * Created an instance of the "powered by" view.
156
+ *
157
+ * @param locale The localization services instance.
158
+ * @param label The label text.
159
+ */
160
+ constructor(locale, label) {
161
+ super(locale);
162
+ const iconView = new IconView();
163
+ const bind = this.bindTemplate;
164
+ iconView.set({
165
+ content: poweredByIcon,
166
+ isColorInherited: false
167
+ });
168
+ iconView.extendTemplate({
169
+ attributes: {
170
+ style: {
171
+ width: ICON_WIDTH + 'px',
172
+ height: ICON_HEIGHT + 'px'
173
+ }
174
+ }
175
+ });
176
+ this.setTemplate({
177
+ tag: 'div',
178
+ attributes: {
179
+ class: ['ck', 'ck-powered-by'],
180
+ 'aria-hidden': true
181
+ },
182
+ children: [
183
+ {
184
+ tag: 'a',
185
+ attributes: {
186
+ href: 'https://ckeditor.com/?utm_source=ckeditor&' +
187
+ 'utm_medium=referral&utm_campaign=701Dn000000hVgmIAE_powered_by_ckeditor_logo',
188
+ target: '_blank',
189
+ tabindex: '-1'
190
+ },
191
+ children: [
192
+ ...label ? [
193
+ {
194
+ tag: 'span',
195
+ attributes: {
196
+ class: ['ck', 'ck-powered-by__label']
197
+ },
198
+ children: [label]
199
+ }
200
+ ] : [],
201
+ iconView
202
+ ],
203
+ on: {
204
+ dragstart: bind.to(evt => evt.preventDefault())
205
+ }
206
+ }
207
+ ]
208
+ });
209
+ }
210
+ }
211
+ function getBalloonAttachOptions(editor, focusedEditableElement) {
212
+ const poweredByConfig = getNormalizedConfig(editor);
213
+ const positioningFunction = poweredByConfig.side === 'right' ?
214
+ getLowerRightCornerPosition(focusedEditableElement, poweredByConfig) :
215
+ getLowerLeftCornerPosition(focusedEditableElement, poweredByConfig);
216
+ return {
217
+ target: focusedEditableElement,
218
+ positions: [positioningFunction]
219
+ };
220
+ }
221
+ function getLowerRightCornerPosition(focusedEditableElement, config) {
222
+ return getLowerCornerPosition(focusedEditableElement, config, (rootRect, balloonRect) => {
223
+ return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset;
224
+ });
225
+ }
226
+ function getLowerLeftCornerPosition(focusedEditableElement, config) {
227
+ return getLowerCornerPosition(focusedEditableElement, config, rootRect => rootRect.left + config.horizontalOffset);
228
+ }
229
+ function getLowerCornerPosition(focusedEditableElement, config, getBalloonLeft) {
230
+ return (visibleEditableElementRect, balloonRect) => {
231
+ const editableElementRect = new Rect(focusedEditableElement);
232
+ if (editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD) {
233
+ return null;
234
+ }
235
+ let balloonTop;
236
+ if (config.position === 'inside') {
237
+ balloonTop = editableElementRect.bottom - balloonRect.height;
238
+ }
239
+ else {
240
+ balloonTop = editableElementRect.bottom - balloonRect.height / 2;
241
+ }
242
+ balloonTop -= config.verticalOffset;
243
+ const balloonLeft = getBalloonLeft(editableElementRect, balloonRect);
244
+ // Clone the editable element rect and place it where the balloon would be placed.
245
+ // This will allow getVisible() to work from editable element's perspective (rect source).
246
+ // and yield a result as if the balloon was on the same (scrollable) layer as the editable element.
247
+ const newBalloonPositionRect = visibleEditableElementRect
248
+ .clone()
249
+ .moveTo(balloonLeft, balloonTop)
250
+ .getIntersection(balloonRect.clone().moveTo(balloonLeft, balloonTop));
251
+ const newBalloonPositionVisibleRect = newBalloonPositionRect.getVisible();
252
+ if (!newBalloonPositionVisibleRect || newBalloonPositionVisibleRect.getArea() < balloonRect.getArea()) {
253
+ return null;
254
+ }
255
+ return {
256
+ top: balloonTop,
257
+ left: balloonLeft,
258
+ name: `position_${config.position}-side_${config.side}`,
259
+ config: {
260
+ withArrow: false
261
+ }
262
+ };
263
+ };
264
+ }
265
+ function getNormalizedConfig(editor) {
266
+ const userConfig = editor.config.get('ui.poweredBy');
267
+ const position = userConfig && userConfig.position || 'border';
268
+ return {
269
+ position,
270
+ label: DEFAULT_LABEL,
271
+ verticalOffset: position === 'inside' ? 5 : 0,
272
+ horizontalOffset: 5,
273
+ side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left',
274
+ ...userConfig
275
+ };
276
+ }