prosemirror-view 1.27.2 → 1.28.1

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
+ ## 1.28.1 (2022-09-21)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where the editor would reset its scroll position to the top of the document for any state reconfiguration, even if the document was unchanged.
6
+
7
+ ## 1.28.0 (2022-09-09)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix an issue where the editor wouldn't realize it was focused on IE when IE's resize controls are on an element inside the content. Adjust Chrome click-override behavior to only happen for invisible selections
12
+
13
+ ### New features
14
+
15
+ The new `transformCopied` prop can be used to transform a copied/cut slice before it is serialized to the clipboard. Adjust doc comment
16
+
1
17
  ## 1.27.2 (2022-08-19)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2590,7 +2590,7 @@ function selectionBetween(view, $anchor, $head, bias) {
2590
2590
  }
2591
2591
 
2592
2592
  function hasFocusAndSelection(view) {
2593
- if (view.editable && view.root.activeElement != view.dom) return false;
2593
+ if (view.editable && !view.hasFocus()) return false;
2594
2594
  return hasSelection(view);
2595
2595
  }
2596
2596
 
@@ -2887,10 +2887,14 @@ function captureKeyDown(view, event) {
2887
2887
  }
2888
2888
 
2889
2889
  function serializeForClipboard(view, slice) {
2890
+ view.someProp("transformCopied", function (f) {
2891
+ slice = f(slice);
2892
+ });
2890
2893
  var context = [],
2891
- content = slice.content,
2892
- openStart = slice.openStart,
2893
- openEnd = slice.openEnd;
2894
+ _slice = slice,
2895
+ content = _slice.content,
2896
+ openStart = _slice.openStart,
2897
+ openEnd = _slice.openEnd;
2894
2898
 
2895
2899
  while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild.childCount == 1) {
2896
2900
  openStart--;
@@ -3562,7 +3566,7 @@ var MouseDown = function () {
3562
3566
  setSelectionOrigin(this.view, "pointer");
3563
3567
  } else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
3564
3568
  event.preventDefault();
3565
- } else if (event.button == 0 && (this.flushed || safari && this.mightDrag && !this.mightDrag.node.isAtom || chrome && !(this.view.state.selection instanceof prosemirrorState.TextSelection) && Math.min(Math.abs(pos.pos - this.view.state.selection.from), Math.abs(pos.pos - this.view.state.selection.to)) <= 2)) {
3569
+ } else if (event.button == 0 && (this.flushed || safari && this.mightDrag && !this.mightDrag.node.isAtom || chrome && !this.view.state.selection.visible && Math.min(Math.abs(pos.pos - this.view.state.selection.from), Math.abs(pos.pos - this.view.state.selection.to)) <= 2)) {
3566
3570
  updateSelection(this.view, prosemirrorState.Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer");
3567
3571
  event.preventDefault();
3568
3572
  } else {
@@ -5419,6 +5423,7 @@ var EditorView = function () {
5419
5423
  key: "update",
5420
5424
  value: function update(props) {
5421
5425
  if (props.handleDOMEvents != this._props.handleDOMEvents) ensureListeners(this);
5426
+ var prevProps = this._props;
5422
5427
  this._props = props;
5423
5428
 
5424
5429
  if (props.plugins) {
@@ -5426,7 +5431,7 @@ var EditorView = function () {
5426
5431
  this.directPlugins = props.plugins;
5427
5432
  }
5428
5433
 
5429
- this.updateStateInner(props.state, true);
5434
+ this.updateStateInner(props.state, prevProps);
5430
5435
  }
5431
5436
  }, {
5432
5437
  key: "setProps",
@@ -5448,11 +5453,11 @@ var EditorView = function () {
5448
5453
  }, {
5449
5454
  key: "updateState",
5450
5455
  value: function updateState(state) {
5451
- this.updateStateInner(state, this.state.plugins != state.plugins);
5456
+ this.updateStateInner(state, this._props);
5452
5457
  }
5453
5458
  }, {
5454
5459
  key: "updateStateInner",
5455
- value: function updateStateInner(state, reconfigured) {
5460
+ value: function updateStateInner(state, prevProps) {
5456
5461
  var prev = this.state,
5457
5462
  redraw = false,
5458
5463
  updateSel = false;
@@ -5463,15 +5468,18 @@ var EditorView = function () {
5463
5468
  }
5464
5469
 
5465
5470
  this.state = state;
5471
+ var pluginsChanged = prev.plugins != state.plugins || this._props.plugins != prevProps.plugins;
5466
5472
 
5467
- if (reconfigured) {
5473
+ if (pluginsChanged || this._props.plugins != prevProps.plugins || this._props.nodeViews != prevProps.nodeViews) {
5468
5474
  var nodeViews = buildNodeViews(this);
5469
5475
 
5470
5476
  if (changedNodeViews(nodeViews, this.nodeViews)) {
5471
5477
  this.nodeViews = nodeViews;
5472
5478
  redraw = true;
5473
5479
  }
5480
+ }
5474
5481
 
5482
+ if (pluginsChanged || prevProps.handleDOMEvents != this._props.handleDOMEvents) {
5475
5483
  ensureListeners(this);
5476
5484
  }
5477
5485
 
@@ -5479,7 +5487,7 @@ var EditorView = function () {
5479
5487
  updateCursorWrapper(this);
5480
5488
  var innerDeco = viewDecorations(this),
5481
5489
  outerDeco = computeDocDeco(this);
5482
- var scroll = reconfigured ? "reset" : state.scrollToSelection > prev.scrollToSelection ? "to selection" : "preserve";
5490
+ var scroll = prev.plugins != state.plugins && !prev.doc.eq(state.doc) ? "reset" : state.scrollToSelection > prev.scrollToSelection ? "to selection" : "preserve";
5483
5491
  var updateDoc = redraw || !this.docView.matchesNode(state.doc, outerDeco, innerDeco);
5484
5492
  if (updateDoc || !state.selection.eq(prev.selection)) updateSel = true;
5485
5493
  var oldScrollPos = scroll == "preserve" && updateSel && this.dom.style.overflowAnchor == null && storeScrollPos(this);
@@ -5588,6 +5596,19 @@ var EditorView = function () {
5588
5596
  }, {
5589
5597
  key: "hasFocus",
5590
5598
  value: function hasFocus() {
5599
+ if (ie) {
5600
+ var node = this.root.activeElement;
5601
+ if (node == this.dom) return true;
5602
+ if (!node || !this.dom.contains(node)) return false;
5603
+
5604
+ while (node && this.dom != node && this.dom.contains(node)) {
5605
+ if (node.contentEditable == 'false') return false;
5606
+ node = node.parentElement;
5607
+ }
5608
+
5609
+ return true;
5610
+ }
5611
+
5591
5612
  return this.root.activeElement == this.dom;
5592
5613
  }
5593
5614
  }, {
package/dist/index.d.ts CHANGED
@@ -701,6 +701,11 @@ interface EditorProps<P = any> {
701
701
  */
702
702
  transformPasted?: (this: P, slice: Slice) => Slice;
703
703
  /**
704
+ Can be used to transform copied or cut content before it is
705
+ serialized to the clipboard.
706
+ */
707
+ transformCopied?: (this: P, slice: Slice) => Slice;
708
+ /**
704
709
  Allows you to pass custom rendering and behavior logic for
705
710
  nodes. Should map node names to constructor functions that
706
711
  produce a [`NodeView`](https://prosemirror.net/docs/ref/#view.NodeView) object implementing the
package/dist/index.js CHANGED
@@ -2203,7 +2203,7 @@ function selectionBetween(view, $anchor, $head, bias) {
2203
2203
  || TextSelection.between($anchor, $head, bias);
2204
2204
  }
2205
2205
  function hasFocusAndSelection(view) {
2206
- if (view.editable && view.root.activeElement != view.dom)
2206
+ if (view.editable && !view.hasFocus())
2207
2207
  return false;
2208
2208
  return hasSelection(view);
2209
2209
  }
@@ -2532,6 +2532,7 @@ function captureKeyDown(view, event) {
2532
2532
  }
2533
2533
 
2534
2534
  function serializeForClipboard(view, slice) {
2535
+ view.someProp("transformCopied", f => { slice = f(slice); });
2535
2536
  let context = [], { content, openStart, openEnd } = slice;
2536
2537
  while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild.childCount == 1) {
2537
2538
  openStart--;
@@ -3129,7 +3130,7 @@ class MouseDown {
3129
3130
  // (hidden) cursor is doesn't change the selection, and
3130
3131
  // thus doesn't get a reaction from ProseMirror. This
3131
3132
  // works around that.
3132
- (chrome && !(this.view.state.selection instanceof TextSelection) &&
3133
+ (chrome && !this.view.state.selection.visible &&
3133
3134
  Math.min(Math.abs(pos.pos - this.view.state.selection.from), Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
3134
3135
  updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer");
3135
3136
  event.preventDefault();
@@ -4906,12 +4907,13 @@ class EditorView {
4906
4907
  update(props) {
4907
4908
  if (props.handleDOMEvents != this._props.handleDOMEvents)
4908
4909
  ensureListeners(this);
4910
+ let prevProps = this._props;
4909
4911
  this._props = props;
4910
4912
  if (props.plugins) {
4911
4913
  props.plugins.forEach(checkStateComponent);
4912
4914
  this.directPlugins = props.plugins;
4913
4915
  }
4914
- this.updateStateInner(props.state, true);
4916
+ this.updateStateInner(props.state, prevProps);
4915
4917
  }
4916
4918
  /**
4917
4919
  Update the view by updating existing props object with the object
@@ -4932,9 +4934,9 @@ class EditorView {
4932
4934
  other props.
4933
4935
  */
4934
4936
  updateState(state) {
4935
- this.updateStateInner(state, this.state.plugins != state.plugins);
4937
+ this.updateStateInner(state, this._props);
4936
4938
  }
4937
- updateStateInner(state, reconfigured) {
4939
+ updateStateInner(state, prevProps) {
4938
4940
  let prev = this.state, redraw = false, updateSel = false;
4939
4941
  // When stored marks are added, stop composition, so that they can
4940
4942
  // be displayed.
@@ -4943,18 +4945,21 @@ class EditorView {
4943
4945
  updateSel = true;
4944
4946
  }
4945
4947
  this.state = state;
4946
- if (reconfigured) {
4948
+ let pluginsChanged = prev.plugins != state.plugins || this._props.plugins != prevProps.plugins;
4949
+ if (pluginsChanged || this._props.plugins != prevProps.plugins || this._props.nodeViews != prevProps.nodeViews) {
4947
4950
  let nodeViews = buildNodeViews(this);
4948
4951
  if (changedNodeViews(nodeViews, this.nodeViews)) {
4949
4952
  this.nodeViews = nodeViews;
4950
4953
  redraw = true;
4951
4954
  }
4955
+ }
4956
+ if (pluginsChanged || prevProps.handleDOMEvents != this._props.handleDOMEvents) {
4952
4957
  ensureListeners(this);
4953
4958
  }
4954
4959
  this.editable = getEditable(this);
4955
4960
  updateCursorWrapper(this);
4956
4961
  let innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this);
4957
- let scroll = reconfigured ? "reset"
4962
+ let scroll = prev.plugins != state.plugins && !prev.doc.eq(state.doc) ? "reset"
4958
4963
  : state.scrollToSelection > prev.scrollToSelection ? "to selection" : "preserve";
4959
4964
  let updateDoc = redraw || !this.docView.matchesNode(state.doc, outerDeco, innerDeco);
4960
4965
  if (updateDoc || !state.selection.eq(prev.selection))
@@ -5073,6 +5078,24 @@ class EditorView {
5073
5078
  Query whether the view has focus.
5074
5079
  */
5075
5080
  hasFocus() {
5081
+ // Work around IE not handling focus correctly if resize handles are shown.
5082
+ // If the cursor is inside an element with resize handles, activeElement
5083
+ // will be that element instead of this.dom.
5084
+ if (ie) {
5085
+ // If activeElement is within this.dom, and there are no other elements
5086
+ // setting `contenteditable` to false in between, treat it as focused.
5087
+ let node = this.root.activeElement;
5088
+ if (node == this.dom)
5089
+ return true;
5090
+ if (!node || !this.dom.contains(node))
5091
+ return false;
5092
+ while (node && this.dom != node && this.dom.contains(node)) {
5093
+ if (node.contentEditable == 'false')
5094
+ return false;
5095
+ node = node.parentElement;
5096
+ }
5097
+ return true;
5098
+ }
5076
5099
  return this.root.activeElement == this.dom;
5077
5100
  }
5078
5101
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.27.2",
3
+ "version": "1.28.1",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/clipboard.ts CHANGED
@@ -3,6 +3,8 @@ import * as browser from "./browser"
3
3
  import {EditorView} from "./index"
4
4
 
5
5
  export function serializeForClipboard(view: EditorView, slice: Slice) {
6
+ view.someProp("transformCopied", f => { slice = f(slice!) })
7
+
6
8
  let context = [], {content, openStart, openEnd} = slice
7
9
  while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild!.childCount == 1) {
8
10
  openStart--
package/src/index.ts CHANGED
@@ -124,12 +124,13 @@ export class EditorView {
124
124
  /// the DOM.
125
125
  update(props: DirectEditorProps) {
126
126
  if (props.handleDOMEvents != this._props.handleDOMEvents) ensureListeners(this)
127
+ let prevProps = this._props
127
128
  this._props = props
128
129
  if (props.plugins) {
129
130
  props.plugins.forEach(checkStateComponent)
130
131
  this.directPlugins = props.plugins
131
132
  }
132
- this.updateStateInner(props.state, true)
133
+ this.updateStateInner(props.state, prevProps)
133
134
  }
134
135
 
135
136
  /// Update the view by updating existing props object with the object
@@ -146,10 +147,10 @@ export class EditorView {
146
147
  /// Update the editor's `state` prop, without touching any of the
147
148
  /// other props.
148
149
  updateState(state: EditorState) {
149
- this.updateStateInner(state, this.state.plugins != state.plugins)
150
+ this.updateStateInner(state, this._props)
150
151
  }
151
152
 
152
- private updateStateInner(state: EditorState, reconfigured: boolean) {
153
+ private updateStateInner(state: EditorState, prevProps: DirectEditorProps) {
153
154
  let prev = this.state, redraw = false, updateSel = false
154
155
  // When stored marks are added, stop composition, so that they can
155
156
  // be displayed.
@@ -158,12 +159,15 @@ export class EditorView {
158
159
  updateSel = true
159
160
  }
160
161
  this.state = state
161
- if (reconfigured) {
162
+ let pluginsChanged = prev.plugins != state.plugins || this._props.plugins != prevProps.plugins
163
+ if (pluginsChanged || this._props.plugins != prevProps.plugins || this._props.nodeViews != prevProps.nodeViews) {
162
164
  let nodeViews = buildNodeViews(this)
163
165
  if (changedNodeViews(nodeViews, this.nodeViews)) {
164
166
  this.nodeViews = nodeViews
165
167
  redraw = true
166
168
  }
169
+ }
170
+ if (pluginsChanged || prevProps.handleDOMEvents != this._props.handleDOMEvents) {
167
171
  ensureListeners(this)
168
172
  }
169
173
 
@@ -171,7 +175,7 @@ export class EditorView {
171
175
  updateCursorWrapper(this)
172
176
  let innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this)
173
177
 
174
- let scroll = reconfigured ? "reset"
178
+ let scroll = prev.plugins != state.plugins && !prev.doc.eq(state.doc) ? "reset"
175
179
  : (state as any).scrollToSelection > (prev as any).scrollToSelection ? "to selection" : "preserve"
176
180
  let updateDoc = redraw || !this.docView.matchesNode(state.doc, outerDeco, innerDeco)
177
181
  if (updateDoc || !state.selection.eq(prev.selection)) updateSel = true
@@ -292,6 +296,21 @@ export class EditorView {
292
296
 
293
297
  /// Query whether the view has focus.
294
298
  hasFocus() {
299
+ // Work around IE not handling focus correctly if resize handles are shown.
300
+ // If the cursor is inside an element with resize handles, activeElement
301
+ // will be that element instead of this.dom.
302
+ if (browser.ie) {
303
+ // If activeElement is within this.dom, and there are no other elements
304
+ // setting `contenteditable` to false in between, treat it as focused.
305
+ let node = this.root.activeElement
306
+ if (node == this.dom) return true
307
+ if (!node || !this.dom.contains(node)) return false
308
+ while (node && this.dom != node && this.dom.contains(node)) {
309
+ if ((node as HTMLElement).contentEditable == 'false') return false
310
+ node = node.parentElement
311
+ }
312
+ return true
313
+ }
295
314
  return this.root.activeElement == this.dom
296
315
  }
297
316
 
@@ -636,6 +655,10 @@ export interface EditorProps<P = any> {
636
655
  /// before it is applied to the document.
637
656
  transformPasted?: (this: P, slice: Slice) => Slice
638
657
 
658
+ /// Can be used to transform copied or cut content before it is
659
+ /// serialized to the clipboard.
660
+ transformCopied?: (this: P, slice: Slice) => Slice
661
+
639
662
  /// Allows you to pass custom rendering and behavior logic for
640
663
  /// nodes. Should map node names to constructor functions that
641
664
  /// produce a [`NodeView`](#view.NodeView) object implementing the
package/src/input.ts CHANGED
@@ -388,7 +388,7 @@ class MouseDown {
388
388
  // (hidden) cursor is doesn't change the selection, and
389
389
  // thus doesn't get a reaction from ProseMirror. This
390
390
  // works around that.
391
- (browser.chrome && !(this.view.state.selection instanceof TextSelection) &&
391
+ (browser.chrome && !this.view.state.selection.visible &&
392
392
  Math.min(Math.abs(pos.pos - this.view.state.selection.from),
393
393
  Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
394
394
  updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer")
package/src/selection.ts CHANGED
@@ -181,7 +181,7 @@ export function selectionBetween(view: EditorView, $anchor: ResolvedPos, $head:
181
181
  }
182
182
 
183
183
  export function hasFocusAndSelection(view: EditorView) {
184
- if (view.editable && view.root.activeElement != view.dom) return false
184
+ if (view.editable && !view.hasFocus()) return false
185
185
  return hasSelection(view)
186
186
  }
187
187