@@ -107,8 +107,12 @@ export class TPMultiSelectElement extends HTMLElement {
107107 const value : string [ ] = [ ] ;
108108
109109 const selectedOptions : NodeListOf < HTMLOptionElement > | null = this . querySelectorAll ( 'select option[selected]' ) ;
110- selectedOptions ?. forEach ( ( option : HTMLOptionElement ) => value . push ( option . value ) ) ;
111-
110+ selectedOptions ?. forEach ( ( option : HTMLOptionElement ) => {
111+ const optionValue = option . getAttribute ( 'value' ) ;
112+ if ( optionValue ) {
113+ value . push ( optionValue ) ;
114+ }
115+ } ) ;
112116 return value ;
113117 }
114118
@@ -118,38 +122,37 @@ export class TPMultiSelectElement extends HTMLElement {
118122 protected updateFormFieldValue ( ) : void {
119123 // Get options.
120124 const styledSelectedOptions : NodeListOf < TPMultiSelectOptionElement > | null = this . querySelectorAll ( `tp-multi-select-option` ) ;
121- const selectFieldOptions : NodeListOf < HTMLOptionElement > | null = this . querySelectorAll ( 'select option ' ) ;
125+ const selectField : HTMLSelectElement | null = this . querySelector ( 'select' ) ;
122126
123- if ( ! styledSelectedOptions || ! selectFieldOptions ) {
127+ if ( ! styledSelectedOptions || ! selectField ) {
124128 return ;
125129 }
126130
131+ const selectOptions : HTMLOptionElement [ ] = Array . from ( selectField . options ) ;
132+
127133 // Traverse options.
128134 styledSelectedOptions . forEach ( ( option : TPMultiSelectOptionElement ) : void => {
129- // Get matching select field options.
130- const matchingSelectOptions : HTMLOptionElement [ ] = [ ...selectFieldOptions ] . filter ( ( selectOption : HTMLOptionElement ) : boolean =>
131- selectOption . getAttribute ( 'value' ) === option . getAttribute ( 'value' ) ) ;
132-
133- if ( 0 === matchingSelectOptions . length ) {
134- return ;
135- }
136-
137- // Check whether to mark them as selected or not.
138- if ( 'yes' === option . getAttribute ( 'selected' ) ) {
139- matchingSelectOptions . forEach ( ( matchingSelectOption : HTMLOptionElement ) : void => {
140- matchingSelectOption . selected = true ;
141- matchingSelectOption . setAttribute ( 'selected' , 'selected' ) ;
142- } ) ;
143- } else {
144- matchingSelectOptions . forEach ( ( matchingSelectOption : HTMLOptionElement ) : void => {
145- matchingSelectOption . selected = false ;
146- matchingSelectOption . removeAttribute ( 'selected' ) ;
147- } ) ;
135+ const optionValue = option . getAttribute ( 'value' ) ?? '' ;
136+ if ( optionValue ) {
137+ const matchingSelectOption : HTMLOptionElement | undefined = selectOptions . find ( ( selectOption ) => selectOption . value === optionValue ) ;
138+
139+ if ( 'yes' === option . getAttribute ( 'selected' ) ) {
140+ if ( matchingSelectOption ) {
141+ matchingSelectOption . setAttribute ( 'selected' , 'selected' ) ;
142+ } else {
143+ const newOption : HTMLOptionElement = document . createElement ( 'option' ) ;
144+ newOption . setAttribute ( 'value' , option . getAttribute ( 'value' ) ?? '' ) ;
145+ newOption . setAttribute ( 'selected' , 'selected' ) ;
146+ selectField ?. append ( newOption ) ;
147+ }
148+ } else {
149+ matchingSelectOption ?. remove ( ) ;
150+ }
148151 }
149152 } ) ;
150153
151154 // Dispatch events.
152- this . querySelector ( 'select' ) ? .dispatchEvent ( new Event ( 'change' ) ) ;
155+ selectField . dispatchEvent ( new Event ( 'change' ) ) ;
153156 }
154157
155158 /**
@@ -195,12 +198,6 @@ export class TPMultiSelectElement extends HTMLElement {
195198 * Initialize component.
196199 */
197200 initialize ( ) : void {
198- // Get options.
199- const options : NodeListOf < HTMLOptionElement > | null = this . querySelectorAll ( 'tp-multi-select-option' ) ;
200- if ( ! options ) {
201- return ;
202- }
203-
204201 // Create select element (if it doesn't already exist).
205202 let selectElement : HTMLSelectElement | null = this . querySelector ( 'select' ) ;
206203 if ( ! selectElement ) {
@@ -216,15 +213,8 @@ export class TPMultiSelectElement extends HTMLElement {
216213 selectElement . innerHTML = '' ;
217214 }
218215
219- // Append new options.
220- options . forEach ( ( option : HTMLOptionElement ) : void => {
221- const newOption : HTMLOptionElement = document . createElement ( 'option' ) ;
222- newOption . setAttribute ( 'value' , option . getAttribute ( 'value' ) ?? '' ) ;
223- if ( 'yes' === option . getAttribute ( 'selected' ) ) {
224- newOption . setAttribute ( 'selected' , 'selected' ) ;
225- }
226- selectElement ?. append ( newOption ) ;
227- } ) ;
216+ // Update components for selected options.
217+ this . update ( ) ;
228218 }
229219
230220 /**
@@ -264,10 +254,7 @@ export class TPMultiSelectElement extends HTMLElement {
264254 if ( 'yes' === this . getAttribute ( 'close-on-select' ) ) {
265255 this . removeAttribute ( 'open' ) ;
266256 }
267-
268- // Trigger events.
269- this . dispatchEvent ( new CustomEvent ( 'select' , { bubbles : true } ) ) ;
270- this . dispatchEvent ( new CustomEvent ( 'change' , { bubbles : true } ) ) ;
257+ this . update ( ) ;
271258 }
272259
273260 /**
@@ -280,9 +267,7 @@ export class TPMultiSelectElement extends HTMLElement {
280267 option . setAttribute ( 'selected' , 'yes' ) ;
281268 }
282269 } ) ;
283-
284- this . dispatchEvent ( new CustomEvent ( 'select-all' , { bubbles : true } ) ) ;
285- this . dispatchEvent ( new CustomEvent ( 'change' , { bubbles : true } ) ) ;
270+ this . update ( ) ;
286271 }
287272
288273 /**
@@ -295,9 +280,7 @@ export class TPMultiSelectElement extends HTMLElement {
295280 styledSelectedOptions ?. forEach ( ( option : TPMultiSelectOptionElement ) : void => {
296281 option . removeAttribute ( 'selected' ) ;
297282 } ) ;
298-
299- this . dispatchEvent ( new CustomEvent ( 'unselect' , { bubbles : true } ) ) ;
300- this . dispatchEvent ( new CustomEvent ( 'change' , { bubbles : true } ) ) ;
283+ this . update ( ) ;
301284 }
302285
303286 /**
@@ -308,9 +291,7 @@ export class TPMultiSelectElement extends HTMLElement {
308291 styledSelectedOptions ?. forEach ( ( option : TPMultiSelectOptionElement ) : void => {
309292 option . removeAttribute ( 'selected' ) ;
310293 } ) ;
311-
312- this . dispatchEvent ( new CustomEvent ( 'unselect-all' , { bubbles : true } ) ) ;
313- this . dispatchEvent ( new CustomEvent ( 'change' , { bubbles : true } ) ) ;
294+ this . update ( ) ;
314295 }
315296
316297 /**
@@ -321,9 +302,11 @@ export class TPMultiSelectElement extends HTMLElement {
321302 handleKeyboardInputs ( e : KeyboardEvent ) : void {
322303 switch ( e . key ) {
323304 case 'ArrowDown' :
305+ e . preventDefault ( ) ;
324306 this . highlightNextOption ( ) ;
325307 break ;
326308 case 'ArrowUp' :
309+ e . preventDefault ( ) ;
327310 this . highlightPreviousOption ( ) ;
328311 break ;
329312 case 'Enter' :
@@ -347,21 +330,32 @@ export class TPMultiSelectElement extends HTMLElement {
347330 return ;
348331 }
349332
350- // Highlight next option.
351- if ( this . currentlyHighlightedOption === options . length - 1 ) {
333+ // Find the next option to be highlighted. Assume next option is the favorable option.
334+ let nextToBeHighlighted = this . currentlyHighlightedOption + 1 ;
335+
336+ // Keep iterating to skip over disabled options until we find a suitable option.
337+ while ( nextToBeHighlighted < options . length && options [ nextToBeHighlighted ] . getAttribute ( 'disabled' ) === 'yes' ) {
338+ nextToBeHighlighted ++ ;
339+ }
340+
341+ // If there are no more options to highlight, exit. Here, the last highlighted option keeps highlighted.
342+ if ( nextToBeHighlighted === options . length ) {
352343 return ;
353344 }
354345
355- this . currentlyHighlightedOption ++ ;
346+ // Remove highlight from the current option, if any.
347+ if ( this . currentlyHighlightedOption !== - 1 ) {
348+ options [ this . currentlyHighlightedOption ] . removeAttribute ( 'highlighted' ) ;
349+ }
356350
357- // Set option attributes based on highlight .
358- options . forEach ( ( option : TPMultiSelectOptionElement , index : number ) : void => {
359- if ( this . currentlyHighlightedOption === index ) {
360- option . setAttribute ( ' highlighted' , 'yes' ) ;
361- } else {
362- option . removeAttribute ( 'highlighted' ) ;
363- }
364- } ) ;
351+ // Highlight the found option .
352+ options [ nextToBeHighlighted ] . setAttribute ( 'highlighted' , 'yes' ) ;
353+
354+ // Scroll the highlighted option into view with smooth behavior.
355+ options [ nextToBeHighlighted ] . scrollIntoView ( { behavior : 'smooth' , block : 'nearest' } ) ;
356+
357+ // Update the currentlyHighlightedOption for the next iteration.
358+ this . currentlyHighlightedOption = nextToBeHighlighted ;
365359 }
366360
367361 /**
@@ -375,21 +369,32 @@ export class TPMultiSelectElement extends HTMLElement {
375369 return ;
376370 }
377371
378- // Highlight next option.
379- if ( this . currentlyHighlightedOption === 0 ) {
372+ // Find the previous option to be highlighted. Assume previous option is the favorable option.
373+ let previousToBeHighlighted = this . currentlyHighlightedOption - 1 ;
374+
375+ // Keep iterating to skip over disabled options until we find a suitable option.
376+ while ( previousToBeHighlighted >= 0 && options [ previousToBeHighlighted ] . getAttribute ( 'disabled' ) === 'yes' ) {
377+ previousToBeHighlighted -- ;
378+ }
379+
380+ // If there are no more options to highlight, exit.
381+ if ( previousToBeHighlighted < 0 ) {
380382 return ;
381383 }
382384
383- this . currentlyHighlightedOption -- ;
385+ // Remove highlight from the current option, if any.
386+ if ( this . currentlyHighlightedOption !== 0 ) {
387+ options [ this . currentlyHighlightedOption ] . removeAttribute ( 'highlighted' ) ;
388+ }
384389
385- // Set option attributes based on highlight .
386- options . forEach ( ( option : TPMultiSelectOptionElement , index : number ) : void => {
387- if ( this . currentlyHighlightedOption === index ) {
388- option . setAttribute ( ' highlighted' , 'yes' ) ;
389- } else {
390- option . removeAttribute ( 'highlighted' ) ;
391- }
392- } ) ;
390+ // Highlight the found option .
391+ options [ previousToBeHighlighted ] . setAttribute ( 'highlighted' , 'yes' ) ;
392+
393+ // Scroll the highlighted option into view with smooth behavior.
394+ options [ previousToBeHighlighted ] . scrollIntoView ( { behavior : 'smooth' , block : 'nearest' } ) ;
395+
396+ // Update the currentlyHighlightedOption for the next iteration.
397+ this . currentlyHighlightedOption = previousToBeHighlighted ;
393398 }
394399
395400 /**
0 commit comments