prosemirror-view 1.33.2 → 1.33.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 1.33.4 (2024-04-08)
2
+
3
+ ### Bug fixes
4
+
5
+ When available, use `Selection.getComposedRanges` on Safari to find the selection inside a shadow DOM.
6
+
7
+ ## 1.33.3 (2024-03-22)
8
+
9
+ ### Bug fixes
10
+
11
+ Further improve preserving of compositions for IME systems that keep the cursor before the composed text.
12
+
1
13
  ## 1.33.2 (2024-03-21)
2
14
 
3
15
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -69,7 +69,7 @@ function textNodeBefore$1(node, offset) {
69
69
  for (;;) {
70
70
  if (node.nodeType == 3 && offset) return node;
71
71
  if (node.nodeType == 1 && offset > 0) {
72
- if (node.contentEditable != "false") return null;
72
+ if (node.contentEditable == "false") return null;
73
73
  node = node.childNodes[offset - 1];
74
74
  offset = nodeSize(node);
75
75
  } else if (node.parentNode && !hasBlockDesc(node)) {
@@ -84,7 +84,7 @@ function textNodeAfter$1(node, offset) {
84
84
  for (;;) {
85
85
  if (node.nodeType == 3 && offset < node.nodeValue.length) return node;
86
86
  if (node.nodeType == 1 && offset < node.childNodes.length) {
87
- if (node.contentEditable != "false") return null;
87
+ if (node.contentEditable == "false") return null;
88
88
  node = node.childNodes[offset];
89
89
  offset = 0;
90
90
  } else if (node.parentNode && !hasBlockDesc(node)) {
@@ -1044,6 +1044,11 @@ var ViewDesc = function () {
1044
1044
  get: function get() {
1045
1045
  return false;
1046
1046
  }
1047
+ }, {
1048
+ key: "isText",
1049
+ value: function isText(text) {
1050
+ return false;
1051
+ }
1047
1052
  }]);
1048
1053
  return ViewDesc;
1049
1054
  }();
@@ -1308,14 +1313,7 @@ var NodeViewDesc = function (_ViewDesc4) {
1308
1313
  from = _view$state$selection.from,
1309
1314
  to = _view$state$selection.to;
1310
1315
  if (!(view.state.selection instanceof prosemirrorState.TextSelection) || from < pos || to > pos + this.node.content.size) return null;
1311
- var sel = view.domSelectionRange();
1312
- var textBefore = textNodeBefore$1(sel.focusNode, sel.focusOffset);
1313
- var textAfter = textNodeAfter$1(sel.focusNode, sel.focusOffset);
1314
- var textNode = textBefore || textAfter;
1315
- if (textBefore && textAfter && textBefore != textAfter) {
1316
- var descAfter = textAfter.pmViewDesc;
1317
- if (!descAfter || descAfter instanceof TextViewDesc && descAfter.node.text != textAfter.nodeValue) textNode = textAfter;
1318
- }
1316
+ var textNode = view.input.compositionNode;
1319
1317
  if (!textNode || !this.dom.contains(textNode.parentNode)) return null;
1320
1318
  if (this.node.inlineContent) {
1321
1319
  var text = textNode.nodeValue;
@@ -1505,6 +1503,11 @@ var TextViewDesc = function (_NodeViewDesc) {
1505
1503
  get: function get() {
1506
1504
  return false;
1507
1505
  }
1506
+ }, {
1507
+ key: "isText",
1508
+ value: function isText(text) {
1509
+ return this.node.text == text;
1510
+ }
1508
1511
  }]);
1509
1512
  return TextViewDesc;
1510
1513
  }(NodeViewDesc);
@@ -2822,6 +2825,7 @@ var InputState = _createClass(function InputState() {
2822
2825
  this.lastTouch = 0;
2823
2826
  this.lastAndroidDelete = 0;
2824
2827
  this.composing = false;
2828
+ this.compositionNode = null;
2825
2829
  this.composingTimeout = -1;
2826
2830
  this.compositionNodes = [];
2827
2831
  this.compositionEndedAt = -2e8;
@@ -3209,6 +3213,7 @@ editHandlers.compositionend = function (view, event) {
3209
3213
  view.input.composing = false;
3210
3214
  view.input.compositionEndedAt = event.timeStamp;
3211
3215
  view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
3216
+ view.input.compositionNode = null;
3212
3217
  if (view.input.compositionPendingChanges) Promise.resolve().then(function () {
3213
3218
  return view.domObserver.flush();
3214
3219
  });
@@ -3229,6 +3234,22 @@ function clearComposition(view) {
3229
3234
  }
3230
3235
  while (view.input.compositionNodes.length > 0) view.input.compositionNodes.pop().markParentsDirty();
3231
3236
  }
3237
+ function findCompositionNode(view) {
3238
+ var sel = view.domSelectionRange();
3239
+ if (!sel.focusNode) return null;
3240
+ var textBefore = textNodeBefore$1(sel.focusNode, sel.focusOffset);
3241
+ var textAfter = textNodeAfter$1(sel.focusNode, sel.focusOffset);
3242
+ if (textBefore && textAfter && textBefore != textAfter) {
3243
+ var descAfter = textAfter.pmViewDesc;
3244
+ if (!descAfter || !descAfter.isText(textAfter.nodeValue)) {
3245
+ return textAfter;
3246
+ } else if (view.input.compositionNode == textAfter) {
3247
+ var descBefore = textBefore.pmViewDesc;
3248
+ if (!(!descBefore || !descBefore.isText(textBefore.nodeValue))) return textAfter;
3249
+ }
3250
+ }
3251
+ return textBefore;
3252
+ }
3232
3253
  function timestampFromCustomEvent() {
3233
3254
  var event = document.createEvent("Event");
3234
3255
  event.initEvent("event", true, true);
@@ -4328,20 +4349,11 @@ function checkCSS(view) {
4328
4349
  cssCheckWarned = true;
4329
4350
  }
4330
4351
  }
4331
- function safariShadowSelectionRange(view) {
4332
- var found;
4333
- function read(event) {
4334
- event.preventDefault();
4335
- event.stopImmediatePropagation();
4336
- found = event.getTargetRanges()[0];
4337
- }
4338
- view.dom.addEventListener("beforeinput", read, true);
4339
- document.execCommand("indent");
4340
- view.dom.removeEventListener("beforeinput", read, true);
4341
- var anchorNode = found.startContainer,
4342
- anchorOffset = found.startOffset;
4343
- var focusNode = found.endContainer,
4344
- focusOffset = found.endOffset;
4352
+ function rangeToSelectionRange(view, range) {
4353
+ var anchorNode = range.startContainer,
4354
+ anchorOffset = range.startOffset;
4355
+ var focusNode = range.endContainer,
4356
+ focusOffset = range.endOffset;
4345
4357
  var currentAnchor = view.domAtPos(view.state.selection.anchor);
4346
4358
  if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset)) {
4347
4359
  var _ref3 = [focusNode, focusOffset, anchorNode, anchorOffset];
@@ -4357,6 +4369,22 @@ function safariShadowSelectionRange(view) {
4357
4369
  focusOffset: focusOffset
4358
4370
  };
4359
4371
  }
4372
+ function safariShadowSelectionRange(view, selection) {
4373
+ if (selection.getComposedRanges) {
4374
+ var range = selection.getComposedRanges(view.root)[0];
4375
+ if (range) return rangeToSelectionRange(view, range);
4376
+ }
4377
+ var found;
4378
+ function read(event) {
4379
+ event.preventDefault();
4380
+ event.stopImmediatePropagation();
4381
+ found = event.getTargetRanges()[0];
4382
+ }
4383
+ view.dom.addEventListener("beforeinput", read, true);
4384
+ document.execCommand("indent");
4385
+ view.dom.removeEventListener("beforeinput", read, true);
4386
+ return found ? rangeToSelectionRange(view, found) : null;
4387
+ }
4360
4388
  function parseBetween(view, from_, to_) {
4361
4389
  var _view$docView$parseRa = view.docView.parseRange(from_, to_),
4362
4390
  parent = _view$docView$parseRa.node,
@@ -4798,6 +4826,7 @@ var EditorView = function () {
4798
4826
  var forceSelUpdate = updateDoc && (ie || chrome) && !this.composing && !prev.selection.empty && !state.selection.empty && selectionContextChanged(prev.selection, state.selection);
4799
4827
  if (updateDoc) {
4800
4828
  var chromeKludge = chrome ? this.trackWrites = this.domSelectionRange().focusNode : null;
4829
+ if (this.composing) this.input.compositionNode = findCompositionNode(this);
4801
4830
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
4802
4831
  this.docView.updateOuterDeco(outerDeco);
4803
4832
  this.docView.destroy();
@@ -5027,7 +5056,8 @@ var EditorView = function () {
5027
5056
  }, {
5028
5057
  key: "domSelectionRange",
5029
5058
  value: function domSelectionRange() {
5030
- return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom ? safariShadowSelectionRange(this) : this.domSelection();
5059
+ var sel = this.domSelection();
5060
+ return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel;
5031
5061
  }
5032
5062
  }, {
5033
5063
  key: "domSelection",
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EditorState, Transaction, Selection, Plugin } from 'prosemirror-state';
2
- import { Mark, Node, ParseRule, Slice, ResolvedPos, DOMParser, DOMSerializer } from 'prosemirror-model';
2
+ import { Mark, Node, TagParseRule, Slice, ResolvedPos, DOMParser, DOMSerializer } from 'prosemirror-model';
3
3
  import { Mapping } from 'prosemirror-transform';
4
4
 
5
5
  type DOMNode = InstanceType<typeof window.Node>;
@@ -324,7 +324,7 @@ declare class ViewDesc {
324
324
  matchesMark(mark: Mark): boolean;
325
325
  matchesNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource): boolean;
326
326
  matchesHack(nodeName: string): boolean;
327
- parseRule(): ParseRule | null;
327
+ parseRule(): Omit<TagParseRule, "tag"> | null;
328
328
  stopEvent(event: Event): boolean;
329
329
  get size(): number;
330
330
  get border(): number;
@@ -361,6 +361,7 @@ declare class ViewDesc {
361
361
  markParentsDirty(): void;
362
362
  get domAtom(): boolean;
363
363
  get ignoreForCoords(): boolean;
364
+ isText(text: string): boolean;
364
365
  }
365
366
  declare class NodeViewDesc extends ViewDesc {
366
367
  node: Node;
@@ -369,7 +370,7 @@ declare class NodeViewDesc extends ViewDesc {
369
370
  readonly nodeDOM: DOMNode;
370
371
  constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, dom: DOMNode, contentDOM: HTMLElement | null, nodeDOM: DOMNode, view: EditorView, pos: number);
371
372
  static create(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView, pos: number): NodeViewDesc | TextViewDesc;
372
- parseRule(): ParseRule | null;
373
+ parseRule(): Omit<TagParseRule, "tag"> | null;
373
374
  matchesNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource): boolean;
374
375
  get size(): number;
375
376
  get border(): 0 | 1;
@@ -393,7 +394,9 @@ declare class NodeViewDesc extends ViewDesc {
393
394
  }
394
395
  declare class TextViewDesc extends NodeViewDesc {
395
396
  constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, dom: DOMNode, nodeDOM: DOMNode, view: EditorView);
396
- parseRule(): ParseRule;
397
+ parseRule(): {
398
+ skip: any;
399
+ };
397
400
  update(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView): boolean;
398
401
  inParent(): boolean;
399
402
  domFromPos(pos: number): {
@@ -405,6 +408,7 @@ declare class TextViewDesc extends NodeViewDesc {
405
408
  slice(from: number, to: number, view: EditorView): TextViewDesc;
406
409
  markDirty(from: number, to: number): void;
407
410
  get domAtom(): boolean;
411
+ isText(text: string): boolean;
408
412
  }
409
413
 
410
414
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EditorState, Transaction, Selection, Plugin } from 'prosemirror-state';
2
- import { Mark, Node, ParseRule, Slice, ResolvedPos, DOMParser, DOMSerializer } from 'prosemirror-model';
2
+ import { Mark, Node, TagParseRule, Slice, ResolvedPos, DOMParser, DOMSerializer } from 'prosemirror-model';
3
3
  import { Mapping } from 'prosemirror-transform';
4
4
 
5
5
  type DOMNode = InstanceType<typeof window.Node>;
@@ -324,7 +324,7 @@ declare class ViewDesc {
324
324
  matchesMark(mark: Mark): boolean;
325
325
  matchesNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource): boolean;
326
326
  matchesHack(nodeName: string): boolean;
327
- parseRule(): ParseRule | null;
327
+ parseRule(): Omit<TagParseRule, "tag"> | null;
328
328
  stopEvent(event: Event): boolean;
329
329
  get size(): number;
330
330
  get border(): number;
@@ -361,6 +361,7 @@ declare class ViewDesc {
361
361
  markParentsDirty(): void;
362
362
  get domAtom(): boolean;
363
363
  get ignoreForCoords(): boolean;
364
+ isText(text: string): boolean;
364
365
  }
365
366
  declare class NodeViewDesc extends ViewDesc {
366
367
  node: Node;
@@ -369,7 +370,7 @@ declare class NodeViewDesc extends ViewDesc {
369
370
  readonly nodeDOM: DOMNode;
370
371
  constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, dom: DOMNode, contentDOM: HTMLElement | null, nodeDOM: DOMNode, view: EditorView, pos: number);
371
372
  static create(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView, pos: number): NodeViewDesc | TextViewDesc;
372
- parseRule(): ParseRule | null;
373
+ parseRule(): Omit<TagParseRule, "tag"> | null;
373
374
  matchesNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource): boolean;
374
375
  get size(): number;
375
376
  get border(): 0 | 1;
@@ -393,7 +394,9 @@ declare class NodeViewDesc extends ViewDesc {
393
394
  }
394
395
  declare class TextViewDesc extends NodeViewDesc {
395
396
  constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, dom: DOMNode, nodeDOM: DOMNode, view: EditorView);
396
- parseRule(): ParseRule;
397
+ parseRule(): {
398
+ skip: any;
399
+ };
397
400
  update(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView): boolean;
398
401
  inParent(): boolean;
399
402
  domFromPos(pos: number): {
@@ -405,6 +408,7 @@ declare class TextViewDesc extends NodeViewDesc {
405
408
  slice(from: number, to: number, view: EditorView): TextViewDesc;
406
409
  markDirty(from: number, to: number): void;
407
410
  get domAtom(): boolean;
411
+ isText(text: string): boolean;
408
412
  }
409
413
 
410
414
  /**
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ function textNodeBefore$1(node, offset) {
65
65
  if (node.nodeType == 3 && offset)
66
66
  return node;
67
67
  if (node.nodeType == 1 && offset > 0) {
68
- if (node.contentEditable != "false")
68
+ if (node.contentEditable == "false")
69
69
  return null;
70
70
  node = node.childNodes[offset - 1];
71
71
  offset = nodeSize(node);
@@ -84,7 +84,7 @@ function textNodeAfter$1(node, offset) {
84
84
  if (node.nodeType == 3 && offset < node.nodeValue.length)
85
85
  return node;
86
86
  if (node.nodeType == 1 && offset < node.childNodes.length) {
87
- if (node.contentEditable != "false")
87
+ if (node.contentEditable == "false")
88
88
  return null;
89
89
  node = node.childNodes[offset];
90
90
  offset = 0;
@@ -1115,6 +1115,7 @@ class ViewDesc {
1115
1115
  }
1116
1116
  get domAtom() { return false; }
1117
1117
  get ignoreForCoords() { return false; }
1118
+ isText(text) { return false; }
1118
1119
  }
1119
1120
  // A widget desc represents a widget decoration, which is a DOM node
1120
1121
  // drawn between the document nodes.
@@ -1377,15 +1378,7 @@ class NodeViewDesc extends ViewDesc {
1377
1378
  let { from, to } = view.state.selection;
1378
1379
  if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size)
1379
1380
  return null;
1380
- let sel = view.domSelectionRange();
1381
- let textBefore = textNodeBefore$1(sel.focusNode, sel.focusOffset);
1382
- let textAfter = textNodeAfter$1(sel.focusNode, sel.focusOffset);
1383
- let textNode = textBefore || textAfter;
1384
- if (textBefore && textAfter && textBefore != textAfter) {
1385
- let descAfter = textAfter.pmViewDesc;
1386
- if (!descAfter || descAfter instanceof TextViewDesc && descAfter.node.text != textAfter.nodeValue)
1387
- textNode = textAfter;
1388
- }
1381
+ let textNode = view.input.compositionNode;
1389
1382
  if (!textNode || !this.dom.contains(textNode.parentNode))
1390
1383
  return null;
1391
1384
  if (this.node.inlineContent) {
@@ -1527,6 +1520,7 @@ class TextViewDesc extends NodeViewDesc {
1527
1520
  this.dirty = NODE_DIRTY;
1528
1521
  }
1529
1522
  get domAtom() { return false; }
1523
+ isText(text) { return this.node.text == text; }
1530
1524
  }
1531
1525
  // A dummy desc used to tag trailing BR or IMG nodes created to work
1532
1526
  // around contentEditable terribleness.
@@ -3000,6 +2994,7 @@ class InputState {
3000
2994
  this.lastTouch = 0;
3001
2995
  this.lastAndroidDelete = 0;
3002
2996
  this.composing = false;
2997
+ this.compositionNode = null;
3003
2998
  this.composingTimeout = -1;
3004
2999
  this.compositionNodes = [];
3005
3000
  this.compositionEndedAt = -2e8;
@@ -3436,6 +3431,7 @@ editHandlers.compositionend = (view, event) => {
3436
3431
  view.input.composing = false;
3437
3432
  view.input.compositionEndedAt = event.timeStamp;
3438
3433
  view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
3434
+ view.input.compositionNode = null;
3439
3435
  if (view.input.compositionPendingChanges)
3440
3436
  Promise.resolve().then(() => view.domObserver.flush());
3441
3437
  view.input.compositionID++;
@@ -3455,6 +3451,25 @@ function clearComposition(view) {
3455
3451
  while (view.input.compositionNodes.length > 0)
3456
3452
  view.input.compositionNodes.pop().markParentsDirty();
3457
3453
  }
3454
+ function findCompositionNode(view) {
3455
+ let sel = view.domSelectionRange();
3456
+ if (!sel.focusNode)
3457
+ return null;
3458
+ let textBefore = textNodeBefore$1(sel.focusNode, sel.focusOffset);
3459
+ let textAfter = textNodeAfter$1(sel.focusNode, sel.focusOffset);
3460
+ if (textBefore && textAfter && textBefore != textAfter) {
3461
+ let descAfter = textAfter.pmViewDesc;
3462
+ if (!descAfter || !descAfter.isText(textAfter.nodeValue)) {
3463
+ return textAfter;
3464
+ }
3465
+ else if (view.input.compositionNode == textAfter) {
3466
+ let descBefore = textBefore.pmViewDesc;
3467
+ if (!(!descBefore || !descBefore.isText(textBefore.nodeValue)))
3468
+ return textAfter;
3469
+ }
3470
+ }
3471
+ return textBefore;
3472
+ }
3458
3473
  function timestampFromCustomEvent() {
3459
3474
  let event = document.createEvent("Event");
3460
3475
  event.initEvent("event", true, true);
@@ -4689,9 +4704,25 @@ function checkCSS(view) {
4689
4704
  cssCheckWarned = true;
4690
4705
  }
4691
4706
  }
4707
+ function rangeToSelectionRange(view, range) {
4708
+ let anchorNode = range.startContainer, anchorOffset = range.startOffset;
4709
+ let focusNode = range.endContainer, focusOffset = range.endOffset;
4710
+ let currentAnchor = view.domAtPos(view.state.selection.anchor);
4711
+ // Since such a range doesn't distinguish between anchor and head,
4712
+ // use a heuristic that flips it around if its end matches the
4713
+ // current anchor.
4714
+ if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
4715
+ [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
4716
+ return { anchorNode, anchorOffset, focusNode, focusOffset };
4717
+ }
4692
4718
  // Used to work around a Safari Selection/shadow DOM bug
4693
4719
  // Based on https://github.com/codemirror/dev/issues/414 fix
4694
- function safariShadowSelectionRange(view) {
4720
+ function safariShadowSelectionRange(view, selection) {
4721
+ if (selection.getComposedRanges) {
4722
+ let range = selection.getComposedRanges(view.root)[0];
4723
+ if (range)
4724
+ return rangeToSelectionRange(view, range);
4725
+ }
4695
4726
  let found;
4696
4727
  function read(event) {
4697
4728
  event.preventDefault();
@@ -4706,15 +4737,7 @@ function safariShadowSelectionRange(view) {
4706
4737
  view.dom.addEventListener("beforeinput", read, true);
4707
4738
  document.execCommand("indent");
4708
4739
  view.dom.removeEventListener("beforeinput", read, true);
4709
- let anchorNode = found.startContainer, anchorOffset = found.startOffset;
4710
- let focusNode = found.endContainer, focusOffset = found.endOffset;
4711
- let currentAnchor = view.domAtPos(view.state.selection.anchor);
4712
- // Since such a range doesn't distinguish between anchor and head,
4713
- // use a heuristic that flips it around if its end matches the
4714
- // current anchor.
4715
- if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
4716
- [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
4717
- return { anchorNode, anchorOffset, focusNode, focusOffset };
4740
+ return found ? rangeToSelectionRange(view, found) : null;
4718
4741
  }
4719
4742
 
4720
4743
  // Note that all referencing and parsing is done with the
@@ -5276,6 +5299,8 @@ class EditorView {
5276
5299
  // tracks that and forces a selection reset when our update
5277
5300
  // did write to the node.
5278
5301
  let chromeKludge = chrome ? (this.trackWrites = this.domSelectionRange().focusNode) : null;
5302
+ if (this.composing)
5303
+ this.input.compositionNode = findCompositionNode(this);
5279
5304
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
5280
5305
  this.docView.updateOuterDeco(outerDeco);
5281
5306
  this.docView.destroy();
@@ -5590,8 +5615,9 @@ class EditorView {
5590
5615
  @internal
5591
5616
  */
5592
5617
  domSelectionRange() {
5593
- return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom
5594
- ? safariShadowSelectionRange(this) : this.domSelection();
5618
+ let sel = this.domSelection();
5619
+ return safari && this.root.nodeType === 11 &&
5620
+ deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel;
5595
5621
  }
5596
5622
  /**
5597
5623
  @internal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.33.2",
3
+ "version": "1.33.4",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -28,7 +28,7 @@
28
28
  "url": "git://github.com/prosemirror/prosemirror-view.git"
29
29
  },
30
30
  "dependencies": {
31
- "prosemirror-model": "^1.16.0",
31
+ "prosemirror-model": "^1.20.0",
32
32
  "prosemirror-state": "^1.0.0",
33
33
  "prosemirror-transform": "^1.1.0"
34
34
  },
package/src/dom.ts CHANGED
@@ -71,7 +71,7 @@ export function textNodeBefore(node: Node, offset: number) {
71
71
  for (;;) {
72
72
  if (node.nodeType == 3 && offset) return node as Text
73
73
  if (node.nodeType == 1 && offset > 0) {
74
- if ((node as HTMLElement).contentEditable != "false") return null
74
+ if ((node as HTMLElement).contentEditable == "false") return null
75
75
  node = node.childNodes[offset - 1]
76
76
  offset = nodeSize(node)
77
77
  } else if (node.parentNode && !hasBlockDesc(node)) {
@@ -87,7 +87,7 @@ export function textNodeAfter(node: Node, offset: number) {
87
87
  for (;;) {
88
88
  if (node.nodeType == 3 && offset < node.nodeValue!.length) return node as Text
89
89
  if (node.nodeType == 1 && offset < node.childNodes.length) {
90
- if ((node as HTMLElement).contentEditable != "false") return null
90
+ if ((node as HTMLElement).contentEditable == "false") return null
91
91
  node = node.childNodes[offset]
92
92
  offset = 0
93
93
  } else if (node.parentNode && !hasBlockDesc(node)) {
package/src/domchange.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {Fragment, DOMParser, ParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
1
+ import {Fragment, DOMParser, TagParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
2
2
  import {Selection, TextSelection} from "prosemirror-state"
3
3
 
4
4
  import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
@@ -55,7 +55,7 @@ function parseBetween(view: EditorView, from_: number, to_: number) {
55
55
  return {doc, sel, from, to}
56
56
  }
57
57
 
58
- function ruleFromNode(dom: DOMNode): ParseRule | null {
58
+ function ruleFromNode(dom: DOMNode): Omit<TagParseRule, "tag"> | null {
59
59
  let desc = dom.pmViewDesc
60
60
  if (desc) {
61
61
  return desc.parseRule()
@@ -1,6 +1,6 @@
1
1
  import {Selection} from "prosemirror-state"
2
2
  import * as browser from "./browser"
3
- import {domIndex, isEquivalentPosition, selectionCollapsed, parentNode, DOMSelectionRange, DOMNode} from "./dom"
3
+ import {domIndex, isEquivalentPosition, selectionCollapsed, parentNode, DOMSelectionRange, DOMNode, DOMSelection} from "./dom"
4
4
  import {hasFocusAndSelection, selectionToDOM, selectionFromDOM} from "./selection"
5
5
  import {EditorView} from "./index"
6
6
 
@@ -278,9 +278,27 @@ function checkCSS(view: EditorView) {
278
278
  }
279
279
  }
280
280
 
281
+ function rangeToSelectionRange(view: EditorView, range: StaticRange) {
282
+ let anchorNode = range.startContainer, anchorOffset = range.startOffset
283
+ let focusNode = range.endContainer, focusOffset = range.endOffset
284
+
285
+ let currentAnchor = view.domAtPos(view.state.selection.anchor)
286
+ // Since such a range doesn't distinguish between anchor and head,
287
+ // use a heuristic that flips it around if its end matches the
288
+ // current anchor.
289
+ if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
290
+ [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset]
291
+ return {anchorNode, anchorOffset, focusNode, focusOffset}
292
+ }
293
+
281
294
  // Used to work around a Safari Selection/shadow DOM bug
282
295
  // Based on https://github.com/codemirror/dev/issues/414 fix
283
- export function safariShadowSelectionRange(view: EditorView): DOMSelectionRange {
296
+ export function safariShadowSelectionRange(view: EditorView, selection: DOMSelection): DOMSelectionRange | null {
297
+ if ((selection as any).getComposedRanges) {
298
+ let range = (selection as any).getComposedRanges(view.root)[0] as StaticRange
299
+ if (range) return rangeToSelectionRange(view, range)
300
+ }
301
+
284
302
  let found: StaticRange | undefined
285
303
  function read(event: InputEvent) {
286
304
  event.preventDefault()
@@ -297,14 +315,5 @@ export function safariShadowSelectionRange(view: EditorView): DOMSelectionRange
297
315
  document.execCommand("indent")
298
316
  view.dom.removeEventListener("beforeinput", read, true)
299
317
 
300
- let anchorNode = found!.startContainer, anchorOffset = found!.startOffset
301
- let focusNode = found!.endContainer, focusOffset = found!.endOffset
302
-
303
- let currentAnchor = view.domAtPos(view.state.selection.anchor)
304
- // Since such a range doesn't distinguish between anchor and head,
305
- // use a heuristic that flips it around if its end matches the
306
- // current anchor.
307
- if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
308
- [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset]
309
- return {anchorNode, anchorOffset, focusNode, focusOffset}
318
+ return found ? rangeToSelectionRange(view, found) : null
310
319
  }
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ import {scrollRectIntoView, posAtCoords, coordsAtPos, endOfTextblock, storeScrol
5
5
  resetScrollPos, focusPreventScroll} from "./domcoords"
6
6
  import {docViewDesc, ViewDesc, NodeView, NodeViewDesc} from "./viewdesc"
7
7
  import {initInput, destroyInput, dispatchEvent, ensureListeners, clearComposition,
8
- InputState, doPaste, Dragging} from "./input"
8
+ InputState, doPaste, Dragging, findCompositionNode} from "./input"
9
9
  import {selectionToDOM, anchorInRightPlace, syncNodeSelection} from "./selection"
10
10
  import {Decoration, viewDecorations, DecorationSource} from "./decoration"
11
11
  import {DOMObserver, safariShadowSelectionRange} from "./domobserver"
@@ -197,6 +197,7 @@ export class EditorView {
197
197
  // tracks that and forces a selection reset when our update
198
198
  // did write to the node.
199
199
  let chromeKludge = browser.chrome ? (this.trackWrites = this.domSelectionRange().focusNode) : null
200
+ if (this.composing) this.input.compositionNode = findCompositionNode(this)
200
201
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
201
202
  this.docView.updateOuterDeco(outerDeco)
202
203
  this.docView.destroy()
@@ -487,8 +488,9 @@ export class EditorView {
487
488
 
488
489
  /// @internal
489
490
  domSelectionRange(): DOMSelectionRange {
490
- return browser.safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom
491
- ? safariShadowSelectionRange(this) : this.domSelection()
491
+ let sel = this.domSelection()
492
+ return browser.safari && this.root.nodeType === 11 &&
493
+ deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel
492
494
  }
493
495
 
494
496
  /// @internal
package/src/input.ts CHANGED
@@ -6,7 +6,7 @@ import * as browser from "./browser"
6
6
  import {captureKeyDown} from "./capturekeys"
7
7
  import {parseFromClipboard, serializeForClipboard} from "./clipboard"
8
8
  import {selectionBetween, selectionToDOM, selectionFromDOM} from "./selection"
9
- import {keyEvent, DOMNode} from "./dom"
9
+ import {keyEvent, DOMNode, textNodeBefore, textNodeAfter} from "./dom"
10
10
  import {EditorView} from "./index"
11
11
  import {ViewDesc} from "./viewdesc"
12
12
 
@@ -30,6 +30,7 @@ export class InputState {
30
30
  lastTouch = 0
31
31
  lastAndroidDelete = 0
32
32
  composing = false
33
+ compositionNode: Text | null = null
33
34
  composingTimeout = -1
34
35
  compositionNodes: ViewDesc[] = []
35
36
  compositionEndedAt = -2e8
@@ -490,6 +491,7 @@ editHandlers.compositionend = (view, event) => {
490
491
  view.input.composing = false
491
492
  view.input.compositionEndedAt = event.timeStamp
492
493
  view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0
494
+ view.input.compositionNode = null
493
495
  if (view.input.compositionPendingChanges) Promise.resolve().then(() => view.domObserver.flush())
494
496
  view.input.compositionID++
495
497
  scheduleComposeEnd(view, 20)
@@ -509,6 +511,24 @@ export function clearComposition(view: EditorView) {
509
511
  while (view.input.compositionNodes.length > 0) view.input.compositionNodes.pop()!.markParentsDirty()
510
512
  }
511
513
 
514
+ export function findCompositionNode(view: EditorView) {
515
+ let sel = view.domSelectionRange()
516
+ if (!sel.focusNode) return null
517
+ let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset)
518
+ let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset)
519
+ if (textBefore && textAfter && textBefore != textAfter) {
520
+ let descAfter = textAfter.pmViewDesc
521
+ if (!descAfter || !descAfter.isText(textAfter.nodeValue!)) {
522
+ return textAfter
523
+ } else if (view.input.compositionNode == textAfter) {
524
+ let descBefore = textBefore.pmViewDesc
525
+ if (!(!descBefore || !descBefore.isText(textBefore.nodeValue!)))
526
+ return textAfter
527
+ }
528
+ }
529
+ return textBefore
530
+ }
531
+
512
532
  function timestampFromCustomEvent() {
513
533
  let event = document.createEvent("Event")
514
534
  event.initEvent("event", true, true)