@@ -3,6 +3,8 @@
let {
items ,
selected = $bindable < string [] >([]),
value = $bindable ( '' ),
mode = 'multiple' ,
excludedIds = [],
fieldName ,
emptyLabel = 'NO MATCHING RECORDS' ,
@@ -13,6 +15,8 @@
} : {
items : Item [];
selected? : string [];
value? : string ;
mode ?: 'multiple' | 'single' ;
excludedIds? : string [];
fieldName : string ;
emptyLabel? : string ;
@@ -22,22 +26,24 @@
emptySelectionLabel? : string ;
} = $props ();
let query = $state ( '' );
const selectedIds = $derived ( mode === 'single' ? ( value ? [ value ] : []) : selected );
const normalizedQuery = $derived ( query . normalize ( 'NFKC' ). trim (). toLocaleLowerCase ());
const available = $derived ( items . filter (( item ) =>
! excludedIds . includes ( item . id )
&& ( ! normalizedQuery || item . name . toLocaleLowerCase (). includes ( normalizedQuery ))
));
const visible = $derived ( available . slice ( 0 , 30 ));
const selectedItems = $derived ( selected . map (( id ) => items . find (( item ) => item . id === id )). filter (( item ) : item is Item => Boolean ( item )));
const selectedItems = $derived ( selectedIds . map (( id ) => items . find (( item ) => item . id === id )). filter (( item ) : item is Item => Boolean ( item )));
const toggle = ( id : string ) => {
selected = selected . includes ( id ) ? selected . filter (( value ) => value ! == id ) : [... selected , id ] ;
if ( mode === 'single' ) value = value = == id ? '' : id ;
else selected = selected . includes ( id ) ? selected . filter (( value ) => value !== id ) : [... selected , id ];
};
</ script >
< div class = "assignment-picker" >
{ #each selected as id } < input type = "hidden" name = { fieldName } value= { id } / > { /each }
{ #each selectedIds as id } < input type = "hidden" name = { fieldName } value= { id } / > { /each }
< div class = "assignment-selected" >
< div class = "assignment-summary" >< span > ASSIGNED</ span >< b > { selected . length } </ b ></ div >
< div class = "assignment-summary" >< span > { mode === 'single' ? 'SELECTED' : ' ASSIGNED' } </ span >< b > { selectedIds . length } </ b ></ div >
< div class = "assignment-chips" >
{ #each selectedItems as item }
< button type = "button" class:pending-reference = { item . pending } onclick= {() => toggle ( item . id )} aria-label = { `Remove ${ item . name } ` } > <span > { item . name }{ #if item . pending } < small > PENDING</ small > { /if } </ span >< b > × </ b ></ button >
@@ -50,8 +56,8 @@
< div class = "assignment-results-head" >< span > { available . length } MATCHES</ span > { #if available . length > visible . length } < b > SHOWING FIRST { visible . length } </ b > { /if } </ div >
< div class = "assignment-results" role = "list" >
{ #each visible as item }
< button type = "button" class:selected = { selected . includes ( item . id )} class:pending-reference= { item . pending } aria-pressed = { selected . includes ( item . id )} onclick= {() => toggle ( item . id )} >
< span > { item . name }{ #if item . pending } < small > PENDING · { item . proposerName ?? 'CONTRIBUTOR' } </ small > { /if } </ span >< b > { selected . includes ( item . id ) ? 'ASSIGNED ' : 'ADD + ' } </ b >
< button type = "button" class:selected = { selectedIds . includes ( item . id )} class:pending-reference= { item . pending } aria-pressed = { selectedIds . includes ( item . id )} onclick= {() => toggle ( item . id )} >
< span > { item . name }{ #if item . pending } < small > PENDING · { item . proposerName ?? 'CONTRIBUTOR' } </ small > { /if } </ span >< b > { selectedIds . includes ( item . id ) ? ( mode === 'single' ? 'SELECTED' : 'ASSIGNED' ) : ( mode === 'single' ? 'SELECT → ' : 'ADD + ' ) }</ b >
</ button >
{ : else }
< p > { emptyLabel } </ p >