@ckeditor/ckeditor5-engine 34.2.0 → 35.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 (125) hide show
  1. package/CHANGELOG.md +823 -0
  2. package/LICENSE.md +4 -0
  3. package/package.json +32 -25
  4. package/src/controller/datacontroller.js +467 -561
  5. package/src/controller/editingcontroller.js +168 -204
  6. package/src/conversion/conversion.js +541 -565
  7. package/src/conversion/conversionhelpers.js +24 -28
  8. package/src/conversion/downcastdispatcher.js +457 -686
  9. package/src/conversion/downcasthelpers.js +1583 -1965
  10. package/src/conversion/mapper.js +518 -707
  11. package/src/conversion/modelconsumable.js +240 -283
  12. package/src/conversion/upcastdispatcher.js +372 -718
  13. package/src/conversion/upcasthelpers.js +707 -818
  14. package/src/conversion/viewconsumable.js +524 -581
  15. package/src/dataprocessor/basichtmlwriter.js +12 -16
  16. package/src/dataprocessor/dataprocessor.js +5 -0
  17. package/src/dataprocessor/htmldataprocessor.js +101 -117
  18. package/src/dataprocessor/htmlwriter.js +1 -18
  19. package/src/dataprocessor/xmldataprocessor.js +117 -138
  20. package/src/dev-utils/model.js +260 -352
  21. package/src/dev-utils/operationreplayer.js +106 -126
  22. package/src/dev-utils/utils.js +34 -51
  23. package/src/dev-utils/view.js +632 -753
  24. package/src/index.js +0 -11
  25. package/src/model/batch.js +111 -127
  26. package/src/model/differ.js +988 -1233
  27. package/src/model/document.js +340 -449
  28. package/src/model/documentfragment.js +327 -364
  29. package/src/model/documentselection.js +996 -1189
  30. package/src/model/element.js +306 -410
  31. package/src/model/history.js +224 -262
  32. package/src/model/item.js +5 -0
  33. package/src/model/liveposition.js +84 -145
  34. package/src/model/liverange.js +108 -185
  35. package/src/model/markercollection.js +379 -480
  36. package/src/model/model.js +883 -1034
  37. package/src/model/node.js +419 -463
  38. package/src/model/nodelist.js +175 -201
  39. package/src/model/operation/attributeoperation.js +153 -182
  40. package/src/model/operation/detachoperation.js +64 -83
  41. package/src/model/operation/insertoperation.js +135 -166
  42. package/src/model/operation/markeroperation.js +114 -140
  43. package/src/model/operation/mergeoperation.js +163 -191
  44. package/src/model/operation/moveoperation.js +157 -187
  45. package/src/model/operation/nooperation.js +28 -38
  46. package/src/model/operation/operation.js +106 -125
  47. package/src/model/operation/operationfactory.js +30 -34
  48. package/src/model/operation/renameoperation.js +109 -135
  49. package/src/model/operation/rootattributeoperation.js +155 -188
  50. package/src/model/operation/splitoperation.js +196 -232
  51. package/src/model/operation/transform.js +1833 -2204
  52. package/src/model/operation/utils.js +140 -204
  53. package/src/model/position.js +899 -1053
  54. package/src/model/range.js +910 -1028
  55. package/src/model/rootelement.js +77 -97
  56. package/src/model/schema.js +1189 -1835
  57. package/src/model/selection.js +745 -862
  58. package/src/model/text.js +90 -114
  59. package/src/model/textproxy.js +204 -240
  60. package/src/model/treewalker.js +316 -397
  61. package/src/model/typecheckable.js +16 -0
  62. package/src/model/utils/autoparagraphing.js +32 -44
  63. package/src/model/utils/deletecontent.js +334 -418
  64. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  65. package/src/model/utils/getselectedcontent.js +96 -118
  66. package/src/model/utils/insertcontent.js +654 -773
  67. package/src/model/utils/insertobject.js +96 -119
  68. package/src/model/utils/modifyselection.js +120 -158
  69. package/src/model/utils/selection-post-fixer.js +153 -201
  70. package/src/model/writer.js +1305 -1474
  71. package/src/view/attributeelement.js +189 -225
  72. package/src/view/containerelement.js +75 -85
  73. package/src/view/document.js +172 -215
  74. package/src/view/documentfragment.js +200 -249
  75. package/src/view/documentselection.js +338 -367
  76. package/src/view/domconverter.js +1371 -1613
  77. package/src/view/downcastwriter.js +1747 -2076
  78. package/src/view/editableelement.js +81 -97
  79. package/src/view/element.js +739 -890
  80. package/src/view/elementdefinition.js +5 -0
  81. package/src/view/emptyelement.js +82 -92
  82. package/src/view/filler.js +35 -50
  83. package/src/view/item.js +5 -0
  84. package/src/view/matcher.js +260 -559
  85. package/src/view/node.js +274 -360
  86. package/src/view/observer/arrowkeysobserver.js +19 -28
  87. package/src/view/observer/bubblingemittermixin.js +120 -263
  88. package/src/view/observer/bubblingeventinfo.js +47 -55
  89. package/src/view/observer/clickobserver.js +7 -13
  90. package/src/view/observer/compositionobserver.js +14 -24
  91. package/src/view/observer/domeventdata.js +57 -67
  92. package/src/view/observer/domeventobserver.js +40 -64
  93. package/src/view/observer/fakeselectionobserver.js +81 -96
  94. package/src/view/observer/focusobserver.js +45 -61
  95. package/src/view/observer/inputobserver.js +7 -13
  96. package/src/view/observer/keyobserver.js +17 -27
  97. package/src/view/observer/mouseobserver.js +7 -14
  98. package/src/view/observer/mutationobserver.js +220 -315
  99. package/src/view/observer/observer.js +81 -102
  100. package/src/view/observer/selectionobserver.js +191 -246
  101. package/src/view/observer/tabobserver.js +23 -36
  102. package/src/view/placeholder.js +128 -173
  103. package/src/view/position.js +350 -401
  104. package/src/view/range.js +453 -513
  105. package/src/view/rawelement.js +85 -112
  106. package/src/view/renderer.js +874 -1014
  107. package/src/view/rooteditableelement.js +80 -90
  108. package/src/view/selection.js +608 -689
  109. package/src/view/styles/background.js +43 -44
  110. package/src/view/styles/border.js +220 -276
  111. package/src/view/styles/margin.js +8 -17
  112. package/src/view/styles/padding.js +8 -16
  113. package/src/view/styles/utils.js +127 -160
  114. package/src/view/stylesmap.js +728 -905
  115. package/src/view/text.js +102 -126
  116. package/src/view/textproxy.js +144 -170
  117. package/src/view/treewalker.js +383 -479
  118. package/src/view/typecheckable.js +19 -0
  119. package/src/view/uielement.js +166 -187
  120. package/src/view/upcastwriter.js +395 -449
  121. package/src/view/view.js +569 -664
  122. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  123. package/src/model/item.jsdoc +0 -14
  124. package/src/view/elementdefinition.jsdoc +0 -59
  125. package/src/view/item.jsdoc +0 -14
@@ -2,245 +2,212 @@
2
2
  * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module engine/view/matcher
8
- */
9
-
10
5
  import { isPlainObject } from 'lodash-es';
11
-
12
6
  import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
13
-
14
7
  /**
15
8
  * View matcher class.
16
9
  * Instance of this class can be used to find {@link module:engine/view/element~Element elements} that match given pattern.
17
10
  */
18
11
  export default class Matcher {
19
- /**
20
- * Creates new instance of Matcher.
21
- *
22
- * @param {String|RegExp|Object} [pattern] Match patterns. See {@link module:engine/view/matcher~Matcher#add add method} for
23
- * more information.
24
- */
25
- constructor( ...pattern ) {
26
- /**
27
- * @private
28
- * @type {Array<String|RegExp|Object>}
29
- */
30
- this._patterns = [];
31
-
32
- this.add( ...pattern );
33
- }
34
-
35
- /**
36
- * Adds pattern or patterns to matcher instance.
37
- *
38
- * // String.
39
- * matcher.add( 'div' );
40
- *
41
- * // Regular expression.
42
- * matcher.add( /^\w/ );
43
- *
44
- * // Single class.
45
- * matcher.add( {
46
- * classes: 'foobar'
47
- * } );
48
- *
49
- * See {@link module:engine/view/matcher~MatcherPattern} for more examples.
50
- *
51
- * Multiple patterns can be added in one call:
52
- *
53
- * matcher.add( 'div', { classes: 'foobar' } );
54
- *
55
- * @param {Object|String|RegExp|Function} pattern Object describing pattern details. If string or regular expression
56
- * is provided it will be used to match element's name. Pattern can be also provided in a form
57
- * of a function - then this function will be called with each {@link module:engine/view/element~Element element} as a parameter.
58
- * Function's return value will be stored under `match` key of the object returned from
59
- * {@link module:engine/view/matcher~Matcher#match match} or {@link module:engine/view/matcher~Matcher#matchAll matchAll} methods.
60
- * @param {String|RegExp} [pattern.name] Name or regular expression to match element's name.
61
- * @param {Object} [pattern.attributes] Object with key-value pairs representing attributes to match. Each object key
62
- * represents attribute name. Value under that key can be either:
63
- * * `true` - then attribute is just required (can be empty),
64
- * * a string - then attribute has to be equal, or
65
- * * a regular expression - then attribute has to match the expression.
66
- * @param {String|RegExp|Array} [pattern.classes] Class name or array of class names to match. Each name can be
67
- * provided in a form of string or regular expression.
68
- * @param {Object} [pattern.styles] Object with key-value pairs representing styles to match. Each object key
69
- * represents style name. Value under that key can be either a string or a regular expression and it will be used
70
- * to match style value.
71
- */
72
- add( ...pattern ) {
73
- for ( let item of pattern ) {
74
- // String or RegExp pattern is used as element's name.
75
- if ( typeof item == 'string' || item instanceof RegExp ) {
76
- item = { name: item };
77
- }
78
-
79
- this._patterns.push( item );
80
- }
81
- }
82
-
83
- /**
84
- * Matches elements for currently stored patterns. Returns match information about first found
85
- * {@link module:engine/view/element~Element element}, otherwise returns `null`.
86
- *
87
- * Example of returned object:
88
- *
89
- * {
90
- * element: <instance of found element>,
91
- * pattern: <pattern used to match found element>,
92
- * match: {
93
- * name: true,
94
- * attributes: [ 'title', 'href' ],
95
- * classes: [ 'foo' ],
96
- * styles: [ 'color', 'position' ]
97
- * }
98
- * }
99
- *
100
- * @see module:engine/view/matcher~Matcher#add
101
- * @see module:engine/view/matcher~Matcher#matchAll
102
- * @param {...module:engine/view/element~Element} element View element to match against stored patterns.
103
- * @returns {Object|null} result
104
- * @returns {module:engine/view/element~Element} result.element Matched view element.
105
- * @returns {Object|String|RegExp|Function} result.pattern Pattern that was used to find matched element.
106
- * @returns {Object} result.match Object representing matched element parts.
107
- * @returns {Boolean} [result.match.name] True if name of the element was matched.
108
- * @returns {Array} [result.match.attributes] Array with matched attribute names.
109
- * @returns {Array} [result.match.classes] Array with matched class names.
110
- * @returns {Array} [result.match.styles] Array with matched style names.
111
- */
112
- match( ...element ) {
113
- for ( const singleElement of element ) {
114
- for ( const pattern of this._patterns ) {
115
- const match = isElementMatching( singleElement, pattern );
116
-
117
- if ( match ) {
118
- return {
119
- element: singleElement,
120
- pattern,
121
- match
122
- };
123
- }
124
- }
125
- }
126
-
127
- return null;
128
- }
129
-
130
- /**
131
- * Matches elements for currently stored patterns. Returns array of match information with all found
132
- * {@link module:engine/view/element~Element elements}. If no element is found - returns `null`.
133
- *
134
- * @see module:engine/view/matcher~Matcher#add
135
- * @see module:engine/view/matcher~Matcher#match
136
- * @param {...module:engine/view/element~Element} element View element to match against stored patterns.
137
- * @returns {Array.<Object>|null} Array with match information about found elements or `null`. For more information
138
- * see {@link module:engine/view/matcher~Matcher#match match method} description.
139
- */
140
- matchAll( ...element ) {
141
- const results = [];
142
-
143
- for ( const singleElement of element ) {
144
- for ( const pattern of this._patterns ) {
145
- const match = isElementMatching( singleElement, pattern );
146
-
147
- if ( match ) {
148
- results.push( {
149
- element: singleElement,
150
- pattern,
151
- match
152
- } );
153
- }
154
- }
155
- }
156
-
157
- return results.length > 0 ? results : null;
158
- }
159
-
160
- /**
161
- * Returns the name of the element to match if there is exactly one pattern added to the matcher instance
162
- * and it matches element name defined by `string` (not `RegExp`). Otherwise, returns `null`.
163
- *
164
- * @returns {String|null} Element name trying to match.
165
- */
166
- getElementName() {
167
- if ( this._patterns.length !== 1 ) {
168
- return null;
169
- }
170
-
171
- const pattern = this._patterns[ 0 ];
172
- const name = pattern.name;
173
-
174
- return ( typeof pattern != 'function' && name && !( name instanceof RegExp ) ) ? name : null;
175
- }
12
+ /**
13
+ * Creates new instance of Matcher.
14
+ *
15
+ * @param {String|RegExp|Object|Function} [pattern] Match patterns. See {@link module:engine/view/matcher~Matcher#add add method} for
16
+ * more information.
17
+ */
18
+ constructor(...pattern) {
19
+ /**
20
+ * @private
21
+ * @type {Array<Object|Function>}
22
+ */
23
+ this._patterns = [];
24
+ this.add(...pattern);
25
+ }
26
+ /**
27
+ * Adds pattern or patterns to matcher instance.
28
+ *
29
+ * // String.
30
+ * matcher.add( 'div' );
31
+ *
32
+ * // Regular expression.
33
+ * matcher.add( /^\w/ );
34
+ *
35
+ * // Single class.
36
+ * matcher.add( {
37
+ * classes: 'foobar'
38
+ * } );
39
+ *
40
+ * See {@link module:engine/view/matcher~MatcherPattern} for more examples.
41
+ *
42
+ * Multiple patterns can be added in one call:
43
+ *
44
+ * matcher.add( 'div', { classes: 'foobar' } );
45
+ *
46
+ * @param {Object|String|RegExp|Function} pattern Object describing pattern details. If string or regular expression
47
+ * is provided it will be used to match element's name. Pattern can be also provided in a form
48
+ * of a function - then this function will be called with each {@link module:engine/view/element~Element element} as a parameter.
49
+ * Function's return value will be stored under `match` key of the object returned from
50
+ * {@link module:engine/view/matcher~Matcher#match match} or {@link module:engine/view/matcher~Matcher#matchAll matchAll} methods.
51
+ * @param {String|RegExp} [pattern.name] Name or regular expression to match element's name.
52
+ * @param {Object} [pattern.attributes] Object with key-value pairs representing attributes to match. Each object key
53
+ * represents attribute name. Value under that key can be either:
54
+ * * `true` - then attribute is just required (can be empty),
55
+ * * a string - then attribute has to be equal, or
56
+ * * a regular expression - then attribute has to match the expression.
57
+ * @param {String|RegExp|Array} [pattern.classes] Class name or array of class names to match. Each name can be
58
+ * provided in a form of string or regular expression.
59
+ * @param {Object} [pattern.styles] Object with key-value pairs representing styles to match. Each object key
60
+ * represents style name. Value under that key can be either a string or a regular expression and it will be used
61
+ * to match style value.
62
+ */
63
+ add(...pattern) {
64
+ for (let item of pattern) {
65
+ // String or RegExp pattern is used as element's name.
66
+ if (typeof item == 'string' || item instanceof RegExp) {
67
+ item = { name: item };
68
+ }
69
+ this._patterns.push(item);
70
+ }
71
+ }
72
+ /**
73
+ * Matches elements for currently stored patterns. Returns match information about first found
74
+ * {@link module:engine/view/element~Element element}, otherwise returns `null`.
75
+ *
76
+ * Example of returned object:
77
+ *
78
+ * {
79
+ * element: <instance of found element>,
80
+ * pattern: <pattern used to match found element>,
81
+ * match: {
82
+ * name: true,
83
+ * attributes: [ 'title', 'href' ],
84
+ * classes: [ 'foo' ],
85
+ * styles: [ 'color', 'position' ]
86
+ * }
87
+ * }
88
+ *
89
+ * @see module:engine/view/matcher~Matcher#add
90
+ * @see module:engine/view/matcher~Matcher#matchAll
91
+ * @param {...module:engine/view/element~Element} element View element to match against stored patterns.
92
+ * @returns {Object|null} result
93
+ * @returns {module:engine/view/element~Element} result.element Matched view element.
94
+ * @returns {Object|String|RegExp|Function} result.pattern Pattern that was used to find matched element.
95
+ * @returns {Object} result.match Object representing matched element parts.
96
+ * @returns {Boolean} [result.match.name] True if name of the element was matched.
97
+ * @returns {Array} [result.match.attributes] Array with matched attribute names.
98
+ * @returns {Array} [result.match.classes] Array with matched class names.
99
+ * @returns {Array} [result.match.styles] Array with matched style names.
100
+ */
101
+ match(...element) {
102
+ for (const singleElement of element) {
103
+ for (const pattern of this._patterns) {
104
+ const match = isElementMatching(singleElement, pattern);
105
+ if (match) {
106
+ return {
107
+ element: singleElement,
108
+ pattern,
109
+ match
110
+ };
111
+ }
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+ /**
117
+ * Matches elements for currently stored patterns. Returns array of match information with all found
118
+ * {@link module:engine/view/element~Element elements}. If no element is found - returns `null`.
119
+ *
120
+ * @see module:engine/view/matcher~Matcher#add
121
+ * @see module:engine/view/matcher~Matcher#match
122
+ * @param {...module:engine/view/element~Element} element View element to match against stored patterns.
123
+ * @returns {Array.<Object>|null} Array with match information about found elements or `null`. For more information
124
+ * see {@link module:engine/view/matcher~Matcher#match match method} description.
125
+ */
126
+ matchAll(...element) {
127
+ const results = [];
128
+ for (const singleElement of element) {
129
+ for (const pattern of this._patterns) {
130
+ const match = isElementMatching(singleElement, pattern);
131
+ if (match) {
132
+ results.push({
133
+ element: singleElement,
134
+ pattern,
135
+ match
136
+ });
137
+ }
138
+ }
139
+ }
140
+ return results.length > 0 ? results : null;
141
+ }
142
+ /**
143
+ * Returns the name of the element to match if there is exactly one pattern added to the matcher instance
144
+ * and it matches element name defined by `string` (not `RegExp`). Otherwise, returns `null`.
145
+ *
146
+ * @returns {String|null} Element name trying to match.
147
+ */
148
+ getElementName() {
149
+ if (this._patterns.length !== 1) {
150
+ return null;
151
+ }
152
+ const pattern = this._patterns[0];
153
+ const name = pattern.name;
154
+ return (typeof pattern != 'function' && name && !(name instanceof RegExp)) ? name : null;
155
+ }
176
156
  }
177
-
178
157
  // Returns match information if {@link module:engine/view/element~Element element} is matching provided pattern.
179
158
  // If element cannot be matched to provided pattern - returns `null`.
180
159
  //
181
160
  // @param {module:engine/view/element~Element} element
182
161
  // @param {Object|String|RegExp|Function} pattern
183
162
  // @returns {Object|null} Returns object with match information or null if element is not matching.
184
- function isElementMatching( element, pattern ) {
185
- // If pattern is provided as function - return result of that function;
186
- if ( typeof pattern == 'function' ) {
187
- return pattern( element );
188
- }
189
-
190
- const match = {};
191
- // Check element's name.
192
- if ( pattern.name ) {
193
- match.name = matchName( pattern.name, element.name );
194
-
195
- if ( !match.name ) {
196
- return null;
197
- }
198
- }
199
-
200
- // Check element's attributes.
201
- if ( pattern.attributes ) {
202
- match.attributes = matchAttributes( pattern.attributes, element );
203
-
204
- if ( !match.attributes ) {
205
- return null;
206
- }
207
- }
208
-
209
- // Check element's classes.
210
- if ( pattern.classes ) {
211
- match.classes = matchClasses( pattern.classes, element );
212
-
213
- if ( !match.classes ) {
214
- return false;
215
- }
216
- }
217
-
218
- // Check element's styles.
219
- if ( pattern.styles ) {
220
- match.styles = matchStyles( pattern.styles, element );
221
-
222
- if ( !match.styles ) {
223
- return false;
224
- }
225
- }
226
-
227
- return match;
163
+ function isElementMatching(element, pattern) {
164
+ // If pattern is provided as function - return result of that function;
165
+ if (typeof pattern == 'function') {
166
+ return pattern(element);
167
+ }
168
+ const match = {};
169
+ // Check element's name.
170
+ if (pattern.name) {
171
+ match.name = matchName(pattern.name, element.name);
172
+ if (!match.name) {
173
+ return null;
174
+ }
175
+ }
176
+ // Check element's attributes.
177
+ if (pattern.attributes) {
178
+ match.attributes = matchAttributes(pattern.attributes, element);
179
+ if (!match.attributes) {
180
+ return null;
181
+ }
182
+ }
183
+ // Check element's classes.
184
+ if (pattern.classes) {
185
+ match.classes = matchClasses(pattern.classes, element);
186
+ if (!match.classes) {
187
+ return null;
188
+ }
189
+ }
190
+ // Check element's styles.
191
+ if (pattern.styles) {
192
+ match.styles = matchStyles(pattern.styles, element);
193
+ if (!match.styles) {
194
+ return null;
195
+ }
196
+ }
197
+ return match;
228
198
  }
229
-
230
199
  // Checks if name can be matched by provided pattern.
231
200
  //
232
201
  // @param {String|RegExp} pattern
233
202
  // @param {String} name
234
203
  // @returns {Boolean} Returns `true` if name can be matched, `false` otherwise.
235
- function matchName( pattern, name ) {
236
- // If pattern is provided as RegExp - test against this regexp.
237
- if ( pattern instanceof RegExp ) {
238
- return !!name.match( pattern );
239
- }
240
-
241
- return pattern === name;
204
+ function matchName(pattern, name) {
205
+ // If pattern is provided as RegExp - test against this regexp.
206
+ if (pattern instanceof RegExp) {
207
+ return !!name.match(pattern);
208
+ }
209
+ return pattern === name;
242
210
  }
243
-
244
211
  // Checks if an array of key/value pairs can be matched against provided patterns.
245
212
  //
246
213
  // Patterns can be provided in a following ways:
@@ -291,31 +258,25 @@ function matchName( pattern, name ) {
291
258
  // @param {Iterable.<String>} keys Attribute, style or class keys.
292
259
  // @param {Function} valueGetter A function providing value for a given item key.
293
260
  // @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched.
294
- function matchPatterns( patterns, keys, valueGetter ) {
295
- const normalizedPatterns = normalizePatterns( patterns );
296
- const normalizedItems = Array.from( keys );
297
- const match = [];
298
-
299
- normalizedPatterns.forEach( ( [ patternKey, patternValue ] ) => {
300
- normalizedItems.forEach( itemKey => {
301
- if (
302
- isKeyMatched( patternKey, itemKey ) &&
303
- isValueMatched( patternValue, itemKey, valueGetter )
304
- ) {
305
- match.push( itemKey );
306
- }
307
- } );
308
- } );
309
-
310
- // Return matches only if there are at least as many of them as there are patterns.
311
- // The RegExp pattern can match more than one item.
312
- if ( !normalizedPatterns.length || match.length < normalizedPatterns.length ) {
313
- return null;
314
- }
315
-
316
- return match;
261
+ function matchPatterns(patterns, keys, valueGetter) {
262
+ const normalizedPatterns = normalizePatterns(patterns);
263
+ const normalizedItems = Array.from(keys);
264
+ const match = [];
265
+ normalizedPatterns.forEach(([patternKey, patternValue]) => {
266
+ normalizedItems.forEach(itemKey => {
267
+ if (isKeyMatched(patternKey, itemKey) &&
268
+ isValueMatched(patternValue, itemKey, valueGetter)) {
269
+ match.push(itemKey);
270
+ }
271
+ });
272
+ });
273
+ // Return matches only if there are at least as many of them as there are patterns.
274
+ // The RegExp pattern can match more than one item.
275
+ if (!normalizedPatterns.length || match.length < normalizedPatterns.length) {
276
+ return undefined;
277
+ }
278
+ return match;
317
279
  }
318
-
319
280
  // Bring all the possible pattern forms to an array of arrays where first item is a key and second is a value.
320
281
  //
321
282
  // Examples:
@@ -363,351 +324,93 @@ function matchPatterns( patterns, keys, valueGetter ) {
363
324
  //
364
325
  // @param {Object|Array} patterns
365
326
  // @returns {Array|null} Returns an array of objects or null if provided patterns were not in an expected form.
366
- function normalizePatterns( patterns ) {
367
- if ( Array.isArray( patterns ) ) {
368
- return patterns.map( pattern => {
369
- if ( isPlainObject( pattern ) ) {
370
- if ( pattern.key === undefined || pattern.value === undefined ) {
371
- // Documented at the end of matcher.js.
372
- logWarning( 'matcher-pattern-missing-key-or-value', pattern );
373
- }
374
-
375
- return [ pattern.key, pattern.value ];
376
- }
377
-
378
- // Assume the pattern is either String or RegExp.
379
- return [ pattern, true ];
380
- } );
381
- }
382
-
383
- if ( isPlainObject( patterns ) ) {
384
- return Object.entries( patterns );
385
- }
386
-
387
- // Other cases (true, string or regexp).
388
- return [ [ patterns, true ] ];
327
+ function normalizePatterns(patterns) {
328
+ if (Array.isArray(patterns)) {
329
+ return patterns.map((pattern) => {
330
+ if (isPlainObject(pattern)) {
331
+ if (pattern.key === undefined || pattern.value === undefined) {
332
+ // Documented at the end of matcher.js.
333
+ logWarning('matcher-pattern-missing-key-or-value', pattern);
334
+ }
335
+ return [pattern.key, pattern.value];
336
+ }
337
+ // Assume the pattern is either String or RegExp.
338
+ return [pattern, true];
339
+ });
340
+ }
341
+ if (isPlainObject(patterns)) {
342
+ return Object.entries(patterns);
343
+ }
344
+ // Other cases (true, string or regexp).
345
+ return [[patterns, true]];
389
346
  }
390
-
391
347
  // @param {String|RegExp} patternKey A pattern representing a key we want to match.
392
348
  // @param {String} itemKey An actual item key (e.g. `'src'`, `'background-color'`, `'ck-widget'`) we're testing against pattern.
393
349
  // @returns {Boolean}
394
- function isKeyMatched( patternKey, itemKey ) {
395
- return patternKey === true ||
396
- patternKey === itemKey ||
397
- patternKey instanceof RegExp && itemKey.match( patternKey );
350
+ function isKeyMatched(patternKey, itemKey) {
351
+ return patternKey === true ||
352
+ patternKey === itemKey ||
353
+ patternKey instanceof RegExp && itemKey.match(patternKey);
398
354
  }
399
-
400
355
  // @param {String|RegExp} patternValue A pattern representing a value we want to match.
401
356
  // @param {String} itemKey An item key, e.g. `background`, `href`, 'rel', etc.
402
357
  // @param {Function} valueGetter A function used to provide a value for a given `itemKey`.
403
358
  // @returns {Boolean}
404
- function isValueMatched( patternValue, itemKey, valueGetter ) {
405
- if ( patternValue === true ) {
406
- return true;
407
- }
408
-
409
- const itemValue = valueGetter( itemKey );
410
-
411
- // For now, the reducers are not returning the full tree of properties.
412
- // Casting to string preserves the old behavior until the root cause is fixed.
413
- // More can be found in https://github.com/ckeditor/ckeditor5/issues/10399.
414
- return patternValue === itemValue ||
415
- patternValue instanceof RegExp && !!String( itemValue ).match( patternValue );
359
+ function isValueMatched(patternValue, itemKey, valueGetter) {
360
+ if (patternValue === true) {
361
+ return true;
362
+ }
363
+ const itemValue = valueGetter(itemKey);
364
+ // For now, the reducers are not returning the full tree of properties.
365
+ // Casting to string preserves the old behavior until the root cause is fixed.
366
+ // More can be found in https://github.com/ckeditor/ckeditor5/issues/10399.
367
+ return patternValue === itemValue ||
368
+ patternValue instanceof RegExp && !!String(itemValue).match(patternValue);
416
369
  }
417
-
418
370
  // Checks if attributes of provided element can be matched against provided patterns.
419
371
  //
420
372
  // @param {Object} patterns Object with information about attributes to match. Each key of the object will be
421
373
  // used as attribute name. Value of each key can be a string or regular expression to match against attribute value.
422
374
  // @param {module:engine/view/element~Element} element Element which attributes will be tested.
423
375
  // @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched.
424
- function matchAttributes( patterns, element ) {
425
- const attributeKeys = new Set( element.getAttributeKeys() );
426
-
427
- // `style` and `class` attribute keys are deprecated. Only allow them in object pattern
428
- // for backward compatibility.
429
- if ( isPlainObject( patterns ) ) {
430
- if ( patterns.style !== undefined ) {
431
- // Documented at the end of matcher.js.
432
- logWarning( 'matcher-pattern-deprecated-attributes-style-key', patterns );
433
- }
434
- if ( patterns.class !== undefined ) {
435
- // Documented at the end of matcher.js.
436
- logWarning( 'matcher-pattern-deprecated-attributes-class-key', patterns );
437
- }
438
- } else {
439
- attributeKeys.delete( 'style' );
440
- attributeKeys.delete( 'class' );
441
- }
442
-
443
- return matchPatterns( patterns, attributeKeys, key => element.getAttribute( key ) );
376
+ function matchAttributes(patterns, element) {
377
+ const attributeKeys = new Set(element.getAttributeKeys());
378
+ // `style` and `class` attribute keys are deprecated. Only allow them in object pattern
379
+ // for backward compatibility.
380
+ if (isPlainObject(patterns)) {
381
+ if (patterns.style !== undefined) {
382
+ // Documented at the end of matcher.js.
383
+ logWarning('matcher-pattern-deprecated-attributes-style-key', patterns);
384
+ }
385
+ if (patterns.class !== undefined) {
386
+ // Documented at the end of matcher.js.
387
+ logWarning('matcher-pattern-deprecated-attributes-class-key', patterns);
388
+ }
389
+ }
390
+ else {
391
+ attributeKeys.delete('style');
392
+ attributeKeys.delete('class');
393
+ }
394
+ return matchPatterns(patterns, attributeKeys, key => element.getAttribute(key));
444
395
  }
445
-
446
396
  // Checks if classes of provided element can be matched against provided patterns.
447
397
  //
448
398
  // @param {Array.<String|RegExp>} patterns Array of strings or regular expressions to match against element's classes.
449
399
  // @param {module:engine/view/element~Element} element Element which classes will be tested.
450
400
  // @returns {Array|null} Returns array with matched class names or `null` if no classes were matched.
451
- function matchClasses( patterns, element ) {
452
- // We don't need `getter` here because patterns for classes are always normalized to `[ className, true ]`.
453
- return matchPatterns( patterns, element.getClassNames() );
401
+ function matchClasses(patterns, element) {
402
+ // We don't need `getter` here because patterns for classes are always normalized to `[ className, true ]`.
403
+ return matchPatterns(patterns, element.getClassNames(), /* istanbul ignore next */ () => { });
454
404
  }
455
-
456
405
  // Checks if styles of provided element can be matched against provided patterns.
457
406
  //
458
407
  // @param {Object} patterns Object with information about styles to match. Each key of the object will be
459
408
  // used as style name. Value of each key can be a string or regular expression to match against style value.
460
409
  // @param {module:engine/view/element~Element} element Element which styles will be tested.
461
410
  // @returns {Array|null} Returns array with matched style names or `null` if no styles were matched.
462
- function matchStyles( patterns, element ) {
463
- return matchPatterns( patterns, element.getStyleNames( true ), key => element.getStyle( key ) );
411
+ function matchStyles(patterns, element) {
412
+ return matchPatterns(patterns, element.getStyleNames(true), key => element.getStyle(key));
464
413
  }
465
-
466
- /**
467
- * An entity that is a valid pattern recognized by a matcher. `MatcherPattern` is used by {@link ~Matcher} to recognize
468
- * if a view element fits in a group of view elements described by the pattern.
469
- *
470
- * `MatcherPattern` can be given as a `String`, a `RegExp`, an `Object` or a `Function`.
471
- *
472
- * If `MatcherPattern` is given as a `String` or `RegExp`, it will match any view element that has a matching name:
473
- *
474
- * // Match any element with name equal to 'div'.
475
- * const pattern = 'div';
476
- *
477
- * // Match any element which name starts on 'p'.
478
- * const pattern = /^p/;
479
- *
480
- * If `MatcherPattern` is given as an `Object`, all the object's properties will be matched with view element properties.
481
- * If the view element does not meet all of the object's pattern properties, the match will not happen.
482
- * Available `Object` matching properties:
483
- *
484
- * Matching view element:
485
- *
486
- * // Match view element's name using String:
487
- * const pattern = { name: 'p' };
488
- *
489
- * // or by providing RegExp:
490
- * const pattern = { name: /^(ul|ol)$/ };
491
- *
492
- * // The name can also be skipped to match any view element with matching attributes:
493
- * const pattern = {
494
- * attributes: {
495
- * 'title': true
496
- * }
497
- * };
498
- *
499
- * Matching view element attributes:
500
- *
501
- * // Match view element with any attribute value.
502
- * const pattern = {
503
- * name: 'p',
504
- * attributes: true
505
- * };
506
- *
507
- * // Match view element which has matching attributes (String).
508
- * const pattern = {
509
- * name: 'figure',
510
- * attributes: 'title' // Match title attribute (can be empty).
511
- * };
512
- *
513
- * // Match view element which has matching attributes (RegExp).
514
- * const pattern = {
515
- * name: 'figure',
516
- * attributes: /^data-.*$/ // Match attributes starting with `data-` e.g. `data-foo` with any value (can be empty).
517
- * };
518
- *
519
- * // Match view element which has matching attributes (Object).
520
- * const pattern = {
521
- * name: 'figure',
522
- * attributes: {
523
- * title: 'foobar', // Match `title` attribute with 'foobar' value.
524
- * alt: true, // Match `alt` attribute with any value (can be empty).
525
- * 'data-type': /^(jpg|png)$/ // Match `data-type` attribute with `jpg` or `png` value.
526
- * }
527
- * };
528
- *
529
- * // Match view element which has matching attributes (Array).
530
- * const pattern = {
531
- * name: 'figure',
532
- * attributes: [
533
- * 'title', // Match `title` attribute (can be empty).
534
- * /^data-*$/ // Match attributes starting with `data-` e.g. `data-foo` with any value (can be empty).
535
- * ]
536
- * };
537
- *
538
- * // Match view element which has matching attributes (key-value pairs).
539
- * const pattern = {
540
- * name: 'input',
541
- * attributes: [
542
- * {
543
- * key: 'type', // Match `type` as an attribute key.
544
- * value: /^(text|number|date)$/ // Match `text`, `number` or `date` values.
545
- * },
546
- * {
547
- * key: /^data-.*$/, // Match attributes starting with `data-` e.g. `data-foo`.
548
- * value: true // Match any value (can be empty).
549
- * }
550
- * ]
551
- * };
552
- *
553
- * Matching view element styles:
554
- *
555
- * // Match view element with any style.
556
- * const pattern = {
557
- * name: 'p',
558
- * styles: true
559
- * };
560
- *
561
- * // Match view element which has matching styles (String).
562
- * const pattern = {
563
- * name: 'p',
564
- * styles: 'color' // Match attributes with `color` style.
565
- * };
566
- *
567
- * // Match view element which has matching styles (RegExp).
568
- * const pattern = {
569
- * name: 'p',
570
- * styles: /^border.*$/ // Match view element with any border style.
571
- * };
572
- *
573
- * // Match view element which has matching styles (Object).
574
- * const pattern = {
575
- * name: 'p',
576
- * styles: {
577
- * color: /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/, // Match `color` in RGB format only.
578
- * 'font-weight': 600, // Match `font-weight` only if it's `600`.
579
- * 'text-decoration': true // Match any text decoration.
580
- * }
581
- * };
582
- *
583
- * // Match view element which has matching styles (Array).
584
- * const pattern = {
585
- * name: 'p',
586
- * styles: [
587
- * 'color', // Match `color` with any value.
588
- * /^border.*$/ // Match all border properties.
589
- * ]
590
- * };
591
- *
592
- * // Match view element which has matching styles (key-value pairs).
593
- * const pattern = {
594
- * name: 'p',
595
- * styles: [
596
- * {
597
- * key: 'color', // Match `color` as an property key.
598
- * value: /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/ // Match RGB format only.
599
- * },
600
- * {
601
- * key: /^border.*$/, // Match any border style.
602
- * value: true // Match any value.
603
- * }
604
- * ]
605
- * };
606
- *
607
- * Matching view element classes:
608
- *
609
- * // Match view element with any class.
610
- * const pattern = {
611
- * name: 'p',
612
- * classes: true
613
- * };
614
- *
615
- * // Match view element which has matching class (String).
616
- * const pattern = {
617
- * name: 'p',
618
- * classes: 'highlighted' // Match `highlighted` class.
619
- * };
620
- *
621
- * // Match view element which has matching classes (RegExp).
622
- * const pattern = {
623
- * name: 'figure',
624
- * classes: /^image-side-(left|right)$/ // Match `image-side-left` or `image-side-right` class.
625
- * };
626
- *
627
- * // Match view element which has matching classes (Object).
628
- * const pattern = {
629
- * name: 'p',
630
- * classes: {
631
- * highlighted: true, // Match `highlighted` class.
632
- * marker: true // Match `marker` class.
633
- * }
634
- * };
635
- *
636
- * // Match view element which has matching classes (Array).
637
- * const pattern = {
638
- * name: 'figure',
639
- * classes: [
640
- * 'image', // Match `image` class.
641
- * /^image-side-(left|right)$/ // Match `image-side-left` or `image-side-right` class.
642
- * ]
643
- * };
644
- *
645
- * // Match view element which has matching classes (key-value pairs).
646
- * const pattern = {
647
- * name: 'figure',
648
- * classes: [
649
- * {
650
- * key: 'image', // Match `image` class.
651
- * value: true
652
- * },
653
- * {
654
- * key: /^image-side-(left|right)$/, // Match `image-side-left` or `image-side-right` class.
655
- * value: true
656
- * }
657
- * ]
658
- * };
659
- *
660
- * Pattern can combine multiple properties allowing for more complex view element matching:
661
- *
662
- * const pattern = {
663
- * name: 'span',
664
- * attributes: [ 'title' ],
665
- * styles: {
666
- * 'font-weight': 'bold'
667
- * },
668
- * classes: 'highlighted'
669
- * };
670
- *
671
- * If `MatcherPattern` is given as a `Function`, the function takes a view element as a first and only parameter and
672
- * the function should decide whether that element matches. If so, it should return what part of the view element has been matched.
673
- * Otherwise, the function should return `null`. The returned result will be included in `match` property of the object
674
- * returned by {@link ~Matcher#match} call.
675
- *
676
- * // Match an empty <div> element.
677
- * const pattern = element => {
678
- * if ( element.name == 'div' && element.childCount > 0 ) {
679
- * // Return which part of the element was matched.
680
- * return { name: true };
681
- * }
682
- *
683
- * return null;
684
- * };
685
- *
686
- * // Match a <p> element with big font ("heading-like" element).
687
- * const pattern = element => {
688
- * if ( element.name == 'p' ) {
689
- * const fontSize = element.getStyle( 'font-size' );
690
- * const size = fontSize.match( /(\d+)/px );
691
- *
692
- * if ( size && Number( size[ 1 ] ) > 26 ) {
693
- * return { name: true, attribute: [ 'font-size' ] };
694
- * }
695
- * }
696
- *
697
- * return null;
698
- * };
699
- *
700
- * `MatcherPattern` is defined in a way that it is a superset of {@link module:engine/view/elementdefinition~ElementDefinition},
701
- * that is, every `ElementDefinition` also can be used as a `MatcherPattern`.
702
- *
703
- * @typedef {String|RegExp|Object|Function} module:engine/view/matcher~MatcherPattern
704
- *
705
- * @property {String|RegExp} [name] View element name to match.
706
- * @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [classes] View element's classes to match.
707
- * @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [styles] View element's styles to match.
708
- * @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [attributes] View element's attributes to match.
709
- */
710
-
711
414
  /**
712
415
  * The key-value matcher pattern is missing key or value. Both must be present.
713
416
  * Refer the documentation: {@link module:engine/view/matcher~MatcherPattern}.
@@ -715,7 +418,6 @@ function matchStyles( patterns, element ) {
715
418
  * @param {Object} pattern Pattern with missing properties.
716
419
  * @error matcher-pattern-missing-key-or-value
717
420
  */
718
-
719
421
  /**
720
422
  * The key-value matcher pattern for `attributes` option is using deprecated `style` key.
721
423
  *
@@ -745,7 +447,6 @@ function matchStyles( patterns, element ) {
745
447
  * @param {Object} pattern Pattern with missing properties.
746
448
  * @error matcher-pattern-deprecated-attributes-style-key
747
449
  */
748
-
749
450
  /**
750
451
  * The key-value matcher pattern for `attributes` option is using deprecated `class` key.
751
452
  *