@@ -6,6 +6,7 @@ import { useRouter } from 'next/router'
6
6
import { useMounted } from 'nextra/hooks'
7
7
import { InformationCircleIcon , SpinnerIcon } from 'nextra/icons'
8
8
import type {
9
+ ChangeEvent ,
9
10
CompositionEvent ,
10
11
FocusEventHandler ,
11
12
KeyboardEvent ,
@@ -34,7 +35,7 @@ export function Search({
34
35
className,
35
36
overlayClassName,
36
37
value,
37
- onChange,
38
+ onChange : onChangeProp ,
38
39
onActive,
39
40
loading,
40
41
error,
@@ -48,8 +49,6 @@ export function Search({
48
49
const input = useRef < HTMLInputElement > ( null )
49
50
const ulRef = useRef < HTMLUListElement > ( null )
50
51
const [ focused , setFocused ] = useState ( false )
51
- // Trigger the search after the Input is complete for languages like Chinese
52
- const [ composition , setComposition ] = useState ( true )
53
52
54
53
useEffect ( ( ) => {
55
54
setActive ( 0 )
@@ -86,12 +85,15 @@ export function Search({
86
85
}
87
86
} , [ ] )
88
87
88
+ const clearValue = useCallback ( ( ) => {
89
+ onChangeProp ( '' )
90
+ } , [ onChangeProp ] )
89
91
const finishSearch = useCallback ( ( ) => {
90
92
input . current ?. blur ( )
91
- onChange ( '' )
93
+ clearValue ( )
92
94
setShow ( false )
93
95
setMenu ( false )
94
- } , [ onChange , setMenu ] )
96
+ } , [ clearValue , setMenu ] )
95
97
96
98
const handleActive = useCallback (
97
99
( e : { currentTarget : { dataset : DOMStringMap } } ) => {
@@ -103,6 +105,9 @@ export function Search({
103
105
104
106
const handleKeyDown = useCallback (
105
107
< T , > ( e : KeyboardEvent < T > ) => {
108
+ // skip the character selection process in the IME for CJK language users.
109
+ if ( e . nativeEvent . isComposing ) return
110
+
106
111
switch ( e . key ) {
107
112
case 'ArrowDown' : {
108
113
if ( active + 1 < results . length ) {
@@ -132,7 +137,7 @@ export function Search({
132
137
}
133
138
case 'Enter' : {
134
139
const result = results [ active ]
135
- if ( result && composition ) {
140
+ if ( result ) {
136
141
void router . push ( result . route )
137
142
finishSearch ( )
138
143
}
@@ -145,7 +150,7 @@ export function Search({
145
150
}
146
151
}
147
152
} ,
148
- [ active , results , router , finishSearch , handleActive , composition ]
153
+ [ active , results , router , finishSearch , handleActive ]
149
154
)
150
155
151
156
const mounted = useMounted ( )
@@ -174,9 +179,7 @@ export function Search({
174
179
: '_pointer-events-none max-sm:_hidden'
175
180
) }
176
181
title = { value ? 'Clear' : undefined }
177
- onClick = { ( ) => {
178
- onChange ( '' )
179
- } }
182
+ onClick = { clearValue }
180
183
>
181
184
{ value && focused
182
185
? 'ESC'
@@ -191,12 +194,6 @@ export function Search({
191
194
</ kbd >
192
195
</ Transition >
193
196
)
194
- const handleComposition = useCallback (
195
- ( e : CompositionEvent < HTMLInputElement > ) => {
196
- setComposition ( e . type === 'compositionend' )
197
- } ,
198
- [ ]
199
- )
200
197
201
198
const handleFocus = useCallback < FocusEventHandler > (
202
199
event => {
@@ -210,6 +207,41 @@ export function Search({
210
207
[ onActive ]
211
208
)
212
209
210
+ // To handle CJK language users, refer to the following approach: https://github.com/SukkaW/foxact/commit/fe17304b410cddc4803d4fdbebf3cd5ecb070618
211
+ // An explicit explanation can be found here: https://github.com/SukkaW/foxact/blob/2b54157187d33ef873c1ab4a9b8700dfdb2e7288/docs/src/pages/use-composition-input.mdx
212
+ const compositionStateRef = useRef ( {
213
+ compositioning : false ,
214
+ emitted : false
215
+ } )
216
+ const handleChange = useCallback (
217
+ ( e : ChangeEvent < HTMLInputElement > | CompositionEvent < HTMLInputElement > ) => {
218
+ if ( ! compositionStateRef . current . compositioning ) {
219
+ const { value } = e . target as HTMLInputElement
220
+ onChangeProp ( value )
221
+ setShow ( Boolean ( value ) )
222
+ compositionStateRef . current . emitted = true
223
+ return
224
+ }
225
+ compositionStateRef . current . emitted = false
226
+ } ,
227
+ [ onChangeProp ]
228
+ )
229
+
230
+ const handleCompositionStart = useCallback ( ( ) => {
231
+ compositionStateRef . current . compositioning = true
232
+ compositionStateRef . current . emitted = false
233
+ } , [ ] )
234
+
235
+ const handleCompositionEnd = useCallback (
236
+ ( e : CompositionEvent < HTMLInputElement > ) => {
237
+ compositionStateRef . current . compositioning = false
238
+ if ( ! compositionStateRef . current . emitted ) {
239
+ handleChange ( e )
240
+ }
241
+ } ,
242
+ [ handleChange ]
243
+ )
244
+
213
245
return (
214
246
< div className = { cn ( 'nextra-search _relative md:_w-64' , className ) } >
215
247
{ renderList && (
@@ -218,16 +250,11 @@ export function Search({
218
250
219
251
< Input
220
252
ref = { input }
221
- value = { value }
222
- onChange = { e => {
223
- const { value } = e . target
224
- onChange ( value )
225
- setShow ( Boolean ( value ) )
226
- } }
253
+ onCompositionStart = { handleCompositionStart }
254
+ onCompositionEnd = { handleCompositionEnd }
255
+ onChange = { handleChange }
227
256
onFocus = { handleFocus }
228
257
onBlur = { handleFocus }
229
- onCompositionStart = { handleComposition }
230
- onCompositionEnd = { handleComposition }
231
258
type = "search"
232
259
placeholder = { renderString ( themeConfig . search . placeholder ) }
233
260
onKeyDown = { handleKeyDown }
0 commit comments