mirror of https://github.com/sveltejs/svelte
commit
863eff9516
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
let name = 'world';
|
||||
</script>
|
||||
|
||||
<h1>Hello {name}!</h1>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Adding data"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Dynamic attributes"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<style>
|
||||
p {
|
||||
color: purple;
|
||||
font-family: 'Comic Sans MS';
|
||||
font-size: 2em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<p>Styled!</p>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Styling"
|
||||
}
|
@ -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 @@
|
||||
<p>...don't affect this element</p>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Nested components"
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
let string = `here's some <strong>HTML!!!</strong>`;
|
||||
</script>
|
||||
|
||||
<p>{@html string}</p>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "HTML tags"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Reactive assignments"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Reactive declarations"
|
||||
}
|
@ -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": "Reactive statements"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Reactivity"
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
export let answer;
|
||||
</script>
|
||||
|
||||
<p>The answer is {answer}</p>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Declaring props"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Default values"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Spread props"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Props"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "If blocks"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Else blocks"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Else-if blocks"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Each blocks"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Keyed each blocks"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Await blocks"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Logic"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "DOM events"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Inline handlers"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
function handleClick() {
|
||||
alert('no more alerts')
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click|once={handleClick}>
|
||||
Click me
|
||||
</button>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Event modifiers"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Component events"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Event forwarding"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "DOM event forwarding"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Events"
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<script>
|
||||
let name = '';
|
||||
</script>
|
||||
|
||||
<input bind:value={name} placeholder="enter your name">
|
||||
<p>Hello {name || 'stranger'}!</p>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Text inputs"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Numeric inputs"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Checkbox inputs"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Group inputs"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Textarea inputs"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Select bindings"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Select multiple"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Each block bindings"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Media elements"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "Dimensions"
|
||||
}
|
@ -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": "Component bindings"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "onMount"
|
||||
}
|
@ -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,3 @@
|
||||
{
|
||||
"title": "onDestroy"
|
||||
}
|
@ -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": "tick"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Lifecycle"
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<script>
|
||||
import { count } from './stores.js';
|
||||
import Incrementer from './Incrementer.svelte';
|
||||
import Decrementer from './Decrementer.svelte';
|
||||
import Resetter from './Resetter.svelte';
|
||||
|
||||
let count_value;
|
||||
|
||||
const unsubscribe = count.subscribe(value => {
|
||||
count_value = value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>The count is {count_value}</h1>
|
||||
|
||||
<Incrementer/>
|
||||
<Decrementer/>
|
||||
<Resetter/>
|
@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import { count } from './stores.js';
|
||||
|
||||
function decrement() {
|
||||
count.update(n => n - 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click={decrement}>
|
||||
-
|
||||
</button>
|
@ -1,11 +1,11 @@
|
||||
<script>
|
||||
let count = 0;
|
||||
import { count } from './stores.js';
|
||||
|
||||
function increment() {
|
||||
count += 1;
|
||||
count.update(n => n + 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click={increment}>
|
||||
clicks: {count}
|
||||
+
|
||||
</button>
|
@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import { count } from './stores.js';
|
||||
|
||||
function reset() {
|
||||
count.set(0);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click={reset}>
|
||||
reset
|
||||
</button>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue