@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.
- package/LICENSE.md +3 -3
- package/lang/contexts.json +5 -1
- package/lang/translations/ar.po +16 -0
- package/lang/translations/ast.po +16 -0
- package/lang/translations/az.po +16 -0
- package/lang/translations/bg.po +16 -0
- package/lang/translations/bn.po +16 -0
- package/lang/translations/ca.po +16 -0
- package/lang/translations/cs.po +16 -0
- package/lang/translations/da.po +16 -0
- package/lang/translations/de-ch.po +16 -0
- package/lang/translations/de.po +16 -0
- package/lang/translations/el.po +16 -0
- package/lang/translations/en-au.po +16 -0
- package/lang/translations/en-gb.po +16 -0
- package/lang/translations/en.po +16 -0
- package/lang/translations/eo.po +16 -0
- package/lang/translations/es.po +16 -0
- package/lang/translations/et.po +16 -0
- package/lang/translations/eu.po +16 -0
- package/lang/translations/fa.po +16 -0
- package/lang/translations/fi.po +16 -0
- package/lang/translations/fr.po +16 -0
- package/lang/translations/gl.po +16 -0
- package/lang/translations/he.po +16 -0
- package/lang/translations/hi.po +16 -0
- package/lang/translations/hr.po +16 -0
- package/lang/translations/hu.po +16 -0
- package/lang/translations/id.po +16 -0
- package/lang/translations/it.po +16 -0
- package/lang/translations/ja.po +16 -0
- package/lang/translations/km.po +16 -0
- package/lang/translations/kn.po +16 -0
- package/lang/translations/ko.po +16 -0
- package/lang/translations/ku.po +16 -0
- package/lang/translations/lt.po +16 -0
- package/lang/translations/lv.po +16 -0
- package/lang/translations/ms.po +16 -0
- package/lang/translations/nb.po +16 -0
- package/lang/translations/ne.po +16 -0
- package/lang/translations/nl.po +16 -0
- package/lang/translations/no.po +16 -0
- package/lang/translations/pl.po +16 -0
- package/lang/translations/pt-br.po +17 -1
- package/lang/translations/pt.po +16 -0
- package/lang/translations/ro.po +16 -0
- package/lang/translations/ru.po +16 -0
- package/lang/translations/sk.po +16 -0
- package/lang/translations/sl.po +16 -0
- package/lang/translations/sq.po +16 -0
- package/lang/translations/sr-latn.po +16 -0
- package/lang/translations/sr.po +16 -0
- package/lang/translations/sv.po +16 -0
- package/lang/translations/th.po +16 -0
- package/lang/translations/tk.po +16 -0
- package/lang/translations/tr.po +16 -0
- package/lang/translations/tt.po +16 -0
- package/lang/translations/ug.po +38 -22
- package/lang/translations/uk.po +16 -0
- package/lang/translations/ur.po +16 -0
- package/lang/translations/uz.po +16 -0
- package/lang/translations/vi.po +16 -0
- package/lang/translations/zh-cn.po +16 -0
- package/lang/translations/zh.po +16 -0
- package/package.json +3 -3
- package/src/arialiveannouncer.d.ts +94 -0
- package/src/arialiveannouncer.js +113 -0
- package/src/autocomplete/autocompleteview.d.ts +81 -0
- package/src/autocomplete/autocompleteview.js +153 -0
- package/src/button/button.d.ts +0 -6
- package/src/button/buttonlabel.d.ts +34 -0
- package/src/button/buttonlabel.js +5 -0
- package/src/button/buttonlabelview.d.ts +31 -0
- package/src/button/buttonlabelview.js +42 -0
- package/src/button/buttonview.d.ts +14 -10
- package/src/button/buttonview.js +11 -25
- package/src/dropdown/dropdownview.js +5 -4
- package/src/dropdown/utils.d.ts +15 -1
- package/src/dropdown/utils.js +47 -21
- package/src/editorui/editorui.d.ts +6 -0
- package/src/editorui/editorui.js +2 -0
- package/src/editorui/poweredby.js +14 -37
- package/src/focuscycler.d.ts +45 -2
- package/src/focuscycler.js +34 -9
- package/src/formheader/formheaderview.d.ts +6 -0
- package/src/formheader/formheaderview.js +6 -0
- package/src/highlightedtext/highlightedtextview.d.ts +38 -0
- package/src/highlightedtext/highlightedtextview.js +102 -0
- package/src/icon/iconview.d.ts +7 -0
- package/src/icon/iconview.js +2 -0
- package/src/index.d.ts +12 -2
- package/src/index.js +8 -0
- package/src/input/inputbase.d.ts +107 -0
- package/src/input/inputbase.js +110 -0
- package/src/input/inputview.d.ts +4 -89
- package/src/input/inputview.js +5 -87
- package/src/labeledfield/labeledfieldview.d.ts +7 -2
- package/src/labeledfield/labeledfieldview.js +2 -2
- package/src/labeledfield/utils.d.ts +34 -4
- package/src/labeledfield/utils.js +51 -6
- package/src/list/listitemgroupview.d.ts +59 -0
- package/src/list/listitemgroupview.js +63 -0
- package/src/list/listitemview.d.ts +2 -1
- package/src/list/listitemview.js +3 -1
- package/src/list/listview.d.ts +59 -2
- package/src/list/listview.js +105 -8
- package/src/panel/balloon/balloonpanelview.js +26 -4
- package/src/panel/sticky/stickypanelview.d.ts +1 -3
- package/src/panel/sticky/stickypanelview.js +53 -50
- package/src/search/filteredview.d.ts +31 -0
- package/src/search/filteredview.js +5 -0
- package/src/search/searchinfoview.d.ts +45 -0
- package/src/search/searchinfoview.js +59 -0
- package/src/search/searchresultsview.d.ts +54 -0
- package/src/search/searchresultsview.js +65 -0
- package/src/search/text/searchtextqueryview.d.ts +76 -0
- package/src/search/text/searchtextqueryview.js +75 -0
- package/src/search/text/searchtextview.d.ts +219 -0
- package/src/search/text/searchtextview.js +201 -0
- package/src/spinner/spinnerview.d.ts +25 -0
- package/src/spinner/spinnerview.js +38 -0
- package/src/textarea/textareaview.d.ts +88 -0
- package/src/textarea/textareaview.js +142 -0
- package/src/toolbar/block/blocktoolbar.js +30 -26
- package/src/toolbar/normalizetoolbarconfig.d.ts +1 -0
- package/src/toolbar/normalizetoolbarconfig.js +9 -8
- package/src/toolbar/toolbarview.d.ts +1 -0
- package/src/toolbar/toolbarview.js +4 -2
- package/theme/components/arialiveannouncer/arialiveannouncer.css +10 -0
- package/theme/components/autocomplete/autocomplete.css +22 -0
- package/theme/components/button/button.css +9 -1
- package/theme/components/formheader/formheader.css +4 -0
- package/theme/components/highlightedtext/highlightedtext.css +12 -0
- package/theme/components/search/search.css +43 -0
- package/theme/components/spinner/spinner.css +23 -0
- package/theme/components/textarea/textarea.css +10 -0
package/src/button/buttonview.js
CHANGED
|
@@ -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
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
160
|
+
* Binds the label view instance it with button attributes.
|
|
158
161
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
})
|
|
136
|
+
});
|
|
137
|
+
this.panelView.position = (optimalPanelPosition ? optimalPanelPosition.name : this._panelPositions[0].name);
|
|
137
138
|
}
|
|
138
139
|
else {
|
|
139
140
|
this.panelView.position = this.panelPosition;
|
package/src/dropdown/utils.d.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/dropdown/utils.js
CHANGED
|
@@ -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
|
|
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
|
*
|
package/src/editorui/editorui.js
CHANGED
|
@@ -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 {
|
|
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 (
|
|
239
|
-
const
|
|
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
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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,
|
package/src/focuscycler.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 {};
|
package/src/focuscycler.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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 !!(
|
|
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
|
+
}
|