# Inline widget.updateDOM not called on specific conditions

**URL:** <https://discuss.codemirror.net/t/inline-widget-updatedom-not-called-on-specific-conditions/9644>\
**Category:** v6\
**Created:** [January 6, 2026, 12:35pm UTC](https://discuss.codemirror.net/t/inline-widget-updatedom-not-called-on-specific-conditions/9644 "2026-01-06T12:35:28Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![tiredstudent](https://discuss.codemirror.net/letter_avatar_proxy/v4/letter/t/f9ae1b/32.png) [@tiredstudent](https://discuss.codemirror.net/u/tiredstudent)\
**Post date:** [January 6, 2026, 12:35pm UTC](https://discuss.codemirror.net/t/inline-widget-updatedom-not-called-on-specific-conditions/9644/1 "2026-01-06T12:35:28Z")

</div>

Hi! This only happens in very specific cases, and to be honest block widgets are probably better for my use case (though I’ve been avoiding moving to them for a separate reason), so I was second guessing whether to report this or not, but thought I’d leave the decision to fix (or not) for maintainers.

When replacing multiple lines, a tall inline widget whose position is out of view is recreated when selection is updated via a click event. So it doesn’t happen when moving with right or left arrow keys, or when just replacing one line. [Here](https://codemirror.net/try/#c=aW1wb3J0IHtiYXNpY1NldHVwfSBmcm9tICJjb2RlbWlycm9yIgppbXBvcnQge0VkaXRvclZpZXcsIERlY29yYXRpb24sIFdpZGdldFR5cGV9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciCmltcG9ydCB7U3RhdGVGaWVsZH0gZnJvbSAiQGNvZGVtaXJyb3Ivc3RhdGUiCmltcG9ydCB7bWFya2Rvd259IGZyb20gIkBjb2RlbWlycm9yL2xhbmctbWFya2Rvd24iCmltcG9ydCB7bGFuZ3VhZ2VzfSBmcm9tICJAY29kZW1pcnJvci9sYW5ndWFnZS1kYXRhIgoKCmNsYXNzIEVuZE9mTGluZVdpZGdldCBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKCBub3RpZnkgKSB7CiAgICBzdXBlcigpCiAgICB0aGlzLm5vdGlmeSA9IG5vdGlmeTsKICB9CiAgZXEoIHdpZGdldCApIHsgcmV0dXJuIHRydWUgfQogIHRvRE9NKCApIHsKICAgIGNvbnNvbGUubG9nKCd3aWRnZXQudG9ET00nKQogICAgY29uc3QgZWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKQogICAgLy8gZWwuc3R5bGUuZGlzcGxheSA9ICdpbmxpbmUtYmxvY2snCiAgICBlbC5zdHlsZS5jb2xvciA9ICdibHVlJwogICAgZWwuc3R5bGUuaGVpZ2h0ID0gJzUwMDBweCcKICAgIGVsLmlubmVySFRNTCA9ICcqJwogICAgdGhpcy5ub3RpZnkgJiYgd2luZG93LmFsZXJ0KCdXaWRnZXQudG9ET00gY2FsbGVkJykKICAgIHJldHVybiBlbAogIH0KfQoKZnVuY3Rpb24gZGVjb3JhdGVMaW5lKCBsaW5lTnVtYmVyLCBzdGF0ZSwgbm90aWZ5ICkgewogIGNvbnN0IGxpbmUgPSBzdGF0ZS5kb2MubGluZShsaW5lTnVtYmVyKQogIGNvbnN0IGRlYyA9IERlY29yYXRpb24ud2lkZ2V0KHt3aWRnZXQ6IG5ldyBFbmRPZkxpbmVXaWRnZXQobm90aWZ5KSwgc2lkZTogMX0pLnJhbmdlKGxpbmUudG8pCiAgcmV0dXJuIGRlYwp9CgpmdW5jdGlvbiByZXBsYWNlTXVsdGlMaW5lKCBsaW5lTnVtYmVyLCBzdGF0ZSApIHsKICBjb25zdCBsaW5lID0gc3RhdGUuZG9jLmxpbmUobGluZU51bWJlcikKICBjb25zdCBsaW5lQmVmb3JlID0gc3RhdGUuZG9jLmxpbmUobGluZU51bWJlciAtIDEpCiAgY29uc3Qgc2VsZWN0aW9uSW5SYW5nZSA9IGxpbmVCZWZvcmUuZnJvbS0yIDw9IHN0YXRlLnNlbGVjdGlvbi5tYWluLmhlYWQgJiYgc3RhdGUuc2VsZWN0aW9uLm1haW4uaGVhZCA8PSBsaW5lLnRvICsgMgogIGNvbnN0IGRlYyA9IHNlbGVjdGlvbkluUmFuZ2UgPyB1bmRlZmluZWQgOiBEZWNvcmF0aW9uLnJlcGxhY2Uoe30pLnJhbmdlKGxpbmVCZWZvcmUuZnJvbSwgbGluZS50bykKICByZXR1cm4gZGVjCn0KCmZ1bmN0aW9uIHJlcGxhY2VMaW5lKCBsaW5lTnVtYmVyLCBzdGF0ZSApIHsKICBjb25zdCBsaW5lID0gc3RhdGUuZG9jLmxpbmUobGluZU51bWJlcikKICBjb25zdCBzZWxlY3Rpb25JblJhbmdlID0gbGluZS5mcm9tLTIgPD0gc3RhdGUuc2VsZWN0aW9uLm1haW4uaGVhZCAmJiBzdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkIDw9IGxpbmUudG8gKyAyCiAgY29uc3QgZGVjID0gc2VsZWN0aW9uSW5SYW5nZSA/IHVuZGVmaW5lZCA6IERlY29yYXRpb24ucmVwbGFjZSh7fSkucmFuZ2UobGluZS5mcm9tLCBsaW5lLnRvKQogIHJldHVybiBkZWMKfQoKCgpjb25zdCBkZWMgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCBzdGF0ZSApIHsKICAgIHJldHVybiBEZWNvcmF0aW9uLnNldChbcmVwbGFjZUxpbmUoOCwgc3RhdGUpLCBkZWNvcmF0ZUxpbmUoOCwgc3RhdGUpXSkKICB9LAogIHVwZGF0ZSh2YWwsIHRyKSB7CiAgICBjb25zdCBkZWNzID0gW2RlY29yYXRlTGluZSg4LCB0ci5zdGF0ZSwgdHJ1ZSldCiAgICBjb25zdCBtYXliZVJlcGxhY2UgPSByZXBsYWNlTXVsdGlMaW5lKDgsIHRyLnN0YXRlKSAvLyB0aGUgYnVnIGhhcHBlbnMgd2hlbiBtdWx0aXBsZSBsaW5lcyBhcmUgcmVwbGFjZWQKICAgIC8vIGNvbnN0IG1heWJlUmVwbGFjZSA9IHJlcGxhY2VMaW5lKDgsIHRyLnN0YXRlKSAvLyByZXBsYWNpbmcganVzdCBvbmUgbGluZSBkb2Vzbid0IGNhdXNlIHRvRE9NIHRvIGJlIHJlcGVhdGVkbHkgY2FsbGVkCgogICAgaWYgKG1heWJlUmVwbGFjZSkgZGVjcy5wdXNoKG1heWJlUmVwbGFjZSkKICAgIHJldHVybiBEZWNvcmF0aW9uLnNldChkZWNzLCB0cnVlKQogIH0sCiAgcHJvdmlkZTogKGYpID0+IEVkaXRvclZpZXcuZGVjb3JhdGlvbnMuZnJvbShmKQp9KQoKbGV0IHZpZXcgPSBuZXcgRWRpdG9yVmlldyh7CiAgZG9jOiAiIyBIZWxsb1xuXG5cblxuQSBsaW5lLlxuQW5vdGhlciBsaW5lLlxuQW5vdGhlciBsaW5lXG5BbmQgYW5vdGhlciBtYXliZSBoaWRkZW4gbGluZVxuLSBDbGljayBvbiB0aGUgbGlzdCBtYXJrLCBhbmQgY2xpY2sgb24gc2F5IGxpbmUgMTFcbiMgQW5vdGhlciBoZWFkaW5nXG5cbiIsCiAgZXh0ZW5zaW9uczogWwogICAgYmFzaWNTZXR1cCwKICAgIG1hcmtkb3duKHtjb2RlTGFuZ3VhZ2VzOiBsYW5ndWFnZXN9KSwKICAgIGRlYwogIF0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5Cn0pCg==), lines 7 and 8 are shown if the cursor is inside line 7’s `from` -2 until line 8’s `to` + 2. There’s no issue toggling between the replacement widget when the viewport is at the top of the document, but when it’s at the bottom, widget `toDOM` is called. [Here](https://codemirror.net/try/#c=aW1wb3J0IHtiYXNpY1NldHVwfSBmcm9tICJjb2RlbWlycm9yIgppbXBvcnQge0VkaXRvclZpZXcsIERlY29yYXRpb24sIFdpZGdldFR5cGV9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciCmltcG9ydCB7U3RhdGVGaWVsZH0gZnJvbSAiQGNvZGVtaXJyb3Ivc3RhdGUiCmltcG9ydCB7bWFya2Rvd259IGZyb20gIkBjb2RlbWlycm9yL2xhbmctbWFya2Rvd24iCmltcG9ydCB7bGFuZ3VhZ2VzfSBmcm9tICJAY29kZW1pcnJvci9sYW5ndWFnZS1kYXRhIgoKCmNsYXNzIEVuZE9mTGluZVdpZGdldCBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKCBub3RpZnkgKSB7CiAgICBzdXBlcigpCiAgICB0aGlzLm5vdGlmeSA9IG5vdGlmeTsKICB9CiAgZXEoIHdpZGdldCApIHsgcmV0dXJuIHRydWUgfQogIHRvRE9NKCApIHsKICAgIGNvbnNvbGUubG9nKCd3aWRnZXQudG9ET00nKQogICAgY29uc3QgZWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKQogICAgLy8gZWwuc3R5bGUuZGlzcGxheSA9ICdpbmxpbmUtYmxvY2snCiAgICBlbC5zdHlsZS5jb2xvciA9ICdibHVlJwogICAgZWwuc3R5bGUuaGVpZ2h0ID0gJzUwMDBweCcKICAgIGVsLmlubmVySFRNTCA9ICcqJwogICAgdGhpcy5ub3RpZnkgJiYgd2luZG93LmFsZXJ0KCdXaWRnZXQudG9ET00gY2FsbGVkJykKICAgIHJldHVybiBlbAogIH0KfQoKZnVuY3Rpb24gZGVjb3JhdGVMaW5lKCBsaW5lTnVtYmVyLCBzdGF0ZSwgbm90aWZ5ICkgewogIGNvbnN0IGxpbmUgPSBzdGF0ZS5kb2MubGluZShsaW5lTnVtYmVyKQogIGNvbnN0IGRlYyA9IERlY29yYXRpb24ud2lkZ2V0KHt3aWRnZXQ6IG5ldyBFbmRPZkxpbmVXaWRnZXQobm90aWZ5KSwgc2lkZTogMX0pLnJhbmdlKGxpbmUudG8pCiAgcmV0dXJuIGRlYwp9CgpmdW5jdGlvbiByZXBsYWNlTXVsdGlMaW5lKCBsaW5lTnVtYmVyLCBzdGF0ZSApIHsKICBjb25zdCBsaW5lID0gc3RhdGUuZG9jLmxpbmUobGluZU51bWJlcikKICBjb25zdCBsaW5lQmVmb3JlID0gc3RhdGUuZG9jLmxpbmUobGluZU51bWJlciAtIDEpCiAgY29uc3Qgc2VsZWN0aW9uSW5SYW5nZSA9IGxpbmVCZWZvcmUuZnJvbS0yIDw9IHN0YXRlLnNlbGVjdGlvbi5tYWluLmhlYWQgJiYgc3RhdGUuc2VsZWN0aW9uLm1haW4uaGVhZCA8PSBsaW5lLnRvICsgMgogIGNvbnN0IGRlYyA9IHNlbGVjdGlvbkluUmFuZ2UgPyB1bmRlZmluZWQgOiBEZWNvcmF0aW9uLnJlcGxhY2Uoe30pLnJhbmdlKGxpbmVCZWZvcmUuZnJvbSwgbGluZS50bykKICByZXR1cm4gZGVjCn0KCmZ1bmN0aW9uIHJlcGxhY2VMaW5lKCBsaW5lTnVtYmVyLCBzdGF0ZSApIHsKICBjb25zdCBsaW5lID0gc3RhdGUuZG9jLmxpbmUobGluZU51bWJlcikKICBjb25zdCBzZWxlY3Rpb25JblJhbmdlID0gbGluZS5mcm9tLTIgPD0gc3RhdGUuc2VsZWN0aW9uLm1haW4uaGVhZCAmJiBzdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkIDw9IGxpbmUudG8gKyAyCiAgY29uc3QgZGVjID0gc2VsZWN0aW9uSW5SYW5nZSA/IHVuZGVmaW5lZCA6IERlY29yYXRpb24ucmVwbGFjZSh7fSkucmFuZ2UobGluZS5mcm9tLCBsaW5lLnRvKQogIHJldHVybiBkZWMKfQoKCgpjb25zdCBkZWMgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCBzdGF0ZSApIHsKICAgIHJldHVybiBEZWNvcmF0aW9uLnNldChbcmVwbGFjZUxpbmUoOCwgc3RhdGUpLCBkZWNvcmF0ZUxpbmUoOCwgc3RhdGUpXSkKICB9LAogIHVwZGF0ZSh2YWwsIHRyKSB7CiAgICBjb25zdCBkZWNzID0gW2RlY29yYXRlTGluZSg4LCB0ci5zdGF0ZSwgdHJ1ZSldCiAgICAvLyBjb25zdCBtYXliZVJlcGxhY2UgPSByZXBsYWNlTXVsdGlMaW5lKDgsIHRyLnN0YXRlKSAvLyB0aGUgYnVnIGhhcHBlbnMgd2hlbiBtdWx0aXBsZSBsaW5lcyBhcmUgcmVwbGFjZWQKICAgIGNvbnN0IG1heWJlUmVwbGFjZSA9IHJlcGxhY2VMaW5lKDgsIHRyLnN0YXRlKSAvLyByZXBsYWNpbmcganVzdCBvbmUgbGluZSBkb2Vzbid0IGNhdXNlIHRvRE9NIHRvIGJlIHJlcGVhdGVkbHkgY2FsbGVkCgogICAgaWYgKG1heWJlUmVwbGFjZSkgZGVjcy5wdXNoKG1heWJlUmVwbGFjZSkKICAgIHJldHVybiBEZWNvcmF0aW9uLnNldChkZWNzLCB0cnVlKQogIH0sCiAgcHJvdmlkZTogKGYpID0+IEVkaXRvclZpZXcuZGVjb3JhdGlvbnMuZnJvbShmKQp9KQoKbGV0IHZpZXcgPSBuZXcgRWRpdG9yVmlldyh7CiAgZG9jOiAiIyBIZWxsb1xuXG5cblxuQSBsaW5lLlxuQW5vdGhlciBsaW5lLlxuQW5vdGhlciBsaW5lXG5BbmQgYW5vdGhlciBtYXliZSBoaWRkZW4gbGluZVxuLSBDbGljayBvbiB0aGUgbGlzdCBtYXJrLCBhbmQgY2xpY2sgb24gc2F5IGxpbmUgMTFcbiMgQW5vdGhlciBoZWFkaW5nXG5cbiIsCiAgZXh0ZW5zaW9uczogWwogICAgYmFzaWNTZXR1cCwKICAgIG1hcmtkb3duKHtjb2RlTGFuZ3VhZ2VzOiBsYW5ndWFnZXN9KSwKICAgIGRlYwogIF0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5Cn0pCg==)’s the same code except the replacement is for one line only, where `toDOM` is not called again.

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/marijn/32/136_2.png) [@marijn](https://discuss.codemirror.net/u/marijn)\
**Post date:** [January 6, 2026, 1:55pm UTC](https://discuss.codemirror.net/t/inline-widget-updatedom-not-called-on-specific-conditions/9644/2 "2026-01-06T13:55:29Z")

</div>

The editor guesses the content it needs to draw incorrectly, because the huge widget is throwing off its expectations. That causes it to first update the viewport to not include the widget, and then, finding out that that draws too little content, redrawing again, including the widget again. Adding an `estimatedHeight` property to the widget type helps.

---

<div class="post-metadata">

**Author:** ![tiredstudent](https://discuss.codemirror.net/letter_avatar_proxy/v4/letter/t/f9ae1b/32.png) [@tiredstudent](https://discuss.codemirror.net/u/tiredstudent)\
**Post date:** [January 6, 2026, 2:42pm UTC](https://discuss.codemirror.net/t/inline-widget-updatedom-not-called-on-specific-conditions/9644/3 "2026-01-06T14:42:22Z")

</div>

Adding `estimatedHeight` as a fixed value did help! However for cases where height might vary, I tried setting the `estimatedHeight` from the height of the widget element. But it looks like at least in this case of replacing at least 2 lines, a new widget type instance is created and the `estimatedHeight` is taken from that instead of the currently displayed widget type instance. Is this correct? And in this case creating block widgets instead of inline widgets is the cleanest solution?

Edit: I guess putting a high enough estimated height might work too and the editor can just update if the estimated height is too large. Is this okay/an intended use of `estimatedHeight` too?
