mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-12-03 19:19:01 +08:00
feat: make the example much better
This commit is contained in:
parent
bd6c257bf6
commit
01d6dc89a8
@ -1,31 +1,31 @@
|
||||
import { Editor, FloatingMenu, useEditorState } from '@tiptap/react'
|
||||
import React, { useRef } from 'react'
|
||||
|
||||
// import { useFocusMenubar } from './useFocusMenubar.js'
|
||||
import { useFocusMenubar } from './useFocusMenubar.js'
|
||||
|
||||
export function InsertMenu({ editor }: { editor: Editor }) {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const editorState = useEditorState({
|
||||
const { activeNodeType } = useEditorState({
|
||||
editor,
|
||||
selector: ctx => {
|
||||
const activeNode = ctx.editor.state.selection.$from.node(1)
|
||||
|
||||
return {
|
||||
activeNodeType: activeNode?.type.name ?? 'paragraph',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// // Handle arrow navigation within a menu bar container, and allow to escape to the editor
|
||||
// useFocusMenubar({
|
||||
// editor,
|
||||
// ref: containerRef,
|
||||
// onEscape: () => {
|
||||
// // On escape, focus the editor & dismiss the menu by moving the selection to the end of the selection
|
||||
// editor.chain().focus().command(({ tr }) => {
|
||||
// tr.setSelection(Selection.near(tr.selection.$to))
|
||||
// return true
|
||||
// }).run()
|
||||
// },
|
||||
// })
|
||||
// Handle arrow navigation within a menu bar container, and allow to escape to the editor
|
||||
const { focusButton } = useFocusMenubar({
|
||||
editor,
|
||||
ref: containerRef,
|
||||
onEscape: () => {
|
||||
// On escape, focus the editor
|
||||
editor.chain().focus().run()
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<FloatingMenu
|
||||
@ -33,16 +33,49 @@ export function InsertMenu({ editor }: { editor: Editor }) {
|
||||
shouldShow={null}
|
||||
aria-orientation="horizontal"
|
||||
role="menubar"
|
||||
aria-label="Insert Element menu"
|
||||
className='floating-menu'
|
||||
// Types are broken here, since we import jsx from vue-2
|
||||
ref={containerRef as any}
|
||||
// This is a raw HTML element, so we can't use onFocus
|
||||
onfocus={() => {
|
||||
// Focus the first button when the menu bar is focused
|
||||
containerRef.current?.querySelector('button')?.focus()
|
||||
onFocus={e => {
|
||||
// The ref we have is to the container, not the menu itself
|
||||
if (containerRef.current === e.target?.parentNode) {
|
||||
// Focus the first button when the menu bar is focused
|
||||
focusButton(containerRef.current?.querySelector('button'))
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
TST
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
className={activeNodeType === 'bulletList' ? 'is-active' : ''}
|
||||
aria-label="Bullet List"
|
||||
tabIndex={-1}
|
||||
>
|
||||
Bullet list
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
className={activeNodeType === 'orderedList' ? 'is-active' : ''}
|
||||
aria-label="Ordered List"
|
||||
tabIndex={-1}
|
||||
>
|
||||
Ordered List
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
||||
aria-label="Horizontal rule"
|
||||
tabIndex={-1}
|
||||
>
|
||||
Horizontal rule
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().setHardBreak().run()}
|
||||
aria-label="Hard break"
|
||||
tabIndex={-1}
|
||||
>
|
||||
Hard break
|
||||
</button>
|
||||
</FloatingMenu>
|
||||
)
|
||||
}
|
||||
|
@ -1,53 +1,39 @@
|
||||
import { Editor } from "@tiptap/core";
|
||||
import React, { useRef } from "react";
|
||||
import { Editor } from '@tiptap/core'
|
||||
import React, { useRef } from 'react'
|
||||
|
||||
import { NodeTypeDropdown } from "./NodeTypeDropdown.js";
|
||||
import { useFocusMenubar } from "./useFocusMenubar.js";
|
||||
import { NodeTypeDropdown } from './NodeTypeDropdown.js'
|
||||
import { useFocusMenubar } from './useFocusMenubar.js'
|
||||
|
||||
/**
|
||||
* An accessible menu bar for the editor
|
||||
*/
|
||||
export const MenuBar = ({ editor }: { editor: Editor }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useFocusMenubar({
|
||||
ref: containerRef,
|
||||
editor,
|
||||
onKeydown: (event) => {
|
||||
onKeydown: event => {
|
||||
// Handle focus on alt + f10
|
||||
if (event.altKey && event.key === "F10") {
|
||||
event.preventDefault();
|
||||
containerRef.current?.querySelector("button")?.focus();
|
||||
return;
|
||||
if (event.altKey && event.key === 'F10') {
|
||||
event.preventDefault()
|
||||
containerRef.current?.querySelector('button')?.focus()
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
if (!editor) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="control-group" role="toolbar" aria-orientation="horizontal" ref={containerRef}>
|
||||
<div className="button-group">
|
||||
<NodeTypeDropdown editor={editor} />
|
||||
<button
|
||||
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
||||
tabIndex={-1}
|
||||
aria-label="Horizontal rule"
|
||||
>
|
||||
Horizontal rule
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().setHardBreak().run()}
|
||||
tabIndex={-1}
|
||||
aria-label="Hard break"
|
||||
>
|
||||
Hard break
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
disabled={!editor.can().chain().focus().undo().run()}
|
||||
disabled={!editor.can().chain().focus().undo()
|
||||
.run()}
|
||||
tabIndex={-1}
|
||||
aria-label="Undo"
|
||||
>
|
||||
@ -55,7 +41,8 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
disabled={!editor.can().chain().focus().redo().run()}
|
||||
disabled={!editor.can().chain().focus().redo()
|
||||
.run()}
|
||||
tabIndex={-1}
|
||||
aria-label="Redo"
|
||||
>
|
||||
@ -63,5 +50,5 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ export function NodeTypeDropdown({ editor }: { editor: Editor }) {
|
||||
const activeNode = ctx.editor.state.selection.$from.node(1)
|
||||
|
||||
return {
|
||||
activeNodeType: activeNode.type.name.slice(0, 1).toUpperCase() + activeNode.type.name.slice(1) || 'Paragraph',
|
||||
activeNodeType: activeNode?.type.name ?? 'paragraph',
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -53,7 +53,7 @@ export function NodeTypeDropdown({ editor }: { editor: Editor }) {
|
||||
}`}
|
||||
tabIndex={-1}
|
||||
>
|
||||
Node Type: {activeNodeType}
|
||||
Node Type: {activeNodeType.slice(0, 1).toUpperCase() + activeNodeType.slice(1)}
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div
|
||||
|
@ -30,7 +30,7 @@ export function TextMenu({ editor }: { editor: Editor }) {
|
||||
})
|
||||
|
||||
// Handle arrow navigation within a menu bar container, and allow to escape to the editor
|
||||
useFocusMenubar({
|
||||
const { focusButton } = useFocusMenubar({
|
||||
editor,
|
||||
ref: containerRef,
|
||||
onEscape: () => {
|
||||
@ -46,14 +46,18 @@ export function TextMenu({ editor }: { editor: Editor }) {
|
||||
<BubbleMenu
|
||||
editor={editor}
|
||||
shouldShow={null}
|
||||
aria-label="Text formatting menu"
|
||||
aria-orientation="horizontal"
|
||||
role="menubar"
|
||||
className='bubble-menu'
|
||||
// Types are broken here, since we import jsx from vue-2
|
||||
ref={containerRef as any}
|
||||
// This is a raw HTML element, so we can't use onFocus
|
||||
onfocus={() => {
|
||||
// Focus the first button when the menu bar is focused
|
||||
containerRef.current?.querySelector('button')?.focus()
|
||||
onFocus={e => {
|
||||
// The ref we have is to the container, not the menu itself
|
||||
if (containerRef.current === e.target?.parentNode) {
|
||||
// Focus the first button when the menu bar is focused
|
||||
focusButton(containerRef.current?.querySelector('button'))
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
|
@ -1,47 +1,41 @@
|
||||
import "./styles.scss";
|
||||
import './styles.scss'
|
||||
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import React from "react";
|
||||
// import Placeholder from '@tiptap/extension-placeholder'
|
||||
import { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import React from 'react'
|
||||
|
||||
import { TextMenu } from "./TextMenu";
|
||||
import { MenuBar } from "./MenuBar.jsx";
|
||||
import { InsertMenu } from "./InsertMenu";
|
||||
import { InsertMenu } from './InsertMenu.jsx'
|
||||
import { MenuBar } from './MenuBar.jsx'
|
||||
import { TextMenu } from './TextMenu.jsx'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Placeholder.configure({
|
||||
placeholder: "Use Alt + F10 to focus the menu bar",
|
||||
}),
|
||||
],
|
||||
immediatelyRender: false,
|
||||
content: `
|
||||
<h2>Accessibility Demo</h2>
|
||||
<p>Tab into the demo & navigate around with only your keyboard</p>
|
||||
<p>Use <code>Alt + F10</code> to focus the menu bar</p>
|
||||
`,
|
||||
editorProps: {
|
||||
attributes: (state): Record<string, string> => {
|
||||
return {
|
||||
// Make sure the editor is announced as a rich text editor
|
||||
"aria-label": "Rich Text Editor",
|
||||
// editor accepts multiline input
|
||||
"aria-multiline": "true",
|
||||
// dynamically set the aria-readonly attribute
|
||||
"aria-readonly": editor?.isEditable ? "false" : "true",
|
||||
};
|
||||
attributes: {
|
||||
// Make sure the editor is announced as a rich text editor
|
||||
'aria-label': 'Rich Text Editor',
|
||||
// editor accepts multiline input
|
||||
'aria-multiline': 'true',
|
||||
'aria-readonly': 'false',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!editor) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div role="application">
|
||||
<div role="application" className="editor-application">
|
||||
<MenuBar editor={editor} />
|
||||
<EditorContent editor={editor} />
|
||||
<TextMenu editor={editor} />
|
||||
<InsertMenu editor={editor} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
@ -14,11 +14,70 @@
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
|
||||
/* Bubble menu */
|
||||
.bubble-menu {
|
||||
background-color: var(--white);
|
||||
border: 1px solid var(--gray-1);
|
||||
border-radius: 0.7rem;
|
||||
box-shadow: var(--shadow);
|
||||
display: flex;
|
||||
padding: 0.2rem;
|
||||
|
||||
button {
|
||||
background-color: unset;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gray-3);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: var(--purple);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--purple-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Floating menu */
|
||||
.floating-menu {
|
||||
display: flex;
|
||||
background-color: var(--gray-3);
|
||||
padding: 0.1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
button {
|
||||
background-color: unset;
|
||||
padding: 0.275rem 0.425rem;
|
||||
border-radius: 0.3rem;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gray-3);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: var(--white);
|
||||
color: var(--purple);
|
||||
|
||||
&:hover {
|
||||
color: var(--purple-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Basic editor styles */
|
||||
.tiptap {
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&:focus {
|
||||
outline: 1px solid var(--purple);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
/* List styles */
|
||||
ul,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Editor } from '@tiptap/core'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import React, { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
/**
|
||||
* Handle arrow navigation within a menu bar container, and allow to escape to the editor
|
||||
@ -46,6 +46,62 @@ export function useFocusMenubar({
|
||||
onKeydown,
|
||||
}
|
||||
|
||||
const focusNextButton = useCallback((el = document.activeElement) => {
|
||||
if (!containerRef.current) {
|
||||
return null
|
||||
}
|
||||
|
||||
const elements = Array.from(containerRef.current.querySelectorAll('button'))
|
||||
const index = elements.findIndex(element => element === el)
|
||||
|
||||
// Find the next enabled button
|
||||
for (let i = index + 1; i <= elements.length; i += 1) {
|
||||
if (!elements[i % elements.length].disabled) {
|
||||
elements[i % elements.length].focus()
|
||||
return elements[i % elements.length]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}, [containerRef])
|
||||
|
||||
const focusPreviousButton = useCallback((el = document.activeElement) => {
|
||||
if (!containerRef.current) {
|
||||
return null
|
||||
}
|
||||
|
||||
const elements = Array.from(containerRef.current.querySelectorAll('button'))
|
||||
const index = elements.findIndex(element => element === el)
|
||||
|
||||
// Find the previous enabled button
|
||||
for (let i = index - 1; i >= -1; i -= 1) {
|
||||
// If we reach the beginning, start from the end
|
||||
if (i < 0) {
|
||||
i = elements.length - 1
|
||||
}
|
||||
if (!elements[i].disabled) {
|
||||
elements[i].focus()
|
||||
return elements[i]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}, [containerRef])
|
||||
|
||||
const focusButton = useCallback((el: HTMLButtonElement | null | undefined, direction: 'forwards' | 'backwards' = 'forwards') => {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
if (!el.disabled) {
|
||||
el.focus()
|
||||
return
|
||||
}
|
||||
if (direction === 'forwards') {
|
||||
focusNextButton(el)
|
||||
}
|
||||
if (direction === 'backwards') {
|
||||
focusPreviousButton(el)
|
||||
}
|
||||
}, [focusNextButton, focusPreviousButton])
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (!containerRef.current) {
|
||||
@ -60,44 +116,25 @@ export function useFocusMenubar({
|
||||
const elements = Array.from(containerRef.current.querySelectorAll('button'))
|
||||
const isFocusedOnButton = elements.includes(event.target as HTMLButtonElement)
|
||||
|
||||
// Allow to escape to the editor
|
||||
if (isFocusedOnButton || event.target === containerRef.current) {
|
||||
// Allow to escape to the editor
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
callbacks.current.onEscape(editor)
|
||||
return
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (isFocusedOnButton) {
|
||||
// Handle arrow navigation within the menu bar
|
||||
if (event.key === 'ArrowRight') {
|
||||
const index = elements.indexOf(event.target as HTMLButtonElement)
|
||||
|
||||
// Find the next enabled button
|
||||
for (let i = index + 1; i <= elements.length; i += 1) {
|
||||
if (!elements[i % elements.length].disabled) {
|
||||
event.preventDefault()
|
||||
elements[i % elements.length].focus()
|
||||
return
|
||||
}
|
||||
if (focusNextButton(event.target as HTMLButtonElement)) {
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
const index = elements.indexOf(event.target as HTMLButtonElement)
|
||||
|
||||
// Find the previous enabled button
|
||||
for (let i = index - 1; i >= -1; i -= 1) {
|
||||
// If we reach the beginning, start from the end
|
||||
if (i < 0) {
|
||||
i = elements.length - 1
|
||||
}
|
||||
if (!elements[i].disabled) {
|
||||
event.preventDefault()
|
||||
elements[i].focus()
|
||||
return
|
||||
}
|
||||
if (focusPreviousButton(event.target as HTMLButtonElement)) {
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,5 +145,7 @@ export function useFocusMenubar({
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [containerRef, editor])
|
||||
}, [containerRef, editor, focusNextButton, focusPreviousButton])
|
||||
|
||||
return { focusNextButton, focusPreviousButton, focusButton }
|
||||
}
|
||||
|
@ -84,7 +84,6 @@ export class FloatingMenuView {
|
||||
const isRootDepth = $anchor.depth === 1
|
||||
const isEmptyTextBlock = $anchor.parent.isTextblock && !$anchor.parent.type.spec.code && !$anchor.parent.textContent
|
||||
|
||||
console.log('should ran')
|
||||
if (
|
||||
!view.hasFocus()
|
||||
|| !empty
|
||||
@ -94,7 +93,6 @@ export class FloatingMenuView {
|
||||
) {
|
||||
return false
|
||||
}
|
||||
console.log(true)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -173,7 +171,6 @@ export class FloatingMenuView {
|
||||
...options,
|
||||
}
|
||||
|
||||
console.log('should show', shouldShow)
|
||||
if (shouldShow) {
|
||||
this.shouldShow = shouldShow
|
||||
}
|
||||
|
@ -11,74 +11,84 @@ export type BubbleMenuProps = Omit<
|
||||
'element' | 'editor'
|
||||
> & {
|
||||
editor: BubbleMenuPluginProps['editor'] | null;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
updateDelay?: number;
|
||||
resizeDelay?: number;
|
||||
options?: BubbleMenuPluginProps['options'];
|
||||
} & Partial<Omit<HTMLDivElement, 'children' | 'class'>>;
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(({
|
||||
pluginKey = 'bubbleMenu',
|
||||
editor,
|
||||
updateDelay,
|
||||
resizeDelay,
|
||||
shouldShow = null,
|
||||
options,
|
||||
children,
|
||||
...restProps
|
||||
}, ref) => {
|
||||
const menuEl = useRef(Object.assign(document.createElement('div'), restProps))
|
||||
|
||||
if (typeof ref === 'function') {
|
||||
ref(menuEl.current)
|
||||
} else if (ref) {
|
||||
ref.current = menuEl.current
|
||||
}
|
||||
|
||||
const { editor: currentEditor } = useCurrentEditor()
|
||||
|
||||
useEffect(() => {
|
||||
const bubbleMenuElement = menuEl.current
|
||||
|
||||
bubbleMenuElement.style.visibility = 'hidden'
|
||||
bubbleMenuElement.style.position = 'absolute'
|
||||
|
||||
if (editor?.isDestroyed || currentEditor?.isDestroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
const attachToEditor = editor || currentEditor
|
||||
|
||||
if (!attachToEditor) {
|
||||
console.warn(
|
||||
'BubbleMenu component is not rendered inside of an editor component or does not have editor prop.',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const plugin = BubbleMenuPlugin({
|
||||
export const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(
|
||||
(
|
||||
{
|
||||
pluginKey = 'bubbleMenu',
|
||||
editor,
|
||||
updateDelay,
|
||||
resizeDelay,
|
||||
editor: attachToEditor,
|
||||
element: bubbleMenuElement,
|
||||
pluginKey,
|
||||
shouldShow,
|
||||
shouldShow = null,
|
||||
options,
|
||||
})
|
||||
children,
|
||||
...restProps
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const menuEl = useRef(document.createElement('div'))
|
||||
|
||||
attachToEditor.registerPlugin(plugin)
|
||||
|
||||
return () => {
|
||||
attachToEditor.unregisterPlugin(pluginKey)
|
||||
window.requestAnimationFrame(() => {
|
||||
if (bubbleMenuElement.parentNode) {
|
||||
bubbleMenuElement.parentNode.removeChild(bubbleMenuElement)
|
||||
}
|
||||
})
|
||||
if (typeof ref === 'function') {
|
||||
ref(menuEl.current)
|
||||
} else if (ref) {
|
||||
ref.current = menuEl.current
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editor, currentEditor])
|
||||
|
||||
return createPortal(<>{children}</>, menuEl.current)
|
||||
})
|
||||
const { editor: currentEditor } = useCurrentEditor()
|
||||
|
||||
useEffect(() => {
|
||||
const bubbleMenuElement = menuEl.current
|
||||
|
||||
bubbleMenuElement.style.visibility = 'hidden'
|
||||
bubbleMenuElement.style.position = 'absolute'
|
||||
|
||||
if (editor?.isDestroyed || currentEditor?.isDestroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
const attachToEditor = editor || currentEditor
|
||||
|
||||
if (!attachToEditor) {
|
||||
console.warn(
|
||||
'BubbleMenu component is not rendered inside of an editor component or does not have editor prop.',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const plugin = BubbleMenuPlugin({
|
||||
updateDelay,
|
||||
resizeDelay,
|
||||
editor: attachToEditor,
|
||||
element: bubbleMenuElement,
|
||||
pluginKey,
|
||||
shouldShow,
|
||||
options,
|
||||
})
|
||||
|
||||
attachToEditor.registerPlugin(plugin)
|
||||
|
||||
return () => {
|
||||
attachToEditor.unregisterPlugin(pluginKey)
|
||||
window.requestAnimationFrame(() => {
|
||||
if (bubbleMenuElement.parentNode) {
|
||||
bubbleMenuElement.parentNode.removeChild(bubbleMenuElement)
|
||||
}
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editor, currentEditor])
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>,
|
||||
menuEl.current,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
@ -11,10 +11,8 @@ export type FloatingMenuProps = Omit<
|
||||
'element' | 'editor'
|
||||
> & {
|
||||
editor: FloatingMenuPluginProps['editor'] | null;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
options?: FloatingMenuPluginProps['options'];
|
||||
} & Partial<Omit<HTMLDivElement, 'children' | 'class'>>;
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(({
|
||||
pluginKey = 'floatingMenu',
|
||||
@ -24,7 +22,7 @@ export const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(
|
||||
children,
|
||||
...restProps
|
||||
}, ref) => {
|
||||
const menuEl = useRef(Object.assign(document.createElement('div'), restProps))
|
||||
const menuEl = useRef(document.createElement('div'))
|
||||
|
||||
if (typeof ref === 'function') {
|
||||
ref(menuEl.current)
|
||||
@ -74,5 +72,12 @@ export const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editor, currentEditor])
|
||||
|
||||
return createPortal(<>{children}</>, menuEl.current)
|
||||
return createPortal(
|
||||
<div
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>,
|
||||
menuEl.current,
|
||||
)
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user