Placement of decorations around marks

As far as I can tell inline decorations that are applied across the same range as a mark are always applied inside whatever marks are applied to the same node. As for widgets, these are applied on the inside of a mark’s DOM node when applied at the start of the mark range and outside of a marks’ DOM node when applied at the end of the range. As far as I can tell side spec for a widget isn’t used here. Have I missed something here, is this behaviour editable in any way? If not is this an implementation detail or is guaranteed to say like this?

Also maybe I’ve misunderstood some of this and it’s quite accurate :wink:

This is indeed an implementation detail that hasn’t been thought through very much yet. The renderer syncs the wraping marks on every regular node, and will just render widgets in the context of the node before them. It would probably not be terribly hard to change this, and have it first sync to the next node when side > 0. Would that help? Do you have any opinion on what should happen when there is no node after the widget?

Ah yeah, I got them the wrong way around. I think it would help if you could use the side to specify where it falls for most use cases. Currently I’m working with decos that ‘belong’ to a mark - a bit ‘out there’ I know - with the potential of overlapping marks occuring. For this particular use case one might need some sort of priority system ultimately for predictable rendering around marks. Maybe that would be too fiddly but, as far as I know, there would be no way to render a widget inside like:

<mark1>
  <widget />
  <mark2>
    text
  </mark2>
</mark1>

I think that would be pretttty fiddly (although I’m not too familiar with the render logic) given that it would ask for a priority system that would, at the very least, be shared across both marks and decos at the very least.

I think using the side to infer the node context would be a good start and helpful for some cases, and as for if there were no node after the widget I’m not sure. Would that not be something that could be handled in “userland” so it would be ok just to throw?

The bigger picture is I’m using decorations to mark the start and end of marks (that can be nested, which is the major issue re: styling) as it gives me the second pass I need to work out the positions of the DOM nodes that represent a contiguous mark, and then style them accordingly.

Hey @rich and @marijn

Do you come to any solution regarding this?

We have a related issue I think:

At the moment, our inline decorations are appearing nested within the document marks. This means our decoration styles do not affect our document marks (In our example strikethroughs are ignored by decorations)

Do you know of a fix or an alternative implementation?

Would it help at all if widgets with a side > 0 are rendered with the same marks as the node after them, and those with a side <= 0 with the same marks as the node before them? If there’s no node before/after, the widget would be rendered without marks.

I’m not sure why I didn’t reply directly to this suggestion above the first time you made it but it would definitely help here. I’m not sure if there are any examples where you’d want a cursor between the end of the mark and the widget (either inside or outside of the mark) but I can’t think of one …

You seem to be under the impression that there’s a significance to whether the cursor is inside or outside a mark—that is not the case. Each inline position has a single cursor position, and the inclusive spec on the marks determines which marks are applied when you type at a position that has different marks before and after it.

Given that fact, does my proposed feature still sound useful?

On that basis that makes definitely makes sense thanks.

However, with my current setup, changing the inclusive value does seem to change where the cursor is rendered, either inside or out. After playing around with it I can see that this is almost certainly to do with pseudo elements added inside the decoration. I didn’t realise this behaviour was limited to that specific edge case. I would also think it would be quite useful (for example if the mark was padded horizontally) to have the cursor render inside or outside depending on whether it was inclusive or exclusive as a visual hint to the user? It’s actually something I’ve been using without realising it wasn’t a feature given the pseudo elements that were unwittingly triggering it for me!

The example I was playing with is here, where, if you change the inclusive tag for strong (in index.js), you can see the rendered cursor position changes at the edges.

Browsers tend to not play nice with this and ‘normalize’ selection positions. Also, we want to mess with the DOM selection as little as possible during editing and normal cursor movement, because doing so tends to disrupt browser features like spell check and drag-selection.

Interesting, I can imagine that now you say it. Also I’m assuming the edge case with the pseudo elements is always likely to be an implementation detail (of the browser) and not something that is fixable or standardizable in the short term.

To be clear, this would still be very useful (and could potentially help me drop the pseudo elements).

Those are created by ProseMirror, as a hack to make sure the text that’s typed next will have the proper marks/styling.

Sorry I’m not talking about pseudo elemtents added by Prosemirror (I think you’re referring to the span with the zero width space inside?). My question was, will the example at jmz0nn6xy - CodeSandbox always work? I.e: me adding in :before / :after pseudo elements (in the CSS) to an inline mark mean that’s inclusive does render in different positions (inside or outside the mark). I think this behaviour is affected by that span that I think you’re talking about but it does seem to go against what the libraries intention is:

And your info around dragging selection seems to make sense here, despite the fact that I find it useful. Sorry if I’m digging in to semantics too much but these are proving to sticky situations for some of the stuff I’m currently doing so thanks for your responses.

This patch implements the treatment of side that we discussed. I’ll release it soon, but I’m still working on some other stuff that I might to put into the same release.

There’s a bunch of things going on in that page, but I guess you are talking about the browser adding extra cursor positions because of the :before/:after pseudo elements. If I remember correctly that’s already not the same in all browsers, so I can’t promise a lot there. I don’t see a reason why ProseMirror would start actively interfering with it, but I guess it’s possible that it will at one point.

Amazing, thanks :+1:

No problems hopefully removing them and adding widgets that are definitely inside a mark will help me here and make this redundant. Out of curiosity is skipIgnoreNodesLeft (spelling) the function that ignores that extra cursor position added by, say, an :after pseudo element?

It ignores widgets and nodes added by the view to work around browser issues (such as cursor wrappers and extra <br> nodes in empty textblocks). Pseudo elements aren’t part of the DOM data structure and can’t be observed by scripts (at least, not like this). This also makes it impossible to intentionally set the selection before or after them — they don’t get their own DOM offset, so you can’t specify whether a Range endpoint should be before or after it. (This is an area where the design of the DOM, CSS, and contentEditable interact in bloody awful ways that I guess the people designing them didn’t ever think about.)