@ckeditor/ckeditor5-widget 35.2.0 → 35.3.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/package.json +31 -23
- package/src/highlightstack.js +105 -139
- package/src/index.js +0 -1
- package/src/utils.js +127 -181
- package/src/verticalnavigation.js +144 -187
- package/src/widget.js +359 -435
- package/src/widgetresize/resizer.js +412 -505
- package/src/widgetresize/resizerstate.js +154 -176
- package/src/widgetresize/sizeview.js +79 -98
- package/src/widgetresize.js +199 -297
- package/src/widgettoolbarrepository.js +244 -296
- package/src/widgettypearound/utils.js +11 -20
- package/src/widgettypearound/widgettypearound.js +748 -876
package/src/widget.js
CHANGED
@@ -2,23 +2,18 @@
|
|
2
2
|
* @license Copyright (c) 2003-2022, 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
|
-
|
6
5
|
/**
|
7
6
|
* @module widget/widget
|
8
7
|
*/
|
9
|
-
|
10
8
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
11
9
|
import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
|
12
10
|
import WidgetTypeAround from './widgettypearound/widgettypearound';
|
13
11
|
import Delete from '@ckeditor/ckeditor5-typing/src/delete';
|
14
12
|
import env from '@ckeditor/ckeditor5-utils/src/env';
|
15
13
|
import { getLocalizedArrowKeyCodeDirection } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
16
|
-
|
17
14
|
import verticalNavigationHandler from './verticalnavigation';
|
18
15
|
import { getLabel, isWidget, WIDGET_SELECTED_CLASS_NAME } from './utils';
|
19
|
-
|
20
16
|
import '../theme/widget.css';
|
21
|
-
|
22
17
|
/**
|
23
18
|
* The widget plugin. It enables base support for widgets.
|
24
19
|
*
|
@@ -35,445 +30,374 @@ import '../theme/widget.css';
|
|
35
30
|
* @extends module:core/plugin~Plugin
|
36
31
|
*/
|
37
32
|
export default class Widget extends Plugin {
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
this._setSelectionOverElement( objectElement );
|
381
|
-
} );
|
382
|
-
|
383
|
-
return true;
|
384
|
-
}
|
385
|
-
}
|
386
|
-
|
387
|
-
/**
|
388
|
-
* Sets {@link module:engine/model/selection~Selection document's selection} over given element.
|
389
|
-
*
|
390
|
-
* @protected
|
391
|
-
* @param {module:engine/model/element~Element} element
|
392
|
-
*/
|
393
|
-
_setSelectionOverElement( element ) {
|
394
|
-
this.editor.model.change( writer => {
|
395
|
-
writer.setSelection( writer.createRangeOn( element ) );
|
396
|
-
} );
|
397
|
-
}
|
398
|
-
|
399
|
-
/**
|
400
|
-
* Checks if {@link module:engine/model/element~Element element} placed next to the current
|
401
|
-
* {@link module:engine/model/selection~Selection model selection} exists and is marked in
|
402
|
-
* {@link module:engine/model/schema~Schema schema} as `object`.
|
403
|
-
*
|
404
|
-
* @protected
|
405
|
-
* @param {Boolean} forward Direction of checking.
|
406
|
-
* @returns {module:engine/model/element~Element|null}
|
407
|
-
*/
|
408
|
-
_getObjectElementNextToSelection( forward ) {
|
409
|
-
const model = this.editor.model;
|
410
|
-
const schema = model.schema;
|
411
|
-
const modelSelection = model.document.selection;
|
412
|
-
|
413
|
-
// Clone current selection to use it as a probe. We must leave default selection as it is so it can return
|
414
|
-
// to its current state after undo.
|
415
|
-
const probe = model.createSelection( modelSelection );
|
416
|
-
model.modifySelection( probe, { direction: forward ? 'forward' : 'backward' } );
|
417
|
-
|
418
|
-
// The selection didn't change so there is nothing there.
|
419
|
-
if ( probe.isEqual( modelSelection ) ) {
|
420
|
-
return null;
|
421
|
-
}
|
422
|
-
|
423
|
-
const objectElement = forward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
|
424
|
-
|
425
|
-
if ( !!objectElement && schema.isObject( objectElement ) ) {
|
426
|
-
return objectElement;
|
427
|
-
}
|
428
|
-
|
429
|
-
return null;
|
430
|
-
}
|
431
|
-
|
432
|
-
/**
|
433
|
-
* Removes CSS class from previously selected widgets.
|
434
|
-
*
|
435
|
-
* @private
|
436
|
-
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
|
437
|
-
*/
|
438
|
-
_clearPreviouslySelectedWidgets( writer ) {
|
439
|
-
for ( const widget of this._previouslySelected ) {
|
440
|
-
writer.removeClass( WIDGET_SELECTED_CLASS_NAME, widget );
|
441
|
-
}
|
442
|
-
|
443
|
-
this._previouslySelected.clear();
|
444
|
-
}
|
33
|
+
/**
|
34
|
+
* @inheritDoc
|
35
|
+
*/
|
36
|
+
static get pluginName() {
|
37
|
+
return 'Widget';
|
38
|
+
}
|
39
|
+
/**
|
40
|
+
* @inheritDoc
|
41
|
+
*/
|
42
|
+
static get requires() {
|
43
|
+
return [WidgetTypeAround, Delete];
|
44
|
+
}
|
45
|
+
/**
|
46
|
+
* @inheritDoc
|
47
|
+
*/
|
48
|
+
init() {
|
49
|
+
const editor = this.editor;
|
50
|
+
const view = editor.editing.view;
|
51
|
+
const viewDocument = view.document;
|
52
|
+
/**
|
53
|
+
* Holds previously selected widgets.
|
54
|
+
*
|
55
|
+
* @private
|
56
|
+
* @type {Set.<module:engine/view/element~Element>}
|
57
|
+
*/
|
58
|
+
this._previouslySelected = new Set();
|
59
|
+
// Model to view selection converter.
|
60
|
+
// Converts selection placed over widget element to fake selection.
|
61
|
+
//
|
62
|
+
// By default, the selection is downcasted by the engine to surround the attribute element, even though its only
|
63
|
+
// child is an inline widget. A similar thing also happens when a collapsed marker is rendered as a UI element
|
64
|
+
// next to an inline widget: the view selection contains both the widget and the marker.
|
65
|
+
//
|
66
|
+
// This prevents creating a correct fake selection when this inline widget is selected. Normalize the selection
|
67
|
+
// in these cases based on the model:
|
68
|
+
//
|
69
|
+
// [<attributeElement><inlineWidget /></attributeElement>] -> <attributeElement>[<inlineWidget />]</attributeElement>
|
70
|
+
// [<uiElement></uiElement><inlineWidget />] -> <uiElement></uiElement>[<inlineWidget />]
|
71
|
+
//
|
72
|
+
// Thanks to this:
|
73
|
+
//
|
74
|
+
// * fake selection can be set correctly,
|
75
|
+
// * any logic depending on (View)Selection#getSelectedElement() also works OK.
|
76
|
+
//
|
77
|
+
// See https://github.com/ckeditor/ckeditor5/issues/9524.
|
78
|
+
this.editor.editing.downcastDispatcher.on('selection', (evt, data, conversionApi) => {
|
79
|
+
const viewWriter = conversionApi.writer;
|
80
|
+
const modelSelection = data.selection;
|
81
|
+
// The collapsed selection can't contain any widget.
|
82
|
+
if (modelSelection.isCollapsed) {
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
const selectedModelElement = modelSelection.getSelectedElement();
|
86
|
+
if (!selectedModelElement) {
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
const selectedViewElement = editor.editing.mapper.toViewElement(selectedModelElement);
|
90
|
+
if (!isWidget(selectedViewElement)) {
|
91
|
+
return;
|
92
|
+
}
|
93
|
+
if (!conversionApi.consumable.consume(modelSelection, 'selection')) {
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
viewWriter.setSelection(viewWriter.createRangeOn(selectedViewElement), {
|
97
|
+
fake: true,
|
98
|
+
label: getLabel(selectedViewElement)
|
99
|
+
});
|
100
|
+
});
|
101
|
+
// Mark all widgets inside the selection with the css class.
|
102
|
+
// This handler is registered at the 'low' priority so it's triggered after the real selection conversion.
|
103
|
+
this.editor.editing.downcastDispatcher.on('selection', (evt, data, conversionApi) => {
|
104
|
+
// Remove selected class from previously selected widgets.
|
105
|
+
this._clearPreviouslySelectedWidgets(conversionApi.writer);
|
106
|
+
const viewWriter = conversionApi.writer;
|
107
|
+
const viewSelection = viewWriter.document.selection;
|
108
|
+
let lastMarked = null;
|
109
|
+
for (const range of viewSelection.getRanges()) {
|
110
|
+
// Note: There could be multiple selected widgets in a range but no fake selection.
|
111
|
+
// All of them must be marked as selected, for instance [<widget></widget><widget></widget>]
|
112
|
+
for (const value of range) {
|
113
|
+
const node = value.item;
|
114
|
+
// Do not mark nested widgets in selected one. See: #4594
|
115
|
+
if (isWidget(node) && !isChild(node, lastMarked)) {
|
116
|
+
viewWriter.addClass(WIDGET_SELECTED_CLASS_NAME, node);
|
117
|
+
this._previouslySelected.add(node);
|
118
|
+
lastMarked = node;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}, { priority: 'low' });
|
123
|
+
// If mouse down is pressed on widget - create selection over whole widget.
|
124
|
+
view.addObserver(MouseObserver);
|
125
|
+
this.listenTo(viewDocument, 'mousedown', (...args) => this._onMousedown(...args));
|
126
|
+
// There are two keydown listeners working on different priorities. This allows other
|
127
|
+
// features such as WidgetTypeAround or TableKeyboard to attach their listeners in between
|
128
|
+
// and customize the behavior even further in different content/selection scenarios.
|
129
|
+
//
|
130
|
+
// * The first listener handles changing the selection on arrow key press
|
131
|
+
// if the widget is selected or if the selection is next to a widget and the widget
|
132
|
+
// should become selected upon the arrow key press.
|
133
|
+
//
|
134
|
+
// * The second (late) listener makes sure the default browser action on arrow key press is
|
135
|
+
// prevented when a widget is selected. This prevents the selection from being moved
|
136
|
+
// from a fake selection container.
|
137
|
+
this.listenTo(viewDocument, 'arrowKey', (...args) => {
|
138
|
+
this._handleSelectionChangeOnArrowKeyPress(...args);
|
139
|
+
}, { context: [isWidget, '$text'] });
|
140
|
+
this.listenTo(viewDocument, 'arrowKey', (...args) => {
|
141
|
+
this._preventDefaultOnArrowKeyPress(...args);
|
142
|
+
}, { context: '$root' });
|
143
|
+
this.listenTo(viewDocument, 'arrowKey', verticalNavigationHandler(this.editor.editing), { context: '$text' });
|
144
|
+
// Handle custom delete behaviour.
|
145
|
+
this.listenTo(viewDocument, 'delete', (evt, data) => {
|
146
|
+
if (this._handleDelete(data.direction == 'forward')) {
|
147
|
+
data.preventDefault();
|
148
|
+
evt.stop();
|
149
|
+
}
|
150
|
+
}, { context: '$root' });
|
151
|
+
}
|
152
|
+
/**
|
153
|
+
* Handles {@link module:engine/view/document~Document#event:mousedown mousedown} events on widget elements.
|
154
|
+
*
|
155
|
+
* @private
|
156
|
+
* @param {module:utils/eventinfo~EventInfo} eventInfo
|
157
|
+
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
|
158
|
+
*/
|
159
|
+
_onMousedown(eventInfo, domEventData) {
|
160
|
+
const editor = this.editor;
|
161
|
+
const view = editor.editing.view;
|
162
|
+
const viewDocument = view.document;
|
163
|
+
let element = domEventData.target;
|
164
|
+
// Do nothing for single or double click inside nested editable.
|
165
|
+
if (isInsideNestedEditable(element)) {
|
166
|
+
// But at least triple click inside nested editable causes broken selection in Safari.
|
167
|
+
// For such event, we select the entire nested editable element.
|
168
|
+
// See: https://github.com/ckeditor/ckeditor5/issues/1463.
|
169
|
+
if ((env.isSafari || env.isGecko) && domEventData.domEvent.detail >= 3) {
|
170
|
+
const mapper = editor.editing.mapper;
|
171
|
+
const viewElement = element.is('attributeElement') ?
|
172
|
+
element.findAncestor(element => !element.is('attributeElement')) : element;
|
173
|
+
const modelElement = mapper.toModelElement(viewElement);
|
174
|
+
domEventData.preventDefault();
|
175
|
+
this.editor.model.change(writer => {
|
176
|
+
writer.setSelection(modelElement, 'in');
|
177
|
+
});
|
178
|
+
}
|
179
|
+
return;
|
180
|
+
}
|
181
|
+
// If target is not a widget element - check if one of the ancestors is.
|
182
|
+
if (!isWidget(element)) {
|
183
|
+
element = element.findAncestor(isWidget);
|
184
|
+
if (!element) {
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
// On Android selection would jump to the first table cell, on other devices
|
189
|
+
// we can't block it (and don't need to) because of drag and drop support.
|
190
|
+
if (env.isAndroid) {
|
191
|
+
domEventData.preventDefault();
|
192
|
+
}
|
193
|
+
// Focus editor if is not focused already.
|
194
|
+
if (!viewDocument.isFocused) {
|
195
|
+
view.focus();
|
196
|
+
}
|
197
|
+
// Create model selection over widget.
|
198
|
+
const modelElement = editor.editing.mapper.toModelElement(element);
|
199
|
+
this._setSelectionOverElement(modelElement);
|
200
|
+
}
|
201
|
+
/**
|
202
|
+
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events and changes
|
203
|
+
* the model selection when:
|
204
|
+
*
|
205
|
+
* * arrow key is pressed when the widget is selected,
|
206
|
+
* * the selection is next to a widget and the widget should become selected upon the arrow key press.
|
207
|
+
*
|
208
|
+
* See {@link #_preventDefaultOnArrowKeyPress}.
|
209
|
+
*
|
210
|
+
* @private
|
211
|
+
* @param {module:utils/eventinfo~EventInfo} eventInfo
|
212
|
+
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
|
213
|
+
*/
|
214
|
+
_handleSelectionChangeOnArrowKeyPress(eventInfo, domEventData) {
|
215
|
+
const keyCode = domEventData.keyCode;
|
216
|
+
const model = this.editor.model;
|
217
|
+
const schema = model.schema;
|
218
|
+
const modelSelection = model.document.selection;
|
219
|
+
const objectElement = modelSelection.getSelectedElement();
|
220
|
+
const direction = getLocalizedArrowKeyCodeDirection(keyCode, this.editor.locale.contentLanguageDirection);
|
221
|
+
const isForward = direction == 'down' || direction == 'right';
|
222
|
+
const isVerticalNavigation = direction == 'up' || direction == 'down';
|
223
|
+
// If object element is selected.
|
224
|
+
if (objectElement && schema.isObject(objectElement)) {
|
225
|
+
const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
|
226
|
+
const newRange = schema.getNearestSelectionRange(position, isForward ? 'forward' : 'backward');
|
227
|
+
if (newRange) {
|
228
|
+
model.change(writer => {
|
229
|
+
writer.setSelection(newRange);
|
230
|
+
});
|
231
|
+
domEventData.preventDefault();
|
232
|
+
eventInfo.stop();
|
233
|
+
}
|
234
|
+
return;
|
235
|
+
}
|
236
|
+
// Handle collapsing of the selection when there is any widget on the edge of selection.
|
237
|
+
// This is needed because browsers have problems with collapsing such selection.
|
238
|
+
if (!modelSelection.isCollapsed && !domEventData.shiftKey) {
|
239
|
+
const firstPosition = modelSelection.getFirstPosition();
|
240
|
+
const lastPosition = modelSelection.getLastPosition();
|
241
|
+
const firstSelectedNode = firstPosition.nodeAfter;
|
242
|
+
const lastSelectedNode = lastPosition.nodeBefore;
|
243
|
+
if (firstSelectedNode && schema.isObject(firstSelectedNode) || lastSelectedNode && schema.isObject(lastSelectedNode)) {
|
244
|
+
model.change(writer => {
|
245
|
+
writer.setSelection(isForward ? lastPosition : firstPosition);
|
246
|
+
});
|
247
|
+
domEventData.preventDefault();
|
248
|
+
eventInfo.stop();
|
249
|
+
}
|
250
|
+
return;
|
251
|
+
}
|
252
|
+
// Return if not collapsed.
|
253
|
+
if (!modelSelection.isCollapsed) {
|
254
|
+
return;
|
255
|
+
}
|
256
|
+
// If selection is next to object element.
|
257
|
+
const objectElementNextToSelection = this._getObjectElementNextToSelection(isForward);
|
258
|
+
if (objectElementNextToSelection && schema.isObject(objectElementNextToSelection)) {
|
259
|
+
// Do not select an inline widget while handling up/down arrow.
|
260
|
+
if (schema.isInline(objectElementNextToSelection) && isVerticalNavigation) {
|
261
|
+
return;
|
262
|
+
}
|
263
|
+
this._setSelectionOverElement(objectElementNextToSelection);
|
264
|
+
domEventData.preventDefault();
|
265
|
+
eventInfo.stop();
|
266
|
+
}
|
267
|
+
}
|
268
|
+
/**
|
269
|
+
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events and prevents
|
270
|
+
* the default browser behavior to make sure the fake selection is not being moved from a fake selection
|
271
|
+
* container.
|
272
|
+
*
|
273
|
+
* See {@link #_handleSelectionChangeOnArrowKeyPress}.
|
274
|
+
*
|
275
|
+
* @private
|
276
|
+
* @param {module:utils/eventinfo~EventInfo} eventInfo
|
277
|
+
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
|
278
|
+
*/
|
279
|
+
_preventDefaultOnArrowKeyPress(eventInfo, domEventData) {
|
280
|
+
const model = this.editor.model;
|
281
|
+
const schema = model.schema;
|
282
|
+
const objectElement = model.document.selection.getSelectedElement();
|
283
|
+
// If object element is selected.
|
284
|
+
if (objectElement && schema.isObject(objectElement)) {
|
285
|
+
domEventData.preventDefault();
|
286
|
+
eventInfo.stop();
|
287
|
+
}
|
288
|
+
}
|
289
|
+
/**
|
290
|
+
* Handles delete keys: backspace and delete.
|
291
|
+
*
|
292
|
+
* @private
|
293
|
+
* @param {Boolean} isForward Set to true if delete was performed in forward direction.
|
294
|
+
* @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
|
295
|
+
*/
|
296
|
+
_handleDelete(isForward) {
|
297
|
+
// Do nothing when the read only mode is enabled.
|
298
|
+
if (this.editor.isReadOnly) {
|
299
|
+
return;
|
300
|
+
}
|
301
|
+
const modelDocument = this.editor.model.document;
|
302
|
+
const modelSelection = modelDocument.selection;
|
303
|
+
// Do nothing on non-collapsed selection.
|
304
|
+
if (!modelSelection.isCollapsed) {
|
305
|
+
return;
|
306
|
+
}
|
307
|
+
const objectElement = this._getObjectElementNextToSelection(isForward);
|
308
|
+
if (objectElement) {
|
309
|
+
this.editor.model.change(writer => {
|
310
|
+
let previousNode = modelSelection.anchor.parent;
|
311
|
+
// Remove previous element if empty.
|
312
|
+
while (previousNode.isEmpty) {
|
313
|
+
const nodeToRemove = previousNode;
|
314
|
+
previousNode = nodeToRemove.parent;
|
315
|
+
writer.remove(nodeToRemove);
|
316
|
+
}
|
317
|
+
this._setSelectionOverElement(objectElement);
|
318
|
+
});
|
319
|
+
return true;
|
320
|
+
}
|
321
|
+
}
|
322
|
+
/**
|
323
|
+
* Sets {@link module:engine/model/selection~Selection document's selection} over given element.
|
324
|
+
*
|
325
|
+
* @internal
|
326
|
+
* @protected
|
327
|
+
* @param {module:engine/model/element~Element} element
|
328
|
+
*/
|
329
|
+
_setSelectionOverElement(element) {
|
330
|
+
this.editor.model.change(writer => {
|
331
|
+
writer.setSelection(writer.createRangeOn(element));
|
332
|
+
});
|
333
|
+
}
|
334
|
+
/**
|
335
|
+
* Checks if {@link module:engine/model/element~Element element} placed next to the current
|
336
|
+
* {@link module:engine/model/selection~Selection model selection} exists and is marked in
|
337
|
+
* {@link module:engine/model/schema~Schema schema} as `object`.
|
338
|
+
*
|
339
|
+
* @internal
|
340
|
+
* @protected
|
341
|
+
* @param {Boolean} forward Direction of checking.
|
342
|
+
* @returns {module:engine/model/element~Element|null}
|
343
|
+
*/
|
344
|
+
_getObjectElementNextToSelection(forward) {
|
345
|
+
const model = this.editor.model;
|
346
|
+
const schema = model.schema;
|
347
|
+
const modelSelection = model.document.selection;
|
348
|
+
// Clone current selection to use it as a probe. We must leave default selection as it is so it can return
|
349
|
+
// to its current state after undo.
|
350
|
+
const probe = model.createSelection(modelSelection);
|
351
|
+
model.modifySelection(probe, { direction: forward ? 'forward' : 'backward' });
|
352
|
+
// The selection didn't change so there is nothing there.
|
353
|
+
if (probe.isEqual(modelSelection)) {
|
354
|
+
return null;
|
355
|
+
}
|
356
|
+
const objectElement = forward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
|
357
|
+
if (!!objectElement && schema.isObject(objectElement)) {
|
358
|
+
return objectElement;
|
359
|
+
}
|
360
|
+
return null;
|
361
|
+
}
|
362
|
+
/**
|
363
|
+
* Removes CSS class from previously selected widgets.
|
364
|
+
*
|
365
|
+
* @private
|
366
|
+
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
|
367
|
+
*/
|
368
|
+
_clearPreviouslySelectedWidgets(writer) {
|
369
|
+
for (const widget of this._previouslySelected) {
|
370
|
+
writer.removeClass(WIDGET_SELECTED_CLASS_NAME, widget);
|
371
|
+
}
|
372
|
+
this._previouslySelected.clear();
|
373
|
+
}
|
445
374
|
}
|
446
|
-
|
447
375
|
// Returns `true` when element is a nested editable or is placed inside one.
|
448
376
|
//
|
449
377
|
// @param {module:engine/view/element~Element}
|
450
378
|
// @returns {Boolean}
|
451
|
-
function isInsideNestedEditable(
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
return false;
|
379
|
+
function isInsideNestedEditable(element) {
|
380
|
+
let currentElement = element;
|
381
|
+
while (currentElement) {
|
382
|
+
if (currentElement.is('editableElement') && !currentElement.is('rootElement')) {
|
383
|
+
return true;
|
384
|
+
}
|
385
|
+
// Click on nested widget should select it.
|
386
|
+
if (isWidget(currentElement)) {
|
387
|
+
return false;
|
388
|
+
}
|
389
|
+
currentElement = currentElement.parent;
|
390
|
+
}
|
391
|
+
return false;
|
466
392
|
}
|
467
|
-
|
468
393
|
// Checks whether the specified `element` is a child of the `parent` element.
|
469
394
|
//
|
470
395
|
// @param {module:engine/view/element~Element} element An element to check.
|
471
396
|
// @param {module:engine/view/element~Element|null} parent A parent for the element.
|
472
397
|
// @returns {Boolean}
|
473
|
-
function isChild(
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
return Array.from( element.getAncestors() ).includes( parent );
|
398
|
+
function isChild(element, parent) {
|
399
|
+
if (!parent) {
|
400
|
+
return false;
|
401
|
+
}
|
402
|
+
return Array.from(element.getAncestors()).includes(parent);
|
479
403
|
}
|