https://grack.com/demos/adventure/
The trick for me was just using a hidden input and updating the state of an in game input box. The code is ancient by today's standards but uses a reasonably simple technique to get the selection bounds of the text.
It works with auto complete on phones and has been stable for a decade.
One of the frustrating things about web dev, I find, is the staggering gulf between apparently nearly identical tasks and unpredictability of it. So often I will find myself on gwernnet asking Said Achmiz, 'this letter is a little too far left in Safari, can we fix that?' and the answer is 'yes but fixing that would require shipping our own browser in a virtual machine.' ¯\_(ツ)_/¯