@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/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( node ) {
43
- if ( !node.is( 'element' ) ) {
44
- return false;
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( element, writer, options = {} ) {
97
- if ( !element.is( 'containerElement' ) ) {
98
- /**
99
- * The element passed to `toWidget()` must be a {@link module:engine/view/containerelement~ContainerElement}
100
- * instance.
101
- *
102
- * @error widget-to-widget-wrong-element-type
103
- * @param {String} element The view element passed to `toWidget()`.
104
- */
105
- throw new CKEditorError(
106
- 'widget-to-widget-wrong-element-type',
107
- null,
108
- { element }
109
- );
110
- }
111
-
112
- writer.setAttribute( 'contenteditable', 'false', element );
113
-
114
- writer.addClass( WIDGET_CLASS_NAME, element );
115
- writer.setCustomProperty( 'widget', true, element );
116
- element.getFillerOffset = getFillerOffset;
117
-
118
- if ( options.label ) {
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( element, descriptor, writer ) {
138
- if ( descriptor.classes ) {
139
- writer.addClass( toArray( descriptor.classes ), element );
140
- }
141
-
142
- if ( descriptor.attributes ) {
143
- for ( const key in descriptor.attributes ) {
144
- writer.setAttribute( key, descriptor.attributes[ key ], element );
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( element, descriptor, writer ) {
156
- if ( descriptor.classes ) {
157
- writer.removeClass( toArray( descriptor.classes ), element );
158
- }
159
-
160
- if ( descriptor.attributes ) {
161
- for ( const key in descriptor.attributes ) {
162
- writer.removeAttribute( key, element );
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( element, writer, add = addHighlight, remove = removeHighlight ) {
177
- const stack = new HighlightStack();
178
-
179
- stack.on( 'change:top', ( evt, data ) => {
180
- if ( data.oldDescriptor ) {
181
- remove( element, data.oldDescriptor, data.writer );
182
- }
183
-
184
- if ( data.newDescriptor ) {
185
- add( element, data.newDescriptor, data.writer );
186
- }
187
- } );
188
-
189
- writer.setCustomProperty( 'addHighlight', ( element, descriptor, writer ) => stack.add( descriptor, writer ), element );
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( element, labelOrCreator, writer ) {
203
- writer.setCustomProperty( 'widgetLabel', labelOrCreator, element );
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( element ) {
213
- const labelCreator = element.getCustomProperty( 'widgetLabel' );
214
-
215
- if ( !labelCreator ) {
216
- return '';
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( editable, writer, options = {} ) {
265
- writer.addClass( [ 'ck-editor__editable', 'ck-editor__nested-editable' ], editable );
266
-
267
- writer.setAttribute( 'role', 'textbox', editable );
268
-
269
- if ( options.label ) {
270
- writer.setAttribute( 'aria-label', options.label, editable );
271
- }
272
-
273
- // Set initial contenteditable value.
274
- writer.setAttribute( 'contenteditable', editable.isReadOnly ? 'false' : 'true', editable );
275
-
276
- // Bind the contenteditable property to element#isReadOnly.
277
- editable.on( 'change:isReadOnly', ( evt, property, is ) => {
278
- writer.setAttribute( 'contenteditable', is ? 'false' : 'true', editable );
279
- } );
280
-
281
- editable.on( 'change:isFocused', ( evt, property, is ) => {
282
- if ( is ) {
283
- writer.addClass( 'ck-editor__nested-editable_focused', editable );
284
- } else {
285
- writer.removeClass( 'ck-editor__nested-editable_focused', editable );
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( selection, model ) {
311
- const selectedElement = selection.getSelectedElement();
312
-
313
- if ( selectedElement ) {
314
- const typeAroundFakeCaretPosition = getTypeAroundFakeCaretPosition( selection );
315
-
316
- // If the WidgetTypeAround "fake caret" is displayed, use its position for the insertion
317
- // to provide the most predictable UX (https://github.com/ckeditor/ckeditor5/issues/7438).
318
- if ( typeAroundFakeCaretPosition ) {
319
- return model.createRange( model.createPositionAt( selectedElement, typeAroundFakeCaretPosition ) );
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( model, viewElementMatcher ) {
370
- return ( evt, data ) => {
371
- const { mapper, viewPosition } = data;
372
-
373
- const viewParent = mapper.findMappedViewAncestor( viewPosition );
374
-
375
- if ( !viewElementMatcher( viewParent ) ) {
376
- return;
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
- return null;
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( widgetElement, writer ) {
397
- const selectionHandle = writer.createUIElement( 'div', { class: 'ck ck-widget__selection-handle' }, function( domDocument ) {
398
- const domElement = this.toDomElement( domDocument );
399
-
400
- // Use the IconView from the ui library.
401
- const icon = new IconView();
402
- icon.set( 'content', dragHandleIcon );
403
-
404
- // Render the icon view right away to append its #element to the selectionHandle DOM element.
405
- icon.render();
406
-
407
- domElement.appendChild( icon.element );
408
-
409
- return domElement;
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
  }