I am doing my best to find a workable strategy for finding DOM bounding rectangles for nodes within the apply function of a decorations plugin. I have no idea if the DOM itself has been updated prior to the apply function being called (i.e. an Enter key adds a new paragraph), so I am not sure how workable this is in the real world because I am not sure of the state of the DOM during the plugin’s apply function execution.
Regardless, one idea I had was to use the following simple function:
function getBoundingRectForNode(view, pos) {
let domElement = view.nodeDOM(pos);
if (domElement)
return domElement.getBoundingClientRect()
return false;
}
Within the apply method of the decoration plugin, I have the following typical code that cycles through the nodes in the transformed doc, calling my bounding rectangle function:
tr.doc.descendants((node, pos) => {
let rect = getBoundingRectForNode(window.view, pos)
…
This works fine on the initial display of the editor contents, but if I then type something anywhere in the document, my function returns false for everything after the spot where I edited. Per the PM docs, nodeDOM “May return null when the position doesn’t point in front of a node or if the node is inside an opaque node view.”
My conclusion is that view I pass in (the editor view stored in the window.view variable) uses the existing but stale view prior to the mapping in the transform and thus the positions are no longer in sync with the latest node positions.
So, I’d love any ideas or strategies to make this work - or work the best it can. All the existing nodes for the rest of the doc are still there, it is just that their document position is no longer synced with the current transform. Clearly if the new text entered has not yet been rendered on screen, it would be tough to get any DOM Node for it (assuming the node display update code is yet to execute), which begs the question of the order in which a key is intercepted in a transaction, the document being updated with new nodes maybe being created and inserted, then the view both running plugins and eventually rendering the DOM.
I can use either the top/bottom or maybe just the height to insert widget decorations that simulate page breaks. Real world Pagination is for me the toughest thing with a document abstraction library such as ProseMirror. I do love the idea of transforms that work independentlty of the DOM, creating the new document and then letting PM redraw what is needed, but as new paragraphs or even word wrapping happens, I need to update where the decorations are placed to have lines/paragraphs move up and down from page to page. The decorations draw that divider between pages.
So, being able to get DOM info on nodes reliably is key, if the transform has mapped node positions to new positions, making the current view obsolete for calculations until it gets updated to reflect the action, it makes the use of the nodeDOM function unusable during the plugin apply method.
I guess one big question is whether a decorations plugin can be flexibly used for rapid on the fly pagination, assuming that when a key is typed it possibly causes new nodes to be added (on an enter key) or new visible lines to occur (due to word wrapping in the on screen DOM), pushing down the stuff below it. If the plugin code is passed a transform where the DOM has yet to be updated to be able to even calculate heights and positions, it makes it tough to recalculate the page break positions.
I muse whether it could work to have a non visible mimic of the editor div , then to render the node there (accounting for margins to get the wrapping silulated) the get its height, then to save the new height somehow with the node.
Note that the holy grail to avoid flickering of the page break is for all those calculations to occur pior to the DOM being refreshed. But if the updated/new nodes have yet to be rendered, there would be no way of knowing the height after the change has been applied.
State fields explicitly don’t get access to the view (though of course you can kludge around that with something like your window.view) because they are part of EditorState, an abstraction that’s independent of the view, and, as you found, state updates run before the view is even updated, so the thing you’re trying to do doesn’t work.
If you want to have decorations that are dependent on the visual dimensions of elements in the editor, your best best is probably to have a plugin view observing the document, checking if your current set of decorations are up to date, and dispatching a transaction whose metadata instructs the state field that holds the decorations to update.
I have mostly solved the issue by creating a hidden div below the editor that carries over the css (for margins etc), using that to calculate the height of nodes with their current contents. Not going to detal my code, but with those updated heights I can determine where to stick in the page break decoration widgets as well as the extra margins needed to keep them stable and not bouncing up and down.
The key to this working is that the plugin is called before anything is being done to update the DOM, so I can get it all right so that it looks like a Word-like page.