@ckeditor/ckeditor5-engine 45.1.0-alpha.7 → 45.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-engine",
3
- "version": "45.1.0-alpha.7",
3
+ "version": "45.2.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.1.0-alpha.7",
27
+ "@ckeditor/ckeditor5-utils": "45.2.0-alpha.0",
28
28
  "es-toolkit": "1.32.0"
29
29
  },
30
30
  "author": "CKSource (http://cksource.com/)",
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/dataprocessor/htmldataprocessor
7
7
  */
8
- /* globals DOMParser */
9
8
  import BasicHtmlWriter from './basichtmlwriter.js';
10
9
  import DomConverter from '../view/domconverter.js';
11
10
  /**
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/dataprocessor/xmldataprocessor
7
7
  */
8
- /* globals DOMParser */
9
8
  import BasicHtmlWriter from './basichtmlwriter.js';
10
9
  import DomConverter from '../view/domconverter.js';
11
10
  /**
@@ -378,7 +378,7 @@ function parseAttributeValue(attribute) {
378
378
  try {
379
379
  return JSON.parse(attribute);
380
380
  }
381
- catch (e) {
381
+ catch {
382
382
  return attribute;
383
383
  }
384
384
  }
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/dev-utils/operationreplayer
7
7
  */
8
- /* global setTimeout */
9
8
  import OperationFactory from '../model/operation/operationfactory.js';
10
9
  /**
11
10
  * Operation replayer is a development tool created for easy replaying of operations on the document from stringified operations.
@@ -9,7 +9,6 @@
9
9
  *
10
10
  * @module engine/dev-utils/utils
11
11
  */
12
- /* globals console */
13
12
  // @if CK_DEBUG_TYPING // const { debounce } = require( 'es-toolkit/compat' );
14
13
  /**
15
14
  * Helper function, converts a map to the 'key1="value1" key2="value1"' format.
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/dev-utils/view
7
7
  */
8
- /* globals document */
9
8
  /**
10
9
  * Collection of methods for manipulating the {@link module:engine/view/view view} for testing purposes.
11
10
  */
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/view/domconverter
7
7
  */
8
- /* globals Node, NodeFilter, DOMParser */
9
8
  import ViewText from './text.js';
10
9
  import ViewElement from './element.js';
11
10
  import ViewUIElement from './uielement.js';
@@ -955,7 +954,7 @@ export default class DomConverter {
955
954
  range.setStart(selection.anchorNode, selection.anchorOffset);
956
955
  range.setEnd(selection.focusNode, selection.focusOffset);
957
956
  }
958
- catch (e) {
957
+ catch {
959
958
  // Safari sometimes gives us a selection that makes Range.set{Start,End} throw.
960
959
  // See https://github.com/ckeditor/ckeditor5/issues/12375.
961
960
  return false;
@@ -1598,7 +1597,7 @@ function isGeckoRestrictedDomSelection(domSelection) {
1598
1597
  try {
1599
1598
  Object.prototype.toString.call(container);
1600
1599
  }
1601
- catch (error) {
1600
+ catch {
1602
1601
  return true;
1603
1602
  }
1604
1603
  return false;
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/view/observer/focusobserver
7
7
  */
8
- /* globals setTimeout, clearTimeout */
9
8
  import DomEventObserver from './domeventobserver.js';
10
9
  // @if CK_DEBUG_TYPING // const { _debouncedLine, _buildLogMessage } = require( '../../dev-utils/utils.js' );
11
10
  /**
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import DomEventObserver from './domeventobserver.js';
9
9
  import DataTransfer from '../datatransfer.js';
10
- import { env } from '@ckeditor/ckeditor5-utils';
10
+ import { env, isText, indexOf } from '@ckeditor/ckeditor5-utils';
11
11
  import { INLINE_FILLER_LENGTH, startsWithFiller } from '../filler.js';
12
12
  // @if CK_DEBUG_TYPING // const { _debouncedLine, _buildLogMessage } = require( '../../dev-utils/utils.js' );
13
13
  /**
@@ -90,7 +90,8 @@ export default class InputObserver extends DomEventObserver {
90
90
  if (viewStart && startsWithFiller(domRange.startContainer) && domRange.startOffset < INLINE_FILLER_LENGTH) {
91
91
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
92
92
  // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'InputObserver',
93
- // @if CK_DEBUG_TYPING // 'Target range starts in an inline filler - adjusting it',
93
+ // @if CK_DEBUG_TYPING // '%cTarget range starts in an inline filler - adjusting it',
94
+ // @if CK_DEBUG_TYPING // 'font-style: italic'
94
95
  // @if CK_DEBUG_TYPING // ) );
95
96
  // @if CK_DEBUG_TYPING // }
96
97
  domEvent.preventDefault();
@@ -107,6 +108,16 @@ export default class InputObserver extends DomEventObserver {
107
108
  return false;
108
109
  }, { direction: 'backward', singleCharacters: true });
109
110
  }
111
+ // Check if there is no an inline filler just after the target range.
112
+ if (isFollowedByInlineFiller(domRange.endContainer, domRange.endOffset)) {
113
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
114
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'InputObserver',
115
+ // @if CK_DEBUG_TYPING // '%cTarget range ends just before an inline filler - prevent default behavior',
116
+ // @if CK_DEBUG_TYPING // 'font-style: italic'
117
+ // @if CK_DEBUG_TYPING // ) );
118
+ // @if CK_DEBUG_TYPING // }
119
+ domEvent.preventDefault();
120
+ }
110
121
  if (viewStart) {
111
122
  return view.createRange(viewStart, viewEnd);
112
123
  }
@@ -157,6 +168,8 @@ export default class InputObserver extends DomEventObserver {
157
168
  // it to paragraphs as it is our default action for enter handling.
158
169
  const parts = data.split(/\n{1,2}/g);
159
170
  let partTargetRanges = targetRanges;
171
+ // Handle all parts on our side as we rely on paragraph inserting and synchronously updated view selection.
172
+ domEvent.preventDefault();
160
173
  for (let i = 0; i < parts.length; i++) {
161
174
  const dataPart = parts[i];
162
175
  if (dataPart != '') {
@@ -197,3 +210,27 @@ export default class InputObserver extends DomEventObserver {
197
210
  // @if CK_DEBUG_TYPING // }
198
211
  }
199
212
  }
213
+ /**
214
+ * Returns `true` if there is an inline filler just after the position in DOM.
215
+ * It walks up the DOM tree if the offset is at the end of the node.
216
+ */
217
+ function isFollowedByInlineFiller(node, offset) {
218
+ while (node.parentNode) {
219
+ if (isText(node)) {
220
+ if (offset != node.data.length) {
221
+ return false;
222
+ }
223
+ }
224
+ else {
225
+ if (offset != node.childNodes.length) {
226
+ return false;
227
+ }
228
+ }
229
+ offset = indexOf(node) + 1;
230
+ node = node.parentNode;
231
+ if (offset < node.childNodes.length && startsWithFiller(node.childNodes[offset])) {
232
+ return true;
233
+ }
234
+ }
235
+ return false;
236
+ }
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/view/observer/mutationobserver
7
7
  */
8
- /* globals window */
9
8
  import Observer from './observer.js';
10
9
  import { startsWithFiller } from '../filler.js';
11
10
  import { isEqualWith } from 'es-toolkit/compat';
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module engine/view/observer/selectionobserver
7
7
  */
8
- /* global setInterval, clearInterval */
9
8
  import Observer from './observer.js';
10
9
  import MutationObserver from './mutationobserver.js';
11
10
  import FocusObserver from './focusobserver.js';
@@ -237,8 +236,9 @@ export default class SelectionObserver extends Observer {
237
236
  this.view.hasDomSelection = true;
238
237
  // Mark the latest focus change as complete (we got new selection after the focus so the selection is in the focused element).
239
238
  this.focusObserver.flush();
240
- // Ignore selection change as the editable is not focused.
241
- if (!this.view.document.isFocused) {
239
+ // Ignore selection change as the editable is not focused. Note that in read-only mode, we have to update
240
+ // the model selection as there won't be any focus change to flush the pending selection changes.
241
+ if (!this.view.document.isFocused && !this.view.document.isReadOnly) {
242
242
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
243
243
  // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'SelectionObserver',
244
244
  // @if CK_DEBUG_TYPING // 'Ignore selection change while editable is not focused'
@@ -240,13 +240,13 @@ export default class Renderer extends /* #__PURE__ */ Renderer_base {
240
240
  /**
241
241
  * Updates the fake selection.
242
242
  *
243
- * @param domRoot A valid DOM root where the fake selection container should be added.
243
+ * @param domEditable A valid DOM editable where the fake selection container should be added.
244
244
  */
245
245
  private _updateFakeSelection;
246
246
  /**
247
247
  * Updates the DOM selection.
248
248
  *
249
- * @param domRoot A valid DOM root where the DOM selection should be rendered.
249
+ * @param domEditable A valid DOM editable where the DOM selection should be rendered.
250
250
  */
251
251
  private _updateDomSelection;
252
252
  /**
@@ -258,7 +258,7 @@ export default class Renderer extends /* #__PURE__ */ Renderer_base {
258
258
  /**
259
259
  * Checks whether the fake selection needs to be updated.
260
260
  *
261
- * @param domRoot A valid DOM root where a new fake selection container should be added.
261
+ * @param domEditable A valid DOM editable where a new fake selection container should be added.
262
262
  */
263
263
  private _fakeSelectionNeedsUpdate;
264
264
  /**
@@ -797,15 +797,25 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
797
797
  this._removeFakeSelection();
798
798
  return;
799
799
  }
800
- const domRoot = this.domConverter.mapViewToDom(this.selection.editableElement);
801
- // Do nothing if there is no focus, or there is no DOM element corresponding to selection's editable element.
802
- if (!this.isFocused || !domRoot) {
800
+ const domEditable = this.domConverter.mapViewToDom(this.selection.editableElement);
801
+ // Do not update DOM selection if there is no focus, or there is no DOM element corresponding to selection's editable element.
802
+ if (!this.isFocused || !domEditable) {
803
803
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
804
804
  // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
805
805
  // @if CK_DEBUG_TYPING // 'Skip updating DOM selection:',
806
- // @if CK_DEBUG_TYPING // `isFocused: ${ this.isFocused }, hasDomRoot: ${ !!domRoot }`
806
+ // @if CK_DEBUG_TYPING // `isFocused: ${ this.isFocused }, hasDomEditable: ${ !!domEditable }`
807
807
  // @if CK_DEBUG_TYPING // ) );
808
808
  // @if CK_DEBUG_TYPING // }
809
+ // But if there was a fake selection, and it is not fake anymore - remove it as it can map to no longer existing widget.
810
+ // See https://github.com/ckeditor/ckeditor5/issues/18123.
811
+ if (!this.selection.isFake && this._fakeSelectionContainer && this._fakeSelectionContainer.isConnected) {
812
+ // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
813
+ // @if CK_DEBUG_TYPING // console.info( ..._buildLogMessage( this, 'Renderer',
814
+ // @if CK_DEBUG_TYPING // 'Remove fake selection (not focused editable)'
815
+ // @if CK_DEBUG_TYPING // ) );
816
+ // @if CK_DEBUG_TYPING // }
817
+ this._removeFakeSelection();
818
+ }
809
819
  return;
810
820
  }
811
821
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
@@ -815,40 +825,40 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
815
825
  // @if CK_DEBUG_TYPING // }
816
826
  // Render fake selection - create the fake selection container (if needed) and move DOM selection to it.
817
827
  if (this.selection.isFake) {
818
- this._updateFakeSelection(domRoot);
828
+ this._updateFakeSelection(domEditable);
819
829
  }
820
830
  // There was a fake selection so remove it and update the DOM selection.
821
831
  // This is especially important on Android because otherwise IME will try to compose over the fake selection container.
822
832
  else if (this._fakeSelectionContainer && this._fakeSelectionContainer.isConnected) {
823
833
  this._removeFakeSelection();
824
- this._updateDomSelection(domRoot);
834
+ this._updateDomSelection(domEditable);
825
835
  }
826
836
  // Update the DOM selection in case of a plain selection change (no fake selection is involved).
827
837
  // On non-Android the whole rendering is disabled in composition mode (including DOM selection update),
828
838
  // but updating DOM selection should be also disabled on Android if in the middle of the composition
829
839
  // (to not interrupt it).
830
840
  else if (!(this.isComposing && env.isAndroid)) {
831
- this._updateDomSelection(domRoot);
841
+ this._updateDomSelection(domEditable);
832
842
  }
833
843
  }
834
844
  /**
835
845
  * Updates the fake selection.
836
846
  *
837
- * @param domRoot A valid DOM root where the fake selection container should be added.
847
+ * @param domEditable A valid DOM editable where the fake selection container should be added.
838
848
  */
839
- _updateFakeSelection(domRoot) {
840
- const domDocument = domRoot.ownerDocument;
849
+ _updateFakeSelection(domEditable) {
850
+ const domDocument = domEditable.ownerDocument;
841
851
  if (!this._fakeSelectionContainer) {
842
852
  this._fakeSelectionContainer = createFakeSelectionContainer(domDocument);
843
853
  }
844
854
  const container = this._fakeSelectionContainer;
845
855
  // Bind fake selection container with the current selection *position*.
846
856
  this.domConverter.bindFakeSelection(container, this.selection);
847
- if (!this._fakeSelectionNeedsUpdate(domRoot)) {
857
+ if (!this._fakeSelectionNeedsUpdate(domEditable)) {
848
858
  return;
849
859
  }
850
- if (!container.parentElement || container.parentElement != domRoot) {
851
- domRoot.appendChild(container);
860
+ if (!container.parentElement || container.parentElement != domEditable) {
861
+ domEditable.appendChild(container);
852
862
  }
853
863
  container.textContent = this.selection.fakeSelectionLabel || '\u00A0';
854
864
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
@@ -865,10 +875,10 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
865
875
  /**
866
876
  * Updates the DOM selection.
867
877
  *
868
- * @param domRoot A valid DOM root where the DOM selection should be rendered.
878
+ * @param domEditable A valid DOM editable where the DOM selection should be rendered.
869
879
  */
870
- _updateDomSelection(domRoot) {
871
- const domSelection = domRoot.ownerDocument.defaultView.getSelection();
880
+ _updateDomSelection(domEditable) {
881
+ const domSelection = domEditable.ownerDocument.defaultView.getSelection();
872
882
  // Let's check whether DOM selection needs updating at all.
873
883
  if (!this._domSelectionNeedsUpdate(domSelection)) {
874
884
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
@@ -924,14 +934,14 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
924
934
  /**
925
935
  * Checks whether the fake selection needs to be updated.
926
936
  *
927
- * @param domRoot A valid DOM root where a new fake selection container should be added.
937
+ * @param domEditable A valid DOM editable where a new fake selection container should be added.
928
938
  */
929
- _fakeSelectionNeedsUpdate(domRoot) {
939
+ _fakeSelectionNeedsUpdate(domEditable) {
930
940
  const container = this._fakeSelectionContainer;
931
- const domSelection = domRoot.ownerDocument.getSelection();
941
+ const domSelection = domEditable.ownerDocument.getSelection();
932
942
  // Fake selection needs to be updated if there's no fake selection container, or the container currently sits
933
943
  // in a different root.
934
- if (!container || container.parentElement !== domRoot) {
944
+ if (!container || container.parentElement !== domEditable) {
935
945
  return true;
936
946
  }
937
947
  // Make sure that the selection actually is within the fake selection.