Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485import getCaretCoordinates from "textarea-caret";
export interface Anchor { line: number; top: number;}
const project = (anchors: Anchor[], value: number, from: keyof Anchor, to: keyof Anchor) => { for (let i = 1; i < anchors.length; i++) { const prev = anchors[i - 1]; const next = anchors[i]; if (value > next[from]) continue; const span = next[from] - prev[from]; if (span <= 0) return prev[to]; return prev[to] + ((value - prev[from]) / span) * (next[to] - prev[to]); } return anchors.at(-1)?.[to] ?? 0;};
export const topForLine = (anchors: Anchor[], line: number) => project(anchors, line, "line", "top");
export const lineForTop = (anchors: Anchor[], top: number) => project(anchors, top, "top", "line");
export const monotonic = (anchors: Anchor[]): Anchor[] => anchors.reduce<Anchor[]>((kept, anchor) => { const last = kept.at(-1); if (!last || (anchor.line > last.line && anchor.top >= last.top)) kept.push(anchor); return kept; }, []);
const lineCount = (value: string) => value.split("\n").length;
const previewAnchors = (pane: HTMLElement, lines: number): Anchor[] => { const origin = pane.getBoundingClientRect().top - pane.scrollTop; const found = Array.from(pane.querySelectorAll<HTMLElement>("[data-line]"), (element) => ({ line: Number(element.dataset.line), top: element.getBoundingClientRect().top - origin })); return monotonic([{ line: 0, top: 0 }, ...found, { line: lines, top: pane.scrollHeight }]);};
export const previewLine = (pane: HTMLElement, value: string) => lineForTop(previewAnchors(pane, lineCount(value)), pane.scrollTop);
export const scrollPreviewToLine = (pane: HTMLElement, value: string, line: number) => { pane.scrollTop = topForLine(previewAnchors(pane, lineCount(value)), line);};
const editorGeometry = (textarea: HTMLTextAreaElement) => { const starts = [0]; for (let i = 0; i < textarea.value.length; i++) { if (textarea.value[i] === "\n") starts.push(i + 1); } const origin = getCaretCoordinates(textarea, 0).top; const lineTop = (line: number) => line >= starts.length ? textarea.scrollHeight : getCaretCoordinates(textarea, starts[line]).top - origin; const around = (line: number): Anchor[] => [ { line, top: lineTop(line) }, { line: line + 1, top: lineTop(line + 1) } ]; return { count: starts.length, lineTop, around };};
export const editorLine = (textarea: HTMLTextAreaElement) => { const { count, lineTop, around } = editorGeometry(textarea); const target = textarea.scrollTop; let low = 0; let high = count - 1; while (low < high) { const mid = Math.ceil((low + high) / 2); if (lineTop(mid) <= target) low = mid; else high = mid - 1; } return lineForTop(around(low), target);};
export const scrollEditorToLine = (textarea: HTMLTextAreaElement, line: number) => { const { count, around } = editorGeometry(textarea); const base = Math.min(Math.max(0, Math.floor(line)), count - 1); textarea.scrollTop = topForLine(around(base), line);};