prosemirror-view 1.34.2 → 1.35.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 1.35.0 (2024-10-29)
2
+
3
+ ### New features
4
+
5
+ The new `NodeView.multiType` flag is now required to opt into handling different node types in a single node view. It used to be the default, but this tripped too many people up.
6
+
7
+ ## 1.34.3 (2024-09-19)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix an issue that would, during some types of document updates, leave the document DOM in an improper form.
12
+
13
+ Handle the case where drag-selecting in Firefox creates a selection with multiple distinct ranges.
14
+
1
15
  ## 1.34.2 (2024-09-03)
2
16
 
3
17
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -1564,7 +1564,7 @@ var CustomNodeViewDesc = function (_NodeViewDesc2) {
1564
1564
  key: "update",
1565
1565
  value: function update(node, outerDeco, innerDeco, view) {
1566
1566
  if (this.dirty == NODE_DIRTY) return false;
1567
- if (this.spec.update) {
1567
+ if (this.spec.update && (this.node.type == node.type || this.spec.multiType)) {
1568
1568
  var result = this.spec.update(node, outerDeco, innerDeco);
1569
1569
  if (result) this.updateInner(node, outerDeco, innerDeco, view);
1570
1570
  return result;
@@ -1840,6 +1840,7 @@ var ViewTreeUpdater = function () {
1840
1840
  this.index++;
1841
1841
  return true;
1842
1842
  } else if (!locked && (updated = this.recreateWrapper(next, node, outerDeco, innerDeco, view, pos))) {
1843
+ this.destroyBetween(this.index, i);
1843
1844
  this.top.children[this.index] = updated;
1844
1845
  if (updated.contentDOM) {
1845
1846
  updated.dirty = CONTENT_DIRTY;
@@ -1858,7 +1859,7 @@ var ViewTreeUpdater = function () {
1858
1859
  }, {
1859
1860
  key: "recreateWrapper",
1860
1861
  value: function recreateWrapper(next, node, outerDeco, innerDeco, view, pos) {
1861
- if (next.dirty || node.isAtom || !next.children.length || !next.node.content.eq(node.content)) return null;
1862
+ if (next.dirty || node.isAtom || !next.children.length || !next.node.content.eq(node.content) || !sameOuterDeco(outerDeco, next.outerDeco) || !innerDeco.eq(next.innerDeco)) return null;
1862
1863
  var wrapper = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos);
1863
1864
  if (wrapper.contentDOM) {
1864
1865
  wrapper.children = next.children;
@@ -2105,10 +2106,10 @@ function selectionFromDOM(view) {
2105
2106
  var head = view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset, 1);
2106
2107
  if (head < 0) return null;
2107
2108
  var $head = doc.resolve(head),
2108
- $anchor,
2109
+ anchor,
2109
2110
  selection;
2110
2111
  if (selectionCollapsed(domSel)) {
2111
- $anchor = $head;
2112
+ anchor = head;
2112
2113
  while (nearestDesc && !nearestDesc.node) nearestDesc = nearestDesc.parent;
2113
2114
  var nearestDescNode = nearestDesc.node;
2114
2115
  if (nearestDesc && nearestDescNode.isAtom && prosemirrorState.NodeSelection.isSelectable(nearestDescNode) && nearestDesc.parent && !(nearestDescNode.isInline && isOnEdge(domSel.focusNode, domSel.focusOffset, nearestDesc.dom))) {
@@ -2116,10 +2117,26 @@ function selectionFromDOM(view) {
2116
2117
  selection = new prosemirrorState.NodeSelection(head == pos ? $head : doc.resolve(pos));
2117
2118
  }
2118
2119
  } else {
2119
- var anchor = view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset, 1);
2120
+ if (domSel instanceof view.dom.ownerDocument.defaultView.Selection && domSel.rangeCount > 1) {
2121
+ var min = head,
2122
+ max = head;
2123
+ for (var i = 0; i < domSel.rangeCount; i++) {
2124
+ var range = domSel.getRangeAt(i);
2125
+ min = Math.min(min, view.docView.posFromDOM(range.startContainer, range.startOffset, 1));
2126
+ max = Math.max(max, view.docView.posFromDOM(range.endContainer, range.endOffset, -1));
2127
+ }
2128
+ if (min < 0) return null;
2129
+ var _ref3 = max == view.state.selection.anchor ? [max, min] : [min, max];
2130
+ var _ref4 = _slicedToArray(_ref3, 2);
2131
+ anchor = _ref4[0];
2132
+ head = _ref4[1];
2133
+ $head = doc.resolve(head);
2134
+ } else {
2135
+ anchor = view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset, 1);
2136
+ }
2120
2137
  if (anchor < 0) return null;
2121
- $anchor = doc.resolve(anchor);
2122
2138
  }
2139
+ var $anchor = doc.resolve(anchor);
2123
2140
  if (!selection) {
2124
2141
  var bias = origin == "pointer" || view.state.selection.head < $head.pos && !inWidget ? 1 : -1;
2125
2142
  selection = selectionBetween(view, $anchor, $head, bias);
@@ -2910,7 +2927,7 @@ editHandlers.keydown = function (view, _event) {
2910
2927
  view.input.lastKeyCode = event.keyCode;
2911
2928
  view.input.lastKeyCodeTime = Date.now();
2912
2929
  if (android && chrome && event.keyCode == 13) return;
2913
- if (view.domObserver.selectionChanged(view.domSelectionRange())) view.domObserver.flush();else if (event.keyCode != 229) view.domObserver.forceFlush();
2930
+ if (event.keyCode != 229) view.domObserver.forceFlush();
2914
2931
  if (ios && event.keyCode == 13 && !event.ctrlKey && !event.altKey && !event.metaKey) {
2915
2932
  var now = Date.now();
2916
2933
  view.input.lastIOSEnter = now;
@@ -4275,11 +4292,6 @@ var DOMObserver = function () {
4275
4292
  }
4276
4293
  return this.queue;
4277
4294
  }
4278
- }, {
4279
- key: "selectionChanged",
4280
- value: function selectionChanged(sel) {
4281
- return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
4282
- }
4283
4295
  }, {
4284
4296
  key: "flush",
4285
4297
  value: function flush() {
@@ -4287,8 +4299,8 @@ var DOMObserver = function () {
4287
4299
  if (!view.docView || this.flushingSoon > -1) return;
4288
4300
  var mutations = this.pendingRecords();
4289
4301
  if (mutations.length) this.queue = [];
4290
- var sel = view.domSelectionRange(),
4291
- newSel = this.selectionChanged(sel);
4302
+ var sel = view.domSelectionRange();
4303
+ var newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4292
4304
  var from = -1,
4293
4305
  to = -1,
4294
4306
  typeOver = false,
@@ -4417,11 +4429,11 @@ function rangeToSelectionRange(view, range) {
4417
4429
  focusOffset = range.endOffset;
4418
4430
  var currentAnchor = view.domAtPos(view.state.selection.anchor);
4419
4431
  if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset)) {
4420
- var _ref3 = [focusNode, focusOffset, anchorNode, anchorOffset];
4421
- anchorNode = _ref3[0];
4422
- anchorOffset = _ref3[1];
4423
- focusNode = _ref3[2];
4424
- focusOffset = _ref3[3];
4432
+ var _ref5 = [focusNode, focusOffset, anchorNode, anchorOffset];
4433
+ anchorNode = _ref5[0];
4434
+ anchorOffset = _ref5[1];
4435
+ focusNode = _ref5[2];
4436
+ focusOffset = _ref5[3];
4425
4437
  }
4426
4438
  return {
4427
4439
  anchorNode: anchorNode,
package/dist/index.d.cts CHANGED
@@ -265,19 +265,28 @@ interface NodeView {
265
265
  */
266
266
  contentDOM?: HTMLElement | null;
267
267
  /**
268
- When given, this will be called when the view is updating itself.
269
- It will be given a node (possibly of a different type), an array
270
- of active decorations around the node (which are automatically
271
- drawn, and the node view may ignore if it isn't interested in
272
- them), and a [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that
273
- represents any decorations that apply to the content of the node
274
- (which again may be ignored). It should return true if it was
275
- able to update to that node, and false otherwise. If the node
276
- view has a `contentDOM` property (or no `dom` property), updating
277
- its child nodes will be handled by ProseMirror.
268
+ When given, this will be called when the view is updating
269
+ itself. It will be given a node, an array of active decorations
270
+ around the node (which are automatically drawn, and the node
271
+ view may ignore if it isn't interested in them), and a
272
+ [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that represents any
273
+ decorations that apply to the content of the node (which again
274
+ may be ignored). It should return true if it was able to update
275
+ to that node, and false otherwise. If the node view has a
276
+ `contentDOM` property (or no `dom` property), updating its child
277
+ nodes will be handled by ProseMirror.
278
278
  */
279
279
  update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean;
280
280
  /**
281
+ By default, `update` will only be called when a node of the same
282
+ node type appears in this view's position. When you set this to
283
+ true, it will be called for any node, making it possible to have
284
+ a node view that representsmultiple types of nodes. You will
285
+ need to check the type of the nodes you get in `update` and
286
+ return `false` for types you cannot handle.
287
+ */
288
+ multiType?: boolean;
289
+ /**
281
290
  Can be used to override the way the node's selected status (as a
282
291
  node selection) is displayed.
283
292
  */
package/dist/index.d.ts CHANGED
@@ -265,19 +265,28 @@ interface NodeView {
265
265
  */
266
266
  contentDOM?: HTMLElement | null;
267
267
  /**
268
- When given, this will be called when the view is updating itself.
269
- It will be given a node (possibly of a different type), an array
270
- of active decorations around the node (which are automatically
271
- drawn, and the node view may ignore if it isn't interested in
272
- them), and a [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that
273
- represents any decorations that apply to the content of the node
274
- (which again may be ignored). It should return true if it was
275
- able to update to that node, and false otherwise. If the node
276
- view has a `contentDOM` property (or no `dom` property), updating
277
- its child nodes will be handled by ProseMirror.
268
+ When given, this will be called when the view is updating
269
+ itself. It will be given a node, an array of active decorations
270
+ around the node (which are automatically drawn, and the node
271
+ view may ignore if it isn't interested in them), and a
272
+ [decoration source](https://prosemirror.net/docs/ref/#view.DecorationSource) that represents any
273
+ decorations that apply to the content of the node (which again
274
+ may be ignored). It should return true if it was able to update
275
+ to that node, and false otherwise. If the node view has a
276
+ `contentDOM` property (or no `dom` property), updating its child
277
+ nodes will be handled by ProseMirror.
278
278
  */
279
279
  update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean;
280
280
  /**
281
+ By default, `update` will only be called when a node of the same
282
+ node type appears in this view's position. When you set this to
283
+ true, it will be called for any node, making it possible to have
284
+ a node view that representsmultiple types of nodes. You will
285
+ need to check the type of the nodes you get in `update` and
286
+ return `false` for types you cannot handle.
287
+ */
288
+ multiType?: boolean;
289
+ /**
281
290
  Can be used to override the way the node's selected status (as a
282
291
  node selection) is displayed.
283
292
  */
package/dist/index.js CHANGED
@@ -1552,7 +1552,7 @@ class CustomNodeViewDesc extends NodeViewDesc {
1552
1552
  update(node, outerDeco, innerDeco, view) {
1553
1553
  if (this.dirty == NODE_DIRTY)
1554
1554
  return false;
1555
- if (this.spec.update) {
1555
+ if (this.spec.update && (this.node.type == node.type || this.spec.multiType)) {
1556
1556
  let result = this.spec.update(node, outerDeco, innerDeco);
1557
1557
  if (result)
1558
1558
  this.updateInner(node, outerDeco, innerDeco, view);
@@ -1870,6 +1870,7 @@ class ViewTreeUpdater {
1870
1870
  return true;
1871
1871
  }
1872
1872
  else if (!locked && (updated = this.recreateWrapper(next, node, outerDeco, innerDeco, view, pos))) {
1873
+ this.destroyBetween(this.index, i);
1873
1874
  this.top.children[this.index] = updated;
1874
1875
  if (updated.contentDOM) {
1875
1876
  updated.dirty = CONTENT_DIRTY;
@@ -1889,7 +1890,8 @@ class ViewTreeUpdater {
1889
1890
  // identical content, move over its children.
1890
1891
  recreateWrapper(next, node, outerDeco, innerDeco, view, pos) {
1891
1892
  if (next.dirty || node.isAtom || !next.children.length ||
1892
- !next.node.content.eq(node.content))
1893
+ !next.node.content.eq(node.content) ||
1894
+ !sameOuterDeco(outerDeco, next.outerDeco) || !innerDeco.eq(next.innerDeco))
1893
1895
  return null;
1894
1896
  let wrapper = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos);
1895
1897
  if (wrapper.contentDOM) {
@@ -2160,9 +2162,9 @@ function selectionFromDOM(view, origin = null) {
2160
2162
  let head = view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset, 1);
2161
2163
  if (head < 0)
2162
2164
  return null;
2163
- let $head = doc.resolve(head), $anchor, selection;
2165
+ let $head = doc.resolve(head), anchor, selection;
2164
2166
  if (selectionCollapsed(domSel)) {
2165
- $anchor = $head;
2167
+ anchor = head;
2166
2168
  while (nearestDesc && !nearestDesc.node)
2167
2169
  nearestDesc = nearestDesc.parent;
2168
2170
  let nearestDescNode = nearestDesc.node;
@@ -2173,11 +2175,25 @@ function selectionFromDOM(view, origin = null) {
2173
2175
  }
2174
2176
  }
2175
2177
  else {
2176
- let anchor = view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset, 1);
2178
+ if (domSel instanceof view.dom.ownerDocument.defaultView.Selection && domSel.rangeCount > 1) {
2179
+ let min = head, max = head;
2180
+ for (let i = 0; i < domSel.rangeCount; i++) {
2181
+ let range = domSel.getRangeAt(i);
2182
+ min = Math.min(min, view.docView.posFromDOM(range.startContainer, range.startOffset, 1));
2183
+ max = Math.max(max, view.docView.posFromDOM(range.endContainer, range.endOffset, -1));
2184
+ }
2185
+ if (min < 0)
2186
+ return null;
2187
+ [anchor, head] = max == view.state.selection.anchor ? [max, min] : [min, max];
2188
+ $head = doc.resolve(head);
2189
+ }
2190
+ else {
2191
+ anchor = view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset, 1);
2192
+ }
2177
2193
  if (anchor < 0)
2178
2194
  return null;
2179
- $anchor = doc.resolve(anchor);
2180
2195
  }
2196
+ let $anchor = doc.resolve(anchor);
2181
2197
  if (!selection) {
2182
2198
  let bias = origin == "pointer" || (view.state.selection.head < $head.pos && !inWidget) ? 1 : -1;
2183
2199
  selection = selectionBetween(view, $anchor, $head, bias);
@@ -3095,9 +3111,7 @@ editHandlers.keydown = (view, _event) => {
3095
3111
  // and handling them eagerly tends to corrupt the input.
3096
3112
  if (android && chrome && event.keyCode == 13)
3097
3113
  return;
3098
- if (view.domObserver.selectionChanged(view.domSelectionRange()))
3099
- view.domObserver.flush();
3100
- else if (event.keyCode != 229)
3114
+ if (event.keyCode != 229)
3101
3115
  view.domObserver.forceFlush();
3102
3116
  // On iOS, if we preventDefault enter key presses, the virtual
3103
3117
  // keyboard gets confused. So the hack here is to set a flag that
@@ -4620,9 +4634,6 @@ class DOMObserver {
4620
4634
  this.queue.push(mut);
4621
4635
  return this.queue;
4622
4636
  }
4623
- selectionChanged(sel) {
4624
- return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
4625
- }
4626
4637
  flush() {
4627
4638
  let { view } = this;
4628
4639
  if (!view.docView || this.flushingSoon > -1)
@@ -4630,7 +4641,8 @@ class DOMObserver {
4630
4641
  let mutations = this.pendingRecords();
4631
4642
  if (mutations.length)
4632
4643
  this.queue = [];
4633
- let sel = view.domSelectionRange(), newSel = this.selectionChanged(sel);
4644
+ let sel = view.domSelectionRange();
4645
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4634
4646
  let from = -1, to = -1, typeOver = false, added = [];
4635
4647
  if (view.editable) {
4636
4648
  for (let i = 0; i < mutations.length; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.34.2",
3
+ "version": "1.35.0",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -163,17 +163,14 @@ export class DOMObserver {
163
163
  return this.queue
164
164
  }
165
165
 
166
- selectionChanged(sel: DOMSelectionRange) {
167
- return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel)
168
- }
169
-
170
166
  flush() {
171
167
  let {view} = this
172
168
  if (!view.docView || this.flushingSoon > -1) return
173
169
  let mutations = this.pendingRecords()
174
170
  if (mutations.length) this.queue = []
175
171
 
176
- let sel = view.domSelectionRange(), newSel = this.selectionChanged(sel)
172
+ let sel = view.domSelectionRange()
173
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
177
174
 
178
175
  let from = -1, to = -1, typeOver = false, added: Node[] = []
179
176
  if (view.editable) {
package/src/input.ts CHANGED
@@ -112,9 +112,7 @@ editHandlers.keydown = (view: EditorView, _event: Event) => {
112
112
  // to be part of a confused sequence of composition events fired,
113
113
  // and handling them eagerly tends to corrupt the input.
114
114
  if (browser.android && browser.chrome && event.keyCode == 13) return
115
-
116
- if (view.domObserver.selectionChanged(view.domSelectionRange())) view.domObserver.flush()
117
- else if (event.keyCode != 229) view.domObserver.forceFlush()
115
+ if (event.keyCode != 229) view.domObserver.forceFlush()
118
116
 
119
117
  // On iOS, if we preventDefault enter key presses, the virtual
120
118
  // keyboard gets confused. So the hack here is to set a flag that
package/src/selection.ts CHANGED
@@ -12,9 +12,9 @@ export function selectionFromDOM(view: EditorView, origin: string | null = null)
12
12
  let nearestDesc = view.docView.nearestDesc(domSel.focusNode), inWidget = nearestDesc && nearestDesc.size == 0
13
13
  let head = view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset, 1)
14
14
  if (head < 0) return null
15
- let $head = doc.resolve(head), $anchor, selection
15
+ let $head = doc.resolve(head), anchor, selection
16
16
  if (selectionCollapsed(domSel)) {
17
- $anchor = $head
17
+ anchor = head
18
18
  while (nearestDesc && !nearestDesc.node) nearestDesc = nearestDesc.parent
19
19
  let nearestDescNode = (nearestDesc as NodeViewDesc).node
20
20
  if (nearestDesc && nearestDescNode.isAtom && NodeSelection.isSelectable(nearestDescNode) && nearestDesc.parent
@@ -23,10 +23,22 @@ export function selectionFromDOM(view: EditorView, origin: string | null = null)
23
23
  selection = new NodeSelection(head == pos ? $head : doc.resolve(pos))
24
24
  }
25
25
  } else {
26
- let anchor = view.docView.posFromDOM(domSel.anchorNode!, domSel.anchorOffset, 1)
26
+ if (domSel instanceof view.dom.ownerDocument.defaultView!.Selection && domSel.rangeCount > 1) {
27
+ let min = head, max = head
28
+ for (let i = 0; i < domSel.rangeCount; i++) {
29
+ let range = domSel.getRangeAt(i)
30
+ min = Math.min(min, view.docView.posFromDOM(range.startContainer, range.startOffset, 1))
31
+ max = Math.max(max, view.docView.posFromDOM(range.endContainer, range.endOffset, -1))
32
+ }
33
+ if (min < 0) return null
34
+ ;[anchor, head] = max == view.state.selection.anchor ? [max, min] : [min, max]
35
+ $head = doc.resolve(head)
36
+ } else {
37
+ anchor = view.docView.posFromDOM(domSel.anchorNode!, domSel.anchorOffset, 1)
38
+ }
27
39
  if (anchor < 0) return null
28
- $anchor = doc.resolve(anchor)
29
40
  }
41
+ let $anchor = doc.resolve(anchor)
30
42
 
31
43
  if (!selection) {
32
44
  let bias = origin == "pointer" || (view.state.selection.head < $head.pos && !inWidget) ? 1 : -1
package/src/viewdesc.ts CHANGED
@@ -33,18 +33,26 @@ export interface NodeView {
33
33
  /// (or deciding not to render) its child nodes.
34
34
  contentDOM?: HTMLElement | null
35
35
 
36
- /// When given, this will be called when the view is updating itself.
37
- /// It will be given a node (possibly of a different type), an array
38
- /// of active decorations around the node (which are automatically
39
- /// drawn, and the node view may ignore if it isn't interested in
40
- /// them), and a [decoration source](#view.DecorationSource) that
41
- /// represents any decorations that apply to the content of the node
42
- /// (which again may be ignored). It should return true if it was
43
- /// able to update to that node, and false otherwise. If the node
44
- /// view has a `contentDOM` property (or no `dom` property), updating
45
- /// its child nodes will be handled by ProseMirror.
36
+ /// When given, this will be called when the view is updating
37
+ /// itself. It will be given a node, an array of active decorations
38
+ /// around the node (which are automatically drawn, and the node
39
+ /// view may ignore if it isn't interested in them), and a
40
+ /// [decoration source](#view.DecorationSource) that represents any
41
+ /// decorations that apply to the content of the node (which again
42
+ /// may be ignored). It should return true if it was able to update
43
+ /// to that node, and false otherwise. If the node view has a
44
+ /// `contentDOM` property (or no `dom` property), updating its child
45
+ /// nodes will be handled by ProseMirror.
46
46
  update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean
47
47
 
48
+ /// By default, `update` will only be called when a node of the same
49
+ /// node type appears in this view's position. When you set this to
50
+ /// true, it will be called for any node, making it possible to have
51
+ /// a node view that representsmultiple types of nodes. You will
52
+ /// need to check the type of the nodes you get in `update` and
53
+ /// return `false` for types you cannot handle.
54
+ multiType?: boolean
55
+
48
56
  /// Can be used to override the way the node's selected status (as a
49
57
  /// node selection) is displayed.
50
58
  selectNode?: () => void
@@ -938,7 +946,7 @@ class CustomNodeViewDesc extends NodeViewDesc {
938
946
  // updates the children.
939
947
  update(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView) {
940
948
  if (this.dirty == NODE_DIRTY) return false
941
- if (this.spec.update) {
949
+ if (this.spec.update && (this.node.type == node.type || this.spec.multiType)) {
942
950
  let result = this.spec.update(node, outerDeco, innerDeco)
943
951
  if (result) this.updateInner(node, outerDeco, innerDeco, view)
944
952
  return result
@@ -1249,6 +1257,7 @@ class ViewTreeUpdater {
1249
1257
  this.index++
1250
1258
  return true
1251
1259
  } else if (!locked && (updated = this.recreateWrapper(next, node, outerDeco, innerDeco, view, pos))) {
1260
+ this.destroyBetween(this.index, i)
1252
1261
  this.top.children[this.index] = updated
1253
1262
  if (updated.contentDOM) {
1254
1263
  updated.dirty = CONTENT_DIRTY
@@ -1270,7 +1279,8 @@ class ViewTreeUpdater {
1270
1279
  recreateWrapper(next: NodeViewDesc, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
1271
1280
  view: EditorView, pos: number) {
1272
1281
  if (next.dirty || node.isAtom || !next.children.length ||
1273
- !next.node.content.eq(node.content)) return null
1282
+ !next.node.content.eq(node.content) ||
1283
+ !sameOuterDeco(outerDeco, next.outerDeco) || !innerDeco.eq(next.innerDeco)) return null
1274
1284
  let wrapper = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos)
1275
1285
  if (wrapper.contentDOM) {
1276
1286
  wrapper.children = next.children