fix(multiselect): Svelte 5 compatibility

Refactor the MultiSelect component to remove circular dependencies which
would cause infinite loops in Svelte 5 (#1986).
This commit is contained in:
Paweł Malinowski 2024-11-09 14:53:33 +01:00
commit f5de892268

View file

@ -168,7 +168,7 @@
*/ */
export let highlightedId = null; export let highlightedId = null;
import { afterUpdate, createEventDispatcher, setContext } from "svelte"; import { afterUpdate, createEventDispatcher, setContext, tick } from "svelte";
import WarningFilled from "../icons/WarningFilled.svelte"; import WarningFilled from "../icons/WarningFilled.svelte";
import WarningAltFilled from "../icons/WarningAltFilled.svelte"; import WarningAltFilled from "../icons/WarningAltFilled.svelte";
import Checkbox from "../Checkbox/Checkbox.svelte"; import Checkbox from "../Checkbox/Checkbox.svelte";
@ -237,7 +237,7 @@
afterUpdate(() => { afterUpdate(() => {
if (checked.length !== prevChecked.length) { if (checked.length !== prevChecked.length) {
if (selectionFeedback === "top") { if (selectionFeedback === "top") {
sortedItems = sort(); items = sort();
} }
prevChecked = checked; prevChecked = checked;
selectedIds = checked.map(({ id }) => id); selectedIds = checked.map(({ id }) => id);
@ -247,33 +247,29 @@
unselected: unchecked, unselected: unchecked,
}); });
} }
});
if (!open) { $: if (!open) {
if (!initialSorted || selectionFeedback !== "fixed") { if (!initialSorted || selectionFeedback !== "fixed") {
sortedItems = sort(); tick().then(() => {
items = sort();
initialSorted = true; initialSorted = true;
});
} }
highlightedIndex = -1; highlightedIndex = -1;
value = ""; value = "";
} }
items = sortedItems;
});
$: menuId = `menu-${id}`; $: menuId = `menu-${id}`;
$: inline = type === "inline"; $: inline = type === "inline";
$: ariaLabel = $$props["aria-label"] || "Choose an item"; $: ariaLabel = $$props["aria-label"] || "Choose an item";
$: sortedItems = items.map((item) => ({ $: checked = items.filter(({ checked }) => checked);
...item, $: unchecked = items.filter(({ checked }) => !checked);
checked: selectedIds.includes(item.id), $: filteredItems = items.filter((item) => filterItem(item, value));
}));
$: checked = sortedItems.filter(({ checked }) => checked);
$: unchecked = sortedItems.filter(({ checked }) => !checked);
$: filteredItems = sortedItems.filter((item) => filterItem(item, value));
$: highlightedId = $: highlightedId =
highlightedIndex > -1 highlightedIndex > -1
? (filterable ? filteredItems : sortedItems)[highlightedIndex]?.id ?? null ? (filterable ? filteredItems : items)[highlightedIndex]?.id ?? null
: null; : null;
</script> </script>
@ -366,7 +362,7 @@
change(-1); change(-1);
} else if (key === 'Enter') { } else if (key === 'Enter') {
if (highlightedIndex > -1) { if (highlightedIndex > -1) {
sortedItems = sortedItems.map((item, i) => { items = items.map((item, i) => {
if (i !== highlightedIndex) return item; if (i !== highlightedIndex) return item;
return { ...item, checked: !item.checked }; return { ...item, checked: !item.checked };
}); });
@ -394,7 +390,7 @@
on:clear on:clear
on:clear="{() => { on:clear="{() => {
selectedIds = []; selectedIds = [];
sortedItems = sortedItems.map((item) => ({ items = items.map((item) => ({
...item, ...item,
checked: false, checked: false,
})); }));
@ -424,10 +420,10 @@
on:keydown|stopPropagation="{({ key }) => { on:keydown|stopPropagation="{({ key }) => {
if (key === 'Enter') { if (key === 'Enter') {
if (highlightedId) { if (highlightedId) {
const filteredItemIndex = sortedItems.findIndex( const filteredItemIndex = items.findIndex(
(item) => item.id === highlightedId (item) => item.id === highlightedId
); );
sortedItems = sortedItems.map((item, i) => { items = items.map((item, i) => {
if (i !== filteredItemIndex) return item; if (i !== filteredItemIndex) return item;
return { ...item, checked: !item.checked }; return { ...item, checked: !item.checked };
}); });
@ -489,7 +485,7 @@
id="{id}" id="{id}"
aria-multiselectable="true" aria-multiselectable="true"
> >
{#each filterable ? filteredItems : sortedItems as item, i (item.id)} {#each filterable ? filteredItems : items as item, i (item.id)}
<ListBoxMenuItem <ListBoxMenuItem
id="{item.id}" id="{item.id}"
role="option" role="option"
@ -503,9 +499,11 @@
e.stopPropagation(); e.stopPropagation();
return; return;
} }
sortedItems = sortedItems.map((_) =>
items = items.map((_) =>
_.id === item.id ? { ..._, checked: !_.checked } : _ _.id === item.id ? { ..._, checked: !_.checked } : _
); );
fieldRef.focus(); fieldRef.focus();
}}" }}"
on:mouseenter="{() => { on:mouseenter="{() => {