Search code examples
sveltesvelte-3

Svelte #each over array of constants how?


Making first steps with Svelte and already loving it , noob question but I guess Svelte can not track such an update?

What would be Svelte way of writing this?

Uncommenting 2 would work, but even though I see different values in

when I click Weeks , Weeks in #each do not receive "new" selected updates

<script lang="ts">
    import { getDays } from '$lib/calendar';
    import Week from './Week.svelte';

    let days = getDays(new Date());

    let selected = 2;

    function handleSelect(week_n) {
        // console.log(week_n);
        selected = week_n;
    }
</script>

<p>Select {selected}</p>

<div class="isolate mt-2 grid grid-rows-5 gap-px rounded-lg  text-sm shadow ring-1 ring-gray-200">
    {#each [0, 1, 2, 3, 4] as week_n, i}
        <Week
            week={week_n === selected // 2
                ? days.slice(week_n * 7, week_n * 7 + 7).map((d) => ({ ...d, selected: true }))
                : days.slice(week_n * 7, week_n * 7 + 7)}
            on:select={(ev) => handleSelect(week_n)}
        />
    {/each}
</div>

and the Week component

<script lang="ts">
    import { createEventDispatcher } from 'svelte';

    import WeekDay from './WeekDay.svelte';

    export let week;

    const dispatch = createEventDispatcher();

    function select() {
        dispatch('select', {
            text: 'Hello!'
        });
    }
</script>

<button
    type="button"
    class="rounded-tl-lg py-1.5 text-gray-400 hover:bg-gray-100 focus:z-10"
    on:click={select}
>
    <div class="mx-auto flex">
        {#each week as { datetime, dd, isToday, thisMonth, selected }, i}
            <WeekDay {datetime} {dd} {isToday} {thisMonth} {selected} />
        {/each}
    </div>
</button>

including leaf WeekDay also for completeness

<script lang="ts">
    export let datetime;
    export let dd;
    export let isToday;
    export let thisMonth;
    export let selected; // = false;

    export let topLeft;
    export let topRight;
    export let bottomLeft;
    export let bottomRight;

    let timeClass = 'mx-auto flex h-7 w-7 items-center justify-center rounded-full';

    if (selected || isToday) {
        timeClass += ' font-semibold';
    }

    if (!selected && !isToday && thisMonth) timeClass += ' bg-white text-gray-900';

    if (!selected && !isToday && !thisMonth) timeClass += ' bg-gray-50';

    if (isToday && !selected) timeClass += ' text-indigo-600';

    if (selected && isToday) timeClass += ' bg-indigo-600 text-white';

    if (selected && !isToday) timeClass += ' bg-gray-900 text-white';
</script>

<time {datetime} class={timeClass}>{dd}</time>


Solution

  • Your code is not reactive the whole way, the properties get changed but the changes don't propagate.

    The let timeClass in WeekDay and all the subsequent if statements only happen once.

    To fix that, it should be $: timeClass = ... and $: if (...) ..., but that could be done more functionally. Something like:

    $: classes = [
      ['mx-auto flex h-7 w-7 items-center justify-center rounded-full', true],
      ['font-semibold', selected || isToday],
      ...
    ]
    .filter(x => x[1]).map(x => x[0])
    .join(' ')
    

    Also, the week property should not require a ternary:

    week={days.slice(week_n * 7, week_n * 7 + 7)
      .map(d => ({ ...d, selected: week_n === selected }))}