@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
33import Tag from './Tag'
44import Input from './Input'
55import Suggestions from './Suggestions'
6+ import { matchExact , matchPartial } from './concerns/matchers'
67
78const KEYS = {
89 ENTER : 'Enter' ,
@@ -25,15 +26,64 @@ const CLASS_NAMES = {
2526 suggestionDisabled : 'is-disabled'
2627}
2728
29+ function pressDelimiterKey ( e ) {
30+ if ( this . state . query || this . state . index > - 1 ) {
31+ e . preventDefault ( )
32+ }
33+
34+ if ( this . state . query . length >= this . props . minQueryLength ) {
35+ // Check if the user typed in an existing suggestion.
36+ const match = this . state . options . findIndex ( ( option ) => {
37+ return matchExact ( this . state . query ) . test ( option . name )
38+ } )
39+
40+ const index = this . state . index === - 1 ? match : this . state . index
41+
42+ if ( index > - 1 ) {
43+ this . addTag ( this . state . options [ index ] )
44+ } else if ( this . props . allowNew ) {
45+ this . addTag ( { name : this . state . query } )
46+ }
47+ }
48+ }
49+
50+ function pressUpKey ( e ) {
51+ e . preventDefault ( )
52+
53+ // if first item, cycle to the bottom
54+ const size = this . state . options . length - 1
55+ this . setState ( { index : this . state . index <= 0 ? size : this . state . index - 1 } )
56+ }
57+
58+ function pressDownKey ( e ) {
59+ e . preventDefault ( )
60+
61+ // if last item, cycle to top
62+ const size = this . state . options . length - 1
63+ this . setState ( { index : this . state . index >= size ? 0 : this . state . index + 1 } )
64+ }
65+
66+ function pressBackspaceKey ( ) {
67+ // when backspace key is pressed and query is blank, delete the last tag
68+ if ( ! this . state . query . length ) {
69+ this . deleteTag ( this . props . tags . length - 1 )
70+ }
71+ }
72+
73+ function filterSuggestions ( query , suggestions ) {
74+ const regexp = matchPartial ( query )
75+ return suggestions . filter ( ( item ) => regexp . test ( item . name ) )
76+ }
77+
2878class ReactTags extends React . Component {
2979 constructor ( props ) {
3080 super ( props )
3181
3282 this . state = {
3383 query : '' ,
3484 focused : false ,
35- expanded : false ,
36- selected : - 1 ,
85+ options : [ ] ,
86+ index : - 1 ,
3787 classNames : Object . assign ( { } , CLASS_NAMES , this . props . classNames )
3888 }
3989 }
@@ -51,55 +101,31 @@ class ReactTags extends React.Component {
51101 this . props . onInput ( query )
52102 }
53103
54- this . setState ( { query } )
104+ if ( query !== this . state . query ) {
105+ const filtered = filterSuggestions ( query , this . props . suggestions )
106+ const options = filtered . slice ( 0 , this . props . maxSuggestionsLength )
107+
108+ this . setState ( { query, options } )
109+ }
55110 }
56111
57112 onKeyDown ( e ) {
58- const { query, selected } = this . state
59- const { delimiters } = this . props
60-
61- // when one of the terminating keys is pressed, add current query to the tags.
62- if ( delimiters . indexOf ( e . key ) > - 1 ) {
63- if ( query || selected > - 1 ) {
64- e . preventDefault ( )
65- }
66-
67- if ( query . length >= this . props . minQueryLength ) {
68- // Check if the user typed in an existing suggestion.
69- const match = this . suggestions . state . options . findIndex ( ( suggestion ) => (
70- suggestion . name . search ( new RegExp ( `^${ query } $` , 'i' ) ) === 0
71- ) )
72-
73- const index = selected === - 1 ? match : selected
74-
75- if ( index > - 1 ) {
76- this . addTag ( this . suggestions . state . options [ index ] )
77- } else if ( this . props . allowNew ) {
78- this . addTag ( { name : query } )
79- }
80- }
113+ // when one of the terminating keys is pressed, add current query to the tags
114+ if ( this . props . delimiters . indexOf ( e . key ) > - 1 ) {
115+ pressDelimiterKey . call ( this , e )
81116 }
82117
83118 // when backspace key is pressed and query is blank, delete the last tag
84- if ( e . key === KEYS . BACKSPACE && query . length === 0 && this . props . allowBackspace ) {
85- this . deleteTag ( this . props . tags . length - 1 )
119+ if ( e . key === KEYS . BACKSPACE && this . props . allowBackspace ) {
120+ pressBackspaceKey . call ( this , e )
86121 }
87122
88123 if ( e . key === KEYS . UP_ARROW ) {
89- e . preventDefault ( )
90-
91- // if last item, cycle to the bottom
92- if ( selected <= 0 ) {
93- this . setState ( { selected : this . suggestions . state . options . length - 1 } )
94- } else {
95- this . setState ( { selected : selected - 1 } )
96- }
124+ pressUpKey . call ( this , e )
97125 }
98126
99127 if ( e . key === KEYS . DOWN_ARROW ) {
100- e . preventDefault ( )
101-
102- this . setState ( { selected : ( selected + 1 ) % this . suggestions . state . options . length } )
128+ pressDownKey . call ( this , e )
103129 }
104130 }
105131
@@ -110,7 +136,7 @@ class ReactTags extends React.Component {
110136 }
111137
112138 onBlur ( ) {
113- this . setState ( { focused : false , selected : - 1 } )
139+ this . setState ( { focused : false , index : - 1 } )
114140
115141 if ( this . props . onBlur ) {
116142 this . props . onBlur ( )
@@ -135,7 +161,7 @@ class ReactTags extends React.Component {
135161 // reset the state
136162 this . setState ( {
137163 query : '' ,
138- selected : - 1
164+ index : - 1
139165 } )
140166 }
141167
@@ -183,9 +209,7 @@ class ReactTags extends React.Component {
183209 ref = { ( c ) => { this . suggestions = c } }
184210 listboxId = { listboxId }
185211 expanded = { expanded }
186- suggestions = { this . props . suggestions }
187- addTag = { this . addTag . bind ( this ) }
188- maxSuggestionsLength = { this . props . maxSuggestionsLength } />
212+ addTag = { this . addTag . bind ( this ) } />
189213 </ div >
190214 </ div >
191215 )
0 commit comments