From 6d4f3dc14f4b8a56a1d727d32befd8e146c0cdef Mon Sep 17 00:00:00 2001 From: Ednilson Maia Date: Sat, 23 May 2026 20:20:38 -0300 Subject: [PATCH 1/2] fix setSelectionRange is not a function in saveAndRestoreFocus after page morph Signed-off-by: Ednilson Maia --- src/idiomorph.js | 22 +++++++++++++++++--- test/restore-focus.js | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/idiomorph.js b/src/idiomorph.js index dd6975c..959ca81 100644 --- a/src/idiomorph.js +++ b/src/idiomorph.js @@ -230,11 +230,27 @@ var Idiomorph = (function () { activeElementId && activeElementId !== document.activeElement?.getAttribute("id") ) { - activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`); + const candidate = ctx.target.querySelector(`[id="${activeElementId}"]`); + // the re-queried element may not be an input/textarea even if the + // original was, since any element can share the saved id + activeElement = + candidate instanceof HTMLInputElement || + candidate instanceof HTMLTextAreaElement + ? candidate + : null; activeElement?.focus(); } - if (activeElement && !activeElement.selectionEnd && selectionEnd) { - activeElement.setSelectionRange(selectionStart, selectionEnd); + if ( + activeElement && + typeof activeElement.setSelectionRange === "function" && + !activeElement.selectionEnd && + selectionEnd + ) { + try { + activeElement.setSelectionRange(selectionStart, selectionEnd); + } catch { + // setSelectionRange throws on input types that don't support text selection + } } return results; diff --git a/test/restore-focus.js b/test/restore-focus.js index 1c4a54a..43eef38 100644 --- a/test/restore-focus.js +++ b/test/restore-focus.js @@ -289,6 +289,53 @@ describe("Option to forcibly restore focus after morph", function () { assertNoFocus(); }); + it("does not throw if the saved id resolves to a non-input element after morph", function () { + getWorkArea().innerHTML = ` +
+ +
`; + setFocusAndSelection("focused", "b"); + + // after the morph nothing input-like has id="focused"; a
does + const after = ` +
+
replaced
+
`; + (function () { + Idiomorph.morph(getWorkArea(), after, { + morphStyle: "innerHTML", + restoreFocus: true, + }); + }).should.not.throw(); + + getWorkArea().innerHTML.should.equal(after); + assertNoFocus(); + }); + + it("does not throw for input types that reject setSelectionRange", function () { + getWorkArea().innerHTML = ` +
+ + +
`; + // type=number does not support selection, so set focus only + setFocus("focused"); + + const after = ` +
+ + +
`; + (function () { + Idiomorph.morph(getWorkArea(), after, { + morphStyle: "innerHTML", + restoreFocus: true, + }); + }).should.not.throw(); + + getWorkArea().innerHTML.should.equal(after); + }); + it("does not restore selection if selection still set or changed", function () { getWorkArea().innerHTML = `
From d991d908834c134dc138e9aaba5d41aad547d976 Mon Sep 17 00:00:00 2001 From: Ednilson Maia Date: Sun, 24 May 2026 08:59:59 -0300 Subject: [PATCH 2/2] simplified fix Signed-off-by: Ednilson Maia --- src/idiomorph.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/idiomorph.js b/src/idiomorph.js index 959ca81..257c040 100644 --- a/src/idiomorph.js +++ b/src/idiomorph.js @@ -230,26 +230,16 @@ var Idiomorph = (function () { activeElementId && activeElementId !== document.activeElement?.getAttribute("id") ) { - const candidate = ctx.target.querySelector(`[id="${activeElementId}"]`); - // the re-queried element may not be an input/textarea even if the - // original was, since any element can share the saved id - activeElement = - candidate instanceof HTMLInputElement || - candidate instanceof HTMLTextAreaElement - ? candidate - : null; + activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`); activeElement?.focus(); } - if ( - activeElement && - typeof activeElement.setSelectionRange === "function" && - !activeElement.selectionEnd && - selectionEnd - ) { + if (activeElement && !activeElement.selectionEnd && selectionEnd) { try { activeElement.setSelectionRange(selectionStart, selectionEnd); } catch { - // setSelectionRange throws on input types that don't support text selection + // the element may not support setSelectionRange: it's no longer an + // input/textarea after the morph, or it's an input type (number, + // email, date, ...) that doesn't support text selection } }