The Selection API is confusing and weird.
But, here’s what I’ve discovered: just use setBaseAndExtend, and when (rarely) needed, extend.
Every selection in a browser consists of:
- an “anchor” – the beginning, where you started dragging, and
- a “focus” – the end, where your stopped dragging, and where your cursor is
To select some text in a browser, find the from and to DOM nodes you want, and how far through them you want to be, and set the selection like this:
const sel = document.getSelection(); sel.setBaseAndExtent(from_node, from_offset, to_node, to_offset);
If you want just a cursor with nothing selected, use the same node and offset for both from and to.
The Internet has a lot of recipes that involve creating Ranges and adding them to the Selection, but there is no need to do that.
Finding the from and to nodes
When the locations are inside a text node in the DOM, it’s easy to find the from and to nodes – they are just the text nodes, and the offsets are how many UTF-16 code units through the text you want to be. [Not sure what a code unit is? See my video Interesting Characters].
“But isn’t your cursor always in a text node?”
No – for example, when I have two <br>s next to each other, and I want the cursor to be in between them (so it’s on the blank line). In this case there is no text node, and browsers handle it weirdly.
In Firefox, when my cursor is between two br nodes, the node is set to the tag (e.g. a div) that contains the brs, and the offset is the index of the second br node in the list.
Other browsers may well do it differently.
So do expect that the nodes may not be text nodes, and where that is the case, the offset will be the index of one of their children.
To select the blank line (between two brs) you can actually specify the br node directly, and give an index number of 0, and it works too. But, it does not match exactly what the browser sets when you click on the blank line, so I can’t guarantee it works exactly the same.
The fly in the ointment is backwards selections: when you click and drag the mouse from right to left, the selection that is created in the browser is backwards: the anchor is on the right and the focus is on the left.
However, if you call setBaseAndExtent attempting to set up a selection like that, the focus and anchor will be swapped so that the anchor is on the left, and the focus is on the right.
But never fear! We can use extend to force the selection to be what we wanted:
const sel = document.getSelection(); sel.setBaseAndExtent(from_node, from_offset, from_node, from_offset); sel.extend(to_node, to_offset);
For more info, check out my demo page: Browser Selections Inventory.