@ckeditor/ckeditor5-engine 32.0.0 → 34.1.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 +2 -2
  2. package/README.md +2 -1
  3. package/package.json +22 -22
  4. package/src/controller/datacontroller.js +58 -66
  5. package/src/controller/editingcontroller.js +82 -5
  6. package/src/conversion/conversion.js +14 -13
  7. package/src/conversion/downcastdispatcher.js +297 -366
  8. package/src/conversion/downcasthelpers.js +859 -80
  9. package/src/conversion/mapper.js +104 -59
  10. package/src/conversion/modelconsumable.js +84 -34
  11. package/src/conversion/upcastdispatcher.js +33 -6
  12. package/src/conversion/upcasthelpers.js +21 -2
  13. package/src/dataprocessor/htmldataprocessor.js +1 -1
  14. package/src/dev-utils/model.js +13 -11
  15. package/src/index.js +12 -0
  16. package/src/model/batch.js +12 -12
  17. package/src/model/differ.js +114 -68
  18. package/src/model/document.js +32 -31
  19. package/src/model/history.js +160 -22
  20. package/src/model/markercollection.js +28 -4
  21. package/src/model/model.js +122 -4
  22. package/src/model/schema.js +79 -10
  23. package/src/model/treewalker.js +2 -3
  24. package/src/model/utils/deletecontent.js +15 -2
  25. package/src/model/utils/findoptimalinsertionrange.js +68 -0
  26. package/src/model/utils/insertobject.js +173 -0
  27. package/src/model/utils/modifyselection.js +14 -7
  28. package/src/model/writer.js +16 -26
  29. package/src/view/attributeelement.js +0 -10
  30. package/src/view/document.js +2 -1
  31. package/src/view/domconverter.js +47 -11
  32. package/src/view/downcastwriter.js +90 -49
  33. package/src/view/element.js +0 -27
  34. package/src/view/emptyelement.js +0 -3
  35. package/src/view/matcher.js +2 -2
  36. package/src/view/observer/clickobserver.js +0 -1
  37. package/src/view/observer/inputobserver.js +1 -1
  38. package/src/view/observer/tabobserver.js +68 -0
  39. package/src/view/placeholder.js +1 -1
  40. package/src/view/rawelement.js +0 -3
  41. package/src/view/uielement.js +0 -3
  42. package/src/view/view.js +4 -0
  43. package/theme/placeholder.css +9 -0
@@ -35,6 +35,7 @@ const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
35
35
  const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER( document ); // eslint-disable-line new-cap
36
36
  const UNSAFE_ATTRIBUTE_NAME_PREFIX = 'data-ck-unsafe-attribute-';
37
37
  const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
38
+ const UNSAFE_ELEMENTS = [ 'script', 'style' ];
38
39
 
39
40
  /**
40
41
  * `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
@@ -323,7 +324,7 @@ export default class DomConverter {
323
324
 
324
325
  // There are certain nodes, that should be renamed to <span> in editing pipeline.
325
326
  if ( this._shouldRenameElement( elementName ) ) {
326
- logWarning( 'domconverter-unsafe-element-detected', { unsafeElement: currentNode } );
327
+ _logUnsafeElement( elementName );
327
328
 
328
329
  currentNode.replaceWith( this._createReplacementDomElement( elementName, currentNode ) );
329
330
  }
@@ -384,7 +385,7 @@ export default class DomConverter {
384
385
  } else {
385
386
  // Create DOM element.
386
387
  if ( this._shouldRenameElement( viewNode.name ) ) {
387
- logWarning( 'domconverter-unsafe-element-detected', { unsafeElement: viewNode } );
388
+ _logUnsafeElement( viewNode.name );
388
389
 
389
390
  domElement = this._createReplacementDomElement( viewNode.name );
390
391
  } else if ( viewNode.hasAttribute( 'xmlns' ) ) {
@@ -428,7 +429,7 @@ export default class DomConverter {
428
429
  * @param {String} key The name of the attribute.
429
430
  * @param {String} value The value of the attribute.
430
431
  * @param {module:engine/view/element~Element} [relatedViewElement] The view element related to the `domElement` (if there is any).
431
- * It helps decide whether the attribute set is unsafe. For instance, view elements created via
432
+ * It helps decide whether the attribute set is unsafe. For instance, view elements created via the
432
433
  * {@link module:engine/view/downcastwriter~DowncastWriter} methods can allow certain attributes that would normally be filtered out.
433
434
  */
434
435
  setDomElementAttribute( domElement, key, value, relatedViewElement = null ) {
@@ -492,7 +493,22 @@ export default class DomConverter {
492
493
  yield this._getBlockFiller( domDocument );
493
494
  }
494
495
 
495
- yield this.viewToDom( childView, domDocument, options );
496
+ const transparentRendering = childView.is( 'element' ) && childView.getCustomProperty( 'dataPipeline:transparentRendering' );
497
+
498
+ if ( transparentRendering && this.renderingMode == 'data' ) {
499
+ yield* this.viewChildrenToDom( childView, domDocument, options );
500
+ } else {
501
+ if ( transparentRendering ) {
502
+ /**
503
+ * The `dataPipeline:transparentRendering` flag is supported only in the data pipeline.
504
+ *
505
+ * @error domconverter-transparent-rendering-unsupported-in-editing-pipeline
506
+ */
507
+ logWarning( 'domconverter-transparent-rendering-unsupported-in-editing-pipeline', { viewElement: childView } );
508
+ }
509
+
510
+ yield this.viewToDom( childView, domDocument, options );
511
+ }
496
512
 
497
513
  offset++;
498
514
  }
@@ -1546,7 +1562,9 @@ export default class DomConverter {
1546
1562
  * @returns {Boolean}
1547
1563
  */
1548
1564
  _shouldRenameElement( elementName ) {
1549
- return this.renderingMode == 'editing' && elementName.toLowerCase() == 'script';
1565
+ const name = elementName.toLowerCase();
1566
+
1567
+ return this.renderingMode === 'editing' && UNSAFE_ELEMENTS.includes( name );
1550
1568
  }
1551
1569
 
1552
1570
  /**
@@ -1626,6 +1644,20 @@ function hasBlockParent( domNode, blockElements ) {
1626
1644
  return parent && parent.tagName && blockElements.includes( parent.tagName.toLowerCase() );
1627
1645
  }
1628
1646
 
1647
+ // Log to console the information about element that was replaced.
1648
+ // Check UNSAFE_ELEMENTS for all recognized unsafe elements.
1649
+ //
1650
+ // @param {String} elementName The name of the view element
1651
+ function _logUnsafeElement( elementName ) {
1652
+ if ( elementName === 'script' ) {
1653
+ logWarning( 'domconverter-unsafe-script-element-detected' );
1654
+ }
1655
+
1656
+ if ( elementName === 'style' ) {
1657
+ logWarning( 'domconverter-unsafe-style-element-detected' );
1658
+ }
1659
+ }
1660
+
1629
1661
  /**
1630
1662
  * Enum representing the type of the block filler.
1631
1663
  *
@@ -1640,13 +1672,17 @@ function hasBlockParent( domNode, blockElements ) {
1640
1672
  */
1641
1673
 
1642
1674
  /**
1643
- * The {@link module:engine/view/domconverter~DomConverter} detected a `<script>` element that may disrupt the
1644
- * {@glink framework/guides/architecture/editing-engine#editing-pipeline editing pipeline} of the editor. To avoid this,
1645
- * the `<script>` element was renamed to `<span data-ck-unsafe-element="script"></span>`.
1675
+ * While rendering the editor content, the {@link module:engine/view/domconverter~DomConverter} detected a `<script>` element that may
1676
+ * disrupt the editing experience. To avoid this, the `<script>` element was replaced with `<span data-ck-unsafe-element="script"></span>`.
1677
+ *
1678
+ * @error domconverter-unsafe-script-element-detected
1679
+ */
1680
+
1681
+ /**
1682
+ * While rendering the editor content, the {@link module:engine/view/domconverter~DomConverter} detected a `<style>` element that may affect
1683
+ * the editing experience. To avoid this, the `<style>` element was replaced with `<span data-ck-unsafe-element="style"></span>`.
1646
1684
  *
1647
- * @error domconverter-unsafe-element-detected
1648
- * @param {module:engine/model/element~Element|HTMLElement} unsafeElement The editing view or DOM element
1649
- * that was renamed.
1685
+ * @error domconverter-unsafe-style-element-detected
1650
1686
  */
1651
1687
 
1652
1688
  /**
@@ -58,6 +58,14 @@ export default class DowncastWriter {
58
58
  * @type {Map.<String,Set>}
59
59
  */
60
60
  this._cloneGroups = new Map();
61
+
62
+ /**
63
+ * The slot factory used by the `elementToStructure` downcast helper.
64
+ *
65
+ * @private
66
+ * @type {Function|null}
67
+ */
68
+ this._slotFactory = null;
61
69
  }
62
70
 
63
71
  /**
@@ -177,10 +185,6 @@ export default class DowncastWriter {
177
185
  * // Set `id` of a marker element so it is not joined or merged with "normal" elements.
178
186
  * writer.createAttributeElement( 'span', { class: 'my-marker' }, { id: 'marker:my' } );
179
187
  *
180
- * **Note:** By default an `AttributeElement` is split by a
181
- * {@link module:engine/view/containerelement~ContainerElement `ContainerElement`} but this behavior can be modified
182
- * with `isAllowedInsideAttributeElement` option set while {@link #createContainerElement creating the element}.
183
- *
184
188
  * @param {String} name Name of the element.
185
189
  * @param {Object} [attributes] Element's attributes.
186
190
  * @param {Object} [options] Element's options.
@@ -222,23 +226,36 @@ export default class DowncastWriter {
222
226
  * // Create element with custom classes.
223
227
  * writer.createContainerElement( 'p', { class: 'foo bar baz' } );
224
228
  *
229
+ * // Create element with children.
230
+ * writer.createContainerElement( 'figure', { class: 'image' }, [
231
+ * writer.createEmptyElement( 'img' ),
232
+ * writer.createContainerElement( 'figcaption' )
233
+ * ] );
234
+ *
235
+ * // Create element with specific options.
236
+ * writer.createContainerElement( 'span', { class: 'placeholder' }, { renderUnsafeAttributes: [ 'foo' ] } );
237
+ *
225
238
  * @param {String} name Name of the element.
226
239
  * @param {Object} [attributes] Elements attributes.
240
+ * @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>|Object} [childrenOrOptions]
241
+ * A node or a list of nodes to be inserted into the created element. If no children were specified, element's `options`
242
+ * can be passed in this argument.
227
243
  * @param {Object} [options] Element's options.
228
- * @param {Boolean} [options.isAllowedInsideAttributeElement=false] Whether an element is
229
- * {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
230
- * with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
231
244
  * @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
232
245
  * pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
233
246
  * @returns {module:engine/view/containerelement~ContainerElement} Created element.
234
247
  */
235
- createContainerElement( name, attributes, options = {} ) {
236
- const containerElement = new ContainerElement( this.document, name, attributes );
248
+ createContainerElement( name, attributes, childrenOrOptions = {}, options = {} ) {
249
+ let children = null;
237
250
 
238
- if ( options.isAllowedInsideAttributeElement !== undefined ) {
239
- containerElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
251
+ if ( isPlainObject( childrenOrOptions ) ) {
252
+ options = childrenOrOptions;
253
+ } else {
254
+ children = childrenOrOptions;
240
255
  }
241
256
 
257
+ const containerElement = new ContainerElement( this.document, name, attributes, children );
258
+
242
259
  if ( options.renderUnsafeAttributes ) {
243
260
  containerElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
244
261
  }
@@ -282,9 +299,6 @@ export default class DowncastWriter {
282
299
  * @param {String} name Name of the element.
283
300
  * @param {Object} [attributes] Elements attributes.
284
301
  * @param {Object} [options] Element's options.
285
- * @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
286
- * {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
287
- * with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
288
302
  * @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
289
303
  * pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
290
304
  * @returns {module:engine/view/emptyelement~EmptyElement} Created element.
@@ -292,10 +306,6 @@ export default class DowncastWriter {
292
306
  createEmptyElement( name, attributes, options = {} ) {
293
307
  const emptyElement = new EmptyElement( this.document, name, attributes );
294
308
 
295
- if ( options.isAllowedInsideAttributeElement !== undefined ) {
296
- emptyElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
297
- }
298
-
299
309
  if ( options.renderUnsafeAttributes ) {
300
310
  emptyElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
301
311
  }
@@ -326,23 +336,15 @@ export default class DowncastWriter {
326
336
  * @param {String} name The name of the element.
327
337
  * @param {Object} [attributes] Element attributes.
328
338
  * @param {Function} [renderFunction] A custom render function.
329
- * @param {Object} [options] Element's options.
330
- * @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
331
- * {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
332
- * with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
333
339
  * @returns {module:engine/view/uielement~UIElement} The created element.
334
340
  */
335
- createUIElement( name, attributes, renderFunction, options = {} ) {
341
+ createUIElement( name, attributes, renderFunction ) {
336
342
  const uiElement = new UIElement( this.document, name, attributes );
337
343
 
338
344
  if ( renderFunction ) {
339
345
  uiElement.render = renderFunction;
340
346
  }
341
347
 
342
- if ( options.isAllowedInsideAttributeElement !== undefined ) {
343
- uiElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
344
- }
345
-
346
348
  return uiElement;
347
349
  }
348
350
 
@@ -369,9 +371,6 @@ export default class DowncastWriter {
369
371
  * @param {Object} [attributes] Element attributes.
370
372
  * @param {Function} [renderFunction] A custom render function.
371
373
  * @param {Object} [options] Element's options.
372
- * @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
373
- * {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
374
- * with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
375
374
  * @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
376
375
  * pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
377
376
  * @returns {module:engine/view/rawelement~RawElement} The created element.
@@ -381,10 +380,6 @@ export default class DowncastWriter {
381
380
 
382
381
  rawElement.render = renderFunction || ( () => {} );
383
382
 
384
- if ( options.isAllowedInsideAttributeElement !== undefined ) {
385
- rawElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
386
- }
387
-
388
383
  if ( options.renderUnsafeAttributes ) {
389
384
  rawElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
390
385
  }
@@ -762,7 +757,7 @@ export default class DowncastWriter {
762
757
 
763
758
  // Break attributes on nodes that do exist in the model tree so they can have attributes, other elements
764
759
  // can't have an attribute in model and won't get wrapped with an AttributeElement while down-casted.
765
- const breakAttributes = !( node.is( 'uiElement' ) && node.isAllowedInsideAttributeElement );
760
+ const breakAttributes = !node.is( 'uiElement' );
766
761
 
767
762
  if ( !lastGroup || lastGroup.breakAttributes != breakAttributes ) {
768
763
  groups.push( {
@@ -951,16 +946,6 @@ export default class DowncastWriter {
951
946
  * Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-nonselection-collapsed-range` when passed range
952
947
  * is collapsed and different than view selection.
953
948
  *
954
- * **Note:** Attribute elements by default can wrap {@link module:engine/view/text~Text},
955
- * {@link module:engine/view/emptyelement~EmptyElement}, {@link module:engine/view/uielement~UIElement},
956
- * {@link module:engine/view/rawelement~RawElement} and other attribute elements with higher priority. Other elements while placed
957
- * inside an attribute element will split it (or nest it in case of an `AttributeElement`). This behavior can be modified by changing
958
- * the `isAllowedInsideAttributeElement` option while using
959
- * {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
960
- * {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement},
961
- * {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement} or
962
- * {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement}.
963
- *
964
949
  * @param {module:engine/view/range~Range} range Range to wrap.
965
950
  * @param {module:engine/view/attributeelement~AttributeElement} attribute Attribute element to use as wrapper.
966
951
  * @returns {module:engine/view/range~Range} range Range after wrapping, spanning over wrapping attribute element.
@@ -1167,7 +1152,7 @@ export default class DowncastWriter {
1167
1152
  }
1168
1153
 
1169
1154
  /**
1170
- Creates new {@link module:engine/view/selection~Selection} instance.
1155
+ * Creates new {@link module:engine/view/selection~Selection} instance.
1171
1156
  *
1172
1157
  * // Creates empty selection without ranges.
1173
1158
  * const selection = writer.createSelection();
@@ -1230,6 +1215,63 @@ export default class DowncastWriter {
1230
1215
  return new Selection( selectable, placeOrOffset, options );
1231
1216
  }
1232
1217
 
1218
+ /**
1219
+ * Creates placeholders for child elements of the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
1220
+ * `elementToStructure()`} conversion helper.
1221
+ *
1222
+ * const viewSlot = conversionApi.writer.createSlot();
1223
+ * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 );
1224
+ *
1225
+ * conversionApi.writer.insert( viewPosition, viewSlot );
1226
+ *
1227
+ * It could be filtered down to a specific subset of children (only `<foo>` model elements in this case):
1228
+ *
1229
+ * const viewSlot = conversionApi.writer.createSlot( node => node.is( 'element', 'foo' ) );
1230
+ * const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 );
1231
+ *
1232
+ * conversionApi.writer.insert( viewPosition, viewSlot );
1233
+ *
1234
+ * While providing a filtered slot, make sure to provide slots for all child nodes. A single node can not be downcasted into
1235
+ * multiple slots.
1236
+ *
1237
+ * **Note**: You should not change the order of nodes. View elements should be in the same order as model nodes.
1238
+ *
1239
+ * @param {'children'|module:engine/conversion/downcasthelpers~SlotFilter} [modeOrFilter='children'] The filter for child nodes.
1240
+ * @returns {module:engine/view/element~Element} The slot element to be placed in to the view structure while processing
1241
+ * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`}.
1242
+ */
1243
+ createSlot( modeOrFilter ) {
1244
+ if ( !this._slotFactory ) {
1245
+ /**
1246
+ * The `createSlot()` method is only allowed inside the `elementToStructure` downcast helper callback.
1247
+ *
1248
+ * @error view-writer-invalid-create-slot-context
1249
+ */
1250
+ throw new CKEditorError( 'view-writer-invalid-create-slot-context', this.document );
1251
+ }
1252
+
1253
+ return this._slotFactory( this, modeOrFilter );
1254
+ }
1255
+
1256
+ /**
1257
+ * Registers a slot factory.
1258
+ *
1259
+ * @protected
1260
+ * @param {Function} slotFactory The slot factory.
1261
+ */
1262
+ _registerSlotFactory( slotFactory ) {
1263
+ this._slotFactory = slotFactory;
1264
+ }
1265
+
1266
+ /**
1267
+ * Clears the registered slot factory.
1268
+ *
1269
+ * @protected
1270
+ */
1271
+ _clearSlotFactory() {
1272
+ this._slotFactory = null;
1273
+ }
1274
+
1233
1275
  /**
1234
1276
  * Inserts a node or nodes at the specified position. Takes care of breaking attributes before insertion
1235
1277
  * and merging them afterwards if requested by the breakAttributes param.
@@ -1314,7 +1356,6 @@ export default class DowncastWriter {
1314
1356
  const child = parent.getChild( i );
1315
1357
  const isText = child.is( '$text' );
1316
1358
  const isAttribute = child.is( 'attributeElement' );
1317
- const isAllowedInsideAttributeElement = child.isAllowedInsideAttributeElement;
1318
1359
 
1319
1360
  //
1320
1361
  // (In all examples, assume that `wrapElement` is `<span class="foo">` element.)
@@ -1333,7 +1374,7 @@ export default class DowncastWriter {
1333
1374
  //
1334
1375
  // <p>abc</p> --> <p><span class="foo">abc</span></p>
1335
1376
  // <p><strong>abc</strong></p> --> <p><span class="foo"><strong>abc</strong></span></p>
1336
- else if ( isText || isAllowedInsideAttributeElement || ( isAttribute && shouldABeOutsideB( wrapElement, child ) ) ) {
1377
+ else if ( isText || !isAttribute || shouldABeOutsideB( wrapElement, child ) ) {
1337
1378
  // Clone attribute.
1338
1379
  const newAttribute = wrapElement._clone();
1339
1380
 
@@ -1351,7 +1392,7 @@ export default class DowncastWriter {
1351
1392
  //
1352
1393
  // <p><a href="foo.html">abc</a></p> --> <p><a href="foo.html"><span class="foo">abc</span></a></p>
1353
1394
  //
1354
- else if ( isAttribute ) {
1395
+ else /* if ( isAttribute ) */ {
1355
1396
  this._wrapChildren( child, 0, child.childCount, wrapElement );
1356
1397
  }
1357
1398
 
@@ -130,15 +130,6 @@ export default class Element extends Node {
130
130
  */
131
131
  this._customProperties = new Map();
132
132
 
133
- /**
134
- * Whether an element is allowed inside an AttributeElement and can be wrapped with
135
- * {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
136
- *
137
- * @protected
138
- * @member {Boolean}
139
- */
140
- this._isAllowedInsideAttributeElement = false;
141
-
142
133
  /**
143
134
  * A list of attribute names that should be rendered in the editing pipeline even though filtering mechanisms
144
135
  * implemented in the {@link module:engine/view/domconverter~DomConverter} (for instance,
@@ -175,17 +166,6 @@ export default class Element extends Node {
175
166
  return this._children.length === 0;
176
167
  }
177
168
 
178
- /**
179
- * Whether the element is allowed inside an AttributeElement and can be wrapped with
180
- * {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
181
- *
182
- * @readonly
183
- * @type {Boolean}
184
- */
185
- get isAllowedInsideAttributeElement() {
186
- return this._isAllowedInsideAttributeElement;
187
- }
188
-
189
169
  /**
190
170
  * Checks whether this object is of the given.
191
171
  *
@@ -350,11 +330,6 @@ export default class Element extends Node {
350
330
  return false;
351
331
  }
352
332
 
353
- // Check isAllowedInsideAttributeElement property.
354
- if ( this.isAllowedInsideAttributeElement != otherElement.isAllowedInsideAttributeElement ) {
355
- return false;
356
- }
357
-
358
333
  // Check number of attributes, classes and styles.
359
334
  if ( this._attrs.size !== otherElement._attrs.size || this._classes.size !== otherElement._classes.size ||
360
335
  this._styles.size !== otherElement._styles.size ) {
@@ -633,8 +608,6 @@ export default class Element extends Node {
633
608
  // is changed by e.g. toWidget() function from ckeditor5-widget. Perhaps this should be one of custom props.
634
609
  cloned.getFillerOffset = this.getFillerOffset;
635
610
 
636
- cloned._isAllowedInsideAttributeElement = this.isAllowedInsideAttributeElement;
637
-
638
611
  return cloned;
639
612
  }
640
613
 
@@ -37,9 +37,6 @@ export default class EmptyElement extends Element {
37
37
  constructor( document, name, attrs, children ) {
38
38
  super( document, name, attrs, children );
39
39
 
40
- // Override the default of the base class.
41
- this._isAllowedInsideAttributeElement = true;
42
-
43
40
  /**
44
41
  * Returns `null` because filler is not needed for EmptyElements.
45
42
  *
@@ -739,7 +739,7 @@ function matchStyles( patterns, element ) {
739
739
  * styles: /^border.*$/
740
740
  * }
741
741
  *
742
- * Refer to the {@glink builds/guides/migration/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
742
+ * Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
743
743
  * and {@link module:engine/view/matcher~MatcherPattern} documentation.
744
744
  *
745
745
  * @param {Object} pattern Pattern with missing properties.
@@ -769,7 +769,7 @@ function matchStyles( patterns, element ) {
769
769
  * classes: 'foobar'
770
770
  * }
771
771
  *
772
- * Refer to the {@glink builds/guides/migration/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
772
+ * Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
773
773
  * and the {@link module:engine/view/matcher~MatcherPattern} documentation.
774
774
  *
775
775
  * @param {Object} pattern Pattern with missing properties.
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
4
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
5
 
6
6
  /**
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module engine/view/observer/tabobserver
8
+ */
9
+
10
+ import Observer from './observer';
11
+ import BubblingEventInfo from './bubblingeventinfo';
12
+
13
+ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
14
+
15
+ /**
16
+ * Tab observer introduces the {@link module:engine/view/document~Document#event:tab `Document#tab`} event.
17
+ *
18
+ * Note that because {@link module:engine/view/observer/tabobserver~TabObserver} is attached by the
19
+ * {@link module:engine/view/view~View}, this event is available by default.
20
+ *
21
+ * @extends module:engine/view/observer/observer~Observer
22
+ */
23
+ export default class TabObserver extends Observer {
24
+ /**
25
+ * @inheritDoc
26
+ */
27
+ constructor( view ) {
28
+ super( view );
29
+
30
+ const doc = this.document;
31
+
32
+ doc.on( 'keydown', ( evt, data ) => {
33
+ if (
34
+ !this.isEnabled ||
35
+ data.keyCode != keyCodes.tab ||
36
+ data.ctrlKey
37
+ ) {
38
+ return;
39
+ }
40
+
41
+ const event = new BubblingEventInfo( doc, 'tab', doc.selection.getFirstRange() );
42
+
43
+ doc.fire( event, data );
44
+
45
+ if ( event.stop.called ) {
46
+ evt.stop();
47
+ }
48
+ } );
49
+ }
50
+
51
+ /**
52
+ * @inheritDoc
53
+ */
54
+ observe() {}
55
+ }
56
+
57
+ /**
58
+ * Event fired when the user presses a tab key.
59
+ *
60
+ * Introduced by {@link module:engine/view/observer/tabobserver~TabObserver}.
61
+ *
62
+ * Note that because {@link module:engine/view/observer/tabobserver~TabObserver} is attached by the
63
+ * {@link module:engine/view/view~View}, this event is available by default.
64
+ *
65
+ * @event module:engine/view/document~Document#event:tab
66
+ *
67
+ * @param {module:engine/view/observer/domeventdata~DomEventData} data
68
+ */
@@ -276,7 +276,7 @@ function getChildPlaceholderHostSubstitute( parent ) {
276
276
  if ( parent.childCount ) {
277
277
  const firstChild = parent.getChild( 0 );
278
278
 
279
- if ( firstChild.is( 'element' ) && !firstChild.is( 'uiElement' ) ) {
279
+ if ( firstChild.is( 'element' ) && !firstChild.is( 'uiElement' ) && !firstChild.is( 'attributeElement' ) ) {
280
280
  return firstChild;
281
281
  }
282
282
  }
@@ -47,9 +47,6 @@ export default class RawElement extends Element {
47
47
  constructor( document, name, attrs, children ) {
48
48
  super( document, name, attrs, children );
49
49
 
50
- // Override the default of the base class.
51
- this._isAllowedInsideAttributeElement = true;
52
-
53
50
  /**
54
51
  * Returns `null` because filler is not needed for raw elements.
55
52
  *
@@ -50,9 +50,6 @@ export default class UIElement extends Element {
50
50
  constructor( document, name, attributes, children ) {
51
51
  super( document, name, attributes, children );
52
52
 
53
- // Override the default of the base class.
54
- this._isAllowedInsideAttributeElement = true;
55
-
56
53
  /**
57
54
  * Returns `null` because filler is not needed for UIElements.
58
55
  *
package/src/view/view.js CHANGED
@@ -23,6 +23,7 @@ import FocusObserver from './observer/focusobserver';
23
23
  import CompositionObserver from './observer/compositionobserver';
24
24
  import InputObserver from './observer/inputobserver';
25
25
  import ArrowKeysObserver from './observer/arrowkeysobserver';
26
+ import TabObserver from './observer/tabobserver';
26
27
 
27
28
  import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
28
29
  import mix from '@ckeditor/ckeditor5-utils/src/mix';
@@ -54,6 +55,8 @@ import env from '@ckeditor/ckeditor5-utils/src/env';
54
55
  * * {@link module:engine/view/observer/keyobserver~KeyObserver},
55
56
  * * {@link module:engine/view/observer/fakeselectionobserver~FakeSelectionObserver}.
56
57
  * * {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
58
+ * * {@link module:engine/view/observer/arrowkeysobserver~ArrowKeysObserver}.
59
+ * * {@link module:engine/view/observer/tabobserver~TabObserver}.
57
60
  *
58
61
  * This class also {@link module:engine/view/view~View#attachDomRoot binds the DOM and the view elements}.
59
62
  *
@@ -186,6 +189,7 @@ export default class View {
186
189
  this.addObserver( FakeSelectionObserver );
187
190
  this.addObserver( CompositionObserver );
188
191
  this.addObserver( ArrowKeysObserver );
192
+ this.addObserver( TabObserver );
189
193
 
190
194
  if ( env.isAndroid ) {
191
195
  this.addObserver( InputObserver );
@@ -25,3 +25,12 @@
25
25
  display: none;
26
26
  }
27
27
  }
28
+
29
+ /*
30
+ * Rules for the `ck-placeholder` are loaded before the rules for `ck-reset_all` in the base CKEditor 5 DLL build.
31
+ * This fix overwrites the incorrectly set `position: static` from `ck-reset_all`.
32
+ * See https://github.com/ckeditor/ckeditor5/issues/11418.
33
+ */
34
+ .ck.ck-reset_all .ck-placeholder {
35
+ position: relative;
36
+ }