@@ -11,6 +11,8 @@ export class TextareaInput implements MarkdownEditorInput {
11
11
protected onChange : ( ) => void ;
12
12
protected eventController = new AbortController ( ) ;
13
13
14
+ protected textSizeCache : { x : number ; y : number } | null = null ;
15
+
14
16
constructor (
15
17
input : HTMLTextAreaElement ,
16
18
shortcuts : MarkdownEditorShortcutMap ,
@@ -25,6 +27,8 @@ export class TextareaInput implements MarkdownEditorInput {
25
27
this . onKeyDown = this . onKeyDown . bind ( this ) ;
26
28
this . configureListeners ( ) ;
27
29
30
+ // TODO - Undo/Redo
31
+
28
32
this . input . style . removeProperty ( "display" ) ;
29
33
}
30
34
@@ -45,15 +49,24 @@ export class TextareaInput implements MarkdownEditorInput {
45
49
this . input . addEventListener ( 'input' , ( ) => {
46
50
this . onChange ( ) ;
47
51
} , { 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
+ } ) ;
48
60
}
49
61
50
62
onKeyDown ( e : KeyboardEvent ) {
51
63
const isApple = navigator . platform . startsWith ( "Mac" ) || navigator . platform === "iPhone" ;
64
+ const key = e . key . length > 1 ? e . key : e . key . toLowerCase ( ) ;
52
65
const keyParts = [
53
66
e . shiftKey ? 'Shift' : null ,
54
67
isApple && e . metaKey ? 'Mod' : null ,
55
68
! isApple && e . ctrlKey ? 'Mod' : null ,
56
- e . key ,
69
+ key ,
57
70
] ;
58
71
59
72
const keyString = keyParts . filter ( Boolean ) . join ( '-' ) ;
@@ -65,10 +78,37 @@ export class TextareaInput implements MarkdownEditorInput {
65
78
66
79
appendText ( text : string ) : void {
67
80
this . input . value += `\n${ text } ` ;
81
+ this . input . dispatchEvent ( new Event ( 'input' ) ) ;
68
82
}
69
83
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
+
72
112
return this . getSelection ( ) ;
73
113
}
74
114
@@ -81,11 +121,11 @@ export class TextareaInput implements MarkdownEditorInput {
81
121
let lineStart = 0 ;
82
122
for ( let i = 0 ; i < lines . length ; i ++ ) {
83
123
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 } ;
87
127
}
88
- lineStart = newEnd ;
128
+ lineStart = lineEnd + 1 ;
89
129
}
90
130
91
131
return { from : 0 , to : 0 } ;
@@ -140,6 +180,7 @@ export class TextareaInput implements MarkdownEditorInput {
140
180
141
181
setText ( text : string , selection ?: MarkdownEditorInputSelection ) : void {
142
182
this . input . value = text ;
183
+ this . input . dispatchEvent ( new Event ( 'input' ) ) ;
143
184
if ( selection ) {
144
185
this . setSelection ( selection , false ) ;
145
186
}
@@ -154,4 +195,52 @@ export class TextareaInput implements MarkdownEditorInput {
154
195
this . setSelection ( newSelection , false ) ;
155
196
}
156
197
}
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
+ }
157
246
}
0 commit comments