@ckeditor/ckeditor5-ui 39.0.2 → 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 (226) hide show
  1. package/lang/contexts.json +5 -1
  2. package/lang/translations/ar.po +16 -0
  3. package/lang/translations/ast.po +16 -0
  4. package/lang/translations/az.po +16 -0
  5. package/lang/translations/bg.po +16 -0
  6. package/lang/translations/bn.po +16 -0
  7. package/lang/translations/ca.po +16 -0
  8. package/lang/translations/cs.po +16 -0
  9. package/lang/translations/da.po +16 -0
  10. package/lang/translations/de-ch.po +16 -0
  11. package/lang/translations/de.po +16 -0
  12. package/lang/translations/el.po +16 -0
  13. package/lang/translations/en-au.po +16 -0
  14. package/lang/translations/en-gb.po +16 -0
  15. package/lang/translations/en.po +16 -0
  16. package/lang/translations/eo.po +16 -0
  17. package/lang/translations/es.po +16 -0
  18. package/lang/translations/et.po +16 -0
  19. package/lang/translations/eu.po +16 -0
  20. package/lang/translations/fa.po +16 -0
  21. package/lang/translations/fi.po +16 -0
  22. package/lang/translations/fr.po +16 -0
  23. package/lang/translations/gl.po +16 -0
  24. package/lang/translations/he.po +16 -0
  25. package/lang/translations/hi.po +16 -0
  26. package/lang/translations/hr.po +16 -0
  27. package/lang/translations/hu.po +16 -0
  28. package/lang/translations/id.po +16 -0
  29. package/lang/translations/it.po +16 -0
  30. package/lang/translations/ja.po +16 -0
  31. package/lang/translations/km.po +16 -0
  32. package/lang/translations/kn.po +16 -0
  33. package/lang/translations/ko.po +16 -0
  34. package/lang/translations/ku.po +16 -0
  35. package/lang/translations/lt.po +16 -0
  36. package/lang/translations/lv.po +16 -0
  37. package/lang/translations/ms.po +16 -0
  38. package/lang/translations/nb.po +16 -0
  39. package/lang/translations/ne.po +16 -0
  40. package/lang/translations/nl.po +16 -0
  41. package/lang/translations/no.po +16 -0
  42. package/lang/translations/pl.po +16 -0
  43. package/lang/translations/pt-br.po +16 -0
  44. package/lang/translations/pt.po +16 -0
  45. package/lang/translations/ro.po +16 -0
  46. package/lang/translations/ru.po +16 -0
  47. package/lang/translations/sk.po +16 -0
  48. package/lang/translations/sl.po +16 -0
  49. package/lang/translations/sq.po +16 -0
  50. package/lang/translations/sr-latn.po +16 -0
  51. package/lang/translations/sr.po +16 -0
  52. package/lang/translations/sv.po +16 -0
  53. package/lang/translations/th.po +16 -0
  54. package/lang/translations/tk.po +16 -0
  55. package/lang/translations/tr.po +16 -0
  56. package/lang/translations/tt.po +16 -0
  57. package/lang/translations/ug.po +16 -0
  58. package/lang/translations/uk.po +16 -0
  59. package/lang/translations/ur.po +16 -0
  60. package/lang/translations/uz.po +16 -0
  61. package/lang/translations/vi.po +16 -0
  62. package/lang/translations/zh-cn.po +16 -0
  63. package/lang/translations/zh.po +16 -0
  64. package/package.json +3 -3
  65. package/src/augmentation.d.ts +86 -86
  66. package/src/augmentation.js +5 -5
  67. package/src/autocomplete/autocompleteview.d.ts +81 -0
  68. package/src/autocomplete/autocompleteview.js +146 -0
  69. package/src/bindings/addkeyboardhandlingforgrid.d.ts +27 -27
  70. package/src/bindings/addkeyboardhandlingforgrid.js +107 -107
  71. package/src/bindings/clickoutsidehandler.d.ts +28 -28
  72. package/src/bindings/clickoutsidehandler.js +36 -36
  73. package/src/bindings/csstransitiondisablermixin.d.ts +40 -40
  74. package/src/bindings/csstransitiondisablermixin.js +55 -55
  75. package/src/bindings/injectcsstransitiondisabler.d.ts +59 -59
  76. package/src/bindings/injectcsstransitiondisabler.js +71 -71
  77. package/src/bindings/preventdefault.d.ts +33 -33
  78. package/src/bindings/preventdefault.js +34 -34
  79. package/src/bindings/submithandler.d.ts +57 -57
  80. package/src/bindings/submithandler.js +47 -47
  81. package/src/button/button.d.ts +178 -178
  82. package/src/button/button.js +5 -5
  83. package/src/button/buttonlabel.d.ts +34 -0
  84. package/src/button/buttonlabel.js +5 -0
  85. package/src/button/buttonlabelview.d.ts +31 -0
  86. package/src/button/buttonlabelview.js +42 -0
  87. package/src/button/buttonview.d.ts +185 -177
  88. package/src/button/buttonview.js +219 -231
  89. package/src/button/switchbuttonview.d.ts +45 -45
  90. package/src/button/switchbuttonview.js +75 -75
  91. package/src/colorgrid/colorgridview.d.ts +132 -132
  92. package/src/colorgrid/colorgridview.js +124 -124
  93. package/src/colorgrid/colortileview.d.ts +28 -28
  94. package/src/colorgrid/colortileview.js +40 -40
  95. package/src/colorgrid/utils.d.ts +47 -47
  96. package/src/colorgrid/utils.js +84 -84
  97. package/src/colorpicker/colorpickerview.d.ts +137 -137
  98. package/src/colorpicker/colorpickerview.js +270 -270
  99. package/src/colorpicker/utils.d.ts +43 -43
  100. package/src/colorpicker/utils.js +99 -99
  101. package/src/colorselector/colorgridsfragmentview.d.ts +194 -194
  102. package/src/colorselector/colorgridsfragmentview.js +289 -289
  103. package/src/colorselector/colorpickerfragmentview.d.ts +128 -128
  104. package/src/colorselector/colorpickerfragmentview.js +205 -205
  105. package/src/colorselector/colorselectorview.d.ts +242 -242
  106. package/src/colorselector/colorselectorview.js +256 -256
  107. package/src/colorselector/documentcolorcollection.d.ts +70 -70
  108. package/src/colorselector/documentcolorcollection.js +42 -42
  109. package/src/componentfactory.d.ts +81 -81
  110. package/src/componentfactory.js +104 -104
  111. package/src/dropdown/button/dropdownbutton.d.ts +25 -25
  112. package/src/dropdown/button/dropdownbutton.js +5 -5
  113. package/src/dropdown/button/dropdownbuttonview.d.ts +48 -48
  114. package/src/dropdown/button/dropdownbuttonview.js +66 -66
  115. package/src/dropdown/button/splitbuttonview.d.ts +161 -161
  116. package/src/dropdown/button/splitbuttonview.js +152 -152
  117. package/src/dropdown/dropdownpanelfocusable.d.ts +21 -21
  118. package/src/dropdown/dropdownpanelfocusable.js +5 -5
  119. package/src/dropdown/dropdownpanelview.d.ts +62 -62
  120. package/src/dropdown/dropdownpanelview.js +97 -97
  121. package/src/dropdown/dropdownview.d.ts +315 -315
  122. package/src/dropdown/dropdownview.js +379 -378
  123. package/src/dropdown/utils.d.ts +235 -221
  124. package/src/dropdown/utils.js +458 -437
  125. package/src/editableui/editableuiview.d.ts +72 -72
  126. package/src/editableui/editableuiview.js +112 -112
  127. package/src/editableui/inline/inlineeditableuiview.d.ts +40 -40
  128. package/src/editableui/inline/inlineeditableuiview.js +48 -48
  129. package/src/editorui/bodycollection.d.ts +55 -55
  130. package/src/editorui/bodycollection.js +84 -84
  131. package/src/editorui/boxed/boxededitoruiview.d.ts +40 -40
  132. package/src/editorui/boxed/boxededitoruiview.js +81 -81
  133. package/src/editorui/editorui.d.ts +282 -282
  134. package/src/editorui/editorui.js +410 -410
  135. package/src/editorui/editoruiview.d.ts +39 -39
  136. package/src/editorui/editoruiview.js +38 -38
  137. package/src/editorui/poweredby.d.ts +71 -71
  138. package/src/editorui/poweredby.js +276 -299
  139. package/src/focuscycler.d.ts +226 -183
  140. package/src/focuscycler.js +245 -220
  141. package/src/formheader/formheaderview.d.ts +59 -53
  142. package/src/formheader/formheaderview.js +69 -63
  143. package/src/highlightedtext/highlightedtextview.d.ts +38 -0
  144. package/src/highlightedtext/highlightedtextview.js +102 -0
  145. package/src/icon/iconview.d.ts +85 -78
  146. package/src/icon/iconview.js +114 -112
  147. package/src/iframe/iframeview.d.ts +50 -50
  148. package/src/iframe/iframeview.js +63 -63
  149. package/src/index.d.ts +73 -63
  150. package/src/index.js +70 -62
  151. package/src/input/inputbase.d.ts +107 -0
  152. package/src/input/inputbase.js +110 -0
  153. package/src/input/inputview.d.ts +36 -121
  154. package/src/input/inputview.js +24 -106
  155. package/src/inputnumber/inputnumberview.d.ts +49 -49
  156. package/src/inputnumber/inputnumberview.js +40 -40
  157. package/src/inputtext/inputtextview.d.ts +18 -18
  158. package/src/inputtext/inputtextview.js +27 -27
  159. package/src/label/labelview.d.ts +36 -36
  160. package/src/label/labelview.js +41 -41
  161. package/src/labeledfield/labeledfieldview.d.ts +187 -182
  162. package/src/labeledfield/labeledfieldview.js +157 -157
  163. package/src/labeledfield/utils.d.ts +123 -93
  164. package/src/labeledfield/utils.js +176 -131
  165. package/src/labeledinput/labeledinputview.d.ts +125 -125
  166. package/src/labeledinput/labeledinputview.js +125 -125
  167. package/src/list/listitemgroupview.d.ts +51 -0
  168. package/src/list/listitemgroupview.js +75 -0
  169. package/src/list/listitemview.d.ts +36 -35
  170. package/src/list/listitemview.js +42 -40
  171. package/src/list/listseparatorview.d.ts +18 -18
  172. package/src/list/listseparatorview.js +28 -28
  173. package/src/list/listview.d.ts +122 -65
  174. package/src/list/listview.js +187 -90
  175. package/src/model.d.ts +22 -22
  176. package/src/model.js +31 -31
  177. package/src/notification/notification.d.ts +211 -211
  178. package/src/notification/notification.js +187 -187
  179. package/src/panel/balloon/balloonpanelview.d.ts +685 -685
  180. package/src/panel/balloon/balloonpanelview.js +1010 -988
  181. package/src/panel/balloon/contextualballoon.d.ts +299 -299
  182. package/src/panel/balloon/contextualballoon.js +572 -572
  183. package/src/panel/sticky/stickypanelview.d.ts +156 -158
  184. package/src/panel/sticky/stickypanelview.js +234 -231
  185. package/src/search/filteredview.d.ts +31 -0
  186. package/src/search/filteredview.js +5 -0
  187. package/src/search/searchinfoview.d.ts +45 -0
  188. package/src/search/searchinfoview.js +59 -0
  189. package/src/search/searchresultsview.d.ts +54 -0
  190. package/src/search/searchresultsview.js +65 -0
  191. package/src/search/text/searchtextqueryview.d.ts +76 -0
  192. package/src/search/text/searchtextqueryview.js +75 -0
  193. package/src/search/text/searchtextview.d.ts +219 -0
  194. package/src/search/text/searchtextview.js +201 -0
  195. package/src/spinner/spinnerview.d.ts +25 -0
  196. package/src/spinner/spinnerview.js +38 -0
  197. package/src/template.d.ts +942 -942
  198. package/src/template.js +1294 -1294
  199. package/src/textarea/textareaview.d.ts +88 -0
  200. package/src/textarea/textareaview.js +140 -0
  201. package/src/toolbar/balloon/balloontoolbar.d.ts +122 -122
  202. package/src/toolbar/balloon/balloontoolbar.js +300 -300
  203. package/src/toolbar/block/blockbuttonview.d.ts +35 -35
  204. package/src/toolbar/block/blockbuttonview.js +41 -41
  205. package/src/toolbar/block/blocktoolbar.d.ts +161 -161
  206. package/src/toolbar/block/blocktoolbar.js +395 -391
  207. package/src/toolbar/normalizetoolbarconfig.d.ts +40 -39
  208. package/src/toolbar/normalizetoolbarconfig.js +51 -51
  209. package/src/toolbar/toolbarlinebreakview.d.ts +18 -18
  210. package/src/toolbar/toolbarlinebreakview.js +28 -28
  211. package/src/toolbar/toolbarseparatorview.d.ts +18 -18
  212. package/src/toolbar/toolbarseparatorview.js +28 -28
  213. package/src/toolbar/toolbarview.d.ts +266 -265
  214. package/src/toolbar/toolbarview.js +719 -717
  215. package/src/tooltipmanager.d.ts +180 -180
  216. package/src/tooltipmanager.js +353 -353
  217. package/src/view.d.ts +422 -422
  218. package/src/view.js +396 -396
  219. package/src/viewcollection.d.ts +139 -139
  220. package/src/viewcollection.js +206 -206
  221. package/theme/components/autocomplete/autocomplete.css +22 -0
  222. package/theme/components/formheader/formheader.css +8 -0
  223. package/theme/components/highlightedtext/highlightedtext.css +12 -0
  224. package/theme/components/search/search.css +43 -0
  225. package/theme/components/spinner/spinner.css +23 -0
  226. 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
+ }