1- import type { ComputedRef , Ref } from 'vue'
2- import { computed , ref , watch } from 'vue'
3- import type { Word } from './useWordSuggestionList'
1+ import type { MaybeRefOrGetter } from 'vue'
2+ import { computed , ref , toValue , watch } from 'vue'
43import useWordSuggesterList from './useWordSuggestionList'
54import useInsertText from '/@/composables/dom/useInsertText'
65import getCaretPosition from '/@/lib/dom/caretPosition'
7- import type { Target } from '/@/lib/suggestion/basic'
8- import { getCurrentWord } from '/@/lib/suggestion/basic'
9-
10- export type WordOrConfirmedPart =
11- | Word
12- | {
13- type : 'confirmed-part'
14- text : string
15- }
16-
17- export interface Candidate {
18- word : Word
19- display ?: string
20- }
21-
22- export interface ConfirmedPart {
23- text : string
24- display ?: string
25- }
6+ import type { Target , WordOrConfirmedPart } from '/@/lib/suggestion/basic'
7+ import {
8+ getCurrentWord ,
9+ getNextCandidateIndex ,
10+ getPrevCandidateIndex ,
11+ getSelectedCandidateIndex
12+ } from '/@/lib/suggestion/basic'
2613
2714/**
2815 * 補完を表示する最小の文字数
@@ -31,51 +18,77 @@ export interface ConfirmedPart {
3118const MIN_LENGTH = 3
3219
3320const useWordSuggester = (
34- textareaRef : ComputedRef < HTMLTextAreaElement | undefined > ,
35- value : Ref < string >
21+ textareaRef : MaybeRefOrGetter < HTMLTextAreaElement >
3622) => {
3723 const isSuggesterShown = ref ( false )
24+
3825 const target = ref < Target > ( {
3926 word : '' ,
4027 begin : 0 ,
4128 end : 0
4229 } )
30+
31+ /**
32+ * nullのときは未選択
33+ * -1のときは確定部分が選択されている
34+ * 0~のときは候補が選択されている
35+ */
36+ const selectedIndex = ref < number | null > ( null )
37+
4338 /**
4439 * targetは補完をしたときに更新されないがこれは更新する
4540 * これはtargetを更新すると候補リストが変わってしまうため
4641 * (補完をしたときは候補リストを変化させたくない)
4742 */
4843 const currentInputWord = ref ( '' )
44+
45+ const { suggestedCandidateWords, confirmedText } = useWordSuggesterList (
46+ ( ) => target . value . word ,
47+ MIN_LENGTH
48+ )
49+
4950 watch (
5051 ( ) => target . value . word ,
5152 word => {
5253 currentInputWord . value = word
54+
55+ selectedIndex . value = getSelectedCandidateIndex (
56+ suggestedCandidateWords . value ,
57+ confirmedText . value ,
58+ word
59+ )
60+ } ,
61+ {
62+ immediate : true
5363 }
5464 )
5565
66+ const { insertText } = useInsertText ( textareaRef , target )
67+
5668 const position = computed ( ( ) => {
57- if ( ! textareaRef . value ) return { top : 0 , left : 0 }
58- return getCaretPosition ( textareaRef . value , target . value . begin )
69+ if ( ! toValue ( textareaRef ) ) return { top : 0 , left : 0 }
70+ return getCaretPosition ( toValue ( textareaRef ) , target . value . begin )
5971 } )
6072
61- const {
62- suggestedCandidateWords,
63- confirmedText,
64- selectedCandidateIndex,
65- prevCandidateText,
66- nextCandidateText
67- } = useWordSuggesterList ( target , currentInputWord , MIN_LENGTH )
73+ const getCandidateTextFromIndex = ( i : number ) => {
74+ if ( i === - 1 ) return confirmedText . value
75+ return suggestedCandidateWords . value [ i ] ?. text ?? ''
76+ }
77+
78+ const insertTextAndMoveTarget = ( index : number ) => {
79+ selectedIndex . value = index
80+ const text = getCandidateTextFromIndex ( index )
6881
69- const { insertText } = useInsertText ( textareaRef , target )
70- const insertTextAndMoveTarget = ( text : string ) => {
7182 insertText ( text )
7283 target . value . end = target . value . begin + text . length
7384 currentInputWord . value = text
7485 }
7586
7687 const updateTarget = ( ) => {
77- if ( ! textareaRef . value ) return
78- target . value = getCurrentWord ( textareaRef . value , value . value )
88+ if ( ! toValue ( textareaRef ) ) return
89+
90+ target . value = getCurrentWord ( toValue ( textareaRef ) )
91+
7992 if ( target . value . word . length < MIN_LENGTH ) {
8093 isSuggesterShown . value = false
8194 return
@@ -88,6 +101,16 @@ const useWordSuggester = (
88101 if ( e . isComposing ) return
89102 if ( ! isSuggesterShown . value ) return
90103
104+ const prevIndex = getPrevCandidateIndex (
105+ suggestedCandidateWords . value . length ,
106+ selectedIndex . value
107+ )
108+
109+ const nextIndex = getNextCandidateIndex (
110+ suggestedCandidateWords . value . length ,
111+ selectedIndex . value
112+ )
113+
91114 // Tabによるフォーカスの移動を防止するため、長押しで連続移動できるようにするためにkeyDownで行う必要がある
92115 if ( e . key === 'Tab' ) {
93116 e . preventDefault ( )
@@ -98,24 +121,25 @@ const useWordSuggester = (
98121
99122 // 未選択状態では確定部分が補完される
100123 if ( e . shiftKey ) {
101- insertTextAndMoveTarget ( prevCandidateText . value )
124+ insertTextAndMoveTarget ( prevIndex )
102125 } else {
103- insertTextAndMoveTarget ( nextCandidateText . value )
126+ insertTextAndMoveTarget ( nextIndex )
104127 }
105128 return
106129 }
107130
108131 if ( e . key === 'ArrowUp' ) {
109132 e . preventDefault ( )
110- insertTextAndMoveTarget ( prevCandidateText . value )
133+ insertTextAndMoveTarget ( prevIndex )
111134 return
112135 }
113136 if ( e . key === 'ArrowDown' ) {
114137 e . preventDefault ( )
115- insertTextAndMoveTarget ( nextCandidateText . value )
138+ insertTextAndMoveTarget ( nextIndex )
116139 return
117140 }
118141 }
142+
119143 const onKeyUp = ( e : KeyboardEvent ) => {
120144 if ( e . key === 'Tab' || e . key === 'ArrowUp' || e . key === 'ArrowDown' ) return
121145 // 文字入力後の状態をとるためkeyUpで行う必要がある
@@ -153,7 +177,7 @@ const useWordSuggester = (
153177 suggesterWidth : 240 ,
154178 position,
155179 suggestedCandidates,
156- selectedCandidateIndex ,
180+ selectedIndex ,
157181 confirmedPart
158182 }
159183}
0 commit comments