import { LexicalComposer, type InitialConfigType } from "@lexical/react/LexicalComposer";import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";import { ContentEditable } from "@lexical/react/LexicalContentEditable";import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";import { $applyNodeReplacement, $createRangeSelection, $getSelection, $setSelection, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, $createLineBreakNode, $createParagraphNode, $createTextNode, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, COMMAND_PRIORITY_HIGH, KEY_BACKSPACE_COMMAND, $getRoot, DecoratorNode, type ElementNode, type LexicalNode, type SerializedLexicalNode, TextNode, type EditorConfig, type EditorState, type NodeKey, type SerializedTextNode, type Spread,} from "lexical";import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, type ClipboardEventHandler, type ReactElement, type Ref,} from "react"; import { clampCollapsedComposerCursor, collapseExpandedComposerCursor, expandCollapsedComposerCursor, isCollapsedCursorAdjacentToInlineToken,} from "~/composer-logic";import { splitPromptIntoComposerSegments } from "~/composer-editor-mentions";import { INLINE_TERMINAL_CONTEXT_PLACEHOLDER, type TerminalContextDraft,} from "~/lib/terminalContext";import { cn } from "~/lib/utils";import { basenameOfPath, getVscodeIconUrlForEntry, inferEntryKindFromPath } from "~/vscode-icons";import { COMPOSER_INLINE_CHIP_CLASS_NAME, COMPOSER_INLINE_CHIP_ICON_CLASS_NAME, COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME,} from "./composerInlineChip";import { ComposerPendingTerminalContextChip } from "./chat/ComposerPendingTerminalContexts"; const COMPOSER_EDITOR_HMR_KEY = `composer-editor-${Math.random().toString(36).slice(2)}`; type SerializedComposerMentionNode = Spread< { path: string; type: "composer-mention"; version: 1; }, SerializedTextNode>; type SerializedComposerTerminalContextNode = Spread< { context: TerminalContextDraft; type: "composer-terminal-context"; version: 1; }, SerializedLexicalNode>; const ComposerTerminalContextActionsContext = createContext<{ onRemoveTerminalContext: (contextId: string) => void;}>({ onRemoveTerminalContext: () => {},}); class ComposerMentionNode extends TextNode { __path: string; static override getType(): string { return "composer-mention"; } static override clone(node: ComposerMentionNode): ComposerMentionNode { return new ComposerMentionNode(node.__path, node.__key); } static override importJSON(serializedNode: SerializedComposerMentionNode): ComposerMentionNode { return $createComposerMentionNode(serializedNode.path); } constructor(path: string, key?: NodeKey) { const normalizedPath = path.startsWith("@") ? path.slice(1) : path; super(`@${normalizedPath}`, key); this.__path = normalizedPath; } override exportJSON(): SerializedComposerMentionNode { return { ...super.exportJSON(), path: this.__path, type: "composer-mention", version: 1, }; } override createDOM(_config: EditorConfig): HTMLElement { const dom = document.createElement("span"); dom.className = COMPOSER_INLINE_CHIP_CLASS_NAME; dom.contentEditable = "false"; dom.setAttribute("spellcheck", "false"); renderMentionChipDom(dom, this.__path); return dom; } override updateDOM( prevNode: ComposerMentionNode, dom: HTMLElement, _config: EditorConfig, ): boolean { dom.contentEditable = "false"; if (prevNode.__text !== this.__text || prevNode.__path !== this.__path) { renderMentionChipDom(dom, this.__path); } return false; } override canInsertTextBefore(): false { return false; } override canInsertTextAfter(): false { return false; } override isTextEntity(): true { return true; } override isToken(): true { return true; }} function $createComposerMentionNode(path: string): ComposerMentionNode { return $applyNodeReplacement(new ComposerMentionNode(path));} function ComposerTerminalContextDecorator(props: { context: TerminalContextDraft }) { return <ComposerPendingTerminalContextChip context={props.context} />;} class ComposerTerminalContextNode extends DecoratorNode<ReactElement> { __context: TerminalContextDraft; static override getType(): string { return "composer-terminal-context"; } static override clone(node: ComposerTerminalContextNode): ComposerTerminalContextNode { return new ComposerTerminalContextNode(node.__context, node.__key); } static override importJSON( serializedNode: SerializedComposerTerminalContextNode, ): ComposerTerminalContextNode { return $createComposerTerminalContextNode(serializedNode.context); } constructor(context: TerminalContextDraft, key?: NodeKey) { super(key); this.__context = context; } override exportJSON(): SerializedComposerTerminalContextNode { return { ...super.exportJSON(), context: this.__context, type: "composer-terminal-context", version: 1, }; } override createDOM(): HTMLElement { const dom = document.createElement("span"); dom.className = "inline-flex align-middle leading-none"; return dom; } override updateDOM(): false { return false; } override getTextContent(): string { return INLINE_TERMINAL_CONTEXT_PLACEHOLDER; } override isInline(): true { return true; } override decorate(): ReactElement { return <ComposerTerminalContextDecorator context={this.__context} />; }} function $createComposerTerminalContextNode( context: TerminalContextDraft,): ComposerTerminalContextNode { return $applyNodeReplacement(new ComposerTerminalContextNode(context));} type ComposerInlineTokenNode = ComposerMentionNode | ComposerTerminalContextNode; function isComposerInlineTokenNode(candidate: unknown): candidate is ComposerInlineTokenNode { return ( candidate instanceof ComposerMentionNode || candidate instanceof ComposerTerminalContextNode );} function resolvedThemeFromDocument(): "light" | "dark" { return document.documentElement.classList.contains("dark") ? "dark" : "light";} function renderMentionChipDom(container: HTMLElement, pathValue: string): void { container.textContent = ""; container.style.setProperty("user-select", "none"); container.style.setProperty("-webkit-user-select", "none"); const theme = resolvedThemeFromDocument(); const icon = document.createElement("img"); icon.alt = ""; icon.ariaHidden = "true"; icon.className = COMPOSER_INLINE_CHIP_ICON_CLASS_NAME; icon.loading = "lazy"; icon.src = getVscodeIconUrlForEntry(pathValue, inferEntryKindFromPath(pathValue), theme); const label = document.createElement("span"); label.className = COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME; label.textContent = basenameOfPath(pathValue); container.append(icon, label);} function terminalContextSignature(contexts: ReadonlyArray<TerminalContextDraft>): string { return contexts .map((context) => [ context.id, context.threadId, context.terminalId, context.terminalLabel, context.lineStart, context.lineEnd, context.createdAt, context.text, ].join("\u001f"), ) .join("\u001e");} function clampExpandedCursor(value: string, cursor: number): number { if (!Number.isFinite(cursor)) return value.length; return Math.max(0, Math.min(value.length, Math.floor(cursor)));} function getComposerInlineTokenTextLength(_node: ComposerInlineTokenNode): 1 { return 1;} function getComposerInlineTokenExpandedTextLength(node: ComposerInlineTokenNode): number { return node.getTextContentSize();} function getAbsoluteOffsetForInlineTokenPoint( node: ComposerInlineTokenNode, absoluteOffset: number, pointOffset: number,): number { return absoluteOffset + (pointOffset > 0 ? getComposerInlineTokenTextLength(node) : 0);} function getExpandedAbsoluteOffsetForInlineTokenPoint( node: ComposerInlineTokenNode, absoluteOffset: number, pointOffset: number,): number { return absoluteOffset + (pointOffset > 0 ? getComposerInlineTokenExpandedTextLength(node) : 0);} function findSelectionPointForInlineToken( node: ComposerInlineTokenNode, remainingRef: { value: number },): { key: string; offset: number; type: "element" } | null { const parent = node.getParent(); if (!parent || !$isElementNode(parent)) return null; const index = node.getIndexWithinParent(); if (remainingRef.value === 0) { return { key: parent.getKey(), offset: index, type: "element", }; } if (remainingRef.value === getComposerInlineTokenTextLength(node)) { return { key: parent.getKey(), offset: index + 1, type: "element", }; } remainingRef.value -= getComposerInlineTokenTextLength(node); return null;} function getComposerNodeTextLength(node: LexicalNode): number { if (isComposerInlineTokenNode(node)) { return getComposerInlineTokenTextLength(node); } if ($isTextNode(node)) { return node.getTextContentSize(); } if ($isLineBreakNode(node)) { return 1; } if ($isElementNode(node)) { return node.getChildren().reduce((total, child) => total + getComposerNodeTextLength(child), 0); } return 0;} function getComposerNodeExpandedTextLength(node: LexicalNode): number { if (isComposerInlineTokenNode(node)) { return getComposerInlineTokenExpandedTextLength(node); } if ($isTextNode(node)) { return node.getTextContentSize(); } if ($isLineBreakNode(node)) { return 1; } if ($isElementNode(node)) { return node .getChildren() .reduce((total, child) => total + getComposerNodeExpandedTextLength(child), 0); } return 0;} function getAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: number): number { let offset = 0; let current: LexicalNode | null = node; while (current) { const nextParent = current.getParent() as LexicalNode | null; if (!nextParent || !$isElementNode(nextParent)) { break; } const siblings = nextParent.getChildren(); const index = current.getIndexWithinParent(); for (let i = 0; i < index; i += 1) { const sibling = siblings[i]; if (!sibling) continue; offset += getComposerNodeTextLength(sibling); } current = nextParent; } if ($isTextNode(node)) { if (node instanceof ComposerMentionNode) { return getAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } return offset + Math.min(pointOffset, node.getTextContentSize()); } if (node instanceof ComposerTerminalContextNode) { return getAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } if ($isLineBreakNode(node)) { return offset + Math.min(pointOffset, 1); } if ($isElementNode(node)) { const children = node.getChildren(); const clampedOffset = Math.max(0, Math.min(pointOffset, children.length)); for (let i = 0; i < clampedOffset; i += 1) { const child = children[i]; if (!child) continue; offset += getComposerNodeTextLength(child); } return offset; } return offset;} function getExpandedAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: number): number { let offset = 0; let current: LexicalNode | null = node; while (current) { const nextParent = current.getParent() as LexicalNode | null; if (!nextParent || !$isElementNode(nextParent)) { break; } const siblings = nextParent.getChildren(); const index = current.getIndexWithinParent(); for (let i = 0; i < index; i += 1) { const sibling = siblings[i]; if (!sibling) continue; offset += getComposerNodeExpandedTextLength(sibling); } current = nextParent; } if ($isTextNode(node)) { if (node instanceof ComposerMentionNode) { return getExpandedAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } return offset + Math.min(pointOffset, node.getTextContentSize()); } if (node instanceof ComposerTerminalContextNode) { return getExpandedAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } if ($isLineBreakNode(node)) { return offset + Math.min(pointOffset, 1); } if ($isElementNode(node)) { const children = node.getChildren(); const clampedOffset = Math.max(0, Math.min(pointOffset, children.length)); for (let i = 0; i < clampedOffset; i += 1) { const child = children[i]; if (!child) continue; offset += getComposerNodeExpandedTextLength(child); } return offset; } return offset;} function findSelectionPointAtOffset( node: LexicalNode, remainingRef: { value: number },): { key: string; offset: number; type: "text" | "element" } | null { if (node instanceof ComposerMentionNode) { return findSelectionPointForInlineToken(node, remainingRef); } if (node instanceof ComposerTerminalContextNode) { return findSelectionPointForInlineToken(node, remainingRef); } if ($isTextNode(node)) { const size = node.getTextContentSize(); if (remainingRef.value <= size) { return { key: node.getKey(), offset: remainingRef.value, type: "text", }; } remainingRef.value -= size; return null; } if ($isLineBreakNode(node)) { const parent = node.getParent(); if (!parent) return null; const index = node.getIndexWithinParent(); if (remainingRef.value === 0) { return { key: parent.getKey(), offset: index, type: "element",Lines 1–500 of 1177. Load more
import { LexicalComposer, type InitialConfigType } from "@lexical/react/LexicalComposer"; import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; import { ContentEditable } from "@lexical/react/LexicalContentEditable"; import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary"; import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin"; import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin"; import { $applyNodeReplacement, $createRangeSelection, $getSelection, $setSelection, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, $createLineBreakNode, $createParagraphNode, $createTextNode, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, COMMAND_PRIORITY_HIGH, KEY_BACKSPACE_COMMAND, $getRoot, DecoratorNode, type ElementNode, type LexicalNode, type SerializedLexicalNode, TextNode, type EditorConfig, type EditorState, type NodeKey, type SerializedTextNode, type Spread, } from "lexical"; import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, type ClipboardEventHandler, type ReactElement, type Ref, } from "react"; import { clampCollapsedComposerCursor, collapseExpandedComposerCursor, expandCollapsedComposerCursor, isCollapsedCursorAdjacentToInlineToken, } from "~/composer-logic"; import { splitPromptIntoComposerSegments } from "~/composer-editor-mentions"; import { INLINE_TERMINAL_CONTEXT_PLACEHOLDER, type TerminalContextDraft, } from "~/lib/terminalContext"; import { cn } from "~/lib/utils"; import { basenameOfPath, getVscodeIconUrlForEntry, inferEntryKindFromPath } from "~/vscode-icons"; import { COMPOSER_INLINE_CHIP_CLASS_NAME, COMPOSER_INLINE_CHIP_ICON_CLASS_NAME, COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME, } from "./composerInlineChip"; import { ComposerPendingTerminalContextChip } from "./chat/ComposerPendingTerminalContexts"; const COMPOSER_EDITOR_HMR_KEY = `composer-editor-${Math.random().toString(36).slice(2)}`; type SerializedComposerMentionNode = Spread< { path: string; type: "composer-mention"; version: 1; }, SerializedTextNode >; type SerializedComposerTerminalContextNode = Spread< { context: TerminalContextDraft; type: "composer-terminal-context"; version: 1; }, SerializedLexicalNode >; const ComposerTerminalContextActionsContext = createContext<{ onRemoveTerminalContext: (contextId: string) => void; }>({ onRemoveTerminalContext: () => {}, }); class ComposerMentionNode extends TextNode { __path: string; static override getType(): string { return "composer-mention"; } static override clone(node: ComposerMentionNode): ComposerMentionNode { return new ComposerMentionNode(node.__path, node.__key); } static override importJSON(serializedNode: SerializedComposerMentionNode): ComposerMentionNode { return $createComposerMentionNode(serializedNode.path); } constructor(path: string, key?: NodeKey) { const normalizedPath = path.startsWith("@") ? path.slice(1) : path; super(`@${normalizedPath}`, key); this.__path = normalizedPath; } override exportJSON(): SerializedComposerMentionNode { return { ...super.exportJSON(), path: this.__path, type: "composer-mention", version: 1, }; } override createDOM(_config: EditorConfig): HTMLElement { const dom = document.createElement("span"); dom.className = COMPOSER_INLINE_CHIP_CLASS_NAME; dom.contentEditable = "false"; dom.setAttribute("spellcheck", "false"); renderMentionChipDom(dom, this.__path); return dom; } override updateDOM( prevNode: ComposerMentionNode, dom: HTMLElement, _config: EditorConfig, ): boolean { dom.contentEditable = "false"; if (prevNode.__text !== this.__text || prevNode.__path !== this.__path) { renderMentionChipDom(dom, this.__path); } return false; } override canInsertTextBefore(): false { return false; } override canInsertTextAfter(): false { return false; } override isTextEntity(): true { return true; } override isToken(): true { return true; } } function $createComposerMentionNode(path: string): ComposerMentionNode { return $applyNodeReplacement(new ComposerMentionNode(path)); } function ComposerTerminalContextDecorator(props: { context: TerminalContextDraft }) { return <ComposerPendingTerminalContextChip context={props.context} />; } class ComposerTerminalContextNode extends DecoratorNode<ReactElement> { __context: TerminalContextDraft; static override getType(): string { return "composer-terminal-context"; } static override clone(node: ComposerTerminalContextNode): ComposerTerminalContextNode { return new ComposerTerminalContextNode(node.__context, node.__key); } static override importJSON( serializedNode: SerializedComposerTerminalContextNode, ): ComposerTerminalContextNode { return $createComposerTerminalContextNode(serializedNode.context); } constructor(context: TerminalContextDraft, key?: NodeKey) { super(key); this.__context = context; } override exportJSON(): SerializedComposerTerminalContextNode { return { ...super.exportJSON(), context: this.__context, type: "composer-terminal-context", version: 1, }; } override createDOM(): HTMLElement { const dom = document.createElement("span"); dom.className = "inline-flex align-middle leading-none"; return dom; } override updateDOM(): false { return false; } override getTextContent(): string { return INLINE_TERMINAL_CONTEXT_PLACEHOLDER; } override isInline(): true { return true; } override decorate(): ReactElement { return <ComposerTerminalContextDecorator context={this.__context} />; } } function $createComposerTerminalContextNode( context: TerminalContextDraft, ): ComposerTerminalContextNode { return $applyNodeReplacement(new ComposerTerminalContextNode(context)); } type ComposerInlineTokenNode = ComposerMentionNode | ComposerTerminalContextNode; function isComposerInlineTokenNode(candidate: unknown): candidate is ComposerInlineTokenNode { return ( candidate instanceof ComposerMentionNode || candidate instanceof ComposerTerminalContextNode ); } function resolvedThemeFromDocument(): "light" | "dark" { return document.documentElement.classList.contains("dark") ? "dark" : "light"; } function renderMentionChipDom(container: HTMLElement, pathValue: string): void { container.textContent = ""; container.style.setProperty("user-select", "none"); container.style.setProperty("-webkit-user-select", "none"); const theme = resolvedThemeFromDocument(); const icon = document.createElement("img"); icon.alt = ""; icon.ariaHidden = "true"; icon.className = COMPOSER_INLINE_CHIP_ICON_CLASS_NAME; icon.loading = "lazy"; icon.src = getVscodeIconUrlForEntry(pathValue, inferEntryKindFromPath(pathValue), theme); const label = document.createElement("span"); label.className = COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME; label.textContent = basenameOfPath(pathValue); container.append(icon, label); } function terminalContextSignature(contexts: ReadonlyArray<TerminalContextDraft>): string { return contexts .map((context) => [ context.id, context.threadId, context.terminalId, context.terminalLabel, context.lineStart, context.lineEnd, context.createdAt, context.text, ].join("\u001f"), ) .join("\u001e"); } function clampExpandedCursor(value: string, cursor: number): number { if (!Number.isFinite(cursor)) return value.length; return Math.max(0, Math.min(value.length, Math.floor(cursor))); } function getComposerInlineTokenTextLength(_node: ComposerInlineTokenNode): 1 { return 1; } function getComposerInlineTokenExpandedTextLength(node: ComposerInlineTokenNode): number { return node.getTextContentSize(); } function getAbsoluteOffsetForInlineTokenPoint( node: ComposerInlineTokenNode, absoluteOffset: number, pointOffset: number, ): number { return absoluteOffset + (pointOffset > 0 ? getComposerInlineTokenTextLength(node) : 0); } function getExpandedAbsoluteOffsetForInlineTokenPoint( node: ComposerInlineTokenNode, absoluteOffset: number, pointOffset: number, ): number { return absoluteOffset + (pointOffset > 0 ? getComposerInlineTokenExpandedTextLength(node) : 0); } function findSelectionPointForInlineToken( node: ComposerInlineTokenNode, remainingRef: { value: number }, ): { key: string; offset: number; type: "element" } | null { const parent = node.getParent(); if (!parent || !$isElementNode(parent)) return null; const index = node.getIndexWithinParent(); if (remainingRef.value === 0) { return { key: parent.getKey(), offset: index, type: "element", }; } if (remainingRef.value === getComposerInlineTokenTextLength(node)) { return { key: parent.getKey(), offset: index + 1, type: "element", }; } remainingRef.value -= getComposerInlineTokenTextLength(node); return null; } function getComposerNodeTextLength(node: LexicalNode): number { if (isComposerInlineTokenNode(node)) { return getComposerInlineTokenTextLength(node); } if ($isTextNode(node)) { return node.getTextContentSize(); } if ($isLineBreakNode(node)) { return 1; } if ($isElementNode(node)) { return node.getChildren().reduce((total, child) => total + getComposerNodeTextLength(child), 0); } return 0; } function getComposerNodeExpandedTextLength(node: LexicalNode): number { if (isComposerInlineTokenNode(node)) { return getComposerInlineTokenExpandedTextLength(node); } if ($isTextNode(node)) { return node.getTextContentSize(); } if ($isLineBreakNode(node)) { return 1; } if ($isElementNode(node)) { return node .getChildren() .reduce((total, child) => total + getComposerNodeExpandedTextLength(child), 0); } return 0; } function getAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: number): number { let offset = 0; let current: LexicalNode | null = node; while (current) { const nextParent = current.getParent() as LexicalNode | null; if (!nextParent || !$isElementNode(nextParent)) { break; } const siblings = nextParent.getChildren(); const index = current.getIndexWithinParent(); for (let i = 0; i < index; i += 1) { const sibling = siblings[i]; if (!sibling) continue; offset += getComposerNodeTextLength(sibling); } current = nextParent; } if ($isTextNode(node)) { if (node instanceof ComposerMentionNode) { return getAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } return offset + Math.min(pointOffset, node.getTextContentSize()); } if (node instanceof ComposerTerminalContextNode) { return getAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } if ($isLineBreakNode(node)) { return offset + Math.min(pointOffset, 1); } if ($isElementNode(node)) { const children = node.getChildren(); const clampedOffset = Math.max(0, Math.min(pointOffset, children.length)); for (let i = 0; i < clampedOffset; i += 1) { const child = children[i]; if (!child) continue; offset += getComposerNodeTextLength(child); } return offset; } return offset; } function getExpandedAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: number): number { let offset = 0; let current: LexicalNode | null = node; while (current) { const nextParent = current.getParent() as LexicalNode | null; if (!nextParent || !$isElementNode(nextParent)) { break; } const siblings = nextParent.getChildren(); const index = current.getIndexWithinParent(); for (let i = 0; i < index; i += 1) { const sibling = siblings[i]; if (!sibling) continue; offset += getComposerNodeExpandedTextLength(sibling); } current = nextParent; } if ($isTextNode(node)) { if (node instanceof ComposerMentionNode) { return getExpandedAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } return offset + Math.min(pointOffset, node.getTextContentSize()); } if (node instanceof ComposerTerminalContextNode) { return getExpandedAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); } if ($isLineBreakNode(node)) { return offset + Math.min(pointOffset, 1); } if ($isElementNode(node)) { const children = node.getChildren(); const clampedOffset = Math.max(0, Math.min(pointOffset, children.length)); for (let i = 0; i < clampedOffset; i += 1) { const child = children[i]; if (!child) continue; offset += getComposerNodeExpandedTextLength(child); } return offset; } return offset; } function findSelectionPointAtOffset( node: LexicalNode, remainingRef: { value: number }, ): { key: string; offset: number; type: "text" | "element" } | null { if (node instanceof ComposerMentionNode) { return findSelectionPointForInlineToken(node, remainingRef); } if (node instanceof ComposerTerminalContextNode) { return findSelectionPointForInlineToken(node, remainingRef); } if ($isTextNode(node)) { const size = node.getTextContentSize(); if (remainingRef.value <= size) { return { key: node.getKey(), offset: remainingRef.value, type: "text", }; } remainingRef.value -= size; return null; } if ($isLineBreakNode(node)) { const parent = node.getParent(); if (!parent) return null; const index = node.getIndexWithinParent(); if (remainingRef.value === 0) { return { key: parent.getKey(), offset: index, type: "element",