jodit 4.14.2 → 4.14.3

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 (71) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +7 -7
  4. package/es2015/jodit.js +469 -313
  5. package/es2015/jodit.min.js +5 -5
  6. package/es2015/plugins/debug/debug.css +1 -1
  7. package/es2015/plugins/debug/debug.js +1 -1
  8. package/es2015/plugins/debug/debug.min.js +1 -1
  9. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  10. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  12. package/es2018/jodit.fat.min.js +7 -7
  13. package/es2018/jodit.min.js +5 -5
  14. package/es2018/plugins/debug/debug.min.js +1 -1
  15. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  16. package/es2021/jodit.css +1 -1
  17. package/es2021/jodit.fat.min.js +9 -9
  18. package/es2021/jodit.js +456 -304
  19. package/es2021/jodit.min.js +6 -6
  20. package/es2021/plugins/debug/debug.css +1 -1
  21. package/es2021/plugins/debug/debug.js +1 -1
  22. package/es2021/plugins/debug/debug.min.js +1 -1
  23. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  24. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  25. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  26. package/es2021.en/jodit.css +1 -1
  27. package/es2021.en/jodit.fat.min.js +5 -5
  28. package/es2021.en/jodit.js +395 -251
  29. package/es2021.en/jodit.min.js +5 -5
  30. package/es2021.en/plugins/debug/debug.css +1 -1
  31. package/es2021.en/plugins/debug/debug.js +1 -1
  32. package/es2021.en/plugins/debug/debug.min.js +1 -1
  33. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  34. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  35. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  36. package/es5/jodit.css +2 -2
  37. package/es5/jodit.fat.min.js +2 -2
  38. package/es5/jodit.js +701 -381
  39. package/es5/jodit.min.css +2 -2
  40. package/es5/jodit.min.js +2 -2
  41. package/es5/plugins/debug/debug.css +1 -1
  42. package/es5/plugins/debug/debug.js +1 -1
  43. package/es5/plugins/debug/debug.min.js +1 -1
  44. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  45. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  46. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  47. package/es5/polyfills.fat.min.js +1 -1
  48. package/es5/polyfills.js +1 -1
  49. package/es5/polyfills.min.js +1 -1
  50. package/esm/core/constants.js +1 -1
  51. package/esm/core/selection/helpers/index.js +5 -2
  52. package/esm/core/selection/selection.d.ts +8 -5
  53. package/esm/core/selection/selection.js +267 -159
  54. package/esm/core/selection/style/api/extract.js +1 -3
  55. package/esm/core/selection/style/api/is-same-attributes.js +5 -2
  56. package/esm/core/selection/style/api/toggle-attributes.js +27 -10
  57. package/esm/core/selection/style/api/unwrap-children.js +39 -33
  58. package/esm/core/selection/style/apply-style.js +23 -25
  59. package/esm/core/selection/style/commit-style.js +3 -0
  60. package/esm/core/selection/style/transactions.js +2 -1
  61. package/esm/langs/az.d.ts +223 -0
  62. package/esm/langs/az.js +240 -0
  63. package/esm/languages.js +2 -0
  64. package/esm/modules/history/snapshot.js +45 -20
  65. package/esm/types/style.d.ts +2 -2
  66. package/esm/types/types.d.ts +3 -0
  67. package/package.json +1 -1
  68. package/types/core/selection/selection.d.ts +8 -5
  69. package/types/langs/az.d.ts +223 -0
  70. package/types/types/style.d.ts +2 -2
  71. package/types/types/types.d.ts +3 -0
@@ -27,6 +27,7 @@ import { cursorInTheEdgeOfString, findCorrectCurrentNode } from "./helpers/index
27
27
  export class Selection {
28
28
  constructor(jodit) {
29
29
  this.jodit = jodit;
30
+ this.__fakeSelections = new WeakMap();
30
31
  jodit.e.on('removeMarkers', () => {
31
32
  this.removeMarkers();
32
33
  });
@@ -85,8 +86,15 @@ export class Selection {
85
86
  */
86
87
  get isInsideArea() {
87
88
  const { sel } = this;
88
- const range = (sel === null || sel === void 0 ? void 0 : sel.rangeCount) ? sel.getRangeAt(0) : null;
89
- return !(!range || !Dom.isOrContains(this.area, range.startContainer));
89
+ if (!(sel === null || sel === void 0 ? void 0 : sel.rangeCount)) {
90
+ return false;
91
+ }
92
+ for (let i = 0; i < sel.rangeCount; i += 1) {
93
+ if (!Dom.isOrContains(this.area, sel.getRangeAt(i).commonAncestorContainer)) {
94
+ return false;
95
+ }
96
+ }
97
+ return true;
90
98
  }
91
99
  /**
92
100
  * Return current selection object
@@ -106,8 +114,11 @@ export class Selection {
106
114
  const sel = this.sel, current = this.current();
107
115
  if (sel && current) {
108
116
  for (let i = 0; i < sel.rangeCount; i += 1) {
109
- sel.getRangeAt(i).deleteContents();
110
- sel.getRangeAt(i).collapse(true);
117
+ const range = sel.getRangeAt(i);
118
+ if (Dom.isOrContains(this.area, range.commonAncestorContainer)) {
119
+ range.deleteContents();
120
+ range.collapse(true);
121
+ }
111
122
  }
112
123
  }
113
124
  }
@@ -138,28 +149,31 @@ export class Selection {
138
149
  * @returns false - Something went wrong
139
150
  */
140
151
  insertCursorAtPoint(x, y) {
141
- this.removeMarkers();
152
+ var _a, _b, _c, _d;
142
153
  try {
143
- const rng = this.createRange();
144
- (() => {
145
- if (this.doc.caretPositionFromPoint) {
146
- const caret = this.doc.caretPositionFromPoint(x, y);
147
- if (caret) {
148
- rng.setStart(caret.offsetNode, caret.offset);
149
- return;
150
- }
151
- }
152
- if (this.doc.caretRangeFromPoint) {
153
- const caret = this.doc.caretRangeFromPoint(x, y);
154
- assert(caret, 'Incorrect caretRangeFromPoint behaviour');
155
- rng.setStart(caret.startContainer, caret.startOffset);
156
- }
157
- })();
158
- rng.collapse(true);
159
- this.selectRange(rng);
154
+ const range = this.createRange();
155
+ const position = (_b = (_a = this.doc).caretPositionFromPoint) === null || _b === void 0 ? void 0 : _b.call(_a, x, y);
156
+ const caret = position
157
+ ? null
158
+ : (_d = (_c = this.doc).caretRangeFromPoint) === null || _d === void 0 ? void 0 : _d.call(_c, x, y);
159
+ if (position) {
160
+ range.setStart(position.offsetNode, position.offset);
161
+ }
162
+ else if (caret) {
163
+ range.setStart(caret.startContainer, caret.startOffset);
164
+ }
165
+ else {
166
+ return false;
167
+ }
168
+ if (!Dom.isOrContains(this.area, range.startContainer)) {
169
+ return false;
170
+ }
171
+ range.collapse(true);
172
+ this.removeMarkers();
173
+ this.selectRange(range);
160
174
  return true;
161
175
  }
162
- catch (_a) { }
176
+ catch (_e) { }
163
177
  return false;
164
178
  }
165
179
  /**
@@ -217,89 +231,131 @@ export class Selection {
217
231
  * Restores user selections using marker invisible elements in the DOM.
218
232
  */
219
233
  restore() {
220
- let range = false;
221
- const markerFilter = (start) => (node) => Dom.isMarker(node) &&
222
- attr(node, 'data-' + consts.MARKER_CLASS) ===
223
- (start ? 'start' : 'end');
224
- const start = Dom.first(this.area, markerFilter(true));
225
- const end = Dom.first(this.area, markerFilter(false));
226
- if (!start) {
227
- return;
228
- }
229
- range = this.createRange();
230
- if (!end) {
231
- const previousNode = start.previousSibling;
232
- if (Dom.isText(previousNode)) {
233
- range.setStart(previousNode, previousNode.nodeValue ? previousNode.nodeValue.length : 0);
234
+ var _a, _b;
235
+ const markers = this.markers;
236
+ const byId = new Map(markers.map(marker => [marker.id, marker]));
237
+ const ranges = [];
238
+ let backward = false;
239
+ for (let i = 0; i < markers.length; i += 1) {
240
+ const start = markers[i];
241
+ if (attr(start, 'data-' + consts.MARKER_CLASS) !== 'start') {
242
+ continue;
243
+ }
244
+ backward || (backward = attr(start, 'data-selection-backward') === 'true');
245
+ const endId = attr(start, 'data-selection-end');
246
+ let end = endId ? byId.get(endId) : undefined;
247
+ if (!endId &&
248
+ markers[i + 1] &&
249
+ attr(markers[i + 1], 'data-' + consts.MARKER_CLASS) === 'end') {
250
+ end = markers[i + 1];
251
+ }
252
+ const range = this.createRange();
253
+ if (end) {
254
+ range.setStartAfter(start);
255
+ range.setEndBefore(end);
234
256
  }
235
257
  else {
236
- range.setStartBefore(start);
258
+ const previous = start.previousSibling;
259
+ if (Dom.isText(previous)) {
260
+ range.setStart(previous, (_b = (_a = previous.nodeValue) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
261
+ }
262
+ else {
263
+ range.setStartBefore(start);
264
+ }
265
+ range.collapse(true);
237
266
  }
238
- Dom.safeRemove(start);
239
- range.collapse(true);
240
- }
241
- else {
242
- range.setStartAfter(start);
243
- Dom.safeRemove(start);
244
- range.setEndBefore(end);
245
- Dom.safeRemove(end);
267
+ ranges.push(range);
246
268
  }
247
- if (range) {
248
- this.selectRange(range);
269
+ Dom.safeRemove(...markers);
270
+ if (ranges.length) {
271
+ this.__selectRanges(ranges, backward);
249
272
  }
250
273
  }
274
+ __isBackward() {
275
+ const sel = this.sel;
276
+ return Boolean(sel &&
277
+ sel.rangeCount === 1 &&
278
+ !sel.isCollapsed &&
279
+ sel.anchorNode === sel.getRangeAt(0).endContainer &&
280
+ sel.anchorOffset === sel.getRangeAt(0).endOffset);
281
+ }
282
+ __ranges() {
283
+ const sel = this.sel;
284
+ return sel
285
+ ? Array.from({ length: sel.rangeCount }, (_, i) => sel.getRangeAt(i).cloneRange())
286
+ : [];
287
+ }
251
288
  /**
252
289
  * Inserts invisible fake nodes on the boundaries of the current selection
253
- * and returns them. Unlike [[Select.save]] the selection stays valid while
290
+ * and returns the first pair as a restoration handle for all ranges. Unlike [[Select.save]] the selection stays valid while
254
291
  * the DOM around it is being modified. Restore it later with [[Select.restoreFakes]].
255
292
  */
256
293
  fakes() {
257
- const sel = this.sel;
258
- if (!sel || !sel.rangeCount) {
294
+ if (!this.isInsideArea) {
259
295
  return [];
260
296
  }
261
- const range = sel.getRangeAt(0);
262
- assert(range, 'Range is null');
263
- const left = range.cloneRange();
264
- left.collapse(true);
265
- const fakeLeft = this.j.createInside.fake();
266
- Dom.safeInsertNode(left, fakeLeft);
267
- range.setStartBefore(fakeLeft);
268
- const result = [fakeLeft];
269
- if (!range.collapsed) {
270
- const right = range.cloneRange();
271
- right.collapse(false);
272
- const fakeRight = this.j.createInside.fake();
273
- Dom.safeInsertNode(right, fakeRight);
274
- range.setEndAfter(fakeRight);
275
- result.push(fakeRight);
276
- }
277
- this.selectRange(range);
278
- return result;
297
+ const backward = this.__isBackward();
298
+ const ranges = this.__ranges();
299
+ const pairs = ranges.map(range => {
300
+ const collapsed = range.collapsed;
301
+ const left = range.cloneRange();
302
+ left.collapse(true);
303
+ const fakeLeft = this.j.createInside.fake();
304
+ Dom.safeInsertNode(left, fakeLeft);
305
+ range.setStartBefore(fakeLeft);
306
+ const pair = [fakeLeft];
307
+ if (!collapsed) {
308
+ const right = range.cloneRange();
309
+ right.collapse(false);
310
+ const fakeRight = this.j.createInside.fake();
311
+ Dom.safeInsertNode(right, fakeRight);
312
+ range.setEndAfter(fakeRight);
313
+ pair.push(fakeRight);
314
+ }
315
+ else {
316
+ range.collapse(true);
317
+ }
318
+ return pair;
319
+ });
320
+ this.__fakeSelections.set(pairs[0][0], { pairs, backward });
321
+ this.__selectRanges(ranges, backward);
322
+ return pairs[0];
279
323
  }
280
- /**
281
- * Restores the selection previously saved with [[Select.fakes]]
282
- * and removes the fake nodes (disconnected fakes are ignored).
283
- */
324
+ /** Restore all ranges associated with the returned fake pair. */
284
325
  restoreFakes(fakes) {
285
- var _a, _b, _c, _d;
286
- const nodes = fakes.filter(n => n.isConnected);
287
- if (!nodes.length) {
326
+ var _a, _b, _c;
327
+ if (!fakes.length) {
288
328
  return;
289
329
  }
290
- const [fakeLeft, fakeRight] = nodes;
291
- const range = this.createRange();
292
- range.setStartAfter(fakeLeft);
293
- if (fakeRight) {
294
- range.setEndBefore(fakeRight);
330
+ const saved = this.__fakeSelections.get(fakes[0]);
331
+ this.__fakeSelections.delete(fakes[0]);
332
+ if (this.j.isInDestruct) {
333
+ return;
295
334
  }
296
- this.selectRange(range);
297
- if (((_a = fakeLeft.parentNode) === null || _a === void 0 ? void 0 : _a.firstChild) !== ((_b = fakeLeft.parentNode) === null || _b === void 0 ? void 0 : _b.lastChild)) {
298
- Dom.safeRemove(fakeLeft);
335
+ const ranges = [];
336
+ for (const pair of (_a = saved === null || saved === void 0 ? void 0 : saved.pairs) !== null && _a !== void 0 ? _a : [fakes]) {
337
+ const nodes = pair.filter(n => n.isConnected && Dom.isOrContains(this.area, n, true));
338
+ if (!nodes.length) {
339
+ continue;
340
+ }
341
+ const [left, right] = nodes;
342
+ const range = this.createRange();
343
+ range.setStartAfter(left);
344
+ if (right) {
345
+ range.setEndBefore(right);
346
+ }
347
+ else {
348
+ range.collapse(true);
349
+ }
350
+ for (const node of nodes) {
351
+ if (((_b = node.parentNode) === null || _b === void 0 ? void 0 : _b.firstChild) !== ((_c = node.parentNode) === null || _c === void 0 ? void 0 : _c.lastChild)) {
352
+ Dom.safeRemove(node);
353
+ }
354
+ }
355
+ ranges.push(range);
299
356
  }
300
- if (((_c = fakeRight === null || fakeRight === void 0 ? void 0 : fakeRight.parentNode) === null || _c === void 0 ? void 0 : _c.firstChild) !==
301
- ((_d = fakeRight === null || fakeRight === void 0 ? void 0 : fakeRight.parentNode) === null || _d === void 0 ? void 0 : _d.lastChild)) {
302
- Dom.safeRemove(fakeRight);
357
+ if (ranges.length) {
358
+ this.__selectRanges(ranges, saved === null || saved === void 0 ? void 0 : saved.backward);
303
359
  }
304
360
  }
305
361
  /**
@@ -314,9 +370,15 @@ export class Selection {
314
370
  if (!sel || !sel.rangeCount) {
315
371
  return [];
316
372
  }
373
+ const backward = this.__isBackward();
317
374
  const info = [], length = sel.rangeCount, ranges = [];
318
375
  for (let i = 0; i < length; i += 1) {
319
376
  ranges[i] = sel.getRangeAt(i);
377
+ if (!Dom.isOrContains(this.area, ranges[i].commonAncestorContainer)) {
378
+ return [];
379
+ }
380
+ }
381
+ for (let i = 0; i < length; i += 1) {
320
382
  if (ranges[i].collapsed) {
321
383
  const start = this.marker(true, ranges[i]);
322
384
  info[i] = {
@@ -328,6 +390,12 @@ export class Selection {
328
390
  else {
329
391
  const start = this.marker(true, ranges[i]);
330
392
  const end = this.marker(false, ranges[i]);
393
+ if (length > 1) {
394
+ attr(start, 'data-selection-end', end.id);
395
+ }
396
+ if (backward) {
397
+ attr(start, 'data-selection-backward', 'true');
398
+ }
331
399
  info[i] = {
332
400
  startId: start.id,
333
401
  endId: end.id,
@@ -340,7 +408,7 @@ export class Selection {
340
408
  if (!silent) {
341
409
  sel.removeAllRanges();
342
410
  for (let i = length - 1; i >= 0; --i) {
343
- const startElm = this.doc.getElementById(info[i].startId);
411
+ const startElm = this.area.querySelector('#' + info[i].startId);
344
412
  if (!startElm) {
345
413
  continue;
346
414
  }
@@ -351,7 +419,7 @@ export class Selection {
351
419
  else {
352
420
  ranges[i].setStartBefore(startElm);
353
421
  if (info[i].endId) {
354
- const endElm = this.doc.getElementById(info[i].endId);
422
+ const endElm = this.area.querySelector('#' + info[i].endId);
355
423
  if (endElm) {
356
424
  ranges[i].setEndAfter(endElm);
357
425
  }
@@ -433,6 +501,9 @@ export class Selection {
433
501
  const range = sel.getRangeAt(0);
434
502
  let node = range.startContainer;
435
503
  let rightMode = false;
504
+ if (!Dom.isOrContains(this.area, node)) {
505
+ return null;
506
+ }
436
507
  const child = (nd) => rightMode ? nd.lastChild : nd.firstChild;
437
508
  if (Dom.isTag(node, 'br') && sel.isCollapsed) {
438
509
  return node;
@@ -457,8 +528,11 @@ export class Selection {
457
528
  */
458
529
  insertNode(node, insertCursorAfter = true, fireChange = true) {
459
530
  this.errorNode(node);
460
- const child = Dom.isFragment(node) ? node.lastChild : node;
461
531
  this.j.e.fire('safeHTML', node);
532
+ let child = Dom.isFragment(node) ? node.lastChild : node;
533
+ if (!child || this.j.isInDestruct) {
534
+ return;
535
+ }
462
536
  if (!this.isFocused() && this.j.isEditorMode()) {
463
537
  this.focus();
464
538
  this.restore();
@@ -469,6 +543,10 @@ export class Selection {
469
543
  this.j.execCommand('Delete');
470
544
  }
471
545
  this.j.e.fire('beforeInsertNode', node);
546
+ child = Dom.isFragment(node) ? node.lastChild : node;
547
+ if (!child || this.j.isInDestruct) {
548
+ return;
549
+ }
472
550
  if (sel && sel.rangeCount) {
473
551
  const range = sel.getRangeAt(0);
474
552
  const { firstChild } = node;
@@ -614,14 +692,22 @@ export class Selection {
614
692
  /**
615
693
  * Call callback for all selection node
616
694
  */
617
- // eslint-disable-next-line complexity
618
695
  eachSelection(callback) {
619
- var _a;
620
- const sel = this.sel;
621
- if (!sel || !sel.rangeCount) {
622
- return;
696
+ const seen = new Set();
697
+ for (const range of this.__ranges()) {
698
+ if (!Dom.isOrContains(this.area, range.commonAncestorContainer)) {
699
+ continue;
700
+ }
701
+ this.__eachRange(range, node => {
702
+ if (!seen.has(node)) {
703
+ seen.add(node);
704
+ callback(node);
705
+ }
706
+ });
623
707
  }
624
- const range = sel.getRangeAt(0);
708
+ }
709
+ __eachRange(range, callback) {
710
+ var _a;
625
711
  let root = range.commonAncestorContainer;
626
712
  if (!Dom.isHTMLElement(root)) {
627
713
  root = root.parentElement;
@@ -706,12 +792,16 @@ export class Selection {
706
792
  */
707
793
  cursorInTheEdge(start, parentBlock, fake = null) {
708
794
  const end = !start, sel = this.sel, range = (sel === null || sel === void 0 ? void 0 : sel.rangeCount) ? sel.getRangeAt(0) : null;
709
- fake !== null && fake !== void 0 ? fake : (fake = this.current(false));
710
- if (!range || !fake || !Dom.isOrContains(parentBlock, fake, true)) {
795
+ if (!range) {
711
796
  return null;
712
797
  }
713
798
  const container = start ? range.startContainer : range.endContainer;
714
799
  const offset = start ? range.startOffset : range.endOffset;
800
+ fake !== null && fake !== void 0 ? fake : (fake = container);
801
+ if (!Dom.isOrContains(parentBlock, container) ||
802
+ !Dom.isOrContains(parentBlock, fake)) {
803
+ return null;
804
+ }
715
805
  const isSignificant = (elm) => Boolean(elm &&
716
806
  !Dom.isTag(elm, 'br') &&
717
807
  !Dom.isEmptyTextNode(elm) &&
@@ -725,14 +815,10 @@ export class Selection {
725
815
  }
726
816
  }
727
817
  else {
728
- const children = toArray(container.childNodes);
729
- if (end) {
730
- if (children.slice(offset).some(isSignificant)) {
731
- return false;
732
- }
733
- }
734
- else {
735
- if (children.slice(0, offset).some(isSignificant)) {
818
+ const children = container.childNodes;
819
+ const limit = end ? children.length : offset;
820
+ for (let i = end ? offset : 0; i < limit; i += 1) {
821
+ if (isSignificant(children[i])) {
736
822
  return false;
737
823
  }
738
824
  }
@@ -745,7 +831,7 @@ export class Selection {
745
831
  continue;
746
832
  }
747
833
  next = nextOne;
748
- if (next && isSignificant(next)) {
834
+ if (isSignificant(next)) {
749
835
  return false;
750
836
  }
751
837
  }
@@ -783,7 +869,7 @@ export class Selection {
783
869
  setCursorNearWith(node, inStart) {
784
870
  var _a, _b;
785
871
  this.errorNode(node);
786
- if (!Dom.up(node, (elm) => elm === this.area || (elm && elm.parentNode === this.area), this.area)) {
872
+ if (!Dom.isOrContains(this.area, node, true)) {
787
873
  throw error('Node element must be in editor');
788
874
  }
789
875
  const range = this.createRange();
@@ -854,13 +940,20 @@ export class Selection {
854
940
  * Set range selection
855
941
  */
856
942
  selectRange(range, focus = true) {
943
+ return this.__selectRanges([range], false, focus);
944
+ }
945
+ __selectRanges(ranges, backward = false, focus = true) {
857
946
  const sel = this.sel;
858
947
  if (focus && !this.isFocused() && this.j.e.current !== 'focus') {
859
948
  this.focus();
860
949
  }
861
950
  if (sel) {
862
951
  sel.removeAllRanges();
863
- sel.addRange(range);
952
+ ranges.forEach(range => sel.addRange(range));
953
+ if (backward && ranges.length === 1) {
954
+ const range = ranges[0];
955
+ sel.setBaseAndExtent(range.endContainer, range.endOffset, range.startContainer, range.startOffset);
956
+ }
864
957
  }
865
958
  /**
866
959
  * Fired after change selection
@@ -879,7 +972,7 @@ export class Selection {
879
972
  throw error('Node element must be in editor');
880
973
  }
881
974
  const range = this.createRange();
882
- range[inward ? 'selectNodeContents' : 'selectNode'](node);
975
+ range[inward || node === this.area ? 'selectNodeContents' : 'selectNode'](node);
883
976
  return this.selectRange(range);
884
977
  }
885
978
  /**
@@ -894,10 +987,8 @@ export class Selection {
894
987
  get html() {
895
988
  const sel = this.sel;
896
989
  if (sel && sel.rangeCount > 0) {
897
- const range = sel.getRangeAt(0);
898
- const clonedSelection = range.cloneContents();
899
990
  const div = this.j.createInside.div();
900
- Dom.append(div, clonedSelection);
991
+ this.__ranges().forEach(range => Dom.append(div, range.cloneContents()));
901
992
  return div.innerHTML;
902
993
  }
903
994
  return '';
@@ -911,8 +1002,7 @@ export class Selection {
911
1002
  * `nativeExecCommand('fontsize', false, '7')` trick which relied on the
912
1003
  * browser to split the selection into `<font size="7">` fragments.
913
1004
  */
914
- __wrapSelectionFragments() {
915
- const range = this.range;
1005
+ __wrapSelectionFragments(range) {
916
1006
  this.__splitSelectionBoundaries(range);
917
1007
  let root = range.commonAncestorContainer;
918
1008
  if (Dom.isText(root)) {
@@ -929,30 +1019,22 @@ export class Selection {
929
1019
  * fully (not partially) selected.
930
1020
  */
931
1021
  __splitSelectionBoundaries(range) {
932
- var _a, _b, _c, _d, _e, _f, _g, _h;
933
- let { startContainer, endContainer } = range;
934
- let { startOffset, endOffset } = range;
1022
+ var _a, _b, _c, _d;
1023
+ const { endContainer, endOffset } = range;
935
1024
  if (Dom.isText(endContainer) &&
936
1025
  endOffset > 0 &&
937
1026
  endOffset < ((_b = (_a = endContainer.nodeValue) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0)) {
938
1027
  endContainer.splitText(endOffset);
939
- endOffset = (_d = (_c = endContainer.nodeValue) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : endOffset;
940
1028
  }
1029
+ const { startContainer, startOffset } = range;
941
1030
  if (Dom.isText(startContainer) &&
942
1031
  startOffset > 0 &&
943
- startOffset < ((_f = (_e = startContainer.nodeValue) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0)) {
1032
+ startOffset < ((_d = (_c = startContainer.nodeValue) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0)) {
944
1033
  const middle = startContainer.splitText(startOffset);
945
- // Selection located inside a single text node - the tail we just
946
- // cut off is the actual selected fragment.
947
- if (startContainer === endContainer) {
948
- endContainer = middle;
949
- endOffset = (_h = (_g = middle.nodeValue) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : 0;
950
- }
951
- startContainer = middle;
952
- startOffset = 0;
1034
+ // splitText updates the live range, including element end offsets.
1035
+ // Only the start at the split point needs to move to the new node.
1036
+ range.setStart(middle, 0);
953
1037
  }
954
- range.setStart(startContainer, startOffset);
955
- range.setEnd(endContainer, endOffset);
956
1038
  // Normalize text-edge boundaries (e.g. `(text, 0)` or `(text, length)`)
957
1039
  // to the element level so that containment checks based on
958
1040
  // `selectNode()` treat a fully selected text node as contained.
@@ -982,20 +1064,19 @@ export class Selection {
982
1064
  * Collects the highest-level nodes that are completely inside the range,
983
1065
  * descending into nodes that are only partially selected.
984
1066
  */
985
- __collectContainedNodes(root, range) {
1067
+ __collectContainedNodes(root, range, scratch = this.createRange()) {
986
1068
  const result = [];
987
1069
  toArray(root.childNodes).forEach(child => {
988
- if (this.__isFullyContained(range, child)) {
1070
+ if (this.__isFullyContained(range, child, scratch)) {
989
1071
  result.push(child);
990
1072
  }
991
1073
  else if (child.childNodes.length && range.intersectsNode(child)) {
992
- result.push(...this.__collectContainedNodes(child, range));
1074
+ result.push(...this.__collectContainedNodes(child, range, scratch));
993
1075
  }
994
1076
  });
995
1077
  return result;
996
1078
  }
997
- __isFullyContained(range, node) {
998
- const nodeRange = this.createRange();
1079
+ __isFullyContained(range, node, nodeRange) {
999
1080
  nodeRange.selectNode(node);
1000
1081
  return (range.compareBoundaryPoints(Range.START_TO_START, nodeRange) <= 0 &&
1001
1082
  range.compareBoundaryPoints(Range.END_TO_END, nodeRange) >= 0);
@@ -1047,33 +1128,52 @@ export class Selection {
1047
1128
  * Wrap all selected fragments inside Tag or apply some callback
1048
1129
  */
1049
1130
  *wrapInTagGen(fakes) {
1131
+ var _a;
1132
+ if (((_a = this.sel) === null || _a === void 0 ? void 0 : _a.rangeCount) && !this.isInsideArea) {
1133
+ return;
1134
+ }
1050
1135
  if (this.isCollapsed()) {
1051
1136
  const font = this.jodit.createInside.element('font', INVISIBLE_SPACE);
1052
1137
  this.insertNode(font, false, false);
1053
1138
  if (fakes && fakes[0]) {
1054
1139
  Dom.append(font, fakes[0]);
1055
1140
  }
1056
- yield font;
1057
- Dom.unwrap(font);
1141
+ try {
1142
+ yield font;
1143
+ }
1144
+ finally {
1145
+ Dom.unwrap(font);
1146
+ }
1058
1147
  return;
1059
1148
  }
1060
- const elms = this.__wrapSelectionFragments();
1061
- for (const font of elms) {
1062
- const { firstChild, lastChild } = font;
1063
- if (firstChild &&
1064
- firstChild === lastChild &&
1065
- isMarker(firstChild)) {
1149
+ const elms = this.__ranges().flatMap(range => this.__wrapSelectionFragments(range));
1150
+ try {
1151
+ for (const font of elms) {
1152
+ if (this.j.isInDestruct ||
1153
+ !Dom.isOrContains(this.area, font, true)) {
1154
+ continue;
1155
+ }
1156
+ const { firstChild, lastChild } = font;
1157
+ if (firstChild &&
1158
+ firstChild === lastChild &&
1159
+ isMarker(firstChild)) {
1160
+ Dom.unwrap(font);
1161
+ continue;
1162
+ }
1163
+ if (firstChild && isMarker(firstChild)) {
1164
+ Dom.before(font, firstChild);
1165
+ }
1166
+ if (lastChild && isMarker(lastChild)) {
1167
+ Dom.after(font, lastChild);
1168
+ }
1169
+ yield font;
1066
1170
  Dom.unwrap(font);
1067
- continue;
1068
- }
1069
- if (firstChild && isMarker(firstChild)) {
1070
- Dom.before(font, firstChild);
1071
- }
1072
- if (lastChild && isMarker(lastChild)) {
1073
- Dom.after(font, lastChild);
1074
1171
  }
1075
- yield font;
1076
- Dom.unwrap(font);
1172
+ }
1173
+ finally {
1174
+ // A callback can throw or stop iteration before later wrappers
1175
+ // have been visited. None of the temporary fonts should survive.
1176
+ elms.forEach(font => Dom.unwrap(font));
1077
1177
  }
1078
1178
  return;
1079
1179
  }
@@ -1133,7 +1233,11 @@ export class Selection {
1133
1233
  * Split selection on two parts: left and right
1134
1234
  */
1135
1235
  splitSelection(currentBox, edge) {
1136
- if (!this.isCollapsed()) {
1236
+ if (!this.isCollapsed() ||
1237
+ !this.isInsideArea ||
1238
+ !Dom.isOrContains(this.area, currentBox, true) ||
1239
+ !Dom.isOrContains(currentBox, this.range.startContainer) ||
1240
+ (edge && !Dom.isOrContains(currentBox, edge, true))) {
1137
1241
  return null;
1138
1242
  }
1139
1243
  const leftRange = this.createRange();
@@ -1220,13 +1324,18 @@ export class Selection {
1220
1324
  * becomes `<p>|<b>test</b>|</p>`)
1221
1325
  */
1222
1326
  expandSelection() {
1223
- if (this.isCollapsed()) {
1327
+ if (this.isCollapsed() || !this.isInsideArea) {
1224
1328
  return this;
1225
1329
  }
1226
- const { range } = this;
1330
+ const backward = this.__isBackward();
1331
+ const ranges = this.__ranges().map(range => this.__expandRange(range));
1332
+ this.__selectRanges(ranges, backward);
1333
+ return this;
1334
+ }
1335
+ __expandRange(range) {
1227
1336
  const c = range.cloneRange();
1228
1337
  if (!Dom.isOrContains(this.j.editor, range.commonAncestorContainer, true)) {
1229
- return this;
1338
+ return range;
1230
1339
  }
1231
1340
  const moveMaxEdgeFake = (start) => {
1232
1341
  const fake = this.j.createInside.fake();
@@ -1270,12 +1379,11 @@ export class Selection {
1270
1379
  }
1271
1380
  }
1272
1381
  }
1273
- this.selectRange(c);
1274
1382
  Dom.safeRemove(leftFake, rightFake);
1275
- if (this.isCollapsed()) {
1383
+ if (c.collapsed) {
1276
1384
  throw error('Selection is collapsed');
1277
1385
  }
1278
- return this;
1386
+ return c;
1279
1387
  }
1280
1388
  }
1281
1389
  __decorate([