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

View File

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