@ckeditor/ckeditor5-engine 45.0.0-alpha.9 → 45.1.0-alpha.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 (49) hide show
  1. package/dist/index.js +187 -84
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/src/controller/editingcontroller.js +1 -1
  5. package/src/conversion/conversion.d.ts +3 -0
  6. package/src/conversion/conversion.js +3 -0
  7. package/src/conversion/downcasthelpers.d.ts +2 -7
  8. package/src/conversion/downcasthelpers.js +8 -11
  9. package/src/dev-utils/utils.js +1 -0
  10. package/src/dev-utils/view.d.ts +1 -3
  11. package/src/dev-utils/view.js +1 -3
  12. package/src/model/documentselection.js +2 -2
  13. package/src/model/history.js +2 -1
  14. package/src/model/nodelist.js +2 -2
  15. package/src/model/operation/attributeoperation.js +5 -5
  16. package/src/model/operation/operation.d.ts +1 -1
  17. package/src/model/operation/operation.js +1 -1
  18. package/src/model/operation/rootattributeoperation.js +8 -10
  19. package/src/model/operation/transform.js +67 -10
  20. package/src/model/operation/utils.d.ts +1 -1
  21. package/src/model/operation/utils.js +1 -1
  22. package/src/model/position.js +7 -7
  23. package/src/model/range.d.ts +1 -1
  24. package/src/model/range.js +1 -1
  25. package/src/model/selection.js +2 -2
  26. package/src/model/utils/deletecontent.d.ts +13 -0
  27. package/src/model/utils/deletecontent.js +20 -1
  28. package/src/model/utils/insertcontent.d.ts +0 -1
  29. package/src/model/utils/insertcontent.js +2 -3
  30. package/src/model/utils/insertobject.d.ts +0 -1
  31. package/src/model/utils/insertobject.js +0 -1
  32. package/src/model/writer.d.ts +1 -1
  33. package/src/model/writer.js +2 -2
  34. package/src/view/domconverter.d.ts +5 -5
  35. package/src/view/domconverter.js +5 -5
  36. package/src/view/downcastwriter.d.ts +3 -2
  37. package/src/view/downcastwriter.js +3 -2
  38. package/src/view/editableelement.d.ts +1 -1
  39. package/src/view/editableelement.js +1 -1
  40. package/src/view/emptyelement.d.ts +1 -1
  41. package/src/view/emptyelement.js +1 -1
  42. package/src/view/filler.d.ts +1 -1
  43. package/src/view/filler.js +1 -1
  44. package/src/view/observer/compositionobserver.js +2 -2
  45. package/src/view/observer/inputobserver.js +29 -3
  46. package/src/view/position.js +3 -3
  47. package/src/view/renderer.js +8 -0
  48. package/src/view/textproxy.d.ts +1 -2
  49. package/src/view/textproxy.js +1 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-engine",
3
- "version": "45.0.0-alpha.9",
3
+ "version": "45.1.0-alpha.0",
4
4
  "description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
5
5
  "keywords": [
6
6
  "wysiwyg",
@@ -24,7 +24,7 @@
24
24
  "type": "module",
25
25
  "main": "src/index.js",
26
26
  "dependencies": {
27
- "@ckeditor/ckeditor5-utils": "45.0.0-alpha.9",
27
+ "@ckeditor/ckeditor5-utils": "45.1.0-alpha.0",
28
28
  "es-toolkit": "1.32.0"
29
29
  },
30
30
  "author": "CKSource (http://cksource.com/)",
@@ -158,7 +158,7 @@ export default class EditingController extends /* #__PURE__ */ ObservableMixin()
158
158
  * The marker with the provided name does not exist and cannot be reconverted.
159
159
  *
160
160
  * @error editingcontroller-reconvertmarker-marker-not-exist
161
- * @param {String} markerName The name of the reconverted marker.
161
+ * @param {string} markerName The name of the reconverted marker.
162
162
  */
163
163
  throw new CKEditorError('editingcontroller-reconvertmarker-marker-not-exist', this, { markerName });
164
164
  }
@@ -470,7 +470,10 @@ export default class Conversion {
470
470
  /**
471
471
  * Creates and caches conversion helpers for given dispatchers group.
472
472
  *
473
+ * @param options Group name.
473
474
  * @param options.name Group name.
475
+ * @param options.dispatchers Dispatchers to register.
476
+ * @param options.isDowncast Whether downcast group.
474
477
  */
475
478
  private _createConversionHelpers;
476
479
  }
@@ -559,7 +559,10 @@ export default class Conversion {
559
559
  /**
560
560
  * Creates and caches conversion helpers for given dispatchers group.
561
561
  *
562
+ * @param options Group name.
562
563
  * @param options.name Group name.
564
+ * @param options.dispatchers Dispatchers to register.
565
+ * @param options.isDowncast Whether downcast group.
563
566
  */
564
567
  _createConversionHelpers({ name, dispatchers, isDowncast }) {
565
568
  if (this._helpers.has(name)) {
@@ -169,13 +169,10 @@ export default class DowncastHelpers extends ConversionHelpers<DowncastDispatche
169
169
  *
170
170
  * @param config Conversion configuration.
171
171
  * @param config.model The description or a name of the model element to convert.
172
- * @param config.model.attributes The list of attribute names that should be consumed while creating
173
- * the view element. Note that the view will be reconverted if any of the listed attributes changes.
174
- * @param config.model.children Specifies whether the view element requires reconversion if the list
175
- * of the model child nodes changed.
176
172
  * @param config.view A view element definition or a function that takes the model element and
177
173
  * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
178
174
  * as parameters and returns a view container element.
175
+ * @param config.converterPriority Converter priority.
179
176
  */
180
177
  elementToElement(config: {
181
178
  model: string | {
@@ -290,12 +287,10 @@ export default class DowncastHelpers extends ConversionHelpers<DowncastDispatche
290
287
  *
291
288
  * @param config Conversion configuration.
292
289
  * @param config.model The description or a name of the model element to convert.
293
- * @param config.model.name The name of the model element to convert.
294
- * @param config.model.attributes The list of attribute names that should be consumed while creating
295
- * the view structure. Note that the view will be reconverted if any of the listed attributes will change.
296
290
  * @param config.view A function that takes the model element and
297
291
  * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters
298
292
  * and returns a view container element with slots for model child nodes to be converted into.
293
+ * @param config.converterPriority Converter priority.
299
294
  */
300
295
  elementToStructure(config: {
301
296
  model: string | {
@@ -161,13 +161,10 @@ export default class DowncastHelpers extends ConversionHelpers {
161
161
  *
162
162
  * @param config Conversion configuration.
163
163
  * @param config.model The description or a name of the model element to convert.
164
- * @param config.model.attributes The list of attribute names that should be consumed while creating
165
- * the view element. Note that the view will be reconverted if any of the listed attributes changes.
166
- * @param config.model.children Specifies whether the view element requires reconversion if the list
167
- * of the model child nodes changed.
168
164
  * @param config.view A view element definition or a function that takes the model element and
169
165
  * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
170
166
  * as parameters and returns a view container element.
167
+ * @param config.converterPriority Converter priority.
171
168
  */
172
169
  elementToElement(config) {
173
170
  return this.add(downcastElementToElement(config));
@@ -276,12 +273,10 @@ export default class DowncastHelpers extends ConversionHelpers {
276
273
  *
277
274
  * @param config Conversion configuration.
278
275
  * @param config.model The description or a name of the model element to convert.
279
- * @param config.model.name The name of the model element to convert.
280
- * @param config.model.attributes The list of attribute names that should be consumed while creating
281
- * the view structure. Note that the view will be reconverted if any of the listed attributes will change.
282
276
  * @param config.view A function that takes the model element and
283
277
  * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters
284
278
  * and returns a view container element with slots for model child nodes to be converted into.
279
+ * @param config.converterPriority Converter priority.
285
280
  */
286
281
  elementToStructure(config) {
287
282
  return this.add(downcastElementToStructure(config));
@@ -1348,6 +1343,7 @@ function changeAttribute(attributeCreator) {
1348
1343
  * ```
1349
1344
  *
1350
1345
  * @error conversion-attribute-to-attribute-on-text
1346
+ * @param {object} data The conversion data.
1351
1347
  */
1352
1348
  throw new CKEditorError('conversion-attribute-to-attribute-on-text', conversionApi.dispatcher, data);
1353
1349
  }
@@ -1610,7 +1606,7 @@ function downcastElementToStructure(config) {
1610
1606
  * ```
1611
1607
  *
1612
1608
  * @error conversion-element-to-structure-disallowed-text
1613
- * @param {String} elementName The name of the element the structure is to be created for.
1609
+ * @param {string} elementName The name of the element the structure is to be created for.
1614
1610
  */
1615
1611
  throw new CKEditorError('conversion-element-to-structure-disallowed-text', dispatcher, { elementName: model.name });
1616
1612
  }
@@ -1999,9 +1995,9 @@ function createConsumer(model) {
1999
1995
  };
2000
1996
  }
2001
1997
  /**
2002
- * Creates a function that create view slots.
1998
+ * Creates a function that creates view slots.
2003
1999
  *
2004
- * @returns Function exposed by writer as createSlot().
2000
+ * @returns Function exposed by the writer as `createSlot()`.
2005
2001
  */
2006
2002
  function createSlotFactory(element, slotsMap, conversionApi) {
2007
2003
  return (writer, modeOrFilter) => {
@@ -2015,9 +2011,10 @@ function createSlotFactory(element, slotsMap, conversionApi) {
2015
2011
  }
2016
2012
  else {
2017
2013
  /**
2018
- * Unknown slot mode was provided to `writer.createSlot()` in downcast converter.
2014
+ * Unknown slot mode was provided to `writer.createSlot()` in the downcast converter.
2019
2015
  *
2020
2016
  * @error conversion-slot-mode-unknown
2017
+ * @param {never} modeOrFilter The specified mode or filter.
2021
2018
  */
2022
2019
  throw new CKEditorError('conversion-slot-mode-unknown', conversionApi.dispatcher, { modeOrFilter });
2023
2020
  }
@@ -107,6 +107,7 @@ export function logDocument(document, version) {
107
107
  // @if CK_DEBUG_TYPING // editor.editing.view.domConverter,
108
108
  // @if CK_DEBUG_TYPING // ...editor.editing.view._observers.values(),
109
109
  // @if CK_DEBUG_TYPING // editor.plugins.get( 'Input' ),
110
+ // @if CK_DEBUG_TYPING // editor.plugins.get( 'Input' )._typingQueue,
110
111
  // @if CK_DEBUG_TYPING // editor.plugins.get( 'WidgetTypeAround' ),
111
112
  // @if CK_DEBUG_TYPING // editor.commands.get( 'delete' ),
112
113
  // @if CK_DEBUG_TYPING // editor.commands.get( 'deleteForward' )
@@ -19,6 +19,7 @@ import type DomConverter from '../view/domconverter.js';
19
19
  /**
20
20
  * Writes the content of the {@link module:engine/view/document~Document document} to an HTML-like string.
21
21
  *
22
+ * @param view The view to stringify.
22
23
  * @param options.withoutSelection Whether to write the selection. When set to `true`, the selection will
23
24
  * not be included in the returned string.
24
25
  * @param options.rootName The name of the root from which the data should be stringified. If not provided,
@@ -27,8 +28,6 @@ import type DomConverter from '../view/domconverter.js';
27
28
  * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
28
29
  * @param options.showPriority When set to `true`, the attribute element's priority will be printed
29
30
  * (`<span view-priority="12">`, `<b view-priority="10">`).
30
- * @param options.showAttributeElementId When set to `true`, the attribute element's ID will be printed
31
- * (`<span id="marker:foo">`).
32
31
  * @param options.renderUIElements When set to `true`, the inner content of each
33
32
  * {@link module:engine/view/uielement~UIElement} will be printed.
34
33
  * @param options.renderRawElements When set to `true`, the inner content of each
@@ -304,7 +303,6 @@ export declare function stringify(node: ViewNode | ViewDocumentFragment, selecti
304
303
  * this node will be used as the root for all parsed nodes.
305
304
  * @param options.sameSelectionCharacters When set to `false`, the selection inside the text should be marked using
306
305
  * `{` and `}` and the selection outside the ext using `[` and `]`. When set to `true`, both should be marked with `[` and `]` only.
307
- * @param options.stylesProcessor Styles processor.
308
306
  * @returns Returns the parsed view node or an object with two fields: `view` and `selection` when selection ranges were included in the
309
307
  * data to parse.
310
308
  */
@@ -44,6 +44,7 @@ const domConverterStub = {
44
44
  /**
45
45
  * Writes the content of the {@link module:engine/view/document~Document document} to an HTML-like string.
46
46
  *
47
+ * @param view The view to stringify.
47
48
  * @param options.withoutSelection Whether to write the selection. When set to `true`, the selection will
48
49
  * not be included in the returned string.
49
50
  * @param options.rootName The name of the root from which the data should be stringified. If not provided,
@@ -52,8 +53,6 @@ const domConverterStub = {
52
53
  * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
53
54
  * @param options.showPriority When set to `true`, the attribute element's priority will be printed
54
55
  * (`<span view-priority="12">`, `<b view-priority="10">`).
55
- * @param options.showAttributeElementId When set to `true`, the attribute element's ID will be printed
56
- * (`<span id="marker:foo">`).
57
56
  * @param options.renderUIElements When set to `true`, the inner content of each
58
57
  * {@link module:engine/view/uielement~UIElement} will be printed.
59
58
  * @param options.renderRawElements When set to `true`, the inner content of each
@@ -351,7 +350,6 @@ export function stringify(node, selectionOrPositionOrRange = null, options = {})
351
350
  * this node will be used as the root for all parsed nodes.
352
351
  * @param options.sameSelectionCharacters When set to `false`, the selection inside the text should be marked using
353
352
  * `{` and `}` and the selection outside the ext using `[` and `]`. When set to `true`, both should be marked with `[` and `]` only.
354
- * @param options.stylesProcessor Styles processor.
355
353
  * @returns Returns the parsed view node or an object with two fields: `view` and `selection` when selection ranges were included in the
356
354
  * data to parse.
357
355
  */
@@ -612,7 +612,7 @@ class LiveSelection extends Selection {
612
612
  * UID obtained from the {@link module:engine/model/writer~Writer#overrideSelectionGravity} to restore.
613
613
  *
614
614
  * @error document-selection-gravity-wrong-restore
615
- * @param uid The unique identifier returned by
615
+ * @param {string} uid The unique identifier returned by
616
616
  * {@link module:engine/model/documentselection~DocumentSelection#_overrideGravity}.
617
617
  */
618
618
  throw new CKEditorError('document-selection-gravity-wrong-restore', this, { uid });
@@ -649,7 +649,7 @@ class LiveSelection extends Selection {
649
649
  * starts or ends at incorrect position.
650
650
  *
651
651
  * @error document-selection-wrong-position
652
- * @param range
652
+ * @param {module:engine/model/range~Range} range The invalid range.
653
653
  */
654
654
  throw new CKEditorError('document-selection-wrong-position', this, { range });
655
655
  }
@@ -77,7 +77,8 @@ export default class History {
77
77
  * Only operations with matching versions can be added to the history.
78
78
  *
79
79
  * @error model-document-history-addoperation-incorrect-version
80
- * @param errorData The operation and the current document history version.
80
+ * @param {module:engine/model/operation/operation~Operation} operation The current operation.
81
+ * @param {number} historyVersion The current document history version.
81
82
  */
82
83
  throw new CKEditorError('model-document-history-addoperation-incorrect-version', this, {
83
84
  operation,
@@ -121,8 +121,8 @@ export default class NodeList {
121
121
  * Given offset cannot be found in the node list.
122
122
  *
123
123
  * @error model-nodelist-offset-out-of-bounds
124
- * @param offset
125
- * @param nodeList Stringified node list.
124
+ * @param {number} offset The offset value.
125
+ * @param {module:engine/model/nodelist~NodeList} nodeList Stringified node list.
126
126
  */
127
127
  throw new CKEditorError('model-nodelist-offset-out-of-bounds', this, {
128
128
  offset,
@@ -125,9 +125,9 @@ export default class AttributeOperation extends Operation {
125
125
  * Changed node has different attribute value than operation's old attribute value.
126
126
  *
127
127
  * @error attribute-operation-wrong-old-value
128
- * @param item
129
- * @param key
130
- * @param value
128
+ * @param {module:engine/model/item~Item} root The item element.
129
+ * @param {string} key The key of the attribute.
130
+ * @param {never} value The value.
131
131
  */
132
132
  throw new CKEditorError('attribute-operation-wrong-old-value', this, { item, key: this.key, value: this.oldValue });
133
133
  }
@@ -136,8 +136,8 @@ export default class AttributeOperation extends Operation {
136
136
  * The attribute with given key already exists for the given node.
137
137
  *
138
138
  * @error attribute-operation-attribute-exists
139
- * @param node
140
- * @param key
139
+ * @param {module:engine/model/item~Item} root The item element.
140
+ * @param {string} key The key of the attribute.
141
141
  */
142
142
  throw new CKEditorError('attribute-operation-attribute-exists', this, { node: item, key: this.key });
143
143
  }
@@ -93,7 +93,7 @@ export default abstract class Operation {
93
93
  * Creates `Operation` object from deserialized object, i.e. from parsed JSON string.
94
94
  *
95
95
  * @param json Deserialized JSON object.
96
- * @param doc Document on which this operation will be applied.
96
+ * @param document Document on which this operation will be applied.
97
97
  */
98
98
  static fromJSON(json: any, document: Document): Operation;
99
99
  }
@@ -70,7 +70,7 @@ export default class Operation {
70
70
  * Creates `Operation` object from deserialized object, i.e. from parsed JSON string.
71
71
  *
72
72
  * @param json Deserialized JSON object.
73
- * @param doc Document on which this operation will be applied.
73
+ * @param document Document on which this operation will be applied.
74
74
  */
75
75
  static fromJSON(json, document) {
76
76
  return new this(json.baseVersion);
@@ -100,9 +100,8 @@ export default class RootAttributeOperation extends Operation {
100
100
  * The element to change is not a root element.
101
101
  *
102
102
  * @error rootattribute-operation-not-a-root
103
- * @param root
104
- * @param key
105
- * @param value
103
+ * @param {module:engine/model/rootelement~RootElement} root The root element.
104
+ * @param {string} key The key of the attribute.
106
105
  */
107
106
  throw new CKEditorError('rootattribute-operation-not-a-root', this, { root: this.root, key: this.key });
108
107
  }
@@ -111,9 +110,8 @@ export default class RootAttributeOperation extends Operation {
111
110
  * The attribute which should be removed does not exist for the given node.
112
111
  *
113
112
  * @error rootattribute-operation-wrong-old-value
114
- * @param root
115
- * @param key
116
- * @param value
113
+ * @param {module:engine/model/rootelement~RootElement} root The root element.
114
+ * @param {string} key The key of the attribute.
117
115
  */
118
116
  throw new CKEditorError('rootattribute-operation-wrong-old-value', this, { root: this.root, key: this.key });
119
117
  }
@@ -122,8 +120,8 @@ export default class RootAttributeOperation extends Operation {
122
120
  * The attribute with given key already exists for the given node.
123
121
  *
124
122
  * @error rootattribute-operation-attribute-exists
125
- * @param root
126
- * @param key
123
+ * @param {module:engine/model/rootelement~RootElement} root The root element.
124
+ * @param {string} key The key of the attribute.
127
125
  */
128
126
  throw new CKEditorError('rootattribute-operation-attribute-exists', this, { root: this.root, key: this.key });
129
127
  }
@@ -163,10 +161,10 @@ export default class RootAttributeOperation extends Operation {
163
161
  static fromJSON(json, document) {
164
162
  if (!document.getRoot(json.root)) {
165
163
  /**
166
- * Cannot create RootAttributeOperation for document. Root with specified name does not exist.
164
+ * Cannot create RootAttributeOperation for document. Root with the specified name does not exist.
167
165
  *
168
166
  * @error rootattribute-operation-fromjson-no-root
169
- * @param rootName
167
+ * @param {string} rootName The root name.
170
168
  */
171
169
  throw new CKEditorError('rootattribute-operation-fromjson-no-root', this, { rootName: json.root });
172
170
  }
@@ -1037,23 +1037,80 @@ setTransformation(MarkerOperation, SplitOperation, (a, b, context) => {
1037
1037
  }
1038
1038
  if (a.newRange) {
1039
1039
  if (context.abRelation) {
1040
+ // If we have context, it means that there was previously a merge operation which transformed this marker operation, and that
1041
+ // merge operation was then undone, and this split operation should reverse the marker to state before the merge operation.
1040
1042
  const aNewRange = a.newRange._getTransformedBySplitOperation(b);
1041
- if (a.newRange.start.isEqual(b.splitPosition) && context.abRelation.wasStartBeforeMergedElement) {
1042
- a.newRange.start = Position._createAt(b.insertionPosition);
1043
- }
1044
- else if (a.newRange.start.isEqual(b.splitPosition) && !context.abRelation.wasInLeftElement) {
1045
- a.newRange.start = Position._createAt(b.moveTargetPosition);
1043
+ if (a.newRange.start.isEqual(b.splitPosition)) {
1044
+ // If marker range start is same as split position, we need to decide where marker start should be placed, as there
1045
+ // are three possible options.
1046
+ //
1047
+ if (context.abRelation.wasStartBeforeMergedElement) {
1048
+ // If the marker start was initially before the merged element, move it back to that place.
1049
+ //
1050
+ // <p>Foo</p>[<p>Bar]</p> -- merge -> <p>Foo[Bar]</p> -- default split -> <p>Foo</p><p>[Bar]</p>
1051
+ // <p>Foo</p>[<p>Bar]</p> -- merge -> <p>Foo[Bar]</p> --- fixed split --> <p>Foo</p>[<p>Bar]</p>
1052
+ //
1053
+ a.newRange.start = Position._createAt(b.insertionPosition);
1054
+ }
1055
+ else if (context.abRelation.wasInLeftElement) {
1056
+ // If the marker start was initially in the "left" element, keep the start position there.
1057
+ //
1058
+ // <p>Foo[</p><p>Bar]</p> -- merge -> <p>Foo[Bar]</p> -- default split -> <p>Foo</p><p>[Bar]</p>
1059
+ // <p>Foo[</p><p>Bar]</p> -- merge -> <p>Foo[Bar]</p> --- fixed split --> <p>Foo[</p><p>Bar]</p>
1060
+ //
1061
+ a.newRange.start = Position._createAt(a.newRange.start);
1062
+ }
1063
+ else {
1064
+ // Finally, the start position must have been at the beginning of the "right" (merged) element.
1065
+ // In this case, move it back to the "right" element.
1066
+ //
1067
+ // Note, that this what the default transformation (`_getTransformedBySplitOperation()`) does, BUT ONLY for
1068
+ // a non-collapsed marker. For a collapsed marker, by default, the whole marker is kept at the split position,
1069
+ // which would be incorrect. This is why this case is needed, and why we don't use `aNewRange.start` here.
1070
+ //
1071
+ // <p>Foo</p><p>[]Bar</p> -- merge -> <p>Foo[]Bar</p> -- default split -> <p>Foo[]</p><p>Bar</p>
1072
+ // <p>Foo</p><p>[]Bar</p> -- merge -> <p>Foo[]Bar</p> --- fixed split --> <p>Foo</p><p>[]Bar</p>
1073
+ //
1074
+ a.newRange.start = Position._createAt(b.moveTargetPosition);
1075
+ }
1046
1076
  }
1047
1077
  else {
1078
+ // If marker range start is not the same as split position, simply use the default transformation, as there is no
1079
+ // ambiguity in this case.
1048
1080
  a.newRange.start = aNewRange.start;
1049
1081
  }
1050
- if (a.newRange.end.isEqual(b.splitPosition) && context.abRelation.wasInRightElement) {
1051
- a.newRange.end = Position._createAt(b.moveTargetPosition);
1052
- }
1053
- else if (a.newRange.end.isEqual(b.splitPosition) && context.abRelation.wasEndBeforeMergedElement) {
1054
- a.newRange.end = Position._createAt(b.insertionPosition);
1082
+ if (a.newRange.end.isEqual(b.splitPosition)) {
1083
+ // If marker range end is same as split position, we need to decide where marker end should be placed, as there
1084
+ // are three possible options.
1085
+ //
1086
+ if (a.newRange.end.isEqual(b.splitPosition) && context.abRelation.wasEndBeforeMergedElement) {
1087
+ // If the marker end was initially before the merged element, move it back to that place.
1088
+ //
1089
+ // <p>[Foo</p>]<p>Bar</p> -- merge -> <p>[Foo]Bar</p> -- default split -> <p>[Foo]</p><p>Bar</p>
1090
+ // <p>[Foo</p>]<p>Bar</p> -- merge -> <p>[Foo]Bar</p> --- fixed split --> <p>[Foo</p>]<p>Bar</p>
1091
+ //
1092
+ a.newRange.end = Position._createAt(b.insertionPosition);
1093
+ }
1094
+ else if (context.abRelation.wasInRightElement) {
1095
+ // If the marker was initially in the "right" element, keep the end position there.
1096
+ //
1097
+ // <p>[Foo</p><p>]Bar</p> -- merge -> <p>[Foo]Bar</p> -- default split -> <p>[Foo]</p><p>Bar</p>
1098
+ // <p>[Foo</p><p>]Bar</p> -- merge -> <p>[Foo]Bar</p> --- fixed split --> <p>[Foo</p><p>]Bar</p>
1099
+ //
1100
+ a.newRange.end = Position._createAt(b.moveTargetPosition);
1101
+ }
1102
+ else {
1103
+ // Finally, the end position must have been at the end of the "left" (merged) element.
1104
+ // In this case, keep it where it is.
1105
+ //
1106
+ // Note, that this is what the default transformation does, so we could use `aNewRange.end`, but this is more clear.
1107
+ //
1108
+ a.newRange.end = Position._createAt(a.newRange.end);
1109
+ }
1055
1110
  }
1056
1111
  else {
1112
+ // If marker range end is not the same as split position, simply use the default transformation, as there is no
1113
+ // ambiguity in this case.
1057
1114
  a.newRange.end = aNewRange.end;
1058
1115
  }
1059
1116
  return [a];
@@ -16,7 +16,7 @@ import type Position from '../position.js';
16
16
  *
17
17
  * @internal
18
18
  * @param position Position at which nodes should be inserted.
19
- * @param normalizedNodes Nodes to insert.
19
+ * @param nodes Nodes to insert.
20
20
  * @returns Range spanning over inserted elements.
21
21
  */
22
22
  export declare function _insert(position: Position, nodes: NodeSet): Range;
@@ -15,7 +15,7 @@ import { CKEditorError, isIterable } from '@ckeditor/ckeditor5-utils';
15
15
  *
16
16
  * @internal
17
17
  * @param position Position at which nodes should be inserted.
18
- * @param normalizedNodes Nodes to insert.
18
+ * @param nodes Nodes to insert.
19
19
  * @returns Range spanning over inserted elements.
20
20
  */
21
21
  export function _insert(position, nodes) {
@@ -100,7 +100,7 @@ export default class Position extends TypeCheckable {
100
100
  * Position path must be an array with at least one item.
101
101
  *
102
102
  * @error model-position-path-incorrect-format
103
- * @param path
103
+ * @param {Array.<number>} path A path to the position.
104
104
  */
105
105
  throw new CKEditorError('model-position-path-incorrect-format', root, { path });
106
106
  }
@@ -155,7 +155,7 @@ export default class Position extends TypeCheckable {
155
155
  * the {@glink framework/architecture/editing-engine#indexes-and-offsets Editing engine architecture} guide.
156
156
  *
157
157
  * @error model-position-path-incorrect
158
- * @param position The incorrect position.
158
+ * @param {module:engine/model/position~Position} position The incorrect position.
159
159
  */
160
160
  throw new CKEditorError('model-position-path-incorrect', this, { position: this });
161
161
  }
@@ -823,10 +823,10 @@ export default class Position extends TypeCheckable {
823
823
  static _createAfter(item, stickiness) {
824
824
  if (!item.parent) {
825
825
  /**
826
- * You can not make a position after a root element.
826
+ * You cannot make a position after a root element.
827
827
  *
828
828
  * @error model-position-after-root
829
- * @param root
829
+ * @param {module:engine/model/rootelement~RootElement} root The root element..
830
830
  */
831
831
  throw new CKEditorError('model-position-after-root', [this, item], { root: item });
832
832
  }
@@ -842,10 +842,10 @@ export default class Position extends TypeCheckable {
842
842
  static _createBefore(item, stickiness) {
843
843
  if (!item.parent) {
844
844
  /**
845
- * You can not make a position before a root element.
845
+ * You cannot make a position before a root element.
846
846
  *
847
847
  * @error model-position-before-root
848
- * @param root
848
+ * @param {module:engine/model/rootelement~RootElement} root The root element..
849
849
  */
850
850
  throw new CKEditorError('model-position-before-root', item, { root: item });
851
851
  }
@@ -869,7 +869,7 @@ export default class Position extends TypeCheckable {
869
869
  * Cannot create position for document. Root with specified name does not exist.
870
870
  *
871
871
  * @error model-position-fromjson-no-root
872
- * @param rootName
872
+ * @param {string} rootName The root name.
873
873
  */
874
874
  throw new CKEditorError('model-position-fromjson-no-root', doc, { rootName: json.root });
875
875
  }
@@ -400,7 +400,7 @@ export default class Range extends TypeCheckable implements Iterable<TreeWalkerV
400
400
  * If the deleted range contains transformed range, `null` will be returned.
401
401
  *
402
402
  * @internal
403
- * @param deletionPosition Position from which nodes are removed.
403
+ * @param deletePosition Position from which nodes are removed.
404
404
  * @param howMany How many nodes are removed.
405
405
  * @returns Result of the transformation.
406
406
  */
@@ -744,7 +744,7 @@ export default class Range extends TypeCheckable {
744
744
  * If the deleted range contains transformed range, `null` will be returned.
745
745
  *
746
746
  * @internal
747
- * @param deletionPosition Position from which nodes are removed.
747
+ * @param deletePosition Position from which nodes are removed.
748
748
  * @param howMany How many nodes are removed.
749
749
  * @returns Result of the transformation.
750
750
  */
@@ -635,8 +635,8 @@ export default class Selection extends /* #__PURE__ */ EmitterMixin(TypeCheckabl
635
635
  * Trying to add a range that intersects with another range in the selection.
636
636
  *
637
637
  * @error model-selection-range-intersects
638
- * @param addedRange Range that was added to the selection.
639
- * @param intersectingRange Range in the selection that intersects with `addedRange`.
638
+ * @param {module:engine/model/range~Range} addedRange Range that was added to the selection.
639
+ * @param {module:engine/model/range~Range} intersectingRange Range in the selection that intersects with `addedRange`.
640
640
  */
641
641
  throw new CKEditorError('model-selection-range-intersects', [this, range], { addedRange: range, intersectingRange: this._ranges[i] });
642
642
  }
@@ -50,9 +50,22 @@ import type Selection from '../selection.js';
50
50
  * **Note:** If there is no valid position for the selection, the paragraph will always be created:
51
51
  *
52
52
  * `[<imageBlock src="foo.jpg"></imageBlock>]` -> `<paragraph>[]</paragraph>`.
53
+ *
54
+ * @param options.doNotFixSelection Whether given selection-to-remove should be fixed if it ends at the beginning of an element.
55
+ *
56
+ * By default, `deleteContent()` will fix selection before performing a deletion, so that the selection does not end at the beginning of
57
+ * an element. For example, selection `<heading>[Heading</heading><paragraph>]Some text.</paragraph>` will be treated as it was
58
+ * `<heading>[Heading]</heading><paragraph>Some text.</paragraph>`. As a result, the elements will not get merged.
59
+ *
60
+ * If selection is as in example, visually, the next element (paragraph) is not selected and it may be confusing for the user that
61
+ * the elements got merged. Selection is set up like this by browsers when a user triple-clicks on some text.
62
+ *
63
+ * However, in some cases, it is expected to remove content exactly as selected in the selection, without any fixing. In these cases,
64
+ * this flag can be set to `true`, which will prevent fixing the selection.
53
65
  */
54
66
  export default function deleteContent(model: Model, selection: Selection | DocumentSelection, options?: {
55
67
  leaveUnmerged?: boolean;
56
68
  doNotResetEntireContent?: boolean;
57
69
  doNotAutoparagraph?: boolean;
70
+ doNotFixSelection?: boolean;
58
71
  }): void;
@@ -50,6 +50,18 @@ import Range from '../range.js';
50
50
  * **Note:** If there is no valid position for the selection, the paragraph will always be created:
51
51
  *
52
52
  * `[<imageBlock src="foo.jpg"></imageBlock>]` -> `<paragraph>[]</paragraph>`.
53
+ *
54
+ * @param options.doNotFixSelection Whether given selection-to-remove should be fixed if it ends at the beginning of an element.
55
+ *
56
+ * By default, `deleteContent()` will fix selection before performing a deletion, so that the selection does not end at the beginning of
57
+ * an element. For example, selection `<heading>[Heading</heading><paragraph>]Some text.</paragraph>` will be treated as it was
58
+ * `<heading>[Heading]</heading><paragraph>Some text.</paragraph>`. As a result, the elements will not get merged.
59
+ *
60
+ * If selection is as in example, visually, the next element (paragraph) is not selected and it may be confusing for the user that
61
+ * the elements got merged. Selection is set up like this by browsers when a user triple-clicks on some text.
62
+ *
63
+ * However, in some cases, it is expected to remove content exactly as selected in the selection, without any fixing. In these cases,
64
+ * this flag can be set to `true`, which will prevent fixing the selection.
53
65
  */
54
66
  export default function deleteContent(model, selection, options = {}) {
55
67
  if (selection.isCollapsed) {
@@ -77,7 +89,14 @@ export default function deleteContent(model, selection, options = {}) {
77
89
  }
78
90
  }
79
91
  // Get the live positions for the range adjusted to span only blocks selected from the user perspective.
80
- const [startPosition, endPosition] = getLivePositionsForSelectedBlocks(selRange);
92
+ let startPosition, endPosition;
93
+ if (!options.doNotFixSelection) {
94
+ [startPosition, endPosition] = getLivePositionsForSelectedBlocks(selRange);
95
+ }
96
+ else {
97
+ startPosition = LivePosition.fromPosition(selRange.start, 'toPrevious');
98
+ endPosition = LivePosition.fromPosition(selRange.end, 'toNext');
99
+ }
81
100
  // 2. Remove the content if there is any.
82
101
  if (!startPosition.isTouching(endPosition)) {
83
102
  writer.remove(writer.createRange(startPosition, endPosition));
@@ -38,7 +38,6 @@ import type Selection from '../selection.js';
38
38
  * @param model The model in context of which the insertion should be performed.
39
39
  * @param content The content to insert.
40
40
  * @param selectable Selection into which the content should be inserted.
41
- * @param placeOrOffset Sets place or offset of the selection.
42
41
  * @returns Range which contains all the performed changes. This is a range that, if removed,
43
42
  * would return the model to the state before the insertion. If no changes were preformed by `insertContent`, returns a range collapsed
44
43
  * at the insertion position.