Reactividad

Asignaciones

Un problema que buscan resolver los frameworks de front-end es la actualización de forma eficiente del DOM cuando el usuario interactúa con la interfáz.

Hay un completo sistema de 'reactividad' para mantener el DOM en sincronía con el estado de la aplicación, por ejemplo en respuesta a un evento.

<script>
    let count = 0;

    function handleClick() {
        count += 1;
    }
</script>Hello worldE

<!-- <button> -->
<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

Puedes ver este ejemplo en acción aquí.

Declaraciones

Las declaraciones reactivas identifican el cambio valor de una variable para actualizar los componentes que hacen uso de este valor. El cambio de valor se detecta a través del operador asignación.

<script>
    let count = 0;
    //let doubled = count * 2; 
    $: doubled = count * 2;

    function handleClick() {
        count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

<p>{count} doubled is {doubled}</p>

Puedes ver este ejemplo en acción aquí.

Proposiciones

El uso de declaraciones reactivas se puede extender a bloques de código y a expresiones condicionales. Recordar que para que la interfaz reaccione a la interacción del usuario la variable debe ser reasignada.

<script>
    let count = 0;

    $: if (count >= 10) {
        alert(`count is dangerously high!`);
        count = 9;
    }

    function handleClick() {
        count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

Puedes ver este ejemplo en acción aquí.

Actualizando Arreglos y objetos

Los arreglos se manipulan a través de metodos como slice, push o reduce los cuales no modifican el arreglo o el objeto a través de un operador de asignación, por lo que la reactividad del framework no se dispara. Para solucionar esto se realiza la operación sobre el objeto usando la flexibilidad sintáctica de JavaScript e involucrando el operador de asignación:

<script>
    let numbers = [1, 2, 3, 4];

    function addNumber() {
        // numbers.push(numbers.length + 1);
        numbers = [...numbers, numbers.length + 1];
    }

    $: sum = numbers.reduce((t, n) => t + n, 0);
</script>

<p>{numbers.join(' + ')} = {sum}</p>

<button on:click={addNumber}>
    Add a number
</button>

Puedes ver este ejemplo en acción aquí.

Props

Props es el diminitivo de Properties y es un objeto que representa y almacena el estado interno de un componente de Svelte. Recordar que las unidades escenciales de una aplicación escrita en Svelte son los componentes.
De esta manera una interfaz implementada con Svelte puede verse como un árbol de estados donde las aristas representan las interacciones entre componentes.
Se habla de árbol y no de un grafo de estados porque por la construcción modular de la interfáz o aplicación mediante la descomposición de unidades da paso a una estructura arbórea.
Por esta razón el estado o la información siempre se mueve de un componente padre a uno hijo. Si se desea que la propagación de la rección sea inversa, se debe aplicar otra técnica que también ofrece Svelte y se verá más adelante.

Declaración de Props

Para indicar que un atributo representa el estado interno del componente se usa la palabra export.

App.svelte

<script>
    import Nested from './Nested.svelte';
</script>

 <Nested answer={42}/>

Nested.svelte

<script>
    // let answer;
    export let answer;
</script>

<p>The answer is {answer}</p>

Puedes ver este ejemplo en acción aquí.

Valores por defecto

Es recomendable trabajar con valores por defecto para evitar inconsistencias en el estado interno de la aplicación. Simplemente basta con inicializar el atributo que se exponen como property:

App.svelte

<script>
    import Nested from './Nested.svelte';
</script>

<Nested answer={42}/>
<Nested/>

Nested.svelte

<script>
    //export let answer;
    export let answer = 'a mystery';
</script>

<p>The answer is {answer}</p>

Puedes ver este ejemplo en acción aquí.

Props extendidos

Como se mencionó antes los props son un objeto, y una forma convencional de hacerlo es pasando cada atributo al componente destino. Svelte provee la siguiente forma compacta para pasar un prop:

App.svelte

<script>
    import Info from './Info.svelte';

    const pkg = {
        name: 'svelte',
        version: 3,
        speed: 'blazing',
        website: 'https://svelte.dev'
    };
</script>

<!-- <Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/> -->
<Info {...pkg}/>

Info.svelte

<script>
    export let name;
    export let version;
    export let speed;
    export let website;
</script>

<p>
    The <code>{name}</code> package is {speed} fast.
    Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a>
    and <a href={website}>learn more here</a>
</p>

Puedes ver este ejemplo en acción aquí.

Slots

Un Slot es un elemento que permite ser usado como "placeholder" y de ésta forma ser reemplazado por el marcado HTML que deseemos. Un ejemplo: Supongamos que se tiene código dentro de un componente llamado Card.svelte

<div> <slot/> </div>

Entonces, por ejemplo, si al usarlo se realiza lo siguiente

<Card> 
    <h3> Inside the slot </h3>
    <p> This is goint to be inside the slot, too</p>
</Card>

Puedes ver este ejemplo en acción aquí.

El marcado HTML (en este caso los elementos h3 y p) serán colocados dentro del elemento div definido en Card.svelte. Ésta técnica es extremadamente cuando se quiere inyectar marcado HTML en un elemento con estilos predefinidos y que van a ser comunes sin importar el marcado que le insertemos. Un muy buen ejemplo ex un e-commerce, que al mostrar sus productos se quiere que cada uno de éstos esté con cierto borde o cierto borde que sea acorde a los colores y estética del negocio.

Slots nombrados

Los Slots nombrados permiten asignar marcado HTML (como los slots corrientes) pero en un específico lugar que el desarrollador/a defina. Por ejemplo, si se modifica el ejemplo anterior y se agregan los siguientes atributos name

<div> <slot name="slot-1"/> <p> In the middle </p> <slot name="slot-2"/> </div>

<Card> 
    <h3 slot="slot-1"> Inside the slot 1</h3>
    <p slot="slot-2"> This is goint to be inside the slot 2</p>
</Card>

Puedes ver este ejemplo en acción aquí.

Entonces, el elemento h3 y p (con atributos slot="slot-1" y slot="slot-2", respectivamente) serán colocados en el lugar de los slots con atributos name="slot-1" y name="slot-2", respectivamente.

Stores

Un Store provee una utilidad similar a la de un Prop. Como su nombre lo indica, un Store es un componente que almacena información y que puede ser accedida por cualquier componente de la aplicación.
Adicionalmente los Stores proveen un mecanismo publish-subscribe para notificar a los componentes en caso de un cambio de estado. Notar que esta propiedad de los Stores va alineada con la reactividad que ofrece el framework Svelte en general.
El ejemplo con varios componentes muestra que el Store se puede acceder desde varios componentes manteniendo integridad:

Stores escribibles

App.svelte

<script>
    import { count } from './stores.js';
    import Incrementer from './Incrementer.svelte';
    import Decrementer from './Decrementer.svelte';
    import Resetter from './Resetter.svelte';

    let count_value;

    //count.subscribe(value => {
    const unsubscribe = count.subscribe(value => {
        count_value = value;
    });
</script>

<h1>The count is {count_value}</h1>

<Incrementer/>
<Decrementer/>
<Resetter/>

Decrementer.svelte

<script>
    import { count } from './stores.js';

    function decrement() {
        count.update(n => n - 1);
    }
</script>

<button on:click={decrement}>
    -
</button>

Incrementer.svelte

<script>
    import { count } from './stores.js';

    function increment() {
        count.update(n => n + 1);
    }
</script>

<button on:click={increment}>
    +
</button>

Resetter.svelte

<script>
    import { count } from './stores.js';

    function reset() {
       count.set(0);
    }
</script>

<button on:click={reset}>
    reset
</button>

store.js

import { writable } from 'svelte/store';

export const count = writable(0);

Auto-subscripciones

El uso del Store que se mostré en la subsección anterior presenta un problema: nunca se desuscribe y en el caso de varias construcciones y destrucciones del componente puede dar paso a un memory-leak.
Para realizar una autosubscripción sólo basta con agregar el símbolo pesos $ como prefijo al identificador del Store.

App.svelte

<script>
    import { count } from './stores.js';
    import Incrementer from './Incrementer.svelte';
    import Decrementer from './Decrementer.svelte';
    import Resetter from './Resetter.svelte';

    /* let count_value;

    count.subscribe(value => {
        count_value = value;
    }); */
</script>

<!-- <h1>The count is {count_value}</h1> -->
<h1>The count is {$count}</h1>

<Incrementer/>
<Decrementer/>
<Resetter/>

Decrementer.svelte

<script>
    import { count } from './stores.js';

    function decrement() {
        count.update(n => n - 1);
    }
</script>

<button on:click={decrement}>
    -
</button>

Incrementer.svelte

<script>
    import { count } from './stores.js';

    function increment() {
        count.update(n => n + 1);
    }
</script>

<button on:click={increment}>
    +
</button>

Resetter.svelte

<script>
    import { count } from './stores.js';

    function reset() {
        count.set(0);
    }
</script>

<button on:click={reset}>
    reset
</button>

store.js

import { writable } from 'svelte/store';

export const count = writable(0);

Stores de sólo lectura

Para ello se usa la función readable que toma como entrada un valor inicial y una función cuyo único parámetro es un callback que se lanza cuando llega el primer subscriptor y que retorna una función cuando el último subscriptor termina la relación:

App.svelte

<script>
    import { time } from './stores.js';

    const formatter = new Intl.DateTimeFormat('en', {
        hour12: true,
        hour: 'numeric',
        minute: '2-digit',
        second: '2-digit'
    });
</script>

<h1>The time is {formatter.format($time)}</h1>

stores.js

import { readable } from 'svelte/store';

// export const time = readable(null, function start(set) {
export const time = readable(new Date(), function start(set) {
   const interval = setInterval(() => {
       set(new Date());
   }, 1000);

    return function stop() {
        clearInterval(interval);
    };
});

Stores derivados

Se pueden crear Stores a partir de otros:

App.svelte

<script>
    import { time, elapsed } from './stores.js';

    const formatter = new Intl.DateTimeFormat('en', {
        hour12: true,
        hour: 'numeric',
        minute: '2-digit',
        second: '2-digit'
    });
</script>

<h1>The time is {formatter.format($time)}</h1>

<p>
    This page has been open for
    {$elapsed} {$elapsed === 1 ? 'second' : 'seconds'}
</p>

stores.js

import { readable, derived } from 'svelte/store';

export const time = readable(new Date(), function start(set) {
    const interval = setInterval(() => {
        set(new Date());
    }, 1000);

    return function stop() {
        clearInterval(interval);
    };
});

const start = new Date();

export const elapsed = derived(
    time,
//  $time => {}
    $time => Math.round(($time - start) / 1000)
);

Stores Personalizados

En vez de exportar un Store como se ha venido haciendo, se puede crear un Store personalizdo siempre que se implemente la función subscribe. Con esta alternativa los ejemplos de esta sección quedan más compactos:

App.svelte

<script>
    import { count } from './stores.js';
</script>

<h1>The count is {$count}</h1>

<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>

stores.js

import { writable } from 'svelte/store';

function createCount() {
    const { subscribe, set, update } = writable(0);

    return {
        subscribe,
//        increment: () => {},
          increment: () => update(n => n + 1),
//        decrement: () => {},
          decrement: () => update(n => n - 1),
//        reset: () => {}
          reset: () => set(0)
    };
}

export const count = createCount();

Store bindings

A continuación, se mostrará un ejemplo de cómo usar stores writable y binding para crear una lista de tareas en Svelte. Este ejemplo permitirá añadir y eliminar tareas de la lista de la siguiente manera:

El archivo stores.js define el estado central de la lista de tareas utilizando un store writable de Svelte. En este archivo, el store tareas se inicializa con una lista de tareas predefinidas, cada una con un id, una descripción y un estado que indica si la tarea ha sido completada o no.

store.js

import { writable } from 'svelte/store';

    export const tareas = writable([
	{ id: 1, texto: 'Aprender Svelte', completada: false },
	{ id: 2, texto: 'Construir una app', completada: false }

    ]);
    

El archivo page.svelte implementa la interfaz de usuario para gestionar la lista de tareas utilizando el store tareas. Este componente permite agregar, eliminar y marcar tareas como completadas.

Input Binding: El campo de entrada está ligado a la variable nuevaTarea usando bind:value={nuevaTarea}, lo que permite que el valor de nuevaTarea se actualice en tiempo real con lo que el usuario escribe.

page.svelte


    <script>
    import { tareas } from './stores.js';
	let nuevaTarea = '';

	function agregarTarea() {
		if (nuevaTarea.trim() !== '') {
			tareas.update(tareas => [
				...tareas,
				{ id: Date.now(), texto: nuevaTarea, completada: false }
			]);
			nuevaTarea = '';
		}
	}

	function eliminarTarea(id) {
		tareas.update(tareas => tareas.filter(tarea => tarea.id !== id));
	}

	function toggleCompletada(id) {
		tareas.update(tareas =>
			tareas.map(tarea =>
				tarea.id === id ? { ...tarea, completada: !tarea.completada } : tarea
			)
		);
	}

    </script>

    <h1>Lista de Tareas</h1>
    <input bind:value={nuevaTarea} placeholder="Nueva tarea"/>
    <button on:click={agregarTarea}>Agregar tarea</button>
    <ul>
        {#each $tareas as tarea (tarea.id)}
        <li>
        <input type="checkbox" checked={tarea.completada} on:change={() => toggleCompletada(tarea.id)}/>
        {tarea.texto}
        <button on:click={() => eliminarTarea(tarea.id)}>Eliminar</button>
    </li>
    {/each}
    </ul>
    

Logic:

Implementación:

Svelte facilita la implementación de lógica a través del uso de javascript estándar. Esta funcionalidad se puede implementar a través del uso de la etiqueta script o con la implementación de módulos de javascript


          <script>
          let a = 5;
          let b = 4;
          let result = a + b;
      
          function calculateSum(x, y) {
            return x + y;
          }
        
          let sum = calculateSum(12, 20);
      
          </script>
          <p>  La suma de {a} y {b} es {result}</p>
          <p> La suma calculada es {sum} </p>
        

Puedes ver este ejemplo en acción aquí.

If:

Podemos usar un condicional if en el archivo .svelte. Las expresiones tienen la misma organización que se usa en javascript, por lo que se pueden usar los mismos operadores lógicos.

  <script>
        let user = { loggedIn: false };

        function toggle() {
        user.loggedIn = !user.loggedIn;
        }
    </script>

    {#if user.loggedIn}
        <button on:click={toggle}>
            Log out
        </button>
    {/if}

    {#if !user.loggedIn}
        <button on:click={toggle}>
            Log in
        </button>
    {/if}

Puedes un ejemplo del uso de #if en acción aquí.

Else:

Podemos añadir una condición else en lugar de dos o más condicionales:

  <script>
        let user = { loggedIn: false };

        function toggle() {
        user.loggedIn = !user.loggedIn;
        }
    </script>

    {#if user.loggedIn}
        <button on:click={toggle}>
            Log out
        </button>
- {/if}
- {#if !user.loggedIn}
+ {:else}
        <button on:click={toggle}>
            Log in
        </button>
    {/if}

Else if:

Svelte permite el uso de else if, y similar al if, es posible usar operadores lóogicos de javascript:

  <script>
        let x = 3;
    </script>

    {#if x >= 10}
        <p>{x} es mayor o igual a 10</p>
    {:else if x < 5 && x >= 0}
        <p>{x} es menor a 5 y mayor o igual a 0</p>
    {:else if x >= 5 && x < 10}
        <p>{x} está entre 5 y 10</p>
    {:else}
        <p>{x} es negativo </p>
    {/if}

    {#if x % 2 == 0}
        <p>{x} es par </p>
    {:else}
        <p>{x} es impar </p>
    {/if}

Operadores lógicos

Svelte permite el uso de operadores lógicos como "&&" o "!" para la creación de condiciones más complejas

<script>
    let user = {
        name: 'Juan',
        age: 30
      };
  </script>
  {#if user && user.age >= 18}

  <p>{user.name} es mayor de edad </p>
{:else}
<p>{user.name} es menor de edad </p>
{/if}

Each:

En Svelte, puedes recorrer una lista utilizando la función each. Esta función se comporta de manera similar a un ciclo for, permitiendo iterar sobre arrays y objetos para renderizar dinámicamente listas de elementos en el DOM. Además, es posible añadir un índice que acompaña a cada elemento recorrido de la lista.

  <script>
        let cats = [
        { id: "J---aiyznGQ", name: "Keyboard Cat" },
        { id: "z_AbfPXTKms", name: "Maru" },
        { id: "OUtn3pvWmpg", name: "Henri The Existential Cat" }
        ];
    </script>

    <h1>The Famous Cats of YouTube</h1>

    <ul>
        {#each cats as cat, i}
            <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
                {i + 1}: {cat.name}
            </a></li>
        {/each}
    </ul>

    <ul>
        {#each cats as {id,name}, i}
            <li><a target="_blank" href="https://www.youtube.com/watch?v={id}">
                {i + 1}: {name}
            </a></li>
        {/each}
    </ul>

Puedes ver un ejemplo deluso de #each en acciónaquí.

Await:

En caso de que la aplicación espere datos a partir de funciones asíncronas, podemos usar la función await que actúa según espera, recibe o falla la promesa. La palabra reservada await es usada cuando la promesa esté en estado resolved, o, en este caso, cuando la respuesta del servidor haya sido recibida por nuestro programa. A su vez, la palabra reservada async se coloca en una función siempre que ésta contenga la palagra await en ella.

  <script>
        let promise = getRandomNumber();

        async function getRandomNumber() {
        const res = await fetch(
            `https://www.random.org/integers/?num=1&min=1&max=6&col=1&base=10&format=plain&rnd=new`
        );
        const text = await res.text();

        if (res.ok) {
            return text;
        } else {
            throw new Error(text);
        }
        }

        function handleClick() {
        promise = getRandomNumber();
        }
    </script>

    <button on:click={handleClick}>
        Generar úmero aleatorio
    </button>

    {#await promise}
        <p>...esperando a random.org</p>
    {:then number}
        <p>Número: {number}</p>
    {:catch error}
        <p style="color: red">{error.message}</p>
    {/await}

Para ver un ejemplo más complejo de varias de las implementaciones de lógica dentro de Svelte haz click aquí.

Events:

Svelte nos facilita hacer uso y definir listeners hacia eventos en el DOM usando

on:<event>

Mouse move:

Para este ejercicio capturamos la posicion del mouse haciendo uso de la funcion handleMousemove

<script>
    let m = { x: 0, y: 0 };

    function handleMousemove(event) {
        m.x = event.clientX;
        m.y = event.clientY;
    }
</script>

<div on:mousemove={handleMousemove}>
    The mouse position is {m.x} x {m.y}
</div>

<style>
    div { width: 100%; height: 100%; }
</style>

Inline Handlers:

Por otro lado la misma aplicación se puede realizar declarando la funcion en la misma linea donde se ejecuta

<script>
    let m = { x: 0, y: 0 };
</script>

<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
    The mouse position is {m.x} x {m.y}
</div>

<style>
    div { width: 100%; height: 100%; }
</style>

Event modifiers:

Los eventos relacionados al DOM pueden tener modificaciones que alteran el comportamiento de este, como pueden ser:

  • once: Permite la emisión del evento por una única vez; por lo tanto, su callback asociado se ejecutará también una única vez.
  • self: Solo desencadena el controlador si event.target es el mismo elemento que emite el evento.
  • capture: Dispara al manipulador durante la fase de captura en lugar de la fase de burbujeo.
  • nonpassive: Configura passive:false

    <script> function handleClick() { alert('no more alerts') } </script> <button on:click|once={handleClick}> Click me </button>

Component events:

En Svelte, los componentes pueden disparar eventos personalizados para comunicar información a otros componentes, generalmente a los componentes padres. Para disparar estos eventos, se utiliza createEventDispatcher. Esta función debe ser llamada cuando el componente se inicializa por primera vez. Esto permite conectar el envío de eventos desde el componente a donde el componente fue instanciado.
 <script>
    import { createEventDispatcher } from 'svelte';
    const dispatch = createEventDispatcher();
    function sayHello() {
            dispatch('message', {
                    text: 'Hello!'
            });
    }
</script>
<button on:click={sayHello}>
    Click to say Hello
</button>

Event forwarding:

A diferencia de los eventos DOM, los eventos de componentes no burbujean (un evento "burbujea" en el sentido de que es emitido por el elemento en cuestión y viaja al padre, al padre del padre y así hasta llegar a la raiz del DOM). Éstos pueden ser escuchados sin importar que tan anidado este el componente. Para este caso, si dentro del elemento Inner se asocia un callback al evento on:message y además se quiere que quien lo use pueda hacer que siga "burbujeando" hacia "arriba" (hacia el padre) es necesario que se especifique el evento on:message (sin asociarle un callback). En este caso, quien usa Inner es el componnente Outer a continuación. De ésta manera, quien use a Outer podrá de igual ó asociar un callback al evento on:message o hacer que siga "burbujeando" a un nivel superior adicional.

<script>
    import Inner from './Inner.svelte';
</script>
<Inner on:message/>

Puedes ver un ejemplo más complejo de la implementación de eventos en Svelte dando click aquí.

Bindings:

Un binding es una propiedad de las etiquetas de Svelte que permite asignarla a un valor definido:

Text input:

Podemos usar la etiqueta input y asociar un valor con una caja de texto. Esto es lo que se conoce como two way binding dado que, en este caso, la variable text declarada en el componente se inyectará como valor en el input (one way, en un sentido), pero a su vez cualquier cambio en el elemento input se reflejará en la variable text del componente (two way, en el sentido opuesto).

<script>
    let text = "";
</script>
<input bind:value={text} placeholder="Texto">
<p>{text || 'Texto'}</p>

Numeric inputs:

De forma similar a la entrada de texto, podemos tener una entrada numérica, tanto en una caja como en un slider:
<script>
    let a = 0;
    let b = 0;
</script>
<label>
    <input type=number bind:value={a} min=0 max=10>
    <input type=range bind:value={a} min=0 max=10>
</label>
<label>
    <input type=number bind:value={b} min=0 max=10>
    <input type=range bind:value={b} min=0 max=10>
</label>
<p>{a} + {b} = {a + b}</p>

Checkbox:

Podemos utilizar checkbox para tomar un valor booleano, y hacer que la aplicación reaccione de forma acorde:
<script>
    let accept = false;
</script>
<label>
    <input type=checkbox bind:checked={accept}>
        He leído y acepto los términos y condiciones de uso
</label>
{#if accept}
    <p/>
{:else}
    <p>Debes aceptar los términos y condiciones de uso para continuar</p>
{/if}  
<button disabled={!accept}>
    Continuar  </button>

Multiple inputs with groups:

Podemos crear diferentes entradas para un grupo de valores, y si usamos checkbox o radio se puede hacer que estos sean mutuamente excluyentes o no:
<script>
    let size = "";
    let flavours = [];
    let menu = ["Hawaiana", "Napolitana"];
    function join(flavours) {
        if (flavours.length === 1) return flavours[0];
            return${flavours.slice(0, -1).join(", ")} y ${
            flavours[flavours.length - 1]
        };
    }
</script>
        
<h2>Tamaño</h2>
        
<label>
    <input type=radio bind:group={size} value={"personal"}>
    Personal
</label>
        
<label>
    <input type=radio bind:group={size} value={"familiar"}>
    Familiar
</label>
        
<h2>Sabores</h2>
        
{#each menu as flavour}
    <label>
        <input type=checkbox bind:group={flavours} value={flavour}>
        {flavour}
    </label>
{/each}
        
{#if flavours.length === 0}
    <p>Escoja uno o dos sabores</p>
{:else}
    <p>
        Pidió una pizza {join(flavours)} de tamaño {size}
    </p>
{/if}

Files:

Podemos asignar una etiqueta label a una etiqueta input, que tiene un bind asignado a los valores de los archivos que se suban, lo que permite subir una imagen, o varios archivos:
<script>
    let files;
</script>
    
<label for="avatar">Una imagen:</label>
<input
    accept="image/png, image/jpeg"
    bind:files
    id="avatar"
    name="avatar"
    type="file"
/>
    
<label for="many">Varios archivos:</label>
<input
    bind:files
    id="many"
    multiple
    type="file"
/>
    
{#if files}
    <h2>Selected files:</h2>
    {#each Array.from(files) as file}
        <p>{file.name} ({file.size} bytes) </p>
    {/each}
{/if}

Select:

Las etiquetas select permiten crear un bind también, y en este caso se muestran en una etiqueta form:
<script>
    let questions = [
        { id: 1, text: ¿Qué sabor quiere? },
        { id: 2, text: ¿Qué tamaño quiere? }
    ];
    
    let selected;
    
    let answer = "";
    
    function handleSubmit() {
        alert(Respondió a ${selected.text} con "${answer}");
    }
</script>
    
<h2>Selección de pizza</h2>
    
<form on:submit|preventDefault={handleSubmit}>
    <select bind:value={selected} on:blur="{() => answer = ''}">
        {#each questions as question}
            <option value={question}>
                {question.text}
            </option>
        {/each}
    </select>
    
    <input bind:value={answer}>
    
    <button disabled={!answer} type=submit>
        Continuar
    </button>
</form>
    
<p>Escogió la pregunta {selected ? selected.id : '[cargando...]'}</p>
    
<style>
    input {
        display: block;
        width: 500px;
        max-width: 100%;
    }
</style>

Multiple select:

De igual forma, podemos hacer que el bind no sea mutuamente excluyente en la etiqueta select:
<script>
    let size = "";
    let flavours = [];
    
    let menu = ["Hawaiana", "Napolitana"];
    
    function join(flavours) {
        if (flavours.length === 1) return flavours[0];
            return ${flavours.slice(0, -1).join(", ")} and ${
            flavours[flavours.length - 1]
        };
    }
</script>
        
<h2>Tamaño</h2>
        
<label>
    <input type=radio bind:group={size} value={"personal"}>
    Personal
</label>
        
<label>
    <input type=radio bind:group={size} value={"familiar"}>
    Familiar
</label>
        
<h2>Sabor</h2>
        
<select multiple bind:value={flavours}>
    {#each menu as flavour}
        <option value={flavour}>
            {flavour}
        </option>
    {/each}
</select>
        
{#if flavours.length === 0}
    <p>Please select at least one flavour</p>
{:else if flavours.length > size}
    <p>Can't order more flavours than scoops!</p>
{:else}
    <p>
        Pidió una pizza {join(flavours)} de tamaño {size}
    </p>
{/if}

Bindings con bloques Each:

Podemos crear bindings en inputs para cada elemento de una lista que se recorre, y así crear varias entradas con un recorrido:
<script>
    let list = [];
    
    function add() {
        list = list.concat({ done: false, text: "" });
    }
    
    function clear() {
        list = list.filter(t => !t.done);
    }
    
    $: remaining = list.filter(t => !t.done).length;
</script>
    
<h1>Lista de tareas</h1>
    
{#each list as item}
    <div>
        <input
            type=checkbox
            bind:checked={item.done}
        >
            
        <input
            placeholder=""
            bind:value={item.text}
            disabled={item.done}
        >
    </div>
{/each}
        
<p>{remaining} tareas pendientes</p>
        
<button on:click={add}>
    Añadir tarea
</button>
<button on:click={clear}>
    Eliminar completadas
</button>

Dimensiones:

Como se hizo anteriormente, es posible tener entradas numéricas con bindings en inputs, pero también crear bindings con los mismos valores en una etiqueta div:
<script>
    let w;
    let h;
    let size = 42;
    let text = "Texto";
</script>
    
<input type=range bind:value={size}>
<input bind:value={text}>
    
<p>Tamaño: {w}px x {h}px</p>
    
<div bind:clientWidth={w} bind:clientHeight={h}>
    <span style="font-size: {size}px">{text}</span>
</div>
    
<style>
    input {
        display: block;
    }
    div {
        display: inline-block;
    }
</style>

Bindings con componentes:

En caso de tener un componente externo .svelte, es posible usar un binding con un valor que exporta, como en este ejemplo que usa el valor pin, y lo asigna al valor value de Keypad.svelte:
<script>
    import Keypad from "./Keypad.svelte";
    
    let pin;
    $: view = pin ? pin.replace(/\d(?!$)/g, "•") : "Pin";
    
    function handleSubmit() {
        alert(submitted ${pin});
    }
</script>
    
<h1 style="color: {pin ? '#333' : '#ccc'}">{view}</h1>
    
<Keypad bind:value={pin} on:submit={handleSubmit}/>
Así se ve Keypad.svelte:
<script>
    import { createEventDispatcher } from "svelte";
    export let value = "";
    
    const dispatch = createEventDispatcher();
    
    const select = num => () => (value += num);
    const clear = () => (value = "");
    const submit = () => dispatch("submit");
</script>
    
<div class="keypad">
    <button on:click={select(1)}>1</button>
    <button on:click={select(2)}>2</button>
    <button on:click={select(3)}>3</button>
    <button on:click={select(4)}>4</button>
    <button on:click={select(5)}>5</button>
    <button on:click={select(6)}>6</button>
    <button on:click={select(7)}>7</button>
    <button on:click={select(8)}>8</button>
    <button on:click={select(9)}>9</button>
    <button disabled={!value} on:click={clear}>clear</button>
    <button on:click={select(0)}>0</button>
    <button on:click={select(0)}>0</button>
    <button disabled={!value} on:click={submit}>submit</button>
</div>
<style>
    .keypad {
        display: grid;
        grid-template-columns: repeat(3, 5em);
        grid-template-rows: repeat(4, 3em);
        grid-gap: 0.5em;
    }

    button {
        margin: 0;
    }
</style>

Lifecycle

Cada componente tiene un ciclo de vida que comienza cuando es creado y se acaba cuando es destruido. Hay muchas funciones que permiten correr codigo en ciertos momentos durante el ciclo de vida. Las funciones del ciclo de vida se deben llamar mientras el componente se está inicializando para que la devolución de llamada esté vinculada a la instancia del componente.

onMount:

Corre despues de que el componente es renderizado primero en el DOM. Si la llamada de onMount devuelve una función, esa función se llamará cuando se destruya el componente

<script>
    import { onMount } from 'svelte';

    let photos = [];

    onMount(async () => {
        const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`);
        photos = await res.json();
    });
</script>

<h1>Photo album</h1>

<div class="photos">
    {#each photos as photo}
        <figure>
            <img src={photo.thumbnailUrl} alt={photo.title}>
            <figcaption>{photo.title}</figcaption>
        </figure>
    {:else}
        <!-- this block renders when photos.length === 0 -->
        <p>loading...</p>
    {/each}
</div>

<style>
    .photos {
        width: 100%;
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        grid-gap: 8px;
    }

    figure, img {
        width: 100%;
        margin: 0;
    }
</style>

Puedes ver este ejemplo en acción aquí.

onDestroy:

Es necesario cuando se quiere ejecutar codigo cuando el componente es destruido.

App.svelte

<script>
//  import { onDestroy } from 'svelte';
    import { onInterval } from './utils.js';

    let seconds = 0;
    onInterval(() => seconds += 1, 1000);
</script>

<p>
    The page has been open for
    {seconds} {seconds === 1 ? 'second' : 'seconds'}
</p>

utils.js

import { onDestroy } from 'svelte';

export function onInterval(callback, milliseconds) {
    // implementation goes here
    const interval = setInterval(callback, milliseconds);

    onDestroy(() => {
        clearInterval(interval);
        });    
}

Puedes ver este ejemplo en acción aquí.

beforeUpdate and afterUpdate:

Son sus contrapartes, la funcion beforeUpdate programa trabajo para ejecutarse antes de que el DOM sea actualizado y el afterUpdate para correr codigo una vez el DOM es sincronizado con la informacion

App.svelte

<script>
    import Eliza from 'elizabot';
    import { beforeUpdate, afterUpdate } from 'svelte';

    let div;
    let autoscroll;    

    beforeUpdate(() => {
        // determine whether we should auto-scroll
        // once the DOM is updated...
        autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
    });

    afterUpdate(() => {
        // ...the DOM is now in sync with the data
        if (autoscroll) div.scrollTo(0, div.scrollHeight);
    });

    const eliza = new Eliza();

    let comments = [
        { author: 'eliza', text: eliza.getInitial() }
    ];

    function handleKeydown(event) {
        if (event.key === 'Enter') {
            const text = event.target.value;
            if (!text) return;

            comments = comments.concat({
                author: 'user',
                text
            });

            event.target.value = '';

            const reply = eliza.transform(text);

            setTimeout(() => {
                comments = comments.concat({
                    author: 'eliza',
                    text: '...',
                    placeholder: true
                });

                setTimeout(() => {
                    comments = comments.filter(comment => !comment.placeholder).concat({
                        author: 'eliza',
                        text: reply
                    });
                }, 500 + Math.random() * 500);
            }, 200 + Math.random() * 200);
        }
    }
</script>

<style>
    .chat {
        display: flex;
        flex-direction: column;
        height: 100%;
        max-width: 320px;
    }

    .scrollable {
        flex: 1 1 auto;
        border-top: 1px solid #eee;
        margin: 0 0 0.5em 0;
        overflow-y: auto;
    }

    article {
        margin: 0.5em 0;
    }

    .user {
        text-align: right;
    }

    span {
        padding: 0.5em 1em;
        display: inline-block;
    }

    .eliza span {
        background-color: #eee;
        border-radius: 1em 1em 1em 0;
    }

    .user span {
        background-color: #0074D9;
        color: white;
        border-radius: 1em 1em 0 1em;
        word-break: break-all;
    }
</style>

<div class="chat">
    <h1>Eliza</h1>

    <div class="scrollable" bind:this={div}>
        {#each comments as comment}
            <article class={comment.author}>
                <span>{comment.text}</span>
            </article>
        {/each}
    </div>

    <input on:keydown={handleKeydown}>
</div>

Puedes ver este ejemplo en acción aquí.

Tick:

Retorna una promesa que se resuelve tan pronto cualquier cambio de estado pendiente ha sido aplicado al DOM. Esto ocurre porque al actualizar el estado de un componente no se actualiza el DOM de manera inmediata sino que espera hasta la siguiente microtarea para mirar si hay algun cambio que aplicar lo que evita trabajo innecesario.

App.svelte

<script>
    import { tick } from 'svelte';

    let text = `Select some text and hit the tab key to toggle uppercase`;

    async function handleKeydown(event) {
        if (event.key !== 'Tab') return;

        event.preventDefault();

        const { selectionStart, selectionEnd, value } = this;
        const selection = value.slice(selectionStart, selectionEnd);

        const replacement = /[a-z]/.test(selection)
            ? selection.toUpperCase()
            : selection.toLowerCase();

        text = (
            value.slice(0, selectionStart) +
            replacement +
            value.slice(selectionEnd)
        );

        await tick();
        this.selectionStart = selectionStart;
        this.selectionEnd = selectionEnd;
    }
</script>

<style>
    textarea {
        width: 100%;
        height: 200px;
    }
</style>

<textarea value={text} on:keydown={handleKeydown}></textarea>

Puedes ver este ejemplo en acción aquí.

Motion

Un Motion en Svelte, está compuesto por dos partes las cuales juegan un papel muy importante en el framework al momento de interactuar con elementos de tipo texto que ayudan a agilizar la creación de procesos como slider, entre otros. Motion está dividido principalmente en dos subgrupos:

Tweened

Con Tweened su principal característica es cuando necesitamos agregar una función de aceleración o cuando queremos hacer clic en los botones, para que la barra de progreso asuma su nuevo valor.
Como ejemplo empezemos cambiando la variable progress a un valor Tweened.

<script> 
    import { tweened } from 'svelte/motion';
    
    const progress = tweened(0);
</script>

Al hacer click en los botones esto causa que la variable progress se actualice a su nuevo valor. Pero esto es un poco robótico y no muy satisfactorio. Así que agreguemos una nueva función sencilla.

    <script> 
        import { tweened } from 'svelte/motion';
        import { cubicOut } from 'svelte/easing';
        
        const progress = tweened(0, {
            duration: 400,
            easing: cubicOut
        });
    </script> 

El módulo svelte/motion nos provee una serie de funciones que nos ayudan a agilizar estos procesos.

Spring

La función spring es una alternativa de tweened que por lo general trabaja mejor con valores que están cambiando constantemente.
Vamos a ver un ejemplo en el que se tiene dos estados, uno está representando las coordenadas y el otro representa el tamaño. Así que como se vería esto con springs.

<script> 
    import { spring } from 'svelte/motion';

    let coords = spring({ x: 50, y: 50 });
    let size = spring(10);
</script>

Ambos springs vienen por defecto con unos valores stiffness y damping, los cuales son los encargados de que el spring se comporte de manera correcta o como se supone un spring se debe comportar. De esta manera podemos especificar nuestro propios valores iniciales.

    let coords = spring({ x: 50, y: 50 }, {
        stiffness: 0.1,
        damping: 0.25
        }); 

Al mover el mouse por la pantalla se va a hacer que el comportamiento de los spring cambie. Nótece que se puede ajustar los valores aun cuando el spring ya se está ejecutando.