mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-02 02:36:52 +00:00
Merge the two implementations from session.tsx and command.tsx into a single shared function that covers tagName checks, contentEditable, and ancestor traversal.
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
export function isEditableTarget(target: EventTarget | null | undefined) {
|
|
if (!(target instanceof HTMLElement)) return false
|
|
if (/^(INPUT|TEXTAREA|SELECT|BUTTON)$/.test(target.tagName)) return true
|
|
if (target.isContentEditable) return true
|
|
if (target.closest("[contenteditable='true']")) return true
|
|
if (target.closest("input, textarea, select")) return true
|
|
return false
|
|
}
|
|
|
|
export function getCharacterOffsetInLine(lineElement: Element, targetNode: Node, offset: number): number {
|
|
const r = document.createRange()
|
|
r.selectNodeContents(lineElement)
|
|
r.setEnd(targetNode, offset)
|
|
return r.toString().length
|
|
}
|
|
|
|
export function getNodeOffsetInLine(lineElement: Element, charIndex: number): { node: Node; offset: number } | null {
|
|
const walker = document.createTreeWalker(lineElement, NodeFilter.SHOW_TEXT, null)
|
|
let remaining = Math.max(0, charIndex)
|
|
let lastText: Node | null = null
|
|
let lastLen = 0
|
|
let node: Node | null
|
|
while ((node = walker.nextNode())) {
|
|
const len = node.textContent?.length || 0
|
|
lastText = node
|
|
lastLen = len
|
|
if (remaining <= len) return { node, offset: remaining }
|
|
remaining -= len
|
|
}
|
|
if (lastText) return { node: lastText, offset: lastLen }
|
|
if (lineElement.firstChild) return { node: lineElement.firstChild, offset: 0 }
|
|
return null
|
|
}
|
|
|
|
export function getSelectionInContainer(
|
|
container: HTMLElement,
|
|
): { sl: number; sch: number; el: number; ech: number } | null {
|
|
const s = window.getSelection()
|
|
if (!s || s.rangeCount === 0) return null
|
|
const r = s.getRangeAt(0)
|
|
const sc = r.startContainer
|
|
const ec = r.endContainer
|
|
const getLineElement = (n: Node) =>
|
|
(n.nodeType === Node.TEXT_NODE ? (n.parentElement as Element) : (n as Element))?.closest(".line")
|
|
const sle = getLineElement(sc)
|
|
const ele = getLineElement(ec)
|
|
if (!sle || !ele) return null
|
|
if (!container.contains(sle as Node) || !container.contains(ele as Node)) return null
|
|
const cc = container.querySelector("code") as HTMLElement | null
|
|
if (!cc) return null
|
|
const lines = Array.from(cc.querySelectorAll(".line"))
|
|
const sli = lines.indexOf(sle as Element)
|
|
const eli = lines.indexOf(ele as Element)
|
|
if (sli === -1 || eli === -1) return null
|
|
const sl = sli + 1
|
|
const el = eli + 1
|
|
const sch = getCharacterOffsetInLine(sle as Element, sc, r.startOffset)
|
|
const ech = getCharacterOffsetInLine(ele as Element, ec, r.endOffset)
|
|
return { sl, sch, el, ech }
|
|
}
|