Merge branch 'sites' into fix/home-page-styles

pull/8454/head
Puru Vijay 3 years ago
commit 1683862092

@ -1,5 +1,10 @@
# Svelte changelog # Svelte changelog
## Unreleased
* Handle `width`/`height` attributes when spreading ([#6752](https://github.com/sveltejs/svelte/issues/6752))
* Add support for resize observer bindings (`<div bind:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize>`) ([#8022](https://github.com/sveltejs/svelte/pull/8022))
## 3.58.0 ## 3.58.0
- Add `bind:innerText` for `contenteditable` elements ([#3311](https://github.com/sveltejs/svelte/issues/3311)) - Add `bind:innerText` for `contenteditable` elements ([#3311](https://github.com/sveltejs/svelte/issues/3311))

@ -546,6 +546,11 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
*/ */
'bind:innerText'?: string | undefined | null; 'bind:innerText'?: string | undefined | null;
readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null;
readonly 'bind:contentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
readonly 'bind:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
readonly 'bind:devicePixelContentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
// SvelteKit // SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;

14
package-lock.json generated

@ -10,7 +10,7 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^0.3.0",
"@jridgewell/sourcemap-codec": "^1.4.14", "@jridgewell/sourcemap-codec": "^1.4.15",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^6.0.0", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-node-resolve": "^11.2.1",
@ -185,9 +185,9 @@
} }
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true "dev": true
}, },
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
@ -5526,9 +5526,9 @@
"dev": true "dev": true
}, },
"@jridgewell/sourcemap-codec": { "@jridgewell/sourcemap-codec": {
"version": "1.4.14", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true "dev": true
}, },
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {

@ -120,7 +120,7 @@
"homepage": "https://svelte.dev", "homepage": "https://svelte.dev",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^0.3.0",
"@jridgewell/sourcemap-codec": "^1.4.14", "@jridgewell/sourcemap-codec": "^1.4.15",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^6.0.0", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-node-resolve": "^11.2.1",

@ -1,5 +1,5 @@
--- ---
title: .svelte files title: Svelte components
--- ---
Components are the building blocks of Svelte applications. They are written into `.svelte` files, using a superset of HTML. Components are the building blocks of Svelte applications. They are written into `.svelte` files, using a superset of HTML.
@ -24,7 +24,7 @@ A `<script>` block contains JavaScript that runs when a component instance is cr
### 1. `export` creates a component prop ### 1. `export` creates a component prop
Svelte uses the `export` keyword to mark a variable declaration as a _property_ or _prop_, which means it becomes accessible to consumers of the component (see the section on [attributes and props](#attributes-and-props) for more information). Svelte uses the `export` keyword to mark a variable declaration as a _property_ or _prop_, which means it becomes accessible to consumers of the component (see the section on [attributes and props](/docs/basic-markup#attributes-and-props) for more information).
```svelte ```svelte
<script> <script>
@ -256,7 +256,7 @@ You cannot `export default`, since the default export is the component itself.
<script context="module"> <script context="module">
let totalComponents = 0; let totalComponents = 0;
// this allows an importer to do e.g. // the export keyword allows this function to imported with e.g.
// `import Example, { alertTotal } from './Example.svelte'` // `import Example, { alertTotal } from './Example.svelte'`
export function alertTotal() { export function alertTotal() {
alert(totalComponents); alert(totalComponents);
@ -340,134 +340,3 @@ In that case, the `<style>` tag will be inserted as-is into the DOM, no scoping
</style> </style>
</div> </div>
``` ```
## Tags
A lowercase tag, like `<div>`, denotes a regular HTML element. A capitalised tag, such as `<Widget>` or `<Namespace.Widget>`, indicates a _component_.
```svelte
<script>
import Widget from './Widget.svelte';
</script>
<div>
<Widget />
</div>
```
## Attributes and props
By default, attributes work exactly like their HTML counterparts.
```svelte
<div class="foo">
<button disabled>can't touch this</button>
</div>
```
As in HTML, values may be unquoted.
```svelte
<input type="checkbox" />
```
Attribute values can contain JavaScript expressions.
```svelte
<a href="page/{p}">page {p}</a>
```
Or they can _be_ JavaScript expressions.
```svelte
<button disabled={!clickable}>...</button>
```
Boolean attributes are included on the element if their value is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and excluded if it's [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy).
All other attributes are included unless their value is [nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish) (`null` or `undefined`).
```svelte
<input required={false} placeholder="This input field is not required" />
<div title={null}>This div has no title attribute</div>
```
An expression might include characters that would cause syntax highlighting to fail in regular HTML, so quoting the value is permitted. The quotes do not affect how the value is parsed:
```svelte
<button disabled={number !== 42}>...</button>
```
When the attribute name and value match (`name={name}`), they can be replaced with `{name}`.
```svelte
<!-- These are equivalent -->
<button {disabled}>...</button>
<button {disabled}>...</button>
```
By convention, values passed to components are referred to as _properties_ or _props_ rather than _attributes_, which are a feature of the DOM.
As with elements, `name={name}` can be replaced with the `{name}` shorthand.
```svelte
<Widget foo={bar} answer={42} text="hello" />
```
_Spread attributes_ allow many attributes or properties to be passed to an element or component at once.
An element or component can have multiple spread attributes, interspersed with regular ones.
```svelte
<Widget {...things} />
```
_`$$props`_ references all props that are passed to a component, including ones that are not declared with `export`. It is not generally recommended, as it is difficult for Svelte to optimise. But it can be useful in rare cases for example, when you don't know at compile time what props might be passed to a component.
```svelte
<Widget {...$$props} />
```
_`$$restProps`_ contains only the props which are _not_ declared with `export`. It can be used to pass down other unknown attributes to an element in a component. It shares the same optimisation problems as _`$$props`_, and is likewise not recommended.
```svelte
<input {...$$restProps} />
```
> The `value` attribute of an `input` element or its children `option` elements must not be set with spread attributes when using `bind:group` or `bind:checked`. Svelte needs to be able to see the element's `value` directly in the markup in these cases so that it can link it to the bound variable.
> Sometimes, the attribute order matters as Svelte sets attributes sequentially in JavaScript. For example, `<input type="range" min="0" max="1" value={0.5} step="0.1"/>`, Svelte will attempt to set the value to `1` (rounding up from 0.5 as the step by default is 1), and then set the step to `0.1`. To fix this, change it to `<input type="range" min="0" max="1" step="0.1" value={0.5}/>`.
> Another example is `<img src="..." loading="lazy" />`. Svelte will set the img `src` before making the img element `loading="lazy"`, which is probably too late. Change this to `<img loading="lazy" src="...">` to make the image lazily loaded.
## Text expressions
```svelte
{expression}
```
Text can also contain JavaScript expressions:
> If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses.
```svelte
<h1>Hello {name}!</h1>
<p>{a} + {b} = {a + b}.</p>
<div>{/^[A-Za-z ]+$/.test(value) ? x : y}</div>
```
## Comments
You can use HTML comments inside components.
```svelte
<!-- this is a comment! --><h1>Hello world</h1>
```
Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually, these are accessibility warnings; make sure that you're disabling them for a good reason.
```svelte
<!-- svelte-ignore a11y-autofocus -->
<input bind:value={name} autofocus />
```

@ -0,0 +1,134 @@
---
title: Basic markup
---
## Tags
A lowercase tag, like `<div>`, denotes a regular HTML element. A capitalised tag, such as `<Widget>` or `<Namespace.Widget>`, indicates a _component_.
```svelte
<script>
import Widget from './Widget.svelte';
</script>
<div>
<Widget />
</div>
```
## Attributes and props
By default, attributes work exactly like their HTML counterparts.
```svelte
<div class="foo">
<button disabled>can't touch this</button>
</div>
```
As in HTML, values may be unquoted.
```svelte
<input type="checkbox" />
```
Attribute values can contain JavaScript expressions.
```svelte
<a href="page/{p}">page {p}</a>
```
Or they can _be_ JavaScript expressions.
```svelte
<button disabled={!clickable}>...</button>
```
Boolean attributes are included on the element if their value is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and excluded if it's [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy).
All other attributes are included unless their value is [nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish) (`null` or `undefined`).
```svelte
<input required={false} placeholder="This input field is not required" />
<div title={null}>This div has no title attribute</div>
```
An expression might include characters that would cause syntax highlighting to fail in regular HTML, so quoting the value is permitted. The quotes do not affect how the value is parsed:
```svelte
<button disabled={number !== 42}>...</button>
```
When the attribute name and value match (`name={name}`), they can be replaced with `{name}`.
```svelte
<!-- These are equivalent -->
<button {disabled}>...</button>
<button {disabled}>...</button>
```
By convention, values passed to components are referred to as _properties_ or _props_ rather than _attributes_, which are a feature of the DOM.
As with elements, `name={name}` can be replaced with the `{name}` shorthand.
```svelte
<Widget foo={bar} answer={42} text="hello" />
```
_Spread attributes_ allow many attributes or properties to be passed to an element or component at once.
An element or component can have multiple spread attributes, interspersed with regular ones.
```svelte
<Widget {...things} />
```
_`$$props`_ references all props that are passed to a component, including ones that are not declared with `export`. It is not generally recommended, as it is difficult for Svelte to optimise. But it can be useful in rare cases for example, when you don't know at compile time what props might be passed to a component.
```svelte
<Widget {...$$props} />
```
_`$$restProps`_ contains only the props which are _not_ declared with `export`. It can be used to pass down other unknown attributes to an element in a component. It shares the same optimisation problems as _`$$props`_, and is likewise not recommended.
```svelte
<input {...$$restProps} />
```
> The `value` attribute of an `input` element or its children `option` elements must not be set with spread attributes when using `bind:group` or `bind:checked`. Svelte needs to be able to see the element's `value` directly in the markup in these cases so that it can link it to the bound variable.
> Sometimes, the attribute order matters as Svelte sets attributes sequentially in JavaScript. For example, `<input type="range" min="0" max="1" value={0.5} step="0.1"/>`, Svelte will attempt to set the value to `1` (rounding up from 0.5 as the step by default is 1), and then set the step to `0.1`. To fix this, change it to `<input type="range" min="0" max="1" step="0.1" value={0.5}/>`.
> Another example is `<img src="..." loading="lazy" />`. Svelte will set the img `src` before making the img element `loading="lazy"`, which is probably too late. Change this to `<img loading="lazy" src="...">` to make the image lazily loaded.
## Text expressions
```svelte
{expression}
```
Text can also contain JavaScript expressions:
> If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses.
```svelte
<h1>Hello {name}!</h1>
<p>{a} + {b} = {a + b}.</p>
<div>{/^[A-Za-z ]+$/.test(value) ? x : y}</div>
```
## Comments
You can use HTML comments inside components.
```svelte
<!-- this is a comment! --><h1>Hello world</h1>
```
Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually, these are accessibility warnings; make sure that you're disabling them for a good reason.
```svelte
<!-- svelte-ignore a11y-autofocus -->
<input bind:value={name} autofocus />
```

@ -4,9 +4,9 @@ title: 'svelte/store'
The `svelte/store` module exports functions for creating [readable](/docs/svelte-store#readable), [writable](/docs/svelte-store#writable) and [derived](/docs/svelte-store#derived) stores. The `svelte/store` module exports functions for creating [readable](/docs/svelte-store#readable), [writable](/docs/svelte-store#writable) and [derived](/docs/svelte-store#derived) stores.
Keep in mind that you don't _have_ to use these functions to enjoy the [reactive `$store` syntax](/docs/dot-svelte-files#4-prefix-stores-with-$-to-access-their-values) in your components. Any object that correctly implements `.subscribe`, unsubscribe, and (optionally) `.set` is a valid store, and will work both with the special syntax, and with Svelte's built-in [`derived` stores](/docs/svelte-store#derived). Keep in mind that you don't _have_ to use these functions to enjoy the [reactive `$store` syntax](/docs/svelte-components#4-prefix-stores-with-$-to-access-their-values) in your components. Any object that correctly implements `.subscribe`, unsubscribe, and (optionally) `.set` is a valid store, and will work both with the special syntax, and with Svelte's built-in [`derived` stores](/docs/svelte-store#derived).
This makes it possible to wrap almost any other reactive state handling library for use in Svelte. Read more about the [store contract](/docs/dot-svelte-files#4-prefix-stores-with-$-to-access-their-values) to see what a correct implementation looks like. This makes it possible to wrap almost any other reactive state handling library for use in Svelte. Read more about the [store contract](/docs/svelte-components#4-prefix-stores-with-$-to-access-their-values) to see what a correct implementation looks like.
## `writable` ## `writable`

@ -33,7 +33,7 @@ document.body.innerHTML = `
`; `;
``` ```
By default, custom elements are compiled with `accessors: true`, which means that any [props](/docs/dot-svelte-files#attributes-and-props) are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible). By default, custom elements are compiled with `accessors: true`, which means that any [props](/docs/basic-markup#attributes-and-props) are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible).
To prevent this, add `accessors={false}` to `<svelte:options>`. To prevent this, add `accessors={false}` to `<svelte:options>`.

@ -9,16 +9,16 @@ If you do want to use a database, set it up on [Supabase](https://supabase.com)
Run the site sub-project: Run the site sub-project:
```bash ```bash
pnpm install npm install
pnpm dev npm run dev
``` ```
and navigate to [localhost:5173](http://localhost:5173). and navigate to [localhost:5173](http://localhost:5173).
The first time you run the site locally, it will update the list of Contributors and REPL dependencies. After this it won't run again unless you force it by running: The first time you run the site locally, it will update the list of Contributors, REPL dependencies and examples data that is used on the [examples page](https://svelte-dev-2.vercel.app/examples). After this it won't run again unless you force it by running:
```bash ```bash
pnpm update npm run update
``` ```
## Running using the local copy of Svelte ## Running using the local copy of Svelte
@ -56,11 +56,153 @@ The GitHub app requires a specific callback URL, and so cannot be used with the
## Building the site ## Building the site
To build the website, run `pnpm build`. The output can be found in `build`. To build the website, run `npm run build`. The output can be found in `.vercel`.
## Testing ## Testing
Tests can be run using `pnpm test`. Tests can be run using `npm run test`.
## Docs & other content
All the docs, examples, tutorials, FAQ live in the [site/content](../../site/content) directory, outside the site sub-project. If you modify these, and your app server is running, you will need to reload the page to see the changes.
Following are the file structures of the different kind of documentations
### Docs structure
```txt
- site/content/docs
- 01-getting-started <- Category
- meta.json <- Metadata
- 01-introduction.md <- Page
- 02-template-syntax <- Category
- meta.json <- Metadata
- 01-logic-blocks.md <- Page
- 02-special-tags.md <- Page
- 03-element-directives.md <- Page
```
If you are creating a new page, it must be within a category. That is, you can't have a .md file in the `docs` directory's root level. You may have a category without any pages in it, but you can't have a page without a category. You can add the new page in an existing category, or create your own, for example:
```txt
- site/content/docs
<!-- Rest of the docs -->
+ - 07-my-new-category <- Category
+ - 01-my-new-page.md <- Page
```
The numbers in front of category folders and page files are just for ordering the content. They may not be consecutive. Their only purpose exists for the docs author to decide how the content is arranged.
> Because of hardcoded regex in docs processing code, the numbers prefixed to pages are REQUIRED and _must be two digits_.
The name of the file is what determines the URL of the page. For example, the URL of `01-introduction.md` is `https://svelte.dev/docs/introduction`. The URL of `02-special-tags.md` is `https://svelte.dev/docs/special-tags`. Even though these are in categories, the URL does not contain the category name. Keep this in mind when creating new pages, as two pages with same slug in different categories will clash.
**meta.json** files contain data about the current category. At the time of writing it only has one field: `title`
```json
{
"title": "Getting Started"
}
```
This `title` field is used as category text in the sidebar on docs page.
Every single .md file in the docs must have frontmatter with `title` in it. For example, this is how the frontmatter of `02-logic-blocks.md` looks like:
```md
---
title: .svelte files
---
Components are the building blocks of Svelte applications. They are written into `.svelte` files, using a superset of HTML.
All three sections — script, styles and markup — are optional.
<!-- REST OF THE CONTENT -->
```
You need not specify a h1 tag(or in markdown, a `#`). The `title` field in the frontmatter will be used as the h1 tag.
The headings in the document must start from h2(`##`). That is, you can't have an h1 tag in the document. h2(`##`), h3(`###`), h4(`####`) and h5(`#####`) are all valid.
#### Processing
Docs are processed in the [`src/lib/server/docs`](./src/lib/server/docs) folder. `get-docs-data.js` is responsible for reading the docs from filesystem and accumulating the metadata in forms of arrays and objects. `docs/index.js` has the code responsible for _rendering_ the markdown files into HTML. These functions are then imported into [src/routes/docs/+layout.server.js](./src/routes/docs/+layout.server.js) and used to generate docs list, and similarly in [src/routes/docs/%5Bslug%5D/+page.server.js](./src/routes/docs/%5Bslug%5D/%2Bpage.server.js) and are rendered there.
### Tutorial structure
```txt
- site/content/tutorial
- 01-introduction <- Category
- meta.json <- Metadata
- 01-basics <- Page's content folder
- text.md <- Text content of tutorial
- app-a <- The initial app folder
- App.svelte
- store.js
- app-b <- The final app folder. Not always present
- App.svelte
- store.js
```
Similar to how docs are structured, only difference is that the pages are in a folders, and their content is in a `text.md` file. Alongside, are two folders, _app-a_ and _app-b_. These are the initial and final apps respectively. The initial app is the one that the tutorial shows, and the final app is the one that the tutorial switches to after user clicks on the **Show me** button.
> app-b is not always there. This means that the _Show me_ button is not present for that page.
The naming scheme of docs is followed here as well. The numbers in front of the folders are just for ordering the content. They may not be consecutive. Their only purpose exists for the tutorial author to decide how the content is arranged. _And they are compulsary_.
#### Processing
Tutorials are processed in the [`src/lib/server/tutorial`](./src/lib/server/tutorial) folder. `get-tutorial-data.js` is responsible for reading the tutorials from filesystem and accumulating the metadata in forms of arrays and objects. `tutorial/index.js` has the code responsible for _rendering_ the markdown files into HTML. These functions are then imported into [src/routes/tutorial/+layout.server.js](./src/routes/tutorial/%2Blayout.server.js) and used to generate tutorial list, and similarly in [src/routes/tutorial/%5Bslug%5D/+page.server.js](./src/routes/tutorial/%5Bslug%5D/%2Bpage.server.js) and are rendered there.
### Examples structure
```txt
- site/content/examples
- 00-introduction <- Category
- meta.json <- Metadata
- 00-hello-world <- Page's content folder
- meta.json <- Metadata
- App.svelte <- code files
- 01-reactivity <- Category
- meta.json <- Metadata
- 00-reactive-assignments <- Page's content folder
- meta.json <- Metadata
- App.svelte <- code files
```
Similar to the tutorial, only difference: There is no `text.md`, and the code files are kept right in the folder, not in `app-` folder.
Same naming scheme as docs and tutorial is followed.
#### Processing
Examples are processed in the [`src/lib/server/examples`](./src/lib/server/examples) folder. `get-examples-data.js` is responsible for reading the examples from filesystem and accumulating the metadata in forms of arrays and objects. `examples/index.js` has the code responsible for _rendering_ the markdown files into HTML. These functions are then imported into [src/routes/examples/%5Bslug%5D/+page.server.js](./src/routes/examples/%5Bslug%5D/%2Bpage.server.js) and are rendered there.
### Blog structure
```txt
- site/content/blog
- 2019-01-01-my-first-post.md
- 2019-01-02-my-second-post.md
```
Compared to the rest of the content, blog posts are not in a folder. They are placed at the root of `site/content/blog` folder. The name of the file is the date of the post, followed by the slug of the post. The slug is the URL where the blog post is available. For example, the slug of `2019-01-01-my-first-post.md` is `my-first-post`.
All the metadata about the blog post is mentioned in the frontematter of a post. For example, this is how the frontmatter of [2023-03-09-zero-config-type-safety.md](../../site/content/blog/2023-03-09-zero-config-type-safety.md) looks like:
```md
---
title: Zero-effort type safety
description: More convenience and correctness, less boilerplate
author: Simon Holthausen
authorURL: https://twitter.com/dummdidumm_
---
```
#### Processing
Blog posts are processed in the [`src/lib/server/blog`](./src/lib/server/blog) folder. `get-blog-data.js` is responsible for reading the blog posts from filesystem and accumulating the metadata in forms of arrays and objects. `blog/index.js` has the code responsible for _rendering_ the markdown files into HTML. These functions are then imported into [src/routes/blog/+page.svelte](./src/routes/blog/%2Bpage.server.js), where they show the list of blogs. The rendering function is imported in [src/routes/blog/%5Bslug%5D/+page.server.js](./src/routes/blog/%5Bslug%5D/%2Bpage.server.js) and renders the individual blog post there.
## Translating the API docs ## Translating the API docs

File diff suppressed because it is too large Load Diff

@ -16,8 +16,8 @@
"test": "uvu -r ts-node/register src/lib/server/markdown" "test": "uvu -r ts-node/register src/lib/server/markdown"
}, },
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^2.14.0", "@supabase/supabase-js": "^2.20.0",
"@sveltejs/repl": "^0.2.0", "@sveltejs/repl": "^0.4.0",
"cookie": "^0.5.0", "cookie": "^0.5.0",
"devalue": "^4.3.0", "devalue": "^4.3.0",
"do-not-zip": "^1.0.0", "do-not-zip": "^1.0.0",
@ -28,10 +28,9 @@
}, },
"devDependencies": { "devDependencies": {
"@resvg/resvg-js": "^2.4.1", "@resvg/resvg-js": "^2.4.1",
"@sveltejs/adapter-vercel": "^2.4.1", "@sveltejs/adapter-vercel": "^2.4.2",
"@sveltejs/kit": "^1.15.1", "@sveltejs/kit": "^1.15.7",
"@sveltejs/site-kit": "^4.0.1", "@sveltejs/site-kit": "^5.0.3",
"@sveltejs/vite-plugin-svelte": "^2.0.4",
"@types/marked": "^4.0.8", "@types/marked": "^4.0.8",
"@types/prismjs": "^1.26.0", "@types/prismjs": "^1.26.0",
"degit": "^2.8.4", "degit": "^2.8.4",
@ -43,15 +42,15 @@
"prettier-plugin-svelte": "^2.10.0", "prettier-plugin-svelte": "^2.10.0",
"prism-svelte": "^0.5.0", "prism-svelte": "^0.5.0",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"satori": "^0.4.4", "satori": "^0.4.11",
"satori-html": "^0.3.2", "satori-html": "^0.3.2",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"shiki": "^0.14.1", "shiki": "^0.14.1",
"shiki-twoslash": "^3.1.1", "shiki-twoslash": "^3.1.1",
"svelte": "^3.58.0", "svelte": "^3.58.0",
"svelte-check": "^3.2.0", "svelte-check": "^3.2.0",
"typescript": "^5.0.3", "typescript": "^5.0.4",
"vite": "^4.2.1", "vite": "^4.2.1",
"vite-imagetools": "^4.0.18" "vite-imagetools": "^4.0.19"
} }
} }

@ -42,7 +42,7 @@
left: 0; left: 0;
top: 0; top: 0;
height: 100%; height: 100%;
background-color: var(--prime); background-color: var(--sk-theme-1);
transition: width 0.4s; transition: width 0.4s;
} }

@ -42,7 +42,7 @@
return { return {
name: file.slice(0, dot), name: file.slice(0, dot),
type: file.slice(dot + 1), type: file.slice(dot + 1),
source, source
}; };
}) })
.filter((x) => x.type === 'svelte' || x.type === 'js') .filter((x) => x.type === 'svelte' || x.type === 'js')
@ -63,7 +63,7 @@
); );
repl.set({ repl.set({
components, components
}); });
} }
} }
@ -80,10 +80,8 @@
browser && version === 'local' browser && version === 'local'
? `${location.origin}/repl/local` ? `${location.origin}/repl/local`
: `https://unpkg.com/svelte@${version}`; : `https://unpkg.com/svelte@${version}`;
const rollupUrl = `https://unpkg.com/rollup@1/dist/rollup.browser.js`;
</script> </script>
{#if browser} {#if browser}
<Repl bind:this={repl} {svelteUrl} {rollupUrl} embedded relaxed /> <Repl bind:this={repl} {svelteUrl} embedded relaxed />
{/if} {/if}

File diff suppressed because it is too large Load Diff

@ -26,11 +26,11 @@
const res = await fetch(`/apps/destroy`, { const res = await fetch(`/apps/destroy`, {
method: 'POST', method: 'POST',
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
ids: selected, ids: selected
}), })
}); });
if (res.ok) { if (res.ok) {
@ -142,8 +142,8 @@
<style> <style>
.apps { .apps {
padding: var(--top-offset) var(--side-nav) 6rem var(--side-nav); padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side);
max-width: var(--main-width); max-width: var(--sk-page-main-width);
margin: 0 auto; margin: 0 auto;
} }
@ -161,7 +161,7 @@
padding: 0 0 0 3.2rem; padding: 0 0 0 3.2rem;
position: relative; position: relative;
margin: 1rem 0; margin: 1rem 0;
color: var(--text); color: var(--sk-text-2);
} }
.avatar { .avatar {
@ -217,7 +217,7 @@
line-height: 1; line-height: 1;
display: flex; display: flex;
border: 1px solid #eee; border: 1px solid #eee;
border-radius: var(--border-r); border-radius: var(--sk-border-radius);
z-index: 2; z-index: 2;
} }
@ -226,7 +226,7 @@
gap: 1rem; gap: 1rem;
padding: 0 1rem; padding: 0 1rem;
height: 100%; height: 100%;
border-radius: var(--border-r); border-radius: var(--sk-border-radius);
align-items: center; align-items: center;
} }
@ -247,8 +247,8 @@
} }
h2 { h2 {
color: var(--text); color: var(--sk-text-2);
font-size: var(--h5); font-size: var(--sk-text-s);
font-weight: 400; font-weight: 400;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -256,11 +256,11 @@
li a { li a {
display: block; display: block;
background: var(--back-light); background: var(--sk-back-3);
padding: 1rem 3rem 1rem 1rem; padding: 1rem 3rem 1rem 1rem;
height: 100%; height: 100%;
line-height: 1; line-height: 1;
border-radius: var(--border-r); border-radius: var(--sk-border-radius);
text-decoration: none; text-decoration: none;
} }
@ -282,7 +282,7 @@
} }
ul:not(.selecting) li:hover a { ul:not(.selecting) li:hover a {
background-color: var(--second); background-color: var(--sk-theme-2);
color: white; color: white;
} }

@ -6,7 +6,6 @@
import { mapbox_setup } from '../../../../config.js'; import { mapbox_setup } from '../../../../config.js';
import AppControls from './AppControls.svelte'; import AppControls from './AppControls.svelte';
/** @type {import('./$types').PageData} */
export let data; export let data;
let version = data.version; let version = data.version;
@ -41,7 +40,7 @@
afterNavigate(() => { afterNavigate(() => {
repl.set({ repl.set({
components: data.gist.components, components: data.gist.components
}); });
}); });
@ -99,11 +98,11 @@
<style> <style>
.repl-outer { .repl-outer {
position: relative; position: relative;
height: calc(100vh - var(--nav-h)); height: calc(100vh - var(--sk-nav-height));
--app-controls-h: 5.6rem; --app-controls-h: 5.6rem;
--pane-controls-h: 4.2rem; --pane-controls-h: 4.2rem;
overflow: hidden; overflow: hidden;
background-color: var(--back); background-color: var(--sk-back-1);
padding: var(--app-controls-h) 0 0 0; padding: var(--app-controls-h) 0 0 0;
/* margin: 0 calc(var(--side-nav) * -1); */ /* margin: 0 calc(var(--side-nav) * -1); */
box-sizing: border-box; box-sizing: border-box;

@ -45,15 +45,15 @@
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
name, name,
files: components.map((component) => ({ files: components.map((component) => ({
name: `${component.name}.${component.type}`, name: `${component.name}.${component.type}`,
source: component.source, source: component.source
})), }))
}), })
}); });
if (r.status < 200 || r.status >= 300) { if (r.status < 200 || r.status >= 300) {
@ -110,15 +110,15 @@
method: 'PUT', method: 'PUT',
credentials: 'include', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
name, name,
files: components.map((component) => ({ files: components.map((component) => ({
name: `${component.name}.${component.type}`, name: `${component.name}.${component.type}`,
source: component.source, source: component.source
})), }))
}), })
}); });
if (r.status < 200 || r.status >= 300) { if (r.status < 200 || r.status >= 300) {
@ -164,7 +164,7 @@
files.push( files.push(
...components.map((component) => ({ ...components.map((component) => ({
path: `src/${component.name}.${component.type}`, path: `src/${component.name}.${component.type}`,
data: component.source, data: component.source
})) }))
); );
files.push({ files.push({
@ -175,7 +175,7 @@ var app = new App({
target: document.body target: document.body
}); });
export default app;`, export default app;`
}); });
downloadBlob(doNotZip.toBlob(files), 'svelte-app.zip'); downloadBlob(doNotZip.toBlob(files), 'svelte-app.zip');
@ -242,7 +242,7 @@ export default app;`,
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0.6rem var(--side-nav); padding: 0.6rem var(--sk-page-padding-side);
background-color: var(--sk-back-4); background-color: var(--sk-back-4);
color: var(--sk-text-1); color: var(--sk-text-1);
white-space: nowrap; white-space: nowrap;

@ -47,7 +47,7 @@
/* position: relative; padding: 0 2em 0 0; */ /* position: relative; padding: 0 2em 0 0; */
line-height: 1; line-height: 1;
display: none; display: none;
font-family: var(--font); font-family: var(--sk-font);
font-size: 1.6rem; font-size: 1.6rem;
opacity: 0.7; opacity: 0.7;
} }
@ -72,7 +72,7 @@
min-width: 10em; min-width: 10em;
top: 3rem; top: 3rem;
right: -1.6rem; right: -1.6rem;
background-color: var(--second); background-color: var(--sk-theme-2);
padding: 0.8rem 1.6rem; padding: 0.8rem 1.6rem;
z-index: 99; z-index: 99;
text-align: left; text-align: left;
@ -84,7 +84,7 @@
.menu button, .menu button,
.menu a { .menu a {
background-color: transparent; background-color: transparent;
font-family: var(--font); font-family: var(--sk-font);
font-size: 1.6rem; font-size: 1.6rem;
opacity: 0.7; opacity: 0.7;
padding: 0.4rem 0; padding: 0.4rem 0;

@ -26,7 +26,7 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--back); background-color: var(--sk-back-1);
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
--pane-controls-h: 4.2rem; --pane-controls-h: 4.2rem;

@ -45,7 +45,7 @@
<style> <style>
.container { .container {
padding: var(--top-offset) var(--side-nav) 6rem var(--side-nav); padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side);
} }
h1, h1,
@ -64,10 +64,10 @@
} }
.error { .error {
background-color: var(--second); background-color: var(--sk-theme-2);
color: white; color: white;
padding: 12px 16px; padding: 12px 16px;
font: 600 16px/1.7 var(--font); font: 600 16px/1.7 var(--sk-font);
border-radius: 2px; border-radius: 2px;
} }
</style> </style>

@ -18,7 +18,7 @@
<Shell nav_visible={$page.url.pathname !== '/repl/embed'}> <Shell nav_visible={$page.url.pathname !== '/repl/embed'}>
<Nav> <Nav>
<svelte:fragment slot="home"> <svelte:fragment slot="home">
<span><strong>svelte</strong><span>.dev</span></span> <strong>svelte</strong>.dev
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="nav-center"> <svelte:fragment slot="nav-center">

@ -83,7 +83,7 @@
} }
footer a { footer a {
color: var(--text); color: var(--sk-text-2);
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
display: block; display: block;
} }

@ -79,7 +79,7 @@
grid-template-columns: 4fr 1fr; grid-template-columns: 4fr 1fr;
color: var(--sk-text-1); color: var(--sk-text-1);
align-items: center; align-items: center;
font-size: var(--h5); font-size: var(--sk-text-s);
} }
a { a {
@ -90,7 +90,7 @@
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
height: 100%; height: 100%;
border-radius: var(--border-r) var(--border-r) 0 0; border-radius: var(--sk-border-radius) var(--sk-border-radius) 0 0;
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
} }
@ -101,7 +101,7 @@
height: 100%; height: 100%;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: var(--border-r) var(--border-r) 0 0; border-radius: var(--sk-border-radius) var(--sk-border-radius) 0 0;
} }
button:hover { button:hover {
@ -141,7 +141,7 @@
@media (min-width: 960px) { @media (min-width: 960px) {
.controls { .controls {
font-size: var(--h4); font-size: var(--sk-text-s);
} }
.large-show { .large-show {

@ -26,7 +26,7 @@
strong { strong {
position: absolute; position: absolute;
font-size: min(4vw, var(--h4)); font-size: min(4vw, var(--sk-text-s));
max-width: 10em; max-width: 10em;
text-transform: uppercase; text-transform: uppercase;
font-weight: 700; font-weight: 700;
@ -59,15 +59,14 @@
.logotype, .logotype,
strong { strong {
left: calc(var(--side-nav) + 8rem); left: calc(var(--sk-page-padding-side) + 8rem);
/* left: calc(50% - 56rem + var(--side-nav)); */
} }
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
strong, strong,
.logotype { .logotype {
left: calc(50% - 56rem + var(--side-nav)); left: calc(50% - 56rem + var(--sk-page-padding-side));
} }
} }

@ -10,7 +10,7 @@
.inner { .inner {
max-width: 120rem; max-width: 120rem;
padding: 0 var(--side-nav); padding: 0 var(--sk-page-padding-side);
margin: 0 auto; margin: 0 auto;
} }
</style> </style>

@ -1,10 +1,9 @@
<script> <script>
import { Section } from '@sveltejs/site-kit/components'; import { Section } from '@sveltejs/site-kit/components';
import TryTerminal from '@sveltejs/site-kit/components/home/TryTerminal.svelte'; import { TryTerminal } from '@sveltejs/site-kit/components';
import { theme } from '@sveltejs/site-kit/theme';
</script> </script>
<div class="container" class:dark={$theme.current === 'dark'}> <div class="try-container">
<Section --background="var(--background-2)"> <Section --background="var(--background-2)">
<div class="grid" style="--columns: 2"> <div class="grid" style="--columns: 2">
<div class="try"> <div class="try">
@ -56,14 +55,28 @@
</div> </div>
<style> <style>
.container { @media (prefers-color-scheme: light) {
--background-1: radial-gradient(circle at top right, rgb(230, 233, 236), rgb(244, 245, 247)); .try-container {
--background-2: var(--sk-back-4); --background-1: radial-gradient(circle at top right, rgb(230, 233, 236), rgb(244, 245, 247));
--background-2: var(--sk-back-4);
}
:global(body.dark .try-container) {
--background-1: #222;
--background-2: #444;
}
} }
.container.dark { @media (prefers-color-scheme: dark) {
--background-1: #222; .try-container {
--background-2: #444; --background-1: #222;
--background-2: #444;
}
:global(body.light .try-container) {
--background-1: radial-gradient(circle at top right, rgb(230, 233, 236), rgb(244, 245, 247));
--background-2: var(--sk-back-4);
}
} }
.grid { .grid {

@ -1,7 +1,10 @@
<script> <script>
import Section from '../Section.svelte'; import Section from '../Section.svelte';
import { companies } from './companies.js'; import { companies } from './companies.js';
import { theme } from '@sveltejs/site-kit/theme'; // @ts-ignore Why is theme not found by the extension, its right there 🤔
import { theme } from '@sveltejs/site-kit/components';
$: console.log($theme);
const sorted = companies.sort((a, b) => (a.alt < b.alt ? -1 : 1)); const sorted = companies.sort((a, b) => (a.alt < b.alt ? -1 : 1));
</script> </script>

@ -1,5 +1,4 @@
<script> <script>
/** @type {import('./$types').PageData} */
export let data; export let data;
</script> </script>
@ -37,7 +36,7 @@
grid-gap: 1em; grid-gap: 1em;
min-height: calc(100vh - var(--sk-nav-height)); min-height: calc(100vh - var(--sk-nav-height));
padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side); padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side);
max-width: var(--main-width); max-width: var(--sk-page-main-width);
margin: 0 auto; margin: 0 auto;
} }
@ -53,7 +52,7 @@
.post:first-child { .post:first-child {
margin: 0 0 2rem 0; margin: 0 0 2rem 0;
padding: 0 0 4rem 0; padding: 0 0 4rem 0;
border-bottom: var(--border-w) solid #6767785b; /* based on --second */ border-bottom: var(--sk-thick-border-width) solid #6767785b; /* based on --second */
} }
.post:first-child h2 { .post:first-child h2 {

@ -36,7 +36,7 @@
<style> <style>
.post { .post {
padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side); padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side);
max-width: var(--main-width); max-width: var(--sk-page-main-width);
margin: 0 auto; margin: 0 auto;
} }
@ -155,7 +155,7 @@
@media (min-width: 910px) { @media (min-width: 910px) {
.post :global(.max) { .post :global(.max) {
width: calc(100vw - 2 * var(--sk-page-padding-side)); width: calc(100vw - 2 * var(--sk-page-padding-side));
margin: 0 calc(var(--main-width) / 2 - 50vw); margin: 0 calc(var(--sk-page-main-width) / 2 - 50vw);
text-align: center; text-align: center;
} }

@ -130,21 +130,22 @@
'accessibility-warnings-a11y-role-supports-aria-props', 'accessibility-warnings-a11y-role-supports-aria-props',
'accessibility-warnings-a11y-structure', 'accessibility-warnings-a11y-structure',
'accessibility-warnings-a11y-unknown-aria-attribute', 'accessibility-warnings-a11y-unknown-aria-attribute',
'accessibility-warnings-a11y-unknown-role', 'accessibility-warnings-a11y-unknown-role'
]; ];
/** @type {Map<RegExp, string>}*/ /** @type {Map<RegExp, string>}*/
const pages_regex_map = new Map([ const pages_regex_map = new Map([
// Basic ones // Basic ones
[/(before-we-begin|getting-started)$/i, 'introduction'], [/(before-we-begin|getting-started)$/i, 'introduction'],
[/(component-format|template-syntax)$/i, 'dot-svelte-files'], [/template-syntax$/i, 'basic-markup'],
[/component-format$/i, 'svelte-components'],
[/run-time$/i, 'svelte'], [/run-time$/i, 'svelte'],
[/compile-time$/i, 'svelte-compiler'], [/compile-time$/i, 'svelte-compiler'],
[/(accessibility-warnings)$/i, '$1'], [/(accessibility-warnings)$/i, '$1'],
// component-format- // component-format-
[/component-format-(script|style|script-context-module)$/i, 'dot-svelte-files#$1'], [/component-format-(script|style|script-context-module)$/i, 'svelte-components#$1'],
[/component-format-(?:script)(?:-?(.*))$/i, 'dot-svelte-files#$1'], [/component-format-(?:script)(?:-?(.*))$/i, 'svelte-components#$1'],
// template-syntax // template-syntax
[/template-syntax-((?:element|component)-directives)-?(.*)/i, '$1#$2'], [/template-syntax-((?:element|component)-directives)-?(.*)/i, '$1#$2'],
@ -152,10 +153,7 @@
[/template-syntax-(?:slot)-?(.*)/i, 'special-elements#$1'], [/template-syntax-(?:slot)-?(.*)/i, 'special-elements#$1'],
[/template-syntax-(if|each|await|key)$/i, 'logic-blocks#$1'], [/template-syntax-(if|each|await|key)$/i, 'logic-blocks#$1'],
[/template-syntax-(const|debug|html)$/i, 'special-tags#$1'], [/template-syntax-(const|debug|html)$/i, 'special-tags#$1'],
[ [/template-syntax-(tags|attributes-and-props|text-expressions|comments)$/i, 'basic-markup#$1'],
/template-syntax-(tags|attributes-and-props|text-expressions|comments)$/i,
'dot-svelte-files#$1',
],
// !!!! This one should stay at the bottom of `template-syntax`, or it may end up hijacking logic blocks and special tags // !!!! This one should stay at the bottom of `template-syntax`, or it may end up hijacking logic blocks and special tags
[/template-syntax-(.+)/i, 'special-elements#$1'], [/template-syntax-(.+)/i, 'special-elements#$1'],
@ -164,7 +162,7 @@
[/run-time-(client-side-component-api)-?(.*)/i, '$1#$2'], [/run-time-(client-side-component-api)-?(.*)/i, '$1#$2'],
[ [
/run-time-(svelte-easing|server-side-component-api|custom-element-api|svelte-register)$/i, /run-time-(svelte-easing|server-side-component-api|custom-element-api|svelte-register)$/i,
'$1', '$1'
], ],
// Catch all, should be at the end or will include store, motion, transition and other modules starting with svelte // Catch all, should be at the end or will include store, motion, transition and other modules starting with svelte
[/run-time-(svelte)(?:-(.+))?/i, '$1#$2'], [/run-time-(svelte)(?:-(.+))?/i, '$1#$2'],
@ -173,7 +171,7 @@
[/compile-time-?(.*)/i, 'svelte-compiler#$1'], [/compile-time-?(.*)/i, 'svelte-compiler#$1'],
// Accessibility warnings // Accessibility warnings
[/(accessibility-warnings)-?(.+)/i, '$1#$2'], [/(accessibility-warnings)-?(.+)/i, '$1#$2']
]); ]);
function get_old_new_ids_map() { function get_old_new_ids_map() {
@ -199,7 +197,6 @@
} }
function getURlToRedirectTo() { function getURlToRedirectTo() {
console.log(get_old_new_ids_map());
const hash = $page.url.hash.replace(/^#/i, ''); const hash = $page.url.hash.replace(/^#/i, '');
if (!hash) return '/docs/introduction'; if (!hash) return '/docs/introduction';
@ -212,5 +209,8 @@
return `/docs/${old_new_map.get(hash)}`; return `/docs/${old_new_map.get(hash)}`;
} }
onMount(() => goto(getURlToRedirectTo(), { replaceState: true })); onMount(() => {
console.log(get_old_new_ids_map());
goto(getURlToRedirectTo(), { replaceState: true });
});
</script> </script>

@ -18,7 +18,7 @@
const clone = (file) => ({ const clone = (file) => ({
name: file.name.replace(/.\w+$/, ''), name: file.name.replace(/.\w+$/, ''),
type: file.type, type: file.type,
source: file.content, source: file.content
}); });
$: mobile = width < 768; // note: same as per media query below $: mobile = width < 768; // note: same as per media query below
@ -112,7 +112,7 @@
height: 100%; height: 100%;
display: grid; display: grid;
/* TODO */ /* TODO */
grid-template-columns: var(--sidebar-mid-w) auto; grid-template-columns: 36rem auto;
grid-auto-rows: 100%; grid-auto-rows: 100%;
transition: none; transition: none;
} }

@ -50,7 +50,7 @@
overflow-y: auto; overflow-y: auto;
height: 100%; height: 100%;
border-right: 1px solid var(--sk-back-4); border-right: 1px solid var(--sk-back-4);
background-color: var(--sk-back-4); background-color: var(--sk-back-3);
color: var(--sk-text-2); color: var(--sk-text-2);
padding: 3rem 3rem 0 3rem; padding: 3rem 3rem 0 3rem;
margin: 0; margin: 0;

@ -34,8 +34,7 @@
grid-gap: 1em; grid-gap: 1em;
min-height: calc(100vh - var(--sk-nav-height)); min-height: calc(100vh - var(--sk-nav-height));
padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side); padding: var(--sk-page-padding-top) var(--sk-page-padding-side) 6rem var(--sk-page-padding-side);
/* TODO: REMOVE */ max-width: var(--sk-page-main-width);
max-width: var(--main-width);
margin: 0 auto; margin: 0 auto;
tab-size: 2; tab-size: 2;
} }

@ -1,10 +1,9 @@
<script> <script>
import Repl from '@sveltejs/repl';
import ScreenToggle from '$lib/components/ScreenToggle.svelte';
import TableOfContents from './TableOfContents.svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import ScreenToggle from '$lib/components/ScreenToggle.svelte';
import Repl from '@sveltejs/repl';
import { mapbox_setup, svelteUrl } from '../../../config.js'; import { mapbox_setup, svelteUrl } from '../../../config.js';
import TableOfContents from './TableOfContents.svelte';
import '@sveltejs/site-kit/styles/code.css'; import '@sveltejs/site-kit/styles/code.css';
@ -32,7 +31,7 @@
slug: chapter.slug, slug: chapter.slug,
section, section,
chapter, chapter,
prev, prev
}; };
lookup.set(chapter.slug, obj); lookup.set(chapter.slug, obj);
@ -54,13 +53,13 @@
const clone = (file) => ({ const clone = (file) => ({
name: file.name.replace(/.\w+$/, ''), name: file.name.replace(/.\w+$/, ''),
type: file.type, type: file.type,
source: file.content, source: file.content
}); });
$: if (repl) { $: if (repl) {
completed = false; completed = false;
repl.set({ repl.set({
components: data.tutorial.initial.map(clone), components: data.tutorial.initial.map(clone)
}); });
} }
@ -68,13 +67,13 @@
function reset() { function reset() {
repl.update({ repl.update({
components: data.tutorial.initial.map(clone), components: data.tutorial.initial.map(clone)
}); });
} }
function complete() { function complete() {
repl.update({ repl.update({
components: data.tutorial.complete.map(clone), components: data.tutorial.complete.map(clone)
}); });
} }
@ -188,7 +187,7 @@
height: 100%; height: 100%;
display: grid; display: grid;
/* TODO */ /* TODO */
grid-template-columns: minmax(33.333%, var(--sidebar-large-w)) auto; grid-template-columns: minmax(33.333%, 48rem) auto;
grid-auto-rows: 100%; grid-auto-rows: 100%;
transition: none; transition: none;
} }
@ -204,7 +203,7 @@
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
border-right: 1px solid var(--sk-back-4); border-right: 1px solid var(--sk-back-4);
background-color: var(--sk-back-4); background-color: var(--sk-back-3);
color: var(--sk-text-2); color: var(--sk-text-2);
} }
@ -287,6 +286,7 @@
padding: 1rem 1rem; padding: 1rem 1rem;
box-shadow: inset 1px 1px 6px hsla(205.7, 63.6%, 30.8%, 0.06); box-shadow: inset 1px 1px 6px hsla(205.7, 63.6%, 30.8%, 0.06);
border-radius: 0.5rem; border-radius: 0.5rem;
--shiki-color-background: var(--sk-back-1);
} }
.controls { .controls {

@ -3,6 +3,7 @@
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true,
"moduleResolution": "bundler"
} }
} }

@ -3,7 +3,7 @@ import get_object from '../utils/get_object';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { regex_dimensions } from '../../utils/patterns'; import { regex_dimensions, regex_box_size } from '../../utils/patterns';
import { Node as ESTreeNode } from 'estree'; import { Node as ESTreeNode } from 'estree';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import Element from './Element'; import Element from './Element';
@ -92,6 +92,7 @@ export default class Binding extends Node {
this.is_readonly = this.is_readonly =
regex_dimensions.test(this.name) || regex_dimensions.test(this.name) ||
regex_box_size.test(this.name) ||
(isElement(parent) && (isElement(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) || ((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file')) /* TODO others? */); (parent.name === 'input' && type === 'file')) /* TODO others? */);

@ -12,7 +12,7 @@ import Text from './Text';
import { namespaces } from '../../utils/namespaces'; import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import { is_name_contenteditable, get_contenteditable_attr } from '../utils/contenteditable'; import { is_name_contenteditable, get_contenteditable_attr } from '../utils/contenteditable';
import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns'; import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character, regex_box_size } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import Let from './Let'; import Let from './Let';
@ -1090,7 +1090,10 @@ export default class Element extends Node {
} else if (contenteditable && !contenteditable.is_static) { } else if (contenteditable && !contenteditable.is_static) {
return component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute); return component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute);
} }
} else if (name !== 'this') { } else if (
name !== 'this' &&
!regex_box_size.test(name)
) {
return component.error(binding, compiler_errors.invalid_binding(binding.name)); return component.error(binding, compiler_errors.invalid_binding(binding.name));
} }
}); });

@ -11,6 +11,7 @@ import { Node, Identifier } from 'estree';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import mark_each_block_bindings from '../shared/mark_each_block_bindings'; import mark_each_block_bindings from '../shared/mark_each_block_bindings';
import handle_select_value_binding from './handle_select_value_binding'; import handle_select_value_binding from './handle_select_value_binding';
import { regex_box_size } from '../../../../utils/patterns';
export default class BindingWrapper { export default class BindingWrapper {
node: Binding; node: Binding;
@ -455,7 +456,12 @@ function get_value_from_dom(
return x`$$value`; return x`$$value`;
} }
// <select bind:value='selected'> // <div bind:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize>
if (regex_box_size.test(name)) {
return x`@ResizeObserverSingleton.entries.get(this)?.${name}`;
}
// <select bind:value='selected>
if (node.name === 'select') { if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ? return node.get_static_attribute_value('multiple') === true ?
x`@select_multiple_value(this)` : x`@select_multiple_value(this)` :

@ -12,7 +12,7 @@ import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute'; import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute'; import StyleAttributeWrapper from './StyleAttribute';
import SpreadAttributeWrapper from './SpreadAttribute'; import SpreadAttributeWrapper from './SpreadAttribute';
import { regex_dimensions, regex_starts_with_newline, regex_backslashes } from '../../../../utils/patterns'; import { regex_dimensions, regex_starts_with_newline, regex_backslashes, regex_border_box_size, regex_content_box_size, regex_device_pixel_content_box_size, regex_content_rect } from '../../../../utils/patterns';
import Binding from './Binding'; import Binding from './Binding';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import { add_event_handler } from '../shared/add_event_handlers'; import { add_event_handler } from '../shared/add_event_handlers';
@ -64,11 +64,29 @@ const events = [
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'input' && node.get_static_attribute_value('type') === 'range' node.name === 'input' && node.get_static_attribute_value('type') === 'range'
}, },
// resize events
{ {
event_names: ['elementresize'], event_names: ['elementresize'],
filter: (_node: Element, name: string) => filter: (_node: Element, name: string) =>
regex_dimensions.test(name) regex_dimensions.test(name)
}, },
{
event_names: ['elementresizecontentbox'],
filter: (_node: Element, name: string) =>
regex_content_rect.test(name) ?? regex_content_box_size.test(name)
},
{
event_names: ['elementresizeborderbox'],
filter: (_node: Element, name: string) =>
regex_border_box_size.test(name)
},
{
event_names: ['elementresizedevicepixelcontentbox'],
filter: (_node: Element, name: string) =>
regex_device_pixel_content_box_size.test(name)
},
// media events // media events
{ {
event_names: ['timeupdate'], event_names: ['timeupdate'],
@ -747,14 +765,33 @@ export default class ElementWrapper extends Wrapper {
`); `);
binding_group.events.forEach(name => { binding_group.events.forEach(name => {
if (name === 'elementresize') { if (['elementresize', 'elementresizecontentbox', 'elementresizeborderbox', 'elementresizedevicepixelcontentbox'].indexOf(name) !== -1) {
// special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`); const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener); block.add_variable(resize_listener);
block.chunks.mount.push( // Can't dynamically do `@fn[name]`, code-red doesn't know how to resolve it
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));` switch (name) {
); case 'elementresize':
block.chunks.mount.push(
b`${resize_listener} = @add_iframe_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
break;
case 'elementresizecontentbox':
block.chunks.mount.push(
b`${resize_listener} = @resize_observer_content_box.observe(${this.var}, ${callee}.bind(${this.var}));`
);
break;
case 'elementresizeborderbox':
block.chunks.mount.push(
b`${resize_listener} = @resize_observer_border_box.observe(${this.var}, ${callee}.bind(${this.var}));`
);
break;
case 'elementresizedevicepixelcontentbox':
block.chunks.mount.push(
b`${resize_listener} = @resize_observer_device_pixel_content_box.observe(${this.var}, ${callee}.bind(${this.var}));`
);
break;
}
block.chunks.destroy.push( block.chunks.destroy.push(
b`${resize_listener}();` b`${resize_listener}();`

@ -22,3 +22,9 @@ export const regex_ends_with_underscore = /_$/;
export const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g; export const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
export const regex_dimensions = /^(?:offset|client)(?:Width|Height)$/; export const regex_dimensions = /^(?:offset|client)(?:Width|Height)$/;
export const regex_content_rect = /^(?:contentRect)$/;
export const regex_content_box_size = /^(?:contentBoxSize)$/;
export const regex_border_box_size = /^(?:borderBoxSize)$/;
export const regex_device_pixel_content_box_size = /^(?:devicePixelContentBoxSize)$/;
export const regex_box_size = /^(?:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize)$/;

@ -0,0 +1,69 @@
import { globals } from './globals';
/**
* Resize observer singleton.
* One listener per element only!
* https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ
*/
export class ResizeObserverSingleton {
constructor(readonly options?: ResizeObserverOptions) {}
observe(element: Element, listener: Listener) {
this._listeners.set(element, listener);
this._getObserver().observe(element, this.options);
return () => {
this._listeners.delete(element);
this._observer.unobserve(element); // this line can probably be removed
};
}
private readonly _listeners: WeakMap<Element, Listener> = 'WeakMap' in globals ? new WeakMap() : undefined;
private _observer?: ResizeObserver;
private _getObserver() {
return this._observer ?? (this._observer = new ResizeObserver((entries) => {
for (const entry of entries) {
(ResizeObserverSingleton as any).entries.set(entry.target, entry);
this._listeners.get(entry.target)?.(entry);
}
}));
}
}
// Needs to be written like this to pass the tree-shake-test
(ResizeObserverSingleton as any).entries = 'WeakMap' in globals ? new WeakMap() : undefined;
type Listener = (entry: ResizeObserverEntry)=>any;
// TODO: Remove this
interface ResizeObserverSize {
readonly blockSize: number;
readonly inlineSize: number;
}
interface ResizeObserverEntry {
readonly borderBoxSize: readonly ResizeObserverSize[];
readonly contentBoxSize: readonly ResizeObserverSize[];
readonly contentRect: DOMRectReadOnly;
readonly devicePixelContentBoxSize: readonly ResizeObserverSize[];
readonly target: Element;
}
type ResizeObserverBoxOptions = 'border-box' | 'content-box' | 'device-pixel-content-box';
interface ResizeObserverOptions {
box?: ResizeObserverBoxOptions;
}
interface ResizeObserver {
disconnect(): void;
observe(target: Element, options?: ResizeObserverOptions): void;
unobserve(target: Element): void;
}
interface ResizeObserverCallback {
(entries: ResizeObserverEntry[], observer: ResizeObserver): void;
}
declare let ResizeObserver: {
prototype: ResizeObserver;
new(callback: ResizeObserverCallback): ResizeObserver;
};

@ -1,3 +1,4 @@
import { ResizeObserverSingleton } from './ResizeObserverSingleton';
import { contenteditable_truthy_values, has_prop } from './utils'; import { contenteditable_truthy_values, has_prop } from './utils';
// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
@ -306,6 +307,15 @@ export function attr(node: Element, attribute: string, value?: string) {
else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);
} }
/**
* List of attributes that should always be set through the attr method,
* because updating them through the property setter doesn't work reliably.
* In the example of `width`/`height`, the problem is that the setter only
* accepts numeric values, but the attribute can also be set to a string like `50%`.
* If this list becomes too big, rethink this approach.
*/
const always_set_through_set_attribute = ['width', 'height'];
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) {
// @ts-ignore // @ts-ignore
const descriptors = Object.getOwnPropertyDescriptors(node.__proto__); const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);
@ -316,7 +326,7 @@ export function set_attributes(node: Element & ElementCSSInlineStyle, attributes
node.style.cssText = attributes[key]; node.style.cssText = attributes[key];
} else if (key === '__value') { } else if (key === '__value') {
(node as any).value = node[key] = attributes[key]; (node as any).value = node[key] = attributes[key];
} else if (descriptors[key] && descriptors[key].set) { } else if (descriptors[key] && descriptors[key].set && always_set_through_set_attribute.indexOf(key) === -1) {
node[key] = attributes[key]; node[key] = attributes[key];
} else { } else {
attr(node, key, attributes[key]); attr(node, key, attributes[key]);
@ -689,7 +699,7 @@ export function is_crossorigin() {
return crossorigin; return crossorigin;
} }
export function add_resize_listener(node: HTMLElement, fn: () => void) { export function add_iframe_resize_listener(node: HTMLElement, fn: () => void) {
const computed_style = getComputedStyle(node); const computed_style = getComputedStyle(node);
if (computed_style.position === 'static') { if (computed_style.position === 'static') {
@ -737,6 +747,11 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
}; };
} }
export const resize_observer_content_box = /* @__PURE__ */ new ResizeObserverSingleton({ box: 'content-box' });
export const resize_observer_border_box = /* @__PURE__ */ new ResizeObserverSingleton({ box: 'border-box' });
export const resize_observer_device_pixel_content_box = /* @__PURE__ */ new ResizeObserverSingleton({ box: 'device-pixel-content-box' });
export { ResizeObserverSingleton };
export function toggle_class(element, name, toggle) { export function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name); element.classList[toggle ? 'add' : 'remove'](name);
} }

@ -1,8 +1,8 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { import {
SvelteComponent, SvelteComponent,
add_iframe_resize_listener,
add_render_callback, add_render_callback,
add_resize_listener,
detach, detach,
element, element,
init, init,
@ -23,7 +23,7 @@ function create_fragment(ctx) {
}, },
m(target, anchor) { m(target, anchor) {
insert(target, div, anchor); insert(target, div, anchor);
div_resize_listener = add_resize_listener(div, /*div_elementresize_handler*/ ctx[2].bind(div)); div_resize_listener = add_iframe_resize_listener(div, /*div_elementresize_handler*/ ctx[2].bind(div));
}, },
p: noop, p: noop,
i: noop, i: noop,

@ -1,8 +1,8 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { import {
SvelteComponent, SvelteComponent,
add_iframe_resize_listener,
add_render_callback, add_render_callback,
add_resize_listener,
detach, detach,
element, element,
init, init,
@ -42,7 +42,7 @@ function create_fragment(ctx) {
}, },
m(target, anchor) { m(target, anchor) {
insert(target, video, anchor); insert(target, video, anchor);
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[7].bind(video)); video_resize_listener = add_iframe_resize_listener(video, /*video_elementresize_handler*/ ctx[7].bind(video));
if (!mounted) { if (!mounted) {
dispose = [ dispose = [

@ -0,0 +1,4 @@
export default {
// https://github.com/sveltejs/svelte/issues/6752
html: '<img height="100%" width="100%" alt="" />'
};

@ -0,0 +1 @@
<img height="100%" width="100%" alt="" {...$$restProps} />
Loading…
Cancel
Save