prosemirror-view 1.33.0 → 1.33.2

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,23 @@
1
+ ## 1.33.2 (2024-03-21)
2
+
3
+ ### Bug fixes
4
+
5
+ Improve Android backspace detection when the native effect is to delete an entire block element.
6
+
7
+ Fix a bug where reconfiguring node views caused the editor to lose focus.
8
+
9
+ Fix an issue where some commands didn't properly scroll the cursor into view on Mobile Safari.
10
+
11
+ Fix a bug where starting a composition with stored marks or after a noninclusive mark, with an IME that keeps the cursor at the start of the composed text (like many Chinese IMEs) disrupted composition on Chrome.
12
+
13
+ Fix a memory leak where a (single) destroyed editor could be kept alive through a reused `Range` object.
14
+
15
+ ## 1.33.1 (2024-02-15)
16
+
17
+ ### Bug fixes
18
+
19
+ Fix a type error in the published type declarations in 1.33.0.
20
+
1
21
  ## 1.33.0 (2024-02-15)
2
22
 
3
23
  ### New features
package/dist/index.cjs CHANGED
@@ -38,6 +38,9 @@ var textRange = function textRange(node, from, to) {
38
38
  range.setStart(node, from || 0);
39
39
  return range;
40
40
  };
41
+ var clearReusedRange = function clearReusedRange() {
42
+ reusedRange = null;
43
+ };
41
44
  var isEquivalentPosition = function isEquivalentPosition(node, off, targetNode, targetOff) {
42
45
  return targetNode && (scanFor(node, off, targetNode, targetOff, -1) || scanFor(node, off, targetNode, targetOff, 1));
43
46
  };
@@ -62,6 +65,36 @@ function scanFor(node, off, targetNode, targetOff, dir) {
62
65
  function nodeSize(node) {
63
66
  return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
64
67
  }
68
+ function textNodeBefore$1(node, offset) {
69
+ for (;;) {
70
+ if (node.nodeType == 3 && offset) return node;
71
+ if (node.nodeType == 1 && offset > 0) {
72
+ if (node.contentEditable != "false") return null;
73
+ node = node.childNodes[offset - 1];
74
+ offset = nodeSize(node);
75
+ } else if (node.parentNode && !hasBlockDesc(node)) {
76
+ offset = domIndex(node);
77
+ node = node.parentNode;
78
+ } else {
79
+ return null;
80
+ }
81
+ }
82
+ }
83
+ function textNodeAfter$1(node, offset) {
84
+ for (;;) {
85
+ if (node.nodeType == 3 && offset < node.nodeValue.length) return node;
86
+ if (node.nodeType == 1 && offset < node.childNodes.length) {
87
+ if (node.contentEditable != "false") return null;
88
+ node = node.childNodes[offset];
89
+ offset = 0;
90
+ } else if (node.parentNode && !hasBlockDesc(node)) {
91
+ offset = domIndex(node) + 1;
92
+ node = node.parentNode;
93
+ } else {
94
+ return null;
95
+ }
96
+ }
97
+ }
65
98
  function isOnEdge(node, offset, parent) {
66
99
  for (var atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd;) {
67
100
  if (node == parent) return true;
@@ -131,6 +164,13 @@ var android = /Android \d/.test(agent);
131
164
  var webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
132
165
  var webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
133
166
  function windowRect(doc) {
167
+ var vp = doc.defaultView && doc.defaultView.visualViewport;
168
+ if (vp) return {
169
+ left: 0,
170
+ right: vp.width,
171
+ top: 0,
172
+ bottom: vp.height
173
+ };
134
174
  return {
135
175
  left: 0,
136
176
  right: doc.documentElement.clientWidth,
@@ -1269,7 +1309,13 @@ var NodeViewDesc = function (_ViewDesc4) {
1269
1309
  to = _view$state$selection.to;
1270
1310
  if (!(view.state.selection instanceof prosemirrorState.TextSelection) || from < pos || to > pos + this.node.content.size) return null;
1271
1311
  var sel = view.domSelectionRange();
1272
- var textNode = nearbyTextNode(sel.focusNode, sel.focusOffset);
1312
+ var textBefore = textNodeBefore$1(sel.focusNode, sel.focusOffset);
1313
+ var textAfter = textNodeAfter$1(sel.focusNode, sel.focusOffset);
1314
+ var textNode = textBefore || textAfter;
1315
+ if (textBefore && textAfter && textBefore != textAfter) {
1316
+ var descAfter = textAfter.pmViewDesc;
1317
+ if (!descAfter || descAfter instanceof TextViewDesc && descAfter.node.text != textAfter.nodeValue) textNode = textAfter;
1318
+ }
1273
1319
  if (!textNode || !this.dom.contains(textNode.parentNode)) return null;
1274
1320
  if (this.node.inlineContent) {
1275
1321
  var text = textNode.nodeValue;
@@ -1998,21 +2044,6 @@ function iosHacks(dom) {
1998
2044
  dom.style.cssText = oldCSS;
1999
2045
  }
2000
2046
  }
2001
- function nearbyTextNode(node, offset) {
2002
- for (;;) {
2003
- if (node.nodeType == 3) return node;
2004
- if (node.nodeType == 1 && offset > 0) {
2005
- if (node.childNodes.length > offset && node.childNodes[offset].nodeType == 3) return node.childNodes[offset];
2006
- node = node.childNodes[offset - 1];
2007
- offset = nodeSize(node);
2008
- } else if (node.nodeType == 1 && offset < node.childNodes.length) {
2009
- node = node.childNodes[offset];
2010
- offset = 0;
2011
- } else {
2012
- return null;
2013
- }
2014
- }
2015
- }
2016
2047
  function findTextInFragment(frag, text, from, to) {
2017
2048
  for (var i = 0, pos = 0; i < frag.childCount && pos <= to;) {
2018
2049
  var child = frag.child(i++),
@@ -4474,13 +4505,6 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4474
4505
  return;
4475
4506
  }
4476
4507
  }
4477
- if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from && parse.sel.head == parse.sel.anchor) {
4478
- var size = change.endB - change.start;
4479
- parse.sel = {
4480
- anchor: parse.sel.anchor + size,
4481
- head: parse.sel.anchor + size
4482
- };
4483
- }
4484
4508
  view.input.domChangeCount++;
4485
4509
  if (view.state.selection.from < view.state.selection.to && change.start == change.endB && view.state.selection instanceof prosemirrorState.TextSelection) {
4486
4510
  if (change.start > view.state.selection.from && change.start <= view.state.selection.from + 2 && view.state.selection.from >= parse.from) {
@@ -4508,7 +4532,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4508
4532
  view.input.lastIOSEnter = 0;
4509
4533
  return;
4510
4534
  }
4511
- if (view.state.selection.anchor > change.start && looksLikeJoin(doc, change.start, change.endA, $from, $to) && view.someProp("handleKeyDown", function (f) {
4535
+ if (view.state.selection.anchor > change.start && looksLikeBackspace(doc, change.start, change.endA, $from, $to) && view.someProp("handleKeyDown", function (f) {
4512
4536
  return f(view, keyEvent(8, "Backspace"));
4513
4537
  })) {
4514
4538
  if (android && chrome) view.domObserver.suppressSelectionUpdates();
@@ -4593,9 +4617,13 @@ function isMarkChange(cur, prev) {
4593
4617
  type: type
4594
4618
  };
4595
4619
  }
4596
- function looksLikeJoin(old, start, end, $newStart, $newEnd) {
4597
- if (!$newStart.parent.isTextblock || end - start <= $newEnd.pos - $newStart.pos || skipClosingAndOpening($newStart, true, false) < $newEnd.pos) return false;
4620
+ function looksLikeBackspace(old, start, end, $newStart, $newEnd) {
4621
+ if (end - start <= $newEnd.pos - $newStart.pos || skipClosingAndOpening($newStart, true, false) < $newEnd.pos) return false;
4598
4622
  var $start = old.resolve(start);
4623
+ if (!$newStart.parent.isTextblock) {
4624
+ var after = $start.nodeAfter;
4625
+ return after != null && end == start + after.nodeSize;
4626
+ }
4599
4627
  if ($start.parentOffset < $start.parent.content.size || !$start.parent.isTextblock) return false;
4600
4628
  var $next = old.resolve(skipClosingAndOpening($start, true, true));
4601
4629
  if (!$next.parent.isTextblock || $next.pos > end || skipClosingAndOpening($next, true, false) < end) return false;
@@ -4771,7 +4799,7 @@ var EditorView = function () {
4771
4799
  if (updateDoc) {
4772
4800
  var chromeKludge = chrome ? this.trackWrites = this.domSelectionRange().focusNode : null;
4773
4801
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
4774
- this.docView.updateOuterDeco([]);
4802
+ this.docView.updateOuterDeco(outerDeco);
4775
4803
  this.docView.destroy();
4776
4804
  this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
4777
4805
  }
@@ -4978,6 +5006,7 @@ var EditorView = function () {
4978
5006
  }
4979
5007
  this.docView.destroy();
4980
5008
  this.docView = null;
5009
+ clearReusedRange();
4981
5010
  }
4982
5011
  }, {
4983
5012
  key: "isDestroyed",
package/dist/index.d.cts CHANGED
@@ -210,11 +210,21 @@ declare class DecorationSet implements DecorationSource {
210
210
  */
211
211
  remove(decorations: Decoration[]): DecorationSet;
212
212
  private removeInner;
213
+ forChild(offset: number, node: Node): DecorationSet | DecorationGroup;
213
214
  /**
214
215
  The empty set of decorations.
215
216
  */
216
217
  static empty: DecorationSet;
217
218
  }
219
+ declare class DecorationGroup implements DecorationSource {
220
+ readonly members: readonly DecorationSet[];
221
+ constructor(members: readonly DecorationSet[]);
222
+ map(mapping: Mapping, doc: Node): DecorationSource;
223
+ forChild(offset: number, child: Node): DecorationSource | DecorationSet;
224
+ eq(other: DecorationGroup): boolean;
225
+ locals(node: Node): readonly any[];
226
+ static from(members: readonly DecorationSource[]): DecorationSource;
227
+ }
218
228
 
219
229
  declare global {
220
230
  interface Node {
package/dist/index.d.ts CHANGED
@@ -210,11 +210,21 @@ declare class DecorationSet implements DecorationSource {
210
210
  */
211
211
  remove(decorations: Decoration[]): DecorationSet;
212
212
  private removeInner;
213
+ forChild(offset: number, node: Node): DecorationSet | DecorationGroup;
213
214
  /**
214
215
  The empty set of decorations.
215
216
  */
216
217
  static empty: DecorationSet;
217
218
  }
219
+ declare class DecorationGroup implements DecorationSource {
220
+ readonly members: readonly DecorationSet[];
221
+ constructor(members: readonly DecorationSet[]);
222
+ map(mapping: Mapping, doc: Node): DecorationSource;
223
+ forChild(offset: number, child: Node): DecorationSource | DecorationSet;
224
+ eq(other: DecorationGroup): boolean;
225
+ locals(node: Node): readonly any[];
226
+ static from(members: readonly DecorationSource[]): DecorationSource;
227
+ }
218
228
 
219
229
  declare global {
220
230
  interface Node {
package/dist/index.js CHANGED
@@ -23,6 +23,9 @@ const textRange = function (node, from, to) {
23
23
  range.setStart(node, from || 0);
24
24
  return range;
25
25
  };
26
+ const clearReusedRange = function () {
27
+ reusedRange = null;
28
+ };
26
29
  // Scans forward and backward through DOM positions equivalent to the
27
30
  // given one to see if the two are in the same place (i.e. after a
28
31
  // text node vs at the end of that text node)
@@ -57,6 +60,44 @@ function scanFor(node, off, targetNode, targetOff, dir) {
57
60
  function nodeSize(node) {
58
61
  return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
59
62
  }
63
+ function textNodeBefore$1(node, offset) {
64
+ for (;;) {
65
+ if (node.nodeType == 3 && offset)
66
+ return node;
67
+ if (node.nodeType == 1 && offset > 0) {
68
+ if (node.contentEditable != "false")
69
+ return null;
70
+ node = node.childNodes[offset - 1];
71
+ offset = nodeSize(node);
72
+ }
73
+ else if (node.parentNode && !hasBlockDesc(node)) {
74
+ offset = domIndex(node);
75
+ node = node.parentNode;
76
+ }
77
+ else {
78
+ return null;
79
+ }
80
+ }
81
+ }
82
+ function textNodeAfter$1(node, offset) {
83
+ for (;;) {
84
+ if (node.nodeType == 3 && offset < node.nodeValue.length)
85
+ return node;
86
+ if (node.nodeType == 1 && offset < node.childNodes.length) {
87
+ if (node.contentEditable != "false")
88
+ return null;
89
+ node = node.childNodes[offset];
90
+ offset = 0;
91
+ }
92
+ else if (node.parentNode && !hasBlockDesc(node)) {
93
+ offset = domIndex(node) + 1;
94
+ node = node.parentNode;
95
+ }
96
+ else {
97
+ return null;
98
+ }
99
+ }
100
+ }
60
101
  function isOnEdge(node, offset, parent) {
61
102
  for (let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd;) {
62
103
  if (node == parent)
@@ -133,6 +174,12 @@ const webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
133
174
  const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
134
175
 
135
176
  function windowRect(doc) {
177
+ let vp = doc.defaultView && doc.defaultView.visualViewport;
178
+ if (vp)
179
+ return {
180
+ left: 0, right: vp.width,
181
+ top: 0, bottom: vp.height
182
+ };
136
183
  return { left: 0, right: doc.documentElement.clientWidth,
137
184
  top: 0, bottom: doc.documentElement.clientHeight };
138
185
  }
@@ -1331,7 +1378,14 @@ class NodeViewDesc extends ViewDesc {
1331
1378
  if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size)
1332
1379
  return null;
1333
1380
  let sel = view.domSelectionRange();
1334
- let textNode = nearbyTextNode(sel.focusNode, sel.focusOffset);
1381
+ let textBefore = textNodeBefore$1(sel.focusNode, sel.focusOffset);
1382
+ let textAfter = textNodeAfter$1(sel.focusNode, sel.focusOffset);
1383
+ let textNode = textBefore || textAfter;
1384
+ if (textBefore && textAfter && textBefore != textAfter) {
1385
+ let descAfter = textAfter.pmViewDesc;
1386
+ if (!descAfter || descAfter instanceof TextViewDesc && descAfter.node.text != textAfter.nodeValue)
1387
+ textNode = textAfter;
1388
+ }
1335
1389
  if (!textNode || !this.dom.contains(textNode.parentNode))
1336
1390
  return null;
1337
1391
  if (this.node.inlineContent) {
@@ -2042,25 +2096,6 @@ function iosHacks(dom) {
2042
2096
  dom.style.cssText = oldCSS;
2043
2097
  }
2044
2098
  }
2045
- function nearbyTextNode(node, offset) {
2046
- for (;;) {
2047
- if (node.nodeType == 3)
2048
- return node;
2049
- if (node.nodeType == 1 && offset > 0) {
2050
- if (node.childNodes.length > offset && node.childNodes[offset].nodeType == 3)
2051
- return node.childNodes[offset];
2052
- node = node.childNodes[offset - 1];
2053
- offset = nodeSize(node);
2054
- }
2055
- else if (node.nodeType == 1 && offset < node.childNodes.length) {
2056
- node = node.childNodes[offset];
2057
- offset = 0;
2058
- }
2059
- else {
2060
- return null;
2061
- }
2062
- }
2063
- }
2064
2099
  // Find a piece of text in an inline fragment, overlapping from-to
2065
2100
  function findTextInFragment(frag, text, from, to) {
2066
2101
  for (let i = 0, pos = 0; i < frag.childCount && pos <= to;) {
@@ -4022,9 +4057,6 @@ class DecorationSet {
4022
4057
  return this;
4023
4058
  return local.length || children.length ? new DecorationSet(local, children) : empty;
4024
4059
  }
4025
- /**
4026
- @internal
4027
- */
4028
4060
  forChild(offset, node) {
4029
4061
  if (this == empty)
4030
4062
  return this;
@@ -4825,13 +4857,6 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4825
4857
  return;
4826
4858
  }
4827
4859
  }
4828
- // Chrome sometimes leaves the cursor before the inserted text when
4829
- // composing after a cursor wrapper. This moves it forward.
4830
- if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
4831
- parse.sel.head == parse.sel.anchor) {
4832
- let size = change.endB - change.start;
4833
- parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
4834
- }
4835
4860
  view.input.domChangeCount++;
4836
4861
  // Handle the case where overwriting a selection by typing matches
4837
4862
  // the start or end of the selected content, creating a change
@@ -4877,7 +4902,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4877
4902
  }
4878
4903
  // Same for backspace
4879
4904
  if (view.state.selection.anchor > change.start &&
4880
- looksLikeJoin(doc, change.start, change.endA, $from, $to) &&
4905
+ looksLikeBackspace(doc, change.start, change.endA, $from, $to) &&
4881
4906
  view.someProp("handleKeyDown", f => f(view, keyEvent(8, "Backspace")))) {
4882
4907
  if (android && chrome)
4883
4908
  view.domObserver.suppressSelectionUpdates(); // #820
@@ -4989,14 +5014,18 @@ function isMarkChange(cur, prev) {
4989
5014
  if (Fragment.from(updated).eq(cur))
4990
5015
  return { mark, type };
4991
5016
  }
4992
- function looksLikeJoin(old, start, end, $newStart, $newEnd) {
4993
- if (!$newStart.parent.isTextblock ||
4994
- // The content must have shrunk
4995
- end - start <= $newEnd.pos - $newStart.pos ||
5017
+ function looksLikeBackspace(old, start, end, $newStart, $newEnd) {
5018
+ if ( // The content must have shrunk
5019
+ end - start <= $newEnd.pos - $newStart.pos ||
4996
5020
  // newEnd must point directly at or after the end of the block that newStart points into
4997
5021
  skipClosingAndOpening($newStart, true, false) < $newEnd.pos)
4998
5022
  return false;
4999
5023
  let $start = old.resolve(start);
5024
+ // Handle the case where, rather than joining blocks, the change just removed an entire block
5025
+ if (!$newStart.parent.isTextblock) {
5026
+ let after = $start.nodeAfter;
5027
+ return after != null && end == start + after.nodeSize;
5028
+ }
5000
5029
  // Start must be at the end of a block
5001
5030
  if ($start.parentOffset < $start.parent.content.size || !$start.parent.isTextblock)
5002
5031
  return false;
@@ -5248,7 +5277,7 @@ class EditorView {
5248
5277
  // did write to the node.
5249
5278
  let chromeKludge = chrome ? (this.trackWrites = this.domSelectionRange().focusNode) : null;
5250
5279
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
5251
- this.docView.updateOuterDeco([]);
5280
+ this.docView.updateOuterDeco(outerDeco);
5252
5281
  this.docView.destroy();
5253
5282
  this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
5254
5283
  }
@@ -5525,6 +5554,7 @@ class EditorView {
5525
5554
  }
5526
5555
  this.docView.destroy();
5527
5556
  this.docView = null;
5557
+ clearReusedRange();
5528
5558
  }
5529
5559
  /**
5530
5560
  This is true when the view has been
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.33.0",
3
+ "version": "1.33.2",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",