
# Events and updates

A packing list you cannot check off is a poster. This step adds the two things you do with the list: mark an item packed, and add a new one.

## Changing the state

Add two functions to `src/store.ts`:

```ts
// The packing list's state and the two ways it changes.
import { signal } from "zerolith";

export interface Item
{
    id: number;
    name: string;
    packed: boolean;
}

export const items = signal<Item[]>([
    { id: 1, name: "Passport", packed: true },
    { id: 2, name: "Phone charger", packed: false },
    { id: 3, name: "Rain jacket", packed: false }
], { name: "items" });

export function toggle(id: number): void
{
    items.set(items.get().map((item) =>
        (item.id === id ? { ...item, packed: !item.packed } : item)));
}

export function add(name: string): void
{
    const id = Math.max(0, ...items.get().map((item) => item.id)) + 1;
    items.set([...items.get(), { id, name, packed: false }]);
}
```

Both functions build a new array and set it, rather than changing the old array in place. A signal notices `set()`, and it compares the new value with the old one: the same array, even with a changed item inside, looks like no change at all. Setting a new array is what tells the page something happened, and `each` then works out which rows actually differ by their keys.

`toggle` copies only the item that changes; every other item keeps its object, so its row sees no change. `add` picks the next free id, one more than the largest so far.

> [!WARNING]
> Changing the array in place does nothing on the page. `items.get().push(item)` never calls `set`, and setting the same array again is ignored as no change, so always set a new array.

## Listening to events

Update `src/components/packing.ts`:

```ts
// The packing list page: check items off, and add new ones.
import { type View, computed, each, html } from "zerolith";
import { add, items, toggle } from "../store.ts";

export function Packing(): View
{
    const count = computed(() => items.get().length);
    const submitted = (event: SubmitEvent): void =>
    {
        event.preventDefault();
        const form = event.currentTarget as HTMLFormElement;
        const name = String(new FormData(form).get("name") ?? "").trim();
        if (name !== "")
        {
            add(name);
            form.reset();
        }
    };
    return html`
        <h1>Packing list</h1>
        <p>${count} items</p>
        <ul>
            ${each(items, (item) => item.id, (item) => html`
                <li>
                    <label>
                        <input type="checkbox"
                            .checked=${computed(() => item.get().packed)}
                            @change=${() => toggle(item.get().id)}>
                        ${computed(() => item.get().name)}
                    </label>
                </li>`)}
        </ul>
        <form @submit=${submitted}>
            <label>New item
                <input name="name" autocomplete="off" required>
            </label>
            <button>Add</button>
        </form>`;
}
```

An attribute that starts with `@` listens to an event: `@change` on the checkbox, `@submit` on the form. The value is the function to call. Two more binding forms appear here:

- `.checked=${...}` with a leading dot sets a property on the element instead of an attribute, which is what a checkbox's checked state needs.
- A plain attribute, like `name="name"`, is static markup.

The form is a real form: it has a label, a required field, and a submit button, so it works with the keyboard and with assistive technology, and pressing Enter submits it. The handler stops the browser's own submission, reads the field through `FormData`, and clears the form after adding.

## What you learned

- `@event=${handler}` listens to an event; `.property=${value}` sets a property.
- State changes by setting a new value; a signal ignores a value that is the same as the old one.
- Native forms and labels give you keyboard and screen reader support for free.

