@ckeditor/ckeditor5-ui 39.0.2 → 40.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/LICENSE.md +3 -3
  2. package/lang/contexts.json +5 -1
  3. package/lang/translations/ar.po +16 -0
  4. package/lang/translations/ast.po +16 -0
  5. package/lang/translations/az.po +16 -0
  6. package/lang/translations/bg.po +16 -0
  7. package/lang/translations/bn.po +16 -0
  8. package/lang/translations/ca.po +16 -0
  9. package/lang/translations/cs.po +16 -0
  10. package/lang/translations/da.po +16 -0
  11. package/lang/translations/de-ch.po +16 -0
  12. package/lang/translations/de.po +16 -0
  13. package/lang/translations/el.po +16 -0
  14. package/lang/translations/en-au.po +16 -0
  15. package/lang/translations/en-gb.po +16 -0
  16. package/lang/translations/en.po +16 -0
  17. package/lang/translations/eo.po +16 -0
  18. package/lang/translations/es.po +16 -0
  19. package/lang/translations/et.po +16 -0
  20. package/lang/translations/eu.po +16 -0
  21. package/lang/translations/fa.po +16 -0
  22. package/lang/translations/fi.po +16 -0
  23. package/lang/translations/fr.po +16 -0
  24. package/lang/translations/gl.po +16 -0
  25. package/lang/translations/he.po +16 -0
  26. package/lang/translations/hi.po +16 -0
  27. package/lang/translations/hr.po +16 -0
  28. package/lang/translations/hu.po +16 -0
  29. package/lang/translations/id.po +16 -0
  30. package/lang/translations/it.po +16 -0
  31. package/lang/translations/ja.po +16 -0
  32. package/lang/translations/km.po +16 -0
  33. package/lang/translations/kn.po +16 -0
  34. package/lang/translations/ko.po +16 -0
  35. package/lang/translations/ku.po +16 -0
  36. package/lang/translations/lt.po +16 -0
  37. package/lang/translations/lv.po +16 -0
  38. package/lang/translations/ms.po +16 -0
  39. package/lang/translations/nb.po +16 -0
  40. package/lang/translations/ne.po +16 -0
  41. package/lang/translations/nl.po +16 -0
  42. package/lang/translations/no.po +16 -0
  43. package/lang/translations/pl.po +16 -0
  44. package/lang/translations/pt-br.po +17 -1
  45. package/lang/translations/pt.po +16 -0
  46. package/lang/translations/ro.po +16 -0
  47. package/lang/translations/ru.po +16 -0
  48. package/lang/translations/sk.po +16 -0
  49. package/lang/translations/sl.po +16 -0
  50. package/lang/translations/sq.po +16 -0
  51. package/lang/translations/sr-latn.po +16 -0
  52. package/lang/translations/sr.po +16 -0
  53. package/lang/translations/sv.po +16 -0
  54. package/lang/translations/th.po +16 -0
  55. package/lang/translations/tk.po +16 -0
  56. package/lang/translations/tr.po +16 -0
  57. package/lang/translations/tt.po +16 -0
  58. package/lang/translations/ug.po +38 -22
  59. package/lang/translations/uk.po +16 -0
  60. package/lang/translations/ur.po +16 -0
  61. package/lang/translations/uz.po +16 -0
  62. package/lang/translations/vi.po +16 -0
  63. package/lang/translations/zh-cn.po +16 -0
  64. package/lang/translations/zh.po +16 -0
  65. package/package.json +3 -3
  66. package/src/arialiveannouncer.d.ts +94 -0
  67. package/src/arialiveannouncer.js +113 -0
  68. package/src/autocomplete/autocompleteview.d.ts +81 -0
  69. package/src/autocomplete/autocompleteview.js +153 -0
  70. package/src/button/button.d.ts +0 -6
  71. package/src/button/buttonlabel.d.ts +34 -0
  72. package/src/button/buttonlabel.js +5 -0
  73. package/src/button/buttonlabelview.d.ts +31 -0
  74. package/src/button/buttonlabelview.js +42 -0
  75. package/src/button/buttonview.d.ts +14 -10
  76. package/src/button/buttonview.js +11 -25
  77. package/src/dropdown/dropdownview.js +5 -4
  78. package/src/dropdown/utils.d.ts +15 -1
  79. package/src/dropdown/utils.js +47 -21
  80. package/src/editorui/editorui.d.ts +6 -0
  81. package/src/editorui/editorui.js +2 -0
  82. package/src/editorui/poweredby.js +14 -37
  83. package/src/focuscycler.d.ts +45 -2
  84. package/src/focuscycler.js +34 -9
  85. package/src/formheader/formheaderview.d.ts +6 -0
  86. package/src/formheader/formheaderview.js +6 -0
  87. package/src/highlightedtext/highlightedtextview.d.ts +38 -0
  88. package/src/highlightedtext/highlightedtextview.js +102 -0
  89. package/src/icon/iconview.d.ts +7 -0
  90. package/src/icon/iconview.js +2 -0
  91. package/src/index.d.ts +12 -2
  92. package/src/index.js +8 -0
  93. package/src/input/inputbase.d.ts +107 -0
  94. package/src/input/inputbase.js +110 -0
  95. package/src/input/inputview.d.ts +4 -89
  96. package/src/input/inputview.js +5 -87
  97. package/src/labeledfield/labeledfieldview.d.ts +7 -2
  98. package/src/labeledfield/labeledfieldview.js +2 -2
  99. package/src/labeledfield/utils.d.ts +34 -4
  100. package/src/labeledfield/utils.js +51 -6
  101. package/src/list/listitemgroupview.d.ts +59 -0
  102. package/src/list/listitemgroupview.js +63 -0
  103. package/src/list/listitemview.d.ts +2 -1
  104. package/src/list/listitemview.js +3 -1
  105. package/src/list/listview.d.ts +59 -2
  106. package/src/list/listview.js +105 -8
  107. package/src/panel/balloon/balloonpanelview.js +26 -4
  108. package/src/panel/sticky/stickypanelview.d.ts +1 -3
  109. package/src/panel/sticky/stickypanelview.js +53 -50
  110. package/src/search/filteredview.d.ts +31 -0
  111. package/src/search/filteredview.js +5 -0
  112. package/src/search/searchinfoview.d.ts +45 -0
  113. package/src/search/searchinfoview.js +59 -0
  114. package/src/search/searchresultsview.d.ts +54 -0
  115. package/src/search/searchresultsview.js +65 -0
  116. package/src/search/text/searchtextqueryview.d.ts +76 -0
  117. package/src/search/text/searchtextqueryview.js +75 -0
  118. package/src/search/text/searchtextview.d.ts +219 -0
  119. package/src/search/text/searchtextview.js +201 -0
  120. package/src/spinner/spinnerview.d.ts +25 -0
  121. package/src/spinner/spinnerview.js +38 -0
  122. package/src/textarea/textareaview.d.ts +88 -0
  123. package/src/textarea/textareaview.js +142 -0
  124. package/src/toolbar/block/blocktoolbar.js +30 -26
  125. package/src/toolbar/normalizetoolbarconfig.d.ts +1 -0
  126. package/src/toolbar/normalizetoolbarconfig.js +9 -8
  127. package/src/toolbar/toolbarview.d.ts +1 -0
  128. package/src/toolbar/toolbarview.js +4 -2
  129. package/theme/components/arialiveannouncer/arialiveannouncer.css +10 -0
  130. package/theme/components/autocomplete/autocomplete.css +22 -0
  131. package/theme/components/button/button.css +9 -1
  132. package/theme/components/formheader/formheader.css +4 -0
  133. package/theme/components/highlightedtext/highlightedtext.css +12 -0
  134. package/theme/components/search/search.css +43 -0
  135. package/theme/components/spinner/spinner.css +23 -0
  136. package/theme/components/textarea/textarea.css +10 -0
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import View from '../view';
9
9
  import IconView from '../icon/iconview';
10
+ import ButtonLabelView from './buttonlabelview';
10
11
  import { env, getEnvKeystrokeText, uid, delay } from '@ckeditor/ckeditor5-utils';
11
12
  import '../../theme/components/button/button.css';
12
13
  /**
@@ -29,9 +30,13 @@ import '../../theme/components/button/button.css';
29
30
  */
30
31
  export default class ButtonView extends View {
31
32
  /**
32
- * @inheritDoc
33
+ * Creates an instance of the button view class.
34
+ *
35
+ * @param locale The {@link module:core/editor/editor~Editor#locale} instance.
36
+ * @param labelView The instance of the button's label. If not provided, an instance of
37
+ * {@link module:ui/button/buttonlabelview~ButtonLabelView} is used.
33
38
  */
34
- constructor(locale) {
39
+ constructor(locale, labelView = new ButtonLabelView()) {
35
40
  super(locale);
36
41
  /**
37
42
  * Delayed focus function for focus handling in Safari.
@@ -40,7 +45,6 @@ export default class ButtonView extends View {
40
45
  const bind = this.bindTemplate;
41
46
  const ariaLabelUid = uid();
42
47
  // Implement the Button interface.
43
- this.set('ariaChecked', undefined);
44
48
  this.set('ariaLabel', undefined);
45
49
  this.set('ariaLabelledBy', `ck-editor__aria-label_${ariaLabelUid}`);
46
50
  this.set('class', undefined);
@@ -60,7 +64,7 @@ export default class ButtonView extends View {
60
64
  this.set('withText', false);
61
65
  this.set('withKeystroke', false);
62
66
  this.children = this.createCollection();
63
- this.labelView = this._createLabelView();
67
+ this.labelView = this._setupLabelView(labelView);
64
68
  this.iconView = new IconView();
65
69
  this.iconView.extendTemplate({
66
70
  attributes: {
@@ -88,7 +92,6 @@ export default class ButtonView extends View {
88
92
  'aria-label': bind.to('ariaLabel'),
89
93
  'aria-labelledby': bind.to('ariaLabelledBy'),
90
94
  'aria-disabled': bind.if('isEnabled', true, value => !value),
91
- 'aria-checked': bind.to('isOn'),
92
95
  'aria-pressed': bind.to('isOn', value => this.isToggleable ? String(!!value) : false),
93
96
  'data-cke-tooltip-text': bind.to('_tooltipString'),
94
97
  'data-cke-tooltip-position': bind.to('tooltipPosition')
@@ -154,27 +157,10 @@ export default class ButtonView extends View {
154
157
  super.destroy();
155
158
  }
156
159
  /**
157
- * Creates a label view instance and binds it with button attributes.
160
+ * Binds the label view instance it with button attributes.
158
161
  */
159
- _createLabelView() {
160
- const labelView = new View();
161
- const bind = this.bindTemplate;
162
- labelView.setTemplate({
163
- tag: 'span',
164
- attributes: {
165
- class: [
166
- 'ck',
167
- 'ck-button__label'
168
- ],
169
- style: bind.to('labelStyle'),
170
- id: this.ariaLabelledBy
171
- },
172
- children: [
173
- {
174
- text: bind.to('label')
175
- }
176
- ]
177
- });
162
+ _setupLabelView(labelView) {
163
+ labelView.bind('text', 'style', 'id').to(this, 'label', 'labelStyle', 'ariaLabelledBy');
178
164
  return labelView;
179
165
  }
180
166
  /**
@@ -78,6 +78,8 @@ export default class DropdownView extends View {
78
78
  this.set('class', undefined);
79
79
  this.set('id', undefined);
80
80
  this.set('panelPosition', 'auto');
81
+ // Toggle the visibility of the panel when the dropdown becomes open.
82
+ this.panelView.bind('isVisible').to(this, 'isOpen');
81
83
  this.keystrokes = new KeystrokeHandler();
82
84
  this.focusTracker = new FocusTracker();
83
85
  this.setTemplate({
@@ -117,8 +119,6 @@ export default class DropdownView extends View {
117
119
  this.listenTo(this.buttonView, 'open', () => {
118
120
  this.isOpen = !this.isOpen;
119
121
  });
120
- // Toggle the visibility of the panel when the dropdown becomes open.
121
- this.panelView.bind('isVisible').to(this, 'isOpen');
122
122
  // Let the dropdown control the position of the panel. The position must
123
123
  // be updated every time the dropdown is open.
124
124
  this.on('change:isOpen', (evt, name, isOpen) => {
@@ -128,12 +128,13 @@ export default class DropdownView extends View {
128
128
  // If "auto", find the best position of the panel to fit into the viewport.
129
129
  // Otherwise, simply assign the static position.
130
130
  if (this.panelPosition === 'auto') {
131
- this.panelView.position = DropdownView._getOptimalPosition({
131
+ const optimalPanelPosition = DropdownView._getOptimalPosition({
132
132
  element: this.panelView.element,
133
133
  target: this.buttonView.element,
134
134
  fitInViewport: true,
135
135
  positions: this._panelPositions
136
- }).name;
136
+ });
137
+ this.panelView.position = (optimalPanelPosition ? optimalPanelPosition.name : this._panelPositions[0].name);
137
138
  }
138
139
  else {
139
140
  this.panelView.position = this.panelPosition;
@@ -202,7 +202,7 @@ export declare function focusChildOnDropdownOpen(dropdownView: DropdownView, chi
202
202
  * A definition of the list item used by the {@link module:ui/dropdown/utils~addListToDropdown}
203
203
  * utility.
204
204
  */
205
- export type ListDropdownItemDefinition = ListDropdownSeparatorDefinition | ListDropdownButtonDefinition;
205
+ export type ListDropdownItemDefinition = ListDropdownSeparatorDefinition | ListDropdownButtonDefinition | ListDropdownGroupDefinition;
206
206
  /**
207
207
  * A definition of the 'separator' list item.
208
208
  */
@@ -219,3 +219,17 @@ export type ListDropdownButtonDefinition = {
219
219
  */
220
220
  model: Model;
221
221
  };
222
+ /**
223
+ * A definition of the group inside the list. A group can contain one or more list items (buttons).
224
+ */
225
+ export type ListDropdownGroupDefinition = {
226
+ type: 'group';
227
+ /**
228
+ * The visible label of the group.
229
+ */
230
+ label: string;
231
+ /**
232
+ * The collection of the child list items inside this group.
233
+ */
234
+ items: Collection<ListDropdownButtonDefinition>;
235
+ };
@@ -20,6 +20,7 @@ import clickOutsideHandler from '../bindings/clickoutsidehandler';
20
20
  import { global, priorities, logWarning } from '@ckeditor/ckeditor5-utils';
21
21
  import '../../theme/components/dropdown/toolbardropdown.css';
22
22
  import '../../theme/components/dropdown/listdropdown.css';
23
+ import ListItemGroupView from '../list/listitemgroupview';
23
24
  /**
24
25
  * A helper for creating dropdowns. It creates an instance of a {@link module:ui/dropdown/dropdownview~DropdownView dropdown},
25
26
  * with a {@link module:ui/dropdown/button/dropdownbutton~DropdownButton button},
@@ -267,27 +268,7 @@ function addListToOpenDropdown(dropdownView, itemsOrCallback, options) {
267
268
  const items = typeof itemsOrCallback == 'function' ? itemsOrCallback() : itemsOrCallback;
268
269
  listView.ariaLabel = options.ariaLabel;
269
270
  listView.role = options.role;
270
- listView.items.bindTo(items).using(def => {
271
- if (def.type === 'separator') {
272
- return new ListSeparatorView(locale);
273
- }
274
- else if (def.type === 'button' || def.type === 'switchbutton') {
275
- const listItemView = new ListItemView(locale);
276
- let buttonView;
277
- if (def.type === 'button') {
278
- buttonView = new ButtonView(locale);
279
- }
280
- else {
281
- buttonView = new SwitchButtonView(locale);
282
- }
283
- // Bind all model properties to the button view.
284
- buttonView.bind(...Object.keys(def.model)).to(def.model);
285
- buttonView.delegate('execute').to(listItemView);
286
- listItemView.children.add(buttonView);
287
- return listItemView;
288
- }
289
- return null;
290
- });
271
+ bindViewCollectionItemsToDefinitions(dropdownView, listView.items, items, locale);
291
272
  dropdownView.panelView.children.add(listView);
292
273
  listView.items.delegate('execute').to(dropdownView);
293
274
  }
@@ -435,3 +416,48 @@ function focusDropdownPanelOnOpen(dropdownView) {
435
416
  // focus of a specific child by kicking in too late and resetting the focus in the panel.
436
417
  }, { priority: 'low' });
437
418
  }
419
+ /**
420
+ * This helper populates a dropdown list with items and groups according to the
421
+ * collection of item definitions. A permanent binding is created in this process allowing
422
+ * dynamic management of the dropdown list content.
423
+ *
424
+ * @param dropdownView
425
+ * @param listItems
426
+ * @param definitions
427
+ * @param locale
428
+ */
429
+ function bindViewCollectionItemsToDefinitions(dropdownView, listItems, definitions, locale) {
430
+ listItems.bindTo(definitions).using(def => {
431
+ if (def.type === 'separator') {
432
+ return new ListSeparatorView(locale);
433
+ }
434
+ else if (def.type === 'group') {
435
+ const groupView = new ListItemGroupView(locale);
436
+ groupView.set({ label: def.label });
437
+ bindViewCollectionItemsToDefinitions(dropdownView, groupView.items, def.items, locale);
438
+ groupView.items.delegate('execute').to(dropdownView);
439
+ return groupView;
440
+ }
441
+ else if (def.type === 'button' || def.type === 'switchbutton') {
442
+ const listItemView = new ListItemView(locale);
443
+ let buttonView;
444
+ if (def.type === 'button') {
445
+ buttonView = new ButtonView(locale);
446
+ buttonView.extendTemplate({
447
+ attributes: {
448
+ 'aria-checked': buttonView.bindTemplate.to('isOn')
449
+ }
450
+ });
451
+ }
452
+ else {
453
+ buttonView = new SwitchButtonView(locale);
454
+ }
455
+ // Bind all model properties to the button view.
456
+ buttonView.bind(...Object.keys(def.model)).to(def.model);
457
+ buttonView.delegate('execute').to(listItemView);
458
+ listItemView.children.add(buttonView);
459
+ return listItemView;
460
+ }
461
+ return null;
462
+ });
463
+ }
@@ -8,6 +8,7 @@
8
8
  import ComponentFactory from '../componentfactory';
9
9
  import TooltipManager from '../tooltipmanager';
10
10
  import PoweredBy from './poweredby';
11
+ import AriaLiveAnnouncer from '../arialiveannouncer';
11
12
  import type EditorUIView from './editoruiview';
12
13
  import type ToolbarView from '../toolbar/toolbarview';
13
14
  import { FocusTracker } from '@ckeditor/ckeditor5-utils';
@@ -42,6 +43,11 @@ export default abstract class EditorUI extends EditorUI_base {
42
43
  * A helper that enables the "powered by" feature in the editor and renders a link to the project's webpage.
43
44
  */
44
45
  readonly poweredBy: PoweredBy;
46
+ /**
47
+ * A helper that manages the content of an `aria-live` regions used by editor features to announce status changes
48
+ * to screen readers.
49
+ */
50
+ readonly ariaLiveAnnouncer: AriaLiveAnnouncer;
45
51
  /**
46
52
  * Indicates the UI is ready. Set `true` after {@link #event:ready} event is fired.
47
53
  *
@@ -9,6 +9,7 @@
9
9
  import ComponentFactory from '../componentfactory';
10
10
  import TooltipManager from '../tooltipmanager';
11
11
  import PoweredBy from './poweredby';
12
+ import AriaLiveAnnouncer from '../arialiveannouncer';
12
13
  import { ObservableMixin, isVisible, FocusTracker } from '@ckeditor/ckeditor5-utils';
13
14
  /**
14
15
  * A class providing the minimal interface that is required to successfully bootstrap any editor UI.
@@ -42,6 +43,7 @@ export default class EditorUI extends ObservableMixin() {
42
43
  this.focusTracker = new FocusTracker();
43
44
  this.tooltipManager = new TooltipManager(editor);
44
45
  this.poweredBy = new PoweredBy(editor);
46
+ this.ariaLiveAnnouncer = new AriaLiveAnnouncer(editor);
45
47
  this.set('viewportOffset', this._readViewportOffsetFromConfig());
46
48
  this.once('ready', () => {
47
49
  this.isReady = true;
@@ -2,7 +2,7 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
- import { Rect, DomEmitterMixin, findClosestScrollableAncestor, verifyLicense } from '@ckeditor/ckeditor5-utils';
5
+ import { DomEmitterMixin, Rect, verifyLicense } from '@ckeditor/ckeditor5-utils';
6
6
  import BalloonPanelView from '../panel/balloon/balloonpanelview';
7
7
  import IconView from '../icon/iconview';
8
8
  import View from '../view';
@@ -15,14 +15,6 @@ const ICON_HEIGHT = 10;
15
15
  const NARROW_ROOT_HEIGHT_THRESHOLD = 50;
16
16
  const NARROW_ROOT_WIDTH_THRESHOLD = 350;
17
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
18
  /**
27
19
  * A helper that enables the "powered by" feature in the editor and renders a link to the project's
28
20
  * webpage next to the bottom of the editable element (editor root, source editing area, etc.) when the editor is focused.
@@ -235,14 +227,10 @@ function getLowerLeftCornerPosition(focusedEditableElement, config) {
235
227
  return getLowerCornerPosition(focusedEditableElement, config, rootRect => rootRect.left + config.horizontalOffset);
236
228
  }
237
229
  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
- }
230
+ return (visibleEditableElementRect, balloonRect) => {
231
+ const editableElementRect = new Rect(focusedEditableElement);
244
232
  if (editableElementRect.width < NARROW_ROOT_WIDTH_THRESHOLD || editableElementRect.height < NARROW_ROOT_HEIGHT_THRESHOLD) {
245
- return OFF_THE_SCREEN_POSITION;
233
+ return null;
246
234
  }
247
235
  let balloonTop;
248
236
  if (config.position === 'inside') {
@@ -253,27 +241,16 @@ function getLowerCornerPosition(focusedEditableElement, config, getBalloonLeft)
253
241
  }
254
242
  balloonTop -= config.verticalOffset;
255
243
  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
- }
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;
277
254
  }
278
255
  return {
279
256
  top: balloonTop,
@@ -8,6 +8,10 @@
8
8
  import { type ArrayOrItem, type FocusTracker, type KeystrokeHandler } from '@ckeditor/ckeditor5-utils';
9
9
  import type View from './view';
10
10
  import type ViewCollection from './viewcollection';
11
+ declare const FocusCycler_base: {
12
+ new (): import("@ckeditor/ckeditor5-utils").Emitter;
13
+ prototype: import("@ckeditor/ckeditor5-utils").Emitter;
14
+ };
11
15
  /**
12
16
  * A utility class that helps cycling over focusable {@link module:ui/view~View views} in a
13
17
  * {@link module:ui/viewcollection~ViewCollection} when the focus is tracked by the
@@ -60,7 +64,7 @@ import type ViewCollection from './viewcollection';
60
64
  *
61
65
  * Check out the {@glink framework/deep-dive/ui/focus-tracking "Deep dive into focus tracking"} guide to learn more.
62
66
  */
63
- export default class FocusCycler {
67
+ export default class FocusCycler extends FocusCycler_base {
64
68
  /**
65
69
  * A {@link module:ui/view~View view} collection that the cycler operates on.
66
70
  */
@@ -162,6 +166,10 @@ export default class FocusCycler {
162
166
  focusPrevious(): void;
163
167
  /**
164
168
  * Focuses the given view if it exists.
169
+ *
170
+ * @param view The view to be focused
171
+ * @param direction The direction of the focus if the view has focusable children.
172
+ * @returns
165
173
  */
166
174
  private _focus;
167
175
  /**
@@ -172,8 +180,22 @@ export default class FocusCycler {
172
180
  */
173
181
  private _getFocusableItem;
174
182
  }
183
+ /**
184
+ * A view that can be focused.
185
+ */
175
186
  export type FocusableView = View & {
176
- focus(): void;
187
+ /**
188
+ * Focuses the view.
189
+ *
190
+ * @param direction This optional parameter helps improve the UX by providing additional information about the direction the focus moved
191
+ * (e.g. in a complex view or a form). It is useful for views that host multiple focusable children (e.g. lists, toolbars):
192
+ * * `1` indicates that the focus moved forward and, in most cases, the first child of the focused view should get focused,
193
+ * * `-1` indicates that the focus moved backwards, and the last focusable child should get focused
194
+ *
195
+ * See {@link module:ui/focuscycler~FocusCycler#event:forwardCycle} and {@link module:ui/focuscycler~FocusCycler#event:backwardCycle}
196
+ * to learn more.
197
+ */
198
+ focus(direction?: 1 | -1): void;
177
199
  };
178
200
  export interface FocusCyclerActions {
179
201
  focusFirst?: ArrayOrItem<string>;
@@ -181,3 +203,24 @@ export interface FocusCyclerActions {
181
203
  focusNext?: ArrayOrItem<string>;
182
204
  focusPrevious?: ArrayOrItem<string>;
183
205
  }
206
+ /**
207
+ * Fired when the focus cycler is about to move the focus from the last focusable item
208
+ * to the first one.
209
+ *
210
+ * @eventName ~FocusCycler#forwardCycle
211
+ */
212
+ export type FocusCyclerForwardCycleEvent = {
213
+ name: 'forwardCycle';
214
+ args: [];
215
+ };
216
+ /**
217
+ * Fired when the focus cycler is about to move the focus from the first focusable item
218
+ * to the last one.
219
+ *
220
+ * @eventName ~FocusCycler#backwardCycle
221
+ */
222
+ export type FocusCyclerBackwardCycleEvent = {
223
+ name: 'backwardCycle';
224
+ args: [];
225
+ };
226
+ export {};
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @module ui/focuscycler
7
7
  */
8
- import { isVisible } from '@ckeditor/ckeditor5-utils';
8
+ import { isVisible, EmitterMixin } from '@ckeditor/ckeditor5-utils';
9
9
  /**
10
10
  * A utility class that helps cycling over focusable {@link module:ui/view~View views} in a
11
11
  * {@link module:ui/viewcollection~ViewCollection} when the focus is tracked by the
@@ -58,13 +58,14 @@ import { isVisible } from '@ckeditor/ckeditor5-utils';
58
58
  *
59
59
  * Check out the {@glink framework/deep-dive/ui/focus-tracking "Deep dive into focus tracking"} guide to learn more.
60
60
  */
61
- export default class FocusCycler {
61
+ export default class FocusCycler extends EmitterMixin() {
62
62
  /**
63
63
  * Creates an instance of the focus cycler utility.
64
64
  *
65
65
  * @param options Configuration options.
66
66
  */
67
67
  constructor(options) {
68
+ super();
68
69
  this.focusables = options.focusables;
69
70
  this.focusTracker = options.focusTracker;
70
71
  this.keystrokeHandler = options.keystrokeHandler;
@@ -83,6 +84,8 @@ export default class FocusCycler {
83
84
  }
84
85
  }
85
86
  }
87
+ this.on('forwardCycle', () => this.focusFirst(), { priority: 'low' });
88
+ this.on('backwardCycle', () => this.focusLast(), { priority: 'low' });
86
89
  }
87
90
  /**
88
91
  * Returns the first focusable view in {@link #focusables}.
@@ -145,7 +148,7 @@ export default class FocusCycler {
145
148
  * **Note**: Hidden views (e.g. with `display: none`) are ignored.
146
149
  */
147
150
  focusFirst() {
148
- this._focus(this.first);
151
+ this._focus(this.first, 1);
149
152
  }
150
153
  /**
151
154
  * Focuses the {@link #last} item in {@link #focusables}.
@@ -153,7 +156,7 @@ export default class FocusCycler {
153
156
  * **Note**: Hidden views (e.g. with `display: none`) are ignored.
154
157
  */
155
158
  focusLast() {
156
- this._focus(this.last);
159
+ this._focus(this.last, -1);
157
160
  }
158
161
  /**
159
162
  * Focuses the {@link #next} item in {@link #focusables}.
@@ -161,7 +164,16 @@ export default class FocusCycler {
161
164
  * **Note**: Hidden views (e.g. with `display: none`) are ignored.
162
165
  */
163
166
  focusNext() {
164
- this._focus(this.next);
167
+ const next = this.next;
168
+ if (next && this.focusables.getIndex(next) === this.current) {
169
+ return;
170
+ }
171
+ if (next === this.first) {
172
+ this.fire('forwardCycle');
173
+ }
174
+ else {
175
+ this._focus(next, 1);
176
+ }
165
177
  }
166
178
  /**
167
179
  * Focuses the {@link #previous} item in {@link #focusables}.
@@ -169,14 +181,27 @@ export default class FocusCycler {
169
181
  * **Note**: Hidden views (e.g. with `display: none`) are ignored.
170
182
  */
171
183
  focusPrevious() {
172
- this._focus(this.previous);
184
+ const previous = this.previous;
185
+ if (previous && this.focusables.getIndex(previous) === this.current) {
186
+ return;
187
+ }
188
+ if (previous === this.last) {
189
+ this.fire('backwardCycle');
190
+ }
191
+ else {
192
+ this._focus(previous, -1);
193
+ }
173
194
  }
174
195
  /**
175
196
  * Focuses the given view if it exists.
197
+ *
198
+ * @param view The view to be focused
199
+ * @param direction The direction of the focus if the view has focusable children.
200
+ * @returns
176
201
  */
177
- _focus(view) {
202
+ _focus(view, direction) {
178
203
  if (view) {
179
- view.focus();
204
+ view.focus(direction);
180
205
  }
181
206
  }
182
207
  /**
@@ -216,5 +241,5 @@ export default class FocusCycler {
216
241
  * @param view A view to be checked.
217
242
  */
218
243
  function isFocusable(view) {
219
- return !!(view.focus && isVisible(view.element));
244
+ return !!('focus' in view && isVisible(view.element));
220
245
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import View from '../view';
9
9
  import type ViewCollection from '../viewcollection';
10
+ import IconView from '../icon/iconview';
10
11
  import type { Locale } from '@ckeditor/ckeditor5-utils';
11
12
  import '../../theme/components/formheader/formheader.css';
12
13
  /**
@@ -39,6 +40,10 @@ export default class FormHeaderView extends View {
39
40
  * @observable
40
41
  */
41
42
  class: string | null;
43
+ /**
44
+ * The icon view instance. Defined only if icon was passed in the constructor's options.
45
+ */
46
+ readonly iconView?: IconView;
42
47
  /**
43
48
  * Creates an instance of the form header class.
44
49
  *
@@ -49,5 +54,6 @@ export default class FormHeaderView extends View {
49
54
  constructor(locale: Locale | undefined, options?: {
50
55
  label?: string | null;
51
56
  class?: string | null;
57
+ icon?: string | null;
52
58
  });
53
59
  }
@@ -6,6 +6,7 @@
6
6
  * @module ui/formheader/formheaderview
7
7
  */
8
8
  import View from '../view';
9
+ import IconView from '../icon/iconview';
9
10
  import '../../theme/components/formheader/formheader.css';
10
11
  /**
11
12
  * The class component representing a form header view. It should be used in more advanced forms to
@@ -45,6 +46,11 @@ export default class FormHeaderView extends View {
45
46
  },
46
47
  children: this.children
47
48
  });
49
+ if (options.icon) {
50
+ this.iconView = new IconView();
51
+ this.iconView.content = options.icon;
52
+ this.children.add(this.iconView);
53
+ }
48
54
  const label = new View(locale);
49
55
  label.setTemplate({
50
56
  tag: 'h2',
@@ -0,0 +1,38 @@
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
+ /**
6
+ * @module ui/highlightedtext/highlightedtextview
7
+ */
8
+ import View from '../view';
9
+ import '../../theme/components/highlightedtext/highlightedtext.css';
10
+ /**
11
+ * A class representing a view that displays a text which subset can be highlighted using the
12
+ * {@link #highlightText} method.
13
+ */
14
+ export default class HighlightedTextView extends View {
15
+ /**
16
+ * The text that can be highlighted using the {@link #highlightText} method.
17
+ *
18
+ * **Note:** When this property changes, the previous highlighting is removed.
19
+ *
20
+ * @observable
21
+ */
22
+ text: string | undefined;
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ constructor();
27
+ /**
28
+ * Highlights view's {@link #text} according to the specified `RegExp`. If the passed RegExp is `null`, the
29
+ * highlighting is removed
30
+ *
31
+ * @param regExp
32
+ */
33
+ highlightText(regExp: RegExp | null): void;
34
+ /**
35
+ * Updates element's `innerHTML` with the passed content.
36
+ */
37
+ private _updateInnerHTML;
38
+ }