@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/utils.js
CHANGED
@@ -2,51 +2,40 @@
|
|
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/utils
|
8
7
|
*/
|
9
|
-
|
10
8
|
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
11
9
|
import toArray from '@ckeditor/ckeditor5-utils/src/toarray';
|
12
|
-
import {
|
13
|
-
findOptimalInsertionRange as engineFindOptimalInsertionRange
|
14
|
-
} from '@ckeditor/ckeditor5-engine/src/model/utils/findoptimalinsertionrange';
|
15
|
-
|
10
|
+
import { findOptimalInsertionRange as engineFindOptimalInsertionRange } from '@ckeditor/ckeditor5-engine/src/model/utils/findoptimalinsertionrange';
|
16
11
|
import HighlightStack from './highlightstack';
|
17
12
|
import { getTypeAroundFakeCaretPosition } from './widgettypearound/utils';
|
18
|
-
|
19
13
|
import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
|
20
14
|
import dragHandleIcon from '../theme/icons/drag-handle.svg';
|
21
|
-
|
22
15
|
/**
|
23
16
|
* CSS class added to each widget element.
|
24
17
|
*
|
25
18
|
* @const {String}
|
26
19
|
*/
|
27
20
|
export const WIDGET_CLASS_NAME = 'ck-widget';
|
28
|
-
|
29
21
|
/**
|
30
22
|
* CSS class added to currently selected widget element.
|
31
23
|
*
|
32
24
|
* @const {String}
|
33
25
|
*/
|
34
26
|
export const WIDGET_SELECTED_CLASS_NAME = 'ck-widget_selected';
|
35
|
-
|
36
27
|
/**
|
37
28
|
* Returns `true` if given {@link module:engine/view/node~Node} is an {@link module:engine/view/element~Element} and a widget.
|
38
29
|
*
|
39
30
|
* @param {module:engine/view/node~Node} node
|
40
31
|
* @returns {Boolean}
|
41
32
|
*/
|
42
|
-
export function isWidget(
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
return !!node.getCustomProperty( 'widget' );
|
33
|
+
export function isWidget(node) {
|
34
|
+
if (!node.is('element')) {
|
35
|
+
return false;
|
36
|
+
}
|
37
|
+
return !!node.getCustomProperty('widget');
|
48
38
|
}
|
49
|
-
|
50
39
|
/**
|
51
40
|
* Converts the given {@link module:engine/view/element~Element} to a widget in the following way:
|
52
41
|
*
|
@@ -93,77 +82,62 @@ export function isWidget( node ) {
|
|
93
82
|
* @param {Boolean} [options.hasSelectionHandle=false] If `true`, the widget will have a selection handle added.
|
94
83
|
* @returns {module:engine/view/element~Element} Returns the same element.
|
95
84
|
*/
|
96
|
-
export function toWidget(
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
setLabel( element, options.label, writer );
|
120
|
-
}
|
121
|
-
|
122
|
-
if ( options.hasSelectionHandle ) {
|
123
|
-
addSelectionHandle( element, writer );
|
124
|
-
}
|
125
|
-
|
126
|
-
setHighlightHandling( element, writer );
|
127
|
-
|
128
|
-
return element;
|
85
|
+
export function toWidget(element, writer, options = {}) {
|
86
|
+
if (!element.is('containerElement')) {
|
87
|
+
/**
|
88
|
+
* The element passed to `toWidget()` must be a {@link module:engine/view/containerelement~ContainerElement}
|
89
|
+
* instance.
|
90
|
+
*
|
91
|
+
* @error widget-to-widget-wrong-element-type
|
92
|
+
* @param {String} element The view element passed to `toWidget()`.
|
93
|
+
*/
|
94
|
+
throw new CKEditorError('widget-to-widget-wrong-element-type', null, { element });
|
95
|
+
}
|
96
|
+
writer.setAttribute('contenteditable', 'false', element);
|
97
|
+
writer.addClass(WIDGET_CLASS_NAME, element);
|
98
|
+
writer.setCustomProperty('widget', true, element);
|
99
|
+
element.getFillerOffset = getFillerOffset;
|
100
|
+
if (options.label) {
|
101
|
+
setLabel(element, options.label, writer);
|
102
|
+
}
|
103
|
+
if (options.hasSelectionHandle) {
|
104
|
+
addSelectionHandle(element, writer);
|
105
|
+
}
|
106
|
+
setHighlightHandling(element, writer);
|
107
|
+
return element;
|
129
108
|
}
|
130
|
-
|
131
109
|
// Default handler for adding a highlight on a widget.
|
132
110
|
// It adds CSS class and attributes basing on the given highlight descriptor.
|
133
111
|
//
|
134
112
|
// @param {module:engine/view/element~Element} element
|
135
113
|
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
|
136
114
|
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
|
137
|
-
function addHighlight(
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
}
|
115
|
+
function addHighlight(element, descriptor, writer) {
|
116
|
+
if (descriptor.classes) {
|
117
|
+
writer.addClass(toArray(descriptor.classes), element);
|
118
|
+
}
|
119
|
+
if (descriptor.attributes) {
|
120
|
+
for (const key in descriptor.attributes) {
|
121
|
+
writer.setAttribute(key, descriptor.attributes[key], element);
|
122
|
+
}
|
123
|
+
}
|
147
124
|
}
|
148
|
-
|
149
125
|
// Default handler for removing a highlight from a widget.
|
150
126
|
// It removes CSS class and attributes basing on the given highlight descriptor.
|
151
127
|
//
|
152
128
|
// @param {module:engine/view/element~Element} element
|
153
129
|
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
|
154
130
|
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
|
155
|
-
function removeHighlight(
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
}
|
131
|
+
function removeHighlight(element, descriptor, writer) {
|
132
|
+
if (descriptor.classes) {
|
133
|
+
writer.removeClass(toArray(descriptor.classes), element);
|
134
|
+
}
|
135
|
+
if (descriptor.attributes) {
|
136
|
+
for (const key in descriptor.attributes) {
|
137
|
+
writer.removeAttribute(key, element);
|
138
|
+
}
|
139
|
+
}
|
165
140
|
}
|
166
|
-
|
167
141
|
/**
|
168
142
|
* Sets highlight handling methods. Uses {@link module:widget/highlightstack~HighlightStack} to
|
169
143
|
* properly determine which highlight descriptor should be used at given time.
|
@@ -173,23 +147,21 @@ function removeHighlight( element, descriptor, writer ) {
|
|
173
147
|
* @param {Function} [add]
|
174
148
|
* @param {Function} [remove]
|
175
149
|
*/
|
176
|
-
export function setHighlightHandling(
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
writer.setCustomProperty( 'removeHighlight', ( element, id, writer ) => stack.remove( id, writer ), element );
|
150
|
+
export function setHighlightHandling(element, writer, add = addHighlight, remove = removeHighlight) {
|
151
|
+
const stack = new HighlightStack();
|
152
|
+
stack.on('change:top', (evt, data) => {
|
153
|
+
if (data.oldDescriptor) {
|
154
|
+
remove(element, data.oldDescriptor, data.writer);
|
155
|
+
}
|
156
|
+
if (data.newDescriptor) {
|
157
|
+
add(element, data.newDescriptor, data.writer);
|
158
|
+
}
|
159
|
+
});
|
160
|
+
const addHighlightCallback = (element, descriptor, writer) => stack.add(descriptor, writer);
|
161
|
+
const removeHighlightCallback = (element, id, writer) => stack.remove(id, writer);
|
162
|
+
writer.setCustomProperty('addHighlight', addHighlightCallback, element);
|
163
|
+
writer.setCustomProperty('removeHighlight', removeHighlightCallback, element);
|
191
164
|
}
|
192
|
-
|
193
165
|
/**
|
194
166
|
* Sets label for given element.
|
195
167
|
* It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by
|
@@ -199,26 +171,22 @@ export function setHighlightHandling( element, writer, add = addHighlight, remov
|
|
199
171
|
* @param {String|Function} labelOrCreator
|
200
172
|
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
|
201
173
|
*/
|
202
|
-
export function setLabel(
|
203
|
-
|
174
|
+
export function setLabel(element, labelOrCreator, writer) {
|
175
|
+
writer.setCustomProperty('widgetLabel', labelOrCreator, element);
|
204
176
|
}
|
205
|
-
|
206
177
|
/**
|
207
178
|
* Returns the label of the provided element.
|
208
179
|
*
|
209
180
|
* @param {module:engine/view/element~Element} element
|
210
181
|
* @returns {String}
|
211
182
|
*/
|
212
|
-
export function getLabel(
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
|
183
|
+
export function getLabel(element) {
|
184
|
+
const labelCreator = element.getCustomProperty('widgetLabel');
|
185
|
+
if (!labelCreator) {
|
186
|
+
return '';
|
187
|
+
}
|
188
|
+
return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
|
220
189
|
}
|
221
|
-
|
222
190
|
/**
|
223
191
|
* Adds functionality to the provided {@link module:engine/view/editableelement~EditableElement} to act as a widget's editable:
|
224
192
|
*
|
@@ -261,36 +229,29 @@ export function getLabel( element ) {
|
|
261
229
|
* @param {String} [options.label] Editable's label used by assistive technologies (e.g. screen readers).
|
262
230
|
* @returns {module:engine/view/editableelement~EditableElement} Returns the same element that was provided in the `editable` parameter
|
263
231
|
*/
|
264
|
-
export function toWidgetEditable(
|
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
|
-
setHighlightHandling( editable, writer );
|
290
|
-
|
291
|
-
return editable;
|
232
|
+
export function toWidgetEditable(editable, writer, options = {}) {
|
233
|
+
writer.addClass(['ck-editor__editable', 'ck-editor__nested-editable'], editable);
|
234
|
+
writer.setAttribute('role', 'textbox', editable);
|
235
|
+
if (options.label) {
|
236
|
+
writer.setAttribute('aria-label', options.label, editable);
|
237
|
+
}
|
238
|
+
// Set initial contenteditable value.
|
239
|
+
writer.setAttribute('contenteditable', editable.isReadOnly ? 'false' : 'true', editable);
|
240
|
+
// Bind the contenteditable property to element#isReadOnly.
|
241
|
+
editable.on('change:isReadOnly', (evt, property, is) => {
|
242
|
+
writer.setAttribute('contenteditable', is ? 'false' : 'true', editable);
|
243
|
+
});
|
244
|
+
editable.on('change:isFocused', (evt, property, is) => {
|
245
|
+
if (is) {
|
246
|
+
writer.addClass('ck-editor__nested-editable_focused', editable);
|
247
|
+
}
|
248
|
+
else {
|
249
|
+
writer.removeClass('ck-editor__nested-editable_focused', editable);
|
250
|
+
}
|
251
|
+
});
|
252
|
+
setHighlightHandling(editable, writer);
|
253
|
+
return editable;
|
292
254
|
}
|
293
|
-
|
294
255
|
/**
|
295
256
|
* Returns a model range which is optimal (in terms of UX) for inserting a widget block.
|
296
257
|
*
|
@@ -307,22 +268,18 @@ export function toWidgetEditable( editable, writer, options = {} ) {
|
|
307
268
|
* @param {module:engine/model/model~Model} model Model instance.
|
308
269
|
* @returns {module:engine/model/range~Range} The optimal range.
|
309
270
|
*/
|
310
|
-
export function findOptimalInsertionRange(
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
}
|
322
|
-
|
323
|
-
return engineFindOptimalInsertionRange( selection, model );
|
271
|
+
export function findOptimalInsertionRange(selection, model) {
|
272
|
+
const selectedElement = selection.getSelectedElement();
|
273
|
+
if (selectedElement) {
|
274
|
+
const typeAroundFakeCaretPosition = getTypeAroundFakeCaretPosition(selection);
|
275
|
+
// If the WidgetTypeAround "fake caret" is displayed, use its position for the insertion
|
276
|
+
// to provide the most predictable UX (https://github.com/ckeditor/ckeditor5/issues/7438).
|
277
|
+
if (typeAroundFakeCaretPosition) {
|
278
|
+
return model.createRange(model.createPositionAt(selectedElement, typeAroundFakeCaretPosition));
|
279
|
+
}
|
280
|
+
}
|
281
|
+
return engineFindOptimalInsertionRange(selection, model);
|
324
282
|
}
|
325
|
-
|
326
283
|
/**
|
327
284
|
* A util to be used in order to map view positions to correct model positions when implementing a widget
|
328
285
|
* which renders non-empty view element for an empty model element.
|
@@ -366,50 +323,39 @@ export function findOptimalInsertionRange( selection, model ) {
|
|
366
323
|
* should be applied to the given view element.
|
367
324
|
* @return {Function}
|
368
325
|
*/
|
369
|
-
export function viewToModelPositionOutsideModelElement(
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
const modelParent = mapper.toModelElement( viewParent );
|
380
|
-
|
381
|
-
data.modelPosition = model.createPositionAt( modelParent, viewPosition.isAtStart ? 'before' : 'after' );
|
382
|
-
};
|
326
|
+
export function viewToModelPositionOutsideModelElement(model, viewElementMatcher) {
|
327
|
+
return (evt, data) => {
|
328
|
+
const { mapper, viewPosition } = data;
|
329
|
+
const viewParent = mapper.findMappedViewAncestor(viewPosition);
|
330
|
+
if (!viewElementMatcher(viewParent)) {
|
331
|
+
return;
|
332
|
+
}
|
333
|
+
const modelParent = mapper.toModelElement(viewParent);
|
334
|
+
data.modelPosition = model.createPositionAt(modelParent, viewPosition.isAtStart ? 'before' : 'after');
|
335
|
+
};
|
383
336
|
}
|
384
|
-
|
385
337
|
// Default filler offset function applied to all widget elements.
|
386
338
|
//
|
387
339
|
// @returns {null}
|
388
340
|
function getFillerOffset() {
|
389
|
-
|
341
|
+
return null;
|
390
342
|
}
|
391
|
-
|
392
343
|
// Adds a drag handle to the widget.
|
393
344
|
//
|
394
345
|
// @param {module:engine/view/containerelement~ContainerElement}
|
395
346
|
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
|
396
|
-
function addSelectionHandle(
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
} );
|
411
|
-
|
412
|
-
// Append the selection handle into the widget wrapper.
|
413
|
-
writer.insert( writer.createPositionAt( widgetElement, 0 ), selectionHandle );
|
414
|
-
writer.addClass( [ 'ck-widget_with-selection-handle' ], widgetElement );
|
347
|
+
function addSelectionHandle(widgetElement, writer) {
|
348
|
+
const selectionHandle = writer.createUIElement('div', { class: 'ck ck-widget__selection-handle' }, function (domDocument) {
|
349
|
+
const domElement = this.toDomElement(domDocument);
|
350
|
+
// Use the IconView from the ui library.
|
351
|
+
const icon = new IconView();
|
352
|
+
icon.set('content', dragHandleIcon);
|
353
|
+
// Render the icon view right away to append its #element to the selectionHandle DOM element.
|
354
|
+
icon.render();
|
355
|
+
domElement.appendChild(icon.element);
|
356
|
+
return domElement;
|
357
|
+
});
|
358
|
+
// Append the selection handle into the widget wrapper.
|
359
|
+
writer.insert(writer.createPositionAt(widgetElement, 0), selectionHandle);
|
360
|
+
writer.addClass(['ck-widget_with-selection-handle'], widgetElement);
|
415
361
|
}
|