Documentation for CodeMirror-measure elt

#1

My project involves lots of CM Widgets, and some drag-and-drop code requires that I get the ClientRect of those widgets periodically.

I can’t find much documentation for the CodeMirror-measure element, but it’s presenting some behavior that is problematic. As far as I can tell, it may contain line content for the longest offscreen lines in the doc - whether or not they’re in the viewport - presumably to keep the scrollbars accurate.

However, it’s positioned at the top of the editor with visibility:hidden, meaning that calling getBoundingClientRect() on the widest widget that’s offscreen will return a ClientRect as if it’s (a) onscreen and (b) positioned at the top of the viewport. This wreaks havoc with the drag-and-drop code, since offscreen widgets are calculating as if they’re onscreen!

So I have two questions:

  1. Is the use of this element documented somewhere? If I understand it better, I can probably work around it.
  2. Is there any reason that it has to be positioned inside the viewport (or the editor window itself)? Simply moving it outside the viewport would prevent any conflicts with DND code.

Thanks in advance!

#2

No—it’s an implementation detail that isn’t supposed to matter to client code.

Yes, this makes sure that the same CSS rules apply to it that apply to the rest of the content.

This is kind of a hidden scratch space where DOM that needs to be measured but not displayed is rendered. It might make sense to clear it right after measuring, to avoid problems like this one. Do you want to submit a PR for that?

#3

Quickly clearing after measuring is certainly one approach, but I could imagine some mysterious conditions where it’s not cleared in time when a call to getBoundingClientRect() comes through. Happy to submit a PR, but I’d like to rule out two other approaches first.

  1. Why can’t we position the measurement parent below the editor window? If it’s still in the same DOM hierarchy, won’t the same CSS rules apply? This feels like the least-invasive option, since it’s literally a CSS tweak.

  2. Is there merit to providing a widget.getBoundingClientRect() method, which produces the dimensions of the widget if it’s visible, and false if it’s not? I know I’m not the only one who would have liked to have this feature anyway, and if CM provides it itself then can I be smart about returning false even if the node is being rendered using for measurement.

If both of these seem cumbersome to you, I’ll submit a PR to clear-after-measure.

#4

Parent selectors.

That’d be easy to do. What would the use case be, exactly?

#5

Huh. I’m scratching my head here. If all the ancestors and children are the same, and we just move the measurement node 50px to the left, how would that break any selectors? I didn’t even know there were selectors that depended on the actual position of the node in the browser window!

Here are a few possible use-cases, off the top of my head:

  • Someone wants to include an image in a program they’re working on. They click a button or a hotkey in the editor, and a widget appears at the cursor location that allows them to drag-and-drop the file. Behind the scenes, some code handles uploading the file to GDrive and returning some boilerplate code that loads the file from the shared URL. Writing this drag-and-drop code may require knowing the boundary of the widget

  • Widget size might determine how it’s styled (see Scratch 3.0, which cleverly switches between horizontal and vertical blocks based on screen dimensions). A cheap-and-easy way of accessing the size of any widget would be handy.

  • The mere existence of widgets brings up the question of whether dropping something on the editor represents the user’s intention to drop it at the top-level (i.e. - at a cursor position), or to drop it on a particular widget. Without knowing whether a drop occurred on a widget, we can’t distinguish the two

#6

No, that wouldn’t cause different selectors to apply—but I’m also not sure how it solves your problem here. And absolute positioning will cause the layout of a node to change in some circumstances.

Can’t you just use the target from the dragstart event?

This is what the handleMouseEvents option is intended for—thought it may not be fine-grained enough. If your widget has internal mouse interaction, you set it to false and can handle events like drop yourself.

#7

OK, I’m definitely convinced that no method is needed. Thanks for talking through this with me. :slight_smile:

As for the CSS - this solves my problem perfectly. I don’t care about ClientRects for nodes that are outside of the editor, so as long as the CodeMirror-measure elements can be moved out the way I’ll be fine. And this does feel right: an implementation detail like this shouldn’t leak information into the viewport, if it’s not supposed to interfere with that viewport.

I was also thinking I could add some JS to my DND code that uses getComputedStyle(widget).visibility, and test whether a node is hidden or not. But that requires me to understand more about how measurement works. If a node is in the viewport, am I guaranteed that it’s not going to be used in the measuring element? Can I treat this as a strictly "it’s in the viewport or it’s visibility:hidden" situation?

#8

I’m still a bit baffled as to what you are trying to do. Are you walking the DOM looking for a node that looks like your widget? Would documenting TextMarker.widgetNode help you?

Can’t guarantee it. All kinds of things might get measured, and the measured value might stick around in the DOM for a bit, so that’s probably not safe.

#9

I’m using a react library to handle drag and drop, and there are three drop conditions I need to consider:

  1. Text is dropped into a valid target, contained within a widget. ( The targets are themselves react components, so this is easy to solve )

  2. Text is dropped onto a widget, but not on a valid target.

  3. Text is dropped at a CM cursor location (not on any widget)

To distinguish between two and three, I need to know if the drop coordinates fall within the boundaries of any widget’s DOM node.

( I know about replacedWith, but what is widgetNode? )

#10

Doesn’t the drop event’s target property (or if that fails elementFromPoint) tell you whether the drop happened in a widget?

#11

I don’t have access to the raw event - only the React wrapper’s API, which exposes little beyond (x,y). And unfortunately, elementFromPoint also seems to ignore visibility:hidden – I just tested it out, and it produced the CodeMirror-line <pre> node that’s stored in the measurement element, even though its parent should be hidden.

Edit: Nope - I’m wrong about the elt returned by elementFromPoint. I can iterate over the widgets and see if any of them contain that element, and that solves the issue in a lovely way.

As always, my hat is off to you! (And if anyone else is reading this, I strongly encourage you to set up a monthly donation!)