When a <lexxy-editor> is present on the page and focus is programmatically moved to another element (in our case a Bootstrap popover that calls element.focus() when it opens), lexxy throws:
Uncaught TypeError: Cannot convert undefined or null to object
at .../@37signals/lexxy/dist/lexxy.esm.js
The error originates inside lexxy.esm.js and appears to be tied to the editor's focus handling (focusin/focusout listeners, registered around lexxy.esm.js:9024-9025). It surfaces as an uncaught exception, which breaks unrelated JavaScript running in the same tick (e.g. a Bootstrap popover never finishes opening).
It reproduces on 0.9.20 and on 0.9.12-beta (i.e. updating to the latest stable did not fix it).
Environment
@37signals/lexxy: 0.9.20 (also reproduced on 0.9.12-beta)
- Bundler: Webpack (Shakapacker) –
import "@37signals/lexxy"
- Framework: Rails 8 + Hotwire (Turbo + Stimulus), Bootstrap 5.3
- Browser: Chrome 150 (headless and headed)
- OS: macOS
What triggers it
We have a page with:
- A
<lexxy-editor> (an ActionText rich-text field) inside a card.
- A Bootstrap popover anchored to a control in that same card. Our popover (Stimulus) controller calls
popoverElement.focus() in the shown.bs.popover callback so that a focusout listener can implement “click/focus outside to close”.
When the popover opens over the card that contains the lexxy editor, the .focus() call moves focus, a focusout fires relative to the editor, and lexxy throws the TypeError. The uncaught error then prevents the popover from finishing its open sequence.
Simplified reproduction (framework-agnostic):
<lexxy-editor name="body"><!-- some content --></lexxy-editor>
<button id="steal">Steal focus</button>
<div id="target" tabindex="-1">target</div>
<script>
// Give the editor focus first (or let it autofocus), then move focus away
// programmatically – this is what the popover's shown handler effectively does.
document.getElementById("steal").addEventListener("click", () => {
document.getElementById("target").focus();
});
</script>
Clicking the button (after the editor has been focused) moves focus to #target, which produces the Cannot convert undefined or null to object error from lexxy.
Note: in our app the failure is somewhat timing-dependent – the same flow occasionally succeeds. That, plus the fact that it disappears entirely when we stop programmatically focusing the other element (see below), strongly suggests a race in lexxy’s focus/selection bookkeeping when focus leaves the editor while the DOM is in a transient state.
Expected behavior
Moving focus away from a <lexxy-editor> (or opening an overlay that takes focus) should not throw. Focus/blur handling should tolerate a null/empty selection or a detached/transient state.
Actual behavior
Uncaught TypeError: Cannot convert undefined or null to object is thrown synchronously from lexxy.esm.js, aborting the surrounding JS (the popover fails to open).
Investigation / isolation
We narrowed it down as follows (in our app):
- It is the programmatic focus move that triggers it. Removing the single
element.focus() call from our popover controller’s shown.bs.popover handler makes the error disappear completely. Restoring it brings the error back.
- It is not caused by re-rendering DOM elsewhere on the page. We reproduced it with the popover open and no other DOM mutation, and also confirmed unrelated DOM replacements (a sibling
<div> being swapped) do not trigger it.
- It only happens when the focus move occurs on a page that has a live
<lexxy-editor>. Pages without an editor are unaffected.
- Updating
tom-select and bootstrap to their latest versions made no difference; only lexxy is in the failing stack frame.
The relevant lexxy code seems to be the editor’s focus listeners:
// lexxy.esm.js ~9024
registerEventListener(this, "focusin", this.#handleFocusIn),
registerEventListener(this, "focusout", this.#handleFocusOut)
and the work done on focus change (#handleFocusIn → dispatchAttributesChange, which reads the editor selection/state). When focus leaves while the selection is null/empty, an Object.* call (e.g. Object.keys/Object.entries) on an undefined value appears to throw.
Possible fix
Guard the focus/selection-derived code paths against a null/undefined selection or editor state (e.g. early-return in the focusin/focusout handlers when there is no current selection, or default to {}/[] before Object.keys/Object.entries).
When a
<lexxy-editor>is present on the page and focus is programmatically moved to another element (in our case a Bootstrap popover that callselement.focus()when it opens), lexxy throws:The error originates inside
lexxy.esm.jsand appears to be tied to the editor's focus handling (focusin/focusoutlisteners, registered aroundlexxy.esm.js:9024-9025). It surfaces as an uncaught exception, which breaks unrelated JavaScript running in the same tick (e.g. a Bootstrap popover never finishes opening).It reproduces on 0.9.20 and on 0.9.12-beta (i.e. updating to the latest stable did not fix it).
Environment
@37signals/lexxy: 0.9.20 (also reproduced on 0.9.12-beta)import "@37signals/lexxy"What triggers it
We have a page with:
<lexxy-editor>(an ActionText rich-text field) inside a card.popoverElement.focus()in theshown.bs.popovercallback so that afocusoutlistener can implement “click/focus outside to close”.When the popover opens over the card that contains the lexxy editor, the
.focus()call moves focus, afocusoutfires relative to the editor, and lexxy throws theTypeError. The uncaught error then prevents the popover from finishing its open sequence.Simplified reproduction (framework-agnostic):
Clicking the button (after the editor has been focused) moves focus to
#target, which produces theCannot convert undefined or null to objecterror from lexxy.Expected behavior
Moving focus away from a
<lexxy-editor>(or opening an overlay that takes focus) should not throw. Focus/blur handling should tolerate anull/empty selection or a detached/transient state.Actual behavior
Uncaught TypeError: Cannot convert undefined or null to objectis thrown synchronously fromlexxy.esm.js, aborting the surrounding JS (the popover fails to open).Investigation / isolation
We narrowed it down as follows (in our app):
element.focus()call from our popover controller’sshown.bs.popoverhandler makes the error disappear completely. Restoring it brings the error back.<div>being swapped) do not trigger it.<lexxy-editor>. Pages without an editor are unaffected.tom-selectandbootstrapto their latest versions made no difference; only lexxy is in the failing stack frame.The relevant lexxy code seems to be the editor’s focus listeners:
and the work done on focus change (
#handleFocusIn→dispatchAttributesChange, which reads the editor selection/state). When focus leaves while the selection isnull/empty, anObject.*call (e.g.Object.keys/Object.entries) on an undefined value appears to throw.Possible fix
Guard the focus/selection-derived code paths against a
null/undefinedselection or editor state (e.g. early-return in the focusin/focusout handlers when there is no current selection, or default to{}/[]beforeObject.keys/Object.entries).