diff --git a/demos/src/Examples/Issue4327/React/foo.ts b/demos/src/Examples/Issue4327/React/foo.ts
new file mode 100644
index 000000000..47708c279
--- /dev/null
+++ b/demos/src/Examples/Issue4327/React/foo.ts
@@ -0,0 +1,26 @@
+import { mergeAttributes, Node } from '@tiptap/core'
+
+export default Node.create({
+ name: 'foo',
+
+ group: 'inline',
+
+ inline: true,
+
+ parseHTML() {
+ return [
+ {
+ tag: 'span',
+ getAttrs: node => (node as HTMLElement).hasAttribute('data-foo') && null,
+ },
+ ]
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['span', mergeAttributes({ 'data-foo': '', HTMLAttributes }), 'foo']
+ },
+
+ renderText() {
+ return 'foo'
+ },
+})
diff --git a/demos/src/Examples/Issue4327/React/index.html b/demos/src/Examples/Issue4327/React/index.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/demos/src/Examples/Issue4327/React/index.tsx b/demos/src/Examples/Issue4327/React/index.tsx
new file mode 100644
index 000000000..beff64ecc
--- /dev/null
+++ b/demos/src/Examples/Issue4327/React/index.tsx
@@ -0,0 +1,27 @@
+import './styles.scss'
+
+import { EditorContent, FloatingMenu, useEditor } from '@tiptap/react'
+import StarterKit from '@tiptap/starter-kit'
+import React from 'react'
+
+import Foo from './foo.js'
+
+export default () => {
+ const editor = useEditor({
+ extensions: [
+ StarterKit, Foo,
+ ],
+ content: `
+
foo
+ `,
+ })
+
+ return (
+ <>
+ {editor &&
+ Hello
+ }
+
+ >
+ )
+}
diff --git a/demos/src/Examples/Issue4327/React/styles.scss b/demos/src/Examples/Issue4327/React/styles.scss
new file mode 100644
index 000000000..1c9f0f6cd
--- /dev/null
+++ b/demos/src/Examples/Issue4327/React/styles.scss
@@ -0,0 +1,10 @@
+.tiptap {
+ > * + * {
+ margin-top: 0.75em;
+ }
+
+ ul,
+ ol {
+ padding: 0 1rem;
+ }
+}
diff --git a/demos/src/Examples/Issue4327/foo.ts b/demos/src/Examples/Issue4327/foo.ts
new file mode 100644
index 000000000..47708c279
--- /dev/null
+++ b/demos/src/Examples/Issue4327/foo.ts
@@ -0,0 +1,26 @@
+import { mergeAttributes, Node } from '@tiptap/core'
+
+export default Node.create({
+ name: 'foo',
+
+ group: 'inline',
+
+ inline: true,
+
+ parseHTML() {
+ return [
+ {
+ tag: 'span',
+ getAttrs: node => (node as HTMLElement).hasAttribute('data-foo') && null,
+ },
+ ]
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['span', mergeAttributes({ 'data-foo': '', HTMLAttributes }), 'foo']
+ },
+
+ renderText() {
+ return 'foo'
+ },
+})
diff --git a/packages/extension-floating-menu/src/floating-menu-plugin.ts b/packages/extension-floating-menu/src/floating-menu-plugin.ts
index ed1079878..94025e1fe 100644
--- a/packages/extension-floating-menu/src/floating-menu-plugin.ts
+++ b/packages/extension-floating-menu/src/floating-menu-plugin.ts
@@ -1,4 +1,7 @@
-import { Editor, posToDOMRect } from '@tiptap/core'
+import {
+ Editor, getText, getTextSerializersFromSchema, posToDOMRect,
+} from '@tiptap/core'
+import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state'
import { EditorView } from '@tiptap/pm/view'
import tippy, { Instance, Props } from 'tippy.js'
@@ -35,11 +38,15 @@ export class FloatingMenuView {
public tippyOptions?: Partial
+ private getTextContent(node:ProseMirrorNode) {
+ return getText(node, { textSerializers: getTextSerializersFromSchema(this.editor.schema) })
+ }
+
public shouldShow: Exclude = ({ view, state }) => {
const { selection } = state
const { $anchor, empty } = selection
const isRootDepth = $anchor.depth === 1
- const isEmptyTextBlock = $anchor.parent.isTextblock && !$anchor.parent.type.spec.code && !$anchor.parent.textContent
+ const isEmptyTextBlock = $anchor.parent.isTextblock && !$anchor.parent.type.spec.code && !this.getTextContent($anchor.parent)
if (
!view.hasFocus()
diff --git a/tests/cypress/integration/Issue4327/index.spec.ts b/tests/cypress/integration/Issue4327/index.spec.ts
new file mode 100644
index 000000000..66494c0fc
--- /dev/null
+++ b/tests/cypress/integration/Issue4327/index.spec.ts
@@ -0,0 +1,18 @@
+///
+
+import { Editor } from '@tiptap/core'
+
+interface EditorElement extends HTMLElement {
+ editor: Editor
+}
+context('/cypress/integration/Issue4327/React/', () => {
+ before(() => {
+ cy.visit('/src/Examples/Issue4327/React/')
+ })
+ it('should not show menu when node has renderText returning text with length > 0', () => {
+ cy.get('.tiptap').then(([editorElement]) => {
+ (editorElement as EditorElement).editor.commands.focus()
+ }).get('.ProseMirror-focused').get('#app')
+ .should('not.have.descendants', '[data-tippy-root]')
+ })
+})