1+ // @refresh reload
12import * as wanakana from "wanakana" ;
23import {
34 createSignal ,
@@ -74,18 +75,18 @@ export function IMEField() {
7475 const unconfirmedText = createMemo ( ( ) => input ( ) . slice ( confirmedIndex ( ) ) ) ;
7576
7677 onMount ( ( ) => {
77- if ( ta ) {
78- wanakana . bind ( ta ) ;
79- ta . value = "" ;
80- setInput ( "" ) ;
81- ta . focus ( ) ;
82- }
78+ if ( ! ta ) return ;
79+ wanakana . bind ( ta ) ;
80+ ta . value = "" ;
81+ setInput ( "" ) ;
82+ ta . focus ( ) ;
8383 } ) ;
8484
8585 onCleanup ( ( ) => {
8686 if ( ta ) wanakana . unbind ( ta ) ;
8787 } ) ;
8888
89+ // reset menu when lookupReading changes
8990 createEffect ( ( ) => {
9091 suggestions ( ) ;
9192 itemRefs = [ ] ;
@@ -122,78 +123,55 @@ export function IMEField() {
122123 setTimeout ( ( ) => ta ?. focus ( ) , 0 ) ;
123124 }
124125
125- function handleKeyDown (
126- e : KeyboardEvent & {
127- currentTarget : HTMLTextAreaElement ;
128- }
129- ) {
126+ function handleKeyDown ( e : KeyboardEvent & { currentTarget : HTMLTextAreaElement } ) {
130127 if ( isMenuOpen ( ) ) {
131128 const len = suggestions ( ) . length ;
132- if ( len > 0 ) {
133- if ( e . key === "ArrowDown" ) {
134- e . preventDefault ( ) ;
135- const newIndex = ( selectedIndex ( ) + 1 ) % len ;
136- setSelectedIndex ( newIndex ) ;
137- const item = itemRefs [ newIndex ] ;
138- const container = listRef ;
139- if ( item && container ) {
140- const itop = item . offsetTop ;
141- const ibot = itop + item . offsetHeight ;
142- const ctop = container . scrollTop ;
143- const cheight = container . clientHeight ;
144- if ( ibot > ctop + cheight ) {
145- container . scrollTop = ibot - cheight ;
146- } else if ( itop < ctop ) {
147- container . scrollTop = itop ;
148- }
149- }
150- return ;
151- }
152- if ( e . key === "ArrowUp" ) {
153- e . preventDefault ( ) ;
154- const newIndex = ( selectedIndex ( ) - 1 + len ) % len ;
155- setSelectedIndex ( newIndex ) ;
156- const item = itemRefs [ newIndex ] ;
157- const container = listRef ;
158- if ( item && container ) {
159- const itop = item . offsetTop ;
160- const ibot = itop + item . offsetHeight ;
161- const ctop = container . scrollTop ;
162- const cheight = container . clientHeight ;
163- if ( itop < ctop ) {
164- container . scrollTop = itop ;
165- } else if ( ibot > ctop + cheight ) {
166- container . scrollTop = ibot - cheight ;
167- }
129+ if ( len > 0 && ( e . key === "ArrowDown" || e . key === "ArrowUp" ) ) {
130+ e . preventDefault ( ) ;
131+ const delta = e . key === "ArrowDown" ? 1 : - 1 ;
132+ const newIndex = ( selectedIndex ( ) + delta + len ) % len ;
133+ setSelectedIndex ( newIndex ) ;
134+ const item = itemRefs [ newIndex ] ;
135+ const container = listRef ;
136+ if ( item && container ) {
137+ const itop = item . offsetTop ;
138+ const ibot = itop + item . offsetHeight ;
139+ const ctop = container . scrollTop ;
140+ const cheight = container . clientHeight ;
141+ if ( ibot > ctop + cheight ) {
142+ container . scrollTop = ibot - cheight ;
143+ } else if ( itop < ctop ) {
144+ container . scrollTop = itop ;
168145 }
169- return ;
170146 }
147+ return ;
171148 }
172149 if ( e . key === "Enter" || e . key === " " ) {
173150 e . preventDefault ( ) ;
174151 commitSuggestion ( selectedIndex ( ) ) ;
175- return ;
176152 }
153+ return ;
177154 }
178155
156+ // backspace to undo last conversion
157+ const lc = lastConversion ( ) ;
179158 if (
180159 e . key === "Backspace" &&
181- lastConversion ( ) &&
182- e . currentTarget . selectionStart === lastConversion ( ) ! . end &&
183- e . currentTarget . selectionEnd === lastConversion ( ) ! . end
160+ lc &&
161+ e . currentTarget . selectionStart === lc . end &&
162+ e . currentTarget . selectionEnd === lc . end
184163 ) {
185164 e . preventDefault ( ) ;
186- const conv = lastConversion ( ) ! ;
187- const before = input ( ) . slice ( 0 , conv . start ) ;
188- const after = input ( ) . slice ( conv . end ) ;
189- const newVal = before + conv . reading + after ;
165+ const before = input ( ) . slice ( 0 , lc . start ) ;
166+ const after = input ( ) . slice ( lc . end ) ;
167+ const newVal = before + lc . reading + after ;
190168 setInput ( newVal ) ;
191- const newPos = conv . start + conv . reading . length ;
169+ const newPos = lc . start + lc . reading . length ;
192170 if ( ta ) {
193171 ta . value = newVal ;
194172 ta . setSelectionRange ( newPos , newPos ) ;
195173 }
196- setConfirmedIndex ( conv . start ) ;
174+ setConfirmedIndex ( lc . start ) ;
197175 setIsComposing ( true ) ;
198176 setLastConversion ( null ) ;
199177 return ;
@@ -213,18 +191,18 @@ export function IMEField() {
213191 const pos = e . currentTarget . selectionStart ;
214192 const reading = input ( ) . slice ( start , pos ) ;
215193 if ( wanakana . isHiragana ( reading ) ) {
216- const katakana = wanakana . toKatakana ( reading ) ;
194+ const kata = wanakana . toKatakana ( reading ) ;
217195 const before = input ( ) . slice ( 0 , start ) ;
218196 const after = input ( ) . slice ( pos ) ;
219- const newVal = before + katakana + after ;
197+ const newVal = before + kata + after ;
220198 setInput ( newVal ) ;
221- const end = before . length + katakana . length ;
199+ const end = before . length + kata . length ;
222200 if ( ta ) {
223201 ta . value = newVal ;
224202 ta . setSelectionRange ( end , end ) ;
225203 }
226204 setLastConversion ( {
227- confirmed : katakana ,
205+ confirmed : kata ,
228206 reading,
229207 start,
230208 end,
@@ -234,33 +212,41 @@ export function IMEField() {
234212 }
235213 return ;
236214 }
215+ }
216+
217+ function handleInput ( e : InputEvent & { currentTarget : HTMLTextAreaElement } ) {
218+ const val = e . currentTarget . value ;
219+ setInput ( val ) ;
220+ setIsComposing ( val . length > confirmedIndex ( ) ) ;
221+ setLastConversion ( null ) ;
237222
238- if ( e . key === " " ) {
223+ if ( e . inputType === "insertText" && e . data === " " && isComposing ( ) ) {
224+ const start = confirmedIndex ( ) ;
239225 const pos = e . currentTarget . selectionStart ;
240- const reading = input ( ) . slice ( confirmedIndex ( ) , pos ) ;
226+ const reading = val . slice ( start , pos - 1 ) ;
241227 if ( wanakana . isHiragana ( reading ) && reading . length ) {
242- e . preventDefault ( ) ;
243- setCompositionStart ( confirmedIndex ( ) ) ;
228+ setCompositionStart ( start ) ;
244229 setLookupReading ( reading ) ;
245230 setSelectedIndex ( 0 ) ;
246231 setIsMenuOpen ( true ) ;
247- return ;
248232 }
249233 }
250234 }
251235
252- function handleInput (
253- e : InputEvent & {
254- currentTarget : HTMLTextAreaElement ;
255- }
256- ) {
257- const val = e . currentTarget . value ;
258- setInput ( val ) ;
259- setIsComposing ( val . length > confirmedIndex ( ) ) ;
260- setLastConversion ( null ) ;
261- if ( isMenuOpen ( ) ) {
262- setIsMenuOpen ( false ) ;
263- setLookupReading ( null ) ;
236+ function handleCompositionStart ( e : CompositionEvent & { currentTarget : HTMLTextAreaElement } ) {
237+ setIsComposing ( true ) ;
238+ setCompositionStart ( e . currentTarget . selectionStart ) ;
239+ }
240+
241+ function handleCompositionEnd ( e : CompositionEvent & { currentTarget : HTMLTextAreaElement } ) {
242+ setIsComposing ( false ) ;
243+ const start = compositionStart ( ) ;
244+ const pos = e . currentTarget . selectionStart ;
245+ const reading = input ( ) . slice ( start , pos ) ;
246+ if ( wanakana . isHiragana ( reading ) && reading . length ) {
247+ setLookupReading ( reading ) ;
248+ setSelectedIndex ( 0 ) ;
249+ setIsMenuOpen ( true ) ;
264250 }
265251 }
266252
@@ -283,6 +269,8 @@ export function IMEField() {
283269 value = { input ( ) }
284270 onInput = { handleInput }
285271 onKeyDown = { handleKeyDown }
272+ onCompositionStart = { handleCompositionStart }
273+ onCompositionEnd = { handleCompositionEnd }
286274 class = "caret-foreground bg-transparent text-transparent"
287275 />
288276 </ div >
@@ -305,7 +293,7 @@ export function IMEField() {
305293 < For each = { suggestions ( ) } >
306294 { ( s , idx ) => (
307295 < DropdownMenuItem
308- ref = { ( el ) => ( itemRefs [ idx ( ) ] = el ) }
296+ ref = { ( el ) => ( itemRefs [ idx ( ) ] = el ! ) }
309297 onSelect = { ( ) => commitSuggestion ( idx ( ) ) }
310298 onFocus = { ( ) => setSelectedIndex ( idx ( ) ) }
311299 data-highlighted = { selectedIndex ( ) === idx ( ) }
@@ -316,9 +304,7 @@ export function IMEField() {
316304 </ For >
317305 </ div >
318306 < div class = "text-muted-foreground flex items-center justify-end border-t px-2 py-1.5 text-xs" >
319- < span >
320- { selectedIndex ( ) + 1 } / { suggestions ( ) . length }
321- </ span >
307+ { selectedIndex ( ) + 1 } / { suggestions ( ) . length }
322308 </ div >
323309 </ >
324310 </ Show >
0 commit comments