How to traverse to another node in a tree-like fashion and apply a transform?

I’m working with a schema in which the doc can only contain “row” nodes (in schema: content: 'rownode*', and each rowNode must contain precisely nodes “left” “mid” and “right” (in schema: content: 'leftnode midnode rightnode')

So a resulting document might look like this:

This rigid structure of container nodes seems to lend itself to working with content in a “tree-like” fashion, whereas working with the nodes illustrated above, the contents of MidNode - which can be a more flexible variety of nodes dealing with text and the like - seems to lend itself to a “flat” structure.

I’d had in mind this sentence from the guide which I interpreted to mean that ProseMirror is flexible in working with both of these approaches:

ProseMirror nodes support two types of indexing—they can be treated as trees, using offsets into individual nodes, or they can be treated as a flat sequence of tokens.

However, when I try to work with nodes in a tree-like manner, it feels like I’m “fighting against the API” to make it do what I want.

Specifically, I do find it pretty easy to traverse to a node in the object hierarchy and get the most types of information I want from it. But then if I want to apply transforms to that node’s content, that requires a (“flat-style”) pos, and that seems like something the tree-like object’s interface makes, perhaps intentionally, difficult to obtain.

This sense that I might be “fighting against the API” compounded by statements on the forum I read which seem to advise against working with it in this way:

So I’m a bit confused as to the fundamentals of what the tree-like structure should and shouldn’t be used for - particularly in the case of examples like the diagram above that enforce a hierarchical structure by way of the schema.

The specific task I’m trying to complete is:

If the cursor is at the very beginning of MidNode B, and the user hits “backspace,” I want it to move the remaining content in MidNode B (if any) to the end of MidNode A, and then delete the entire RowNode B and its contents.

I figured I’d approach this by creating a new command that checks for appropriate conditions to do this move/delete, and then dispatches the transform, and then I’d add the command to the backspace key mapping chain of commands.

To “check for appropriate conditions” is pretty easy- the “tree” aspect being that I can “find” MidNode A if it exists by way of the tree from a ResolvedPos in MidNode B. But when it comes to actually go ahead and put the data there, it seems very difficult, since I’ve traversed by tree and now need a pos. Between the difficulty and the forum comments I highlighted above, it has me wondering if there’s another way I should be going about this.

Is there a proper way to get the node positions I need to do the transform I’m describing? Or is there another way I should be approaching this task?

You can either pass the current position offset down your recursive functions, or work with resolved positions instead, which can to a large extent replace recursion.

Here’s an example of the code I’m working with. I’m using the tree structures in the resolved position of the selection to locate the midNode above it in the document. (I’m not using recursion, but rather using explicit or relative indices based on the assumption the document is adhering to the schema I gave it.)

function deleteRow (selection) {
  if (selection.empty && selection.$cursor
    && selection.$anchor.parentOffset === 0
    && selection.$anchor.depth === 3
    && selection.$anchor.node(2) === "midNode"
    && selection.$anchor.index(0) > 0) {

    let currentMidNode = selection.$anchor.node(2)
    let midNodeAbove = selection.$anchor.doc.content.content[selection.$anchor.index(0) - 1].content.content[1]

    // Now I can read from the midNode above this one.
    // But I don't know its pos, so how do I insert data into it?

  } else {
    return false

So, the tree structure in the resolvedPos is helping me find and read from the midNode “above” this one on the page. But it doesn’t seem to help me write to it. Is there something I’m missing or do I need to approach it differently?

This code is attempting to do the task I described earlier:

OK, I think I was able to figure this out.

Instead of trying to go “up” the hierarchy by calling the resolvedPos’s node(depth) method, I’ll do it by getting the resolvedPos of the pos just outside the node’s boundaries. This pos’s parent will be the original node’s parent, so I’ve effectively gone “up” the hierarchy, but at this point I have the parentOffset, which I can use to either traverse further, or actually perform transforms.