@tiptap/react
Advanced tools
| import { render } from '@testing-library/react' | ||
| import React, { createRef } from 'react' | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' | ||
| import { BubbleMenu } from './BubbleMenu.js' | ||
| const { bubbleMenuPluginMock } = vi.hoisted(() => ({ | ||
| bubbleMenuPluginMock: vi.fn(() => ({ key: 'bubble-menu-plugin' })), | ||
| })) | ||
| vi.mock('@tiptap/extension-bubble-menu', () => ({ | ||
| BubbleMenuPlugin: bubbleMenuPluginMock, | ||
| })) | ||
| function createEditor() { | ||
| const tr = { | ||
| setMeta: vi.fn(() => tr), | ||
| } | ||
| return { | ||
| isDestroyed: false, | ||
| registerPlugin: vi.fn(), | ||
| unregisterPlugin: vi.fn(), | ||
| view: { | ||
| dispatch: vi.fn(), | ||
| }, | ||
| state: { | ||
| tr, | ||
| }, | ||
| } | ||
| } | ||
| describe('BubbleMenu', () => { | ||
| beforeEach(() => { | ||
| bubbleMenuPluginMock.mockClear() | ||
| }) | ||
| afterEach(() => { | ||
| document.body.innerHTML = '' | ||
| }) | ||
| it('applies html props to the actual menu element', () => { | ||
| const editor = createEditor() | ||
| const ref = createRef<HTMLDivElement>() | ||
| const handleClick = vi.fn() | ||
| let lastEvent: any | ||
| const handleClickCapture = vi.fn() | ||
| const handleDoubleClick = vi.fn() | ||
| const initialProps = { | ||
| editor: editor as never, | ||
| className: 'bubble-menu', | ||
| 'data-testid': 'menu-element', | ||
| 'aria-label': 'Bubble menu', | ||
| style: { zIndex: 9999, marginTop: 8, position: 'relative' }, | ||
| onClick: (event: unknown) => { | ||
| lastEvent = event | ||
| handleClick() | ||
| }, | ||
| onClickCapture: handleClickCapture, | ||
| onDoubleClick: handleDoubleClick, | ||
| dangerouslySetInnerHTML: { __html: 'ignored' }, | ||
| pluginKey: 'bubbleMenu', | ||
| tabIndex: 3, | ||
| ref, | ||
| children: React.createElement('button', { type: 'button' }, 'Menu action'), | ||
| } as any | ||
| const updatedProps = { | ||
| editor: editor as never, | ||
| className: 'bubble-menu-updated', | ||
| style: { zIndex: 1000, marginTop: 16, position: 'static' }, | ||
| onClick: undefined, | ||
| onClickCapture: undefined, | ||
| onDoubleClick: undefined, | ||
| pluginKey: 'bubbleMenu', | ||
| tabIndex: undefined, | ||
| ref, | ||
| children: React.createElement('button', { type: 'button' }, 'Updated action'), | ||
| } as any | ||
| const { rerender, unmount } = render(React.createElement(BubbleMenu, initialProps)) | ||
| expect(editor.registerPlugin).toHaveBeenCalledTimes(1) | ||
| expect(bubbleMenuPluginMock).toHaveBeenCalledTimes(1) | ||
| const [{ element }] = bubbleMenuPluginMock.mock.calls[0] as unknown as [{ element: HTMLDivElement }] | ||
| expect(element).toBeInstanceOf(HTMLDivElement) | ||
| expect(element).toBe(ref.current) | ||
| expect(element.className).toBe('bubble-menu') | ||
| expect(element.getAttribute('data-testid')).toBe('menu-element') | ||
| expect(element.getAttribute('aria-label')).toBe('Bubble menu') | ||
| expect(element.tabIndex).toBe(3) | ||
| expect(element.style.zIndex).toBe('9999') | ||
| expect(element.style.marginTop).toBe('8px') | ||
| expect(element.style.position).toBe('absolute') | ||
| expect(element.getAttribute('dangerouslySetInnerHTML')).toBeNull() | ||
| element.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })) | ||
| element.querySelector('button')?.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| expect(handleClick).toHaveBeenCalledTimes(2) | ||
| expect(handleDoubleClick).toHaveBeenCalledTimes(1) | ||
| expect(handleClickCapture).toHaveBeenCalledTimes(2) | ||
| expect(lastEvent.nativeEvent).toBeInstanceOf(MouseEvent) | ||
| expect(lastEvent.currentTarget).toBe(element) | ||
| expect(lastEvent.target).toBeInstanceOf(Element) | ||
| expect(typeof lastEvent.persist).toBe('function') | ||
| expect(element.textContent).toContain('Menu action') | ||
| rerender(React.createElement(BubbleMenu, updatedProps)) | ||
| expect(element.className).toBe('bubble-menu-updated') | ||
| expect(element.getAttribute('data-testid')).toBeNull() | ||
| expect(element.getAttribute('aria-label')).toBeNull() | ||
| expect(element.tabIndex).toBe(-1) | ||
| expect(element.style.zIndex).toBe('1000') | ||
| expect(element.style.marginTop).toBe('16px') | ||
| expect(element.style.position).toBe('absolute') | ||
| element.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })) | ||
| element.querySelector('button')?.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| expect(handleClick).toHaveBeenCalledTimes(2) | ||
| expect(handleDoubleClick).toHaveBeenCalledTimes(1) | ||
| expect(handleClickCapture).toHaveBeenCalledTimes(2) | ||
| expect(element.textContent).toContain('Updated action') | ||
| unmount() | ||
| expect(editor.unregisterPlugin).toHaveBeenCalledWith('bubbleMenu') | ||
| }) | ||
| it('creates unique plugin keys when none are provided', () => { | ||
| const editor = createEditor() | ||
| const shouldShowA = vi.fn(() => true) | ||
| const shouldShowB = vi.fn(() => false) | ||
| const { unmount } = render( | ||
| React.createElement( | ||
| React.Fragment, | ||
| null, | ||
| React.createElement(BubbleMenu, { | ||
| editor: editor as never, | ||
| shouldShow: shouldShowA, | ||
| children: React.createElement('button', { type: 'button' }, 'First'), | ||
| } as any), | ||
| React.createElement(BubbleMenu, { | ||
| editor: editor as never, | ||
| shouldShow: shouldShowB, | ||
| children: React.createElement('button', { type: 'button' }, 'Second'), | ||
| } as any), | ||
| ), | ||
| ) | ||
| expect(bubbleMenuPluginMock).toHaveBeenCalledTimes(2) | ||
| const pluginCalls = bubbleMenuPluginMock.mock.calls as unknown as Array<[{ pluginKey: unknown }]> | ||
| const firstPluginKey = pluginCalls[0][0].pluginKey | ||
| const secondPluginKey = pluginCalls[1][0].pluginKey | ||
| expect(firstPluginKey).toBeDefined() | ||
| expect(secondPluginKey).toBeDefined() | ||
| expect(firstPluginKey).not.toBe(secondPluginKey) | ||
| unmount() | ||
| const unregisterCalls = editor.unregisterPlugin.mock.calls as unknown as Array<[unknown]> | ||
| expect(unregisterCalls.some(([key]) => key === firstPluginKey)).toBe(true) | ||
| expect(unregisterCalls.some(([key]) => key === secondPluginKey)).toBe(true) | ||
| }) | ||
| }) |
| import { render } from '@testing-library/react' | ||
| import React, { createRef } from 'react' | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' | ||
| import { FloatingMenu } from './FloatingMenu.js' | ||
| const { floatingMenuPluginMock } = vi.hoisted(() => ({ | ||
| floatingMenuPluginMock: vi.fn(() => ({ key: 'floating-menu-plugin' })), | ||
| })) | ||
| vi.mock('@tiptap/extension-floating-menu', () => ({ | ||
| FloatingMenuPlugin: floatingMenuPluginMock, | ||
| })) | ||
| function createEditor() { | ||
| const tr = { | ||
| setMeta: vi.fn(() => tr), | ||
| } | ||
| return { | ||
| isDestroyed: false, | ||
| registerPlugin: vi.fn(), | ||
| unregisterPlugin: vi.fn(), | ||
| view: { | ||
| dispatch: vi.fn(), | ||
| }, | ||
| state: { | ||
| tr, | ||
| }, | ||
| } | ||
| } | ||
| describe('FloatingMenu', () => { | ||
| beforeEach(() => { | ||
| floatingMenuPluginMock.mockClear() | ||
| }) | ||
| afterEach(() => { | ||
| document.body.innerHTML = '' | ||
| }) | ||
| it('applies html props to the actual menu element', () => { | ||
| const editor = createEditor() | ||
| const ref = createRef<HTMLDivElement>() | ||
| const handleClick = vi.fn() | ||
| const handleClickCapture = vi.fn() | ||
| const handleDoubleClick = vi.fn() | ||
| const initialProps = { | ||
| editor: editor as never, | ||
| className: 'floating-menu', | ||
| 'data-testid': 'floating-element', | ||
| 'aria-label': 'Floating menu', | ||
| style: { zIndex: 8888, marginTop: 12, position: 'relative' }, | ||
| onClick: handleClick, | ||
| onClickCapture: handleClickCapture, | ||
| onDoubleClick: handleDoubleClick, | ||
| pluginKey: 'floatingMenu', | ||
| tabIndex: 5, | ||
| ref, | ||
| children: React.createElement('button', { type: 'button' }, 'Floating action'), | ||
| } as any | ||
| const updatedProps = { | ||
| editor: editor as never, | ||
| className: 'floating-menu-updated', | ||
| style: { zIndex: 7777, marginTop: 20, position: 'static' }, | ||
| onClick: undefined, | ||
| onClickCapture: undefined, | ||
| onDoubleClick: undefined, | ||
| pluginKey: 'floatingMenu', | ||
| tabIndex: undefined, | ||
| ref, | ||
| children: React.createElement('button', { type: 'button' }, 'Updated floating action'), | ||
| } as any | ||
| const { rerender, unmount } = render(React.createElement(FloatingMenu, initialProps)) | ||
| expect(editor.registerPlugin).toHaveBeenCalledTimes(1) | ||
| expect(floatingMenuPluginMock).toHaveBeenCalledTimes(1) | ||
| const [{ element }] = floatingMenuPluginMock.mock.calls[0] as unknown as [{ element: HTMLDivElement }] | ||
| expect(element).toBeInstanceOf(HTMLDivElement) | ||
| expect(element).toBe(ref.current) | ||
| expect(element.className).toBe('floating-menu') | ||
| expect(element.getAttribute('data-testid')).toBe('floating-element') | ||
| expect(element.getAttribute('aria-label')).toBe('Floating menu') | ||
| expect(element.tabIndex).toBe(5) | ||
| expect(element.style.zIndex).toBe('8888') | ||
| expect(element.style.marginTop).toBe('12px') | ||
| expect(element.style.position).toBe('absolute') | ||
| element.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })) | ||
| element.querySelector('button')?.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| expect(handleClick).toHaveBeenCalledTimes(2) | ||
| expect(handleDoubleClick).toHaveBeenCalledTimes(1) | ||
| expect(handleClickCapture).toHaveBeenCalledTimes(2) | ||
| expect(element.textContent).toContain('Floating action') | ||
| rerender(React.createElement(FloatingMenu, updatedProps)) | ||
| expect(element.className).toBe('floating-menu-updated') | ||
| expect(element.getAttribute('data-testid')).toBeNull() | ||
| expect(element.getAttribute('aria-label')).toBeNull() | ||
| expect(element.tabIndex).toBe(-1) | ||
| expect(element.style.zIndex).toBe('7777') | ||
| expect(element.style.marginTop).toBe('20px') | ||
| expect(element.style.position).toBe('absolute') | ||
| element.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })) | ||
| element.querySelector('button')?.dispatchEvent(new MouseEvent('click', { bubbles: true })) | ||
| expect(handleClick).toHaveBeenCalledTimes(2) | ||
| expect(handleDoubleClick).toHaveBeenCalledTimes(1) | ||
| expect(handleClickCapture).toHaveBeenCalledTimes(2) | ||
| expect(element.textContent).toContain('Updated floating action') | ||
| unmount() | ||
| expect(editor.unregisterPlugin).toHaveBeenCalledWith('floatingMenu') | ||
| }) | ||
| it('creates unique plugin keys when none are provided', () => { | ||
| const editor = createEditor() | ||
| const shouldShowA = vi.fn(() => true) | ||
| const shouldShowB = vi.fn(() => false) | ||
| const { unmount } = render( | ||
| React.createElement( | ||
| React.Fragment, | ||
| null, | ||
| React.createElement(FloatingMenu, { | ||
| editor: editor as never, | ||
| shouldShow: shouldShowA, | ||
| children: React.createElement('button', { type: 'button' }, 'First'), | ||
| } as any), | ||
| React.createElement(FloatingMenu, { | ||
| editor: editor as never, | ||
| shouldShow: shouldShowB, | ||
| children: React.createElement('button', { type: 'button' }, 'Second'), | ||
| } as any), | ||
| ), | ||
| ) | ||
| expect(floatingMenuPluginMock).toHaveBeenCalledTimes(2) | ||
| const pluginCalls = floatingMenuPluginMock.mock.calls as unknown as Array<[{ pluginKey: unknown }]> | ||
| const firstPluginKey = pluginCalls[0][0].pluginKey | ||
| const secondPluginKey = pluginCalls[1][0].pluginKey | ||
| expect(firstPluginKey).toBeDefined() | ||
| expect(secondPluginKey).toBeDefined() | ||
| expect(firstPluginKey).not.toBe(secondPluginKey) | ||
| unmount() | ||
| const unregisterCalls = editor.unregisterPlugin.mock.calls as unknown as Array<[unknown]> | ||
| expect(unregisterCalls.some(([key]) => key === firstPluginKey)).toBe(true) | ||
| expect(unregisterCalls.some(([key]) => key === secondPluginKey)).toBe(true) | ||
| }) | ||
| }) |
| import { PluginKey } from '@tiptap/pm/state' | ||
| export function getAutoPluginKey(pluginKey: PluginKey | string | undefined, defaultName: string) { | ||
| return pluginKey ?? new PluginKey(defaultName) | ||
| } |
| import type { CSSProperties, HTMLAttributes } from 'react' | ||
| import { useEffect, useLayoutEffect, useRef } from 'react' | ||
| const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect | ||
| type MenuElementProps = HTMLAttributes<HTMLDivElement> | ||
| type MenuSyntheticEvent = Event & { | ||
| nativeEvent: Event | ||
| currentTarget: HTMLDivElement | ||
| target: EventTarget | null | ||
| persist: () => void | ||
| isDefaultPrevented: () => boolean | ||
| isPropagationStopped: () => boolean | ||
| } | ||
| type MenuEventListener = (event: MenuSyntheticEvent) => void | ||
| type MenuNativeListener = (event: Event) => void | ||
| type MenuEventListenerOptions = { | ||
| capture?: boolean | ||
| } | ||
| type EventListenerEntry = { | ||
| eventName: string | ||
| listener: MenuNativeListener | ||
| options?: MenuEventListenerOptions | ||
| } | ||
| const PLUGIN_MANAGED_STYLE_PROPERTIES = new Set(['left', 'opacity', 'position', 'top', 'visibility', 'width']) | ||
| const UNITLESS_STYLE_PROPERTIES = new Set([ | ||
| 'animationIterationCount', | ||
| 'aspectRatio', | ||
| 'borderImageOutset', | ||
| 'borderImageSlice', | ||
| 'borderImageWidth', | ||
| 'columnCount', | ||
| 'columns', | ||
| 'fillOpacity', | ||
| 'flex', | ||
| 'flexGrow', | ||
| 'flexShrink', | ||
| 'fontWeight', | ||
| 'gridArea', | ||
| 'gridColumn', | ||
| 'gridColumnEnd', | ||
| 'gridColumnStart', | ||
| 'gridRow', | ||
| 'gridRowEnd', | ||
| 'gridRowStart', | ||
| 'lineClamp', | ||
| 'lineHeight', | ||
| 'opacity', | ||
| 'order', | ||
| 'orphans', | ||
| 'scale', | ||
| 'stopOpacity', | ||
| 'strokeDasharray', | ||
| 'strokeDashoffset', | ||
| 'strokeMiterlimit', | ||
| 'strokeOpacity', | ||
| 'strokeWidth', | ||
| 'tabSize', | ||
| 'widows', | ||
| 'zIndex', | ||
| 'zoom', | ||
| ]) | ||
| const ATTRIBUTE_EXCLUSIONS = new Set(['children', 'className', 'style']) | ||
| const DIRECT_PROPERTY_KEYS = new Set(['tabIndex']) | ||
| const FORWARDED_ATTRIBUTE_KEYS = new Set([ | ||
| 'accessKey', | ||
| 'autoCapitalize', | ||
| 'contentEditable', | ||
| 'contextMenu', | ||
| 'dir', | ||
| 'draggable', | ||
| 'enterKeyHint', | ||
| 'hidden', | ||
| 'id', | ||
| 'lang', | ||
| 'nonce', | ||
| 'role', | ||
| 'slot', | ||
| 'spellCheck', | ||
| 'tabIndex', | ||
| 'title', | ||
| 'translate', | ||
| ]) | ||
| const SPECIAL_EVENT_NAMES: Record<string, string> = { | ||
| Blur: 'focusout', | ||
| DoubleClick: 'dblclick', | ||
| Focus: 'focusin', | ||
| MouseEnter: 'mouseenter', | ||
| MouseLeave: 'mouseleave', | ||
| } | ||
| function isEventProp(key: string, value: unknown): value is MenuEventListener { | ||
| return /^on[A-Z]/.test(key) && typeof value === 'function' | ||
| } | ||
| function toAttributeName(key: string) { | ||
| if (key.startsWith('aria-') || key.startsWith('data-')) { | ||
| return key | ||
| } | ||
| return key | ||
| } | ||
| function isForwardedAttributeKey(key: string) { | ||
| return key.startsWith('aria-') || key.startsWith('data-') || FORWARDED_ATTRIBUTE_KEYS.has(key) | ||
| } | ||
| function toStylePropertyName(key: string) { | ||
| if (key.startsWith('--')) { | ||
| return key | ||
| } | ||
| return key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) | ||
| } | ||
| function toEventConfig(key: string) { | ||
| const useCapture = key.endsWith('Capture') | ||
| const baseKey = useCapture ? key.slice(0, -7) : key | ||
| const reactEventName = baseKey.slice(2) | ||
| const eventName = SPECIAL_EVENT_NAMES[reactEventName] ?? reactEventName.toLowerCase() | ||
| return { | ||
| eventName, | ||
| options: useCapture ? { capture: true } : undefined, | ||
| } | ||
| } | ||
| function createSyntheticEvent(element: HTMLDivElement, nativeEvent: Event): MenuSyntheticEvent { | ||
| let defaultPrevented = nativeEvent.defaultPrevented | ||
| let propagationStopped = false | ||
| const syntheticEvent = Object.create(nativeEvent) | ||
| Object.defineProperties(syntheticEvent, { | ||
| nativeEvent: { value: nativeEvent }, | ||
| currentTarget: { value: element }, | ||
| target: { value: nativeEvent.target }, | ||
| persist: { value: () => undefined }, | ||
| isDefaultPrevented: { value: () => defaultPrevented }, | ||
| isPropagationStopped: { value: () => propagationStopped }, | ||
| preventDefault: { | ||
| value: () => { | ||
| defaultPrevented = true | ||
| nativeEvent.preventDefault() | ||
| }, | ||
| }, | ||
| stopPropagation: { | ||
| value: () => { | ||
| propagationStopped = true | ||
| nativeEvent.stopPropagation() | ||
| }, | ||
| }, | ||
| }) | ||
| return syntheticEvent as MenuSyntheticEvent | ||
| } | ||
| function isDirectPropertyKey(key: string) { | ||
| return DIRECT_PROPERTY_KEYS.has(key) | ||
| } | ||
| function setDirectProperty(element: HTMLDivElement, key: string, value: unknown) { | ||
| if (key === 'tabIndex') { | ||
| element.tabIndex = Number(value) | ||
| return | ||
| } | ||
| ;(element as unknown as Record<string, unknown>)[key] = value | ||
| } | ||
| function clearDirectProperty(element: HTMLDivElement, key: string) { | ||
| if (key === 'tabIndex') { | ||
| element.removeAttribute('tabindex') | ||
| return | ||
| } | ||
| const propertyValue = (element as unknown as Record<string, unknown>)[key] | ||
| if (typeof propertyValue === 'boolean') { | ||
| ;(element as unknown as Record<string, unknown>)[key] = false | ||
| return | ||
| } | ||
| if (typeof propertyValue === 'number') { | ||
| ;(element as unknown as Record<string, unknown>)[key] = 0 | ||
| return | ||
| } | ||
| ;(element as unknown as Record<string, unknown>)[key] = '' | ||
| } | ||
| function toStyleValue(styleName: string, value: string | number) { | ||
| if ( | ||
| typeof value !== 'number' || | ||
| value === 0 || | ||
| styleName.startsWith('--') || | ||
| UNITLESS_STYLE_PROPERTIES.has(styleName) | ||
| ) { | ||
| return String(value) | ||
| } | ||
| return `${value}px` | ||
| } | ||
| function removeStyleProperty(element: HTMLDivElement, styleName: string) { | ||
| if (PLUGIN_MANAGED_STYLE_PROPERTIES.has(styleName)) { | ||
| return | ||
| } | ||
| element.style.removeProperty(toStylePropertyName(styleName)) | ||
| } | ||
| function applyStyleProperty(element: HTMLDivElement, styleName: string, value: string | number) { | ||
| if (PLUGIN_MANAGED_STYLE_PROPERTIES.has(styleName)) { | ||
| return | ||
| } | ||
| element.style.setProperty(toStylePropertyName(styleName), toStyleValue(styleName, value)) | ||
| } | ||
| function syncAttributes(element: HTMLDivElement, prevProps: MenuElementProps, nextProps: MenuElementProps) { | ||
| const allKeys = new Set([...Object.keys(prevProps), ...Object.keys(nextProps)]) | ||
| allKeys.forEach(key => { | ||
| if ( | ||
| ATTRIBUTE_EXCLUSIONS.has(key) || | ||
| !isForwardedAttributeKey(key) || | ||
| isEventProp(key, prevProps[key as keyof MenuElementProps]) || | ||
| isEventProp(key, nextProps[key as keyof MenuElementProps]) | ||
| ) { | ||
| return | ||
| } | ||
| const prevValue = prevProps[key as keyof MenuElementProps] | ||
| const nextValue = nextProps[key as keyof MenuElementProps] | ||
| if (prevValue === nextValue) { | ||
| return | ||
| } | ||
| const attributeName = toAttributeName(key) | ||
| if (nextValue == null || nextValue === false) { | ||
| if (isDirectPropertyKey(key)) { | ||
| clearDirectProperty(element, key) | ||
| } | ||
| element.removeAttribute(attributeName) | ||
| return | ||
| } | ||
| if (nextValue === true) { | ||
| if (isDirectPropertyKey(key)) { | ||
| setDirectProperty(element, key, true) | ||
| } | ||
| element.setAttribute(attributeName, '') | ||
| return | ||
| } | ||
| if (isDirectPropertyKey(key)) { | ||
| setDirectProperty(element, key, nextValue) | ||
| return | ||
| } | ||
| element.setAttribute(attributeName, String(nextValue)) | ||
| }) | ||
| } | ||
| function syncClassName(element: HTMLDivElement, prevClassName?: string, nextClassName?: string) { | ||
| if (prevClassName === nextClassName) { | ||
| return | ||
| } | ||
| if (nextClassName) { | ||
| element.className = nextClassName | ||
| return | ||
| } | ||
| element.removeAttribute('class') | ||
| } | ||
| function syncStyles( | ||
| element: HTMLDivElement, | ||
| prevStyle: CSSProperties | undefined, | ||
| nextStyle: CSSProperties | undefined, | ||
| ) { | ||
| const previousStyle = prevStyle ?? {} | ||
| const currentStyle = nextStyle ?? {} | ||
| const allStyleNames = new Set([...Object.keys(previousStyle), ...Object.keys(currentStyle)]) | ||
| allStyleNames.forEach(styleName => { | ||
| const prevValue = previousStyle[styleName as keyof CSSProperties] | ||
| const nextValue = currentStyle[styleName as keyof CSSProperties] | ||
| if (prevValue === nextValue) { | ||
| return | ||
| } | ||
| if (nextValue == null) { | ||
| removeStyleProperty(element, styleName) | ||
| return | ||
| } | ||
| applyStyleProperty(element, styleName, nextValue as string | number) | ||
| }) | ||
| } | ||
| function syncEventListeners(element: HTMLDivElement, prevListeners: EventListenerEntry[], nextProps: MenuElementProps) { | ||
| prevListeners.forEach(({ eventName, listener, options }) => { | ||
| element.removeEventListener(eventName, listener, options) | ||
| }) | ||
| const nextListeners: EventListenerEntry[] = [] | ||
| Object.entries(nextProps).forEach(([key, value]) => { | ||
| if (!isEventProp(key, value)) { | ||
| return | ||
| } | ||
| const { eventName, options } = toEventConfig(key) | ||
| const listener: MenuNativeListener = event => { | ||
| value(createSyntheticEvent(element, event)) | ||
| } | ||
| element.addEventListener(eventName, listener, options) | ||
| nextListeners.push({ eventName, listener, options }) | ||
| }) | ||
| return nextListeners | ||
| } | ||
| export function useMenuElementProps(element: HTMLDivElement, props: MenuElementProps) { | ||
| const previousPropsRef = useRef<MenuElementProps>({}) | ||
| const listenersRef = useRef<EventListenerEntry[]>([]) | ||
| useIsomorphicLayoutEffect(() => { | ||
| const previousProps = previousPropsRef.current | ||
| syncClassName(element, previousProps.className, props.className) | ||
| syncStyles(element, previousProps.style, props.style) | ||
| syncAttributes(element, previousProps, props) | ||
| listenersRef.current = syncEventListeners(element, listenersRef.current, props) | ||
| previousPropsRef.current = props | ||
| return () => { | ||
| listenersRef.current.forEach(({ eventName, listener, options }) => { | ||
| element.removeEventListener(eventName, listener, options) | ||
| }) | ||
| listenersRef.current = [] | ||
| } | ||
| }, [element, props]) | ||
| } |
+7
-7
| { | ||
| "name": "@tiptap/react", | ||
| "description": "React components for tiptap", | ||
| "version": "3.20.2", | ||
| "version": "3.20.3", | ||
| "homepage": "https://tiptap.dev", | ||
@@ -51,8 +51,8 @@ "keywords": [ | ||
| "react-dom": "^19.0.0", | ||
| "@tiptap/core": "^3.20.2", | ||
| "@tiptap/pm": "^3.20.2" | ||
| "@tiptap/core": "^3.20.3", | ||
| "@tiptap/pm": "^3.20.3" | ||
| }, | ||
| "optionalDependencies": { | ||
| "@tiptap/extension-bubble-menu": "^3.20.2", | ||
| "@tiptap/extension-floating-menu": "^3.20.2" | ||
| "@tiptap/extension-bubble-menu": "^3.20.3", | ||
| "@tiptap/extension-floating-menu": "^3.20.3" | ||
| }, | ||
@@ -64,4 +64,4 @@ "peerDependencies": { | ||
| "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", | ||
| "@tiptap/core": "^3.20.2", | ||
| "@tiptap/pm": "^3.20.2" | ||
| "@tiptap/core": "^3.20.3", | ||
| "@tiptap/pm": "^3.20.3" | ||
| }, | ||
@@ -68,0 +68,0 @@ "repository": { |
| import { type BubbleMenuPluginProps, BubbleMenuPlugin } from '@tiptap/extension-bubble-menu' | ||
| import type { PluginKey } from '@tiptap/pm/state' | ||
| import { useCurrentEditor } from '@tiptap/react' | ||
@@ -6,2 +7,5 @@ import React, { useEffect, useRef, useState } from 'react' | ||
| import { getAutoPluginKey } from './getAutoPluginKey.js' | ||
| import { useMenuElementProps } from './useMenuElementProps.js' | ||
| type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K> | ||
@@ -15,3 +19,3 @@ | ||
| { | ||
| pluginKey = 'bubbleMenu', | ||
| pluginKey, | ||
| editor, | ||
@@ -30,3 +34,6 @@ updateDelay, | ||
| const menuEl = useRef(document.createElement('div')) | ||
| const resolvedPluginKey = useRef<PluginKey | string>(getAutoPluginKey(pluginKey, 'bubbleMenu')).current | ||
| useMenuElementProps(menuEl.current, restProps) | ||
| if (typeof ref === 'function') { | ||
@@ -51,3 +58,3 @@ ref(menuEl.current) | ||
| appendTo, | ||
| pluginKey, | ||
| pluginKey: resolvedPluginKey, | ||
| shouldShow, | ||
@@ -131,3 +138,3 @@ getReferencedVirtualElement, | ||
| pluginEditor.view.dispatch( | ||
| pluginEditor.state.tr.setMeta(pluginKey, { | ||
| pluginEditor.state.tr.setMeta(resolvedPluginKey, { | ||
| type: 'updateOptions', | ||
@@ -146,6 +153,7 @@ options: bubbleMenuPluginPropsRef.current, | ||
| getReferencedVirtualElement, | ||
| resolvedPluginKey, | ||
| ]) | ||
| return createPortal(<div {...restProps}>{children}</div>, menuEl.current) | ||
| return createPortal(children, menuEl.current) | ||
| }, | ||
| ) |
| import type { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu' | ||
| import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu' | ||
| import type { PluginKey } from '@tiptap/pm/state' | ||
| import { useCurrentEditor } from '@tiptap/react' | ||
@@ -7,2 +8,5 @@ import React, { useEffect, useRef, useState } from 'react' | ||
| import { getAutoPluginKey } from './getAutoPluginKey.js' | ||
| import { useMenuElementProps } from './useMenuElementProps.js' | ||
| type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K> | ||
@@ -17,17 +21,10 @@ | ||
| ( | ||
| { | ||
| pluginKey = 'floatingMenu', | ||
| editor, | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| shouldShow = null, | ||
| options, | ||
| children, | ||
| ...restProps | ||
| }, | ||
| { pluginKey, editor, updateDelay, resizeDelay, appendTo, shouldShow = null, options, children, ...restProps }, | ||
| ref, | ||
| ) => { | ||
| const menuEl = useRef(document.createElement('div')) | ||
| const resolvedPluginKey = useRef<PluginKey | string>(getAutoPluginKey(pluginKey, 'floatingMenu')).current | ||
| useMenuElementProps(menuEl.current, restProps) | ||
| if (typeof ref === 'function') { | ||
@@ -52,3 +49,3 @@ ref(menuEl.current) | ||
| appendTo, | ||
| pluginKey, | ||
| pluginKey: resolvedPluginKey, | ||
| shouldShow, | ||
@@ -134,3 +131,3 @@ options, | ||
| pluginEditor.view.dispatch( | ||
| pluginEditor.state.tr.setMeta(pluginKey, { | ||
| pluginEditor.state.tr.setMeta(resolvedPluginKey, { | ||
| type: 'updateOptions', | ||
@@ -140,6 +137,6 @@ options: floatingMenuPluginPropsRef.current, | ||
| ) | ||
| }, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo]) | ||
| }, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo, resolvedPluginKey]) | ||
| return createPortal(<div {...restProps}>{children}</div>, menuEl.current) | ||
| return createPortal(children, menuEl.current) | ||
| }, | ||
| ) |
-1195
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| EditorConsumer: () => EditorConsumer, | ||
| EditorContent: () => EditorContent, | ||
| EditorContext: () => EditorContext, | ||
| EditorProvider: () => EditorProvider, | ||
| MarkViewContent: () => MarkViewContent, | ||
| NodeViewContent: () => NodeViewContent, | ||
| NodeViewWrapper: () => NodeViewWrapper, | ||
| PureEditorContent: () => PureEditorContent, | ||
| ReactMarkView: () => ReactMarkView, | ||
| ReactMarkViewContext: () => ReactMarkViewContext, | ||
| ReactMarkViewRenderer: () => ReactMarkViewRenderer, | ||
| ReactNodeView: () => ReactNodeView, | ||
| ReactNodeViewContentProvider: () => ReactNodeViewContentProvider, | ||
| ReactNodeViewContext: () => ReactNodeViewContext, | ||
| ReactNodeViewRenderer: () => ReactNodeViewRenderer, | ||
| ReactRenderer: () => ReactRenderer, | ||
| Tiptap: () => Tiptap, | ||
| TiptapContent: () => TiptapContent, | ||
| TiptapContext: () => TiptapContext, | ||
| TiptapWrapper: () => TiptapWrapper, | ||
| useCurrentEditor: () => useCurrentEditor, | ||
| useEditor: () => useEditor, | ||
| useEditorState: () => useEditorState, | ||
| useReactNodeView: () => useReactNodeView, | ||
| useTiptap: () => useTiptap, | ||
| useTiptapState: () => useTiptapState | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/Context.tsx | ||
| var import_react4 = require("react"); | ||
| // src/EditorContent.tsx | ||
| var import_react = __toESM(require("react"), 1); | ||
| var import_react_dom = __toESM(require("react-dom"), 1); | ||
| var import_shim = require("use-sync-external-store/shim/index.js"); | ||
| var import_jsx_runtime = require("react/jsx-runtime"); | ||
| var mergeRefs = (...refs) => { | ||
| return (node) => { | ||
| refs.forEach((ref) => { | ||
| if (typeof ref === "function") { | ||
| ref(node); | ||
| } else if (ref) { | ||
| ; | ||
| ref.current = node; | ||
| } | ||
| }); | ||
| }; | ||
| }; | ||
| var Portals = ({ contentComponent }) => { | ||
| const renderers = (0, import_shim.useSyncExternalStore)( | ||
| contentComponent.subscribe, | ||
| contentComponent.getSnapshot, | ||
| contentComponent.getServerSnapshot | ||
| ); | ||
| return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: Object.values(renderers) }); | ||
| }; | ||
| function getInstance() { | ||
| const subscribers = /* @__PURE__ */ new Set(); | ||
| let renderers = {}; | ||
| return { | ||
| /** | ||
| * Subscribe to the editor instance's changes. | ||
| */ | ||
| subscribe(callback) { | ||
| subscribers.add(callback); | ||
| return () => { | ||
| subscribers.delete(callback); | ||
| }; | ||
| }, | ||
| getSnapshot() { | ||
| return renderers; | ||
| }, | ||
| getServerSnapshot() { | ||
| return renderers; | ||
| }, | ||
| /** | ||
| * Adds a new NodeView Renderer to the editor. | ||
| */ | ||
| setRenderer(id, renderer) { | ||
| renderers = { | ||
| ...renderers, | ||
| [id]: import_react_dom.default.createPortal(renderer.reactElement, renderer.element, id) | ||
| }; | ||
| subscribers.forEach((subscriber) => subscriber()); | ||
| }, | ||
| /** | ||
| * Removes a NodeView Renderer from the editor. | ||
| */ | ||
| removeRenderer(id) { | ||
| const nextRenderers = { ...renderers }; | ||
| delete nextRenderers[id]; | ||
| renderers = nextRenderers; | ||
| subscribers.forEach((subscriber) => subscriber()); | ||
| } | ||
| }; | ||
| } | ||
| var PureEditorContent = class extends import_react.default.Component { | ||
| constructor(props) { | ||
| var _a; | ||
| super(props); | ||
| this.editorContentRef = import_react.default.createRef(); | ||
| this.initialized = false; | ||
| this.state = { | ||
| hasContentComponentInitialized: Boolean((_a = props.editor) == null ? void 0 : _a.contentComponent) | ||
| }; | ||
| } | ||
| componentDidMount() { | ||
| this.init(); | ||
| } | ||
| componentDidUpdate() { | ||
| this.init(); | ||
| } | ||
| init() { | ||
| var _a; | ||
| const editor = this.props.editor; | ||
| if (editor && !editor.isDestroyed && ((_a = editor.view.dom) == null ? void 0 : _a.parentNode)) { | ||
| if (editor.contentComponent) { | ||
| return; | ||
| } | ||
| const element = this.editorContentRef.current; | ||
| element.append(...editor.view.dom.parentNode.childNodes); | ||
| editor.setOptions({ | ||
| element | ||
| }); | ||
| editor.contentComponent = getInstance(); | ||
| if (!this.state.hasContentComponentInitialized) { | ||
| this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => { | ||
| this.setState((prevState) => { | ||
| if (!prevState.hasContentComponentInitialized) { | ||
| return { | ||
| hasContentComponentInitialized: true | ||
| }; | ||
| } | ||
| return prevState; | ||
| }); | ||
| if (this.unsubscribeToContentComponent) { | ||
| this.unsubscribeToContentComponent(); | ||
| } | ||
| }); | ||
| } | ||
| editor.createNodeViews(); | ||
| this.initialized = true; | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| var _a; | ||
| const editor = this.props.editor; | ||
| if (!editor) { | ||
| return; | ||
| } | ||
| this.initialized = false; | ||
| if (!editor.isDestroyed) { | ||
| editor.view.setProps({ | ||
| nodeViews: {} | ||
| }); | ||
| } | ||
| if (this.unsubscribeToContentComponent) { | ||
| this.unsubscribeToContentComponent(); | ||
| } | ||
| editor.contentComponent = null; | ||
| try { | ||
| if (!((_a = editor.view.dom) == null ? void 0 : _a.parentNode)) { | ||
| return; | ||
| } | ||
| const newElement = document.createElement("div"); | ||
| newElement.append(...editor.view.dom.parentNode.childNodes); | ||
| editor.setOptions({ | ||
| element: newElement | ||
| }); | ||
| } catch { | ||
| } | ||
| } | ||
| render() { | ||
| const { editor, innerRef, ...rest } = this.props; | ||
| return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ | ||
| /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }), | ||
| (editor == null ? void 0 : editor.contentComponent) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portals, { contentComponent: editor.contentComponent }) | ||
| ] }); | ||
| } | ||
| }; | ||
| var EditorContentWithKey = (0, import_react.forwardRef)( | ||
| (props, ref) => { | ||
| const key = import_react.default.useMemo(() => { | ||
| return Math.floor(Math.random() * 4294967295).toString(); | ||
| }, [props.editor]); | ||
| return import_react.default.createElement(PureEditorContent, { | ||
| key, | ||
| innerRef: ref, | ||
| ...props | ||
| }); | ||
| } | ||
| ); | ||
| var EditorContent = import_react.default.memo(EditorContentWithKey); | ||
| // src/useEditor.ts | ||
| var import_core = require("@tiptap/core"); | ||
| var import_react3 = require("react"); | ||
| var import_shim2 = require("use-sync-external-store/shim/index.js"); | ||
| // src/useEditorState.ts | ||
| var import_fast_equals = require("fast-equals"); | ||
| var import_react2 = require("react"); | ||
| var import_with_selector = require("use-sync-external-store/shim/with-selector.js"); | ||
| var useIsomorphicLayoutEffect = typeof window !== "undefined" ? import_react2.useLayoutEffect : import_react2.useEffect; | ||
| var EditorStateManager = class { | ||
| constructor(initialEditor) { | ||
| this.transactionNumber = 0; | ||
| this.lastTransactionNumber = 0; | ||
| this.subscribers = /* @__PURE__ */ new Set(); | ||
| this.editor = initialEditor; | ||
| this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 }; | ||
| this.getSnapshot = this.getSnapshot.bind(this); | ||
| this.getServerSnapshot = this.getServerSnapshot.bind(this); | ||
| this.watch = this.watch.bind(this); | ||
| this.subscribe = this.subscribe.bind(this); | ||
| } | ||
| /** | ||
| * Get the current editor instance. | ||
| */ | ||
| getSnapshot() { | ||
| if (this.transactionNumber === this.lastTransactionNumber) { | ||
| return this.lastSnapshot; | ||
| } | ||
| this.lastTransactionNumber = this.transactionNumber; | ||
| this.lastSnapshot = { editor: this.editor, transactionNumber: this.transactionNumber }; | ||
| return this.lastSnapshot; | ||
| } | ||
| /** | ||
| * Always disable the editor on the server-side. | ||
| */ | ||
| getServerSnapshot() { | ||
| return { editor: null, transactionNumber: 0 }; | ||
| } | ||
| /** | ||
| * Subscribe to the editor instance's changes. | ||
| */ | ||
| subscribe(callback) { | ||
| this.subscribers.add(callback); | ||
| return () => { | ||
| this.subscribers.delete(callback); | ||
| }; | ||
| } | ||
| /** | ||
| * Watch the editor instance for changes. | ||
| */ | ||
| watch(nextEditor) { | ||
| this.editor = nextEditor; | ||
| if (this.editor) { | ||
| const fn = () => { | ||
| this.transactionNumber += 1; | ||
| this.subscribers.forEach((callback) => callback()); | ||
| }; | ||
| const currentEditor = this.editor; | ||
| currentEditor.on("transaction", fn); | ||
| return () => { | ||
| currentEditor.off("transaction", fn); | ||
| }; | ||
| } | ||
| return void 0; | ||
| } | ||
| }; | ||
| function useEditorState(options) { | ||
| var _a; | ||
| const [editorStateManager] = (0, import_react2.useState)(() => new EditorStateManager(options.editor)); | ||
| const selectedState = (0, import_with_selector.useSyncExternalStoreWithSelector)( | ||
| editorStateManager.subscribe, | ||
| editorStateManager.getSnapshot, | ||
| editorStateManager.getServerSnapshot, | ||
| options.selector, | ||
| (_a = options.equalityFn) != null ? _a : import_fast_equals.deepEqual | ||
| ); | ||
| useIsomorphicLayoutEffect(() => { | ||
| return editorStateManager.watch(options.editor); | ||
| }, [options.editor, editorStateManager]); | ||
| (0, import_react2.useDebugValue)(selectedState); | ||
| return selectedState; | ||
| } | ||
| // src/useEditor.ts | ||
| var isDev = process.env.NODE_ENV !== "production"; | ||
| var isSSR = typeof window === "undefined"; | ||
| var isNext = isSSR || Boolean(typeof window !== "undefined" && window.next); | ||
| var EditorInstanceManager = class _EditorInstanceManager { | ||
| constructor(options) { | ||
| /** | ||
| * The current editor instance. | ||
| */ | ||
| this.editor = null; | ||
| /** | ||
| * The subscriptions to notify when the editor instance | ||
| * has been created or destroyed. | ||
| */ | ||
| this.subscriptions = /* @__PURE__ */ new Set(); | ||
| /** | ||
| * Whether the editor has been mounted. | ||
| */ | ||
| this.isComponentMounted = false; | ||
| /** | ||
| * The most recent dependencies array. | ||
| */ | ||
| this.previousDeps = null; | ||
| /** | ||
| * The unique instance ID. This is used to identify the editor instance. And will be re-generated for each new instance. | ||
| */ | ||
| this.instanceId = ""; | ||
| this.options = options; | ||
| this.subscriptions = /* @__PURE__ */ new Set(); | ||
| this.setEditor(this.getInitialEditor()); | ||
| this.scheduleDestroy(); | ||
| this.getEditor = this.getEditor.bind(this); | ||
| this.getServerSnapshot = this.getServerSnapshot.bind(this); | ||
| this.subscribe = this.subscribe.bind(this); | ||
| this.refreshEditorInstance = this.refreshEditorInstance.bind(this); | ||
| this.scheduleDestroy = this.scheduleDestroy.bind(this); | ||
| this.onRender = this.onRender.bind(this); | ||
| this.createEditor = this.createEditor.bind(this); | ||
| } | ||
| setEditor(editor) { | ||
| this.editor = editor; | ||
| this.instanceId = Math.random().toString(36).slice(2, 9); | ||
| this.subscriptions.forEach((cb) => cb()); | ||
| } | ||
| getInitialEditor() { | ||
| if (this.options.current.immediatelyRender === void 0) { | ||
| if (isSSR || isNext) { | ||
| if (isDev) { | ||
| throw new Error( | ||
| "Tiptap Error: SSR has been detected, please set `immediatelyRender` explicitly to `false` to avoid hydration mismatches." | ||
| ); | ||
| } | ||
| return null; | ||
| } | ||
| return this.createEditor(); | ||
| } | ||
| if (this.options.current.immediatelyRender && isSSR && isDev) { | ||
| throw new Error( | ||
| "Tiptap Error: SSR has been detected, and `immediatelyRender` has been set to `true` this is an unsupported configuration that may result in errors, explicitly set `immediatelyRender` to `false` to avoid hydration mismatches." | ||
| ); | ||
| } | ||
| if (this.options.current.immediatelyRender) { | ||
| return this.createEditor(); | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Create a new editor instance. And attach event listeners. | ||
| */ | ||
| createEditor() { | ||
| const optionsToApply = { | ||
| ...this.options.current, | ||
| // Always call the most recent version of the callback function by default | ||
| onBeforeCreate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onBeforeCreate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onBlur: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onBlur) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onCreate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onCreate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onDestroy: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onDestroy) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onFocus: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onFocus) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onSelectionUpdate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onSelectionUpdate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onTransaction: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onTransaction) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onUpdate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onUpdate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onContentError: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onContentError) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onDrop: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onDrop) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onPaste: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onPaste) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onDelete: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onDelete) == null ? void 0 : _b.call(_a, ...args); | ||
| } | ||
| }; | ||
| const editor = new import_core.Editor(optionsToApply); | ||
| return editor; | ||
| } | ||
| /** | ||
| * Get the current editor instance. | ||
| */ | ||
| getEditor() { | ||
| return this.editor; | ||
| } | ||
| /** | ||
| * Always disable the editor on the server-side. | ||
| */ | ||
| getServerSnapshot() { | ||
| return null; | ||
| } | ||
| /** | ||
| * Subscribe to the editor instance's changes. | ||
| */ | ||
| subscribe(onStoreChange) { | ||
| this.subscriptions.add(onStoreChange); | ||
| return () => { | ||
| this.subscriptions.delete(onStoreChange); | ||
| }; | ||
| } | ||
| static compareOptions(a, b) { | ||
| return Object.keys(a).every((key) => { | ||
| if ([ | ||
| "onCreate", | ||
| "onBeforeCreate", | ||
| "onDestroy", | ||
| "onUpdate", | ||
| "onTransaction", | ||
| "onFocus", | ||
| "onBlur", | ||
| "onSelectionUpdate", | ||
| "onContentError", | ||
| "onDrop", | ||
| "onPaste" | ||
| ].includes(key)) { | ||
| return true; | ||
| } | ||
| if (key === "extensions" && a.extensions && b.extensions) { | ||
| if (a.extensions.length !== b.extensions.length) { | ||
| return false; | ||
| } | ||
| return a.extensions.every((extension, index) => { | ||
| var _a; | ||
| if (extension !== ((_a = b.extensions) == null ? void 0 : _a[index])) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }); | ||
| } | ||
| if (a[key] !== b[key]) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }); | ||
| } | ||
| /** | ||
| * On each render, we will create, update, or destroy the editor instance. | ||
| * @param deps The dependencies to watch for changes | ||
| * @returns A cleanup function | ||
| */ | ||
| onRender(deps) { | ||
| return () => { | ||
| this.isComponentMounted = true; | ||
| clearTimeout(this.scheduledDestructionTimeout); | ||
| if (this.editor && !this.editor.isDestroyed && deps.length === 0) { | ||
| if (!_EditorInstanceManager.compareOptions(this.options.current, this.editor.options)) { | ||
| this.editor.setOptions({ | ||
| ...this.options.current, | ||
| editable: this.editor.isEditable | ||
| }); | ||
| } | ||
| } else { | ||
| this.refreshEditorInstance(deps); | ||
| } | ||
| return () => { | ||
| this.isComponentMounted = false; | ||
| this.scheduleDestroy(); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Recreate the editor instance if the dependencies have changed. | ||
| */ | ||
| refreshEditorInstance(deps) { | ||
| if (this.editor && !this.editor.isDestroyed) { | ||
| if (this.previousDeps === null) { | ||
| this.previousDeps = deps; | ||
| return; | ||
| } | ||
| const depsAreEqual = this.previousDeps.length === deps.length && this.previousDeps.every((dep, index) => dep === deps[index]); | ||
| if (depsAreEqual) { | ||
| return; | ||
| } | ||
| } | ||
| if (this.editor && !this.editor.isDestroyed) { | ||
| this.editor.destroy(); | ||
| } | ||
| this.setEditor(this.createEditor()); | ||
| this.previousDeps = deps; | ||
| } | ||
| /** | ||
| * Schedule the destruction of the editor instance. | ||
| * This will only destroy the editor if it was not mounted on the next tick. | ||
| * This is to avoid destroying the editor instance when it's actually still mounted. | ||
| */ | ||
| scheduleDestroy() { | ||
| const currentInstanceId = this.instanceId; | ||
| const currentEditor = this.editor; | ||
| this.scheduledDestructionTimeout = setTimeout(() => { | ||
| if (this.isComponentMounted && this.instanceId === currentInstanceId) { | ||
| if (currentEditor) { | ||
| currentEditor.setOptions(this.options.current); | ||
| } | ||
| return; | ||
| } | ||
| if (currentEditor && !currentEditor.isDestroyed) { | ||
| currentEditor.destroy(); | ||
| if (this.instanceId === currentInstanceId) { | ||
| this.setEditor(null); | ||
| } | ||
| } | ||
| }, 1); | ||
| } | ||
| }; | ||
| function useEditor(options = {}, deps = []) { | ||
| const mostRecentOptions = (0, import_react3.useRef)(options); | ||
| mostRecentOptions.current = options; | ||
| const [instanceManager] = (0, import_react3.useState)(() => new EditorInstanceManager(mostRecentOptions)); | ||
| const editor = (0, import_shim2.useSyncExternalStore)( | ||
| instanceManager.subscribe, | ||
| instanceManager.getEditor, | ||
| instanceManager.getServerSnapshot | ||
| ); | ||
| (0, import_react3.useDebugValue)(editor); | ||
| (0, import_react3.useEffect)(instanceManager.onRender(deps)); | ||
| useEditorState({ | ||
| editor, | ||
| selector: ({ transactionNumber }) => { | ||
| if (options.shouldRerenderOnTransaction === false || options.shouldRerenderOnTransaction === void 0) { | ||
| return null; | ||
| } | ||
| if (options.immediatelyRender && transactionNumber === 0) { | ||
| return 0; | ||
| } | ||
| return transactionNumber + 1; | ||
| } | ||
| }); | ||
| return editor; | ||
| } | ||
| // src/Context.tsx | ||
| var import_jsx_runtime2 = require("react/jsx-runtime"); | ||
| var EditorContext = (0, import_react4.createContext)({ | ||
| editor: null | ||
| }); | ||
| var EditorConsumer = EditorContext.Consumer; | ||
| var useCurrentEditor = () => (0, import_react4.useContext)(EditorContext); | ||
| function EditorProvider({ | ||
| children, | ||
| slotAfter, | ||
| slotBefore, | ||
| editorContainerProps = {}, | ||
| ...editorOptions | ||
| }) { | ||
| const editor = useEditor(editorOptions); | ||
| const contextValue = (0, import_react4.useMemo)(() => ({ editor }), [editor]); | ||
| if (!editor) { | ||
| return null; | ||
| } | ||
| return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(EditorContext.Provider, { value: contextValue, children: [ | ||
| slotBefore, | ||
| /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EditorConsumer, { children: ({ editor: currentEditor }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EditorContent, { editor: currentEditor, ...editorContainerProps }) }), | ||
| children, | ||
| slotAfter | ||
| ] }); | ||
| } | ||
| // src/useReactNodeView.ts | ||
| var import_react5 = require("react"); | ||
| var ReactNodeViewContext = (0, import_react5.createContext)({ | ||
| onDragStart: () => { | ||
| }, | ||
| nodeViewContentChildren: void 0, | ||
| nodeViewContentRef: () => { | ||
| } | ||
| }); | ||
| var ReactNodeViewContentProvider = ({ children, content }) => { | ||
| return (0, import_react5.createElement)(ReactNodeViewContext.Provider, { value: { nodeViewContentChildren: content } }, children); | ||
| }; | ||
| var useReactNodeView = () => (0, import_react5.useContext)(ReactNodeViewContext); | ||
| // src/NodeViewContent.tsx | ||
| var import_jsx_runtime3 = ( | ||
| // @ts-ignore | ||
| require("react/jsx-runtime") | ||
| ); | ||
| function NodeViewContent({ | ||
| as: Tag = "div", | ||
| ...props | ||
| }) { | ||
| const { nodeViewContentRef, nodeViewContentChildren } = useReactNodeView(); | ||
| return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( | ||
| Tag, | ||
| { | ||
| ...props, | ||
| ref: nodeViewContentRef, | ||
| "data-node-view-content": "", | ||
| style: { | ||
| whiteSpace: "pre-wrap", | ||
| ...props.style | ||
| }, | ||
| children: nodeViewContentChildren | ||
| } | ||
| ); | ||
| } | ||
| // src/NodeViewWrapper.tsx | ||
| var import_react6 = __toESM(require("react"), 1); | ||
| var import_jsx_runtime4 = ( | ||
| // @ts-ignore | ||
| require("react/jsx-runtime") | ||
| ); | ||
| var NodeViewWrapper = import_react6.default.forwardRef((props, ref) => { | ||
| const { onDragStart } = useReactNodeView(); | ||
| const Tag = props.as || "div"; | ||
| return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( | ||
| Tag, | ||
| { | ||
| ...props, | ||
| ref, | ||
| "data-node-view-wrapper": "", | ||
| onDragStart, | ||
| style: { | ||
| whiteSpace: "normal", | ||
| ...props.style | ||
| } | ||
| } | ||
| ); | ||
| }); | ||
| // src/ReactMarkViewRenderer.tsx | ||
| var import_core2 = require("@tiptap/core"); | ||
| var import_react8 = __toESM(require("react"), 1); | ||
| // src/ReactRenderer.tsx | ||
| var import_react7 = require("react"); | ||
| var import_react_dom2 = require("react-dom"); | ||
| var import_jsx_runtime5 = require("react/jsx-runtime"); | ||
| function isClassComponent(Component) { | ||
| return !!(typeof Component === "function" && Component.prototype && Component.prototype.isReactComponent); | ||
| } | ||
| function isForwardRefComponent(Component) { | ||
| return !!(typeof Component === "object" && Component.$$typeof && (Component.$$typeof.toString() === "Symbol(react.forward_ref)" || Component.$$typeof.description === "react.forward_ref")); | ||
| } | ||
| function isMemoComponent(Component) { | ||
| return !!(typeof Component === "object" && Component.$$typeof && (Component.$$typeof.toString() === "Symbol(react.memo)" || Component.$$typeof.description === "react.memo")); | ||
| } | ||
| function canReceiveRef(Component) { | ||
| if (isClassComponent(Component)) { | ||
| return true; | ||
| } | ||
| if (isForwardRefComponent(Component)) { | ||
| return true; | ||
| } | ||
| if (isMemoComponent(Component)) { | ||
| const wrappedComponent = Component.type; | ||
| if (wrappedComponent) { | ||
| return isClassComponent(wrappedComponent) || isForwardRefComponent(wrappedComponent); | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function isReact19Plus() { | ||
| try { | ||
| if (import_react7.version) { | ||
| const majorVersion = parseInt(import_react7.version.split(".")[0], 10); | ||
| return majorVersion >= 19; | ||
| } | ||
| } catch { | ||
| } | ||
| return false; | ||
| } | ||
| var ReactRenderer = class { | ||
| /** | ||
| * Immediately creates element and renders the provided React component. | ||
| */ | ||
| constructor(component, { editor, props = {}, as = "div", className = "" }) { | ||
| this.ref = null; | ||
| /** | ||
| * Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown. | ||
| */ | ||
| this.destroyed = false; | ||
| this.id = Math.floor(Math.random() * 4294967295).toString(); | ||
| this.component = component; | ||
| this.editor = editor; | ||
| this.props = props; | ||
| this.element = document.createElement(as); | ||
| this.element.classList.add("react-renderer"); | ||
| if (className) { | ||
| this.element.classList.add(...className.split(" ")); | ||
| } | ||
| if (this.editor.isInitialized) { | ||
| (0, import_react_dom2.flushSync)(() => { | ||
| this.render(); | ||
| }); | ||
| } else { | ||
| queueMicrotask(() => { | ||
| if (this.destroyed) { | ||
| return; | ||
| } | ||
| this.render(); | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Render the React component. | ||
| */ | ||
| render() { | ||
| var _a; | ||
| if (this.destroyed) { | ||
| return; | ||
| } | ||
| const Component = this.component; | ||
| const props = this.props; | ||
| const editor = this.editor; | ||
| const isReact19 = isReact19Plus(); | ||
| const componentCanReceiveRef = canReceiveRef(Component); | ||
| const elementProps = { ...props }; | ||
| if (elementProps.ref && !(isReact19 || componentCanReceiveRef)) { | ||
| delete elementProps.ref; | ||
| } | ||
| if (!elementProps.ref && (isReact19 || componentCanReceiveRef)) { | ||
| elementProps.ref = (ref) => { | ||
| this.ref = ref; | ||
| }; | ||
| } | ||
| this.reactElement = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Component, { ...elementProps }); | ||
| (_a = editor == null ? void 0 : editor.contentComponent) == null ? void 0 : _a.setRenderer(this.id, this); | ||
| } | ||
| /** | ||
| * Re-renders the React component with new props. | ||
| */ | ||
| updateProps(props = {}) { | ||
| if (this.destroyed) { | ||
| return; | ||
| } | ||
| this.props = { | ||
| ...this.props, | ||
| ...props | ||
| }; | ||
| this.render(); | ||
| } | ||
| /** | ||
| * Destroy the React component. | ||
| */ | ||
| destroy() { | ||
| var _a; | ||
| this.destroyed = true; | ||
| const editor = this.editor; | ||
| (_a = editor == null ? void 0 : editor.contentComponent) == null ? void 0 : _a.removeRenderer(this.id); | ||
| try { | ||
| if (this.element && this.element.parentNode) { | ||
| this.element.parentNode.removeChild(this.element); | ||
| } | ||
| } catch { | ||
| } | ||
| } | ||
| /** | ||
| * Update the attributes of the element that holds the React component. | ||
| */ | ||
| updateAttributes(attributes) { | ||
| Object.keys(attributes).forEach((key) => { | ||
| this.element.setAttribute(key, attributes[key]); | ||
| }); | ||
| } | ||
| }; | ||
| // src/ReactMarkViewRenderer.tsx | ||
| var import_jsx_runtime6 = ( | ||
| // @ts-ignore | ||
| require("react/jsx-runtime") | ||
| ); | ||
| var ReactMarkViewContext = import_react8.default.createContext({ | ||
| markViewContentRef: () => { | ||
| } | ||
| }); | ||
| var MarkViewContent = (props) => { | ||
| const { as: Tag = "span", ...rest } = props; | ||
| const { markViewContentRef } = import_react8.default.useContext(ReactMarkViewContext); | ||
| return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Tag, { ...rest, ref: markViewContentRef, "data-mark-view-content": "" }); | ||
| }; | ||
| var ReactMarkView = class extends import_core2.MarkView { | ||
| constructor(component, props, options) { | ||
| super(component, props, options); | ||
| const { as = "span", attrs, className = "" } = options || {}; | ||
| const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) }; | ||
| this.contentDOMElement = document.createElement("span"); | ||
| const markViewContentRef = (el) => { | ||
| if (el && !el.contains(this.contentDOMElement)) { | ||
| el.appendChild(this.contentDOMElement); | ||
| } | ||
| }; | ||
| const context = { | ||
| markViewContentRef | ||
| }; | ||
| const ReactMarkViewProvider = import_react8.default.memo((componentProps2) => { | ||
| return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ReactMarkViewContext.Provider, { value: context, children: import_react8.default.createElement(component, componentProps2) }); | ||
| }); | ||
| ReactMarkViewProvider.displayName = "ReactMarkView"; | ||
| this.renderer = new ReactRenderer(ReactMarkViewProvider, { | ||
| editor: props.editor, | ||
| props: componentProps, | ||
| as, | ||
| className: `mark-${props.mark.type.name} ${className}`.trim() | ||
| }); | ||
| if (attrs) { | ||
| this.renderer.updateAttributes(attrs); | ||
| } | ||
| } | ||
| get dom() { | ||
| return this.renderer.element; | ||
| } | ||
| get contentDOM() { | ||
| return this.contentDOMElement; | ||
| } | ||
| }; | ||
| function ReactMarkViewRenderer(component, options = {}) { | ||
| return (props) => new ReactMarkView(component, props, options); | ||
| } | ||
| // src/ReactNodeViewRenderer.tsx | ||
| var import_core3 = require("@tiptap/core"); | ||
| var import_react9 = require("react"); | ||
| var import_jsx_runtime7 = require("react/jsx-runtime"); | ||
| var ReactNodeView = class extends import_core3.NodeView { | ||
| constructor(component, props, options) { | ||
| super(component, props, options); | ||
| /** | ||
| * The requestAnimationFrame ID used for selection updates. | ||
| */ | ||
| this.selectionRafId = null; | ||
| this.cachedExtensionWithSyncedStorage = null; | ||
| if (!this.node.isLeaf) { | ||
| if (this.options.contentDOMElementTag) { | ||
| this.contentDOMElement = document.createElement(this.options.contentDOMElementTag); | ||
| } else { | ||
| this.contentDOMElement = document.createElement(this.node.isInline ? "span" : "div"); | ||
| } | ||
| this.contentDOMElement.dataset.nodeViewContentReact = ""; | ||
| this.contentDOMElement.dataset.nodeViewWrapper = ""; | ||
| this.contentDOMElement.style.whiteSpace = "inherit"; | ||
| const contentTarget = this.dom.querySelector("[data-node-view-content]"); | ||
| if (!contentTarget) { | ||
| return; | ||
| } | ||
| contentTarget.appendChild(this.contentDOMElement); | ||
| } | ||
| } | ||
| /** | ||
| * Returns a proxy of the extension that redirects storage access to the editor's mutable storage. | ||
| * This preserves the original prototype chain (instanceof checks, methods like configure/extend work). | ||
| * Cached to avoid proxy creation on every update. | ||
| */ | ||
| get extensionWithSyncedStorage() { | ||
| if (!this.cachedExtensionWithSyncedStorage) { | ||
| const editor = this.editor; | ||
| const extension = this.extension; | ||
| this.cachedExtensionWithSyncedStorage = new Proxy(extension, { | ||
| get(target, prop, receiver) { | ||
| var _a; | ||
| if (prop === "storage") { | ||
| return (_a = editor.storage[extension.name]) != null ? _a : {}; | ||
| } | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| }); | ||
| } | ||
| return this.cachedExtensionWithSyncedStorage; | ||
| } | ||
| /** | ||
| * Setup the React component. | ||
| * Called on initialization. | ||
| */ | ||
| mount() { | ||
| const props = { | ||
| editor: this.editor, | ||
| node: this.node, | ||
| decorations: this.decorations, | ||
| innerDecorations: this.innerDecorations, | ||
| view: this.view, | ||
| selected: false, | ||
| extension: this.extensionWithSyncedStorage, | ||
| HTMLAttributes: this.HTMLAttributes, | ||
| getPos: () => this.getPos(), | ||
| updateAttributes: (attributes = {}) => this.updateAttributes(attributes), | ||
| deleteNode: () => this.deleteNode(), | ||
| ref: (0, import_react9.createRef)() | ||
| }; | ||
| if (!this.component.displayName) { | ||
| const capitalizeFirstChar = (string) => { | ||
| return string.charAt(0).toUpperCase() + string.substring(1); | ||
| }; | ||
| this.component.displayName = capitalizeFirstChar(this.extension.name); | ||
| } | ||
| const onDragStart = this.onDragStart.bind(this); | ||
| const nodeViewContentRef = (element) => { | ||
| if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) { | ||
| if (element.hasAttribute("data-node-view-wrapper")) { | ||
| element.removeAttribute("data-node-view-wrapper"); | ||
| } | ||
| element.appendChild(this.contentDOMElement); | ||
| } | ||
| }; | ||
| const context = { onDragStart, nodeViewContentRef }; | ||
| const Component = this.component; | ||
| const ReactNodeViewProvider = (0, import_react9.memo)((componentProps) => { | ||
| return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ReactNodeViewContext.Provider, { value: context, children: (0, import_react9.createElement)(Component, componentProps) }); | ||
| }); | ||
| ReactNodeViewProvider.displayName = "ReactNodeView"; | ||
| let as = this.node.isInline ? "span" : "div"; | ||
| if (this.options.as) { | ||
| as = this.options.as; | ||
| } | ||
| const { className = "" } = this.options; | ||
| this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this); | ||
| this.renderer = new ReactRenderer(ReactNodeViewProvider, { | ||
| editor: this.editor, | ||
| props, | ||
| as, | ||
| className: `node-${this.node.type.name} ${className}`.trim() | ||
| }); | ||
| this.editor.on("selectionUpdate", this.handleSelectionUpdate); | ||
| this.updateElementAttributes(); | ||
| } | ||
| /** | ||
| * Return the DOM element. | ||
| * This is the element that will be used to display the node view. | ||
| */ | ||
| get dom() { | ||
| var _a; | ||
| if (this.renderer.element.firstElementChild && !((_a = this.renderer.element.firstElementChild) == null ? void 0 : _a.hasAttribute("data-node-view-wrapper"))) { | ||
| throw Error("Please use the NodeViewWrapper component for your node view."); | ||
| } | ||
| return this.renderer.element; | ||
| } | ||
| /** | ||
| * Return the content DOM element. | ||
| * This is the element that will be used to display the rich-text content of the node. | ||
| */ | ||
| get contentDOM() { | ||
| if (this.node.isLeaf) { | ||
| return null; | ||
| } | ||
| return this.contentDOMElement; | ||
| } | ||
| /** | ||
| * On editor selection update, check if the node is selected. | ||
| * If it is, call `selectNode`, otherwise call `deselectNode`. | ||
| */ | ||
| handleSelectionUpdate() { | ||
| if (this.selectionRafId) { | ||
| cancelAnimationFrame(this.selectionRafId); | ||
| this.selectionRafId = null; | ||
| } | ||
| this.selectionRafId = requestAnimationFrame(() => { | ||
| this.selectionRafId = null; | ||
| const { from, to } = this.editor.state.selection; | ||
| const pos = this.getPos(); | ||
| if (typeof pos !== "number") { | ||
| return; | ||
| } | ||
| if (from <= pos && to >= pos + this.node.nodeSize) { | ||
| if (this.renderer.props.selected) { | ||
| return; | ||
| } | ||
| this.selectNode(); | ||
| } else { | ||
| if (!this.renderer.props.selected) { | ||
| return; | ||
| } | ||
| this.deselectNode(); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * On update, update the React component. | ||
| * To prevent unnecessary updates, the `update` option can be used. | ||
| */ | ||
| update(node, decorations, innerDecorations) { | ||
| const rerenderComponent = (props) => { | ||
| this.renderer.updateProps(props); | ||
| if (typeof this.options.attrs === "function") { | ||
| this.updateElementAttributes(); | ||
| } | ||
| }; | ||
| if (node.type !== this.node.type) { | ||
| return false; | ||
| } | ||
| if (typeof this.options.update === "function") { | ||
| const oldNode = this.node; | ||
| const oldDecorations = this.decorations; | ||
| const oldInnerDecorations = this.innerDecorations; | ||
| this.node = node; | ||
| this.decorations = decorations; | ||
| this.innerDecorations = innerDecorations; | ||
| return this.options.update({ | ||
| oldNode, | ||
| oldDecorations, | ||
| newNode: node, | ||
| newDecorations: decorations, | ||
| oldInnerDecorations, | ||
| innerDecorations, | ||
| updateProps: () => rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }) | ||
| }); | ||
| } | ||
| if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) { | ||
| return true; | ||
| } | ||
| this.node = node; | ||
| this.decorations = decorations; | ||
| this.innerDecorations = innerDecorations; | ||
| rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }); | ||
| return true; | ||
| } | ||
| /** | ||
| * Select the node. | ||
| * Add the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| selectNode() { | ||
| this.renderer.updateProps({ | ||
| selected: true | ||
| }); | ||
| this.renderer.element.classList.add("ProseMirror-selectednode"); | ||
| } | ||
| /** | ||
| * Deselect the node. | ||
| * Remove the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| deselectNode() { | ||
| this.renderer.updateProps({ | ||
| selected: false | ||
| }); | ||
| this.renderer.element.classList.remove("ProseMirror-selectednode"); | ||
| } | ||
| /** | ||
| * Destroy the React component instance. | ||
| */ | ||
| destroy() { | ||
| this.renderer.destroy(); | ||
| this.editor.off("selectionUpdate", this.handleSelectionUpdate); | ||
| this.contentDOMElement = null; | ||
| if (this.selectionRafId) { | ||
| cancelAnimationFrame(this.selectionRafId); | ||
| this.selectionRafId = null; | ||
| } | ||
| } | ||
| /** | ||
| * Update the attributes of the top-level element that holds the React component. | ||
| * Applying the attributes defined in the `attrs` option. | ||
| */ | ||
| updateElementAttributes() { | ||
| if (this.options.attrs) { | ||
| let attrsObj = {}; | ||
| if (typeof this.options.attrs === "function") { | ||
| const extensionAttributes = this.editor.extensionManager.attributes; | ||
| const HTMLAttributes = (0, import_core3.getRenderedAttributes)(this.node, extensionAttributes); | ||
| attrsObj = this.options.attrs({ node: this.node, HTMLAttributes }); | ||
| } else { | ||
| attrsObj = this.options.attrs; | ||
| } | ||
| this.renderer.updateAttributes(attrsObj); | ||
| } | ||
| } | ||
| }; | ||
| function ReactNodeViewRenderer(component, options) { | ||
| return (props) => { | ||
| if (!props.editor.contentComponent) { | ||
| return {}; | ||
| } | ||
| return new ReactNodeView(component, props, options); | ||
| }; | ||
| } | ||
| // src/Tiptap.tsx | ||
| var import_react10 = require("react"); | ||
| var import_jsx_runtime8 = require("react/jsx-runtime"); | ||
| var TiptapContext = (0, import_react10.createContext)({ | ||
| get editor() { | ||
| throw new Error("useTiptap must be used within a <Tiptap> provider"); | ||
| } | ||
| }); | ||
| TiptapContext.displayName = "TiptapContext"; | ||
| var useTiptap = () => (0, import_react10.useContext)(TiptapContext); | ||
| function useTiptapState(selector, equalityFn) { | ||
| const { editor } = useTiptap(); | ||
| return useEditorState({ | ||
| editor, | ||
| selector, | ||
| equalityFn | ||
| }); | ||
| } | ||
| function TiptapWrapper({ editor, instance, children }) { | ||
| const resolvedEditor = editor != null ? editor : instance; | ||
| if (!resolvedEditor) { | ||
| throw new Error("Tiptap: An editor instance is required. Pass a non-null `editor` prop."); | ||
| } | ||
| const tiptapContextValue = (0, import_react10.useMemo)(() => ({ editor: resolvedEditor }), [resolvedEditor]); | ||
| const legacyContextValue = (0, import_react10.useMemo)(() => ({ editor: resolvedEditor }), [resolvedEditor]); | ||
| return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditorContext.Provider, { value: legacyContextValue, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TiptapContext.Provider, { value: tiptapContextValue, children }) }); | ||
| } | ||
| TiptapWrapper.displayName = "Tiptap"; | ||
| function TiptapContent({ ...rest }) { | ||
| const { editor } = useTiptap(); | ||
| return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditorContent, { editor, ...rest }); | ||
| } | ||
| TiptapContent.displayName = "Tiptap.Content"; | ||
| var Tiptap = Object.assign(TiptapWrapper, { | ||
| /** | ||
| * The Tiptap Content component that renders the EditorContent with the editor instance from the context. | ||
| * @see TiptapContent | ||
| */ | ||
| Content: TiptapContent | ||
| }); | ||
| // src/index.ts | ||
| __reExport(index_exports, require("@tiptap/core"), module.exports); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| EditorConsumer, | ||
| EditorContent, | ||
| EditorContext, | ||
| EditorProvider, | ||
| MarkViewContent, | ||
| NodeViewContent, | ||
| NodeViewWrapper, | ||
| PureEditorContent, | ||
| ReactMarkView, | ||
| ReactMarkViewContext, | ||
| ReactMarkViewRenderer, | ||
| ReactNodeView, | ||
| ReactNodeViewContentProvider, | ||
| ReactNodeViewContext, | ||
| ReactNodeViewRenderer, | ||
| ReactRenderer, | ||
| Tiptap, | ||
| TiptapContent, | ||
| TiptapContext, | ||
| TiptapWrapper, | ||
| useCurrentEditor, | ||
| useEditor, | ||
| useEditorState, | ||
| useReactNodeView, | ||
| useTiptap, | ||
| useTiptapState, | ||
| ...require("@tiptap/core") | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
Sorry, the diff of this file is too big to display
-534
| import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| import { EditorOptions, Editor, MarkViewRendererOptions, MarkView, MarkViewProps, MarkViewRenderer, NodeViewProps, NodeViewRendererOptions, NodeView, NodeViewRendererProps, NodeViewRenderer } from '@tiptap/core'; | ||
| export * from '@tiptap/core'; | ||
| import * as React from 'react'; | ||
| import React__default, { DependencyList, ReactNode, HTMLAttributes, HTMLProps, ForwardedRef, ComponentProps, ComponentClass, FunctionComponent, ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, ComponentType as ComponentType$1 } from 'react'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { Decoration, DecorationSource } from '@tiptap/pm/view'; | ||
| /** | ||
| * The options for the `useEditor` hook. | ||
| */ | ||
| type UseEditorOptions = Partial<EditorOptions> & { | ||
| /** | ||
| * Whether to render the editor on the first render. | ||
| * If client-side rendering, set this to `true`. | ||
| * If server-side rendering, set this to `false`. | ||
| * @default true | ||
| */ | ||
| immediatelyRender?: boolean; | ||
| /** | ||
| * Whether to re-render the editor on each transaction. | ||
| * This is legacy behavior that will be removed in future versions. | ||
| * @default false | ||
| */ | ||
| shouldRerenderOnTransaction?: boolean; | ||
| }; | ||
| /** | ||
| * This hook allows you to create an editor instance. | ||
| * @param options The editor options | ||
| * @param deps The dependencies to watch for changes | ||
| * @returns The editor instance | ||
| * @example const editor = useEditor({ extensions: [...] }) | ||
| */ | ||
| declare function useEditor(options: UseEditorOptions & { | ||
| immediatelyRender: false; | ||
| }, deps?: DependencyList): Editor | null; | ||
| /** | ||
| * This hook allows you to create an editor instance. | ||
| * @param options The editor options | ||
| * @param deps The dependencies to watch for changes | ||
| * @returns The editor instance | ||
| * @example const editor = useEditor({ extensions: [...] }) | ||
| */ | ||
| declare function useEditor(options: UseEditorOptions, deps?: DependencyList): Editor; | ||
| type EditorContextValue = { | ||
| editor: Editor | null; | ||
| }; | ||
| declare const EditorContext: React__default.Context<EditorContextValue>; | ||
| declare const EditorConsumer: React__default.Consumer<EditorContextValue>; | ||
| /** | ||
| * A hook to get the current editor instance. | ||
| */ | ||
| declare const useCurrentEditor: () => EditorContextValue; | ||
| type EditorProviderProps = { | ||
| children?: ReactNode; | ||
| slotBefore?: ReactNode; | ||
| slotAfter?: ReactNode; | ||
| editorContainerProps?: HTMLAttributes<HTMLDivElement>; | ||
| } & UseEditorOptions; | ||
| /** | ||
| * This is the provider component for the editor. | ||
| * It allows the editor to be accessible across the entire component tree | ||
| * with `useCurrentEditor`. | ||
| */ | ||
| declare function EditorProvider({ children, slotAfter, slotBefore, editorContainerProps, ...editorOptions }: EditorProviderProps): react_jsx_runtime.JSX.Element | null; | ||
| interface EditorContentProps extends HTMLProps<HTMLDivElement> { | ||
| editor: Editor | null; | ||
| innerRef?: ForwardedRef<HTMLDivElement | null>; | ||
| } | ||
| declare class PureEditorContent extends React__default.Component<EditorContentProps, { | ||
| hasContentComponentInitialized: boolean; | ||
| }> { | ||
| editorContentRef: React__default.RefObject<any>; | ||
| initialized: boolean; | ||
| unsubscribeToContentComponent?: () => void; | ||
| constructor(props: EditorContentProps); | ||
| componentDidMount(): void; | ||
| componentDidUpdate(): void; | ||
| init(): void; | ||
| componentWillUnmount(): void; | ||
| render(): react_jsx_runtime.JSX.Element; | ||
| } | ||
| declare const EditorContent: React__default.NamedExoticComponent<Omit<EditorContentProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>; | ||
| type NodeViewContentProps<T extends keyof React__default.JSX.IntrinsicElements = 'div'> = { | ||
| as?: NoInfer<T>; | ||
| } & ComponentProps<T>; | ||
| declare function NodeViewContent<T extends keyof React__default.JSX.IntrinsicElements = 'div'>({ as: Tag, ...props }: NodeViewContentProps<T>): react_jsx_runtime.JSX.Element; | ||
| interface NodeViewWrapperProps { | ||
| [key: string]: any; | ||
| as?: React__default.ElementType; | ||
| } | ||
| declare const NodeViewWrapper: React__default.FC<NodeViewWrapperProps>; | ||
| interface ReactRendererOptions { | ||
| /** | ||
| * The editor instance. | ||
| * @type {Editor} | ||
| */ | ||
| editor: Editor; | ||
| /** | ||
| * The props for the component. | ||
| * @type {Record<string, any>} | ||
| * @default {} | ||
| */ | ||
| props?: Record<string, any>; | ||
| /** | ||
| * The tag name of the element. | ||
| * @type {string} | ||
| * @default 'div' | ||
| */ | ||
| as?: string; | ||
| /** | ||
| * The class name of the element. | ||
| * @type {string} | ||
| * @default '' | ||
| * @example 'foo bar' | ||
| */ | ||
| className?: string; | ||
| } | ||
| type ComponentType<R, P> = ComponentClass<P> | FunctionComponent<P> | ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<R>>; | ||
| /** | ||
| * The ReactRenderer class. It's responsible for rendering React components inside the editor. | ||
| * @example | ||
| * new ReactRenderer(MyComponent, { | ||
| * editor, | ||
| * props: { | ||
| * foo: 'bar', | ||
| * }, | ||
| * as: 'span', | ||
| * }) | ||
| */ | ||
| declare class ReactRenderer<R = unknown, P extends Record<string, any> = object> { | ||
| id: string; | ||
| editor: Editor; | ||
| component: any; | ||
| element: HTMLElement; | ||
| props: P; | ||
| reactElement: ReactNode; | ||
| ref: R | null; | ||
| /** | ||
| * Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown. | ||
| */ | ||
| destroyed: boolean; | ||
| /** | ||
| * Immediately creates element and renders the provided React component. | ||
| */ | ||
| constructor(component: ComponentType<R, P>, { editor, props, as, className }: ReactRendererOptions); | ||
| /** | ||
| * Render the React component. | ||
| */ | ||
| render(): void; | ||
| /** | ||
| * Re-renders the React component with new props. | ||
| */ | ||
| updateProps(props?: Record<string, any>): void; | ||
| /** | ||
| * Destroy the React component. | ||
| */ | ||
| destroy(): void; | ||
| /** | ||
| * Update the attributes of the element that holds the React component. | ||
| */ | ||
| updateAttributes(attributes: Record<string, string>): void; | ||
| } | ||
| interface MarkViewContextProps { | ||
| markViewContentRef: (element: HTMLElement | null) => void; | ||
| } | ||
| declare const ReactMarkViewContext: React__default.Context<MarkViewContextProps>; | ||
| type MarkViewContentProps<T extends keyof React__default.JSX.IntrinsicElements = 'span'> = { | ||
| as?: T; | ||
| } & Omit<React__default.ComponentProps<T>, 'as'>; | ||
| declare const MarkViewContent: <T extends keyof React__default.JSX.IntrinsicElements = "span">(props: MarkViewContentProps<T>) => react_jsx_runtime.JSX.Element; | ||
| interface ReactMarkViewRendererOptions extends MarkViewRendererOptions { | ||
| /** | ||
| * The tag name of the element wrapping the React component. | ||
| */ | ||
| as?: string; | ||
| className?: string; | ||
| attrs?: { | ||
| [key: string]: string; | ||
| }; | ||
| } | ||
| declare class ReactMarkView extends MarkView<React__default.ComponentType<MarkViewProps>, ReactMarkViewRendererOptions> { | ||
| renderer: ReactRenderer; | ||
| contentDOMElement: HTMLElement; | ||
| constructor(component: React__default.ComponentType<MarkViewProps>, props: MarkViewProps, options?: Partial<ReactMarkViewRendererOptions>); | ||
| get dom(): HTMLElement; | ||
| get contentDOM(): HTMLElement; | ||
| } | ||
| declare function ReactMarkViewRenderer(component: React__default.ComponentType<MarkViewProps>, options?: Partial<ReactMarkViewRendererOptions>): MarkViewRenderer; | ||
| type ReactNodeViewProps<T = HTMLElement> = NodeViewProps & { | ||
| ref: React__default.RefObject<T | null>; | ||
| }; | ||
| interface ReactNodeViewRendererOptions extends NodeViewRendererOptions { | ||
| /** | ||
| * This function is called when the node view is updated. | ||
| * It allows you to compare the old node with the new node and decide if the component should update. | ||
| */ | ||
| update: ((props: { | ||
| oldNode: Node; | ||
| oldDecorations: readonly Decoration[]; | ||
| oldInnerDecorations: DecorationSource; | ||
| newNode: Node; | ||
| newDecorations: readonly Decoration[]; | ||
| innerDecorations: DecorationSource; | ||
| updateProps: () => void; | ||
| }) => boolean) | null; | ||
| /** | ||
| * The tag name of the element wrapping the React component. | ||
| */ | ||
| as?: string; | ||
| /** | ||
| * The class name of the element wrapping the React component. | ||
| */ | ||
| className?: string; | ||
| /** | ||
| * Attributes that should be applied to the element wrapping the React component. | ||
| * If this is a function, it will be called each time the node view is updated. | ||
| * If this is an object, it will be applied once when the node view is mounted. | ||
| */ | ||
| attrs?: Record<string, string> | ((props: { | ||
| node: Node; | ||
| HTMLAttributes: Record<string, any>; | ||
| }) => Record<string, string>); | ||
| } | ||
| declare class ReactNodeView<T = HTMLElement, Component extends ComponentType$1<ReactNodeViewProps<T>> = ComponentType$1<ReactNodeViewProps<T>>, NodeEditor extends Editor = Editor, Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions> extends NodeView<Component, NodeEditor, Options> { | ||
| /** | ||
| * The renderer instance. | ||
| */ | ||
| renderer: ReactRenderer<unknown, ReactNodeViewProps<T>>; | ||
| /** | ||
| * The element that holds the rich-text content of the node. | ||
| */ | ||
| contentDOMElement: HTMLElement | null; | ||
| /** | ||
| * The requestAnimationFrame ID used for selection updates. | ||
| */ | ||
| selectionRafId: number | null; | ||
| constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>); | ||
| private cachedExtensionWithSyncedStorage; | ||
| /** | ||
| * Returns a proxy of the extension that redirects storage access to the editor's mutable storage. | ||
| * This preserves the original prototype chain (instanceof checks, methods like configure/extend work). | ||
| * Cached to avoid proxy creation on every update. | ||
| */ | ||
| get extensionWithSyncedStorage(): NodeViewRendererProps['extension']; | ||
| /** | ||
| * Setup the React component. | ||
| * Called on initialization. | ||
| */ | ||
| mount(): void; | ||
| /** | ||
| * Return the DOM element. | ||
| * This is the element that will be used to display the node view. | ||
| */ | ||
| get dom(): HTMLElement; | ||
| /** | ||
| * Return the content DOM element. | ||
| * This is the element that will be used to display the rich-text content of the node. | ||
| */ | ||
| get contentDOM(): HTMLElement | null; | ||
| /** | ||
| * On editor selection update, check if the node is selected. | ||
| * If it is, call `selectNode`, otherwise call `deselectNode`. | ||
| */ | ||
| handleSelectionUpdate(): void; | ||
| /** | ||
| * On update, update the React component. | ||
| * To prevent unnecessary updates, the `update` option can be used. | ||
| */ | ||
| update(node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource): boolean; | ||
| /** | ||
| * Select the node. | ||
| * Add the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| selectNode(): void; | ||
| /** | ||
| * Deselect the node. | ||
| * Remove the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| deselectNode(): void; | ||
| /** | ||
| * Destroy the React component instance. | ||
| */ | ||
| destroy(): void; | ||
| /** | ||
| * Update the attributes of the top-level element that holds the React component. | ||
| * Applying the attributes defined in the `attrs` option. | ||
| */ | ||
| updateElementAttributes(): void; | ||
| } | ||
| /** | ||
| * Create a React node view renderer. | ||
| */ | ||
| declare function ReactNodeViewRenderer<T = HTMLElement>(component: ComponentType$1<ReactNodeViewProps<T>>, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer; | ||
| /** | ||
| * The shape of the React context used by the `<Tiptap />` components. | ||
| * | ||
| * The editor instance is always available when using the default `useEditor` | ||
| * configuration. For SSR scenarios where `immediatelyRender: false` is used, | ||
| * consider using the legacy `EditorProvider` pattern instead. | ||
| */ | ||
| type TiptapContextType = { | ||
| /** The Tiptap editor instance. */ | ||
| editor: Editor; | ||
| }; | ||
| /** | ||
| * React context that stores the current editor instance. | ||
| * | ||
| * Use `useTiptap()` to read from this context in child components. | ||
| */ | ||
| declare const TiptapContext: React.Context<TiptapContextType>; | ||
| /** | ||
| * Hook to read the Tiptap context and access the editor instance. | ||
| * | ||
| * This is a small convenience wrapper around `useContext(TiptapContext)`. | ||
| * The editor is always available when used within a `<Tiptap>` provider. | ||
| * | ||
| * @returns The current `TiptapContextType` value from the provider. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * import { useTiptap } from '@tiptap/react' | ||
| * | ||
| * function Toolbar() { | ||
| * const { editor } = useTiptap() | ||
| * | ||
| * return ( | ||
| * <button onClick={() => editor.chain().focus().toggleBold().run()}> | ||
| * Bold | ||
| * </button> | ||
| * ) | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare const useTiptap: () => TiptapContextType; | ||
| /** | ||
| * Select a slice of the editor state using the context-provided editor. | ||
| * | ||
| * This is a thin wrapper around `useEditorState` that reads the `editor` | ||
| * instance from `useTiptap()` so callers don't have to pass it manually. | ||
| * | ||
| * @typeParam TSelectorResult - The type returned by the selector. | ||
| * @param selector - Function that receives the editor state snapshot and | ||
| * returns the piece of state you want to subscribe to. | ||
| * @param equalityFn - Optional function to compare previous/next selected | ||
| * values and avoid unnecessary updates. | ||
| * @returns The selected slice of the editor state. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * function WordCount() { | ||
| * const wordCount = useTiptapState(state => { | ||
| * const text = state.editor.state.doc.textContent | ||
| * return text.split(/\s+/).filter(Boolean).length | ||
| * }) | ||
| * | ||
| * return <span>{wordCount} words</span> | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare function useTiptapState<TSelectorResult>(selector: (context: EditorStateSnapshot<Editor>) => TSelectorResult, equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean): TSelectorResult; | ||
| /** | ||
| * Props for the `Tiptap` root/provider component. | ||
| */ | ||
| type TiptapWrapperProps = { | ||
| /** | ||
| * The editor instance to provide to child components. | ||
| * Use `useEditor()` to create this instance. | ||
| */ | ||
| editor?: Editor; | ||
| /** | ||
| * @deprecated Use `editor` instead. Will be removed in the next major version. | ||
| */ | ||
| instance?: Editor; | ||
| children: ReactNode; | ||
| }; | ||
| /** | ||
| * Top-level provider component that makes the editor instance available via | ||
| * React context to all child components. | ||
| * | ||
| * This component also provides backwards compatibility with the legacy | ||
| * `EditorContext`, so components using `useCurrentEditor()` will work | ||
| * inside a `<Tiptap>` provider. | ||
| * | ||
| * @param props - Component props. | ||
| * @returns A context provider element wrapping `children`. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * import { Tiptap, useEditor } from '@tiptap/react' | ||
| * | ||
| * function App() { | ||
| * const editor = useEditor({ extensions: [...] }) | ||
| * | ||
| * return ( | ||
| * <Tiptap editor={editor}> | ||
| * <Toolbar /> | ||
| * <Tiptap.Content /> | ||
| * </Tiptap> | ||
| * ) | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare function TiptapWrapper({ editor, instance, children }: TiptapWrapperProps): react_jsx_runtime.JSX.Element; | ||
| declare namespace TiptapWrapper { | ||
| var displayName: string; | ||
| } | ||
| /** | ||
| * Convenience component that renders `EditorContent` using the context-provided | ||
| * editor instance. Use this instead of manually passing the `editor` prop. | ||
| * | ||
| * @param props - All `EditorContent` props except `editor` and `ref`. | ||
| * @returns An `EditorContent` element bound to the context editor. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * // inside a Tiptap provider | ||
| * <Tiptap.Content className="editor" /> | ||
| * ``` | ||
| */ | ||
| declare function TiptapContent({ ...rest }: Omit<EditorContentProps, 'editor' | 'ref'>): react_jsx_runtime.JSX.Element; | ||
| declare namespace TiptapContent { | ||
| var displayName: string; | ||
| } | ||
| /** | ||
| * Root `Tiptap` component. Use it as the provider for all child components. | ||
| * | ||
| * The exported object includes the `Content` subcomponent for rendering the | ||
| * editor content area. | ||
| * | ||
| * This component provides both the new `TiptapContext` (accessed via `useTiptap()`) | ||
| * and the legacy `EditorContext` (accessed via `useCurrentEditor()`) for | ||
| * backwards compatibility. | ||
| * | ||
| * For bubble menus and floating menus, import them separately from | ||
| * `@tiptap/react/menus` to keep floating-ui as an optional dependency. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * import { Tiptap, useEditor } from '@tiptap/react' | ||
| * import { BubbleMenu } from '@tiptap/react/menus' | ||
| * | ||
| * function App() { | ||
| * const editor = useEditor({ extensions: [...] }) | ||
| * | ||
| * return ( | ||
| * <Tiptap editor={editor}> | ||
| * <Tiptap.Content /> | ||
| * <BubbleMenu> | ||
| * <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button> | ||
| * </BubbleMenu> | ||
| * </Tiptap> | ||
| * ) | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare const Tiptap: typeof TiptapWrapper & { | ||
| /** | ||
| * The Tiptap Content component that renders the EditorContent with the editor instance from the context. | ||
| * @see TiptapContent | ||
| */ | ||
| Content: typeof TiptapContent; | ||
| }; | ||
| type EditorStateSnapshot<TEditor extends Editor | null = Editor | null> = { | ||
| editor: TEditor; | ||
| transactionNumber: number; | ||
| }; | ||
| type UseEditorStateOptions<TSelectorResult, TEditor extends Editor | null = Editor | null> = { | ||
| /** | ||
| * The editor instance. | ||
| */ | ||
| editor: TEditor; | ||
| /** | ||
| * A selector function to determine the value to compare for re-rendering. | ||
| */ | ||
| selector: (context: EditorStateSnapshot<TEditor>) => TSelectorResult; | ||
| /** | ||
| * A custom equality function to determine if the editor should re-render. | ||
| * @default `deepEqual` from `fast-deep-equal` | ||
| */ | ||
| equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean; | ||
| }; | ||
| /** | ||
| * This hook allows you to watch for changes on the editor instance. | ||
| * It will allow you to select a part of the editor state and re-render the component when it changes. | ||
| * @example | ||
| * ```tsx | ||
| * const editor = useEditor({...options}) | ||
| * const { currentSelection } = useEditorState({ | ||
| * editor, | ||
| * selector: snapshot => ({ currentSelection: snapshot.editor.state.selection }), | ||
| * }) | ||
| */ | ||
| declare function useEditorState<TSelectorResult>(options: UseEditorStateOptions<TSelectorResult, Editor>): TSelectorResult; | ||
| /** | ||
| * This hook allows you to watch for changes on the editor instance. | ||
| * It will allow you to select a part of the editor state and re-render the component when it changes. | ||
| * @example | ||
| * ```tsx | ||
| * const editor = useEditor({...options}) | ||
| * const { currentSelection } = useEditorState({ | ||
| * editor, | ||
| * selector: snapshot => ({ currentSelection: snapshot.editor.state.selection }), | ||
| * }) | ||
| */ | ||
| declare function useEditorState<TSelectorResult>(options: UseEditorStateOptions<TSelectorResult, Editor | null>): TSelectorResult | null; | ||
| interface ReactNodeViewContextProps { | ||
| onDragStart?: (event: DragEvent) => void; | ||
| nodeViewContentRef?: (element: HTMLElement | null) => void; | ||
| /** | ||
| * This allows you to add children into the NodeViewContent component. | ||
| * This is useful when statically rendering the content of a node view. | ||
| */ | ||
| nodeViewContentChildren?: ReactNode; | ||
| } | ||
| declare const ReactNodeViewContext: React.Context<ReactNodeViewContextProps>; | ||
| declare const ReactNodeViewContentProvider: ({ children, content }: { | ||
| children: ReactNode; | ||
| content: ReactNode; | ||
| }) => React.FunctionComponentElement<React.ProviderProps<ReactNodeViewContextProps>>; | ||
| declare const useReactNodeView: () => ReactNodeViewContextProps; | ||
| export { EditorConsumer, EditorContent, type EditorContentProps, EditorContext, type EditorContextValue, EditorProvider, type EditorProviderProps, type EditorStateSnapshot, MarkViewContent, type MarkViewContentProps, type MarkViewContextProps, NodeViewContent, type NodeViewContentProps, NodeViewWrapper, type NodeViewWrapperProps, PureEditorContent, ReactMarkView, ReactMarkViewContext, ReactMarkViewRenderer, type ReactMarkViewRendererOptions, ReactNodeView, ReactNodeViewContentProvider, ReactNodeViewContext, type ReactNodeViewContextProps, type ReactNodeViewProps, ReactNodeViewRenderer, type ReactNodeViewRendererOptions, ReactRenderer, type ReactRendererOptions, Tiptap, TiptapContent, TiptapContext, type TiptapContextType, TiptapWrapper, type TiptapWrapperProps, type UseEditorOptions, type UseEditorStateOptions, useCurrentEditor, useEditor, useEditorState, useReactNodeView, useTiptap, useTiptapState }; |
-534
| import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| import { EditorOptions, Editor, MarkViewRendererOptions, MarkView, MarkViewProps, MarkViewRenderer, NodeViewProps, NodeViewRendererOptions, NodeView, NodeViewRendererProps, NodeViewRenderer } from '@tiptap/core'; | ||
| export * from '@tiptap/core'; | ||
| import * as React from 'react'; | ||
| import React__default, { DependencyList, ReactNode, HTMLAttributes, HTMLProps, ForwardedRef, ComponentProps, ComponentClass, FunctionComponent, ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, ComponentType as ComponentType$1 } from 'react'; | ||
| import { Node } from '@tiptap/pm/model'; | ||
| import { Decoration, DecorationSource } from '@tiptap/pm/view'; | ||
| /** | ||
| * The options for the `useEditor` hook. | ||
| */ | ||
| type UseEditorOptions = Partial<EditorOptions> & { | ||
| /** | ||
| * Whether to render the editor on the first render. | ||
| * If client-side rendering, set this to `true`. | ||
| * If server-side rendering, set this to `false`. | ||
| * @default true | ||
| */ | ||
| immediatelyRender?: boolean; | ||
| /** | ||
| * Whether to re-render the editor on each transaction. | ||
| * This is legacy behavior that will be removed in future versions. | ||
| * @default false | ||
| */ | ||
| shouldRerenderOnTransaction?: boolean; | ||
| }; | ||
| /** | ||
| * This hook allows you to create an editor instance. | ||
| * @param options The editor options | ||
| * @param deps The dependencies to watch for changes | ||
| * @returns The editor instance | ||
| * @example const editor = useEditor({ extensions: [...] }) | ||
| */ | ||
| declare function useEditor(options: UseEditorOptions & { | ||
| immediatelyRender: false; | ||
| }, deps?: DependencyList): Editor | null; | ||
| /** | ||
| * This hook allows you to create an editor instance. | ||
| * @param options The editor options | ||
| * @param deps The dependencies to watch for changes | ||
| * @returns The editor instance | ||
| * @example const editor = useEditor({ extensions: [...] }) | ||
| */ | ||
| declare function useEditor(options: UseEditorOptions, deps?: DependencyList): Editor; | ||
| type EditorContextValue = { | ||
| editor: Editor | null; | ||
| }; | ||
| declare const EditorContext: React__default.Context<EditorContextValue>; | ||
| declare const EditorConsumer: React__default.Consumer<EditorContextValue>; | ||
| /** | ||
| * A hook to get the current editor instance. | ||
| */ | ||
| declare const useCurrentEditor: () => EditorContextValue; | ||
| type EditorProviderProps = { | ||
| children?: ReactNode; | ||
| slotBefore?: ReactNode; | ||
| slotAfter?: ReactNode; | ||
| editorContainerProps?: HTMLAttributes<HTMLDivElement>; | ||
| } & UseEditorOptions; | ||
| /** | ||
| * This is the provider component for the editor. | ||
| * It allows the editor to be accessible across the entire component tree | ||
| * with `useCurrentEditor`. | ||
| */ | ||
| declare function EditorProvider({ children, slotAfter, slotBefore, editorContainerProps, ...editorOptions }: EditorProviderProps): react_jsx_runtime.JSX.Element | null; | ||
| interface EditorContentProps extends HTMLProps<HTMLDivElement> { | ||
| editor: Editor | null; | ||
| innerRef?: ForwardedRef<HTMLDivElement | null>; | ||
| } | ||
| declare class PureEditorContent extends React__default.Component<EditorContentProps, { | ||
| hasContentComponentInitialized: boolean; | ||
| }> { | ||
| editorContentRef: React__default.RefObject<any>; | ||
| initialized: boolean; | ||
| unsubscribeToContentComponent?: () => void; | ||
| constructor(props: EditorContentProps); | ||
| componentDidMount(): void; | ||
| componentDidUpdate(): void; | ||
| init(): void; | ||
| componentWillUnmount(): void; | ||
| render(): react_jsx_runtime.JSX.Element; | ||
| } | ||
| declare const EditorContent: React__default.NamedExoticComponent<Omit<EditorContentProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>; | ||
| type NodeViewContentProps<T extends keyof React__default.JSX.IntrinsicElements = 'div'> = { | ||
| as?: NoInfer<T>; | ||
| } & ComponentProps<T>; | ||
| declare function NodeViewContent<T extends keyof React__default.JSX.IntrinsicElements = 'div'>({ as: Tag, ...props }: NodeViewContentProps<T>): react_jsx_runtime.JSX.Element; | ||
| interface NodeViewWrapperProps { | ||
| [key: string]: any; | ||
| as?: React__default.ElementType; | ||
| } | ||
| declare const NodeViewWrapper: React__default.FC<NodeViewWrapperProps>; | ||
| interface ReactRendererOptions { | ||
| /** | ||
| * The editor instance. | ||
| * @type {Editor} | ||
| */ | ||
| editor: Editor; | ||
| /** | ||
| * The props for the component. | ||
| * @type {Record<string, any>} | ||
| * @default {} | ||
| */ | ||
| props?: Record<string, any>; | ||
| /** | ||
| * The tag name of the element. | ||
| * @type {string} | ||
| * @default 'div' | ||
| */ | ||
| as?: string; | ||
| /** | ||
| * The class name of the element. | ||
| * @type {string} | ||
| * @default '' | ||
| * @example 'foo bar' | ||
| */ | ||
| className?: string; | ||
| } | ||
| type ComponentType<R, P> = ComponentClass<P> | FunctionComponent<P> | ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<R>>; | ||
| /** | ||
| * The ReactRenderer class. It's responsible for rendering React components inside the editor. | ||
| * @example | ||
| * new ReactRenderer(MyComponent, { | ||
| * editor, | ||
| * props: { | ||
| * foo: 'bar', | ||
| * }, | ||
| * as: 'span', | ||
| * }) | ||
| */ | ||
| declare class ReactRenderer<R = unknown, P extends Record<string, any> = object> { | ||
| id: string; | ||
| editor: Editor; | ||
| component: any; | ||
| element: HTMLElement; | ||
| props: P; | ||
| reactElement: ReactNode; | ||
| ref: R | null; | ||
| /** | ||
| * Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown. | ||
| */ | ||
| destroyed: boolean; | ||
| /** | ||
| * Immediately creates element and renders the provided React component. | ||
| */ | ||
| constructor(component: ComponentType<R, P>, { editor, props, as, className }: ReactRendererOptions); | ||
| /** | ||
| * Render the React component. | ||
| */ | ||
| render(): void; | ||
| /** | ||
| * Re-renders the React component with new props. | ||
| */ | ||
| updateProps(props?: Record<string, any>): void; | ||
| /** | ||
| * Destroy the React component. | ||
| */ | ||
| destroy(): void; | ||
| /** | ||
| * Update the attributes of the element that holds the React component. | ||
| */ | ||
| updateAttributes(attributes: Record<string, string>): void; | ||
| } | ||
| interface MarkViewContextProps { | ||
| markViewContentRef: (element: HTMLElement | null) => void; | ||
| } | ||
| declare const ReactMarkViewContext: React__default.Context<MarkViewContextProps>; | ||
| type MarkViewContentProps<T extends keyof React__default.JSX.IntrinsicElements = 'span'> = { | ||
| as?: T; | ||
| } & Omit<React__default.ComponentProps<T>, 'as'>; | ||
| declare const MarkViewContent: <T extends keyof React__default.JSX.IntrinsicElements = "span">(props: MarkViewContentProps<T>) => react_jsx_runtime.JSX.Element; | ||
| interface ReactMarkViewRendererOptions extends MarkViewRendererOptions { | ||
| /** | ||
| * The tag name of the element wrapping the React component. | ||
| */ | ||
| as?: string; | ||
| className?: string; | ||
| attrs?: { | ||
| [key: string]: string; | ||
| }; | ||
| } | ||
| declare class ReactMarkView extends MarkView<React__default.ComponentType<MarkViewProps>, ReactMarkViewRendererOptions> { | ||
| renderer: ReactRenderer; | ||
| contentDOMElement: HTMLElement; | ||
| constructor(component: React__default.ComponentType<MarkViewProps>, props: MarkViewProps, options?: Partial<ReactMarkViewRendererOptions>); | ||
| get dom(): HTMLElement; | ||
| get contentDOM(): HTMLElement; | ||
| } | ||
| declare function ReactMarkViewRenderer(component: React__default.ComponentType<MarkViewProps>, options?: Partial<ReactMarkViewRendererOptions>): MarkViewRenderer; | ||
| type ReactNodeViewProps<T = HTMLElement> = NodeViewProps & { | ||
| ref: React__default.RefObject<T | null>; | ||
| }; | ||
| interface ReactNodeViewRendererOptions extends NodeViewRendererOptions { | ||
| /** | ||
| * This function is called when the node view is updated. | ||
| * It allows you to compare the old node with the new node and decide if the component should update. | ||
| */ | ||
| update: ((props: { | ||
| oldNode: Node; | ||
| oldDecorations: readonly Decoration[]; | ||
| oldInnerDecorations: DecorationSource; | ||
| newNode: Node; | ||
| newDecorations: readonly Decoration[]; | ||
| innerDecorations: DecorationSource; | ||
| updateProps: () => void; | ||
| }) => boolean) | null; | ||
| /** | ||
| * The tag name of the element wrapping the React component. | ||
| */ | ||
| as?: string; | ||
| /** | ||
| * The class name of the element wrapping the React component. | ||
| */ | ||
| className?: string; | ||
| /** | ||
| * Attributes that should be applied to the element wrapping the React component. | ||
| * If this is a function, it will be called each time the node view is updated. | ||
| * If this is an object, it will be applied once when the node view is mounted. | ||
| */ | ||
| attrs?: Record<string, string> | ((props: { | ||
| node: Node; | ||
| HTMLAttributes: Record<string, any>; | ||
| }) => Record<string, string>); | ||
| } | ||
| declare class ReactNodeView<T = HTMLElement, Component extends ComponentType$1<ReactNodeViewProps<T>> = ComponentType$1<ReactNodeViewProps<T>>, NodeEditor extends Editor = Editor, Options extends ReactNodeViewRendererOptions = ReactNodeViewRendererOptions> extends NodeView<Component, NodeEditor, Options> { | ||
| /** | ||
| * The renderer instance. | ||
| */ | ||
| renderer: ReactRenderer<unknown, ReactNodeViewProps<T>>; | ||
| /** | ||
| * The element that holds the rich-text content of the node. | ||
| */ | ||
| contentDOMElement: HTMLElement | null; | ||
| /** | ||
| * The requestAnimationFrame ID used for selection updates. | ||
| */ | ||
| selectionRafId: number | null; | ||
| constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>); | ||
| private cachedExtensionWithSyncedStorage; | ||
| /** | ||
| * Returns a proxy of the extension that redirects storage access to the editor's mutable storage. | ||
| * This preserves the original prototype chain (instanceof checks, methods like configure/extend work). | ||
| * Cached to avoid proxy creation on every update. | ||
| */ | ||
| get extensionWithSyncedStorage(): NodeViewRendererProps['extension']; | ||
| /** | ||
| * Setup the React component. | ||
| * Called on initialization. | ||
| */ | ||
| mount(): void; | ||
| /** | ||
| * Return the DOM element. | ||
| * This is the element that will be used to display the node view. | ||
| */ | ||
| get dom(): HTMLElement; | ||
| /** | ||
| * Return the content DOM element. | ||
| * This is the element that will be used to display the rich-text content of the node. | ||
| */ | ||
| get contentDOM(): HTMLElement | null; | ||
| /** | ||
| * On editor selection update, check if the node is selected. | ||
| * If it is, call `selectNode`, otherwise call `deselectNode`. | ||
| */ | ||
| handleSelectionUpdate(): void; | ||
| /** | ||
| * On update, update the React component. | ||
| * To prevent unnecessary updates, the `update` option can be used. | ||
| */ | ||
| update(node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource): boolean; | ||
| /** | ||
| * Select the node. | ||
| * Add the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| selectNode(): void; | ||
| /** | ||
| * Deselect the node. | ||
| * Remove the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| deselectNode(): void; | ||
| /** | ||
| * Destroy the React component instance. | ||
| */ | ||
| destroy(): void; | ||
| /** | ||
| * Update the attributes of the top-level element that holds the React component. | ||
| * Applying the attributes defined in the `attrs` option. | ||
| */ | ||
| updateElementAttributes(): void; | ||
| } | ||
| /** | ||
| * Create a React node view renderer. | ||
| */ | ||
| declare function ReactNodeViewRenderer<T = HTMLElement>(component: ComponentType$1<ReactNodeViewProps<T>>, options?: Partial<ReactNodeViewRendererOptions>): NodeViewRenderer; | ||
| /** | ||
| * The shape of the React context used by the `<Tiptap />` components. | ||
| * | ||
| * The editor instance is always available when using the default `useEditor` | ||
| * configuration. For SSR scenarios where `immediatelyRender: false` is used, | ||
| * consider using the legacy `EditorProvider` pattern instead. | ||
| */ | ||
| type TiptapContextType = { | ||
| /** The Tiptap editor instance. */ | ||
| editor: Editor; | ||
| }; | ||
| /** | ||
| * React context that stores the current editor instance. | ||
| * | ||
| * Use `useTiptap()` to read from this context in child components. | ||
| */ | ||
| declare const TiptapContext: React.Context<TiptapContextType>; | ||
| /** | ||
| * Hook to read the Tiptap context and access the editor instance. | ||
| * | ||
| * This is a small convenience wrapper around `useContext(TiptapContext)`. | ||
| * The editor is always available when used within a `<Tiptap>` provider. | ||
| * | ||
| * @returns The current `TiptapContextType` value from the provider. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * import { useTiptap } from '@tiptap/react' | ||
| * | ||
| * function Toolbar() { | ||
| * const { editor } = useTiptap() | ||
| * | ||
| * return ( | ||
| * <button onClick={() => editor.chain().focus().toggleBold().run()}> | ||
| * Bold | ||
| * </button> | ||
| * ) | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare const useTiptap: () => TiptapContextType; | ||
| /** | ||
| * Select a slice of the editor state using the context-provided editor. | ||
| * | ||
| * This is a thin wrapper around `useEditorState` that reads the `editor` | ||
| * instance from `useTiptap()` so callers don't have to pass it manually. | ||
| * | ||
| * @typeParam TSelectorResult - The type returned by the selector. | ||
| * @param selector - Function that receives the editor state snapshot and | ||
| * returns the piece of state you want to subscribe to. | ||
| * @param equalityFn - Optional function to compare previous/next selected | ||
| * values and avoid unnecessary updates. | ||
| * @returns The selected slice of the editor state. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * function WordCount() { | ||
| * const wordCount = useTiptapState(state => { | ||
| * const text = state.editor.state.doc.textContent | ||
| * return text.split(/\s+/).filter(Boolean).length | ||
| * }) | ||
| * | ||
| * return <span>{wordCount} words</span> | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare function useTiptapState<TSelectorResult>(selector: (context: EditorStateSnapshot<Editor>) => TSelectorResult, equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean): TSelectorResult; | ||
| /** | ||
| * Props for the `Tiptap` root/provider component. | ||
| */ | ||
| type TiptapWrapperProps = { | ||
| /** | ||
| * The editor instance to provide to child components. | ||
| * Use `useEditor()` to create this instance. | ||
| */ | ||
| editor?: Editor; | ||
| /** | ||
| * @deprecated Use `editor` instead. Will be removed in the next major version. | ||
| */ | ||
| instance?: Editor; | ||
| children: ReactNode; | ||
| }; | ||
| /** | ||
| * Top-level provider component that makes the editor instance available via | ||
| * React context to all child components. | ||
| * | ||
| * This component also provides backwards compatibility with the legacy | ||
| * `EditorContext`, so components using `useCurrentEditor()` will work | ||
| * inside a `<Tiptap>` provider. | ||
| * | ||
| * @param props - Component props. | ||
| * @returns A context provider element wrapping `children`. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * import { Tiptap, useEditor } from '@tiptap/react' | ||
| * | ||
| * function App() { | ||
| * const editor = useEditor({ extensions: [...] }) | ||
| * | ||
| * return ( | ||
| * <Tiptap editor={editor}> | ||
| * <Toolbar /> | ||
| * <Tiptap.Content /> | ||
| * </Tiptap> | ||
| * ) | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare function TiptapWrapper({ editor, instance, children }: TiptapWrapperProps): react_jsx_runtime.JSX.Element; | ||
| declare namespace TiptapWrapper { | ||
| var displayName: string; | ||
| } | ||
| /** | ||
| * Convenience component that renders `EditorContent` using the context-provided | ||
| * editor instance. Use this instead of manually passing the `editor` prop. | ||
| * | ||
| * @param props - All `EditorContent` props except `editor` and `ref`. | ||
| * @returns An `EditorContent` element bound to the context editor. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * // inside a Tiptap provider | ||
| * <Tiptap.Content className="editor" /> | ||
| * ``` | ||
| */ | ||
| declare function TiptapContent({ ...rest }: Omit<EditorContentProps, 'editor' | 'ref'>): react_jsx_runtime.JSX.Element; | ||
| declare namespace TiptapContent { | ||
| var displayName: string; | ||
| } | ||
| /** | ||
| * Root `Tiptap` component. Use it as the provider for all child components. | ||
| * | ||
| * The exported object includes the `Content` subcomponent for rendering the | ||
| * editor content area. | ||
| * | ||
| * This component provides both the new `TiptapContext` (accessed via `useTiptap()`) | ||
| * and the legacy `EditorContext` (accessed via `useCurrentEditor()`) for | ||
| * backwards compatibility. | ||
| * | ||
| * For bubble menus and floating menus, import them separately from | ||
| * `@tiptap/react/menus` to keep floating-ui as an optional dependency. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * import { Tiptap, useEditor } from '@tiptap/react' | ||
| * import { BubbleMenu } from '@tiptap/react/menus' | ||
| * | ||
| * function App() { | ||
| * const editor = useEditor({ extensions: [...] }) | ||
| * | ||
| * return ( | ||
| * <Tiptap editor={editor}> | ||
| * <Tiptap.Content /> | ||
| * <BubbleMenu> | ||
| * <button onClick={() => editor.chain().focus().toggleBold().run()}>Bold</button> | ||
| * </BubbleMenu> | ||
| * </Tiptap> | ||
| * ) | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare const Tiptap: typeof TiptapWrapper & { | ||
| /** | ||
| * The Tiptap Content component that renders the EditorContent with the editor instance from the context. | ||
| * @see TiptapContent | ||
| */ | ||
| Content: typeof TiptapContent; | ||
| }; | ||
| type EditorStateSnapshot<TEditor extends Editor | null = Editor | null> = { | ||
| editor: TEditor; | ||
| transactionNumber: number; | ||
| }; | ||
| type UseEditorStateOptions<TSelectorResult, TEditor extends Editor | null = Editor | null> = { | ||
| /** | ||
| * The editor instance. | ||
| */ | ||
| editor: TEditor; | ||
| /** | ||
| * A selector function to determine the value to compare for re-rendering. | ||
| */ | ||
| selector: (context: EditorStateSnapshot<TEditor>) => TSelectorResult; | ||
| /** | ||
| * A custom equality function to determine if the editor should re-render. | ||
| * @default `deepEqual` from `fast-deep-equal` | ||
| */ | ||
| equalityFn?: (a: TSelectorResult, b: TSelectorResult | null) => boolean; | ||
| }; | ||
| /** | ||
| * This hook allows you to watch for changes on the editor instance. | ||
| * It will allow you to select a part of the editor state and re-render the component when it changes. | ||
| * @example | ||
| * ```tsx | ||
| * const editor = useEditor({...options}) | ||
| * const { currentSelection } = useEditorState({ | ||
| * editor, | ||
| * selector: snapshot => ({ currentSelection: snapshot.editor.state.selection }), | ||
| * }) | ||
| */ | ||
| declare function useEditorState<TSelectorResult>(options: UseEditorStateOptions<TSelectorResult, Editor>): TSelectorResult; | ||
| /** | ||
| * This hook allows you to watch for changes on the editor instance. | ||
| * It will allow you to select a part of the editor state and re-render the component when it changes. | ||
| * @example | ||
| * ```tsx | ||
| * const editor = useEditor({...options}) | ||
| * const { currentSelection } = useEditorState({ | ||
| * editor, | ||
| * selector: snapshot => ({ currentSelection: snapshot.editor.state.selection }), | ||
| * }) | ||
| */ | ||
| declare function useEditorState<TSelectorResult>(options: UseEditorStateOptions<TSelectorResult, Editor | null>): TSelectorResult | null; | ||
| interface ReactNodeViewContextProps { | ||
| onDragStart?: (event: DragEvent) => void; | ||
| nodeViewContentRef?: (element: HTMLElement | null) => void; | ||
| /** | ||
| * This allows you to add children into the NodeViewContent component. | ||
| * This is useful when statically rendering the content of a node view. | ||
| */ | ||
| nodeViewContentChildren?: ReactNode; | ||
| } | ||
| declare const ReactNodeViewContext: React.Context<ReactNodeViewContextProps>; | ||
| declare const ReactNodeViewContentProvider: ({ children, content }: { | ||
| children: ReactNode; | ||
| content: ReactNode; | ||
| }) => React.FunctionComponentElement<React.ProviderProps<ReactNodeViewContextProps>>; | ||
| declare const useReactNodeView: () => ReactNodeViewContextProps; | ||
| export { EditorConsumer, EditorContent, type EditorContentProps, EditorContext, type EditorContextValue, EditorProvider, type EditorProviderProps, type EditorStateSnapshot, MarkViewContent, type MarkViewContentProps, type MarkViewContextProps, NodeViewContent, type NodeViewContentProps, NodeViewWrapper, type NodeViewWrapperProps, PureEditorContent, ReactMarkView, ReactMarkViewContext, ReactMarkViewRenderer, type ReactMarkViewRendererOptions, ReactNodeView, ReactNodeViewContentProvider, ReactNodeViewContext, type ReactNodeViewContextProps, type ReactNodeViewProps, ReactNodeViewRenderer, type ReactNodeViewRendererOptions, ReactRenderer, type ReactRendererOptions, Tiptap, TiptapContent, TiptapContext, type TiptapContextType, TiptapWrapper, type TiptapWrapperProps, type UseEditorOptions, type UseEditorStateOptions, useCurrentEditor, useEditor, useEditorState, useReactNodeView, useTiptap, useTiptapState }; |
-1131
| // src/Context.tsx | ||
| import { createContext, useContext, useMemo } from "react"; | ||
| // src/EditorContent.tsx | ||
| import React, { forwardRef } from "react"; | ||
| import ReactDOM from "react-dom"; | ||
| import { useSyncExternalStore } from "use-sync-external-store/shim/index.js"; | ||
| import { Fragment, jsx, jsxs } from "react/jsx-runtime"; | ||
| var mergeRefs = (...refs) => { | ||
| return (node) => { | ||
| refs.forEach((ref) => { | ||
| if (typeof ref === "function") { | ||
| ref(node); | ||
| } else if (ref) { | ||
| ; | ||
| ref.current = node; | ||
| } | ||
| }); | ||
| }; | ||
| }; | ||
| var Portals = ({ contentComponent }) => { | ||
| const renderers = useSyncExternalStore( | ||
| contentComponent.subscribe, | ||
| contentComponent.getSnapshot, | ||
| contentComponent.getServerSnapshot | ||
| ); | ||
| return /* @__PURE__ */ jsx(Fragment, { children: Object.values(renderers) }); | ||
| }; | ||
| function getInstance() { | ||
| const subscribers = /* @__PURE__ */ new Set(); | ||
| let renderers = {}; | ||
| return { | ||
| /** | ||
| * Subscribe to the editor instance's changes. | ||
| */ | ||
| subscribe(callback) { | ||
| subscribers.add(callback); | ||
| return () => { | ||
| subscribers.delete(callback); | ||
| }; | ||
| }, | ||
| getSnapshot() { | ||
| return renderers; | ||
| }, | ||
| getServerSnapshot() { | ||
| return renderers; | ||
| }, | ||
| /** | ||
| * Adds a new NodeView Renderer to the editor. | ||
| */ | ||
| setRenderer(id, renderer) { | ||
| renderers = { | ||
| ...renderers, | ||
| [id]: ReactDOM.createPortal(renderer.reactElement, renderer.element, id) | ||
| }; | ||
| subscribers.forEach((subscriber) => subscriber()); | ||
| }, | ||
| /** | ||
| * Removes a NodeView Renderer from the editor. | ||
| */ | ||
| removeRenderer(id) { | ||
| const nextRenderers = { ...renderers }; | ||
| delete nextRenderers[id]; | ||
| renderers = nextRenderers; | ||
| subscribers.forEach((subscriber) => subscriber()); | ||
| } | ||
| }; | ||
| } | ||
| var PureEditorContent = class extends React.Component { | ||
| constructor(props) { | ||
| var _a; | ||
| super(props); | ||
| this.editorContentRef = React.createRef(); | ||
| this.initialized = false; | ||
| this.state = { | ||
| hasContentComponentInitialized: Boolean((_a = props.editor) == null ? void 0 : _a.contentComponent) | ||
| }; | ||
| } | ||
| componentDidMount() { | ||
| this.init(); | ||
| } | ||
| componentDidUpdate() { | ||
| this.init(); | ||
| } | ||
| init() { | ||
| var _a; | ||
| const editor = this.props.editor; | ||
| if (editor && !editor.isDestroyed && ((_a = editor.view.dom) == null ? void 0 : _a.parentNode)) { | ||
| if (editor.contentComponent) { | ||
| return; | ||
| } | ||
| const element = this.editorContentRef.current; | ||
| element.append(...editor.view.dom.parentNode.childNodes); | ||
| editor.setOptions({ | ||
| element | ||
| }); | ||
| editor.contentComponent = getInstance(); | ||
| if (!this.state.hasContentComponentInitialized) { | ||
| this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => { | ||
| this.setState((prevState) => { | ||
| if (!prevState.hasContentComponentInitialized) { | ||
| return { | ||
| hasContentComponentInitialized: true | ||
| }; | ||
| } | ||
| return prevState; | ||
| }); | ||
| if (this.unsubscribeToContentComponent) { | ||
| this.unsubscribeToContentComponent(); | ||
| } | ||
| }); | ||
| } | ||
| editor.createNodeViews(); | ||
| this.initialized = true; | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| var _a; | ||
| const editor = this.props.editor; | ||
| if (!editor) { | ||
| return; | ||
| } | ||
| this.initialized = false; | ||
| if (!editor.isDestroyed) { | ||
| editor.view.setProps({ | ||
| nodeViews: {} | ||
| }); | ||
| } | ||
| if (this.unsubscribeToContentComponent) { | ||
| this.unsubscribeToContentComponent(); | ||
| } | ||
| editor.contentComponent = null; | ||
| try { | ||
| if (!((_a = editor.view.dom) == null ? void 0 : _a.parentNode)) { | ||
| return; | ||
| } | ||
| const newElement = document.createElement("div"); | ||
| newElement.append(...editor.view.dom.parentNode.childNodes); | ||
| editor.setOptions({ | ||
| element: newElement | ||
| }); | ||
| } catch { | ||
| } | ||
| } | ||
| render() { | ||
| const { editor, innerRef, ...rest } = this.props; | ||
| return /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| /* @__PURE__ */ jsx("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }), | ||
| (editor == null ? void 0 : editor.contentComponent) && /* @__PURE__ */ jsx(Portals, { contentComponent: editor.contentComponent }) | ||
| ] }); | ||
| } | ||
| }; | ||
| var EditorContentWithKey = forwardRef( | ||
| (props, ref) => { | ||
| const key = React.useMemo(() => { | ||
| return Math.floor(Math.random() * 4294967295).toString(); | ||
| }, [props.editor]); | ||
| return React.createElement(PureEditorContent, { | ||
| key, | ||
| innerRef: ref, | ||
| ...props | ||
| }); | ||
| } | ||
| ); | ||
| var EditorContent = React.memo(EditorContentWithKey); | ||
| // src/useEditor.ts | ||
| import { Editor } from "@tiptap/core"; | ||
| import { useDebugValue as useDebugValue2, useEffect as useEffect2, useRef, useState as useState2 } from "react"; | ||
| import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js"; | ||
| // src/useEditorState.ts | ||
| import { deepEqual } from "fast-equals"; | ||
| import { useDebugValue, useEffect, useLayoutEffect, useState } from "react"; | ||
| import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js"; | ||
| var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect; | ||
| var EditorStateManager = class { | ||
| constructor(initialEditor) { | ||
| this.transactionNumber = 0; | ||
| this.lastTransactionNumber = 0; | ||
| this.subscribers = /* @__PURE__ */ new Set(); | ||
| this.editor = initialEditor; | ||
| this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 }; | ||
| this.getSnapshot = this.getSnapshot.bind(this); | ||
| this.getServerSnapshot = this.getServerSnapshot.bind(this); | ||
| this.watch = this.watch.bind(this); | ||
| this.subscribe = this.subscribe.bind(this); | ||
| } | ||
| /** | ||
| * Get the current editor instance. | ||
| */ | ||
| getSnapshot() { | ||
| if (this.transactionNumber === this.lastTransactionNumber) { | ||
| return this.lastSnapshot; | ||
| } | ||
| this.lastTransactionNumber = this.transactionNumber; | ||
| this.lastSnapshot = { editor: this.editor, transactionNumber: this.transactionNumber }; | ||
| return this.lastSnapshot; | ||
| } | ||
| /** | ||
| * Always disable the editor on the server-side. | ||
| */ | ||
| getServerSnapshot() { | ||
| return { editor: null, transactionNumber: 0 }; | ||
| } | ||
| /** | ||
| * Subscribe to the editor instance's changes. | ||
| */ | ||
| subscribe(callback) { | ||
| this.subscribers.add(callback); | ||
| return () => { | ||
| this.subscribers.delete(callback); | ||
| }; | ||
| } | ||
| /** | ||
| * Watch the editor instance for changes. | ||
| */ | ||
| watch(nextEditor) { | ||
| this.editor = nextEditor; | ||
| if (this.editor) { | ||
| const fn = () => { | ||
| this.transactionNumber += 1; | ||
| this.subscribers.forEach((callback) => callback()); | ||
| }; | ||
| const currentEditor = this.editor; | ||
| currentEditor.on("transaction", fn); | ||
| return () => { | ||
| currentEditor.off("transaction", fn); | ||
| }; | ||
| } | ||
| return void 0; | ||
| } | ||
| }; | ||
| function useEditorState(options) { | ||
| var _a; | ||
| const [editorStateManager] = useState(() => new EditorStateManager(options.editor)); | ||
| const selectedState = useSyncExternalStoreWithSelector( | ||
| editorStateManager.subscribe, | ||
| editorStateManager.getSnapshot, | ||
| editorStateManager.getServerSnapshot, | ||
| options.selector, | ||
| (_a = options.equalityFn) != null ? _a : deepEqual | ||
| ); | ||
| useIsomorphicLayoutEffect(() => { | ||
| return editorStateManager.watch(options.editor); | ||
| }, [options.editor, editorStateManager]); | ||
| useDebugValue(selectedState); | ||
| return selectedState; | ||
| } | ||
| // src/useEditor.ts | ||
| var isDev = process.env.NODE_ENV !== "production"; | ||
| var isSSR = typeof window === "undefined"; | ||
| var isNext = isSSR || Boolean(typeof window !== "undefined" && window.next); | ||
| var EditorInstanceManager = class _EditorInstanceManager { | ||
| constructor(options) { | ||
| /** | ||
| * The current editor instance. | ||
| */ | ||
| this.editor = null; | ||
| /** | ||
| * The subscriptions to notify when the editor instance | ||
| * has been created or destroyed. | ||
| */ | ||
| this.subscriptions = /* @__PURE__ */ new Set(); | ||
| /** | ||
| * Whether the editor has been mounted. | ||
| */ | ||
| this.isComponentMounted = false; | ||
| /** | ||
| * The most recent dependencies array. | ||
| */ | ||
| this.previousDeps = null; | ||
| /** | ||
| * The unique instance ID. This is used to identify the editor instance. And will be re-generated for each new instance. | ||
| */ | ||
| this.instanceId = ""; | ||
| this.options = options; | ||
| this.subscriptions = /* @__PURE__ */ new Set(); | ||
| this.setEditor(this.getInitialEditor()); | ||
| this.scheduleDestroy(); | ||
| this.getEditor = this.getEditor.bind(this); | ||
| this.getServerSnapshot = this.getServerSnapshot.bind(this); | ||
| this.subscribe = this.subscribe.bind(this); | ||
| this.refreshEditorInstance = this.refreshEditorInstance.bind(this); | ||
| this.scheduleDestroy = this.scheduleDestroy.bind(this); | ||
| this.onRender = this.onRender.bind(this); | ||
| this.createEditor = this.createEditor.bind(this); | ||
| } | ||
| setEditor(editor) { | ||
| this.editor = editor; | ||
| this.instanceId = Math.random().toString(36).slice(2, 9); | ||
| this.subscriptions.forEach((cb) => cb()); | ||
| } | ||
| getInitialEditor() { | ||
| if (this.options.current.immediatelyRender === void 0) { | ||
| if (isSSR || isNext) { | ||
| if (isDev) { | ||
| throw new Error( | ||
| "Tiptap Error: SSR has been detected, please set `immediatelyRender` explicitly to `false` to avoid hydration mismatches." | ||
| ); | ||
| } | ||
| return null; | ||
| } | ||
| return this.createEditor(); | ||
| } | ||
| if (this.options.current.immediatelyRender && isSSR && isDev) { | ||
| throw new Error( | ||
| "Tiptap Error: SSR has been detected, and `immediatelyRender` has been set to `true` this is an unsupported configuration that may result in errors, explicitly set `immediatelyRender` to `false` to avoid hydration mismatches." | ||
| ); | ||
| } | ||
| if (this.options.current.immediatelyRender) { | ||
| return this.createEditor(); | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Create a new editor instance. And attach event listeners. | ||
| */ | ||
| createEditor() { | ||
| const optionsToApply = { | ||
| ...this.options.current, | ||
| // Always call the most recent version of the callback function by default | ||
| onBeforeCreate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onBeforeCreate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onBlur: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onBlur) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onCreate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onCreate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onDestroy: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onDestroy) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onFocus: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onFocus) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onSelectionUpdate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onSelectionUpdate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onTransaction: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onTransaction) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onUpdate: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onUpdate) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onContentError: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onContentError) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onDrop: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onDrop) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onPaste: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onPaste) == null ? void 0 : _b.call(_a, ...args); | ||
| }, | ||
| onDelete: (...args) => { | ||
| var _a, _b; | ||
| return (_b = (_a = this.options.current).onDelete) == null ? void 0 : _b.call(_a, ...args); | ||
| } | ||
| }; | ||
| const editor = new Editor(optionsToApply); | ||
| return editor; | ||
| } | ||
| /** | ||
| * Get the current editor instance. | ||
| */ | ||
| getEditor() { | ||
| return this.editor; | ||
| } | ||
| /** | ||
| * Always disable the editor on the server-side. | ||
| */ | ||
| getServerSnapshot() { | ||
| return null; | ||
| } | ||
| /** | ||
| * Subscribe to the editor instance's changes. | ||
| */ | ||
| subscribe(onStoreChange) { | ||
| this.subscriptions.add(onStoreChange); | ||
| return () => { | ||
| this.subscriptions.delete(onStoreChange); | ||
| }; | ||
| } | ||
| static compareOptions(a, b) { | ||
| return Object.keys(a).every((key) => { | ||
| if ([ | ||
| "onCreate", | ||
| "onBeforeCreate", | ||
| "onDestroy", | ||
| "onUpdate", | ||
| "onTransaction", | ||
| "onFocus", | ||
| "onBlur", | ||
| "onSelectionUpdate", | ||
| "onContentError", | ||
| "onDrop", | ||
| "onPaste" | ||
| ].includes(key)) { | ||
| return true; | ||
| } | ||
| if (key === "extensions" && a.extensions && b.extensions) { | ||
| if (a.extensions.length !== b.extensions.length) { | ||
| return false; | ||
| } | ||
| return a.extensions.every((extension, index) => { | ||
| var _a; | ||
| if (extension !== ((_a = b.extensions) == null ? void 0 : _a[index])) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }); | ||
| } | ||
| if (a[key] !== b[key]) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }); | ||
| } | ||
| /** | ||
| * On each render, we will create, update, or destroy the editor instance. | ||
| * @param deps The dependencies to watch for changes | ||
| * @returns A cleanup function | ||
| */ | ||
| onRender(deps) { | ||
| return () => { | ||
| this.isComponentMounted = true; | ||
| clearTimeout(this.scheduledDestructionTimeout); | ||
| if (this.editor && !this.editor.isDestroyed && deps.length === 0) { | ||
| if (!_EditorInstanceManager.compareOptions(this.options.current, this.editor.options)) { | ||
| this.editor.setOptions({ | ||
| ...this.options.current, | ||
| editable: this.editor.isEditable | ||
| }); | ||
| } | ||
| } else { | ||
| this.refreshEditorInstance(deps); | ||
| } | ||
| return () => { | ||
| this.isComponentMounted = false; | ||
| this.scheduleDestroy(); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Recreate the editor instance if the dependencies have changed. | ||
| */ | ||
| refreshEditorInstance(deps) { | ||
| if (this.editor && !this.editor.isDestroyed) { | ||
| if (this.previousDeps === null) { | ||
| this.previousDeps = deps; | ||
| return; | ||
| } | ||
| const depsAreEqual = this.previousDeps.length === deps.length && this.previousDeps.every((dep, index) => dep === deps[index]); | ||
| if (depsAreEqual) { | ||
| return; | ||
| } | ||
| } | ||
| if (this.editor && !this.editor.isDestroyed) { | ||
| this.editor.destroy(); | ||
| } | ||
| this.setEditor(this.createEditor()); | ||
| this.previousDeps = deps; | ||
| } | ||
| /** | ||
| * Schedule the destruction of the editor instance. | ||
| * This will only destroy the editor if it was not mounted on the next tick. | ||
| * This is to avoid destroying the editor instance when it's actually still mounted. | ||
| */ | ||
| scheduleDestroy() { | ||
| const currentInstanceId = this.instanceId; | ||
| const currentEditor = this.editor; | ||
| this.scheduledDestructionTimeout = setTimeout(() => { | ||
| if (this.isComponentMounted && this.instanceId === currentInstanceId) { | ||
| if (currentEditor) { | ||
| currentEditor.setOptions(this.options.current); | ||
| } | ||
| return; | ||
| } | ||
| if (currentEditor && !currentEditor.isDestroyed) { | ||
| currentEditor.destroy(); | ||
| if (this.instanceId === currentInstanceId) { | ||
| this.setEditor(null); | ||
| } | ||
| } | ||
| }, 1); | ||
| } | ||
| }; | ||
| function useEditor(options = {}, deps = []) { | ||
| const mostRecentOptions = useRef(options); | ||
| mostRecentOptions.current = options; | ||
| const [instanceManager] = useState2(() => new EditorInstanceManager(mostRecentOptions)); | ||
| const editor = useSyncExternalStore2( | ||
| instanceManager.subscribe, | ||
| instanceManager.getEditor, | ||
| instanceManager.getServerSnapshot | ||
| ); | ||
| useDebugValue2(editor); | ||
| useEffect2(instanceManager.onRender(deps)); | ||
| useEditorState({ | ||
| editor, | ||
| selector: ({ transactionNumber }) => { | ||
| if (options.shouldRerenderOnTransaction === false || options.shouldRerenderOnTransaction === void 0) { | ||
| return null; | ||
| } | ||
| if (options.immediatelyRender && transactionNumber === 0) { | ||
| return 0; | ||
| } | ||
| return transactionNumber + 1; | ||
| } | ||
| }); | ||
| return editor; | ||
| } | ||
| // src/Context.tsx | ||
| import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; | ||
| var EditorContext = createContext({ | ||
| editor: null | ||
| }); | ||
| var EditorConsumer = EditorContext.Consumer; | ||
| var useCurrentEditor = () => useContext(EditorContext); | ||
| function EditorProvider({ | ||
| children, | ||
| slotAfter, | ||
| slotBefore, | ||
| editorContainerProps = {}, | ||
| ...editorOptions | ||
| }) { | ||
| const editor = useEditor(editorOptions); | ||
| const contextValue = useMemo(() => ({ editor }), [editor]); | ||
| if (!editor) { | ||
| return null; | ||
| } | ||
| return /* @__PURE__ */ jsxs2(EditorContext.Provider, { value: contextValue, children: [ | ||
| slotBefore, | ||
| /* @__PURE__ */ jsx2(EditorConsumer, { children: ({ editor: currentEditor }) => /* @__PURE__ */ jsx2(EditorContent, { editor: currentEditor, ...editorContainerProps }) }), | ||
| children, | ||
| slotAfter | ||
| ] }); | ||
| } | ||
| // src/useReactNodeView.ts | ||
| import { createContext as createContext2, createElement, useContext as useContext2 } from "react"; | ||
| var ReactNodeViewContext = createContext2({ | ||
| onDragStart: () => { | ||
| }, | ||
| nodeViewContentChildren: void 0, | ||
| nodeViewContentRef: () => { | ||
| } | ||
| }); | ||
| var ReactNodeViewContentProvider = ({ children, content }) => { | ||
| return createElement(ReactNodeViewContext.Provider, { value: { nodeViewContentChildren: content } }, children); | ||
| }; | ||
| var useReactNodeView = () => useContext2(ReactNodeViewContext); | ||
| // src/NodeViewContent.tsx | ||
| import { jsx as jsx3 } from "react/jsx-runtime"; | ||
| function NodeViewContent({ | ||
| as: Tag = "div", | ||
| ...props | ||
| }) { | ||
| const { nodeViewContentRef, nodeViewContentChildren } = useReactNodeView(); | ||
| return ( | ||
| // @ts-ignore | ||
| /* @__PURE__ */ jsx3( | ||
| Tag, | ||
| { | ||
| ...props, | ||
| ref: nodeViewContentRef, | ||
| "data-node-view-content": "", | ||
| style: { | ||
| whiteSpace: "pre-wrap", | ||
| ...props.style | ||
| }, | ||
| children: nodeViewContentChildren | ||
| } | ||
| ) | ||
| ); | ||
| } | ||
| // src/NodeViewWrapper.tsx | ||
| import React3 from "react"; | ||
| import { jsx as jsx4 } from "react/jsx-runtime"; | ||
| var NodeViewWrapper = React3.forwardRef((props, ref) => { | ||
| const { onDragStart } = useReactNodeView(); | ||
| const Tag = props.as || "div"; | ||
| return ( | ||
| // @ts-ignore | ||
| /* @__PURE__ */ jsx4( | ||
| Tag, | ||
| { | ||
| ...props, | ||
| ref, | ||
| "data-node-view-wrapper": "", | ||
| onDragStart, | ||
| style: { | ||
| whiteSpace: "normal", | ||
| ...props.style | ||
| } | ||
| } | ||
| ) | ||
| ); | ||
| }); | ||
| // src/ReactMarkViewRenderer.tsx | ||
| import { MarkView } from "@tiptap/core"; | ||
| import React4 from "react"; | ||
| // src/ReactRenderer.tsx | ||
| import { version as reactVersion } from "react"; | ||
| import { flushSync } from "react-dom"; | ||
| import { jsx as jsx5 } from "react/jsx-runtime"; | ||
| function isClassComponent(Component) { | ||
| return !!(typeof Component === "function" && Component.prototype && Component.prototype.isReactComponent); | ||
| } | ||
| function isForwardRefComponent(Component) { | ||
| return !!(typeof Component === "object" && Component.$$typeof && (Component.$$typeof.toString() === "Symbol(react.forward_ref)" || Component.$$typeof.description === "react.forward_ref")); | ||
| } | ||
| function isMemoComponent(Component) { | ||
| return !!(typeof Component === "object" && Component.$$typeof && (Component.$$typeof.toString() === "Symbol(react.memo)" || Component.$$typeof.description === "react.memo")); | ||
| } | ||
| function canReceiveRef(Component) { | ||
| if (isClassComponent(Component)) { | ||
| return true; | ||
| } | ||
| if (isForwardRefComponent(Component)) { | ||
| return true; | ||
| } | ||
| if (isMemoComponent(Component)) { | ||
| const wrappedComponent = Component.type; | ||
| if (wrappedComponent) { | ||
| return isClassComponent(wrappedComponent) || isForwardRefComponent(wrappedComponent); | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function isReact19Plus() { | ||
| try { | ||
| if (reactVersion) { | ||
| const majorVersion = parseInt(reactVersion.split(".")[0], 10); | ||
| return majorVersion >= 19; | ||
| } | ||
| } catch { | ||
| } | ||
| return false; | ||
| } | ||
| var ReactRenderer = class { | ||
| /** | ||
| * Immediately creates element and renders the provided React component. | ||
| */ | ||
| constructor(component, { editor, props = {}, as = "div", className = "" }) { | ||
| this.ref = null; | ||
| /** | ||
| * Flag to track if the renderer has been destroyed, preventing queued or asynchronous renders from executing after teardown. | ||
| */ | ||
| this.destroyed = false; | ||
| this.id = Math.floor(Math.random() * 4294967295).toString(); | ||
| this.component = component; | ||
| this.editor = editor; | ||
| this.props = props; | ||
| this.element = document.createElement(as); | ||
| this.element.classList.add("react-renderer"); | ||
| if (className) { | ||
| this.element.classList.add(...className.split(" ")); | ||
| } | ||
| if (this.editor.isInitialized) { | ||
| flushSync(() => { | ||
| this.render(); | ||
| }); | ||
| } else { | ||
| queueMicrotask(() => { | ||
| if (this.destroyed) { | ||
| return; | ||
| } | ||
| this.render(); | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Render the React component. | ||
| */ | ||
| render() { | ||
| var _a; | ||
| if (this.destroyed) { | ||
| return; | ||
| } | ||
| const Component = this.component; | ||
| const props = this.props; | ||
| const editor = this.editor; | ||
| const isReact19 = isReact19Plus(); | ||
| const componentCanReceiveRef = canReceiveRef(Component); | ||
| const elementProps = { ...props }; | ||
| if (elementProps.ref && !(isReact19 || componentCanReceiveRef)) { | ||
| delete elementProps.ref; | ||
| } | ||
| if (!elementProps.ref && (isReact19 || componentCanReceiveRef)) { | ||
| elementProps.ref = (ref) => { | ||
| this.ref = ref; | ||
| }; | ||
| } | ||
| this.reactElement = /* @__PURE__ */ jsx5(Component, { ...elementProps }); | ||
| (_a = editor == null ? void 0 : editor.contentComponent) == null ? void 0 : _a.setRenderer(this.id, this); | ||
| } | ||
| /** | ||
| * Re-renders the React component with new props. | ||
| */ | ||
| updateProps(props = {}) { | ||
| if (this.destroyed) { | ||
| return; | ||
| } | ||
| this.props = { | ||
| ...this.props, | ||
| ...props | ||
| }; | ||
| this.render(); | ||
| } | ||
| /** | ||
| * Destroy the React component. | ||
| */ | ||
| destroy() { | ||
| var _a; | ||
| this.destroyed = true; | ||
| const editor = this.editor; | ||
| (_a = editor == null ? void 0 : editor.contentComponent) == null ? void 0 : _a.removeRenderer(this.id); | ||
| try { | ||
| if (this.element && this.element.parentNode) { | ||
| this.element.parentNode.removeChild(this.element); | ||
| } | ||
| } catch { | ||
| } | ||
| } | ||
| /** | ||
| * Update the attributes of the element that holds the React component. | ||
| */ | ||
| updateAttributes(attributes) { | ||
| Object.keys(attributes).forEach((key) => { | ||
| this.element.setAttribute(key, attributes[key]); | ||
| }); | ||
| } | ||
| }; | ||
| // src/ReactMarkViewRenderer.tsx | ||
| import { jsx as jsx6 } from "react/jsx-runtime"; | ||
| var ReactMarkViewContext = React4.createContext({ | ||
| markViewContentRef: () => { | ||
| } | ||
| }); | ||
| var MarkViewContent = (props) => { | ||
| const { as: Tag = "span", ...rest } = props; | ||
| const { markViewContentRef } = React4.useContext(ReactMarkViewContext); | ||
| return ( | ||
| // @ts-ignore | ||
| /* @__PURE__ */ jsx6(Tag, { ...rest, ref: markViewContentRef, "data-mark-view-content": "" }) | ||
| ); | ||
| }; | ||
| var ReactMarkView = class extends MarkView { | ||
| constructor(component, props, options) { | ||
| super(component, props, options); | ||
| const { as = "span", attrs, className = "" } = options || {}; | ||
| const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) }; | ||
| this.contentDOMElement = document.createElement("span"); | ||
| const markViewContentRef = (el) => { | ||
| if (el && !el.contains(this.contentDOMElement)) { | ||
| el.appendChild(this.contentDOMElement); | ||
| } | ||
| }; | ||
| const context = { | ||
| markViewContentRef | ||
| }; | ||
| const ReactMarkViewProvider = React4.memo((componentProps2) => { | ||
| return /* @__PURE__ */ jsx6(ReactMarkViewContext.Provider, { value: context, children: React4.createElement(component, componentProps2) }); | ||
| }); | ||
| ReactMarkViewProvider.displayName = "ReactMarkView"; | ||
| this.renderer = new ReactRenderer(ReactMarkViewProvider, { | ||
| editor: props.editor, | ||
| props: componentProps, | ||
| as, | ||
| className: `mark-${props.mark.type.name} ${className}`.trim() | ||
| }); | ||
| if (attrs) { | ||
| this.renderer.updateAttributes(attrs); | ||
| } | ||
| } | ||
| get dom() { | ||
| return this.renderer.element; | ||
| } | ||
| get contentDOM() { | ||
| return this.contentDOMElement; | ||
| } | ||
| }; | ||
| function ReactMarkViewRenderer(component, options = {}) { | ||
| return (props) => new ReactMarkView(component, props, options); | ||
| } | ||
| // src/ReactNodeViewRenderer.tsx | ||
| import { getRenderedAttributes, NodeView } from "@tiptap/core"; | ||
| import { createElement as createElement2, createRef, memo } from "react"; | ||
| import { jsx as jsx7 } from "react/jsx-runtime"; | ||
| var ReactNodeView = class extends NodeView { | ||
| constructor(component, props, options) { | ||
| super(component, props, options); | ||
| /** | ||
| * The requestAnimationFrame ID used for selection updates. | ||
| */ | ||
| this.selectionRafId = null; | ||
| this.cachedExtensionWithSyncedStorage = null; | ||
| if (!this.node.isLeaf) { | ||
| if (this.options.contentDOMElementTag) { | ||
| this.contentDOMElement = document.createElement(this.options.contentDOMElementTag); | ||
| } else { | ||
| this.contentDOMElement = document.createElement(this.node.isInline ? "span" : "div"); | ||
| } | ||
| this.contentDOMElement.dataset.nodeViewContentReact = ""; | ||
| this.contentDOMElement.dataset.nodeViewWrapper = ""; | ||
| this.contentDOMElement.style.whiteSpace = "inherit"; | ||
| const contentTarget = this.dom.querySelector("[data-node-view-content]"); | ||
| if (!contentTarget) { | ||
| return; | ||
| } | ||
| contentTarget.appendChild(this.contentDOMElement); | ||
| } | ||
| } | ||
| /** | ||
| * Returns a proxy of the extension that redirects storage access to the editor's mutable storage. | ||
| * This preserves the original prototype chain (instanceof checks, methods like configure/extend work). | ||
| * Cached to avoid proxy creation on every update. | ||
| */ | ||
| get extensionWithSyncedStorage() { | ||
| if (!this.cachedExtensionWithSyncedStorage) { | ||
| const editor = this.editor; | ||
| const extension = this.extension; | ||
| this.cachedExtensionWithSyncedStorage = new Proxy(extension, { | ||
| get(target, prop, receiver) { | ||
| var _a; | ||
| if (prop === "storage") { | ||
| return (_a = editor.storage[extension.name]) != null ? _a : {}; | ||
| } | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| }); | ||
| } | ||
| return this.cachedExtensionWithSyncedStorage; | ||
| } | ||
| /** | ||
| * Setup the React component. | ||
| * Called on initialization. | ||
| */ | ||
| mount() { | ||
| const props = { | ||
| editor: this.editor, | ||
| node: this.node, | ||
| decorations: this.decorations, | ||
| innerDecorations: this.innerDecorations, | ||
| view: this.view, | ||
| selected: false, | ||
| extension: this.extensionWithSyncedStorage, | ||
| HTMLAttributes: this.HTMLAttributes, | ||
| getPos: () => this.getPos(), | ||
| updateAttributes: (attributes = {}) => this.updateAttributes(attributes), | ||
| deleteNode: () => this.deleteNode(), | ||
| ref: createRef() | ||
| }; | ||
| if (!this.component.displayName) { | ||
| const capitalizeFirstChar = (string) => { | ||
| return string.charAt(0).toUpperCase() + string.substring(1); | ||
| }; | ||
| this.component.displayName = capitalizeFirstChar(this.extension.name); | ||
| } | ||
| const onDragStart = this.onDragStart.bind(this); | ||
| const nodeViewContentRef = (element) => { | ||
| if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) { | ||
| if (element.hasAttribute("data-node-view-wrapper")) { | ||
| element.removeAttribute("data-node-view-wrapper"); | ||
| } | ||
| element.appendChild(this.contentDOMElement); | ||
| } | ||
| }; | ||
| const context = { onDragStart, nodeViewContentRef }; | ||
| const Component = this.component; | ||
| const ReactNodeViewProvider = memo((componentProps) => { | ||
| return /* @__PURE__ */ jsx7(ReactNodeViewContext.Provider, { value: context, children: createElement2(Component, componentProps) }); | ||
| }); | ||
| ReactNodeViewProvider.displayName = "ReactNodeView"; | ||
| let as = this.node.isInline ? "span" : "div"; | ||
| if (this.options.as) { | ||
| as = this.options.as; | ||
| } | ||
| const { className = "" } = this.options; | ||
| this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this); | ||
| this.renderer = new ReactRenderer(ReactNodeViewProvider, { | ||
| editor: this.editor, | ||
| props, | ||
| as, | ||
| className: `node-${this.node.type.name} ${className}`.trim() | ||
| }); | ||
| this.editor.on("selectionUpdate", this.handleSelectionUpdate); | ||
| this.updateElementAttributes(); | ||
| } | ||
| /** | ||
| * Return the DOM element. | ||
| * This is the element that will be used to display the node view. | ||
| */ | ||
| get dom() { | ||
| var _a; | ||
| if (this.renderer.element.firstElementChild && !((_a = this.renderer.element.firstElementChild) == null ? void 0 : _a.hasAttribute("data-node-view-wrapper"))) { | ||
| throw Error("Please use the NodeViewWrapper component for your node view."); | ||
| } | ||
| return this.renderer.element; | ||
| } | ||
| /** | ||
| * Return the content DOM element. | ||
| * This is the element that will be used to display the rich-text content of the node. | ||
| */ | ||
| get contentDOM() { | ||
| if (this.node.isLeaf) { | ||
| return null; | ||
| } | ||
| return this.contentDOMElement; | ||
| } | ||
| /** | ||
| * On editor selection update, check if the node is selected. | ||
| * If it is, call `selectNode`, otherwise call `deselectNode`. | ||
| */ | ||
| handleSelectionUpdate() { | ||
| if (this.selectionRafId) { | ||
| cancelAnimationFrame(this.selectionRafId); | ||
| this.selectionRafId = null; | ||
| } | ||
| this.selectionRafId = requestAnimationFrame(() => { | ||
| this.selectionRafId = null; | ||
| const { from, to } = this.editor.state.selection; | ||
| const pos = this.getPos(); | ||
| if (typeof pos !== "number") { | ||
| return; | ||
| } | ||
| if (from <= pos && to >= pos + this.node.nodeSize) { | ||
| if (this.renderer.props.selected) { | ||
| return; | ||
| } | ||
| this.selectNode(); | ||
| } else { | ||
| if (!this.renderer.props.selected) { | ||
| return; | ||
| } | ||
| this.deselectNode(); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * On update, update the React component. | ||
| * To prevent unnecessary updates, the `update` option can be used. | ||
| */ | ||
| update(node, decorations, innerDecorations) { | ||
| const rerenderComponent = (props) => { | ||
| this.renderer.updateProps(props); | ||
| if (typeof this.options.attrs === "function") { | ||
| this.updateElementAttributes(); | ||
| } | ||
| }; | ||
| if (node.type !== this.node.type) { | ||
| return false; | ||
| } | ||
| if (typeof this.options.update === "function") { | ||
| const oldNode = this.node; | ||
| const oldDecorations = this.decorations; | ||
| const oldInnerDecorations = this.innerDecorations; | ||
| this.node = node; | ||
| this.decorations = decorations; | ||
| this.innerDecorations = innerDecorations; | ||
| return this.options.update({ | ||
| oldNode, | ||
| oldDecorations, | ||
| newNode: node, | ||
| newDecorations: decorations, | ||
| oldInnerDecorations, | ||
| innerDecorations, | ||
| updateProps: () => rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }) | ||
| }); | ||
| } | ||
| if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) { | ||
| return true; | ||
| } | ||
| this.node = node; | ||
| this.decorations = decorations; | ||
| this.innerDecorations = innerDecorations; | ||
| rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage }); | ||
| return true; | ||
| } | ||
| /** | ||
| * Select the node. | ||
| * Add the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| selectNode() { | ||
| this.renderer.updateProps({ | ||
| selected: true | ||
| }); | ||
| this.renderer.element.classList.add("ProseMirror-selectednode"); | ||
| } | ||
| /** | ||
| * Deselect the node. | ||
| * Remove the `selected` prop and the `ProseMirror-selectednode` class. | ||
| */ | ||
| deselectNode() { | ||
| this.renderer.updateProps({ | ||
| selected: false | ||
| }); | ||
| this.renderer.element.classList.remove("ProseMirror-selectednode"); | ||
| } | ||
| /** | ||
| * Destroy the React component instance. | ||
| */ | ||
| destroy() { | ||
| this.renderer.destroy(); | ||
| this.editor.off("selectionUpdate", this.handleSelectionUpdate); | ||
| this.contentDOMElement = null; | ||
| if (this.selectionRafId) { | ||
| cancelAnimationFrame(this.selectionRafId); | ||
| this.selectionRafId = null; | ||
| } | ||
| } | ||
| /** | ||
| * Update the attributes of the top-level element that holds the React component. | ||
| * Applying the attributes defined in the `attrs` option. | ||
| */ | ||
| updateElementAttributes() { | ||
| if (this.options.attrs) { | ||
| let attrsObj = {}; | ||
| if (typeof this.options.attrs === "function") { | ||
| const extensionAttributes = this.editor.extensionManager.attributes; | ||
| const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes); | ||
| attrsObj = this.options.attrs({ node: this.node, HTMLAttributes }); | ||
| } else { | ||
| attrsObj = this.options.attrs; | ||
| } | ||
| this.renderer.updateAttributes(attrsObj); | ||
| } | ||
| } | ||
| }; | ||
| function ReactNodeViewRenderer(component, options) { | ||
| return (props) => { | ||
| if (!props.editor.contentComponent) { | ||
| return {}; | ||
| } | ||
| return new ReactNodeView(component, props, options); | ||
| }; | ||
| } | ||
| // src/Tiptap.tsx | ||
| import { createContext as createContext3, useContext as useContext3, useMemo as useMemo2 } from "react"; | ||
| import { jsx as jsx8 } from "react/jsx-runtime"; | ||
| var TiptapContext = createContext3({ | ||
| get editor() { | ||
| throw new Error("useTiptap must be used within a <Tiptap> provider"); | ||
| } | ||
| }); | ||
| TiptapContext.displayName = "TiptapContext"; | ||
| var useTiptap = () => useContext3(TiptapContext); | ||
| function useTiptapState(selector, equalityFn) { | ||
| const { editor } = useTiptap(); | ||
| return useEditorState({ | ||
| editor, | ||
| selector, | ||
| equalityFn | ||
| }); | ||
| } | ||
| function TiptapWrapper({ editor, instance, children }) { | ||
| const resolvedEditor = editor != null ? editor : instance; | ||
| if (!resolvedEditor) { | ||
| throw new Error("Tiptap: An editor instance is required. Pass a non-null `editor` prop."); | ||
| } | ||
| const tiptapContextValue = useMemo2(() => ({ editor: resolvedEditor }), [resolvedEditor]); | ||
| const legacyContextValue = useMemo2(() => ({ editor: resolvedEditor }), [resolvedEditor]); | ||
| return /* @__PURE__ */ jsx8(EditorContext.Provider, { value: legacyContextValue, children: /* @__PURE__ */ jsx8(TiptapContext.Provider, { value: tiptapContextValue, children }) }); | ||
| } | ||
| TiptapWrapper.displayName = "Tiptap"; | ||
| function TiptapContent({ ...rest }) { | ||
| const { editor } = useTiptap(); | ||
| return /* @__PURE__ */ jsx8(EditorContent, { editor, ...rest }); | ||
| } | ||
| TiptapContent.displayName = "Tiptap.Content"; | ||
| var Tiptap = Object.assign(TiptapWrapper, { | ||
| /** | ||
| * The Tiptap Content component that renders the EditorContent with the editor instance from the context. | ||
| * @see TiptapContent | ||
| */ | ||
| Content: TiptapContent | ||
| }); | ||
| // src/index.ts | ||
| export * from "@tiptap/core"; | ||
| export { | ||
| EditorConsumer, | ||
| EditorContent, | ||
| EditorContext, | ||
| EditorProvider, | ||
| MarkViewContent, | ||
| NodeViewContent, | ||
| NodeViewWrapper, | ||
| PureEditorContent, | ||
| ReactMarkView, | ||
| ReactMarkViewContext, | ||
| ReactMarkViewRenderer, | ||
| ReactNodeView, | ||
| ReactNodeViewContentProvider, | ||
| ReactNodeViewContext, | ||
| ReactNodeViewRenderer, | ||
| ReactRenderer, | ||
| Tiptap, | ||
| TiptapContent, | ||
| TiptapContext, | ||
| TiptapWrapper, | ||
| useCurrentEditor, | ||
| useEditor, | ||
| useEditorState, | ||
| useReactNodeView, | ||
| useTiptap, | ||
| useTiptapState | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
Sorry, the diff of this file is too big to display
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/menus/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| BubbleMenu: () => BubbleMenu, | ||
| FloatingMenu: () => FloatingMenu | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/menus/BubbleMenu.tsx | ||
| var import_extension_bubble_menu = require("@tiptap/extension-bubble-menu"); | ||
| var import_react = require("@tiptap/react"); | ||
| var import_react2 = __toESM(require("react"), 1); | ||
| var import_react_dom = require("react-dom"); | ||
| var import_jsx_runtime = require("react/jsx-runtime"); | ||
| var BubbleMenu = import_react2.default.forwardRef( | ||
| ({ | ||
| pluginKey = "bubbleMenu", | ||
| editor, | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| shouldShow = null, | ||
| getReferencedVirtualElement, | ||
| options, | ||
| children, | ||
| ...restProps | ||
| }, ref) => { | ||
| const menuEl = (0, import_react2.useRef)(document.createElement("div")); | ||
| if (typeof ref === "function") { | ||
| ref(menuEl.current); | ||
| } else if (ref) { | ||
| ref.current = menuEl.current; | ||
| } | ||
| const { editor: currentEditor } = (0, import_react.useCurrentEditor)(); | ||
| const pluginEditor = editor || currentEditor; | ||
| const bubbleMenuPluginProps = { | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| pluginKey, | ||
| shouldShow, | ||
| getReferencedVirtualElement, | ||
| options | ||
| }; | ||
| const bubbleMenuPluginPropsRef = (0, import_react2.useRef)(bubbleMenuPluginProps); | ||
| bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps; | ||
| const [pluginInitialized, setPluginInitialized] = (0, import_react2.useState)(false); | ||
| const skipFirstUpdateRef = (0, import_react2.useRef)(true); | ||
| (0, import_react2.useEffect)(() => { | ||
| if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (!pluginEditor) { | ||
| console.warn("BubbleMenu component is not rendered inside of an editor component or does not have editor prop."); | ||
| return; | ||
| } | ||
| const bubbleMenuElement = menuEl.current; | ||
| bubbleMenuElement.style.visibility = "hidden"; | ||
| bubbleMenuElement.style.position = "absolute"; | ||
| const plugin = (0, import_extension_bubble_menu.BubbleMenuPlugin)({ | ||
| ...bubbleMenuPluginPropsRef.current, | ||
| editor: pluginEditor, | ||
| element: bubbleMenuElement | ||
| }); | ||
| pluginEditor.registerPlugin(plugin); | ||
| const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey; | ||
| skipFirstUpdateRef.current = true; | ||
| setPluginInitialized(true); | ||
| return () => { | ||
| setPluginInitialized(false); | ||
| pluginEditor.unregisterPlugin(createdPluginKey); | ||
| window.requestAnimationFrame(() => { | ||
| if (bubbleMenuElement.parentNode) { | ||
| bubbleMenuElement.parentNode.removeChild(bubbleMenuElement); | ||
| } | ||
| }); | ||
| }; | ||
| }, [pluginEditor]); | ||
| (0, import_react2.useEffect)(() => { | ||
| if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (skipFirstUpdateRef.current) { | ||
| skipFirstUpdateRef.current = false; | ||
| return; | ||
| } | ||
| pluginEditor.view.dispatch( | ||
| pluginEditor.state.tr.setMeta(pluginKey, { | ||
| type: "updateOptions", | ||
| options: bubbleMenuPluginPropsRef.current | ||
| }) | ||
| ); | ||
| }, [ | ||
| pluginInitialized, | ||
| pluginEditor, | ||
| updateDelay, | ||
| resizeDelay, | ||
| shouldShow, | ||
| options, | ||
| appendTo, | ||
| getReferencedVirtualElement | ||
| ]); | ||
| return (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...restProps, children }), menuEl.current); | ||
| } | ||
| ); | ||
| // src/menus/FloatingMenu.tsx | ||
| var import_extension_floating_menu = require("@tiptap/extension-floating-menu"); | ||
| var import_react3 = require("@tiptap/react"); | ||
| var import_react4 = __toESM(require("react"), 1); | ||
| var import_react_dom2 = require("react-dom"); | ||
| var import_jsx_runtime2 = require("react/jsx-runtime"); | ||
| var FloatingMenu = import_react4.default.forwardRef( | ||
| ({ | ||
| pluginKey = "floatingMenu", | ||
| editor, | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| shouldShow = null, | ||
| options, | ||
| children, | ||
| ...restProps | ||
| }, ref) => { | ||
| const menuEl = (0, import_react4.useRef)(document.createElement("div")); | ||
| if (typeof ref === "function") { | ||
| ref(menuEl.current); | ||
| } else if (ref) { | ||
| ref.current = menuEl.current; | ||
| } | ||
| const { editor: currentEditor } = (0, import_react3.useCurrentEditor)(); | ||
| const pluginEditor = editor || currentEditor; | ||
| const floatingMenuPluginProps = { | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| pluginKey, | ||
| shouldShow, | ||
| options | ||
| }; | ||
| const floatingMenuPluginPropsRef = (0, import_react4.useRef)(floatingMenuPluginProps); | ||
| floatingMenuPluginPropsRef.current = floatingMenuPluginProps; | ||
| const [pluginInitialized, setPluginInitialized] = (0, import_react4.useState)(false); | ||
| const skipFirstUpdateRef = (0, import_react4.useRef)(true); | ||
| (0, import_react4.useEffect)(() => { | ||
| if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (!pluginEditor) { | ||
| console.warn( | ||
| "FloatingMenu component is not rendered inside of an editor component or does not have editor prop." | ||
| ); | ||
| return; | ||
| } | ||
| const floatingMenuElement = menuEl.current; | ||
| floatingMenuElement.style.visibility = "hidden"; | ||
| floatingMenuElement.style.position = "absolute"; | ||
| const plugin = (0, import_extension_floating_menu.FloatingMenuPlugin)({ | ||
| ...floatingMenuPluginPropsRef.current, | ||
| editor: pluginEditor, | ||
| element: floatingMenuElement | ||
| }); | ||
| pluginEditor.registerPlugin(plugin); | ||
| const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey; | ||
| skipFirstUpdateRef.current = true; | ||
| setPluginInitialized(true); | ||
| return () => { | ||
| setPluginInitialized(false); | ||
| pluginEditor.unregisterPlugin(createdPluginKey); | ||
| window.requestAnimationFrame(() => { | ||
| if (floatingMenuElement.parentNode) { | ||
| floatingMenuElement.parentNode.removeChild(floatingMenuElement); | ||
| } | ||
| }); | ||
| }; | ||
| }, [pluginEditor]); | ||
| (0, import_react4.useEffect)(() => { | ||
| if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (skipFirstUpdateRef.current) { | ||
| skipFirstUpdateRef.current = false; | ||
| return; | ||
| } | ||
| pluginEditor.view.dispatch( | ||
| pluginEditor.state.tr.setMeta(pluginKey, { | ||
| type: "updateOptions", | ||
| options: floatingMenuPluginPropsRef.current | ||
| }) | ||
| ); | ||
| }, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo]); | ||
| return (0, import_react_dom2.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...restProps, children }), menuEl.current); | ||
| } | ||
| ); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| BubbleMenu, | ||
| FloatingMenu | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
| {"version":3,"sources":["../../src/menus/index.ts","../../src/menus/BubbleMenu.tsx","../../src/menus/FloatingMenu.tsx"],"sourcesContent":["export * from './BubbleMenu.js'\nexport * from './FloatingMenu.js'\n","import { type BubbleMenuPluginProps, BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'\nimport { useCurrentEditor } from '@tiptap/react'\nimport React, { useEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type BubbleMenuProps = Optional<Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> &\n React.HTMLAttributes<HTMLDivElement>\n\nexport const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(\n (\n {\n pluginKey = 'bubbleMenu',\n editor,\n updateDelay,\n resizeDelay,\n appendTo,\n shouldShow = null,\n getReferencedVirtualElement,\n options,\n children,\n ...restProps\n },\n ref,\n ) => {\n const menuEl = useRef(document.createElement('div'))\n\n if (typeof ref === 'function') {\n ref(menuEl.current)\n } else if (ref) {\n ref.current = menuEl.current\n }\n\n const { editor: currentEditor } = useCurrentEditor()\n\n /**\n * The editor instance where the bubble menu plugin will be registered.\n */\n const pluginEditor = editor || currentEditor\n\n // Creating a useMemo would be more computationally expensive than just\n // re-creating this object on every render.\n const bubbleMenuPluginProps: Omit<BubbleMenuPluginProps, 'editor' | 'element'> = {\n updateDelay,\n resizeDelay,\n appendTo,\n pluginKey,\n shouldShow,\n getReferencedVirtualElement,\n options,\n }\n /**\n * The props for the bubble menu plugin. They are accessed inside a ref to\n * avoid running the useEffect hook and re-registering the plugin when the\n * props change.\n */\n const bubbleMenuPluginPropsRef = useRef(bubbleMenuPluginProps)\n bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps\n\n /**\n * Track whether the plugin has been initialized, so we only send updates\n * after the initial registration.\n */\n const [pluginInitialized, setPluginInitialized] = useState(false)\n\n /**\n * Track whether we need to skip the first options update dispatch.\n * This prevents unnecessary updates right after plugin initialization.\n */\n const skipFirstUpdateRef = useRef(true)\n\n useEffect(() => {\n if (pluginEditor?.isDestroyed) {\n return\n }\n\n if (!pluginEditor) {\n console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.')\n return\n }\n\n const bubbleMenuElement = menuEl.current\n bubbleMenuElement.style.visibility = 'hidden'\n bubbleMenuElement.style.position = 'absolute'\n\n const plugin = BubbleMenuPlugin({\n ...bubbleMenuPluginPropsRef.current,\n editor: pluginEditor,\n element: bubbleMenuElement,\n })\n\n pluginEditor.registerPlugin(plugin)\n\n const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey\n\n skipFirstUpdateRef.current = true\n setPluginInitialized(true)\n\n return () => {\n setPluginInitialized(false)\n pluginEditor.unregisterPlugin(createdPluginKey)\n window.requestAnimationFrame(() => {\n if (bubbleMenuElement.parentNode) {\n bubbleMenuElement.parentNode.removeChild(bubbleMenuElement)\n }\n })\n }\n }, [pluginEditor])\n\n /**\n * Update the plugin options when props change after the plugin has been initialized.\n * This allows dynamic updates to options like scrollTarget without re-registering the entire plugin.\n */\n useEffect(() => {\n if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {\n return\n }\n\n // Skip the first update right after initialization since the plugin was just created with these options\n if (skipFirstUpdateRef.current) {\n skipFirstUpdateRef.current = false\n return\n }\n\n pluginEditor.view.dispatch(\n pluginEditor.state.tr.setMeta(pluginKey, {\n type: 'updateOptions',\n options: bubbleMenuPluginPropsRef.current,\n }),\n )\n }, [\n pluginInitialized,\n pluginEditor,\n updateDelay,\n resizeDelay,\n shouldShow,\n options,\n appendTo,\n getReferencedVirtualElement,\n ])\n\n return createPortal(<div {...restProps}>{children}</div>, menuEl.current)\n },\n)\n","import type { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'\nimport { FloatingMenuPlugin } from '@tiptap/extension-floating-menu'\nimport { useCurrentEditor } from '@tiptap/react'\nimport React, { useEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {\n editor: FloatingMenuPluginProps['editor'] | null\n options?: FloatingMenuPluginProps['options']\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(\n (\n {\n pluginKey = 'floatingMenu',\n editor,\n updateDelay,\n resizeDelay,\n appendTo,\n shouldShow = null,\n options,\n children,\n ...restProps\n },\n ref,\n ) => {\n const menuEl = useRef(document.createElement('div'))\n\n if (typeof ref === 'function') {\n ref(menuEl.current)\n } else if (ref) {\n ref.current = menuEl.current\n }\n\n const { editor: currentEditor } = useCurrentEditor()\n\n /**\n * The editor instance where the floating menu plugin will be registered.\n */\n const pluginEditor = editor || currentEditor\n\n // Creating a useMemo would be more computationally expensive than just\n // re-creating this object on every render.\n const floatingMenuPluginProps: Omit<FloatingMenuPluginProps, 'editor' | 'element'> = {\n updateDelay,\n resizeDelay,\n appendTo,\n pluginKey,\n shouldShow,\n options,\n }\n\n /**\n * The props for the floating menu plugin. They are accessed inside a ref to\n * avoid running the useEffect hook and re-registering the plugin when the\n * props change.\n */\n const floatingMenuPluginPropsRef = useRef(floatingMenuPluginProps)\n floatingMenuPluginPropsRef.current = floatingMenuPluginProps\n\n /**\n * Track whether the plugin has been initialized, so we only send updates\n * after the initial registration.\n */\n const [pluginInitialized, setPluginInitialized] = useState(false)\n\n /**\n * Track whether we need to skip the first options update dispatch.\n * This prevents unnecessary updates right after plugin initialization.\n */\n const skipFirstUpdateRef = useRef(true)\n\n useEffect(() => {\n if (pluginEditor?.isDestroyed) {\n return\n }\n\n if (!pluginEditor) {\n console.warn(\n 'FloatingMenu component is not rendered inside of an editor component or does not have editor prop.',\n )\n return\n }\n\n const floatingMenuElement = menuEl.current\n floatingMenuElement.style.visibility = 'hidden'\n floatingMenuElement.style.position = 'absolute'\n\n const plugin = FloatingMenuPlugin({\n ...floatingMenuPluginPropsRef.current,\n editor: pluginEditor,\n element: floatingMenuElement,\n })\n\n pluginEditor.registerPlugin(plugin)\n\n const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey\n\n skipFirstUpdateRef.current = true\n setPluginInitialized(true)\n\n return () => {\n setPluginInitialized(false)\n pluginEditor.unregisterPlugin(createdPluginKey)\n window.requestAnimationFrame(() => {\n if (floatingMenuElement.parentNode) {\n floatingMenuElement.parentNode.removeChild(floatingMenuElement)\n }\n })\n }\n }, [pluginEditor])\n\n /**\n * Update the plugin options when props change after the plugin has been initialized.\n * This allows dynamic updates to options like scrollTarget without re-registering the entire plugin.\n */\n useEffect(() => {\n if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {\n return\n }\n\n // Skip the first update right after initialization since the plugin was just created with these options\n if (skipFirstUpdateRef.current) {\n skipFirstUpdateRef.current = false\n return\n }\n\n pluginEditor.view.dispatch(\n pluginEditor.state.tr.setMeta(pluginKey, {\n type: 'updateOptions',\n options: floatingMenuPluginPropsRef.current,\n }),\n )\n }, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo])\n\n return createPortal(<div {...restProps}>{children}</div>, menuEl.current)\n },\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mCAA6D;AAC7D,mBAAiC;AACjC,IAAAA,gBAAmD;AACnD,uBAA6B;AA2IL;AApIjB,IAAM,aAAa,cAAAC,QAAM;AAAA,EAC9B,CACE;AAAA,IACE,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,aAAS,sBAAO,SAAS,cAAc,KAAK,CAAC;AAEnD,QAAI,OAAO,QAAQ,YAAY;AAC7B,UAAI,OAAO,OAAO;AAAA,IACpB,WAAW,KAAK;AACd,UAAI,UAAU,OAAO;AAAA,IACvB;AAEA,UAAM,EAAE,QAAQ,cAAc,QAAI,+BAAiB;AAKnD,UAAM,eAAe,UAAU;AAI/B,UAAM,wBAA2E;AAAA,MAC/E;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAMA,UAAM,+BAA2B,sBAAO,qBAAqB;AAC7D,6BAAyB,UAAU;AAMnC,UAAM,CAAC,mBAAmB,oBAAoB,QAAI,wBAAS,KAAK;AAMhE,UAAM,yBAAqB,sBAAO,IAAI;AAEtC,iCAAU,MAAM;AACd,UAAI,6CAAc,aAAa;AAC7B;AAAA,MACF;AAEA,UAAI,CAAC,cAAc;AACjB,gBAAQ,KAAK,kGAAkG;AAC/G;AAAA,MACF;AAEA,YAAM,oBAAoB,OAAO;AACjC,wBAAkB,MAAM,aAAa;AACrC,wBAAkB,MAAM,WAAW;AAEnC,YAAM,aAAS,+CAAiB;AAAA,QAC9B,GAAG,yBAAyB;AAAA,QAC5B,QAAQ;AAAA,QACR,SAAS;AAAA,MACX,CAAC;AAED,mBAAa,eAAe,MAAM;AAElC,YAAM,mBAAmB,yBAAyB,QAAQ;AAE1D,yBAAmB,UAAU;AAC7B,2BAAqB,IAAI;AAEzB,aAAO,MAAM;AACX,6BAAqB,KAAK;AAC1B,qBAAa,iBAAiB,gBAAgB;AAC9C,eAAO,sBAAsB,MAAM;AACjC,cAAI,kBAAkB,YAAY;AAChC,8BAAkB,WAAW,YAAY,iBAAiB;AAAA,UAC5D;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,YAAY,CAAC;AAMjB,iCAAU,MAAM;AACd,UAAI,CAAC,qBAAqB,CAAC,gBAAgB,aAAa,aAAa;AACnE;AAAA,MACF;AAGA,UAAI,mBAAmB,SAAS;AAC9B,2BAAmB,UAAU;AAC7B;AAAA,MACF;AAEA,mBAAa,KAAK;AAAA,QAChB,aAAa,MAAM,GAAG,QAAQ,WAAW;AAAA,UACvC,MAAM;AAAA,UACN,SAAS,yBAAyB;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF,GAAG;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,eAAO,+BAAa,4CAAC,SAAK,GAAG,WAAY,UAAS,GAAQ,OAAO,OAAO;AAAA,EAC1E;AACF;;;AC/IA,qCAAmC;AACnC,IAAAC,gBAAiC;AACjC,IAAAA,gBAAmD;AACnD,IAAAC,oBAA6B;AAqIL,IAAAC,sBAAA;AA5HjB,IAAM,eAAe,cAAAC,QAAM;AAAA,EAChC,CACE;AAAA,IACE,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,aAAS,sBAAO,SAAS,cAAc,KAAK,CAAC;AAEnD,QAAI,OAAO,QAAQ,YAAY;AAC7B,UAAI,OAAO,OAAO;AAAA,IACpB,WAAW,KAAK;AACd,UAAI,UAAU,OAAO;AAAA,IACvB;AAEA,UAAM,EAAE,QAAQ,cAAc,QAAI,gCAAiB;AAKnD,UAAM,eAAe,UAAU;AAI/B,UAAM,0BAA+E;AAAA,MACnF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAOA,UAAM,iCAA6B,sBAAO,uBAAuB;AACjE,+BAA2B,UAAU;AAMrC,UAAM,CAAC,mBAAmB,oBAAoB,QAAI,wBAAS,KAAK;AAMhE,UAAM,yBAAqB,sBAAO,IAAI;AAEtC,iCAAU,MAAM;AACd,UAAI,6CAAc,aAAa;AAC7B;AAAA,MACF;AAEA,UAAI,CAAC,cAAc;AACjB,gBAAQ;AAAA,UACN;AAAA,QACF;AACA;AAAA,MACF;AAEA,YAAM,sBAAsB,OAAO;AACnC,0BAAoB,MAAM,aAAa;AACvC,0BAAoB,MAAM,WAAW;AAErC,YAAM,aAAS,mDAAmB;AAAA,QAChC,GAAG,2BAA2B;AAAA,QAC9B,QAAQ;AAAA,QACR,SAAS;AAAA,MACX,CAAC;AAED,mBAAa,eAAe,MAAM;AAElC,YAAM,mBAAmB,2BAA2B,QAAQ;AAE5D,yBAAmB,UAAU;AAC7B,2BAAqB,IAAI;AAEzB,aAAO,MAAM;AACX,6BAAqB,KAAK;AAC1B,qBAAa,iBAAiB,gBAAgB;AAC9C,eAAO,sBAAsB,MAAM;AACjC,cAAI,oBAAoB,YAAY;AAClC,gCAAoB,WAAW,YAAY,mBAAmB;AAAA,UAChE;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,YAAY,CAAC;AAMjB,iCAAU,MAAM;AACd,UAAI,CAAC,qBAAqB,CAAC,gBAAgB,aAAa,aAAa;AACnE;AAAA,MACF;AAGA,UAAI,mBAAmB,SAAS;AAC9B,2BAAmB,UAAU;AAC7B;AAAA,MACF;AAEA,mBAAa,KAAK;AAAA,QAChB,aAAa,MAAM,GAAG,QAAQ,WAAW;AAAA,UACvC,MAAM;AAAA,UACN,SAAS,2BAA2B;AAAA,QACtC,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,mBAAmB,cAAc,aAAa,aAAa,YAAY,SAAS,QAAQ,CAAC;AAE7F,eAAO,gCAAa,6CAAC,SAAK,GAAG,WAAY,UAAS,GAAQ,OAAO,OAAO;AAAA,EAC1E;AACF;","names":["import_react","React","import_react","import_react_dom","import_jsx_runtime","React"]} |
| import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'; | ||
| import React from 'react'; | ||
| import { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'; | ||
| type Optional$1<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; | ||
| type BubbleMenuProps = Optional$1<Omit<Optional$1<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> & React.HTMLAttributes<HTMLDivElement>; | ||
| declare const BubbleMenu: React.ForwardRefExoticComponent<Pick<Partial<Omit<Optional$1<BubbleMenuPluginProps, "pluginKey">, "element">>, "editor"> & Omit<Omit<Optional$1<BubbleMenuPluginProps, "pluginKey">, "element">, "editor"> & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>; | ||
| type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; | ||
| type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & { | ||
| editor: FloatingMenuPluginProps['editor'] | null; | ||
| options?: FloatingMenuPluginProps['options']; | ||
| } & React.HTMLAttributes<HTMLDivElement>; | ||
| declare const FloatingMenu: React.ForwardRefExoticComponent<Omit<Optional<FloatingMenuPluginProps, "pluginKey">, "editor" | "element"> & { | ||
| editor: FloatingMenuPluginProps["editor"] | null; | ||
| options?: FloatingMenuPluginProps["options"]; | ||
| } & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>; | ||
| export { BubbleMenu, type BubbleMenuProps, FloatingMenu, type FloatingMenuProps }; |
| import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'; | ||
| import React from 'react'; | ||
| import { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'; | ||
| type Optional$1<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; | ||
| type BubbleMenuProps = Optional$1<Omit<Optional$1<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> & React.HTMLAttributes<HTMLDivElement>; | ||
| declare const BubbleMenu: React.ForwardRefExoticComponent<Pick<Partial<Omit<Optional$1<BubbleMenuPluginProps, "pluginKey">, "element">>, "editor"> & Omit<Omit<Optional$1<BubbleMenuPluginProps, "pluginKey">, "element">, "editor"> & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>; | ||
| type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; | ||
| type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & { | ||
| editor: FloatingMenuPluginProps['editor'] | null; | ||
| options?: FloatingMenuPluginProps['options']; | ||
| } & React.HTMLAttributes<HTMLDivElement>; | ||
| declare const FloatingMenu: React.ForwardRefExoticComponent<Omit<Optional<FloatingMenuPluginProps, "pluginKey">, "editor" | "element"> & { | ||
| editor: FloatingMenuPluginProps["editor"] | null; | ||
| options?: FloatingMenuPluginProps["options"]; | ||
| } & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>; | ||
| export { BubbleMenu, type BubbleMenuProps, FloatingMenu, type FloatingMenuProps }; |
| // src/menus/BubbleMenu.tsx | ||
| import { BubbleMenuPlugin } from "@tiptap/extension-bubble-menu"; | ||
| import { useCurrentEditor } from "@tiptap/react"; | ||
| import React, { useEffect, useRef, useState } from "react"; | ||
| import { createPortal } from "react-dom"; | ||
| import { jsx } from "react/jsx-runtime"; | ||
| var BubbleMenu = React.forwardRef( | ||
| ({ | ||
| pluginKey = "bubbleMenu", | ||
| editor, | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| shouldShow = null, | ||
| getReferencedVirtualElement, | ||
| options, | ||
| children, | ||
| ...restProps | ||
| }, ref) => { | ||
| const menuEl = useRef(document.createElement("div")); | ||
| if (typeof ref === "function") { | ||
| ref(menuEl.current); | ||
| } else if (ref) { | ||
| ref.current = menuEl.current; | ||
| } | ||
| const { editor: currentEditor } = useCurrentEditor(); | ||
| const pluginEditor = editor || currentEditor; | ||
| const bubbleMenuPluginProps = { | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| pluginKey, | ||
| shouldShow, | ||
| getReferencedVirtualElement, | ||
| options | ||
| }; | ||
| const bubbleMenuPluginPropsRef = useRef(bubbleMenuPluginProps); | ||
| bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps; | ||
| const [pluginInitialized, setPluginInitialized] = useState(false); | ||
| const skipFirstUpdateRef = useRef(true); | ||
| useEffect(() => { | ||
| if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (!pluginEditor) { | ||
| console.warn("BubbleMenu component is not rendered inside of an editor component or does not have editor prop."); | ||
| return; | ||
| } | ||
| const bubbleMenuElement = menuEl.current; | ||
| bubbleMenuElement.style.visibility = "hidden"; | ||
| bubbleMenuElement.style.position = "absolute"; | ||
| const plugin = BubbleMenuPlugin({ | ||
| ...bubbleMenuPluginPropsRef.current, | ||
| editor: pluginEditor, | ||
| element: bubbleMenuElement | ||
| }); | ||
| pluginEditor.registerPlugin(plugin); | ||
| const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey; | ||
| skipFirstUpdateRef.current = true; | ||
| setPluginInitialized(true); | ||
| return () => { | ||
| setPluginInitialized(false); | ||
| pluginEditor.unregisterPlugin(createdPluginKey); | ||
| window.requestAnimationFrame(() => { | ||
| if (bubbleMenuElement.parentNode) { | ||
| bubbleMenuElement.parentNode.removeChild(bubbleMenuElement); | ||
| } | ||
| }); | ||
| }; | ||
| }, [pluginEditor]); | ||
| useEffect(() => { | ||
| if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (skipFirstUpdateRef.current) { | ||
| skipFirstUpdateRef.current = false; | ||
| return; | ||
| } | ||
| pluginEditor.view.dispatch( | ||
| pluginEditor.state.tr.setMeta(pluginKey, { | ||
| type: "updateOptions", | ||
| options: bubbleMenuPluginPropsRef.current | ||
| }) | ||
| ); | ||
| }, [ | ||
| pluginInitialized, | ||
| pluginEditor, | ||
| updateDelay, | ||
| resizeDelay, | ||
| shouldShow, | ||
| options, | ||
| appendTo, | ||
| getReferencedVirtualElement | ||
| ]); | ||
| return createPortal(/* @__PURE__ */ jsx("div", { ...restProps, children }), menuEl.current); | ||
| } | ||
| ); | ||
| // src/menus/FloatingMenu.tsx | ||
| import { FloatingMenuPlugin } from "@tiptap/extension-floating-menu"; | ||
| import { useCurrentEditor as useCurrentEditor2 } from "@tiptap/react"; | ||
| import React2, { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react"; | ||
| import { createPortal as createPortal2 } from "react-dom"; | ||
| import { jsx as jsx2 } from "react/jsx-runtime"; | ||
| var FloatingMenu = React2.forwardRef( | ||
| ({ | ||
| pluginKey = "floatingMenu", | ||
| editor, | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| shouldShow = null, | ||
| options, | ||
| children, | ||
| ...restProps | ||
| }, ref) => { | ||
| const menuEl = useRef2(document.createElement("div")); | ||
| if (typeof ref === "function") { | ||
| ref(menuEl.current); | ||
| } else if (ref) { | ||
| ref.current = menuEl.current; | ||
| } | ||
| const { editor: currentEditor } = useCurrentEditor2(); | ||
| const pluginEditor = editor || currentEditor; | ||
| const floatingMenuPluginProps = { | ||
| updateDelay, | ||
| resizeDelay, | ||
| appendTo, | ||
| pluginKey, | ||
| shouldShow, | ||
| options | ||
| }; | ||
| const floatingMenuPluginPropsRef = useRef2(floatingMenuPluginProps); | ||
| floatingMenuPluginPropsRef.current = floatingMenuPluginProps; | ||
| const [pluginInitialized, setPluginInitialized] = useState2(false); | ||
| const skipFirstUpdateRef = useRef2(true); | ||
| useEffect2(() => { | ||
| if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (!pluginEditor) { | ||
| console.warn( | ||
| "FloatingMenu component is not rendered inside of an editor component or does not have editor prop." | ||
| ); | ||
| return; | ||
| } | ||
| const floatingMenuElement = menuEl.current; | ||
| floatingMenuElement.style.visibility = "hidden"; | ||
| floatingMenuElement.style.position = "absolute"; | ||
| const plugin = FloatingMenuPlugin({ | ||
| ...floatingMenuPluginPropsRef.current, | ||
| editor: pluginEditor, | ||
| element: floatingMenuElement | ||
| }); | ||
| pluginEditor.registerPlugin(plugin); | ||
| const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey; | ||
| skipFirstUpdateRef.current = true; | ||
| setPluginInitialized(true); | ||
| return () => { | ||
| setPluginInitialized(false); | ||
| pluginEditor.unregisterPlugin(createdPluginKey); | ||
| window.requestAnimationFrame(() => { | ||
| if (floatingMenuElement.parentNode) { | ||
| floatingMenuElement.parentNode.removeChild(floatingMenuElement); | ||
| } | ||
| }); | ||
| }; | ||
| }, [pluginEditor]); | ||
| useEffect2(() => { | ||
| if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) { | ||
| return; | ||
| } | ||
| if (skipFirstUpdateRef.current) { | ||
| skipFirstUpdateRef.current = false; | ||
| return; | ||
| } | ||
| pluginEditor.view.dispatch( | ||
| pluginEditor.state.tr.setMeta(pluginKey, { | ||
| type: "updateOptions", | ||
| options: floatingMenuPluginPropsRef.current | ||
| }) | ||
| ); | ||
| }, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo]); | ||
| return createPortal2(/* @__PURE__ */ jsx2("div", { ...restProps, children }), menuEl.current); | ||
| } | ||
| ); | ||
| export { | ||
| BubbleMenu, | ||
| FloatingMenu | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"sources":["../../src/menus/BubbleMenu.tsx","../../src/menus/FloatingMenu.tsx"],"sourcesContent":["import { type BubbleMenuPluginProps, BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'\nimport { useCurrentEditor } from '@tiptap/react'\nimport React, { useEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type BubbleMenuProps = Optional<Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>, 'editor'> &\n React.HTMLAttributes<HTMLDivElement>\n\nexport const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(\n (\n {\n pluginKey = 'bubbleMenu',\n editor,\n updateDelay,\n resizeDelay,\n appendTo,\n shouldShow = null,\n getReferencedVirtualElement,\n options,\n children,\n ...restProps\n },\n ref,\n ) => {\n const menuEl = useRef(document.createElement('div'))\n\n if (typeof ref === 'function') {\n ref(menuEl.current)\n } else if (ref) {\n ref.current = menuEl.current\n }\n\n const { editor: currentEditor } = useCurrentEditor()\n\n /**\n * The editor instance where the bubble menu plugin will be registered.\n */\n const pluginEditor = editor || currentEditor\n\n // Creating a useMemo would be more computationally expensive than just\n // re-creating this object on every render.\n const bubbleMenuPluginProps: Omit<BubbleMenuPluginProps, 'editor' | 'element'> = {\n updateDelay,\n resizeDelay,\n appendTo,\n pluginKey,\n shouldShow,\n getReferencedVirtualElement,\n options,\n }\n /**\n * The props for the bubble menu plugin. They are accessed inside a ref to\n * avoid running the useEffect hook and re-registering the plugin when the\n * props change.\n */\n const bubbleMenuPluginPropsRef = useRef(bubbleMenuPluginProps)\n bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps\n\n /**\n * Track whether the plugin has been initialized, so we only send updates\n * after the initial registration.\n */\n const [pluginInitialized, setPluginInitialized] = useState(false)\n\n /**\n * Track whether we need to skip the first options update dispatch.\n * This prevents unnecessary updates right after plugin initialization.\n */\n const skipFirstUpdateRef = useRef(true)\n\n useEffect(() => {\n if (pluginEditor?.isDestroyed) {\n return\n }\n\n if (!pluginEditor) {\n console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.')\n return\n }\n\n const bubbleMenuElement = menuEl.current\n bubbleMenuElement.style.visibility = 'hidden'\n bubbleMenuElement.style.position = 'absolute'\n\n const plugin = BubbleMenuPlugin({\n ...bubbleMenuPluginPropsRef.current,\n editor: pluginEditor,\n element: bubbleMenuElement,\n })\n\n pluginEditor.registerPlugin(plugin)\n\n const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey\n\n skipFirstUpdateRef.current = true\n setPluginInitialized(true)\n\n return () => {\n setPluginInitialized(false)\n pluginEditor.unregisterPlugin(createdPluginKey)\n window.requestAnimationFrame(() => {\n if (bubbleMenuElement.parentNode) {\n bubbleMenuElement.parentNode.removeChild(bubbleMenuElement)\n }\n })\n }\n }, [pluginEditor])\n\n /**\n * Update the plugin options when props change after the plugin has been initialized.\n * This allows dynamic updates to options like scrollTarget without re-registering the entire plugin.\n */\n useEffect(() => {\n if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {\n return\n }\n\n // Skip the first update right after initialization since the plugin was just created with these options\n if (skipFirstUpdateRef.current) {\n skipFirstUpdateRef.current = false\n return\n }\n\n pluginEditor.view.dispatch(\n pluginEditor.state.tr.setMeta(pluginKey, {\n type: 'updateOptions',\n options: bubbleMenuPluginPropsRef.current,\n }),\n )\n }, [\n pluginInitialized,\n pluginEditor,\n updateDelay,\n resizeDelay,\n shouldShow,\n options,\n appendTo,\n getReferencedVirtualElement,\n ])\n\n return createPortal(<div {...restProps}>{children}</div>, menuEl.current)\n },\n)\n","import type { FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'\nimport { FloatingMenuPlugin } from '@tiptap/extension-floating-menu'\nimport { useCurrentEditor } from '@tiptap/react'\nimport React, { useEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>\n\nexport type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element' | 'editor'> & {\n editor: FloatingMenuPluginProps['editor'] | null\n options?: FloatingMenuPluginProps['options']\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport const FloatingMenu = React.forwardRef<HTMLDivElement, FloatingMenuProps>(\n (\n {\n pluginKey = 'floatingMenu',\n editor,\n updateDelay,\n resizeDelay,\n appendTo,\n shouldShow = null,\n options,\n children,\n ...restProps\n },\n ref,\n ) => {\n const menuEl = useRef(document.createElement('div'))\n\n if (typeof ref === 'function') {\n ref(menuEl.current)\n } else if (ref) {\n ref.current = menuEl.current\n }\n\n const { editor: currentEditor } = useCurrentEditor()\n\n /**\n * The editor instance where the floating menu plugin will be registered.\n */\n const pluginEditor = editor || currentEditor\n\n // Creating a useMemo would be more computationally expensive than just\n // re-creating this object on every render.\n const floatingMenuPluginProps: Omit<FloatingMenuPluginProps, 'editor' | 'element'> = {\n updateDelay,\n resizeDelay,\n appendTo,\n pluginKey,\n shouldShow,\n options,\n }\n\n /**\n * The props for the floating menu plugin. They are accessed inside a ref to\n * avoid running the useEffect hook and re-registering the plugin when the\n * props change.\n */\n const floatingMenuPluginPropsRef = useRef(floatingMenuPluginProps)\n floatingMenuPluginPropsRef.current = floatingMenuPluginProps\n\n /**\n * Track whether the plugin has been initialized, so we only send updates\n * after the initial registration.\n */\n const [pluginInitialized, setPluginInitialized] = useState(false)\n\n /**\n * Track whether we need to skip the first options update dispatch.\n * This prevents unnecessary updates right after plugin initialization.\n */\n const skipFirstUpdateRef = useRef(true)\n\n useEffect(() => {\n if (pluginEditor?.isDestroyed) {\n return\n }\n\n if (!pluginEditor) {\n console.warn(\n 'FloatingMenu component is not rendered inside of an editor component or does not have editor prop.',\n )\n return\n }\n\n const floatingMenuElement = menuEl.current\n floatingMenuElement.style.visibility = 'hidden'\n floatingMenuElement.style.position = 'absolute'\n\n const plugin = FloatingMenuPlugin({\n ...floatingMenuPluginPropsRef.current,\n editor: pluginEditor,\n element: floatingMenuElement,\n })\n\n pluginEditor.registerPlugin(plugin)\n\n const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey\n\n skipFirstUpdateRef.current = true\n setPluginInitialized(true)\n\n return () => {\n setPluginInitialized(false)\n pluginEditor.unregisterPlugin(createdPluginKey)\n window.requestAnimationFrame(() => {\n if (floatingMenuElement.parentNode) {\n floatingMenuElement.parentNode.removeChild(floatingMenuElement)\n }\n })\n }\n }, [pluginEditor])\n\n /**\n * Update the plugin options when props change after the plugin has been initialized.\n * This allows dynamic updates to options like scrollTarget without re-registering the entire plugin.\n */\n useEffect(() => {\n if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {\n return\n }\n\n // Skip the first update right after initialization since the plugin was just created with these options\n if (skipFirstUpdateRef.current) {\n skipFirstUpdateRef.current = false\n return\n }\n\n pluginEditor.view.dispatch(\n pluginEditor.state.tr.setMeta(pluginKey, {\n type: 'updateOptions',\n options: floatingMenuPluginPropsRef.current,\n }),\n )\n }, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo])\n\n return createPortal(<div {...restProps}>{children}</div>, menuEl.current)\n },\n)\n"],"mappings":";AAAA,SAAqC,wBAAwB;AAC7D,SAAS,wBAAwB;AACjC,OAAO,SAAS,WAAW,QAAQ,gBAAgB;AACnD,SAAS,oBAAoB;AA2IL;AApIjB,IAAM,aAAa,MAAM;AAAA,EAC9B,CACE;AAAA,IACE,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,SAAS,OAAO,SAAS,cAAc,KAAK,CAAC;AAEnD,QAAI,OAAO,QAAQ,YAAY;AAC7B,UAAI,OAAO,OAAO;AAAA,IACpB,WAAW,KAAK;AACd,UAAI,UAAU,OAAO;AAAA,IACvB;AAEA,UAAM,EAAE,QAAQ,cAAc,IAAI,iBAAiB;AAKnD,UAAM,eAAe,UAAU;AAI/B,UAAM,wBAA2E;AAAA,MAC/E;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAMA,UAAM,2BAA2B,OAAO,qBAAqB;AAC7D,6BAAyB,UAAU;AAMnC,UAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,KAAK;AAMhE,UAAM,qBAAqB,OAAO,IAAI;AAEtC,cAAU,MAAM;AACd,UAAI,6CAAc,aAAa;AAC7B;AAAA,MACF;AAEA,UAAI,CAAC,cAAc;AACjB,gBAAQ,KAAK,kGAAkG;AAC/G;AAAA,MACF;AAEA,YAAM,oBAAoB,OAAO;AACjC,wBAAkB,MAAM,aAAa;AACrC,wBAAkB,MAAM,WAAW;AAEnC,YAAM,SAAS,iBAAiB;AAAA,QAC9B,GAAG,yBAAyB;AAAA,QAC5B,QAAQ;AAAA,QACR,SAAS;AAAA,MACX,CAAC;AAED,mBAAa,eAAe,MAAM;AAElC,YAAM,mBAAmB,yBAAyB,QAAQ;AAE1D,yBAAmB,UAAU;AAC7B,2BAAqB,IAAI;AAEzB,aAAO,MAAM;AACX,6BAAqB,KAAK;AAC1B,qBAAa,iBAAiB,gBAAgB;AAC9C,eAAO,sBAAsB,MAAM;AACjC,cAAI,kBAAkB,YAAY;AAChC,8BAAkB,WAAW,YAAY,iBAAiB;AAAA,UAC5D;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,YAAY,CAAC;AAMjB,cAAU,MAAM;AACd,UAAI,CAAC,qBAAqB,CAAC,gBAAgB,aAAa,aAAa;AACnE;AAAA,MACF;AAGA,UAAI,mBAAmB,SAAS;AAC9B,2BAAmB,UAAU;AAC7B;AAAA,MACF;AAEA,mBAAa,KAAK;AAAA,QAChB,aAAa,MAAM,GAAG,QAAQ,WAAW;AAAA,UACvC,MAAM;AAAA,UACN,SAAS,yBAAyB;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF,GAAG;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO,aAAa,oBAAC,SAAK,GAAG,WAAY,UAAS,GAAQ,OAAO,OAAO;AAAA,EAC1E;AACF;;;AC/IA,SAAS,0BAA0B;AACnC,SAAS,oBAAAA,yBAAwB;AACjC,OAAOC,UAAS,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgB;AACnD,SAAS,gBAAAC,qBAAoB;AAqIL,gBAAAC,YAAA;AA5HjB,IAAM,eAAeL,OAAM;AAAA,EAChC,CACE;AAAA,IACE,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,SAASE,QAAO,SAAS,cAAc,KAAK,CAAC;AAEnD,QAAI,OAAO,QAAQ,YAAY;AAC7B,UAAI,OAAO,OAAO;AAAA,IACpB,WAAW,KAAK;AACd,UAAI,UAAU,OAAO;AAAA,IACvB;AAEA,UAAM,EAAE,QAAQ,cAAc,IAAIH,kBAAiB;AAKnD,UAAM,eAAe,UAAU;AAI/B,UAAM,0BAA+E;AAAA,MACnF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAOA,UAAM,6BAA6BG,QAAO,uBAAuB;AACjE,+BAA2B,UAAU;AAMrC,UAAM,CAAC,mBAAmB,oBAAoB,IAAIC,UAAS,KAAK;AAMhE,UAAM,qBAAqBD,QAAO,IAAI;AAEtC,IAAAD,WAAU,MAAM;AACd,UAAI,6CAAc,aAAa;AAC7B;AAAA,MACF;AAEA,UAAI,CAAC,cAAc;AACjB,gBAAQ;AAAA,UACN;AAAA,QACF;AACA;AAAA,MACF;AAEA,YAAM,sBAAsB,OAAO;AACnC,0BAAoB,MAAM,aAAa;AACvC,0BAAoB,MAAM,WAAW;AAErC,YAAM,SAAS,mBAAmB;AAAA,QAChC,GAAG,2BAA2B;AAAA,QAC9B,QAAQ;AAAA,QACR,SAAS;AAAA,MACX,CAAC;AAED,mBAAa,eAAe,MAAM;AAElC,YAAM,mBAAmB,2BAA2B,QAAQ;AAE5D,yBAAmB,UAAU;AAC7B,2BAAqB,IAAI;AAEzB,aAAO,MAAM;AACX,6BAAqB,KAAK;AAC1B,qBAAa,iBAAiB,gBAAgB;AAC9C,eAAO,sBAAsB,MAAM;AACjC,cAAI,oBAAoB,YAAY;AAClC,gCAAoB,WAAW,YAAY,mBAAmB;AAAA,UAChE;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,YAAY,CAAC;AAMjB,IAAAA,WAAU,MAAM;AACd,UAAI,CAAC,qBAAqB,CAAC,gBAAgB,aAAa,aAAa;AACnE;AAAA,MACF;AAGA,UAAI,mBAAmB,SAAS;AAC9B,2BAAmB,UAAU;AAC7B;AAAA,MACF;AAEA,mBAAa,KAAK;AAAA,QAChB,aAAa,MAAM,GAAG,QAAQ,WAAW;AAAA,UACvC,MAAM;AAAA,UACN,SAAS,2BAA2B;AAAA,QACtC,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,mBAAmB,cAAc,aAAa,aAAa,YAAY,SAAS,QAAQ,CAAC;AAE7F,WAAOG,cAAa,gBAAAC,KAAC,SAAK,GAAG,WAAY,UAAS,GAAQ,OAAO,OAAO;AAAA,EAC1E;AACF;","names":["useCurrentEditor","React","useEffect","useRef","useState","createPortal","jsx"]} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
1
-66.67%93774
-76.15%24
-25%2515
-51.47%1
Infinity%