@tiptap/y-tiptap 3.0.5 → 3.0.7

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/src/lib.d.ts CHANGED
@@ -96,7 +96,11 @@ export function yDocToProsemirrorJSON(ydoc: Y.Doc, xmlFragment?: string): Record
96
96
  export function yXmlFragmentToProsemirrorJSON(xmlFragment: Y.XmlFragment): Record<string, any>;
97
97
  export function setMeta(view: any, key: any, value: any): void;
98
98
  export function absolutePositionToRelativePosition(pos: number, type: Y.XmlFragment, mapping: ProsemirrorMapping): any;
99
+ export function isMisresolvedTextPosition(y: Y.Doc, relPos: Y.RelativePosition, absPos: number | null): boolean;
99
100
  export function relativePositionToAbsolutePosition(y: Y.Doc, documentType: Y.XmlFragment, relPos: any, mapping: ProsemirrorMapping): null | number;
101
+ export function findAbsolutePositionAfterStructuralChange(oldDoc: import('prosemirror-model').Node, newDoc: import('prosemirror-model').Node, absPos: number): number | null;
102
+ export function isStructuralTransaction(tr: import('prosemirror-state').Transaction, oldDoc: import('prosemirror-model').Node): boolean;
103
+ export function isMisresolvedAfterStructuralChange(oldDoc: import('prosemirror-model').Node, newDoc: import('prosemirror-model').Node, oldAbs: number, resolvedAbs: number | null): boolean;
100
104
  export function yXmlFragmentToProseMirrorFragment(yXmlFragment: Y.XmlFragment, schema: Schema): Fragment;
101
105
  export function yXmlFragmentToProseMirrorRootNode(yXmlFragment: Y.XmlFragment, schema: Schema): Node;
102
106
  export function initProseMirrorDoc(yXmlFragment: Y.XmlFragment, schema: Schema): {
@@ -9,6 +9,8 @@ export function getRelativeSelection(pmbinding: ProsemirrorBinding, state: impor
9
9
  depth: any;
10
10
  anchor: any;
11
11
  head: any;
12
+ absAnchor: number;
13
+ absHead: number;
12
14
  };
13
15
  /**
14
16
  * Binding for prosemirror.
@@ -48,6 +50,8 @@ export class ProsemirrorBinding {
48
50
  depth: any;
49
51
  anchor: any;
50
52
  head: any;
53
+ absAnchor: number;
54
+ absHead: number;
51
55
  };
52
56
  beforeAllTransactions: () => void;
53
57
  afterAllTransactions: () => void;
@@ -2,4 +2,4 @@ export * from "./plugins/cursor-plugin.js";
2
2
  export * from "./plugins/undo-plugin.js";
3
3
  export * from "./plugins/keys.js";
4
4
  export { ySyncPlugin, isVisible, getRelativeSelection, ProsemirrorBinding, updateYFragment } from "./plugins/sync-plugin.js";
5
- export { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc, prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON, yXmlFragmentToProsemirror, prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, yXmlFragmentToProseMirrorFragment, initProseMirrorDoc } from "./lib.js";
5
+ export { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, findAbsolutePositionAfterStructuralChange, isMisresolvedTextPosition, isMisresolvedAfterStructuralChange, isStructuralTransaction, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc, prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON, yXmlFragmentToProsemirror, prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, yXmlFragmentToProseMirrorFragment, initProseMirrorDoc } from "./lib.js";
package/dist/y-tiptap.cjs CHANGED
@@ -20,6 +20,7 @@ var eventloop = require('lib0/eventloop');
20
20
  var map = require('lib0/map');
21
21
  var sha256 = require('lib0/hash/sha256');
22
22
  var buf = require('lib0/buffer');
23
+ var prosemirrorTransform = require('prosemirror-transform');
23
24
 
24
25
  function _interopNamespace(e) {
25
26
  if (e && e.__esModule) return e;
@@ -314,12 +315,38 @@ const ySyncPlugin = (yXmlFragment, {
314
315
  return plugin
315
316
  };
316
317
 
318
+ /**
319
+ * Resolves one text-selection endpoint after a remote update. Falls back to
320
+ * content-based matching when the Yjs resolution is missing or lands in the
321
+ * wrong block, and keeps the Yjs resolution when the fallback finds nothing.
322
+ *
323
+ * @param {import('prosemirror-model').Node} newDoc
324
+ * @param {import('prosemirror-model').Node} oldDoc
325
+ * @param {number|null|undefined} oldAbs
326
+ * @param {number|null} resolved
327
+ * @return {number|null}
328
+ */
329
+ const recoverSelectionEndpoint = (newDoc, oldDoc, oldAbs, resolved) => {
330
+ if (oldAbs == null) {
331
+ return resolved
332
+ }
333
+ const misresolved = resolved === null ||
334
+ (oldAbs > 1 && resolved <= 1) ||
335
+ isMisresolvedAfterStructuralChange(oldDoc, newDoc, oldAbs, resolved);
336
+ if (!misresolved) {
337
+ return resolved
338
+ }
339
+ const recovered = findAbsolutePositionAfterStructuralChange(oldDoc, newDoc, oldAbs);
340
+ return recovered !== null ? recovered : resolved
341
+ };
342
+
317
343
  /**
318
344
  * @param {import('prosemirror-state').Transaction} tr
319
345
  * @param {ReturnType<typeof getRelativeSelection>} relSel
320
346
  * @param {ProsemirrorBinding} binding
347
+ * @param {import('prosemirror-model').Node} [oldDoc]
321
348
  */
322
- const restoreRelativeSelection = (tr, relSel, binding) => {
349
+ const restoreRelativeSelection = (tr, relSel, binding, oldDoc) => {
323
350
  if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
324
351
  if (relSel.type === 'all') {
325
352
  tr.setSelection(new prosemirrorState.AllSelection(tr.doc));
@@ -353,18 +380,30 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
353
380
  tr.setSelection(selection);
354
381
  }
355
382
  } else {
356
- const anchor = relativePositionToAbsolutePosition(
383
+ let anchor = relativePositionToAbsolutePosition(
357
384
  binding.doc,
358
385
  binding.type,
359
386
  relSel.anchor,
360
387
  binding.mapping
361
388
  );
362
- const head = relativePositionToAbsolutePosition(
389
+ let head = relativePositionToAbsolutePosition(
363
390
  binding.doc,
364
391
  binding.type,
365
392
  relSel.head,
366
393
  binding.mapping
367
394
  );
395
+ if (oldDoc != null) {
396
+ anchor = recoverSelectionEndpoint(tr.doc, oldDoc, relSel.absAnchor, anchor);
397
+ head = recoverSelectionEndpoint(tr.doc, oldDoc, relSel.absHead, head);
398
+ }
399
+ // Collapse to the surviving endpoint instead of dropping the selection;
400
+ // an unset selection maps through the full-doc replace to the doc start.
401
+ if (anchor === null) {
402
+ anchor = head;
403
+ }
404
+ if (head === null) {
405
+ head = anchor;
406
+ }
368
407
  if (anchor !== null && head !== null) {
369
408
  tr.setSelection(prosemirrorState.TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head)));
370
409
  }
@@ -445,7 +484,9 @@ const getRelativeSelection = (pmbinding, state) => {
445
484
  state.selection.head,
446
485
  pmbinding.type,
447
486
  pmbinding.mapping
448
- )
487
+ ),
488
+ absAnchor: state.selection.anchor,
489
+ absHead: state.selection.head
449
490
  }
450
491
  };
451
492
 
@@ -770,6 +811,9 @@ class ProsemirrorBinding {
770
811
  );
771
812
  transaction.changed.forEach(delType);
772
813
  transaction.changedParentTypes.forEach(delType);
814
+ // Rebuild the full Y↔PM mapping so relative cursor positions resolve against
815
+ // current node sizes after structural changes (e.g. drag-and-drop block moves).
816
+ this.mapping.clear();
773
817
  const fragmentContent = this.type.toArray().map((t) =>
774
818
  createNodeIfNotExists(
775
819
  /** @type {Y.XmlElement | Y.XmlHook} */ (t),
@@ -777,13 +821,14 @@ class ProsemirrorBinding {
777
821
  this
778
822
  )
779
823
  ).filter((n) => n !== null);
824
+ const oldDoc = this.prosemirrorView.state.doc;
780
825
  // @ts-ignore
781
826
  let tr = this._tr.replace(
782
827
  0,
783
828
  this.prosemirrorView.state.doc.content.size,
784
829
  new PModel__namespace.Slice(PModel__namespace.Fragment.from(fragmentContent), 0, 0)
785
830
  );
786
- restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
831
+ restoreRelativeSelection(tr, this.beforeTransactionSelection, this, oldDoc);
787
832
  tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof Y__namespace.UndoManager });
788
833
  if (
789
834
  this.beforeTransactionSelection !== null && this._isLocalCursorInView()
@@ -1669,6 +1714,26 @@ const absolutePositionToRelativePosition = (pos, type, mapping) => {
1669
1714
  return Y__namespace.createRelativePositionFromTypeIndex(type, type._length, -1)
1670
1715
  };
1671
1716
 
1717
+ /**
1718
+ * Item-id based relative positions can misresolve to the document start after
1719
+ * block reorder during collaborative drag-and-drop.
1720
+ *
1721
+ * @param {Y.Doc} y
1722
+ * @param {Y.RelativePosition} relPos
1723
+ * @param {number|null} absPos
1724
+ * @return {boolean}
1725
+ */
1726
+ const isMisresolvedTextPosition = (y, relPos, absPos) => {
1727
+ if (absPos === null) {
1728
+ return false
1729
+ }
1730
+ const decoded = Y__namespace.createAbsolutePositionFromRelativePosition(relPos, y);
1731
+ return decoded !== null &&
1732
+ decoded.type instanceof Y__namespace.XmlText &&
1733
+ relPos.item !== null &&
1734
+ absPos <= 1
1735
+ };
1736
+
1672
1737
  const createRelativePosition = (type, item) => {
1673
1738
  let typeid = null;
1674
1739
  let tname = null;
@@ -1746,7 +1811,279 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
1746
1811
  }
1747
1812
  type = /** @type {Y.AbstractType} */ (parent);
1748
1813
  }
1749
- return pos - 1 // we don't count the most outer tag, because it is a fragment
1814
+ const absPos = pos - 1; // we don't count the most outer tag, because it is a fragment
1815
+ if (isMisresolvedTextPosition(y, relPos, absPos)) {
1816
+ return null
1817
+ }
1818
+ return absPos
1819
+ };
1820
+
1821
+ /**
1822
+ * Shallow attrs comparison. Attr values are primitives in most schemas;
1823
+ * non-primitive values fail the check and callers fall back to text matching.
1824
+ *
1825
+ * @param {Object<string, any>} a
1826
+ * @param {Object<string, any>} b
1827
+ * @return {boolean}
1828
+ */
1829
+ const attrsEqual = (a, b) => {
1830
+ if (a === b) {
1831
+ return true
1832
+ }
1833
+ const aKeys = Object.keys(a);
1834
+ return aKeys.length === Object.keys(b).length && aKeys.every((k) => a[k] === b[k])
1835
+ };
1836
+
1837
+ /**
1838
+ * Returns true when any attr deviates from its spec default or has none.
1839
+ * Default-only attrs cannot tell same-type siblings apart.
1840
+ *
1841
+ * @param {import('prosemirror-model').Node} node
1842
+ * @return {boolean}
1843
+ */
1844
+ const hasDistinctiveAttrs = (node) => {
1845
+ const specAttrs = node.type.spec.attrs || {};
1846
+ return Object.keys(node.attrs).some((key) => {
1847
+ const spec = specAttrs[key];
1848
+ return spec == null ||
1849
+ !Object.prototype.hasOwnProperty.call(spec, 'default') ||
1850
+ spec.default !== node.attrs[key]
1851
+ })
1852
+ };
1853
+
1854
+ /**
1855
+ * Remaps a position into a matched block by walking the same child-index path
1856
+ * it had in the old block. A raw byte offset would overshoot into a sibling
1857
+ * inner textblock when the old block contains local keystrokes that are not
1858
+ * yet part of the rebuilt document.
1859
+ *
1860
+ * @param {import('prosemirror-model').ResolvedPos} $oldPos
1861
+ * @param {number} newBlockStart
1862
+ * @param {import('prosemirror-model').Node} newBlock
1863
+ * @return {number|null}
1864
+ */
1865
+ const remapIntoBlock = ($oldPos, newBlockStart, newBlock) => {
1866
+ let pos = newBlockStart + 1;
1867
+ let node = newBlock;
1868
+ for (let depth = 1; depth < $oldPos.depth; depth++) {
1869
+ const idx = $oldPos.index(depth);
1870
+ if (idx >= node.childCount) {
1871
+ return null
1872
+ }
1873
+ for (let i = 0; i < idx; i++) {
1874
+ pos += node.child(i).nodeSize;
1875
+ }
1876
+ pos += 1;
1877
+ node = node.child(idx);
1878
+ if (node.type !== $oldPos.node(depth + 1).type) {
1879
+ return null
1880
+ }
1881
+ }
1882
+ if (!node.isTextblock) {
1883
+ return null
1884
+ }
1885
+ return pos + Math.min($oldPos.parentOffset, node.content.size)
1886
+ };
1887
+
1888
+ /**
1889
+ * @param {import('prosemirror-model').Node} oldDoc
1890
+ * @param {import('prosemirror-model').Node} newDoc
1891
+ * @param {number} absPos
1892
+ * @return {number|null}
1893
+ */
1894
+ const findAbsolutePositionAfterStructuralChange = (oldDoc, newDoc, absPos) => {
1895
+ let pos = 0;
1896
+ let targetIdx = 0;
1897
+ for (; targetIdx < oldDoc.childCount; targetIdx++) {
1898
+ const child = oldDoc.child(targetIdx);
1899
+ if (pos + child.nodeSize > absPos) {
1900
+ break
1901
+ }
1902
+ pos += child.nodeSize;
1903
+ }
1904
+ if (targetIdx >= oldDoc.childCount) {
1905
+ return null
1906
+ }
1907
+ const targetChild = oldDoc.child(targetIdx);
1908
+ const $oldPos = oldDoc.resolve(absPos);
1909
+
1910
+ /**
1911
+ * @param {number} newBlockStart
1912
+ * @param {import('prosemirror-model').Node} newBlock
1913
+ * @return {number|null}
1914
+ */
1915
+ const place = (newBlockStart, newBlock) => {
1916
+ // Positions between top-level blocks carry no inner path; clamp them just
1917
+ // inside the matched block like the previous raw-offset remap did.
1918
+ if ($oldPos.depth === 0) {
1919
+ const remapped = newBlockStart + (absPos - pos);
1920
+ const contentStart = newBlockStart + 1;
1921
+ const contentEnd = newBlockStart + newBlock.nodeSize - 1;
1922
+ return Math.max(contentStart, Math.min(remapped, contentEnd))
1923
+ }
1924
+ return remapIntoBlock($oldPos, newBlockStart, newBlock)
1925
+ };
1926
+
1927
+ /**
1928
+ * Finds the Nth block in newDoc matching `pred`, where N is the number of
1929
+ * matching blocks in oldDoc up to and including the target block.
1930
+ *
1931
+ * @param {function(import('prosemirror-model').Node): boolean} pred
1932
+ * @param {boolean} requireUnique
1933
+ * @return {number|null}
1934
+ */
1935
+ const findByPredicate = (pred, requireUnique = false) => {
1936
+ let occurrence = 0;
1937
+ for (let i = 0; i <= targetIdx; i++) {
1938
+ if (pred(oldDoc.child(i))) {
1939
+ occurrence++;
1940
+ }
1941
+ }
1942
+ let matchCount = 0;
1943
+ let matchStart = -1;
1944
+ let matchBlock = null;
1945
+ let newPos = 0;
1946
+ for (let i = 0; i < newDoc.childCount; i++) {
1947
+ const child = newDoc.child(i);
1948
+ if (pred(child)) {
1949
+ matchCount++;
1950
+ if (matchCount === occurrence) {
1951
+ matchStart = newPos;
1952
+ matchBlock = child;
1953
+ }
1954
+ }
1955
+ newPos += child.nodeSize;
1956
+ }
1957
+ if (matchBlock === null || (requireUnique && (occurrence !== 1 || matchCount !== 1))) {
1958
+ return null
1959
+ }
1960
+ return place(matchStart, matchBlock)
1961
+ };
1962
+
1963
+ /**
1964
+ * @param {import('prosemirror-model').Node} child
1965
+ * @return {boolean}
1966
+ */
1967
+ const sameTypeAndAttrs = (child) =>
1968
+ child.type === targetChild.type && attrsEqual(child.attrs, targetChild.attrs);
1969
+ const oldText = targetChild.textContent;
1970
+
1971
+ const byAll = findByPredicate((child) => sameTypeAndAttrs(child) && child.textContent === oldText);
1972
+ if (byAll !== null) {
1973
+ return byAll
1974
+ }
1975
+
1976
+ // Text must be matched before attrs: after a remote attr-only edit, the
1977
+ // attrs pass would steer the cursor into a sibling that kept the old attrs.
1978
+ const byText = findByPredicate(
1979
+ (child) => child.type === targetChild.type && child.textContent === oldText
1980
+ );
1981
+ if (byText !== null) {
1982
+ return byText
1983
+ }
1984
+
1985
+ // In-flight local typing diverges the text between both docs. Distinctive
1986
+ // attrs still identify the block; default-only attrs match every sibling.
1987
+ if (hasDistinctiveAttrs(targetChild)) {
1988
+ const byAttrs = findByPredicate(sameTypeAndAttrs, true);
1989
+ if (byAttrs !== null) {
1990
+ return byAttrs
1991
+ }
1992
+ }
1993
+
1994
+ // Trailing in-flight keystrokes leave a prefix relation between old and new
1995
+ // text. Empty text is a prefix of everything and must never match.
1996
+ return findByPredicate(
1997
+ (child) => sameTypeAndAttrs(child) &&
1998
+ oldText !== '' && child.textContent !== '' &&
1999
+ (oldText.startsWith(child.textContent) || child.textContent.startsWith(oldText)),
2000
+ true
2001
+ )
2002
+ };
2003
+
2004
+ /**
2005
+ * Returns true when a transaction changes block structure rather than only
2006
+ * editing inline content inside existing blocks.
2007
+ *
2008
+ * @param {import('prosemirror-state').Transaction} tr
2009
+ * @param {import('prosemirror-model').Node} oldDoc
2010
+ * @return {boolean}
2011
+ */
2012
+ const isStructuralTransaction = (tr, oldDoc) => {
2013
+ if (!tr.docChanged) {
2014
+ return false
2015
+ }
2016
+ if (tr.doc.childCount !== oldDoc.childCount) {
2017
+ return true
2018
+ }
2019
+ for (const step of tr.steps) {
2020
+ if (step instanceof prosemirrorTransform.ReplaceStep) {
2021
+ if (step.from === 0 && step.to === oldDoc.content.size) {
2022
+ return true
2023
+ }
2024
+ if (step.slice.content.size > 0) {
2025
+ let hasBlock = false;
2026
+ step.slice.content.forEach((node) => {
2027
+ if (node.isBlock) {
2028
+ hasBlock = true;
2029
+ }
2030
+ });
2031
+ if (hasBlock) {
2032
+ return true
2033
+ }
2034
+ } else if (step.to > step.from) {
2035
+ const $from = oldDoc.resolve(step.from);
2036
+ const $to = oldDoc.resolve(step.to);
2037
+ if ($from.depth === 0 && $to.depth === 0 && $from.index() !== $to.index()) {
2038
+ return true
2039
+ }
2040
+ }
2041
+ }
2042
+ }
2043
+ return false
2044
+ };
2045
+
2046
+ /**
2047
+ * Detect stale relative positions after structural changes that resolve to the
2048
+ * wrong text block or to the start of the correct block.
2049
+ *
2050
+ * @param {import('prosemirror-model').Node} oldDoc
2051
+ * @param {import('prosemirror-model').Node} newDoc
2052
+ * @param {number} oldAbs
2053
+ * @param {number|null} resolvedAbs
2054
+ * @return {boolean}
2055
+ */
2056
+ const isMisresolvedAfterStructuralChange = (oldDoc, newDoc, oldAbs, resolvedAbs) => {
2057
+ if (resolvedAbs === null) {
2058
+ return false
2059
+ }
2060
+ const $old = oldDoc.resolve(oldAbs);
2061
+ const $new = newDoc.resolve(resolvedAbs);
2062
+ if (!$old.parent.isTextblock) {
2063
+ return false
2064
+ }
2065
+ // A textblock cursor cannot legitimately resolve into a non-textblock;
2066
+ // a structural reorder replaced the block via delete + insert.
2067
+ if (!$new.parent.isTextblock) {
2068
+ return true
2069
+ }
2070
+ if ($old.parent.textContent !== $new.parent.textContent) {
2071
+ return true
2072
+ }
2073
+ if ($old.parentOffset !== 0 && $new.parentOffset === 0) {
2074
+ return true
2075
+ }
2076
+ const bothAtStart = $old.parentOffset === 0 && $new.parentOffset === 0;
2077
+ // A changed offset, type or attrs hints at a same-text sibling. When all
2078
+ // of them agree there is no signal left and the Yjs resolution must win.
2079
+ const suspicious = $old.parentOffset !== $new.parentOffset ||
2080
+ $old.parent.type !== $new.parent.type ||
2081
+ !attrsEqual($old.parent.attrs, $new.parent.attrs);
2082
+ if (bothAtStart || suspicious) {
2083
+ const expected = findAbsolutePositionAfterStructuralChange(oldDoc, newDoc, oldAbs);
2084
+ return expected !== null && expected !== resolvedAbs
2085
+ }
2086
+ return false
1750
2087
  };
1751
2088
 
1752
2089
  /**
@@ -2165,11 +2502,21 @@ const yCursorPlugin = (
2165
2502
  selectionBuilder
2166
2503
  )
2167
2504
  },
2168
- apply (tr, prevState, _oldState, newState) {
2505
+ apply (tr, prevState, oldState, newState) {
2169
2506
  const ystate = ySyncPluginKey.getState(newState);
2170
2507
  const yCursorState = tr.getMeta(yCursorPluginKey);
2508
+ const isRemoteChange = ystate && ystate.isChangeOrigin;
2171
2509
  if (
2172
- (ystate && ystate.isChangeOrigin) ||
2510
+ tr.docChanged &&
2511
+ !isRemoteChange &&
2512
+ isStructuralTransaction(tr, oldState.doc)
2513
+ ) {
2514
+ // The ProseMirror document leads the Yjs mapping during local moves.
2515
+ // Hide stale awareness until the collaborator publishes its new cursor.
2516
+ return prosemirrorView.DecorationSet.empty
2517
+ }
2518
+ if (
2519
+ isRemoteChange ||
2173
2520
  (yCursorState && yCursorState.awarenessUpdated)
2174
2521
  ) {
2175
2522
  return createDecorations(
@@ -2180,7 +2527,10 @@ const yCursorPlugin = (
2180
2527
  selectionBuilder
2181
2528
  )
2182
2529
  }
2183
- return prevState.map(tr.mapping, tr.doc)
2530
+ if (tr.docChanged) {
2531
+ return prevState.map(tr.mapping, tr.doc)
2532
+ }
2533
+ return prevState
2184
2534
  }
2185
2535
  },
2186
2536
  props: {
@@ -2195,15 +2545,19 @@ const yCursorPlugin = (
2195
2545
  setMeta(view, yCursorPluginKey, { awarenessUpdated: true });
2196
2546
  }
2197
2547
  };
2198
- const updateCursorInfo = () => {
2199
- const ystate = ySyncPluginKey.getState(view.state);
2548
+ /**
2549
+ * @param {import('prosemirror-view').EditorView} editorView
2550
+ * @param {{ force?: boolean }} [opts]
2551
+ */
2552
+ const updateCursorInfo = (editorView, opts = {}) => {
2553
+ const ystate = ySyncPluginKey.getState(editorView.state);
2200
2554
  // `ystate` is undefined during sync-plugin init and Y.Doc transitions;
2201
2555
  // reading from it here would throw and break cursor tracking for the view.
2202
2556
  if (!ystate || !ystate.binding) return
2203
2557
  // @note We make implicit checks when checking for the cursor property
2204
2558
  const current = awareness.getLocalState() || {};
2205
- if (view.hasFocus()) {
2206
- const selection = getSelection(view.state);
2559
+ if (editorView.hasFocus()) {
2560
+ const selection = getSelection(editorView.state);
2207
2561
  /**
2208
2562
  * @type {Y.RelativePosition}
2209
2563
  */
@@ -2221,6 +2575,7 @@ const yCursorPlugin = (
2221
2575
  ystate.binding.mapping
2222
2576
  );
2223
2577
  if (
2578
+ opts.force ||
2224
2579
  current.cursor == null ||
2225
2580
  !Y__namespace.compareRelativePositions(
2226
2581
  Y__namespace.createRelativePositionFromJSON(current.cursor.anchor),
@@ -2249,14 +2604,23 @@ const yCursorPlugin = (
2249
2604
  awareness.setLocalStateField(cursorStateField, null);
2250
2605
  }
2251
2606
  };
2607
+ const onFocusChange = () => updateCursorInfo(view);
2252
2608
  awareness.on('change', awarenessListener);
2253
- view.dom.addEventListener('focusin', updateCursorInfo);
2254
- view.dom.addEventListener('focusout', updateCursorInfo);
2609
+ view.dom.addEventListener('focusin', onFocusChange);
2610
+ view.dom.addEventListener('focusout', onFocusChange);
2255
2611
  return {
2256
- update: updateCursorInfo,
2612
+ update: (editorView, prevState) => {
2613
+ const ystate = ySyncPluginKey.getState(editorView.state);
2614
+ const forceAwarenessUpdate = !!(
2615
+ ystate?.isChangeOrigin &&
2616
+ prevState?.doc &&
2617
+ !prevState.doc.eq(editorView.state.doc)
2618
+ );
2619
+ updateCursorInfo(editorView, { force: forceAwarenessUpdate });
2620
+ },
2257
2621
  destroy: () => {
2258
- view.dom.removeEventListener('focusin', updateCursorInfo);
2259
- view.dom.removeEventListener('focusout', updateCursorInfo);
2622
+ view.dom.removeEventListener('focusin', onFocusChange);
2623
+ view.dom.removeEventListener('focusout', onFocusChange);
2260
2624
  awareness.off('change', awarenessListener);
2261
2625
  awareness.setLocalStateField(cursorStateField, null);
2262
2626
  }
@@ -2386,8 +2750,12 @@ exports.defaultCursorBuilder = defaultCursorBuilder;
2386
2750
  exports.defaultDeleteFilter = defaultDeleteFilter;
2387
2751
  exports.defaultProtectedNodes = defaultProtectedNodes;
2388
2752
  exports.defaultSelectionBuilder = defaultSelectionBuilder;
2753
+ exports.findAbsolutePositionAfterStructuralChange = findAbsolutePositionAfterStructuralChange;
2389
2754
  exports.getRelativeSelection = getRelativeSelection;
2390
2755
  exports.initProseMirrorDoc = initProseMirrorDoc;
2756
+ exports.isMisresolvedAfterStructuralChange = isMisresolvedAfterStructuralChange;
2757
+ exports.isMisresolvedTextPosition = isMisresolvedTextPosition;
2758
+ exports.isStructuralTransaction = isStructuralTransaction;
2391
2759
  exports.isVisible = isVisible;
2392
2760
  exports.prosemirrorJSONToYDoc = prosemirrorJSONToYDoc;
2393
2761
  exports.prosemirrorJSONToYXmlFragment = prosemirrorJSONToYXmlFragment;