11import { consume } from '@lit/context' ;
22import { html , LitElement , nothing } from 'lit' ;
33import { query , state } from 'lit/decorators.js' ;
4+ import { cache } from 'lit/directives/cache.js' ;
45import { ifDefined } from 'lit/directives/if-defined.js' ;
5- import { createRef , ref } from 'lit/directives/ref.js' ;
66import { until } from 'lit/directives/until.js' ;
77import { addThemingController } from '../../theming/theming-controller.js' ;
88import IgcIconButtonComponent from '../button/icon-button.js' ;
99import IgcChipComponent from '../chip/chip.js' ;
1010import { chatContext , chatUserInputContext } from '../common/context.js' ;
11- import { addKeybindings } from '../common/controllers/key-bindings.js' ;
12- import { watch } from '../common/decorators/watch.js' ;
11+ import { enterKey } from '../common/controllers/key-bindings.js' ;
1312import { registerComponent } from '../common/definitions/register.js' ;
1413import { partMap } from '../common/part-map.js' ;
15- import { isEmpty } from '../common/util.js' ;
14+ import { bindIf , hasFiles , isEmpty } from '../common/util.js' ;
1615import IgcIconComponent from '../icon/icon.js' ;
1716import IgcTextareaComponent from '../textarea/textarea.js' ;
1817import type { ChatState } from './chat-state.js' ;
@@ -60,7 +59,6 @@ type DefaultInputRenderers = {
6059 */
6160export default class IgcChatInputComponent extends LitElement {
6261 public static readonly tagName = 'igc-chat-input' ;
63-
6462 public static override styles = [ styles , shared ] ;
6563
6664 /* blazorSuppress */
@@ -75,36 +73,29 @@ export default class IgcChatInputComponent extends LitElement {
7573 }
7674
7775 @consume ( { context : chatContext , subscribe : true } )
78- private readonly _chatState ! : ChatState ;
76+ private readonly _state ! : ChatState ;
7977
8078 @consume ( { context : chatUserInputContext , subscribe : true } )
8179 private readonly _userInputState ! : ChatState ;
8280
83- private get _acceptedTypes ( ) {
84- return this . _chatState . acceptedFileTypes ;
85- }
86-
8781 @query ( IgcTextareaComponent . tagName )
8882 private readonly _textInputElement ! : IgcTextareaComponent ;
8983
9084 @query ( '#input_attachments' )
91- protected readonly _inputAttachmentsButton ! : IgcIconButtonComponent ;
92-
93- private readonly _attachmentsButtonInputRef = createRef < HTMLInputElement > ( ) ;
94-
95- @watch ( 'acceptedFiles' , { waitUntilFirstUpdate : true } )
96- protected acceptedFilesChange ( ) : void {
97- this . _chatState . updateAcceptedTypesCache ( ) ;
98- }
85+ protected readonly _fileInput ! : HTMLInputElement ;
9986
10087 @state ( )
10188 private _parts = { 'input-container' : true , dragging : false } ;
10289
90+ private get _acceptedTypes ( ) {
91+ return this . _state . acceptedFileTypes ;
92+ }
93+
10394 private readonly _defaults : Readonly < DefaultInputRenderers > = Object . freeze ( {
10495 input : ( ) => this . _renderTextArea ( ) ,
10596 inputActions : ( ) => this . renderActionsArea ( ) ,
10697 inputAttachments : ( ctx ) => this . renderAttachmentsArea ( ctx . param ) ,
107- fileUploadButton : ( ) => this . renderFileUploadButton ( ) ,
98+ fileUploadButton : ( ) => this . _renderFileUploadButton ( ) ,
10899 sendButton : ( ) => this . _renderSendButton ( ) ,
109100 } ) ;
110101
@@ -114,34 +105,36 @@ export default class IgcChatInputComponent extends LitElement {
114105 }
115106
116107 protected override firstUpdated ( ) {
117- this . _chatState . updateAcceptedTypesCache ( ) ;
118- this . _chatState . textArea = this . _textInputElement ;
119-
120- // Use keybindings controller to capture all key events
121- // Custom skip function that never skips - this captures ALL key events
122- const keybindings = addKeybindings ( this , {
123- skip : ( ) => false , // Never skip any key events
124- ref : this . _chatState . textAreaRef ,
125- } ) ;
126-
127- // Override the controller's handleEvent to capture all keys
128- // This is a more direct approach that doesn't require listing specific keys
129- keybindings . handleEvent = ( event : KeyboardEvent ) => {
130- // Call our handler for every key event
131- this . _chatState . handleKeyDown ( event ) ;
132- } ;
108+ this . _state . updateAcceptedTypesCache ( ) ;
109+ this . _state . textArea = this . _textInputElement ;
133110 }
134111
135112 private _getRenderer (
136113 name : keyof DefaultInputRenderers
137114 ) : ChatTemplateRenderer < any > {
138- return this . _chatState ? .options ?. renderers
139- ? ( this . _chatState . options . renderers [ name ] ?? this . _defaults [ name ] )
115+ return this . _state . options ?. renderers
116+ ? ( this . _state . options . renderers [ name ] ?? this . _defaults [ name ] )
140117 : this . _defaults [ name ] ;
141118 }
142119
120+ private _handleKeydown ( event : KeyboardEvent ) : void {
121+ const isSend = event . key === enterKey && ! event . shiftKey ;
122+
123+ if ( isSend ) {
124+ event . preventDefault ( ) ;
125+ this . _state . sendMessage ( ) ;
126+ } else {
127+ // TODO:
128+ this . _state . handleKeyDown ( event ) ;
129+ }
130+ }
131+
132+ private _handleFileInputClick ( ) : void {
133+ this . _fileInput . showPicker ( ) ;
134+ }
135+
143136 private _handleFocusState ( event : FocusEvent ) : void {
144- this . _chatState . emitEvent (
137+ this . _state . emitEvent (
145138 event . type === 'focus' ? 'igcInputFocus' : 'igcInputBlur'
146139 ) ;
147140 }
@@ -152,7 +145,7 @@ export default class IgcChatInputComponent extends LitElement {
152145
153146 const validFiles = getChatAcceptedFiles ( event , this . _acceptedTypes ) ;
154147 this . _parts = { 'input-container' : true , dragging : ! isEmpty ( validFiles ) } ;
155- this . _chatState . emitEvent ( 'igcAttachmentDrag' ) ;
148+ this . _state . emitEvent ( 'igcAttachmentDrag' ) ;
156149 }
157150
158151 private _handleDragOver ( event : DragEvent ) : void {
@@ -185,8 +178,8 @@ export default class IgcChatInputComponent extends LitElement {
185178 this . _parts = { 'input-container' : true , dragging : false } ;
186179
187180 const validFiles = getChatAcceptedFiles ( event , this . _acceptedTypes ) ;
188- this . _chatState . emitEvent ( 'igcAttachmentDrop' ) ;
189- this . _chatState . attachFiles ( validFiles ) ;
181+ this . _state . emitEvent ( 'igcAttachmentDrop' ) ;
182+ this . _state . attachFiles ( validFiles ) ;
190183 this . requestUpdate ( ) ;
191184 }
192185
@@ -196,20 +189,18 @@ export default class IgcChatInputComponent extends LitElement {
196189 * @param e Input event from the text area
197190 */
198191 private _handleInput ( { detail } : CustomEvent < string > ) : void {
199- if ( detail === this . _chatState . inputValue ) return ;
192+ if ( detail === this . _state . inputValue ) return ;
200193
201- this . _chatState . inputValue = detail ;
202- this . _chatState . emitEvent ( 'igcInputChange' , { detail : { value : detail } } ) ;
194+ this . _state . inputValue = detail ;
195+ this . _state . emitEvent ( 'igcInputChange' , { detail : { value : detail } } ) ;
203196 }
204197
205198 private _handleFileUpload ( event : Event ) : void {
206199 const input = event . target as HTMLInputElement ;
207200
208- if ( ! input . files || input . files . length === 0 ) {
209- return ;
201+ if ( hasFiles ( input ) ) {
202+ this . _state . attachFiles ( Array . from ( input . files ! ) ) ;
210203 }
211-
212- this . _chatState . attachFiles ( Array . from ( input . files ) ) ;
213204 }
214205 /**
215206 * Default attachments area template used when no custom template is provided.
@@ -222,7 +213,7 @@ export default class IgcChatInputComponent extends LitElement {
222213 < div part ="attachment-wrapper " role ="listitem ">
223214 < igc-chip
224215 removable
225- @igcRemove =${ ( ) => this . _chatState ? .removeAttachment ( index ) }
216+ @igcRemove =${ ( ) => this . _state . removeAttachment ( index ) }
226217 >
227218 < igc-icon
228219 slot ="prefix "
@@ -244,11 +235,12 @@ export default class IgcChatInputComponent extends LitElement {
244235 return html `
245236 < igc-textarea
246237 part ="text-input "
247- placeholder =${ ifDefined ( this . _chatState ? .options ?. inputPlaceholder ) }
238+ placeholder =${ ifDefined ( this . _state . options ?. inputPlaceholder ) }
248239 resize ="auto"
249240 rows="1"
250241 .value=${ this . _userInputState ?. inputValue }
251242 @igcInput=${ this . _handleInput }
243+ @keydown=${ this . _handleKeydown }
252244 @focus=${ this . _handleFocusState }
253245 @blur=${ this . _handleFocusState }
254246 > </ igc-textarea >
@@ -260,26 +252,31 @@ export default class IgcChatInputComponent extends LitElement {
260252 * Renders a file input for attaching files.
261253 * @returns TemplateResult containing the file upload button
262254 */
263- private renderFileUploadButton ( ) {
264- if ( this . _chatState ?. options ?. disableInputAttachments ) return nothing ;
265- return html `
266- < label for ="input_attachments " part ="upload-button ">
267- < igc-icon-button
268- variant ="flat "
269- name ="attach_file "
270- @click =${ ( ) => this . _attachmentsButtonInputRef ?. value ?. click ( ) }
271- > </ igc-icon-button >
272- < input
273- type ="file "
274- id ="input_attachments "
275- name ="input_attachments "
276- ${ ref ( this . _attachmentsButtonInputRef ) }
277- multiple
278- accept =${ ifDefined ( this . _chatState ?. options ?. acceptedFiles === '' ? undefined : this . _chatState ?. options ?. acceptedFiles ) }
279- @change =${ this . _handleFileUpload } >
280- </ input >
281- </ label >
282- ` ;
255+ private _renderFileUploadButton ( ) {
256+ const accepted = this . _state . options ?. acceptedFiles ;
257+ const attachmentsDisabled = this . _state . options ?. disableInputAttachments ;
258+
259+ return html `${ cache (
260+ attachmentsDisabled
261+ ? nothing
262+ : html `
263+ < label for ="input_attachments " part ="upload-button ">
264+ < igc-icon-button
265+ variant ="flat "
266+ name ="attach_file "
267+ @click =${ this . _handleFileInputClick }
268+ > </ igc-icon-button >
269+ < input
270+ type ="file "
271+ id ="input_attachments "
272+ name ="input_attachments "
273+ multiple
274+ accept =${ bindIf ( accepted , accepted ) }
275+ @change =${ this . _handleFileUpload }
276+ />
277+ </ label >
278+ `
279+ ) } `;
283280 }
284281
285282 /**
@@ -288,15 +285,17 @@ export default class IgcChatInputComponent extends LitElement {
288285 * @returns TemplateResult containing the send button
289286 */
290287 private _renderSendButton ( ) {
288+ const enabled =
289+ this . _state . hasInputValue || this . _state . hasInputAttachments ;
290+
291291 return html `
292292 < igc-icon-button
293293 aria-label ="Send message "
294294 name ="send_message "
295295 variant ="contained "
296296 part ="send-button "
297- ?disabled =${ ! this . _chatState ?. inputValue . trim ( ) &&
298- this . _chatState ?. inputAttachments . length === 0 }
299- @click =${ this . _chatState ?. sendMessage }
297+ ?disabled =${ ! enabled }
298+ @click =${ this . _state . sendMessage }
300299 > </ igc-icon-button >
301300 ` ;
302301 }
@@ -305,19 +304,19 @@ export default class IgcChatInputComponent extends LitElement {
305304 return html ` ${ this . _getRenderer ( 'fileUploadButton' ) ( {
306305 param : undefined ,
307306 defaults : this . _defaults ,
308- options : this . _chatState . options ,
307+ options : this . _state . options ,
309308 } ) }
310309 ${ this . _getRenderer ( 'sendButton' ) ( {
311310 param : undefined ,
312311 defaults : this . _defaults ,
313- options : this . _chatState . options ,
312+ options : this . _state . options ,
314313 } ) } `;
315314 }
316315
317316 protected override render ( ) {
318317 const partialCtx = {
319318 defaults : this . _defaults ,
320- options : this . _chatState . options ,
319+ options : this . _state . options ,
321320 } ;
322321
323322 return html `
@@ -328,13 +327,13 @@ export default class IgcChatInputComponent extends LitElement {
328327 @dragleave=${ this . _handleDragLeave }
329328 @drop=${ this . _handleDrop }
330329 >
331- ${ this . _chatState . inputAttachments &&
332- this . _chatState . inputAttachments . length > 0
330+ ${ this . _state . inputAttachments &&
331+ this . _state . inputAttachments . length > 0
333332 ? html ` < div part ="attachments " role ="list " aria-label ="Attachments ">
334333 ${ until (
335334 this . _getRenderer ( 'inputAttachments' ) ( {
336335 ...partialCtx ,
337- param : this . _chatState . inputAttachments ,
336+ param : this . _state . inputAttachments ,
338337 } )
339338 ) }
340339 </ div > `
@@ -343,7 +342,7 @@ export default class IgcChatInputComponent extends LitElement {
343342 ${ until (
344343 this . _getRenderer ( 'input' ) ( {
345344 ...partialCtx ,
346- param : this . _chatState . inputValue ,
345+ param : this . _state . inputValue ,
347346 } )
348347 ) }
349348 </ div >
0 commit comments