From 6bba70a2a53a12dbf6c2da0253119d8cd8a5a724 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 14 Nov 2023 18:09:32 -0500 Subject: [PATCH] Docs (#9449) * rename file * add snippet docs * add note on deprecation * they're not attributes * event docs * prettier * remove unnecessary div * Apply suggestions from code review Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * make example less confusing * note breaking props.children change --------- Co-authored-by: Rich Harris Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .prettierrc | 6 + .../routes/docs/content/01-api/02-runes.md | 5 +- .../routes/docs/content/01-api/03-snippets.md | 228 ++++++++++++++++++ .../docs/content/01-api/04-event-handlers.md | 187 ++++++++++++++ .../{03-functions.md => 05-functions.md} | 0 .../routes/docs/content/03-appendix/01-faq.md | 4 +- .../03-appendix/02-breaking-changes.md | 4 + 7 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md create mode 100644 sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md rename sites/svelte-5-preview/src/routes/docs/content/01-api/{03-functions.md => 05-functions.md} (100%) diff --git a/.prettierrc b/.prettierrc index c2d09a4289..c4fd5d9f2f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -17,6 +17,12 @@ "useTabs": false, "tabWidth": 2 } + }, + { + "files": ["sites/svelte-5-preview/src/routes/docs/content/**/*.md"], + "options": { + "printWidth": 60 + } } ] } diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md index c9f312963f..37fcf82016 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md @@ -128,7 +128,10 @@ In rare cases, you may need to run code _before_ the DOM updates. For this we ca messages; // autoscroll when new messages are added - if (div.offsetHeight + div.scrollTop > div.scrollHeight - 20) { + if ( + div.offsetHeight + div.scrollTop > + div.scrollHeight - 20 + ) { tick().then(() => { div.scrollTo(0, div.scrollHeight); }); diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md new file mode 100644 index 0000000000..a1114007d1 --- /dev/null +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -0,0 +1,228 @@ +--- +title: Snippets +--- + +Snippets, and _render tags_, are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like [this](/#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)... + +```svelte +{#each images as image} + {#if image.href} + +
+ {image.caption} +
{image.caption}
+
+
+ {:else} +
+ {image.caption} +
{image.caption}
+
+ {/if} +{/each} +``` + +...you can write [this](/#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=): + +```diff ++{#snippet figure(image)} +
+ {image.caption} +
{image.caption}
+
++{/snippet} + +{#each images as image} + {#if image.href} + ++ {@render figure(image)} + + {:else} ++ {@render figure(image)} + {/if} +{/each} +``` + +A snippet can have at most one parameter. You can destructure it, just like a function argument ([demo](/#H4sIAAAAAAAAE5VTYW-bMBD9KyeiKYlEY4jWfSAk2n5H6QcXDmwVbMs2SzuL_z6DTRqp2rQJ2Ycfd_ced2eXtLxHkxRPLhF0wKRIfiiVpIl9V_PB_MTeoj8bOep6RkpTa67spRKV7dECH2iHBs7wNCOVdcFU1ui6gC2zVpmCEMVrMw4HxaSVhnzLMnLMsm26Ol95Y1kBHr9BDHnHbAHHO6ymynIpfF7LuAncwKgBCj0Xrx_5mMb2jh3f6KB6PNRy2AaXKf1fuY__KPfxj3KlQGikL5aQdpUxm-dTJUryUVdRsvwSqEviX2fIbYzgSvmCt7wbNe4ceMUpRIoUFkkpBBkw7ZfMZXC-BLKSDx3Q3p5djJrA-SR-X4K9DdHT6u-jo-flFlKSO3ThIDcSR6LIKUhGWrN1QGhs16LLbXgbjoe5U1PkozCfzu7uy2WtpfuuUTSo1_9ffPZrJKGLoyuwNxjBv0Q4wmdSR2aFi9jS2Pc-FIrlEKeilcI-GP4LfVtxOM1gyO1XSLp6vtD6tdNyFE0BV8YtngKuaNNw0RWQx_jKDlR33M9E5h-PQhZxfxEt6gIaLdWDYbSR191RvcFXv_LMb7p7obssXZ5Dvt_f9HgzdzZKibOZZ9mXmHkdTTpaefqsd4OIay4_hksd_I0fZMNbjk1SWD3i9Dz9BpdEPu8sBAAA)): + +```svelte +{#snippet figure({ src, caption, width, height })} +
+ {caption} +
{caption}
+
+{/snippet} +``` + +## Snippet scope + +Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the ` + +{#snippet hello(name)} +

hello {name}! {message}!

+{/snippet} + +{@render hello('alice')} +{@render hello('bob')} +``` + +...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings): + +```svelte +
+ {#snippet x()} + {#snippet y()}...{/snippet} + + + {@render y()} + {/snippet} + + + {@render y()} +
+ + +{@render x()} +``` + +Snippets can reference themselves and each other ([demo](/#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)): + +```svelte +{#snippet blastoff()} + 🚀 +{/snippet} + +{#snippet countdown(n)} + {#if n > 0} + {n}... + {@render countdown(n - 1)} + {:else} + {@render blastoff()} + {/if} +{/snippet} + +{@render countdown(10)} +``` + +## Passing snippets to components + +Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)): + +```svelte + + +{#snippet header()} + fruit + qty + price + total +{/snippet} + +{#snippet row(d)} + {d.name} + {d.qty} + {d.price} + {d.qty * d.price} +{/snippet} + + +``` + +As an authoring convenience, snippets declare directly _inside_ a component implicitly become props _on_ the component ([demo](/#H4sIAAAAAAAAE41Sy27bMBD8lYVcwHYrW4kBXxRFaP-htzgHSqQsojLJkuu2BqF_74qUrfhxCHQRh7MzO9z1SSM74ZL8zSeKHUSSJz-MSdIET2Y4uD-iQ0Fnp4-2HpDC1VYaLHdqh_JgtEX4yapOQGP1AebrLJzWsXD-QjQi1lo5JMZRooNXeBuwHXoYLHOYM2OoiXkKv_GUwzYFY2VNFxvo0xtqxRR9F-7z04X8fE-uW2GtnJQ3E_tpvYV-oL9Ti0U2hVJFjMMZslcfW-5DWj9zShojEFrBuLCLZR_9CmzLQCwy-psw8rxBgvkNhhpZd8F8NppE7Stbq_8u-GTKS8_XQ9Keqnl5BZP1AzTYP2bDV7i7_9hLEeda0iocNJeNFDzJ0R5Fn142JzA-uzsdBfLhldPxPdMhIPS0H1-M1cYtlnejwdBDfBXZjHXTFOg4BhuOtvTfrVDEmAZG2ew5ezYV-Ew2fVzVAivNTyPHzwSr29AlMAe8f6g-zuWDts-GusAmdBSkv3P7qnB4GpMEEHwsRPEPV6yTe5VDJxp8iXClLRmtnGG1VHva3oCPHQd9QJsrbFd1Kzu-2Khvz8uzZsXqX3urj4rnMBNCXNUG83zf6Yp1C2yXKdxA_KJjGOfRfb0Vh7MKDShEuV-M9_4_nq6svF4EAAA=)): + +```svelte + +
+ {#snippet header()} + + + + + {/snippet} + + {#snippet row(d)} + + + + + {/snippet} +
fruitqtypricetotal{d.name}{d.qty}{d.price}{d.qty * d.price}
+``` + +Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/#H4sIAAAAAAAAE41S247aMBD9lVFYCegGsiDxks1G7T_0bdkHJ3aI1cR27aEtsvzvtZ0LZeGhiiJ5js-cmTMemzS8YybJ320iSM-SPPmmVJImeFEhML9Yh8zHRp51HZDC1JorLI_iiLxXUiN8J1XHoNGyh-U2i9F2SFy-epon1lIY9IwzRwNv8B6wI1oIJXNYEqV8E8sUfuIlh0MKSvPaX-zBpZ-oFRH-m7m7l5m8uyfXLdOaX5X3V_bL9gAu0D98i0V2NSWKwQ4lSN7s0LKLbgtsyxgXmT9NiBe-iaP-DYISSTcj4bcLI7hSDEHL3yu6dkPfBdLS0m1o3nk-LW9gX-gBGss9ZsMXuLu32VjZBdfRaelft5eUN5zRJEd9Zi6dlyEy_ncdOm_IxsGlULe8o5qJNFgE5x_9SWmpzGp9N2-MXQxz4c2cOQ-lZWQyF0Jd2q_-mjI9U1fr4FBPE8iuKTbjjRt2sMBK0svIsQtG6jb2CsQAdQ_1x9f5R9tmIS-yPToK-tNkQRQGL6ObCIIdEpH9wQ3p-Enk0LEGXwe4ktoX2hhFai5Ofi0jPnYc9QF1LrDdRK-rvXjerSfNitQ_TlqeBc1hwRi7yY3F81MnK9KtsF2n8Amis44ilA7VtwfWTyr-kaKV-_X4cH8BTOhfRzcEAAA=)): + +```diff + +- {#snippet header()} +- +- +- +- +- {/snippet} ++ ++ ++ ++ + + +
fruitqtypricetotalfruitqtypricetotal
+``` + +```diff + + + +- {#if header} ++ {#if children} + +- {@render header()} ++ {@render children()} + + {/if} + + +
+``` + +> Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name + +## Snippets and slots + +In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/special-elements#slot). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5. + +They continue to work, however, and you can mix and match snippets and slots in your components. + +## Typing snippets + +Right now, it's not possible to add types for snippets and their parameters. This is something we hope to address before we ship Svelte 5. diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md new file mode 100644 index 0000000000..937026e5a4 --- /dev/null +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md @@ -0,0 +1,187 @@ +--- +title: Event handlers +--- + +Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we use the `on:` directive to attach an event listener to an element, in Svelte 5 they are properties like any other: + +```diff + + +- +``` + +Since they're just properties, you can use the normal shorthand syntax... + +```svelte + + + +``` + +...though when using a named event handler function it's usually better to use a more descriptive name. + +Traditional `on:` event handlers will continue to work, but are deprecated in Svelte 5. + +## Component events + +In Svelte 4, components could emit events by creating a dispatcher with [`createEventDispatcher`](https://svelte.dev/docs/svelte#createeventdispatcher). + +This function is deprecated in Svelte 5. Instead, components should accept _callback props_ ([demo](/#H4sIAAAAAAAAE41TS27bMBC9ykBtELu1ZTmAG0C2hPYG3dddyPIwJkKRAjmy4wrad9VFL5BV75cjlKQof5osutCHb968-XCmjRgXaKL0WxvJosIojb7UdTSJ6Fi7g9mjILRnoxpdOmRlSs1rytdyTbyqlSb42lQ1MK0quI1n7hD3brdLR3KPQALDfyBk8N5QQTiaL8bLwbJptKGziRXCoLdaO2tkSVxJ0GiQRmNovSYFtfmij0GDhnf2WLeWq9k5WblymfmsJRM2TtZatSy_EvyYwSDIGYw8lsP9YnzKkXQT5Dv33uJbWhe-ybgvfDooO7-ZT6h9Z3le10utNg2RLVTJUvDyMWt9xV0u8QCbQgilbD09xzd_ZepCQikKY7J1tFGqWkf5y_PvP7Zqa7GcNkXbjO4Nci-3jsDQUaBFTFkITKFN4mQOH3zKnZXry3l5_vXTi5yEZ5x1vqfe39N8gFB_rQx3l5YC40-4DR0VyCiFJJxI1efDgW9pl8I8SW4CskP-sKMriClJU5eZR_eHQQifaFoI_mDDlSgJ9RCPS5yedJZDatxRpri3VJOCVPI0Lu4Th94MpZAu5FCMbxIk8Z259rCtH-iF5FXRsz2cxAsDTOlDobdXXp8f8ci03TgDl_7JDbQQLiOJP0HXw3eLK_x-MRhcey4sPdxPfrgZu7uV2nLGcRulbnq7yWnV3Ub87667RW0h7M4EwuBD5_a21qo2I7ey1xv370QH7y4PPxfz_IobAnR5-DlxXxf0vfsLb_4Z08cEAAA=)): + +```svelte + + + { + size += 5; + if (size > 75) burst = true; + }} + deflate={() => { + if (size > 0) size -= 5; + }} +/> + +{#if burst} + + 💥 +{:else} + + 🎈 + +{/if} +``` + +```svelte + + + + +``` + +## Bubbling events + +Instead of doing ` +``` + +Note that this also means you can 'spread' event handlers onto the element along with other props: + +```svelte + + + +``` + +## Event modifiers + +In Svelte 4, you can add event modifiers to handlers: + +```svelte + +``` + +Modifiers are specific to `on:` and as such do not work with modern event handlers. Adding things like `event.preventDefault()` inside the handler itself is preferable, since all the logic lives in one place rather than being split between handler and modifiers. + +Since event handlers are just functions, you can create your own wrappers as necessary: + +```svelte + + + +``` + +There are three modifiers — `capture`, `passive` and `nonpassive` — that can't be expressed as wrapper functions, since they need to be applied when the event handler is bound rather than when it runs. + +For `capture`, we add the modifier to the event name: + +```svelte + +``` + +Changing the [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) option of an event handler, meanwhile, is not something to be done lightly. If you have a use case for it — and you probably don't! - then you will need to use an action to apply the event handler yourself. + +## Multiple event handlers + +In Svelte 4, this is possible: + +```svelte + +``` + +This is something of an anti-pattern, since it impedes readability (if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other) and implies that the two handlers are independent, when in fact something like `event.stopImmediatePropagation()` inside `one` would prevent `two` from being called. + +Duplicate attributes/properties on elements — which now includes event handlers — are not allowed. Instead, do this: + +```svelte + +``` + +## Why the change? + +By deprecating `createEventDispatcher` and the `on:` directive in favour of callback props and normal element properties, we: + +- reduce Svelte's learning curve +- remove boilerplate, particularly around `createEventDispatcher` +- remove the overhead of creating `CustomEvent` objects for events that may not even have listeners +- add the ability to spread event handlers +- add the ability to know which event handlers were provided to a component +- add the ability to express whether a given event handler is required or optional +- increase type safety (previously, it was effectively impossible for Svelte to guarantee that a component didn't emit a particular event) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-functions.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-functions.md similarity index 100% rename from sites/svelte-5-preview/src/routes/docs/content/01-api/03-functions.md rename to sites/svelte-5-preview/src/routes/docs/content/01-api/05-functions.md diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md index 160a2bab8b..af431b850e 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/01-faq.md @@ -53,7 +53,9 @@ Beyond the complexities listed above, the current design imposes some unfortunat export {}; // @filename: index.ts // ---cut--- - let theme: 'light' | 'dark' = $derived(dark ? 'dark' : 'light'); + let theme: 'light' | 'dark' = $derived( + dark ? 'dark' : 'light' + ); ``` - Updating values inside `$:` statements can cause [confusing behaviour](https://github.com/sveltejs/svelte/issues/6732) and [impossible to resolve bugs](https://github.com/sveltejs/svelte/issues/4933) and the statements may run in an [unexpected order](https://github.com/sveltejs/svelte/issues/4516) - `$: {...}` doesn't let you return a cleanup function the way that [`$effect`](runes#$effect) does diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md index ce774f2b16..f2b3753e76 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md @@ -54,6 +54,10 @@ Svelte now use Mutation Observers intead of IFrames to measure dimensions for `b - The `tag` option was removed. Use `` inside the component instead - The `loopGuardTimeout`, `format`, `sveltePath`, `errorMode` and `varsReport` options were removed +## The `children` prop is reserved + +Content inside component tags becomes a [snippet prop](/docs/snippets) called `children`. You cannot have a separate prop by that name. + ## Other breaking changes ### Stricter `@const` assignment validation