Merge branch 'master' into gh-2025

pull/2251/head
Richard Harris 7 years ago
commit 2907b1a96c

819
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.0.0-beta.10", "version": "3.0.0-beta.18",
"description": "The magical disappearing UI framework", "description": "The magical disappearing UI framework",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -51,7 +51,7 @@
"devDependencies": { "devDependencies": {
"@types/mocha": "^5.2.0", "@types/mocha": "^5.2.0",
"@types/node": "^10.5.5", "@types/node": "^10.5.5",
"acorn": "^6.0.5", "acorn": "^6.1.1",
"acorn-dynamic-import": "^4.0.0", "acorn-dynamic-import": "^4.0.0",
"agadoo": "^1.0.1", "agadoo": "^1.0.1",
"c8": "^3.4.0", "c8": "^3.4.0",
@ -67,7 +67,7 @@
"locate-character": "^2.0.5", "locate-character": "^2.0.5",
"magic-string": "^0.25.2", "magic-string": "^0.25.2",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"nightmare": "^3.0.1", "puppeteer": "^1.13.0",
"rollup": "^1.1.2", "rollup": "^1.1.2",
"rollup-plugin-commonjs": "^9.1.0", "rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-json": "^3.0.0", "rollup-plugin-json": "^3.0.0",

@ -48,7 +48,7 @@ What happens if we use the new model as a starting point?
## Introducing Sapper ## Introducing Sapper
<aside><p>The <a href="https://sapper.svelte.technology/guide#why-the-name-">name comes from</a> the term for combat engineers, and is also short for Svelte app maker</p></aside> <aside><p>The <a href="https://sapper.svelte.technology/docs#why-the-name-">name comes from</a> the term for combat engineers, and is also short for Svelte app maker</p></aside>
[Sapper](https://sapper.svelte.technology) is the answer to that question. **Sapper is a Next.js-style framework that aims to meet the eleven criteria at the top of this article while dramatically reducing the amount of code that gets sent to the browser.** It's implemented as Express-compatible middleware, meaning it's easy to understand and customise. [Sapper](https://sapper.svelte.technology) is the answer to that question. **Sapper is a Next.js-style framework that aims to meet the eleven criteria at the top of this article while dramatically reducing the amount of code that gets sent to the browser.** It's implemented as Express-compatible middleware, meaning it's easy to understand and customise.
@ -67,7 +67,7 @@ But size is only part of the story. Svelte apps are also extremely performant an
The biggest drawback for many developers evaluating Sapper would be 'but I like React, and I already know how to use it', which is fair. The biggest drawback for many developers evaluating Sapper would be 'but I like React, and I already know how to use it', which is fair.
If you're in that camp, I'd invite you to at least try alternative frameworks. You might be pleasantly surprised! The [Sapper RealWorld](https://github.com/sveltejs/realworld) implementation totals 1,201 lines of source code, compared to 2,377 for the reference implementation, because you're able to express concepts very concisely using Svelte's template syntax (which [takes all of five minutes to master](https://svelte.technology/guide#template-syntax)). You get [scoped CSS](the-zen-of-just-writing-css), with unused style removal and minification built-in, and you can use preprocessors like LESS if you want. You no longer need to use Babel. SSR is ridiculously fast, because it's just string concatenation. And we recently introduced [svelte/store](https://svelte.technology/guide#state-management), a tiny global store that synchronises state across your component hierarchy with zero boilerplate. The worst that can happen is that you'll end up feeling vindicated! If you're in that camp, I'd invite you to at least try alternative frameworks. You might be pleasantly surprised! The [Sapper RealWorld](https://github.com/sveltejs/realworld) implementation totals 1,201 lines of source code, compared to 2,377 for the reference implementation, because you're able to express concepts very concisely using Svelte's template syntax (which [takes all of five minutes to master](https://svelte.technology/docs#template-syntax)). You get [scoped CSS](the-zen-of-just-writing-css), with unused style removal and minification built-in, and you can use preprocessors like LESS if you want. You no longer need to use Babel. SSR is ridiculously fast, because it's just string concatenation. And we recently introduced [svelte/store](https://svelte.technology/docs#state-management), a tiny global store that synchronises state across your component hierarchy with zero boilerplate. The worst that can happen is that you'll end up feeling vindicated!
But there are trade-offs nonetheless. Some people have a pathological aversion to any form of 'template language', and maybe that applies to you. JSX proponents will clobber you with the 'it's just JavaScript' mantra, and therein lies React's greatest strength, which is that it is infinitely flexible. That flexibility comes with its own set of trade-offs, but we're not here to discuss those. But there are trade-offs nonetheless. Some people have a pathological aversion to any form of 'template language', and maybe that applies to you. JSX proponents will clobber you with the 'it's just JavaScript' mantra, and therein lies React's greatest strength, which is that it is infinitely flexible. That flexibility comes with its own set of trade-offs, but we're not here to discuss those.

@ -86,7 +86,7 @@ If you need to support IE11 and friends, you will need to use a transpiler like
## New lifecycle hooks ## New lifecycle hooks
In addition to `oncreate` and `ondestroy`, Svelte v2 adds two more [lifecycle hooks](guide#lifecycle-hooks) for responding to state changes: In addition to `oncreate` and `ondestroy`, Svelte v2 adds two more [lifecycle hooks](docs#lifecycle-hooks) for responding to state changes:
```js ```js
export default { export default {
@ -169,7 +169,7 @@ This change might seem annoying initially, but it's the right move: among other
## event_handler.destroy ## event_handler.destroy
If your app has [custom event handlers](guide#custom-event-handlers), they must return an object with a `destroy` method, *not* a `teardown` method (this aligns event handlers with the component API). If your app has [custom event handlers](docs#custom-event-handlers), they must return an object with a `destroy` method, *not* a `teardown` method (this aligns event handlers with the component API).
## No more type coercion ## No more type coercion

@ -0,0 +1,5 @@
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>

@ -0,0 +1,7 @@
<script>
let src = 'tutorial/image.gif';
let name = 'Rick Astley';
</script>
<!-- {src} is short for src={src} -->
<img {src} alt="{name} dancing">

@ -0,0 +1,9 @@
<style>
p {
color: purple;
font-family: 'Comic Sans MS';
font-size: 2em;
}
</style>
<p>Styled!</p>

@ -0,0 +1,14 @@
<script>
import Nested from './Nested.svelte';
</script>
<style>
p {
color: purple;
font-family: 'Comic Sans MS';
font-size: 2em;
}
</style>
<p>These styles...</p>
<Nested/>

@ -0,0 +1,5 @@
<script>
let string = `here's some <strong>HTML!!!</strong>`;
</script>
<p>{@html string}</p>

@ -0,0 +1,3 @@
{
"title": "Introduction"
}

@ -0,0 +1,11 @@
<script>
let count = 0;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

@ -0,0 +1,18 @@
<script>
let count = 1;
// the `$:` means 're-run whenever these values change'
$: doubled = count * 2;
$: quadrupled = doubled * 2;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Count: {count}
</button>
<p>{count} * 2 = {doubled}</p>
<p>{doubled} * 2 = {quadrupled}</p>

@ -0,0 +1,16 @@
<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>

@ -0,0 +1,3 @@
{
"title": "Reactivity"
}

@ -2,5 +2,4 @@
import Nested from './Nested.svelte'; import Nested from './Nested.svelte';
</script> </script>
<p>This is a top-level element.</p> <Nested answer={42}/>
<Nested/>

@ -0,0 +1,5 @@
<script>
export let answer;
</script>
<p>The answer is {answer}</p>

@ -0,0 +1,6 @@
<script>
import Nested from './Nested.svelte';
</script>
<Nested answer={42}/>
<Nested/>

@ -0,0 +1,5 @@
<script>
export let answer = 'a mystery';
</script>
<p>The answer is {answer}</p>

@ -0,0 +1,12 @@
<script>
import Info from './Info.svelte';
const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.technology'
};
</script>
<Info {...pkg}/>

@ -0,0 +1,12 @@
<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>

@ -0,0 +1,19 @@
<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}

@ -0,0 +1,17 @@
<script>
let user = { loggedIn: false };
function toggle() {
user.loggedIn = !user.loggedIn;
}
</script>
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{:else}
<button on:click={toggle}>
Log in
</button>
{/if}

@ -0,0 +1,11 @@
<script>
let x = 7;
</script>
{#if x > 10}
<p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else}
<p>{x} is between 5 and 10</p>
{/if}

@ -0,0 +1,17 @@
<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 { id, name }, i}
<li><a target="_blank" href="https://www.youtube.com/watch?v={id}">
{i + 1}: {name}
</a></li>
{/each}
</ul>

@ -0,0 +1,23 @@
<script>
import Thing from './Thing.svelte';
let things = [
{ id: 1, value: 'a' },
{ id: 2, value: 'b' },
{ id: 3, value: 'c' },
{ id: 4, value: 'd' },
{ id: 5, value: 'e' }
];
function handleClick() {
things = things.slice(1);
}
</script>
<button on:click={handleClick}>
Remove first thing
</button>
{#each things as thing (thing.id)}
<Thing value={thing.value}/>
{/each}

@ -0,0 +1,9 @@
<script>
// `value` is updated whenever the prop value changes...
export let value;
// ...but `valueAtStart` is fixed upon initialisation
const valueAtStart = value;
</script>
<p>{valueAtStart} / {value}</p>

@ -0,0 +1,30 @@
<script>
let promise = getRandomNumber();
async function getRandomNumber() {
const res = await fetch(`tutorial/random-number`);
const text = await res.text();
if (res.ok) {
return text;
} else {
throw new Error(text);
}
}
function handleClick() {
promise = getRandomNumber();
}
</script>
<button on:click={handleClick}>
generate random number
</button>
{#await promise}
<p>...waiting</p>
{:then number}
<p>The number is {number}</p>
{:catch error}
<p style="color: red">{error.message}</p>
{/await}

@ -0,0 +1,16 @@
<script>
let m = { x: 0, y: 0 };
function handleMousemove(event) {
m.x = event.clientX;
m.y = event.clientY;
}
</script>
<style>
div { width: 100%; height: 100%; }
</style>
<div on:mousemove={handleMousemove}>
The mouse position is {m.x} x {m.y}
</div>

@ -0,0 +1,11 @@
<script>
let m = { x: 0, y: 0 };
</script>
<style>
div { width: 100%; height: 100%; }
</style>
<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
The mouse position is {m.x} x {m.y}
</div>

@ -0,0 +1,9 @@
<script>
function handleClick() {
alert('no more alerts')
}
</script>
<button on:click|once={handleClick}>
Click me
</button>

@ -0,0 +1,9 @@
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/>

@ -0,0 +1,15 @@
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>

@ -0,0 +1,9 @@
<script>
import Outer from './Outer.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Outer on:message={handleMessage}/>

@ -0,0 +1,15 @@
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>
Click to say hello
</button>

@ -0,0 +1,5 @@
<script>
import Inner from './Inner.svelte';
</script>
<Inner on:message/>

@ -0,0 +1,9 @@
<script>
import FancyButton from './FancyButton.svelte';
function handleClick() {
alert('clicked');
}
</script>
<FancyButton on:click={handleClick}/>

@ -0,0 +1,15 @@
<style>
button {
font-family: 'Comic Sans MS';
font-size: 2em;
padding: 0.5em 1em;
color: royalblue;
background: gold;
border-radius: 1em;
box-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
</style>
<button on:click>
Click me
</button>

@ -0,0 +1,6 @@
<script>
let name = '';
</script>
<input bind:value={name} placeholder="enter your name">
<p>Hello {name || 'stranger'}!</p>

@ -0,0 +1,16 @@
<script>
let a = 1;
let b = 2;
</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>

@ -0,0 +1,18 @@
<script>
let yes = false;
</script>
<label>
<input type=checkbox bind:checked={yes}>
Yes! Send me regular email spam
</label>
{#if yes}
<p>Thank you. We will bombard your inbox and sell your personal details.</p>
{:else}
<p>You must opt in to continue. If you're not paying, you're the product.</p>
{/if}
<button disabled={!yes}>
Subscribe
</button>

@ -0,0 +1,52 @@
<script>
let scoops = 1;
let flavours = ['Mint choc chip'];
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>
<h2>Size</h2>
<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>
<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>
<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>
<h2>Flavours</h2>
{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
{flavour}
</label>
{/each}
{#if flavours.length === 0}
<p>Please select at least one flavour</p>
{:else if flavours.length > scoops}
<p>Can't order more flavours than scoops!</p>
{:else}
<p>
You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
of {join(flavours)}
</p>
{/if}

@ -0,0 +1,12 @@
<script>
import marked from 'marked';
let value = `Some words are *italic*, some are **bold**`;
</script>
<style>
textarea { width: 100%; height: 200px; }
</style>
<textarea bind:value></textarea>
{@html marked(value)}

@ -0,0 +1,39 @@
<script>
let questions = [
{ id: 1, text: `Where did you go to school?` },
{ id: 2, text: `What is your mother's name?` },
{ id: 3, text: `What is another personal fact that an attacker could easily find with Google?` }
];
let selected;
let answer = '';
function handleSubmit() {
alert(`answered question ${selected.id} (${selected.text}) with "${answer}"`);
}
</script>
<style>
input { display: block; width: 500px; max-width: 100%; }
</style>
<h2>Insecurity questions</h2>
<form on:submit|preventDefault={handleSubmit}>
<select bind:value={selected} on:change="{() => answer = ''}">
{#each questions as question}
<option value={question}>
{question.text}
</option>
{/each}
</select>
<input bind:value={answer}>
<button disabled={!answer} type=submit>
Submit
</button>
</form>
<p>selected question {selected ? selected.id : '[waiting...]'}</p>

@ -0,0 +1,53 @@
<script>
let scoops = 1;
let flavours = ['Mint choc chip'];
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>
<h2>Size</h2>
<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>
<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>
<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>
<h2>Flavours</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 > scoops}
<p>Can't order more flavours than scoops!</p>
{:else}
<p>
You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
of {join(flavours)}
</p>
{/if}

@ -0,0 +1,39 @@
<script>
let todos = [
{ done: false, text: 'finish Svelte tutorial' },
{ done: false, text: 'build an app' },
{ done: false, text: 'world domination' }
];
function add() {
todos = todos.concat({ done: false, text: '' });
}
function clear() {
todos = todos.filter(t => !t.done);
}
</script>
<h1>Todos</h1>
{#each todos as todo}
<div>
<input
type=checkbox
bind:checked={todo.done}
>
<input
placeholder="What needs to be done?"
bind:value={todo.text}
>
</div>
{/each}
<button on:click={add}>
Add new
</button>
<button on:click={clear}>
Clear completed
</button>

@ -0,0 +1,130 @@
<script>
// These values are bound to properties of the video
let time = 0;
let duration;
let paused = true;
let showControls = true;
let showControlsTimeout;
function handleMousemove(e) {
// Make the controls visible, but fade out after
// 2.5 seconds of inactivity
clearTimeout(showControlsTimeout);
showControlsTimeout = setTimeout(() => showControls = false, 2500);
showControls = true;
if (e.which !== 1) return; // mouse not down
if (!duration) return; // video not loaded yet
const { left, right } = this.getBoundingClientRect();
time = duration * (e.clientX - left) / (right - left);
}
function handleMousedown(e) {
// we can't rely on the built-in click event, because it fires
// after a drag — we have to listen for clicks ourselves
function handleMouseup() {
if (paused) e.target.play();
else e.target.pause();
cancel();
}
function cancel() {
e.target.removeEventListener('mouseup', handleMouseup);
}
e.target.addEventListener('mouseup', handleMouseup);
setTimeout(cancel, 200);
}
function format(seconds) {
if (isNaN(seconds)) return '...';
const minutes = Math.floor(seconds / 60);
seconds = Math.floor(seconds % 60);
if (seconds < 10) seconds = '0' + seconds;
return `${minutes}:${seconds}`;
}
</script>
<style>
div {
position: relative;
}
.controls {
position: absolute;
top: 0;
width: 100%;
transition: opacity 1s;
}
.info {
display: flex;
width: 100%;
justify-content: space-between;
}
span {
padding: 0.2em 0.5em;
color: white;
text-shadow: 0 0 8px black;
font-size: 1.4em;
opacity: 0.7;
}
.time {
width: 3em;
}
.time:last-child { text-align: right }
progress {
display: block;
width: 100%;
height: 10px;
-webkit-appearance: none;
appearance: none;
}
progress::-webkit-progress-bar {
background-color: rgba(0,0,0,0.2);
}
progress::-webkit-progress-value {
background-color: rgba(255,255,255,0.6);
}
video {
width: 100%;
}
</style>
<h1>Caminandes: Llamigos</h1>
<p>From <a href="https://cloud.blender.org/open-projects">Blender Open Projects</a>. CC-BY</p>
<div>
<video
poster="http://svelte-assets.surge.sh/caminandes-llamigos.jpg"
src="http://svelte-assets.surge.sh/caminandes-llamigos.mp4"
on:mousemove={handleMousemove}
on:mousedown={handleMousedown}
bind:currentTime={time}
bind:duration
bind:paused
></video>
<div class="controls" style="opacity: {duration && showControls ? 1 : 0}">
<progress value="{(time / duration) || 0}"/>
<div class="info">
<span class="time">{format(time)}</span>
<span>click anywhere to {paused ? 'play' : 'pause'} / drag to seek</span>
<span class="time">{format(duration)}</span>
</div>
</div>
</div>

@ -0,0 +1,20 @@
<script>
let w;
let h;
let size = 42;
let text = 'edit me';
</script>
<style>
input { display: block; }
div { display: inline-block; }
</style>
<input type=range bind:value={size}>
<input bind:value={text}>
<p>size: {w}px x {h}px</p>
<div bind:clientWidth={w} bind:clientHeight={h}>
<span style="font-size: {size}px">{text}</span>
</div>

@ -0,0 +1,59 @@
<script>
import { onMount } from 'svelte';
let canvas;
let ctx;
let running = false;
const r = Math.random();
onMount(() => {
const ctx = canvas.getContext('2d');
let frame;
(function loop() {
frame = requestAnimationFrame(loop);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let p = 0; p < imageData.data.length; p += 4) {
const i = p / 4;
const x = i % canvas.width;
const y = i / canvas.height >>> 0;
const t = window.performance.now();
const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000));
const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000));
const b = 128;
imageData.data[p + 0] = r;
imageData.data[p + 1] = g;
imageData.data[p + 2] = b;
imageData.data[p + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
}());
return () => {
cancelAnimationFrame(frame);
};
});
</script>
<style>
canvas {
width: 100%;
height: 100%;
background-color: #666;
-webkit-mask: url(logo-mask.svg) 50% 50% no-repeat;
mask: url(logo-mask.svg) 50% 50% no-repeat;
}
</style>
<canvas
bind:this={canvas}
width={32}
height={32}
></canvas>

@ -0,0 +1,3 @@
{
"title": "bind:this={canvas}"
}

@ -0,0 +1,14 @@
<script>
import Keypad from './Keypad.svelte';
let pin;
$: view = pin ? pin.replace(/\d(?!$)/g, '•') : 'enter your pin';
function handleSubmit() {
alert(`submitted ${pin}`);
}
</script>
<h1 style="color: {pin ? '#333' : '#ccc'}">{view}</h1>
<Keypad bind:value={pin} on:submit={handleSubmit}/>

@ -0,0 +1,40 @@
<script>
import { createEventDispatcher } from 'svelte';
export let value = '';
const dispatch = createEventDispatcher();
const select = num => () => value += num;
const clear = () => value = '';
const submit = () => dispatch('submit');
</script>
<style>
.keypad {
display: grid;
grid-template-columns: repeat(3, 5em);
grid-template-rows: repeat(4, 3em);
grid-gap: 0.5em
}
button {
margin: 0
}
</style>
<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 disabled={!value} on:click={submit}>submit</button>
</div>

@ -0,0 +1,3 @@
{
"title": "Bindings"
}

@ -0,0 +1,38 @@
<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>
<style>
.photos {
width: 100%;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 8px;
}
figure, img {
width: 100%;
margin: 0;
}
</style>
<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>

@ -0,0 +1,11 @@
<script>
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>

@ -0,0 +1,9 @@
import { onDestroy } from 'svelte';
export function onInterval(callback, milliseconds) {
const interval = setInterval(callback, milliseconds);
onDestroy(() => {
clearInterval(interval);
});
}

@ -0,0 +1,106 @@
<script>
import Eliza from 'elizanode';
import { beforeUpdate, afterUpdate } from 'svelte';
let div;
let autoscroll;
beforeUpdate(() => {
autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
});
afterUpdate(() => {
if (autoscroll) div.scrollTo(0, div.scrollHeight);
});
const eliza = new Eliza();
let comments = [
{ author: 'eliza', text: eliza.getInitial() }
];
function handleKeydown(event) {
if (event.which === 13) {
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;
}
</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>

@ -0,0 +1,3 @@
{
"title": "beforeUpdate and afterUpdate"
}

@ -0,0 +1,37 @@
<script>
import { tick } from 'svelte';
let text = `Select some text and hit the tab key to toggle uppercase`;
async function handleKeydown(event) {
if (event.which !== 9) 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>

@ -0,0 +1,3 @@
{
"title": "Lifecycle"
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save