@ckeditor/ckeditor5-ui 40.0.0 → 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/translations/pt-br.po +1 -1
- package/lang/translations/ug.po +26 -26
- package/package.json +3 -3
- package/src/arialiveannouncer.d.ts +94 -0
- package/src/arialiveannouncer.js +113 -0
- package/src/augmentation.d.ts +86 -86
- package/src/augmentation.js +5 -5
- package/src/autocomplete/autocompleteview.d.ts +81 -81
- package/src/autocomplete/autocompleteview.js +153 -146
- package/src/bindings/addkeyboardhandlingforgrid.d.ts +27 -27
- package/src/bindings/addkeyboardhandlingforgrid.js +107 -107
- package/src/bindings/clickoutsidehandler.d.ts +28 -28
- package/src/bindings/clickoutsidehandler.js +36 -36
- package/src/bindings/csstransitiondisablermixin.d.ts +40 -40
- package/src/bindings/csstransitiondisablermixin.js +55 -55
- package/src/bindings/injectcsstransitiondisabler.d.ts +59 -59
- package/src/bindings/injectcsstransitiondisabler.js +71 -71
- package/src/bindings/preventdefault.d.ts +33 -33
- package/src/bindings/preventdefault.js +34 -34
- package/src/bindings/submithandler.d.ts +57 -57
- package/src/bindings/submithandler.js +47 -47
- package/src/button/button.d.ts +172 -178
- package/src/button/button.js +5 -5
- package/src/button/buttonlabel.d.ts +34 -34
- package/src/button/buttonlabel.js +5 -5
- package/src/button/buttonlabelview.d.ts +31 -31
- package/src/button/buttonlabelview.js +42 -42
- package/src/button/buttonview.d.ts +181 -185
- package/src/button/buttonview.js +217 -219
- package/src/button/switchbuttonview.d.ts +45 -45
- package/src/button/switchbuttonview.js +75 -75
- package/src/colorgrid/colorgridview.d.ts +132 -132
- package/src/colorgrid/colorgridview.js +124 -124
- package/src/colorgrid/colortileview.d.ts +28 -28
- package/src/colorgrid/colortileview.js +40 -40
- package/src/colorgrid/utils.d.ts +47 -47
- package/src/colorgrid/utils.js +84 -84
- package/src/colorpicker/colorpickerview.d.ts +137 -137
- package/src/colorpicker/colorpickerview.js +270 -270
- package/src/colorpicker/utils.d.ts +43 -43
- package/src/colorpicker/utils.js +99 -99
- package/src/colorselector/colorgridsfragmentview.d.ts +194 -194
- package/src/colorselector/colorgridsfragmentview.js +289 -289
- package/src/colorselector/colorpickerfragmentview.d.ts +128 -128
- package/src/colorselector/colorpickerfragmentview.js +205 -205
- package/src/colorselector/colorselectorview.d.ts +242 -242
- package/src/colorselector/colorselectorview.js +256 -256
- package/src/colorselector/documentcolorcollection.d.ts +70 -70
- package/src/colorselector/documentcolorcollection.js +42 -42
- package/src/componentfactory.d.ts +81 -81
- package/src/componentfactory.js +104 -104
- package/src/dropdown/button/dropdownbutton.d.ts +25 -25
- package/src/dropdown/button/dropdownbutton.js +5 -5
- package/src/dropdown/button/dropdownbuttonview.d.ts +48 -48
- package/src/dropdown/button/dropdownbuttonview.js +66 -66
- package/src/dropdown/button/splitbuttonview.d.ts +161 -161
- package/src/dropdown/button/splitbuttonview.js +152 -152
- package/src/dropdown/dropdownpanelfocusable.d.ts +21 -21
- package/src/dropdown/dropdownpanelfocusable.js +5 -5
- package/src/dropdown/dropdownpanelview.d.ts +62 -62
- package/src/dropdown/dropdownpanelview.js +97 -97
- package/src/dropdown/dropdownview.d.ts +315 -315
- package/src/dropdown/dropdownview.js +379 -379
- package/src/dropdown/utils.d.ts +235 -235
- package/src/dropdown/utils.js +463 -458
- package/src/editableui/editableuiview.d.ts +72 -72
- package/src/editableui/editableuiview.js +112 -112
- package/src/editableui/inline/inlineeditableuiview.d.ts +40 -40
- package/src/editableui/inline/inlineeditableuiview.js +48 -48
- package/src/editorui/bodycollection.d.ts +55 -55
- package/src/editorui/bodycollection.js +84 -84
- package/src/editorui/boxed/boxededitoruiview.d.ts +40 -40
- package/src/editorui/boxed/boxededitoruiview.js +81 -81
- package/src/editorui/editorui.d.ts +288 -282
- package/src/editorui/editorui.js +412 -410
- package/src/editorui/editoruiview.d.ts +39 -39
- package/src/editorui/editoruiview.js +38 -38
- package/src/editorui/poweredby.d.ts +71 -71
- package/src/editorui/poweredby.js +276 -276
- package/src/focuscycler.d.ts +226 -226
- package/src/focuscycler.js +245 -245
- package/src/formheader/formheaderview.d.ts +59 -59
- package/src/formheader/formheaderview.js +69 -69
- package/src/highlightedtext/highlightedtextview.d.ts +38 -38
- package/src/highlightedtext/highlightedtextview.js +102 -102
- package/src/icon/iconview.d.ts +85 -85
- package/src/icon/iconview.js +114 -114
- package/src/iframe/iframeview.d.ts +50 -50
- package/src/iframe/iframeview.js +63 -63
- package/src/index.d.ts +73 -73
- package/src/index.js +70 -70
- package/src/input/inputbase.d.ts +107 -107
- package/src/input/inputbase.js +110 -110
- package/src/input/inputview.d.ts +36 -36
- package/src/input/inputview.js +24 -24
- package/src/inputnumber/inputnumberview.d.ts +49 -49
- package/src/inputnumber/inputnumberview.js +40 -40
- package/src/inputtext/inputtextview.d.ts +18 -18
- package/src/inputtext/inputtextview.js +27 -27
- package/src/label/labelview.d.ts +36 -36
- package/src/label/labelview.js +41 -41
- package/src/labeledfield/labeledfieldview.d.ts +187 -187
- package/src/labeledfield/labeledfieldview.js +157 -157
- package/src/labeledfield/utils.d.ts +123 -123
- package/src/labeledfield/utils.js +176 -176
- package/src/labeledinput/labeledinputview.d.ts +125 -125
- package/src/labeledinput/labeledinputview.js +125 -125
- package/src/list/listitemgroupview.d.ts +59 -51
- package/src/list/listitemgroupview.js +63 -75
- package/src/list/listitemview.d.ts +36 -36
- package/src/list/listitemview.js +42 -42
- package/src/list/listseparatorview.d.ts +18 -18
- package/src/list/listseparatorview.js +28 -28
- package/src/list/listview.d.ts +122 -122
- package/src/list/listview.js +187 -187
- package/src/model.d.ts +22 -22
- package/src/model.js +31 -31
- package/src/notification/notification.d.ts +211 -211
- package/src/notification/notification.js +187 -187
- package/src/panel/balloon/balloonpanelview.d.ts +685 -685
- package/src/panel/balloon/balloonpanelview.js +1010 -1010
- package/src/panel/balloon/contextualballoon.d.ts +299 -299
- package/src/panel/balloon/contextualballoon.js +572 -572
- package/src/panel/sticky/stickypanelview.d.ts +156 -156
- package/src/panel/sticky/stickypanelview.js +234 -234
- package/src/search/filteredview.d.ts +31 -31
- package/src/search/filteredview.js +5 -5
- package/src/search/searchinfoview.d.ts +45 -45
- package/src/search/searchinfoview.js +59 -59
- package/src/search/searchresultsview.d.ts +54 -54
- package/src/search/searchresultsview.js +65 -65
- package/src/search/text/searchtextqueryview.d.ts +76 -76
- package/src/search/text/searchtextqueryview.js +75 -75
- package/src/search/text/searchtextview.d.ts +219 -219
- package/src/search/text/searchtextview.js +201 -201
- package/src/spinner/spinnerview.d.ts +25 -25
- package/src/spinner/spinnerview.js +38 -38
- package/src/template.d.ts +942 -942
- package/src/template.js +1294 -1294
- package/src/textarea/textareaview.d.ts +88 -88
- package/src/textarea/textareaview.js +142 -140
- package/src/toolbar/balloon/balloontoolbar.d.ts +122 -122
- package/src/toolbar/balloon/balloontoolbar.js +300 -300
- package/src/toolbar/block/blockbuttonview.d.ts +35 -35
- package/src/toolbar/block/blockbuttonview.js +41 -41
- package/src/toolbar/block/blocktoolbar.d.ts +161 -161
- package/src/toolbar/block/blocktoolbar.js +395 -395
- package/src/toolbar/normalizetoolbarconfig.d.ts +40 -40
- package/src/toolbar/normalizetoolbarconfig.js +52 -51
- package/src/toolbar/toolbarlinebreakview.d.ts +18 -18
- package/src/toolbar/toolbarlinebreakview.js +28 -28
- package/src/toolbar/toolbarseparatorview.d.ts +18 -18
- package/src/toolbar/toolbarseparatorview.js +28 -28
- package/src/toolbar/toolbarview.d.ts +266 -266
- package/src/toolbar/toolbarview.js +719 -719
- package/src/tooltipmanager.d.ts +180 -180
- package/src/tooltipmanager.js +353 -353
- package/src/view.d.ts +422 -422
- package/src/view.js +396 -396
- package/src/viewcollection.d.ts +139 -139
- package/src/viewcollection.js +206 -206
- package/theme/components/arialiveannouncer/arialiveannouncer.css +10 -0
- package/theme/components/button/button.css +9 -1
- package/theme/components/formheader/formheader.css +0 -4
|
@@ -1,572 +1,572 @@
|
|
|
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/panel/balloon/contextualballoon
|
|
7
|
-
*/
|
|
8
|
-
import BalloonPanelView from './balloonpanelview';
|
|
9
|
-
import View from '../../view';
|
|
10
|
-
import ButtonView from '../../button/buttonview';
|
|
11
|
-
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
12
|
-
import { CKEditorError, FocusTracker, Rect, toUnit } from '@ckeditor/ckeditor5-utils';
|
|
13
|
-
import prevIcon from '../../../theme/icons/previous-arrow.svg';
|
|
14
|
-
import nextIcon from '../../../theme/icons/next-arrow.svg';
|
|
15
|
-
import '../../../theme/components/panel/balloonrotator.css';
|
|
16
|
-
import '../../../theme/components/panel/fakepanel.css';
|
|
17
|
-
const toPx = toUnit('px');
|
|
18
|
-
/**
|
|
19
|
-
* Provides the common contextual balloon for the editor.
|
|
20
|
-
*
|
|
21
|
-
* The role of this plugin is to unify the contextual balloons logic, simplify views management and help
|
|
22
|
-
* avoid the unnecessary complexity of handling multiple {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
|
|
23
|
-
* instances in the editor.
|
|
24
|
-
*
|
|
25
|
-
* This plugin allows for creating single or multiple panel stacks.
|
|
26
|
-
*
|
|
27
|
-
* Each stack may have multiple views, with the one on the top being visible. When the visible view is removed from the stack,
|
|
28
|
-
* the previous view becomes visible.
|
|
29
|
-
*
|
|
30
|
-
* It might be useful to implement nested navigation in a balloon. For instance, a toolbar view may contain a link button.
|
|
31
|
-
* When you click it, a link view (which lets you set the URL) is created and put on top of the toolbar view, so the link panel
|
|
32
|
-
* is displayed. When you finish editing the link and close (remove) the link view, the toolbar view is visible again.
|
|
33
|
-
*
|
|
34
|
-
* However, there are cases when there are multiple independent balloons to be displayed, for instance, if the selection
|
|
35
|
-
* is inside two inline comments at the same time. For such cases, you can create two independent panel stacks.
|
|
36
|
-
* The contextual balloon plugin will create a navigation bar to let the users switch between these panel stacks using the "Next"
|
|
37
|
-
* and "Previous" buttons.
|
|
38
|
-
*
|
|
39
|
-
* If there are no views in the current stack, the balloon panel will try to switch to the next stack. If there are no
|
|
40
|
-
* panels in any stack, the balloon panel will be hidden.
|
|
41
|
-
*
|
|
42
|
-
* **Note**: To force the balloon panel to show only one view, even if there are other stacks, use the `singleViewMode=true` option
|
|
43
|
-
* when {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon#add adding} a view to a panel.
|
|
44
|
-
*
|
|
45
|
-
* From the implementation point of view, the contextual ballon plugin is reusing a single
|
|
46
|
-
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView} instance to display multiple contextual balloon
|
|
47
|
-
* panels in the editor. It also creates a special {@link module:ui/panel/balloon/contextualballoon~RotatorView rotator view},
|
|
48
|
-
* used to manage multiple panel stacks. Rotator view is a child of the balloon panel view and the parent of the specific
|
|
49
|
-
* view you want to display. If there is more than one panel stack to be displayed, the rotator view will add a
|
|
50
|
-
* navigation bar. If there is only one stack, the rotator view is transparent (it does not add any UI elements).
|
|
51
|
-
*/
|
|
52
|
-
export default class ContextualBalloon extends Plugin {
|
|
53
|
-
/**
|
|
54
|
-
* @inheritDoc
|
|
55
|
-
*/
|
|
56
|
-
static get pluginName() {
|
|
57
|
-
return 'ContextualBalloon';
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* @inheritDoc
|
|
61
|
-
*/
|
|
62
|
-
constructor(editor) {
|
|
63
|
-
super(editor);
|
|
64
|
-
/**
|
|
65
|
-
* The map of views and their stacks.
|
|
66
|
-
*/
|
|
67
|
-
this._viewToStack = new Map();
|
|
68
|
-
/**
|
|
69
|
-
* The map of IDs and stacks.
|
|
70
|
-
*/
|
|
71
|
-
this._idToStack = new Map();
|
|
72
|
-
/**
|
|
73
|
-
* The common balloon panel view.
|
|
74
|
-
*/
|
|
75
|
-
this._view = null;
|
|
76
|
-
/**
|
|
77
|
-
* Rotator view embedded in the contextual balloon.
|
|
78
|
-
* Displays the currently visible view in the balloon and provides navigation for switching stacks.
|
|
79
|
-
*/
|
|
80
|
-
this._rotatorView = null;
|
|
81
|
-
/**
|
|
82
|
-
* Displays fake panels under the balloon panel view when multiple stacks are added to the balloon.
|
|
83
|
-
*/
|
|
84
|
-
this._fakePanelsView = null;
|
|
85
|
-
this.positionLimiter = () => {
|
|
86
|
-
const view = this.editor.editing.view;
|
|
87
|
-
const viewDocument = view.document;
|
|
88
|
-
const editableElement = viewDocument.selection.editableElement;
|
|
89
|
-
if (editableElement) {
|
|
90
|
-
return view.domConverter.mapViewToDom(editableElement.root);
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
};
|
|
94
|
-
this.set('visibleView', null);
|
|
95
|
-
this.set('_numberOfStacks', 0);
|
|
96
|
-
this.set('_singleViewMode', false);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* @inheritDoc
|
|
100
|
-
*/
|
|
101
|
-
destroy() {
|
|
102
|
-
super.destroy();
|
|
103
|
-
if (this._view) {
|
|
104
|
-
this._view.destroy();
|
|
105
|
-
}
|
|
106
|
-
if (this._rotatorView) {
|
|
107
|
-
this._rotatorView.destroy();
|
|
108
|
-
}
|
|
109
|
-
if (this._fakePanelsView) {
|
|
110
|
-
this._fakePanelsView.destroy();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* The common balloon panel view.
|
|
115
|
-
*/
|
|
116
|
-
get view() {
|
|
117
|
-
if (!this._view) {
|
|
118
|
-
this._createPanelView();
|
|
119
|
-
}
|
|
120
|
-
return this._view;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Returns `true` when the given view is in one of the stacks. Otherwise returns `false`.
|
|
124
|
-
*/
|
|
125
|
-
hasView(view) {
|
|
126
|
-
return Array.from(this._viewToStack.keys()).includes(view);
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Adds a new view to the stack and makes it visible if the current stack is visible
|
|
130
|
-
* or it is the first view in the balloon.
|
|
131
|
-
*
|
|
132
|
-
* @param data The configuration of the view.
|
|
133
|
-
* @param data.stackId The ID of the stack that the view is added to. Defaults to `'main'`.
|
|
134
|
-
* @param data.view The content of the balloon.
|
|
135
|
-
* @param data.position Positioning options.
|
|
136
|
-
* @param data.balloonClassName An additional CSS class added to the {@link #view balloon} when visible.
|
|
137
|
-
* @param data.withArrow Whether the {@link #view balloon} should be rendered with an arrow. Defaults to `true`.
|
|
138
|
-
* @param data.singleViewMode Whether the view should be the only visible view even if other stacks were added. Defaults to `false`.
|
|
139
|
-
*/
|
|
140
|
-
add(data) {
|
|
141
|
-
if (!this._view) {
|
|
142
|
-
this._createPanelView();
|
|
143
|
-
}
|
|
144
|
-
if (this.hasView(data.view)) {
|
|
145
|
-
/**
|
|
146
|
-
* Trying to add configuration of the same view more than once.
|
|
147
|
-
*
|
|
148
|
-
* @error contextualballoon-add-view-exist
|
|
149
|
-
*/
|
|
150
|
-
throw new CKEditorError('contextualballoon-add-view-exist', [this, data]);
|
|
151
|
-
}
|
|
152
|
-
const stackId = data.stackId || 'main';
|
|
153
|
-
// If new stack is added, creates it and show view from this stack.
|
|
154
|
-
if (!this._idToStack.has(stackId)) {
|
|
155
|
-
this._idToStack.set(stackId, new Map([[data.view, data]]));
|
|
156
|
-
this._viewToStack.set(data.view, this._idToStack.get(stackId));
|
|
157
|
-
this._numberOfStacks = this._idToStack.size;
|
|
158
|
-
if (!this._visibleStack || data.singleViewMode) {
|
|
159
|
-
this.showStack(stackId);
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const stack = this._idToStack.get(stackId);
|
|
164
|
-
if (data.singleViewMode) {
|
|
165
|
-
this.showStack(stackId);
|
|
166
|
-
}
|
|
167
|
-
// Add new view to the stack.
|
|
168
|
-
stack.set(data.view, data);
|
|
169
|
-
this._viewToStack.set(data.view, stack);
|
|
170
|
-
// And display it if is added to the currently visible stack.
|
|
171
|
-
if (stack === this._visibleStack) {
|
|
172
|
-
this._showView(data);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Removes the given view from the stack. If the removed view was visible,
|
|
177
|
-
* the view preceding it in the stack will become visible instead.
|
|
178
|
-
* When there is no view in the stack, the next stack will be displayed.
|
|
179
|
-
* When there are no more stacks, the balloon will hide.
|
|
180
|
-
*
|
|
181
|
-
* @param view A view to be removed from the balloon.
|
|
182
|
-
*/
|
|
183
|
-
remove(view) {
|
|
184
|
-
if (!this.hasView(view)) {
|
|
185
|
-
/**
|
|
186
|
-
* Trying to remove the configuration of the view not defined in the stack.
|
|
187
|
-
*
|
|
188
|
-
* @error contextualballoon-remove-view-not-exist
|
|
189
|
-
*/
|
|
190
|
-
throw new CKEditorError('contextualballoon-remove-view-not-exist', [this, view]);
|
|
191
|
-
}
|
|
192
|
-
const stack = this._viewToStack.get(view);
|
|
193
|
-
if (this._singleViewMode && this.visibleView === view) {
|
|
194
|
-
this._singleViewMode = false;
|
|
195
|
-
}
|
|
196
|
-
// When visible view will be removed we need to show a preceding view or next stack
|
|
197
|
-
// if a view is the only view in the stack.
|
|
198
|
-
if (this.visibleView === view) {
|
|
199
|
-
if (stack.size === 1) {
|
|
200
|
-
if (this._idToStack.size > 1) {
|
|
201
|
-
this._showNextStack();
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
this.view.hide();
|
|
205
|
-
this.visibleView = null;
|
|
206
|
-
this._rotatorView.hideView();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
this._showView(Array.from(stack.values())[stack.size - 2]);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (stack.size === 1) {
|
|
214
|
-
this._idToStack.delete(this._getStackId(stack));
|
|
215
|
-
this._numberOfStacks = this._idToStack.size;
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
stack.delete(view);
|
|
219
|
-
}
|
|
220
|
-
this._viewToStack.delete(view);
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Updates the position of the balloon using the position data of the first visible view in the stack.
|
|
224
|
-
* When new position data is given, the position data of the currently visible view will be updated.
|
|
225
|
-
*
|
|
226
|
-
* @param position Position options.
|
|
227
|
-
*/
|
|
228
|
-
updatePosition(position) {
|
|
229
|
-
if (position) {
|
|
230
|
-
this._visibleStack.get(this.visibleView).position = position;
|
|
231
|
-
}
|
|
232
|
-
this.view.pin(this._getBalloonPosition());
|
|
233
|
-
this._fakePanelsView.updatePosition();
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Shows the last view from the stack of a given ID.
|
|
237
|
-
*/
|
|
238
|
-
showStack(id) {
|
|
239
|
-
this.visibleStack = id;
|
|
240
|
-
const stack = this._idToStack.get(id);
|
|
241
|
-
if (!stack) {
|
|
242
|
-
/**
|
|
243
|
-
* Trying to show a stack that does not exist.
|
|
244
|
-
*
|
|
245
|
-
* @error contextualballoon-showstack-stack-not-exist
|
|
246
|
-
*/
|
|
247
|
-
throw new CKEditorError('contextualballoon-showstack-stack-not-exist', this);
|
|
248
|
-
}
|
|
249
|
-
if (this._visibleStack === stack) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
this._showView(Array.from(stack.values()).pop());
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Initializes view instances.
|
|
256
|
-
*/
|
|
257
|
-
_createPanelView() {
|
|
258
|
-
this._view = new BalloonPanelView(this.editor.locale);
|
|
259
|
-
this.editor.ui.view.body.add(this._view);
|
|
260
|
-
this.editor.ui.focusTracker.add(this._view.element);
|
|
261
|
-
this._rotatorView = this._createRotatorView();
|
|
262
|
-
this._fakePanelsView = this._createFakePanelsView();
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Returns the stack of the currently visible view.
|
|
266
|
-
*/
|
|
267
|
-
get _visibleStack() {
|
|
268
|
-
return this._viewToStack.get(this.visibleView);
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Returns the ID of the given stack.
|
|
272
|
-
*/
|
|
273
|
-
_getStackId(stack) {
|
|
274
|
-
const entry = Array.from(this._idToStack.entries()).find(entry => entry[1] === stack);
|
|
275
|
-
return entry[0];
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Shows the last view from the next stack.
|
|
279
|
-
*/
|
|
280
|
-
_showNextStack() {
|
|
281
|
-
const stacks = Array.from(this._idToStack.values());
|
|
282
|
-
let nextIndex = stacks.indexOf(this._visibleStack) + 1;
|
|
283
|
-
if (!stacks[nextIndex]) {
|
|
284
|
-
nextIndex = 0;
|
|
285
|
-
}
|
|
286
|
-
this.showStack(this._getStackId(stacks[nextIndex]));
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Shows the last view from the previous stack.
|
|
290
|
-
*/
|
|
291
|
-
_showPrevStack() {
|
|
292
|
-
const stacks = Array.from(this._idToStack.values());
|
|
293
|
-
let nextIndex = stacks.indexOf(this._visibleStack) - 1;
|
|
294
|
-
if (!stacks[nextIndex]) {
|
|
295
|
-
nextIndex = stacks.length - 1;
|
|
296
|
-
}
|
|
297
|
-
this.showStack(this._getStackId(stacks[nextIndex]));
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Creates a rotator view.
|
|
301
|
-
*/
|
|
302
|
-
_createRotatorView() {
|
|
303
|
-
const view = new RotatorView(this.editor.locale);
|
|
304
|
-
const t = this.editor.locale.t;
|
|
305
|
-
this.view.content.add(view);
|
|
306
|
-
// Hide navigation when there is only a one stack & not in single view mode.
|
|
307
|
-
view.bind('isNavigationVisible').to(this, '_numberOfStacks', this, '_singleViewMode', (value, isSingleViewMode) => {
|
|
308
|
-
return !isSingleViewMode && value > 1;
|
|
309
|
-
});
|
|
310
|
-
// Update balloon position after toggling navigation.
|
|
311
|
-
view.on('change:isNavigationVisible', () => (this.updatePosition()), { priority: 'low' });
|
|
312
|
-
// Update stacks counter value.
|
|
313
|
-
view.bind('counter').to(this, 'visibleView', this, '_numberOfStacks', (visibleView, numberOfStacks) => {
|
|
314
|
-
if (numberOfStacks < 2) {
|
|
315
|
-
return '';
|
|
316
|
-
}
|
|
317
|
-
const current = Array.from(this._idToStack.values()).indexOf(this._visibleStack) + 1;
|
|
318
|
-
return t('%0 of %1', [current, numberOfStacks]);
|
|
319
|
-
});
|
|
320
|
-
view.buttonNextView.on('execute', () => {
|
|
321
|
-
// When current view has a focus then move focus to the editable before removing it,
|
|
322
|
-
// otherwise editor will lost focus.
|
|
323
|
-
if (view.focusTracker.isFocused) {
|
|
324
|
-
this.editor.editing.view.focus();
|
|
325
|
-
}
|
|
326
|
-
this._showNextStack();
|
|
327
|
-
});
|
|
328
|
-
view.buttonPrevView.on('execute', () => {
|
|
329
|
-
// When current view has a focus then move focus to the editable before removing it,
|
|
330
|
-
// otherwise editor will lost focus.
|
|
331
|
-
if (view.focusTracker.isFocused) {
|
|
332
|
-
this.editor.editing.view.focus();
|
|
333
|
-
}
|
|
334
|
-
this._showPrevStack();
|
|
335
|
-
});
|
|
336
|
-
return view;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Creates a fake panels view.
|
|
340
|
-
*/
|
|
341
|
-
_createFakePanelsView() {
|
|
342
|
-
const view = new FakePanelsView(this.editor.locale, this.view);
|
|
343
|
-
view.bind('numberOfPanels').to(this, '_numberOfStacks', this, '_singleViewMode', (number, isSingleViewMode) => {
|
|
344
|
-
const showPanels = !isSingleViewMode && number >= 2;
|
|
345
|
-
return showPanels ? Math.min(number - 1, 2) : 0;
|
|
346
|
-
});
|
|
347
|
-
view.listenTo(this.view, 'change:top', () => view.updatePosition());
|
|
348
|
-
view.listenTo(this.view, 'change:left', () => view.updatePosition());
|
|
349
|
-
this.editor.ui.view.body.add(view);
|
|
350
|
-
return view;
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Sets the view as the content of the balloon and attaches the balloon using position
|
|
354
|
-
* options of the first view.
|
|
355
|
-
*
|
|
356
|
-
* @param data Configuration.
|
|
357
|
-
* @param data.view The view to show in the balloon.
|
|
358
|
-
* @param data.balloonClassName Additional class name which will be added to the {@link #view balloon}.
|
|
359
|
-
* @param data.withArrow Whether the {@link #view balloon} should be rendered with an arrow.
|
|
360
|
-
*/
|
|
361
|
-
_showView({ view, balloonClassName = '', withArrow = true, singleViewMode = false }) {
|
|
362
|
-
this.view.class = balloonClassName;
|
|
363
|
-
this.view.withArrow = withArrow;
|
|
364
|
-
this._rotatorView.showView(view);
|
|
365
|
-
this.visibleView = view;
|
|
366
|
-
this.view.pin(this._getBalloonPosition());
|
|
367
|
-
this._fakePanelsView.updatePosition();
|
|
368
|
-
if (singleViewMode) {
|
|
369
|
-
this._singleViewMode = true;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Returns position options of the last view in the stack.
|
|
374
|
-
* This keeps the balloon in the same position when the view is changed.
|
|
375
|
-
*/
|
|
376
|
-
_getBalloonPosition() {
|
|
377
|
-
let position = Array.from(this._visibleStack.values()).pop().position;
|
|
378
|
-
if (position) {
|
|
379
|
-
// Use the default limiter if none has been specified.
|
|
380
|
-
if (!position.limiter) {
|
|
381
|
-
// Don't modify the original options object.
|
|
382
|
-
position = Object.assign({}, position, {
|
|
383
|
-
limiter: this.positionLimiter
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
// Don't modify the original options object.
|
|
387
|
-
position = Object.assign({}, position, {
|
|
388
|
-
viewportOffsetConfig: this.editor.ui.viewportOffset
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
return position;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Rotator view is a helper class for the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon}.
|
|
396
|
-
* It is used for displaying the last view from the current stack and providing navigation buttons for switching stacks.
|
|
397
|
-
* See the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon} documentation to learn more.
|
|
398
|
-
*/
|
|
399
|
-
export class RotatorView extends View {
|
|
400
|
-
/**
|
|
401
|
-
* @inheritDoc
|
|
402
|
-
*/
|
|
403
|
-
constructor(locale) {
|
|
404
|
-
super(locale);
|
|
405
|
-
const t = locale.t;
|
|
406
|
-
const bind = this.bindTemplate;
|
|
407
|
-
this.set('isNavigationVisible', true);
|
|
408
|
-
this.focusTracker = new FocusTracker();
|
|
409
|
-
this.buttonPrevView = this._createButtonView(t('Previous'), prevIcon);
|
|
410
|
-
this.buttonNextView = this._createButtonView(t('Next'), nextIcon);
|
|
411
|
-
this.content = this.createCollection();
|
|
412
|
-
this.setTemplate({
|
|
413
|
-
tag: 'div',
|
|
414
|
-
attributes: {
|
|
415
|
-
class: [
|
|
416
|
-
'ck',
|
|
417
|
-
'ck-balloon-rotator'
|
|
418
|
-
],
|
|
419
|
-
'z-index': '-1'
|
|
420
|
-
},
|
|
421
|
-
children: [
|
|
422
|
-
{
|
|
423
|
-
tag: 'div',
|
|
424
|
-
attributes: {
|
|
425
|
-
class: [
|
|
426
|
-
'ck-balloon-rotator__navigation',
|
|
427
|
-
bind.to('isNavigationVisible', value => value ? '' : 'ck-hidden')
|
|
428
|
-
]
|
|
429
|
-
},
|
|
430
|
-
children: [
|
|
431
|
-
this.buttonPrevView,
|
|
432
|
-
{
|
|
433
|
-
tag: 'span',
|
|
434
|
-
attributes: {
|
|
435
|
-
class: [
|
|
436
|
-
'ck-balloon-rotator__counter'
|
|
437
|
-
]
|
|
438
|
-
},
|
|
439
|
-
children: [
|
|
440
|
-
{
|
|
441
|
-
text: bind.to('counter')
|
|
442
|
-
}
|
|
443
|
-
]
|
|
444
|
-
},
|
|
445
|
-
this.buttonNextView
|
|
446
|
-
]
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
tag: 'div',
|
|
450
|
-
attributes: {
|
|
451
|
-
class: 'ck-balloon-rotator__content'
|
|
452
|
-
},
|
|
453
|
-
children: this.content
|
|
454
|
-
}
|
|
455
|
-
]
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* @inheritDoc
|
|
460
|
-
*/
|
|
461
|
-
render() {
|
|
462
|
-
super.render();
|
|
463
|
-
this.focusTracker.add(this.element);
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* @inheritDoc
|
|
467
|
-
*/
|
|
468
|
-
destroy() {
|
|
469
|
-
super.destroy();
|
|
470
|
-
this.focusTracker.destroy();
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Shows a given view.
|
|
474
|
-
*
|
|
475
|
-
* @param view The view to show.
|
|
476
|
-
*/
|
|
477
|
-
showView(view) {
|
|
478
|
-
this.hideView();
|
|
479
|
-
this.content.add(view);
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Hides the currently displayed view.
|
|
483
|
-
*/
|
|
484
|
-
hideView() {
|
|
485
|
-
this.content.clear();
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Creates a navigation button view.
|
|
489
|
-
*
|
|
490
|
-
* @param label The button label.
|
|
491
|
-
* @param icon The button icon.
|
|
492
|
-
*/
|
|
493
|
-
_createButtonView(label, icon) {
|
|
494
|
-
const view = new ButtonView(this.locale);
|
|
495
|
-
view.set({
|
|
496
|
-
label,
|
|
497
|
-
icon,
|
|
498
|
-
tooltip: true
|
|
499
|
-
});
|
|
500
|
-
return view;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Displays additional layers under the balloon when multiple stacks are added to the balloon.
|
|
505
|
-
*/
|
|
506
|
-
class FakePanelsView extends View {
|
|
507
|
-
/**
|
|
508
|
-
* @inheritDoc
|
|
509
|
-
*/
|
|
510
|
-
constructor(locale, balloonPanelView) {
|
|
511
|
-
super(locale);
|
|
512
|
-
const bind = this.bindTemplate;
|
|
513
|
-
this.set('top', 0);
|
|
514
|
-
this.set('left', 0);
|
|
515
|
-
this.set('height', 0);
|
|
516
|
-
this.set('width', 0);
|
|
517
|
-
this.set('numberOfPanels', 0);
|
|
518
|
-
this.content = this.createCollection();
|
|
519
|
-
this._balloonPanelView = balloonPanelView;
|
|
520
|
-
this.setTemplate({
|
|
521
|
-
tag: 'div',
|
|
522
|
-
attributes: {
|
|
523
|
-
class: [
|
|
524
|
-
'ck-fake-panel',
|
|
525
|
-
bind.to('numberOfPanels', number => number ? '' : 'ck-hidden')
|
|
526
|
-
],
|
|
527
|
-
style: {
|
|
528
|
-
top: bind.to('top', toPx),
|
|
529
|
-
left: bind.to('left', toPx),
|
|
530
|
-
width: bind.to('width', toPx),
|
|
531
|
-
height: bind.to('height', toPx)
|
|
532
|
-
}
|
|
533
|
-
},
|
|
534
|
-
children: this.content
|
|
535
|
-
});
|
|
536
|
-
this.on('change:numberOfPanels', (evt, name, next, prev) => {
|
|
537
|
-
if (next > prev) {
|
|
538
|
-
this._addPanels(next - prev);
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
this._removePanels(prev - next);
|
|
542
|
-
}
|
|
543
|
-
this.updatePosition();
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
_addPanels(number) {
|
|
547
|
-
while (number--) {
|
|
548
|
-
const view = new View();
|
|
549
|
-
view.setTemplate({ tag: 'div' });
|
|
550
|
-
this.content.add(view);
|
|
551
|
-
this.registerChild(view);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
_removePanels(number) {
|
|
555
|
-
while (number--) {
|
|
556
|
-
const view = this.content.last;
|
|
557
|
-
this.content.remove(view);
|
|
558
|
-
this.deregisterChild(view);
|
|
559
|
-
view.destroy();
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Updates coordinates of fake panels.
|
|
564
|
-
*/
|
|
565
|
-
updatePosition() {
|
|
566
|
-
if (this.numberOfPanels) {
|
|
567
|
-
const { top, left } = this._balloonPanelView;
|
|
568
|
-
const { width, height } = new Rect(this._balloonPanelView.element);
|
|
569
|
-
Object.assign(this, { top, left, width, height });
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
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/panel/balloon/contextualballoon
|
|
7
|
+
*/
|
|
8
|
+
import BalloonPanelView from './balloonpanelview';
|
|
9
|
+
import View from '../../view';
|
|
10
|
+
import ButtonView from '../../button/buttonview';
|
|
11
|
+
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
12
|
+
import { CKEditorError, FocusTracker, Rect, toUnit } from '@ckeditor/ckeditor5-utils';
|
|
13
|
+
import prevIcon from '../../../theme/icons/previous-arrow.svg';
|
|
14
|
+
import nextIcon from '../../../theme/icons/next-arrow.svg';
|
|
15
|
+
import '../../../theme/components/panel/balloonrotator.css';
|
|
16
|
+
import '../../../theme/components/panel/fakepanel.css';
|
|
17
|
+
const toPx = toUnit('px');
|
|
18
|
+
/**
|
|
19
|
+
* Provides the common contextual balloon for the editor.
|
|
20
|
+
*
|
|
21
|
+
* The role of this plugin is to unify the contextual balloons logic, simplify views management and help
|
|
22
|
+
* avoid the unnecessary complexity of handling multiple {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
|
|
23
|
+
* instances in the editor.
|
|
24
|
+
*
|
|
25
|
+
* This plugin allows for creating single or multiple panel stacks.
|
|
26
|
+
*
|
|
27
|
+
* Each stack may have multiple views, with the one on the top being visible. When the visible view is removed from the stack,
|
|
28
|
+
* the previous view becomes visible.
|
|
29
|
+
*
|
|
30
|
+
* It might be useful to implement nested navigation in a balloon. For instance, a toolbar view may contain a link button.
|
|
31
|
+
* When you click it, a link view (which lets you set the URL) is created and put on top of the toolbar view, so the link panel
|
|
32
|
+
* is displayed. When you finish editing the link and close (remove) the link view, the toolbar view is visible again.
|
|
33
|
+
*
|
|
34
|
+
* However, there are cases when there are multiple independent balloons to be displayed, for instance, if the selection
|
|
35
|
+
* is inside two inline comments at the same time. For such cases, you can create two independent panel stacks.
|
|
36
|
+
* The contextual balloon plugin will create a navigation bar to let the users switch between these panel stacks using the "Next"
|
|
37
|
+
* and "Previous" buttons.
|
|
38
|
+
*
|
|
39
|
+
* If there are no views in the current stack, the balloon panel will try to switch to the next stack. If there are no
|
|
40
|
+
* panels in any stack, the balloon panel will be hidden.
|
|
41
|
+
*
|
|
42
|
+
* **Note**: To force the balloon panel to show only one view, even if there are other stacks, use the `singleViewMode=true` option
|
|
43
|
+
* when {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon#add adding} a view to a panel.
|
|
44
|
+
*
|
|
45
|
+
* From the implementation point of view, the contextual ballon plugin is reusing a single
|
|
46
|
+
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView} instance to display multiple contextual balloon
|
|
47
|
+
* panels in the editor. It also creates a special {@link module:ui/panel/balloon/contextualballoon~RotatorView rotator view},
|
|
48
|
+
* used to manage multiple panel stacks. Rotator view is a child of the balloon panel view and the parent of the specific
|
|
49
|
+
* view you want to display. If there is more than one panel stack to be displayed, the rotator view will add a
|
|
50
|
+
* navigation bar. If there is only one stack, the rotator view is transparent (it does not add any UI elements).
|
|
51
|
+
*/
|
|
52
|
+
export default class ContextualBalloon extends Plugin {
|
|
53
|
+
/**
|
|
54
|
+
* @inheritDoc
|
|
55
|
+
*/
|
|
56
|
+
static get pluginName() {
|
|
57
|
+
return 'ContextualBalloon';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @inheritDoc
|
|
61
|
+
*/
|
|
62
|
+
constructor(editor) {
|
|
63
|
+
super(editor);
|
|
64
|
+
/**
|
|
65
|
+
* The map of views and their stacks.
|
|
66
|
+
*/
|
|
67
|
+
this._viewToStack = new Map();
|
|
68
|
+
/**
|
|
69
|
+
* The map of IDs and stacks.
|
|
70
|
+
*/
|
|
71
|
+
this._idToStack = new Map();
|
|
72
|
+
/**
|
|
73
|
+
* The common balloon panel view.
|
|
74
|
+
*/
|
|
75
|
+
this._view = null;
|
|
76
|
+
/**
|
|
77
|
+
* Rotator view embedded in the contextual balloon.
|
|
78
|
+
* Displays the currently visible view in the balloon and provides navigation for switching stacks.
|
|
79
|
+
*/
|
|
80
|
+
this._rotatorView = null;
|
|
81
|
+
/**
|
|
82
|
+
* Displays fake panels under the balloon panel view when multiple stacks are added to the balloon.
|
|
83
|
+
*/
|
|
84
|
+
this._fakePanelsView = null;
|
|
85
|
+
this.positionLimiter = () => {
|
|
86
|
+
const view = this.editor.editing.view;
|
|
87
|
+
const viewDocument = view.document;
|
|
88
|
+
const editableElement = viewDocument.selection.editableElement;
|
|
89
|
+
if (editableElement) {
|
|
90
|
+
return view.domConverter.mapViewToDom(editableElement.root);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
};
|
|
94
|
+
this.set('visibleView', null);
|
|
95
|
+
this.set('_numberOfStacks', 0);
|
|
96
|
+
this.set('_singleViewMode', false);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @inheritDoc
|
|
100
|
+
*/
|
|
101
|
+
destroy() {
|
|
102
|
+
super.destroy();
|
|
103
|
+
if (this._view) {
|
|
104
|
+
this._view.destroy();
|
|
105
|
+
}
|
|
106
|
+
if (this._rotatorView) {
|
|
107
|
+
this._rotatorView.destroy();
|
|
108
|
+
}
|
|
109
|
+
if (this._fakePanelsView) {
|
|
110
|
+
this._fakePanelsView.destroy();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The common balloon panel view.
|
|
115
|
+
*/
|
|
116
|
+
get view() {
|
|
117
|
+
if (!this._view) {
|
|
118
|
+
this._createPanelView();
|
|
119
|
+
}
|
|
120
|
+
return this._view;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Returns `true` when the given view is in one of the stacks. Otherwise returns `false`.
|
|
124
|
+
*/
|
|
125
|
+
hasView(view) {
|
|
126
|
+
return Array.from(this._viewToStack.keys()).includes(view);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Adds a new view to the stack and makes it visible if the current stack is visible
|
|
130
|
+
* or it is the first view in the balloon.
|
|
131
|
+
*
|
|
132
|
+
* @param data The configuration of the view.
|
|
133
|
+
* @param data.stackId The ID of the stack that the view is added to. Defaults to `'main'`.
|
|
134
|
+
* @param data.view The content of the balloon.
|
|
135
|
+
* @param data.position Positioning options.
|
|
136
|
+
* @param data.balloonClassName An additional CSS class added to the {@link #view balloon} when visible.
|
|
137
|
+
* @param data.withArrow Whether the {@link #view balloon} should be rendered with an arrow. Defaults to `true`.
|
|
138
|
+
* @param data.singleViewMode Whether the view should be the only visible view even if other stacks were added. Defaults to `false`.
|
|
139
|
+
*/
|
|
140
|
+
add(data) {
|
|
141
|
+
if (!this._view) {
|
|
142
|
+
this._createPanelView();
|
|
143
|
+
}
|
|
144
|
+
if (this.hasView(data.view)) {
|
|
145
|
+
/**
|
|
146
|
+
* Trying to add configuration of the same view more than once.
|
|
147
|
+
*
|
|
148
|
+
* @error contextualballoon-add-view-exist
|
|
149
|
+
*/
|
|
150
|
+
throw new CKEditorError('contextualballoon-add-view-exist', [this, data]);
|
|
151
|
+
}
|
|
152
|
+
const stackId = data.stackId || 'main';
|
|
153
|
+
// If new stack is added, creates it and show view from this stack.
|
|
154
|
+
if (!this._idToStack.has(stackId)) {
|
|
155
|
+
this._idToStack.set(stackId, new Map([[data.view, data]]));
|
|
156
|
+
this._viewToStack.set(data.view, this._idToStack.get(stackId));
|
|
157
|
+
this._numberOfStacks = this._idToStack.size;
|
|
158
|
+
if (!this._visibleStack || data.singleViewMode) {
|
|
159
|
+
this.showStack(stackId);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const stack = this._idToStack.get(stackId);
|
|
164
|
+
if (data.singleViewMode) {
|
|
165
|
+
this.showStack(stackId);
|
|
166
|
+
}
|
|
167
|
+
// Add new view to the stack.
|
|
168
|
+
stack.set(data.view, data);
|
|
169
|
+
this._viewToStack.set(data.view, stack);
|
|
170
|
+
// And display it if is added to the currently visible stack.
|
|
171
|
+
if (stack === this._visibleStack) {
|
|
172
|
+
this._showView(data);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Removes the given view from the stack. If the removed view was visible,
|
|
177
|
+
* the view preceding it in the stack will become visible instead.
|
|
178
|
+
* When there is no view in the stack, the next stack will be displayed.
|
|
179
|
+
* When there are no more stacks, the balloon will hide.
|
|
180
|
+
*
|
|
181
|
+
* @param view A view to be removed from the balloon.
|
|
182
|
+
*/
|
|
183
|
+
remove(view) {
|
|
184
|
+
if (!this.hasView(view)) {
|
|
185
|
+
/**
|
|
186
|
+
* Trying to remove the configuration of the view not defined in the stack.
|
|
187
|
+
*
|
|
188
|
+
* @error contextualballoon-remove-view-not-exist
|
|
189
|
+
*/
|
|
190
|
+
throw new CKEditorError('contextualballoon-remove-view-not-exist', [this, view]);
|
|
191
|
+
}
|
|
192
|
+
const stack = this._viewToStack.get(view);
|
|
193
|
+
if (this._singleViewMode && this.visibleView === view) {
|
|
194
|
+
this._singleViewMode = false;
|
|
195
|
+
}
|
|
196
|
+
// When visible view will be removed we need to show a preceding view or next stack
|
|
197
|
+
// if a view is the only view in the stack.
|
|
198
|
+
if (this.visibleView === view) {
|
|
199
|
+
if (stack.size === 1) {
|
|
200
|
+
if (this._idToStack.size > 1) {
|
|
201
|
+
this._showNextStack();
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
this.view.hide();
|
|
205
|
+
this.visibleView = null;
|
|
206
|
+
this._rotatorView.hideView();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this._showView(Array.from(stack.values())[stack.size - 2]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (stack.size === 1) {
|
|
214
|
+
this._idToStack.delete(this._getStackId(stack));
|
|
215
|
+
this._numberOfStacks = this._idToStack.size;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
stack.delete(view);
|
|
219
|
+
}
|
|
220
|
+
this._viewToStack.delete(view);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Updates the position of the balloon using the position data of the first visible view in the stack.
|
|
224
|
+
* When new position data is given, the position data of the currently visible view will be updated.
|
|
225
|
+
*
|
|
226
|
+
* @param position Position options.
|
|
227
|
+
*/
|
|
228
|
+
updatePosition(position) {
|
|
229
|
+
if (position) {
|
|
230
|
+
this._visibleStack.get(this.visibleView).position = position;
|
|
231
|
+
}
|
|
232
|
+
this.view.pin(this._getBalloonPosition());
|
|
233
|
+
this._fakePanelsView.updatePosition();
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Shows the last view from the stack of a given ID.
|
|
237
|
+
*/
|
|
238
|
+
showStack(id) {
|
|
239
|
+
this.visibleStack = id;
|
|
240
|
+
const stack = this._idToStack.get(id);
|
|
241
|
+
if (!stack) {
|
|
242
|
+
/**
|
|
243
|
+
* Trying to show a stack that does not exist.
|
|
244
|
+
*
|
|
245
|
+
* @error contextualballoon-showstack-stack-not-exist
|
|
246
|
+
*/
|
|
247
|
+
throw new CKEditorError('contextualballoon-showstack-stack-not-exist', this);
|
|
248
|
+
}
|
|
249
|
+
if (this._visibleStack === stack) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
this._showView(Array.from(stack.values()).pop());
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Initializes view instances.
|
|
256
|
+
*/
|
|
257
|
+
_createPanelView() {
|
|
258
|
+
this._view = new BalloonPanelView(this.editor.locale);
|
|
259
|
+
this.editor.ui.view.body.add(this._view);
|
|
260
|
+
this.editor.ui.focusTracker.add(this._view.element);
|
|
261
|
+
this._rotatorView = this._createRotatorView();
|
|
262
|
+
this._fakePanelsView = this._createFakePanelsView();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Returns the stack of the currently visible view.
|
|
266
|
+
*/
|
|
267
|
+
get _visibleStack() {
|
|
268
|
+
return this._viewToStack.get(this.visibleView);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Returns the ID of the given stack.
|
|
272
|
+
*/
|
|
273
|
+
_getStackId(stack) {
|
|
274
|
+
const entry = Array.from(this._idToStack.entries()).find(entry => entry[1] === stack);
|
|
275
|
+
return entry[0];
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Shows the last view from the next stack.
|
|
279
|
+
*/
|
|
280
|
+
_showNextStack() {
|
|
281
|
+
const stacks = Array.from(this._idToStack.values());
|
|
282
|
+
let nextIndex = stacks.indexOf(this._visibleStack) + 1;
|
|
283
|
+
if (!stacks[nextIndex]) {
|
|
284
|
+
nextIndex = 0;
|
|
285
|
+
}
|
|
286
|
+
this.showStack(this._getStackId(stacks[nextIndex]));
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Shows the last view from the previous stack.
|
|
290
|
+
*/
|
|
291
|
+
_showPrevStack() {
|
|
292
|
+
const stacks = Array.from(this._idToStack.values());
|
|
293
|
+
let nextIndex = stacks.indexOf(this._visibleStack) - 1;
|
|
294
|
+
if (!stacks[nextIndex]) {
|
|
295
|
+
nextIndex = stacks.length - 1;
|
|
296
|
+
}
|
|
297
|
+
this.showStack(this._getStackId(stacks[nextIndex]));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Creates a rotator view.
|
|
301
|
+
*/
|
|
302
|
+
_createRotatorView() {
|
|
303
|
+
const view = new RotatorView(this.editor.locale);
|
|
304
|
+
const t = this.editor.locale.t;
|
|
305
|
+
this.view.content.add(view);
|
|
306
|
+
// Hide navigation when there is only a one stack & not in single view mode.
|
|
307
|
+
view.bind('isNavigationVisible').to(this, '_numberOfStacks', this, '_singleViewMode', (value, isSingleViewMode) => {
|
|
308
|
+
return !isSingleViewMode && value > 1;
|
|
309
|
+
});
|
|
310
|
+
// Update balloon position after toggling navigation.
|
|
311
|
+
view.on('change:isNavigationVisible', () => (this.updatePosition()), { priority: 'low' });
|
|
312
|
+
// Update stacks counter value.
|
|
313
|
+
view.bind('counter').to(this, 'visibleView', this, '_numberOfStacks', (visibleView, numberOfStacks) => {
|
|
314
|
+
if (numberOfStacks < 2) {
|
|
315
|
+
return '';
|
|
316
|
+
}
|
|
317
|
+
const current = Array.from(this._idToStack.values()).indexOf(this._visibleStack) + 1;
|
|
318
|
+
return t('%0 of %1', [current, numberOfStacks]);
|
|
319
|
+
});
|
|
320
|
+
view.buttonNextView.on('execute', () => {
|
|
321
|
+
// When current view has a focus then move focus to the editable before removing it,
|
|
322
|
+
// otherwise editor will lost focus.
|
|
323
|
+
if (view.focusTracker.isFocused) {
|
|
324
|
+
this.editor.editing.view.focus();
|
|
325
|
+
}
|
|
326
|
+
this._showNextStack();
|
|
327
|
+
});
|
|
328
|
+
view.buttonPrevView.on('execute', () => {
|
|
329
|
+
// When current view has a focus then move focus to the editable before removing it,
|
|
330
|
+
// otherwise editor will lost focus.
|
|
331
|
+
if (view.focusTracker.isFocused) {
|
|
332
|
+
this.editor.editing.view.focus();
|
|
333
|
+
}
|
|
334
|
+
this._showPrevStack();
|
|
335
|
+
});
|
|
336
|
+
return view;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Creates a fake panels view.
|
|
340
|
+
*/
|
|
341
|
+
_createFakePanelsView() {
|
|
342
|
+
const view = new FakePanelsView(this.editor.locale, this.view);
|
|
343
|
+
view.bind('numberOfPanels').to(this, '_numberOfStacks', this, '_singleViewMode', (number, isSingleViewMode) => {
|
|
344
|
+
const showPanels = !isSingleViewMode && number >= 2;
|
|
345
|
+
return showPanels ? Math.min(number - 1, 2) : 0;
|
|
346
|
+
});
|
|
347
|
+
view.listenTo(this.view, 'change:top', () => view.updatePosition());
|
|
348
|
+
view.listenTo(this.view, 'change:left', () => view.updatePosition());
|
|
349
|
+
this.editor.ui.view.body.add(view);
|
|
350
|
+
return view;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Sets the view as the content of the balloon and attaches the balloon using position
|
|
354
|
+
* options of the first view.
|
|
355
|
+
*
|
|
356
|
+
* @param data Configuration.
|
|
357
|
+
* @param data.view The view to show in the balloon.
|
|
358
|
+
* @param data.balloonClassName Additional class name which will be added to the {@link #view balloon}.
|
|
359
|
+
* @param data.withArrow Whether the {@link #view balloon} should be rendered with an arrow.
|
|
360
|
+
*/
|
|
361
|
+
_showView({ view, balloonClassName = '', withArrow = true, singleViewMode = false }) {
|
|
362
|
+
this.view.class = balloonClassName;
|
|
363
|
+
this.view.withArrow = withArrow;
|
|
364
|
+
this._rotatorView.showView(view);
|
|
365
|
+
this.visibleView = view;
|
|
366
|
+
this.view.pin(this._getBalloonPosition());
|
|
367
|
+
this._fakePanelsView.updatePosition();
|
|
368
|
+
if (singleViewMode) {
|
|
369
|
+
this._singleViewMode = true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Returns position options of the last view in the stack.
|
|
374
|
+
* This keeps the balloon in the same position when the view is changed.
|
|
375
|
+
*/
|
|
376
|
+
_getBalloonPosition() {
|
|
377
|
+
let position = Array.from(this._visibleStack.values()).pop().position;
|
|
378
|
+
if (position) {
|
|
379
|
+
// Use the default limiter if none has been specified.
|
|
380
|
+
if (!position.limiter) {
|
|
381
|
+
// Don't modify the original options object.
|
|
382
|
+
position = Object.assign({}, position, {
|
|
383
|
+
limiter: this.positionLimiter
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
// Don't modify the original options object.
|
|
387
|
+
position = Object.assign({}, position, {
|
|
388
|
+
viewportOffsetConfig: this.editor.ui.viewportOffset
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return position;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Rotator view is a helper class for the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon}.
|
|
396
|
+
* It is used for displaying the last view from the current stack and providing navigation buttons for switching stacks.
|
|
397
|
+
* See the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon} documentation to learn more.
|
|
398
|
+
*/
|
|
399
|
+
export class RotatorView extends View {
|
|
400
|
+
/**
|
|
401
|
+
* @inheritDoc
|
|
402
|
+
*/
|
|
403
|
+
constructor(locale) {
|
|
404
|
+
super(locale);
|
|
405
|
+
const t = locale.t;
|
|
406
|
+
const bind = this.bindTemplate;
|
|
407
|
+
this.set('isNavigationVisible', true);
|
|
408
|
+
this.focusTracker = new FocusTracker();
|
|
409
|
+
this.buttonPrevView = this._createButtonView(t('Previous'), prevIcon);
|
|
410
|
+
this.buttonNextView = this._createButtonView(t('Next'), nextIcon);
|
|
411
|
+
this.content = this.createCollection();
|
|
412
|
+
this.setTemplate({
|
|
413
|
+
tag: 'div',
|
|
414
|
+
attributes: {
|
|
415
|
+
class: [
|
|
416
|
+
'ck',
|
|
417
|
+
'ck-balloon-rotator'
|
|
418
|
+
],
|
|
419
|
+
'z-index': '-1'
|
|
420
|
+
},
|
|
421
|
+
children: [
|
|
422
|
+
{
|
|
423
|
+
tag: 'div',
|
|
424
|
+
attributes: {
|
|
425
|
+
class: [
|
|
426
|
+
'ck-balloon-rotator__navigation',
|
|
427
|
+
bind.to('isNavigationVisible', value => value ? '' : 'ck-hidden')
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
children: [
|
|
431
|
+
this.buttonPrevView,
|
|
432
|
+
{
|
|
433
|
+
tag: 'span',
|
|
434
|
+
attributes: {
|
|
435
|
+
class: [
|
|
436
|
+
'ck-balloon-rotator__counter'
|
|
437
|
+
]
|
|
438
|
+
},
|
|
439
|
+
children: [
|
|
440
|
+
{
|
|
441
|
+
text: bind.to('counter')
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
},
|
|
445
|
+
this.buttonNextView
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
tag: 'div',
|
|
450
|
+
attributes: {
|
|
451
|
+
class: 'ck-balloon-rotator__content'
|
|
452
|
+
},
|
|
453
|
+
children: this.content
|
|
454
|
+
}
|
|
455
|
+
]
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* @inheritDoc
|
|
460
|
+
*/
|
|
461
|
+
render() {
|
|
462
|
+
super.render();
|
|
463
|
+
this.focusTracker.add(this.element);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* @inheritDoc
|
|
467
|
+
*/
|
|
468
|
+
destroy() {
|
|
469
|
+
super.destroy();
|
|
470
|
+
this.focusTracker.destroy();
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Shows a given view.
|
|
474
|
+
*
|
|
475
|
+
* @param view The view to show.
|
|
476
|
+
*/
|
|
477
|
+
showView(view) {
|
|
478
|
+
this.hideView();
|
|
479
|
+
this.content.add(view);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Hides the currently displayed view.
|
|
483
|
+
*/
|
|
484
|
+
hideView() {
|
|
485
|
+
this.content.clear();
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Creates a navigation button view.
|
|
489
|
+
*
|
|
490
|
+
* @param label The button label.
|
|
491
|
+
* @param icon The button icon.
|
|
492
|
+
*/
|
|
493
|
+
_createButtonView(label, icon) {
|
|
494
|
+
const view = new ButtonView(this.locale);
|
|
495
|
+
view.set({
|
|
496
|
+
label,
|
|
497
|
+
icon,
|
|
498
|
+
tooltip: true
|
|
499
|
+
});
|
|
500
|
+
return view;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Displays additional layers under the balloon when multiple stacks are added to the balloon.
|
|
505
|
+
*/
|
|
506
|
+
class FakePanelsView extends View {
|
|
507
|
+
/**
|
|
508
|
+
* @inheritDoc
|
|
509
|
+
*/
|
|
510
|
+
constructor(locale, balloonPanelView) {
|
|
511
|
+
super(locale);
|
|
512
|
+
const bind = this.bindTemplate;
|
|
513
|
+
this.set('top', 0);
|
|
514
|
+
this.set('left', 0);
|
|
515
|
+
this.set('height', 0);
|
|
516
|
+
this.set('width', 0);
|
|
517
|
+
this.set('numberOfPanels', 0);
|
|
518
|
+
this.content = this.createCollection();
|
|
519
|
+
this._balloonPanelView = balloonPanelView;
|
|
520
|
+
this.setTemplate({
|
|
521
|
+
tag: 'div',
|
|
522
|
+
attributes: {
|
|
523
|
+
class: [
|
|
524
|
+
'ck-fake-panel',
|
|
525
|
+
bind.to('numberOfPanels', number => number ? '' : 'ck-hidden')
|
|
526
|
+
],
|
|
527
|
+
style: {
|
|
528
|
+
top: bind.to('top', toPx),
|
|
529
|
+
left: bind.to('left', toPx),
|
|
530
|
+
width: bind.to('width', toPx),
|
|
531
|
+
height: bind.to('height', toPx)
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
children: this.content
|
|
535
|
+
});
|
|
536
|
+
this.on('change:numberOfPanels', (evt, name, next, prev) => {
|
|
537
|
+
if (next > prev) {
|
|
538
|
+
this._addPanels(next - prev);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
this._removePanels(prev - next);
|
|
542
|
+
}
|
|
543
|
+
this.updatePosition();
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
_addPanels(number) {
|
|
547
|
+
while (number--) {
|
|
548
|
+
const view = new View();
|
|
549
|
+
view.setTemplate({ tag: 'div' });
|
|
550
|
+
this.content.add(view);
|
|
551
|
+
this.registerChild(view);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
_removePanels(number) {
|
|
555
|
+
while (number--) {
|
|
556
|
+
const view = this.content.last;
|
|
557
|
+
this.content.remove(view);
|
|
558
|
+
this.deregisterChild(view);
|
|
559
|
+
view.destroy();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Updates coordinates of fake panels.
|
|
564
|
+
*/
|
|
565
|
+
updatePosition() {
|
|
566
|
+
if (this.numberOfPanels) {
|
|
567
|
+
const { top, left } = this._balloonPanelView;
|
|
568
|
+
const { width, height } = new Rect(this._balloonPanelView.element);
|
|
569
|
+
Object.assign(this, { top, left, width, height });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|