prosemirror-view 1.26.4 → 1.27.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/dist/index.js CHANGED
@@ -422,7 +422,7 @@ const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
422
422
  // Given a position in the document model, get a bounding box of the
423
423
  // character at that position, relative to the window.
424
424
  function coordsAtPos(view, pos, side) {
425
- let { node, offset } = view.docView.domFromPos(pos, side < 0 ? -1 : 1);
425
+ let { node, offset, atom } = view.docView.domFromPos(pos, side < 0 ? -1 : 1);
426
426
  let supportEmptyRange = webkit || gecko;
427
427
  if (node.nodeType == 3) {
428
428
  // These browsers support querying empty text ranges. Prefer that in
@@ -458,17 +458,18 @@ 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
+ let $dom = view.state.doc.resolve(pos - (atom || 0));
464
465
  // Return a horizontal line in block context
465
- if (!view.state.doc.resolve(pos).parent.inlineContent) {
466
- if (offset && (side < 0 || offset == nodeSize(node))) {
466
+ if (!$dom.parent.inlineContent) {
467
+ if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
467
468
  let before = node.childNodes[offset - 1];
468
469
  if (before.nodeType == 1)
469
470
  return flattenH(before.getBoundingClientRect(), false);
470
471
  }
471
- if (offset < nodeSize(node)) {
472
+ if (atom == null && offset < nodeSize(node)) {
472
473
  let after = node.childNodes[offset];
473
474
  if (after.nodeType == 1)
474
475
  return flattenH(after.getBoundingClientRect(), true);
@@ -476,7 +477,7 @@ function coordsAtPos(view, pos, side) {
476
477
  return flattenH(node.getBoundingClientRect(), side >= 0);
477
478
  }
478
479
  // Inline, not in text node (this is not Bidi-safe)
479
- if (offset && (side < 0 || offset == nodeSize(node))) {
480
+ if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
480
481
  let before = node.childNodes[offset - 1];
481
482
  let target = before.nodeType == 3 ? textRange(before, nodeSize(before) - (supportEmptyRange ? 0 : 1))
482
483
  // BR nodes tend to only return the rectangle before them.
@@ -485,7 +486,7 @@ function coordsAtPos(view, pos, side) {
485
486
  if (target)
486
487
  return flattenV(singleRect(target, 1), false);
487
488
  }
488
- if (offset < nodeSize(node)) {
489
+ if (atom == null && offset < nodeSize(node)) {
489
490
  let after = node.childNodes[offset];
490
491
  while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords)
491
492
  after = after.nextSibling;
@@ -797,7 +798,7 @@ class ViewDesc {
797
798
  }
798
799
  domFromPos(pos, side) {
799
800
  if (!this.contentDOM)
800
- return { node: this.dom, offset: 0 };
801
+ return { node: this.dom, offset: 0, atom: pos + 1 };
801
802
  // First find the position in the child array
802
803
  let i = 0, offset = 0;
803
804
  for (let curPos = 0; i < this.children.length; i++) {
@@ -1241,7 +1242,7 @@ class NodeViewDesc extends ViewDesc {
1241
1242
  let composition = view.composing ? this.localCompositionInfo(view, pos) : null;
1242
1243
  let localComposition = composition && composition.pos > -1 ? composition : null;
1243
1244
  let compositionInChild = composition && composition.pos < 0;
1244
- let updater = new ViewTreeUpdater(this, localComposition && localComposition.node);
1245
+ let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
1245
1246
  iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
1246
1247
  if (widget.spec.marks)
1247
1248
  updater.syncToMarks(widget.spec.marks, inline, view);
@@ -1623,8 +1624,9 @@ function rm(dom) {
1623
1624
  // Helper class for incrementally updating a tree of mark descs and
1624
1625
  // the widget and node descs inside of them.
1625
1626
  class ViewTreeUpdater {
1626
- constructor(top, lock) {
1627
+ constructor(top, lock, view) {
1627
1628
  this.lock = lock;
1629
+ this.view = view;
1628
1630
  // Index into `this.top`'s child array, represents the current
1629
1631
  // update position.
1630
1632
  this.index = 0;
@@ -1723,7 +1725,7 @@ class ViewTreeUpdater {
1723
1725
  if (!child.update(node, outerDeco, innerDeco, view))
1724
1726
  return false;
1725
1727
  this.destroyBetween(this.index, index);
1726
- this.index = index + 1;
1728
+ this.index++;
1727
1729
  return true;
1728
1730
  }
1729
1731
  findIndexWithChild(domNode) {
@@ -1798,7 +1800,8 @@ class ViewTreeUpdater {
1798
1800
  }
1799
1801
  if (!lastChild || // Empty textblock
1800
1802
  !(lastChild instanceof TextViewDesc) ||
1801
- /\n$/.test(lastChild.node.text)) {
1803
+ /\n$/.test(lastChild.node.text) ||
1804
+ (this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text))) {
1802
1805
  // Avoid bugs in Safari's cursor drawing (#1165) and Chrome's mouse selection (#1152)
1803
1806
  if ((safari || chrome) && lastChild && lastChild.dom.contentEditable == "false")
1804
1807
  this.addHackNode("IMG", parent);
@@ -2771,7 +2774,8 @@ function addContext(slice, context) {
2771
2774
  // A collection of DOM events that occur within the editor, and callback functions
2772
2775
  // to invoke when the event fires.
2773
2776
  const handlers = {};
2774
- let editHandlers = {};
2777
+ const editHandlers = {};
2778
+ const passiveHandlers = { touchstart: true, touchmove: true };
2775
2779
  class InputState {
2776
2780
  constructor() {
2777
2781
  this.shiftKey = false;
@@ -2783,6 +2787,8 @@ class InputState {
2783
2787
  this.lastSelectionTime = 0;
2784
2788
  this.lastIOSEnter = 0;
2785
2789
  this.lastIOSEnterFallbackTimeout = -1;
2790
+ this.lastFocus = 0;
2791
+ this.lastTouch = 0;
2786
2792
  this.lastAndroidDelete = 0;
2787
2793
  this.composing = false;
2788
2794
  this.composingTimeout = -1;
@@ -2800,7 +2806,7 @@ function initInput(view) {
2800
2806
  if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
2801
2807
  (view.editable || !(event.type in editHandlers)))
2802
2808
  handler(view, event);
2803
- });
2809
+ }, passiveHandlers[event] ? { passive: true } : undefined);
2804
2810
  }
2805
2811
  // On Safari, for reasons beyond my understanding, adding an input
2806
2812
  // event handler makes an issue where the composition vanishes when
@@ -3140,10 +3146,15 @@ class MouseDown {
3140
3146
  this.done();
3141
3147
  }
3142
3148
  }
3143
- handlers.touchdown = view => {
3149
+ handlers.touchstart = view => {
3150
+ view.input.lastTouch = Date.now();
3144
3151
  forceDOMFlush(view);
3145
3152
  setSelectionOrigin(view, "pointer");
3146
3153
  };
3154
+ handlers.touchmove = view => {
3155
+ view.input.lastTouch = Date.now();
3156
+ setSelectionOrigin(view, "pointer");
3157
+ };
3147
3158
  handlers.contextmenu = view => forceDOMFlush(view);
3148
3159
  function inOrNearComposition(view, event) {
3149
3160
  if (view.composing)
@@ -3395,8 +3406,6 @@ editHandlers.drop = (view, _event) => {
3395
3406
  if (!eventPos)
3396
3407
  return;
3397
3408
  let $mouse = view.state.doc.resolve(eventPos.pos);
3398
- if (!$mouse)
3399
- return;
3400
3409
  let slice = dragging && dragging.slice;
3401
3410
  if (slice) {
3402
3411
  view.someProp("transformPasted", f => { slice = f(slice); });
@@ -3441,6 +3450,7 @@ editHandlers.drop = (view, _event) => {
3441
3450
  view.dispatch(tr.setMeta("uiEvent", "drop"));
3442
3451
  };
3443
3452
  handlers.focus = view => {
3453
+ view.input.lastFocus = Date.now();
3444
3454
  if (!view.focused) {
3445
3455
  view.domObserver.stop();
3446
3456
  view.dom.classList.add("ProseMirror-focused");
@@ -3955,23 +3965,26 @@ function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, op
3955
3965
  let children = oldChildren.slice();
3956
3966
  // Mark the children that are directly touched by changes, and
3957
3967
  // move those that are after the changes.
3958
- let shift = (oldStart, oldEnd, newStart, newEnd) => {
3959
- for (let i = 0; i < children.length; i += 3) {
3960
- let end = children[i + 1], dSize;
3961
- if (end < 0 || oldStart > end + oldOffset)
3962
- continue;
3963
- let start = children[i] + oldOffset;
3964
- if (oldEnd >= start) {
3965
- children[i + 1] = oldStart <= start ? -2 : -1;
3966
- }
3967
- else if (newStart >= offset && (dSize = (newEnd - newStart) - (oldEnd - oldStart))) {
3968
- children[i] += dSize;
3969
- children[i + 1] += dSize;
3968
+ for (let i = 0; i < mapping.maps.length; i++) {
3969
+ let moved = 0;
3970
+ mapping.maps[i].forEach((oldStart, oldEnd, newStart, newEnd) => {
3971
+ let dSize = (newEnd - newStart) - (oldEnd - oldStart);
3972
+ for (let i = 0; i < children.length; i += 3) {
3973
+ let end = children[i + 1];
3974
+ if (end < 0 || oldStart > end + oldOffset - moved)
3975
+ continue;
3976
+ let start = children[i] + oldOffset - moved;
3977
+ if (oldEnd >= start) {
3978
+ children[i + 1] = oldStart <= start ? -2 : -1;
3979
+ }
3980
+ else if (newStart >= offset && dSize) {
3981
+ children[i] += dSize;
3982
+ children[i + 1] += dSize;
3983
+ }
3970
3984
  }
3971
- }
3972
- };
3973
- for (let i = 0; i < mapping.maps.length; i++)
3974
- mapping.maps[i].forEach(shift);
3985
+ moved += dSize;
3986
+ });
3987
+ }
3975
3988
  // Find the child nodes that still correspond to a single node,
3976
3989
  // recursively call mapInner on them and update their positions.
3977
3990
  let mustRebuild = false;
@@ -4299,17 +4312,18 @@ class DOMObserver {
4299
4312
  }
4300
4313
  }
4301
4314
  flush() {
4302
- if (!this.view.docView || this.flushingSoon > -1)
4315
+ let { view } = this;
4316
+ if (!view.docView || this.flushingSoon > -1)
4303
4317
  return;
4304
4318
  let mutations = this.observer ? this.observer.takeRecords() : [];
4305
4319
  if (this.queue.length) {
4306
4320
  mutations = this.queue.concat(mutations);
4307
4321
  this.queue.length = 0;
4308
4322
  }
4309
- let sel = this.view.domSelection();
4310
- let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
4323
+ let sel = view.domSelection();
4324
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4311
4325
  let from = -1, to = -1, typeOver = false, added = [];
4312
- if (this.view.editable) {
4326
+ if (view.editable) {
4313
4327
  for (let i = 0; i < mutations.length; i++) {
4314
4328
  let result = this.registerMutation(mutations[i], added);
4315
4329
  if (result) {
@@ -4330,16 +4344,29 @@ class DOMObserver {
4330
4344
  a.remove();
4331
4345
  }
4332
4346
  }
4333
- if (from > -1 || newSel) {
4347
+ let readSel = null;
4348
+ // If it looks like the browser has reset the selection to the
4349
+ // start of the document after focus, restore the selection from
4350
+ // the state
4351
+ if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
4352
+ view.input.lastTouch < Date.now() - 300 &&
4353
+ selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
4354
+ readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
4355
+ view.input.lastFocus = 0;
4356
+ selectionToDOM(view);
4357
+ this.currentSelection.set(sel);
4358
+ view.scrollToSelection();
4359
+ }
4360
+ else if (from > -1 || newSel) {
4334
4361
  if (from > -1) {
4335
- this.view.docView.markDirty(from, to);
4336
- checkCSS(this.view);
4362
+ view.docView.markDirty(from, to);
4363
+ checkCSS(view);
4337
4364
  }
4338
4365
  this.handleDOMChange(from, to, typeOver, added);
4339
- if (this.view.docView && this.view.docView.dirty)
4340
- this.view.updateState(this.view.state);
4366
+ if (view.docView && view.docView.dirty)
4367
+ view.updateState(view.state);
4341
4368
  else if (!this.currentSelection.eq(sel))
4342
- selectionToDOM(this.view);
4369
+ selectionToDOM(view);
4343
4370
  this.currentSelection.set(sel);
4344
4371
  }
4345
4372
  }
@@ -4396,13 +4423,19 @@ class DOMObserver {
4396
4423
  }
4397
4424
  }
4398
4425
  }
4399
- let cssChecked = false;
4426
+ let cssChecked = new WeakMap();
4427
+ let cssCheckWarned = false;
4400
4428
  function checkCSS(view) {
4401
- if (cssChecked)
4429
+ if (cssChecked.has(view))
4402
4430
  return;
4403
- cssChecked = true;
4404
- if (getComputedStyle(view.dom).whiteSpace == "normal")
4431
+ cssChecked.set(view, null);
4432
+ if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
4433
+ view.requiresGeckoHackNode = gecko;
4434
+ if (cssCheckWarned)
4435
+ return;
4405
4436
  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.");
4437
+ cssCheckWarned = true;
4438
+ }
4406
4439
  }
4407
4440
 
4408
4441
  // Note that all referencing and parsing is done with the
@@ -4500,7 +4533,8 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4500
4533
  let parse = parseBetween(view, from, to);
4501
4534
  // Chrome sometimes leaves the cursor before the inserted text when
4502
4535
  // composing after a cursor wrapper. This moves it forward.
4503
- if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from) {
4536
+ if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
4537
+ parse.sel.head == parse.sel.anchor) {
4504
4538
  let text = view.cursorWrapper.deco.type.toDOM.nextSibling;
4505
4539
  let size = text && text.nodeValue ? text.nodeValue.length : 1;
4506
4540
  parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
@@ -4808,6 +4842,12 @@ class EditorView {
4808
4842
  this.prevDirectPlugins = [];
4809
4843
  this.pluginViews = [];
4810
4844
  /**
4845
+ Holds `true` when a hack node is needed in Firefox to prevent the
4846
+ [space is eaten issue](https://github.com/ProseMirror/prosemirror/issues/651)
4847
+ @internal
4848
+ */
4849
+ this.requiresGeckoHackNode = false;
4850
+ /**
4811
4851
  When editor content is being dragged, this object contains
4812
4852
  information about the dragged slice and whether it is being
4813
4853
  copied or moved. At any other time, it is null.
@@ -4958,21 +4998,27 @@ class EditorView {
4958
4998
  this.dom.scrollTop = 0;
4959
4999
  }
4960
5000
  else if (scroll == "to selection") {
4961
- let startDOM = this.domSelection().focusNode;
4962
- if (this.someProp("handleScrollToSelection", f => f(this))) ;
4963
- else if (state.selection instanceof NodeSelection) {
4964
- let target = this.docView.domAfterPos(state.selection.from);
4965
- if (target.nodeType == 1)
4966
- scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
4967
- }
4968
- else {
4969
- scrollRectIntoView(this, this.coordsAtPos(state.selection.head, 1), startDOM);
4970
- }
5001
+ this.scrollToSelection();
4971
5002
  }
4972
5003
  else if (oldScrollPos) {
4973
5004
  resetScrollPos(oldScrollPos);
4974
5005
  }
4975
5006
  }
5007
+ /**
5008
+ @internal
5009
+ */
5010
+ scrollToSelection() {
5011
+ let startDOM = this.domSelection().focusNode;
5012
+ if (this.someProp("handleScrollToSelection", f => f(this))) ;
5013
+ else if (this.state.selection instanceof NodeSelection) {
5014
+ let target = this.docView.domAfterPos(this.state.selection.from);
5015
+ if (target.nodeType == 1)
5016
+ scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
5017
+ }
5018
+ else {
5019
+ scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM);
5020
+ }
5021
+ }
4976
5022
  destroyPluginViews() {
4977
5023
  let view;
4978
5024
  while (view = this.pluginViews.pop())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.26.4",
3
+ "version": "1.27.1",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/decoration.ts CHANGED
@@ -550,20 +550,24 @@ 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; 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 + oldOffset - moved) continue
560
+ let start = (children[i] as number) + oldOffset - 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
+ })
565
570
  }
566
- for (let i = 0; i < mapping.maps.length; i++) mapping.maps[i].forEach(shift)
567
571
 
568
572
  // Find the child nodes that still correspond to a single node,
569
573
  // recursively call mapInner on them and update their positions.
package/src/domchange.ts CHANGED
@@ -99,7 +99,8 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
99
99
 
100
100
  // Chrome sometimes leaves the cursor before the inserted text when
101
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) {
102
+ if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
103
+ parse.sel.head == parse.sel.anchor) {
103
104
  let text = (view.cursorWrapper.deco.type as any).toDOM.nextSibling as DOMNode
104
105
  let size = text && text.nodeValue ? text.nodeValue.length : 1
105
106
  parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
package/src/domcoords.ts CHANGED
@@ -315,7 +315,7 @@ const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/
315
315
  // Given a position in the document model, get a bounding box of the
316
316
  // character at that position, relative to the window.
317
317
  export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
318
- let {node, offset} = view.docView.domFromPos(pos, side < 0 ? -1 : 1)
318
+ let {node, offset, atom} = view.docView.domFromPos(pos, side < 0 ? -1 : 1)
319
319
 
320
320
  let supportEmptyRange = browser.webkit || browser.gecko
321
321
  if (node.nodeType == 3) {
@@ -341,17 +341,18 @@ 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
 
348
+ let $dom = view.state.doc.resolve(pos - (atom || 0))
348
349
  // Return a horizontal line in block context
349
- if (!view.state.doc.resolve(pos).parent.inlineContent) {
350
- if (offset && (side < 0 || offset == nodeSize(node))) {
350
+ if (!$dom.parent.inlineContent) {
351
+ if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
351
352
  let before = node.childNodes[offset - 1]
352
353
  if (before.nodeType == 1) return flattenH((before as HTMLElement).getBoundingClientRect(), false)
353
354
  }
354
- if (offset < nodeSize(node)) {
355
+ if (atom == null && offset < nodeSize(node)) {
355
356
  let after = node.childNodes[offset]
356
357
  if (after.nodeType == 1) return flattenH((after as HTMLElement).getBoundingClientRect(), true)
357
358
  }
@@ -359,7 +360,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
359
360
  }
360
361
 
361
362
  // Inline, not in text node (this is not Bidi-safe)
362
- if (offset && (side < 0 || offset == nodeSize(node))) {
363
+ if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
363
364
  let before = node.childNodes[offset - 1]
364
365
  let target = before.nodeType == 3 ? textRange(before as Text, nodeSize(before) - (supportEmptyRange ? 0 : 1))
365
366
  // BR nodes tend to only return the rectangle before them.
@@ -367,7 +368,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
367
368
  : before.nodeType == 1 && (before.nodeName != "BR" || !before.nextSibling) ? before : null
368
369
  if (target) return flattenV(singleRect(target as Range | HTMLElement, 1), false)
369
370
  }
370
- if (offset < nodeSize(node)) {
371
+ if (atom == null && offset < nodeSize(node)) {
371
372
  let after = node.childNodes[offset]
372
373
  while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords) after = after.nextSibling!
373
374
  let target = !after ? null : after.nodeType == 3 ? textRange(after as Text, 0, (supportEmptyRange ? 0 : 1))
@@ -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
  }