happy-dom 5.3.3 → 6.0.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.

Potentially problematic release.


This version of happy-dom might be problematic. Click here for more details.

Files changed (144) hide show
  1. package/.eslintrc.js +2 -2
  2. package/lib/base64/Base64.d.ts +23 -0
  3. package/lib/base64/Base64.js +89 -0
  4. package/lib/base64/Base64.js.map +1 -0
  5. package/lib/config/ElementTag.d.ts +2 -1
  6. package/lib/config/ElementTag.js +2 -1
  7. package/lib/config/ElementTag.js.map +1 -1
  8. package/lib/config/NonImplemenetedElementClasses.js +0 -1
  9. package/lib/config/NonImplemenetedElementClasses.js.map +1 -1
  10. package/lib/dom-implementation/DOMImplementation.d.ts +7 -1
  11. package/lib/dom-implementation/DOMImplementation.js +10 -2
  12. package/lib/dom-implementation/DOMImplementation.js.map +1 -1
  13. package/lib/dom-parser/DOMParser.d.ts +9 -4
  14. package/lib/dom-parser/DOMParser.js +13 -2
  15. package/lib/dom-parser/DOMParser.js.map +1 -1
  16. package/lib/exception/DOMExceptionNameEnum.d.ts +5 -1
  17. package/lib/exception/DOMExceptionNameEnum.js +4 -0
  18. package/lib/exception/DOMExceptionNameEnum.js.map +1 -1
  19. package/lib/fetch/Request.d.ts +8 -0
  20. package/lib/fetch/Request.js +15 -6
  21. package/lib/fetch/Request.js.map +1 -1
  22. package/lib/fetch/Response.d.ts +5 -0
  23. package/lib/fetch/Response.js +12 -6
  24. package/lib/fetch/Response.js.map +1 -1
  25. package/lib/file/FileReader.d.ts +7 -2
  26. package/lib/file/FileReader.js +9 -3
  27. package/lib/file/FileReader.js.map +1 -1
  28. package/lib/index.d.ts +4 -1
  29. package/lib/index.js +5 -1
  30. package/lib/index.js.map +1 -1
  31. package/lib/location/URL.js +1 -1
  32. package/lib/location/URL.js.map +1 -1
  33. package/lib/nodes/comment/Comment.d.ts +1 -1
  34. package/lib/nodes/document/Document.d.ts +18 -15
  35. package/lib/nodes/document/Document.js +36 -33
  36. package/lib/nodes/document/Document.js.map +1 -1
  37. package/lib/nodes/document/IDocument.d.ts +7 -0
  38. package/lib/nodes/document-fragment/DocumentFragment.d.ts +1 -1
  39. package/lib/nodes/document-type/DocumentType.d.ts +1 -1
  40. package/lib/nodes/element/DOMRectListFactory.d.ts +23 -0
  41. package/lib/nodes/element/DOMRectListFactory.js +33 -0
  42. package/lib/nodes/element/DOMRectListFactory.js.map +1 -0
  43. package/lib/nodes/element/Element.d.ts +6 -5
  44. package/lib/nodes/element/Element.js +9 -11
  45. package/lib/nodes/element/Element.js.map +1 -1
  46. package/lib/nodes/element/IDOMRectList.d.ts +11 -0
  47. package/lib/nodes/element/IDOMRectList.js +3 -0
  48. package/lib/nodes/element/IDOMRectList.js.map +1 -0
  49. package/lib/nodes/element/IElement.d.ts +5 -4
  50. package/lib/nodes/html-dialog-element/HTMLDialogElement.d.ts +31 -0
  51. package/lib/nodes/html-dialog-element/HTMLDialogElement.js +51 -0
  52. package/lib/nodes/html-dialog-element/HTMLDialogElement.js.map +1 -0
  53. package/lib/nodes/html-dialog-element/IHTMLDialogElement.d.ts +25 -0
  54. package/lib/nodes/html-dialog-element/IHTMLDialogElement.js +3 -0
  55. package/lib/nodes/html-dialog-element/IHTMLDialogElement.js.map +1 -0
  56. package/lib/nodes/html-template-element/HTMLTemplateElement.d.ts +1 -10
  57. package/lib/nodes/html-template-element/HTMLTemplateElement.js +2 -15
  58. package/lib/nodes/html-template-element/HTMLTemplateElement.js.map +1 -1
  59. package/lib/nodes/node/INode.d.ts +8 -6
  60. package/lib/nodes/node/Node.d.ts +20 -17
  61. package/lib/nodes/node/Node.js +19 -15
  62. package/lib/nodes/node/Node.js.map +1 -1
  63. package/lib/nodes/node/NodeTypeEnum.d.ts +10 -0
  64. package/lib/nodes/node/NodeTypeEnum.js +14 -0
  65. package/lib/nodes/node/NodeTypeEnum.js.map +1 -0
  66. package/lib/nodes/node/NodeUtility.d.ts +59 -0
  67. package/lib/nodes/node/NodeUtility.js +123 -0
  68. package/lib/nodes/node/NodeUtility.js.map +1 -0
  69. package/lib/nodes/text/IText.d.ts +9 -0
  70. package/lib/nodes/text/Text.d.ts +10 -1
  71. package/lib/nodes/text/Text.js +24 -0
  72. package/lib/nodes/text/Text.js.map +1 -1
  73. package/lib/range/IRangeBoundaryPoint.d.ts +8 -0
  74. package/lib/range/IRangeBoundaryPoint.js +3 -0
  75. package/lib/range/IRangeBoundaryPoint.js.map +1 -0
  76. package/lib/range/Range.d.ts +249 -0
  77. package/lib/range/Range.js +820 -0
  78. package/lib/range/Range.js.map +1 -0
  79. package/lib/range/RangeHowEnum.d.ts +7 -0
  80. package/lib/range/RangeHowEnum.js +11 -0
  81. package/lib/range/RangeHowEnum.js.map +1 -0
  82. package/lib/range/RangeUtility.d.ts +46 -0
  83. package/lib/range/RangeUtility.js +92 -0
  84. package/lib/range/RangeUtility.js.map +1 -0
  85. package/lib/selection/Selection.d.ts +167 -44
  86. package/lib/selection/Selection.js +369 -58
  87. package/lib/selection/Selection.js.map +1 -1
  88. package/lib/selection/SelectionDirectionEnum.d.ts +6 -0
  89. package/lib/selection/SelectionDirectionEnum.js +10 -0
  90. package/lib/selection/SelectionDirectionEnum.js.map +1 -0
  91. package/lib/window/IWindow.d.ts +22 -0
  92. package/lib/window/Window.d.ts +25 -9
  93. package/lib/window/Window.js +85 -35
  94. package/lib/window/Window.js.map +1 -1
  95. package/lib/xml-parser/XMLParser.d.ts +2 -2
  96. package/lib/xml-parser/XMLParser.js +5 -3
  97. package/lib/xml-parser/XMLParser.js.map +1 -1
  98. package/lib/xml-serializer/XMLSerializer.js +4 -1
  99. package/lib/xml-serializer/XMLSerializer.js.map +1 -1
  100. package/package.json +2 -2
  101. package/src/base64/Base64.ts +97 -0
  102. package/src/config/ElementTag.ts +2 -1
  103. package/src/config/NonImplemenetedElementClasses.ts +0 -1
  104. package/src/dom-implementation/DOMImplementation.ts +13 -2
  105. package/src/dom-parser/DOMParser.ts +20 -7
  106. package/src/exception/DOMExceptionNameEnum.ts +5 -1
  107. package/src/fetch/Request.ts +16 -6
  108. package/src/fetch/Response.ts +13 -6
  109. package/src/file/FileReader.ts +14 -4
  110. package/src/index.ts +7 -1
  111. package/src/location/URL.ts +1 -1
  112. package/src/nodes/document/Document.ts +47 -39
  113. package/src/nodes/document/IDocument.ts +8 -0
  114. package/src/nodes/element/DOMRectListFactory.ts +33 -0
  115. package/src/nodes/element/Element.ts +10 -11
  116. package/src/nodes/element/IDOMRectList.ts +11 -0
  117. package/src/nodes/element/IElement.ts +5 -4
  118. package/src/nodes/html-dialog-element/HTMLDialogElement.ts +47 -0
  119. package/src/nodes/html-dialog-element/IHTMLDialogElement.ts +29 -0
  120. package/src/nodes/html-template-element/HTMLTemplateElement.ts +3 -21
  121. package/src/nodes/node/INode.ts +8 -6
  122. package/src/nodes/node/Node.ts +24 -19
  123. package/src/nodes/node/NodeTypeEnum.ts +11 -0
  124. package/src/nodes/node/NodeUtility.ts +139 -0
  125. package/src/nodes/text/IText.ts +10 -0
  126. package/src/nodes/text/Text.ts +33 -0
  127. package/src/range/IRangeBoundaryPoint.ts +9 -0
  128. package/src/range/Range.ts +1057 -0
  129. package/src/range/RangeHowEnum.ts +8 -0
  130. package/src/range/RangeUtility.ts +114 -0
  131. package/src/selection/Selection.ts +444 -60
  132. package/src/selection/SelectionDirectionEnum.ts +7 -0
  133. package/src/window/IWindow.ts +24 -0
  134. package/src/window/Window.ts +104 -32
  135. package/src/xml-parser/XMLParser.ts +15 -7
  136. package/src/xml-serializer/XMLSerializer.ts +6 -1
  137. package/lib/nodes/element/Range.d.ts +0 -167
  138. package/lib/nodes/element/Range.js +0 -215
  139. package/lib/nodes/element/Range.js.map +0 -1
  140. package/lib/window/WindowBase64.d.ts +0 -19
  141. package/lib/window/WindowBase64.js +0 -88
  142. package/lib/window/WindowBase64.js.map +0 -1
  143. package/src/nodes/element/Range.ts +0 -237
  144. package/src/window/WindowBase64.ts +0 -95
@@ -0,0 +1,1057 @@
1
+ import INode from '../nodes/node/INode';
2
+ import IDocument from '../nodes/document/IDocument';
3
+ import IDocumentFragment from '../nodes/document-fragment/IDocumentFragment';
4
+ import DOMRect from '../nodes/element/DOMRect';
5
+ import RangeHowEnum from './RangeHowEnum';
6
+ import DOMException from '../exception/DOMException';
7
+ import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum';
8
+ import RangeUtility from './RangeUtility';
9
+ import NodeTypeEnum from '../nodes/node/NodeTypeEnum';
10
+ import NodeUtility from '../nodes/node/NodeUtility';
11
+ import XMLParser from '../xml-parser/XMLParser';
12
+ import IComment from '../nodes/comment/IComment';
13
+ import IText from '../nodes/text/IText';
14
+ import DOMRectListFactory from '../nodes/element/DOMRectListFactory';
15
+ import IDOMRectList from '../nodes/element/IDOMRectList';
16
+ import IRangeBoundaryPoint from './IRangeBoundaryPoint';
17
+
18
+ /**
19
+ * Range.
20
+ *
21
+ * Based on logic from:
22
+ * https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/range/Range-impl.js
23
+ *
24
+ * Reference:
25
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range.
26
+ */
27
+ export default class Range {
28
+ // Owner document is set by a sub-class in the Window constructor
29
+ public static _ownerDocument: IDocument = null;
30
+ public static readonly END_TO_END: number = RangeHowEnum.endToEnd;
31
+ public static readonly END_TO_START: number = RangeHowEnum.endToStart;
32
+ public static readonly START_TO_END: number = RangeHowEnum.startToEnd;
33
+ public static readonly START_TO_START: number = RangeHowEnum.startToStart;
34
+ public readonly END_TO_END: number = RangeHowEnum.endToEnd;
35
+ public readonly END_TO_START: number = RangeHowEnum.endToStart;
36
+ public readonly START_TO_END: number = RangeHowEnum.startToEnd;
37
+ public readonly START_TO_START: number = RangeHowEnum.startToStart;
38
+ public readonly _ownerDocument: IDocument = null;
39
+ public _start: IRangeBoundaryPoint = null;
40
+ public _end: IRangeBoundaryPoint = null;
41
+
42
+ /**
43
+ * Constructor.
44
+ */
45
+ constructor() {
46
+ this._ownerDocument = (<typeof Range>this.constructor)._ownerDocument;
47
+ this._start = { node: this._ownerDocument, offset: 0 };
48
+ this._end = { node: this._ownerDocument, offset: 0 };
49
+ }
50
+
51
+ /**
52
+ * Returns start container.
53
+ *
54
+ * @see https://dom.spec.whatwg.org/#dom-range-startcontainer
55
+ * @returns Start container.
56
+ */
57
+ public get startContainer(): INode {
58
+ return this._start.node;
59
+ }
60
+
61
+ /**
62
+ * Returns end container.
63
+ *
64
+ * @see https://dom.spec.whatwg.org/#dom-range-endcontainer
65
+ * @returns End container.
66
+ */
67
+ public get endContainer(): INode {
68
+ return this._end.node;
69
+ }
70
+
71
+ /**
72
+ * Returns start offset.
73
+ *
74
+ * @see https://dom.spec.whatwg.org/#dom-range-startoffset
75
+ * @returns Start offset.
76
+ */
77
+ public get startOffset(): number {
78
+ if (this._start.offset > 0) {
79
+ const length = NodeUtility.getNodeLength(this._start.node);
80
+ if (this._start.offset > length) {
81
+ this._start.offset = length;
82
+ } else if (length === 0) {
83
+ this._start.offset = 0;
84
+ }
85
+ }
86
+
87
+ return this._start.offset;
88
+ }
89
+
90
+ /**
91
+ * Returns end offset.
92
+ *
93
+ * @see https://dom.spec.whatwg.org/#dom-range-endoffset
94
+ * @returns End offset.
95
+ */
96
+ public get endOffset(): number {
97
+ if (this._end.offset > 0) {
98
+ const length = NodeUtility.getNodeLength(this._end.node);
99
+ if (this._end.offset > length) {
100
+ this._end.offset = length;
101
+ } else if (length === 0) {
102
+ this._end.offset = 0;
103
+ }
104
+ }
105
+
106
+ return this._end.offset;
107
+ }
108
+
109
+ /**
110
+ * Returns a boolean value indicating whether the range's start and end points are at the same position.
111
+ *
112
+ * @see https://dom.spec.whatwg.org/#dom-range-collapsed
113
+ * @returns Collapsed.
114
+ */
115
+ public get collapsed(): boolean {
116
+ return this._start.node === this._end.node && this.startOffset === this.endOffset;
117
+ }
118
+
119
+ /**
120
+ * Returns the deepest Node that contains the startContainer and endContainer nodes.
121
+ *
122
+ * @see https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer
123
+ * @returns Node.
124
+ */
125
+ public get commonAncestorContainer(): INode {
126
+ let container = this._start.node;
127
+
128
+ while (container) {
129
+ if (NodeUtility.isInclusiveAncestor(container, this._end.node)) {
130
+ return container;
131
+ }
132
+ container = container.parentNode;
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * Returns -1, 0, or 1 depending on whether the referenceNode is before, the same as, or after the Range.
140
+ *
141
+ * @see https://dom.spec.whatwg.org/#dom-range-collapse
142
+ * @param toStart A boolean value: true collapses the Range to its start, false to its end. If omitted, it defaults to false.
143
+ */
144
+ public collapse(toStart = false): void {
145
+ if (toStart) {
146
+ this._end = Object.assign({}, this._start);
147
+ } else {
148
+ this._start = Object.assign({}, this._end);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Compares the boundary points of the Range with those of another range.
154
+ *
155
+ * @see https://dom.spec.whatwg.org/#dom-range-compareboundarypoints
156
+ * @param how How.
157
+ * @param sourceRange Range.
158
+ * @returns A number, -1, 0, or 1, indicating whether the corresponding boundary-point of the Range is respectively before, equal to, or after the corresponding boundary-point of sourceRange.
159
+ */
160
+ public compareBoundaryPoints(how: RangeHowEnum, sourceRange: Range): number {
161
+ if (
162
+ how !== RangeHowEnum.startToStart &&
163
+ how !== RangeHowEnum.startToEnd &&
164
+ how !== RangeHowEnum.endToEnd &&
165
+ how !== RangeHowEnum.endToStart
166
+ ) {
167
+ throw new DOMException(
168
+ `The comparison method provided must be one of '${RangeHowEnum.startToStart}', '${RangeHowEnum.startToEnd}', '${RangeHowEnum.endToEnd}' or '${RangeHowEnum.endToStart}'.`,
169
+ DOMExceptionNameEnum.notSupportedError
170
+ );
171
+ }
172
+
173
+ if (this._ownerDocument !== sourceRange._ownerDocument) {
174
+ throw new DOMException(
175
+ `The two Ranges are not in the same tree.`,
176
+ DOMExceptionNameEnum.wrongDocumentError
177
+ );
178
+ }
179
+
180
+ const thisPoint: { node: INode; offset: number } = {
181
+ node: null,
182
+ offset: 0
183
+ };
184
+ const sourcePoint: { node: INode; offset: number } = {
185
+ node: null,
186
+ offset: 0
187
+ };
188
+
189
+ switch (how) {
190
+ case RangeHowEnum.startToStart:
191
+ thisPoint.node = this._start.node;
192
+ thisPoint.offset = this.startOffset;
193
+ sourcePoint.node = sourceRange._start.node;
194
+ sourcePoint.offset = sourceRange.startOffset;
195
+ break;
196
+ case RangeHowEnum.startToEnd:
197
+ thisPoint.node = this._end.node;
198
+ thisPoint.offset = this.endOffset;
199
+ sourcePoint.node = sourceRange._start.node;
200
+ sourcePoint.offset = sourceRange.startOffset;
201
+ break;
202
+ case RangeHowEnum.endToEnd:
203
+ thisPoint.node = this._end.node;
204
+ thisPoint.offset = this.endOffset;
205
+ sourcePoint.node = sourceRange._end.node;
206
+ sourcePoint.offset = sourceRange.endOffset;
207
+ break;
208
+ case RangeHowEnum.endToStart:
209
+ thisPoint.node = this._start.node;
210
+ thisPoint.offset = this.startOffset;
211
+ sourcePoint.node = sourceRange._end.node;
212
+ sourcePoint.offset = sourceRange.endOffset;
213
+ break;
214
+ }
215
+
216
+ return RangeUtility.compareBoundaryPointsPosition(thisPoint, sourcePoint);
217
+ }
218
+
219
+ /**
220
+ * Returns -1, 0, or 1 depending on whether the referenceNode is before, the same as, or after the Range.
221
+ *
222
+ * @see https://dom.spec.whatwg.org/#dom-range-comparepoint
223
+ * @param node Reference node.
224
+ * @param offset Offset.
225
+ * @returns -1,0, or 1.
226
+ */
227
+ public comparePoint(node: INode, offset): number {
228
+ if (node.ownerDocument !== this._ownerDocument) {
229
+ throw new DOMException(
230
+ `The two Ranges are not in the same tree.`,
231
+ DOMExceptionNameEnum.wrongDocumentError
232
+ );
233
+ }
234
+
235
+ RangeUtility.validateBoundaryPoint({ node, offset });
236
+
237
+ const boundaryPoint = { node, offset };
238
+
239
+ if (
240
+ RangeUtility.compareBoundaryPointsPosition(boundaryPoint, {
241
+ node: this._start.node,
242
+ offset: this.startOffset
243
+ }) === -1
244
+ ) {
245
+ return -1;
246
+ } else if (
247
+ RangeUtility.compareBoundaryPointsPosition(boundaryPoint, {
248
+ node: this._end.node,
249
+ offset: this.endOffset
250
+ }) === 1
251
+ ) {
252
+ return 1;
253
+ }
254
+
255
+ return 0;
256
+ }
257
+
258
+ /**
259
+ * Returns a DocumentFragment copying the objects of type Node included in the Range.
260
+ *
261
+ * @see https://dom.spec.whatwg.org/#concept-range-clone
262
+ * @returns Document fragment.
263
+ */
264
+ public cloneContents(): IDocumentFragment {
265
+ const fragment = this._ownerDocument.createDocumentFragment();
266
+ const startOffset = this.startOffset;
267
+ const endOffset = this.endOffset;
268
+
269
+ if (this.collapsed) {
270
+ return fragment;
271
+ }
272
+
273
+ if (
274
+ this._start.node === this._end.node &&
275
+ (this._start.node.nodeType === NodeTypeEnum.textNode ||
276
+ this._start.node.nodeType === NodeTypeEnum.processingInstructionNode ||
277
+ this._start.node.nodeType === NodeTypeEnum.commentNode)
278
+ ) {
279
+ const clone = (<IText | IComment>this._start.node).cloneNode(false);
280
+ clone['_data'] = clone.substringData(startOffset, endOffset - startOffset);
281
+ fragment.appendChild(clone);
282
+ return fragment;
283
+ }
284
+
285
+ let commonAncestor = this._start.node;
286
+ while (!NodeUtility.isInclusiveAncestor(commonAncestor, this._end.node)) {
287
+ commonAncestor = commonAncestor.parentNode;
288
+ }
289
+
290
+ let firstPartialContainedChild = null;
291
+ if (!NodeUtility.isInclusiveAncestor(this._start.node, this._end.node)) {
292
+ let candidate = commonAncestor.firstChild;
293
+ while (!firstPartialContainedChild) {
294
+ if (RangeUtility.isPartiallyContained(candidate, this)) {
295
+ firstPartialContainedChild = candidate;
296
+ }
297
+
298
+ candidate = candidate.nextSibling;
299
+ }
300
+ }
301
+
302
+ let lastPartiallyContainedChild = null;
303
+ if (!NodeUtility.isInclusiveAncestor(this._end.node, this._start.node)) {
304
+ let candidate = commonAncestor.lastChild;
305
+ while (!lastPartiallyContainedChild) {
306
+ if (RangeUtility.isPartiallyContained(candidate, this)) {
307
+ lastPartiallyContainedChild = candidate;
308
+ }
309
+
310
+ candidate = candidate.previousSibling;
311
+ }
312
+ }
313
+
314
+ const containedChildren = [];
315
+
316
+ for (const node of commonAncestor.childNodes) {
317
+ if (RangeUtility.isContained(node, this)) {
318
+ if (node.nodeType === NodeTypeEnum.documentTypeNode) {
319
+ throw new DOMException(
320
+ 'Invalid document type element.',
321
+ DOMExceptionNameEnum.hierarchyRequestError
322
+ );
323
+ }
324
+ containedChildren.push(node);
325
+ }
326
+ }
327
+
328
+ if (
329
+ firstPartialContainedChild !== null &&
330
+ (firstPartialContainedChild.nodeType === NodeTypeEnum.textNode ||
331
+ firstPartialContainedChild.nodeType === NodeTypeEnum.processingInstructionNode ||
332
+ firstPartialContainedChild.nodeType === NodeTypeEnum.commentNode)
333
+ ) {
334
+ const clone = (<IText | IComment>this._start.node).cloneNode(false);
335
+ clone['_data'] = clone.substringData(
336
+ startOffset,
337
+ NodeUtility.getNodeLength(this._start.node) - startOffset
338
+ );
339
+
340
+ fragment.appendChild(clone);
341
+ } else if (firstPartialContainedChild !== null) {
342
+ const clone = firstPartialContainedChild.cloneNode();
343
+ fragment.appendChild(clone);
344
+
345
+ const subRange = new Range();
346
+ subRange._start.node = this._end.node;
347
+ subRange._start.offset = endOffset;
348
+ subRange._end.node = firstPartialContainedChild;
349
+ subRange._end.offset = NodeUtility.getNodeLength(firstPartialContainedChild);
350
+
351
+ const subDocumentFragment = subRange.cloneContents();
352
+ clone.appendChild(subDocumentFragment);
353
+ }
354
+
355
+ for (const containedChild of containedChildren) {
356
+ const clone = containedChild.cloneNode(true);
357
+ fragment.appendChild(clone);
358
+ }
359
+
360
+ if (
361
+ lastPartiallyContainedChild !== null &&
362
+ (lastPartiallyContainedChild.nodeType === NodeTypeEnum.textNode ||
363
+ lastPartiallyContainedChild.nodeType === NodeTypeEnum.processingInstructionNode ||
364
+ lastPartiallyContainedChild.nodeType === NodeTypeEnum.commentNode)
365
+ ) {
366
+ const clone = (<IText | IComment>this._end.node).cloneNode(false);
367
+ clone['_data'] = clone.substringData(0, endOffset);
368
+
369
+ fragment.appendChild(clone);
370
+ } else if (lastPartiallyContainedChild !== null) {
371
+ const clone = lastPartiallyContainedChild.cloneNode(false);
372
+ fragment.appendChild(clone);
373
+
374
+ const subRange = new Range();
375
+ subRange._start.node = lastPartiallyContainedChild;
376
+ subRange._start.offset = 0;
377
+ subRange._end.node = this._end.node;
378
+ subRange._end.offset = endOffset;
379
+
380
+ const subFragment = subRange.cloneContents();
381
+ clone.appendChild(subFragment);
382
+ }
383
+
384
+ return fragment;
385
+ }
386
+
387
+ /**
388
+ * Returns a Range object with boundary points identical to the cloned Range.
389
+ *
390
+ * @see https://dom.spec.whatwg.org/#dom-range-clonerange
391
+ * @returns Range.
392
+ */
393
+ public cloneRange(): Range {
394
+ const clone = new Range();
395
+
396
+ clone._start.node = this._start.node;
397
+ clone._start.offset = this._start.offset;
398
+ clone._end.node = this._end.node;
399
+ clone._end.offset = this._end.offset;
400
+
401
+ return clone;
402
+ }
403
+
404
+ /**
405
+ * Returns a DocumentFragment by invoking the HTML fragment parsing algorithm or the XML fragment parsing algorithm with the start of the range (the parent of the selected node) as the context node. The HTML fragment parsing algorithm is used if the range belongs to a Document whose HTMLness bit is set. In the HTML case, if the context node would be html, for historical reasons the fragment parsing algorithm is invoked with body as the context instead.
406
+ *
407
+ * @see https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm
408
+ * @param tagString Tag string.
409
+ * @returns Document fragment.
410
+ */
411
+ public createContextualFragment(tagString: string): IDocumentFragment {
412
+ // TODO: We only have support for HTML in the parser currently, so it is not necessary to check which context it is
413
+ return XMLParser.parse(this._ownerDocument, tagString);
414
+ }
415
+
416
+ /**
417
+ * Removes the contents of the Range from the Document.
418
+ *
419
+ * @see https://dom.spec.whatwg.org/#dom-range-deletecontents
420
+ */
421
+ public deleteContents(): void {
422
+ const startOffset = this.startOffset;
423
+ const endOffset = this.endOffset;
424
+
425
+ if (this.collapsed) {
426
+ return;
427
+ }
428
+
429
+ if (
430
+ this._start.node === this._end.node &&
431
+ (this._start.node.nodeType === NodeTypeEnum.textNode ||
432
+ this._start.node.nodeType === NodeTypeEnum.processingInstructionNode ||
433
+ this._start.node.nodeType === NodeTypeEnum.commentNode)
434
+ ) {
435
+ (<IText | IComment>this._start.node).replaceData(startOffset, endOffset - startOffset, '');
436
+ return;
437
+ }
438
+
439
+ const nodesToRemove = [];
440
+ let currentNode = this._start.node;
441
+ const endNode = NodeUtility.nextDecendantNode(this._end.node);
442
+ while (currentNode && currentNode !== endNode) {
443
+ if (
444
+ RangeUtility.isContained(currentNode, this) &&
445
+ !RangeUtility.isContained(currentNode.parentNode, this)
446
+ ) {
447
+ nodesToRemove.push(currentNode);
448
+ }
449
+
450
+ currentNode = NodeUtility.following(currentNode);
451
+ }
452
+
453
+ let newNode;
454
+ let newOffset;
455
+ if (NodeUtility.isInclusiveAncestor(this._start.node, this._end.node)) {
456
+ newNode = this._start.node;
457
+ newOffset = startOffset;
458
+ } else {
459
+ let referenceNode = this._start.node;
460
+
461
+ while (
462
+ referenceNode &&
463
+ !NodeUtility.isInclusiveAncestor(referenceNode.parentNode, this._end.node)
464
+ ) {
465
+ referenceNode = referenceNode.parentNode;
466
+ }
467
+
468
+ newNode = referenceNode.parentNode;
469
+ newOffset = referenceNode.parentNode.childNodes.indexOf(referenceNode) + 1;
470
+ }
471
+
472
+ if (
473
+ this._start.node.nodeType === NodeTypeEnum.textNode ||
474
+ this._start.node.nodeType === NodeTypeEnum.processingInstructionNode ||
475
+ this._start.node.nodeType === NodeTypeEnum.commentNode
476
+ ) {
477
+ (<IText | IComment>this._start.node).replaceData(
478
+ this.startOffset,
479
+ NodeUtility.getNodeLength(this._start.node) - this.startOffset,
480
+ ''
481
+ );
482
+ }
483
+
484
+ for (const node of nodesToRemove) {
485
+ const parent = node.parentNode;
486
+ parent.removeChild(node);
487
+ }
488
+
489
+ if (
490
+ this._end.node.nodeType === NodeTypeEnum.textNode ||
491
+ this._end.node.nodeType === NodeTypeEnum.processingInstructionNode ||
492
+ this._end.node.nodeType === NodeTypeEnum.commentNode
493
+ ) {
494
+ (<IText | IComment>this._end.node).replaceData(0, endOffset, '');
495
+ }
496
+
497
+ this._start.node = newNode;
498
+ this._start.offset = newOffset;
499
+ this._end.node = newNode;
500
+ this._end.offset = newOffset;
501
+ }
502
+
503
+ /**
504
+ * Does nothing. It used to disable the Range object and enable the browser to release associated resources. The method has been kept for compatibility.
505
+ *
506
+ * @see https://dom.spec.whatwg.org/#dom-range-detach
507
+ */
508
+ public detach(): void {
509
+ // Do nothing by spec
510
+ }
511
+
512
+ /**
513
+ * Moves contents of the Range from the document tree into a DocumentFragment.
514
+ *
515
+ * @see https://dom.spec.whatwg.org/#dom-range-extractcontents
516
+ * @returns Document fragment.
517
+ */
518
+ public extractContents(): IDocumentFragment {
519
+ const fragment = this._ownerDocument.createDocumentFragment();
520
+ const startOffset = this.startOffset;
521
+ const endOffset = this.endOffset;
522
+
523
+ if (this.collapsed) {
524
+ return fragment;
525
+ }
526
+
527
+ if (
528
+ this._start.node === this._end.node &&
529
+ (this._start.node.nodeType === NodeTypeEnum.textNode ||
530
+ this._start.node.nodeType === NodeTypeEnum.processingInstructionNode ||
531
+ this._start.node.nodeType === NodeTypeEnum.commentNode)
532
+ ) {
533
+ const clone = <IText | IComment>this._start.node.cloneNode(false);
534
+ clone['_data'] = clone.substringData(startOffset, endOffset - startOffset);
535
+
536
+ fragment.appendChild(clone);
537
+
538
+ (<IText | IComment>this._start.node).replaceData(startOffset, endOffset - startOffset, '');
539
+
540
+ return fragment;
541
+ }
542
+
543
+ let commonAncestor = this._start.node;
544
+ while (!NodeUtility.isInclusiveAncestor(commonAncestor, this._end.node)) {
545
+ commonAncestor = commonAncestor.parentNode;
546
+ }
547
+
548
+ let firstPartialContainedChild = null;
549
+ if (!NodeUtility.isInclusiveAncestor(this._start.node, this._end.node)) {
550
+ let candidate = commonAncestor.firstChild;
551
+ while (!firstPartialContainedChild) {
552
+ if (RangeUtility.isPartiallyContained(candidate, this)) {
553
+ firstPartialContainedChild = candidate;
554
+ }
555
+
556
+ candidate = candidate.nextSibling;
557
+ }
558
+ }
559
+
560
+ let lastPartiallyContainedChild = null;
561
+ if (!NodeUtility.isInclusiveAncestor(this._end.node, this._start.node)) {
562
+ let candidate = commonAncestor.lastChild;
563
+ while (!lastPartiallyContainedChild) {
564
+ if (RangeUtility.isPartiallyContained(candidate, this)) {
565
+ lastPartiallyContainedChild = candidate;
566
+ }
567
+
568
+ candidate = candidate.previousSibling;
569
+ }
570
+ }
571
+
572
+ const containedChildren = [];
573
+
574
+ for (const node of commonAncestor.childNodes) {
575
+ if (RangeUtility.isContained(node, this)) {
576
+ if (node.nodeType === NodeTypeEnum.documentTypeNode) {
577
+ throw new DOMException(
578
+ 'Invalid document type element.',
579
+ DOMExceptionNameEnum.hierarchyRequestError
580
+ );
581
+ }
582
+ containedChildren.push(node);
583
+ }
584
+ }
585
+
586
+ let newNode;
587
+ let newOffset;
588
+ if (NodeUtility.isInclusiveAncestor(this._start.node, this._end.node)) {
589
+ newNode = this._start.node;
590
+ newOffset = startOffset;
591
+ } else {
592
+ let referenceNode = this._start.node;
593
+
594
+ while (
595
+ referenceNode &&
596
+ !NodeUtility.isInclusiveAncestor(referenceNode.parentNode, this._end.node)
597
+ ) {
598
+ referenceNode = referenceNode.parentNode;
599
+ }
600
+
601
+ newNode = referenceNode.parentNode;
602
+ newOffset = referenceNode.parentNode.childNodes.indexOf(referenceNode) + 1;
603
+ }
604
+
605
+ if (
606
+ firstPartialContainedChild !== null &&
607
+ (firstPartialContainedChild.nodeType === NodeTypeEnum.textNode ||
608
+ firstPartialContainedChild.nodeType === NodeTypeEnum.processingInstructionNode ||
609
+ firstPartialContainedChild.nodeType === NodeTypeEnum.commentNode)
610
+ ) {
611
+ const clone = <IText | IComment>this._start.node.cloneNode(false);
612
+ clone['_data'] = clone.substringData(
613
+ startOffset,
614
+ NodeUtility.getNodeLength(this._start.node) - startOffset
615
+ );
616
+
617
+ fragment.appendChild(clone);
618
+
619
+ (<IText | IComment>this._start.node).replaceData(
620
+ startOffset,
621
+ NodeUtility.getNodeLength(this._start.node) - startOffset,
622
+ ''
623
+ );
624
+ } else if (firstPartialContainedChild !== null) {
625
+ const clone = firstPartialContainedChild.cloneNode(false);
626
+ fragment.appendChild(clone);
627
+
628
+ const subRange = new Range();
629
+ subRange._start.node = this._start.node;
630
+ subRange._start.offset = startOffset;
631
+ subRange._end.node = firstPartialContainedChild;
632
+ subRange._end.offset = NodeUtility.getNodeLength(firstPartialContainedChild);
633
+
634
+ const subFragment = subRange.extractContents();
635
+ clone.appendChild(subFragment);
636
+ }
637
+
638
+ for (const containedChild of containedChildren) {
639
+ fragment.appendChild(containedChild);
640
+ }
641
+
642
+ if (
643
+ lastPartiallyContainedChild !== null &&
644
+ (lastPartiallyContainedChild.nodeType === NodeTypeEnum.textNode ||
645
+ lastPartiallyContainedChild.nodeType === NodeTypeEnum.processingInstructionNode ||
646
+ lastPartiallyContainedChild.nodeType === NodeTypeEnum.commentNode)
647
+ ) {
648
+ const clone = <IText | IComment>this._end.node.cloneNode(false);
649
+ clone['_data'] = clone.substringData(0, endOffset);
650
+
651
+ fragment.appendChild(clone);
652
+
653
+ (<IText | IComment>this._end.node).replaceData(0, endOffset, '');
654
+ } else if (lastPartiallyContainedChild !== null) {
655
+ const clone = lastPartiallyContainedChild.cloneNode(false);
656
+ fragment.appendChild(clone);
657
+
658
+ const subRange = new Range();
659
+ subRange._start.node = lastPartiallyContainedChild;
660
+ subRange._start.offset = 0;
661
+ subRange._end.node = this._end.node;
662
+ subRange._end.offset = endOffset;
663
+
664
+ const subFragment = subRange.extractContents();
665
+ clone.appendChild(subFragment);
666
+ }
667
+
668
+ this._start.node = newNode;
669
+ this._start.offset = newOffset;
670
+ this._end.node = newNode;
671
+ this._end.offset = newOffset;
672
+
673
+ return fragment;
674
+ }
675
+
676
+ /**
677
+ * Returns a DOMRect object that bounds the contents of the range; this is a rectangle enclosing the union of the bounding rectangles for all the elements in the range.
678
+ *
679
+ * @returns DOMRect object.
680
+ */
681
+ public getBoundingClientRect(): DOMRect {
682
+ // TODO: Not full implementation
683
+ return new DOMRect();
684
+ }
685
+
686
+ /**
687
+ * The Range.getClientRects() method returns a list of DOMRect objects representing the area of the screen occupied by the range. This is created by aggregating the results of calls to Element.getClientRects() for all the elements in the range.
688
+ *
689
+ * @returns DOMRect objects.
690
+ */
691
+ public getClientRects(): IDOMRectList<DOMRect> {
692
+ // TODO: Not full implementation
693
+ return DOMRectListFactory.create();
694
+ }
695
+
696
+ /**
697
+ * Returns a boolean indicating whether the given point is in the Range.
698
+ *
699
+ * @see https://dom.spec.whatwg.org/#dom-range-ispointinrange
700
+ * @param node Reference node.
701
+ * @param offset Offset.
702
+ * @returns "true" if in range.
703
+ */
704
+ public isPointInRange(node: INode, offset = 0): boolean {
705
+ if (node.ownerDocument !== this._ownerDocument) {
706
+ return false;
707
+ }
708
+
709
+ const boundaryPoint = { node, offset };
710
+
711
+ RangeUtility.validateBoundaryPoint(boundaryPoint);
712
+
713
+ if (
714
+ RangeUtility.compareBoundaryPointsPosition(boundaryPoint, {
715
+ node: this._start.node,
716
+ offset: this.startOffset
717
+ }) === -1 ||
718
+ RangeUtility.compareBoundaryPointsPosition(boundaryPoint, {
719
+ node: this._end.node,
720
+ offset: this.endOffset
721
+ }) === 1
722
+ ) {
723
+ return false;
724
+ }
725
+
726
+ return true;
727
+ }
728
+
729
+ /**
730
+ * Inserts a node at the start of the Range.
731
+ *
732
+ * @see https://dom.spec.whatwg.org/#concept-range-insert
733
+ * @param newNode New node.
734
+ */
735
+ public insertNode(newNode: INode): void {
736
+ if (
737
+ this._start.node.nodeType === NodeTypeEnum.processingInstructionNode ||
738
+ this._start.node.nodeType === NodeTypeEnum.commentNode ||
739
+ (this._start.node.nodeType === NodeTypeEnum.textNode && !this._start.node.parentNode) ||
740
+ newNode === this._start.node
741
+ ) {
742
+ throw new DOMException('Invalid start node.', DOMExceptionNameEnum.hierarchyRequestError);
743
+ }
744
+
745
+ let referenceNode =
746
+ this._start.node.nodeType === NodeTypeEnum.textNode
747
+ ? this._start.node
748
+ : this._start.node.childNodes[this.startOffset] || null;
749
+ const parent = !referenceNode ? this._start.node : referenceNode.parentNode;
750
+
751
+ if (this._start.node.nodeType === NodeTypeEnum.textNode) {
752
+ referenceNode = (<IText>this._start.node).splitText(this.startOffset);
753
+ }
754
+
755
+ if (newNode === referenceNode) {
756
+ referenceNode = referenceNode.nextSibling;
757
+ }
758
+
759
+ const nodeParent = newNode.parentNode;
760
+ if (nodeParent) {
761
+ nodeParent.removeChild(newNode);
762
+ }
763
+
764
+ let newOffset = !referenceNode
765
+ ? NodeUtility.getNodeLength(parent)
766
+ : referenceNode.parentNode.childNodes.indexOf(referenceNode);
767
+ newOffset +=
768
+ newNode.nodeType === NodeTypeEnum.documentFragmentNode
769
+ ? NodeUtility.getNodeLength(newNode)
770
+ : 1;
771
+
772
+ parent.insertBefore(newNode, referenceNode);
773
+
774
+ if (this.collapsed) {
775
+ this._end.node = parent;
776
+ this._end.offset = newOffset;
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Returns a boolean indicating whether the given Node intersects the Range.
782
+ *
783
+ * @see https://dom.spec.whatwg.org/#dom-range-intersectsnode
784
+ * @param node Reference node.
785
+ * @returns "true" if it intersects.
786
+ */
787
+ public intersectsNode(node: INode): boolean {
788
+ if (node.ownerDocument !== this._ownerDocument) {
789
+ return false;
790
+ }
791
+
792
+ const parent = node.parentNode;
793
+
794
+ if (!parent) {
795
+ return true;
796
+ }
797
+
798
+ const offset = parent.childNodes.indexOf(node);
799
+
800
+ return (
801
+ RangeUtility.compareBoundaryPointsPosition(
802
+ { node: parent, offset },
803
+ { node: this._end.node, offset: this.endOffset }
804
+ ) === -1 &&
805
+ RangeUtility.compareBoundaryPointsPosition(
806
+ { node: parent, offset: offset + 1 },
807
+ { node: this._start.node, offset: this.startOffset }
808
+ ) === 1
809
+ );
810
+ }
811
+
812
+ /**
813
+ * Sets the Range to contain the Node and its contents.
814
+ *
815
+ * @see https://dom.spec.whatwg.org/#concept-range-select
816
+ * @param node Reference node.
817
+ */
818
+ public selectNode(node: INode): void {
819
+ if (!node.parentNode) {
820
+ throw new DOMException(
821
+ `The given Node has no parent.`,
822
+ DOMExceptionNameEnum.invalidNodeTypeError
823
+ );
824
+ }
825
+
826
+ const index = node.parentNode.childNodes.indexOf(node);
827
+
828
+ this._start.node = node.parentNode;
829
+ this._start.offset = index;
830
+ this._end.node = node.parentNode;
831
+ this._end.offset = index + 1;
832
+ }
833
+
834
+ /**
835
+ * Sets the Range to contain the contents of a Node.
836
+ *
837
+ * @see https://dom.spec.whatwg.org/#dom-range-selectnodecontents
838
+ * @param node Reference node.
839
+ */
840
+ public selectNodeContents(node: INode): void {
841
+ if (node.nodeType === NodeTypeEnum.documentTypeNode) {
842
+ throw new DOMException(
843
+ "DocumentType Node can't be used as boundary point.",
844
+ DOMExceptionNameEnum.invalidNodeTypeError
845
+ );
846
+ }
847
+
848
+ this._start.node = node;
849
+ this._start.offset = 0;
850
+ this._end.node = node;
851
+ this._end.offset = NodeUtility.getNodeLength(node);
852
+ }
853
+
854
+ /**
855
+ * Sets the end position of a Range to be located at the given offset into the specified node x.
856
+ *
857
+ * @see https://dom.spec.whatwg.org/#dom-range-setend
858
+ * @param node End node.
859
+ * @param offset End offset.
860
+ */
861
+ public setEnd(node: INode, offset = 0): void {
862
+ RangeUtility.validateBoundaryPoint({ node, offset });
863
+
864
+ const boundaryPoint = { node, offset };
865
+
866
+ if (
867
+ node.ownerDocument !== this._ownerDocument ||
868
+ RangeUtility.compareBoundaryPointsPosition(boundaryPoint, {
869
+ node: this._start.node,
870
+ offset: this.startOffset
871
+ }) === -1
872
+ ) {
873
+ this._start.node = node;
874
+ this._start.offset = offset;
875
+ }
876
+
877
+ this._end.node = node;
878
+ this._end.offset = offset;
879
+ }
880
+
881
+ /**
882
+ * Sets the start position of a Range.
883
+ *
884
+ * @see https://dom.spec.whatwg.org/#dom-range-setstart
885
+ * @param node Start node.
886
+ * @param offset Start offset.
887
+ */
888
+ public setStart(node: INode, offset = 0): void {
889
+ RangeUtility.validateBoundaryPoint({ node, offset });
890
+
891
+ const boundaryPoint = { node, offset };
892
+
893
+ if (
894
+ node.ownerDocument !== this._ownerDocument ||
895
+ RangeUtility.compareBoundaryPointsPosition(boundaryPoint, {
896
+ node: this._end.node,
897
+ offset: this.endOffset
898
+ }) === 1
899
+ ) {
900
+ this._end.node = node;
901
+ this._end.offset = offset;
902
+ }
903
+
904
+ this._start.node = node;
905
+ this._start.offset = offset;
906
+ }
907
+
908
+ /**
909
+ * Sets the end position of a Range relative to another Node.
910
+ *
911
+ * @see https://dom.spec.whatwg.org/#dom-range-setendafter
912
+ * @param node Reference node.
913
+ */
914
+ public setEndAfter(node: INode): void {
915
+ if (!node.parentNode) {
916
+ throw new DOMException(
917
+ 'The given Node has no parent.',
918
+ DOMExceptionNameEnum.invalidNodeTypeError
919
+ );
920
+ }
921
+ this.setEnd(node.parentNode, node.parentNode.childNodes.indexOf(node) + 1);
922
+ }
923
+
924
+ /**
925
+ * Sets the end position of a Range relative to another Node.
926
+ *
927
+ * @see https://dom.spec.whatwg.org/#dom-range-setendbefore
928
+ * @param node Reference node.
929
+ */
930
+ public setEndBefore(node: INode): void {
931
+ if (!node.parentNode) {
932
+ throw new DOMException(
933
+ 'The given Node has no parent.',
934
+ DOMExceptionNameEnum.invalidNodeTypeError
935
+ );
936
+ }
937
+ this.setEnd(node.parentNode, node.parentNode.childNodes.indexOf(node));
938
+ }
939
+
940
+ /**
941
+ * Sets the start position of a Range relative to a Node.
942
+ *
943
+ * @see https://dom.spec.whatwg.org/#dom-range-setstartafter
944
+ * @param node Reference node.
945
+ */
946
+ public setStartAfter(node: INode): void {
947
+ if (!node.parentNode) {
948
+ throw new DOMException(
949
+ 'The given Node has no parent.',
950
+ DOMExceptionNameEnum.invalidNodeTypeError
951
+ );
952
+ }
953
+ this.setStart(node.parentNode, node.parentNode.childNodes.indexOf(node) + 1);
954
+ }
955
+
956
+ /**
957
+ * Sets the start position of a Range relative to another Node.
958
+ *
959
+ * @see https://dom.spec.whatwg.org/#dom-range-setstartbefore
960
+ * @param node Reference node.
961
+ */
962
+ public setStartBefore(node: INode): void {
963
+ if (!node.parentNode) {
964
+ throw new DOMException(
965
+ 'The given Node has no parent.',
966
+ DOMExceptionNameEnum.invalidNodeTypeError
967
+ );
968
+ }
969
+ this.setStart(node.parentNode, node.parentNode.childNodes.indexOf(node));
970
+ }
971
+
972
+ /**
973
+ * Moves content of the Range into a new node, placing the new node at the start of the specified range.
974
+ *
975
+ * @see https://dom.spec.whatwg.org/#dom-range-surroundcontents
976
+ * @param newParent New parent.
977
+ */
978
+ public surroundContents(newParent: INode): void {
979
+ let node = this.commonAncestorContainer;
980
+ const endNode = NodeUtility.nextDecendantNode(node);
981
+ while (node !== endNode) {
982
+ if (
983
+ node.nodeType !== NodeTypeEnum.textNode &&
984
+ RangeUtility.isPartiallyContained(node, this)
985
+ ) {
986
+ throw new DOMException(
987
+ 'The Range has partially contains a non-Text node.',
988
+ DOMExceptionNameEnum.invalidStateError
989
+ );
990
+ }
991
+
992
+ node = NodeUtility.following(node);
993
+ }
994
+
995
+ if (
996
+ newParent.nodeType === NodeTypeEnum.documentNode ||
997
+ newParent.nodeType === NodeTypeEnum.documentTypeNode ||
998
+ newParent.nodeType === NodeTypeEnum.documentFragmentNode
999
+ ) {
1000
+ throw new DOMException('Invalid element type.', DOMExceptionNameEnum.invalidNodeTypeError);
1001
+ }
1002
+
1003
+ const fragment = this.extractContents();
1004
+
1005
+ while (newParent.firstChild) {
1006
+ newParent.removeChild(newParent.firstChild);
1007
+ }
1008
+
1009
+ this.insertNode(newParent);
1010
+
1011
+ newParent.appendChild(fragment);
1012
+
1013
+ this.selectNode(newParent);
1014
+ }
1015
+
1016
+ /**
1017
+ * Returns the text of the Range.
1018
+ *
1019
+ * @see https://dom.spec.whatwg.org/#dom-range-stringifier
1020
+ */
1021
+ public toString(): string {
1022
+ const startOffset = this.startOffset;
1023
+ const endOffset = this.endOffset;
1024
+ let string = '';
1025
+
1026
+ if (
1027
+ this._start.node === this._end.node &&
1028
+ this._start.node.nodeType === NodeTypeEnum.textNode
1029
+ ) {
1030
+ return (<IText>this._start.node).data.slice(startOffset, endOffset);
1031
+ }
1032
+
1033
+ if (this._start.node.nodeType === NodeTypeEnum.textNode) {
1034
+ string += (<IText>this._start.node).data.slice(startOffset);
1035
+ }
1036
+
1037
+ const endNode = NodeUtility.nextDecendantNode(this._end.node);
1038
+ let currentNode = this._start.node;
1039
+
1040
+ while (currentNode && currentNode !== endNode) {
1041
+ if (
1042
+ currentNode.nodeType === NodeTypeEnum.textNode &&
1043
+ RangeUtility.isContained(currentNode, this)
1044
+ ) {
1045
+ string += (<IText>currentNode).data;
1046
+ }
1047
+
1048
+ currentNode = NodeUtility.following(currentNode);
1049
+ }
1050
+
1051
+ if (this._end.node.nodeType === NodeTypeEnum.textNode) {
1052
+ string += (<IText>this._end.node).data.slice(0, endOffset);
1053
+ }
1054
+
1055
+ return string;
1056
+ }
1057
+ }