A world-class math input for the web
at main 77 lines 2.2 kB view raw
1import { Cursor } from "./cursor"; 2import { StrandPath } from "./path"; 3 4export class SelectionRange { 5 readonly anchor: Cursor; 6 readonly head: Cursor; 7 8 constructor(anchor = new Cursor(), head = anchor.clone()) { 9 this.anchor = anchor; 10 this.head = head; 11 } 12 13 get commonStrandPath(): StrandPath { 14 const minLength = Math.min( 15 this.anchor.strandPath.length, 16 this.head.strandPath.length 17 ); 18 const commonPath: StrandPath = []; 19 for (let i = 0; i < minLength; i++) { 20 const anchorLevel = this.anchor.strandPath[i]; 21 const headLevel = this.head.strandPath[i]; 22 if ( 23 anchorLevel.tokenIndex === headLevel.tokenIndex && 24 anchorLevel.childIndex === headLevel.childIndex 25 ) { 26 commonPath.push(anchorLevel); 27 } else { 28 break; 29 } 30 } 31 return commonPath; 32 } 33 34 get commonStartPos(): number { 35 const anchorPos = this.projectCursorPosOnCommonStrand(this.anchor, "left"); 36 const headPos = this.projectCursorPosOnCommonStrand(this.head, "left"); 37 return Math.min(anchorPos, headPos); 38 } 39 40 get commonEndPos(): number { 41 const anchorPos = this.projectCursorPosOnCommonStrand(this.anchor, "right"); 42 const headPos = this.projectCursorPosOnCommonStrand(this.head, "right"); 43 return Math.max(anchorPos, headPos); 44 } 45 46 private projectCursorPosOnCommonStrand( 47 cursor: Cursor, 48 bias: "left" | "right" 49 ): number { 50 const commonStrandPath = this.commonStrandPath; 51 for (let i = 0; i < commonStrandPath.length; i++) { 52 const { tokenIndex, childIndex } = cursor.strandPath[i]; 53 const commonLevel = commonStrandPath[i]; 54 if ( 55 tokenIndex !== commonLevel.tokenIndex || 56 childIndex !== commonLevel.childIndex 57 ) { 58 throw new Error("Cursor is not on or within the common strand"); 59 } 60 } 61 62 if (cursor.strandPath.length === commonStrandPath.length) { 63 return cursor.pos; 64 } 65 66 const { tokenIndex } = cursor.strandPath[commonStrandPath.length]; 67 if (bias === "left") { 68 return tokenIndex; 69 } else { 70 return tokenIndex + 1; 71 } 72 } 73 74 isCollapsed(): boolean { 75 return this.anchor.equals(this.head); 76 } 77}