How to set custom class to a prosemirror plugin on focused state only

How to set custom class to a prosemirror plugin on focused state only ?

I am using tiptap.dev for creating a custom Mark extension. Now I need to have a class in my custom extension only when it is focused. How to achieve this?

Code:

import { Mark, mergeAttributes } from '@tiptap/core'
import { Extension } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Decoration, DecorationSet } from '@tiptap/pm/view'

export const Suggestion = Mark.create({
    name: 'suggestion',

    addOptions() {
        return {
            HTMLAttributes: {
                class: 'suggestion-highlight',
            },
        }
    },

    parseHTML() {
        return [
            {
                tag: 'span[data-type="suggestion"]',
            },
        ]
    },

    renderHTML({ HTMLAttributes }) {
        return [
            'span',
            mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
            0,
        ]
    },
    addProseMirrorPlugins() {
        return [
            new Plugin({
                key: new PluginKey('activeSuggestion'),
                props: {
                    decorations: (state) => {
                        console.log('state', state)
                    }
                }
            })
        ]
    }
})

I have tried this which kind of works but creates a nested span which I don’t really want.

addProseMirrorPlugins() {
        return [
            new Plugin({
                key: new PluginKey('activeSuggestion'),
                props: {
                    decorations(state) {
                        const selection = state.selection;
                        const decorations = [];

                        state.doc.nodesBetween(selection.from, selection.to, (node, position) => {
                            const isSuggestionNode = node.marks.some(mark => mark.type.name === 'suggestion');

                            if (isSuggestionNode) {
                                decorations.push(Decoration.inline(position, position + node.nodeSize, { class: 'suggestion-active' }));
                                console.log(position, position + node.nodeSize)
                                console.log(node)
                            }

                        });

                        return DecorationSet.create(state.doc, decorations);
                    }
                }
            })
        ]
    }

When not focused:

<span class="suggestion-highlight">Hello</span>

When focused:

<span class="suggestion-highlight"><span class="suggestion-active">Hello</span></span>

If by ‘focused’ you mean that the editor has focus, you can use a selector like .ProseMirror-focused .suggestion-active. If you mean that the cursor is in the element, you’ll have to make your plugin check for that and only create the decoration when that is the case.

1 Like

by focus I meant, to the element when having the cursor in it. Can you please help me to construct the plugin for that?

That’s how I am doing it, and I think I am very close, however it wraps contents inside additional span which i don’t want.

addProseMirrorPlugins() {
        return [
            new Plugin({
                key: new PluginKey('activeSuggestion'),
                props: {
                    decorations(state) {
                        const selection = state.selection;
                        const decorations = [];

                        state.doc.nodesBetween(selection.from, selection.to, (node, position) => {
                            const isSuggestionNode = node.marks.some(mark => mark.type.name === 'suggestion');

                            if (isSuggestionNode) {
                                decorations.push(Decoration.inline(position, position + node.nodeSize, { class: 'suggestion-active' }));
                            }

                        });

                        return DecorationSet.create(state.doc, decorations);
                    }
                }
            })
        ]
    }

There’s no way to add attributes to mark nodes. Wrapping them in an extra element is the only way to decorate these.

Thanks for clarifying. I am happy to say that I have made it work with the wrapping span.

Anyone else looking for the same in future -

You may need to use the ‘AI suggestion extension’ from tiptap or try to build a custom extension taking reference from it’s code.