2024-10-15 20:06:22 +00:00
import "tom-select/dist/css/tom-select.css" ;
2024-10-16 12:59:02 +00:00
import { InheritedComponent } from "#core:utils/web-components" ;
2024-10-15 20:06:22 +00:00
import TomSelect from "tom-select" ;
2024-10-16 00:22:27 +00:00
import type { TomItem , TomLoadCallback , TomOption } from "tom-select/dist/types/types" ;
2024-10-15 20:06:22 +00:00
import type { escape_html } from "tom-select/dist/types/utils" ;
import { type UserProfileSchema , userSearchUsers } from "#openapi" ;
2024-10-16 12:59:02 +00:00
export class AjaxSelect extends InheritedComponent < "select" > {
2024-10-15 20:06:22 +00:00
widget : TomSelect ;
filter ? : < T > ( items : T [ ] ) = > T [ ] ;
constructor ( ) {
2024-10-16 12:59:02 +00:00
super ( "select" ) ;
2024-10-15 20:06:22 +00:00
window . addEventListener ( "DOMContentLoaded" , ( ) = > {
this . loadTomSelect ( ) ;
} ) ;
}
loadTomSelect() {
2024-10-16 12:59:02 +00:00
const minCharNumberForSearch = 2 ;
2024-10-15 20:06:22 +00:00
let maxItems = 1 ;
2024-10-16 12:59:02 +00:00
if ( this . node . multiple ) {
maxItems = Number . parseInt ( this . node . dataset . max ) ? ? null ;
2024-10-15 20:06:22 +00:00
}
2024-10-16 12:59:02 +00:00
this . widget = new TomSelect ( this . node , {
2024-10-15 20:06:22 +00:00
hideSelected : true ,
2024-10-16 00:22:27 +00:00
diacritics : true ,
duplicates : false ,
2024-10-15 20:06:22 +00:00
maxItems : maxItems ,
2024-10-16 12:59:02 +00:00
loadThrottle : Number.parseInt ( this . node . dataset . delay ) ? ? null ,
2024-10-15 20:06:22 +00:00
valueField : "id" ,
labelField : "display_name" ,
searchField : [ "display_name" , "nick_name" , "first_name" , "last_name" ] ,
2024-10-16 12:59:02 +00:00
placeholder : this.node.dataset.placeholder ? ? "" ,
2024-10-16 00:22:27 +00:00
shouldLoad : ( query : string ) = > {
2024-10-16 12:59:02 +00:00
return query . length >= minCharNumberForSearch ; // Avoid launching search with less than 2 characters
2024-10-16 00:22:27 +00:00
} ,
2024-10-15 20:06:22 +00:00
load : ( query : string , callback : TomLoadCallback ) = > {
userSearchUsers ( {
query : {
search : query ,
} ,
} ) . then ( ( response ) = > {
if ( response . data ) {
if ( this . filter ) {
callback ( this . filter ( response . data . results ) , [ ] ) ;
} else {
callback ( response . data . results , [ ] ) ;
}
return ;
}
callback ( [ ] , [ ] ) ;
} ) ;
} ,
render : {
option : ( item : UserProfileSchema , sanitize : typeof escape_html ) = > {
return ` <div class="select-item">
< img
src = "${sanitize(item.profile_pict)}"
alt = "${sanitize(item.display_name)}"
onerror = "this.src = '/static/core/img/unknown.jpg'"
/ >
< span class = "select-item-text" > $ { sanitize ( item . display_name ) } < / span >
< / div > ` ;
} ,
item : ( item : UserProfileSchema , sanitize : typeof escape_html ) = > {
return ` <span><i class="fa fa-times"></i> ${ sanitize ( item . display_name ) } </span> ` ;
} ,
2024-10-16 00:22:27 +00:00
// biome-ignore lint/style/useNamingConvention: that's how it's defined
not_loading : ( data : TomOption , _sanitize : typeof escape_html ) = > {
2024-10-16 12:59:02 +00:00
return ` <div class="no-results"> ${ interpolate ( gettext ( "You need to type %(number)s more characters" ) , { number : minCharNumberForSearch - data . input . length } , true)}</div> ` ;
2024-10-16 00:22:27 +00:00
} ,
// biome-ignore lint/style/useNamingConvention: that's how it's defined
no_results : ( _data : TomOption , _sanitize : typeof escape_html ) = > {
return ` <div class="no-results"> ${ gettext ( "No results found" ) } </div> ` ;
} ,
2024-10-15 20:06:22 +00:00
} ,
} ) ;
this . widget . on ( "item_select" , ( item : TomItem ) = > {
this . widget . removeItem ( item ) ;
} ) ;
}
}
2024-10-16 12:59:02 +00:00
window . customElements . define ( "ajax-select" , AjaxSelect ) ;