Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support "disabled" state in MultiSelect #1377

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions src/lib/forms/MultiSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
export let size: FormSizeType = 'md';
export let dropdownClass: string = '';
export let placeholder: string = '';
export let disabled: boolean = false;
$: selectItems = items.filter((x) => value.includes(x.value));
let show: boolean = false;

Expand All @@ -21,7 +22,7 @@
};

// Container
const multiSelectClass: string = 'relative border border-gray-300 flex items-center rounded-lg gap-2 dark:border-gray-600 focus-within:ring-1 focus-within:border-primary-500 ring-primary-500 dark:focus-within:border-primary-500 dark:ring-primary-500 focus-visible:outline-none';
const multiSelectClass: string = 'relative border border-gray-300 flex items-center rounded-lg gap-2 dark:border-gray-600 ring-primary-500 dark:ring-primary-500 focus-visible:outline-none';
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I ensure that clicking the input doesn't highlight it when focused. Visually this is good, but conceptually I don't think any of the children should be retaining focus. I think it would be better to try and propagate the disabled state through all the children for accessibility, but I didn't attempt to do that.


// Dropdown
let multiSelectDropdown: string;
Expand All @@ -38,6 +39,10 @@
const activeItemClass: string = 'bg-primary-100 text-primary-500 dark:bg-primary-500 dark:text-primary-100 hover:bg-primary-100 dark:hover:bg-primary-500 hover:text-primary-600 dark:hover:text-primary-100';

const selectOption = (select: SelectOptionType<any>) => {
if (disabled) {
return;
}

if (value.includes(select.value)) {
clearThisOption(select);
} else if (!value.includes(select.value)) {
Expand All @@ -47,12 +52,20 @@
};

const clearAll = (e: MouseEvent) => {
if (disabled) {
return;
}

e.stopPropagation();
value = [];
dispatcher('change');
};

const clearThisOption = (select: SelectOptionType<any>) => {
if (disabled) {
return;
}

if (value.includes(select.value)) {
value = value.filter((o) => o !== select.value);
dispatcher('change');
Expand All @@ -61,11 +74,19 @@

// Keyboard navigation
function handleEscape() {
if (disabled) {
return;
}

if (show) {
show = false;
}
}
function handleToggleActiveItem() {
if (disabled) {
return;
}

if (!show) {
show = true;
activeIndex = 0;
Expand All @@ -75,6 +96,10 @@
}
}
function handleArrowUpDown(offset: number) {
if (disabled) {
return;
}

if (!show) {
show = true;
activeIndex = 0;
Expand All @@ -89,6 +114,10 @@
}
}
function handleKeyDown(event: KeyboardEvent) {
if (disabled) {
return;
}

switch (event.key) {
case 'Escape':
handleEscape();
Expand Down Expand Up @@ -119,15 +148,15 @@
{/each}
</select>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div on:click={() => (show = !show)} on:focusout={() => (show = false)} on:keydown={handleKeyDown} tabindex="0" role="listbox" class={twMerge(multiSelectClass, sizes[size], $$props.class)}>
<div on:click={() => !disabled && (show = !show)} on:focusout={() => !disabled && (show = false)} on:keydown={handleKeyDown} tabindex="0" role="listbox" class={twMerge(multiSelectClass, sizes[size], $$props.class, !disabled && "focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500", disabled && "opacity-50 cursor-not-allowed"))}>
{#if !selectItems.length}
<span class="text-gray-400">{placeholder}</span>
{/if}
<span class="flex gap-2 flex-wrap">
{#if selectItems.length}
{#each selectItems as item (item.name)}
<slot {item} clear={() => clearThisOption(item)}>
<Badge color="dark" large={size === 'lg'} dismissable params={{ duration: 100 }} on:close={() => clearThisOption(item)}>
<Badge color="dark" large={size === 'lg'} dismissable params={{ duration: 100 }} on:close={() => clearThisOption(item)} class={disabled && "pointer-events-none"} >
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably overkill to do pointer-events: none here since all helper methods have early exits, but meh.

{item.name}
</Badge>
</slot>
Expand All @@ -136,10 +165,10 @@
</span>
<div class="flex ms-auto gap-2 items-center">
{#if selectItems.length}
<CloseButton {size} on:click={clearAll} color="none" class="p-0 focus:ring-gray-400 dark:text-white" />
<CloseButton {size} on:click={clearAll} color="none" class={twMerge("p-0 focus:ring-gray-400 dark:text-white", disabled && "cursor-not-allowed")} disabled={disabled} />
{/if}
<div class="w-[1px] bg-gray-300 dark:bg-gray-600"></div>
<svg class="cursor-pointer h-3 w-3 ms-1 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<svg class={twMerge("cursor-pointer h-3 w-3 ms-1 text-gray-800 dark:text-white", disabled && "cursor-not-allowed")} aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={show ? 'm1 5 4-4 4 4' : 'm9 1-4 4-4-4'} />
</svg>
</div>
Expand All @@ -148,7 +177,7 @@
<div on:click|stopPropagation role="presentation" class={multiSelectDropdown}>
{#each items as item (item.name)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div on:click={() => selectOption(item)} role="presentation" class={twMerge(itemsClass, selectItems.includes(item) && itemsSelectClass, activeItem === item && activeItemClass)}>
<div on:click={() => selectOption(item)} role="presentation" class={twMerge(itemsClass, selectItems.includes(item) && itemsSelectClass, activeItem === item && activeItemClass, disabled && "pointer-events-none")}>
{item.name}
</div>
{/each}
Expand Down