@codemirror/view 0.19.40 → 0.19.41

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.19.41 (2022-02-04)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where the editor's view of its content height could go out of sync with the DOM when a line-wrapping editor had its width changed, causing wrapping to change.
6
+
7
+ Fix a bug that caused the editor to draw way too much content when scrolling to a position in an editor (much) taller than the window.
8
+
9
+ Report an error when a replace decoration from a plugin crosses a line break, rather than silently ignoring it.
10
+
11
+ Fix an issue where reading DOM changes was broken when `lineSeparator` contained more than one character.
12
+
13
+ Make ordering of replace and mark decorations with the same extent and inclusivness more predictable by giving replace decorations precedence.
14
+
15
+ Fix a bug where, on Chrome, replacement across line boundaries and next to widgets could cause bogus zero-width characters to appear in the content.
16
+
1
17
  ## 0.19.40 (2022-01-19)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -602,9 +602,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
602
602
  replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
603
603
  }
604
604
 
605
- let [nav, doc] = typeof navigator != "undefined"
606
- ? [navigator, document]
607
- : [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
605
+ let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
606
+ let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
608
607
  const ie_edge = /Edge\/(\d+)/.exec(nav.userAgent);
609
608
  const ie_upto10 = /MSIE \d/.test(nav.userAgent);
610
609
  const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
@@ -1131,8 +1130,8 @@ class Decoration extends rangeset.RangeValue {
1131
1130
  static replace(spec) {
1132
1131
  let block = !!spec.block;
1133
1132
  let { start, end } = getInclusive(spec, block);
1134
- let startSide = block ? (start ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */;
1135
- let endSide = block ? (end ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */;
1133
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1134
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1136
1135
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1137
1136
  }
1138
1137
  /**
@@ -1577,11 +1576,13 @@ class ContentBuilder {
1577
1576
  this.openStart = openStart;
1578
1577
  }
1579
1578
  filterPoint(from, to, value, index) {
1580
- if (index >= this.disallowBlockEffectsBelow || !(value instanceof PointDecoration))
1581
- return true;
1582
- if (value.block)
1583
- throw new RangeError("Block decorations may not be specified via plugins");
1584
- return to <= this.doc.lineAt(this.pos).to;
1579
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1580
+ if (value.block)
1581
+ throw new RangeError("Block decorations may not be specified via plugins");
1582
+ if (to > this.doc.lineAt(this.pos).to)
1583
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1584
+ }
1585
+ return true;
1585
1586
  }
1586
1587
  static build(text, from, to, decorations, pluginDecorationLength) {
1587
1588
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2305,12 +2306,18 @@ function moveVisually(line, order, dir, start, forward) {
2305
2306
  return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2306
2307
  }
2307
2308
 
2309
+ const LineBreakPlaceholder = "\uffff";
2308
2310
  class DOMReader {
2309
- constructor(points, view) {
2311
+ constructor(points, state$1) {
2310
2312
  this.points = points;
2311
- this.view = view;
2312
2313
  this.text = "";
2313
- this.lineBreak = view.state.lineBreak;
2314
+ this.lineSeparator = state$1.facet(state.EditorState.lineSeparator);
2315
+ }
2316
+ append(text) {
2317
+ this.text += text;
2318
+ }
2319
+ lineBreak() {
2320
+ this.text += LineBreakPlaceholder;
2314
2321
  }
2315
2322
  readRange(start, end) {
2316
2323
  if (!start)
@@ -2326,42 +2333,61 @@ class DOMReader {
2326
2333
  if (view && nextView ? view.breakAfter :
2327
2334
  (view ? view.breakAfter : isBlockElement(cur)) ||
2328
2335
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2329
- this.text += this.lineBreak;
2336
+ this.lineBreak();
2330
2337
  cur = next;
2331
2338
  }
2332
2339
  this.findPointBefore(parent, end);
2333
2340
  return this;
2334
2341
  }
2335
2342
  readTextNode(node) {
2336
- var _a, _b;
2337
2343
  let text = node.nodeValue;
2338
- if (/^\u200b/.test(text) && ((_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.contentEditable) == "false")
2339
- text = text.slice(1);
2340
- if (/\u200b$/.test(text) && ((_b = node.nextSibling) === null || _b === void 0 ? void 0 : _b.contentEditable) == "false")
2341
- text = text.slice(0, text.length - 1);
2342
- return text;
2344
+ for (let point of this.points)
2345
+ if (point.node == node)
2346
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2347
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2348
+ let nextBreak = -1, breakSize = 1, m;
2349
+ if (this.lineSeparator) {
2350
+ nextBreak = text.indexOf(this.lineSeparator, off);
2351
+ breakSize = this.lineSeparator.length;
2352
+ }
2353
+ else if (m = re.exec(text)) {
2354
+ nextBreak = m.index;
2355
+ breakSize = m[0].length;
2356
+ }
2357
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2358
+ if (nextBreak < 0)
2359
+ break;
2360
+ this.lineBreak();
2361
+ if (breakSize > 1)
2362
+ for (let point of this.points)
2363
+ if (point.node == node && point.pos > this.text.length)
2364
+ point.pos -= breakSize - 1;
2365
+ off = nextBreak + breakSize;
2366
+ }
2343
2367
  }
2344
2368
  readNode(node) {
2345
2369
  if (node.cmIgnore)
2346
2370
  return;
2347
2371
  let view = ContentView.get(node);
2348
2372
  let fromView = view && view.overrideDOMText;
2349
- let text;
2350
- if (fromView != null)
2351
- text = fromView.sliceString(0, undefined, this.lineBreak);
2352
- else if (node.nodeType == 3)
2353
- text = this.readTextNode(node);
2354
- else if (node.nodeName == "BR")
2355
- text = node.nextSibling ? this.lineBreak : "";
2356
- else if (node.nodeType == 1)
2373
+ if (fromView != null) {
2374
+ this.findPointInside(node);
2375
+ for (let i = fromView.iter(); !i.next().done;) {
2376
+ if (i.lineBreak)
2377
+ this.lineBreak();
2378
+ else
2379
+ this.append(i.value);
2380
+ }
2381
+ }
2382
+ else if (node.nodeType == 3) {
2383
+ this.readTextNode(node);
2384
+ }
2385
+ else if (node.nodeName == "BR") {
2386
+ if (node.nextSibling)
2387
+ this.lineBreak();
2388
+ }
2389
+ else if (node.nodeType == 1) {
2357
2390
  this.readRange(node.firstChild, null);
2358
- if (text != null) {
2359
- this.findPointIn(node, text.length);
2360
- this.text += text;
2361
- // Chrome inserts two newlines when pressing shift-enter at the
2362
- // end of a line. This drops one of those.
2363
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2364
- this.text = this.text.slice(0, -1);
2365
2391
  }
2366
2392
  }
2367
2393
  findPointBefore(node, next) {
@@ -2369,10 +2395,10 @@ class DOMReader {
2369
2395
  if (point.node == node && node.childNodes[point.offset] == next)
2370
2396
  point.pos = this.text.length;
2371
2397
  }
2372
- findPointIn(node, maxLen) {
2398
+ findPointInside(node) {
2373
2399
  for (let point of this.points)
2374
- if (point.node == node)
2375
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2400
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2401
+ point.pos = this.text.length;
2376
2402
  }
2377
2403
  }
2378
2404
  function isBlockElement(node) {
@@ -2815,16 +2841,16 @@ function computeCompositionDeco(view, changes) {
2815
2841
  let { from, to, node, text: textNode } = surrounding;
2816
2842
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2817
2843
  let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2818
- new DOMReader([], view).readRange(node.firstChild, null).text;
2844
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2819
2845
  if (newTo - newFrom < text.length) {
2820
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2846
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2821
2847
  newTo = newFrom + text.length;
2822
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2848
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2823
2849
  newFrom = newTo - text.length;
2824
2850
  else
2825
2851
  return Decoration.none;
2826
2852
  }
2827
- else if (state.sliceDoc(newFrom, newTo) != text) {
2853
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2828
2854
  return Decoration.none;
2829
2855
  }
2830
2856
  return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
@@ -4697,6 +4723,12 @@ class ViewState {
4697
4723
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4698
4724
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4699
4725
  let result = 0, bias = 0;
4726
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4727
+ if (oracle.lineWrapping)
4728
+ measureContent = true;
4729
+ this.editorWidth = view.scrollDOM.clientWidth;
4730
+ result |= 8 /* Geometry */;
4731
+ }
4700
4732
  if (measureContent) {
4701
4733
  this.mustMeasureContent = false;
4702
4734
  this.contentDOMHeight = dom.clientHeight;
@@ -4722,11 +4754,9 @@ class ViewState {
4722
4754
  if (!this.inView)
4723
4755
  return 0;
4724
4756
  let contentWidth = dom.clientWidth;
4725
- if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4726
- this.editorWidth != view.scrollDOM.clientWidth) {
4757
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4727
4758
  this.contentDOMWidth = contentWidth;
4728
4759
  this.editorHeight = view.scrollDOM.clientHeight;
4729
- this.editorWidth = view.scrollDOM.clientWidth;
4730
4760
  result |= 8 /* Geometry */;
4731
4761
  }
4732
4762
  if (measureContent) {
@@ -4781,8 +4811,9 @@ class ViewState {
4781
4811
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4782
4812
  // If scrollTarget is given, make sure the viewport includes that position
4783
4813
  if (scrollTarget) {
4784
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4814
+ let { head } = scrollTarget.range;
4785
4815
  if (head < viewport.from || head > viewport.to) {
4816
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4786
4817
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4787
4818
  if (scrollTarget.y == "center")
4788
4819
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5582,9 +5613,8 @@ function applyDOMChange(view, start, end, typeOver) {
5582
5613
  return;
5583
5614
  let { from, to } = bounds;
5584
5615
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5585
- let reader = new DOMReader(selPoints, view);
5616
+ let reader = new DOMReader(selPoints, view.state);
5586
5617
  reader.readRange(bounds.startDOM, bounds.endDOM);
5587
- newSel = selectionFromPoints(selPoints, from);
5588
5618
  let preferredPos = sel.from, preferredSide = null;
5589
5619
  // Prefer anchoring to end when Backspace is pressed (or, on
5590
5620
  // Android, when something was deleted)
@@ -5593,10 +5623,29 @@ function applyDOMChange(view, start, end, typeOver) {
5593
5623
  preferredPos = sel.to;
5594
5624
  preferredSide = "end";
5595
5625
  }
5596
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5597
- if (diff)
5626
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5627
+ if (diff) {
5628
+ let orig = diff;
5629
+ // Chrome inserts two newlines when pressing shift-enter at the
5630
+ // end of a line. This drops one of those.
5631
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5632
+ diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5633
+ diff.toB--;
5634
+ // Strip leading and trailing zero-width spaces from the inserted
5635
+ // content, to work around widget buffers being moved into text
5636
+ // nodes by the browser.
5637
+ while (diff.from < diff.toB && reader.text[diff.from] == "\u200b") {
5638
+ diff = { from: diff.from + 1, toA: diff.toA, toB: diff.toB };
5639
+ selPoints.forEach(p => p.pos -= p.pos > orig.from ? 1 : 0);
5640
+ }
5641
+ while (diff.toB > diff.from && reader.text[diff.toB - 1] == "\u200b") {
5642
+ diff = { from: diff.from, toA: diff.toA, toB: diff.toB - 1 };
5643
+ selPoints.forEach(p => p.pos -= p.pos > orig.toB ? 1 : 0);
5644
+ }
5598
5645
  change = { from: from + diff.from, to: from + diff.toA,
5599
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5646
+ insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5647
+ }
5648
+ newSel = selectionFromPoints(selPoints, from);
5600
5649
  }
5601
5650
  else if (view.hasFocus || !view.state.facet(editable)) {
5602
5651
  let domSel = view.observer.selectionRange;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
1
+ import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, EditorState, CharCategory, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
2
2
  import { StyleModule } from 'style-mod';
3
3
  import { RangeSet, RangeValue, RangeSetBuilder } from '@codemirror/rangeset';
4
4
  export { Range } from '@codemirror/rangeset';
@@ -599,9 +599,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
599
599
  replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
600
600
  }
601
601
 
602
- let [nav, doc] = typeof navigator != "undefined"
603
- ? [navigator, document]
604
- : [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
602
+ let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
603
+ let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
605
604
  const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
606
605
  const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
607
606
  const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
@@ -1127,8 +1126,8 @@ class Decoration extends RangeValue {
1127
1126
  static replace(spec) {
1128
1127
  let block = !!spec.block;
1129
1128
  let { start, end } = getInclusive(spec, block);
1130
- let startSide = block ? (start ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */;
1131
- let endSide = block ? (end ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */;
1129
+ let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
1130
+ let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
1132
1131
  return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1133
1132
  }
1134
1133
  /**
@@ -1573,11 +1572,13 @@ class ContentBuilder {
1573
1572
  this.openStart = openStart;
1574
1573
  }
1575
1574
  filterPoint(from, to, value, index) {
1576
- if (index >= this.disallowBlockEffectsBelow || !(value instanceof PointDecoration))
1577
- return true;
1578
- if (value.block)
1579
- throw new RangeError("Block decorations may not be specified via plugins");
1580
- return to <= this.doc.lineAt(this.pos).to;
1575
+ if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
1576
+ if (value.block)
1577
+ throw new RangeError("Block decorations may not be specified via plugins");
1578
+ if (to > this.doc.lineAt(this.pos).to)
1579
+ throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1580
+ }
1581
+ return true;
1581
1582
  }
1582
1583
  static build(text, from, to, decorations, pluginDecorationLength) {
1583
1584
  let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
@@ -2300,12 +2301,18 @@ function moveVisually(line, order, dir, start, forward) {
2300
2301
  return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2301
2302
  }
2302
2303
 
2304
+ const LineBreakPlaceholder = "\uffff";
2303
2305
  class DOMReader {
2304
- constructor(points, view) {
2306
+ constructor(points, state) {
2305
2307
  this.points = points;
2306
- this.view = view;
2307
2308
  this.text = "";
2308
- this.lineBreak = view.state.lineBreak;
2309
+ this.lineSeparator = state.facet(EditorState.lineSeparator);
2310
+ }
2311
+ append(text) {
2312
+ this.text += text;
2313
+ }
2314
+ lineBreak() {
2315
+ this.text += LineBreakPlaceholder;
2309
2316
  }
2310
2317
  readRange(start, end) {
2311
2318
  if (!start)
@@ -2321,42 +2328,61 @@ class DOMReader {
2321
2328
  if (view && nextView ? view.breakAfter :
2322
2329
  (view ? view.breakAfter : isBlockElement(cur)) ||
2323
2330
  (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2324
- this.text += this.lineBreak;
2331
+ this.lineBreak();
2325
2332
  cur = next;
2326
2333
  }
2327
2334
  this.findPointBefore(parent, end);
2328
2335
  return this;
2329
2336
  }
2330
2337
  readTextNode(node) {
2331
- var _a, _b;
2332
2338
  let text = node.nodeValue;
2333
- if (/^\u200b/.test(text) && ((_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.contentEditable) == "false")
2334
- text = text.slice(1);
2335
- if (/\u200b$/.test(text) && ((_b = node.nextSibling) === null || _b === void 0 ? void 0 : _b.contentEditable) == "false")
2336
- text = text.slice(0, text.length - 1);
2337
- return text;
2339
+ for (let point of this.points)
2340
+ if (point.node == node)
2341
+ point.pos = this.text.length + Math.min(point.offset, text.length);
2342
+ for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2343
+ let nextBreak = -1, breakSize = 1, m;
2344
+ if (this.lineSeparator) {
2345
+ nextBreak = text.indexOf(this.lineSeparator, off);
2346
+ breakSize = this.lineSeparator.length;
2347
+ }
2348
+ else if (m = re.exec(text)) {
2349
+ nextBreak = m.index;
2350
+ breakSize = m[0].length;
2351
+ }
2352
+ this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2353
+ if (nextBreak < 0)
2354
+ break;
2355
+ this.lineBreak();
2356
+ if (breakSize > 1)
2357
+ for (let point of this.points)
2358
+ if (point.node == node && point.pos > this.text.length)
2359
+ point.pos -= breakSize - 1;
2360
+ off = nextBreak + breakSize;
2361
+ }
2338
2362
  }
2339
2363
  readNode(node) {
2340
2364
  if (node.cmIgnore)
2341
2365
  return;
2342
2366
  let view = ContentView.get(node);
2343
2367
  let fromView = view && view.overrideDOMText;
2344
- let text;
2345
- if (fromView != null)
2346
- text = fromView.sliceString(0, undefined, this.lineBreak);
2347
- else if (node.nodeType == 3)
2348
- text = this.readTextNode(node);
2349
- else if (node.nodeName == "BR")
2350
- text = node.nextSibling ? this.lineBreak : "";
2351
- else if (node.nodeType == 1)
2368
+ if (fromView != null) {
2369
+ this.findPointInside(node);
2370
+ for (let i = fromView.iter(); !i.next().done;) {
2371
+ if (i.lineBreak)
2372
+ this.lineBreak();
2373
+ else
2374
+ this.append(i.value);
2375
+ }
2376
+ }
2377
+ else if (node.nodeType == 3) {
2378
+ this.readTextNode(node);
2379
+ }
2380
+ else if (node.nodeName == "BR") {
2381
+ if (node.nextSibling)
2382
+ this.lineBreak();
2383
+ }
2384
+ else if (node.nodeType == 1) {
2352
2385
  this.readRange(node.firstChild, null);
2353
- if (text != null) {
2354
- this.findPointIn(node, text.length);
2355
- this.text += text;
2356
- // Chrome inserts two newlines when pressing shift-enter at the
2357
- // end of a line. This drops one of those.
2358
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2359
- this.text = this.text.slice(0, -1);
2360
2386
  }
2361
2387
  }
2362
2388
  findPointBefore(node, next) {
@@ -2364,10 +2390,10 @@ class DOMReader {
2364
2390
  if (point.node == node && node.childNodes[point.offset] == next)
2365
2391
  point.pos = this.text.length;
2366
2392
  }
2367
- findPointIn(node, maxLen) {
2393
+ findPointInside(node) {
2368
2394
  for (let point of this.points)
2369
- if (point.node == node)
2370
- point.pos = this.text.length + Math.min(point.offset, maxLen);
2395
+ if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2396
+ point.pos = this.text.length;
2371
2397
  }
2372
2398
  }
2373
2399
  function isBlockElement(node) {
@@ -2810,16 +2836,16 @@ function computeCompositionDeco(view, changes) {
2810
2836
  let { from, to, node, text: textNode } = surrounding;
2811
2837
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2812
2838
  let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2813
- new DOMReader([], view).readRange(node.firstChild, null).text;
2839
+ new DOMReader([], state).readRange(node.firstChild, null).text;
2814
2840
  if (newTo - newFrom < text.length) {
2815
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2841
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2816
2842
  newTo = newFrom + text.length;
2817
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2843
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2818
2844
  newFrom = newTo - text.length;
2819
2845
  else
2820
2846
  return Decoration.none;
2821
2847
  }
2822
- else if (state.sliceDoc(newFrom, newTo) != text) {
2848
+ else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2823
2849
  return Decoration.none;
2824
2850
  }
2825
2851
  return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
@@ -4691,6 +4717,12 @@ class ViewState {
4691
4717
  let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4692
4718
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4693
4719
  let result = 0, bias = 0;
4720
+ if (this.editorWidth != view.scrollDOM.clientWidth) {
4721
+ if (oracle.lineWrapping)
4722
+ measureContent = true;
4723
+ this.editorWidth = view.scrollDOM.clientWidth;
4724
+ result |= 8 /* Geometry */;
4725
+ }
4694
4726
  if (measureContent) {
4695
4727
  this.mustMeasureContent = false;
4696
4728
  this.contentDOMHeight = dom.clientHeight;
@@ -4716,11 +4748,9 @@ class ViewState {
4716
4748
  if (!this.inView)
4717
4749
  return 0;
4718
4750
  let contentWidth = dom.clientWidth;
4719
- if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight ||
4720
- this.editorWidth != view.scrollDOM.clientWidth) {
4751
+ if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4721
4752
  this.contentDOMWidth = contentWidth;
4722
4753
  this.editorHeight = view.scrollDOM.clientHeight;
4723
- this.editorWidth = view.scrollDOM.clientWidth;
4724
4754
  result |= 8 /* Geometry */;
4725
4755
  }
4726
4756
  if (measureContent) {
@@ -4775,8 +4805,9 @@ class ViewState {
4775
4805
  let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4776
4806
  // If scrollTarget is given, make sure the viewport includes that position
4777
4807
  if (scrollTarget) {
4778
- let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4808
+ let { head } = scrollTarget.range;
4779
4809
  if (head < viewport.from || head > viewport.to) {
4810
+ let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4780
4811
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4781
4812
  if (scrollTarget.y == "center")
4782
4813
  topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
@@ -5576,9 +5607,8 @@ function applyDOMChange(view, start, end, typeOver) {
5576
5607
  return;
5577
5608
  let { from, to } = bounds;
5578
5609
  let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5579
- let reader = new DOMReader(selPoints, view);
5610
+ let reader = new DOMReader(selPoints, view.state);
5580
5611
  reader.readRange(bounds.startDOM, bounds.endDOM);
5581
- newSel = selectionFromPoints(selPoints, from);
5582
5612
  let preferredPos = sel.from, preferredSide = null;
5583
5613
  // Prefer anchoring to end when Backspace is pressed (or, on
5584
5614
  // Android, when something was deleted)
@@ -5587,10 +5617,29 @@ function applyDOMChange(view, start, end, typeOver) {
5587
5617
  preferredPos = sel.to;
5588
5618
  preferredSide = "end";
5589
5619
  }
5590
- let diff = findDiff(view.state.sliceDoc(from, to), reader.text, preferredPos - from, preferredSide);
5591
- if (diff)
5620
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5621
+ if (diff) {
5622
+ let orig = diff;
5623
+ // Chrome inserts two newlines when pressing shift-enter at the
5624
+ // end of a line. This drops one of those.
5625
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5626
+ diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5627
+ diff.toB--;
5628
+ // Strip leading and trailing zero-width spaces from the inserted
5629
+ // content, to work around widget buffers being moved into text
5630
+ // nodes by the browser.
5631
+ while (diff.from < diff.toB && reader.text[diff.from] == "\u200b") {
5632
+ diff = { from: diff.from + 1, toA: diff.toA, toB: diff.toB };
5633
+ selPoints.forEach(p => p.pos -= p.pos > orig.from ? 1 : 0);
5634
+ }
5635
+ while (diff.toB > diff.from && reader.text[diff.toB - 1] == "\u200b") {
5636
+ diff = { from: diff.from, toA: diff.toA, toB: diff.toB - 1 };
5637
+ selPoints.forEach(p => p.pos -= p.pos > orig.toB ? 1 : 0);
5638
+ }
5592
5639
  change = { from: from + diff.from, to: from + diff.toA,
5593
- insert: view.state.toText(reader.text.slice(diff.from, diff.toB)) };
5640
+ insert: Text$1.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5641
+ }
5642
+ newSel = selectionFromPoints(selPoints, from);
5594
5643
  }
5595
5644
  else if (view.hasFocus || !view.state.facet(editable)) {
5596
5645
  let domSel = view.observer.selectionRange;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.40",
3
+ "version": "0.19.41",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",