snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
This commit is contained in:
97
packages/react/src/ReactWidgetRenderer.tsx
Normal file
97
packages/react/src/ReactWidgetRenderer.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { createWidgetDecoration } from '@tiptap/core'
|
||||
import type { Editor, WidgetDecoration, WidgetDecorationOptions } from '@tiptap/core'
|
||||
import type { ComponentType } from 'react'
|
||||
|
||||
import { ReactRenderer } from './ReactRenderer.js'
|
||||
|
||||
/**
|
||||
* Props every widget-decoration component receives in addition to the props you
|
||||
* pass through `ReactWidgetRenderer`.
|
||||
*/
|
||||
export interface ReactWidgetDecorationProps {
|
||||
editor: Editor
|
||||
getPos: () => number | undefined
|
||||
}
|
||||
|
||||
export interface ReactWidgetRendererOptions<
|
||||
P extends Record<string, any> = object,
|
||||
> extends WidgetDecorationOptions {
|
||||
/**
|
||||
* The editor instance.
|
||||
*/
|
||||
editor: Editor
|
||||
/**
|
||||
* The document position the widget is rendered at.
|
||||
*/
|
||||
pos: number
|
||||
/**
|
||||
* A stable, position-independent identifier for the widget.
|
||||
* Reusing the same key keeps the component mounted across re-renders.
|
||||
* Good: `comment-${id}`. Bad: paragraph index or document position.
|
||||
*/
|
||||
key: string
|
||||
/**
|
||||
* Props passed to the component (merged with {@link ReactWidgetDecorationProps}).
|
||||
*/
|
||||
props?: P
|
||||
/**
|
||||
* The tag name of the wrapper element. Defaults to `'span'` (widgets are
|
||||
* usually inline). Applied only when the renderer is first created; later
|
||||
* renders with the same `key` keep the original tag.
|
||||
*/
|
||||
as?: string
|
||||
/**
|
||||
* Applied only when the renderer is first created; later renders with the
|
||||
* same `key` keep the original class.
|
||||
*/
|
||||
className?: string
|
||||
}
|
||||
|
||||
const WIDGET_CACHE = Symbol('tiptapReactWidgetCache')
|
||||
|
||||
/**
|
||||
* Renders a React component into a ProseMirror widget decoration.
|
||||
* Reuses Tiptap's `ReactRenderer` so the component lives in the editor's
|
||||
* React tree (context and hooks work as usual). Use a stable `key` for
|
||||
* stateful widgets.
|
||||
* @example
|
||||
* addDecorations() {
|
||||
* return {
|
||||
* create: ({ editor, state }) =>
|
||||
* findMatches(state.doc).map(match =>
|
||||
* ReactWidgetRenderer(MyWidget, {
|
||||
* editor, pos: match.pos, key: `match-${match.id}`,
|
||||
* props: { label: match.label },
|
||||
* }),
|
||||
* ),
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export function ReactWidgetRenderer<P extends Record<string, any> = object>(
|
||||
component: ComponentType<P & ReactWidgetDecorationProps>,
|
||||
options: ReactWidgetRendererOptions<P>,
|
||||
): WidgetDecoration {
|
||||
const { editor, props = {} as P, as = 'span', className } = options
|
||||
|
||||
return createWidgetDecoration<ReactRenderer>({
|
||||
// Forwards editor, pos, key and the ProseMirror widget options unchanged.
|
||||
...options,
|
||||
props,
|
||||
cacheKey: WIDGET_CACHE,
|
||||
context: getPos => ({ editor, getPos }),
|
||||
create: renderProps =>
|
||||
new ReactRenderer(component, {
|
||||
editor,
|
||||
as,
|
||||
className,
|
||||
props: renderProps,
|
||||
}),
|
||||
materialize: renderer => {
|
||||
// Re-register the portal on every materialization so the editor's
|
||||
// content component is available even on late first renders.
|
||||
renderer.render()
|
||||
|
||||
return renderer.element
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user