@ckeditor/ckeditor5-ui 39.0.1 → 40.0.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 (229) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/LICENSE.md +1 -1
  3. package/README.md +4 -4
  4. package/lang/contexts.json +5 -1
  5. package/lang/translations/ar.po +17 -0
  6. package/lang/translations/ast.po +17 -0
  7. package/lang/translations/az.po +17 -0
  8. package/lang/translations/bg.po +17 -0
  9. package/lang/translations/bn.po +17 -0
  10. package/lang/translations/ca.po +17 -0
  11. package/lang/translations/cs.po +17 -0
  12. package/lang/translations/da.po +17 -0
  13. package/lang/translations/de-ch.po +17 -0
  14. package/lang/translations/de.po +17 -0
  15. package/lang/translations/el.po +17 -0
  16. package/lang/translations/en-au.po +17 -0
  17. package/lang/translations/en-gb.po +17 -0
  18. package/lang/translations/en.po +17 -0
  19. package/lang/translations/eo.po +17 -0
  20. package/lang/translations/es.po +17 -0
  21. package/lang/translations/et.po +17 -0
  22. package/lang/translations/eu.po +17 -0
  23. package/lang/translations/fa.po +17 -0
  24. package/lang/translations/fi.po +17 -0
  25. package/lang/translations/fr.po +17 -0
  26. package/lang/translations/gl.po +17 -0
  27. package/lang/translations/he.po +17 -0
  28. package/lang/translations/hi.po +17 -0
  29. package/lang/translations/hr.po +17 -0
  30. package/lang/translations/hu.po +17 -0
  31. package/lang/translations/id.po +17 -0
  32. package/lang/translations/it.po +17 -0
  33. package/lang/translations/ja.po +17 -0
  34. package/lang/translations/km.po +17 -0
  35. package/lang/translations/kn.po +17 -0
  36. package/lang/translations/ko.po +17 -0
  37. package/lang/translations/ku.po +17 -0
  38. package/lang/translations/lt.po +17 -0
  39. package/lang/translations/lv.po +17 -0
  40. package/lang/translations/ms.po +17 -0
  41. package/lang/translations/nb.po +17 -0
  42. package/lang/translations/ne.po +17 -0
  43. package/lang/translations/nl.po +17 -0
  44. package/lang/translations/no.po +17 -0
  45. package/lang/translations/pl.po +17 -0
  46. package/lang/translations/pt-br.po +17 -0
  47. package/lang/translations/pt.po +17 -0
  48. package/lang/translations/ro.po +17 -0
  49. package/lang/translations/ru.po +17 -0
  50. package/lang/translations/sk.po +17 -0
  51. package/lang/translations/sl.po +17 -0
  52. package/lang/translations/sq.po +17 -0
  53. package/lang/translations/sr-latn.po +17 -0
  54. package/lang/translations/sr.po +17 -0
  55. package/lang/translations/sv.po +17 -0
  56. package/lang/translations/th.po +17 -0
  57. package/lang/translations/tk.po +17 -0
  58. package/lang/translations/tr.po +17 -0
  59. package/lang/translations/tt.po +17 -0
  60. package/lang/translations/ug.po +17 -0
  61. package/lang/translations/uk.po +17 -0
  62. package/lang/translations/ur.po +17 -0
  63. package/lang/translations/uz.po +17 -0
  64. package/lang/translations/vi.po +17 -0
  65. package/lang/translations/zh-cn.po +17 -0
  66. package/lang/translations/zh.po +17 -0
  67. package/package.json +3 -7
  68. package/src/augmentation.d.ts +86 -86
  69. package/src/augmentation.js +5 -5
  70. package/src/autocomplete/autocompleteview.d.ts +81 -0
  71. package/src/autocomplete/autocompleteview.js +146 -0
  72. package/src/bindings/addkeyboardhandlingforgrid.d.ts +27 -27
  73. package/src/bindings/addkeyboardhandlingforgrid.js +107 -107
  74. package/src/bindings/clickoutsidehandler.d.ts +28 -28
  75. package/src/bindings/clickoutsidehandler.js +36 -36
  76. package/src/bindings/csstransitiondisablermixin.d.ts +40 -40
  77. package/src/bindings/csstransitiondisablermixin.js +55 -55
  78. package/src/bindings/injectcsstransitiondisabler.d.ts +59 -59
  79. package/src/bindings/injectcsstransitiondisabler.js +71 -71
  80. package/src/bindings/preventdefault.d.ts +33 -33
  81. package/src/bindings/preventdefault.js +34 -34
  82. package/src/bindings/submithandler.d.ts +57 -57
  83. package/src/bindings/submithandler.js +47 -47
  84. package/src/button/button.d.ts +178 -178
  85. package/src/button/button.js +5 -5
  86. package/src/button/buttonlabel.d.ts +34 -0
  87. package/src/button/buttonlabel.js +5 -0
  88. package/src/button/buttonlabelview.d.ts +31 -0
  89. package/src/button/buttonlabelview.js +42 -0
  90. package/src/button/buttonview.d.ts +185 -177
  91. package/src/button/buttonview.js +219 -231
  92. package/src/button/switchbuttonview.d.ts +45 -45
  93. package/src/button/switchbuttonview.js +75 -75
  94. package/src/colorgrid/colorgridview.d.ts +132 -132
  95. package/src/colorgrid/colorgridview.js +124 -124
  96. package/src/colorgrid/colortileview.d.ts +28 -28
  97. package/src/colorgrid/colortileview.js +40 -40
  98. package/src/colorgrid/utils.d.ts +47 -47
  99. package/src/colorgrid/utils.js +84 -84
  100. package/src/colorpicker/colorpickerview.d.ts +137 -137
  101. package/src/colorpicker/colorpickerview.js +270 -270
  102. package/src/colorpicker/utils.d.ts +43 -43
  103. package/src/colorpicker/utils.js +99 -99
  104. package/src/colorselector/colorgridsfragmentview.d.ts +194 -194
  105. package/src/colorselector/colorgridsfragmentview.js +289 -289
  106. package/src/colorselector/colorpickerfragmentview.d.ts +128 -128
  107. package/src/colorselector/colorpickerfragmentview.js +205 -205
  108. package/src/colorselector/colorselectorview.d.ts +242 -242
  109. package/src/colorselector/colorselectorview.js +256 -256
  110. package/src/colorselector/documentcolorcollection.d.ts +70 -70
  111. package/src/colorselector/documentcolorcollection.js +42 -42
  112. package/src/componentfactory.d.ts +81 -81
  113. package/src/componentfactory.js +104 -104
  114. package/src/dropdown/button/dropdownbutton.d.ts +25 -25
  115. package/src/dropdown/button/dropdownbutton.js +5 -5
  116. package/src/dropdown/button/dropdownbuttonview.d.ts +48 -48
  117. package/src/dropdown/button/dropdownbuttonview.js +66 -66
  118. package/src/dropdown/button/splitbuttonview.d.ts +161 -161
  119. package/src/dropdown/button/splitbuttonview.js +152 -152
  120. package/src/dropdown/dropdownpanelfocusable.d.ts +21 -21
  121. package/src/dropdown/dropdownpanelfocusable.js +5 -5
  122. package/src/dropdown/dropdownpanelview.d.ts +62 -62
  123. package/src/dropdown/dropdownpanelview.js +97 -97
  124. package/src/dropdown/dropdownview.d.ts +315 -315
  125. package/src/dropdown/dropdownview.js +379 -378
  126. package/src/dropdown/utils.d.ts +235 -221
  127. package/src/dropdown/utils.js +458 -437
  128. package/src/editableui/editableuiview.d.ts +72 -72
  129. package/src/editableui/editableuiview.js +112 -112
  130. package/src/editableui/inline/inlineeditableuiview.d.ts +40 -40
  131. package/src/editableui/inline/inlineeditableuiview.js +48 -48
  132. package/src/editorui/bodycollection.d.ts +55 -55
  133. package/src/editorui/bodycollection.js +84 -84
  134. package/src/editorui/boxed/boxededitoruiview.d.ts +40 -40
  135. package/src/editorui/boxed/boxededitoruiview.js +81 -81
  136. package/src/editorui/editorui.d.ts +282 -282
  137. package/src/editorui/editorui.js +410 -410
  138. package/src/editorui/editoruiview.d.ts +39 -39
  139. package/src/editorui/editoruiview.js +38 -38
  140. package/src/editorui/poweredby.d.ts +71 -71
  141. package/src/editorui/poweredby.js +276 -299
  142. package/src/focuscycler.d.ts +226 -183
  143. package/src/focuscycler.js +245 -220
  144. package/src/formheader/formheaderview.d.ts +59 -53
  145. package/src/formheader/formheaderview.js +69 -63
  146. package/src/highlightedtext/highlightedtextview.d.ts +38 -0
  147. package/src/highlightedtext/highlightedtextview.js +102 -0
  148. package/src/icon/iconview.d.ts +85 -78
  149. package/src/icon/iconview.js +114 -112
  150. package/src/iframe/iframeview.d.ts +50 -50
  151. package/src/iframe/iframeview.js +63 -63
  152. package/src/index.d.ts +73 -63
  153. package/src/index.js +70 -62
  154. package/src/input/inputbase.d.ts +107 -0
  155. package/src/input/inputbase.js +110 -0
  156. package/src/input/inputview.d.ts +36 -121
  157. package/src/input/inputview.js +24 -106
  158. package/src/inputnumber/inputnumberview.d.ts +49 -49
  159. package/src/inputnumber/inputnumberview.js +40 -40
  160. package/src/inputtext/inputtextview.d.ts +18 -18
  161. package/src/inputtext/inputtextview.js +27 -27
  162. package/src/label/labelview.d.ts +36 -36
  163. package/src/label/labelview.js +41 -41
  164. package/src/labeledfield/labeledfieldview.d.ts +187 -182
  165. package/src/labeledfield/labeledfieldview.js +157 -157
  166. package/src/labeledfield/utils.d.ts +123 -93
  167. package/src/labeledfield/utils.js +176 -131
  168. package/src/labeledinput/labeledinputview.d.ts +125 -125
  169. package/src/labeledinput/labeledinputview.js +125 -125
  170. package/src/list/listitemgroupview.d.ts +51 -0
  171. package/src/list/listitemgroupview.js +75 -0
  172. package/src/list/listitemview.d.ts +36 -35
  173. package/src/list/listitemview.js +42 -40
  174. package/src/list/listseparatorview.d.ts +18 -18
  175. package/src/list/listseparatorview.js +28 -28
  176. package/src/list/listview.d.ts +122 -65
  177. package/src/list/listview.js +187 -90
  178. package/src/model.d.ts +22 -22
  179. package/src/model.js +31 -31
  180. package/src/notification/notification.d.ts +211 -211
  181. package/src/notification/notification.js +187 -187
  182. package/src/panel/balloon/balloonpanelview.d.ts +685 -685
  183. package/src/panel/balloon/balloonpanelview.js +1010 -988
  184. package/src/panel/balloon/contextualballoon.d.ts +299 -299
  185. package/src/panel/balloon/contextualballoon.js +572 -572
  186. package/src/panel/sticky/stickypanelview.d.ts +156 -158
  187. package/src/panel/sticky/stickypanelview.js +234 -231
  188. package/src/search/filteredview.d.ts +31 -0
  189. package/src/search/filteredview.js +5 -0
  190. package/src/search/searchinfoview.d.ts +45 -0
  191. package/src/search/searchinfoview.js +59 -0
  192. package/src/search/searchresultsview.d.ts +54 -0
  193. package/src/search/searchresultsview.js +65 -0
  194. package/src/search/text/searchtextqueryview.d.ts +76 -0
  195. package/src/search/text/searchtextqueryview.js +75 -0
  196. package/src/search/text/searchtextview.d.ts +219 -0
  197. package/src/search/text/searchtextview.js +201 -0
  198. package/src/spinner/spinnerview.d.ts +25 -0
  199. package/src/spinner/spinnerview.js +38 -0
  200. package/src/template.d.ts +942 -942
  201. package/src/template.js +1294 -1294
  202. package/src/textarea/textareaview.d.ts +88 -0
  203. package/src/textarea/textareaview.js +140 -0
  204. package/src/toolbar/balloon/balloontoolbar.d.ts +122 -122
  205. package/src/toolbar/balloon/balloontoolbar.js +300 -300
  206. package/src/toolbar/block/blockbuttonview.d.ts +35 -35
  207. package/src/toolbar/block/blockbuttonview.js +41 -41
  208. package/src/toolbar/block/blocktoolbar.d.ts +161 -161
  209. package/src/toolbar/block/blocktoolbar.js +395 -391
  210. package/src/toolbar/normalizetoolbarconfig.d.ts +40 -39
  211. package/src/toolbar/normalizetoolbarconfig.js +51 -51
  212. package/src/toolbar/toolbarlinebreakview.d.ts +18 -18
  213. package/src/toolbar/toolbarlinebreakview.js +28 -28
  214. package/src/toolbar/toolbarseparatorview.d.ts +18 -18
  215. package/src/toolbar/toolbarseparatorview.js +28 -28
  216. package/src/toolbar/toolbarview.d.ts +266 -265
  217. package/src/toolbar/toolbarview.js +719 -717
  218. package/src/tooltipmanager.d.ts +180 -180
  219. package/src/tooltipmanager.js +353 -353
  220. package/src/view.d.ts +422 -422
  221. package/src/view.js +396 -396
  222. package/src/viewcollection.d.ts +139 -139
  223. package/src/viewcollection.js +206 -206
  224. package/theme/components/autocomplete/autocomplete.css +22 -0
  225. package/theme/components/formheader/formheader.css +8 -0
  226. package/theme/components/highlightedtext/highlightedtext.css +12 -0
  227. package/theme/components/search/search.css +43 -0
  228. package/theme/components/spinner/spinner.css +23 -0
  229. package/theme/components/textarea/textarea.css +10 -0
@@ -1,299 +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 { Rect, DomEmitterMixin, findClosestScrollableAncestor, 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
- const OFF_THE_SCREEN_POSITION = {
19
- top: -99999,
20
- left: -99999,
21
- name: 'invalid',
22
- config: {
23
- withArrow: false
24
- }
25
- };
26
- /**
27
- * A helper that enables the "powered by" feature in the editor and renders a link to the project's
28
- * webpage next to the bottom of the editable element (editor root, source editing area, etc.) when the editor is focused.
29
- *
30
- * @private
31
- */
32
- export default class PoweredBy extends DomEmitterMixin() {
33
- /**
34
- * Creates a "powered by" helper for a given editor. The feature is initialized on Editor#ready
35
- * event.
36
- *
37
- * @param editor
38
- */
39
- constructor(editor) {
40
- super();
41
- this.editor = editor;
42
- this._balloonView = null;
43
- this._lastFocusedEditableElement = null;
44
- this._showBalloonThrottled = throttle(this._showBalloon.bind(this), 50, { leading: true });
45
- editor.on('ready', this._handleEditorReady.bind(this));
46
- }
47
- /**
48
- * Destroys the "powered by" helper along with its view.
49
- */
50
- destroy() {
51
- const balloon = this._balloonView;
52
- if (balloon) {
53
- // Balloon gets destroyed by the body collection.
54
- // The powered by view gets destroyed by the balloon.
55
- balloon.unpin();
56
- this._balloonView = null;
57
- }
58
- this._showBalloonThrottled.cancel();
59
- this.stopListening();
60
- }
61
- /**
62
- * Enables "powered by" label once the editor (ui) is ready.
63
- */
64
- _handleEditorReady() {
65
- const editor = this.editor;
66
- const forceVisible = !!editor.config.get('ui.poweredBy.forceVisible');
67
- /* istanbul ignore next -- @preserve */
68
- if (!forceVisible && verifyLicense(editor.config.get('licenseKey')) === 'VALID') {
69
- return;
70
- }
71
- // No view means no body collection to append the powered by balloon to.
72
- if (!editor.ui.view) {
73
- return;
74
- }
75
- editor.ui.focusTracker.on('change:isFocused', (evt, data, isFocused) => {
76
- this._updateLastFocusedEditableElement();
77
- if (isFocused) {
78
- this._showBalloon();
79
- }
80
- else {
81
- this._hideBalloon();
82
- }
83
- });
84
- editor.ui.focusTracker.on('change:focusedElement', (evt, data, focusedElement) => {
85
- this._updateLastFocusedEditableElement();
86
- if (focusedElement) {
87
- this._showBalloon();
88
- }
89
- });
90
- editor.ui.on('update', () => {
91
- this._showBalloonThrottled();
92
- });
93
- }
94
- /**
95
- * Creates an instance of the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView balloon panel}
96
- * with the "powered by" view inside ready for positioning.
97
- */
98
- _createBalloonView() {
99
- const editor = this.editor;
100
- const balloon = this._balloonView = new BalloonPanelView();
101
- const poweredByConfig = getNormalizedConfig(editor);
102
- const view = new PoweredByView(editor.locale, poweredByConfig.label);
103
- balloon.content.add(view);
104
- balloon.set({
105
- class: 'ck-powered-by-balloon'
106
- });
107
- editor.ui.view.body.add(balloon);
108
- editor.ui.focusTracker.add(balloon.element);
109
- this._balloonView = balloon;
110
- }
111
- /**
112
- * Attempts to display the balloon with the "powered by" view.
113
- */
114
- _showBalloon() {
115
- if (!this._lastFocusedEditableElement) {
116
- return;
117
- }
118
- const attachOptions = getBalloonAttachOptions(this.editor, this._lastFocusedEditableElement);
119
- if (attachOptions) {
120
- if (!this._balloonView) {
121
- this._createBalloonView();
122
- }
123
- this._balloonView.pin(attachOptions);
124
- }
125
- }
126
- /**
127
- * Hides the "powered by" balloon if already visible.
128
- */
129
- _hideBalloon() {
130
- if (this._balloonView) {
131
- this._balloonView.unpin();
132
- }
133
- }
134
- /**
135
- * Updates the {@link #_lastFocusedEditableElement} based on the state of the global focus tracker.
136
- */
137
- _updateLastFocusedEditableElement() {
138
- const editor = this.editor;
139
- const isFocused = editor.ui.focusTracker.isFocused;
140
- const focusedElement = editor.ui.focusTracker.focusedElement;
141
- if (!isFocused || !focusedElement) {
142
- this._lastFocusedEditableElement = null;
143
- return;
144
- }
145
- const editableEditorElements = Array.from(editor.ui.getEditableElementsNames()).map(name => {
146
- return editor.ui.getEditableElement(name);
147
- });
148
- if (editableEditorElements.includes(focusedElement)) {
149
- this._lastFocusedEditableElement = focusedElement;
150
- }
151
- else {
152
- // If it's none of the editable element, then the focus is somewhere in the UI. Let's display powered by
153
- // over the first element then.
154
- this._lastFocusedEditableElement = editableEditorElements[0];
155
- }
156
- }
157
- }
158
- /**
159
- * A view displaying a "powered by" label and project logo wrapped in a link.
160
- */
161
- class PoweredByView extends View {
162
- /**
163
- * Created an instance of the "powered by" view.
164
- *
165
- * @param locale The localization services instance.
166
- * @param label The label text.
167
- */
168
- constructor(locale, label) {
169
- super(locale);
170
- const iconView = new IconView();
171
- const bind = this.bindTemplate;
172
- iconView.set({
173
- content: poweredByIcon,
174
- isColorInherited: false
175
- });
176
- iconView.extendTemplate({
177
- attributes: {
178
- style: {
179
- width: ICON_WIDTH + 'px',
180
- height: ICON_HEIGHT + 'px'
181
- }
182
- }
183
- });
184
- this.setTemplate({
185
- tag: 'div',
186
- attributes: {
187
- class: ['ck', 'ck-powered-by'],
188
- 'aria-hidden': true
189
- },
190
- children: [
191
- {
192
- tag: 'a',
193
- attributes: {
194
- href: 'https://ckeditor.com/?utm_source=ckeditor&' +
195
- 'utm_medium=referral&utm_campaign=701Dn000000hVgmIAE_powered_by_ckeditor_logo',
196
- target: '_blank',
197
- tabindex: '-1'
198
- },
199
- children: [
200
- ...label ? [
201
- {
202
- tag: 'span',
203
- attributes: {
204
- class: ['ck', 'ck-powered-by__label']
205
- },
206
- children: [label]
207
- }
208
- ] : [],
209
- iconView
210
- ],
211
- on: {
212
- dragstart: bind.to(evt => evt.preventDefault())
213
- }
214
- }
215
- ]
216
- });
217
- }
218
- }
219
- function getBalloonAttachOptions(editor, focusedEditableElement) {
220
- const poweredByConfig = getNormalizedConfig(editor);
221
- const positioningFunction = poweredByConfig.side === 'right' ?
222
- getLowerRightCornerPosition(focusedEditableElement, poweredByConfig) :
223
- getLowerLeftCornerPosition(focusedEditableElement, poweredByConfig);
224
- return {
225
- target: focusedEditableElement,
226
- positions: [positioningFunction]
227
- };
228
- }
229
- function getLowerRightCornerPosition(focusedEditableElement, config) {
230
- return getLowerCornerPosition(focusedEditableElement, config, (rootRect, balloonRect) => {
231
- return rootRect.left + rootRect.width - balloonRect.width - config.horizontalOffset;
232
- });
233
- }
234
- function getLowerLeftCornerPosition(focusedEditableElement, config) {
235
- return getLowerCornerPosition(focusedEditableElement, config, rootRect => rootRect.left + config.horizontalOffset);
236
- }
237
- function getLowerCornerPosition(focusedEditableElement, config, getBalloonLeft) {
238
- return (editableElementRect, balloonRect) => {
239
- const visibleEditableElementRect = editableElementRect.getVisible();
240
- // Root cropped by ancestors.
241
- if (!visibleEditableElementRect) {
242
- return OFF_THE_SCREEN_POSITION;
243
- }
244
- if (editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD) {
245
- return OFF_THE_SCREEN_POSITION;
246
- }
247
- let balloonTop;
248
- if (config.position === 'inside') {
249
- balloonTop = editableElementRect.bottom - balloonRect.height;
250
- }
251
- else {
252
- balloonTop = editableElementRect.bottom - balloonRect.height / 2;
253
- }
254
- balloonTop -= config.verticalOffset;
255
- const balloonLeft = getBalloonLeft(editableElementRect, balloonRect);
256
- if (config.position === 'inside') {
257
- const newBalloonRect = balloonRect.clone().moveTo(balloonLeft, balloonTop);
258
- // The watermark cannot be positioned in this corner because the corner is not quite visible.
259
- if (newBalloonRect.getIntersectionArea(visibleEditableElementRect) < newBalloonRect.getArea()) {
260
- return OFF_THE_SCREEN_POSITION;
261
- }
262
- }
263
- else {
264
- const firstScrollableEditableElementAncestor = findClosestScrollableAncestor(focusedEditableElement);
265
- if (firstScrollableEditableElementAncestor) {
266
- const firstScrollableEditableElementAncestorRect = new Rect(firstScrollableEditableElementAncestor);
267
- const notVisibleVertically = visibleEditableElementRect.bottom + balloonRect.height / 2 >
268
- firstScrollableEditableElementAncestorRect.bottom;
269
- const notVisibleHorizontally = config.side === 'left' ?
270
- editableElementRect.left < firstScrollableEditableElementAncestorRect.left :
271
- editableElementRect.right > firstScrollableEditableElementAncestorRect.right;
272
- // The watermark cannot be positioned in this corner because the corner is "not visible enough".
273
- if (notVisibleVertically || notVisibleHorizontally) {
274
- return OFF_THE_SCREEN_POSITION;
275
- }
276
- }
277
- }
278
- return {
279
- top: balloonTop,
280
- left: balloonLeft,
281
- name: `position_${config.position}-side_${config.side}`,
282
- config: {
283
- withArrow: false
284
- }
285
- };
286
- };
287
- }
288
- function getNormalizedConfig(editor) {
289
- const userConfig = editor.config.get('ui.poweredBy');
290
- const position = userConfig && userConfig.position || 'border';
291
- return {
292
- position,
293
- label: DEFAULT_LABEL,
294
- verticalOffset: position === 'inside' ? 5 : 0,
295
- horizontalOffset: 5,
296
- side: editor.locale.contentLanguageDirection === 'ltr' ? 'right' : 'left',
297
- ...userConfig
298
- };
299
- }
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
+ }