I'm learning Svelte and I'm trying to make a component (a color palette). I blindly went for a solution where each ColorSelector
(the color div you click on) is a Svelte component. (I'd gladly take an example where no child components are used)
I export a selected
property in the ColorSelector.svelte component file. I'd like to set that property to false
on every ColorSelectors instantiated when one of them is clicked except on the one that has been clicked.
However, I'm struggling to find how to keep a reference to an instantiated component in a loop. How can I achieve this?
<script lang="ts">
import { Colors, Color } from "./modules/colors";
import ColorSelector from "./ColorSelector.svelte";
const DEFAULT_COLOR = Colors.LIGHT_WHITE;
let selectedColor:Color = DEFAULT_COLOR;
function handleClick(event, i) {
selectedColor = event.detail.color;
// When clicked set ColorSelector.selected = false on evert ColorSelectors
// except the one that has been clicked
}
</script>
<div>
{#each Object.values(Colors) as color, i}
<ColorSelector on:selected={handleSelect} color={color}></ColorSelector>
{/each}
</div>
<style>
div {
display: flex;
}
</style>
To keep a reference to a component inside a loop you could use bind:this
to an array. Have a look at this question Svelte how to bind div inside each lop to obtain a reference using this
To be able to set the property on the component the option must be activated with <svelte:options accessors={true}/>
Here's a solution with the data flow I assume you were trying to build >> REPL
<script>
import ColorSelector from './ColorSelector.svelte'
import {Colors} from './Colors'
const DEFAULT_COLOR = Colors.LIGHT_WHITE;
let selectedColor = DEFAULT_COLOR
let colorSelectors = []
function handleSelect(e,i) {
selectedColor = e.detail.color
colorSelectors.forEach(cS => cS.selected=false)
colorSelectors[i].selected = true
}
</script>
<p><b>{selectedColor}</b></p>
<div>
{#each Object.values(Colors) as color, i}
<ColorSelector {color} bind:this={colorSelectors[i]}
selected={color === DEFAULT_COLOR ? true : false}
on:selected="{(e) => handleSelect(e,i)}"/>
{/each}
</div>
<style>
div {
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
}
</style>
[ColorSelector.svelte]
<svelte:options accessors={true}/>
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let color, selected
function handleClick() {
dispatch('selected', {
color
});
}
</script>
<div class="color-selector"
class:selected
style:background={color}
on:click={handleClick}
></div>
<style>
.color-selector {
flex-basis: 200px;
flex-grow: 1;
height: 100px;
box-sizing: border-box;
}
.selected {
border: 5px solid black;
}
</style>
But I think these features are not really needed to build the selector nor is the selected = true/false
flag because only the one selected color is important.
Here's a simpler solution with and without a component REPL
<script>
import ColorSelector from './ColorSelector.svelte'
import {Colors} from './Colors'
const DEFAULT_COLOR = Colors.LIGHT_WHITE;
let selectedColor = DEFAULT_COLOR;
</script>
<p><b>{selectedColor}</b></p>
<div>
{#each Object.values(Colors) as color, i}
<div class="color-selector"
class:selected-color={selectedColor === color}
style:background={color}
on:click={() => selectedColor = color}
></div>
{/each}
</div>
<hr>
<div>
{#each Object.values(Colors) as color, i}
<ColorSelector {color} bind:selectedColor />
{/each}
</div>
<style>
div {
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
}
.color-selector {
flex-basis: 200px;
flex-grow: 1;
height: 100px;
}
.selected-color {
border: 5px solid black;
}
hr {
margin: 3rem;
}
</style>
[ColorSelector.svelte]
<script>
export let color, selectedColor
</script>
<div class="color-selector"
class:selected-color={selectedColor === color}
style:background={color}
on:click={() => selectedColor = color}
></div>
<style>
.color-selector {
flex-basis: 200px;
flex-grow: 1;
height: 100px;
box-sizing: border-box;
}
.selected-color {
border: 5px solid black;
}
</style>