toeverything/AFFiNE · error · BlockSuiteError
ErrorCode.InlineEditorError
ErrorCode.InlineEditorError
Error message
failed to find vElement for a text note in an embed element
What it means
Thrown by inlineRangeToDomRange() in blocksuite/framework/std/src/inline/utils/range-conversion.ts, which converts an InlineRange (index/length in the inline text model) into a DOM Range. When the computed anchor text node sits inside an inline embed (isInEmbedElement returns true), the code walks up from startText.parentElement to the nearest 'v-element' custom element to relocate the caret to the embed's neighbor. If the embed's DOM was not rendered with the standard v-element wrapper (or the parent chain is detached), closest('v-element') returns null and this invariant error is thrown.
Solutions
- Make custom embed renderers output the standard structure (wrap the embed content in a v-element via the v-element base class / VElement component) so closest('v-element') resolves.
- Await the inline editor's render (e.g. requestUpdate + rAF, or inlineEditor.slots.rendered) after mutating embeds before setting a selection that touches the embed.
- Wrap selection-setting code in try/catch for BlockSuiteError with code ErrorCode.InlineEditorError and retry after the next render.
- If the embed is the last node in a line, ensure the editor still renders a trailing text/gap node after it so neighbor lookups succeed.
Example fix
// before: custom embed renders a bare <img>
render() { return html`<img src=${this.src} />`; }
// after: use the standard inline embed element so the v-element wrapper exists
import { VElement } from '@blocksuite/inline/elements';
render() { return html`<v-element><img src=${this.src} /></v-element>`; } Defensive patterns
Strategy: try-catch
Validate before calling
// Before setting a range that may land in an embed, verify the embed DOM is standard:
const embed = rootElement.querySelectorAll('v-element')[i];
const isStandard = embed instanceof HTMLElement && embed.closest('v-line') !== null;
if (!isStandard) return; // skip selection into this embed Type guard
function hasStandardEmbedWrapper(node: Text): boolean {
return node.parentElement?.closest('v-element') instanceof HTMLElement;
} Try / catch
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
try {
inlineEditor.setInlineRange(range);
} catch (e) {
if (e instanceof BlockSuiteError && e.code === ErrorCode.InlineEditorError) {
// DOM not ready / non-standard embed: retry after next paint or clamp range
requestAnimationFrame(() => inlineEditor.setInlineRange(clampToText(range)));
} else throw e;
} Prevention
- Build custom inline embeds on the standard VElement base so the v-element wrapper always exists.
- Only call setInlineRange after the inline editor's render pass (await requestUpdate / rendered slot).
- Avoid range indexes that land inside embeds; clamp to adjacent text.
When it happens
Trigger: Calling inlineEditor.setInlineRange(), syncing native selection, or any API that maps a model range to a DOM range when the range boundary (index) lands inside an inline embed element whose rendered DOM is not wrapped in a <v-element> tag: a custom embed renderer returning non-standard markup, DOM mutated mid-conversion, or a v-element that was removed from the document before conversion ran.
Common situations: Custom inline embeds (mentions, inline images, formulas) whose render() does not use the standard v-element structure; races between a model update and the next Lit render pass; SSR or test environments where the custom elements are not upgraded/registered.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/dcbb1defdd4ea93b.
Report an issue: GitHub.
Appendix: source
Thrown at blocksuite/framework/std/src/inline/utils/range-conversion.ts:307
if (startText && endText) {
break;
}
index += textLength;
}
// the one because of the line break
index += 1;
}
if (!startText || !endText) {
return null;
}
if (isInEmbedElement(startText)) {
const anchorVElement = startText.parentElement?.closest('v-element');
if (!anchorVElement) {
throw new BlockSuiteError(
ErrorCode.InlineEditorError,
'failed to find vElement for a text note in an embed element'
);
}
const nextSibling = anchorVElement.nextElementSibling;
if (!nextSibling) {
throw new BlockSuiteError(
ErrorCode.InlineEditorError,
'failed to find nextSibling sibling of an embed element'
);
}
const texts = getTextNodesFromElement(nextSibling);
if (texts.length === 0) {
throw new BlockSuiteError(
ErrorCode.InlineEditorError,
'text node in v-text not found'
);View on GitHub (pinned to b4c8548c09)