🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@tiptap/react

Package Overview
Dependencies
Maintainers
6
Versions
407
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tiptap/react - npm Package Compare versions

Comparing version
3.29.1
to
3.29.2
+76
src/captureDOMSelection.spec.ts
import { afterEach, describe, expect, it } from 'vitest'
import { captureDOMSelection } from './captureDOMSelection.js'
const createContent = () => {
const oldParent = document.createElement('div')
const newParent = document.createElement('div')
const content = document.createElement('div')
const text = document.createTextNode('Alpha')
content.appendChild(text)
oldParent.appendChild(content)
document.body.append(oldParent, newParent)
return { content, newParent, text }
}
const selectInside = (node: Node, offset: number) => {
const selection = document.getSelection()!
selection.removeAllRanges()
const range = document.createRange()
range.setStart(node, offset)
range.collapse(true)
selection.addRange(range)
}
afterEach(() => {
document.getSelection()?.removeAllRanges()
document.body.innerHTML = ''
})
describe('captureDOMSelection', () => {
it('restores a selection that lives inside the element', () => {
const { content, newParent, text } = createContent()
selectInside(text, 3)
const restore = captureDOMSelection(content)
newParent.appendChild(content)
// jsdom keeps the selection on a move, browsers drop it. Fake that here.
document.getSelection()?.removeAllRanges()
restore?.()
const selection = document.getSelection()!
expect(restore).toBeTypeOf('function')
expect(selection.anchorNode).toBe(text)
expect(selection.anchorOffset).toBe(3)
})
it('returns null when there is no selection', () => {
const { content } = createContent()
document.getSelection()?.removeAllRanges()
expect(captureDOMSelection(content)).toBeNull()
})
it('returns null when the selection is outside of the element', () => {
const { content } = createContent()
const outside = document.createElement('div')
outside.appendChild(document.createTextNode('Beta'))
document.body.appendChild(outside)
selectInside(outside.firstChild!, 2)
expect(captureDOMSelection(content)).toBeNull()
})
})
type SelectionRoot = Node & { getSelection?: () => Selection | null }
/**
* Saves the DOM selection inside `element`, returns a function to put it back.
* Browsers drop the selection when an element moves, so restore it after the move.
*/
export function captureDOMSelection(element: HTMLElement): (() => void) | null {
const root = element.getRootNode() as SelectionRoot
const selection =
typeof root.getSelection === 'function'
? root.getSelection()
: element.ownerDocument?.defaultView?.getSelection()
if (!selection || selection.rangeCount === 0) {
return null
}
const { anchorNode, anchorOffset, focusNode, focusOffset } = selection
if (!anchorNode || !focusNode) {
return null
}
if (!element.contains(anchorNode) || !element.contains(focusNode)) {
return null
}
return () => {
try {
selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)
} catch {
// The saved nodes can be gone by now.
}
}
}
+27
-0

@@ -902,2 +902,27 @@ "use strict";

var import_react9 = require("react");
// src/captureDOMSelection.ts
function captureDOMSelection(element) {
var _a, _b;
const root = element.getRootNode();
const selection = typeof root.getSelection === "function" ? root.getSelection() : (_b = (_a = element.ownerDocument) == null ? void 0 : _a.defaultView) == null ? void 0 : _b.getSelection();
if (!selection || selection.rangeCount === 0) {
return null;
}
const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
if (!anchorNode || !focusNode) {
return null;
}
if (!element.contains(anchorNode) || !element.contains(focusNode)) {
return null;
}
return () => {
try {
selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
} catch {
}
};
}
// src/ReactNodeViewRenderer.tsx
var import_jsx_runtime7 = require("react/jsx-runtime");

@@ -1000,3 +1025,5 @@ var ReactNodeView = class extends import_core3.NodeView {

}
const restoreSelection = captureDOMSelection(this.contentDOMElement);
element.appendChild(this.contentDOMElement);
restoreSelection == null ? void 0 : restoreSelection();
}

@@ -1003,0 +1030,0 @@ };

@@ -840,2 +840,27 @@ "use client";

import { createElement as createElement2, createRef, memo } from "react";
// src/captureDOMSelection.ts
function captureDOMSelection(element) {
var _a, _b;
const root = element.getRootNode();
const selection = typeof root.getSelection === "function" ? root.getSelection() : (_b = (_a = element.ownerDocument) == null ? void 0 : _a.defaultView) == null ? void 0 : _b.getSelection();
if (!selection || selection.rangeCount === 0) {
return null;
}
const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
if (!anchorNode || !focusNode) {
return null;
}
if (!element.contains(anchorNode) || !element.contains(focusNode)) {
return null;
}
return () => {
try {
selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
} catch {
}
};
}
// src/ReactNodeViewRenderer.tsx
import { jsx as jsx7 } from "react/jsx-runtime";

@@ -938,3 +963,5 @@ var ReactNodeView = class extends NodeView {

}
const restoreSelection = captureDOMSelection(this.contentDOMElement);
element.appendChild(this.contentDOMElement);
restoreSelection == null ? void 0 : restoreSelection();
}

@@ -941,0 +968,0 @@ };

+7
-7
{
"name": "@tiptap/react",
"version": "3.29.1",
"version": "3.29.2",
"description": "React components for tiptap",

@@ -60,4 +60,4 @@ "keywords": [

"react-dom": "^19.0.0",
"@tiptap/core": "^3.29.1",
"@tiptap/pm": "^3.29.1"
"@tiptap/core": "^3.29.2",
"@tiptap/pm": "^3.29.2"
},

@@ -69,8 +69,8 @@ "peerDependencies": {

"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@tiptap/core": "3.29.1",
"@tiptap/pm": "3.29.1"
"@tiptap/core": "3.29.2",
"@tiptap/pm": "3.29.2"
},
"optionalDependencies": {
"@tiptap/extension-bubble-menu": "^3.29.1",
"@tiptap/extension-floating-menu": "^3.29.1"
"@tiptap/extension-bubble-menu": "^3.29.2",
"@tiptap/extension-floating-menu": "^3.29.2"
},

@@ -77,0 +77,0 @@ "scripts": {

@@ -14,2 +14,3 @@ import type {

import { captureDOMSelection } from './captureDOMSelection.js'
import type { EditorWithContentComponent } from './Editor.js'

@@ -197,3 +198,9 @@ import { ReactRenderer } from './ReactRenderer.js'

}
// The caret can already be inside here. Moving would lose it.
const restoreSelection = captureDOMSelection(this.contentDOMElement)
element.appendChild(this.contentDOMElement)
restoreSelection?.()
}

@@ -200,0 +207,0 @@ }

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display