prosemirror-view 1.26.5 → 1.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/index.cjs +124 -76
- package/dist/index.d.ts +28 -24
- package/dist/index.js +108 -59
- package/package.json +1 -1
- package/src/decoration.ts +17 -12
- package/src/domchange.ts +8 -8
- package/src/domcoords.ts +1 -1
- package/src/domobserver.ts +37 -18
- package/src/index.ts +46 -33
- package/src/input.ts +21 -7
- package/src/viewdesc.ts +4 -3
- package/dist/index.es.js +0 -5558
- package/dist/index.es.js.map +0 -1
- package/dist/index.js.map +0 -1
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),
|
|
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);
|
|
@@ -2772,7 +2774,8 @@ function addContext(slice, context) {
|
|
|
2772
2774
|
// A collection of DOM events that occur within the editor, and callback functions
|
|
2773
2775
|
// to invoke when the event fires.
|
|
2774
2776
|
const handlers = {};
|
|
2775
|
-
|
|
2777
|
+
const editHandlers = {};
|
|
2778
|
+
const passiveHandlers = { touchstart: true, touchmove: true };
|
|
2776
2779
|
class InputState {
|
|
2777
2780
|
constructor() {
|
|
2778
2781
|
this.shiftKey = false;
|
|
@@ -2784,6 +2787,8 @@ class InputState {
|
|
|
2784
2787
|
this.lastSelectionTime = 0;
|
|
2785
2788
|
this.lastIOSEnter = 0;
|
|
2786
2789
|
this.lastIOSEnterFallbackTimeout = -1;
|
|
2790
|
+
this.lastFocus = 0;
|
|
2791
|
+
this.lastTouch = 0;
|
|
2787
2792
|
this.lastAndroidDelete = 0;
|
|
2788
2793
|
this.composing = false;
|
|
2789
2794
|
this.composingTimeout = -1;
|
|
@@ -2801,7 +2806,7 @@ function initInput(view) {
|
|
|
2801
2806
|
if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
|
|
2802
2807
|
(view.editable || !(event.type in editHandlers)))
|
|
2803
2808
|
handler(view, event);
|
|
2804
|
-
});
|
|
2809
|
+
}, passiveHandlers[event] ? { passive: true } : undefined);
|
|
2805
2810
|
}
|
|
2806
2811
|
// On Safari, for reasons beyond my understanding, adding an input
|
|
2807
2812
|
// event handler makes an issue where the composition vanishes when
|
|
@@ -3106,6 +3111,7 @@ class MouseDown {
|
|
|
3106
3111
|
let pos = this.pos;
|
|
3107
3112
|
if (this.view.state.doc != this.startDoc)
|
|
3108
3113
|
pos = this.view.posAtCoords(eventCoords(event));
|
|
3114
|
+
this.updateAllowDefault(event);
|
|
3109
3115
|
if (this.allowDefault || !pos) {
|
|
3110
3116
|
setSelectionOrigin(this.view, "pointer");
|
|
3111
3117
|
}
|
|
@@ -3133,18 +3139,26 @@ class MouseDown {
|
|
|
3133
3139
|
}
|
|
3134
3140
|
}
|
|
3135
3141
|
move(event) {
|
|
3136
|
-
|
|
3137
|
-
Math.abs(this.event.y - event.clientY) > 4))
|
|
3138
|
-
this.allowDefault = true;
|
|
3142
|
+
this.updateAllowDefault(event);
|
|
3139
3143
|
setSelectionOrigin(this.view, "pointer");
|
|
3140
3144
|
if (event.buttons == 0)
|
|
3141
3145
|
this.done();
|
|
3142
3146
|
}
|
|
3147
|
+
updateAllowDefault(event) {
|
|
3148
|
+
if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
|
|
3149
|
+
Math.abs(this.event.y - event.clientY) > 4))
|
|
3150
|
+
this.allowDefault = true;
|
|
3151
|
+
}
|
|
3143
3152
|
}
|
|
3144
|
-
handlers.
|
|
3153
|
+
handlers.touchstart = view => {
|
|
3154
|
+
view.input.lastTouch = Date.now();
|
|
3145
3155
|
forceDOMFlush(view);
|
|
3146
3156
|
setSelectionOrigin(view, "pointer");
|
|
3147
3157
|
};
|
|
3158
|
+
handlers.touchmove = view => {
|
|
3159
|
+
view.input.lastTouch = Date.now();
|
|
3160
|
+
setSelectionOrigin(view, "pointer");
|
|
3161
|
+
};
|
|
3148
3162
|
handlers.contextmenu = view => forceDOMFlush(view);
|
|
3149
3163
|
function inOrNearComposition(view, event) {
|
|
3150
3164
|
if (view.composing)
|
|
@@ -3396,8 +3410,6 @@ editHandlers.drop = (view, _event) => {
|
|
|
3396
3410
|
if (!eventPos)
|
|
3397
3411
|
return;
|
|
3398
3412
|
let $mouse = view.state.doc.resolve(eventPos.pos);
|
|
3399
|
-
if (!$mouse)
|
|
3400
|
-
return;
|
|
3401
3413
|
let slice = dragging && dragging.slice;
|
|
3402
3414
|
if (slice) {
|
|
3403
3415
|
view.someProp("transformPasted", f => { slice = f(slice); });
|
|
@@ -3442,6 +3454,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
3442
3454
|
view.dispatch(tr.setMeta("uiEvent", "drop"));
|
|
3443
3455
|
};
|
|
3444
3456
|
handlers.focus = view => {
|
|
3457
|
+
view.input.lastFocus = Date.now();
|
|
3445
3458
|
if (!view.focused) {
|
|
3446
3459
|
view.domObserver.stop();
|
|
3447
3460
|
view.dom.classList.add("ProseMirror-focused");
|
|
@@ -3956,23 +3969,27 @@ function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, op
|
|
|
3956
3969
|
let children = oldChildren.slice();
|
|
3957
3970
|
// Mark the children that are directly touched by changes, and
|
|
3958
3971
|
// move those that are after the changes.
|
|
3959
|
-
let
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3972
|
+
for (let i = 0, baseOffset = oldOffset; i < mapping.maps.length; i++) {
|
|
3973
|
+
let moved = 0;
|
|
3974
|
+
mapping.maps[i].forEach((oldStart, oldEnd, newStart, newEnd) => {
|
|
3975
|
+
let dSize = (newEnd - newStart) - (oldEnd - oldStart);
|
|
3976
|
+
for (let i = 0; i < children.length; i += 3) {
|
|
3977
|
+
let end = children[i + 1];
|
|
3978
|
+
if (end < 0 || oldStart > end + baseOffset - moved)
|
|
3979
|
+
continue;
|
|
3980
|
+
let start = children[i] + baseOffset - moved;
|
|
3981
|
+
if (oldEnd >= start) {
|
|
3982
|
+
children[i + 1] = oldStart <= start ? -2 : -1;
|
|
3983
|
+
}
|
|
3984
|
+
else if (newStart >= offset && dSize) {
|
|
3985
|
+
children[i] += dSize;
|
|
3986
|
+
children[i + 1] += dSize;
|
|
3987
|
+
}
|
|
3971
3988
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3989
|
+
moved += dSize;
|
|
3990
|
+
});
|
|
3991
|
+
baseOffset = mapping.maps[i].map(baseOffset, -1);
|
|
3992
|
+
}
|
|
3976
3993
|
// Find the child nodes that still correspond to a single node,
|
|
3977
3994
|
// recursively call mapInner on them and update their positions.
|
|
3978
3995
|
let mustRebuild = false;
|
|
@@ -4300,17 +4317,18 @@ class DOMObserver {
|
|
|
4300
4317
|
}
|
|
4301
4318
|
}
|
|
4302
4319
|
flush() {
|
|
4303
|
-
|
|
4320
|
+
let { view } = this;
|
|
4321
|
+
if (!view.docView || this.flushingSoon > -1)
|
|
4304
4322
|
return;
|
|
4305
4323
|
let mutations = this.observer ? this.observer.takeRecords() : [];
|
|
4306
4324
|
if (this.queue.length) {
|
|
4307
4325
|
mutations = this.queue.concat(mutations);
|
|
4308
4326
|
this.queue.length = 0;
|
|
4309
4327
|
}
|
|
4310
|
-
let sel =
|
|
4311
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(
|
|
4328
|
+
let sel = view.domSelection();
|
|
4329
|
+
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
|
|
4312
4330
|
let from = -1, to = -1, typeOver = false, added = [];
|
|
4313
|
-
if (
|
|
4331
|
+
if (view.editable) {
|
|
4314
4332
|
for (let i = 0; i < mutations.length; i++) {
|
|
4315
4333
|
let result = this.registerMutation(mutations[i], added);
|
|
4316
4334
|
if (result) {
|
|
@@ -4331,16 +4349,29 @@ class DOMObserver {
|
|
|
4331
4349
|
a.remove();
|
|
4332
4350
|
}
|
|
4333
4351
|
}
|
|
4334
|
-
|
|
4352
|
+
let readSel = null;
|
|
4353
|
+
// If it looks like the browser has reset the selection to the
|
|
4354
|
+
// start of the document after focus, restore the selection from
|
|
4355
|
+
// the state
|
|
4356
|
+
if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
|
|
4357
|
+
view.input.lastTouch < Date.now() - 300 &&
|
|
4358
|
+
selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
|
|
4359
|
+
readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
|
|
4360
|
+
view.input.lastFocus = 0;
|
|
4361
|
+
selectionToDOM(view);
|
|
4362
|
+
this.currentSelection.set(sel);
|
|
4363
|
+
view.scrollToSelection();
|
|
4364
|
+
}
|
|
4365
|
+
else if (from > -1 || newSel) {
|
|
4335
4366
|
if (from > -1) {
|
|
4336
|
-
|
|
4337
|
-
checkCSS(
|
|
4367
|
+
view.docView.markDirty(from, to);
|
|
4368
|
+
checkCSS(view);
|
|
4338
4369
|
}
|
|
4339
4370
|
this.handleDOMChange(from, to, typeOver, added);
|
|
4340
|
-
if (
|
|
4341
|
-
|
|
4371
|
+
if (view.docView && view.docView.dirty)
|
|
4372
|
+
view.updateState(view.state);
|
|
4342
4373
|
else if (!this.currentSelection.eq(sel))
|
|
4343
|
-
selectionToDOM(
|
|
4374
|
+
selectionToDOM(view);
|
|
4344
4375
|
this.currentSelection.set(sel);
|
|
4345
4376
|
}
|
|
4346
4377
|
}
|
|
@@ -4397,13 +4428,19 @@ class DOMObserver {
|
|
|
4397
4428
|
}
|
|
4398
4429
|
}
|
|
4399
4430
|
}
|
|
4400
|
-
let cssChecked =
|
|
4431
|
+
let cssChecked = new WeakMap();
|
|
4432
|
+
let cssCheckWarned = false;
|
|
4401
4433
|
function checkCSS(view) {
|
|
4402
|
-
if (cssChecked)
|
|
4434
|
+
if (cssChecked.has(view))
|
|
4403
4435
|
return;
|
|
4404
|
-
cssChecked
|
|
4405
|
-
if (getComputedStyle(view.dom).whiteSpace
|
|
4436
|
+
cssChecked.set(view, null);
|
|
4437
|
+
if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
|
|
4438
|
+
view.requiresGeckoHackNode = gecko;
|
|
4439
|
+
if (cssCheckWarned)
|
|
4440
|
+
return;
|
|
4406
4441
|
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.");
|
|
4442
|
+
cssCheckWarned = true;
|
|
4443
|
+
}
|
|
4407
4444
|
}
|
|
4408
4445
|
|
|
4409
4446
|
// Note that all referencing and parsing is done with the
|
|
@@ -4499,13 +4536,6 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4499
4536
|
to = view.state.doc.resolve(to).after(shared + 1);
|
|
4500
4537
|
let sel = view.state.selection;
|
|
4501
4538
|
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
|
-
let text = view.cursorWrapper.deco.type.toDOM.nextSibling;
|
|
4506
|
-
let size = text && text.nodeValue ? text.nodeValue.length : 1;
|
|
4507
|
-
parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
|
|
4508
|
-
}
|
|
4509
4539
|
let doc = view.state.doc, compare = doc.slice(parse.from, parse.to);
|
|
4510
4540
|
let preferredPos, preferredSide;
|
|
4511
4541
|
// Prefer anchoring to end when Backspace is pressed
|
|
@@ -4540,6 +4570,13 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4540
4570
|
return;
|
|
4541
4571
|
}
|
|
4542
4572
|
}
|
|
4573
|
+
// Chrome sometimes leaves the cursor before the inserted text when
|
|
4574
|
+
// composing after a cursor wrapper. This moves it forward.
|
|
4575
|
+
if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
|
|
4576
|
+
parse.sel.head == parse.sel.anchor) {
|
|
4577
|
+
let size = change.endB - change.start;
|
|
4578
|
+
parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
|
|
4579
|
+
}
|
|
4543
4580
|
view.input.domChangeCount++;
|
|
4544
4581
|
// Handle the case where overwriting a selection by typing matches
|
|
4545
4582
|
// the start or end of the selected content, creating a change
|
|
@@ -4809,6 +4846,12 @@ class EditorView {
|
|
|
4809
4846
|
this.prevDirectPlugins = [];
|
|
4810
4847
|
this.pluginViews = [];
|
|
4811
4848
|
/**
|
|
4849
|
+
Holds `true` when a hack node is needed in Firefox to prevent the
|
|
4850
|
+
[space is eaten issue](https://github.com/ProseMirror/prosemirror/issues/651)
|
|
4851
|
+
@internal
|
|
4852
|
+
*/
|
|
4853
|
+
this.requiresGeckoHackNode = false;
|
|
4854
|
+
/**
|
|
4812
4855
|
When editor content is being dragged, this object contains
|
|
4813
4856
|
information about the dragged slice and whether it is being
|
|
4814
4857
|
copied or moved. At any other time, it is null.
|
|
@@ -4959,21 +5002,27 @@ class EditorView {
|
|
|
4959
5002
|
this.dom.scrollTop = 0;
|
|
4960
5003
|
}
|
|
4961
5004
|
else if (scroll == "to selection") {
|
|
4962
|
-
|
|
4963
|
-
if (this.someProp("handleScrollToSelection", f => f(this))) ;
|
|
4964
|
-
else if (state.selection instanceof NodeSelection) {
|
|
4965
|
-
let target = this.docView.domAfterPos(state.selection.from);
|
|
4966
|
-
if (target.nodeType == 1)
|
|
4967
|
-
scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
|
|
4968
|
-
}
|
|
4969
|
-
else {
|
|
4970
|
-
scrollRectIntoView(this, this.coordsAtPos(state.selection.head, 1), startDOM);
|
|
4971
|
-
}
|
|
5005
|
+
this.scrollToSelection();
|
|
4972
5006
|
}
|
|
4973
5007
|
else if (oldScrollPos) {
|
|
4974
5008
|
resetScrollPos(oldScrollPos);
|
|
4975
5009
|
}
|
|
4976
5010
|
}
|
|
5011
|
+
/**
|
|
5012
|
+
@internal
|
|
5013
|
+
*/
|
|
5014
|
+
scrollToSelection() {
|
|
5015
|
+
let startDOM = this.domSelection().focusNode;
|
|
5016
|
+
if (this.someProp("handleScrollToSelection", f => f(this))) ;
|
|
5017
|
+
else if (this.state.selection instanceof NodeSelection) {
|
|
5018
|
+
let target = this.docView.domAfterPos(this.state.selection.from);
|
|
5019
|
+
if (target.nodeType == 1)
|
|
5020
|
+
scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
|
|
5021
|
+
}
|
|
5022
|
+
else {
|
|
5023
|
+
scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM);
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
4977
5026
|
destroyPluginViews() {
|
|
4978
5027
|
let view;
|
|
4979
5028
|
while (view = this.pluginViews.pop())
|
package/package.json
CHANGED
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
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
let
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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,14 +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
|
-
let text = (view.cursorWrapper.deco.type as any).toDOM.nextSibling as DOMNode
|
|
104
|
-
let size = text && text.nodeValue ? text.nodeValue.length : 1
|
|
105
|
-
parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
100
|
let doc = view.state.doc, compare = doc.slice(parse.from, parse.to)
|
|
109
101
|
let preferredPos, preferredSide: "start" | "end"
|
|
110
102
|
// Prefer anchoring to end when Backspace is pressed
|
|
@@ -137,6 +129,14 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
137
129
|
return
|
|
138
130
|
}
|
|
139
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
|
+
|
|
140
140
|
view.input.domChangeCount++
|
|
141
141
|
// Handle the case where overwriting a selection by typing matches
|
|
142
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),
|
|
344
|
+
return flattenV(singleRect(textRange(node as Text, from, to), 1), takeSide < 0)
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
package/src/domobserver.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
163
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
checkCSS(
|
|
202
|
+
view.docView.markDirty(from, to)
|
|
203
|
+
checkCSS(view)
|
|
190
204
|
}
|
|
191
205
|
this.handleDOMChange(from, to, typeOver, added)
|
|
192
|
-
if (
|
|
193
|
-
else if (!this.currentSelection.eq(sel)) selectionToDOM(
|
|
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 =
|
|
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
|
|
251
|
-
if (getComputedStyle(view.dom).whiteSpace
|
|
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
|
}
|