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/CHANGELOG.md +28 -0
- package/dist/index.cjs +116 -69
- package/dist/index.d.ts +29 -24
- package/dist/index.js +104 -58
- package/package.json +1 -1
- package/src/decoration.ts +16 -12
- package/src/domchange.ts +2 -1
- package/src/domcoords.ts +8 -7
- package/src/domobserver.ts +37 -18
- package/src/index.ts +46 -33
- package/src/input.ts +13 -4
- package/src/viewdesc.ts +7 -6
- 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
|
@@ -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),
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
4310
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
4336
|
-
checkCSS(
|
|
4362
|
+
view.docView.markDirty(from, to);
|
|
4363
|
+
checkCSS(view);
|
|
4337
4364
|
}
|
|
4338
4365
|
this.handleDOMChange(from, to, typeOver, added);
|
|
4339
|
-
if (
|
|
4340
|
-
|
|
4366
|
+
if (view.docView && view.docView.dirty)
|
|
4367
|
+
view.updateState(view.state);
|
|
4341
4368
|
else if (!this.currentSelection.eq(sel))
|
|
4342
|
-
selectionToDOM(
|
|
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 =
|
|
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
|
|
4404
|
-
if (getComputedStyle(view.dom).whiteSpace
|
|
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
|
-
|
|
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
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
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
let
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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),
|
|
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 (
|
|
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))
|
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
|
}
|