Skip to content

Commit 6283794

Browse files
committed
feat: added 'multiple' as an option to Listbox
1 parent 0b67ac0 commit 6283794

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

src/lib/components/listbox/Listbox.svelte

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
Open,
44
Closed,
55
}
6+
export enum ValueMode {
7+
Single,
8+
Multi,
9+
}
610
export type ListboxOptionDataRef = {
711
textValue: string;
812
disabled: boolean;
@@ -14,6 +18,7 @@
1418
listboxState: ListboxStates;
1519
value: unknown;
1620
orientation: "vertical" | "horizontal";
21+
mode: ValueMode;
1722
1823
labelRef: Writable<HTMLLabelElement | null>;
1924
buttonRef: Writable<HTMLButtonElement | null>;
@@ -61,6 +66,8 @@
6166
horizontal?: boolean;
6267
/** The selected value */
6368
value?: StateDefinition["value"];
69+
/** Wether the `Listbox` should allow mutliple selections */
70+
multiple?: boolean;
6471
};
6572
</script>
6673

@@ -89,6 +96,7 @@
8996
export let use: HTMLActionArray = [];
9097
export let disabled = false;
9198
export let horizontal = false;
99+
export let multiple = false;
92100
export let value: StateDefinition["value"];
93101
94102
/***** Events *****/
@@ -112,6 +120,9 @@
112120
let options: StateDefinition["options"] = [];
113121
let searchQuery: StateDefinition["searchQuery"] = "";
114122
let activeOptionIndex: StateDefinition["activeOptionIndex"] = null;
123+
let mode: StateDefinition["mode"] = multiple
124+
? ValueMode.Multi
125+
: ValueMode.Single;
115126
116127
let api = writable<StateDefinition>({
117128
listboxState,
@@ -124,6 +135,7 @@
124135
activeOptionIndex,
125136
disabled,
126137
orientation,
138+
mode,
127139
closeListbox() {
128140
if (disabled) return;
129141
if (listboxState === ListboxStates.Closed) return;
@@ -231,7 +243,25 @@
231243
},
232244
select(value: unknown) {
233245
if (disabled) return;
234-
dispatch("change", value);
246+
dispatch(
247+
"change",
248+
match(mode, {
249+
[ValueMode.Single]: () => value,
250+
[ValueMode.Multi]: () => {
251+
let copy = ($api.value as unknown[]).slice();
252+
let raw = value;
253+
254+
let idx = copy.indexOf(raw);
255+
if (idx === -1) {
256+
copy.push(raw);
257+
} else {
258+
copy.splice(idx, 1);
259+
}
260+
261+
return copy;
262+
},
263+
})
264+
);
235265
},
236266
});
237267
setContext(LISTBOX_CONTEXT_NAME, api);
@@ -256,6 +286,7 @@
256286
activeOptionIndex,
257287
disabled,
258288
orientation,
289+
mode,
259290
};
260291
});
261292

src/lib/components/listbox/ListboxOption.svelte

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212

1313
<script lang="ts">
1414
import { onDestroy, onMount, tick } from "svelte";
15-
import { ListboxStates, useListboxContext } from "./Listbox.svelte";
15+
import {
16+
ListboxStates,
17+
useListboxContext,
18+
ValueMode,
19+
} from "./Listbox.svelte";
1620
import { useId } from "$lib/hooks/use-id";
1721
import { Focus } from "$lib/utils/calculate-active-index";
1822
import Render from "$lib/utils/Render.svelte";
@@ -21,6 +25,7 @@
2125
import { get_current_component } from "svelte/internal";
2226
import type { HTMLActionArray } from "$lib/hooks/use-actions";
2327
import type { TPassThroughProps } from "$lib/types";
28+
import { match } from "$lib/utils/match";
2429
2530
/***** Props *****/
2631
type TAsProp = $$Generic<SupportedAs>;
@@ -39,13 +44,26 @@
3944
let id = `headlessui-listbox-option-${useId()}`;
4045
4146
let buttonRef = $api.buttonRef;
47+
let isFirstSelected = false;
4248
4349
$: active =
4450
$api.activeOptionIndex !== null
4551
? $api.options[$api.activeOptionIndex].id === id
4652
: false;
4753
48-
$: selected = $api.value === value;
54+
$: selected = match($api.mode, {
55+
[ValueMode.Single]: () => $api.value === value,
56+
[ValueMode.Multi]: () => ($api.value as unknown[]).includes(value),
57+
});
58+
59+
$: isFirstSelected = match($api.mode, {
60+
[ValueMode.Single]: () => selected,
61+
[ValueMode.Multi]: () =>
62+
$api.options.find((option: unknown) =>
63+
($api.value as unknown[]).includes(option)
64+
)?.id === id,
65+
});
66+
4967
$: dataRef = {
5068
disabled,
5169
value,
@@ -75,7 +93,14 @@
7593
await tick();
7694
if (newState !== oldState || newSelected !== oldSelected) {
7795
if (newState === ListboxStates.Open && newSelected) {
78-
$api.goToOption(Focus.Specific, id);
96+
match($api.mode, {
97+
[ValueMode.Multi]: () => {
98+
if (isFirstSelected) $api.goToOption(Focus.Specific, id);
99+
},
100+
[ValueMode.Single]: () => {
101+
$api.goToOption(Focus.Specific, id);
102+
},
103+
});
79104
}
80105
}
81106
if (newState !== oldState || newActive !== oldActive) {

0 commit comments

Comments
 (0)