Placeholder for empty text input element

Hi!

Remark:

  1. My english is bad, I’m sorry x)
  2. It’s possible that someone asked this question here, I tried to find something similar but it was unsuccessful. Maybe this topic will help someone in the future!

I started learning how to use ProseMirror, it isn’t easy but I feel how this is a strong tool. First of all, I wanted to realize placeholder for an empty text element at the focus time. I did it by making the plugin, my decision:

import { Plugin } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';

const placeholder = placeholder => {
  return new Plugin({
    props: {
      decorations(state) {
        const textContent = state.tr.selection.$from.parent.textContent;
        const resolved = state.doc.resolve(state.selection.from);

        if (!textContent) {
          const decoration = Decoration.node(
            resolved.before(), 
            resolved.after(), 
            {
              'data-placeholder': placeholder,
            },
          );

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

export {
  placeholder,
}
.editor__paragraph[data-placeholder]::before {
  pointer-events: none;
  position: absolute;
  content: attr(data-placeholder);
}

I’d be happy if you could appreciate my result, I’m not sure how much it’s fine to use state.tr.selection.$from.parent.textContent for defination a current editable node.

Thanks all! :man_technologist:

It’s ok, but not good.

Here is a better version.

import type { Node as ProsemirrorNode } from 'prosemirror-model';
import { Plugin } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view';

const isEmptyParagraph = (node: ProsemirrorNode): boolean => {
  return node.type.name === 'paragraph' && node.nodeSize === 2;
};

export const placeholder = (placeholder = 'Default placeholder') => {
  return new Plugin({
    props: {
      decorations(state) {
        const { $from } = state.selection;
        if (isEmptyParagraph($from.parent)) {
          const decoration = Decoration.node($from.before(), $from.after(), {
            'data-placeholder': placeholder,
          });
          return DecorationSet.create(state.doc, [decoration]);
        }
      },
    },
  });
};

Thanks for the reply! If I understand correctly, your variant differs because you check a node on type before a modification, yeah? It could be important. And you check nodeSize, not textContent, hmm, I think that checking of textContent is more obvious.