@ckeditor/ckeditor5-engine 42.0.2-alpha.2 → 43.0.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 (38) hide show
  1. package/CHANGELOG.md +1 -820
  2. package/dist/dev-utils/model.d.ts +2 -0
  3. package/dist/dev-utils/view.d.ts +1 -0
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.js +466 -271
  6. package/dist/index.js.map +1 -1
  7. package/dist/model/schema.d.ts +149 -51
  8. package/dist/view/observer/focusobserver.d.ts +12 -0
  9. package/dist/view/observer/mutationobserver.d.ts +34 -5
  10. package/dist/view/observer/selectionobserver.d.ts +1 -2
  11. package/dist/view/renderer.d.ts +12 -0
  12. package/dist/view/view.d.ts +1 -4
  13. package/package.json +2 -2
  14. package/src/conversion/upcasthelpers.js +0 -7
  15. package/src/dev-utils/model.d.ts +2 -0
  16. package/src/dev-utils/model.js +4 -2
  17. package/src/dev-utils/utils.js +7 -0
  18. package/src/dev-utils/view.d.ts +1 -0
  19. package/src/dev-utils/view.js +3 -0
  20. package/src/index.d.ts +3 -1
  21. package/src/index.js +2 -0
  22. package/src/model/model.js +1 -5
  23. package/src/model/schema.d.ts +149 -51
  24. package/src/model/schema.js +200 -70
  25. package/src/model/utils/insertcontent.js +21 -65
  26. package/src/view/domconverter.js +13 -9
  27. package/src/view/observer/compositionobserver.js +2 -0
  28. package/src/view/observer/focusobserver.d.ts +12 -0
  29. package/src/view/observer/focusobserver.js +55 -25
  30. package/src/view/observer/inputobserver.js +7 -5
  31. package/src/view/observer/mutationobserver.d.ts +34 -5
  32. package/src/view/observer/mutationobserver.js +8 -11
  33. package/src/view/observer/selectionobserver.d.ts +1 -2
  34. package/src/view/observer/selectionobserver.js +27 -9
  35. package/src/view/renderer.d.ts +12 -0
  36. package/src/view/renderer.js +111 -63
  37. package/src/view/view.d.ts +1 -4
  38. package/src/view/view.js +9 -0
@@ -35,6 +35,24 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
35
35
  * A dictionary containing attribute properties.
36
36
  */
37
37
  this._attributeProperties = {};
38
+ /**
39
+ * Stores additional callbacks registered for schema items, which are evaluated when {@link ~Schema#checkChild} is called.
40
+ *
41
+ * Keys are schema item names for which the callbacks are registered. Values are arrays with the callbacks.
42
+ *
43
+ * Some checks are added under {@link ~Schema#_genericCheckSymbol} key, these are evaluated for every {@link ~Schema#checkChild} call.
44
+ */
45
+ this._customChildChecks = new Map();
46
+ /**
47
+ * Stores additional callbacks registered for attribute names, which are evaluated when {@link ~Schema#checkAttribute} is called.
48
+ *
49
+ * Keys are schema attribute names for which the callbacks are registered. Values are arrays with the callbacks.
50
+ *
51
+ * Some checks are added under {@link ~Schema#_genericCheckSymbol} key, these are evaluated for every
52
+ * {@link ~Schema#checkAttribute} call.
53
+ */
54
+ this._customAttributeChecks = new Map();
55
+ this._genericCheckSymbol = Symbol('$generic');
38
56
  this.decorate('checkChild');
39
57
  this.decorate('checkAttribute');
40
58
  this.on('checkAttribute', (evt, args) => {
@@ -314,7 +332,7 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
314
332
  return !!(def.isContent || def.isObject);
315
333
  }
316
334
  /**
317
- * Checks whether the given node (`child`) can be a child of the given context.
335
+ * Checks whether the given node can be a child of the given context.
318
336
  *
319
337
  * ```ts
320
338
  * schema.checkChild( model.document.getRoot(), paragraph ); // -> false
@@ -322,28 +340,34 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
322
340
  * schema.register( 'paragraph', {
323
341
  * allowIn: '$root'
324
342
  * } );
343
+ *
325
344
  * schema.checkChild( model.document.getRoot(), paragraph ); // -> true
326
345
  * ```
327
346
  *
328
- * Note: When verifying whether the given node can be a child of the given context, the
329
- * schema also verifies the entire context – from its root to its last element. Therefore, it is possible
330
- * for `checkChild()` to return `false` even though the context's last element can contain the checked child.
331
- * It happens if one of the context's elements does not allow its child.
347
+ * Both {@link module:engine/model/schema~Schema#addChildCheck callback checks} and declarative rules (added when
348
+ * {@link module:engine/model/schema~Schema#register registering} and {@link module:engine/model/schema~Schema#extend extending} items)
349
+ * are evaluated when this method is called.
350
+ *
351
+ * Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
352
+ *
353
+ * Note that when verifying whether the given node can be a child of the given context, the schema also verifies the entire
354
+ * context – from its root to its last element. Therefore, it is possible for `checkChild()` to return `false` even though
355
+ * the `context` last element can contain the checked child. It happens if one of the `context` elements does not allow its child.
356
+ * When `context` is verified, {@link module:engine/model/schema~Schema#addChildCheck custom checks} are considered as well.
332
357
  *
333
358
  * @fires checkChild
334
359
  * @param context The context in which the child will be checked.
335
360
  * @param def The child to check.
336
361
  */
337
362
  checkChild(context, def) {
338
- // Note: context and child are already normalized here to a SchemaContext and SchemaCompiledItemDefinition.
363
+ // Note: `context` and `def` are already normalized here to `SchemaContext` and `SchemaCompiledItemDefinition`.
339
364
  if (!def) {
340
365
  return false;
341
366
  }
342
- return this._checkContextMatch(def, context);
367
+ return this._checkContextMatch(context, def);
343
368
  }
344
369
  /**
345
- * Checks whether the given attribute can be applied in the given context (on the last
346
- * item of the context).
370
+ * Checks whether the given attribute can be applied in the given context (on the last item of the context).
347
371
  *
348
372
  * ```ts
349
373
  * schema.checkAttribute( textNode, 'bold' ); // -> false
@@ -351,23 +375,37 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
351
375
  * schema.extend( '$text', {
352
376
  * allowAttributes: 'bold'
353
377
  * } );
378
+ *
354
379
  * schema.checkAttribute( textNode, 'bold' ); // -> true
355
380
  * ```
356
381
  *
382
+ * Both {@link module:engine/model/schema~Schema#addAttributeCheck callback checks} and declarative rules (added when
383
+ * {@link module:engine/model/schema~Schema#register registering} and {@link module:engine/model/schema~Schema#extend extending} items)
384
+ * are evaluated when this method is called.
385
+ *
386
+ * Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
387
+ *
357
388
  * @fires checkAttribute
358
389
  * @param context The context in which the attribute will be checked.
390
+ * @param attributeName Name of attribute to check in the given context.
359
391
  */
360
392
  checkAttribute(context, attributeName) {
393
+ // Note: `context` is already normalized here to `SchemaContext`.
361
394
  const def = this.getDefinition(context.last);
362
395
  if (!def) {
363
396
  return false;
364
397
  }
365
- return def.allowAttributes.includes(attributeName);
398
+ // First, check all attribute checks declared as callbacks.
399
+ // Note that `_evaluateAttributeChecks()` will return `undefined` if neither child check was applicable (no decision was made).
400
+ const isAllowed = this._evaluateAttributeChecks(context, attributeName);
401
+ // If the decision was not made inside attribute check callbacks, then use declarative rules.
402
+ return isAllowed !== undefined ? isAllowed : def.allowAttributes.includes(attributeName);
366
403
  }
367
404
  /**
368
405
  * Checks whether the given element (`elementToMerge`) can be merged with the specified base element (`positionOrBaseElement`).
369
406
  *
370
- * In other words – whether `elementToMerge`'s children {@link #checkChild are allowed} in the `positionOrBaseElement`.
407
+ * In other words – both elements are not a limit elements and whether `elementToMerge`'s children
408
+ * {@link #checkChild are allowed} in the `positionOrBaseElement`.
371
409
  *
372
410
  * This check ensures that elements merged with {@link module:engine/model/writer~Writer#merge `Writer#merge()`}
373
411
  * will be valid.
@@ -400,6 +438,9 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
400
438
  }
401
439
  return this.checkMerge(nodeBefore, nodeAfter);
402
440
  }
441
+ if (this.isLimit(positionOrBaseElement) || this.isLimit(elementToMerge)) {
442
+ return false;
443
+ }
403
444
  for (const child of elementToMerge.getChildren()) {
404
445
  if (!this.checkChild(positionOrBaseElement, child)) {
405
446
  return false;
@@ -412,110 +453,139 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
412
453
  *
413
454
  * Callbacks allow you to implement rules which are not otherwise possible to achieve
414
455
  * by using the declarative API of {@link module:engine/model/schema~SchemaItemDefinition}.
415
- * For example, by using this method you can disallow elements in specific contexts.
416
456
  *
417
- * This method is a shorthand for using the {@link #event:checkChild} event. For even better control,
418
- * you can use that event instead.
457
+ * Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
419
458
  *
420
- * Example:
459
+ * For example, by using this method you can disallow elements in specific contexts:
421
460
  *
422
461
  * ```ts
423
- * // Disallow heading1 directly inside a blockQuote.
462
+ * // Disallow `heading1` inside a `blockQuote` that is inside a table.
424
463
  * schema.addChildCheck( ( context, childDefinition ) => {
425
- * if ( context.endsWith( 'blockQuote' ) && childDefinition.name == 'heading1' ) {
464
+ * if ( context.endsWith( 'tableCell blockQuote' ) ) {
426
465
  * return false;
427
466
  * }
467
+ * }, 'heading1' );
468
+ * ```
469
+ *
470
+ * You can skip the optional `itemName` parameter to evaluate the callback for every `checkChild()` call.
471
+ *
472
+ * ```ts
473
+ * // Inside specific custom element, allow only children, which allows for a specific attribute.
474
+ * schema.addChildCheck( ( context, childDefinition ) => {
475
+ * if ( context.endsWith( 'myElement' ) ) {
476
+ * return childDefinition.allowAttributes.includes( 'myAttribute' );
477
+ * }
428
478
  * } );
429
479
  * ```
430
480
  *
431
- * Which translates to:
481
+ * Please note that the generic callbacks may affect the editor performance and should be avoided if possible.
482
+ *
483
+ * When one of the callbacks makes a decision (returns `true` or `false`) the processing is finished and other callbacks are not fired.
484
+ * Callbacks are fired in the order they were added, however generic callbacks are fired before callbacks added for a specified item.
485
+ *
486
+ * You can also use `checkChild` event, if you need even better control. The result from the example above could also be
487
+ * achieved with following event callback:
432
488
  *
433
489
  * ```ts
434
490
  * schema.on( 'checkChild', ( evt, args ) => {
435
491
  * const context = args[ 0 ];
436
492
  * const childDefinition = args[ 1 ];
437
493
  *
438
- * if ( context.endsWith( 'blockQuote' ) && childDefinition && childDefinition.name == 'heading1' ) {
494
+ * if ( context.endsWith( 'myElement' ) ) {
439
495
  * // Prevent next listeners from being called.
440
496
  * evt.stop();
441
- * // Set the checkChild()'s return value.
442
- * evt.return = false;
497
+ * // Set the `checkChild()` return value.
498
+ * evt.return = childDefinition.allowAttributes.includes( 'myAttribute' );
443
499
  * }
444
500
  * }, { priority: 'high' } );
445
501
  * ```
446
502
  *
503
+ * Note that the callback checks and declarative rules checks are processed on `normal` priority.
504
+ *
505
+ * Adding callbacks this way can also negatively impact editor performance.
506
+ *
447
507
  * @param callback The callback to be called. It is called with two parameters:
448
508
  * {@link module:engine/model/schema~SchemaContext} (context) instance and
449
- * {@link module:engine/model/schema~SchemaCompiledItemDefinition} (child-to-check definition).
450
- * The callback may return `true/false` to override `checkChild()`'s return value. If it does not return
451
- * a boolean value, the default algorithm (or other callbacks) will define `checkChild()`'s return value.
509
+ * {@link module:engine/model/schema~SchemaCompiledItemDefinition} (definition). The callback may return `true/false` to override
510
+ * `checkChild()`'s return value. If it does not return a boolean value, the default algorithm (or other callbacks) will define
511
+ * `checkChild()`'s return value.
512
+ * @param itemName Name of the schema item for which the callback is registered. If specified, the callback will be run only for
513
+ * `checkChild()` calls which `def` parameter matches the `itemName`. Otherwise, the callback will run for every `checkChild` call.
452
514
  */
453
- addChildCheck(callback) {
454
- this.on('checkChild', (evt, [ctx, childDef]) => {
455
- // checkChild() was called with a non-registered child.
456
- // In 99% cases such check should return false, so not to overcomplicate all callbacks
457
- // don't even execute them.
458
- if (!childDef) {
459
- return;
460
- }
461
- const retValue = callback(ctx, childDef);
462
- if (typeof retValue == 'boolean') {
463
- evt.stop();
464
- evt.return = retValue;
465
- }
466
- }, { priority: 'high' });
515
+ addChildCheck(callback, itemName) {
516
+ const key = itemName !== undefined ? itemName : this._genericCheckSymbol;
517
+ const checks = this._customChildChecks.get(key) || [];
518
+ checks.push(callback);
519
+ this._customChildChecks.set(key, checks);
467
520
  }
468
521
  /**
469
522
  * Allows registering a callback to the {@link #checkAttribute} method calls.
470
523
  *
471
524
  * Callbacks allow you to implement rules which are not otherwise possible to achieve
472
525
  * by using the declarative API of {@link module:engine/model/schema~SchemaItemDefinition}.
473
- * For example, by using this method you can disallow attribute if node to which it is applied
474
- * is contained within some other element (e.g. you want to disallow `bold` on `$text` within `heading1`).
475
526
  *
476
- * This method is a shorthand for using the {@link #event:checkAttribute} event. For even better control,
477
- * you can use that event instead.
527
+ * Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
528
+ *
529
+ * For example, by using this method you can disallow setting attributes on nodes in specific contexts:
530
+ *
531
+ * ```ts
532
+ * // Disallow setting `bold` on text inside `heading1` element:
533
+ * schema.addAttributeCheck( context => {
534
+ * if ( context.endsWith( 'heading1 $text' ) ) {
535
+ * return false;
536
+ * }
537
+ * }, 'bold' );
538
+ * ```
478
539
  *
479
- * Example:
540
+ * You can skip the optional `attributeName` parameter to evaluate the callback for every `checkAttribute()` call.
480
541
  *
481
542
  * ```ts
482
- * // Disallow bold on $text inside heading1.
543
+ * // Disallow formatting attributes on text inside custom `myTitle` element:
483
544
  * schema.addAttributeCheck( ( context, attributeName ) => {
484
- * if ( context.endsWith( 'heading1 $text' ) && attributeName == 'bold' ) {
545
+ * if ( context.endsWith( 'myTitle $text' ) && schema.getAttributeProperties( attributeName ).isFormatting ) {
485
546
  * return false;
486
547
  * }
487
548
  * } );
488
549
  * ```
489
550
  *
490
- * Which translates to:
551
+ * Please note that the generic callbacks may affect the editor performance and should be avoided if possible.
552
+ *
553
+ * When one of the callbacks makes a decision (returns `true` or `false`) the processing is finished and other callbacks are not fired.
554
+ * Callbacks are fired in the order they were added, however generic callbacks are fired before callbacks added for a specified item.
555
+ *
556
+ * You can also use {@link #event:checkAttribute} event, if you need even better control. The result from the example above could also
557
+ * be achieved with following event callback:
491
558
  *
492
559
  * ```ts
493
560
  * schema.on( 'checkAttribute', ( evt, args ) => {
494
561
  * const context = args[ 0 ];
495
562
  * const attributeName = args[ 1 ];
496
563
  *
497
- * if ( context.endsWith( 'heading1 $text' ) && attributeName == 'bold' ) {
564
+ * if ( context.endsWith( 'myTitle $text' ) && schema.getAttributeProperties( attributeName ).isFormatting ) {
498
565
  * // Prevent next listeners from being called.
499
566
  * evt.stop();
500
- * // Set the checkAttribute()'s return value.
567
+ * // Set the `checkAttribute()` return value.
501
568
  * evt.return = false;
502
569
  * }
503
570
  * }, { priority: 'high' } );
504
571
  * ```
505
572
  *
573
+ * Note that the callback checks and declarative rules checks are processed on `normal` priority.
574
+ *
575
+ * Adding callbacks this way can also negatively impact editor performance.
576
+ *
506
577
  * @param callback The callback to be called. It is called with two parameters:
507
- * {@link module:engine/model/schema~SchemaContext} (context) instance and attribute name.
508
- * The callback may return `true/false` to override `checkAttribute()`'s return value. If it does not return
509
- * a boolean value, the default algorithm (or other callbacks) will define `checkAttribute()`'s return value.
578
+ * {@link module:engine/model/schema~SchemaContext `context`} and attribute name. The callback may return `true` or `false`, to
579
+ * override `checkAttribute()`'s return value. If it does not return a boolean value, the default algorithm (or other callbacks)
580
+ * will define `checkAttribute()`'s return value.
581
+ * @param attributeName Name of the attribute for which the callback is registered. If specified, the callback will be run only for
582
+ * `checkAttribute()` calls with matching `attributeName`. Otherwise, the callback will run for every `checkAttribute()` call.
510
583
  */
511
- addAttributeCheck(callback) {
512
- this.on('checkAttribute', (evt, [ctx, attributeName]) => {
513
- const retValue = callback(ctx, attributeName);
514
- if (typeof retValue == 'boolean') {
515
- evt.stop();
516
- evt.return = retValue;
517
- }
518
- }, { priority: 'high' });
584
+ addAttributeCheck(callback, attributeName) {
585
+ const key = attributeName !== undefined ? attributeName : this._genericCheckSymbol;
586
+ const checks = this._customAttributeChecks.get(key) || [];
587
+ checks.push(callback);
588
+ this._customAttributeChecks.set(key, checks);
519
589
  }
520
590
  /**
521
591
  * This method allows assigning additional metadata to the model attributes. For example,
@@ -861,20 +931,64 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
861
931
  // Compile final definitions. Unnecessary properties are removed and some additional cleaning is applied.
862
932
  this._compiledDefinitions = compileDefinitions(definitions);
863
933
  }
864
- _checkContextMatch(def, context, contextItemIndex = context.length - 1) {
865
- const contextItem = context.getItem(contextItemIndex);
866
- if (def.allowIn.includes(contextItem.name)) {
867
- if (contextItemIndex == 0) {
868
- return true;
869
- }
870
- else {
871
- const parentRule = this.getDefinition(contextItem);
872
- return this._checkContextMatch(parentRule, context, contextItemIndex - 1);
873
- }
934
+ _checkContextMatch(context, def) {
935
+ const parentItem = context.last;
936
+ // First, check all child checks declared as callbacks.
937
+ // Note that `_evaluateChildChecks()` will return `undefined` if neither child check was applicable (no decision was made).
938
+ let isAllowed = this._evaluateChildChecks(context, def);
939
+ // If the decision was not made inside child check callbacks, then use declarative rules.
940
+ isAllowed = isAllowed !== undefined ? isAllowed : def.allowIn.includes(parentItem.name);
941
+ // If the item is not allowed in the `context`, return `false`.
942
+ if (!isAllowed) {
943
+ return false;
874
944
  }
875
- else {
945
+ // If the item is allowed, recursively verify the rest of the `context`.
946
+ const parentItemDefinition = this.getDefinition(parentItem);
947
+ const parentContext = context.trimLast();
948
+ // One of the items in the original `context` did not have a definition specified. In this case, the whole context is disallowed.
949
+ if (!parentItemDefinition) {
876
950
  return false;
877
951
  }
952
+ // Whole `context` was verified and passed checks.
953
+ if (parentContext.length == 0) {
954
+ return true;
955
+ }
956
+ // Verify "truncated" parent context. The last item of the original context is now the definition to check.
957
+ return this._checkContextMatch(parentContext, parentItemDefinition);
958
+ }
959
+ /**
960
+ * Calls child check callbacks to decide whether `def` is allowed in `context`. It uses both generic and specific (defined for `def`
961
+ * item) callbacks. If neither callback makes a decision, `undefined` is returned.
962
+ *
963
+ * Note that the first callback that makes a decision "wins", i.e., if any callback returns `true` or `false`, then the processing
964
+ * is over and that result is returned.
965
+ */
966
+ _evaluateChildChecks(context, def) {
967
+ const genericChecks = this._customChildChecks.get(this._genericCheckSymbol) || [];
968
+ const childChecks = this._customChildChecks.get(def.name) || [];
969
+ for (const check of [...genericChecks, ...childChecks]) {
970
+ const result = check(context, def);
971
+ if (result !== undefined) {
972
+ return result;
973
+ }
974
+ }
975
+ }
976
+ /**
977
+ * Calls attribute check callbacks to decide whether `attributeName` can be set on the last element of `context`. It uses both
978
+ * generic and specific (defined for `attributeName`) callbacks. If neither callback makes a decision, `undefined` is returned.
979
+ *
980
+ * Note that the first callback that makes a decision "wins", i.e., if any callback returns `true` or `false`, then the processing
981
+ * is over and that result is returned.
982
+ */
983
+ _evaluateAttributeChecks(context, attributeName) {
984
+ const genericChecks = this._customAttributeChecks.get(this._genericCheckSymbol) || [];
985
+ const childChecks = this._customAttributeChecks.get(attributeName) || [];
986
+ for (const check of [...genericChecks, ...childChecks]) {
987
+ const result = check(context, attributeName);
988
+ if (result !== undefined) {
989
+ return result;
990
+ }
991
+ }
878
992
  }
879
993
  /**
880
994
  * Takes a flat range and an attribute name. Traverses the range recursively and deeply to find and return all ranges
@@ -1050,6 +1164,22 @@ export class SchemaContext {
1050
1164
  ctx._items = [...this._items, ...ctx._items];
1051
1165
  return ctx;
1052
1166
  }
1167
+ /**
1168
+ * Returns a new schema context that is based on this context but has the last item removed.
1169
+ *
1170
+ * ```ts
1171
+ * const ctxParagraph = new SchemaContext( [ '$root', 'blockQuote', 'paragraph' ] );
1172
+ * const ctxBlockQuote = ctxParagraph.trimLast(); // Items in `ctxBlockQuote` are: `$root` an `blockQuote`.
1173
+ * const ctxRoot = ctxBlockQuote.trimLast(); // Items in `ctxRoot` are: `$root`.
1174
+ * ```
1175
+ *
1176
+ * @returns A new reduced schema context instance.
1177
+ */
1178
+ trimLast() {
1179
+ const ctx = new SchemaContext([]);
1180
+ ctx._items = this._items.slice(0, -1);
1181
+ return ctx;
1182
+ }
1053
1183
  /**
1054
1184
  * Gets an item on the given index.
1055
1185
  */
@@ -299,25 +299,14 @@ class Insertion {
299
299
  * Handles insertion of a single node.
300
300
  */
301
301
  _handleNode(node) {
302
- // Let's handle object in a special way.
303
- // * They should never be merged with other elements.
304
- // * If they are not allowed in any of the selection ancestors, they could be either autoparagraphed or totally removed.
305
- if (this.schema.isObject(node)) {
306
- this._handleObject(node);
307
- return;
308
- }
309
- // Try to find a place for the given node.
310
- // Check if a node can be inserted in the given position or it would be accepted if a paragraph would be inserted.
311
- // Inserts the auto paragraph if it would allow for insertion.
312
- let isAllowed = this._checkAndAutoParagraphToAllowedPosition(node);
313
- if (!isAllowed) {
314
- // Split the position.parent's branch up to a point where the node can be inserted.
315
- // If it isn't allowed in the whole branch, then of course don't split anything.
316
- isAllowed = this._checkAndSplitToAllowedPosition(node);
317
- if (!isAllowed) {
302
+ // Split the position.parent's branch up to a point where the node can be inserted.
303
+ // If it isn't allowed in the whole branch, then of course don't split anything.
304
+ if (!this._checkAndSplitToAllowedPosition(node)) {
305
+ // Handle element children if it's not an object (strip container).
306
+ if (!this.schema.isObject(node)) {
318
307
  this._handleDisallowedNode(node);
319
- return;
320
308
  }
309
+ return;
321
310
  }
322
311
  // Add node to the current temporary DocumentFragment.
323
312
  this._appendToFragment(node);
@@ -354,19 +343,6 @@ class Insertion {
354
343
  this.position = livePosition.toPosition();
355
344
  livePosition.detach();
356
345
  }
357
- /**
358
- * @param node The object element.
359
- */
360
- _handleObject(node) {
361
- // Try finding it a place in the tree.
362
- if (this._checkAndSplitToAllowedPosition(node)) {
363
- this._appendToFragment(node);
364
- }
365
- // Try autoparagraphing.
366
- else {
367
- this._tryAutoparagraphing(node);
368
- }
369
- }
370
346
  /**
371
347
  * @param node The disallowed node which needs to be handled.
372
348
  */
@@ -375,10 +351,6 @@ class Insertion {
375
351
  if (node.is('element')) {
376
352
  this.handleNodes(node.getChildren());
377
353
  }
378
- // If text is not allowed, try autoparagraphing it.
379
- else {
380
- this._tryAutoparagraphing(node);
381
- }
382
354
  }
383
355
  /**
384
356
  * Append a node to the temporary DocumentFragment.
@@ -594,37 +566,9 @@ class Insertion {
594
566
  this.model.schema.checkMerge(node, nextSibling);
595
567
  }
596
568
  /**
597
- * Tries wrapping the node in a new paragraph and inserting it this way.
598
- *
599
- * @param node The node which needs to be autoparagraphed.
600
- */
601
- _tryAutoparagraphing(node) {
602
- const paragraph = this.writer.createElement('paragraph');
603
- // Do not autoparagraph if the paragraph won't be allowed there,
604
- // cause that would lead to an infinite loop. The paragraph would be rejected in
605
- // the next _handleNode() call and we'd be here again.
606
- if (this._getAllowedIn(this.position.parent, paragraph) && this.schema.checkChild(paragraph, node)) {
607
- paragraph._appendChild(node);
608
- this._handleNode(paragraph);
609
- }
610
- }
611
- /**
612
- * Checks if a node can be inserted in the given position or it would be accepted if a paragraph would be inserted.
613
- * It also handles inserting the paragraph.
614
- *
615
- * @returns Whether an allowed position was found.
616
- * `false` is returned if the node isn't allowed at the current position or in auto paragraph, `true` if was.
569
+ * Inserts a paragraph and moves the insertion position into it.
617
570
  */
618
- _checkAndAutoParagraphToAllowedPosition(node) {
619
- if (this.schema.checkChild(this.position.parent, node)) {
620
- return true;
621
- }
622
- // Do not auto paragraph if the paragraph won't be allowed there,
623
- // cause that would lead to an infinite loop. The paragraph would be rejected in
624
- // the next _handleNode() call and we'd be here again.
625
- if (!this.schema.checkChild(this.position.parent, 'paragraph') || !this.schema.checkChild('paragraph', node)) {
626
- return false;
627
- }
571
+ _insertAutoParagraph() {
628
572
  // Insert nodes collected in temporary DocumentFragment if the position parent needs change to process further nodes.
629
573
  this._insertPartialFragment();
630
574
  // Insert a paragraph and move insertion position to it.
@@ -633,7 +577,6 @@ class Insertion {
633
577
  this._setAffectedBoundaries(this.position);
634
578
  this._lastAutoParagraph = paragraph;
635
579
  this.position = this.writer.createPositionAt(paragraph, 0);
636
- return true;
637
580
  }
638
581
  /**
639
582
  * @returns Whether an allowed position was found.
@@ -680,18 +623,31 @@ class Insertion {
680
623
  this.canMergeWith.add(this.position.nodeAfter);
681
624
  }
682
625
  }
626
+ // At this point, we split elements up to the parent in which `node` is allowed.
627
+ // Note that `_getAllowedIn()` checks if the `node` is allowed either directly, or when auto-paragraphed.
628
+ // So, let's check if the `node` is allowed directly. If not, we need to auto-paragraph it.
629
+ if (!this.schema.checkChild(this.position.parent, node)) {
630
+ this._insertAutoParagraph();
631
+ }
683
632
  return true;
684
633
  }
685
634
  /**
686
635
  * Gets the element in which the given node is allowed. It checks the passed element and all its ancestors.
687
636
  *
637
+ * It also verifies if auto-paragraphing could help.
638
+ *
688
639
  * @param contextElement The element in which context the node should be checked.
689
640
  * @param childNode The node to check.
690
641
  */
691
642
  _getAllowedIn(contextElement, childNode) {
643
+ // Check if a node can be inserted in the given context...
692
644
  if (this.schema.checkChild(contextElement, childNode)) {
693
645
  return contextElement;
694
646
  }
647
+ // ...or it would be accepted if a paragraph would be inserted.
648
+ if (this.schema.checkChild(contextElement, 'paragraph') && this.schema.checkChild('paragraph', childNode)) {
649
+ return contextElement;
650
+ }
695
651
  // If the child wasn't allowed in the context element and the element is a limit there's no point in
696
652
  // checking any further towards the root. This is it: the limit is unsplittable and there's nothing
697
653
  // we can do about it. Without this check, the algorithm will analyze parent of the limit and may create
@@ -1288,24 +1288,28 @@ export default class DomConverter {
1288
1288
  startPosition: getNext ? ViewPosition._createAfter(node) : ViewPosition._createBefore(node),
1289
1289
  direction: getNext ? 'forward' : 'backward'
1290
1290
  });
1291
- for (const value of treeWalker) {
1291
+ for (const { item } of treeWalker) {
1292
+ // Found a text node in the same container element.
1293
+ if (item.is('$textProxy')) {
1294
+ return item;
1295
+ }
1296
+ // Found a transparent element, skip it and continue inside it.
1297
+ else if (item.is('element') && item.getCustomProperty('dataPipeline:transparentRendering')) {
1298
+ continue;
1299
+ }
1292
1300
  // <br> found – it works like a block boundary, so do not scan further.
1293
- if (value.item.is('element', 'br')) {
1301
+ else if (item.is('element', 'br')) {
1294
1302
  return null;
1295
1303
  }
1296
1304
  // Found an inline object (for example an image).
1297
- else if (this._isInlineObjectElement(value.item)) {
1298
- return value.item;
1305
+ else if (this._isInlineObjectElement(item)) {
1306
+ return item;
1299
1307
  }
1300
1308
  // ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
1301
1309
  // text node in its container element.
1302
- else if (value.item.is('containerElement')) {
1310
+ else if (item.is('containerElement')) {
1303
1311
  return null;
1304
1312
  }
1305
- // Found a text node in the same container element.
1306
- else if (value.item.is('$textProxy')) {
1307
- return value.item;
1308
- }
1309
1313
  }
1310
1314
  return null;
1311
1315
  }
@@ -6,6 +6,7 @@
6
6
  * @module engine/view/observer/compositionobserver
7
7
  */
8
8
  import DomEventObserver from './domeventobserver.js';
9
+ // @if CK_DEBUG_TYPING // const { _debouncedLine } = require( '../../dev-utils/utils.js' );
9
10
  /**
10
11
  * {@link module:engine/view/document~Document#event:compositionstart Compositionstart},
11
12
  * {@link module:engine/view/document~Document#event:compositionupdate compositionupdate} and
@@ -48,6 +49,7 @@ export default class CompositionObserver extends DomEventObserver {
48
49
  */
49
50
  onDomEvent(domEvent) {
50
51
  // @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
52
+ // @if CK_DEBUG_TYPING // _debouncedLine();
51
53
  // @if CK_DEBUG_TYPING // console.group( `%c[CompositionObserver]%c ${ domEvent.type }`, 'color: green', '' );
52
54
  // @if CK_DEBUG_TYPING // }
53
55
  this.fire(domEvent.type, domEvent, {
@@ -47,6 +47,18 @@ export default class FocusObserver extends DomEventObserver<'focus' | 'blur'> {
47
47
  * @inheritDoc
48
48
  */
49
49
  destroy(): void;
50
+ /**
51
+ * The `focus` event handler.
52
+ */
53
+ private _handleFocus;
54
+ /**
55
+ * The `blur` event handler.
56
+ */
57
+ private _handleBlur;
58
+ /**
59
+ * Clears timeout.
60
+ */
61
+ private _clearTimeout;
50
62
  }
51
63
  /**
52
64
  * Fired when one of the editables gets focus.