feat(react): allow attrs to be a callback

This commit is contained in:
yaokailun 2024-08-18 19:27:30 +08:00 committed by GitHub
parent c99627d7ce
commit 4ff2a4eaa1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 17 deletions

View File

@ -0,0 +1,5 @@
---
"@tiptap/react": patch
---
ReactNodeViewRenderer now accepts a callback for attrs of the wrapping element to be updated on each node view update

View File

@ -1,6 +1,7 @@
import { import {
DecorationWithType, DecorationWithType,
Editor, Editor,
getRenderedAttributes,
NodeView, NodeView,
NodeViewProps, NodeViewProps,
NodeViewRenderer, NodeViewRenderer,
@ -27,7 +28,12 @@ export interface ReactNodeViewRendererOptions extends NodeViewRendererOptions {
| null | null
as?: string as?: string
className?: string className?: string
attrs?: Record<string, string> attrs?:
| Record<string, string>
| ((props: {
node: ProseMirrorNode
HTMLAttributes: Record<string, any>
}) => Record<string, string>)
} }
class ReactNodeView extends NodeView< class ReactNodeView extends NodeView<
@ -110,8 +116,9 @@ class ReactNodeView extends NodeView<
props, props,
as, as,
className: `node-${this.node.type.name} ${className}`.trim(), className: `node-${this.node.type.name} ${className}`.trim(),
attrs: this.options.attrs,
}) })
this.updateElementAttributes()
} }
get dom() { get dom() {
@ -154,6 +161,9 @@ class ReactNodeView extends NodeView<
update(node: ProseMirrorNode, decorations: DecorationWithType[]) { update(node: ProseMirrorNode, decorations: DecorationWithType[]) {
const updateProps = (props?: Record<string, any>) => { const updateProps = (props?: Record<string, any>) => {
this.renderer.updateProps(props) this.renderer.updateProps(props)
if (typeof this.options.attrs === 'function') {
this.updateElementAttributes()
}
} }
if (node.type !== this.node.type) { if (node.type !== this.node.type) {
@ -207,6 +217,23 @@ class ReactNodeView extends NodeView<
this.editor.off('selectionUpdate', this.handleSelectionUpdate) this.editor.off('selectionUpdate', this.handleSelectionUpdate)
this.contentDOMElement = null this.contentDOMElement = null
} }
updateElementAttributes() {
if (this.options.attrs) {
let attrsObj: Record<string, string> = {}
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)
}
}
} }
export function ReactNodeViewRenderer( export function ReactNodeViewRenderer(

View File

@ -57,14 +57,6 @@ export interface ReactRendererOptions {
* @example 'foo bar' * @example 'foo bar'
*/ */
className?: string, className?: string,
/**
* The attributes of the element.
* @type {Record<string, string>}
* @default {}
* @example { 'data-foo': 'bar' }
*/
attrs?: Record<string, string>,
} }
type ComponentType<R, P> = type ComponentType<R, P> =
@ -103,7 +95,6 @@ export class ReactRenderer<R = unknown, P = unknown> {
props = {}, props = {},
as = 'div', as = 'div',
className = '', className = '',
attrs,
}: ReactRendererOptions) { }: ReactRendererOptions) {
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString() this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
this.component = component this.component = component
@ -116,12 +107,6 @@ export class ReactRenderer<R = unknown, P = unknown> {
this.element.classList.add(...className.split(' ')) this.element.classList.add(...className.split(' '))
} }
if (attrs) {
Object.keys(attrs).forEach(key => {
this.element.setAttribute(key, attrs[key])
})
}
if (this.editor.isInitialized) { if (this.editor.isInitialized) {
// On first render, we need to flush the render synchronously // On first render, we need to flush the render synchronously
// Renders afterwards can be async, but this fixes a cursor positioning issue // Renders afterwards can be async, but this fixes a cursor positioning issue
@ -163,4 +148,10 @@ export class ReactRenderer<R = unknown, P = unknown> {
editor?.contentComponent?.removeRenderer(this.id) editor?.contentComponent?.removeRenderer(this.id)
} }
updateAttributes(attributes: Record<string, string>): void {
Object.keys(attributes).forEach(key => {
this.element.setAttribute(key, attributes[key])
})
}
} }