|
5 | 5 | // value and drops the caret at the end. The update function knows the offset the |
6 | 6 | // caret should have, and calls this once the patch has landed. |
7 | 7 | // |
8 | | -// The rAF is what waits for that patch: the update returns before miso has |
9 | | -// touched the DOM, so setting the selection synchronously would set it on the |
10 | | -// pre-rewrite text and be overwritten a frame later. |
11 | | -globalThis.setEditorCaret = function (offset) { |
12 | | - requestAnimationFrame(function () { |
| 8 | +// Waiting for that patch is the whole difficulty. miso diffs inside an animation |
| 9 | +// frame of its own, queued after the one this call could take, and assigning |
| 10 | +// .value moves the caret to the end — so setting the selection a frame too early |
| 11 | +// is silently undone. Rather than guess the ordering, wait until the textarea |
| 12 | +// actually holds the text the caret offset refers to, then place it. |
| 13 | +// |
| 14 | +// The bound on retries keeps a mismatch (an edit that arrived meanwhile, say) |
| 15 | +// from spinning forever; giving up leaves the caret where the browser put it. |
| 16 | +globalThis.setEditorCaret = function (offset, expected) { |
| 17 | + let tries = 0; |
| 18 | + const place = function () { |
13 | 19 | const el = document.querySelector("textarea.editor"); |
14 | 20 | if (!el) return; |
| 21 | + if (el.value !== expected && tries < 10) { |
| 22 | + tries++; |
| 23 | + requestAnimationFrame(place); |
| 24 | + return; |
| 25 | + } |
15 | 26 | const i = Math.max(0, Math.min(Number(offset) || 0, el.value.length)); |
16 | 27 | el.focus(); |
17 | 28 | el.setSelectionRange(i, i); |
18 | | - }); |
| 29 | + }; |
| 30 | + requestAnimationFrame(place); |
19 | 31 | }; |
0 commit comments