@@ -2,16 +2,16 @@ import HTMLParsedElement from "./html-parsed-element";
22import { WoltlabCoreListItemElement } from "./woltlab-core-list-item" ;
33
44{
5- type ChangePayload = { selected : string } ;
6-
75 interface WoltlabCoreListBoxEventMap {
8- change : CustomEvent < ChangePayload > ;
6+ change : CustomEvent < void > ;
97 }
108
119 // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
1210 class WoltlabCoreListBoxElement extends HTMLParsedElement {
11+ #multiple = false ;
1312 #position = - 1 ;
1413 #selected = "" ;
14+ #selectedValues: string [ ] = [ ] ;
1515 readonly #formInput: HTMLInputElement ;
1616 readonly #knownItems: WeakSet < WoltlabCoreListItemElement > = new WeakSet ( ) ;
1717 readonly #shadow: ShadowRoot ;
@@ -24,20 +24,21 @@ import { WoltlabCoreListItemElement } from "./woltlab-core-list-item";
2424 style . textContent = `
2525:host {
2626 background-color: var(--wcfDropdownBackground);
27- border-radius: 4px;
28- box-shadow: var(--wcfBoxShadow);
29- color: var(--wcfDropdownText);
27+ border-radius: 4px;
28+ box-shadow: var(--wcfBoxShadow);
29+ color: var(--wcfDropdownText);
3030 display: flex;
31- flex-direction: column;
32- min-width: 160px !important;
33- padding: 4px 0;
34- pointer-events: all;
35- position: fixed;
36- text-align: left;
37- z-index: 450;
31+ min-width: 160px !important;
32+ padding: 4px 0;
33+ pointer-events: all;
34+ position: fixed;
35+ text-align: left;
36+ z-index: 450;
3837}
3938
4039.content {
40+ display: flex;
41+ flex-direction: column;
4142 overflow: auto;
4243}
4344 ` ;
@@ -69,12 +70,6 @@ import { WoltlabCoreListItemElement } from "./woltlab-core-list-item";
6970 } ) ;
7071
7172 this . addEventListener ( "keydown" , ( event ) => {
72- if ( event . key . length === 1 ) {
73- event . preventDefault ( ) ;
74- this . #focusFirstMatchingItem( event . key ) ;
75- return ;
76- }
77-
7873 switch ( event . key ) {
7974 case "ArrowDown" :
8075 event . preventDefault ( ) ;
@@ -92,22 +87,42 @@ import { WoltlabCoreListItemElement } from "./woltlab-core-list-item";
9287 break ;
9388
9489 case "Enter" :
95- event . preventDefault ( ) ;
96- this . #selectItem( ) ;
90+ if ( ! this . #multiple) {
91+ event . preventDefault ( ) ;
92+ this . #selectItem( ) ;
93+ }
9794 break ;
9895
9996 case "Home" :
10097 event . preventDefault ( ) ;
10198 this . #focusFirstItem( ) ;
10299 break ;
100+
101+ case " " :
102+ // The space is always intercepted because in a single selection
103+ // list it would trigger a scroll event.
104+ event . preventDefault ( ) ;
105+
106+ if ( this . #multiple) {
107+ this . #selectItem( ) ;
108+ }
109+ break ;
110+
111+ default :
112+ if ( event . key . length === 1 && ! event . ctrlKey && ! event . altKey && ! event . metaKey ) {
113+ event . preventDefault ( ) ;
114+ this . #focusFirstMatchingItem( event . key ) ;
115+ }
116+ break ;
103117 }
104118 } ) ;
105119 }
106120
107121 parsedCallback ( ) {
108122 this . classList . add ( "listBox" ) ;
109123 this . role = "listbox" ;
110- this . ariaMultiSelectable = "false" ;
124+ this . #multiple = this . hasAttribute ( "multiple" ) ;
125+ this . ariaMultiSelectable = String ( this . #multiple) ;
111126 this . ariaOrientation = "vertical" ;
112127 this . tabIndex = 0 ;
113128
@@ -135,12 +150,8 @@ import { WoltlabCoreListItemElement } from "./woltlab-core-list-item";
135150 if ( ! this . #knownItems. has ( element ) ) {
136151 this . #knownItems. add ( element ) ;
137152
138- element . addEventListener ( "change" , ( event ) => {
139- if ( event . detail . selected ) {
140- this . #changeSelection( element . value ) ;
141- } else {
142- throw new Error ( "TODO: not implemented" ) ;
143- }
153+ element . addEventListener ( "change" , ( ) => {
154+ this . #updateSelection( element ) ;
144155 } ) ;
145156 }
146157 }
@@ -149,29 +160,37 @@ import { WoltlabCoreListItemElement } from "./woltlab-core-list-item";
149160 this . #updateFormInput( this . name ) ;
150161 }
151162
152- #changeSelection( value : string ) : void {
153- this . #selected = value ;
154- this . setAttribute ( "selected" , value ) ;
163+ #updateSelection( changedItem : WoltlabCoreListItemElement ) : void {
164+ const items = this . #getItems( ) ;
165+
166+ if ( this . #multiple) {
167+ this . #selectedValues = items . filter ( ( item ) => item . selected ) . map ( ( item ) => item . value ) ;
168+
169+ const position = items . indexOf ( changedItem ) ;
170+ this . #setFocus( items , position ) ;
171+ } else {
172+ const { value } = changedItem ;
155173
156- for ( const item of this . #getItems( ) ) {
157- if ( item . selected ) {
158- if ( item . value !== value ) {
174+ for ( const item of items ) {
175+ if ( ! item . selected ) {
176+ continue ;
177+ }
178+
179+ if ( changedItem === undefined ) {
180+ item . selected = false ;
181+ } else if ( item . value === value ) {
182+ this . #selected = value ;
183+ } else {
159184 item . selected = false ;
160185 }
161- } else if ( item . value === value ) {
162- item . selected = true ;
163186 }
164187 }
165188
166- if ( this . #formInput !== undefined ) {
189+ /* if (this.#formInput !== undefined) {
167190 this.#formInput.value = value;
168- }
191+ }*/
169192
170- const event = new CustomEvent < ChangePayload > ( "change" , {
171- detail : {
172- selected : value ,
173- } ,
174- } ) ;
193+ const event = new CustomEvent < void > ( "change" ) ;
175194 this . dispatchEvent ( event ) ;
176195 }
177196
@@ -290,7 +309,7 @@ import { WoltlabCoreListItemElement } from "./woltlab-core-list-item";
290309 return ;
291310 }
292311
293- this . #changeSelection ( item . value ) ;
312+ item . toggle ( ) ;
294313 }
295314
296315 #updateFormInput( name : string ) : void {
@@ -311,17 +330,33 @@ import { WoltlabCoreListItemElement } from "./woltlab-core-list-item";
311330 ) ;
312331 }
313332
314- get selected ( ) : string {
333+ get selected ( ) : string | undefined {
334+ if ( this . #multiple) {
335+ return undefined ;
336+ }
337+
315338 return this . #selected;
316339 }
317340
341+ get selectedValues ( ) : string [ ] | undefined {
342+ if ( this . #multiple) {
343+ return this . #selectedValues;
344+ }
345+
346+ return undefined ;
347+ }
348+
318349 get name ( ) : string {
319350 return this . getAttribute ( "name" ) || "" ;
320351 }
321352
322353 set name ( name : string ) {
323354 this . #updateFormInput( name ) ;
324355 }
356+
357+ get multiple ( ) : boolean {
358+ return this . #multiple;
359+ }
325360 }
326361
327362 // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
0 commit comments