@ckeditor/ckeditor5-ui 40.0.0 → 40.2.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 (171) hide show
  1. package/CHANGELOG.md +26 -26
  2. package/LICENSE.md +3 -3
  3. package/lang/translations/gl.po +4 -4
  4. package/lang/translations/pt-br.po +1 -1
  5. package/lang/translations/sr-latn.po +6 -6
  6. package/lang/translations/ug.po +26 -26
  7. package/package.json +3 -3
  8. package/src/arialiveannouncer.d.ts +94 -0
  9. package/src/arialiveannouncer.js +113 -0
  10. package/src/augmentation.d.ts +86 -86
  11. package/src/augmentation.js +5 -5
  12. package/src/autocomplete/autocompleteview.d.ts +81 -81
  13. package/src/autocomplete/autocompleteview.js +153 -146
  14. package/src/bindings/addkeyboardhandlingforgrid.d.ts +27 -27
  15. package/src/bindings/addkeyboardhandlingforgrid.js +107 -107
  16. package/src/bindings/clickoutsidehandler.d.ts +28 -28
  17. package/src/bindings/clickoutsidehandler.js +36 -36
  18. package/src/bindings/csstransitiondisablermixin.d.ts +40 -40
  19. package/src/bindings/csstransitiondisablermixin.js +55 -55
  20. package/src/bindings/injectcsstransitiondisabler.d.ts +59 -59
  21. package/src/bindings/injectcsstransitiondisabler.js +71 -71
  22. package/src/bindings/preventdefault.d.ts +33 -33
  23. package/src/bindings/preventdefault.js +34 -34
  24. package/src/bindings/submithandler.d.ts +57 -57
  25. package/src/bindings/submithandler.js +47 -47
  26. package/src/button/button.d.ts +172 -178
  27. package/src/button/button.js +5 -5
  28. package/src/button/buttonlabel.d.ts +34 -34
  29. package/src/button/buttonlabel.js +5 -5
  30. package/src/button/buttonlabelview.d.ts +31 -31
  31. package/src/button/buttonlabelview.js +42 -42
  32. package/src/button/buttonview.d.ts +181 -185
  33. package/src/button/buttonview.js +217 -219
  34. package/src/button/switchbuttonview.d.ts +45 -45
  35. package/src/button/switchbuttonview.js +75 -75
  36. package/src/collapsible/collapsibleview.d.ts +69 -0
  37. package/src/collapsible/collapsibleview.js +95 -0
  38. package/src/colorgrid/colorgridview.d.ts +132 -132
  39. package/src/colorgrid/colorgridview.js +124 -124
  40. package/src/colorgrid/colortileview.d.ts +28 -28
  41. package/src/colorgrid/colortileview.js +40 -40
  42. package/src/colorgrid/utils.d.ts +47 -47
  43. package/src/colorgrid/utils.js +84 -84
  44. package/src/colorpicker/colorpickerview.d.ts +137 -137
  45. package/src/colorpicker/colorpickerview.js +270 -270
  46. package/src/colorpicker/utils.d.ts +43 -43
  47. package/src/colorpicker/utils.js +99 -99
  48. package/src/colorselector/colorgridsfragmentview.d.ts +194 -194
  49. package/src/colorselector/colorgridsfragmentview.js +289 -289
  50. package/src/colorselector/colorpickerfragmentview.d.ts +128 -128
  51. package/src/colorselector/colorpickerfragmentview.js +205 -205
  52. package/src/colorselector/colorselectorview.d.ts +242 -242
  53. package/src/colorselector/colorselectorview.js +256 -256
  54. package/src/colorselector/documentcolorcollection.d.ts +70 -70
  55. package/src/colorselector/documentcolorcollection.js +42 -42
  56. package/src/componentfactory.d.ts +81 -81
  57. package/src/componentfactory.js +104 -104
  58. package/src/dropdown/button/dropdownbutton.d.ts +25 -25
  59. package/src/dropdown/button/dropdownbutton.js +5 -5
  60. package/src/dropdown/button/dropdownbuttonview.d.ts +48 -48
  61. package/src/dropdown/button/dropdownbuttonview.js +66 -66
  62. package/src/dropdown/button/splitbuttonview.d.ts +162 -161
  63. package/src/dropdown/button/splitbuttonview.js +154 -152
  64. package/src/dropdown/dropdownpanelfocusable.d.ts +21 -21
  65. package/src/dropdown/dropdownpanelfocusable.js +5 -5
  66. package/src/dropdown/dropdownpanelview.d.ts +62 -62
  67. package/src/dropdown/dropdownpanelview.js +97 -97
  68. package/src/dropdown/dropdownview.d.ts +315 -315
  69. package/src/dropdown/dropdownview.js +379 -379
  70. package/src/dropdown/utils.d.ts +235 -235
  71. package/src/dropdown/utils.js +463 -458
  72. package/src/editableui/editableuiview.d.ts +72 -72
  73. package/src/editableui/editableuiview.js +112 -112
  74. package/src/editableui/inline/inlineeditableuiview.d.ts +40 -40
  75. package/src/editableui/inline/inlineeditableuiview.js +48 -48
  76. package/src/editorui/bodycollection.d.ts +55 -55
  77. package/src/editorui/bodycollection.js +84 -84
  78. package/src/editorui/boxed/boxededitoruiview.d.ts +40 -40
  79. package/src/editorui/boxed/boxededitoruiview.js +81 -81
  80. package/src/editorui/editorui.d.ts +288 -282
  81. package/src/editorui/editorui.js +412 -410
  82. package/src/editorui/editoruiview.d.ts +39 -39
  83. package/src/editorui/editoruiview.js +38 -38
  84. package/src/editorui/poweredby.d.ts +71 -71
  85. package/src/editorui/poweredby.js +276 -276
  86. package/src/focuscycler.d.ts +226 -226
  87. package/src/focuscycler.js +245 -245
  88. package/src/formheader/formheaderview.d.ts +59 -59
  89. package/src/formheader/formheaderview.js +69 -69
  90. package/src/highlightedtext/highlightedtextview.d.ts +38 -38
  91. package/src/highlightedtext/highlightedtextview.js +102 -102
  92. package/src/icon/iconview.d.ts +85 -85
  93. package/src/icon/iconview.js +114 -114
  94. package/src/iframe/iframeview.d.ts +50 -50
  95. package/src/iframe/iframeview.js +63 -63
  96. package/src/index.d.ts +74 -73
  97. package/src/index.js +71 -70
  98. package/src/input/inputbase.d.ts +107 -107
  99. package/src/input/inputbase.js +110 -110
  100. package/src/input/inputview.d.ts +36 -36
  101. package/src/input/inputview.js +24 -24
  102. package/src/inputnumber/inputnumberview.d.ts +49 -49
  103. package/src/inputnumber/inputnumberview.js +40 -40
  104. package/src/inputtext/inputtextview.d.ts +18 -18
  105. package/src/inputtext/inputtextview.js +27 -27
  106. package/src/label/labelview.d.ts +36 -36
  107. package/src/label/labelview.js +41 -41
  108. package/src/labeledfield/labeledfieldview.d.ts +187 -187
  109. package/src/labeledfield/labeledfieldview.js +157 -157
  110. package/src/labeledfield/utils.d.ts +123 -123
  111. package/src/labeledfield/utils.js +176 -176
  112. package/src/labeledinput/labeledinputview.d.ts +125 -125
  113. package/src/labeledinput/labeledinputview.js +125 -125
  114. package/src/list/listitemgroupview.d.ts +59 -51
  115. package/src/list/listitemgroupview.js +67 -75
  116. package/src/list/listitemview.d.ts +36 -36
  117. package/src/list/listitemview.js +42 -42
  118. package/src/list/listseparatorview.d.ts +18 -18
  119. package/src/list/listseparatorview.js +28 -28
  120. package/src/list/listview.d.ts +123 -122
  121. package/src/list/listview.js +188 -187
  122. package/src/model.d.ts +22 -22
  123. package/src/model.js +31 -31
  124. package/src/notification/notification.d.ts +211 -211
  125. package/src/notification/notification.js +187 -187
  126. package/src/panel/balloon/balloonpanelview.d.ts +685 -685
  127. package/src/panel/balloon/balloonpanelview.js +1010 -1010
  128. package/src/panel/balloon/contextualballoon.d.ts +299 -299
  129. package/src/panel/balloon/contextualballoon.js +572 -572
  130. package/src/panel/sticky/stickypanelview.d.ts +156 -156
  131. package/src/panel/sticky/stickypanelview.js +234 -234
  132. package/src/search/filteredview.d.ts +31 -31
  133. package/src/search/filteredview.js +5 -5
  134. package/src/search/searchinfoview.d.ts +45 -45
  135. package/src/search/searchinfoview.js +59 -59
  136. package/src/search/searchresultsview.d.ts +54 -54
  137. package/src/search/searchresultsview.js +65 -65
  138. package/src/search/text/searchtextqueryview.d.ts +76 -76
  139. package/src/search/text/searchtextqueryview.js +75 -75
  140. package/src/search/text/searchtextview.d.ts +219 -219
  141. package/src/search/text/searchtextview.js +201 -201
  142. package/src/spinner/spinnerview.d.ts +25 -25
  143. package/src/spinner/spinnerview.js +38 -38
  144. package/src/template.d.ts +942 -942
  145. package/src/template.js +1294 -1294
  146. package/src/textarea/textareaview.d.ts +88 -88
  147. package/src/textarea/textareaview.js +142 -140
  148. package/src/toolbar/balloon/balloontoolbar.d.ts +122 -122
  149. package/src/toolbar/balloon/balloontoolbar.js +300 -300
  150. package/src/toolbar/block/blockbuttonview.d.ts +35 -35
  151. package/src/toolbar/block/blockbuttonview.js +41 -41
  152. package/src/toolbar/block/blocktoolbar.d.ts +161 -161
  153. package/src/toolbar/block/blocktoolbar.js +395 -395
  154. package/src/toolbar/normalizetoolbarconfig.d.ts +40 -40
  155. package/src/toolbar/normalizetoolbarconfig.js +52 -51
  156. package/src/toolbar/toolbarlinebreakview.d.ts +18 -18
  157. package/src/toolbar/toolbarlinebreakview.js +28 -28
  158. package/src/toolbar/toolbarseparatorview.d.ts +18 -18
  159. package/src/toolbar/toolbarseparatorview.js +28 -28
  160. package/src/toolbar/toolbarview.d.ts +266 -266
  161. package/src/toolbar/toolbarview.js +719 -719
  162. package/src/tooltipmanager.d.ts +180 -180
  163. package/src/tooltipmanager.js +353 -353
  164. package/src/view.d.ts +422 -422
  165. package/src/view.js +396 -396
  166. package/src/viewcollection.d.ts +139 -139
  167. package/src/viewcollection.js +206 -206
  168. package/theme/components/arialiveannouncer/arialiveannouncer.css +10 -0
  169. package/theme/components/button/button.css +9 -1
  170. package/theme/components/collapsible/collapsible.css +10 -0
  171. 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
+ }