@ckeditor/ckeditor5-engine 27.1.0 → 29.2.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.
Files changed (43) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/package.json +22 -21
  4. package/src/controller/datacontroller.js +28 -7
  5. package/src/controller/editingcontroller.js +1 -1
  6. package/src/conversion/conversion.js +4 -4
  7. package/src/conversion/downcastdispatcher.js +6 -2
  8. package/src/conversion/downcasthelpers.js +48 -39
  9. package/src/conversion/mapper.js +1 -0
  10. package/src/conversion/modelconsumable.js +10 -5
  11. package/src/conversion/upcastdispatcher.js +6 -6
  12. package/src/conversion/upcasthelpers.js +34 -30
  13. package/src/dataprocessor/dataprocessor.jsdoc +5 -5
  14. package/src/dataprocessor/htmldataprocessor.js +38 -9
  15. package/src/dataprocessor/xmldataprocessor.js +5 -5
  16. package/src/index.js +1 -0
  17. package/src/model/element.js +3 -3
  18. package/src/model/liveposition.js +1 -1
  19. package/src/model/model.js +5 -5
  20. package/src/model/node.js +3 -3
  21. package/src/model/range.js +5 -3
  22. package/src/model/schema.js +103 -39
  23. package/src/model/selection.js +1 -1
  24. package/src/model/treewalker.js +3 -4
  25. package/src/model/utils/deletecontent.js +17 -4
  26. package/src/model/utils/insertcontent.js +15 -15
  27. package/src/model/utils/selection-post-fixer.js +1 -1
  28. package/src/view/documentselection.js +2 -2
  29. package/src/view/domconverter.js +150 -72
  30. package/src/view/downcastwriter.js +2 -1
  31. package/src/view/element.js +3 -2
  32. package/src/view/filler.js +4 -4
  33. package/src/view/matcher.js +419 -93
  34. package/src/view/observer/focusobserver.js +7 -3
  35. package/src/view/observer/mouseobserver.js +1 -1
  36. package/src/view/renderer.js +9 -1
  37. package/src/view/selection.js +2 -2
  38. package/src/view/styles/background.js +2 -0
  39. package/src/view/styles/border.js +107 -21
  40. package/src/view/styles/utils.js +1 -1
  41. package/src/view/stylesmap.js +45 -5
  42. package/src/view/upcastwriter.js +12 -11
  43. package/src/view/view.js +5 -0
@@ -39,9 +39,6 @@ import { getShorthandValues, getBoxSidesValueReducer, getBoxSidesValues, isLengt
39
39
  * }
40
40
  * };
41
41
  *
42
- * The `border` value is reduced to a 4 values for each box edge (even if they could be further reduces to a single
43
- * `border:<width> <style> <color>` style.
44
- *
45
42
  * @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor
46
43
  */
47
44
  export function addBorderRules( stylesProcessor ) {
@@ -102,7 +99,7 @@ export function addBorderRules( stylesProcessor ) {
102
99
  stylesProcessor.setReducer( 'border-right', getBorderPositionReducer( 'right' ) );
103
100
  stylesProcessor.setReducer( 'border-bottom', getBorderPositionReducer( 'bottom' ) );
104
101
  stylesProcessor.setReducer( 'border-left', getBorderPositionReducer( 'left' ) );
105
- stylesProcessor.setReducer( 'border', borderReducer );
102
+ stylesProcessor.setReducer( 'border', getBorderReducer() );
106
103
 
107
104
  stylesProcessor.setStyleRelation( 'border', [
108
105
  'border-color', 'border-style', 'border-width',
@@ -238,39 +235,128 @@ function normalizeBorderShorthand( string ) {
238
235
  return result;
239
236
  }
240
237
 
241
- function borderReducer( value ) {
242
- const reduced = [];
238
+ // The border reducer factory.
239
+ //
240
+ // It tries to produce the most optimal output for the specified styles.
241
+ //
242
+ // For a border style:
243
+ //
244
+ // style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
245
+ //
246
+ // It will produce: `border-style: solid`.
247
+ // For a border style and color:
248
+ //
249
+ // color: {top: "#ff0", bottom: "#ff0", right: "#ff0", left: "#ff0"}
250
+ // style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
251
+ //
252
+ // It will produce: `border-color: #ff0; border-style: solid`.
253
+ // If all border parameters are specified:
254
+ //
255
+ // color: {top: "#ff0", bottom: "#ff0", right: "#ff0", left: "#ff0"}
256
+ // style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
257
+ // width: {top: "2px", bottom: "2px", right: "2px", left: "2px"}
258
+ //
259
+ // It will combine everything into a single property: `border: 2px solid #ff0`.
260
+ //
261
+ // The definitions are merged only if all border selectors have the same values.
262
+ //
263
+ // @returns {Function}
264
+ function getBorderReducer() {
265
+ return value => {
266
+ const topStyles = extractBorderPosition( value, 'top' );
267
+ const rightStyles = extractBorderPosition( value, 'right' );
268
+ const bottomStyles = extractBorderPosition( value, 'bottom' );
269
+ const leftStyles = extractBorderPosition( value, 'left' );
270
+
271
+ const borderStyles = [ topStyles, rightStyles, bottomStyles, leftStyles ];
272
+
273
+ const borderStylesByType = {
274
+ width: getReducedStyleValueForType( borderStyles, 'width' ),
275
+ style: getReducedStyleValueForType( borderStyles, 'style' ),
276
+ color: getReducedStyleValueForType( borderStyles, 'color' )
277
+ };
278
+
279
+ // Try reducing to a single `border:` property.
280
+ const reducedBorderStyle = reduceBorderPosition( borderStylesByType, 'all' );
281
+
282
+ if ( reducedBorderStyle.length ) {
283
+ return reducedBorderStyle;
284
+ }
285
+
286
+ // Try reducing to `border-style:`, `border-width:`, `border-color:` properties.
287
+ const reducedStyleTypes = Object.entries( borderStylesByType ).reduce( ( reducedStyleTypes, [ type, value ] ) => {
288
+ if ( value ) {
289
+ reducedStyleTypes.push( [ `border-${ type }`, value ] );
290
+
291
+ // Remove it from the full set to not include it in the most specific properties later.
292
+ borderStyles.forEach( style => ( style[ type ] = null ) );
293
+ }
243
294
 
244
- reduced.push( ...reduceBorderPosition( extractBorderPosition( value, 'top' ), 'top' ) );
245
- reduced.push( ...reduceBorderPosition( extractBorderPosition( value, 'right' ), 'right' ) );
246
- reduced.push( ...reduceBorderPosition( extractBorderPosition( value, 'bottom' ), 'bottom' ) );
247
- reduced.push( ...reduceBorderPosition( extractBorderPosition( value, 'left' ), 'left' ) );
295
+ return reducedStyleTypes;
296
+ }, [] );
297
+
298
+ // The reduced properties (by type) and all that remains that could not be reduced.
299
+ return [
300
+ ...reducedStyleTypes,
301
+ ...reduceBorderPosition( topStyles, 'top' ),
302
+ ...reduceBorderPosition( rightStyles, 'right' ),
303
+ ...reduceBorderPosition( bottomStyles, 'bottom' ),
304
+ ...reduceBorderPosition( leftStyles, 'left' )
305
+ ];
306
+ };
248
307
 
249
- return reduced;
308
+ // @param {Array.<Object>} styles The array of objects with `style`, `color`, `width` properties.
309
+ // @param {'width'|'style'|'color'} type
310
+ function getReducedStyleValueForType( styles, type ) {
311
+ return styles
312
+ .map( style => style[ type ] )
313
+ .reduce( ( result, style ) => result == style ? result : null );
314
+ }
250
315
  }
251
316
 
252
317
  function getBorderPositionReducer( which ) {
253
318
  return value => reduceBorderPosition( value, which );
254
319
  }
255
320
 
321
+ // Returns an array with reduced border styles depending on the specified values.
322
+ //
323
+ // If all border properties (width, style, color) are specified, the returned selector will be
324
+ // merged into a group: `border-*: [width] [style] [color]`.
325
+ //
326
+ // Otherwise, the specific definitions will be returned: `border-(width|style|color)-*: [value]`.
327
+ //
328
+ // @param {Object|null} value Styles if defined.
329
+ // @param {'top'|'right'|'bottom'|'left'|'all'} which The border position.
330
+ // @returns {Array}
256
331
  function reduceBorderPosition( value, which ) {
257
- const reduced = [];
332
+ const borderTypes = [];
333
+
334
+ if ( value && value.width ) {
335
+ borderTypes.push( 'width' );
336
+ }
258
337
 
259
- if ( value && value.width !== undefined ) {
260
- reduced.push( value.width );
338
+ if ( value && value.style ) {
339
+ borderTypes.push( 'style' );
261
340
  }
262
341
 
263
- if ( value && value.style !== undefined ) {
264
- reduced.push( value.style );
342
+ if ( value && value.color ) {
343
+ borderTypes.push( 'color' );
265
344
  }
266
345
 
267
- if ( value && value.color !== undefined ) {
268
- reduced.push( value.color );
346
+ if ( borderTypes.length == 3 ) {
347
+ const borderValue = borderTypes.map( item => value[ item ] ).join( ' ' );
348
+
349
+ return [
350
+ which == 'all' ? [ 'border', borderValue ] : [ `border-${ which }`, borderValue ]
351
+ ];
269
352
  }
270
353
 
271
- if ( reduced.length ) {
272
- return [ [ `border-${ which }`, reduced.join( ' ' ) ] ];
354
+ // We are unable to reduce to a single `border:` property.
355
+ if ( which == 'all' ) {
356
+ return [];
273
357
  }
274
358
 
275
- return [];
359
+ return borderTypes.map( type => {
360
+ return [ `border-${ which }-${ type }`, value[ type ] ];
361
+ } );
276
362
  }
@@ -220,7 +220,7 @@ export function getBoxSidesValueReducer( styleShorthand ) {
220
220
  * // will return '1px 1px 2px'
221
221
  *
222
222
  * @param {module:engine/view/stylesmap~BoxSides} styleShorthand
223
- * @returns {Function}
223
+ * @returns {String}
224
224
  */
225
225
  export function getBoxSidesShorthandValue( { top, right, bottom, left } ) {
226
226
  const out = [];
@@ -25,7 +25,7 @@ export default class StylesMap {
25
25
  * Keeps an internal representation of styles map. Normalized styles are kept as object tree to allow unified modification and
26
26
  * value access model using lodash's get, set, unset, etc methods.
27
27
  *
28
- * When no style processor rules are defined the it acts as simple key-value storage.
28
+ * When no style processor rules are defined it acts as simple key-value storage.
29
29
  *
30
30
  * @private
31
31
  * @type {Object}
@@ -44,7 +44,7 @@ export default class StylesMap {
44
44
  /**
45
45
  * Returns true if style map has no styles set.
46
46
  *
47
- * @returns {Boolean}
47
+ * @type {Boolean}
48
48
  */
49
49
  get isEmpty() {
50
50
  const entries = Object.entries( this._styles );
@@ -350,15 +350,28 @@ export default class StylesMap {
350
350
  }
351
351
 
352
352
  /**
353
- * Returns style property names as they would appear when using {@link #toString `#toString()`}.
353
+ * Returns all style properties names as they would appear when using {@link #toString `#toString()`}.
354
+ *
355
+ * When `expand` is set to true and there's a shorthand style property set, it will also return all equivalent styles:
356
+ *
357
+ * stylesMap.setTo( 'margin: 1em' )
358
+ *
359
+ * will be expanded to:
360
+ *
361
+ * [ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ]
354
362
  *
363
+ * @param {Boolean} [expand=false] Expand shorthand style properties and all return equivalent style representations.
355
364
  * @returns {Array.<String>}
356
365
  */
357
- getStyleNames() {
366
+ getStyleNames( expand = false ) {
358
367
  if ( this.isEmpty ) {
359
368
  return [];
360
369
  }
361
370
 
371
+ if ( expand ) {
372
+ return this._styleProcessor.getStyleNames( this._styles );
373
+ }
374
+
362
375
  const entries = this._getStylesEntries();
363
376
 
364
377
  return entries.map( ( [ key ] ) => key );
@@ -540,7 +553,6 @@ export class StylesProcessor {
540
553
  *
541
554
  * **Note**: To define reducer callbacks use {@link #setReducer}.
542
555
  *
543
- * @param {String} name
544
556
  * @param {String} name Name of style property.
545
557
  * @param {Object} styles Object holding normalized styles.
546
558
  * @returns {Array.<module:engine/view/stylesmap~PropertyDescriptor>}
@@ -562,6 +574,34 @@ export class StylesProcessor {
562
574
  return [ [ name, normalizedValue ] ];
563
575
  }
564
576
 
577
+ /**
578
+ * Return all style properties. Also expand shorthand properties (e.g. `margin`, `background`) if respective extractor is available.
579
+ *
580
+ * @param {Object} styles Object holding normalized styles.
581
+ * @returns {Array.<String>}
582
+ */
583
+ getStyleNames( styles ) {
584
+ // Find all extractable styles that have a value.
585
+ const expandedStyleNames = Array.from( this._consumables.keys() ).filter( name => {
586
+ const style = this.getNormalized( name, styles );
587
+
588
+ if ( style && typeof style == 'object' ) {
589
+ return Object.keys( style ).length;
590
+ }
591
+
592
+ return style;
593
+ } );
594
+
595
+ // For simple styles (for example `color`) we don't have a map of those styles
596
+ // but they are 1 to 1 with normalized object keys.
597
+ const styleNamesKeysSet = new Set( [
598
+ ...expandedStyleNames,
599
+ ...Object.keys( styles )
600
+ ] );
601
+
602
+ return Array.from( styleNamesKeysSet.values() );
603
+ }
604
+
565
605
  /**
566
606
  * Returns related style names.
567
607
  *
@@ -222,7 +222,7 @@ export default class UpcastWriter {
222
222
  /**
223
223
  * Adds or overwrites element's attribute with a specified key and value.
224
224
  *
225
- * writer.setAttribute( linkElement, 'href', 'http://ckeditor.com' );
225
+ * writer.setAttribute( 'href', 'http://ckeditor.com', linkElement );
226
226
  *
227
227
  * @see module:engine/view/element~Element#_setAttribute
228
228
  * @param {String} key Attribute key.
@@ -236,7 +236,7 @@ export default class UpcastWriter {
236
236
  /**
237
237
  * Removes attribute from the element.
238
238
  *
239
- * writer.removeAttribute( linkElement, 'href' );
239
+ * writer.removeAttribute( 'href', linkElement );
240
240
  *
241
241
  * @see module:engine/view/element~Element#_removeAttribute
242
242
  * @param {String} key Attribute key.
@@ -249,8 +249,8 @@ export default class UpcastWriter {
249
249
  /**
250
250
  * Adds specified class to the element.
251
251
  *
252
- * writer.addClass( linkElement, 'foo' );
253
- * writer.addClass( linkElement, [ 'foo', 'bar' ] );
252
+ * writer.addClass( 'foo', linkElement );
253
+ * writer.addClass( [ 'foo', 'bar' ], linkElement );
254
254
  *
255
255
  * @see module:engine/view/element~Element#_addClass
256
256
  * @param {Array.<String>|String} className Single class name or array of class names which will be added.
@@ -263,8 +263,8 @@ export default class UpcastWriter {
263
263
  /**
264
264
  * Removes specified class from the element.
265
265
  *
266
- * writer.removeClass( linkElement, 'foo' );
267
- * writer.removeClass( linkElement, [ 'foo', 'bar' ] );
266
+ * writer.removeClass( 'foo', linkElement );
267
+ * writer.removeClass( [ 'foo', 'bar' ], linkElement );
268
268
  *
269
269
  * @see module:engine/view/element~Element#_removeClass
270
270
  * @param {Array.<String>|String} className Single class name or array of class names which will be removed.
@@ -277,11 +277,11 @@ export default class UpcastWriter {
277
277
  /**
278
278
  * Adds style to the element.
279
279
  *
280
- * writer.setStyle( element, 'color', 'red' );
281
- * writer.setStyle( element, {
280
+ * writer.setStyle( 'color', 'red', element );
281
+ * writer.setStyle( {
282
282
  * color: 'red',
283
283
  * position: 'fixed'
284
- * } );
284
+ * }, element );
285
285
  *
286
286
  * **Note**: This method can work with normalized style names if
287
287
  * {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
@@ -302,8 +302,8 @@ export default class UpcastWriter {
302
302
  /**
303
303
  * Removes specified style from the element.
304
304
  *
305
- * writer.removeStyle( element, 'color' ); // Removes 'color' style.
306
- * writer.removeStyle( element, [ 'color', 'border-top' ] ); // Removes both 'color' and 'border-top' styles.
305
+ * writer.removeStyle( 'color', element ); // Removes 'color' style.
306
+ * writer.removeStyle( [ 'color', 'border-top' ], element ); // Removes both 'color' and 'border-top' styles.
307
307
  *
308
308
  * **Note**: This method can work with normalized style names if
309
309
  * {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
@@ -358,6 +358,7 @@ export default class UpcastWriter {
358
358
  * @param {module:engine/view/item~Item|module:engine/model/position~Position} itemOrPosition
359
359
  * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
360
360
  * first parameter is a {@link module:engine/view/item~Item view item}.
361
+ * @returns {module:engine/view/position~Position}
361
362
  */
362
363
  createPositionAt( itemOrPosition, offset ) {
363
364
  return Position._createAt( itemOrPosition, offset );
package/src/view/view.js CHANGED
@@ -210,6 +210,11 @@ export default class View {
210
210
  this.listenTo( this.document.selection, 'change', () => {
211
211
  this._hasChangedSinceTheLastRendering = true;
212
212
  } );
213
+
214
+ // Trigger re-render if only the focus changed.
215
+ this.listenTo( this.document, 'change:isFocused', () => {
216
+ this._hasChangedSinceTheLastRendering = true;
217
+ } );
213
218
  }
214
219
 
215
220
  /**