In shadcn ui port for svelte 5 - Select component (using bitsui select) bind:value asks for state(string), but Writable<string> works too as well, so bind:value={$theme or $language} is properly getting and setting value. But my settings component works with additional setting passed to it as props with store as one of elements of object and i have trouble getting writable properly work from object.
<script lang="ts" module>
interface SelectItem {
label: string;
labelRu: string;
value: string;
}
export interface Setting {
store: Writable<string>;
name: string;
nameRu: string;
values: SelectItem[];
}
</script>
<script lang="ts">
import * as Select from '$shared/components/ui/select/index.js';
import { theme } from '$shared/stores/theme';
import { language } from '$shared/stores/language';
import { derived, type Writable } from 'svelte/store';
let { additionalSettings = null }: { additionalSettings?: { [key: string]: Setting } | null } =
$props();
let clazz = $state('');
export { clazz as class };
let settings: { [key: string]: Setting } = {
theme: {
store: theme,
name: 'Theme',
nameRu: 'Тема',
values: [
{ label: 'Light', labelRu: 'Светлая', value: 'light' },
{ label: 'Dark', labelRu: 'Темная', value: 'dark' }
]
},
language: {
store: language,
name: 'Lingo',
nameRu: 'Язык',
values: [
{ label: 'English', labelRu: 'Английский', value: 'en' },
{ label: 'Russian', labelRu: 'Русский', value: 'ru' }
]
}
};
settings = { ...settings, ...(additionalSettings ?? {}) };
const settingLabel = Object.fromEntries(
Object.entries(settings).map(([key, setting]) => [
key,
derived([setting.store, language], ([$store, $language]) => {
const selectedValue = setting.values.find((value) => value.value === $store);
return $language === 'ru' ? selectedValue?.labelRu : selectedValue?.label;
})
])
);
</script>
<div
class="{clazz !== '' ? clazz + ' ' : ''}flex min-w-[240px] flex-col space-y-4 px-2 py-4 text-sm"
>
{#each Object.entries(settings) as [key, setting]}
<div class="flex items-center space-x-2">
<p class="min-w-[48px]">{$language === 'ru' ? setting.nameRu : setting.name}:</p>
<Select.Root type="single" name={key} bind:value={$setting.store}>
<Select.Trigger>
{#await settingLabel[key] then label}
{(label ?? $language === 'ru')
? 'Выберите ' + setting.nameRu
: 'Select ' + setting.name}
{/await}
</Select.Trigger>
<Select.Content>
{#each setting.values as value}
<Select.Item value={value.value}>
{$language === 'ru' ? value.labelRu : value.label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
{/each}
</div>
I'm newby to Svelte 5 reactive functionality, so maybe i'm asking something easy fixing. Sorry for this(
Stores can only be used with $
-syntax if they are declared at the root of the component which is not the case here since the store variable comes from an #each
block.
You can extract the contents of the #each
to a new component, pass the store to said component then it will satisfy the necessary criteria there.
But generally I would not recommend using stores at all in Svelte 5 unless it's a third party dependency, though even then you can use the fromStore
helper function to turn the store into a state object. This should be possible here as well.
A simplified example:
<script>
import { writable, derived, fromStore } from 'svelte/store';
const value = writable(2);
const double = derived(value, v => v * 2);
const settings = [
{ name: 'Original', store: value },
{ name: 'Doubled', store: double },
];
</script>
{#each settings as { name, store }}
{@const state = fromStore(store)}
<label>
{name}
{#if 'set' in store}
<input type=number bind:value={state.current} />
{:else}
{state.current}
{/if}
</label> <br>
{/each}
Stores: {$value} * 2 = {$double}