diff --git a/site/content/tutorial/10-transitions/02-adding-parameters/app-a/App.svelte b/site/content/tutorial/10-transitions/02-adding-parameters-to-transitions/app-a/App.svelte similarity index 100% rename from site/content/tutorial/10-transitions/02-adding-parameters/app-a/App.svelte rename to site/content/tutorial/10-transitions/02-adding-parameters-to-transitions/app-a/App.svelte diff --git a/site/content/tutorial/10-transitions/02-adding-parameters/app-b/App.svelte b/site/content/tutorial/10-transitions/02-adding-parameters-to-transitions/app-b/App.svelte similarity index 100% rename from site/content/tutorial/10-transitions/02-adding-parameters/app-b/App.svelte rename to site/content/tutorial/10-transitions/02-adding-parameters-to-transitions/app-b/App.svelte diff --git a/site/content/tutorial/10-transitions/02-adding-parameters/text.md b/site/content/tutorial/10-transitions/02-adding-parameters-to-transitions/text.md similarity index 100% rename from site/content/tutorial/10-transitions/02-adding-parameters/text.md rename to site/content/tutorial/10-transitions/02-adding-parameters-to-transitions/text.md diff --git a/site/content/tutorial/11-animations/01-animate/app-a/App.svelte b/site/content/tutorial/11-animations/01-animate/app-a/App.svelte new file mode 100644 index 0000000000..c46096c204 --- /dev/null +++ b/site/content/tutorial/11-animations/01-animate/app-a/App.svelte @@ -0,0 +1,146 @@ + + + + +
+ + +
+

todo

+ {#each todos.filter(t => !t.done) as todo (todo.id)} + + {/each} +
+ +
+

done

+ {#each todos.filter(t => t.done) as todo (todo.id)} + + {/each} +
+
\ No newline at end of file diff --git a/site/content/tutorial/11-animations/01-animate/app-a/crossfade.js b/site/content/tutorial/11-animations/01-animate/app-a/crossfade.js new file mode 100644 index 0000000000..e11e18b60e --- /dev/null +++ b/site/content/tutorial/11-animations/01-animate/app-a/crossfade.js @@ -0,0 +1,65 @@ +import { quintOut } from 'svelte/easing'; + +export default function crossfade({ send, receive, fallback }) { + let requested = new Map(); + let provided = new Map(); + + function crossfade(from, node) { + const to = node.getBoundingClientRect(); + const dx = from.left - to.left; + const dy = from.top - to.top; + + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + + return { + duration: 400, + easing: quintOut, + css: (t, u) => ` + opacity: ${t}; + transform: ${transform} translate(${u * dx}px,${u * dy}px); + ` + }; + } + + return { + send(node, params) { + provided.set(params.key, { + rect: node.getBoundingClientRect() + }); + + return () => { + if (requested.has(params.key)) { + const { rect } = requested.get(params.key); + requested.delete(params.key); + + return crossfade(rect, node); + } + + // if the node is disappearing altogether + // (i.e. wasn't claimed by the other list) + // then we need to supply an outro + provided.delete(params.key); + return fallback(node, params); + }; + }, + + receive(node, params) { + requested.set(params.key, { + rect: node.getBoundingClientRect() + }); + + return () => { + if (provided.has(params.key)) { + const { rect } = provided.get(params.key); + provided.delete(params.key); + + return crossfade(rect, node); + } + + requested.delete(params.key); + return fallback(node, params); + }; + } + }; +} \ No newline at end of file diff --git a/site/content/tutorial/11-animations/01-animate/app-b/App.svelte b/site/content/tutorial/11-animations/01-animate/app-b/App.svelte new file mode 100644 index 0000000000..c46096c204 --- /dev/null +++ b/site/content/tutorial/11-animations/01-animate/app-b/App.svelte @@ -0,0 +1,146 @@ + + + + +
+ + +
+

todo

+ {#each todos.filter(t => !t.done) as todo (todo.id)} + + {/each} +
+ +
+

done

+ {#each todos.filter(t => t.done) as todo (todo.id)} + + {/each} +
+
\ No newline at end of file diff --git a/site/content/tutorial/11-animations/01-animate/app-b/crossfade.js b/site/content/tutorial/11-animations/01-animate/app-b/crossfade.js new file mode 100644 index 0000000000..e11e18b60e --- /dev/null +++ b/site/content/tutorial/11-animations/01-animate/app-b/crossfade.js @@ -0,0 +1,65 @@ +import { quintOut } from 'svelte/easing'; + +export default function crossfade({ send, receive, fallback }) { + let requested = new Map(); + let provided = new Map(); + + function crossfade(from, node) { + const to = node.getBoundingClientRect(); + const dx = from.left - to.left; + const dy = from.top - to.top; + + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + + return { + duration: 400, + easing: quintOut, + css: (t, u) => ` + opacity: ${t}; + transform: ${transform} translate(${u * dx}px,${u * dy}px); + ` + }; + } + + return { + send(node, params) { + provided.set(params.key, { + rect: node.getBoundingClientRect() + }); + + return () => { + if (requested.has(params.key)) { + const { rect } = requested.get(params.key); + requested.delete(params.key); + + return crossfade(rect, node); + } + + // if the node is disappearing altogether + // (i.e. wasn't claimed by the other list) + // then we need to supply an outro + provided.delete(params.key); + return fallback(node, params); + }; + }, + + receive(node, params) { + requested.set(params.key, { + rect: node.getBoundingClientRect() + }); + + return () => { + if (provided.has(params.key)) { + const { rect } = provided.get(params.key); + provided.delete(params.key); + + return crossfade(rect, node); + } + + requested.delete(params.key); + return fallback(node, params); + }; + } + }; +} \ No newline at end of file diff --git a/site/content/tutorial/11-animations/01-animate/text.md b/site/content/tutorial/11-animations/01-animate/text.md new file mode 100644 index 0000000000..7b8268e37b --- /dev/null +++ b/site/content/tutorial/11-animations/01-animate/text.md @@ -0,0 +1,5 @@ +--- +title: The animate directive +--- + +TODO fix https://github.com/sveltejs/svelte/issues/2159 before working on `animate` \ No newline at end of file diff --git a/site/content/tutorial/11-animations/meta.json b/site/content/tutorial/11-animations/meta.json new file mode 100644 index 0000000000..c71301495e --- /dev/null +++ b/site/content/tutorial/11-animations/meta.json @@ -0,0 +1,3 @@ +{ + "title": "Animations" +} \ No newline at end of file diff --git a/site/content/tutorial/12-actions/01-actions/app-a/App.svelte b/site/content/tutorial/12-actions/01-actions/app-a/App.svelte new file mode 100644 index 0000000000..cbd1f81282 --- /dev/null +++ b/site/content/tutorial/12-actions/01-actions/app-a/App.svelte @@ -0,0 +1,47 @@ + + + + +
\ No newline at end of file diff --git a/site/content/tutorial/12-actions/01-actions/app-a/pannable.js b/site/content/tutorial/12-actions/01-actions/app-a/pannable.js new file mode 100644 index 0000000000..332cd3e147 --- /dev/null +++ b/site/content/tutorial/12-actions/01-actions/app-a/pannable.js @@ -0,0 +1,9 @@ +export function pannable(node) { + // setup work goes here... + + return { + destroy() { + // ...cleanup goes here + } + }; +} \ No newline at end of file diff --git a/site/content/tutorial/12-actions/01-actions/app-b/App.svelte b/site/content/tutorial/12-actions/01-actions/app-b/App.svelte new file mode 100644 index 0000000000..1324277bac --- /dev/null +++ b/site/content/tutorial/12-actions/01-actions/app-b/App.svelte @@ -0,0 +1,49 @@ + + + + +
\ No newline at end of file diff --git a/site/content/tutorial/12-actions/01-actions/app-b/pannable.js b/site/content/tutorial/12-actions/01-actions/app-b/pannable.js new file mode 100644 index 0000000000..f7d15328be --- /dev/null +++ b/site/content/tutorial/12-actions/01-actions/app-b/pannable.js @@ -0,0 +1,47 @@ +export function pannable(node) { + let x; + let y; + + function handleMousedown(event) { + x = event.clientX; + y = event.clientY; + + node.dispatchEvent(new CustomEvent('panstart', { + detail: { x, y } + })); + + window.addEventListener('mousemove', handleMousemove); + window.addEventListener('mouseup', handleMouseup); + } + + function handleMousemove(event) { + const dx = event.clientX - x; + const dy = event.clientY - y; + x = event.clientX; + y = event.clientY; + + node.dispatchEvent(new CustomEvent('panmove', { + detail: { x, y, dx, dy } + })); + } + + function handleMouseup(event) { + x = event.clientX; + y = event.clientY; + + node.dispatchEvent(new CustomEvent('panend', { + detail: { x, y } + })); + + window.removeEventListener('mousemove', handleMousemove); + window.removeEventListener('mouseup', handleMouseup); + } + + node.addEventListener('mousedown', handleMousedown); + + return { + destroy() { + node.removeEventListener('mousedown', handleMousedown); + } + }; +} \ No newline at end of file diff --git a/site/content/tutorial/12-actions/01-actions/text.md b/site/content/tutorial/12-actions/01-actions/text.md new file mode 100644 index 0000000000..279dc3123f --- /dev/null +++ b/site/content/tutorial/12-actions/01-actions/text.md @@ -0,0 +1,87 @@ +--- +title: The use directive +--- + +Actions are essentially element-level lifecycle functions. They're useful for things like: + +* interfacing with third-party libraries +* lazy-loaded images +* tooltips +* adding custom event handlers + +In this app, we want to make the orange box 'pannable'. It has event handlers for the `panstart`, `panmove` and `panend` events, but these aren't native DOM events. We have to dispatch them ourselves. First, import the `pannable` function... + +```js +import { pannable } from './pannable.js'; +``` + +...then use it with the element: + +```html +
+``` + +Open the `pannable.js` file. Like transition functions, an action function receives a `node` and some optional parameters, and returns an action object. That object must have a `destroy` function, which is called when the element is unmounted. + +We want to fire `panstart` event when the user mouses down on the element, `panmove` events (with `dx` and `dy` properties showing how far the mouse moved) when they drag it, and `panend` events when they mouse up. One possible implementation looks like this: + +```js +export function pannable(node) { + let x; + let y; + + function handleMousedown(event) { + x = event.clientX; + y = event.clientY; + + node.dispatchEvent(new CustomEvent('panstart', { + detail: { x, y } + })); + + window.addEventListener('mousemove', handleMousemove); + window.addEventListener('mouseup', handleMouseup); + } + + function handleMousemove(event) { + const dx = event.clientX - x; + const dy = event.clientY - y; + x = event.clientX; + y = event.clientY; + + node.dispatchEvent(new CustomEvent('panmove', { + detail: { x, y, dx, dy } + })); + } + + function handleMouseup(event) { + x = event.clientX; + y = event.clientY; + + node.dispatchEvent(new CustomEvent('panend', { + detail: { x, y } + })); + + window.removeEventListener('mousemove', handleMousemove); + window.removeEventListener('mouseup', handleMouseup); + } + + node.addEventListener('mousedown', handleMousedown); + + return { + destroy() { + node.removeEventListener('mousedown', handleMousedown); + } + }; +} +``` + +Update the `pannable` function and try moving the box around. + +> This implementation is for demonstration purposes — a more complete one would also consider touch events. + diff --git a/site/content/tutorial/12-actions/02-adding-parameters-to-actions/app-a/App.svelte b/site/content/tutorial/12-actions/02-adding-parameters-to-actions/app-a/App.svelte new file mode 100644 index 0000000000..30404ce4c5 --- /dev/null +++ b/site/content/tutorial/12-actions/02-adding-parameters-to-actions/app-a/App.svelte @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/site/content/tutorial/12-actions/02-adding-parameters-to-actions/app-b/App.svelte b/site/content/tutorial/12-actions/02-adding-parameters-to-actions/app-b/App.svelte new file mode 100644 index 0000000000..30404ce4c5 --- /dev/null +++ b/site/content/tutorial/12-actions/02-adding-parameters-to-actions/app-b/App.svelte @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/site/content/tutorial/12-actions/02-adding-parameters-to-actions/text.md b/site/content/tutorial/12-actions/02-adding-parameters-to-actions/text.md new file mode 100644 index 0000000000..5631bebbc5 --- /dev/null +++ b/site/content/tutorial/12-actions/02-adding-parameters-to-actions/text.md @@ -0,0 +1,5 @@ +--- +title: Adding parameters +--- + +TODO example with Prism highlighting \ No newline at end of file diff --git a/site/content/tutorial/12-actions/meta.json b/site/content/tutorial/12-actions/meta.json new file mode 100644 index 0000000000..a154610283 --- /dev/null +++ b/site/content/tutorial/12-actions/meta.json @@ -0,0 +1,3 @@ +{ + "title": "Actions" +} \ No newline at end of file diff --git a/site/content/tutorial/99-todo/99-todo/text.md b/site/content/tutorial/99-todo/99-todo/text.md index 93cbdb6121..17c4cbb5e2 100644 --- a/site/content/tutorial/99-todo/99-todo/text.md +++ b/site/content/tutorial/99-todo/99-todo/text.md @@ -85,6 +85,7 @@ Maybe lifecycle should go first, since we're using `onMount` in the `this` demo? * [ ] custom stores * [ ] `bind:value={$foo}` * [ ] `$foo += 1` (if we implement it) +* [ ] Adapting Immer, Redux, Microstates, xstate etc ## Motion @@ -119,7 +120,7 @@ Maybe lifecycle should go first, since we're using `onMount` in the `this` demo? ## use: directive -* [ ] `use:foo` +* [x] `use:foo` * [ ] `use:foo={bar}` ## class: directive diff --git a/site/src/routes/tutorial/[slug]/index.svelte b/site/src/routes/tutorial/[slug]/index.svelte index 082c9a98bc..23222045d2 100644 --- a/site/src/routes/tutorial/[slug]/index.svelte +++ b/site/src/routes/tutorial/[slug]/index.svelte @@ -89,7 +89,7 @@ function handle_change(event) { completed = event.detail.components.every((file, i) => { const expected = chapter.app_b[i]; - return ( + return expected && ( file.name === expected.name && file.type === expected.type && file.source === expected.source