@@ -11,6 +11,8 @@ export class TextareaInput implements MarkdownEditorInput {
1111 protected onChange : ( ) => void ;
1212 protected eventController = new AbortController ( ) ;
1313
14+ protected textSizeCache : { x : number ; y : number } | null = null ;
15+
1416 constructor (
1517 input : HTMLTextAreaElement ,
1618 shortcuts : MarkdownEditorShortcutMap ,
@@ -25,6 +27,8 @@ export class TextareaInput implements MarkdownEditorInput {
2527 this . onKeyDown = this . onKeyDown . bind ( this ) ;
2628 this . configureListeners ( ) ;
2729
30+ // TODO - Undo/Redo
31+
2832 this . input . style . removeProperty ( "display" ) ;
2933 }
3034
@@ -45,15 +49,24 @@ export class TextareaInput implements MarkdownEditorInput {
4549 this . input . addEventListener ( 'input' , ( ) => {
4650 this . onChange ( ) ;
4751 } , { signal : this . eventController . signal } ) ;
52+
53+ this . input . addEventListener ( 'click' , ( event : MouseEvent ) => {
54+ const x = event . clientX ;
55+ const y = event . clientY ;
56+ const range = this . eventToPosition ( event ) ;
57+ const text = this . getText ( ) . split ( '' ) ;
58+ console . log ( range , text . slice ( 0 , 20 ) ) ;
59+ } ) ;
4860 }
4961
5062 onKeyDown ( e : KeyboardEvent ) {
5163 const isApple = navigator . platform . startsWith ( "Mac" ) || navigator . platform === "iPhone" ;
64+ const key = e . key . length > 1 ? e . key : e . key . toLowerCase ( ) ;
5265 const keyParts = [
5366 e . shiftKey ? 'Shift' : null ,
5467 isApple && e . metaKey ? 'Mod' : null ,
5568 ! isApple && e . ctrlKey ? 'Mod' : null ,
56- e . key ,
69+ key ,
5770 ] ;
5871
5972 const keyString = keyParts . filter ( Boolean ) . join ( '-' ) ;
@@ -65,10 +78,37 @@ export class TextareaInput implements MarkdownEditorInput {
6578
6679 appendText ( text : string ) : void {
6780 this . input . value += `\n${ text } ` ;
81+ this . input . dispatchEvent ( new Event ( 'input' ) ) ;
6882 }
6983
70- coordsToSelection ( x : number , y : number ) : MarkdownEditorInputSelection {
71- // TODO
84+ eventToPosition ( event : MouseEvent ) : MarkdownEditorInputSelection {
85+ const eventCoords = this . mouseEventToTextRelativeCoords ( event ) ;
86+ const textSize = this . measureTextSize ( ) ;
87+ const lineWidth = this . measureLineCharCount ( textSize . x ) ;
88+
89+ const lines = this . getText ( ) . split ( '\n' ) ;
90+
91+ // TODO - Check this
92+
93+ let currY = 0 ;
94+ let currPos = 0 ;
95+ for ( const line of lines ) {
96+ let linePos = 0 ;
97+ const wrapCount = Math . max ( Math . ceil ( line . length / lineWidth ) , 1 ) ;
98+ for ( let i = 0 ; i < wrapCount ; i ++ ) {
99+ currY += textSize . y ;
100+ if ( currY > eventCoords . y ) {
101+ const targetX = Math . floor ( eventCoords . x / textSize . x ) ;
102+ const maxPos = Math . min ( currPos + linePos + targetX , currPos + line . length ) ;
103+ return { from : maxPos , to : maxPos } ;
104+ }
105+
106+ linePos += lineWidth ;
107+ }
108+
109+ currPos += line . length + 1 ;
110+ }
111+
72112 return this . getSelection ( ) ;
73113 }
74114
@@ -81,11 +121,11 @@ export class TextareaInput implements MarkdownEditorInput {
81121 let lineStart = 0 ;
82122 for ( let i = 0 ; i < lines . length ; i ++ ) {
83123 const line = lines [ i ] ;
84- const newEnd = lineStart + line . length + 1 ;
85- if ( position < newEnd ) {
86- return { from : lineStart , to : newEnd } ;
124+ const lineEnd = lineStart + line . length ;
125+ if ( position <= lineEnd ) {
126+ return { from : lineStart , to : lineEnd } ;
87127 }
88- lineStart = newEnd ;
128+ lineStart = lineEnd + 1 ;
89129 }
90130
91131 return { from : 0 , to : 0 } ;
@@ -140,6 +180,7 @@ export class TextareaInput implements MarkdownEditorInput {
140180
141181 setText ( text : string , selection ?: MarkdownEditorInputSelection ) : void {
142182 this . input . value = text ;
183+ this . input . dispatchEvent ( new Event ( 'input' ) ) ;
143184 if ( selection ) {
144185 this . setSelection ( selection , false ) ;
145186 }
@@ -154,4 +195,52 @@ export class TextareaInput implements MarkdownEditorInput {
154195 this . setSelection ( newSelection , false ) ;
155196 }
156197 }
198+
199+ protected measureTextSize ( ) : { x : number ; y : number } {
200+ if ( this . textSizeCache ) {
201+ return this . textSizeCache ;
202+ }
203+
204+ const el = document . createElement ( "div" ) ;
205+ el . textContent = `a\nb` ;
206+ const inputStyles = window . getComputedStyle ( this . input )
207+ el . style . font = inputStyles . font ;
208+ el . style . lineHeight = inputStyles . lineHeight ;
209+ el . style . padding = '0px' ;
210+ el . style . display = 'inline-block' ;
211+ el . style . visibility = 'hidden' ;
212+ el . style . position = 'absolute' ;
213+ el . style . whiteSpace = 'pre' ;
214+ this . input . after ( el ) ;
215+
216+ const bounds = el . getBoundingClientRect ( ) ;
217+ el . remove ( ) ;
218+ this . textSizeCache = {
219+ x : bounds . width ,
220+ y : bounds . height / 2 ,
221+ } ;
222+ return this . textSizeCache ;
223+ }
224+
225+ protected measureLineCharCount ( textWidth : number ) : number {
226+ const inputStyles = window . getComputedStyle ( this . input ) ;
227+ const paddingLeft = Number ( inputStyles . paddingLeft . replace ( 'px' , '' ) ) ;
228+ const paddingRight = Number ( inputStyles . paddingRight . replace ( 'px' , '' ) ) ;
229+ const width = Number ( inputStyles . width . replace ( 'px' , '' ) ) ;
230+ const textSpace = width - ( paddingLeft + paddingRight ) ;
231+
232+ return Math . floor ( textSpace / textWidth ) ;
233+ }
234+
235+ protected mouseEventToTextRelativeCoords ( event : MouseEvent ) : { x : number ; y : number } {
236+ const inputBounds = this . input . getBoundingClientRect ( ) ;
237+ const inputStyles = window . getComputedStyle ( this . input ) ;
238+ const paddingTop = Number ( inputStyles . paddingTop . replace ( 'px' , '' ) ) ;
239+ const paddingLeft = Number ( inputStyles . paddingLeft . replace ( 'px' , '' ) ) ;
240+
241+ const xPos = Math . max ( event . clientX - ( inputBounds . left + paddingLeft ) , 0 ) ;
242+ const yPos = Math . max ( ( event . clientY - ( inputBounds . top + paddingTop ) ) + this . input . scrollTop , 0 ) ;
243+
244+ return { x : xPos , y : yPos } ;
245+ }
157246}
0 commit comments