|
| 1 | +function scrollTo(element, top, behavior) { |
| 2 | + const target = Math.max(0, Math.min(top, element.scrollHeight - element.clientHeight)); |
| 3 | + if (Math.abs(target - element.scrollTop) < 1) return Promise.resolve(); |
| 4 | + return new Promise(resolve => { |
| 5 | + const events = element === document.scrollingElement ? document : element; |
| 6 | + if (behavior === 'smooth') events.addEventListener('scrollend', resolve, { once: true }); |
| 7 | + element.scrollTo({ top: target, behavior }); |
| 8 | + if (behavior === 'instant') resolve(); |
| 9 | + }); |
| 10 | +} |
| 11 | + |
| 12 | +export function navigateTo(view, anchor) { |
| 13 | + view.requestMeasure({ |
| 14 | + read: () => { |
| 15 | + const line = view.lineBlockAt(anchor); |
| 16 | + const editor = view.dom.getBoundingClientRect(); |
| 17 | + return { |
| 18 | + editorTop: line.top + line.height / 2 - view.scrollDOM.clientHeight / 2, |
| 19 | + pageTop: window.scrollY + editor.top + editor.height / 2 - window.innerHeight / 2 |
| 20 | + }; |
| 21 | + }, |
| 22 | + write: async ({ editorTop, pageTop }) => { |
| 23 | + const behavior = matchMedia('(prefers-reduced-motion: reduce)').matches ? 'instant' : 'smooth'; |
| 24 | + await Promise.all([ |
| 25 | + scrollTo(view.scrollDOM, editorTop, behavior), |
| 26 | + scrollTo(document.scrollingElement, pageTop, behavior) |
| 27 | + ]); |
| 28 | + view.dispatch({ selection: { anchor } }); |
| 29 | + view.focus(); |
| 30 | + } |
| 31 | + }); |
| 32 | +} |
0 commit comments