Skip to content

Commit c4c335a

Browse files
authored
fix(multi-select): address a11y issues (#2231)
Fixes #2172
1 parent 4873a64 commit c4c335a

File tree

2 files changed

+208
-92
lines changed

2 files changed

+208
-92
lines changed

src/MultiSelect/MultiSelect.svelte

Lines changed: 98 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@
275275
let sortedItems = sort();
276276
277277
$: menuId = `menu-${id}`;
278+
$: comboId = `combo-${id}`;
278279
$: inline = type === "inline";
279280
$: ariaLabel = $$props["aria-label"] || "Choose an item";
280281
$: if (
@@ -324,6 +325,8 @@
324325
{/if}
325326
<ListBox
326327
role={undefined}
328+
id={comboId}
329+
aria-label={ariaLabel}
327330
{disabled}
328331
{invalid}
329332
{invalidText}
@@ -347,79 +350,23 @@
347350
class="bx--list-box__invalid-icon bx--list-box__invalid-icon--warning"
348351
/>
349352
{/if}
350-
<ListBoxField
351-
role="button"
352-
tabindex="0"
353-
aria-expanded={open}
354-
on:click={() => {
355-
if (disabled) return;
356-
if (filterable) {
357-
open = true;
358-
inputRef.focus();
359-
} else {
360-
open = !open;
361-
}
362-
}}
363-
on:keydown={(e) => {
364-
if (filterable) {
365-
return;
366-
}
367-
const key = e.key;
368-
if ([" ", "ArrowUp", "ArrowDown"].includes(key)) {
369-
e.preventDefault();
370-
}
371-
if (key === " ") {
372-
open = !open;
373-
} else if (key === "Tab") {
374-
if (selectionRef && checked.length > 0) {
375-
selectionRef.focus();
376-
} else {
377-
open = false;
378-
}
379-
} else if (key === "ArrowDown") {
380-
change(1);
381-
} else if (key === "ArrowUp") {
382-
change(-1);
383-
} else if (key === "Enter") {
384-
if (highlightedIndex > -1) {
385-
sortedItems = sortedItems.map((item, i) => {
386-
if (i !== highlightedIndex) return item;
387-
return { ...item, checked: !item.checked };
388-
});
389-
}
390-
} else if (key === "Escape") {
391-
open = false;
392-
}
393-
}}
394-
on:focus={() => {
395-
if (filterable) {
396-
open = true;
397-
if (inputRef) inputRef.focus();
398-
}
399-
}}
400-
on:blur={(e) => {
401-
if (!filterable) dispatch("blur", e);
402-
}}
403-
{id}
404-
{disabled}
405-
{translateWithId}
406-
>
407-
{#if checked.length > 0}
408-
<ListBoxSelection
409-
selectionCount={checked.length}
410-
on:clear
411-
on:clear={() => {
412-
selectedIds = [];
413-
sortedItems = sortedItems.map((item) => ({
414-
...item,
415-
checked: false,
416-
}));
417-
}}
418-
translateWithId={translateWithIdSelection}
419-
{disabled}
420-
/>
421-
{/if}
422-
{#if filterable}
353+
{#if filterable}
354+
<div class:bx--list-box__field={true}>
355+
{#if checked.length > 0}
356+
<ListBoxSelection
357+
selectionCount={checked.length}
358+
on:clear
359+
on:clear={() => {
360+
selectedIds = [];
361+
sortedItems = sortedItems.map((item) => ({
362+
...item,
363+
checked: false,
364+
}));
365+
}}
366+
translateWithId={translateWithIdSelection}
367+
{disabled}
368+
/>
369+
{/if}
423370
<input
424371
bind:this={inputRef}
425372
bind:value
@@ -430,11 +377,17 @@
430377
aria-autocomplete="list"
431378
aria-expanded={open}
432379
aria-activedescendant={highlightedId}
380+
aria-labelledby={comboId}
433381
aria-disabled={disabled}
434-
aria-controls={menuId}
382+
aria-controls={open ? menuId : undefined}
383+
aria-owns={open ? menuId : undefined}
435384
class:bx--text-input={true}
436385
class:bx--text-input--empty={value === ""}
437386
class:bx--text-input--light={light}
387+
on:click={() => {
388+
if (disabled) return;
389+
open = true;
390+
}}
438391
on:keydown
439392
on:keydown|stopPropagation={({ key }) => {
440393
if (key === "Enter") {
@@ -472,6 +425,11 @@
472425
{#if invalid}
473426
<WarningFilled class="bx--list-box__invalid-icon" />
474427
{/if}
428+
{#if !invalid && warn}
429+
<WarningAltFilled
430+
class="bx--list-box__invalid-icon bx--list-box__invalid-icon--warning"
431+
/>
432+
{/if}
475433
{#if value}
476434
<ListBoxSelection
477435
on:clear={() => {
@@ -484,20 +442,81 @@
484442
/>
485443
{/if}
486444
<ListBoxMenuIcon
487-
style="pointer-events: {open ? 'auto' : 'none'}"
488445
on:click={(e) => {
446+
if (disabled) return;
489447
e.stopPropagation();
490448
open = !open;
491449
}}
492450
{translateWithId}
493451
{open}
494452
/>
495-
{/if}
496-
{#if !filterable}
453+
</div>
454+
{:else}
455+
<ListBoxField
456+
role="combobox"
457+
tabindex="0"
458+
aria-expanded={open}
459+
aria-activedescendant={highlightedId}
460+
aria-controls={open ? menuId : undefined}
461+
aria-owns={open ? menuId : undefined}
462+
on:click={() => {
463+
if (disabled) return;
464+
open = !open;
465+
}}
466+
on:keydown={(e) => {
467+
const key = e.key;
468+
if ([" ", "ArrowUp", "ArrowDown"].includes(key)) {
469+
e.preventDefault();
470+
}
471+
if (key === " ") {
472+
open = !open;
473+
} else if (key === "Tab") {
474+
if (selectionRef && checked.length > 0) {
475+
selectionRef.focus();
476+
} else {
477+
open = false;
478+
}
479+
} else if (key === "ArrowDown") {
480+
change(1);
481+
} else if (key === "ArrowUp") {
482+
change(-1);
483+
} else if (key === "Enter") {
484+
if (highlightedIndex > -1) {
485+
sortedItems = sortedItems.map((item, i) => {
486+
if (i !== highlightedIndex) return item;
487+
return { ...item, checked: !item.checked };
488+
});
489+
}
490+
} else if (key === "Escape") {
491+
open = false;
492+
}
493+
}}
494+
on:blur={(e) => {
495+
dispatch("blur", e);
496+
}}
497+
{id}
498+
{disabled}
499+
{translateWithId}
500+
>
501+
{#if checked.length > 0}
502+
<ListBoxSelection
503+
selectionCount={checked.length}
504+
on:clear
505+
on:clear={() => {
506+
selectedIds = [];
507+
sortedItems = sortedItems.map((item) => ({
508+
...item,
509+
checked: false,
510+
}));
511+
}}
512+
translateWithId={translateWithIdSelection}
513+
{disabled}
514+
/>
515+
{/if}
497516
<span class:bx--list-box__label={true}>{label}</span>
498517
<ListBoxMenuIcon {open} {translateWithId} />
499-
{/if}
500-
</ListBoxField>
518+
</ListBoxField>
519+
{/if}
501520
<div style:display={open ? "block" : "none"}>
502521
<ListBoxMenu aria-label={ariaLabel} {id} aria-multiselectable="true">
503522
{#each filterable ? filteredItems : sortedItems as item, i (item.id)}

0 commit comments

Comments
 (0)