@@ -40,50 +40,94 @@ export function ChipInput(props: ChipInputProps) {
4040 'disabled' ,
4141 ] ) ;
4242
43- const [ inputValue , setInputValue ] = createSignal ( '' ) ;
43+ const [ inputRef , setInputRef ] = createSignal < HTMLInputElement > ( ) ;
4444
4545 function onKeyDown ( event : KeyboardEvent ) {
46- if ( event . key === 'Enter' && inputValue ( ) !== '' ) {
46+ // Get value directly from input element.
47+ const inputValue = inputRef ( ) ?. value ;
48+
49+ // Add a chip when enter or comma is pressed.
50+ if ( event . key === 'Enter' || event . key === ',' ) {
4751 event . preventDefault ( ) ;
48- addChip ( inputValue ( ) ) ;
49- setInputValue ( '' ) ;
52+
53+ const trimmed = inputValue ?. trim ( ) ;
54+
55+ if ( trimmed ) {
56+ addChip ( trimmed ) ;
57+ inputRef ( ) ! . value = '' ;
58+ return ;
59+ }
60+ }
61+
62+ // Remove the last chip when backspace is pressed.
63+ if ( event . key === 'Backspace' && ! inputValue && props . value ?. length ) {
64+ removeChip ( props . value [ props . value . length - 1 ] ! ) ;
5065 }
5166 }
5267
5368 function addChip ( chip : string ) {
54- props . onChange ?.( [ ...( props . value ?? [ ] ) , chip ] ) ;
69+ // Prevent adding duplicate chips.
70+ if ( ! props . value ?. includes ( chip ) ) {
71+ props . onChange ?.( [ ...( props . value ?? [ ] ) , chip ] ) ;
72+ }
5573 }
5674
5775 function removeChip ( chip : string ) {
5876 props . onChange ?.( props . value ?. filter ( t => t !== chip ) ?? [ ] ) ;
5977 }
6078
79+ // Handle paste to add multiple chips at once.
80+ function onPaste ( event : ClipboardEvent ) {
81+ event . preventDefault ( ) ;
82+
83+ const pasteData = event . clipboardData ?. getData ( 'text' ) ;
84+ const pastedChips = ( pasteData ?. split ( / , | \n / ) ?? [ ] )
85+ . map ( item => item . trim ( ) )
86+ . filter ( Boolean ) ;
87+
88+ const newChips = [ ...( props . value ?? [ ] ) ] ;
89+ pastedChips . forEach ( chip => {
90+ if ( ! newChips . includes ( chip ) ) {
91+ newChips . push ( chip ) ;
92+ }
93+ } ) ;
94+
95+ props . onChange ?.( newChips ) ;
96+ }
97+
6198 return (
62- < div { ...rest } >
63- < TextInput
64- disabled = { props . disabled }
65- onBlur = { ( ) => props . onBlur ?.( ) }
66- onKeyDown = { onKeyDown }
67- onChange = { setInputValue }
68- placeholder = { props . placeholder }
69- value = { inputValue ( ) }
70- />
71-
72- < Show when = { props . value ?. length } >
73- < div class = "flex flex-wrap gap-2 mt-2" >
99+ < div { ...rest } class = "w-full" >
100+ < div
101+ onClick = { ( ) => inputRef ( ) ?. focus ( ) }
102+ class = "flex flex-wrap items-center gap-2 p-2 border border-gray-300 rounded-md focus-within:ring-2 focus-within:ring-primary-500 focus-within:border-primary-500 min-h-12"
103+ >
104+ < Show when = { props . value ?. length } >
74105 { props . value ?. map ( chip => (
75- < Badge variant = "secondary" >
106+ < Badge
107+ variant = "secondary"
108+ class = "flex items-center gap-1 px-2 py-1"
109+ >
76110 { chip }
77111 { ! props . disabled && (
78112 < IconX
79113 onClick = { ( ) => removeChip ( chip ) }
80- class = "ml-1 h-3 w-3 cursor-pointer"
114+ class = "h-3 w-3 cursor-pointer"
81115 />
82116 ) }
83117 </ Badge >
84118 ) ) }
85- </ div >
86- </ Show >
119+ </ Show >
120+
121+ < TextInput
122+ ref = { setInputRef }
123+ disabled = { props . disabled }
124+ onBlur = { ( ) => props . onBlur ?.( ) }
125+ onKeyDown = { onKeyDown }
126+ onPaste = { onPaste }
127+ placeholder = { props . value ?. length ? '' : props . placeholder }
128+ class = "flex-grow !border-0 !ring-0 !shadow-none !p-0 min-w-20"
129+ />
130+ </ div >
87131 </ div >
88132 ) ;
89133}
0 commit comments