Multiple "fields" on a single line


My schema includes a “replace” node which contains two child nodes: label and content. I want these to be displayed on the same line, so I’m using spans to represent them in the DOM. I visually separate the label and the content using ::before and ::after CSS pseudo elements.

replace: {
  content: "replace_label replace_content",
  group: "block",
  defining: true,
  parseDOM: [{tag: "div.replace"}],
  toDOM() { return ["div", {"class": "replace"}, 0] }
replace_label: {
  content: "text*",
  marks: "",
  parseDOM: [{tag: "span.replace_label"}],
  toDOM() { return ["span", {"class": "replace_label"}, 0] }
replace_content: {
  content: "inline*",
  parseDOM: [{tag: "span.replace_content"}],
  toDOM() { return ["span", {"class": "replace_content"}, 0] }

Keyboard navigation of these nodes was problematic, but I was able to fix this by binding custom functions to the Enter and ArrowLeft keys.

One issue that remains is that a <br> element is inserted when the label is empty, splitting my replace node over two lines. From reading other forum topics, I understand this <br> is required for things to work. I’ve tried hiding the <br> using CSS’s display: none, but this makes it impossible to position the cursor in the label node.

I’ve looked at making the label and content nodes inline elements. If I understand it correctly, other forum posts suggest that inline nodes with text content are not supported. My first experiments with this did result in crashes in PM code.

I’m not sure how to proceed with this. Any suggestions are appreciated! My current idea is to see whether a placeholder could perhaps replace the <br>, which would solve the problem and provide a better user experience as a nice extra.

Have you tried making the elements regular blocks and giving their parent a display: flex style?

Thanks, Marijn. I’m not really sure what you meant (my HTML/CSS knowledge is pretty limited), but I managed to get the desired effect by setting display: flex on the .replace div. For some reason, it was also necessary to move the .replace_label::after content to .replace_content::before to make this work. I don’t understand why, but hey :-D.

This also happens to fix arrow-key navigation, so I don’t need to handle that with a custom key binding either!

Correction: this doesn’t fix arrow-key navigation in Firefox, but it did fix other navigation/inputrule issues in Safari and Chrome. So I still need the custom ArrowLeft key binding, but now it seems to work well across these three browsers.

Another issue I discovered is that with display: flex on .replace, vertical keyboard navigation is no longer intuitive. Without display: flex, horizontal navigation on Chrome and Safari has issues. So this solution is not yet completely satisfactory. I’ll try to remember to report my findings when I revisit this.

This remains to be very problematic, especially with trying to support multiple browsers.

@marijn Would you consider making it configurable which DOM element is placed inside empty text block nodes (on a per-node type basis)? I think that being able to set a zero-width no-break space instead of a <br> would solve these issues. I would be happy to provide a pull request.

EDIT: My initial tests for this indicate that this isn’t as simple as I thought. I tried inserting a span (with a zero-width no-break space or a placeholder) with contenteditable=false makes it impossible to position the cursor at that position.

Perhaps it’s possible to write a plugin that detects empty nodes and inserts a zero-width space. This space can then be removed after the user adds text. But that feels a bit too hacky…

The <wbr> seemed like a good candidate to replace <br>. Alas, this also causes problems such as the cursor disappearing…