Skip to content

Commit

Permalink
feat(react): allow attrs to be a callback
Browse files Browse the repository at this point in the history
  • Loading branch information
YaoKaiLun authored Aug 18, 2024
1 parent c99627d commit 4ff2a4e
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/warm-bananas-call.md
Original file line number Diff line number Diff line change
@@ -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
31 changes: 29 additions & 2 deletions packages/react/src/ReactNodeViewRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
DecorationWithType,
Editor,
getRenderedAttributes,
NodeView,
NodeViewProps,
NodeViewRenderer,
Expand All @@ -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<
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down
21 changes: 6 additions & 15 deletions packages/react/src/ReactRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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> =
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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])
})
}
}

0 comments on commit 4ff2a4e

Please sign in to comment.