prosemirror-view 1.27.0 → 1.28.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/dist/index.js CHANGED
@@ -458,7 +458,7 @@ function coordsAtPos(view, pos, side) {
458
458
  else {
459
459
  to++;
460
460
  }
461
- return flattenV(singleRect(textRange(node, from, to), takeSide), takeSide < 0);
461
+ return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0);
462
462
  }
463
463
  }
464
464
  let $dom = view.state.doc.resolve(pos - (atom || 0));
@@ -1242,7 +1242,7 @@ class NodeViewDesc extends ViewDesc {
1242
1242
  let composition = view.composing ? this.localCompositionInfo(view, pos) : null;
1243
1243
  let localComposition = composition && composition.pos > -1 ? composition : null;
1244
1244
  let compositionInChild = composition && composition.pos < 0;
1245
- let updater = new ViewTreeUpdater(this, localComposition && localComposition.node);
1245
+ let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
1246
1246
  iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
1247
1247
  if (widget.spec.marks)
1248
1248
  updater.syncToMarks(widget.spec.marks, inline, view);
@@ -1624,8 +1624,9 @@ function rm(dom) {
1624
1624
  // Helper class for incrementally updating a tree of mark descs and
1625
1625
  // the widget and node descs inside of them.
1626
1626
  class ViewTreeUpdater {
1627
- constructor(top, lock) {
1627
+ constructor(top, lock, view) {
1628
1628
  this.lock = lock;
1629
+ this.view = view;
1629
1630
  // Index into `this.top`'s child array, represents the current
1630
1631
  // update position.
1631
1632
  this.index = 0;
@@ -1799,7 +1800,8 @@ class ViewTreeUpdater {
1799
1800
  }
1800
1801
  if (!lastChild || // Empty textblock
1801
1802
  !(lastChild instanceof TextViewDesc) ||
1802
- /\n$/.test(lastChild.node.text)) {
1803
+ /\n$/.test(lastChild.node.text) ||
1804
+ (this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text))) {
1803
1805
  // Avoid bugs in Safari's cursor drawing (#1165) and Chrome's mouse selection (#1152)
1804
1806
  if ((safari || chrome) && lastChild && lastChild.dom.contentEditable == "false")
1805
1807
  this.addHackNode("IMG", parent);
@@ -2201,7 +2203,7 @@ function selectionBetween(view, $anchor, $head, bias) {
2201
2203
  || TextSelection.between($anchor, $head, bias);
2202
2204
  }
2203
2205
  function hasFocusAndSelection(view) {
2204
- if (view.editable && view.root.activeElement != view.dom)
2206
+ if (view.editable && !view.hasFocus())
2205
2207
  return false;
2206
2208
  return hasSelection(view);
2207
2209
  }
@@ -2530,6 +2532,7 @@ function captureKeyDown(view, event) {
2530
2532
  }
2531
2533
 
2532
2534
  function serializeForClipboard(view, slice) {
2535
+ view.someProp("transformCopied", f => { slice = f(slice); });
2533
2536
  let context = [], { content, openStart, openEnd } = slice;
2534
2537
  while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild.childCount == 1) {
2535
2538
  openStart--;
@@ -2772,7 +2775,8 @@ function addContext(slice, context) {
2772
2775
  // A collection of DOM events that occur within the editor, and callback functions
2773
2776
  // to invoke when the event fires.
2774
2777
  const handlers = {};
2775
- let editHandlers = {};
2778
+ const editHandlers = {};
2779
+ const passiveHandlers = { touchstart: true, touchmove: true };
2776
2780
  class InputState {
2777
2781
  constructor() {
2778
2782
  this.shiftKey = false;
@@ -2784,6 +2788,8 @@ class InputState {
2784
2788
  this.lastSelectionTime = 0;
2785
2789
  this.lastIOSEnter = 0;
2786
2790
  this.lastIOSEnterFallbackTimeout = -1;
2791
+ this.lastFocus = 0;
2792
+ this.lastTouch = 0;
2787
2793
  this.lastAndroidDelete = 0;
2788
2794
  this.composing = false;
2789
2795
  this.composingTimeout = -1;
@@ -2801,7 +2807,7 @@ function initInput(view) {
2801
2807
  if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
2802
2808
  (view.editable || !(event.type in editHandlers)))
2803
2809
  handler(view, event);
2804
- });
2810
+ }, passiveHandlers[event] ? { passive: true } : undefined);
2805
2811
  }
2806
2812
  // On Safari, for reasons beyond my understanding, adding an input
2807
2813
  // event handler makes an issue where the composition vanishes when
@@ -3106,6 +3112,7 @@ class MouseDown {
3106
3112
  let pos = this.pos;
3107
3113
  if (this.view.state.doc != this.startDoc)
3108
3114
  pos = this.view.posAtCoords(eventCoords(event));
3115
+ this.updateAllowDefault(event);
3109
3116
  if (this.allowDefault || !pos) {
3110
3117
  setSelectionOrigin(this.view, "pointer");
3111
3118
  }
@@ -3123,7 +3130,7 @@ class MouseDown {
3123
3130
  // (hidden) cursor is doesn't change the selection, and
3124
3131
  // thus doesn't get a reaction from ProseMirror. This
3125
3132
  // works around that.
3126
- (chrome && !(this.view.state.selection instanceof TextSelection) &&
3133
+ (chrome && !this.view.state.selection.visible &&
3127
3134
  Math.min(Math.abs(pos.pos - this.view.state.selection.from), Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
3128
3135
  updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer");
3129
3136
  event.preventDefault();
@@ -3133,18 +3140,26 @@ class MouseDown {
3133
3140
  }
3134
3141
  }
3135
3142
  move(event) {
3136
- if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
3137
- Math.abs(this.event.y - event.clientY) > 4))
3138
- this.allowDefault = true;
3143
+ this.updateAllowDefault(event);
3139
3144
  setSelectionOrigin(this.view, "pointer");
3140
3145
  if (event.buttons == 0)
3141
3146
  this.done();
3142
3147
  }
3148
+ updateAllowDefault(event) {
3149
+ if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
3150
+ Math.abs(this.event.y - event.clientY) > 4))
3151
+ this.allowDefault = true;
3152
+ }
3143
3153
  }
3144
- handlers.touchdown = view => {
3154
+ handlers.touchstart = view => {
3155
+ view.input.lastTouch = Date.now();
3145
3156
  forceDOMFlush(view);
3146
3157
  setSelectionOrigin(view, "pointer");
3147
3158
  };
3159
+ handlers.touchmove = view => {
3160
+ view.input.lastTouch = Date.now();
3161
+ setSelectionOrigin(view, "pointer");
3162
+ };
3148
3163
  handlers.contextmenu = view => forceDOMFlush(view);
3149
3164
  function inOrNearComposition(view, event) {
3150
3165
  if (view.composing)
@@ -3396,8 +3411,6 @@ editHandlers.drop = (view, _event) => {
3396
3411
  if (!eventPos)
3397
3412
  return;
3398
3413
  let $mouse = view.state.doc.resolve(eventPos.pos);
3399
- if (!$mouse)
3400
- return;
3401
3414
  let slice = dragging && dragging.slice;
3402
3415
  if (slice) {
3403
3416
  view.someProp("transformPasted", f => { slice = f(slice); });
@@ -3442,6 +3455,7 @@ editHandlers.drop = (view, _event) => {
3442
3455
  view.dispatch(tr.setMeta("uiEvent", "drop"));
3443
3456
  };
3444
3457
  handlers.focus = view => {
3458
+ view.input.lastFocus = Date.now();
3445
3459
  if (!view.focused) {
3446
3460
  view.domObserver.stop();
3447
3461
  view.dom.classList.add("ProseMirror-focused");
@@ -3956,23 +3970,27 @@ function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, op
3956
3970
  let children = oldChildren.slice();
3957
3971
  // Mark the children that are directly touched by changes, and
3958
3972
  // move those that are after the changes.
3959
- let shift = (oldStart, oldEnd, newStart, newEnd) => {
3960
- for (let i = 0; i < children.length; i += 3) {
3961
- let end = children[i + 1], dSize;
3962
- if (end < 0 || oldStart > end + oldOffset)
3963
- continue;
3964
- let start = children[i] + oldOffset;
3965
- if (oldEnd >= start) {
3966
- children[i + 1] = oldStart <= start ? -2 : -1;
3967
- }
3968
- else if (newStart >= offset && (dSize = (newEnd - newStart) - (oldEnd - oldStart))) {
3969
- children[i] += dSize;
3970
- children[i + 1] += dSize;
3973
+ for (let i = 0, baseOffset = oldOffset; i < mapping.maps.length; i++) {
3974
+ let moved = 0;
3975
+ mapping.maps[i].forEach((oldStart, oldEnd, newStart, newEnd) => {
3976
+ let dSize = (newEnd - newStart) - (oldEnd - oldStart);
3977
+ for (let i = 0; i < children.length; i += 3) {
3978
+ let end = children[i + 1];
3979
+ if (end < 0 || oldStart > end + baseOffset - moved)
3980
+ continue;
3981
+ let start = children[i] + baseOffset - moved;
3982
+ if (oldEnd >= start) {
3983
+ children[i + 1] = oldStart <= start ? -2 : -1;
3984
+ }
3985
+ else if (newStart >= offset && dSize) {
3986
+ children[i] += dSize;
3987
+ children[i + 1] += dSize;
3988
+ }
3971
3989
  }
3972
- }
3973
- };
3974
- for (let i = 0; i < mapping.maps.length; i++)
3975
- mapping.maps[i].forEach(shift);
3990
+ moved += dSize;
3991
+ });
3992
+ baseOffset = mapping.maps[i].map(baseOffset, -1);
3993
+ }
3976
3994
  // Find the child nodes that still correspond to a single node,
3977
3995
  // recursively call mapInner on them and update their positions.
3978
3996
  let mustRebuild = false;
@@ -4300,17 +4318,18 @@ class DOMObserver {
4300
4318
  }
4301
4319
  }
4302
4320
  flush() {
4303
- if (!this.view.docView || this.flushingSoon > -1)
4321
+ let { view } = this;
4322
+ if (!view.docView || this.flushingSoon > -1)
4304
4323
  return;
4305
4324
  let mutations = this.observer ? this.observer.takeRecords() : [];
4306
4325
  if (this.queue.length) {
4307
4326
  mutations = this.queue.concat(mutations);
4308
4327
  this.queue.length = 0;
4309
4328
  }
4310
- let sel = this.view.domSelection();
4311
- let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
4329
+ let sel = view.domSelection();
4330
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4312
4331
  let from = -1, to = -1, typeOver = false, added = [];
4313
- if (this.view.editable) {
4332
+ if (view.editable) {
4314
4333
  for (let i = 0; i < mutations.length; i++) {
4315
4334
  let result = this.registerMutation(mutations[i], added);
4316
4335
  if (result) {
@@ -4331,16 +4350,29 @@ class DOMObserver {
4331
4350
  a.remove();
4332
4351
  }
4333
4352
  }
4334
- if (from > -1 || newSel) {
4353
+ let readSel = null;
4354
+ // If it looks like the browser has reset the selection to the
4355
+ // start of the document after focus, restore the selection from
4356
+ // the state
4357
+ if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
4358
+ view.input.lastTouch < Date.now() - 300 &&
4359
+ selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
4360
+ readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
4361
+ view.input.lastFocus = 0;
4362
+ selectionToDOM(view);
4363
+ this.currentSelection.set(sel);
4364
+ view.scrollToSelection();
4365
+ }
4366
+ else if (from > -1 || newSel) {
4335
4367
  if (from > -1) {
4336
- this.view.docView.markDirty(from, to);
4337
- checkCSS(this.view);
4368
+ view.docView.markDirty(from, to);
4369
+ checkCSS(view);
4338
4370
  }
4339
4371
  this.handleDOMChange(from, to, typeOver, added);
4340
- if (this.view.docView && this.view.docView.dirty)
4341
- this.view.updateState(this.view.state);
4372
+ if (view.docView && view.docView.dirty)
4373
+ view.updateState(view.state);
4342
4374
  else if (!this.currentSelection.eq(sel))
4343
- selectionToDOM(this.view);
4375
+ selectionToDOM(view);
4344
4376
  this.currentSelection.set(sel);
4345
4377
  }
4346
4378
  }
@@ -4397,13 +4429,19 @@ class DOMObserver {
4397
4429
  }
4398
4430
  }
4399
4431
  }
4400
- let cssChecked = false;
4432
+ let cssChecked = new WeakMap();
4433
+ let cssCheckWarned = false;
4401
4434
  function checkCSS(view) {
4402
- if (cssChecked)
4435
+ if (cssChecked.has(view))
4403
4436
  return;
4404
- cssChecked = true;
4405
- if (getComputedStyle(view.dom).whiteSpace == "normal")
4437
+ cssChecked.set(view, null);
4438
+ if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
4439
+ view.requiresGeckoHackNode = gecko;
4440
+ if (cssCheckWarned)
4441
+ return;
4406
4442
  console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.");
4443
+ cssCheckWarned = true;
4444
+ }
4407
4445
  }
4408
4446
 
4409
4447
  // Note that all referencing and parsing is done with the
@@ -4499,14 +4537,6 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4499
4537
  to = view.state.doc.resolve(to).after(shared + 1);
4500
4538
  let sel = view.state.selection;
4501
4539
  let parse = parseBetween(view, from, to);
4502
- // Chrome sometimes leaves the cursor before the inserted text when
4503
- // composing after a cursor wrapper. This moves it forward.
4504
- if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
4505
- parse.sel.head == parse.sel.anchor) {
4506
- let text = view.cursorWrapper.deco.type.toDOM.nextSibling;
4507
- let size = text && text.nodeValue ? text.nodeValue.length : 1;
4508
- parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
4509
- }
4510
4540
  let doc = view.state.doc, compare = doc.slice(parse.from, parse.to);
4511
4541
  let preferredPos, preferredSide;
4512
4542
  // Prefer anchoring to end when Backspace is pressed
@@ -4541,6 +4571,13 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4541
4571
  return;
4542
4572
  }
4543
4573
  }
4574
+ // Chrome sometimes leaves the cursor before the inserted text when
4575
+ // composing after a cursor wrapper. This moves it forward.
4576
+ if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
4577
+ parse.sel.head == parse.sel.anchor) {
4578
+ let size = change.endB - change.start;
4579
+ parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
4580
+ }
4544
4581
  view.input.domChangeCount++;
4545
4582
  // Handle the case where overwriting a selection by typing matches
4546
4583
  // the start or end of the selected content, creating a change
@@ -4810,6 +4847,12 @@ class EditorView {
4810
4847
  this.prevDirectPlugins = [];
4811
4848
  this.pluginViews = [];
4812
4849
  /**
4850
+ Holds `true` when a hack node is needed in Firefox to prevent the
4851
+ [space is eaten issue](https://github.com/ProseMirror/prosemirror/issues/651)
4852
+ @internal
4853
+ */
4854
+ this.requiresGeckoHackNode = false;
4855
+ /**
4813
4856
  When editor content is being dragged, this object contains
4814
4857
  information about the dragged slice and whether it is being
4815
4858
  copied or moved. At any other time, it is null.
@@ -4960,21 +5003,27 @@ class EditorView {
4960
5003
  this.dom.scrollTop = 0;
4961
5004
  }
4962
5005
  else if (scroll == "to selection") {
4963
- let startDOM = this.domSelection().focusNode;
4964
- if (this.someProp("handleScrollToSelection", f => f(this))) ;
4965
- else if (state.selection instanceof NodeSelection) {
4966
- let target = this.docView.domAfterPos(state.selection.from);
4967
- if (target.nodeType == 1)
4968
- scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
4969
- }
4970
- else {
4971
- scrollRectIntoView(this, this.coordsAtPos(state.selection.head, 1), startDOM);
4972
- }
5006
+ this.scrollToSelection();
4973
5007
  }
4974
5008
  else if (oldScrollPos) {
4975
5009
  resetScrollPos(oldScrollPos);
4976
5010
  }
4977
5011
  }
5012
+ /**
5013
+ @internal
5014
+ */
5015
+ scrollToSelection() {
5016
+ let startDOM = this.domSelection().focusNode;
5017
+ if (this.someProp("handleScrollToSelection", f => f(this))) ;
5018
+ else if (this.state.selection instanceof NodeSelection) {
5019
+ let target = this.docView.domAfterPos(this.state.selection.from);
5020
+ if (target.nodeType == 1)
5021
+ scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
5022
+ }
5023
+ else {
5024
+ scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM);
5025
+ }
5026
+ }
4978
5027
  destroyPluginViews() {
4979
5028
  let view;
4980
5029
  while (view = this.pluginViews.pop())
@@ -5025,6 +5074,24 @@ class EditorView {
5025
5074
  Query whether the view has focus.
5026
5075
  */
5027
5076
  hasFocus() {
5077
+ // Work around IE not handling focus correctly if resize handles are shown.
5078
+ // If the cursor is inside an element with resize handles, activeElement
5079
+ // will be that element instead of this.dom.
5080
+ if (ie) {
5081
+ // If activeElement is within this.dom, and there are no other elements
5082
+ // setting `contenteditable` to false in between, treat it as focused.
5083
+ let node = this.root.activeElement;
5084
+ if (node == this.dom)
5085
+ return true;
5086
+ if (!node || !this.dom.contains(node))
5087
+ return false;
5088
+ while (node && this.dom != node && this.dom.contains(node)) {
5089
+ if (node.contentEditable == 'false')
5090
+ return false;
5091
+ node = node.parentElement;
5092
+ }
5093
+ return true;
5094
+ }
5028
5095
  return this.root.activeElement == this.dom;
5029
5096
  }
5030
5097
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
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/decoration.ts CHANGED
@@ -550,20 +550,25 @@ function mapChildren(
550
550
 
551
551
  // Mark the children that are directly touched by changes, and
552
552
  // move those that are after the changes.
553
- let shift = (oldStart: number, oldEnd: number, newStart: number, newEnd: number) => {
554
- for (let i = 0; i < children.length; i += 3) {
555
- let end = children[i + 1] as number, dSize
556
- if (end < 0 || oldStart > end + oldOffset) continue
557
- let start = (children[i] as number) + oldOffset
558
- if (oldEnd >= start) {
559
- children[i + 1] = oldStart <= start ? -2 : -1
560
- } else if (newStart >= offset && (dSize = (newEnd - newStart) - (oldEnd - oldStart))) {
561
- ;(children[i] as number) += dSize
562
- ;(children[i + 1] as number) += dSize
553
+ for (let i = 0, baseOffset = oldOffset; i < mapping.maps.length; i++) {
554
+ let moved = 0
555
+ mapping.maps[i].forEach((oldStart: number, oldEnd: number, newStart: number, newEnd: number) => {
556
+ let dSize = (newEnd - newStart) - (oldEnd - oldStart)
557
+ for (let i = 0; i < children.length; i += 3) {
558
+ let end = children[i + 1] as number
559
+ if (end < 0 || oldStart > end + baseOffset - moved) continue
560
+ let start = (children[i] as number) + baseOffset - moved
561
+ if (oldEnd >= start) {
562
+ children[i + 1] = oldStart <= start ? -2 : -1
563
+ } else if (newStart >= offset && dSize) {
564
+ ;(children[i] as number) += dSize
565
+ ;(children[i + 1] as number) += dSize
566
+ }
563
567
  }
564
- }
568
+ moved += dSize
569
+ })
570
+ baseOffset = mapping.maps[i].map(baseOffset, -1)
565
571
  }
566
- for (let i = 0; i < mapping.maps.length; i++) mapping.maps[i].forEach(shift)
567
572
 
568
573
  // Find the child nodes that still correspond to a single node,
569
574
  // recursively call mapInner on them and update their positions.
package/src/domchange.ts CHANGED
@@ -97,15 +97,6 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
97
97
  let sel = view.state.selection
98
98
  let parse = parseBetween(view, from, to)
99
99
 
100
- // Chrome sometimes leaves the cursor before the inserted text when
101
- // composing after a cursor wrapper. This moves it forward.
102
- if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
103
- parse.sel.head == parse.sel.anchor) {
104
- let text = (view.cursorWrapper.deco.type as any).toDOM.nextSibling as DOMNode
105
- let size = text && text.nodeValue ? text.nodeValue.length : 1
106
- parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
107
- }
108
-
109
100
  let doc = view.state.doc, compare = doc.slice(parse.from, parse.to)
110
101
  let preferredPos, preferredSide: "start" | "end"
111
102
  // Prefer anchoring to end when Backspace is pressed
@@ -138,6 +129,14 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
138
129
  return
139
130
  }
140
131
  }
132
+ // Chrome sometimes leaves the cursor before the inserted text when
133
+ // composing after a cursor wrapper. This moves it forward.
134
+ if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
135
+ parse.sel.head == parse.sel.anchor) {
136
+ let size = change.endB - change.start
137
+ parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
138
+ }
139
+
141
140
  view.input.domChangeCount++
142
141
  // Handle the case where overwriting a selection by typing matches
143
142
  // the start or end of the selected content, creating a change
package/src/domcoords.ts CHANGED
@@ -341,7 +341,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
341
341
  else if (side >= 0 && offset == node.nodeValue!.length) { from--; takeSide = 1 }
342
342
  else if (side < 0) { from-- }
343
343
  else { to ++ }
344
- return flattenV(singleRect(textRange(node as Text, from, to), takeSide), takeSide < 0)
344
+ return flattenV(singleRect(textRange(node as Text, from, to), 1), takeSide < 0)
345
345
  }
346
346
  }
347
347
 
@@ -1,6 +1,7 @@
1
+ import {Selection} from "prosemirror-state"
1
2
  import * as browser from "./browser"
2
- import {domIndex, isEquivalentPosition} from "./dom"
3
- import {hasFocusAndSelection, selectionToDOM} from "./selection"
3
+ import {domIndex, isEquivalentPosition, selectionCollapsed, DOMSelection} from "./dom"
4
+ import {hasFocusAndSelection, selectionToDOM, selectionFromDOM} from "./selection"
4
5
  import {EditorView} from "./index"
5
6
 
6
7
  const observeOptions = {
@@ -20,7 +21,7 @@ class SelectionState {
20
21
  focusNode: Node | null = null
21
22
  focusOffset: number = 0
22
23
 
23
- set(sel: Selection) {
24
+ set(sel: DOMSelection) {
24
25
  this.anchorNode = sel.anchorNode; this.anchorOffset = sel.anchorOffset
25
26
  this.focusNode = sel.focusNode; this.focusOffset = sel.focusOffset
26
27
  }
@@ -29,7 +30,7 @@ class SelectionState {
29
30
  this.anchorNode = this.focusNode = null
30
31
  }
31
32
 
32
- eq(sel: Selection) {
33
+ eq(sel: DOMSelection) {
33
34
  return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset &&
34
35
  sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset
35
36
  }
@@ -138,7 +139,7 @@ export class DOMObserver {
138
139
  this.currentSelection.set(this.view.domSelection())
139
140
  }
140
141
 
141
- ignoreSelectionChange(sel: Selection) {
142
+ ignoreSelectionChange(sel: DOMSelection) {
142
143
  if (sel.rangeCount == 0) return true
143
144
  let container = sel.getRangeAt(0).commonAncestorContainer
144
145
  let desc = this.view.docView.nearestDesc(container)
@@ -152,18 +153,19 @@ export class DOMObserver {
152
153
  }
153
154
 
154
155
  flush() {
155
- if (!this.view.docView || this.flushingSoon > -1) return
156
+ let {view} = this
157
+ if (!view.docView || this.flushingSoon > -1) return
156
158
  let mutations = this.observer ? this.observer.takeRecords() : []
157
159
  if (this.queue.length) {
158
160
  mutations = this.queue.concat(mutations)
159
161
  this.queue.length = 0
160
162
  }
161
163
 
162
- let sel = this.view.domSelection()
163
- let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel)
164
+ let sel = view.domSelection()
165
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
164
166
 
165
167
  let from = -1, to = -1, typeOver = false, added: Node[] = []
166
- if (this.view.editable) {
168
+ if (view.editable) {
167
169
  for (let i = 0; i < mutations.length; i++) {
168
170
  let result = this.registerMutation(mutations[i], added)
169
171
  if (result) {
@@ -183,14 +185,26 @@ export class DOMObserver {
183
185
  }
184
186
  }
185
187
 
186
- if (from > -1 || newSel) {
188
+ let readSel: Selection | null = null
189
+ // If it looks like the browser has reset the selection to the
190
+ // start of the document after focus, restore the selection from
191
+ // the state
192
+ if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
193
+ view.input.lastTouch < Date.now() - 300 &&
194
+ selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
195
+ readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
196
+ view.input.lastFocus = 0
197
+ selectionToDOM(view)
198
+ this.currentSelection.set(sel)
199
+ view.scrollToSelection()
200
+ } else if (from > -1 || newSel) {
187
201
  if (from > -1) {
188
- this.view.docView.markDirty(from, to)
189
- checkCSS(this.view)
202
+ view.docView.markDirty(from, to)
203
+ checkCSS(view)
190
204
  }
191
205
  this.handleDOMChange(from, to, typeOver, added)
192
- if (this.view.docView && this.view.docView.dirty) this.view.updateState(this.view.state)
193
- else if (!this.currentSelection.eq(sel)) selectionToDOM(this.view)
206
+ if (view.docView && view.docView.dirty) view.updateState(view.state)
207
+ else if (!this.currentSelection.eq(sel)) selectionToDOM(view)
194
208
  this.currentSelection.set(sel)
195
209
  }
196
210
  }
@@ -243,11 +257,16 @@ export class DOMObserver {
243
257
  }
244
258
  }
245
259
 
246
- let cssChecked = false
260
+ let cssChecked: WeakMap<EditorView, null> = new WeakMap()
261
+ let cssCheckWarned: boolean = false
247
262
 
248
263
  function checkCSS(view: EditorView) {
249
- if (cssChecked) return
250
- cssChecked = true
251
- if (getComputedStyle(view.dom).whiteSpace == "normal")
264
+ if (cssChecked.has(view)) return
265
+ cssChecked.set(view, null)
266
+ if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
267
+ view.requiresGeckoHackNode = browser.gecko
268
+ if (cssCheckWarned) return
252
269
  console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.")
270
+ cssCheckWarned = true
271
+ }
253
272
  }