11import { consume } from '@lit/context' ;
2- import { LitElement , html } from 'lit' ;
3- import { property , query , state } from 'lit/decorators.js' ;
2+ import { html , LitElement , nothing } from 'lit' ;
3+ import { query , state } from 'lit/decorators.js' ;
44import IgcIconButtonComponent from '../button/icon-button.js' ;
55import IgcChipComponent from '../chip/chip.js' ;
66import { chatContext } from '../common/context.js' ;
@@ -10,13 +10,9 @@ import IgcFileInputComponent from '../file-input/file-input.js';
1010import IgcIconComponent from '../icon/icon.js' ;
1111import { registerIconFromText } from '../icon/icon.registry.js' ;
1212import IgcTextareaComponent from '../textarea/textarea.js' ;
13- import type IgcChatComponent from './chat.js' ;
13+ import type { ChatState } from './chat-state .js' ;
1414import { styles } from './themes/input.base.css.js' ;
15- import {
16- type IgcMessageAttachment ,
17- attachmentIcon ,
18- sendButtonIcon ,
19- } from './types.js' ;
15+ import { attachmentIcon , sendButtonIcon } from './types.js' ;
2016
2117/**
2218 *
@@ -29,7 +25,7 @@ export default class IgcChatInputComponent extends LitElement {
2925 public static override styles = styles ;
3026
3127 @consume ( { context : chatContext , subscribe : true } )
32- private _chat ?: IgcChatComponent ;
28+ private _chatState ?: ChatState ;
3329
3430 /* blazorSuppress */
3531 public static register ( ) {
@@ -48,7 +44,7 @@ export default class IgcChatInputComponent extends LitElement {
4844
4945 @watch ( 'acceptedFiles' , { waitUntilFirstUpdate : true } )
5046 protected acceptedFilesChange ( ) : void {
51- this . updateAcceptedTypesCache ( ) ;
47+ this . _chatState ?. updateAcceptedTypesCache ( ) ;
5248 }
5349
5450 @state ( )
@@ -57,16 +53,6 @@ export default class IgcChatInputComponent extends LitElement {
5753 @state ( )
5854 private dragClass = '' ;
5955
60- @property ( { attribute : false } )
61- public attachments : IgcMessageAttachment [ ] = [ ] ;
62-
63- // Cache for accepted file types
64- private _acceptedTypesCache : {
65- extensions : Set < string > ;
66- mimeTypes : Set < string > ;
67- wildcardTypes : Set < string > ;
68- } | null = null ;
69-
7056 constructor ( ) {
7157 super ( ) ;
7258 registerIconFromText ( 'attachment' , attachmentIcon , 'material' ) ;
@@ -75,46 +61,40 @@ export default class IgcChatInputComponent extends LitElement {
7561
7662 protected override firstUpdated ( ) {
7763 this . setupDragAndDrop ( ) ;
78- this . updateAcceptedTypesCache ( ) ;
64+ this . _chatState ?. updateAcceptedTypesCache ( ) ;
7965 }
8066
8167 private handleInput ( e : Event ) {
8268 const target = e . target as HTMLTextAreaElement ;
8369 this . inputValue = target . value ;
70+ this . _chatState ?. handleInputChange ( this . inputValue ) ;
8471 this . adjustTextareaHeight ( ) ;
85- const inputEvent = new CustomEvent ( 'input-change' , {
86- detail : { value : this . inputValue } ,
87- } ) ;
88- this . dispatchEvent ( inputEvent ) ;
8972 }
9073
9174 private handleKeyDown ( e : KeyboardEvent ) {
9275 if ( e . key === 'Enter' && ! e . shiftKey ) {
9376 e . preventDefault ( ) ;
9477 this . sendMessage ( ) ;
9578 } else {
96- const typingEvent = new CustomEvent ( 'typing-change ', {
79+ this . _chatState ?. emitEvent ( 'igcTypingChange ', {
9780 detail : { isTyping : true } ,
9881 } ) ;
99- this . dispatchEvent ( typingEvent ) ;
82+
10083 // wait 3 seconds and dispatch a stop-typing event
10184 setTimeout ( ( ) => {
102- const stopTypingEvent = new CustomEvent ( 'typing-change ', {
85+ this . _chatState ?. emitEvent ( 'igcTypingChange ', {
10386 detail : { isTyping : false } ,
10487 } ) ;
105- this . dispatchEvent ( stopTypingEvent ) ;
10688 } , 3000 ) ;
10789 }
10890 }
10991
11092 private handleFocus ( ) {
111- const focusEvent = new CustomEvent ( 'focus-input' ) ;
112- this . dispatchEvent ( focusEvent ) ;
93+ this . _chatState ?. emitEvent ( 'igcInputFocus' ) ;
11394 }
11495
11596 private handleBlur ( ) {
116- const blurEvent = new CustomEvent ( 'blur-input' ) ;
117- this . dispatchEvent ( blurEvent ) ;
97+ this . _chatState ?. emitEvent ( 'igcInputBlur' ) ;
11898 }
11999
120100 private setupDragAndDrop ( ) {
@@ -137,13 +117,12 @@ export default class IgcChatInputComponent extends LitElement {
137117 ( item ) => item . kind === 'file'
138118 ) ;
139119 const hasValidFiles = files . some ( ( item ) =>
140- this . isFileTypeAccepted ( item . getAsFile ( ) as File , item . type )
120+ this . _chatState ?. isFileTypeAccepted ( item . getAsFile ( ) as File , item . type )
141121 ) ;
142122
143123 this . dragClass = hasValidFiles ? 'dragging' : '' ;
144124
145- const dragEvent = new CustomEvent ( 'drag-attachment' ) ;
146- this . dispatchEvent ( dragEvent ) ;
125+ this . _chatState ?. emitEvent ( 'igcAttachmentDrag' ) ;
147126 }
148127
149128 private handleDragOver ( e : DragEvent ) {
@@ -178,12 +157,14 @@ export default class IgcChatInputComponent extends LitElement {
178157 const files = Array . from ( e . dataTransfer ?. files || [ ] ) ;
179158 if ( files . length === 0 ) return ;
180159
181- const validFiles = files . filter ( ( file ) => this . isFileTypeAccepted ( file ) ) ;
160+ const validFiles = files . filter ( ( file ) =>
161+ this . _chatState ?. isFileTypeAccepted ( file )
162+ ) ;
182163
183- const dropEvent = new CustomEvent ( 'drop-attachment' ) ;
184- this . dispatchEvent ( dropEvent ) ;
164+ this . _chatState ?. emitEvent ( 'igcAttachmentDrop' ) ;
185165
186- this . attachFiles ( validFiles ) ;
166+ this . _chatState ?. attachFiles ( validFiles ) ;
167+ this . requestUpdate ( ) ;
187168 }
188169
189170 private adjustTextareaHeight ( ) {
@@ -196,13 +177,16 @@ export default class IgcChatInputComponent extends LitElement {
196177 }
197178
198179 private sendMessage ( ) {
199- if ( ! this . inputValue . trim ( ) && this . attachments . length === 0 ) return ;
180+ if (
181+ ! this . inputValue . trim ( ) &&
182+ this . _chatState ?. inputAttachments . length === 0
183+ )
184+ return ;
200185
201- const messageEvent = new CustomEvent ( 'message-created' , {
202- detail : { text : this . inputValue , attachments : this . attachments } ,
186+ this . _chatState ?. addMessage ( {
187+ text : this . inputValue ,
188+ attachments : this . _chatState ?. inputAttachments ,
203189 } ) ;
204-
205- this . dispatchEvent ( messageEvent ) ;
206190 this . inputValue = '' ;
207191
208192 if ( this . textInputElement ) {
@@ -219,92 +203,22 @@ export default class IgcChatInputComponent extends LitElement {
219203 if ( ! input . files || input . files . length === 0 ) return ;
220204
221205 const files = Array . from ( input . files ) ;
222- this . attachFiles ( files ) ;
223- }
224-
225- private attachFiles ( files : File [ ] ) {
226- const newAttachments : IgcMessageAttachment [ ] = [ ] ;
227- let count = this . attachments . length ;
228- files . forEach ( ( file ) => {
229- const isImage = file . type . startsWith ( 'image/' ) ;
230- newAttachments . push ( {
231- id : Date . now ( ) . toString ( ) + count ++ ,
232- // type: isImage ? 'image' : 'file',
233- url : URL . createObjectURL ( file ) ,
234- name : file . name ,
235- file : file ,
236- thumbnail : isImage ? URL . createObjectURL ( file ) : undefined ,
237- } ) ;
238- } ) ;
239-
240- const attachmentEvent = new CustomEvent ( 'attachment-change' , {
241- detail : [ ...this . attachments , ...newAttachments ] ,
242- } ) ;
243- this . dispatchEvent ( attachmentEvent ) ;
244- }
245-
246- private updateAcceptedTypesCache ( ) {
247- if ( ! this . _chat ?. options ?. acceptedFiles ) {
248- this . _acceptedTypesCache = null ;
249- return ;
250- }
251-
252- const types = this . _chat ?. options ?. acceptedFiles
253- . split ( ',' )
254- . map ( ( type ) => type . trim ( ) . toLowerCase ( ) ) ;
255- this . _acceptedTypesCache = {
256- extensions : new Set ( types . filter ( ( t ) => t . startsWith ( '.' ) ) ) ,
257- mimeTypes : new Set (
258- types . filter ( ( t ) => ! t . startsWith ( '.' ) && ! t . endsWith ( '/*' ) )
259- ) ,
260- wildcardTypes : new Set (
261- types . filter ( ( t ) => t . endsWith ( '/*' ) ) . map ( ( t ) => t . slice ( 0 , - 2 ) )
262- ) ,
263- } ;
264- }
265-
266- private isFileTypeAccepted ( file : File , type = '' ) : boolean {
267- if ( ! this . _acceptedTypesCache ) return true ;
268-
269- if ( file === null && type === '' ) return false ;
270-
271- const fileType =
272- file != null ? file . type . toLowerCase ( ) : type . toLowerCase ( ) ;
273- const fileExtension =
274- file != null
275- ? `.${ file . name . split ( '.' ) . pop ( ) ?. toLowerCase ( ) } `
276- : `.${ type . split ( '/' ) . pop ( ) ?. toLowerCase ( ) } ` ;
277-
278- // Check file extension
279- if ( this . _acceptedTypesCache . extensions . has ( fileExtension ) ) {
280- return true ;
281- }
282-
283- // Check exact MIME type
284- if ( this . _acceptedTypesCache . mimeTypes . has ( fileType ) ) {
285- return true ;
286- }
287-
288- // Check wildcard MIME types
289- const [ fileBaseType ] = fileType . split ( '/' ) ;
290- return this . _acceptedTypesCache . wildcardTypes . has ( fileBaseType ) ;
206+ this . _chatState ?. attachFiles ( files ) ;
207+ this . requestUpdate ( ) ;
291208 }
292209
293210 private removeAttachment ( index : number ) {
294- const attachmentEvent = new CustomEvent ( 'attachment-change' , {
295- detail : this . attachments . filter ( ( _ , i ) => i !== index ) ,
296- } ) ;
297-
298- this . dispatchEvent ( attachmentEvent ) ;
211+ this . _chatState ?. removeAttachment ( index ) ;
212+ this . requestUpdate ( ) ;
299213 }
300214
301215 private renderFileUploadArea ( ) {
302- return html ` ${ this . _chat ?. options ?. disableAttachments
303- ? ''
216+ return html `${ this . _chatState ?. options ?. disableAttachments
217+ ? nothing
304218 : html `
305219 < igc-file-input
306220 multiple
307- .accept =${ this . _chat ?. options ?. acceptedFiles }
221+ .accept =${ this . _chatState ?. options ?. acceptedFiles }
308222 @igcChange =${ this . handleFileUpload }
309223 >
310224 < igc-icon
@@ -317,21 +231,22 @@ export default class IgcChatInputComponent extends LitElement {
317231 }
318232
319233 private renderActionsArea ( ) {
320- return html ` < div class ="buttons-container ">
234+ return html `< div class ="buttons-container ">
321235 < igc-icon-button
322236 name ="send-message "
323237 collection ="material "
324238 variant ="contained "
325239 class ="small "
326- ?disabled =${ ! this . inputValue . trim ( ) && this . attachments . length === 0 }
240+ ?disabled =${ ! this . inputValue . trim ( ) &&
241+ this . _chatState ?. inputAttachments . length === 0 }
327242 @click =${ this . sendMessage }
328243 > </ igc-icon-button >
329244 </ div > ` ;
330245 }
331246
332247 private renderAttachmentsArea ( ) {
333- return html ` < div >
334- ${ this . attachments ?. map (
248+ return html `< div >
249+ ${ this . _chatState ?. inputAttachments ?. map (
335250 ( attachment , index ) => html `
336251 < div class ="attachment-wrapper ">
337252 < igc-chip
0 commit comments